Download
699 lines 18.6 KiBC++Modified 2026-05-02 raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699
/**
 * Copyright (c) 2006-2026 LOVE Development Team
 *
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the authors be held liable for any damages
 * arising from the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software. If you use this software
 *    in a product, an acknowledgment in the product documentation would be
 *    appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 **/

// LOVE
#include "common/config.h"

#include "Shader.h"
#include "ShaderStage.h"
#include "Graphics.h"
#include "graphics/vertex.h"

// C++
#include <algorithm>
#include <limits>
#include <sstream>

namespace love
{
namespace graphics
{
namespace opengl
{

Shader::Shader(StrongRef<love::graphics::ShaderStage> stages[SHADERSTAGE_MAX_ENUM], const CompileOptions &options)
	: love::graphics::Shader(stages, options)
	, program(0)
	, builtinUniforms()
	, builtinUniformInfo()
{
	// load shader source and create program object
	loadVolatile();
}

Shader::~Shader()
{
	unloadVolatile();

	for (const auto &p : reflection.allUniforms)
	{
		// Allocated with malloc().
		if (p.second->data != nullptr)
			free(p.second->data);
	}
}

void Shader::mapActiveUniforms()
{
	// Built-in uniform locations default to -1 (nonexistent.)
	for (int i = 0; i < int(BUILTIN_MAX_ENUM); i++)
	{
		builtinUniforms[i] = -1;
		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);

	gl.useProgram(program);

	GLint numuniforms;
	glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &numuniforms);

	GLchar cname[256];
	const GLint bufsize = (GLint) (sizeof(cname) / sizeof(GLchar));

	for (int uindex = 0; uindex < numuniforms; uindex++)
	{
		GLsizei namelen = 0;
		GLenum gltype = 0;
		int count = 0;

		glGetActiveUniform(program, (GLuint) uindex, bufsize, &namelen, &count, &gltype, cname);

		std::string name(cname, (size_t) namelen);
		int location = glGetUniformLocation(program, name.c_str());

		if (location == -1)
			continue;

		name = canonicaliizeUniformName(name);

		const auto &uniformit = reflection.allUniforms.find(name);
		if (uniformit == reflection.allUniforms.end())
		{
			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.baseType == UNIFORM_SAMPLER && builtin != BUILTIN_TEXTURE_MAIN) || u.baseType == UNIFORM_TEXELBUFFER)
		{
			TextureUnit unit;
			unit.type = u.textureType;
			unit.active = true;
			unit.texture = 0; // Handled below.
			unit.isTexelBuffer = u.baseType == UNIFORM_TEXELBUFFER;

			for (int i = 0; i < u.count; i++)
				textureUnits.push_back(unit);
		}
		else if (u.baseType == UNIFORM_STORAGETEXTURE)
		{
			StorageTextureBinding binding = {};
			binding.gltexture = 0; // Handled below.
			binding.type = u.textureType;

			if ((u.access & (ACCESS_READ | ACCESS_WRITE)) != 0)
				binding.access = GL_READ_WRITE;
			else if ((u.access & ACCESS_WRITE) != 0)
				binding.access = GL_WRITE_ONLY;
			else if ((u.access & ACCESS_READ) != 0)
				binding.access = GL_READ_ONLY;

			auto fmt = OpenGL::convertPixelFormat(u.storageTextureFormat);
			binding.internalFormat = fmt.internalformat;

			for (int i = 0; i < u.count; i++)
				storageTextureBindings.push_back(binding);
		}

		if (u.dataSizeAllocated == 0)
		{
			u.dataSizePacked = u.dataSizeAllocated = getUniformDataSizePacked(u);
			u.data = malloc(u.dataSizeAllocated);
			memset(u.data, 0, u.dataSizeAllocated);

			const auto &valuesit = reflection.localUniformInitializerValues.find(u.name);
			if (valuesit != reflection.localUniformInitializerValues.end())
			{
				const auto &values = valuesit->second;
				if (!values.empty())
					memcpy(u.data, values.data(), std::min(u.dataSizePacked, sizeof(LocalUniformValue) * values.size()));
			}
		}

		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;
		}
		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;
		}

		updateUniform(&u, u.count, true);

		if (builtin != BUILTIN_MAX_ENUM)
			builtinUniformInfo[(int)builtin] = &u;

