#include "technique.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "parse_constants.hpp" namespace { struct ProxyTextureData { osg::Texture::WrapMode wrap_s = osg::Texture::CLAMP_TO_EDGE; osg::Texture::WrapMode wrap_t = osg::Texture::CLAMP_TO_EDGE; osg::Texture::WrapMode wrap_r = osg::Texture::CLAMP_TO_EDGE; osg::Texture::FilterMode min_filter = osg::Texture::LINEAR_MIPMAP_LINEAR; osg::Texture::FilterMode mag_filter = osg::Texture::LINEAR; osg::Texture::InternalFormatMode compression = osg::Texture::USE_IMAGE_DATA_FORMAT; std::optional source_format; std::optional source_type; std::optional internal_format; }; } namespace fx { namespace { VFS::Path::Normalized makeFilePath(std::string_view name) { std::string fileName(name); fileName += Technique::sExt; VFS::Path::Normalized result(Technique::sSubdir); result /= fileName; return result; } } Technique::Technique(const VFS::Manager& vfs, Resource::ImageManager& imageManager, std::string name, int width, int height, bool ubo, bool supportsNormals) : mName(std::move(name)) , mFilePath(makeFilePath(mName)) , mLastModificationTime(std::filesystem::file_time_type::clock::now()) , mWidth(width) , mHeight(height) , mVFS(vfs) , mImageManager(imageManager) , mUBO(ubo) , mSupportsNormals(supportsNormals) { clear(); } void Technique::clear() { mTextures.clear(); mStatus = Status::Uncompiled; mValid = false; mHDR = false; mNormals = false; mLights = false; mEnabled = true; mPassMap.clear(); mPasses.clear(); mPassKeys.clear(); mDefinedUniforms.clear(); mRenderTargets.clear(); mLastAppliedType = Pass::Type::None; mFlags = 0; mShared.clear(); mAuthor = {}; mDescription = {}; mVersion = {}; mGLSLExtensions.clear(); mGLSLVersion = mUBO ? 330 : 120; mGLSLProfile.clear(); mDynamic = false; } std::string Technique::getBlockWithLineDirective() { auto block = mLexer->getLastJumpBlock(); std::string content = std::string(block.content); content = "\n#line " + std::to_string(block.line + 1) + "\n" + std::string(block.content) + "\n"; return content; } Technique::UniformMap::iterator Technique::findUniform(const std::string& name) { return std::find_if(mDefinedUniforms.begin(), mDefinedUniforms.end(), [&name](const auto& uniform) { return uniform->mName == name; }); } bool Technique::compile() { clear(); if (!mVFS.exists(mFilePath)) { Log(Debug::Error) << "Could not load technique, file does not exist '" << mFilePath << "'"; mStatus = Status::File_Not_exists; return false; } try { std::string source(std::istreambuf_iterator(*mVFS.get(getFileName())), {}); parse(std::move(source)); if (mPassKeys.empty()) error("no pass list found, ensure you define one in a 'technique' block"); int swaps = 0; for (auto& name : mPassKeys) { auto it = mPassMap.find(name); if (it == mPassMap.end()) error( Misc::StringUtils::format("pass '%s' was found in the pass list, but there was no matching " "'fragment', 'vertex' or 'compute' block", std::string(name))); if (mLastAppliedType != Pass::Type::None && mLastAppliedType != it->second->mType) { swaps++; if (swaps == 2) Log(Debug::Warning) << "compute and pixel shaders are being swapped multiple times in shader " "chain, this can lead to serious performance drain."; } else mLastAppliedType = it->second->mType; if (Stereo::getMultiview()) { mGLSLExtensions.insert("GL_OVR_multiview"); mGLSLExtensions.insert("GL_OVR_multiview2"); mGLSLExtensions.insert("GL_EXT_texture_array"); } it->second->compile(*this, mShared); if (!it->second->mTarget.empty()) { auto rtIt = mRenderTargets.find(it->second->mTarget); if (rtIt == mRenderTargets.end()) error(Misc::StringUtils::format("target '%s' not defined", std::string(it->second->mTarget))); } mPasses.emplace_back(it->second); } if (mPasses.empty()) error("invalid pass list, no passes defined for technique"); mValid = true; } catch (const std::runtime_error& e) { clear(); mStatus = Status::Parse_Error; mLastError = "Failed parsing technique '" + getName() + "' " + e.what(); Log(Debug::Error) << mLastError; } return mValid; } std::string Technique::getName() const { return mName; } bool Technique::setLastModificationTime(std::filesystem::file_time_type timeStamp) { const bool isDirty = timeStamp != mLastModificationTime; mLastModificationTime = timeStamp; return isDirty; } [[noreturn]] void Technique::error(const std::string& msg) { mLexer->error(msg); } template <> void Technique::parseBlockImp() { if (!mLexer->jump()) error(Misc::StringUtils::format("unterminated 'shared' block, expected closing brackets")); if (!mShared.empty()) error("repeated 'shared' block, only one allowed per technique file"); mShared = getBlockWithLineDirective(); } template <> void Technique::parseBlockImp() { if (!mPassKeys.empty()) error("exactly one 'technique' block can appear per file"); while (!isNext() && !isNext()) { expect(); auto key = std::get(mToken).value; expect(); if (key == "passes") mPassKeys = parseLiteralList(); else if (key == "version") mVersion = parseString(); else if (key == "description") mDescription = parseString(); else if (key == "author") mAuthor = parseString(); else if (key == "glsl_version") { int version = parseInteger(); if (mUBO && version > 330) mGLSLVersion = version; } else if (key == "flags") mFlags = parseFlags(); else if (key == "hdr") mHDR = parseBool(); else if (key == "pass_normals") mNormals = parseBool() && mSupportsNormals; else if (key == "pass_lights") mLights = parseBool(); else if (key == "glsl_profile") { expect(); mGLSLProfile = std::string(std::get(mToken).value); } else if (key == "glsl_extensions") { for (const auto& ext : parseLiteralList()) mGLSLExtensions.emplace(ext); } else if (key == "dynamic") mDynamic = parseBool(); else error(Misc::StringUtils::format("unexpected key '%s'", std::string{ key })); expect(); } if (mPassKeys.empty()) error("pass list in 'technique' block cannot be empty."); } template <> void Technique::parseBlockImp() { if (mRenderTargets.count(mBlockName)) error(Misc::StringUtils::format("redeclaration of render target '%s'", std::string(mBlockName))); fx::Types::RenderTarget rt; rt.mTarget->setTextureSize(mWidth, mHeight); rt.mTarget->setSourceFormat(GL_RGB); rt.mTarget->setInternalFormat(GL_RGB); rt.mTarget->setSourceType(GL_UNSIGNED_BYTE); rt.mTarget->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); rt.mTarget->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); rt.mTarget->setName(std::string(mBlockName)); while (!isNext() && !isNext()) { expect(); auto key = std::get(mToken).value; expect(); if (key == "min_filter") rt.mTarget->setFilter(osg::Texture2D::MIN_FILTER, parseFilterMode()); else if (key == "mag_filter") rt.mTarget->setFilter(osg::Texture2D::MAG_FILTER, parseFilterMode()); else if (key == "wrap_s") rt.mTarget->setWrap(osg::Texture2D::WRAP_S, parseWrapMode()); else if (key == "wrap_t") rt.mTarget->setWrap(osg::Texture2D::WRAP_T, parseWrapMode()); else if (key == "width_ratio") rt.mSize.mWidthRatio = parseFloat(); else if (key == "height_ratio") rt.mSize.mHeightRatio = parseFloat(); else if (key == "width") rt.mSize.mWidth = parseInteger(); else if (key == "height") rt.mSize.mHeight = parseInteger(); else if (key == "internal_format") rt.mTarget->setInternalFormat(parseInternalFormat()); else if (key == "source_type") rt.mTarget->setSourceType(parseSourceType()); else if (key == "source_format") rt.mTarget->setSourceFormat(parseSourceFormat()); else if (key == "mipmaps") rt.mMipMap = parseBool(); else if (key == "clear_color") rt.mClearColor = parseVec(); else error(Misc::StringUtils::format("unexpected key '%s'", std::string(key))); expect(); } mRenderTargets.emplace(mBlockName, std::move(rt)); } template <> void Technique::parseBlockImp() { if (!mLexer->jump()) error(Misc::StringUtils::format("unterminated 'vertex' block, expected closing brackets")); auto& pass = mPassMap[mBlockName]; if (!pass) pass = std::make_shared(); pass->mName = mBlockName; if (pass->mCompute) error(Misc::StringUtils::format("'compute' block already defined. Usage is ambiguous.")); else if (!pass->mVertex) pass->mVertex = new osg::Shader(osg::Shader::VERTEX, getBlockWithLineDirective()); else error(Misc::StringUtils::format("duplicate vertex shader for block '%s'", std::string(mBlockName))); pass->mType = Pass::Type::Pixel; } template <> void Technique::parseBlockImp() { if (!mLexer->jump()) error(Misc::StringUtils::format("unterminated 'fragment' block, expected closing brackets")); auto& pass = mPassMap[mBlockName]; if (!pass) pass = std::make_shared(); pass->mUBO = mUBO; pass->mName = mBlockName; if (pass->mCompute) error(Misc::StringUtils::format("'compute' block already defined. Usage is ambiguous.")); else if (!pass->mFragment) pass->mFragment = new osg::Shader(osg::Shader::FRAGMENT, getBlockWithLineDirective()); else error(Misc::StringUtils::format("duplicate vertex shader for block '%s'", std::string(mBlockName))); pass->mType = Pass::Type::Pixel; } template <> void Technique::parseBlockImp() { if (!mLexer->jump()) error(Misc::StringUtils::format("unterminated 'compute' block, expected closing brackets")); auto& pass = mPassMap[mBlockName]; if (!pass) pass = std::make_shared(); pass->mName = mBlockName; if (pass->mFragment) error(Misc::StringUtils::format("'fragment' block already defined. Usage is ambiguous.")); else if (pass->mVertex) error(Misc::StringUtils::format("'vertex' block already defined. Usage is ambiguous.")); else if (!pass->mFragment) pass->mCompute = new osg::Shader(osg::Shader::COMPUTE, getBlockWithLineDirective()); else error(Misc::StringUtils::format("duplicate vertex shader for block '%s'", std::string(mBlockName))); pass->mType = Pass::Type::Compute; } template void Technique::parseSampler() { if (findUniform(std::string(mBlockName)) != mDefinedUniforms.end()) error(Misc::StringUtils::format("redeclaration of uniform '%s'", std::string(mBlockName))); ProxyTextureData proxy; osg::ref_ptr sampler; constexpr bool is1D = std::is_same_v; constexpr bool is3D = std::is_same_v; Types::SamplerType type; while (!isNext() && !isNext()) { expect(); auto key = asLiteral(); expect(); if (!is1D && key == "min_filter") proxy.min_filter = parseFilterMode(); else if (!is1D && key == "mag_filter") proxy.mag_filter = parseFilterMode(); else if (key == "wrap_s") proxy.wrap_s = parseWrapMode(); else if (key == "wrap_t") proxy.wrap_t = parseWrapMode(); else if (is3D && key == "wrap_r") proxy.wrap_r = parseWrapMode(); else if (key == "compression") proxy.compression = parseCompression(); else if (key == "source_type") proxy.source_type = parseSourceType(); else if (key == "source_format") proxy.source_format = parseSourceFormat(); else if (key == "internal_format") proxy.internal_format = parseInternalFormat(); else if (key == "source") { expect(); const osg::ref_ptr image = mImageManager.getImage(VFS::Path::Normalized(std::get(mToken).value), is3D); if constexpr (is1D) { type = Types::SamplerType::Texture_1D; sampler = new osg::Texture1D(image); } else if constexpr (is3D) { type = Types::SamplerType::Texture_3D; sampler = new osg::Texture3D(image); } else { type = Types::SamplerType::Texture_2D; sampler = new osg::Texture2D(image); } } else error(Misc::StringUtils::format("unexpected key '%s'", std::string{ key })); expect(); } if (!sampler) error(Misc::StringUtils::format( "%s '%s' requires a filename", std::string(T::repr), std::string{ mBlockName })); if (!is1D) { sampler->setFilter(osg::Texture::MIN_FILTER, proxy.min_filter); sampler->setFilter(osg::Texture::MAG_FILTER, proxy.mag_filter); } if (is3D) sampler->setWrap(osg::Texture::WRAP_R, proxy.wrap_r); sampler->setWrap(osg::Texture::WRAP_S, proxy.wrap_s); sampler->setWrap(osg::Texture::WRAP_T, proxy.wrap_t); sampler->setInternalFormatMode(proxy.compression); if (proxy.internal_format.has_value()) sampler->setInternalFormat(proxy.internal_format.value()); if (proxy.source_type.has_value()) sampler->setSourceType(proxy.source_type.value()); if (proxy.internal_format.has_value()) sampler->setSourceFormat(proxy.internal_format.value()); sampler->setName(std::string{ mBlockName }); sampler->setResizeNonPowerOfTwoHint(false); mTextures.emplace_back(sampler); std::shared_ptr uniform = std::make_shared(); uniform->mSamplerType = type; uniform->mName = std::string(mBlockName); mDefinedUniforms.emplace_back(std::move(uniform)); } template SrcT Technique::getUniformValue() { constexpr bool isVec = std::is_same_v || std::is_same_v || std::is_same_v; constexpr bool isFloat = std::is_same_v; constexpr bool isInt = std::is_same_v; constexpr bool isBool = std::is_same_v; static_assert(isVec || isFloat || isInt || isBool, "Unsupported type"); if constexpr (isVec) { return parseVec(); } else if constexpr (isFloat) { return parseFloat(); } else if constexpr (isInt) { return parseInteger(); } else if constexpr (isBool) { return parseBool(); } error(Misc::StringUtils::format("failed setting uniform type")); } template void Technique::parseUniform() { if (findUniform(std::string(mBlockName)) != mDefinedUniforms.end()) error(Misc::StringUtils::format("redeclaration of uniform '%s'", std::string(mBlockName))); std::shared_ptr uniform = std::make_shared(); Types::Uniform data = Types::Uniform(); while (!isNext() && !isNext()) { expect(); auto key = asLiteral(); expect("error parsing config for uniform block"); if (key == "default") { data.mDefault = getUniformValue(); } else if (key == "size") { if constexpr (std::is_same_v) error("bool arrays currently unsupported"); int size = parseInteger(); if (size > 1) data.mArray = std::vector(size); } else if (key == "min") { data.mMin = getUniformValue(); } else if (key == "max") { data.mMax = getUniformValue(); } else if (key == "step") uniform->mStep = parseFloat(); else if (key == "static") uniform->mStatic = parseBool(); else if (key == "description") { uniform->mDescription = parseString(); } else if (key == "header") { uniform->mHeader = parseString(); } else if (key == "display_name") { uniform->mDisplayName = parseString(); } else if (key == "widget_type") { parseWidgetType(data); } else error(Misc::StringUtils::format("unexpected key '%s'", std::string{ key })); expect(); } if (data.isArray()) uniform->mStatic = false; uniform->mName = std::string(mBlockName); uniform->mData = data; uniform->mTechniqueName = mName; if (data.mArray) { if constexpr (!std::is_same_v) { if (auto cached = Settings::ShaderManager::get().getValue>(mName, uniform->mName)) uniform->setValue(cached.value()); } } else if (auto cached = Settings::ShaderManager::get().getValue(mName, uniform->mName)) { uniform->setValue(cached.value()); } mDefinedUniforms.emplace_back(std::move(uniform)); } template <> void Technique::parseBlockImp() { parseSampler(); } template <> void Technique::parseBlockImp() { parseSampler(); } template <> void Technique::parseBlockImp() { parseSampler(); } template <> void Technique::parseBlockImp() { parseUniform(); } template <> void Technique::parseBlockImp() { parseUniform(); } template <> void Technique::parseBlockImp() { parseUniform(); } template <> void Technique::parseBlockImp() { parseUniform(); } template <> void Technique::parseBlockImp() { parseUniform(); } template <> void Technique::parseBlockImp() { parseUniform(); } template void Technique::expect(const std::string& err) { mToken = mLexer->next(); if (!std::holds_alternative(mToken)) { if (err.empty()) error(Misc::StringUtils::format("Expected %s", std::string(T::repr))); else error(Misc::StringUtils::format("%s. Expected %s", err, std::string(T::repr))); } } template void Technique::expect(const std::string& err) { mToken = mLexer->next(); if (!std::holds_alternative(mToken) && !std::holds_alternative(mToken)) { if (err.empty()) error(Misc::StringUtils::format( "%s. Expected %s or %s", err, std::string(T::repr), std::string(T2::repr))); else error(Misc::StringUtils::format("Expected %s or %s", std::string(T::repr), std::string(T2::repr))); } } template bool Technique::isNext() { return std::holds_alternative(mLexer->peek()); } void Technique::parse(std::string&& buffer) { mBuffer = std::move(buffer); Misc::StringUtils::replaceAll(mBuffer, "\r\n", "\n"); mLexer = std::make_unique(mBuffer); for (auto t = mLexer->next(); !std::holds_alternative(t); t = mLexer->next()) { std::visit( [this](auto&& arg) { using T = std::decay_t; if constexpr (std::is_same_v) parseBlock(false); else if constexpr (std::is_same_v) parseBlock(false); else if constexpr (std::is_same_v) parseBlock(); else if constexpr (std::is_same_v) parseBlock(); else if constexpr (std::is_same_v) parseBlock(); else if constexpr (std::is_same_v) parseBlock(); else if constexpr (std::is_same_v) parseBlock(); else if constexpr (std::is_same_v) parseBlock(); else if constexpr (std::is_same_v) parseBlock(); else if constexpr (std::is_same_v) parseBlock(); else if constexpr (std::is_same_v) parseBlock(); else if constexpr (std::is_same_v) parseBlock(); else if constexpr (std::is_same_v) parseBlock(); else if constexpr (std::is_same_v) parseBlock(); else if constexpr (std::is_same_v) parseBlock(); else error("invalid top level block"); }, t); } } template void Technique::parseBlock(bool named) { mBlockName = T::repr; if (named) { expect("name is required for preceeding block decleration"); mBlockName = std::get(mToken).value; if (isNext()) parseBlockHeader(); } expect(); parseBlockImp(); expect(); } template std::vector Technique::parseLiteralList() { std::vector data; while (!isNext()) { expect(); data.emplace_back(std::get(mToken).value); if (!isNext()) break; mLexer->next(); } return data; } void Technique::parseBlockHeader() { expect(); if (isNext()) { mLexer->next(); return; } auto& pass = mPassMap[mBlockName]; if (!pass) pass = std::make_shared(); while (!isNext()) { expect("invalid key in block header"); std::string_view key = std::get(mToken).value; expect(); if (key == "target") { expect(); pass->mTarget = std::get(mToken).value; } else if (key == "rt1") { expect(); pass->mRenderTargets[0] = std::get(mToken).value; } else if (key == "rt2") { expect(); pass->mRenderTargets[1] = std::get(mToken).value; } else if (key == "rt3") { expect(); pass->mRenderTargets[2] = std::get(mToken).value; } else if (key == "blend") { expect(); osg::BlendEquation::Equation blendEq = parseBlendEquation(); expect(); osg::BlendFunc::BlendFuncMode blendSrc = parseBlendFuncMode(); expect(); osg::BlendFunc::BlendFuncMode blendDest = parseBlendFuncMode(); expect(); pass->mBlendSource = blendSrc; pass->mBlendDest = blendDest; if (blendEq != osg::BlendEquation::FUNC_ADD) pass->mBlendEq = blendEq; } else error(Misc::StringUtils::format("unrecognized key '%s' in block header", std::string(key))); mToken = mLexer->next(); if (std::holds_alternative(mToken)) { if (std::holds_alternative(mLexer->peek())) error(Misc::StringUtils::format("leading comma in '%s' is not allowed", std::string(mBlockName))); else continue; } if (std::holds_alternative(mToken)) return; } error("malformed block header"); } std::string_view Technique::asLiteral() const { return std::get(mToken).value; } FlagsType Technique::parseFlags() { auto parseBit = [this](std::string_view term) { for (const auto& [identifer, bit] : constants::TechniqueFlag) { if (Misc::StringUtils::ciEqual(term, identifer)) return bit; } error(Misc::StringUtils::format("unrecognized flag '%s'", std::string(term))); }; FlagsType flag = 0; for (const auto& bit : parseLiteralList()) flag |= parseBit(bit); return flag; } osg::Texture::FilterMode Technique::parseFilterMode() { expect(); for (const auto& [identifer, mode] : constants::FilterMode) { if (asLiteral() == identifer) return mode; } error(Misc::StringUtils::format("unrecognized filter mode '%s'", std::string{ asLiteral() })); } osg::Texture::WrapMode Technique::parseWrapMode() { expect(); for (const auto& [identifer, mode] : constants::WrapMode) { if (asLiteral() == identifer) return mode; } if (asLiteral() == "clamp") error( "unsupported wrap mode 'clamp'; 'clamp_to_edge' was likely intended, look for an updated shader or " "contact author"); error(Misc::StringUtils::format("unrecognized wrap mode '%s'", std::string{ asLiteral() })); } osg::Texture::InternalFormatMode Technique::parseCompression() { expect(); for (const auto& [identifer, mode] : constants::Compression) { if (asLiteral() == identifer) return mode; } error(Misc::StringUtils::format("unrecognized compression '%s'", std::string{ asLiteral() })); } int Technique::parseInternalFormat() { expect(); for (const auto& [identifer, mode] : constants::InternalFormat) { if (asLiteral() == identifer) return mode; } error(Misc::StringUtils::format("unrecognized internal format '%s'", std::string{ asLiteral() })); } int Technique::parseSourceType() { expect(); for (const auto& [identifer, mode] : constants::SourceType) { if (asLiteral() == identifer) return mode; } error(Misc::StringUtils::format("unrecognized source type '%s'", std::string{ asLiteral() })); } int Technique::parseSourceFormat() { expect(); for (const auto& [identifer, mode] : constants::SourceFormat) { if (asLiteral() == identifer) return mode; } error(Misc::StringUtils::format("unrecognized source format '%s'", std::string{ asLiteral() })); } osg::BlendEquation::Equation Technique::parseBlendEquation() { expect(); for (const auto& [identifer, mode] : constants::BlendEquation) { if (asLiteral() == identifer) return mode; } error(Misc::StringUtils::format("unrecognized blend equation '%s'", std::string{ asLiteral() })); } osg::BlendFunc::BlendFuncMode Technique::parseBlendFuncMode() { expect(); for (const auto& [identifer, mode] : constants::BlendFunc) { if (asLiteral() == identifer) return mode; } error(Misc::StringUtils::format("unrecognized blend function '%s'", std::string{ asLiteral() })); } template void Technique::parseWidgetType(Types::Uniform& uniform) { expect(); if (asLiteral() == "choice") { /* Example usage widget_type = choice( "Option A": , "Option B": , "Option C": ); */ expect(); std::vector> choices; while (!isNext()) { fx::Types::Choice choice; choice.mLabel = parseString(); expect(); choice.mValue = getUniformValue(); choices.push_back(choice); if (isNext()) { mToken = mLexer->next(); // Handle leading comma if (isNext()) { break; } continue; } break; } uniform.mChoices = std::move(choices); expect(); } else { error(Misc::StringUtils::format("unrecognized widget type '%s'", std::string{ asLiteral() })); } } bool Technique::parseBool() { mToken = mLexer->next(); if (std::holds_alternative(mToken)) return true; if (std::holds_alternative(mToken)) return false; error("expected 'true' or 'false' as boolean value"); } std::string_view Technique::parseString() { expect(); return std::get(mToken).value; } float Technique::parseFloat() { mToken = mLexer->next(); if (std::holds_alternative(mToken)) return std::get(mToken).value; if (std::holds_alternative(mToken)) return static_cast(std::get(mToken).value); error("expected float value"); } int Technique::parseInteger() { expect(); return std::get(mToken).value; } template OSGVec Technique::parseVec() { expect(); expect(); OSGVec value; for (int i = 0; i < OSGVec::num_components; ++i) { value[i] = parseFloat(); if (i < OSGVec::num_components - 1) expect(); } expect("check definition of the vector"); return value; } }