mirror of
				https://github.com/OpenMW/openmw.git
				synced 2025-10-31 22:56:40 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1122 lines
		
	
	
	
		
			36 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1122 lines
		
	
	
	
		
			36 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include "technique.hpp"
 | |
| 
 | |
| #include <array>
 | |
| #include <string>
 | |
| #include <utility>
 | |
| 
 | |
| #include <osg/Texture1D>
 | |
| #include <osg/Texture2D>
 | |
| #include <osg/Texture3D>
 | |
| 
 | |
| #include <SDL_opengl_glext.h>
 | |
| 
 | |
| #include <components/debug/debuglog.hpp>
 | |
| #include <components/files/conversion.hpp>
 | |
| #include <components/misc/strings/algorithm.hpp>
 | |
| #include <components/resource/imagemanager.hpp>
 | |
| #include <components/stereo/multiview.hpp>
 | |
| #include <components/vfs/manager.hpp>
 | |
| 
 | |
| #include "parseconstants.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<int> source_format;
 | |
|         std::optional<int> source_type;
 | |
|         std::optional<int> internal_format;
 | |
|     };
 | |
| }
 | |
| 
 | |
| namespace fx
 | |
| {
 | |
|     VFS::Path::Normalized Technique::makeFileName(std::string_view name)
 | |
|     {
 | |
|         std::string fileName(name);
 | |
|         fileName += '.';
 | |
|         fileName += Technique::sExt;
 | |
|         VFS::Path::Normalized result(Technique::sSubdir);
 | |
|         result /= fileName;
 | |
|         return result;
 | |
|     }
 | |
| 
 | |
|     Technique::Technique(const VFS::Manager& vfs, Resource::ImageManager& imageManager,
 | |
|         VFS::Path::NormalizedView fileName, std::string name, int width, int height, bool ubo, bool supportsNormals)
 | |
|         : mName(std::move(name))
 | |
|         , mFilePath(fileName)
 | |
|         , 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 || Stereo::getMultiview()) ? 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 (std::ranges::count(mFilePath.value(), '/') > 1)
 | |
