Initial support for writing to textures in compute shaders.

This commit is contained in:
Alex Szpakowski
2021-08-13 14:43:03 -03:00
parent cffd485976
commit a551fcd492
14 changed files with 383 additions and 50 deletions
+1 -1
View File
@@ -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;
+2 -2
View File
@@ -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.
+76 -5
View File
@@ -731,6 +731,55 @@ bool Shader::validate(StrongRef<ShaderStage> 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<ShaderStage> stages[], std::string &err, ValidationReflection &reflection)
{
glslang::TProgram program;
@@ -781,12 +830,34 @@ bool Shader::validateInternal(StrongRef<ShaderStage> 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<ShaderStage> 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;
+9
View File
@@ -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<std::string, BufferReflection> storageBuffers;
std::map<std::string, StorageTextureReflection> storageTextures;
int localThreadgroupSize[3];
bool usesPointSize;
};
+19 -2
View File
@@ -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<Texture::SettingType, Texture::SETTING_MAX_ENUM>::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 },
};
+6
View File
@@ -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;
+10 -12
View File
@@ -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;
+3 -3
View File
@@ -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<love::graphics::Buffer> 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
+47 -1
View File
@@ -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<Graphics>(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:
+159 -15
View File
@@ -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;
}
+12 -2
View File
@@ -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<love::graphics::ShaderStage> stages[SHADERSTAGE_MAX_ENUM]);
virtual ~Shader();
@@ -66,7 +75,7 @@ public:
void updateBuiltinUniforms(love::graphics::Graphics *gfx, int viewportW, int viewportH);
const std::vector<Buffer *> &getActiveWritableStorageBuffers() const { return activeWritableStorageBuffers; }
const std::vector<Texture *> &getActiveWritableTextures() const { return activeWritableTextures; }
const std::vector<StorageTextureBinding> &getStorageTextureBindings() const { return storageTextureBindings; }
private:
@@ -118,11 +127,12 @@ private:
// Texture unit pool for setting textures
std::vector<TextureUnit> textureUnits;
std::vector<StorageTextureBinding> storageTextureBindings;
std::vector<std::pair<int, int>> storageBufferBindingIndexToActiveBinding;
std::vector<BufferBinding> activeStorageBufferBindings;
std::vector<Buffer *> activeWritableStorageBuffers;
std::vector<Texture *> activeWritableTextures;
std::vector<std::pair<const UniformInfo *, int>> pendingUniformUpdates;
+20 -6
View File
@@ -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
+3 -1
View File
@@ -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;
+16
View File
@@ -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 },