From 7d09352e94b2ccf62ab6bdeb09ed1b68195426b4 Mon Sep 17 00:00:00 2001 From: Erin Maus Date: Wed, 22 Jul 2026 13:54:59 -0400 Subject: [PATCH] Relax storage restrictions on vertex/pixel shaders. --- src/modules/graphics/Graphics.cpp | 2 +- src/modules/graphics/Shader.cpp | 22 +++++++----- src/modules/graphics/Shader.h | 13 +++++-- src/modules/graphics/opengl/Graphics.cpp | 45 ++++++++++++++++++++++-- src/modules/graphics/vulkan/Graphics.cpp | 26 ++++++++++++-- src/modules/graphics/vulkan/Graphics.h | 1 + src/modules/graphics/wrap_Graphics.cpp | 14 ++++++++ 7 files changed, 106 insertions(+), 17 deletions(-) diff --git a/src/modules/graphics/Graphics.cpp b/src/modules/graphics/Graphics.cpp index ca57be5d8..17aeb4574 100644 --- a/src/modules/graphics/Graphics.cpp +++ b/src/modules/graphics/Graphics.cpp @@ -566,7 +566,7 @@ bool Graphics::validateShader(bool gles, const std::vector &stagess } } - return Shader::validate(stages, err); + return Shader::validate(stages, err, options); } Texture *Graphics::getDefaultTexture(TextureType type, DataBaseType dataType, bool depthSample) diff --git a/src/modules/graphics/Shader.cpp b/src/modules/graphics/Shader.cpp index 10800a6ba..b222b8a7f 100644 --- a/src/modules/graphics/Shader.cpp +++ b/src/modules/graphics/Shader.cpp @@ -650,7 +650,7 @@ Shader::Shader(StrongRef _stages[], const CompileOptions &options) , debugName(options.debugName) { std::string err; - if (!validateInternal(_stages, err, reflection)) + if (!validateInternal(_stages, err, reflection, options)) throw love::Exception("%s", err.c_str()); std::vector unsetVertexInputLocations; @@ -1029,10 +1029,10 @@ bool Shader::isUsingDeprecatedTextureUniform() const return it != reflection.allUniforms.end() && it->second->stageMask != 0; } -bool Shader::validate(StrongRef stages[], std::string& err) +bool Shader::validate(StrongRef stages[], std::string& err, const CompileOptions &options) { Reflection reflection; - return validateInternal(stages, err, reflection); + return validateInternal(stages, err, reflection, options); } static DataBaseType getBaseType(glslang::TBasicType basictype) @@ -1246,7 +1246,7 @@ static bool AddFieldsToFormat(std::vector &format, int return true; } -bool Shader::validateInternal(StrongRef stages[], std::string &err, Reflection &reflection) +bool Shader::validateInternal(StrongRef stages[], std::string &err, Reflection &reflection, const CompileOptions &options) { glslang::TProgram program; @@ -1349,9 +1349,9 @@ bool Shader::validateInternal(StrongRef stages[], std::string &err, } else if (type->isImage()) { - if ((info.stages & (~EShLangComputeMask)) != 0) + if ((info.stages & (~EShLangComputeMask)) != 0 && !options.features[FEATURE_STORAGE_TEXTURES]) { - err = "Shader validation error:\nStorage Texture uniform variables (image2D, etc) are only allowed in compute shaders."; + err = "Shader validation error:\nStorage Texture uniform variables (image2D, etc) are only allowed in compute shaders unless explicitly enabled."; return false; } @@ -1467,9 +1467,9 @@ bool Shader::validateInternal(StrongRef stages[], std::string &err, { const glslang::TQualifier &qualifiers = type->getQualifier(); - if ((!qualifiers.isReadOnly() || qualifiers.isWriteOnly()) && ((info.stages & (~EShLangComputeMask)) != 0)) + if ((!qualifiers.isReadOnly() || qualifiers.isWriteOnly()) && ((info.stages & (~EShLangComputeMask)) != 0) && !options.features[FEATURE_WRITABLE_BUFFERS]) { - err = "Shader validation error:\nStorage Buffer block '" + info.name + "' must be marked as readonly in vertex and pixel shaders."; + err = "Shader validation error:\nStorage Buffer block '" + info.name + "' must be marked as readonly in vertex and pixel shaders unless explicitly enabled."; return false; } @@ -1872,5 +1872,11 @@ bool Shader::getConstant(BuiltinUniform in, const char *&out) return builtinNames.find(in, out); } +STRINGMAP_CLASS_BEGIN(Shader, Shader::Feature, Shader::FEATURE_MAX_ENUM, feature) +{ + { "write", Shader::FEATURE_WRITE }, +} +STRINGMAP_CLASS_END(Shader, Shader::Feature, Shader::FEATURE_MAX_ENUM, feature) + } // graphics } // love diff --git a/src/modules/graphics/Shader.h b/src/modules/graphics/Shader.h index 96d118e94..c4cc75369 100644 --- a/src/modules/graphics/Shader.h +++ b/src/modules/graphics/Shader.h @@ -56,6 +56,12 @@ public: LANGUAGE_MAX_ENUM }; + enum Feature + { + FEATURE_WRITE, + FEATURE_MAX_ENUM + }; + // Built-in uniform variables. enum BuiltinUniform { @@ -119,6 +125,7 @@ public: { std::map defines; std::string debugName; + bool features[FEATURE_MAX_ENUM] = {}; }; struct SourceInfo @@ -275,7 +282,7 @@ public: static SourceInfo getSourceInfo(const std::string &src); 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); + static bool validate(StrongRef stages[], std::string &err, const CompileOptions &options); static bool initialize(); static void deinitialize(); @@ -288,6 +295,8 @@ public: static bool getConstant(const char *in, BuiltinUniform &out); static bool getConstant(BuiltinUniform in, const char *&out); + STRINGMAP_CLASS_DECLARE(Feature); + protected: struct Reflection @@ -330,7 +339,7 @@ protected: static std::string canonicaliizeUniformName(const std::string &name); static size_t getUniformDataSizePacked(const UniformInfo &u); - static bool validateInternal(StrongRef stages[], std::string& err, Reflection &reflection); + static bool validateInternal(StrongRef stages[], std::string& err, Reflection &reflection, const CompileOptions &options); static DataBaseType getDataBaseType(PixelFormat format); static bool isResourceBaseTypeCompatible(DataBaseType a, DataBaseType b); diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index c66d137d1..464b7b869 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/src/modules/graphics/opengl/Graphics.cpp @@ -444,7 +444,7 @@ void Graphics::setActive(bool enable) active = enable; } -static bool computeDispatchBarriers(Shader *shader, GLbitfield &preDispatchBarriers, GLbitfield &postDispatchBarriers) +static bool shaderBarriers(Shader *shader, GLbitfield &preDispatchBarriers, GLbitfield &postDispatchBarriers) { for (auto buffer : shader->getActiveWritableStorageBuffers()) { @@ -505,7 +505,7 @@ bool Graphics::dispatch(love::graphics::Shader *s, int x, int y, int z) GLbitfield preDispatchBarriers = 0; GLbitfield postDispatchBarriers = 0; - if (!computeDispatchBarriers(shader, preDispatchBarriers, postDispatchBarriers)) + if (!shaderBarriers(shader, preDispatchBarriers, postDispatchBarriers)) return false; // glMemoryBarrier before dispatch to make sure non-compute-read -> @@ -534,7 +534,7 @@ bool Graphics::dispatch(love::graphics::Shader *s, love::graphics::Buffer *indir GLbitfield preDispatchBarriers = 0; GLbitfield postDispatchBarriers = 0; - if (!computeDispatchBarriers(shader, preDispatchBarriers, postDispatchBarriers)) + if (!shaderBarriers(shader, preDispatchBarriers, postDispatchBarriers)) return false; if (preDispatchBarriers != 0) @@ -559,6 +559,13 @@ void Graphics::draw(const DrawCommand &cmd) VertexAttributes attributes; findVertexAttributes(cmd.attributesID, attributes); + GLbitfield preDrawBarriers = 0; + GLbitfield postDrawBarriers = 0; + + shaderBarriers((Shader *)Shader::current, preDrawBarriers, postDrawBarriers); + if (preDrawBarriers != 0) + glMemoryBarrier(preDrawBarriers); + gl.prepareDraw(this); gl.setVertexAttributes(attributes, *cmd.buffers); gl.bindTextureToUnit(cmd.texture, 0, false); @@ -576,6 +583,9 @@ void Graphics::draw(const DrawCommand &cmd) else glDrawArrays(glprimitivetype, cmd.vertexStart, cmd.vertexCount); + if (postDrawBarriers != 0) + glMemoryBarrier(postDrawBarriers); + ++drawCalls; } @@ -584,6 +594,13 @@ void Graphics::draw(const DrawIndexedCommand &cmd) VertexAttributes attributes; findVertexAttributes(cmd.attributesID, attributes); + GLbitfield preDrawBarriers = 0; + GLbitfield postDrawBarriers = 0; + + shaderBarriers((Shader *)Shader::current, preDrawBarriers, postDrawBarriers); + if (preDrawBarriers != 0) + glMemoryBarrier(preDrawBarriers); + gl.prepareDraw(this); gl.setVertexAttributes(attributes, *cmd.buffers); gl.bindTextureToUnit(cmd.texture, 0, false); @@ -606,6 +623,9 @@ void Graphics::draw(const DrawIndexedCommand &cmd) glDrawElementsInstanced(glprimitivetype, cmd.indexCount, gldatatype, gloffset, cmd.instanceCount); else glDrawElements(glprimitivetype, cmd.indexCount, gldatatype, gloffset); + + if (postDrawBarriers != 0) + glMemoryBarrier(postDrawBarriers); ++drawCalls; } @@ -638,6 +658,11 @@ void Graphics::drawQuads(int start, int count, VertexAttributesID attributesID, const int MAX_VERTICES_PER_DRAW = LOVE_UINT16_MAX; const int MAX_QUADS_PER_DRAW = MAX_VERTICES_PER_DRAW / 4; + GLbitfield preDrawBarriers = 0; + GLbitfield postDrawBarriers = 0; + + shaderBarriers((Shader *)Shader::current, preDrawBarriers, postDrawBarriers); + VertexAttributes attributes; findVertexAttributes(attributesID, attributes); @@ -655,9 +680,16 @@ void Graphics::drawQuads(int start, int count, VertexAttributesID attributesID, for (int quadindex = 0; quadindex < count; quadindex += MAX_QUADS_PER_DRAW) { + if (preDrawBarriers != 0) + glMemoryBarrier(preDrawBarriers); + int quadcount = std::min(MAX_QUADS_PER_DRAW, count - quadindex); glDrawElementsBaseVertex(GL_TRIANGLES, quadcount * 6, GL_UNSIGNED_SHORT, BUFFER_OFFSET(0), basevertex); + + if (postDrawBarriers != 0) + glMemoryBarrier(postDrawBarriers); + ++drawCalls; basevertex += quadcount * 4; @@ -671,11 +703,18 @@ void Graphics::drawQuads(int start, int count, VertexAttributesID attributesID, for (int quadindex = 0; quadindex < count; quadindex += MAX_QUADS_PER_DRAW) { + if (preDrawBarriers != 0) + glMemoryBarrier(preDrawBarriers); + gl.setVertexAttributes(attributes, bufferscopy); int quadcount = std::min(MAX_QUADS_PER_DRAW, count - quadindex); glDrawElements(GL_TRIANGLES, quadcount * 6, GL_UNSIGNED_SHORT, BUFFER_OFFSET(0)); + + if (postDrawBarriers != 0) + glMemoryBarrier(postDrawBarriers); + ++drawCalls; if (count > MAX_QUADS_PER_DRAW) diff --git a/src/modules/graphics/vulkan/Graphics.cpp b/src/modules/graphics/vulkan/Graphics.cpp index 92f8eb0c0..ec977f90e 100644 --- a/src/modules/graphics/vulkan/Graphics.cpp +++ b/src/modules/graphics/vulkan/Graphics.cpp @@ -972,6 +972,7 @@ void Graphics::draw(const DrawCommand &cmd) 0); } + endDraw(); drawCalls++; } @@ -1005,6 +1006,7 @@ void Graphics::draw(const DrawIndexedCommand &cmd) 0); } + endDraw(); drawCalls++; } @@ -1036,6 +1038,7 @@ void Graphics::drawQuads(int start, int count, VertexAttributesID attributesID, 0); baseVertex += quadcount * 4; + endDraw(); drawCalls++; } } @@ -1281,7 +1284,7 @@ graphics::StreamBuffer *Graphics::newStreamBuffer(BufferUsage type, size_t size) return new StreamBuffer(this, type, size); } -static bool computeDispatchBarrierFlags(Shader *shader, VkAccessFlags &dstAccessFlags, VkPipelineStageFlags &dstStageFlags) +static bool shaderBarrierFlags(Shader *shader, VkAccessFlags &dstAccessFlags, VkPipelineStageFlags &dstStageFlags) { for (const auto &info : shader->getActiveTextureInfo()) { @@ -1323,7 +1326,7 @@ bool Graphics::dispatch(love::graphics::Shader *shader, int x, int y, int z) barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; VkPipelineStageFlags dstStageMask = 0; - if (!computeDispatchBarrierFlags(computeShader, barrier.dstAccessMask, dstStageMask)) + if (!shaderBarrierFlags(computeShader, barrier.dstAccessMask, dstStageMask)) return false; usedShadersInFrame.insert(computeShader); @@ -1352,7 +1355,7 @@ bool Graphics::dispatch(love::graphics::Shader *shader, love::graphics::Buffer * barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; VkPipelineStageFlags dstStageMask = 0; - if (!computeDispatchBarrierFlags(computeShader, barrier.dstAccessMask, dstStageMask)) + if (!shaderBarrierFlags(computeShader, barrier.dstAccessMask, dstStageMask)) return false; usedShadersInFrame.insert(computeShader); @@ -2778,6 +2781,23 @@ void Graphics::prepareDraw(VertexAttributesID attributesID, const BufferBindings vkCmdBindVertexBuffers(commandBuffers.at(currentFrame), VERTEX_BUFFER_BINDING_START, buffercount, vkbuffers, vkoffsets); } +void Graphics::endDraw() +{ + auto shader = dynamic_cast(Shader::current); + if (!shader) + return; + + VkMemoryBarrier barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; + barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; + VkPipelineStageFlags dstStageMask = 0; + if (!shaderBarrierFlags(shader, barrier.dstAccessMask, dstStageMask)) + return; + + if (barrier.dstAccessMask != 0 || dstStageMask != 0) + vkCmdPipelineBarrier(commandBuffers.at(currentFrame), VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, dstStageMask, 0, 1, &barrier, 0, nullptr, 0, nullptr); +} + void Graphics::setDefaultRenderPass() { uint32_t numClearValues = 2; diff --git a/src/modules/graphics/vulkan/Graphics.h b/src/modules/graphics/vulkan/Graphics.h index 06035ec45..6016c230c 100644 --- a/src/modules/graphics/vulkan/Graphics.h +++ b/src/modules/graphics/vulkan/Graphics.h @@ -379,6 +379,7 @@ private: void applyScissor(); VkSampler createSampler(const SamplerState &sampler); void requestSwapchainRecreation(); + void endDraw(); VkInstance instance = VK_NULL_HANDLE; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; diff --git a/src/modules/graphics/wrap_Graphics.cpp b/src/modules/graphics/wrap_Graphics.cpp index 349927c3a..1ee7d5977 100644 --- a/src/modules/graphics/wrap_Graphics.cpp +++ b/src/modules/graphics/wrap_Graphics.cpp @@ -1586,6 +1586,20 @@ static int w_getShaderSource(lua_State *L, int startidx, std::vector