Browse Source

graphics: most uniform reflection is handled at a higher level instead of backend code.

Fixes boolean uniforms when Metal is used.
Sasha Szpakowski 1 year ago
parent
commit
2079f657e7

+ 240 - 65
src/modules/graphics/Shader.cpp

@@ -691,9 +691,48 @@ Shader::Shader(StrongRef<ShaderStage> _stages[], const CompileOptions &options)
 	, debugName(options.debugName)
 {
 	std::string err;
-	if (!validateInternal(_stages, err, validationReflection))
+	if (!validateInternal(_stages, err, reflection))
 		throw love::Exception("%s", err.c_str());
 
+	activeTextures.resize(reflection.textureCount);
+	activeBuffers.resize(reflection.bufferCount);
+
+	auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
+
+	// Default bindings for read-only resources.
+	for (const auto &kvp : reflection.allUniforms)
+	{
+		const auto &u = *kvp.second;
+
+		if (u.resourceIndex < 0)
+			continue;
+
+		if ((u.access & ACCESS_WRITE) != 0)
+			continue;
+
+		if (u.baseType == UNIFORM_SAMPLER || u.baseType == UNIFORM_STORAGETEXTURE)
+		{
+			auto tex = gfx->getDefaultTexture(u.textureType, u.dataBaseType);
+			for (int i = 0; i < u.count; i++)
+			{
+				tex->retain();
+				activeTextures[u.resourceIndex + i] = tex;
+			}
+		}
+		else if (u.baseType == UNIFORM_TEXELBUFFER || u.baseType == UNIFORM_STORAGEBUFFER)
+		{
+			auto buffer = u.baseType == UNIFORM_TEXELBUFFER
+				? gfx->getDefaultTexelBuffer(u.dataBaseType)
+				: gfx->getDefaultStorageBuffer();
+
+			for (int i = 0; i < u.count; i++)
+			{
+				buffer->retain();
+				activeBuffers[u.resourceIndex + i] = buffer;
+			}
+		}
+	}
+
 	for (int i = 0; i < SHADERSTAGE_MAX_ENUM; i++)
 		stages[i] = _stages[i];
 }
@@ -708,6 +747,18 @@ Shader::~Shader()
 
 	if (current == this)
 		attachDefault(STANDARD_DEFAULT);
+
+	for (Texture *tex : activeTextures)
+	{
+		if (tex)
+			tex->release();
+	}
+
+	for (Buffer *buffer : activeBuffers)
+	{
+		if (buffer)
+			buffer->release();
+	}
 }
 
 bool Shader::hasStage(ShaderStageType stage)
@@ -740,6 +791,18 @@ bool Shader::isDefaultActive()
 	return false;
 }
 