		if (u.baseType == UNIFORM_SAMPLER || u.baseType == UNIFORM_STORAGETEXTURE)
			sendTextures(&u, &activeTextures[u.resourceIndex], u.count, true);
		else if (u.baseType == UNIFORM_TEXELBUFFER)
			sendBuffers(&u, &activeBuffers[u.resourceIndex], u.count, true);
	}

	if (gl.isBufferUsageSupported(BUFFERUSAGE_SHADER_STORAGE))
	{
		GLint numstoragebuffers = 0;
		glGetProgramInterfaceiv(program, GL_SHADER_STORAGE_BLOCK, GL_ACTIVE_RESOURCES, &numstoragebuffers);

		char namebuffer[2048] = { '\0' };
		int nextstoragebufferbinding = 0;

		for (int sindex = 0; sindex < numstoragebuffers; sindex++)
		{
			GLsizei namelength = 0;
			glGetProgramResourceName(program, GL_SHADER_STORAGE_BLOCK, sindex, 2048, &namelength, namebuffer);

			std::string name = canonicaliizeUniformName(std::string(namebuffer, namelength));

			const auto &uniformit = reflection.storageBuffers.find(name);
			if (uniformit == reflection.storageBuffers.end())
			{
				handleUnknownUniformName(name.c_str());
				continue;
			}

			UniformInfo &u = uniformit->second;

			u.active = true;

			if (u.dataSizeAllocated == 0)
			{
				u.dataSizePacked = u.dataSizeAllocated = sizeof(int) * u.count;
				u.data = malloc(u.dataSizeAllocated);
				for (int i = 0; i < u.count; i++)
					u.ints[i] = -1;
			}

			// Unlike local uniforms and attributes, OpenGL doesn't auto-assign storage
			// block bindings if they're unspecified in the shader. So we overwrite them
			// regardless, here.
			u.ints[0] = nextstoragebufferbinding++;
			glShaderStorageBlockBinding(program, sindex, u.ints[0]);

			BufferBinding binding;
			binding.bindingindex = u.ints[0];
			binding.buffer = 0;

			if (binding.bindingindex >= 0)
			{
				int activeindex = (int)activeStorageBufferBindings.size();
				activeStorageBufferBindings.push_back(binding);

				auto p = std::make_pair(activeindex, -1);

				if (u.access & ACCESS_WRITE)
				{
					p.second = (int)activeWritableStorageBuffers.size();
					activeWritableStorageBuffers.push_back(activeBuffers[u.resourceIndex]);
				}

				storageBufferBindingIndexToActiveBinding[binding.bindingindex] = p;
			}

			sendBuffers(&u, &activeBuffers[u.resourceIndex], u.count, true);
		}
	}

	gl.useProgram(activeprogram);
}

bool Shader::loadVolatile()
{
	OpenGL::TempDebugGroup debuggroup("Shader load");

	// zero out active texture list
	textureUnits.clear();
	textureUnits.push_back(TextureUnit());

	activeStorageBufferBindings.clear();

	storageBufferBindingIndexToActiveBinding.resize(gl.getMaxShaderStorageBufferBindings(), std::make_pair(-1, -1));
	activeStorageBufferBindings.clear();
	activeWritableStorageBuffers.clear();

	for (const auto &stage : stages)
	{
		if (stage.get() != nullptr)
			((ShaderStage*)stage.get())->loadVolatile();
	}

	program = glCreateProgram();

	if (program == 0)
		throw love::Exception("Cannot create shader program object.");

	if (!debugName.empty() && (GLAD_VERSION_4_3 || GLAD_ES_VERSION_3_2))
		glObjectLabel(GL_PROGRAM, program, -1, debugName.c_str());

	for (const auto &stage : stages)
	{
		if (stage.get() != nullptr)
			glAttachShader(program, (GLuint) stage->getHandle());
	}

	// Bind generic vertex attribute indices to names in the shader.
	for (int i = 0; i < int(ATTRIB_MAX_ENUM); i++)
	{
		const char *name = nullptr;
		if (graphics::getConstant((BuiltinVertexAttribute) i, name))
			glBindAttribLocation(program, i, (const GLchar *) name);
	}

	glLinkProgram(program);

	GLint status;
	glGetProgramiv(program, GL_LINK_STATUS, &status);

	if (status == GL_FALSE)
	{
		std::string warnings = getProgramWarnings();
		glDeleteProgram(program);
		program = 0;
		throw love::Exception("Cannot link shader program object:\n%s", warnings.c_str());
	}

	// Get all active uniform variables in this shader from OpenGL.
	mapActiveUniforms();

	if (current == this)
	{
		// make sure glUseProgram gets called.
		current = nullptr;
		attach();
	}

	return true;
}

