Browse Source

Add new texel buffer type.

GLSL 3+ shaders can read from texel buffers by declaring a uniform samplerBuffer variable and calling texelFetch with a 0-based index, in the shader code. All attributes in a texel buffer's format array must have the same data format (e.g. all 'floatvec4' formats).

Shader:send accepts a texel Buffer for samplerBuffer uniforms.

Added 'texelbuffer' to love.graphics.getSupported (requires GLSL 3+ and desktop OpenGL).
Added 'texelbuffersize' to love.graphics.getSystemLimits. Returns the max number of values in a texel buffer (array length multiplied by the number of attributes declared in the buffer's format array).
Alex Szpakowski 5 years ago
parent
commit
b011487a3e

+ 38 - 3
src/modules/graphics/Buffer.cpp

@@ -43,13 +43,18 @@ Buffer::Buffer(Graphics *gfx, const Settings &settings, const std::vector<DataDe
 	if (bufferformat.size() == 0)
 		throw love::Exception("Data format must contain values.");
 
-	bool supportsGLSL3 = gfx->getCapabilities().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<DataDe
 
 			if (bufferformat.size() > 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<DataDe
 
 			if ((info.baseType == DATA_BASETYPE_INT || info.baseType == DATA_BASETYPE_UINT) && !supportsGLSL3)
 				throw love::Exception("Integer vertex attribute data types require GLSL 3 support.");
+
+			if (decl.name.empty())
+				throw love::Exception("Vertex buffer attributes must have a name.");
+		}
+
+		if (texelbuffer)
+		{
+			if (format != bufferformat[0].format)
+				throw love::Exception("All values in a texel buffer must have the same format.");
+
+			if (decl.arrayLength > 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::vector<DataDe
 	this->arrayStride = 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()

+ 7 - 4
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<DataDeclaration> getCommonFormatDeclaration(CommonFormat format);
 
 	class Mapper

+ 2 - 0
src/modules/graphics/Graphics.cpp

@@ -1932,6 +1932,7 @@ StringMap<Graphics::Feature, Graphics::FEATURE_MAX_ENUM>::Entry Graphics::featur
 	{ "glsl3",                    FEATURE_GLSL3                },
 	{ "glsl4",                    FEATURE_GLSL4                },
 	{ "instancing",               FEATURE_INSTANCING           },
+	{ "texelbuffer",              FEATURE_TEXEL_BUFFER         },
 };
 
 StringMap<Graphics::Feature, Graphics::FEATURE_MAX_ENUM> Graphics::features(Graphics::featureEntries, sizeof(Graphics::featureEntries));
@@ -1943,6 +1944,7 @@ StringMap<Graphics::SystemLimit, Graphics::LIMIT_MAX_ENUM>::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          },

+ 2 - 0
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,

+ 9 - 1
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

+ 58 - 16
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<DataDeclaration> &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);

+ 8 - 3
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;

+ 26 - 2
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<Buffer::DataDeclaration> 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);

+ 3 - 0
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<love::graphics::Buffer> defaultBuffers[BUFFERTYPE_MAX_ENUM];
+
 	// [rendertarget][readable][srgb]
 	OptionalBool supportedFormats[PIXELFORMAT_MAX_ENUM][2][2][2];
 

+ 29 - 3
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);

+ 17 - 1
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<GLuint> boundTextures[TEXTURE_MAX_ENUM];
+		std::vector<GLuint> 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;
 

+ 204 - 16
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<Volatile *>(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<Volatile *>(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;
 			}
+			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.textures;
+				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)

+ 6 - 0
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<std::pair<const UniformInfo *, int>> pendingUniformUpdates;
 
+	std::vector<Buffer *> buffersToUnmap;
+
 	float lastPointSize;
 
 }; // Shader

+ 1 - 0
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)
 

+ 1 - 0
src/modules/graphics/vertex.h

@@ -58,6 +58,7 @@ enum BufferType
 {
 	BUFFERTYPE_VERTEX = 0,
 	BUFFERTYPE_INDEX,
+	BUFFERTYPE_TEXEL,
 	BUFFERTYPE_MAX_ENUM
 };
 

+ 2 - 2
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;

+ 12 - 10
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<Buffer::DataDeclaration> &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<Buffer::Da
 		Buffer::DataDeclaration decl("", DATAFORMAT_MAX_ENUM);
 
 		lua_getfield(L, -1, "name");
-		if (lua_type(L, -1) != LUA_TSTRING)
-		{
-			std::ostringstream ss;
-			ss << "'name' field expected in array element #";
-			ss << i;
-			ss << " of format table";
-			std::string str = ss.str();
-			luaL_argerror(L, idx, str.c_str());
-		}
-		decl.name = luax_checkstring(L, -1);
+		if (!lua_isnoneornil(L, -1))
+			decl.name = luax_checkstring(L, -1);
 		lua_pop(L, 1);
 
 		lua_getfield(L, -1, "format");

+ 19 - 0
src/modules/graphics/wrap_Shader.cpp

@@ -287,6 +287,23 @@ int w_Shader_sendTextures(lua_State *L, int startidx, Shader *shader, const Shad
 	return 0;
 }
 
+int w_Shader_sendBuffers(lua_State *L, int startidx, Shader *shader, const Shader::UniformInfo *info)
+{
+	int count = _getCount(L, startidx, info);
+
+	std::vector<Buffer *> buffers;
+	buffers.reserve(count);
+
+	for (int i = 0; i < count; i++)
+	{
+		Buffer *buffer = luax_checktype<Buffer>(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);
 	}