Relax storage restrictions on vertex/pixel shaders.

This commit is contained in:
Erin Maus
2026-07-22 13:54:59 -04:00
parent 8c18d3ce8e
commit 7d09352e94
7 changed files with 106 additions and 17 deletions
+1 -1
View File
@@ -566,7 +566,7 @@ bool Graphics::validateShader(bool gles, const std::vector<std::string> &stagess
}
}
return Shader::validate(stages, err);
return Shader::validate(stages, err, options);
}
Texture *Graphics::getDefaultTexture(TextureType type, DataBaseType dataType, bool depthSample)
+14 -8
View File
@@ -650,7 +650,7 @@ Shader::Shader(StrongRef<ShaderStage> _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<std::string> unsetVertexInputLocations;
@@ -1029,10 +1029,10 @@ bool Shader::isUsingDeprecatedTextureUniform() const
return it != reflection.allUniforms.end() && it->second->stageMask != 0;
}
bool Shader::validate(StrongRef<ShaderStage> stages[], std::string& err)
bool Shader::validate(StrongRef<ShaderStage> 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<Buffer::DataDeclaration> &format, int
return true;
}
bool Shader::validateInternal(StrongRef<ShaderStage> stages[], std::string &err, Reflection &reflection)
bool Shader::validateInternal(StrongRef<ShaderStage> stages[], std::string &err, Reflection &reflection, const CompileOptions &options)
{
glslang::TProgram program;
@@ -1349,9 +1349,9 @@ bool Shader::validateInternal(StrongRef<ShaderStage> 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<ShaderStage> 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
+11 -2
View File
@@ -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<std::string, std::string> 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<ShaderStage> stages[], std::string &err);
static bool validate(StrongRef<ShaderStage> 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<ShaderStage> stages[], std::string& err, Reflection &reflection);
static bool validateInternal(StrongRef<ShaderStage> stages[], std::string& err, Reflection &reflection, const CompileOptions &options);
static DataBaseType getDataBaseType(PixelFormat format);
static bool isResourceBaseTypeCompatible(DataBaseType a, DataBaseType b);
+42 -3
View File
@@ -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)
+23 -3
View File
@@ -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 *>(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;
+1
View File
@@ -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;
+14
View File
@@ -1586,6 +1586,20 @@ static int w_getShaderSource(lua_State *L, int startidx, std::vector<std::string
if (!lua_isnoneornil(L, -1))
options.debugName = luax_checkstring(L, -1);
lua_pop(L, 1);
for (int feature = 0; feature < Shader::FEATURE_MAX_ENUM; ++feature)
{
const char *featureKey;
if (Shader::getConstant((Shader::Feature)feature, featureKey))
{
lua_getfield(L, optionsidx, featureKey);
if (!lua_isnoneornil(L, -1))
{
options.features[feature] = lua_toboolean(L, -1);
}
lua_pop(L, 1);
}
}
}
return 0;