void Shader::unloadVolatile()
{
	if (program != 0)
	{
		if (current == this)
			gl.useProgram(0);

		glDeleteProgram(program);
		program = 0;
	}

	// active texture list is probably invalid, clear it
	textureUnits.clear();
	textureUnits.push_back(TextureUnit());

	attributes.clear();

	// And the locations of any built-in uniform variables.
	for (int i = 0; i < int(BUILTIN_MAX_ENUM); i++)
		builtinUniforms[i] = -1;
}

std::string Shader::getProgramWarnings() const
{
	GLint strsize, nullpos;
	glGetProgramiv(program, GL_INFO_LOG_LENGTH, &strsize);

	if (strsize == 0)
		return "";

	char *tempstr = new char[strsize];
	// be extra sure that the error string will be 0-terminated
	memset(tempstr, '\0', strsize);
	glGetProgramInfoLog(program, strsize, &nullpos, tempstr);
	tempstr[nullpos] = '\0';

	std::string warnings(tempstr);
	delete[] tempstr;

	return warnings;
}

std::string Shader::getWarnings() const
{
	std::string warnings;
	const char *stagestr;

	for (const auto &stage : stages)
	{
		if (stage.get() == nullptr)
			continue;

		const std::string &stagewarnings = stage->getWarnings();

		if (!stagewarnings.empty() && ShaderStage::getConstant(stage->getStageType(), stagestr))
			warnings += std::string(stagestr) + std::string(" shader:\n") + stagewarnings;
	}

	warnings += getProgramWarnings();

	return warnings;
}

void Shader::attach()
{
	if (current != this)
	{
		Graphics::flushBatchedDrawsGlobal();

		gl.useProgram(program);
		current = this;
		// retain/release happens in Graphics::setShader.

		// Make sure all textures are bound to their respective texture units.
		for (int i = 0; i < (int) textureUnits.size(); i++)
		{
			const TextureUnit &unit = textureUnits[i];
			if (unit.active)
			{
				if (unit.isTexelBuffer)
					gl.bindBufferTextureToUnit(unit.texture, i, false, false);
				else
					gl.bindTextureToUnit(unit.type, unit.texture, i, false, false);
			}
		}

		for (size_t i = 0; i < storageTextureBindings.size(); i++)
		{
			const auto &binding = storageTextureBindings[i];
			glBindImageTexture((GLuint) i, binding.gltexture, 0, GL_TRUE, 0, binding.access, binding.internalFormat);
		}

		for (auto bufferbinding : activeStorageBufferBindings)
			gl.bindIndexedBuffer(bufferbinding.buffer, BUFFERUSAGE_SHADER_STORAGE, bufferbinding.bindingindex);

		// send any pending uniforms to the shader program.
		for (const auto &p : pendingUniformUpdates)
			updateUniform(p.first, p.second, true);

		pendingUniformUpdates.clear();
	}
}

const Shader::UniformInfo *Shader::getUniformInfo(BuiltinUniform builtin) const
{
	return builtinUniformInfo[(int)builtin];
}

void Shader::updateUniform(const UniformInfo *info, int count)
{
	updateUniform(info, count, false);
}

