Browse Source

Graphics Buffers can be used as Shader Storage Buffers.

Alex Szpakowski 4 years ago
parent
commit
50ff86bd92

+ 59 - 8
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<DataDe
 	bool indexbuffer = settings.typeFlags & TYPEFLAG_INDEX;
 	bool vertexbuffer = settings.typeFlags & TYPEFLAG_VERTEX;
 	bool texelbuffer = settings.typeFlags & TYPEFLAG_TEXEL;
+	bool storagebuffer = settings.typeFlags & TYPEFLAG_SHADER_STORAGE;
 
-	if (!indexbuffer && !vertexbuffer && !texelbuffer)
-		throw love::Exception("Buffer must be created with at least one buffer type (index, vertex, or texel).");
+	if (!indexbuffer && !vertexbuffer && !texelbuffer && !storagebuffer)
+		throw love::Exception("Buffer must be created with at least one buffer type (index, vertex, texel, or shaderstorage).");
 
 	if (texelbuffer && !caps.features[Graphics::FEATURE_TEXEL_BUFFER])
 		throw love::Exception("Texel buffers are not supported on this system.");
 
+	if (storagebuffer && !caps.features[Graphics::FEATURE_GLSL4])
+		throw love::Exception("Shader Storage buffers are not supported on this system (GLSL 4 support is necessary.)");
+
 	size_t offset = 0;
 	size_t stride = 0;
+	size_t structurealignment = 1;
 
 	for (const DataDeclaration &decl : bufferformat)
 	{
@@ -116,16 +122,60 @@ Buffer::Buffer(Graphics *gfx, const Settings &settings, const std::vector<DataDe
 				throw love::Exception("Signed normalized formats are not supported in texel buffers.");
 		}
 
-		// TODO: alignment
-		member.offset = offset;
-		member.size = member.info.size;
+		size_t memberoffset = offset;
+		size_t membersize = member.info.size;
+
+		// Storage buffers are always treated as being an array of a structure.
+		// The structure's contents are the buffer format declaration.
+		if (storagebuffer)
+		{
+			// TODO: We can support these.
+			if (decl.arrayLength > 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;
 
-		offset += member.size;
+			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::vector<DataDe
 	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]);
+		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()

+ 3 - 0
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

+ 10 - 9
src/modules/graphics/Graphics.cpp

@@ -1944,15 +1944,16 @@ StringMap<Graphics::Feature, Graphics::FEATURE_MAX_ENUM> Graphics::features(Grap
 
 StringMap<Graphics::SystemLimit, Graphics::LIMIT_MAX_ENUM>::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::SystemLimit, Graphics::LIMIT_MAX_ENUM> Graphics::systemLimits(Graphics::systemLimitEntries, sizeof(Graphics::systemLimitEntries));

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

+ 64 - 2
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 <string>
 #include <regex>
@@ -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;
 }
 

+ 18 - 0
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<std::string, BufferReflection> storageBuffers;
+	};
+
+	static bool validateInternal(ShaderStage* vertex, ShaderStage* pixel, std::string& err, ValidationReflection &reflection);
+
 	StrongRef<ShaderStage> stages[ShaderStage::STAGE_MAX_ENUM];
 
+	ValidationReflection validationReflection;
+
 }; // Shader
 
 } // graphics

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

+ 19 - 3
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<Buffer::DataDeclaration> format = {{"", DATAFORMAT_FLOAT, 0}};
+
+		std::vector<float> 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);

+ 70 - 13
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;

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

+ 167 - 25
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<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
 	// 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;
+
+	bool texelbinding = info->baseType == UNIFORM_TEXELBUFFER;
+	bool storagebinding = info->baseType == UNIFORM_STORAGEBUFFER;
 
-	uint32 requiredtypeflags = Buffer::TYPEFLAG_TEXEL;
+	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;
+		}
 	}
 }
 

+ 9 - 0
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<TextureUnit> textureUnits;
 
+	std::vector<int> storageBufferBindingIndexToActiveBinding;
+	std::vector<BufferBinding> activeStorageBufferBindings;
+
 	std::vector<std::pair<const UniformInfo *, int>> pendingUniformUpdates;
 
 	float lastPointSize;

+ 4 - 3
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)
 

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

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

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