Browse Source

Shader uniforms keep their values after love.window.setMode (resolves issue #1174).

Also allow arrays of images/samplers in shaders, although driver support is iffy and you have to index into the array using a compile-time constant (which might exclude loop indexing).

--HG--
branch : minor
Alex Szpakowski 8 years ago
parent
commit
9d2ee6d909

+ 3 - 0
src/modules/graphics/opengl/Canvas.cpp

@@ -151,6 +151,9 @@ Canvas::~Canvas()
 
 bool Canvas::loadVolatile()
 {
+	if (texture != 0)
+		return true;
+
 	OpenGL::TempDebugGroup debuggroup("Canvas load");
 
 	fbo = texture = 0;

+ 3 - 0
src/modules/graphics/opengl/Image.cpp

@@ -281,6 +281,9 @@ void Image::loadFromImageData()
 
 bool Image::loadVolatile()
 {
+	if (texture != 0)
+		return true;
+
 	OpenGL::TempDebugGroup debuggroup("Image load");
 
 	if (isCompressed() && !hasCompressedTextureSupport(cdata[0]->getFormat(), sRGB))

+ 2 - 2
src/modules/graphics/opengl/OpenGL.h

@@ -106,8 +106,8 @@ public:
 
 	enum FramebufferTarget
 	{
-		FRAMEBUFFER_READ = (1 << 1),
-		FRAMEBUFFER_DRAW = (1 << 2),
+		FRAMEBUFFER_READ = (1 << 0),
+		FRAMEBUFFER_DRAW = (1 << 1),
 		FRAMEBUFFER_ALL  = (FRAMEBUFFER_READ | FRAMEBUFFER_DRAW),
 	};
 

+ 290 - 181
src/modules/graphics/opengl/Shader.cpp

@@ -41,11 +41,12 @@ namespace
 	// reattaches the originally active program when destroyed
 	struct TemporaryAttacher
 	{
-		TemporaryAttacher(Shader *shader)
+		TemporaryAttacher(Shader *shader, bool attachNow)
 		: curShader(shader)
 		, prevShader(Shader::current)
 		{
-			curShader->attach(true);
+			if (attachNow)
+				attach();
 		}
 
 		~TemporaryAttacher()
@@ -56,6 +57,11 @@ namespace
 				curShader->detach();
 		}
 
+		void attach()
+		{
+			curShader->attach(true);
+		}
+
 		Shader *curShader;
 		Shader *prevShader;
 	};
@@ -69,8 +75,6 @@ Shader *Shader::defaultVideoShader = nullptr;
 Shader::ShaderSource Shader::defaultCode[Graphics::RENDERER_MAX_ENUM][2];
 Shader::ShaderSource Shader::defaultVideoCode[Graphics::RENDERER_MAX_ENUM][2];
 
-std::vector<int> Shader::textureCounters;
-
 Shader::Shader(const ShaderSource &source)
 	: shaderSource(source)
 	, program(0)
@@ -84,10 +88,6 @@ Shader::Shader(const ShaderSource &source)
 	if (source.vertex.empty() && source.pixel.empty())
 		throw love::Exception("Cannot create shader: no source code!");
 
-	// initialize global texture id counters if needed
-	if ((int) textureCounters.size() < gl.getMaxTextureUnits() - 1)
-		textureCounters.resize(gl.getMaxTextureUnits() - 1, 0);
-
 	// load shader source and create program object
 	loadVolatile();
 }
@@ -97,12 +97,25 @@ Shader::~Shader()
 	if (current == this)
 		detach();
 
-	for (const auto &retainable : boundRetainables)
-		retainable.second->release();
+	unloadVolatile();
+
+	for (const auto &p : uniforms)
+	{
+		// Allocated with malloc().
+		if (p.second.data != nullptr)
+			free(p.second.data);
 
-	boundRetainables.clear();
+		if (p.second.baseType == UNIFORM_SAMPLER)
+		{
+			for (int i = 0; i < p.second.count; i++)
+			{
+				if (p.second.textures[i] != nullptr)
+					p.second.textures[i]->release();
+			}
 
-	unloadVolatile();
+			delete[] p.second.textures;
+		}
+	}
 }
 
 GLuint Shader::compileCode(ShaderStage stage, const std::string &code)
