diff --git a/src/modules/graphics/Buffer.cpp b/src/modules/graphics/Buffer.cpp index c9c3f8a5f..aaf9b2228 100644 --- a/src/modules/graphics/Buffer.cpp +++ b/src/modules/graphics/Buffer.cpp @@ -20,6 +20,7 @@ #include "Buffer.h" #include "Graphics.h" +#include "common/memory.h" namespace love { @@ -48,15 +49,20 @@ Buffer::Buffer(Graphics *gfx, const Settings &settings, const std::vector 0) + throw love::Exception("Arrays are not currently supported in shader storage buffers."); + + if (info.baseType == DATA_BASETYPE_BOOL) + throw love::Exception("Bool types are not supported in shader storage buffers."); + + if (info.baseType == DATA_BASETYPE_UNORM || info.baseType == DATA_BASETYPE_SNORM) + throw love::Exception("Normalized formats are not supported in shader storage buffers."); + + size_t alignment = 1; + + // GLSL's std430 packing rules. We also assume all matrices are + // column-major. + if (info.isMatrix) + alignment = info.matrixRows * info.componentSize; + else + alignment = info.components * info.componentSize; + + structurealignment = std::max(structurealignment, alignment); + + memberoffset = alignUp(memberoffset, alignment); + + if (memberoffset != offset && (indexbuffer || vertexbuffer || texelbuffer)) + throw love::Exception("Cannot create Buffer:\nInternal alignment of member '%s' is preventing Buffer from being created as both a shader storage buffer and other buffer types\nMember byte offset needed for shader storage buffer: %d\nMember byte offset needed for other buffer types: %d", + member.decl.name.c_str(), memberoffset, offset); + } + + member.offset = memberoffset; + member.size = membersize; + + offset = member.offset + member.size; dataMembers.push_back(member); } - stride = offset; + stride = alignUp(offset, structurealignment); + + if (storagebuffer && (indexbuffer || vertexbuffer || texelbuffer)) + { + if (stride != offset) + throw love::Exception("Cannot create Buffer:\nBuffer used as a shader storage buffer would have a different number of bytes per array element (%d) than when used as other buffer types (%d)", + stride, offset); + } + + if (storagebuffer && stride > SHADER_STORAGE_BUFFER_MAX_STRIDE) + throw love::Exception("Shader storage buffers cannot have more than %d bytes within each array element.", SHADER_STORAGE_BUFFER_MAX_STRIDE); if (size != 0) { @@ -144,7 +194,8 @@ Buffer::Buffer(Graphics *gfx, const Settings &settings, const std::vectorsize = size; if (texelbuffer && arraylength * dataMembers.size() > caps.limits[Graphics::LIMIT_TEXEL_BUFFER_SIZE]) - throw love::Exception("Cannot create texel buffer: total number of values in the buffer (%d * %d) is too large for this system (maximum %d).", (int) dataMembers.size(), (int) arraylength, caps.limits[Graphics::LIMIT_TEXEL_BUFFER_SIZE]); + throw love::Exception("Cannot create texel buffer: total number of values in the buffer (%d * %d) is too large for this system (maximum %d).", + (int) dataMembers.size(), (int) arraylength, caps.limits[Graphics::LIMIT_TEXEL_BUFFER_SIZE]); } Buffer::~Buffer() diff --git a/src/modules/graphics/Buffer.h b/src/modules/graphics/Buffer.h index 40f98fb1e..483b9679e 100644 --- a/src/modules/graphics/Buffer.h +++ b/src/modules/graphics/Buffer.h @@ -48,6 +48,8 @@ public: static love::Type type; + static const size_t SHADER_STORAGE_BUFFER_MAX_STRIDE = 2048; + enum MapType { MAP_WRITE_INVALIDATE, @@ -59,6 +61,7 @@ public: TYPEFLAG_VERTEX = 1 << BUFFERTYPE_VERTEX, TYPEFLAG_INDEX = 1 << BUFFERTYPE_INDEX, TYPEFLAG_TEXEL = 1 << BUFFERTYPE_TEXEL, + TYPEFLAG_SHADER_STORAGE = 1 << BUFFERTYPE_SHADER_STORAGE, }; struct DataDeclaration diff --git a/src/modules/graphics/Graphics.cpp b/src/modules/graphics/Graphics.cpp index f0c2cdfd7..25b1ad775 100644 --- a/src/modules/graphics/Graphics.cpp +++ b/src/modules/graphics/Graphics.cpp @@ -1944,15 +1944,16 @@ StringMap Graphics::features(Grap StringMap::Entry Graphics::systemLimitEntries[] = { - { "pointsize", LIMIT_POINT_SIZE }, - { "texturesize", LIMIT_TEXTURE_SIZE }, - { "texturelayers", LIMIT_TEXTURE_LAYERS }, - { "volumetexturesize", LIMIT_VOLUME_TEXTURE_SIZE }, - { "cubetexturesize", LIMIT_CUBE_TEXTURE_SIZE }, - { "texelbuffersize", LIMIT_TEXEL_BUFFER_SIZE }, - { "rendertargets", LIMIT_RENDER_TARGETS }, - { "texturemsaa", LIMIT_TEXTURE_MSAA }, - { "anisotropy", LIMIT_ANISOTROPY }, + { "pointsize", LIMIT_POINT_SIZE }, + { "texturesize", LIMIT_TEXTURE_SIZE }, + { "texturelayers", LIMIT_TEXTURE_LAYERS }, + { "volumetexturesize", LIMIT_VOLUME_TEXTURE_SIZE }, + { "cubetexturesize", LIMIT_CUBE_TEXTURE_SIZE }, + { "texelbuffersize", LIMIT_TEXEL_BUFFER_SIZE }, + { "shaderstoragebuffersize", LIMIT_SHADER_STORAGE_BUFFER_SIZE }, + { "rendertargets", LIMIT_RENDER_TARGETS }, + { "texturemsaa", LIMIT_TEXTURE_MSAA }, + { "anisotropy", LIMIT_ANISOTROPY }, }; StringMap Graphics::systemLimits(Graphics::systemLimitEntries, sizeof(Graphics::systemLimitEntries)); diff --git a/src/modules/graphics/Graphics.h b/src/modules/graphics/Graphics.h index 0ad0c5528..88258532d 100644 --- a/src/modules/graphics/Graphics.h +++ b/src/modules/graphics/Graphics.h @@ -162,6 +162,7 @@ public: LIMIT_CUBE_TEXTURE_SIZE, LIMIT_TEXTURE_LAYERS, LIMIT_TEXEL_BUFFER_SIZE, + LIMIT_SHADER_STORAGE_BUFFER_SIZE, LIMIT_RENDER_TARGETS, LIMIT_TEXTURE_MSAA, LIMIT_ANISOTROPY, diff --git a/src/modules/graphics/Shader.cpp b/src/modules/graphics/Shader.cpp index df1990fc3..605d74bd6 100644 --- a/src/modules/graphics/Shader.cpp +++ b/src/modules/graphics/Shader.cpp @@ -26,6 +26,9 @@ // glslang #include "libraries/glslang/glslang/Public/ShaderLang.h" +// Needed for reflection information. +#include "libraries/glslang/glslang/Include/Types.h" + // C++ #include #include @@ -546,7 +549,7 @@ Shader::Shader(ShaderStage *vertex, ShaderStage *pixel) : stages() { std::string err; - if (!validate(vertex, pixel, err)) + if (!validateInternal(vertex, pixel, err, validationReflection)) throw love::Exception("%s", err.c_str()); stages[ShaderStage::STAGE_VERTEX] = vertex; @@ -629,7 +632,13 @@ void Shader::checkMainTexture(Texture *tex) const checkMainTextureType(tex->getTextureType(), tex->getSamplerState().depthSampleMode.hasValue); } -bool Shader::validate(ShaderStage *vertex, ShaderStage *pixel, std::string &err) +bool Shader::validate(ShaderStage* vertex, ShaderStage* pixel, std::string& err) +{ + ValidationReflection reflection; + return validateInternal(vertex, pixel, err, reflection); +} + +bool Shader::validateInternal(ShaderStage *vertex, ShaderStage *pixel, std::string &err, ValidationReflection &reflection) { glslang::TProgram program; @@ -645,6 +654,59 @@ bool Shader::validate(ShaderStage *vertex, ShaderStage *pixel, std::string &err) return false; } + if (!program.buildReflection(EShReflectionSeparateBuffers)) + { + err = "Cannot get reflection information for shader."; + return false; + } + + for (int i = 0; i < program.getNumBufferBlocks(); i++) + { + const glslang::TObjectReflection &info = program.getBufferBlock(i); + const glslang::TType *type = info.getType(); + if (type != nullptr) + { + const glslang::TQualifier &qualifiers = type->getQualifier(); + + if ((!qualifiers.isReadOnly() || qualifiers.isWriteOnly()) && (info.stages & (EShLangVertexMask | EShLangFragmentMask))) + { + err = "Shader validation error:\nStorage Buffer block '" + info.name + "' must be marked as readonly in vertex and pixel shaders."; + return false; + } + + if (qualifiers.layoutPacking != glslang::ElpStd430) + { + err = "Shader validation error:\nStorage Buffer block '" + info.name + "' must use the std430 packing layout."; + return false; + } + + const glslang::TTypeList *structure = type->getStruct(); + if (structure == nullptr || structure->size() != 1) + { + err = "Shader validation error:\nStorage Buffer block '" + info.name + "' must contain a single unsized array of structs."; + return false; + } + + const glslang::TType* structtype = (*structure)[0].type; + if (structtype == nullptr || structtype->getBasicType() != glslang::EbtStruct || !structtype->isUnsizedArray()) + { + err = "Shader validation error:\nStorage Buffer block '" + info.name + "' must contain a single unsized array of structs."; + return false; + } + + BufferReflection bufferReflection = {}; + bufferReflection.stride = (size_t) info.size; + bufferReflection.memberCount = (size_t) info.numMembers; + + reflection.storageBuffers[info.name] = bufferReflection; + } + else + { + err = "Shader validation error:\nCannot retrieve type information for Storage Buffer Block '" + info.name + "'."; + return false; + } + } + return true; } diff --git a/src/modules/graphics/Shader.h b/src/modules/graphics/Shader.h index d75a6100c..c04c51bb8 100644 --- a/src/modules/graphics/Shader.h +++ b/src/modules/graphics/Shader.h @@ -78,6 +78,7 @@ public: UNIFORM_BOOL, UNIFORM_SAMPLER, UNIFORM_TEXELBUFFER, + UNIFORM_STORAGEBUFFER, UNIFORM_UNKNOWN, UNIFORM_MAX_ENUM }; @@ -126,6 +127,8 @@ public: TextureType textureType; DataBaseType texelBufferType; bool isDepthSampler; + size_t bufferStride; + size_t bufferMemberCount; std::string name; union @@ -227,8 +230,23 @@ public: protected: + struct BufferReflection + { + size_t stride; + size_t memberCount; + }; + + struct ValidationReflection + { + std::map storageBuffers; + }; + + static bool validateInternal(ShaderStage* vertex, ShaderStage* pixel, std::string& err, ValidationReflection &reflection); + StrongRef stages[ShaderStage::STAGE_MAX_ENUM]; + ValidationReflection validationReflection; + }; // Shader } // graphics diff --git a/src/modules/graphics/opengl/Buffer.cpp b/src/modules/graphics/opengl/Buffer.cpp index 43276e59f..a9e33c0f9 100644 --- a/src/modules/graphics/opengl/Buffer.cpp +++ b/src/modules/graphics/opengl/Buffer.cpp @@ -78,6 +78,8 @@ Buffer::Buffer(love::graphics::Graphics *gfx, const Settings &settings, const st mapType = BUFFERTYPE_VERTEX; else if (typeFlags & TYPEFLAG_INDEX) mapType = BUFFERTYPE_INDEX; + else if (typeFlags & TYPEFLAG_SHADER_STORAGE) + mapType = BUFFERTYPE_SHADER_STORAGE; target = OpenGL::getGLBufferType(mapType); diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index 3c6437a62..94b3deab7 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/src/modules/graphics/opengl/Graphics.cpp @@ -345,7 +345,19 @@ bool Graphics::setMode(int width, int height, int pixelwidth, int pixelheight, b const float texel[] = {0.0f, 0.0f, 0.0f, 1.0f}; - love::graphics::Buffer *buffer = newBuffer(settings, format, texel, sizeof(texel), 1); + auto buffer = newBuffer(settings, format, texel, sizeof(texel), 1); + defaultBuffers[BUFFERTYPE_TEXEL].set(buffer, Acquire::NORETAIN); + } + + if (capabilities.features[FEATURE_GLSL4] && defaultBuffers[BUFFERTYPE_SHADER_STORAGE].get() == nullptr) + { + Buffer::Settings settings(Buffer::TYPEFLAG_SHADER_STORAGE, BUFFERUSAGE_STATIC); + std::vector format = {{"", DATAFORMAT_FLOAT, 0}}; + + std::vector data; + data.resize(Buffer::SHADER_STORAGE_BUFFER_MAX_STRIDE / 4); + + auto buffer = newBuffer(settings, format, data.data(), data.size() * sizeof(float), data.size()); defaultBuffers[BUFFERTYPE_TEXEL].set(buffer, Acquire::NORETAIN); } @@ -359,6 +371,9 @@ bool Graphics::setMode(int width, int height, int pixelwidth, int pixelheight, b if (defaultBuffers[BUFFERTYPE_TEXEL].get()) gl.setDefaultTexelBuffer((GLuint) defaultBuffers[BUFFERTYPE_TEXEL]->getTexelBufferHandle()); + if (defaultBuffers[BUFFERTYPE_SHADER_STORAGE].get()) + gl.setDefaultStorageBuffer((GLuint) defaultBuffers[BUFFERTYPE_SHADER_STORAGE]->getHandle()); + // Reload all volatile objects. if (!Volatile::loadAll()) ::printf("Could not reload all volatile objects.\n"); @@ -1515,7 +1530,7 @@ void Graphics::initCapabilities() capabilities.features[FEATURE_GLSL3] = GLAD_ES_VERSION_3_0 || gl.isCoreProfile(); capabilities.features[FEATURE_GLSL4] = GLAD_ES_VERSION_3_1 || (gl.isCoreProfile() && GLAD_VERSION_4_3); capabilities.features[FEATURE_INSTANCING] = gl.isInstancingSupported(); - capabilities.features[FEATURE_TEXEL_BUFFER] = gl.areTexelBuffersSupported(); + capabilities.features[FEATURE_TEXEL_BUFFER] = gl.isBufferTypeSupported(BUFFERTYPE_TEXEL); static_assert(FEATURE_MAX_ENUM == 11, "Graphics::initCapabilities must be updated when adding a new graphics feature!"); capabilities.limits[LIMIT_POINT_SIZE] = gl.getMaxPointSize(); @@ -1524,10 +1539,11 @@ void Graphics::initCapabilities() capabilities.limits[LIMIT_VOLUME_TEXTURE_SIZE] = gl.getMax3DTextureSize(); capabilities.limits[LIMIT_CUBE_TEXTURE_SIZE] = gl.getMaxCubeTextureSize(); capabilities.limits[LIMIT_TEXEL_BUFFER_SIZE] = gl.getMaxTexelBufferSize(); + capabilities.limits[LIMIT_SHADER_STORAGE_BUFFER_SIZE] = gl.getMaxShaderStorageBufferSize(); capabilities.limits[LIMIT_RENDER_TARGETS] = gl.getMaxRenderTargets(); capabilities.limits[LIMIT_TEXTURE_MSAA] = gl.getMaxSamples(); capabilities.limits[LIMIT_ANISOTROPY] = gl.getMaxAnisotropy(); - static_assert(LIMIT_MAX_ENUM == 9, "Graphics::initCapabilities must be updated when adding a new system limit!"); + static_assert(LIMIT_MAX_ENUM == 10, "Graphics::initCapabilities must be updated when adding a new system limit!"); for (int i = 0; i < TEXTURE_MAX_ENUM; i++) capabilities.textureTypes[i] = gl.isTextureTypeSupported((TextureType) i); diff --git a/src/modules/graphics/opengl/OpenGL.cpp b/src/modules/graphics/opengl/OpenGL.cpp index 7d6c0faab..0974da473 100644 --- a/src/modules/graphics/opengl/OpenGL.cpp +++ b/src/modules/graphics/opengl/OpenGL.cpp @@ -101,9 +101,12 @@ OpenGL::OpenGL() , max3DTextureSize(0) , maxCubeTextureSize(0) , maxTextureArrayLayers(0) + , maxTexelBufferSize(0) + , maxShaderStorageBufferSize(0) , maxRenderTargets(1) , maxSamples(1) , maxTextureUnits(1) + , maxShaderStorageBufferBindings(0) , maxPointSize(1) , coreProfile(false) , vendor(VENDOR_UNKNOWN) @@ -235,9 +238,13 @@ void OpenGL::setupContext() for (int i = 0; i < (int) BUFFERTYPE_MAX_ENUM; i++) { state.boundBuffers[i] = 0; - glBindBuffer(getGLBufferType((BufferType) i), 0); + if (isBufferTypeSupported((BufferType) i)) + glBindBuffer(getGLBufferType((BufferType) i), 0); } + if (isBufferTypeSupported(BUFFERTYPE_SHADER_STORAGE)) + state.boundIndexedBuffers[BUFFERTYPE_SHADER_STORAGE].resize(maxShaderStorageBufferBindings, 0); + // Initialize multiple texture unit support for shaders. for (int i = 0; i < TEXTURE_MAX_ENUM + 1; i++) { @@ -290,10 +297,6 @@ void OpenGL::deInitContext() } } - if (state.defaultTexelBuffer != 0) - gl.deleteTexture(state.defaultTexelBuffer); - state.defaultTexelBuffer = 0; - contextInitialized = false; } @@ -481,11 +484,22 @@ void OpenGL::initMaxValues() else maxTextureArrayLayers = 0; - if (areTexelBuffersSupported()) + if (isBufferTypeSupported(BUFFERTYPE_TEXEL)) glGetIntegerv(GL_MAX_TEXTURE_BUFFER_SIZE, &maxTexelBufferSize); else maxTexelBufferSize = 0; + if (isBufferTypeSupported(BUFFERTYPE_SHADER_STORAGE)) + { + glGetIntegerv(GL_MAX_SHADER_STORAGE_BLOCK_SIZE, &maxShaderStorageBufferSize); + glGetIntegerv(GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS, &maxShaderStorageBufferBindings); + } + else + { + maxShaderStorageBufferSize = 0; + maxShaderStorageBufferBindings = 0; + } + int maxattachments = 1; int maxdrawbuffers = 1; @@ -606,6 +620,7 @@ GLenum OpenGL::getGLBufferType(BufferType type) case BUFFERTYPE_VERTEX: return GL_ARRAY_BUFFER; case BUFFERTYPE_INDEX: return GL_ELEMENT_ARRAY_BUFFER; case BUFFERTYPE_TEXEL: return GL_TEXTURE_BUFFER; + case BUFFERTYPE_SHADER_STORAGE: return GL_SHADER_STORAGE_BUFFER; case BUFFERTYPE_MAX_ENUM: return GL_ZERO; } @@ -803,6 +818,12 @@ void OpenGL::deleteBuffer(GLuint buffer) { if (state.boundBuffers[i] == buffer) state.boundBuffers[i] = 0; + + for (GLuint &bufferid : state.boundIndexedBuffers[i]) + { + if (bufferid == buffer) + bufferid = 0; + } } } @@ -1142,6 +1163,19 @@ void OpenGL::bindTextureToUnit(Texture *texture, int textureunit, bool restorepr bindTextureToUnit(textype, handle, textureunit, restoreprev, bindforedit); } +void OpenGL::bindIndexedBuffer(GLuint buffer, BufferType type, int index) +{ + auto &bindings = state.boundIndexedBuffers[type]; + if (bindings.size() > (size_t) index && buffer != bindings[index]) + { + bindings[index] = buffer; + glBindBufferBase(getGLBufferType(type), index, buffer); + + // glBindBufferBase affects glBindBuffer as well... for some reason. + state.boundBuffers[type] = buffer; + } +} + void OpenGL::deleteTexture(GLuint texture) { // glDeleteTextures binds texture 0 to all texture units the deleted texture @@ -1392,9 +1426,28 @@ bool OpenGL::isTextureTypeSupported(TextureType type) const return GLAD_VERSION_3_0 || GLAD_ES_VERSION_3_0 || GLAD_EXT_texture_array; case TEXTURE_CUBE: return GLAD_VERSION_1_3 || GLAD_ES_VERSION_2_0; - default: + case TEXTURE_MAX_ENUM: return false; } + return false; +} + +bool OpenGL::isBufferTypeSupported(BufferType type) const +{ + switch (type) + { + case BUFFERTYPE_VERTEX: + case BUFFERTYPE_INDEX: + return true; + case BUFFERTYPE_TEXEL: + // Not supported in ES until 3.2, which we don't support shaders for... + return GLAD_VERSION_3_1; + case BUFFERTYPE_SHADER_STORAGE: + return (GLAD_VERSION_4_3 && isCoreProfile()) || GLAD_ES_VERSION_3_1; + case BUFFERTYPE_MAX_ENUM: + return false; + } + return false; } bool OpenGL::isClampZeroOneTextureWrapSupported() const @@ -1435,12 +1488,6 @@ bool OpenGL::isMultiFormatMRTSupported() const return getMaxRenderTargets() > 1 && (GLAD_ES_VERSION_3_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object); } -bool OpenGL::areTexelBuffersSupported() const -{ - // Not supported in ES until 3.2, which we don't support shaders for... - return GLAD_VERSION_3_1; -} - int OpenGL::getMax2DTextureSize() const { return std::max(max2DTextureSize, 1); @@ -1466,6 +1513,11 @@ int OpenGL::getMaxTexelBufferSize() const return maxTexelBufferSize; } +int OpenGL::getMaxShaderStorageBufferSize() const +{ + return maxShaderStorageBufferSize; +} + int OpenGL::getMaxRenderTargets() const { return std::min(maxRenderTargets, MAX_COLOR_RENDER_TARGETS); @@ -1481,6 +1533,11 @@ int OpenGL::getMaxTextureUnits() const return maxTextureUnits; } +int OpenGL::getMaxShaderStorageBufferBindings() const +{ + return maxShaderStorageBufferBindings; +} + float OpenGL::getMaxPointSize() const { return maxPointSize; diff --git a/src/modules/graphics/opengl/OpenGL.h b/src/modules/graphics/opengl/OpenGL.h index df7317f53..d1ac0ae86 100644 --- a/src/modules/graphics/opengl/OpenGL.h +++ b/src/modules/graphics/opengl/OpenGL.h @@ -319,6 +319,9 @@ public: GLuint getDefaultTexelBuffer() const { return state.defaultTexelBuffer; } void setDefaultTexelBuffer(GLuint tex) { state.defaultTexelBuffer = tex; } + GLuint getDefaultStorageBuffer() const { return state.defaultStorageBuffer; } + void setDefaultStorageBuffer(GLuint buf) { state.defaultStorageBuffer = buf; } + /** * Helper for setting the active texture unit. * @@ -338,6 +341,8 @@ public: void bindBufferTextureToUnit(GLuint texture, int textureunit, bool restoreprev, bool bindforedit); + void bindIndexedBuffer(GLuint buffer, BufferType type, int index); + /** * Helper for deleting an OpenGL texture. * Cleans up if the texture is currently bound. @@ -357,6 +362,7 @@ public: bool rawTexStorage(TextureType target, int levels, PixelFormat pixelformat, bool &isSRGB, int width, int height, int depth = 1); bool isTextureTypeSupported(TextureType type) const; + bool isBufferTypeSupported(BufferType type) const; bool isClampZeroOneTextureWrapSupported() const; bool isPixelShaderHighpSupported() const; bool isInstancingSupported() const; @@ -364,7 +370,6 @@ public: bool isSamplerLODBiasSupported() const; bool isBaseVertexSupported() const; bool isMultiFormatMRTSupported() const; - bool areTexelBuffersSupported() const; /** * Returns the maximum supported width or height of a texture. @@ -379,6 +384,11 @@ public: **/ int getMaxTexelBufferSize() const; + /** + * Returns the maximum number of bytes in a shader storage buffer. + **/ + int getMaxShaderStorageBufferSize() const; + /** * Returns the maximum supported number of simultaneous render targets. **/ @@ -394,6 +404,11 @@ public: **/ int getMaxTextureUnits() const; + /** + * Returns the maximum number of shader storage buffer bindings. + **/ + int getMaxShaderStorageBufferBindings() const; + /** * Returns the maximum point size. **/ @@ -458,9 +473,11 @@ private: int maxCubeTextureSize; int maxTextureArrayLayers; int maxTexelBufferSize; + int maxShaderStorageBufferSize; int maxRenderTargets; int maxSamples; int maxTextureUnits; + int maxShaderStorageBufferBindings; float maxPointSize; bool coreProfile; @@ -475,6 +492,8 @@ private: // Texture unit state (currently bound texture for each texture unit.) std::vector boundTextures[TEXTURE_MAX_ENUM + 1]; + std::vector boundIndexedBuffers[BUFFERTYPE_MAX_ENUM]; + bool enableState[ENABLE_MAX_ENUM]; GLenum faceCullMode; @@ -495,6 +514,7 @@ private: GLuint defaultTexture[TEXTURE_MAX_ENUM]; GLuint defaultTexelBuffer; + GLuint defaultStorageBuffer; } state; diff --git a/src/modules/graphics/opengl/Shader.cpp b/src/modules/graphics/opengl/Shader.cpp index 9f518e204..458091db2 100644 --- a/src/modules/graphics/opengl/Shader.cpp +++ b/src/modules/graphics/opengl/Shader.cpp @@ -37,6 +37,11 @@ namespace graphics namespace opengl { +static bool isBuffer(Shader::UniformType utype) +{ + return utype == Shader::UNIFORM_TEXELBUFFER || utype == Shader::UNIFORM_STORAGEBUFFER; +} + Shader::Shader(love::graphics::ShaderStage *vertex, love::graphics::ShaderStage *pixel) : love::graphics::Shader(vertex, pixel) , program(0) @@ -69,7 +74,7 @@ Shader::~Shader() delete[] p.second.textures; } - else if (p.second.baseType == UNIFORM_TEXELBUFFER) + else if (isBuffer(p.second.baseType)) { for (int i = 0; i < p.second.count; i++) { @@ -195,7 +200,7 @@ void Shader::mapActiveUniforms() u.data = malloc(u.dataSize); break; case UNIFORM_MATRIX: - u.dataSize = sizeof(float) * (u.matrix.rows * u.matrix.columns) * u.count; + u.dataSize = sizeof(float) * ((size_t)u.matrix.rows * u.matrix.columns) * u.count; u.data = malloc(u.dataSize); break; default: @@ -267,7 +272,7 @@ void Shader::mapActiveUniforms() break; case UNIFORM_MATRIX: glGetUniformfv(program, location, &u.floats[offset]); - offset += u.matrix.rows * u.matrix.columns; + offset += (size_t)u.matrix.rows * u.matrix.columns; break; default: break; @@ -310,13 +315,90 @@ void Shader::mapActiveUniforms() } } + if (gl.isBufferTypeSupported(BUFFERTYPE_SHADER_STORAGE)) + { + GLint numstoragebuffers = 0; + glGetProgramInterfaceiv(program, GL_SHADER_STORAGE_BLOCK, GL_ACTIVE_RESOURCES, &numstoragebuffers); + + char namebuffer[2048] = { '\0' }; + + for (int sindex = 0; sindex < numstoragebuffers; sindex++) + { + UniformInfo u = {}; + u.baseType = UNIFORM_STORAGEBUFFER; + + GLsizei namelength = 0; + glGetProgramResourceName(program, GL_SHADER_STORAGE_BLOCK, sindex, 2048, &namelength, namebuffer); + + u.name = std::string(namebuffer, namelength); + u.count = 1; + + const auto reflectionit = validationReflection.storageBuffers.find(u.name); + if (reflectionit != validationReflection.storageBuffers.end()) + { + u.bufferStride = reflectionit->second.stride; + u.bufferMemberCount = reflectionit->second.memberCount; + } + + // Make sure previously set uniform data is preserved, and shader- + // initialized values are retrieved. + auto oldu = olduniforms.find(u.name); + if (oldu != olduniforms.end()) + { + u.data = oldu->second.data; + u.dataSize = oldu->second.dataSize; + u.buffers = oldu->second.buffers; + } + else + { + u.dataSize = sizeof(int) * 1; + u.data = malloc(u.dataSize); + + u.ints[0] = -1; + + u.buffers = new love::graphics::Buffer * [u.count]; + memset(u.buffers, 0, sizeof(Buffer*)* u.count); + } + + GLenum props[] = { GL_BUFFER_BINDING }; + glGetProgramResourceiv(program, GL_SHADER_STORAGE_BLOCK, sindex, 1, props, 1, nullptr, u.ints); + + BufferBinding binding; + binding.bindingindex = u.ints[0]; + binding.buffer = gl.getDefaultStorageBuffer(); + + if (binding.bindingindex >= 0) + { + int activeindex = (int)activeStorageBufferBindings.size(); + + storageBufferBindingIndexToActiveBinding[binding.bindingindex] = activeindex; + + activeStorageBufferBindings.push_back(binding); + } + + uniforms[u.name] = u; + + for (int i = 0; i < u.count; i++) + { + if (u.buffers[i] == nullptr) + continue; + Volatile* v = dynamic_cast(u.buffers[i]); + if (v != nullptr) + v->loadVolatile(); + } + + sendBuffers(&u, u.buffers, u.count, true); + } + } + // Make sure uniforms that existed before but don't exist anymore are // cleaned up. This theoretically shouldn't happen, but... for (const auto &p : olduniforms) { if (uniforms.find(p.first) == uniforms.end()) { - free(p.second.data); + if (p.second.data != nullptr) + free(p.second.data); if (p.second.baseType == UNIFORM_SAMPLER) { @@ -328,7 +410,7 @@ void Shader::mapActiveUniforms() delete[] p.second.textures; } - else if (p.second.baseType == UNIFORM_TEXELBUFFER) + else if (isBuffer(p.second.baseType)) { for (int i = 0; i < p.second.count; i++) { @@ -354,6 +436,9 @@ bool Shader::loadVolatile() textureUnits.clear(); textureUnits.push_back(TextureUnit()); + storageBufferBindingIndexToActiveBinding.resize(gl.getMaxShaderStorageBufferBindings(), -1); + activeStorageBufferBindings.clear(); + for (const auto &stage : stages) { if (stage.get() != nullptr) @@ -500,6 +585,9 @@ void Shader::attach() } } + for (auto bufferbinding : activeStorageBufferBindings) + gl.bindIndexedBuffer(bufferbinding.buffer, BUFFERTYPE_SHADER_STORAGE, bufferbinding.bindingindex); + // send any pending uniforms to the shader program. for (const auto &p : pendingUniformUpdates) updateUniform(p.first, p.second, true); @@ -722,10 +810,18 @@ static bool isTexelBufferTypeCompatible(DataBaseType a, DataBaseType b) void Shader::sendBuffers(const UniformInfo *info, love::graphics::Buffer **buffers, int count, bool internalUpdate) { - if (info->baseType != UNIFORM_TEXELBUFFER) - return; + uint32 requiredtypeflags = 0; - uint32 requiredtypeflags = Buffer::TYPEFLAG_TEXEL; + bool texelbinding = info->baseType == UNIFORM_TEXELBUFFER; + bool storagebinding = info->baseType == UNIFORM_STORAGEBUFFER; + + if (texelbinding) + requiredtypeflags = Buffer::TYPEFLAG_TEXEL; + else if (storagebinding) + requiredtypeflags = Buffer::TYPEFLAG_SHADER_STORAGE; + + if (requiredtypeflags == 0) + return; bool shaderactive = current == this; @@ -745,17 +841,43 @@ void Shader::sendBuffers(const UniformInfo *info, love::graphics::Buffer **buffe { if (internalUpdate) continue; - else + else if (texelbinding) throw love::Exception("Shader uniform '%s' is a texel buffer, but the given Buffer was not created with texel buffer capabilities.", info->name.c_str()); + else if (storagebinding) + throw love::Exception("Shader uniform '%s' is a shader storage buffer block, but the given Buffer was not created with shader storage buffer capabilities.", info->name.c_str()); + else + throw love::Exception("Shader uniform '%s' does not match the types supported by the given Buffer.", info->name.c_str()); } - DataBaseType basetype = buffer->getDataMember(0).info.baseType; - if (!isTexelBufferTypeCompatible(basetype, info->texelBufferType)) + if (texelbinding) { - if (internalUpdate) - continue; - else - throw love::Exception("Texel buffer's data format base type must match the variable declared in the shader."); + DataBaseType basetype = buffer->getDataMember(0).info.baseType; + if (!isTexelBufferTypeCompatible(basetype, info->texelBufferType)) + { + if (internalUpdate) + continue; + else + throw love::Exception("Texel buffer's data format base type must match the variable declared in the shader."); + } + } + else if (storagebinding) + { + if (info->bufferStride != buffer->getArrayStride()) + { + if (internalUpdate) + continue; + else + throw love::Exception("Shader storage block '%s' has an array stride of %d bytes, but the given Buffer has an array stride of %d bytes.", + info->name.c_str(), info->bufferStride, buffer->getArrayStride()); + } + else if (info->bufferMemberCount != buffer->getDataMembers().size()) + { + if (internalUpdate) + continue; + else + throw love::Exception("Shader storage block '%s' has a struct with %d fields, but the given Buffer has a format with %d members.", + info->name.c_str(), info->bufferMemberCount, buffer->getDataMembers().size()); + } } buffer->retain(); @@ -766,19 +888,39 @@ void Shader::sendBuffers(const UniformInfo *info, love::graphics::Buffer **buffe info->buffers[i] = buffer; - GLuint gltex = 0; - if (buffers[i] != nullptr) - gltex = (GLuint) buffer->getTexelBufferHandle(); - else - gltex = gl.getDefaultTexelBuffer(); + if (texelbinding) + { + GLuint gltex = 0; + if (buffers[i] != nullptr) + gltex = (GLuint) buffer->getTexelBufferHandle(); + else + gltex = gl.getDefaultTexelBuffer(); - int texunit = info->ints[i]; + int texunit = info->ints[i]; - if (shaderactive) - gl.bindBufferTextureToUnit(gltex, texunit, false, false); + if (shaderactive) + gl.bindBufferTextureToUnit(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; + } + else if (storagebinding) + { + int bindingindex = info->ints[i]; + + GLuint glbuffer = 0; + if (buffers[i] != nullptr) + glbuffer = (GLuint) buffer->getHandle(); + else + glbuffer = gl.getDefaultStorageBuffer(); + + if (shaderactive) + gl.bindIndexedBuffer(glbuffer, BUFFERTYPE_SHADER_STORAGE, bindingindex); + + int activeindex = storageBufferBindingIndexToActiveBinding[bindingindex]; + if (activeindex >= 0) + activeStorageBufferBindings[activeindex].buffer = glbuffer; + } } } diff --git a/src/modules/graphics/opengl/Shader.h b/src/modules/graphics/opengl/Shader.h index 393c7fb6c..2f215c687 100644 --- a/src/modules/graphics/opengl/Shader.h +++ b/src/modules/graphics/opengl/Shader.h @@ -80,6 +80,12 @@ private: bool active = false; }; + struct BufferBinding + { + int bindingindex = 0; + GLuint buffer = 0; + }; + // Map active uniform names to their locations. void mapActiveUniforms(); @@ -117,6 +123,9 @@ private: // Texture unit pool for setting textures std::vector textureUnits; + std::vector storageBufferBindingIndexToActiveBinding; + std::vector activeStorageBufferBindings; + std::vector> pendingUniformUpdates; float lastPointSize; diff --git a/src/modules/graphics/vertex.cpp b/src/modules/graphics/vertex.cpp index 15d16891e..d448b9895 100644 --- a/src/modules/graphics/vertex.cpp +++ b/src/modules/graphics/vertex.cpp @@ -339,9 +339,10 @@ const char *getConstant(BuiltinVertexAttribute attrib) STRINGMAP_BEGIN(BufferType, BUFFERTYPE_MAX_ENUM, bufferTypeName) { - { "vertex", BUFFERTYPE_VERTEX }, - { "index", BUFFERTYPE_INDEX }, - { "texel", BUFFERTYPE_TEXEL }, + { "vertex", BUFFERTYPE_VERTEX }, + { "index", BUFFERTYPE_INDEX }, + { "texel", BUFFERTYPE_TEXEL }, + { "shaderstorage", BUFFERTYPE_SHADER_STORAGE }, } STRINGMAP_END(BufferType, BUFFERTYPE_MAX_ENUM, bufferTypeName) diff --git a/src/modules/graphics/vertex.h b/src/modules/graphics/vertex.h index f1479ccf8..21ff230ce 100644 --- a/src/modules/graphics/vertex.h +++ b/src/modules/graphics/vertex.h @@ -59,6 +59,7 @@ enum BufferType BUFFERTYPE_VERTEX = 0, BUFFERTYPE_INDEX, BUFFERTYPE_TEXEL, + BUFFERTYPE_SHADER_STORAGE, BUFFERTYPE_MAX_ENUM }; diff --git a/src/modules/graphics/wrap_Shader.cpp b/src/modules/graphics/wrap_Shader.cpp index ad73f5c91..bf7a3a8e1 100644 --- a/src/modules/graphics/wrap_Shader.cpp +++ b/src/modules/graphics/wrap_Shader.cpp @@ -278,8 +278,6 @@ int w_Shader_sendTextures(lua_State *L, int startidx, Shader *shader, const Shad for (int i = 0; i < count; i++) { Texture *tex = luax_checktexture(L, startidx + i); - if (tex->getTextureType() != info->textureType) - return luaL_argerror(L, startidx + i, "invalid texture type for uniform"); textures.push_back(tex); } @@ -321,6 +319,7 @@ static int w_Shader_sendLuaValues(lua_State *L, int startidx, Shader *shader, co case Shader::UNIFORM_SAMPLER: return w_Shader_sendTextures(L, startidx, shader, info); case Shader::UNIFORM_TEXELBUFFER: + case Shader::UNIFORM_STORAGEBUFFER: return w_Shader_sendBuffers(L, startidx, shader, info); default: return luaL_error(L, "Unknown variable type for shader uniform '%s", name); @@ -329,8 +328,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) - return luaL_error(L, "Uniform sampler values (textures) cannot be sent to Shaders via Data objects."); + if (info->baseType == Shader::UNIFORM_SAMPLER || 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; int dataidx = startidx;