From 0fa00e0bdc2e873f1ed4269dd866bce7d2d90995 Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Sun, 20 Feb 2022 12:23:41 -0400 Subject: [PATCH] love.graphics.newShader: add a compile options table parameter. The only field currently read from the table is 'defines', which can contain either an array of define names, or name-value pairs. For example: newShader(file, {defines={"MYFEATURE_ENABLED", MYSETTING=1}}) Fixes #1577 --- src/modules/graphics/Graphics.cpp | 35 +++++++++----- src/modules/graphics/Graphics.h | 8 ++-- src/modules/graphics/Shader.cpp | 6 ++- src/modules/graphics/Shader.h | 7 ++- src/modules/graphics/metal/Graphics.mm | 3 +- src/modules/graphics/opengl/Graphics.cpp | 3 +- src/modules/graphics/wrap_Graphics.cpp | 60 ++++++++++++++++++++---- 7 files changed, 94 insertions(+), 28 deletions(-) diff --git a/src/modules/graphics/Graphics.cpp b/src/modules/graphics/Graphics.cpp index 14f839c83..94adee465 100644 --- a/src/modules/graphics/Graphics.cpp +++ b/src/modules/graphics/Graphics.cpp @@ -307,12 +307,18 @@ love::graphics::ParticleSystem *Graphics::newParticleSystem(Texture *texture, in return new ParticleSystem(texture, size); } -ShaderStage *Graphics::newShaderStage(ShaderStageType stage, const std::string &source, const Shader::SourceInfo &info) +ShaderStage *Graphics::newShaderStage(ShaderStageType stage, const std::string &source, const Shader::CompileOptions &options, const Shader::SourceInfo &info, bool cache) { ShaderStage *s = nullptr; std::string cachekey; - if (!source.empty()) + // Never cache if there are custom defines set... because hashing would get + // more complicated/expensive, and there shouldn't be a lot of duplicate + // shader stages with custom defines anyway. + if (!options.defines.empty()) + cache = false; + + if (cache && !source.empty()) { data::HashFunction::Value hashvalue; data::hash(data::HashFunction::FUNCTION_SHA1, source.c_str(), source.size(), hashvalue); @@ -330,16 +336,16 @@ ShaderStage *Graphics::newShaderStage(ShaderStageType stage, const std::string & if (s == nullptr) { bool glsles = usesGLSLES(); - std::string glsl = Shader::createShaderStageCode(this, stage, source, info, glsles, true); + std::string glsl = Shader::createShaderStageCode(this, stage, source, options, info, glsles, true); s = newShaderStageInternal(stage, cachekey, glsl, glsles); - if (!cachekey.empty()) + if (cache && !cachekey.empty()) cachedShaderStages[stage][cachekey] = s; } return s; } -Shader *Graphics::newShader(const std::vector &stagessource) +Shader *Graphics::newShader(const std::vector &stagessource, const Shader::CompileOptions &options) { StrongRef stages[SHADERSTAGE_MAX_ENUM] = {}; @@ -360,12 +366,12 @@ Shader *Graphics::newShader(const std::vector &stagessource) if (info.stages[i] != Shader::ENTRYPOINT_NONE) { isanystage = true; - stages[i].set(newShaderStage((ShaderStageType) i, source, info), Acquire::NORETAIN); + stages[i].set(newShaderStage((ShaderStageType) i, source, options, info, true), Acquire::NORETAIN); } } if (!isanystage) - throw love::Exception("Could not parse shader code (missing 'position' or 'effect' function?)"); + throw love::Exception("Could not parse shader code (missing shader entry point function such as 'position' or 'effect')"); } for (int i = 0; i < SHADERSTAGE_MAX_ENUM; i++) @@ -375,7 +381,8 @@ Shader *Graphics::newShader(const std::vector &stagessource) { const std::string &source = Shader::getDefaultCode(Shader::STANDARD_DEFAULT, stype); Shader::SourceInfo info = Shader::getSourceInfo(source); - stages[i].set(newShaderStage(stype, source, info), Acquire::NORETAIN); + Shader::CompileOptions opts; + stages[i].set(newShaderStage(stype, source, opts, info, true), Acquire::NORETAIN); } } @@ -383,7 +390,7 @@ Shader *Graphics::newShader(const std::vector &stagessource) return newShaderInternal(stages); } -Shader *Graphics::newComputeShader(const std::string &source) +Shader *Graphics::newComputeShader(const std::string &source, const Shader::CompileOptions &options) { Shader::SourceInfo info = Shader::getSourceInfo(source); @@ -391,7 +398,11 @@ Shader *Graphics::newComputeShader(const std::string &source) throw love::Exception("Could not parse compute shader code (missing 'computemain' function?)"); StrongRef stages[SHADERSTAGE_MAX_ENUM]; - stages[SHADERSTAGE_COMPUTE].set(newShaderStage(SHADERSTAGE_COMPUTE, source, info)); + + // Don't bother caching compute shader intermediate source, since there + // shouldn't be much reuse. + stages[SHADERSTAGE_COMPUTE].set(newShaderStage(SHADERSTAGE_COMPUTE, source, options, info, false)); + return newShaderInternal(stages); } @@ -426,7 +437,7 @@ void Graphics::cleanupCachedShaderStage(ShaderStageType type, const std::string cachedShaderStages[type].erase(hashkey); } -bool Graphics::validateShader(bool gles, const std::vector &stagessource, std::string &err) +bool Graphics::validateShader(bool gles, const std::vector &stagessource, const Shader::CompileOptions &options, std::string &err) { StrongRef stages[SHADERSTAGE_MAX_ENUM] = {}; @@ -452,7 +463,7 @@ bool Graphics::validateShader(bool gles, const std::vector &stagess if (info.stages[i] != Shader::ENTRYPOINT_NONE) { isanystage = true; - std::string glsl = Shader::createShaderStageCode(this, stype, source, info, gles, false); + std::string glsl = Shader::createShaderStageCode(this, stype, source, options, info, gles, false); stages[i].set(new ShaderStageForValidation(this, stype, glsl, gles), Acquire::NORETAIN); } } diff --git a/src/modules/graphics/Graphics.h b/src/modules/graphics/Graphics.h index d5709f642..b00d959f1 100644 --- a/src/modules/graphics/Graphics.h +++ b/src/modules/graphics/Graphics.h @@ -448,8 +448,8 @@ public: SpriteBatch *newSpriteBatch(Texture *texture, int size, BufferDataUsage usage); ParticleSystem *newParticleSystem(Texture *texture, int size); - Shader *newShader(const std::vector &stagessource); - Shader *newComputeShader(const std::string &source); + Shader *newShader(const std::vector &stagessource, const Shader::CompileOptions &options); + Shader *newComputeShader(const std::string &source, const Shader::CompileOptions &options); virtual Buffer *newBuffer(const Buffer::Settings &settings, const std::vector &format, const void *data, size_t size, size_t arraylength) = 0; virtual Buffer *newBuffer(const Buffer::Settings &settings, DataFormat format, const void *data, size_t size, size_t arraylength); @@ -460,7 +460,7 @@ public: Text *newText(Font *font, const std::vector &text = {}); - bool validateShader(bool gles, const std::vector &stages, std::string &err); + bool validateShader(bool gles, const std::vector &stages, const Shader::CompileOptions &options, std::string &err); /** * Resets the current color, background color, line style, and so forth. @@ -965,7 +965,7 @@ protected: {} }; - ShaderStage *newShaderStage(ShaderStageType stage, const std::string &source, const Shader::SourceInfo &info); + ShaderStage *newShaderStage(ShaderStageType stage, const std::string &source, const Shader::CompileOptions &options, const Shader::SourceInfo &info, bool cache); virtual ShaderStage *newShaderStageInternal(ShaderStageType stage, const std::string &cachekey, const std::string &source, bool gles) = 0; virtual Shader *newShaderInternal(StrongRef stages[SHADERSTAGE_MAX_ENUM]) = 0; virtual StreamBuffer *newStreamBuffer(BufferUsage type, size_t size) = 0; diff --git a/src/modules/graphics/Shader.cpp b/src/modules/graphics/Shader.cpp index 4d9f562bb..17a7bb463 100644 --- a/src/modules/graphics/Shader.cpp +++ b/src/modules/graphics/Shader.cpp @@ -526,7 +526,7 @@ Shader::SourceInfo Shader::getSourceInfo(const std::string &src) return info; } -std::string Shader::createShaderStageCode(Graphics *gfx, ShaderStageType stage, const std::string &code, const Shader::SourceInfo &info, bool gles, bool checksystemfeatures) +std::string Shader::createShaderStageCode(Graphics *gfx, ShaderStageType stage, const std::string &code, const CompileOptions &options, const Shader::SourceInfo &info, bool gles, bool checksystemfeatures) { if (info.language == Shader::LANGUAGE_MAX_ENUM) throw love::Exception("Invalid shader language"); @@ -574,6 +574,10 @@ std::string Shader::createShaderStageCode(Graphics *gfx, ShaderStageType stage, ss << "#define LOVE_GAMMA_CORRECT 1\n"; if (info.usesMRT) ss << "#define LOVE_MULTI_RENDER_TARGETS 1\n"; + + for (const auto &def : options.defines) + ss << "#define " + def.first + " " + def.second + "\n"; + ss << glsl::global_syntax; ss << stageinfo.header; ss << stageinfo.uniforms; diff --git a/src/modules/graphics/Shader.h b/src/modules/graphics/Shader.h index c8d0ed838..862e9f268 100644 --- a/src/modules/graphics/Shader.h +++ b/src/modules/graphics/Shader.h @@ -107,6 +107,11 @@ public: ACCESS_WRITE = (1 << 1), }; + struct CompileOptions + { + std::map defines; + }; + struct SourceInfo { Language language; @@ -236,7 +241,7 @@ public: void getLocalThreadgroupSize(int *x, int *y, int *z); static SourceInfo getSourceInfo(const std::string &src); - static std::string createShaderStageCode(Graphics *gfx, ShaderStageType stage, const std::string &code, const SourceInfo &info, bool gles, bool checksystemfeatures); + static std::string createShaderStageCode(Graphics *gfx, ShaderStageType stage, const std::string &code, const CompileOptions &options, const SourceInfo &info, bool gles, bool checksystemfeatures); static bool validate(StrongRef stages[], std::string &err); diff --git a/src/modules/graphics/metal/Graphics.mm b/src/modules/graphics/metal/Graphics.mm index baf263867..7c1d97351 100644 --- a/src/modules/graphics/metal/Graphics.mm +++ b/src/modules/graphics/metal/Graphics.mm @@ -343,9 +343,10 @@ Graphics::Graphics() if (!Shader::standardShaders[i]) { std::vector stages; + Shader::CompileOptions opts; stages.push_back(Shader::getDefaultCode(stype, SHADERSTAGE_VERTEX)); stages.push_back(Shader::getDefaultCode(stype, SHADERSTAGE_PIXEL)); - Shader::standardShaders[i] = newShader(stages); + Shader::standardShaders[i] = newShader(stages, opts); } } diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index cff45294d..ae9a23eb3 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/src/modules/graphics/opengl/Graphics.cpp @@ -425,9 +425,10 @@ bool Graphics::setMode(void */*context*/, int width, int height, int pixelwidth, if (!Shader::standardShaders[i]) { std::vector stages; + Shader::CompileOptions opts; stages.push_back(Shader::getDefaultCode(stype, SHADERSTAGE_VERTEX)); stages.push_back(Shader::getDefaultCode(stype, SHADERSTAGE_PIXEL)); - Shader::standardShaders[i] = newShader(stages); + Shader::standardShaders[i] = newShader(stages, opts); } } catch (love::Exception &) diff --git a/src/modules/graphics/wrap_Graphics.cpp b/src/modules/graphics/wrap_Graphics.cpp index 70b1de307..d919c4130 100644 --- a/src/modules/graphics/wrap_Graphics.cpp +++ b/src/modules/graphics/wrap_Graphics.cpp @@ -1347,7 +1347,7 @@ int w_newParticleSystem(lua_State *L) return 1; } -static int w_getShaderSource(lua_State *L, int startidx, std::vector &stages) +static int w_getShaderSource(lua_State *L, int startidx, std::vector &stages, Shader::CompileOptions &options) { using namespace love::filesystem; @@ -1369,7 +1369,6 @@ static int w_getShaderSource(lua_State *L, int startidx, std::vector stages; - w_getShaderSource(L, 1, stages); + Shader::CompileOptions options; + w_getShaderSource(L, 1, stages, options); bool should_error = false; try { - Shader *shader = instance()->newShader(stages); + Shader *shader = instance()->newShader(stages, options); luax_pushtype(L, shader); shader->release(); } @@ -1446,12 +1488,13 @@ int w_newShader(lua_State *L) int w_newComputeShader(lua_State* L) { std::vector stages; - w_getShaderSource(L, 1, stages); + Shader::CompileOptions options; + w_getShaderSource(L, 1, stages, options); bool should_error = false; try { - Shader *shader = instance()->newComputeShader(stages[0]); + Shader *shader = instance()->newComputeShader(stages[0], options); luax_pushtype(L, shader); shader->release(); } @@ -1476,13 +1519,14 @@ int w_validateShader(lua_State *L) bool gles = luax_checkboolean(L, 1); std::vector stages; - w_getShaderSource(L, 2, stages); + Shader::CompileOptions options; + w_getShaderSource(L, 2, stages, options); bool success = true; std::string err; try { - success = instance()->validateShader(gles, stages, err); + success = instance()->validateShader(gles, stages, options, err); } catch (love::Exception &e) {