@@ -176,8 +189,6 @@ void Shader::mapActiveUniforms()
 	for (int i = 0; i < int(BUILTIN_MAX_ENUM); i++)
 		builtinUniforms[i] = -1;
 
-	uniforms.clear();
-
 	GLint activeprogram = 0;
 	glGetIntegerv(GL_CURRENT_PROGRAM, &activeprogram);
 
@@ -189,6 +200,9 @@ void Shader::mapActiveUniforms()
 	GLchar cname[256];
 	const GLint bufsize = (GLint) (sizeof(cname) / sizeof(GLchar));
 
+	std::map<std::string, UniformInfo> olduniforms = uniforms;
+	uniforms.clear();
+
 	for (int i = 0; i < numuniforms; i++)
 	{
 		GLsizei namelen = 0;
@@ -206,12 +220,6 @@ void Shader::mapActiveUniforms()
 		else
 			u.components = getUniformTypeComponents(gltype);
 
-		// Initialize all samplers to 0. Both GLSL and GLSL ES are supposed to
-		// do this themselves, but some Android devices (galaxy tab 3 and 4)
-		// don't seem to do it...
-		if (u.baseType == UNIFORM_SAMPLER)
-			glUniform1i(u.location, 0);
-
 		// glGetActiveUniform appends "[0]" to the end of array uniform names...
 		if (u.name.length() > 3)
 		{
@@ -225,8 +233,135 @@ void Shader::mapActiveUniforms()
 		if (builtinNames.find(u.name.c_str(), builtin))
 			builtinUniforms[int(builtin)] = u.location;
 
-		if (u.location != -1)
-			uniforms[u.name] = u;
+		if (u.location == -1)
+			continue;
+
+		// 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.textures = oldu->second.textures;
+
+			updateUniform(&u, u.count, true);
+
+			if (u.baseType == UNIFORM_SAMPLER)
+			{
+				// 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);
+			}
+		}
+		else
+		{
+			size_t datasize = 0;
+
+			switch (u.baseType)
+			{
+			case UNIFORM_FLOAT:
+				datasize = sizeof(float) * u.components * u.count;
+				u.data = malloc(datasize);
+				break;
+			case UNIFORM_INT:
+			case UNIFORM_BOOL:
+			case UNIFORM_SAMPLER:
+				datasize = sizeof(int) * u.components * u.count;
+				u.data = malloc(datasize);
+				break;
+			case UNIFORM_MATRIX:
+				datasize = sizeof(float) * (u.matrix.rows * u.matrix.columns) * u.count;
+				u.data = malloc(datasize);
+				break;
+			default:
+				break;
+			}
+
+			if (datasize > 0)
+			{
+				memset(u.data, 0, datasize);
+
+				if (u.baseType == UNIFORM_SAMPLER)
+				{
+					// Initialize all samplers to 0. Both GLSL and GLSL ES are
+					// supposed to do this themselves, but some Android devices
+					// (galaxy tab 3 and 4) don't seem to do it...
+					glUniform1iv(u.location, u.count, u.ints);
+
+					u.textures = new Texture*[u.count];
+					memset(u.textures, 0, sizeof(Texture *) * u.count);
+				}
+			}
+
+			size_t offset = 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::string indexname = u.name + "[" + std::to_string(i) + "]";
+					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_MATRIX:
+					glGetUniformfv(program, location, &u.floats[offset]);
+					offset += u.matrix.rows * u.matrix.columns;
+					break;
+				default:
+					break;
+				}
+			}
+		}
+
+		uniforms[u.name] = u;
+	}
+
+	// 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.baseType != UNIFORM_SAMPLER)
+				continue;
+
+			for (int i = 0; i < p.second.count; i++)
+			{
+				if (p.second.textures[i] != nullptr)
+					p.second.textures[i]->release();
+			}
+
+			delete[] p.second.textures;
+		}
 	}
 
 	gl.useProgram(activeprogram);