|         {
 | |
|             Log(Debug::Error) << "Could not load technique, invalid location '" << mFilePath << "'";
 | |
| 
 | |
|             mStatus = Status::File_Not_exists;
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         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<char>(*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<Lexer::Shared>()
 | |
|     {
 | |
|         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<Lexer::Technique>()
 | |
|     {
 | |
|         if (!mPassKeys.empty())
 | |
|             error("exactly one 'technique' block can appear per file");
 | |
| 
 | |
|         while (!isNext<Lexer::Close_bracket>() && !isNext<Lexer::Eof>())
 | |
|         {
 | |
|             expect<Lexer::Literal>();
 | |
| 
 | |
|             auto key = std::get<Lexer::Literal>(mToken).value;
 | |
| 
 | |
|             expect<Lexer::Equal>();
 | |
| 
 | |
|             if (key == "passes")
 | |
|                 mPassKeys = parseLiteralList<Lexer::Comma>();
 | |
|             else if (key == "version")
 | |
|                 mVersion = parseString();
 | |
|             else if (key == "description")
 | |
|                 mDescription = parseString();
 | |
|             else if (key == "author")
 | |
|                 mAuthor = parseString();
 | |
|             else if (key == "glsl_version")
 | |
|                 mGLSLVersion = std::max(mGLSLVersion, parseInteger());
 | |
|             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<Lexer::String>();
 | |
|                 mGLSLProfile = std::string(std::get<Lexer::String>(mToken).value);
 | |
|             }
 | |
|             else if (key == "glsl_extensions")
 | |
|             {
 | |
|                 for (const auto& ext : parseLiteralList<Lexer::Comma>())
 | |
|                     mGLSLExtensions.emplace(ext);
 | |
|             }
 | |
|             else if (key == "dynamic")
 | |
|                 mDynamic = parseBool();
 | |
|             else
 | |
|                 error(Misc::StringUtils::format("unexpected key '%s'", std::string{ key }));
 | |
| 
 | |
|             expect<Lexer::SemiColon>();
 | |
|         }
 | |
| 
 | |
|         if (mPassKeys.empty())
 | |
|             error("pass list in 'technique' block cannot be empty.");
 | |
|     }
 | |
| 
 | |
|     template <>
 | |
|     void Technique::parseBlockImp<Lexer::Render_Target>()
 | |
|     {
 | |
|         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<Lexer::Close_bracket>() && !isNext<Lexer::Eof>())
 | |
|         {
 | |
|             expect<Lexer::Literal>();
 | |
| 
 | |
|             auto key = std::get<Lexer::Literal>(mToken).value;
 | |
| 
 | |
|             expect<Lexer::Equal>();
 | |
| 
 | |
|             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<osg::Vec4f, Lexer::Vec4>();
 | |
|             else
 | |
|                 error(Misc::StringUtils::format("unexpected key '%s'", std::string(key)));
 | |
| 
 | |
|             expect<Lexer::SemiColon>();
 | |
|         }
 | |
| 
 | |
|         mRenderTargets.emplace(mBlockName, std::move(rt));
 | |
|     }
 | |
| 
 | |
|     template <>
 | |
|     void Technique::parseBlockImp<Lexer::Vertex>()
 | |
|     {
 | |
|         if (!mLexer->jump())
 | |
|             error(Misc::StringUtils::format("unterminated 'vertex' block, expected closing brackets"));
 | |
| 
 | |
|         auto& pass = mPassMap[mBlockName];
 | |
| 
 | |
|         if (!pass)
 | |
|             pass = std::make_shared<fx::Pass>();
 | |
| 
 | |
|         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<Lexer::Fragment>()
 | |
|     {
 | |
|         if (!mLexer->jump())
 | |
|             error(Misc::StringUtils::format("unterminated 'fragment' block, expected closing brackets"));
 | |
| 
 | |
|         auto& pass = mPassMap[mBlockName];
 | |
| 
 | |
|         if (!pass)
 | |
|             pass = std::make_shared<fx::Pass>();
 | |
| 
 | |
|         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<Lexer::Compute>()
 | |
|     {
 | |
|         if (!mLexer->jump())
 | |
|             error(Misc::StringUtils::format("unterminated 'compute' block, expected closing brackets"));
 | |
| 
 | |
|         auto& pass = mPassMap[mBlockName];
 | |
| 
 | |
|         if (!pass)
 | |
|             pass = std::make_shared<fx::Pass>();
 | |
| 
 | |
|         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 <class T>
 | |
|     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<osg::Texture> sampler;
 | |
| 
 | |
|         constexpr bool is1D = std::is_same_v<Lexer::Sampler_1D, T>;
 | |
|         constexpr bool is3D = std::is_same_v<Lexer::Sampler_3D, T>;
 | |
| 
 | |
|         Types::SamplerType type;
 | |
| 
 | |
|         while (!isNext<Lexer::Close_bracket>() && !isNext<Lexer::Eof>())
 | |
|         {
 | |
|             expect<Lexer::Literal>();
 | |
| 
 | |
|             auto key = asLiteral();
 | |
| 
 | |
|             expect<Lexer::Equal>();
 | |
| 
 | |
|             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<Lexer::String>();
 | |
|                 const osg::ref_ptr<osg::Image> image
 | |
|                     = mImageManager.getImage(VFS::Path::Normalized(std::get<Lexer::String>(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<Lexer::SemiColon>();
 | |
|         }
 | |
|         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<Types::UniformBase> uniform = std::make_shared<Types::UniformBase>();
 | |
|         uniform->mSamplerType = type;
 | |
|         uniform->mName = std::string(mBlockName);
 | |
|         mDefinedUniforms.emplace_back(std::move(uniform));
 | |
|     }
 | |
| 
 | |
|     template <class SrcT, class T>
 | |
|     SrcT Technique::getUniformValue()
 | |
|     {
 | |
|         constexpr bool isVec
 | |
|             = std::is_same_v<osg::Vec2f, SrcT> || std::is_same_v<osg::Vec3f, SrcT> || std::is_same_v<osg::Vec4f, SrcT>;
 | |
|         constexpr bool isFloat = std::is_same_v<float, SrcT>;
 | |
|         constexpr bool isInt = std::is_same_v<int, SrcT>;
 | |
|         constexpr bool isBool = std::is_same_v<bool, SrcT>;
 | |
| 
 | |
|         static_assert(isVec || isFloat || isInt || isBool, "Unsupported type");
 | |
| 
 | |
|         if constexpr (isVec)
 | |
|         {
 | |
|             return parseVec<SrcT, T>();
 | |
|         }
 | |
|         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 <class SrcT, class T>
 | |
|     void Technique::parseUniform()
 | |
|     {
 | |
|         if (findUniform(std::string(mBlockName)) != mDefinedUniforms.end())
 | |
|             error(Misc::StringUtils::format("redeclaration of uniform '%s'", std::string(mBlockName)));
 | |
| 
 | |
|         std::shared_ptr<Types::UniformBase> uniform = std::make_shared<Types::UniformBase>();
 | |
|         Types::Uniform<SrcT> data = Types::Uniform<SrcT>();
 | |
| 
 | |
|         while (!isNext<Lexer::Close_bracket>() && !isNext<Lexer::Eof>())
 | |
|         {
 | |
|             expect<Lexer::Literal>();
 | |
| 
 | |
|             auto key = asLiteral();
 | |
| 
 | |
|             expect<Lexer::Equal>("error parsing config for uniform block");
 | |
| 
 | |
|             if (key == "default")
 | |
|             {
 | |
|                 data.mDefault = getUniformValue<SrcT, T>();
 | |
|             }
 | |
|             else if (key == "size")
 | |
|             {
 | |
|                 if constexpr (std::is_same_v<bool, SrcT>)
 | |
|                     error("bool arrays currently unsupported");
 | |
| 
 | |
|                 int size = parseInteger();
 | |
|                 if (size > 1)
 | |
|                     data.mArray = std::vector<SrcT>(size);
 | |
|             }
 | |
|             else if (key == "min")
 | |
|             {
 | |
|                 data.mMin = getUniformValue<SrcT, T>();
 | |
|             }
 | |
|             else if (key == "max")
 | |
|             {
 | |
|                 data.mMax = getUniformValue<SrcT, T>();
 | |
|             }
 | |
|             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<SrcT, T>(data);
 | |
|             }
 | |
|             else
 | |
|                 error(Misc::StringUtils::format("unexpected key '%s'", std::string{ key }));
 | |
| 
 | |
|             expect<Lexer::SemiColon>();
 | |
|         }
 | |
| 
 | |
|         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<bool, SrcT>)
 | |
|             {
 | |
|                 if (auto cached = Settings::ShaderManager::get().getValue<std::vector<SrcT>>(mName, uniform->mName))
 | |
|                     uniform->setValue(cached.value());
 | |
|             }
 | |
|         }
 | |
|         else if (auto cached = Settings::ShaderManager::get().getValue<SrcT>(mName, uniform->mName))
 | |
|         {
 | |
|             uniform->setValue(cached.value());
 | |
|         }
 | |
| 
 | |
|         mDefinedUniforms.emplace_back(std::move(uniform));
 | |
|     }
 | |
| 
 | |
|     template <>
 | |
|     void Technique::parseBlockImp<Lexer::Sampler_1D>()
 | |
|     {
 | |
|         parseSampler<Lexer::Sampler_1D>();
 | |
|     }
 | |
| 
 | |
|     template <>
 | |
|     void Technique::parseBlockImp<Lexer::Sampler_2D>()
 | |
|     {
 | |
|         parseSampler<Lexer::Sampler_2D>();
 | |
|     }
 | |
| 
 | |
|     template <>
 | |
|     void Technique::parseBlockImp<Lexer::Sampler_3D>()
 | |
|     {
 | |
|         parseSampler<Lexer::Sampler_3D>();
 | |
|     }
 | |
| 
 | |
|     template <>
 | |
|     void Technique::parseBlockImp<Lexer::Uniform_Bool>()
 | |
|     {
 | |
|         parseUniform<bool, bool>();
 | |
|     }
 | |
| 
 | |
|     template <>
 | |
|     void Technique::parseBlockImp<Lexer::Uniform_Float>()
 | |
|     {
 | |
|         parseUniform<float, float>();
 | |
|     }
 | |
| 
 | |
|     template <>
 | |
|     void Technique::parseBlockImp<Lexer::Uniform_Int>()
 | |
|     {
 | |
|         parseUniform<int, int>();
 | |
|     }
 | |
| 
 | |
|     template <>
 | |
|     void Technique::parseBlockImp<Lexer::Uniform_Vec2>()
 | |
|     {
 | |
|         parseUniform<osg::Vec2f, Lexer::Vec2>();
 | |
|     }
 | |
| 
 | |
|     template <>
 | |
|     void Technique::parseBlockImp<Lexer::Uniform_Vec3>()
 | |
|     {
 | |
|         parseUniform<osg::Vec3f, Lexer::Vec3>();
 | |
|     }
 | |
| 
 | |
|     template <>
 | |
|     void Technique::parseBlockImp<Lexer::Uniform_Vec4>()
 | |
|     {
 | |
|         parseUniform<osg::Vec4f, Lexer::Vec4>();
 | |
|     }
 | |
| 
 | |
|     template <class T>
 | |
|     void Technique::expect(const std::string& err)
 | |
|     {
 | |
|         mToken = mLexer->next();
 | |
|         if (!std::holds_alternative<T>(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 <class T, class T2>
 | |
|     void Technique::expect(const std::string& err)
 | |
|     {
 | |
|         mToken = mLexer->next();
 | |
|         if (!std::holds_alternative<T>(mToken) && !std::holds_alternative<T2>(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 <class T>
 | |
|     bool Technique::isNext()
 | |
|     {
 | |
|         return std::holds_alternative<T>(mLexer->peek());
 | |
|     }
 | |
| 
 | |
|     void Technique::parse(std::string&& buffer)
 | |
|     {
 | |
|         mBuffer = std::move(buffer);
 | |
|         Misc::StringUtils::replaceAll(mBuffer, "\r\n", "\n");
 | |
|         mLexer = std::make_unique<Lexer::Lexer>(mBuffer);
 | |
| 
 | |
|         for (auto t = mLexer->next(); !std::holds_alternative<Lexer::Eof>(t); t = mLexer->next())
 | |
|         {
 | |
|             std::visit(
 | |
|                 [this](auto&& arg) {
 | |
|                     using T = std::decay_t<decltype(arg)>;
 | |
| 
 | |
|                     if constexpr (std::is_same_v<Lexer::Shared, T>)
 | |
|                         parseBlock<Lexer::Shared>(false);
 | |
|                     else if constexpr (std::is_same_v<Lexer::Technique, T>)
 | |
|                         parseBlock<Lexer::Technique>(false);
 | |
|                     else if constexpr (std::is_same_v<Lexer::Render_Target, T>)
 | |
|                         parseBlock<Lexer::Render_Target>();
 | |
|                     else if constexpr (std::is_same_v<Lexer::Vertex, T>)
 | |
|                         parseBlock<Lexer::Vertex>();
 | |
|                     else if constexpr (std::is_same_v<Lexer::Fragment, T>)
 | |
|                         parseBlock<Lexer::Fragment>();
 | |
|                     else if constexpr (std::is_same_v<Lexer::Compute, T>)
 | |
|                         parseBlock<Lexer::Compute>();
 | |
|                     else if constexpr (std::is_same_v<Lexer::Sampler_1D, T>)
 | |
|                         parseBlock<Lexer::Sampler_1D>();
 | |
|                     else if constexpr (std::is_same_v<Lexer::Sampler_2D, T>)
 | |
|                         parseBlock<Lexer::Sampler_2D>();
 | |
|                     else if constexpr (std::is_same_v<Lexer::Sampler_3D, T>)
 | |
|                         parseBlock<Lexer::Sampler_3D>();
 | |
|                     else if constexpr (std::is_same_v<Lexer::Uniform_Bool, T>)
 | |
|                         parseBlock<Lexer::Uniform_Bool>();
 | |
|                     else if constexpr (std::is_same_v<Lexer::Uniform_Float, T>)
 | |
|                         parseBlock<Lexer::Uniform_Float>();
 | |
|                     else if constexpr (std::is_same_v<Lexer::Uniform_Int, T>)
 | |
|                         parseBlock<Lexer::Uniform_Int>();
 | |
|                     else if constexpr (std::is_same_v<Lexer::Uniform_Vec2, T>)
 | |
|                         parseBlock<Lexer::Uniform_Vec2>();
 | |
|                     else if constexpr (std::is_same_v<Lexer::Uniform_Vec3, T>)
 | |
|                         parseBlock<Lexer::Uniform_Vec3>();
 | |
|                     else if constexpr (std::is_same_v<Lexer::Uniform_Vec4, T>)
 | |
|                         parseBlock<Lexer::Uniform_Vec4>();
 | |
|                     else
 | |
|                         error("invalid top level block");
 | |
|                 },
 | |
|                 t);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     template <class T>
 | |
|     void Technique::parseBlock(bool named)
 | |
|     {
 | |
|         mBlockName = T::repr;
 | |
| 
 | |
|         if (named)
 | |
|         {
 | |
|             expect<Lexer::Literal>("name is required for preceeding block decleration");
 | |
| 
 | |
|             mBlockName = std::get<Lexer::Literal>(mToken).value;
 | |
| 
 | |
|             if (isNext<Lexer::Open_Parenthesis>())
 | |
|                 parseBlockHeader();
 | |
|         }
 | |
| 
 | |
|         expect<Lexer::Open_bracket>();
 | |
| 
 | |
|         parseBlockImp<T>();
 | |
| 
 | |
|         expect<Lexer::Close_bracket>();
 | |
|     }
 | |
| 
 | |
|     template <class TDelimeter>
 | |
|     std::vector<std::string_view> Technique::parseLiteralList()
 | |
|     {
 | |
|         std::vector<std::string_view> data;
 | |
| 
 | |
|         while (!isNext<Lexer::Eof>())
 | |
|         {
 | |
|             expect<Lexer::Literal>();
 | |
| 
 | |
|             data.emplace_back(std::get<Lexer::Literal>(mToken).value);
 | |
| 
 | |
|             if (!isNext<TDelimeter>())
 | |
|                 break;
 | |
| 
 | |
|             mLexer->next();
 | |
|         }
 | |
| 
 | |
|         return data;
 | |
|     }
 | |
| 
 | |
|     void Technique::parseBlockHeader()
 | |
|     {
 | |
|         expect<Lexer::Open_Parenthesis>();
 | |
| 
 | |
|         if (isNext<Lexer::Close_Parenthesis>())
 | |
|         {
 | |
|             mLexer->next();
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         auto& pass = mPassMap[mBlockName];
 | |
| 
 | |
|         if (!pass)
 | |
|             pass = std::make_shared<fx::Pass>();
 | |
| 
 | |
|         while (!isNext<Lexer::Eof>())
 | |
|         {
 | |
|             expect<Lexer::Literal>("invalid key in block header");
 | |
| 
 | |
|             std::string_view key = std::get<Lexer::Literal>(mToken).value;
 | |
| 
 | |
|             expect<Lexer::Equal>();
 | |
| 
 | |
|             if (key == "target")
 | |
|             {
 | |
|                 expect<Lexer::Literal>();
 | |
|                 pass->mTarget = std::get<Lexer::Literal>(mToken).value;
 | |
|             }
 | |
|             else if (key == "rt1")
 | |
|             {
 | |
|                 expect<Lexer::Literal>();
 | |
|                 pass->mRenderTargets[0] = std::get<Lexer::Literal>(mToken).value;
 | |
|             }
 | |
|             else if (key == "rt2")
 | |
|             {
 | |
|                 expect<Lexer::Literal>();
 | |
|                 pass->mRenderTargets[1] = std::get<Lexer::Literal>(mToken).value;
 | |
|             }
 | |
|             else if (key == "rt3")
 | |
|             {
 | |
|                 expect<Lexer::Literal>();
 | |
|                 pass->mRenderTargets[2] = std::get<Lexer::Literal>(mToken).value;
 | |
|             }
 | |
|             else if (key == "blend")
 | |
|             {
 | |
|                 expect<Lexer::Open_Parenthesis>();
 | |
|                 osg::BlendEquation::Equation blendEq = parseBlendEquation();
 | |
|                 expect<Lexer::Comma>();
 | |
|                 osg::BlendFunc::BlendFuncMode blendSrc = parseBlendFuncMode();
 | |
|                 expect<Lexer::Comma>();
 | |
|                 osg::BlendFunc::BlendFuncMode blendDest = parseBlendFuncMode();
 | |
|                 expect<Lexer::Close_Parenthesis>();
 | |
| 
 | |
|                 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<Lexer::Comma>(mToken))
 | |
|             {
 | |
|                 if (std::holds_alternative<Lexer::Close_Parenthesis>(mLexer->peek()))
 | |
|                     error(Misc::StringUtils::format("leading comma in '%s' is not allowed", std::string(mBlockName)));
 | |
|                 else
 | |
|                     continue;
 | |
|             }
 | |
| 
 | |
|             if (std::holds_alternative<Lexer::Close_Parenthesis>(mToken))
 | |
|                 return;
 | |
|         }
 | |
| 
 | |
|         error("malformed block header");
 | |
|     }
 | |
| 
 | |
|     std::string_view Technique::asLiteral() const
 | |
|     {
 | |
|         return std::get<Lexer::Literal>(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<Lexer::Comma>())
 | |
|             flag |= parseBit(bit);
 | |
| 
 | |
|         return flag;
 | |
|     }
 | |
| 
 | |
|     osg::Texture::FilterMode Technique::parseFilterMode()
 | |
|     {
 | |
|         expect<Lexer::Literal>();
 | |
| 
 | |
|         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<Lexer::Literal>();
 | |
| 
 | |
|         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<Lexer::Literal>();
 | |
| 
 | |
|         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<Lexer::Literal>();
 | |
| 
 | |
|         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<Lexer::Literal>();
 | |
| 
 | |
|         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<Lexer::Literal>();
 | |
| 
 | |
|         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<Lexer::Literal>();
 | |
| 
 | |
|         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<Lexer::Literal>();
 | |
| 
 | |
|         for (const auto& [identifer, mode] : constants::BlendFunc)
 | |
|         {
 | |
|             if (asLiteral() == identifer)
 | |
|                 return mode;
 | |
|         }
 | |
| 
 | |
|         error(Misc::StringUtils::format("unrecognized blend function '%s'", std::string{ asLiteral() }));
 | |
|     }
 | |
| 
 | |
|     template <class SrcT, class T>
 | |
|     void Technique::parseWidgetType(Types::Uniform<SrcT>& uniform)
 | |
|     {
 | |
|         expect<Lexer::Literal>();
 | |
| 
 | |
|         if (asLiteral() == "choice")
 | |
|         {
 | |
|             /* Example usage
 | |
| 
 | |
|                widget_type = choice(
 | |
|                 "Option A": <T>,
 | |
|                 "Option B": <T>,
 | |
|                 "Option C": <T>
 | |
|                );
 | |
| 
 | |
|             */
 | |
|             expect<Lexer::Open_Parenthesis>();
 | |
| 
 | |
|             std::vector<fx::Types::Choice<SrcT>> choices;
 | |
| 
 | |
|             while (!isNext<Lexer::Eof>())
 | |
|             {
 | |
|                 fx::Types::Choice<SrcT> choice;
 | |
|                 choice.mLabel = parseString();
 | |
|                 expect<Lexer::Equal>();
 | |
|                 choice.mValue = getUniformValue<SrcT, T>();
 | |
|                 choices.push_back(choice);
 | |
| 
 | |
|                 if (isNext<Lexer::Comma>())
 | |
|                 {
 | |
|                     mToken = mLexer->next();
 | |
| 
 | |
|                     // Handle leading comma
 | |
|                     if (isNext<Lexer::Close_Parenthesis>())
 | |
|                     {
 | |
|                         break;
 | |
|                     }
 | |
| 
 | |
|                     continue;
 | |
|                 }
 | |
| 
 | |
|                 break;
 | |
|             }
 | |
| 
 | |
|             uniform.mChoices = std::move(choices);
 | |
| 
 | |
|             expect<Lexer::Close_Parenthesis>();
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             error(Misc::StringUtils::format("unrecognized widget type '%s'", std::string{ asLiteral() }));
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     bool Technique::parseBool()
 | |
|     {
 | |
|         mToken = mLexer->next();
 | |
| 
 | |
|         if (std::holds_alternative<Lexer::True>(mToken))
 | |
|             return true;
 | |
|         if (std::holds_alternative<Lexer::False>(mToken))
 | |
|             return false;
 | |
| 
 | |
|         error("expected 'true' or 'false' as boolean value");
 | |
|     }
 | |
| 
 | |
|     std::string_view Technique::parseString()
 | |
|     {
 | |
|         expect<Lexer::String>();
 | |
| 
 | |
|         return std::get<Lexer::String>(mToken).value;
 | |
|     }
 | |
| 
 | |
|     float Technique::parseFloat()
 | |
|     {
 | |
|         mToken = mLexer->next();
 | |
| 
 | |
|         if (std::holds_alternative<Lexer::Float>(mToken))
 | |
|             return std::get<Lexer::Float>(mToken).value;
 | |
|         if (std::holds_alternative<Lexer::Integer>(mToken))
 | |
|             return static_cast<float>(std::get<Lexer::Integer>(mToken).value);
 | |
| 
 | |
|         error("expected float value");
 | |
|     }
 | |
| 
 | |
|     int Technique::parseInteger()
 | |
|     {
 | |
|         expect<Lexer::Integer>();
 | |
| 
 | |
|         return std::get<Lexer::Integer>(mToken).value;
 | |
|     }
 | |
| 
 | |
|     template <class OSGVec, class T>
 | |
|     OSGVec Technique::parseVec()
 | |
|     {
 | |
|         expect<T>();
 | |
|         expect<Lexer::Open_Parenthesis>();
 | |
| 
 | |
|         OSGVec value;
 | |
| 
 | |
|         for (int i = 0; i < OSGVec::num_components; ++i)
 | |
|         {
 | |
|             value[i] = parseFloat();
 | |
| 
 | |
|             if (i < OSGVec::num_components - 1)
 | |
|                 expect<Lexer::Comma>();
 | |
|         }
 | |
| 
 | |
|         expect<Lexer::Close_Parenthesis>("check definition of the vector");
 | |
| 
 | |
|         return value;
 | |
|     }
 | |
| }
 |