diff --git a/src/modules/graphics/Buffer.cpp b/src/modules/graphics/Buffer.cpp index 6f4996d10..6124e515d 100644 --- a/src/modules/graphics/Buffer.cpp +++ b/src/modules/graphics/Buffer.cpp @@ -43,13 +43,18 @@ Buffer::Buffer(Graphics *gfx, const Settings &settings, const std::vectorgetCapabilities().features[Graphics::FEATURE_GLSL3]; + const auto &caps = gfx->getCapabilities(); + bool supportsGLSL3 = caps.features[Graphics::FEATURE_GLSL3]; bool indexbuffer = settings.typeFlags & TYPEFLAG_INDEX; bool vertexbuffer = settings.typeFlags & TYPEFLAG_VERTEX; + bool texelbuffer = settings.typeFlags & TYPEFLAG_TEXEL; - if (!indexbuffer && !vertexbuffer) - throw love::Exception("Buffer must be created with at least one buffer type (index or vertex)."); + if (!indexbuffer && !vertexbuffer && !texelbuffer) + throw love::Exception("Buffer must be created with at least one buffer type (index, vertex, or texel)."); + + if (texelbuffer && !caps.features[Graphics::FEATURE_TEXEL_BUFFER]) + throw love::Exception("Texel buffers are not supported on this system."); size_t offset = 0; size_t stride = 0; @@ -68,6 +73,9 @@ Buffer::Buffer(Graphics *gfx, const Settings &settings, const std::vector 1) throw love::Exception("Index buffers only support a single value per element."); + + if (decl.arrayLength > 0) + throw love::Exception("Arrays are not supported in index buffers."); } if (vertexbuffer) @@ -83,6 +91,30 @@ Buffer::Buffer(Graphics *gfx, const Settings &settings, const std::vector 0) + throw love::Exception("Arrays are not supported in texel buffers."); + + if (info.isMatrix) + throw love::Exception("Matrix types are not supported in texel buffers."); + + if (info.baseType == DATA_BASETYPE_BOOL) + throw love::Exception("Bool types are not supported in texel buffers."); + + if (info.components == 3) + throw love::Exception("3-component formats are not supported in texel buffers."); + + if (info.baseType == DATA_BASETYPE_SNORM) + throw love::Exception("Signed normalized formats are not supported in texel buffers."); } // TODO: alignment @@ -111,6 +143,9 @@ Buffer::Buffer(Graphics *gfx, const Settings &settings, const std::vectorarrayStride = stride; this->arrayLength = arraylength; this->size = 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]); } Buffer::~Buffer() diff --git a/src/modules/graphics/Buffer.h b/src/modules/graphics/Buffer.h index 6411f9064..36f684702 100644 --- a/src/modules/graphics/Buffer.h +++ b/src/modules/graphics/Buffer.h @@ -60,6 +60,7 @@ public: TYPEFLAG_NONE = 0, TYPEFLAG_VERTEX = 1 << BUFFERTYPE_VERTEX, TYPEFLAG_INDEX = 1 << BUFFERTYPE_INDEX, + TYPEFLAG_TEXEL = 1 << BUFFERTYPE_TEXEL, }; struct DataDeclaration @@ -141,10 +142,6 @@ public: /** * Fill a portion of the buffer with data and marks the range as modified. - * - * @param offset The offset in the GLBuffer to store the data. - * @param size The size of the incoming data. - * @param data Pointer to memory to copy data from. */ virtual void fill(size_t offset, size_t size, const void *data) = 0; @@ -153,6 +150,12 @@ public: **/ virtual void copyTo(size_t offset, size_t size, Buffer *other, size_t otheroffset) = 0; + /** + * Texel buffers may use an additional texture handle as well as a buffer + * handle. + **/ + virtual ptrdiff_t getTexelBufferHandle() const = 0; + static std::vector getCommonFormatDeclaration(CommonFormat format); class Mapper diff --git a/src/modules/graphics/Graphics.cpp b/src/modules/graphics/Graphics.cpp index 7fbe0a7b5..001806e85 100644 --- a/src/modules/graphics/Graphics.cpp +++ b/src/modules/graphics/Graphics.cpp @@ -1932,6 +1932,7 @@ StringMap::Entry Graphics::featur { "glsl3", FEATURE_GLSL3 }, { "glsl4", FEATURE_GLSL4 }, { "instancing", FEATURE_INSTANCING }, + { "texelbuffer", FEATURE_TEXEL_BUFFER }, }; StringMap Graphics::features(Graphics::featureEntries, sizeof(Graphics::featureEntries)); @@ -1943,6 +1944,7 @@ StringMap::Entry Graphics::syst { "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 }, diff --git a/src/modules/graphics/Graphics.h b/src/modules/graphics/Graphics.h index 1a2f75640..d26ba929a 100644 --- a/src/modules/graphics/Graphics.h +++ b/src/modules/graphics/Graphics.h @@ -143,6 +143,7 @@ public: FEATURE_GLSL3, FEATURE_GLSL4, FEATURE_INSTANCING, + FEATURE_TEXEL_BUFFER, FEATURE_MAX_ENUM }; @@ -160,6 +161,7 @@ public: LIMIT_VOLUME_TEXTURE_SIZE, LIMIT_CUBE_TEXTURE_SIZE, LIMIT_TEXTURE_LAYERS, + LIMIT_TEXEL_BUFFER_SIZE, LIMIT_RENDER_TARGETS, LIMIT_TEXTURE_MSAA, LIMIT_ANISOTROPY, diff --git a/src/modules/graphics/Shader.h b/src/modules/graphics/Shader.h index f6b9b2a08..2c4717626 100644 --- a/src/modules/graphics/Shader.h +++ b/src/modules/graphics/Shader.h @@ -39,6 +39,7 @@ namespace graphics { class Graphics; +class Buffer; // A GLSL shader class Shader : public Object, public Resource @@ -76,6 +77,7 @@ public: UNIFORM_UINT, UNIFORM_BOOL, UNIFORM_SAMPLER, + UNIFORM_TEXELBUFFER, UNIFORM_UNKNOWN, UNIFORM_MAX_ENUM }; @@ -115,6 +117,7 @@ public: UniformType baseType; TextureType textureType; + DataBaseType texelBufferType; bool isDepthSampler; std::string name; @@ -128,7 +131,11 @@ public: size_t dataSize; - Texture **textures; + union + { + Texture **textures; + Buffer **buffers; + }; }; // The members in here must respect uniform buffer alignment/padding rules. @@ -178,6 +185,7 @@ public: virtual void updateUniform(const UniformInfo *info, int count) = 0; virtual void sendTextures(const UniformInfo *info, Texture **textures, int count) = 0; + virtual void sendBuffers(const UniformInfo *info, Buffer **buffers, int count) = 0; /** * Gets whether a uniform with the specified name exists and is actively diff --git a/src/modules/graphics/opengl/Buffer.cpp b/src/modules/graphics/opengl/Buffer.cpp index fbd8eae95..61eb6e61b 100644 --- a/src/modules/graphics/opengl/Buffer.cpp +++ b/src/modules/graphics/opengl/Buffer.cpp @@ -35,13 +35,45 @@ namespace graphics namespace opengl { +static GLenum getGLFormat(DataFormat format) +{ + switch (format) + { + case DATAFORMAT_FLOAT: return GL_R32F; + case DATAFORMAT_FLOAT_VEC2: return GL_RG32F; + case DATAFORMAT_FLOAT_VEC3: return GL_RGB32F; + case DATAFORMAT_FLOAT_VEC4: return GL_RGBA32F; + case DATAFORMAT_INT32: return GL_R32I; + case DATAFORMAT_INT32_VEC2: return GL_RG32I; + case DATAFORMAT_INT32_VEC3: return GL_RGB32I; + case DATAFORMAT_INT32_VEC4: return GL_RGBA32I; + case DATAFORMAT_UINT32: return GL_R32UI; + case DATAFORMAT_UINT32_VEC2: return GL_RG32UI; + case DATAFORMAT_UINT32_VEC3: return GL_RGB32UI; + case DATAFORMAT_UINT32_VEC4: return GL_RGBA32UI; + case DATAFORMAT_UNORM8_VEC4: return GL_RGBA8; + case DATAFORMAT_INT8_VEC4: return GL_RGBA8I; + case DATAFORMAT_UINT8_VEC4: return GL_RGBA8UI; + case DATAFORMAT_UNORM16_VEC2: return GL_RG16; + case DATAFORMAT_UNORM16_VEC4: return GL_RGBA16; + case DATAFORMAT_INT16_VEC2: return GL_RG16I; + case DATAFORMAT_INT16_VEC4: return GL_RGBA16I; + case DATAFORMAT_UINT16: return GL_R16UI; + case DATAFORMAT_UINT16_VEC2: return GL_RG16UI; + case DATAFORMAT_UINT16_VEC4: return GL_RGBA16UI; + default: return GL_ZERO; + } +} + Buffer::Buffer(love::graphics::Graphics *gfx, const Settings &settings, const std::vector &format, const void *data, size_t size, size_t arraylength) : love::graphics::Buffer(gfx, settings, format, size, arraylength) { size = getSize(); arraylength = getArrayLength(); - if (typeFlags & TYPEFLAG_VERTEX) + if (typeFlags & TYPEFLAG_TEXEL) + mapType = BUFFERTYPE_TEXEL; + else if (typeFlags & TYPEFLAG_VERTEX) mapType = BUFFERTYPE_VERTEX; else if (typeFlags & TYPEFLAG_INDEX) mapType = BUFFERTYPE_INDEX; @@ -62,8 +94,9 @@ Buffer::Buffer(love::graphics::Graphics *gfx, const Settings &settings, const st if (!load(data != nullptr)) { + unloadVolatile(); delete[] memoryMap; - throw love::Exception("Could not load vertex buffer (out of VRAM?)"); + throw love::Exception("Could not create buffer (out of VRAM?)"); } } @@ -75,31 +108,45 @@ Buffer::~Buffer() bool Buffer::loadVolatile() { + if (buffer != 0) + return true; + return load(true); } void Buffer::unloadVolatile() { mapped = false; - if (vbo != 0) - gl.deleteBuffer(vbo); - vbo = 0; + if (buffer != 0) + gl.deleteBuffer(buffer); + buffer = 0; + if (texture != 0) + gl.deleteTexture(texture); + texture = 0; } bool Buffer::load(bool restore) { - glGenBuffers(1, &vbo); - gl.bindBuffer(mapType, vbo); - while (glGetError() != GL_NO_ERROR) /* Clear the error buffer. */; + glGenBuffers(1, &buffer); + gl.bindBuffer(mapType, buffer); + // Copy the old buffer only if 'restore' was requested. const GLvoid *src = restore ? memoryMap : nullptr; // Note that if 'src' is '0', no data will be copied. glBufferData(target, (GLsizeiptr) getSize(), src, OpenGL::getGLBufferUsage(getUsage())); + if (getTypeFlags() & TYPEFLAG_TEXEL) + { + glGenTextures(1, &texture); + gl.bindBufferTextureToUnit(texture, 0, false, true); + + glTexBuffer(target, getGLFormat(getDataMember(0).decl.format), buffer); + } + return (glGetError() == GL_NO_ERROR); } @@ -123,7 +170,7 @@ void Buffer::unmapStatic(size_t offset, size_t size) return; // Upload the mapped data to the buffer. - gl.bindBuffer(mapType, vbo); + gl.bindBuffer(mapType, buffer); glBufferSubData(target, (GLintptr) offset, (GLsizeiptr) size, memoryMap + offset); } @@ -133,7 +180,7 @@ void Buffer::unmapStream() // "orphan" current buffer to avoid implicit synchronisation on the GPU: // http://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-AsynchronousBufferTransfers.pdf - gl.bindBuffer(mapType, vbo); + gl.bindBuffer(mapType, buffer); glBufferData(target, (GLsizeiptr) getSize(), nullptr, glusage); #if LOVE_WINDOWS @@ -224,16 +271,11 @@ void Buffer::fill(size_t offset, size_t size, const void *data) setMappedRangeModified(offset, size); else { - gl.bindBuffer(mapType, vbo); + gl.bindBuffer(mapType, buffer); glBufferSubData(target, (GLintptr) offset, (GLsizeiptr) size, data); } } -ptrdiff_t Buffer::getHandle() const -{ - return vbo; -} - void Buffer::copyTo(size_t offset, size_t size, love::graphics::Buffer *other, size_t otheroffset) { other->fill(otheroffset, size, memoryMap + offset); diff --git a/src/modules/graphics/opengl/Buffer.h b/src/modules/graphics/opengl/Buffer.h index 64fde2f3c..b0a96bb5f 100644 --- a/src/modules/graphics/opengl/Buffer.h +++ b/src/modules/graphics/opengl/Buffer.h @@ -53,7 +53,9 @@ public: void unmap() override; void setMappedRangeModified(size_t offset, size_t size) override; void fill(size_t offset, size_t size, const void *data) override; - ptrdiff_t getHandle() const override; + + ptrdiff_t getHandle() const override { return buffer; }; + ptrdiff_t getTexelBufferHandle() const override { return texture; }; void copyTo(size_t offset, size_t size, love::graphics::Buffer *other, size_t otheroffset) override; @@ -67,8 +69,11 @@ private: BufferType mapType = BUFFERTYPE_VERTEX; GLenum target = 0; - // The VBO identifier. Assigned by OpenGL. - GLuint vbo = 0; + // The buffer object identifier. Assigned by OpenGL. + GLuint buffer = 0; + + // Used for Texel Buffer types. + GLuint texture = 0; // A pointer to mapped memory. char *memoryMap = nullptr; diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index 77e4f49e1..8f6bfffb1 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/src/modules/graphics/opengl/Graphics.cpp @@ -91,6 +91,7 @@ static GLenum getGLBlendFactor(BlendFactor factor) Graphics::Graphics() : windowHasStencil(false) , mainVAO(0) + , defaultBuffers() , supportedFormats() { gl = OpenGL(); @@ -243,6 +244,27 @@ bool Graphics::setMode(int width, int height, int pixelwidth, int pixelheight, b batchedDrawState.indexBuffer = CreateStreamBuffer(BUFFERTYPE_INDEX, sizeof(uint16) * LOVE_UINT16_MAX); } + if (capabilities.features[FEATURE_TEXEL_BUFFER] && defaultBuffers[BUFFERTYPE_TEXEL].get() == nullptr) + { + Buffer::Settings settings(Buffer::TYPEFLAG_TEXEL, 0, BUFFERUSAGE_STATIC); + std::vector format = {{"", DATAFORMAT_FLOAT_VEC4, 0}}; + + const float texel[] = {0.0f, 0.0f, 0.0f, 1.0f}; + + love::graphics::Buffer *buffer = newBuffer(settings, format, texel, sizeof(texel), 1); + defaultBuffers[BUFFERTYPE_TEXEL].set(buffer, Acquire::NORETAIN); + } + + // Load default resources before other Volatile. + for (int i = 0; i < BUFFERTYPE_MAX_ENUM; i++) + { + if (defaultBuffers[i].get()) + ((Buffer *) defaultBuffers[i].get())->loadVolatile(); + } + + if (defaultBuffers[BUFFERTYPE_TEXEL].get()) + gl.setDefaultTexelBuffer((GLuint) defaultBuffers[BUFFERTYPE_TEXEL]->getTexelBufferHandle()); + // Reload all volatile objects. if (!Volatile::loadAll()) ::printf("Could not reload all volatile objects.\n"); @@ -1366,17 +1388,19 @@ 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(); - static_assert(FEATURE_MAX_ENUM == 10, "Graphics::initCapabilities must be updated when adding a new graphics feature!"); + capabilities.features[FEATURE_TEXEL_BUFFER] = gl.areTexelBuffersSupported(); + static_assert(FEATURE_MAX_ENUM == 11, "Graphics::initCapabilities must be updated when adding a new graphics feature!"); capabilities.limits[LIMIT_POINT_SIZE] = gl.getMaxPointSize(); capabilities.limits[LIMIT_TEXTURE_SIZE] = gl.getMax2DTextureSize(); capabilities.limits[LIMIT_TEXTURE_LAYERS] = gl.getMaxTextureLayers(); 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_RENDER_TARGETS] = gl.getMaxRenderTargets(); capabilities.limits[LIMIT_TEXTURE_MSAA] = gl.getMaxSamples(); capabilities.limits[LIMIT_ANISOTROPY] = gl.getMaxAnisotropy(); - static_assert(LIMIT_MAX_ENUM == 8, "Graphics::initCapabilities must be updated when adding a new system limit!"); + static_assert(LIMIT_MAX_ENUM == 9, "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/Graphics.h b/src/modules/graphics/opengl/Graphics.h index d23934eb9..62b0ae71f 100644 --- a/src/modules/graphics/opengl/Graphics.h +++ b/src/modules/graphics/opengl/Graphics.h @@ -147,6 +147,9 @@ private: bool windowHasStencil; GLuint mainVAO; + // Only needed for buffer types that can be bound to shaders. + StrongRef defaultBuffers[BUFFERTYPE_MAX_ENUM]; + // [rendertarget][readable][srgb] OptionalBool supportedFormats[PIXELFORMAT_MAX_ENUM][2][2][2]; diff --git a/src/modules/graphics/opengl/OpenGL.cpp b/src/modules/graphics/opengl/OpenGL.cpp index 8fb8a497b..d3ac04fd2 100644 --- a/src/modules/graphics/opengl/OpenGL.cpp +++ b/src/modules/graphics/opengl/OpenGL.cpp @@ -229,7 +229,7 @@ void OpenGL::setupContext() } // Initialize multiple texture unit support for shaders. - for (int i = 0; i < TEXTURE_MAX_ENUM; i++) + for (int i = 0; i < TEXTURE_MAX_ENUM + 1; i++) { state.boundTextures[i].clear(); state.boundTextures[i].resize(maxTextureUnits, 0); @@ -280,6 +280,10 @@ void OpenGL::deInitContext() } } + if (state.defaultTexelBuffer != 0) + gl.deleteTexture(state.defaultTexelBuffer); + state.defaultTexelBuffer = 0; + contextInitialized = false; } @@ -459,6 +463,11 @@ void OpenGL::initMaxValues() else maxTextureArrayLayers = 0; + if (areTexelBuffersSupported()) + glGetIntegerv(GL_MAX_TEXTURE_BUFFER_SIZE, &maxTexelBufferSize); + else + maxTexelBufferSize = 0; + int maxattachments = 1; int maxdrawbuffers = 1; @@ -578,6 +587,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_MAX_ENUM: return GL_ZERO; } @@ -592,7 +602,7 @@ GLenum OpenGL::getGLTextureType(TextureType type) case TEXTURE_VOLUME: return GL_TEXTURE_3D; case TEXTURE_2D_ARRAY: return GL_TEXTURE_2D_ARRAY; case TEXTURE_CUBE: return GL_TEXTURE_CUBE_MAP; - case TEXTURE_MAX_ENUM: return GL_ZERO; + case TEXTURE_MAX_ENUM: return GL_TEXTURE_BUFFER; // Hack } return GL_ZERO; @@ -1084,6 +1094,11 @@ void OpenGL::bindTextureToUnit(TextureType target, GLuint texture, int textureun } } +void OpenGL::bindBufferTextureToUnit(GLuint texture, int textureunit, bool restoreprev, bool bindforedit) +{ + bindTextureToUnit(TEXTURE_MAX_ENUM, texture, textureunit, restoreprev, bindforedit); +} + void OpenGL::bindTextureToUnit(Texture *texture, int textureunit, bool restoreprev, bool bindforedit) { TextureType textype = TEXTURE_2D; @@ -1113,7 +1128,7 @@ void OpenGL::deleteTexture(GLuint texture) { // glDeleteTextures binds texture 0 to all texture units the deleted texture // was bound to before deletion. - for (int i = 0; i < TEXTURE_MAX_ENUM; i++) + for (int i = 0; i < TEXTURE_MAX_ENUM + 1; i++) { for (GLuint &texid : state.boundTextures[i]) { @@ -1402,6 +1417,12 @@ 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); @@ -1422,6 +1443,11 @@ int OpenGL::getMaxTextureLayers() const return std::max(maxTextureArrayLayers, 1); } +int OpenGL::getMaxTexelBufferSize() const +{ + return maxTexelBufferSize; +} + int OpenGL::getMaxRenderTargets() const { return std::min(maxRenderTargets, MAX_COLOR_RENDER_TARGETS); diff --git a/src/modules/graphics/opengl/OpenGL.h b/src/modules/graphics/opengl/OpenGL.h index a16d20313..2ae6d13f4 100644 --- a/src/modules/graphics/opengl/OpenGL.h +++ b/src/modules/graphics/opengl/OpenGL.h @@ -306,6 +306,12 @@ public: **/ GLuint getDefaultTexture(TextureType type) const; + /** + * Gets the texture ID for love's default texel buffer. + **/ + GLuint getDefaultTexelBuffer() const { return state.defaultTexelBuffer; } + void setDefaultTexelBuffer(GLuint tex) { state.defaultTexelBuffer = tex; } + /** * Helper for setting the active texture unit. * @@ -323,6 +329,8 @@ public: void bindTextureToUnit(TextureType target, GLuint texture, int textureunit, bool restoreprev, bool bindforedit = true); void bindTextureToUnit(Texture *texture, int textureunit, bool restoreprev, bool bindforedit = true); + void bindBufferTextureToUnit(GLuint texture, int textureunit, bool restoreprev, bool bindforedit); + /** * Helper for deleting an OpenGL texture. * Cleans up if the texture is currently bound. @@ -349,6 +357,7 @@ public: bool isSamplerLODBiasSupported() const; bool isBaseVertexSupported() const; bool isMultiFormatMRTSupported() const; + bool areTexelBuffersSupported() const; /** * Returns the maximum supported width or height of a texture. @@ -358,6 +367,11 @@ public: int getMaxCubeTextureSize() const; int getMaxTextureLayers() const; + /** + * Returns the maximum number of values in a texel buffer. + **/ + int getMaxTexelBufferSize() const; + /** * Returns the maximum supported number of simultaneous render targets. **/ @@ -436,6 +450,7 @@ private: int max3DTextureSize; int maxCubeTextureSize; int maxTextureArrayLayers; + int maxTexelBufferSize; int maxRenderTargets; int maxSamples; int maxTextureUnits; @@ -451,7 +466,7 @@ private: GLuint boundBuffers[BUFFERTYPE_MAX_ENUM]; // Texture unit state (currently bound texture for each texture unit.) - std::vector boundTextures[TEXTURE_MAX_ENUM]; + std::vector boundTextures[TEXTURE_MAX_ENUM + 1]; bool enableState[ENABLE_MAX_ENUM]; @@ -472,6 +487,7 @@ private: GLuint boundFramebuffers[2]; GLuint defaultTexture[TEXTURE_MAX_ENUM]; + GLuint defaultTexelBuffer; } state; diff --git a/src/modules/graphics/opengl/Shader.cpp b/src/modules/graphics/opengl/Shader.cpp index f93f126b9..ed5256f10 100644 --- a/src/modules/graphics/opengl/Shader.cpp +++ b/src/modules/graphics/opengl/Shader.cpp @@ -69,6 +69,16 @@ Shader::~Shader() delete[] p.second.textures; } + else if (p.second.baseType == UNIFORM_TEXELBUFFER) + { + for (int i = 0; i < p.second.count; i++) + { + if (p.second.buffers[i] != nullptr) + p.second.buffers[i]->release(); + } + + delete[] p.second.buffers; + } } } @@ -107,6 +117,7 @@ void Shader::mapActiveUniforms() u.location = glGetUniformLocation(program, u.name.c_str()); u.baseType = getUniformBaseType(gltype); u.textureType = getUniformTextureType(gltype); + u.texelBufferType = getUniformTexelBufferType(gltype); u.isDepthSampler = isDepthTextureType(gltype); if (u.baseType == UNIFORM_MATRIX) @@ -130,12 +141,22 @@ void Shader::mapActiveUniforms() if (u.location == -1) continue; - if (u.baseType == UNIFORM_SAMPLER && builtin != BUILTIN_TEXTURE_MAIN) + if ((u.baseType == UNIFORM_SAMPLER && builtin != BUILTIN_TEXTURE_MAIN) || u.baseType == UNIFORM_TEXELBUFFER) { TextureUnit unit; unit.type = u.textureType; unit.active = true; - unit.texture = gl.getDefaultTexture(u.textureType); + + if (u.baseType == UNIFORM_TEXELBUFFER) + { + unit.isTexelBuffer = true; + unit.texture = gl.getDefaultTexelBuffer(); + } + else + { + unit.isTexelBuffer = false; + unit.texture = gl.getDefaultTexture(u.textureType); + } for (int i = 0; i < u.count; i++) textureUnits.push_back(unit); @@ -165,6 +186,7 @@ void Shader::mapActiveUniforms() case UNIFORM_INT: case UNIFORM_BOOL: case UNIFORM_SAMPLER: + case UNIFORM_TEXELBUFFER: u.dataSize = sizeof(int) * u.components * u.count; u.data = malloc(u.dataSize); break; @@ -184,7 +206,7 @@ void Shader::mapActiveUniforms() { memset(u.data, 0, u.dataSize); - if (u.baseType == UNIFORM_SAMPLER) + if (u.baseType == UNIFORM_SAMPLER || u.baseType == UNIFORM_TEXELBUFFER) { int startunit = (int) textureUnits.size() - u.count; @@ -196,8 +218,16 @@ void Shader::mapActiveUniforms() glUniform1iv(u.location, u.count, u.ints); - u.textures = new love::graphics::Texture*[u.count]; - memset(u.textures, 0, sizeof(Texture *) * u.count); + if (u.baseType == UNIFORM_TEXELBUFFER) + { + u.buffers = new love::graphics::Buffer*[u.count]; + memset(u.buffers, 0, sizeof(Buffer *) * u.count); + } + else + { + u.textures = new love::graphics::Texture*[u.count]; + memset(u.textures, 0, sizeof(Texture *) * u.count); + } } } @@ -258,7 +288,6 @@ void Shader::mapActiveUniforms() { if (u.textures[i] == nullptr) continue; - Volatile *v = dynamic_cast(u.textures[i]); if (v != nullptr) v->loadVolatile(); @@ -266,6 +295,19 @@ void Shader::mapActiveUniforms() sendTextures(&u, u.textures, u.count, true); } + else if (u.baseType == UNIFORM_TEXELBUFFER) + { + 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 @@ -276,16 +318,26 @@ void Shader::mapActiveUniforms() { free(p.second.data); - if (p.second.baseType != UNIFORM_SAMPLER) - continue; - - for (int i = 0; i < p.second.count; i++) + if (p.second.baseType == UNIFORM_SAMPLER) { - if (p.second.textures[i] != nullptr) - p.second.textures[i]->release(); - } + for (int i = 0; i < p.second.count; i++) + { + if (p.second.textures[i] != nullptr) + p.second.textures[i]->release(); + } - delete[] p.second.textures; + delete[] p.second.textures; + } + else if (p.second.baseType == UNIFORM_TEXELBUFFER) + { + for (int i = 0; i < p.second.count; i++) + { + if (p.second.buffers[i] != nullptr) + p.second.buffers[i]->release(); + } + + delete[] p.second.buffers; + } } } @@ -440,7 +492,12 @@ void Shader::attach() { const TextureUnit &unit = textureUnits[i]; if (unit.active) - gl.bindTextureToUnit(unit.type, unit.texture, i, false, false); + { + if (unit.isTexelBuffer) + gl.bindBufferTextureToUnit(unit.texture, i, false, false); + else + gl.bindTextureToUnit(unit.type, unit.texture, i, false, false); + } } // send any pending uniforms to the shader program. @@ -503,7 +560,7 @@ void Shader::updateUniform(const UniformInfo *info, int count, bool internalupda break; } } - else if (type == UNIFORM_INT || type == UNIFORM_BOOL || type == UNIFORM_SAMPLER) + else if (type == UNIFORM_INT || type == UNIFORM_BOOL || type == UNIFORM_SAMPLER || type == UNIFORM_TEXELBUFFER) { switch (info->components) { @@ -644,6 +701,109 @@ void Shader::sendTextures(const UniformInfo *info, love::graphics::Texture **tex } } +void Shader::sendBuffers(const UniformInfo *info, love::graphics::Buffer **buffers, int count) +{ + Shader::sendBuffers(info, buffers, count, false); +} + +static bool isTexelBufferTypeCompatible(DataBaseType a, DataBaseType b) +{ + if (a == DATA_BASETYPE_FLOAT || a == DATA_BASETYPE_UNORM || a == DATA_BASETYPE_SNORM) + return b == DATA_BASETYPE_FLOAT || b == DATA_BASETYPE_UNORM || b == DATA_BASETYPE_SNORM; + + if (a == DATA_BASETYPE_INT && b == DATA_BASETYPE_INT) + return true; + + if (a == DATA_BASETYPE_UINT && b == DATA_BASETYPE_UINT) + return true; + + return false; +} + +void Shader::sendBuffers(const UniformInfo *info, love::graphics::Buffer **buffers, int count, bool internalUpdate) +{ + if (info->baseType != UNIFORM_TEXELBUFFER) + return; + + uint32 requiredtypeflags = Buffer::TYPEFLAG_TEXEL; + + bool shaderactive = current == this; + + if (!internalUpdate && shaderactive) + flushBatchedDraws(); + + count = std::min(count, info->count); + + // Bind the textures to the texture units. + for (int i = 0; i < count; i++) + { + love::graphics::Buffer *buffer = buffers[i]; + + if (buffer != nullptr) + { + if ((buffer->getTypeFlags() & requiredtypeflags) == 0) + { + if (internalUpdate) + continue; + else + 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()); + } + + 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."); + } + + buffer->retain(); + } + + bool addbuffertoarray = true; + + if (info->buffers[i] != nullptr) + { + Buffer *oldbuffer = info->buffers[i]; + auto it = std::find(buffersToUnmap.begin(), buffersToUnmap.end(), oldbuffer); + if (it != buffersToUnmap.end()) + { + addbuffertoarray = false; + if (buffer != nullptr) + *it = buffer; + else + { + auto last = buffersToUnmap.end() - 1; + *it = *last; + buffersToUnmap.erase(last); + } + } + + oldbuffer->release(); + } + + if (addbuffertoarray && buffer != nullptr) + buffersToUnmap.push_back(buffer); + + info->buffers[i] = buffer; + + GLuint gltex = 0; + if (buffers[i] != nullptr) + gltex = (GLuint) buffer->getTexelBufferHandle(); + else + gltex = gl.getDefaultTexelBuffer(); + + int texunit = info->ints[i]; + + 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; + } +} + void Shader::flushBatchedDraws() const { if (current == this) @@ -756,11 +916,20 @@ void Shader::updateBuiltinUniforms(love::graphics::Graphics *gfx, int viewportW, GLint location = builtinUniforms[BUILTIN_UNIFORMS_PER_DRAW]; if (location >= 0) glUniform4fv(location, 13, (const GLfloat *) &data); + + // TODO: Find a better place to put this. + // Buffers used in this shader can be mapped by external code without + // unmapping. We need to make sure the data on the GPU is up to date, + // otherwise the shader can read from old data. + for (Buffer *buffer : buffersToUnmap) + buffer->unmap(); } int Shader::getUniformTypeComponents(GLenum type) const { - if (getUniformBaseType(type) == UNIFORM_SAMPLER) + UniformType basetype = getUniformBaseType(type); + + if (basetype == UNIFORM_SAMPLER || basetype == UNIFORM_TEXELBUFFER) return 1; switch (type) @@ -889,6 +1058,10 @@ Shader::UniformType Shader::getUniformBaseType(GLenum type) const case GL_SAMPLER_CUBE_MAP_ARRAY: case GL_SAMPLER_CUBE_MAP_ARRAY_SHADOW: return UNIFORM_SAMPLER; + case GL_SAMPLER_BUFFER: + case GL_INT_SAMPLER_BUFFER: + case GL_UNSIGNED_INT_SAMPLER_BUFFER: + return UNIFORM_TEXELBUFFER; default: return UNIFORM_UNKNOWN; } @@ -932,6 +1105,21 @@ TextureType Shader::getUniformTextureType(GLenum type) const } } +DataBaseType Shader::getUniformTexelBufferType(GLenum type) const +{ + switch (type) + { + case GL_SAMPLER_BUFFER: + return DATA_BASETYPE_FLOAT; + case GL_INT_SAMPLER_BUFFER: + return DATA_BASETYPE_INT; + case GL_UNSIGNED_INT_SAMPLER_BUFFER: + return DATA_BASETYPE_UINT; + default: + return DATA_BASETYPE_MAX_ENUM; + } +} + bool Shader::isDepthTextureType(GLenum type) const { switch (type) diff --git a/src/modules/graphics/opengl/Shader.h b/src/modules/graphics/opengl/Shader.h index 868563970..10591744e 100644 --- a/src/modules/graphics/opengl/Shader.h +++ b/src/modules/graphics/opengl/Shader.h @@ -62,6 +62,7 @@ public: const UniformInfo *getUniformInfo(BuiltinUniform builtin) const override; void updateUniform(const UniformInfo *info, int count) override; void sendTextures(const UniformInfo *info, love::graphics::Texture **textures, int count) override; + void sendBuffers(const UniformInfo *info, love::graphics::Buffer **buffers, int count) override; bool hasUniform(const std::string &name) const override; ptrdiff_t getHandle() const override; void setVideoTextures(love::graphics::Texture *ytexture, love::graphics::Texture *cbtexture, love::graphics::Texture *crtexture) override; @@ -75,6 +76,7 @@ private: { GLuint texture = 0; TextureType type = TEXTURE_2D; + bool isTexelBuffer = false; bool active = false; }; @@ -83,11 +85,13 @@ private: void updateUniform(const UniformInfo *info, int count, bool internalupdate); void sendTextures(const UniformInfo *info, love::graphics::Texture **textures, int count, bool internalupdate); + void sendBuffers(const UniformInfo *info, love::graphics::Buffer **buffers, int count, bool internalupdate); int getUniformTypeComponents(GLenum type) const; MatrixSize getMatrixSize(GLenum type) const; UniformType getUniformBaseType(GLenum type) const; TextureType getUniformTextureType(GLenum type) const; + DataBaseType getUniformTexelBufferType(GLenum type) const; bool isDepthTextureType(GLenum type) const; void flushBatchedDraws() const; @@ -115,6 +119,8 @@ private: std::vector> pendingUniformUpdates; + std::vector buffersToUnmap; + float lastPointSize; }; // Shader diff --git a/src/modules/graphics/vertex.cpp b/src/modules/graphics/vertex.cpp index ffc3e448c..15d16891e 100644 --- a/src/modules/graphics/vertex.cpp +++ b/src/modules/graphics/vertex.cpp @@ -341,6 +341,7 @@ STRINGMAP_BEGIN(BufferType, BUFFERTYPE_MAX_ENUM, bufferTypeName) { { "vertex", BUFFERTYPE_VERTEX }, { "index", BUFFERTYPE_INDEX }, + { "texel", BUFFERTYPE_TEXEL }, } STRINGMAP_END(BufferType, BUFFERTYPE_MAX_ENUM, bufferTypeName) diff --git a/src/modules/graphics/vertex.h b/src/modules/graphics/vertex.h index 5dd74ce47..f1479ccf8 100644 --- a/src/modules/graphics/vertex.h +++ b/src/modules/graphics/vertex.h @@ -58,6 +58,7 @@ enum BufferType { BUFFERTYPE_VERTEX = 0, BUFFERTYPE_INDEX, + BUFFERTYPE_TEXEL, BUFFERTYPE_MAX_ENUM }; diff --git a/src/modules/graphics/wrap_Buffer.cpp b/src/modules/graphics/wrap_Buffer.cpp index 78813170b..fe61b39ea 100644 --- a/src/modules/graphics/wrap_Buffer.cpp +++ b/src/modules/graphics/wrap_Buffer.cpp @@ -315,7 +315,7 @@ static int w_Buffer_setElement(lua_State *L) size_t index = (size_t) (luaL_checkinteger(L, 2) - 1); if (index >= t->getArrayLength()) - return luaL_error(L, "Invalid Buffer element index: %ld", index); + return luaL_error(L, "Invalid Buffer element index: %d", (int) index + 1); size_t stride = t->getArrayStride(); size_t offset = index * stride; @@ -361,7 +361,7 @@ static int w_Buffer_getElement(lua_State *L) size_t index = (size_t) (luaL_checkinteger(L, 2) - 1); if (index >= t->getArrayLength()) - return luaL_error(L, "Invalid Buffer element index: %ld", index); + return luaL_error(L, "Invalid Buffer element index: %d", (int) index + 1); size_t offset = index * t->getArrayStride(); const char *data = (const char *) t->map() + offset; diff --git a/src/modules/graphics/wrap_Graphics.cpp b/src/modules/graphics/wrap_Graphics.cpp index f411028be..b20f34148 100644 --- a/src/modules/graphics/wrap_Graphics.cpp +++ b/src/modules/graphics/wrap_Graphics.cpp @@ -1517,6 +1517,16 @@ static void luax_optbuffersettings(lua_State *L, int idx, Buffer::Settings &sett static void luax_checkbufferformat(lua_State *L, int idx, std::vector &format) { + if (lua_type(L, idx) == LUA_TSTRING) + { + Buffer::DataDeclaration decl("", DATAFORMAT_MAX_ENUM); + const char *formatstr = luaL_checkstring(L, idx); + if (!getConstant(formatstr, decl.format)) + luax_enumerror(L, "data format", getConstants(decl.format), formatstr); + format.push_back(decl); + return; + } + luaL_checktype(L, idx, LUA_TTABLE); int tablelen = luax_objlen(L, idx); @@ -1528,16 +1538,8 @@ static void luax_checkbufferformat(lua_State *L, int idx, std::vector buffers; + buffers.reserve(count); + + for (int i = 0; i < count; i++) + { + Buffer *buffer = luax_checktype(L, startidx + i); + buffers.push_back(buffer); + } + + luax_catchexcept(L, [&]() { shader->sendBuffers(info, buffers.data(), count); }); + return 0; +} + static int w_Shader_sendLuaValues(lua_State *L, int startidx, Shader *shader, const Shader::UniformInfo *info, const char *name) { switch (info->baseType) @@ -303,6 +320,8 @@ static int w_Shader_sendLuaValues(lua_State *L, int startidx, Shader *shader, co return w_Shader_sendBooleans(L, startidx, shader, info); case Shader::UNIFORM_SAMPLER: return w_Shader_sendTextures(L, startidx, shader, info); + case Shader::UNIFORM_TEXELBUFFER: + return w_Shader_sendBuffers(L, startidx, shader, info); default: return luaL_error(L, "Unknown variable type for shader uniform '%s", name); }