@@ -251,8 +386,8 @@ bool Shader::loadVolatile()
 		videoTextureUnits[i] = 0;
 
 	// zero out active texture list
-	activeTexUnits.clear();
-	activeTexUnits.insert(activeTexUnits.begin(), gl.getMaxTextureUnits() - 1, 0);
+	textureUnits.clear();
+	textureUnits.resize(gl.getMaxTextureUnits(), TextureUnit());
 
 	std::vector<GLuint> shaderids;
 
@@ -348,22 +483,12 @@ void Shader::unloadVolatile()
 		program = 0;
 	}
 
-	// decrement global texture id counters for texture units which had textures bound from this shader
-	for (size_t i = 0; i < activeTexUnits.size(); ++i)
-	{
-		if (activeTexUnits[i] > 0)
-			textureCounters[i] = std::max(textureCounters[i] - 1, 0);
-	}
-
 	// active texture list is probably invalid, clear it
-	activeTexUnits.clear();
-	activeTexUnits.resize(gl.getMaxTextureUnits() - 1, 0);
+	textureUnits.clear();
+	textureUnits.resize(gl.getMaxTextureUnits(), TextureUnit());
 
 	attributes.clear();
 
-	// same with uniform location list
-	uniforms.clear();
-
 	// And the locations of any built-in uniform variables.
 	for (int i = 0; i < int(BUILTIN_MAX_ENUM); i++)
 		builtinUniforms[i] = -1;
@@ -420,10 +545,10 @@ void Shader::attach(bool temporary)
 		{
 			// make sure all sent textures are properly bound to their respective texture units
 			// note: list potentially contains texture ids of deleted/invalid textures!
-			for (int i = 0; i < (int) activeTexUnits.size(); ++i)
+			for (int i = 1; i < (int) textureUnits.size(); ++i)
 			{
-				if (activeTexUnits[i] > 0)
-					gl.bindTextureToUnit(activeTexUnits[i], i + 1, false);
+				if (textureUnits[i].active)
+					gl.bindTextureToUnit(textureUnits[i].texture, i, false);
 			}
 		}
 	}
@@ -455,160 +580,148 @@ const Shader::UniformInfo *Shader::getUniformInfo(const std::string &name) const
 	return &(it->second);
 }
 
