mirror of https://github.com/OpenMW/openmw.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1121 lines
36 KiB
C++
1121 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 "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<int> source_format;
|
|
std::optional<int> source_type;
|
|
std::optional<int> 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<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")
|
|
{
|
|
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<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;
|
|
}
|
|
}
|