From d9ee9a40ac48e48badfdcba2341d48ac3ba587bc Mon Sep 17 00:00:00 2001 From: Erin Maus Date: Thu, 23 Jul 2026 05:48:40 -0400 Subject: [PATCH] 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);