-void Shader::sendInts(const UniformInfo *info, const int *vec, int count)
+void Shader::updateUniform(const UniformInfo *info, int count, bool internalUpdate)
 {
-	if (info->baseType != UNIFORM_INT && info->baseType != UNIFORM_BOOL)
-		return;
-
-	TemporaryAttacher attacher(this);
+	TemporaryAttacher attacher(this, !internalUpdate);
 
 	int location = info->location;
+	UniformType type = info->baseType;
 
-	switch (info->components)
+	if (type == UNIFORM_FLOAT)
 	{
-	case 4:
-		glUniform4iv(location, count, vec);
-		break;
-	case 3:
-		glUniform3iv(location, count, vec);
-		break;
-	case 2:
-		glUniform2iv(location, count, vec);
-		break;
-	case 1:
-	default:
-		glUniform1iv(location, count, vec);
-		break;
+		switch (info->components)
+		{
+		case 1:
+			glUniform1fv(location, count, info->floats);
+			break;
+		case 2:
+			glUniform2fv(location, count, info->floats);
+			break;
+		case 3:
+			glUniform3fv(location, count, info->floats);
+			break;
+		case 4:
+			glUniform4fv(location, count, info->floats);
+			break;
+		}
 	}
-}
-
-void Shader::sendFloats(const UniformInfo *info, const float *vec, int count)
-{
-	if (info->baseType != UNIFORM_FLOAT && info->baseType != UNIFORM_BOOL)
-		return;
-
-	TemporaryAttacher attacher(this);
-
-	int location = info->location;
-
-	switch (info->components)
+	else if (type == UNIFORM_INT || type == UNIFORM_BOOL || type == UNIFORM_SAMPLER)
 	{
-	case 4:
-		glUniform4fv(location, count, vec);
-		break;
-	case 3:
-		glUniform3fv(location, count, vec);
-		break;
-	case 2:
-		glUniform2fv(location, count, vec);
-		break;
-	case 1:
-	default:
-		glUniform1fv(location, count, vec);
-		break;
+		switch (info->components)
+		{
+		case 1:
+			glUniform1iv(location, count, info->ints);
+			break;
+		case 2:
+			glUniform2iv(location, count, info->ints);
+			break;
+		case 3:
+			glUniform3iv(location, count, info->ints);
+			break;
+		case 4:
+			glUniform4iv(location, count, info->ints);
+			break;
+		}
+	}
+	else if (type == UNIFORM_MATRIX)
+	{
+		int columns = info->matrix.columns;
+		int rows = info->matrix.rows;
+
+		if (columns == 2 && rows == 2)
+			glUniformMatrix2fv(location, count, GL_FALSE, info->floats);
+		else if (columns == 3 && rows == 3)
+			glUniformMatrix3fv(location, count, GL_FALSE, info->floats);
+		else if (columns == 4 && rows == 4)
+			glUniformMatrix4fv(location, count, GL_FALSE, info->floats);
+		else if (columns == 2 && rows == 3)
+			glUniformMatrix2x3fv(location, count, GL_FALSE, info->floats);
+		else if (columns == 2 && rows == 4)
+			glUniformMatrix2x4fv(location, count, GL_FALSE, info->floats);
+		else if (columns == 3 && rows == 2)
+			glUniformMatrix3x2fv(location, count, GL_FALSE, info->floats);
+		else if (columns == 3 && rows == 4)
+			glUniformMatrix3x4fv(location, count, GL_FALSE, info->floats);
+		else if (columns == 4 && rows == 2)
+			glUniformMatrix4x2fv(location, count, GL_FALSE, info->floats);
+		else if (columns == 4 && rows == 3)
+			glUniformMatrix4x3fv(location, count, GL_FALSE, info->floats);
 	}
 }
 
-void Shader::sendMatrices(const UniformInfo *info, const float *m, int count)
+int Shader::getFreeTextureUnits(int count)
 {
-	if (info->baseType != UNIFORM_MATRIX)
-		return;
+	int startunit = -1;
 
-	TemporaryAttacher attacher(this);
+	// Ignore the first texture unit for Shader-local texture bindings.
+	for (int i = 1; i < (int) textureUnits.size(); i++)
+	{
+		if (!textureUnits[i].active && i + count <= (int) textureUnits.size())
+		{
+			startunit = i;
+			break;
+		}
+	}
 
-	int location = info->location;
+	if (startunit == -1)
+		throw love::Exception("No more texture units available for shader.");
 
-	int columns = info->matrix.columns;
-	int rows = info->matrix.rows;
-
-	if (columns == 2 && rows == 2)
-		glUniformMatrix2fv(location, count, GL_FALSE, m);
-	else if (columns == 3 && rows == 3)
-		glUniformMatrix3fv(location, count, GL_FALSE, m);
-	else if (columns == 4 && rows == 4)
-		glUniformMatrix4fv(location, count, GL_FALSE, m);
-	else if (columns == 2 && rows == 3)
-		glUniformMatrix2x3fv(location, count, GL_FALSE, m);
-	else if (columns == 2 && rows == 4)
-		glUniformMatrix2x4fv(location, count, GL_FALSE, m);
-	else if (columns == 3 && rows == 2)
-		glUniformMatrix3x2fv(location, count, GL_FALSE, m);
-	else if (columns == 3 && rows == 4)
-		glUniformMatrix3x4fv(location, count, GL_FALSE, m);
-	else if (columns == 4 && rows == 2)
-		glUniformMatrix4x2fv(location, count, GL_FALSE, m);
-	else if (columns == 4 && rows == 3)
-		glUniformMatrix4x3fv(location, count, GL_FALSE, m);
+	return startunit;
 }
 
