From 7d09352e94b2ccf62ab6bdeb09ed1b68195426b4 Mon Sep 17 00:00:00 2001 From: Erin Maus Date: Wed, 22 Jul 2026 13:54:59 -0400 Subject: [PATCH 1/6] 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 Date: Thu, 23 Jul 2026 05:48:40 -0400 Subject: [PATCH 2/6] Add new Graphics::Features. --- src/modules/graphics/Graphics.cpp | 3 +++ src/modules/graphics/Graphics.h | 3 +++ src/modules/graphics/Shader.cpp | 29 ++++++++++++++++++++++-- src/modules/graphics/metal/Graphics.mm | 18 ++++++++++++++- src/modules/graphics/opengl/Graphics.cpp | 5 +++- src/modules/graphics/vulkan/Graphics.cpp | 7 +++++- 6 files changed, 60 insertions(+), 5 deletions(-) diff --git a/src/modules/graphics/Graphics.cpp b/src/modules/graphics/Graphics.cpp index 17aeb4574..870eb8a7f 100644 --- a/src/modules/graphics/Graphics.cpp +++ b/src/modules/graphics/Graphics.cpp @@ -2988,6 +2988,9 @@ STRINGMAP_CLASS_BEGIN(Graphics, Graphics::Feature, Graphics::FEATURE_MAX_ENUM, f { "texelbuffer", Graphics::FEATURE_TEXEL_BUFFER }, { "copytexturetobuffer", Graphics::FEATURE_COPY_TEXTURE_TO_BUFFER }, { "indirectdraw", Graphics::FEATURE_INDIRECT_DRAW }, + { "vertexwrite", Graphics::FEATURE_VERTEX_WRITE }, + { "pixelwrite", Graphics::FEATURE_PIXEL_WRITE }, + { "imageatomics", Graphics::FEATURE_IMAGE_ATOMICS }, } STRINGMAP_CLASS_END(Graphics, Graphics::Feature, Graphics::FEATURE_MAX_ENUM, feature) diff --git a/src/modules/graphics/Graphics.h b/src/modules/graphics/Graphics.h index 0e44c3356..83c1abbf6 100644 --- a/src/modules/graphics/Graphics.h +++ b/src/modules/graphics/Graphics.h @@ -163,6 +163,9 @@ public: FEATURE_TEXEL_BUFFER, FEATURE_COPY_TEXTURE_TO_BUFFER, FEATURE_INDIRECT_DRAW, + FEATURE_VERTEX_WRITE, + FEATURE_PIXEL_WRITE, + FEATURE_IMAGE_ATOMICS, FEATURE_MAX_ENUM }; diff --git a/src/modules/graphics/Shader.cpp b/src/modules/graphics/Shader.cpp index b222b8a7f..90d286e27 100644 --- a/src/modules/graphics/Shader.cpp +++ b/src/modules/graphics/Shader.cpp @@ -1307,6 +1307,7 @@ bool Shader::validateInternal(StrongRef stages[], std::string &err, reflection.textureCount = 0; reflection.bufferCount = 0; + auto &capabilities = Module::getInstance(Module::M_GRAPHICS)->getCapabilities(); for (int i = 0; i < program.getNumUniformVariables(); i++) { const glslang::TObjectReflection &info = program.getUniform(i); @@ -1349,12 +1350,24 @@ bool Shader::validateInternal(StrongRef stages[], std::string &err, } else if (type->isImage()) { - if ((info.stages & (~EShLangComputeMask)) != 0 && !options.features[FEATURE_STORAGE_TEXTURES]) + if ((info.stages & (~EShLangComputeMask)) != 0 && !options.features[FEATURE_WRITE]) { err = "Shader validation error:\nStorage Texture uniform variables (image2D, etc) are only allowed in compute shaders unless explicitly enabled."; return false; } + if ((!qualifiers.isReadOnly() || qualifiers.isWriteOnly()) && ((info.stages & (~EShLangFragmentMask)) != 0) && !capabilities.features[Graphics::FEATURE_PIXEL_WRITE]) + { + err = "Shader validation error:\nPlatform does not have writable Storage Texture uniform variables (image2D, etc) capabilities in pixel shaders."; + return false; + } + + if ((!qualifiers.isReadOnly() || qualifiers.isWriteOnly()) && ((info.stages & (~EShLangVertexMask)) != 0) && !capabilities.features[Graphics::FEATURE_VERTEX_WRITE]) + { + err = "Shader validation error:\nPlatform does not have writable Storage Texture uniform variables (image2D, etc) capabilities in vertex shaders."; + return false; + } + if (!qualifiers.hasFormat()) { err = "Shader validation error:\nStorage Texture '" + u.name + "' must have an explicit format set in its layout declaration."; @@ -1467,11 +1480,23 @@ bool Shader::validateInternal(StrongRef stages[], std::string &err, { const glslang::TQualifier &qualifiers = type->getQualifier(); - if ((!qualifiers.isReadOnly() || qualifiers.isWriteOnly()) && ((info.stages & (~EShLangComputeMask)) != 0) && !options.features[FEATURE_WRITABLE_BUFFERS]) + if ((!qualifiers.isReadOnly() || qualifiers.isWriteOnly()) && ((info.stages & (~EShLangComputeMask)) != 0) && !options.features[FEATURE_WRITE]) { err = "Shader validation error:\nStorage Buffer block '" + info.name + "' must be marked as readonly in vertex and pixel shaders unless explicitly enabled."; return false; } + + if ((!qualifiers.isReadOnly() || qualifiers.isWriteOnly()) && ((info.stages & (~EShLangFragmentMask)) != 0) && !capabilities.features[Graphics::FEATURE_PIXEL_WRITE]) + { + err = "Shader validation error:\nPlatform does not have writable Storage Buffer blocks capabilities in pixel shaders."; + return false; + } + + if ((!qualifiers.isReadOnly() || qualifiers.isWriteOnly()) && ((info.stages & (~EShLangVertexMask)) != 0) && !capabilities.features[Graphics::FEATURE_VERTEX_WRITE]) + { + err = "Shader validation error:\nPlatform does not have writable Storage Buffer blocks capabilities in vertex shaders."; + return false; + } if (qualifiers.layoutPacking != glslang::ElpStd430) { diff --git a/src/modules/graphics/metal/Graphics.mm b/src/modules/graphics/metal/Graphics.mm index c0b8cdcc3..338ed5b6f 100644 --- a/src/modules/graphics/metal/Graphics.mm +++ b/src/modules/graphics/metal/Graphics.mm @@ -2250,8 +2250,24 @@ void Graphics::initCapabilities() capabilities.features[FEATURE_INDIRECT_DRAW] = true; else capabilities.features[FEATURE_INDIRECT_DRAW] = false; + + // Apple 3 devices support read/write to buffers in functions, while Apple 4 supports read/write to images. + // So let's err on the safe side and check support for Apple 4. + if (families.apple[4]) + { + capabilities.features[FEATURE_VERTEX_WRITE] = true; + capabilities.features[FEATURE_PIXEL_WRITE] = true; + } + else + { + capabilities.features[FEATURE_VERTEX_WRITE] = false; + capabilities.features[FEATURE_PIXEL_WRITE] = false; + } + + // Not supported until Metal 3 + capabilities.features[FEATURE_IMAGE_ATOMICS] = false; - static_assert(FEATURE_MAX_ENUM == 13, "Graphics::initCapabilities must be updated when adding a new graphics feature!"); + static_assert(FEATURE_MAX_ENUM == 16, "Graphics::initCapabilities must be updated when adding a new graphics feature!"); // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf capabilities.limits[LIMIT_POINT_SIZE] = 511; diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index 464b7b869..482af8e45 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/src/modules/graphics/opengl/Graphics.cpp @@ -1632,7 +1632,10 @@ void Graphics::initCapabilities() capabilities.features[FEATURE_TEXEL_BUFFER] = gl.isBufferUsageSupported(BUFFERUSAGE_TEXEL); capabilities.features[FEATURE_COPY_TEXTURE_TO_BUFFER] = gl.isCopyTextureToBufferSupported(); capabilities.features[FEATURE_INDIRECT_DRAW] = capabilities.features[FEATURE_GLSL4]; - static_assert(FEATURE_MAX_ENUM == 13, "Graphics::initCapabilities must be updated when adding a new graphics feature!"); + capabilities.features[FEATURE_VERTEX_WRITE] = true; + capabilities.features[FEATURE_PIXEL_WRITE] = true; + capabilities.features[FEATURE_IMAGE_ATOMICS] = true; + static_assert(FEATURE_MAX_ENUM == 16, "Graphics::initCapabilities must be updated when adding a new graphics feature!"); capabilities.limits[LIMIT_POINT_SIZE] = gl.getMaxPointSize(); capabilities.limits[LIMIT_TEXTURE_SIZE] = gl.getMax2DTextureSize(); diff --git a/src/modules/graphics/vulkan/Graphics.cpp b/src/modules/graphics/vulkan/Graphics.cpp index ec977f90e..b59ca081b 100644 --- a/src/modules/graphics/vulkan/Graphics.cpp +++ b/src/modules/graphics/vulkan/Graphics.cpp @@ -821,6 +821,8 @@ bool Graphics::setMode(void *context, const BackbufferSettings &settings) void Graphics::initCapabilities() { + VkPhysicalDeviceFeatures features; + vkGetPhysicalDeviceFeatures(physicalDevice, &features); capabilities.features[FEATURE_MULTI_RENDER_TARGET_FORMATS] = true; capabilities.features[FEATURE_CLAMP_ZERO] = true; capabilities.features[FEATURE_CLAMP_ONE] = true; @@ -834,7 +836,10 @@ void Graphics::initCapabilities() capabilities.features[FEATURE_TEXEL_BUFFER] = true; capabilities.features[FEATURE_COPY_TEXTURE_TO_BUFFER] = true; capabilities.features[FEATURE_INDIRECT_DRAW] = true; - static_assert(FEATURE_MAX_ENUM == 13, "Graphics::initCapabilities must be updated when adding a new graphics feature!"); + capabilities.features[FEATURE_VERTEX_WRITE] = features.vertexPipelineStoresAndAtomics; + capabilities.features[FEATURE_PIXEL_WRITE] = features.fragmentStoresAndAtomics; + capabilities.features[FEATURE_IMAGE_ATOMICS] = true; + static_assert(FEATURE_MAX_ENUM == 16, "Graphics::initCapabilities must be updated when adding a new graphics feature!"); VkPhysicalDeviceProperties properties; vkGetPhysicalDeviceProperties(physicalDevice, &properties); From 64118835658ed466973655d481bf988f96086137 Mon Sep 17 00:00:00 2001 From: Erin Maus Date: Thu, 23 Jul 2026 05:55:47 -0400 Subject: [PATCH 3/6] Add tests for new relaxed storage restrictions. --- src/modules/graphics/opengl/Graphics.cpp | 6 +-- testing/tests/graphics.lua | 66 ++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index 482af8e45..b5ce4cd2d 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/src/modules/graphics/opengl/Graphics.cpp @@ -1632,9 +1632,9 @@ void Graphics::initCapabilities() capabilities.features[FEATURE_TEXEL_BUFFER] = gl.isBufferUsageSupported(BUFFERUSAGE_TEXEL); capabilities.features[FEATURE_COPY_TEXTURE_TO_BUFFER] = gl.isCopyTextureToBufferSupported(); capabilities.features[FEATURE_INDIRECT_DRAW] = capabilities.features[FEATURE_GLSL4]; - capabilities.features[FEATURE_VERTEX_WRITE] = true; - capabilities.features[FEATURE_PIXEL_WRITE] = true; - capabilities.features[FEATURE_IMAGE_ATOMICS] = true; + capabilities.features[FEATURE_VERTEX_WRITE] = capabilities.features[FEATURE_GLSL4]; + capabilities.features[FEATURE_PIXEL_WRITE] = capabilities.features[FEATURE_GLSL4]; + capabilities.features[FEATURE_IMAGE_ATOMICS] = capabilities.features[FEATURE_GLSL4]; static_assert(FEATURE_MAX_ENUM == 16, "Graphics::initCapabilities must be updated when adding a new graphics feature!"); capabilities.limits[LIMIT_POINT_SIZE] = gl.getMaxPointSize(); diff --git a/testing/tests/graphics.lua b/testing/tests/graphics.lua index 6f19f1581..d1621c956 100644 --- a/testing/tests/graphics.lua +++ b/testing/tests/graphics.lua @@ -1003,6 +1003,72 @@ love.test.graphics.Shader = function(test) else test:assertTrue(true, "skip shader IO test") end + + if love.graphics.getSupported().glsl4 and love.graphics.getSupported().vertexwrite and love.graphics.getSupported().pixelwrite then + local success, message = pcall(love.graphics.newShader, [[ + #pragma language glsl4 + + buffer ColorBuffer + { + vec4 colors[]; + }; + + void effect() + { + colors[0] = VaryingColor; + love_PixelColor = colors[0]; + } + ]]) + + test:assertFalse(success, "shader should not compile") + test:assertEquals("Shader validation error:\nStorage Buffer block 'ColorBuffer' must be marked as readonly in vertex and pixel shaders unless explicitly enabled.", message) + + success, message = pcall(love.graphics.newShader, [[ + #pragma language glsl4 + + buffer ColorBuffer + { + vec4 colors[]; + }; + + void effect() + { + colors[0] = VaryingColor; + love_PixelColor = colors[0]; + } + ]], { write = true }) + + test:assertTrue(success, "shader should compile") + + success, message = pcall(love.graphics.newShader, [[ + #pragma language glsl4 + + layout(rgba32f) uniform image2D RGBAImage; + + void effect() + { + love_PixelColor = VaryingColor * imageLoad(RGBAImage, ivec2(gl_FragCoord.xy)); + } + ]]) + + test:assertFalse(success, "shader should not compile") + test:assertEquals("Shader validation error:\nStorage Texture uniform variables (image2D, etc) are only allowed in compute shaders unless explicitly enabled.", message) + + success, message = pcall(love.graphics.newShader, [[ + #pragma language glsl4 + + layout(rgba32f) uniform image2D RGBAImage; + + void effect() + { + love_PixelColor = VaryingColor * imageLoad(RGBAImage, ivec2(gl_FragCoord.xy)); + } + ]], { write = true }) + + test:assertTrue(success, "shader should compile") + else + test:assertTrue(true, "skip feature test") + end end From efa18664a05995d632d11b5fadb93473eeed6e53 Mon Sep 17 00:00:00 2001 From: Erin Maus Date: Thu, 23 Jul 2026 12:24:47 -0400 Subject: [PATCH 4/6] Fix issues with OpenGL ES. --- testing/tests/graphics.lua | 62 ++++++++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 22 deletions(-) diff --git a/testing/tests/graphics.lua b/testing/tests/graphics.lua index d1621c956..fb6f01715 100644 --- a/testing/tests/graphics.lua +++ b/testing/tests/graphics.lua @@ -1005,9 +1005,13 @@ love.test.graphics.Shader = function(test) end if love.graphics.getSupported().glsl4 and love.graphics.getSupported().vertexwrite and love.graphics.getSupported().pixelwrite then - local success, message = pcall(love.graphics.newShader, [[ + local name = love.graphics.getRendererInfo() + local isGLES = name:match("OpenGL ES") ~= nil + + local success, message = love.graphics.validateShader(isGLES, [[ #pragma language glsl4 + #ifdef PIXEL buffer ColorBuffer { vec4 colors[]; @@ -1018,12 +1022,20 @@ love.test.graphics.Shader = function(test) colors[0] = VaryingColor; love_PixelColor = colors[0]; } + #endif + + #ifdef VERTEX + vec4 position(mat4 m, vec4 p) + { + return m * p; + } + #endif ]]) - test:assertFalse(success, "shader should not compile") + test:assertFalse(success, "shader should not validate (SSBO)") test:assertEquals("Shader validation error:\nStorage Buffer block 'ColorBuffer' must be marked as readonly in vertex and pixel shaders unless explicitly enabled.", message) - success, message = pcall(love.graphics.newShader, [[ + success, message = love.graphics.validateShader(isGLES, [[ #pragma language glsl4 buffer ColorBuffer @@ -1038,34 +1050,40 @@ love.test.graphics.Shader = function(test) } ]], { write = true }) - test:assertTrue(success, "shader should compile") + test:assertTrue(success, "shader should validate (SSBO)") + test:assertEquals(nil, message) - success, message = pcall(love.graphics.newShader, [[ - #pragma language glsl4 - - layout(rgba32f) uniform image2D RGBAImage; - - void effect() - { - love_PixelColor = VaryingColor * imageLoad(RGBAImage, ivec2(gl_FragCoord.xy)); - } - ]]) - - test:assertFalse(success, "shader should not compile") - test:assertEquals("Shader validation error:\nStorage Texture uniform variables (image2D, etc) are only allowed in compute shaders unless explicitly enabled.", message) - - success, message = pcall(love.graphics.newShader, [[ + success, message = love.graphics.validateShader(isGLES, [[ #pragma language glsl4 - layout(rgba32f) uniform image2D RGBAImage; + layout(rgba32f) uniform readonly highp image2D RGBAImage; void effect() { love_PixelColor = VaryingColor * imageLoad(RGBAImage, ivec2(gl_FragCoord.xy)); } - ]], { write = true }) + ]]) + + test:assertFalse(success, "shader should not validate (image)") + test:assertEquals("Shader validation error:\nStorage Texture uniform variables (image2D, etc) are only allowed in compute shaders unless explicitly enabled.", message) + + success, message = love.graphics.validateShader(isGLES, [[ + #pragma language glsl4 - test:assertTrue(success, "shader should compile") + #ifdef GL_ES + precision highp float; + #endif + + layout(rgba32f) uniform readonly highp image2D RGBAImage; + + void effect() + { + love_PixelColor = VaryingColor * imageLoad(RGBAImage, ivec2(gl_FragCoord.xy)); + } + ]], { write = true }) + + test:assertTrue(success, "shader should validate (image)") + test:assertEquals(nil, message) else test:assertTrue(true, "skip feature test") end From d8430c39df979a29082e93c4814baa0b53997c51 Mon Sep 17 00:00:00 2001 From: Erin Maus Date: Fri, 24 Jul 2026 08:06:52 -0400 Subject: [PATCH 5/6] Respond to PR feedback. --- src/modules/graphics/Graphics.cpp | 1 - src/modules/graphics/Graphics.h | 1 - src/modules/graphics/metal/Graphics.mm | 5 +---- src/modules/graphics/opengl/Graphics.cpp | 3 +-- src/modules/graphics/vulkan/Graphics.cpp | 3 +-- 5 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/modules/graphics/Graphics.cpp b/src/modules/graphics/Graphics.cpp index 870eb8a7f..50930f11c 100644 --- a/src/modules/graphics/Graphics.cpp +++ b/src/modules/graphics/Graphics.cpp @@ -2990,7 +2990,6 @@ STRINGMAP_CLASS_BEGIN(Graphics, Graphics::Feature, Graphics::FEATURE_MAX_ENUM, f { "indirectdraw", Graphics::FEATURE_INDIRECT_DRAW }, { "vertexwrite", Graphics::FEATURE_VERTEX_WRITE }, { "pixelwrite", Graphics::FEATURE_PIXEL_WRITE }, - { "imageatomics", Graphics::FEATURE_IMAGE_ATOMICS }, } STRINGMAP_CLASS_END(Graphics, Graphics::Feature, Graphics::FEATURE_MAX_ENUM, feature) diff --git a/src/modules/graphics/Graphics.h b/src/modules/graphics/Graphics.h index 83c1abbf6..0ef90b8b3 100644 --- a/src/modules/graphics/Graphics.h +++ b/src/modules/graphics/Graphics.h @@ -165,7 +165,6 @@ public: FEATURE_INDIRECT_DRAW, FEATURE_VERTEX_WRITE, FEATURE_PIXEL_WRITE, - FEATURE_IMAGE_ATOMICS, FEATURE_MAX_ENUM }; diff --git a/src/modules/graphics/metal/Graphics.mm b/src/modules/graphics/metal/Graphics.mm index 338ed5b6f..fdbeefbe3 100644 --- a/src/modules/graphics/metal/Graphics.mm +++ b/src/modules/graphics/metal/Graphics.mm @@ -2263,11 +2263,8 @@ void Graphics::initCapabilities() capabilities.features[FEATURE_VERTEX_WRITE] = false; capabilities.features[FEATURE_PIXEL_WRITE] = false; } - - // Not supported until Metal 3 - capabilities.features[FEATURE_IMAGE_ATOMICS] = false; - static_assert(FEATURE_MAX_ENUM == 16, "Graphics::initCapabilities must be updated when adding a new graphics feature!"); + static_assert(FEATURE_MAX_ENUM == 15, "Graphics::initCapabilities must be updated when adding a new graphics feature!"); // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf capabilities.limits[LIMIT_POINT_SIZE] = 511; diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index b5ce4cd2d..4baa7b9e3 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/src/modules/graphics/opengl/Graphics.cpp @@ -1634,8 +1634,7 @@ void Graphics::initCapabilities() capabilities.features[FEATURE_INDIRECT_DRAW] = capabilities.features[FEATURE_GLSL4]; capabilities.features[FEATURE_VERTEX_WRITE] = capabilities.features[FEATURE_GLSL4]; capabilities.features[FEATURE_PIXEL_WRITE] = capabilities.features[FEATURE_GLSL4]; - capabilities.features[FEATURE_IMAGE_ATOMICS] = capabilities.features[FEATURE_GLSL4]; - static_assert(FEATURE_MAX_ENUM == 16, "Graphics::initCapabilities must be updated when adding a new graphics feature!"); + static_assert(FEATURE_MAX_ENUM == 15, "Graphics::initCapabilities must be updated when adding a new graphics feature!"); capabilities.limits[LIMIT_POINT_SIZE] = gl.getMaxPointSize(); capabilities.limits[LIMIT_TEXTURE_SIZE] = gl.getMax2DTextureSize(); diff --git a/src/modules/graphics/vulkan/Graphics.cpp b/src/modules/graphics/vulkan/Graphics.cpp index b59ca081b..854832de0 100644 --- a/src/modules/graphics/vulkan/Graphics.cpp +++ b/src/modules/graphics/vulkan/Graphics.cpp @@ -838,8 +838,7 @@ void Graphics::initCapabilities() capabilities.features[FEATURE_INDIRECT_DRAW] = true; capabilities.features[FEATURE_VERTEX_WRITE] = features.vertexPipelineStoresAndAtomics; capabilities.features[FEATURE_PIXEL_WRITE] = features.fragmentStoresAndAtomics; - capabilities.features[FEATURE_IMAGE_ATOMICS] = true; - static_assert(FEATURE_MAX_ENUM == 16, "Graphics::initCapabilities must be updated when adding a new graphics feature!"); + static_assert(FEATURE_MAX_ENUM == 15, "Graphics::initCapabilities must be updated when adding a new graphics feature!"); VkPhysicalDeviceProperties properties; vkGetPhysicalDeviceProperties(physicalDevice, &properties); From f7a7086604407ae7eafa7ebd594ede0b56e8b408 Mon Sep 17 00:00:00 2001 From: Erin Maus Date: Wed, 29 Jul 2026 14:52:03 -0400 Subject: [PATCH 6/6] Don't draw if not all writable images and/or buffers are set. --- src/modules/graphics/metal/Graphics.h | 2 +- src/modules/graphics/metal/Graphics.mm | 38 ++++++++++++++++++++--- src/modules/graphics/opengl/Graphics.cpp | 11 +++++-- src/modules/graphics/vulkan/Graphics.cpp | 39 +++++++++++++++++++----- src/modules/graphics/vulkan/Graphics.h | 3 +- 5 files changed, 77 insertions(+), 16 deletions(-) diff --git a/src/modules/graphics/metal/Graphics.h b/src/modules/graphics/metal/Graphics.h index c57684b6c..c7331ba37 100644 --- a/src/modules/graphics/metal/Graphics.h +++ b/src/modules/graphics/metal/Graphics.h @@ -210,7 +210,7 @@ private: id getCachedDepthStencilState(const DepthState &depth, const StencilState &stencil); void applyRenderState(id renderEncoder, VertexAttributesID attributesID); bool applyShaderUniforms(id encoder, love::graphics::Shader *shader); - void applyShaderUniforms(id renderEncoder, love::graphics::Shader *shader, Texture *maintex); + bool applyShaderUniforms(id renderEncoder, love::graphics::Shader *shader, Texture *maintex); id commandQueue; diff --git a/src/modules/graphics/metal/Graphics.mm b/src/modules/graphics/metal/Graphics.mm index fdbeefbe3..23dde4676 100644 --- a/src/modules/graphics/metal/Graphics.mm +++ b/src/modules/graphics/metal/Graphics.mm @@ -1105,7 +1105,7 @@ bool Graphics::applyShaderUniforms(id encoder, love::g return allWritableVariablesSet; } -void Graphics::applyShaderUniforms(id renderEncoder, love::graphics::Shader *shader, love::graphics::Texture *maintex) +bool Graphics::applyShaderUniforms(id renderEncoder, love::graphics::Shader *shader, love::graphics::Texture *maintex) { Shader *s = (Shader *)shader; @@ -1168,6 +1168,8 @@ void Graphics::applyShaderUniforms(id renderEncoder, lo uniformBufferOffset += alignUp(size, alignment); + bool allWritableVariablesSet = true; + for (const Shader::TextureBinding &b : s->getTextureBindings()) { id texture = b.texture; @@ -1183,7 +1185,13 @@ void Graphics::applyShaderUniforms(id renderEncoder, lo uint8 sampindex = b.samplerStages[SHADERSTAGE_VERTEX]; if (texindex != LOVE_UINT8_MAX) + { setTexture(renderEncoder, bindings, SHADERSTAGE_VERTEX, texindex, texture); + + if ((b.access & Shader::ACCESS_WRITE) != 0 && texture == nil) + allWritableVariablesSet = false; + } + if (sampindex != LOVE_UINT8_MAX) setSampler(renderEncoder, bindings, SHADERSTAGE_VERTEX, sampindex, samplertex); @@ -1191,7 +1199,13 @@ void Graphics::applyShaderUniforms(id renderEncoder, lo sampindex = b.samplerStages[SHADERSTAGE_PIXEL]; if (texindex != LOVE_UINT8_MAX) + { setTexture(renderEncoder, bindings, SHADERSTAGE_PIXEL, texindex, texture); + + if ((b.access & Shader::ACCESS_WRITE) != 0 && texture == nil) + allWritableVariablesSet = false; + } + if (sampindex != LOVE_UINT8_MAX) setSampler(renderEncoder, bindings, SHADERSTAGE_PIXEL, sampindex, samplertex); } @@ -1200,11 +1214,24 @@ void Graphics::applyShaderUniforms(id renderEncoder, lo { uint8 index = b.stages[SHADERSTAGE_VERTEX]; if (index != LOVE_UINT8_MAX) + { setBuffer(renderEncoder, bindings, SHADERSTAGE_VERTEX, index, b.buffer, 0); + + if ((b.access & Shader::ACCESS_WRITE) != 0 && b.buffer == nil) + allWritableVariablesSet = false; + } + index = b.stages[SHADERSTAGE_PIXEL]; if (index != LOVE_UINT8_MAX) + { setBuffer(renderEncoder, bindings, SHADERSTAGE_PIXEL, index, b.buffer, 0); + + if ((b.access & Shader::ACCESS_WRITE) != 0 && b.buffer == nil) + allWritableVariablesSet = false; + } } + + return allWritableVariablesSet; } static void setVertexBuffers(id encoder, love::graphics::Shader *shader, const BufferBindings *buffers, Graphics::RenderEncoderBindings &bindings) @@ -1240,7 +1267,8 @@ void Graphics::draw(const DrawCommand &cmd) } applyRenderState(encoder, cmd.attributesID); - applyShaderUniforms(encoder, Shader::current, cmd.texture); + if (!applyShaderUniforms(encoder, Shader::current, cmd.texture)) + return; setVertexBuffers(encoder, Shader::current, cmd.buffers, renderBindings); @@ -1272,7 +1300,8 @@ void Graphics::draw(const DrawIndexedCommand &cmd) } applyRenderState(encoder, cmd.attributesID); - applyShaderUniforms(encoder, Shader::current, cmd.texture); + if (!applyShaderUniforms(encoder, Shader::current, cmd.texture)) + return; setVertexBuffers(encoder, Shader::current, cmd.buffers, renderBindings); @@ -1337,7 +1366,8 @@ void Graphics::drawQuads(int start, int count, VertexAttributesID attributesID, } applyRenderState(encoder, attributesID); - applyShaderUniforms(encoder, Shader::current, texture); + if (!applyShaderUniforms(encoder, Shader::current, texture)) + return; id ib = getMTLBuffer(quadIndexBuffer); diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index 4baa7b9e3..6e8f2ba31 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/src/modules/graphics/opengl/Graphics.cpp @@ -562,7 +562,9 @@ void Graphics::draw(const DrawCommand &cmd) GLbitfield preDrawBarriers = 0; GLbitfield postDrawBarriers = 0; - shaderBarriers((Shader *)Shader::current, preDrawBarriers, postDrawBarriers); + if (!shaderBarriers((Shader *)Shader::current, preDrawBarriers, postDrawBarriers)) + return; + if (preDrawBarriers != 0) glMemoryBarrier(preDrawBarriers); @@ -597,7 +599,9 @@ void Graphics::draw(const DrawIndexedCommand &cmd) GLbitfield preDrawBarriers = 0; GLbitfield postDrawBarriers = 0; - shaderBarriers((Shader *)Shader::current, preDrawBarriers, postDrawBarriers); + if (!shaderBarriers((Shader *)Shader::current, preDrawBarriers, postDrawBarriers)) + return; + if (preDrawBarriers != 0) glMemoryBarrier(preDrawBarriers); @@ -661,7 +665,8 @@ void Graphics::drawQuads(int start, int count, VertexAttributesID attributesID, GLbitfield preDrawBarriers = 0; GLbitfield postDrawBarriers = 0; - shaderBarriers((Shader *)Shader::current, preDrawBarriers, postDrawBarriers); + if (!shaderBarriers((Shader *)Shader::current, preDrawBarriers, postDrawBarriers)) + return; VertexAttributes attributes; findVertexAttributes(attributesID, attributes); diff --git a/src/modules/graphics/vulkan/Graphics.cpp b/src/modules/graphics/vulkan/Graphics.cpp index 854832de0..f2673fa6a 100644 --- a/src/modules/graphics/vulkan/Graphics.cpp +++ b/src/modules/graphics/vulkan/Graphics.cpp @@ -957,6 +957,11 @@ void Graphics::draw(const DrawCommand &cmd) { prepareDraw(cmd.attributesID, *cmd.buffers, cmd.texture, cmd.primitiveType, cmd.cullMode); + VkAccessFlags dstAccessMask; + VkPipelineStageFlags dstStageMask; + if (!prepareBarrier(dstAccessMask, dstStageMask)) + return; + if (cmd.indirectBuffer != nullptr) { vkCmdDrawIndirect( @@ -976,7 +981,7 @@ void Graphics::draw(const DrawCommand &cmd) 0); } - endDraw(); + tryBarrier(dstAccessMask, dstStageMask); drawCalls++; } @@ -984,6 +989,11 @@ void Graphics::draw(const DrawIndexedCommand &cmd) { prepareDraw(cmd.attributesID, *cmd.buffers, cmd.texture, cmd.primitiveType, cmd.cullMode); + VkAccessFlags dstAccessMask; + VkPipelineStageFlags dstStageMask; + if (!prepareBarrier(dstAccessMask, dstStageMask)) + return; + vkCmdBindIndexBuffer( commandBuffers.at(currentFrame), (VkBuffer) cmd.indexBuffer->getHandle(), @@ -1010,7 +1020,7 @@ void Graphics::draw(const DrawIndexedCommand &cmd) 0); } - endDraw(); + tryBarrier(dstAccessMask, dstStageMask); drawCalls++; } @@ -1021,6 +1031,12 @@ void Graphics::drawQuads(int start, int count, VertexAttributesID attributesID, prepareDraw(attributesID, buffers, texture, PRIMITIVE_TRIANGLES, CULL_NONE); + VkAccessFlags dstAccessMask; + VkPipelineStageFlags dstStageMask; + if (!prepareBarrier(dstAccessMask, dstStageMask)) + return; + + vkCmdBindIndexBuffer( commandBuffers.at(currentFrame), (VkBuffer)quadIndexBuffer->getHandle(), @@ -1042,7 +1058,7 @@ void Graphics::drawQuads(int start, int count, VertexAttributesID attributesID, 0); baseVertex += quadcount * 4; - endDraw(); + tryBarrier(dstAccessMask, dstStageMask); drawCalls++; } } @@ -2785,18 +2801,27 @@ void Graphics::prepareDraw(VertexAttributesID attributesID, const BufferBindings vkCmdBindVertexBuffers(commandBuffers.at(currentFrame), VERTEX_BUFFER_BINDING_START, buffercount, vkbuffers, vkoffsets); } -void Graphics::endDraw() +bool Graphics::prepareBarrier(VkAccessFlags &dstAccessMask, VkPipelineStageFlags &dstStageMask) { auto shader = dynamic_cast(Shader::current); if (!shader) + return false; + + if (!shaderBarrierFlags(shader, dstAccessMask, dstStageMask)) + return false; + + return true; +} + +void Graphics::tryBarrier(VkAccessFlags dstAccessMask, VkPipelineStageFlags dstStageMask) +{ + if (dstAccessMask == 0 && dstStageMask == 0) 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; + barrier.dstAccessMask = dstAccessMask; 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); diff --git a/src/modules/graphics/vulkan/Graphics.h b/src/modules/graphics/vulkan/Graphics.h index 6016c230c..fc7f60133 100644 --- a/src/modules/graphics/vulkan/Graphics.h +++ b/src/modules/graphics/vulkan/Graphics.h @@ -372,6 +372,8 @@ private: VertexAttributesID attributesID, const BufferBindings &buffers, graphics::Texture *texture, PrimitiveType, CullMode); + bool prepareBarrier(VkAccessFlags &dstAccessMask, VkPipelineStageFlags &dstStageMask); + void tryBarrier(VkAccessFlags dstAccessMask, VkPipelineStageFlags dstStageMask); void setRenderPass(const RenderTargets &rts, int pixelw, int pixelh); void setDefaultRenderPass(); void startRenderPass(); @@ -379,7 +381,6 @@ private: void applyScissor(); VkSampler createSampler(const SamplerState &sampler); void requestSwapchainRecreation(); - void endDraw(); VkInstance instance = VK_NULL_HANDLE; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;