void Shader::updateUniform(const UniformInfo *info, int count, bool internalupdate)
{
	if (current != this && !internalupdate)
	{
		pendingUniformUpdates.push_back(std::make_pair(info, count));
		return;
	}

	if (!internalupdate)
		flushBatchedDraws();

	int location = info->location;
	UniformType type = info->baseType;

	if (type == UNIFORM_FLOAT)
	{
		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;
		}
	}
	else if (type == UNIFORM_INT || type == UNIFORM_BOOL || type == UNIFORM_SAMPLER || type == UNIFORM_STORAGETEXTURE || type == UNIFORM_TEXELBUFFER)
	{
		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_UINT)
	{
		switch (info->components)
		{
		case 1:
			glUniform1uiv(location, count, info->uints);
			break;
		case 2:
			glUniform2uiv(location, count, info->uints);
			break;
		case 3:
			glUniform3uiv(location, count, info->uints);
			break;
		case 4:
			glUniform4uiv(location, count, info->uints);
			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::applyTexture(const UniformInfo *info, int i, love::graphics::Texture *texture, UniformType basetype, bool isdefault)
{
	bool shaderactive = current == this;

	if (basetype == UNIFORM_STORAGETEXTURE)
	{
		GLuint gltex = (GLuint) texture->getHandle();

		int bindingindex = info->ints[i];
		auto &binding = storageTextureBindings[bindingindex];

		if (isdefault && (info->access & ACCESS_WRITE) != 0)
		{
			binding.texture = nullptr;
			binding.gltexture = 0;
		}
		else
		{
			binding.texture = texture;
			binding.gltexture = gltex;

			if (shaderactive)
				glBindImageTexture(bindingindex, binding.gltexture, 0, GL_TRUE, 0, binding.access, binding.internalFormat);
		}
	}
	else
	{
		GLuint gltex = (GLuint) texture->getHandle();

		int texunit = info->ints[i];

		if (shaderactive)
			gl.bindTextureToUnit(info->textureType, gltex, texunit, false, false);

		// Store texture id so it can be re-bound to the texture unit later.
		textureUnits[texunit].texture = gltex;
	}
}

void Shader::applyBuffer(const UniformInfo *info, int i, love::graphics::Buffer *buffer, UniformType basetype, bool isdefault)
{
	bool shaderactive = current == this;

	if (basetype == UNIFORM_TEXELBUFFER)
	{
		GLuint gltex = (GLuint) buffer->getTexelBufferHandle();
		int texunit = info->ints[i];

		if (shaderactive)
			gl.bindBufferTextureToUnit(gltex, texunit, false, false);

		// Store texture id so it can be re-bound to the texture unit later.
		textureUnits[texunit].texture = gltex;
	}
	else if (basetype == UNIFORM_STORAGEBUFFER)
	{
		GLuint glbuffer = (GLuint)buffer->getHandle();
		int bindingindex = info->ints[i];

		if (shaderactive)
			gl.bindIndexedBuffer(glbuffer, BUFFERUSAGE_SHADER_STORAGE, bindingindex);

		auto activeindex = storageBufferBindingIndexToActiveBinding[bindingindex];

		if (activeindex.first >= 0)
			activeStorageBufferBindings[activeindex.first].buffer = glbuffer;

		if (activeindex.second >= 0)
			activeWritableStorageBuffers[activeindex.second] = isdefault ? nullptr : buffer;
	}
}

ptrdiff_t Shader::getHandle() const
{
	return program;
}

int Shader::getVertexAttributeIndex(const std::string &name)
{
	auto it = attributes.find(name);
	if (it != attributes.end())
		return it->second;

	GLint location = glGetAttribLocation(program, name.c_str());

	attributes[name] = location;
	return location;
}

void Shader::updateBuiltinUniforms(love::graphics::Graphics *gfx, int viewportW, int viewportH)
{
	if (current != this)
		return;

	bool rt = gfx->isRenderTargetActive();

	BuiltinUniformData data;

	data.transformMatrix = gfx->getTransform();
	data.projectionMatrix = gfx->getDeviceProjection();

	data.scaleParams.x = (float) gfx->getCurrentDPIScale();
	data.scaleParams.y = gfx->getPointSize();

	// Users expect to work with y-up NDC, y-down pixel coordinates and textures
	// (see graphics/Shader.h).
	// OpenGL has y-up NDC and y-up pixel coordinates and textures. If we just flip
	// NDC y when rendering to a texture, it's enough to make (0, 0) on the texture
	// match what we expect when sampling from it - so it's the same as if textures
	// are y-down with y-up NDC.
	// Windowing systems treat (0, 0) on the backbuffer texture as the bottom left,
	// so we don't need to do that there.
	uint32 clipflags = 0;
	if (rt)
		clipflags |= CLIP_TRANSFORM_FLIP_Y;
	data.clipSpaceParams = computeClipSpaceParams(clipflags);

	data.screenSizeParams.x = viewportW;
	data.screenSizeParams.y = viewportH;

	// The shader does pixcoord.y = gl_FragCoord.y * params.z + params.w.
	// This lets us flip pixcoord.y when needed, to be consistent (drawing
	// with no RT active makes the pixel coordinates y-flipped.)
	if (rt)
	{
		// No flipping: pixcoord.y = gl_FragCoord.y * 1.0 + 0.0.
		data.screenSizeParams.z = 1.0f;
		data.screenSizeParams.w = 0.0f;
	}
	else
	{
		// gl_FragCoord.y is flipped when drawing to the screen, so we
		// un-flip: pixcoord.y = gl_FragCoord.y * -1.0 + height.
		data.screenSizeParams.z = -1.0f;
		data.screenSizeParams.w = viewportH;
	}

	data.constantColor = gfx->getColor();
	gammaCorrectColor(data.constantColor);

	GLint location = builtinUniforms[BUILTIN_UNIFORMS_PER_DRAW];
	if (location >= 0)
		glUniform4fv(location, 12, (const GLfloat *) &data);
}

} // opengl
} // graphics
} // love