-void Shader::sendTexture(const UniformInfo *info, Texture *texture)
+void Shader::sendTextures(const UniformInfo *info, Texture **textures, int count, bool internalUpdate)
 {
 	if (info->baseType != UNIFORM_SAMPLER)
 		return;
 
-	GLuint gltex = *(GLuint *) texture->getHandle();
-
-	TemporaryAttacher attacher(this);
-
-	int texunit = getTextureUnit(info->name);
-
-	// bind texture to assigned texture unit and send uniform to shader program
-	gl.bindTextureToUnit(gltex, texunit, false);
-
-	glUniform1i(info->location, texunit);
+	count = std::min(count, info->count);
+	bool updateuniform = false;
 
-	// increment global shader texture id counter for this texture unit, if we haven't already
-	if (activeTexUnits[texunit-1] == 0)
-		++textureCounters[texunit-1];
-
-	// store texture id so it can be re-bound to the proper texture unit later
-	activeTexUnits[texunit-1] = gltex;
-
-	retainObject(info->name, texture);
-}
-
-void Shader::retainObject(const std::string &name, Object *object)
-{
-	object->retain();
+	// Make sure the shader's samplers are associated with texture units.
+	for (int i = 0; i < count; i++)
+	{
+		if (info->ints[i] == 0 && textures[i] != nullptr)
+		{
+			int texunit = getFreeTextureUnits(1);
+			textureUnits[texunit].active = true;
 
-	auto it = boundRetainables.find(name);
-	if (it != boundRetainables.end())
-		it->second->release();
+			info->ints[i] = texunit;
+			updateuniform = true;
+		}
+	}
 
-	boundRetainables[name] = object;
-}
+	if (updateuniform)
+		updateUniform(info, count, internalUpdate);
 
