From a551fcd4929c8f097faa1e39699e6ea740d6175d Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Fri, 13 Aug 2021 14:43:03 -0300 Subject: [PATCH] Initial support for writing to textures in compute shaders. --- src/modules/graphics/Graphics.cpp | 2 +- src/modules/graphics/Graphics.h | 4 +- src/modules/graphics/Shader.cpp | 81 ++++++++++- src/modules/graphics/Shader.h | 9 ++ src/modules/graphics/Texture.cpp | 21 ++- src/modules/graphics/Texture.h | 6 + src/modules/graphics/opengl/Graphics.cpp | 22 ++- src/modules/graphics/opengl/Graphics.h | 6 +- src/modules/graphics/opengl/OpenGL.cpp | 48 ++++++- src/modules/graphics/opengl/Shader.cpp | 174 +++++++++++++++++++++-- src/modules/graphics/opengl/Shader.h | 14 +- src/modules/graphics/wrap_Graphics.cpp | 26 +++- src/modules/graphics/wrap_Shader.cpp | 4 +- src/modules/graphics/wrap_Texture.cpp | 16 +++ 14 files changed, 383 insertions(+), 50 deletions(-) diff --git a/src/modules/graphics/Graphics.cpp b/src/modules/graphics/Graphics.cpp index 719acaa51..6dbde4356 100644 --- a/src/modules/graphics/Graphics.cpp +++ b/src/modules/graphics/Graphics.cpp @@ -768,7 +768,7 @@ void Graphics::setRenderTargets(const RenderTargets &rts) PixelFormat dsformat = PIXELFORMAT_STENCIL8; if (wantsdepth && wantsstencil) dsformat = PIXELFORMAT_DEPTH24_UNORM_STENCIL8; - else if (wantsdepth && isPixelFormatSupported(PIXELFORMAT_DEPTH24_UNORM, true, false, false)) + else if (wantsdepth && isPixelFormatSupported(PIXELFORMAT_DEPTH24_UNORM, PIXELFORMATUSAGEFLAGS_RENDERTARGET, false)) dsformat = PIXELFORMAT_DEPTH24_UNORM; else if (wantsdepth) dsformat = PIXELFORMAT_DEPTH16_UNORM; diff --git a/src/modules/graphics/Graphics.h b/src/modules/graphics/Graphics.h index 92e6639e2..e0bc49e1d 100644 --- a/src/modules/graphics/Graphics.h +++ b/src/modules/graphics/Graphics.h @@ -787,9 +787,9 @@ public: virtual PixelFormat getSizedFormat(PixelFormat format, bool rendertarget, bool readable) const = 0; /** - * Gets whether the specified pixel format is supported. + * Gets whether the specified pixel format usage is supported. **/ - virtual bool isPixelFormatSupported(PixelFormat format, bool rendertarget, bool readable, bool sRGB = false) = 0; + virtual bool isPixelFormatSupported(PixelFormat format, PixelFormatUsageFlags usage, bool sRGB = false) = 0; /** * Gets the renderer used by love.graphics. diff --git a/src/modules/graphics/Shader.cpp b/src/modules/graphics/Shader.cpp index fee230eb2..d88a60587 100644 --- a/src/modules/graphics/Shader.cpp +++ b/src/modules/graphics/Shader.cpp @@ -731,6 +731,55 @@ bool Shader::validate(StrongRef stages[], std::string& err) return validateInternal(stages, err, reflection); } +static PixelFormat getPixelFormat(glslang::TLayoutFormat format) +{ + using namespace glslang; + + switch (format) + { + case ElfNone: return PIXELFORMAT_UNKNOWN; + case ElfRgba32f: return PIXELFORMAT_RGBA32_FLOAT; + case ElfRgba16f: return PIXELFORMAT_RGBA16_FLOAT; + case ElfR32f: return PIXELFORMAT_R32_FLOAT; + case ElfRgba8: return PIXELFORMAT_RGBA8_UNORM; + case ElfRgba8Snorm: return PIXELFORMAT_UNKNOWN; // no snorm yet + case ElfRg32f: return PIXELFORMAT_RG32_FLOAT; + case ElfRg16f: return PIXELFORMAT_RG16_FLOAT; + case ElfR11fG11fB10f: return PIXELFORMAT_RG11B10_FLOAT; + case ElfR16f: return PIXELFORMAT_R16_FLOAT; + case ElfRgba16: return PIXELFORMAT_RGBA16_UNORM; + case ElfRgb10A2: return PIXELFORMAT_RGB10A2_UNORM; + case ElfRg16: return PIXELFORMAT_RG16_UNORM; + case ElfRg8: return PIXELFORMAT_RG8_UNORM; + case ElfR8: return PIXELFORMAT_R8_UNORM; + case ElfRgba16Snorm: return PIXELFORMAT_UNKNOWN; + case ElfRg16Snorm: return PIXELFORMAT_UNKNOWN; + case ElfRg8Snorm: return PIXELFORMAT_UNKNOWN; + case ElfR16Snorm: return PIXELFORMAT_UNKNOWN; + case ElfR8Snorm: return PIXELFORMAT_UNKNOWN; + case ElfRgba32i: return PIXELFORMAT_RGBA32_INT; + case ElfRgba16i: return PIXELFORMAT_RGBA16_INT; + case ElfRgba8i: return PIXELFORMAT_RGBA8_INT; + case ElfR32i: return PIXELFORMAT_R32_INT; + case ElfRg32i: return PIXELFORMAT_RG32_INT; + case ElfRg16i: return PIXELFORMAT_RG16_INT; + case ElfRg8i: return PIXELFORMAT_RG8_INT; + case ElfR16i: return PIXELFORMAT_R16_INT; + case ElfR8i: return PIXELFORMAT_R8_INT; + case ElfRgba32ui: return PIXELFORMAT_RGBA32_UINT; + case ElfRgba16ui: return PIXELFORMAT_RGBA16_UINT; + case ElfRgba8ui: return PIXELFORMAT_RGBA8_UINT; + case ElfR32ui: return PIXELFORMAT_R32_UINT; + case ElfRg32ui: return PIXELFORMAT_RG32_UINT; + case ElfRg16ui: return PIXELFORMAT_RG16_UINT; + case ElfRgb10a2ui: return PIXELFORMAT_UNKNOWN; + case ElfRg8ui: return PIXELFORMAT_RG8_UINT; + case ElfR16ui: return PIXELFORMAT_R16_UINT; + case ElfR8ui: return PIXELFORMAT_R8_UINT; + default: return PIXELFORMAT_UNKNOWN; + } +} + bool Shader::validateInternal(StrongRef stages[], std::string &err, ValidationReflection &reflection) { glslang::TProgram program; @@ -781,12 +830,34 @@ bool Shader::validateInternal(StrongRef stages[], std::string &err, if (type == nullptr) continue; + const glslang::TQualifier &qualifiers = type->getQualifier(); + if (type->isImage()) { - // TODO: Change this check to only error if a writable image is - // declared in non-compute, once support is added for them. - err = "Shader validation error:\nImage load/store (image2D uniforms, etc.) is not supported yet."; - return false; + if ((info.stages & EShLangComputeMask) == 0) + { + err = "Shader validation error:\nStorage Texture uniform variables (image2D, etc) are only allowed in compute shaders."; + return false; + } + + if (!qualifiers.hasFormat()) + { + err = "Shader validation error:\nStorage Texture '" + info.name + "' must have an explicit format set in its layout declaration."; + return false; + } + + StorageTextureReflection texreflection = {}; + + texreflection.format = getPixelFormat(qualifiers.getFormat()); + + if (qualifiers.isReadOnly()) + texreflection.access = ACCESS_READ; + else if (qualifiers.isWriteOnly()) + texreflection.access = ACCESS_WRITE; + else + texreflection.access = (Access)(ACCESS_READ | ACCESS_WRITE); + + reflection.storageTextures[info.name] = texreflection; } } @@ -798,7 +869,7 @@ bool Shader::validateInternal(StrongRef stages[], std::string &err, { const glslang::TQualifier &qualifiers = type->getQualifier(); - if ((!qualifiers.isReadOnly() || qualifiers.isWriteOnly()) && (info.stages & (EShLangVertexMask | EShLangFragmentMask))) + if ((!qualifiers.isReadOnly() || qualifiers.isWriteOnly()) && (info.stages & EShLangComputeMask) == 0) { err = "Shader validation error:\nStorage Buffer block '" + info.name + "' must be marked as readonly in vertex and pixel shaders."; return false; diff --git a/src/modules/graphics/Shader.h b/src/modules/graphics/Shader.h index b9b497969..8163a585f 100644 --- a/src/modules/graphics/Shader.h +++ b/src/modules/graphics/Shader.h @@ -76,6 +76,7 @@ public: UNIFORM_UINT, UNIFORM_BOOL, UNIFORM_SAMPLER, + UNIFORM_STORAGETEXTURE, UNIFORM_TEXELBUFFER, UNIFORM_STORAGEBUFFER, UNIFORM_UNKNOWN, @@ -135,6 +136,7 @@ public: TextureType textureType; Access access; bool isDepthSampler; + PixelFormat storageTextureFormat; size_t bufferStride; size_t bufferMemberCount; std::string name; @@ -251,9 +253,16 @@ protected: Access access; }; + struct StorageTextureReflection + { + PixelFormat format; + Access access; + }; + struct ValidationReflection { std::map storageBuffers; + std::map storageTextures; int localThreadgroupSize[3]; bool usesPointSize; }; diff --git a/src/modules/graphics/Texture.cpp b/src/modules/graphics/Texture.cpp index cae50dd65..24292079a 100644 --- a/src/modules/graphics/Texture.cpp +++ b/src/modules/graphics/Texture.cpp @@ -163,6 +163,7 @@ Texture::Texture(const Settings &settings, const Slices *slices) : texType(settings.type) , format(settings.format) , renderTarget(settings.renderTarget) + , computeWrite(settings.computeWrite) , readable(true) , mipmapsMode(settings.mipmaps) , sRGB(isGammaCorrect() && !settings.linear) @@ -257,7 +258,15 @@ Texture::Texture(const Settings &settings, const Slices *slices) if (isCompressed() && renderTarget) throw love::Exception("Compressed textures cannot be render targets."); - if (!gfx->isPixelFormatSupported(format, renderTarget, readable, sRGB)) + uint32 usage = PIXELFORMATUSAGEFLAGS_NONE; + if (renderTarget) + usage |= PIXELFORMATUSAGEFLAGS_RENDERTARGET; + if (readable) + usage |= PIXELFORMATUSAGEFLAGS_SAMPLE; + if (computeWrite) + usage |= PIXELFORMATUSAGEFLAGS_COMPUTEWRITE; + + if (!gfx->isPixelFormatSupported(format, (PixelFormatUsageFlags) usage, sRGB)) { const char *fstr = "unknown"; love::getConstant(format, fstr); @@ -267,7 +276,9 @@ Texture::Texture(const Settings &settings, const Slices *slices) readablestr = readable ? " readable" : " non-readable"; const char *rtstr = ""; - if (renderTarget) + if (computeWrite) + rtstr = " as a compute shader-writable texture"; + else if (renderTarget) rtstr = " as a render target"; throw love::Exception("The %s%s pixel format is not supported%s on this system.", fstr, readablestr, rtstr); @@ -571,6 +582,11 @@ bool Texture::isRenderTarget() const return renderTarget; } +bool Texture::isComputeWritable() const +{ + return computeWrite; +} + bool Texture::isReadable() const { return readable; @@ -934,6 +950,7 @@ static StringMap::Entry setting { "dpiscale", Texture::SETTING_DPI_SCALE }, { "msaa", Texture::SETTING_MSAA }, { "canvas", Texture::SETTING_RENDER_TARGET }, + { "computewrite", Texture::SETTING_COMPUTE_WRITE }, { "readable", Texture::SETTING_READABLE }, }; diff --git a/src/modules/graphics/Texture.h b/src/modules/graphics/Texture.h index 69cc49eea..83b93a79a 100644 --- a/src/modules/graphics/Texture.h +++ b/src/modules/graphics/Texture.h @@ -63,6 +63,7 @@ enum PixelFormatUsage PIXELFORMATUSAGE_RENDERTARGET, PIXELFORMATUSAGE_BLEND, PIXELFORMATUSAGE_MSAA, + PIXELFORMATUSAGE_COMPUTEWRITE, PIXELFORMATUSAGE_MAX_ENUM }; @@ -74,6 +75,7 @@ enum PixelFormatUsageFlags PIXELFORMATUSAGEFLAGS_RENDERTARGET = (1 << PIXELFORMATUSAGE_RENDERTARGET), PIXELFORMATUSAGEFLAGS_BLEND = (1 << PIXELFORMATUSAGE_BLEND), PIXELFORMATUSAGEFLAGS_MSAA = (1 << PIXELFORMATUSAGE_MSAA), + PIXELFORMATUSAGEFLAGS_COMPUTEWRITE = (1 << PIXELFORMATUSAGE_COMPUTEWRITE), }; struct SamplerState @@ -169,6 +171,7 @@ public: SETTING_DPI_SCALE, SETTING_MSAA, SETTING_RENDER_TARGET, + SETTING_COMPUTE_WRITE, SETTING_READABLE, SETTING_MAX_ENUM }; @@ -186,6 +189,7 @@ public: float dpiScale = 1.0f; int msaa = 1; bool renderTarget = false; + bool computeWrite = false; OptionalBool readable; }; @@ -249,6 +253,7 @@ public: MipmapsMode getMipmapsMode() const; bool isRenderTarget() const; + bool isComputeWritable() const; bool isReadable() const; bool isCompressed() const; @@ -307,6 +312,7 @@ protected: PixelFormat format; bool renderTarget; + bool computeWrite; bool readable; MipmapsMode mipmapsMode; diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index 0fd4a6411..bfb766ceb 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/src/modules/graphics/opengl/Graphics.cpp @@ -501,9 +501,9 @@ static bool computeDispatchBarriers(Shader *shader, GLbitfield &preDispatchBarri postDispatchBarriers |= GL_PIXEL_BUFFER_BARRIER_BIT; } - for (auto texture : shader->getActiveWritableTextures()) + for (const auto &binding : shader->getStorageTextureBindings()) { - if (texture == nullptr) + if (binding.texture == nullptr) return false; preDispatchBarriers |= GL_SHADER_IMAGE_ACCESS_BARRIER_BIT; @@ -512,7 +512,7 @@ static bool computeDispatchBarriers(Shader *shader, GLbitfield &preDispatchBarri | GL_TEXTURE_UPDATE_BARRIER_BIT | GL_TEXTURE_FETCH_BARRIER_BIT; - if (texture->isRenderTarget()) + if (binding.texture->isRenderTarget()) postDispatchBarriers |= GL_FRAMEBUFFER_BARRIER_BIT; } @@ -1684,7 +1684,7 @@ PixelFormat Graphics::getSizedFormat(PixelFormat format, bool rendertarget, bool } } -bool Graphics::isPixelFormatSupported(PixelFormat format, bool rendertarget, bool readable, bool sRGB) +bool Graphics::isPixelFormatSupported(PixelFormat format, PixelFormatUsageFlags usage, bool sRGB) { if (sRGB && format == PIXELFORMAT_RGBA8_UNORM) { @@ -1692,22 +1692,20 @@ bool Graphics::isPixelFormatSupported(PixelFormat format, bool rendertarget, boo sRGB = false; } - uint32 requiredflags = 0; - if (rendertarget) - requiredflags |= PIXELFORMATUSAGEFLAGS_RENDERTARGET; - if (readable) - requiredflags |= PIXELFORMATUSAGEFLAGS_SAMPLE; + bool rendertarget = (usage & PIXELFORMATUSAGEFLAGS_RENDERTARGET) != 0; + bool readable = (usage & PIXELFORMATUSAGEFLAGS_SAMPLE) != 0; + bool computewrite = (usage & PIXELFORMATUSAGEFLAGS_COMPUTEWRITE) != 0; format = getSizedFormat(format, rendertarget, readable); - OptionalBool &supported = supportedFormats[format][rendertarget ? 1 : 0][readable ? 1 : 0][sRGB ? 1 : 0]; + OptionalBool &supported = supportedFormats[format][rendertarget ? 1 : 0][readable ? 1 : 0][computewrite ? 1 : 0][sRGB ? 1 : 0]; if (supported.hasValue) return supported.value; - auto supportedflags = OpenGL::getPixelFormatUsageFlags(format); + uint32 supportedflags = OpenGL::getPixelFormatUsageFlags(format); - if ((requiredflags & supportedflags) != requiredflags) + if ((usage & supportedflags) != usage) { supported.set(false); return supported.value; diff --git a/src/modules/graphics/opengl/Graphics.h b/src/modules/graphics/opengl/Graphics.h index b568c4ebb..1eff225b6 100644 --- a/src/modules/graphics/opengl/Graphics.h +++ b/src/modules/graphics/opengl/Graphics.h @@ -107,7 +107,7 @@ public: void setWireframe(bool enable) override; PixelFormat getSizedFormat(PixelFormat format, bool rendertarget, bool readable) const override; - bool isPixelFormatSupported(PixelFormat format, bool rendertarget, bool readable, bool sRGB = false) override; + bool isPixelFormatSupported(PixelFormat format, PixelFormatUsageFlags usage, bool sRGB = false) override; Renderer getRenderer() const override; RendererInfo getRendererInfo() const override; @@ -170,8 +170,8 @@ private: // Only needed for buffer types that can be bound to shaders. StrongRef defaultBuffers[BUFFERUSAGE_MAX_ENUM]; - // [rendertarget][readable][srgb] - OptionalBool supportedFormats[PIXELFORMAT_MAX_ENUM][2][2][2]; + // [rendertarget][readable][computewrite][srgb] + OptionalBool supportedFormats[PIXELFORMAT_MAX_ENUM][2][2][2][2]; }; // Graphics diff --git a/src/modules/graphics/opengl/OpenGL.cpp b/src/modules/graphics/opengl/OpenGL.cpp index 5bd065c0a..eeb07b501 100644 --- a/src/modules/graphics/opengl/OpenGL.cpp +++ b/src/modules/graphics/opengl/OpenGL.cpp @@ -292,7 +292,7 @@ void OpenGL::setupContext() // This can't be done in initContext with the rest of the bug checks because // isPixelFormatSupported relies on state initialized here / after init. auto gfx = Module::getInstance(Module::M_GRAPHICS); - if (GLAD_ES_VERSION_3_0 && gfx != nullptr && !gfx->isPixelFormatSupported(PIXELFORMAT_R8_UNORM, true, true)) + if (GLAD_ES_VERSION_3_0 && gfx != nullptr && !gfx->isPixelFormatSupported(PIXELFORMAT_R8_UNORM, PIXELFORMATUSAGEFLAGS_SAMPLE | PIXELFORMATUSAGEFLAGS_RENDERTARGET)) bugs.brokenR8PixelFormat = true; #endif } @@ -2085,6 +2085,7 @@ uint32 OpenGL::getPixelFormatUsageFlags(PixelFormat pixelformat) { const uint32 commonsample = PIXELFORMATUSAGEFLAGS_SAMPLE | PIXELFORMATUSAGEFLAGS_LINEAR; const uint32 commonrender = PIXELFORMATUSAGEFLAGS_RENDERTARGET | PIXELFORMATUSAGEFLAGS_BLEND | PIXELFORMATUSAGEFLAGS_MSAA; + const uint32 computewrite = PIXELFORMATUSAGEFLAGS_COMPUTEWRITE; uint32 flags = PIXELFORMATUSAGEFLAGS_NONE; @@ -2096,11 +2097,15 @@ uint32 OpenGL::getPixelFormatUsageFlags(PixelFormat pixelformat) flags |= commonsample | commonrender; else if (pixelformat == PIXELFORMAT_R8_UNORM && (GLAD_ES_VERSION_2_0 || GLAD_VERSION_1_1)) flags |= commonsample; // We'll use OpenGL's luminance format internally. + if (GLAD_VERSION_4_3) + flags |= computewrite; break; case PIXELFORMAT_RGBA8_UNORM: flags |= commonsample; if (GLAD_VERSION_1_0 || GLAD_ES_VERSION_3_0 || GLAD_OES_rgb8_rgba8 || GLAD_ARM_rgba8) flags |= commonrender; + if (GLAD_VERSION_4_3 || GLAD_ES_VERSION_3_1) + flags |= computewrite; break; case PIXELFORMAT_sRGBA8_UNORM: if (gl.bugs.brokenSRGB) @@ -2110,6 +2115,8 @@ uint32 OpenGL::getPixelFormatUsageFlags(PixelFormat pixelformat) if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0 || ((GLAD_ARB_framebuffer_sRGB || GLAD_EXT_framebuffer_sRGB) && (GLAD_VERSION_2_1 || GLAD_EXT_texture_sRGB))) flags |= commonrender; + if (GLAD_VERSION_4_3 || GLAD_ES_VERSION_3_1) + flags |= computewrite; break; case PIXELFORMAT_R16_UNORM: case PIXELFORMAT_RG16_UNORM: @@ -2117,10 +2124,14 @@ uint32 OpenGL::getPixelFormatUsageFlags(PixelFormat pixelformat) || (GLAD_VERSION_1_1 && GLAD_ARB_texture_rg) || (GLAD_EXT_texture_norm16 && (GLAD_ES_VERSION_3_0 || GLAD_EXT_texture_rg))) flags |= commonsample | commonrender; + if (GLAD_VERSION_4_3) + flags |= computewrite; break; case PIXELFORMAT_RGBA16_UNORM: if (GLAD_VERSION_1_1 || GLAD_EXT_texture_norm16) flags |= commonsample | commonrender; + if (GLAD_VERSION_4_3) + flags |= computewrite; break; case PIXELFORMAT_R16_FLOAT: case PIXELFORMAT_RG16_FLOAT: @@ -2132,6 +2143,8 @@ uint32 OpenGL::getPixelFormatUsageFlags(PixelFormat pixelformat) flags |= commonrender; if (!(GLAD_VERSION_1_1 || GLAD_ES_VERSION_3_0 || GLAD_OES_texture_half_float_linear)) flags &= ~PIXELFORMATUSAGEFLAGS_LINEAR; + if (GLAD_VERSION_4_3) + flags |= computewrite; break; case PIXELFORMAT_RGBA16_FLOAT: if (GLAD_VERSION_3_0 || (GLAD_VERSION_1_0 && GLAD_ARB_texture_float && GLAD_ARB_half_float_pixel)) @@ -2142,8 +2155,13 @@ uint32 OpenGL::getPixelFormatUsageFlags(PixelFormat pixelformat) flags |= commonrender; if (!(GLAD_VERSION_1_1 || GLAD_ES_VERSION_3_0 || GLAD_OES_texture_half_float_linear)) flags &= ~PIXELFORMATUSAGEFLAGS_LINEAR; + if (GLAD_VERSION_4_3 || GLAD_ES_VERSION_3_1) + flags |= computewrite; break; case PIXELFORMAT_R32_FLOAT: + if (GLAD_ES_VERSION_3_1) + flags |= computewrite; + // Fallthrough. case PIXELFORMAT_RG32_FLOAT: if (GLAD_VERSION_3_0 || (GLAD_VERSION_1_0 && GLAD_ARB_texture_float && GLAD_ARB_texture_rg)) flags |= commonsample | commonrender; @@ -2151,6 +2169,8 @@ uint32 OpenGL::getPixelFormatUsageFlags(PixelFormat pixelformat) flags |= commonsample; if (!(GLAD_VERSION_1_1 || GLAD_ES_VERSION_3_0 || GLAD_OES_texture_half_float_linear)) flags &= ~PIXELFORMATUSAGEFLAGS_LINEAR; + if (GLAD_VERSION_4_3) + flags |= computewrite; break; case PIXELFORMAT_RGBA32_FLOAT: if (GLAD_VERSION_3_0 || (GLAD_VERSION_1_0 && GLAD_ARB_texture_float)) @@ -2159,6 +2179,8 @@ uint32 OpenGL::getPixelFormatUsageFlags(PixelFormat pixelformat) flags |= commonsample; if (!(GLAD_VERSION_1_1 || GLAD_OES_texture_float_linear)) flags &= ~PIXELFORMATUSAGEFLAGS_LINEAR; + if (GLAD_VERSION_4_3 || GLAD_ES_VERSION_3_1) + flags |= computewrite; break; case PIXELFORMAT_R8_INT: @@ -2181,6 +2203,26 @@ uint32 OpenGL::getPixelFormatUsageFlags(PixelFormat pixelformat) case PIXELFORMAT_RGBA32_UINT: if (GLAD_VERSION_3_0 || GLAD_ES_VERSION_3_0) flags |= PIXELFORMATUSAGEFLAGS_SAMPLE | PIXELFORMATUSAGEFLAGS_RENDERTARGET; + if (GLAD_VERSION_4_3) + flags |= computewrite; + if (GLAD_ES_VERSION_3_1) + { + switch (pixelformat) + { + case PIXELFORMAT_RGBA8_INT: + case PIXELFORMAT_RGBA8_UINT: + case PIXELFORMAT_RGBA16_INT: + case PIXELFORMAT_RGBA16_UINT: + case PIXELFORMAT_R32_INT: + case PIXELFORMAT_R32_UINT: + case PIXELFORMAT_RGBA32_INT: + case PIXELFORMAT_RGBA32_UINT: + flags |= computewrite; + break; + default: + break; + } + } break; case PIXELFORMAT_LA8_UNORM: @@ -2198,12 +2240,16 @@ uint32 OpenGL::getPixelFormatUsageFlags(PixelFormat pixelformat) case PIXELFORMAT_RGB10A2_UNORM: if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_1_0) flags |= commonsample | commonrender; + if (GLAD_VERSION_4_3) + flags |= computewrite; break; case PIXELFORMAT_RG11B10_FLOAT: if (GLAD_VERSION_3_0 || GLAD_EXT_packed_float || GLAD_APPLE_texture_packed_float) flags |= commonsample; if (GLAD_VERSION_3_0 || GLAD_EXT_packed_float || GLAD_APPLE_color_buffer_packed_float) flags |= commonrender; + if (GLAD_VERSION_4_3) + flags |= computewrite; break; case PIXELFORMAT_STENCIL8: diff --git a/src/modules/graphics/opengl/Shader.cpp b/src/modules/graphics/opengl/Shader.cpp index eea214d57..d68d95563 100644 --- a/src/modules/graphics/opengl/Shader.cpp +++ b/src/modules/graphics/opengl/Shader.cpp @@ -63,7 +63,7 @@ Shader::~Shader() if (p.second.data != nullptr) free(p.second.data); - if (p.second.baseType == UNIFORM_SAMPLER) + if (p.second.baseType == UNIFORM_SAMPLER || p.second.baseType == UNIFORM_STORAGETEXTURE) { for (int i = 0; i < p.second.count; i++) { @@ -158,6 +158,26 @@ void Shader::mapActiveUniforms() for (int i = 0; i < u.count; i++) textureUnits.push_back(unit); } + else if (u.baseType == UNIFORM_STORAGETEXTURE) + { + StorageTextureBinding binding = {}; + binding.gltexture = gl.getDefaultTexture(u.textureType, u.dataBaseType); + binding.type = u.textureType; + + if ((u.access & (ACCESS_READ | ACCESS_WRITE)) != 0) + binding.access = GL_READ_WRITE; + else if ((u.access & ACCESS_WRITE) != 0) + binding.access = GL_WRITE_ONLY; + else if ((u.access & ACCESS_READ) != 0) + binding.access = GL_READ_ONLY; + + bool sRGB = false; + auto fmt = OpenGL::convertPixelFormat(u.storageTextureFormat, false, sRGB); + binding.internalFormat = fmt.internalformat; + + for (int i = 0; i < u.count; i++) + storageTextureBindings.push_back(binding); + } // Make sure previously set uniform data is preserved, and shader- // initialized values are retrieved. @@ -183,6 +203,7 @@ void Shader::mapActiveUniforms() case UNIFORM_INT: case UNIFORM_BOOL: case UNIFORM_SAMPLER: + case UNIFORM_STORAGETEXTURE: case UNIFORM_TEXELBUFFER: u.dataSize = sizeof(int) * u.components * u.count; u.data = malloc(u.dataSize); @@ -226,6 +247,17 @@ void Shader::mapActiveUniforms() memset(u.textures, 0, sizeof(Texture *) * u.count); } } + else if (u.baseType == UNIFORM_STORAGETEXTURE) + { + int startbinding = (int) storageTextureBindings.size() - u.count; + for (int i = 0; i < u.count; i++) + u.ints[i] = startbinding + i; + + glUniform1iv(u.location, u.count, u.ints); + + u.textures = new love::graphics::Texture*[u.count]; + memset(u.textures, 0, sizeof(Texture *) * u.count); + } } size_t offset = 0; @@ -277,7 +309,7 @@ void Shader::mapActiveUniforms() if (builtin != BUILTIN_MAX_ENUM) builtinUniformInfo[(int)builtin] = &uniforms[u.name]; - if (u.baseType == UNIFORM_SAMPLER) + if (u.baseType == UNIFORM_SAMPLER || u.baseType == UNIFORM_STORAGETEXTURE) { // Make sure all stored textures have their Volatiles loaded before // the sendTextures call, since it calls getHandle(). @@ -401,7 +433,7 @@ void Shader::mapActiveUniforms() if (p.second.data != nullptr) free(p.second.data); - if (p.second.baseType == UNIFORM_SAMPLER) + if (p.second.baseType == UNIFORM_SAMPLER || p.second.baseType == UNIFORM_STORAGETEXTURE) { for (int i = 0; i < p.second.count; i++) { @@ -435,6 +467,8 @@ bool Shader::loadVolatile() textureUnits.clear(); textureUnits.push_back(TextureUnit()); + activeStorageBufferBindings.clear(); + storageBufferBindingIndexToActiveBinding.resize(gl.getMaxShaderStorageBufferBindings(), std::make_pair(-1, -1)); activeStorageBufferBindings.clear(); activeWritableStorageBuffers.clear(); @@ -573,7 +607,7 @@ void Shader::attach() // retain/release happens in Graphics::setShader. // Make sure all textures are bound to their respective texture units. - for (int i = 0; i < (int) textureUnits.size(); ++i) + for (int i = 0; i < (int) textureUnits.size(); i++) { const TextureUnit &unit = textureUnits[i]; if (unit.active) @@ -585,6 +619,12 @@ void Shader::attach() } } + for (size_t i = 0; i < storageTextureBindings.size(); i++) + { + const auto &binding = storageTextureBindings[i]; + glBindImageTexture((GLuint) i, binding.gltexture, 0, GL_TRUE, 0, binding.access, binding.internalFormat); + } + for (auto bufferbinding : activeStorageBufferBindings) gl.bindIndexedBuffer(bufferbinding.buffer, BUFFERUSAGE_SHADER_STORAGE, bufferbinding.bindingindex); @@ -648,7 +688,7 @@ void Shader::updateUniform(const UniformInfo *info, int count, bool internalupda break; } } - else if (type == UNIFORM_INT || type == UNIFORM_BOOL || type == UNIFORM_SAMPLER || type == UNIFORM_TEXELBUFFER) + else if (type == UNIFORM_INT || type == UNIFORM_BOOL || type == UNIFORM_SAMPLER || type == UNIFORM_STORAGETEXTURE || type == UNIFORM_TEXELBUFFER) { switch (info->components) { @@ -717,7 +757,10 @@ void Shader::sendTextures(const UniformInfo *info, love::graphics::Texture **tex void Shader::sendTextures(const UniformInfo *info, love::graphics::Texture **textures, int count, bool internalUpdate) { - if (info->baseType != UNIFORM_SAMPLER) + bool issampler = info->baseType == UNIFORM_SAMPLER; + bool isstoragetex = info->baseType == UNIFORM_STORAGETEXTURE; + + if (!issampler && !isstoragetex) return; bool shaderactive = current == this; @@ -770,6 +813,26 @@ void Shader::sendTextures(const UniformInfo *info, love::graphics::Texture **tex else throw love::Exception("Texture's data format base type must match the uniform variable declared in the shader (float, int, or uint)."); } + else if (isstoragetex && !tex->isComputeWritable()) + { + if (internalUpdate) + continue; + else + throw love::Exception("Texture must be created with the computewrite flag set to true in order to be used with a storage texture (image2D etc) shader uniform variable."); + } + else if (isstoragetex && info->storageTextureFormat != getLinearPixelFormat(tex->getPixelFormat())) + { + if (internalUpdate) + continue; + else + { + const char *texpfstr = "unknown"; + const char *shaderpfstr = "unknown"; + love::getConstant(getLinearPixelFormat(tex->getPixelFormat()), texpfstr); + love::getConstant(info->storageTextureFormat, shaderpfstr); + throw love::Exception("Texture's pixel format (%s) must match the shader uniform variable %s's pixel format (%s)", texpfstr, info->name.c_str(), shaderpfstr); + } + } tex->retain(); } @@ -779,19 +842,39 @@ void Shader::sendTextures(const UniformInfo *info, love::graphics::Texture **tex info->textures[i] = tex; - GLuint gltex = 0; - if (textures[i] != nullptr) - gltex = (GLuint) tex->getHandle(); + if (isstoragetex) + { + GLuint gltex = 0; + if (tex != nullptr) + gltex = (GLuint) tex->getHandle(); + else + gltex = gl.getDefaultTexture(info->textureType, info->dataBaseType); + + int bindingindex = info->ints[i]; + auto &binding = storageTextureBindings[bindingindex]; + + binding.texture = tex; + binding.gltexture = gltex; + + if (shaderactive) + glBindImageTexture(bindingindex, binding.gltexture, 0, GL_TRUE, 0, binding.access, binding.internalFormat); + } else - gltex = gl.getDefaultTexture(info->textureType, info->dataBaseType); + { + GLuint gltex = 0; + if (textures[i] != nullptr) + gltex = (GLuint) tex->getHandle(); + else + gltex = gl.getDefaultTexture(info->textureType, info->dataBaseType); - int texunit = info->ints[i]; + int texunit = info->ints[i]; - if (shaderactive) - gl.bindTextureToUnit(info->textureType, gltex, texunit, false, false); + if (shaderactive) + gl.bindTextureToUnit(info->textureType, gltex, texunit, false, false); - // Store texture id so it can be re-bound to the texture unit later. - textureUnits[texunit].texture = gltex; + // Store texture id so it can be re-bound to the texture unit later. + textureUnits[texunit].texture = gltex; + } } } @@ -1243,6 +1326,67 @@ void Shader::computeUniformTypeInfo(GLenum type, UniformInfo &u) u.dataBaseType = DATA_BASETYPE_UINT; break; + case GL_IMAGE_2D: + u.baseType = UNIFORM_STORAGETEXTURE; + u.dataBaseType = DATA_BASETYPE_FLOAT; + u.textureType = TEXTURE_2D; + break; + case GL_INT_IMAGE_2D: + u.baseType = UNIFORM_STORAGETEXTURE; + u.dataBaseType = DATA_BASETYPE_INT; + u.textureType = TEXTURE_2D; + break; + case GL_UNSIGNED_INT_IMAGE_2D: + u.baseType = UNIFORM_STORAGETEXTURE; + u.dataBaseType = DATA_BASETYPE_UINT; + u.textureType = TEXTURE_2D; + break; + case GL_IMAGE_2D_ARRAY: + u.baseType = UNIFORM_STORAGETEXTURE; + u.dataBaseType = DATA_BASETYPE_FLOAT; + u.textureType = TEXTURE_2D_ARRAY; + break; + case GL_INT_IMAGE_2D_ARRAY: + u.baseType = UNIFORM_STORAGETEXTURE; + u.dataBaseType = DATA_BASETYPE_INT; + u.textureType = TEXTURE_2D_ARRAY; + break; + case GL_UNSIGNED_INT_IMAGE_2D_ARRAY: + u.baseType = UNIFORM_STORAGETEXTURE; + u.dataBaseType = DATA_BASETYPE_UINT; + u.textureType = TEXTURE_2D_ARRAY; + break; + case GL_IMAGE_3D: + u.baseType = UNIFORM_STORAGETEXTURE; + u.dataBaseType = DATA_BASETYPE_FLOAT; + u.textureType = TEXTURE_VOLUME; + break; + case GL_INT_IMAGE_3D: + u.baseType = UNIFORM_STORAGETEXTURE; + u.dataBaseType = DATA_BASETYPE_INT; + u.textureType = TEXTURE_VOLUME; + break; + case GL_UNSIGNED_INT_IMAGE_3D: + u.baseType = UNIFORM_STORAGETEXTURE; + u.dataBaseType = DATA_BASETYPE_UINT; + u.textureType = TEXTURE_VOLUME; + break; + case GL_IMAGE_CUBE: + u.baseType = UNIFORM_STORAGETEXTURE; + u.dataBaseType = DATA_BASETYPE_FLOAT; + u.textureType = TEXTURE_CUBE; + break; + case GL_INT_IMAGE_CUBE: + u.baseType = UNIFORM_STORAGETEXTURE; + u.dataBaseType = DATA_BASETYPE_INT; + u.textureType = TEXTURE_CUBE; + break; + case GL_UNSIGNED_INT_IMAGE_CUBE: + u.baseType = UNIFORM_STORAGETEXTURE; + u.dataBaseType = DATA_BASETYPE_UINT; + u.textureType = TEXTURE_CUBE; + break; + default: break; } diff --git a/src/modules/graphics/opengl/Shader.h b/src/modules/graphics/opengl/Shader.h index 987acec2a..efab5ab59 100644 --- a/src/modules/graphics/opengl/Shader.h +++ b/src/modules/graphics/opengl/Shader.h @@ -43,6 +43,15 @@ class Shader final : public love::graphics::Shader, public Volatile { public: + struct StorageTextureBinding + { + Texture *texture = nullptr; + GLuint gltexture = 0; + TextureType type = TEXTURE_2D; + GLenum access = GL_READ_ONLY; + GLenum internalFormat; + }; + Shader(StrongRef stages[SHADERSTAGE_MAX_ENUM]); virtual ~Shader(); @@ -66,7 +75,7 @@ public: void updateBuiltinUniforms(love::graphics::Graphics *gfx, int viewportW, int viewportH); const std::vector &getActiveWritableStorageBuffers() const { return activeWritableStorageBuffers; } - const std::vector &getActiveWritableTextures() const { return activeWritableTextures; } + const std::vector &getStorageTextureBindings() const { return storageTextureBindings; } private: @@ -118,11 +127,12 @@ private: // Texture unit pool for setting textures std::vector textureUnits; + std::vector storageTextureBindings; + std::vector> storageBufferBindingIndexToActiveBinding; std::vector activeStorageBufferBindings; std::vector activeWritableStorageBuffers; - std::vector activeWritableTextures; std::vector> pendingUniformUpdates; diff --git a/src/modules/graphics/wrap_Graphics.cpp b/src/modules/graphics/wrap_Graphics.cpp index 18196a67e..12418d625 100644 --- a/src/modules/graphics/wrap_Graphics.cpp +++ b/src/modules/graphics/wrap_Graphics.cpp @@ -801,6 +801,8 @@ static void luax_checktexturesettings(lua_State *L, int idx, bool opt, bool chec s.linear = luax_boolflag(L, idx, Texture::getConstant(Texture::SETTING_LINEAR), s.linear); s.msaa = luax_intflag(L, idx, Texture::getConstant(Texture::SETTING_MSAA), s.msaa); + s.computeWrite = luax_boolflag(L, idx, Texture::getConstant(Texture::SETTING_COMPUTE_WRITE), s.computeWrite); + lua_getfield(L, idx, Texture::getConstant(Texture::SETTING_READABLE)); if (!lua_isnoneornil(L, -1)) s.readable.set(luax_checkboolean(L, -1)); @@ -2640,6 +2642,7 @@ int w_getTextureFormats(lua_State *L) bool rt = luax_checkboolflag(L, 1, Texture::getConstant(Texture::SETTING_RENDER_TARGET)); bool linear = luax_boolflag(L, 1, Texture::getConstant(Texture::SETTING_LINEAR), false); + bool computewrite = luax_boolflag(L, 1, Texture::getConstant(Texture::SETTING_COMPUTE_WRITE), false); OptionalBool readable; lua_getfield(L, 1, Texture::getConstant(Texture::SETTING_READABLE)); @@ -2663,10 +2666,17 @@ int w_getTextureFormats(lua_State *L) if (rt && isPixelFormatDepth(format)) continue; - bool formatReadable = readable.get(!isPixelFormatDepthStencil(format)); bool sRGB = isGammaCorrect() && !linear; - luax_pushboolean(L, instance()->isPixelFormatSupported(format, rt, formatReadable, sRGB)); + uint32 usage = PIXELFORMATUSAGEFLAGS_NONE; + if (rt) + usage |= PIXELFORMATUSAGEFLAGS_RENDERTARGET; + if (readable.get(!isPixelFormatDepthStencil(format))) + usage |= PIXELFORMATUSAGEFLAGS_SAMPLE; + if (computewrite) + usage |= PIXELFORMATUSAGEFLAGS_COMPUTEWRITE; + + luax_pushboolean(L, instance()->isPixelFormatSupported(format, (PixelFormatUsageFlags) usage, sRGB)); lua_setfield(L, -2, name); } @@ -2709,14 +2719,15 @@ int w_getCanvasFormats(lua_State *L) { supported = [](PixelFormat format) -> bool { - return instance()->isPixelFormatSupported(format, true, true, false); + const uint32 usage = PIXELFORMATUSAGEFLAGS_SAMPLE | PIXELFORMATUSAGEFLAGS_RENDERTARGET; + return instance()->isPixelFormatSupported(format, (PixelFormatUsageFlags) usage, false); }; } else { supported = [](PixelFormat format) -> bool { - return instance()->isPixelFormatSupported(format, true, false, false); + return instance()->isPixelFormatSupported(format, PIXELFORMATUSAGEFLAGS_RENDERTARGET, false); }; } } @@ -2725,7 +2736,10 @@ int w_getCanvasFormats(lua_State *L) supported = [](PixelFormat format) -> bool { bool readable = !isPixelFormatDepthStencil(format); - return instance()->isPixelFormatSupported(format, true, readable, false); + uint32 usage = PIXELFORMATUSAGEFLAGS_RENDERTARGET; + if (readable) + usage |= PIXELFORMATUSAGEFLAGS_SAMPLE; + return instance()->isPixelFormatSupported(format, (PixelFormatUsageFlags) usage, false); }; } @@ -2738,7 +2752,7 @@ int w_getImageFormats(lua_State *L) const auto supported = [](PixelFormat format) -> bool { - return instance()->isPixelFormatSupported(format, false, true, false); + return instance()->isPixelFormatSupported(format, PIXELFORMATUSAGEFLAGS_SAMPLE, false); }; const auto ignore = [](PixelFormat format) -> bool diff --git a/src/modules/graphics/wrap_Shader.cpp b/src/modules/graphics/wrap_Shader.cpp index 3f8434423..706c77de3 100644 --- a/src/modules/graphics/wrap_Shader.cpp +++ b/src/modules/graphics/wrap_Shader.cpp @@ -317,6 +317,7 @@ static int w_Shader_sendLuaValues(lua_State *L, int startidx, Shader *shader, co case Shader::UNIFORM_BOOL: return w_Shader_sendBooleans(L, startidx, shader, info); case Shader::UNIFORM_SAMPLER: + case Shader::UNIFORM_STORAGETEXTURE: return w_Shader_sendTextures(L, startidx, shader, info); case Shader::UNIFORM_TEXELBUFFER: case Shader::UNIFORM_STORAGEBUFFER: @@ -328,7 +329,8 @@ static int w_Shader_sendLuaValues(lua_State *L, int startidx, Shader *shader, co static int w_Shader_sendData(lua_State *L, int startidx, Shader *shader, const Shader::UniformInfo *info, bool colors) { - if (info->baseType == Shader::UNIFORM_SAMPLER || info->baseType == Shader::UNIFORM_TEXELBUFFER || info->baseType == Shader::UNIFORM_STORAGEBUFFER) + if (info->baseType == Shader::UNIFORM_SAMPLER || info->baseType == Shader::UNIFORM_STORAGETEXTURE + || info->baseType == Shader::UNIFORM_TEXELBUFFER || info->baseType == Shader::UNIFORM_STORAGEBUFFER) return luaL_error(L, "Only value types (floats, ints, vectors, matrices, etc) be sent to Shaders via Data objects."); math::Transform::MatrixLayout layout = math::Transform::MATRIX_ROW_MAJOR; diff --git a/src/modules/graphics/wrap_Texture.cpp b/src/modules/graphics/wrap_Texture.cpp index 8926c14bd..a0fee702c 100644 --- a/src/modules/graphics/wrap_Texture.cpp +++ b/src/modules/graphics/wrap_Texture.cpp @@ -279,6 +279,20 @@ int w_Texture_getFormat(lua_State *L) return 1; } +int w_Texture_isRenderTarget(lua_State *L) +{ + Texture *t = luax_checktexture(L, 1); + luax_pushboolean(L, t->isRenderTarget()); + return 1; +} + +int w_Texture_isComputeWritable(lua_State *L) +{ + Texture *t = luax_checktexture(L, 1); + luax_pushboolean(L, t->isComputeWritable()); + return 1; +} + int w_Texture_isReadable(lua_State *L) { Texture *t = luax_checktexture(L, 1); @@ -479,6 +493,8 @@ const luaL_Reg w_Texture_functions[] = { "setWrap", w_Texture_setWrap }, { "getWrap", w_Texture_getWrap }, { "getFormat", w_Texture_getFormat }, + { "isRenderTarget", w_Texture_isRenderTarget }, + { "isComputeWritable", w_Texture_isComputeWritable }, { "isReadable", w_Texture_isReadable }, { "getMipmapMode", w_Texture_getMipmapMode }, { "getDepthSampleMode", w_Texture_getDepthSampleMode },