+const Shader::UniformInfo *Shader::getUniformInfo(const std::string &name) const
+{
+	const auto it = reflection.allUniforms.find(name);
+	return it != reflection.allUniforms.end() ? it->second : nullptr;
+}
+
+bool Shader::hasUniform(const std::string &name) const
+{
+	const auto it = reflection.allUniforms.find(name);
+	return it != reflection.allUniforms.end() && it->second->active;
+}
+
 const Shader::UniformInfo *Shader::getMainTextureInfo() const
 {
 	return getUniformInfo(BUILTIN_TEXTURE_MAIN);
@@ -781,9 +844,9 @@ bool Shader::isResourceBaseTypeCompatible(DataBaseType a, DataBaseType b)
 
 void Shader::validateDrawState(PrimitiveType primtype, Texture *maintex) const
 {
-	if ((primtype == PRIMITIVE_POINTS) != validationReflection.usesPointSize)
+	if ((primtype == PRIMITIVE_POINTS) != reflection.usesPointSize)
 	{
-		if (validationReflection.usesPointSize)
+		if (reflection.usesPointSize)
 			throw love::Exception("The active shader can only be used to draw points.");
 		else
 			throw love::Exception("The gl_PointSize variable must be set in a vertex shader when drawing points.");
@@ -825,17 +888,29 @@ void Shader::validateDrawState(PrimitiveType primtype, Texture *maintex) const
 
 void Shader::getLocalThreadgroupSize(int *x, int *y, int *z)
 {
-	*x = validationReflection.localThreadgroupSize[0];
-	*y = validationReflection.localThreadgroupSize[1];
-	*z = validationReflection.localThreadgroupSize[2];
+	*x = reflection.localThreadgroupSize[0];
+	*y = reflection.localThreadgroupSize[1];
+	*z = reflection.localThreadgroupSize[2];
 }
 
 bool Shader::validate(StrongRef<ShaderStage> stages[], std::string& err)
 {
-	ValidationReflection reflection;
+	Reflection reflection;
 	return validateInternal(stages, err, reflection);
 }
 
+static DataBaseType getBaseType(glslang::TBasicType basictype)
+{
+	switch (basictype)
+	{
+		case glslang::EbtInt: return DATA_BASETYPE_INT;
+		case glslang::EbtUint: return DATA_BASETYPE_UINT;
+		case glslang::EbtFloat: return DATA_BASETYPE_FLOAT;
+		case glslang::EbtBool: return DATA_BASETYPE_BOOL;
+		default: return DATA_BASETYPE_FLOAT;
+	}
+}
+
 static PixelFormat getPixelFormat(glslang::TLayoutFormat format)
 {
 	using namespace glslang;
@@ -885,6 +960,30 @@ static PixelFormat getPixelFormat(glslang::TLayoutFormat format)
 	}
 }
 
+static TextureType getTextureType(const glslang::TSampler &sampler)
+{
+	if (sampler.is2D())
+		return sampler.isArrayed() ? TEXTURE_2D_ARRAY : TEXTURE_2D;
+	else if (sampler.dim == glslang::EsdCube)
+		return sampler.isArrayed() ? TEXTURE_MAX_ENUM : TEXTURE_CUBE;
+	else if (sampler.dim == glslang::Esd3D)
+		return TEXTURE_VOLUME;
+	else
+		return TEXTURE_MAX_ENUM;
+}
+
+static uint32 getStageMask(EShLanguageMask mask)
+{
+	uint32 m = 0;
+	if (mask & EShLangVertexMask)
+		m |= SHADERSTAGEMASK_VERTEX;
+	if (mask & EShLangFragmentMask)
+		m |= SHADERSTAGEMASK_PIXEL;
+	if (mask & EShLangComputeMask)
+		m |= SHADERSTAGEMASK_COMPUTE;
+	return m;
+}
+
 template <typename T>
 static T convertData(const glslang::TConstUnion &data)
 {
@@ -903,7 +1002,7 @@ static T convertData(const glslang::TConstUnion &data)
 	}
 }
 
-bool Shader::validateInternal(StrongRef<ShaderStage> stages[], std::string &err, ValidationReflection &reflection)
+bool Shader::validateInternal(StrongRef<ShaderStage> stages[], std::string &err, Reflection &reflection)
 {
 	glslang::TProgram program;
 
@@ -946,6 +1045,9 @@ bool Shader::validateInternal(StrongRef<ShaderStage> stages[], std::string &err,
 		}
 	}
 
+	reflection.textureCount = 0;
+	reflection.bufferCount = 0;
+
 	for (int i = 0; i < program.getNumUniformVariables(); i++)
 	{
 		const glslang::TObjectReflection &info = program.getUniform(i);
@@ -955,9 +1057,40 @@ bool Shader::validateInternal(StrongRef<ShaderStage> stages[], std::string &err,
 
 		const glslang::TQualifier &qualifiers = type->getQualifier();
 
-		if (type->isImage())
+		UniformInfo u = {};
+
+		u.name = canonicaliizeUniformName(info.name);
+		u.location = -1;
+		u.access = ACCESS_READ;
+		u.stageMask = getStageMask(info.stages);
+		u.components = 1;
+		u.resourceIndex = -1;
+
+		if (type->isSizedArray())
+			u.count = type->getArraySizes()->getCumulativeSize();
+		else
+			u.count = 1;
+
+		const auto &sampler = type->getSampler();
+
+		if (type->isTexture() && type->getSampler().isCombined())
 		{
-			if ((info.stages & EShLangComputeMask) == 0)
+			u.baseType = UNIFORM_SAMPLER;
+			u.dataBaseType = getBaseType(sampler.getBasicType());
+			u.isDepthSampler = sampler.isShadow();
+			u.textureType = getTextureType(sampler);
+
+			if (u.textureType == TEXTURE_MAX_ENUM)
+				continue;
+
+			u.resourceIndex = reflection.textureCount;
+			reflection.textureCount += u.count;
+
+			reflection.sampledTextures[u.name] = u;
+		}
+		else if (type->isImage())
+		{
+			if ((info.stages & (~EShLangComputeMask)) != 0)
 			{
 				err = "Shader validation error:\nStorage Texture uniform variables (image2D, etc) are only allowed in compute shaders.";
 				return false;
@@ -965,36 +1098,63 @@ bool Shader::validateInternal(StrongRef<ShaderStage> stages[], std::string &err,
 
 			if (!qualifiers.hasFormat())
 			{
-				err = "Shader validation error:\nStorage Texture '" + info.name + "' must have an explicit format set in its layout declaration.";
+				err = "Shader validation error:\nStorage Texture '" + u.name + "' must have an explicit format set in its layout declaration.";
 				return false;
 			}
 
-			StorageTextureReflection texreflection = {};
+			u.baseType = UNIFORM_STORAGETEXTURE;
+			u.storageTextureFormat = getPixelFormat(qualifiers.getFormat());
+			u.dataBaseType = getDataBaseType(u.storageTextureFormat);
+			u.textureType = getTextureType(sampler);
 
-			texreflection.format = getPixelFormat(qualifiers.getFormat());
+			if (u.textureType == TEXTURE_MAX_ENUM)
+				continue;
+
+			u.resourceIndex = reflection.textureCount;
+			reflection.textureCount += u.count;
 
 			if (qualifiers.isReadOnly())
-				texreflection.access = ACCESS_READ;
+				u.access = ACCESS_READ;
 			else if (qualifiers.isWriteOnly())
-				texreflection.access = ACCESS_WRITE;
+				u.access = ACCESS_WRITE;
 			else
-				texreflection.access = (Access)(ACCESS_READ | ACCESS_WRITE);
+				u.access = (Access)(ACCESS_READ | ACCESS_WRITE);
+
+			reflection.storageTextures[u.name] = u;
+		}
+		else if (type->getBasicType() == glslang::EbtSampler && type->getSampler().isBuffer())
+		{
+			u.baseType = UNIFORM_TEXELBUFFER;
+			u.dataBaseType = getBaseType(sampler.getBasicType());
+
+			u.resourceIndex = reflection.bufferCount;
+			reflection.bufferCount += u.count;
 
-			reflection.storageTextures[info.name] = texreflection;
+			reflection.texelBuffers[u.name] = u;
 		}
 		else if (!type->isOpaque())
 		{
-			LocalUniform u = {};
-			auto &values = u.initializerValues;
+			std::vector<LocalUniformValue> values;
 			const glslang::TConstUnionArray *constarray = info.getConstArray();
 
+			if (type->isMatrix())
+			{
+				u.matrix.rows = type->getMatrixRows();
+				u.matrix.columns = type->getMatrixCols();
+			}
+			else
+			{
+				u.components = type->getVectorSize();
+			}
+
 			// Store initializer values for local uniforms. Some love graphics
 			// backends strip these out of the shader so we need to be able to
 			// access them (to re-send them) by getting them here.
 			switch (type->getBasicType())
 			{
 			case glslang::EbtFloat:
-				u.dataType = DATA_BASETYPE_FLOAT;
+				u.baseType = type->isMatrix() ? UNIFORM_MATRIX : UNIFORM_FLOAT;
+				u.dataBaseType = DATA_BASETYPE_FLOAT;
 				if (constarray != nullptr)
 				{
 					values.resize(constarray->size());
@@ -1003,7 +1163,8 @@ bool Shader::validateInternal(StrongRef<ShaderStage> stages[], std::string &err,
 				}
 				break;
 			case glslang::EbtUint:
-				u.dataType = DATA_BASETYPE_UINT;
+				u.baseType = UNIFORM_UINT;
+				u.dataBaseType = DATA_BASETYPE_UINT;
 				if (constarray != nullptr)
 				{
 					values.resize(constarray->size());
@@ -1012,7 +1173,8 @@ bool Shader::validateInternal(StrongRef<ShaderStage> stages[], std::string &err,
 				}
 				break;
 			case glslang::EbtBool:
-				u.dataType = DATA_BASETYPE_BOOL;
+				u.baseType = UNIFORM_BOOL;
+				u.dataBaseType = DATA_BASETYPE_BOOL;
 				if (constarray != nullptr)
 				{
 					values.resize(constarray->size());
@@ -1022,7 +1184,8 @@ bool Shader::validateInternal(StrongRef<ShaderStage> stages[], std::string &err,
 				break;
 			case glslang::EbtInt:
 			default:
-				u.dataType = DATA_BASETYPE_INT;
+				u.baseType = UNIFORM_INT;
+				u.dataBaseType = DATA_BASETYPE_INT;
 				if (constarray != nullptr)
 				{
 					values.resize(constarray->size());
@@ -1032,7 +1195,8 @@ bool Shader::validateInternal(StrongRef<ShaderStage> stages[], std::string &err,
 				break;
 			}
 
-			reflection.localUniforms[info.name] = u;
+			reflection.localUniforms[u.name] = u;
+			reflection.localUniformInitializerValues[u.name] = values;
 		}
 	}
 
@@ -1070,18 +1234,29 @@ bool Shader::validateInternal(StrongRef<ShaderStage> stages[], std::string &err,
 				return false;
 			}
 
-			BufferReflection bufferReflection = {};
-			bufferReflection.stride = (size_t) info.size;
-			bufferReflection.memberCount = (size_t) info.numMembers;
+			UniformInfo u = {};
+			u.name = canonicaliizeUniformName(info.name);
+			u.location = -1;
+
+			if (type->isSizedArray())
+				u.count = type->getArraySizes()->getCumulativeSize();
+			else
+				u.count = 1;
+
+			u.bufferStride = (size_t) info.size;
+			u.bufferMemberCount = (size_t) info.numMembers;
+
+			u.resourceIndex = reflection.bufferCount;
+			reflection.bufferCount += u.count;
 
 			if (qualifiers.isReadOnly())
-				bufferReflection.access = ACCESS_READ;
+				u.access = ACCESS_READ;
 			else if (qualifiers.isWriteOnly())
-				bufferReflection.access = ACCESS_WRITE;
+				u.access = ACCESS_WRITE;
 			else
-				bufferReflection.access = (Access)(ACCESS_READ | ACCESS_WRITE);
+				u.access = (Access)(ACCESS_READ | ACCESS_WRITE);
 
-			reflection.storageBuffers[info.name] = bufferReflection;
+			reflection.storageBuffers[u.name] = u;
 		}
 		else
 		{
@@ -1090,6 +1265,21 @@ bool Shader::validateInternal(StrongRef<ShaderStage> stages[], std::string &err,
 		}
 	}
 
+	for (auto &kvp : reflection.texelBuffers)
+		reflection.allUniforms[kvp.first] = &kvp.second;
+
+	for (auto &kvp : reflection.storageBuffers)
+		reflection.allUniforms[kvp.first] = &kvp.second;
+
+	for (auto &kvp : reflection.sampledTextures)
+		reflection.allUniforms[kvp.first] = &kvp.second;
+
+	for (auto &kvp : reflection.storageTextures)
+		reflection.allUniforms[kvp.first] = &kvp.second;
+
+	for (auto &kvp : reflection.localUniforms)
+		reflection.allUniforms[kvp.first] = &kvp.second;
+
 	return true;
 }
 
@@ -1216,55 +1406,40 @@ bool Shader::validateBuffer(const UniformInfo *info, Buffer *buffer, bool intern
 	return true;
 }
 
-bool Shader::fillUniformReflectionData(UniformInfo &u)
+std::string Shader::getShaderStageDebugName(ShaderStageType stage) const
 {
-	const auto &r = validationReflection;
-
-	if (u.baseType == UNIFORM_STORAGETEXTURE)
-	{
-		const auto reflectionit = r.storageTextures.find(u.name);
-		if (reflectionit != r.storageTextures.end())
-		{
-			u.storageTextureFormat = reflectionit->second.format;
-			u.access = reflectionit->second.access;
-			return true;
-		}
+	std::string name = debugName;
 
-		// No reflection info - maybe glslang was better at detecting dead code
-		// than the driver's compiler?
-		return false;
-	}
-	else if (u.baseType == UNIFORM_STORAGEBUFFER)
+	if (!name.empty())
 	{
-		const auto reflectionit = r.storageBuffers.find(u.name);
-		if (reflectionit != r.storageBuffers.end())
-		{
-			u.bufferStride = reflectionit->second.stride;
-			u.bufferMemberCount = reflectionit->second.memberCount;
-			u.access = reflectionit->second.access;
-			return true;
-		}
-
-		return false;
+		const char *stagename = "unknown";
+		ShaderStage::getConstant(stage, stagename);
+		name += " (" + std::string(stagename) + ")";
 	}
 
-	return true;
+	return name;
 }
 
-std::string Shader::getShaderStageDebugName(ShaderStageType stage) const
+std::string Shader::canonicaliizeUniformName(const std::string &n)
 {
-	std::string name = debugName;
+	std::string name(n);
 
-	if (!name.empty())
+	// Some drivers/compilers append "[0]" to the end of array uniform names.
+	if (name.length() > 3)
 	{
-		const char *stagename = "unknown";
-		ShaderStage::getConstant(stage, stagename);
-		name += " (" + std::string(stagename) + ")";
+		size_t findpos = name.rfind("[0]");
+		if (findpos != std::string::npos && findpos == name.length() - 3)
+			name.erase(name.length() - 3);
 	}
 
 	return name;
 }
 
+void Shader::handleUnknownUniformName(const char */*name*/)
+{
+	// TODO: do something here?
+}
+
 bool Shader::initialize()
 {
 	return glslang::InitializeProcess();

+ 27 - 33
src/modules/graphics/Shader.h

@@ -129,6 +129,10 @@ public:
 
 	struct UniformInfo
 	{
+		UniformType baseType;
+		uint32 stageMask;
+		bool active;
+
 		int location;
 		int count;
 
@@ -138,7 +142,6 @@ public:
 			MatrixSize matrix;
 		};
 
-		UniformType baseType;
 		DataBaseType dataBaseType;
 		TextureType textureType;
 		Access access;
@@ -148,6 +151,8 @@ public:
 		size_t bufferMemberCount;
 		std::string name;
 
+		int resourceIndex;
+
 		union
 		{
 			void *data;
@@ -157,12 +162,6 @@ public:
 		};
 
 		size_t dataSize;
-
-		union
-		{
-			Texture **textures;
-			Buffer **buffers;
-		};
 	};
 
 	union LocalUniformValue
@@ -222,7 +221,7 @@ public:
 
 	virtual int getVertexAttributeIndex(const std::string &name) = 0;
 
-	virtual const UniformInfo *getUniformInfo(const std::string &name) const = 0;
+	const UniformInfo *getUniformInfo(const std::string &name) const;
 	virtual const UniformInfo *getUniformInfo(BuiltinUniform builtin) const = 0;
 
 	virtual void updateUniform(const UniformInfo *info, int count) = 0;
@@ -234,7 +233,7 @@ public:
 	 * Gets whether a uniform with the specified name exists and is actively
 	 * used in the shader.
 	 **/
-	virtual bool hasUniform(const std::string &name) const = 0;
+	bool hasUniform(const std::string &name) const;
 
 	/**
 	 * Sets the textures used when rendering a video. For internal use only.
@@ -264,39 +263,31 @@ public:
 
 protected:
 
-	struct BufferReflection
+	struct Reflection
 	{
-		size_t stride;
-		size_t memberCount;
-		Access access;
-	};
+		std::map<std::string, UniformInfo> texelBuffers;
+		std::map<std::string, UniformInfo> storageBuffers;
+		std::map<std::string, UniformInfo> sampledTextures;
+		std::map<std::string, UniformInfo> storageTextures;
+		std::map<std::string, UniformInfo> localUniforms;
 
-	struct StorageTextureReflection
-	{
-		PixelFormat format;
-		Access access;
-	};
+		std::map<std::string, UniformInfo *> allUniforms;
 
-	struct LocalUniform
-	{
-		DataBaseType dataType;
-		std::vector<LocalUniformValue> initializerValues;
-	};
+		std::map<std::string, std::vector<LocalUniformValue>> localUniformInitializerValues;
+
+		int textureCount;
+		int bufferCount;
 
-	struct ValidationReflection
-	{
-		std::map<std::string, BufferReflection> storageBuffers;
-		std::map<std::string, StorageTextureReflection> storageTextures;
-		std::map<std::string, LocalUniform> localUniforms;
 		int localThreadgroupSize[3];
 		bool usesPointSize;
 	};
 
-	bool fillUniformReflectionData(UniformInfo &u);
-
 	std::string getShaderStageDebugName(ShaderStageType stage) const;
 
-	static bool validateInternal(StrongRef<ShaderStage> stages[], std::string& err, ValidationReflection &reflection);
+	void handleUnknownUniformName(const char *name);
+
+	static std::string canonicaliizeUniformName(const std::string &name);
+	static bool validateInternal(StrongRef<ShaderStage> stages[], std::string& err, Reflection &reflection);
 	static DataBaseType getDataBaseType(PixelFormat format);
 	static bool isResourceBaseTypeCompatible(DataBaseType a, DataBaseType b);
 
@@ -305,7 +296,10 @@ protected:
 
 	StrongRef<ShaderStage> stages[SHADERSTAGE_MAX_ENUM];
 
-	ValidationReflection validationReflection;
+	Reflection reflection;
+
+	std::vector<Texture *> activeTextures;
+	std::vector<Buffer *> activeBuffers;
 
 	std::string debugName;
 

+ 8 - 0
src/modules/graphics/ShaderStage.h

@@ -48,6 +48,14 @@ enum ShaderStageType
 	SHADERSTAGE_MAX_ENUM
 };
 
+enum ShaderStageMask
+{
+	SHADERSTAGEMASK_NONE = 0,
+	SHADERSTAGEMASK_VERTEX = 1 << SHADERSTAGE_VERTEX,
+	SHADERSTAGEMASK_PIXEL = 1 << SHADERSTAGE_PIXEL,
+	SHADERSTAGEMASK_COMPUTE = 1 << SHADERSTAGE_COMPUTE,
+};
+
 class ShaderStage : public love::Object
 {
 public:

+ 1 - 4
src/modules/graphics/metal/Shader.h

@@ -107,12 +107,10 @@ public:
 	void attach() override;
 	std::string getWarnings() const override { return ""; }
 	int getVertexAttributeIndex(const std::string &name) override;
-	const UniformInfo *getUniformInfo(const std::string &name) const override;
 	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 { return 0; }
 	void setVideoTextures(love::graphics::Texture *ytexture, love::graphics::Texture *cbtexture, love::graphics::Texture *crtexture) override;
 
@@ -140,13 +138,12 @@ private:
 	};
 
 	void buildLocalUniforms(const spirv_cross::CompilerMSL &msl, const spirv_cross::SPIRType &type, size_t baseoffset, const std::string &basename);
-	void addImage(const spirv_cross::CompilerMSL &msl, const spirv_cross::Resource &resource, UniformType baseType);
+	void addImage(const spirv_cross::Resource &resource);
 	void compileFromGLSLang(id<MTLDevice> device, const glslang::TProgram &program);
 
 	id<MTLFunction> functions[SHADERSTAGE_MAX_ENUM];
 
 	UniformInfo *builtinUniformInfo[BUILTIN_MAX_ENUM];
-	std::map<std::string, UniformInfo> uniforms;
 
 	uint8 *localUniformStagingData;
 	uint8 *localUniformBufferData;

+ 91 - 167
src/modules/graphics/metal/Shader.mm

@@ -299,132 +299,69 @@ void Shader::buildLocalUniforms(const spirv_cross::CompilerMSL &msl, const spirv
 				continue;
 		}
 
+		name = canonicaliizeUniformName(name);
+
 		if (offset + membersize > localUniformBufferSize)
 			throw love::Exception("Invalid uniform offset + size for '%s' (offset=%d, size=%d, buffer size=%d)", name.c_str(), (int)offset, (int)membersize, (int)localUniformBufferSize);
 
-		UniformInfo u = {};
-		u.name = name;
-		u.dataSize = membersize;
-		u.count = membertype.array.empty() ? 1 : membertype.array[0];
-		u.components = 1;
-
-		u.data = localUniformStagingData + offset;
-		if (membertype.columns == 1)
-		{
-			if (membertype.basetype == SPIRType::Int)
-				u.baseType = UNIFORM_INT;
-			else if (membertype.basetype == SPIRType::UInt)
-				u.baseType = UNIFORM_UINT;
-			else
-				u.baseType = UNIFORM_FLOAT;
-			u.components = membertype.vecsize;
-		}
-		else
+		auto uniformit = reflection.allUniforms.find(name);
+		if (uniformit == reflection.allUniforms.end())
 		{
-			u.baseType = UNIFORM_MATRIX;
-			u.matrix.rows = membertype.vecsize;
-			u.matrix.columns = membertype.columns;
+			handleUnknownUniformName(name.c_str());
+			continue;
 		}
 
-		const auto &reflectionit = validationReflection.localUniforms.find(u.name);
-		if (reflectionit != validationReflection.localUniforms.end())
+		UniformInfo &u = *(uniformit->second);
+		u.active = true;
+
+		if (u.dataSize > 0)
+			continue;
+
+		u.dataSize = membersize;
+		u.data = localUniformStagingData + offset;
+
+		const auto &reflectionit = reflection.localUniformInitializerValues.find(u.name);
+		if (reflectionit != reflection.localUniformInitializerValues.end())
 		{
-			const auto &localuniform = reflectionit->second;
-			const auto &values = localuniform.initializerValues;
+			const auto &values = reflectionit->second;
 			if (!values.empty())
 				memcpy(u.data, values.data(), std::min(u.dataSize, values.size() * sizeof(LocalUniformValue)));
 		}
 
-		uniforms[u.name] = u;
-
 		BuiltinUniform builtin = BUILTIN_MAX_ENUM;
 		if (getConstant(u.name.c_str(), builtin))
 		{
 			if (builtin == BUILTIN_UNIFORMS_PER_DRAW)
 				builtinUniformDataOffset = offset;
-			builtinUniformInfo[builtin] = &uniforms[u.name];
+			builtinUniformInfo[builtin] = &u;
 		}
 
 		updateUniform(&u, u.count);
 	}
 }
 
-void Shader::addImage(const spirv_cross::CompilerMSL &msl, const spirv_cross::Resource &resource, UniformType baseType)
+void Shader::addImage(const spirv_cross::Resource &resource)
 {
 	using namespace spirv_cross;
 
-	const SPIRType &basetype = msl.get_type(resource.base_type_id);
-	const SPIRType &type = msl.get_type(resource.type_id);
-	const SPIRType &imagetype = msl.get_type(basetype.image.type);
-
-	UniformInfo u = {};
-	u.baseType = baseType;
-	u.name = resource.name;
-	u.count = type.array.empty() ? 1 : type.array[0];
-	u.isDepthSampler = type.image.depth;
-	u.components = 1;
-
-	auto it = uniforms.find(u.name);
-	if (it != uniforms.end())
-		return;
-
-	if (!fillUniformReflectionData(u))
+	std::string name = canonicaliizeUniformName(resource.name);
+	auto uniformit = reflection.allUniforms.find(name);
+	if (uniformit == reflection.allUniforms.end())
 		return;
 
-	switch (imagetype.basetype)
-	{
-	case SPIRType::Float:
-		u.dataBaseType = DATA_BASETYPE_FLOAT;
-		break;
-	case SPIRType::Int:
-		u.dataBaseType = DATA_BASETYPE_INT;
-		break;
-	case SPIRType::UInt:
-		u.dataBaseType = DATA_BASETYPE_UINT;
-		break;
-	default:
-		break;
-	}
+	UniformInfo &u = *(uniformit->second);
 
-	switch (basetype.image.dim)
+	if (u.dataSize == 0)
 	{
-	case spv::Dim2D:
-		u.textureType = basetype.image.arrayed ? TEXTURE_2D_ARRAY : TEXTURE_2D;
-		u.textures = new love::graphics::Texture*[u.count];
-		memset(u.textures, 0, sizeof(love::graphics::Texture *) * u.count);
-		break;
-	case spv::Dim3D:
-		u.textureType = TEXTURE_VOLUME;
-		u.textures = new love::graphics::Texture*[u.count];
-		memset(u.textures, 0, sizeof(love::graphics::Texture *) * u.count);
-		break;
-	case spv::DimCube:
-		if (basetype.image.arrayed)
-			throw love::Exception("Cubemap Arrays are not currently supported.");
-		u.textureType = TEXTURE_CUBE;
-		u.textures = new love::graphics::Texture*[u.count];
-		memset(u.textures, 0, sizeof(love::graphics::Texture *) * u.count);
-		break;
-	case spv::DimBuffer:
-		u.baseType = UNIFORM_TEXELBUFFER;
-		u.buffers = new love::graphics::Buffer*[u.count];
-		memset(u.buffers, 0, sizeof(love::graphics::Buffer *) * u.count);
-		break;
-	default:
-		// TODO: error? continue?
-		break;
+		u.dataSize = sizeof(int) * u.count;
+		u.data = malloc(u.dataSize);
+		for (int i = 0; i < u.count; i++)
+			u.ints[i] = -1; // Initialized below, after compiling.
 	}
 
-	u.dataSize = sizeof(int) * u.count;
-	u.data = malloc(u.dataSize);
-	for (int i = 0; i < u.count; i++)
-		u.ints[i] = -1; // Initialized below, after compiling.
-
-	uniforms[u.name] = u;
-
 	BuiltinUniform builtin;
-	if (getConstant(resource.name.c_str(), builtin))
-		builtinUniformInfo[builtin] = &uniforms[u.name];
+	if (getConstant(name.c_str(), builtin))
+		builtinUniformInfo[builtin] = &u;
 }
 
 void Shader::compileFromGLSLang(id<MTLDevice> device, const glslang::TProgram &program)
@@ -473,19 +410,18 @@ void Shader::compileFromGLSLang(id<MTLDevice> device, const glslang::TProgram &p
 		auto &msl = *mslpointer;
 
 		auto interfacevars = msl.get_active_interface_variables();
+		ShaderResources resources = msl.get_shader_resources(interfacevars);
 
 		msl.set_enabled_interface_variables(interfacevars);
 
-		ShaderResources resources = msl.get_shader_resources();
-
 		for (const auto &resource : resources.storage_images)
 		{
-			addImage(msl, resource, UNIFORM_STORAGETEXTURE);
+			addImage(resource);
 		}
 
 		for (const auto &resource : resources.sampled_images)
 		{
-			addImage(msl, resource, UNIFORM_SAMPLER);
+			addImage(resource);
 		}
 
 		for (const auto &resource : resources.uniform_buffers)
@@ -536,32 +472,24 @@ void Shader::compileFromGLSLang(id<MTLDevice> device, const glslang::TProgram &p
 			binding.msl_buffer = metalBufferIndices[stageindex]++;
 			msl.add_msl_resource_binding(binding);
 
-			auto it = uniforms.find(resource.name);
-			if (it != uniforms.end())
+			std::string name = canonicaliizeUniformName(resource.name);
+			auto uniformit = reflection.allUniforms.find(name);
+			if (uniformit == reflection.allUniforms.end())
+			{
+				handleUnknownUniformName(name.c_str());
 				continue;
+			}
 
-			const SPIRType &type = msl.get_type(resource.type_id);
-
-			UniformInfo u = {};
-			u.baseType = UNIFORM_STORAGEBUFFER;
-			u.components = 1;
-			u.name = resource.name;
-			u.count = type.array.empty() ? 1 : type.array[0];
+			UniformInfo &u = *(uniformit->second);
 
-			if (!fillUniformReflectionData(u))
+			if (u.dataSize > 0)
 				continue;
 
-			u.buffers = new love::graphics::Buffer*[u.count];
 			u.dataSize = sizeof(int) * u.count;
 			u.data = malloc(u.dataSize);
 
 			for (int i = 0; i < u.count; i++)
-			{
 				u.ints[i] = -1; // Initialized below, after compiling.
-				u.buffers[i] = nullptr;
-			}
-
-			uniforms[u.name] = u;
 		}
 
 		if (stageindex == SHADERSTAGE_VERTEX)
@@ -634,7 +562,8 @@ void Shader::compileFromGLSLang(id<MTLDevice> device, const glslang::TProgram &p
 		if (library == nil && err != nil)
 		{
 			NSLog(@"errors: %@", err);
-			throw love::Exception("Error compiling converted Metal shader code");
+			NSString *errorstr = err.localizedDescription;
+			throw love::Exception("Error compiling converted Metal shader code:\n\n%s", errorstr.UTF8String);
 		}
 
 		functions[stageindex] = [library newFunctionWithName:library.functionNames[0]];
@@ -645,11 +574,12 @@ void Shader::compileFromGLSLang(id<MTLDevice> device, const glslang::TProgram &p
 
 		auto setTextureBinding = [this](CompilerMSL &msl, int stageindex, const spirv_cross::Resource &resource) -> void
 		{
-			auto it = uniforms.find(resource.name);
-			if (it == uniforms.end())
+			std::string name = canonicaliizeUniformName(resource.name);
+			auto it = reflection.allUniforms.find(name);
+			if (it == reflection.allUniforms.end())
 				return;
 
-			UniformInfo &u = it->second;
+			UniformInfo &u = *(it->second);
 
 			uint32 texturebinding = msl.get_automatic_msl_resource_binding(resource.id);
 			uint32 samplerbinding = msl.get_automatic_msl_resource_binding_secondary(resource.id);
@@ -657,10 +587,11 @@ void Shader::compileFromGLSLang(id<MTLDevice> device, const glslang::TProgram &p
 			if (texturebinding == (uint32)-1)
 			{
 				// No valid binding, the uniform was likely optimized out because it's not used.
-				uniforms.erase(resource.name);
 				return;
 			}
 
+			u.active = true;
+
 			for (int i = 0; i < u.count; i++)
 			{
 				if (u.ints[i] == -1)
@@ -702,8 +633,9 @@ void Shader::compileFromGLSLang(id<MTLDevice> device, const glslang::TProgram &p
 
 		for (const auto &resource : resources.storage_buffers)
 		{
-			auto it = uniforms.find(resource.name);
-			if (it == uniforms.end())
+			std::string name = canonicaliizeUniformName(resource.name);
+			auto it = reflection.storageBuffers.find(name);
+			if (it == reflection.storageBuffers.end())
 				continue;
 
 			UniformInfo &u = it->second;
@@ -712,10 +644,11 @@ void Shader::compileFromGLSLang(id<MTLDevice> device, const glslang::TProgram &p
 			if (bufferbinding == (uint32)-1)
 			{
 				// No valid binding, the uniform was likely optimized out because it's not used.
-				uniforms.erase(resource.name);
 				continue;
 			}
 
+			u.active = true;
+
 			for (int i = 0; i < u.count; i++)
 			{
 				if (u.ints[i] == -1)
@@ -736,18 +669,18 @@ void Shader::compileFromGLSLang(id<MTLDevice> device, const glslang::TProgram &p
 	}
 
 	// Initialize default resource bindings.
-	for (auto &kvp : uniforms)
+	for (const auto &kvp : reflection.allUniforms)
 	{
-		UniformInfo &info = kvp.second;
-		switch (info.baseType)
+		const UniformInfo *info = kvp.second;
+		switch (info->baseType)
 		{
 		case UNIFORM_SAMPLER:
 		case UNIFORM_STORAGETEXTURE:
-			sendTextures(&info, info.textures, info.count);
+			sendTextures(info, &activeTextures[info->resourceIndex], info->count);
 			break;
 		case UNIFORM_TEXELBUFFER:
 		case UNIFORM_STORAGEBUFFER:
-			sendBuffers(&info, info.buffers, info.count);
+			sendBuffers(info, &activeBuffers[info->resourceIndex], info->count);
 			break;
 		default:
 			break;
@@ -767,29 +700,13 @@ Shader::~Shader()
 
 	cachedRenderPipelines.clear();
 
-	for (const auto &it : uniforms)
+	for (const auto &it : reflection.allUniforms)
 	{
-		const auto &u = it.second;
+		const auto &u = *(it.second);
 		if (u.baseType == UNIFORM_SAMPLER || u.baseType == UNIFORM_STORAGETEXTURE)
-		{
 			free(u.data);
-			for (int i = 0; i < u.count; i++)
-			{
-				if (u.textures[i] != nullptr)
-					u.textures[i]->release();
-			}
-			delete[] u.textures;
-		}
 		else if (u.baseType == UNIFORM_TEXELBUFFER || u.baseType == UNIFORM_STORAGEBUFFER)
-		{
 			free(u.data);
-			for (int i = 0; i < u.count; i++)
-			{
-				if (u.buffers[i] != nullptr)
-					u.buffers[i]->release();
-			}
-			delete[] u.buffers;
-		}
 	}
 
 	delete[] localUniformStagingData;
@@ -813,12 +730,6 @@ int Shader::getVertexAttributeIndex(const std::string &name)
 	return it != attributes.end() ? it->second : -1;
 }
 
-const Shader::UniformInfo *Shader::getUniformInfo(const std::string &name) const
-{
-	const auto it = uniforms.find(name);
-	return it != uniforms.end() ? &(it->second) : nullptr;
-}
-
 const Shader::UniformInfo *Shader::getUniformInfo(BuiltinUniform builtin) const
 {
 	return builtinUniformInfo[(int)builtin];
@@ -826,6 +737,9 @@ const Shader::UniformInfo *Shader::getUniformInfo(BuiltinUniform builtin) const
 
 void Shader::updateUniform(const UniformInfo *info, int count)
 {
+	if (info->dataSize == 0)
+		return;
+
 	if (current == this)
 		Graphics::flushBatchedDrawsGlobal();
 
@@ -895,12 +809,21 @@ void Shader::sendTextures(const UniformInfo *info, love::graphics::Texture **tex
 
 		tex->retain();
 
-		if (info->textures[i] != nullptr)
-			info->textures[i]->release();
+		int resourceindex = info->resourceIndex + i;
 
-		info->textures[i] = tex;
+		if (activeTextures[resourceindex] != nullptr)
+			activeTextures[resourceindex]->release();
 
-		auto &binding = textureBindings[info->ints[i]];
+		activeTextures[resourceindex] = tex;
+
+		if (info->dataSize == 0)
+			continue;
+
+		int bindingindex = info->ints[i];
+		if (bindingindex < 0)
+			continue;
+
+		auto &binding = textureBindings[bindingindex];
 		if (isdefault && (binding.access & ACCESS_WRITE) != 0)
 		{
 			binding.texture = nil;
@@ -949,18 +872,24 @@ void Shader::sendBuffers(const UniformInfo *info, love::graphics::Buffer **buffe
 
 		buffer->retain();
 
-		if (info->buffers[i] != nullptr)
-			info->buffers[i]->release();
+		int resourceindex = info->resourceIndex + i;
+
+		if (activeBuffers[resourceindex] != nullptr)
+			activeBuffers[resourceindex]->release();
 
-		info->buffers[i] = buffer;
+		activeBuffers[resourceindex] = buffer;
+
+		if (info->dataSize == 0)
+			continue;
 
-		if (texelbinding)
+		int bindingindex = info->ints[i];
+		if (texelbinding && bindingindex >= 0)
 		{
-			textureBindings[info->ints[i]].texture = getMTLTexture(buffer);
+			textureBindings[bindingindex].texture = getMTLTexture(buffer);
 		}
-		else if (storagebinding)
+		else if (storagebinding && bindingindex >= 0)
 		{
-			auto &binding = bufferBindings[info->ints[i]];
+			auto &binding = bufferBindings[bindingindex];
 			if (isdefault && (binding.access & ACCESS_WRITE) != 0)
 				binding.buffer = nil;
 			else
@@ -987,11 +916,6 @@ void Shader::setVideoTextures(love::graphics::Texture *ytexture, love::graphics:
 	}
 }
 
-bool Shader::hasUniform(const std::string &name) const
-{
-	return uniforms.find(name) != uniforms.end();
-}
-
 id<MTLRenderPipelineState> Shader::getCachedRenderPipeline(const RenderPipelineKey &key)
 {
 	auto it = cachedRenderPipelines.find(key);

+ 96 - 588
src/modules/graphics/opengl/Shader.cpp

@@ -38,11 +38,6 @@ namespace graphics
 namespace opengl
 {
 
-static bool isBuffer(Shader::UniformType utype)
-{
-	return utype == Shader::UNIFORM_TEXELBUFFER || utype == Shader::UNIFORM_STORAGEBUFFER;
-}
-
 Shader::Shader(StrongRef<love::graphics::ShaderStage> stages[SHADERSTAGE_MAX_ENUM], const CompileOptions &options)
 	: love::graphics::Shader(stages, options)
 	, program(0)
@@ -58,32 +53,11 @@ Shader::~Shader()
 {
 	unloadVolatile();
 
-	for (const auto &p : uniforms)
+	for (const auto &p : reflection.allUniforms)
 	{
 		// Allocated with malloc().
-		if (p.second.data != nullptr)
-			free(p.second.data);
-
-		if (p.second.baseType == UNIFORM_SAMPLER || p.second.baseType == UNIFORM_STORAGETEXTURE)
-		{
-			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 (isBuffer(p.second.baseType))
-		{
-			for (int i = 0; i < p.second.count; i++)
-			{
-				if (p.second.buffers[i] != nullptr)
-					p.second.buffers[i]->release();
-			}
-
-			delete[] p.second.buffers;
-		}
+		if (p.second->data != nullptr)
+			free(p.second->data);
 	}
 }
 
@@ -96,6 +70,26 @@ void Shader::mapActiveUniforms()
 		builtinUniformInfo[i] = nullptr;
 	}
 
+	// Make sure all stored resources have their Volatiles loaded before
+	// the sendTextures/sendBuffers calls below, since they call getHandle().
+	for (love::graphics::Texture *tex : activeTextures)
+	{
+		if (tex == nullptr)
+			continue;
+		Volatile *v = dynamic_cast<Volatile *>(tex);
+		if (v != nullptr)
+			v->loadVolatile();
+	}
+
+	for (love::graphics::Buffer *buffer : activeBuffers)
+	{
+		if (buffer == nullptr)
+			continue;
+		Volatile *v = dynamic_cast<Volatile *>(buffer);
+		if (v != nullptr)
+			v->loadVolatile();
+	}
+
 	GLint activeprogram = 0;
 	glGetIntegerv(GL_CURRENT_PROGRAM, &activeprogram);
 
@@ -107,43 +101,39 @@ void Shader::mapActiveUniforms()
 	GLchar cname[256];
 	const GLint bufsize = (GLint) (sizeof(cname) / sizeof(GLchar));
 
-	std::map<std::string, UniformInfo> olduniforms = uniforms;
-	uniforms.clear();
-
-	auto gfx = Module::getInstance<love::graphics::Graphics>(Module::M_GRAPHICS);
-
 	for (int uindex = 0; uindex < numuniforms; uindex++)
 	{
 		GLsizei namelen = 0;
 		GLenum gltype = 0;
-		UniformInfo u = {};
+		int count = 0;
 
-		glGetActiveUniform(program, (GLuint) uindex, bufsize, &namelen, &u.count, &gltype, cname);
+		glGetActiveUniform(program, (GLuint) uindex, bufsize, &namelen, &count, &gltype, cname);
 
-		u.name = std::string(cname, (size_t) namelen);
-		u.location = glGetUniformLocation(program, u.name.c_str());
-		u.access = ACCESS_READ;
-		computeUniformTypeInfo(gltype, u);
+		std::string name(cname, (size_t) namelen);
+		int location = glGetUniformLocation(program, name.c_str());
+
+		if (location == -1)
+			continue;
 
-		// glGetActiveUniform appends "[0]" to the end of array uniform names...
-		if (u.name.length() > 3)
+		name = canonicaliizeUniformName(name);
+
+		const auto &uniformit = reflection.allUniforms.find(name);
+		if (uniformit == reflection.allUniforms.end())
 		{
-			size_t findpos = u.name.find("[0]");
-			if (findpos != std::string::npos && findpos == u.name.length() - 3)
-				u.name.erase(u.name.length() - 3);
+			handleUnknownUniformName(name.c_str());
+			continue;
 		}
 
+		UniformInfo &u = *uniformit->second;
+
+		u.active = true;
+		u.location = location;
+
 		// If this is a built-in (LOVE-created) uniform, store the location.
 		BuiltinUniform builtin = BUILTIN_MAX_ENUM;
 		if (getConstant(u.name.c_str(), builtin))
 			builtinUniforms[int(builtin)] = u.location;
 
-		if (u.location == -1)
-			continue;
-
-		if (!fillUniformReflectionData(u))
-			continue;
-
 		if ((u.baseType == UNIFORM_SAMPLER && builtin != BUILTIN_TEXTURE_MAIN) || u.baseType == UNIFORM_TEXELBUFFER)
 		{
 			TextureUnit unit;
@@ -175,183 +165,51 @@ void Shader::mapActiveUniforms()
 				storageTextureBindings.push_back(binding);
 		}
 
-		// Make sure previously set uniform data is preserved, and shader-
-		// initialized values are retrieved.
-		auto oldu = olduniforms.find(u.name);
-		if (oldu != olduniforms.end())
+		if (u.dataSize == 0)
 		{
-			u.data = oldu->second.data;
-			u.dataSize = oldu->second.dataSize;
-			u.textures = oldu->second.textures;
+			if (u.baseType == UNIFORM_MATRIX)
+				u.dataSize = sizeof(uint32) * u.matrix.rows * u.matrix.columns * u.count;
+			else
+				u.dataSize = sizeof(uint32) * u.components * u.count;
 
-			updateUniform(&u, u.count, true);
-		}
-		else
-		{
-			u.dataSize = 0;
+			u.data = malloc(u.dataSize);
+			memset(u.data, 0, u.dataSize);
 
-			switch (u.baseType)
+			const auto &valuesit = reflection.localUniformInitializerValues.find(u.name);
+			if (valuesit != reflection.localUniformInitializerValues.end())
 			{
-			case UNIFORM_FLOAT:
-				u.dataSize = sizeof(float) * u.components * u.count;
-				u.data = malloc(u.dataSize);
-				break;
-			case UNIFORM_INT:
-			case UNIFORM_BOOL:
-			case UNIFORM_SAMPLER:
-			case UNIFORM_STORAGETEXTURE:
-			case UNIFORM_TEXELBUFFER:
-				u.dataSize = sizeof(int) * u.components * u.count;
-				u.data = malloc(u.dataSize);
-				break;
-			case UNIFORM_UINT:
-				u.dataSize = sizeof(unsigned int) * u.components * u.count;
-				u.data = malloc(u.dataSize);
-				break;
-			case UNIFORM_MATRIX:
-				u.dataSize = sizeof(float) * ((size_t)u.matrix.rows * u.matrix.columns) * u.count;
-				u.data = malloc(u.dataSize);
-				break;
-			default:
-				break;
+				const auto &values = valuesit->second;
+				if (!values.empty())
+					memcpy(u.data, values.data(), std::min(u.dataSize, sizeof(LocalUniformValue) * values.size()));
 			}
+		}
 
-			if (u.dataSize > 0)
-			{
-				memset(u.data, 0, u.dataSize);
-
-				if (u.baseType == UNIFORM_SAMPLER || u.baseType == UNIFORM_TEXELBUFFER)
-				{
-					int startunit = (int) textureUnits.size() - u.count;
-
-					if (builtin == BUILTIN_TEXTURE_MAIN)
-						startunit = 0;
-
-					for (int i = 0; i < u.count; i++)
-						u.ints[i] = startunit + i;
-
-					glUniform1iv(u.location, u.count, u.ints);
-
-					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];
-
-						auto *tex = gfx->getDefaultTexture(u.textureType, u.dataBaseType);
-						for (int i = 0; i < u.count; i++)
-						{
-							tex->retain();
-							u.textures[i] = tex;
-						}
-					}
-				}
-				else if (u.baseType == UNIFORM_STORAGETEXTURE)
-				{
-					int startbinding = (int) storageTextureBindings.size() - u.count;
-					for (int i = 0; i < u.count; i++)
-						u.ints[i] = startbinding + i;
-
-					glUniform1iv(u.location, u.count, u.ints);
-
-					u.textures = new love::graphics::Texture*[u.count];
-
-					if ((u.access & ACCESS_WRITE) != 0)
-					{
-						memset(u.textures, 0, sizeof(Texture *) * u.count);
-					}
-					else
-					{
-						auto *tex = gfx->getDefaultTexture(u.textureType, u.dataBaseType);
-						for (int i = 0; i < u.count; i++)
-						{
-							tex->retain();
-							u.textures[i] = tex;
-						}
-					}
-				}
-			}
+		if (u.baseType == UNIFORM_SAMPLER || u.baseType == UNIFORM_TEXELBUFFER)
+		{
+			int startunit = (int) textureUnits.size() - u.count;
 
-			size_t offset = 0;
+			if (builtin == BUILTIN_TEXTURE_MAIN)
+				startunit = 0;
 
-			// Store any shader-initialized values in our own memory.
 			for (int i = 0; i < u.count; i++)
-			{
-				GLint location = u.location;
-
-				if (u.count > 1)
-				{
-					std::ostringstream ss;
-					ss << i;
-
-					std::string indexname = u.name + "[" + ss.str() + "]";
-					location = glGetUniformLocation(program, indexname.c_str());
-				}
-
-				if (location == -1)
-					continue;
-
-				switch (u.baseType)
-				{
-				case UNIFORM_FLOAT:
-					glGetUniformfv(program, location, &u.floats[offset]);
-					offset += u.components;
-					break;
-				case UNIFORM_INT:
-				case UNIFORM_BOOL:
-					glGetUniformiv(program, location, &u.ints[offset]);
-					offset += u.components;
-					break;
-				case UNIFORM_UINT:
-					glGetUniformuiv(program, location, &u.uints[offset]);
-					offset += u.components;
-					break;
-				case UNIFORM_MATRIX:
-					glGetUniformfv(program, location, &u.floats[offset]);
-					offset += (size_t)u.matrix.rows * u.matrix.columns;
-					break;
-				default:
-					break;
-				}
-			}
+				u.ints[i] = startunit + i;
+		}
+		else if (u.baseType == UNIFORM_STORAGETEXTURE)
+		{
+			int startbinding = (int) storageTextureBindings.size() - u.count;
+			for (int i = 0; i < u.count; i++)
+				u.ints[i] = startbinding + i;
 		}
 
-		uniforms[u.name] = u;
+		updateUniform(&u, u.count, true);
 
 		if (builtin != BUILTIN_MAX_ENUM)
-			builtinUniformInfo[(int)builtin] = &uniforms[u.name];
+			builtinUniformInfo[(int)builtin] = &u;
 
 		if (u.baseType == UNIFORM_SAMPLER || u.baseType == UNIFORM_STORAGETEXTURE)
-		{
-			// Make sure all stored textures have their Volatiles loaded before
-			// the sendTextures call, since it calls getHandle().
-			for (int i = 0; i < u.count; i++)
-			{
-				if (u.textures[i] == nullptr)
-					continue;
-				Volatile *v = dynamic_cast<Volatile *>(u.textures[i]);
-				if (v != nullptr)
-					v->loadVolatile();
-			}
-
-			sendTextures(&u, u.textures, u.count, true);
-		}
+			sendTextures(&u, &activeTextures[u.resourceIndex], 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);
-		}
+			sendBuffers(&u, &activeBuffers[u.resourceIndex], u.count, true);
 	}
 
 	if (gl.isBufferUsageSupported(BUFFERUSAGE_SHADER_STORAGE))
@@ -364,37 +222,28 @@ void Shader::mapActiveUniforms()
 
 		for (int sindex = 0; sindex < numstoragebuffers; sindex++)
 		{
-			UniformInfo u = {};
-			u.baseType = UNIFORM_STORAGEBUFFER;
-			u.access = ACCESS_READ;
-
 			GLsizei namelength = 0;
 			glGetProgramResourceName(program, GL_SHADER_STORAGE_BLOCK, sindex, 2048, &namelength, namebuffer);
 
-			u.name = std::string(namebuffer, namelength);
-			u.count = 1;
-
-			if (!fillUniformReflectionData(u))
-				continue;
+			std::string name = canonicaliizeUniformName(std::string(namebuffer, namelength));
 
-			// Make sure previously set uniform data is preserved, and shader-
-			// initialized values are retrieved.
-			auto oldu = olduniforms.find(u.name);
-			if (oldu != olduniforms.end())
+			const auto &uniformit = reflection.storageBuffers.find(name);
+			if (uniformit == reflection.storageBuffers.end())
 			{
-				u.data = oldu->second.data;
-				u.dataSize = oldu->second.dataSize;
-				u.buffers = oldu->second.buffers;
+				handleUnknownUniformName(name.c_str());
+				continue;
 			}
-			else
-			{
-				u.dataSize = sizeof(int) * 1;
-				u.data = malloc(u.dataSize);
 
-				u.ints[0] = -1;
+			UniformInfo &u = uniformit->second;
+
+			u.active = true;
 
-				u.buffers = new love::graphics::Buffer * [u.count];
-				memset(u.buffers, 0, sizeof(Buffer*)* u.count);
+			if (u.dataSize == 0)
+			{
+				u.dataSize = sizeof(int) * u.count;
+				u.data = malloc(u.dataSize);
+				for (int i = 0; i < u.count; i++)
+					u.ints[i] = -1;
 			}
 
 			// Unlike local uniforms and attributes, OpenGL doesn't auto-assign storage
@@ -417,56 +266,13 @@ void Shader::mapActiveUniforms()
 				if (u.access & ACCESS_WRITE)
 				{
 					p.second = (int)activeWritableStorageBuffers.size();
-					activeWritableStorageBuffers.push_back(u.buffers[0]);
+					activeWritableStorageBuffers.push_back(activeBuffers[u.resourceIndex]);
 				}
 
 				storageBufferBindingIndexToActiveBinding[binding.bindingindex] = p;
 			}
 
-			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())
-		{
-			if (p.second.data != nullptr)
-				free(p.second.data);
-
-			if (p.second.baseType == UNIFORM_SAMPLER || p.second.baseType == UNIFORM_STORAGETEXTURE)
-			{
-				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 (isBuffer(p.second.baseType))
-			{
-				for (int i = 0; i < p.second.count; i++)
-				{
-					if (p.second.buffers[i] != nullptr)
-						p.second.buffers[i]->release();
-				}
-
-				delete[] p.second.buffers;
-			}
+			sendBuffers(&u, &activeBuffers[u.resourceIndex], u.count, true);
 		}
 	}
 
@@ -649,16 +455,6 @@ void Shader::attach()
 	}
 }
 
-const Shader::UniformInfo *Shader::getUniformInfo(const std::string &name) const
-{
-	const auto it = uniforms.find(name);
-
-	if (it == uniforms.end())
-		return nullptr;
-
-	return &(it->second);
-}
-
 const Shader::UniformInfo *Shader::getUniformInfo(BuiltinUniform builtin) const
 {
 	return builtinUniformInfo[(int)builtin];
@@ -802,10 +598,12 @@ void Shader::sendTextures(const UniformInfo *info, love::graphics::Texture **tex
 
 		tex->retain();
 
-		if (info->textures[i] != nullptr)
-			info->textures[i]->release();
+		int resourceindex = info->resourceIndex + i;
 
-		info->textures[i] = tex;
+		if (activeTextures[resourceindex] != nullptr)
+			activeTextures[resourceindex]->release();
+
+		activeTextures[resourceindex] = tex;
 
 		if (isstoragetex)
 		{
@@ -885,10 +683,12 @@ void Shader::sendBuffers(const UniformInfo *info, love::graphics::Buffer **buffe
 
 		buffer->retain();
 
-		if (info->buffers[i] != nullptr)
-			info->buffers[i]->release();
+		int resourceindex = info->resourceIndex + i;
+
+		if (activeBuffers[resourceindex] != nullptr)
+			activeBuffers[resourceindex]->release();
 
-		info->buffers[i] = buffer;
+		activeBuffers[resourceindex] = buffer;
 
 		if (texelbinding)
 		{
@@ -926,11 +726,6 @@ void Shader::flushBatchedDraws() const
 		Graphics::flushBatchedDrawsGlobal();
 }
 
-bool Shader::hasUniform(const std::string &name) const
-{
-	return uniforms.find(name) != uniforms.end();
-}
-
 ptrdiff_t Shader::getHandle() const
 {
 	return program;
@@ -1043,293 +838,6 @@ void Shader::updateBuiltinUniforms(love::graphics::Graphics *gfx, int viewportW,
 	}
 }
 
-int Shader::getUniformTypeComponents(GLenum type) const
-{
-	switch (type)
-	{
-	case GL_INT:
-	case GL_UNSIGNED_INT:
-	case GL_FLOAT:
-	case GL_BOOL:
-		return 1;
-	case GL_INT_VEC2:
-	case GL_UNSIGNED_INT_VEC2:
-	case GL_FLOAT_VEC2:
-	case GL_FLOAT_MAT2:
-	case GL_BOOL_VEC2:
-		return 2;
-	case GL_INT_VEC3:
-	case GL_UNSIGNED_INT_VEC3:
-	case GL_FLOAT_VEC3:
-	case GL_FLOAT_MAT3:
-	case GL_BOOL_VEC3:
-		return 3;
-	case GL_INT_VEC4:
-	case GL_UNSIGNED_INT_VEC4:
-	case GL_FLOAT_VEC4:
-	case GL_FLOAT_MAT4:
-	case GL_BOOL_VEC4:
-		return 4;
-	default:
-		return 1;
-	}
-}
-
-Shader::MatrixSize Shader::getMatrixSize(GLenum type) const
-{
-	MatrixSize m;
-
-	switch (type)
-	{
-	case GL_FLOAT_MAT2:
-		m.columns = m.rows = 2;
-		break;
-	case GL_FLOAT_MAT3:
-		m.columns = m.rows = 3;
-		break;
-	case GL_FLOAT_MAT4:
-		m.columns = m.rows = 4;
-		break;
-	case GL_FLOAT_MAT2x3:
-		m.columns = 2;
-		m.rows = 3;
-		break;
-	case GL_FLOAT_MAT2x4:
-		m.columns = 2;
-		m.rows = 4;
-		break;
-	case GL_FLOAT_MAT3x2:
-		m.columns = 3;
-		m.rows = 2;
-		break;
-	case GL_FLOAT_MAT3x4:
-		m.columns = 3;
-		m.rows = 4;
-		break;
-	case GL_FLOAT_MAT4x2:
-		m.columns = 4;
-		m.rows = 2;
-		break;
-	case GL_FLOAT_MAT4x3:
-		m.columns = 4;
-		m.rows = 3;
-		break;
-	default:
-		m.columns = m.rows = 0;
-		break;
-	}
-
-	return m;
-}
-
-void Shader::computeUniformTypeInfo(GLenum type, UniformInfo &u)
-{
-	u.isDepthSampler = false;
-	u.components = getUniformTypeComponents(type);
-	u.baseType = UNIFORM_UNKNOWN;
-
-	switch (type)
-	{
-	case GL_INT:
-	case GL_INT_VEC2:
-	case GL_INT_VEC3:
-	case GL_INT_VEC4:
-		u.baseType = UNIFORM_INT;
-		u.dataBaseType = DATA_BASETYPE_INT;
-		break;
-	case GL_UNSIGNED_INT:
-	case GL_UNSIGNED_INT_VEC2:
-	case GL_UNSIGNED_INT_VEC3:
-	case GL_UNSIGNED_INT_VEC4:
-		u.baseType = UNIFORM_UINT;
-		u.dataBaseType = DATA_BASETYPE_UINT;
-		break;
-	case GL_FLOAT:
-	case GL_FLOAT_VEC2:
-	case GL_FLOAT_VEC3:
-	case GL_FLOAT_VEC4:
-		u.baseType = UNIFORM_FLOAT;
-		u.dataBaseType = DATA_BASETYPE_FLOAT;
-		break;
-	case GL_FLOAT_MAT2:
-	case GL_FLOAT_MAT3:
-	case GL_FLOAT_MAT4:
-	case GL_FLOAT_MAT2x3:
-	case GL_FLOAT_MAT2x4:
-	case GL_FLOAT_MAT3x2:
-	case GL_FLOAT_MAT3x4:
-	case GL_FLOAT_MAT4x2:
-	case GL_FLOAT_MAT4x3:
-		u.baseType = UNIFORM_MATRIX;
-		u.dataBaseType = DATA_BASETYPE_FLOAT;
-		u.matrix = getMatrixSize(type);
-		break;
-	case GL_BOOL:
-	case GL_BOOL_VEC2:
-	case GL_BOOL_VEC3:
-	case GL_BOOL_VEC4:
-		u.baseType = UNIFORM_BOOL;
-		u.dataBaseType = DATA_BASETYPE_BOOL;
-		break;
-
-	case GL_SAMPLER_2D:
-		u.baseType = UNIFORM_SAMPLER;
-		u.dataBaseType = DATA_BASETYPE_FLOAT;
-		u.textureType = TEXTURE_2D;
-		break;
-	case GL_SAMPLER_2D_SHADOW:
-		u.baseType = UNIFORM_SAMPLER;
-		u.dataBaseType = DATA_BASETYPE_FLOAT;
-		u.textureType = TEXTURE_2D;
-		u.isDepthSampler = true;
-		break;
-	case GL_INT_SAMPLER_2D:
-		u.baseType = UNIFORM_SAMPLER;
-		u.dataBaseType = DATA_BASETYPE_INT;
-		u.textureType = TEXTURE_2D;
-		break;
-	case GL_UNSIGNED_INT_SAMPLER_2D:
-		u.baseType = UNIFORM_SAMPLER;
-		u.dataBaseType = DATA_BASETYPE_UINT;
-		u.textureType = TEXTURE_2D;
-		break;
-	case GL_SAMPLER_2D_ARRAY:
-		u.baseType = UNIFORM_SAMPLER;
-		u.dataBaseType = DATA_BASETYPE_FLOAT;
-		u.textureType = TEXTURE_2D_ARRAY;
-		break;
-	case GL_SAMPLER_2D_ARRAY_SHADOW:
-		u.baseType = UNIFORM_SAMPLER;
-		u.dataBaseType = DATA_BASETYPE_FLOAT;
-		u.textureType = TEXTURE_2D_ARRAY;
-		u.isDepthSampler = true;
-		break;
-	case GL_INT_SAMPLER_2D_ARRAY:
-		u.baseType = UNIFORM_SAMPLER;
-		u.dataBaseType = DATA_BASETYPE_INT;
-		u.textureType = TEXTURE_2D_ARRAY;
-		break;
-	case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
-		u.baseType = UNIFORM_SAMPLER;
-		u.dataBaseType = DATA_BASETYPE_UINT;
-		u.textureType = TEXTURE_2D_ARRAY;
-		break;
-	case GL_SAMPLER_3D:
-		u.baseType = UNIFORM_SAMPLER;
-		u.dataBaseType = DATA_BASETYPE_FLOAT;
-		u.textureType = TEXTURE_VOLUME;
-		break;
-	case GL_INT_SAMPLER_3D:
-		u.baseType = UNIFORM_SAMPLER;
-		u.dataBaseType = DATA_BASETYPE_INT;
-		u.textureType = TEXTURE_VOLUME;
-		break;
-	case GL_UNSIGNED_INT_SAMPLER_3D:
-		u.baseType = UNIFORM_SAMPLER;
-		u.dataBaseType = DATA_BASETYPE_UINT;
-		u.textureType = TEXTURE_VOLUME;
-		break;
-	case GL_SAMPLER_CUBE:
-		u.baseType = UNIFORM_SAMPLER;
-		u.dataBaseType = DATA_BASETYPE_FLOAT;
-		u.textureType = TEXTURE_CUBE;
-		break;
-	case GL_SAMPLER_CUBE_SHADOW:
-		u.baseType = UNIFORM_SAMPLER;
-		u.dataBaseType = DATA_BASETYPE_FLOAT;
-		u.textureType = TEXTURE_CUBE;
-		u.isDepthSampler = true;
-		break;
-	case GL_INT_SAMPLER_CUBE:
-		u.baseType = UNIFORM_SAMPLER;
-		u.dataBaseType = DATA_BASETYPE_INT;
-		u.textureType = TEXTURE_CUBE;
-		break;
-	case GL_UNSIGNED_INT_SAMPLER_CUBE:
-		u.baseType = UNIFORM_SAMPLER;
-		u.dataBaseType = DATA_BASETYPE_UINT;
-		u.textureType = TEXTURE_CUBE;
-		break;
-
-	case GL_SAMPLER_BUFFER:
-		u.baseType = UNIFORM_TEXELBUFFER;
-		u.dataBaseType = DATA_BASETYPE_FLOAT;
-		break;
-	case GL_INT_SAMPLER_BUFFER:
-		u.baseType = UNIFORM_TEXELBUFFER;
-		u.dataBaseType = DATA_BASETYPE_INT;
-		break;
-	case GL_UNSIGNED_INT_SAMPLER_BUFFER:
-		u.baseType = UNIFORM_TEXELBUFFER;
-		u.dataBaseType = DATA_BASETYPE_UINT;
-		break;
-
-	case GL_IMAGE_2D:
-		u.baseType = UNIFORM_STORAGETEXTURE;
-		u.dataBaseType = DATA_BASETYPE_FLOAT;
-		u.textureType = TEXTURE_2D;
-		break;
-	case GL_INT_IMAGE_2D:
-		u.baseType = UNIFORM_STORAGETEXTURE;
-		u.dataBaseType = DATA_BASETYPE_INT;
-		u.textureType = TEXTURE_2D;
-		break;
-	case GL_UNSIGNED_INT_IMAGE_2D:
-		u.baseType = UNIFORM_STORAGETEXTURE;
-		u.dataBaseType = DATA_BASETYPE_UINT;
-		u.textureType = TEXTURE_2D;
-		break;
-	case GL_IMAGE_2D_ARRAY:
-		u.baseType = UNIFORM_STORAGETEXTURE;
-		u.dataBaseType = DATA_BASETYPE_FLOAT;
-		u.textureType = TEXTURE_2D_ARRAY;
-		break;
-	case GL_INT_IMAGE_2D_ARRAY:
-		u.baseType = UNIFORM_STORAGETEXTURE;
-		u.dataBaseType = DATA_BASETYPE_INT;
-		u.textureType = TEXTURE_2D_ARRAY;
-		break;
-	case GL_UNSIGNED_INT_IMAGE_2D_ARRAY:
-		u.baseType = UNIFORM_STORAGETEXTURE;
-		u.dataBaseType = DATA_BASETYPE_UINT;
-		u.textureType = TEXTURE_2D_ARRAY;
-		break;
-	case GL_IMAGE_3D:
-		u.baseType = UNIFORM_STORAGETEXTURE;
-		u.dataBaseType = DATA_BASETYPE_FLOAT;
-		u.textureType = TEXTURE_VOLUME;
-		break;
-	case GL_INT_IMAGE_3D:
-		u.baseType = UNIFORM_STORAGETEXTURE;
-		u.dataBaseType = DATA_BASETYPE_INT;
-		u.textureType = TEXTURE_VOLUME;
-		break;
-	case GL_UNSIGNED_INT_IMAGE_3D:
-		u.baseType = UNIFORM_STORAGETEXTURE;
-		u.dataBaseType = DATA_BASETYPE_UINT;
-		u.textureType = TEXTURE_VOLUME;
-		break;
-	case GL_IMAGE_CUBE:
-		u.baseType = UNIFORM_STORAGETEXTURE;
-		u.dataBaseType = DATA_BASETYPE_FLOAT;
-		u.textureType = TEXTURE_CUBE;
-		break;
-	case GL_INT_IMAGE_CUBE:
-		u.baseType = UNIFORM_STORAGETEXTURE;
-		u.dataBaseType = DATA_BASETYPE_INT;
-		u.textureType = TEXTURE_CUBE;
-		break;
-	case GL_UNSIGNED_INT_IMAGE_CUBE:
-		u.baseType = UNIFORM_STORAGETEXTURE;
-		u.dataBaseType = DATA_BASETYPE_UINT;
-		u.textureType = TEXTURE_CUBE;
-		break;
-
-	default:
-		break;
-	}
-}
-
 } // opengl
 } // graphics
 } // love

+ 0 - 9
src/modules/graphics/opengl/Shader.h

@@ -63,12 +63,10 @@ public:
 	void attach() override;
 	std::string getWarnings() const override;
 	int getVertexAttributeIndex(const std::string &name) override;
-	const UniformInfo *getUniformInfo(const std::string &name) const override;
 	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;
 
@@ -100,10 +98,6 @@ private:
 	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;
-	void computeUniformTypeInfo(GLenum type, UniformInfo &u);
-	MatrixSize getMatrixSize(GLenum type) const;
-
 	void flushBatchedDraws() const;
 
 	// Get any warnings or errors generated only by the shader program object.
@@ -120,9 +114,6 @@ private:
 
 	std::map<std::string, GLint> attributes;
 
-	// Uniform location buffer map
-	std::map<std::string, UniformInfo> uniforms;
-
 	// Texture unit pool for setting textures
 	std::vector<TextureUnit> textureUnits;
 

+ 86 - 195
src/modules/graphics/vulkan/Shader.cpp

@@ -173,31 +173,6 @@ void Shader::unloadVolatile()
 	if (shaderModules.empty())
 		return;
 
-	for (const auto &uniform : uniformInfos)
-	{
-		switch (uniform.second.baseType)
-		{
-		case UNIFORM_SAMPLER:
-		case UNIFORM_STORAGETEXTURE:
-			for (int i = 0; i < uniform.second.count; i++)
-			{
-				if (uniform.second.textures[i] != nullptr)
-					uniform.second.textures[i]->release();
-			}
-			delete[] uniform.second.textures;
-			break;
-		case UNIFORM_TEXELBUFFER:
-		case UNIFORM_STORAGEBUFFER:
-			for (int i = 0; i < uniform.second.count; i++)
-			{
-				if (uniform.second.buffers[i] != nullptr)
-					uniform.second.buffers[i]->release();
-			}
-			delete[] uniform.second.buffers;
-			break;
-		}
-	}
-
 	vgfx->queueCleanUp([shaderModules = std::move(shaderModules), device = device, descriptorSetLayout = descriptorSetLayout, pipelineLayout = pipelineLayout, descriptorPools = descriptorPools, computePipeline = computePipeline](){
 		for (const auto &pools : descriptorPools)
 		{
@@ -320,9 +295,9 @@ void Shader::cmdPushDescriptorSets(VkCommandBuffer commandBuffer, VkPipelineBind
 		currentUsedUniformStreamBuffersCount++;
 	}
 
-	for (const auto &u : uniformInfos)
+	for (const auto &u : reflection.allUniforms)
 	{
-		auto &info = u.second;
+		auto &info = *(u.second);
 
 		if (usesLocalUniformData(&info))
 			continue;
@@ -333,7 +308,7 @@ void Shader::cmdPushDescriptorSets(VkCommandBuffer commandBuffer, VkPipelineBind
 
 			for (int i = 0; i < info.count; i++)
 			{
-				auto vkTexture = dynamic_cast<Texture*>(info.textures[i]);
+				auto vkTexture = dynamic_cast<Texture*>(activeTextures[info.resourceIndex + i]);
 
 				if (vkTexture == nullptr)
 					throw love::Exception("uniform variable %s is not set.", info.name.c_str());
@@ -374,13 +349,14 @@ void Shader::cmdPushDescriptorSets(VkCommandBuffer commandBuffer, VkPipelineBind
 
 			for (int i = 0; i < info.count; i++)
 			{
-				if (info.buffers[i] == nullptr)
+				auto b = activeBuffers[info.resourceIndex + i];
+				if (b == nullptr)
 					throw love::Exception("uniform variable %s is not set.", info.name.c_str());
 
 				VkDescriptorBufferInfo bufferInfo{};
-				bufferInfo.buffer = (VkBuffer)info.buffers[i]->getHandle();;
+				bufferInfo.buffer = (VkBuffer)b->getHandle();
 				bufferInfo.offset = 0;
-				bufferInfo.range = info.buffers[i]->getSize();
+				bufferInfo.range = b->getSize();
 
 				bufferInfos.push_back(bufferInfo);
 			}
@@ -396,15 +372,16 @@ void Shader::cmdPushDescriptorSets(VkCommandBuffer commandBuffer, VkPipelineBind
 			write.dstSet = currentDescriptorSet;
 			write.dstBinding = info.location;
 			write.dstArrayElement = 0;
-			write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER;
+			write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER;
 			write.descriptorCount = info.count;
 
 			for (int i = 0; i < info.count; i++)
 			{
-				if (info.buffers[i] == nullptr)
+				auto b = activeBuffers[info.resourceIndex + i];
+				if (b == nullptr)
 					throw love::Exception("uniform variable %s is not set.", info.name.c_str());
 
-				bufferViews.push_back((VkBufferView)info.buffers[i]->getTexelBufferHandle());
+				bufferViews.push_back((VkBufferView)b->getTexelBufferHandle());
 			}
 
 			write.pTexelBufferView = &bufferViews[bufferViews.size() - info.count];
@@ -444,12 +421,6 @@ int Shader::getVertexAttributeIndex(const std::string &name)
 	return it == attributes.end() ? -1 : it->second;
 }
 
-const Shader::UniformInfo *Shader::getUniformInfo(const std::string &name) const
-{
-	const auto it = uniformInfos.find(name);
-	return it != uniformInfos.end() ? &(it->second) : nullptr;
-}
-
 const Shader::UniformInfo *Shader::getUniformInfo(BuiltinUniform builtin) const
 {
 	return builtinUniformInfo[builtin];
@@ -468,9 +439,10 @@ void Shader::sendTextures(const UniformInfo *info, graphics::Texture **textures,
 {
 	for (int i = 0; i < count; i++)
 	{
-		auto oldTexture = info->textures[i];
-		info->textures[i] = textures[i];
-		info->textures[i]->retain();
+		int resourceindex = info->resourceIndex + i;
+		auto oldTexture = activeTextures[resourceindex];
+		activeTextures[resourceindex] = textures[i];
+		activeTextures[resourceindex]->retain();
 		if (oldTexture)
 			oldTexture->release();
 	}
@@ -480,9 +452,10 @@ void Shader::sendBuffers(const UniformInfo *info, love::graphics::Buffer **buffe
 {
 	for (int i = 0; i < count; i++)
 	{
-		auto oldBuffer = info->buffers[i];
-		info->buffers[i] = buffers[i];
-		info->buffers[i]->retain();
+		int resourceindex = info->resourceIndex + i;
+		auto oldBuffer = activeBuffers[resourceindex];
+		activeBuffers[resourceindex] = buffers[i];
+		activeBuffers[resourceindex]->retain();
 		if (oldBuffer)
 			oldBuffer->release();
 	}
@@ -524,38 +497,25 @@ void Shader::buildLocalUniforms(spirv_cross::Compiler &comp, const spirv_cross::
 			continue;
 		}
 
-		UniformInfo u{};
-		u.name = name;
-		u.dataSize = memberSize;
-		u.count = memberType.array.empty() ? 1 : memberType.array[0];
-		u.components = 1;
-		u.data = localUniformStagingData.data() + offset;
+		name = canonicaliizeUniformName(name);
 
-		if (memberType.columns == 1)
+		auto uniformit = reflection.allUniforms.find(name);
+		if (uniformit == reflection.allUniforms.end())
 		{
-			if (memberType.basetype == SPIRType::Int)
-				u.baseType = UNIFORM_INT;
-			else if (memberType.basetype == SPIRType::UInt)
-				u.baseType = UNIFORM_UINT;
-			else
-				u.baseType = UNIFORM_FLOAT;
-			u.components = memberType.vecsize;
-		}
-		else
-		{
-			u.baseType = UNIFORM_MATRIX;
-			u.matrix.rows = memberType.vecsize;
-			u.matrix.columns = memberType.columns;
+			handleUnknownUniformName(name.c_str());
+			continue;
 		}
 
-		const auto &reflectionIt = validationReflection.localUniforms.find(u.name);
-		if (reflectionIt != validationReflection.localUniforms.end())
-		{
-			const auto &localUniform = reflectionIt->second;
-			if (localUniform.dataType == DATA_BASETYPE_BOOL)
-				u.baseType = UNIFORM_BOOL;
+		UniformInfo &u = *(uniformit->second);
 
-			const auto &values = localUniform.initializerValues;
+		u.active = true;
+		u.dataSize = memberSize;
+		u.data = localUniformStagingData.data() + offset;
+
+		const auto &valuesit = reflection.localUniformInitializerValues.find(name);
+		if (valuesit != reflection.localUniformInitializerValues.end())
+		{
+			const auto &values = valuesit->second;
 			if (!values.empty())
 				memcpy(
 					u.data,
@@ -563,14 +523,12 @@ void Shader::buildLocalUniforms(spirv_cross::Compiler &comp, const spirv_cross::
 					std::min(u.dataSize, values.size() * sizeof(LocalUniformValue)));
 		}
 
-		uniformInfos[u.name] = u;
-
 		BuiltinUniform builtin = BUILTIN_MAX_ENUM;
 		if (getConstant(u.name.c_str(), builtin))
 		{
 			if (builtin == BUILTIN_UNIFORMS_PER_DRAW)
 				builtinUniformDataOffset = offset;
-			builtinUniformInfo[builtin] = &uniformInfos[u.name];
+			builtinUniformInfo[builtin] = &u;
 		}
 	}
 }
@@ -643,8 +601,6 @@ void Shader::compileShaders()
 	if (!program->mapIO())
 		throw love::Exception("mapIO failed");
 
-	uniformInfos.clear();
-
 	BindingMapper bindingMapper;
 
 	for (int i = 0; i < SHADERSTAGE_MAX_ENUM; i++)
@@ -695,119 +651,51 @@ void Shader::compileShaders()
 
 		for (const auto &r : shaderResources.sampled_images)
 		{
-			const SPIRType &basetype = comp.get_type(r.base_type_id);
-			const SPIRType &type = comp.get_type(r.type_id);
-			const SPIRType &imagetype = comp.get_type(basetype.image.type);
-
-			graphics::Shader::UniformInfo info;
-			info.location = bindingMapper(comp, spirv, r.name, r.id);
-			info.baseType = UNIFORM_SAMPLER;
-			info.name = r.name;
-			info.count = type.array.empty() ? 1 : type.array[0];
-			info.isDepthSampler = type.image.depth;
-			info.components = 1;
-
-			switch (imagetype.basetype)
+			std::string name = canonicaliizeUniformName(r.name);
+			auto uniformit = reflection.allUniforms.find(name);
+			if (uniformit == reflection.allUniforms.end())
 			{
-			case SPIRType::Float:
-				info.dataBaseType = DATA_BASETYPE_FLOAT;
-				break;
-			case SPIRType::Int:
-				info.dataBaseType = DATA_BASETYPE_INT;
-				break;
-			case SPIRType::UInt:
-				info.dataBaseType = DATA_BASETYPE_UINT;
-				break;
-			default:
-				break;
+				handleUnknownUniformName(name.c_str());
+				continue;
 			}
 
-			switch (basetype.image.dim)
-			{
-			case spv::Dim2D:
-				info.textureType = basetype.image.arrayed ? TEXTURE_2D_ARRAY : TEXTURE_2D;
-				info.textures = new love::graphics::Texture *[info.count];
-				break;
-			case spv::Dim3D:
-				info.textureType = TEXTURE_VOLUME;
-				info.textures = new love::graphics::Texture *[info.count];
-				break;
-			case spv::DimCube:
-				if (basetype.image.arrayed) {
-					throw love::Exception("cubemap arrays are not currently supported");
-				}
-				info.textureType = TEXTURE_CUBE;
-				info.textures = new love::graphics::Texture *[info.count];
-				break;
-			case spv::DimBuffer:
-				info.baseType = UNIFORM_TEXELBUFFER;
-				info.buffers = new love::graphics::Buffer *[info.count];
-				break;
-			default:
-				throw love::Exception("unknown dim");
-			}
+			UniformInfo &u = *(uniformit->second);
+			u.active = true;
+			u.location = bindingMapper(comp, spirv, name, r.id);
 
-			if (info.baseType == UNIFORM_TEXELBUFFER)
-			{
-				for (int i = 0; i < info.count; i++)
-					info.buffers[i] = nullptr;
-			}
-			else
-			{
-				for (int i = 0; i < info.count; i++)
-				{
-					info.textures[i] = nullptr;
-				}
-			}
-
-			uniformInfos[r.name] = info;
 			BuiltinUniform builtin;
-			if (getConstant(r.name.c_str(), builtin))
-				builtinUniformInfo[builtin] = &uniformInfos[info.name];
+			if (getConstant(name.c_str(), builtin))
+				builtinUniformInfo[builtin] = &u;
 		}
 
 		for (const auto &r : shaderResources.storage_buffers)
 		{
-			const auto &type = comp.get_type(r.type_id);
-
-			UniformInfo u{};
-			u.baseType = UNIFORM_STORAGEBUFFER;
-			u.components = 1;
-			u.name = r.name;
-			u.count = type.array.empty() ? 1 : type.array[0];
-
-			if (!fillUniformReflectionData(u))
+			std::string name = canonicaliizeUniformName(r.name);
+			auto &uniformit = reflection.storageBuffers.find(name);
+			if (uniformit == reflection.storageBuffers.end())
+			{
+				handleUnknownUniformName(name.c_str());
 				continue;
+			}
 
-			u.location = bindingMapper(comp, spirv, r.name, r.id);
-			u.buffers = new love::graphics::Buffer *[u.count];
-
-			for (int i = 0; i < u.count; i++)
-				u.buffers[i] = nullptr;
-
-			uniformInfos[u.name] = u;
+			UniformInfo &u = uniformit->second;
+			u.active = true;
+			u.location = bindingMapper(comp, spirv, name, r.id);
 		}
 
 		for (const auto &r : shaderResources.storage_images)
 		{
-			const auto &type = comp.get_type(r.type_id);
-
-			UniformInfo u{};
-			u.baseType = UNIFORM_STORAGETEXTURE;
-			u.components = 1;
-			u.name = r.name;
-			u.count = type.array.empty() ? 1 : type.array[0];
-
-			if (!fillUniformReflectionData(u))
+			std::string name = canonicaliizeUniformName(r.name);
+			auto &uniformit = reflection.storageBuffers.find(name);
+			if (uniformit == reflection.storageBuffers.end())
+			{
+				handleUnknownUniformName(name.c_str());
 				continue;
+			}
 
-			u.textures = new love::graphics::Texture *[u.count];
-			u.location = bindingMapper(comp, spirv, r.name, r.id);
-
-			for (int i = 0; i < u.count; i++)
-				u.textures[i] = nullptr;
-
-			uniformInfos[u.name] = u;
+			UniformInfo &u = uniformit->second;
+			u.active = true;
+			u.location = bindingMapper(comp, spirv, name, r.id);
 		}
 
 		if (shaderStage == SHADERSTAGE_VERTEX)
@@ -875,9 +763,12 @@ void Shader::compileShaders()
 	if (localUniformData.size() > 0)
 		numBuffers++;
 
-	for (const auto &u : uniformInfos)
+	for (const auto kvp : reflection.allUniforms)
 	{
-		switch (u.second.baseType)
+		if (kvp.second->location < 0)
+			continue;
+
+		switch (kvp.second->baseType)
 		{
 		case UNIFORM_SAMPLER:
 		case UNIFORM_STORAGETEXTURE:
@@ -905,16 +796,16 @@ void Shader::createDescriptorSetLayout()
 	else
 		stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
 
-	for (auto const &entry : uniformInfos)
+	for (auto const &entry : reflection.allUniforms)
 	{
-		auto type = Vulkan::getDescriptorType(entry.second.baseType);
+		auto type = Vulkan::getDescriptorType(entry.second->baseType);
 		if (type != VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER)
 		{
 			VkDescriptorSetLayoutBinding layoutBinding{};
 
-			layoutBinding.binding = entry.second.location;
+			layoutBinding.binding = entry.second->location;
 			layoutBinding.descriptorType = type;
-			layoutBinding.descriptorCount = entry.second.count;
+			layoutBinding.descriptorCount = entry.second->count;
 			layoutBinding.stageFlags = stageFlags;
 
 			bindings.push_back(layoutBinding);
@@ -976,10 +867,13 @@ void Shader::createDescriptorPoolSizes()
 		descriptorPoolSizes.push_back(size);
 	}
 
-	for (const auto &entry : uniformInfos)
+	for (const auto &entry : reflection.allUniforms)
 	{
+		if (entry.second->location < 0)
+			continue;
+
 		VkDescriptorPoolSize size{};
-		auto type = Vulkan::getDescriptorType(entry.second.baseType);
+		auto type = Vulkan::getDescriptorType(entry.second->baseType);
 		if (type == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER)
 			continue;
 
@@ -1012,29 +906,26 @@ void Shader::setVideoTextures(graphics::Texture *ytexture, graphics::Texture *cb
 
 	for (size_t i = 0; i < textures.size(); i++)
 	{
-		if (builtinUniformInfo[builtIns[i]] != nullptr)
+		const UniformInfo *u = builtinUniformInfo[builtIns[i]];
+		if (u != nullptr)
 		{
 			textures[i]->retain();
-			if (builtinUniformInfo[builtIns[i]]->textures[0])
-				builtinUniformInfo[builtIns[i]]->textures[0]->release();
-			builtinUniformInfo[builtIns[i]]->textures[0] = textures[i];
+			if (activeTextures[u->resourceIndex])
+				activeTextures[u->resourceIndex]->release();
+			activeTextures[u->resourceIndex] = textures[i];
 		}
 	}
 }
 
-bool Shader::hasUniform(const std::string &name) const
-{
-	return uniformInfos.find(name) != uniformInfos.end();
-}
-
 void Shader::setMainTex(graphics::Texture *texture)
 {
-	if (builtinUniformInfo[BUILTIN_TEXTURE_MAIN] != nullptr)
+	const UniformInfo *u = builtinUniformInfo[BUILTIN_TEXTURE_MAIN];
+	if (u != nullptr)
 	{
 		texture->retain();
-		if (builtinUniformInfo[BUILTIN_TEXTURE_MAIN]->textures[0]) 
-			builtinUniformInfo[BUILTIN_TEXTURE_MAIN]->textures[0]->release();
-		builtinUniformInfo[BUILTIN_TEXTURE_MAIN]->textures[0] = texture;
+		if (activeTextures[u->resourceIndex])
+			activeTextures[u->resourceIndex]->release();
+		activeTextures[u->resourceIndex] = texture;
 	}
 }
 

+ 0 - 4
src/modules/graphics/vulkan/Shader.h

@@ -76,7 +76,6 @@ public:
 
 	int getVertexAttributeIndex(const std::string &name) override;
 
-	const UniformInfo *getUniformInfo(const std::string &name) const override;
 	const UniformInfo *getUniformInfo(BuiltinUniform builtin) const override;
 
 	void updateUniform(const UniformInfo *info, int count) override;
@@ -84,8 +83,6 @@ public:
 	void sendTextures(const UniformInfo *info, 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;
-
 	void setVideoTextures(graphics::Texture *ytexture, graphics::Texture *cbtexture, graphics::Texture *crtexture) override;
 
 	void setMainTex(graphics::Texture *texture);
@@ -126,7 +123,6 @@ private:
 
 	bool isCompute = false;
 
-	std::unordered_map<std::string, graphics::Shader::UniformInfo> uniformInfos;
 	UniformInfo *builtinUniformInfo[BUILTIN_MAX_ENUM];
 
 	std::unique_ptr<StreamBuffer> uniformBufferObjectBuffer;

+ 4 - 7
src/modules/graphics/wrap_Shader.cpp

@@ -446,7 +446,7 @@ int w_Shader_send(lua_State *L)
 	const char *name = luaL_checkstring(L, 2);
 
 	const Shader::UniformInfo *info = shader->getUniformInfo(name);
-	if (info == nullptr)
+	if (info == nullptr || !info->active)
 		return luaL_error(L, "Shader uniform '%s' does not exist.\nA common error is to define but not use the variable.", name);
 
 	if (luax_istype(L, 3, Data::type) || (info->baseType == Shader::UNIFORM_MATRIX && luax_istype(L, 4, Data::type)))
@@ -461,14 +461,11 @@ int w_Shader_sendColors(lua_State *L)
 	const char *name = luaL_checkstring(L, 2);
 
 	const Shader::UniformInfo *info = shader->getUniformInfo(name);
-	if (info == nullptr)
-	{
-		luax_pushboolean(L, false);
-		return 1;
-	}
+	if (info == nullptr || !info->active)
+		return luaL_error(L, "Shader uniform '%s' does not exist.\nA common error is to define but not use the variable.", name);
 
 	if (info->baseType != Shader::UNIFORM_FLOAT || info->components < 3)
-		return luaL_error(L, "sendColor can only be used on vec3 or vec4 uniforms.");
+		return luaL_error(L, "Shader:sendColor can only be used with vec3 or vec4 uniforms.");
 
 	if (luax_istype(L, 3, Data::type))
 		w_Shader_sendData(L, 3, shader, info, true);