-int Shader::getTextureUnit(const std::string &name)
-{
-	auto it = texUnitPool.find(name);
+	// Bind the textures to the texture units.
+	for (int i = 0; i < count; i++)
+	{
+		if (textures[i] != nullptr)
+			textures[i]->retain();
 
-	if (it != texUnitPool.end())
-		return it->second;
+		if (info->textures[i] != nullptr)
+			info->textures[i]->release();
 
-	int texunit = 1;
+		info->textures[i] = textures[i];
 
-	// prefer texture units which are unused by all other shaders
-	auto freeunit_it = std::find(textureCounters.begin(), textureCounters.end(), 0);
+		int texunit = info->ints[i];
 
-	if (freeunit_it != textureCounters.end())
-	{
-		// we don't want to use unit 0
-		texunit = (int) std::distance(textureCounters.begin(), freeunit_it) + 1;
-	}
-	else
-	{
-		// no completely unused texture units exist, try to use next free slot in our own list
-		auto nextunit_it = std::find(activeTexUnits.begin(), activeTexUnits.end(), 0);
+		if (textures[i] != nullptr)
+		{
+			GLuint gltex = *(GLuint *) textures[i]->getHandle();
 
-		if (nextunit_it == activeTexUnits.end())
-			throw love::Exception("No more texture units available for shader.");
+			gl.bindTextureToUnit(gltex, texunit, false);
 
-		// we don't want to use unit 0
-		texunit = (int) std::distance(activeTexUnits.begin(), nextunit_it) + 1;
+			// store texture id so it can be re-bound to the proper texture unit later
+			textureUnits[texunit].texture = gltex;
+		}
+		else
+		{
+			gl.bindTextureToUnit(0, texunit, false);
+			textureUnits[texunit].texture = 0;
+			textureUnits[texunit].active = false;
+		}
 	}
-
-	texUnitPool[name] = texunit;
-	return texunit;
 }
 
 bool Shader::hasUniform(const std::string &name) const
@@ -635,12 +748,12 @@ bool Shader::hasVertexAttrib(VertexAttribID attrib) const
 
 void Shader::setVideoTextures(GLuint ytexture, GLuint cbtexture, GLuint crtexture)
 {
-	TemporaryAttacher attacher(this);
-
 	// Set up the texture units that will be used by the shader to sample from
 	// the textures, if they haven't been set up yet.
 	if (videoTextureUnits[0] == 0)
 	{
+		TemporaryAttacher attacher(this, true);
+
 		const GLint locs[3] = {
 			builtinUniforms[BUILTIN_VIDEO_Y_CHANNEL],
 			builtinUniforms[BUILTIN_VIDEO_CB_CHANNEL],
@@ -656,14 +769,15 @@ void Shader::setVideoTextures(GLuint ytexture, GLuint cbtexture, GLuint crtextur
 		{
 			if (locs[i] >= 0 && names[i] != nullptr)
 			{
-				videoTextureUnits[i] = getTextureUnit(names[i]);
-
-				// Increment global shader texture id counter for this texture
-				// unit, if we haven't already.
-				if (activeTexUnits[videoTextureUnits[i] - 1] == 0)
-					++textureCounters[videoTextureUnits[i] - 1];
-
-				glUniform1i(locs[i], videoTextureUnits[i]);
+				const UniformInfo *info = getUniformInfo(names[i]);
+				if (info != nullptr)
+				{
+					videoTextureUnits[i] = getFreeTextureUnits(1);
+					textureUnits[videoTextureUnits[i]].active = true;
+
+					info->ints[0] = videoTextureUnits[i];
+					updateUniform(info, 1);
+				}
 			}
 		}
 	}
@@ -676,7 +790,7 @@ void Shader::setVideoTextures(GLuint ytexture, GLuint cbtexture, GLuint crtextur
 		if (videoTextureUnits[i] != 0)
 		{
 			// Store texture id so it can be re-bound later.
-			activeTexUnits[videoTextureUnits[i] - 1] = textures[i];
+			textureUnits[videoTextureUnits[i]].texture = textures[i];
 			gl.bindTextureToUnit(textures[i], videoTextureUnits[i], false);
 		}
 	}
@@ -718,7 +832,7 @@ void Shader::checkSetScreenParams()
 
 	if (location >= 0)
 	{
-		TemporaryAttacher attacher(this);
+		TemporaryAttacher attacher(this, true);
 		glUniform4fv(location, 1, params);
 	}
 
@@ -735,7 +849,7 @@ void Shader::checkSetPointSize(float size)
 
 	if (location >= 0)
 	{
-		TemporaryAttacher attacher(this);
+		TemporaryAttacher attacher(this, true);
 		glUniform1f(location, size);
 	}
 
@@ -755,7 +869,7 @@ void Shader::checkSetBuiltinUniforms()
 		const Matrix4 &curxform = gl.matrices.transform.back();
 		const Matrix4 &curproj = gl.matrices.projection;
 
-		TemporaryAttacher attacher(this);
+		TemporaryAttacher attacher(this, true);
 
 		bool tpmatrixneedsupdate = false;
 
@@ -802,11 +916,6 @@ void Shader::checkSetBuiltinUniforms()
 	}
 }
 
-const std::map<std::string, Object *> &Shader::getBoundRetainables() const
-{
-	return boundRetainables;
-}
-
 std::string Shader::getGLSLVersion()
 {
 	const char *tmp = (const char *) glGetString(GL_SHADING_LANGUAGE_VERSION);

+ 21 - 30
src/modules/graphics/opengl/Shader.h

@@ -96,13 +96,24 @@ public:
 	{
 		int location;
 		int count;
+
 		union
 		{
 			int components;
 			MatrixSize matrix;
 		};
+
 		UniformType baseType;
 		std::string name;
+
+		union
+		{
+			void *data;
+			float *floats;
+			int *ints;
+		};
+
+		Texture **textures;
 	};
 
 	// Pointer to currently active Shader.
@@ -147,11 +158,9 @@ public:
 	std::string getWarnings() const;
 
 	const UniformInfo *getUniformInfo(const std::string &name) const;
+	void updateUniform(const UniformInfo *info, int count, bool internalUpdate = false);
 
-	void sendInts(const UniformInfo *info, const int *vec, int count);
-	void sendFloats(const UniformInfo *info, const float *vec, int count);
-	void sendMatrices(const UniformInfo *info, const float *m, int count);
-	void sendTexture(const UniformInfo *info, Texture *texture);
+	void sendTextures(const UniformInfo *info, Texture **textures, int count, bool internalUpdate = false);
 
 	/**
 	 * Gets whether a uniform with the specified name exists and is actively
@@ -171,24 +180,11 @@ public:
 	void checkSetPointSize(float size);
 	void checkSetBuiltinUniforms();
 
-	const std::map<std::string, Object *> &getBoundRetainables() const;
-
 	GLuint getProgram() const
 	{
 		return program;
 	}
 
-	template <typename T>
-	T *getScratchBuffer(size_t count)
-	{
-		size_t bytes = sizeof(T) * count;
-
-		if (scratchBuffer.size() < bytes)
-			scratchBuffer.resize(bytes);
-
-		return (T *) scratchBuffer.data();
-	}
-
 	static std::string getGLSLVersion();
 	static bool isSupported();
 
@@ -200,6 +196,12 @@ public:
 
 private:
 
+	struct TextureUnit
+	{
+		GLuint texture = 0;
+		bool active = false;
+	};
+
 	// Map active uniform names to their locations.
 	void mapActiveUniforms();
 
@@ -209,9 +211,7 @@ private:
 
 	GLuint compileCode(ShaderStage stage, const std::string &code);
 
-	int getTextureUnit(const std::string &name);
-
-	void retainObject(const std::string &name, Object *object);
+	int getFreeTextureUnits(int count);
 
 	// Get any warnings or errors generated only by the shader program object.
 	std::string getProgramWarnings() const;
@@ -237,11 +237,7 @@ private:
 	std::map<std::string, UniformInfo> uniforms;
 
 	// Texture unit pool for setting images
-	std::map<std::string, GLint> texUnitPool; // texUnitPool[name] = textureunit
-	std::vector<GLuint> activeTexUnits; // activeTexUnits[textureunit-1] = textureid
-
-	// Uniform name to retainable objects
-	std::map<std::string, Object*> boundRetainables;
+	std::vector<TextureUnit> textureUnits;
 
 	bool canvasWasActive;
 	OpenGL::Viewport lastViewport;
@@ -253,11 +249,6 @@ private:
 
 	GLuint videoTextureUnits[3];
 
-	std::vector<char> scratchBuffer;
-
-	// Counts total number of textures bound to each texture unit in all shaders
-	static std::vector<int> textureCounters;
-
 	static StringMap<ShaderStage, STAGE_MAX_ENUM>::Entry stageNameEntries[];
 	static StringMap<ShaderStage, STAGE_MAX_ENUM> stageNames;
 

+ 24 - 21
src/modules/graphics/opengl/wrap_Shader.cpp

@@ -52,10 +52,8 @@ static int _getCount(lua_State *L, int startidx, const Shader::UniformInfo *info
 }
 
 template <typename T>
-static T *_getNumbers(lua_State *L, int startidx, Shader *shader, int components, int count)
+static void _updateNumbers(lua_State *L, int startidx, T *values, int components, int count)
 {
-	T *values = shader->getScratchBuffer<T>(components * count);
-
 	if (components == 1)
 	{
 		for (int i = 0; i < count; ++i)
@@ -76,16 +74,15 @@ static T *_getNumbers(lua_State *L, int startidx, Shader *shader, int components
 			lua_pop(L, components);
 		}
 	}
-
-	return values;
 }
 
 int w_Shader_sendFloats(lua_State *L, int startidx, Shader *shader, const Shader::UniformInfo *info, bool colors)
 {
 	int count = _getCount(L, startidx, info);
 	int components = info->components;
+	float *values = info->floats;
 
-	float *values = _getNumbers<float>(L, startidx, shader, components, count);
+	_updateNumbers(L, startidx, values, components, count);
 
 	if (colors && graphics::isGammaCorrect())
 	{
@@ -99,15 +96,15 @@ int w_Shader_sendFloats(lua_State *L, int startidx, Shader *shader, const Shader
 		}
 	}
 
-	luax_catchexcept(L, [&]() { shader->sendFloats(info, values, count); });
+	luax_catchexcept(L, [&]() { shader->updateUniform(info, count); });
 	return 0;
 }
 
 int w_Shader_sendInts(lua_State *L, int startidx, Shader *shader, const Shader::UniformInfo *info)
 {
 	int count = _getCount(L, startidx, info);
-	int *values = _getNumbers<int>(L, startidx, shader, info->components, count);
-	luax_catchexcept(L, [&]() { shader->sendInts(info, values, count); });
+	_updateNumbers(L, startidx, info->ints, info->components, count);
+	luax_catchexcept(L, [&]() { shader->updateUniform(info, count); });
 	return 0;
 }
 
@@ -116,15 +113,15 @@ int w_Shader_sendBooleans(lua_State *L, int startidx, Shader *shader, const Shad
 	int count = _getCount(L, startidx, info);
 	int components = info->components;
 
-	// We have to send booleans as ints or floats.
-	float *values = shader->getScratchBuffer<float>(components * count);
+	// We have to send booleans as ints.
+	int *values = info->ints;
 
 	if (components == 1)
 	{
 		for (int i = 0; i < count; i++)
 		{
 			luaL_checktype(L, startidx + i, LUA_TBOOLEAN);
-			values[i] = (float) lua_toboolean(L, startidx + i);
+			values[i] = (int) lua_toboolean(L, startidx + i);
 		}
 	}
 	else
@@ -137,14 +134,14 @@ int w_Shader_sendBooleans(lua_State *L, int startidx, Shader *shader, const Shad
 			{
 				lua_rawgeti(L, startidx + i, k);
 				luaL_checktype(L, -1, LUA_TBOOLEAN);
-				values[i * components + k - 1] = (float) lua_toboolean(L, -1);
+				values[i * components + k - 1] = (int) lua_toboolean(L, -1);
 			}
 
 			lua_pop(L, components);
 		}
 	}
 
-	luax_catchexcept(L, [&]() { shader->sendFloats(info, values, count); });
+	luax_catchexcept(L, [&]() { shader->updateUniform(info, count); });
 	return 0;
 }
 
@@ -163,7 +160,7 @@ int w_Shader_sendMatrices(lua_State *L, int startidx, Shader *shader, const Shad
 	int rows = info->matrix.rows;
 	int elements = columns * rows;
 
-	float *values = shader->getScratchBuffer<float>(elements * count);
+	float *values = info->floats;
 
 	for (int i = 0; i < count; i++)
 	{
@@ -243,15 +240,21 @@ int w_Shader_sendMatrices(lua_State *L, int startidx, Shader *shader, const Shad
 		}
 	}
 
-	shader->sendMatrices(info, values, count);
+	shader->updateUniform(info, count);
 	return 0;
 }
 
-int w_Shader_sendTexture(lua_State *L, int startidx, Shader *shader, const Shader::UniformInfo *info)
+int w_Shader_sendTextures(lua_State *L, int startidx, Shader *shader, const Shader::UniformInfo *info)
 {
-	// We don't support arrays of textures (yet).
-	Texture *texture = luax_checktexture(L, startidx);
-	luax_catchexcept(L, [&]() { shader->sendTexture(info, texture); });
+	int count = _getCount(L, startidx, info);
+
+	std::vector<Texture *> textures;
+	textures.reserve(count);
+
+	for (int i = 0; i < count; i++)
+		textures.push_back(luax_checktexture(L, startidx + i));
+
+	luax_catchexcept(L, [&]() { shader->sendTextures(info, textures.data(), count); });
 	return 0;
 }
 
@@ -277,7 +280,7 @@ int w_Shader_send(lua_State *L)
 	case Shader::UNIFORM_BOOL:
 		return w_Shader_sendBooleans(L, startidx, shader, info);
 	case Shader::UNIFORM_SAMPLER:
-		return w_Shader_sendTexture(L, startidx, shader, info);
+		return w_Shader_sendTextures(L, startidx, shader, info);
 	default:
 		return luaL_error(L, "Unknown variable type for shader uniform '%s", name);
 	}