Browse Source

vulkan: use push constants for builtin uniforms
when available

niki 2 years ago
parent
commit
47e3fc85d2

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

@@ -155,6 +155,7 @@ public:
 		FEATURE_FULL_NPOT,
 		FEATURE_PIXEL_SHADER_HIGHP,
 		FEATURE_SHADER_DERIVATIVES,
+		FEATURE_BUILTIN_UNIFORM_PUSH_CONSTANT,
 		FEATURE_GLSL3,
 		FEATURE_GLSL4,
 		FEATURE_INSTANCING,

+ 11 - 0
src/modules/graphics/Shader.cpp

@@ -86,6 +86,13 @@ static const char global_syntax[] = R"(
 )";
 
 static const char render_uniforms[] = R"(
+#ifdef LOVE_BUILTIN_UNIFORM_PUSH_CONSTANT
+layout( push_constant, std430 ) uniform LoveBuiltinUniforms
+{
+	vec4 data[13];
+} LovePushConstants;
+#define love_UniformsPerDraw (LovePushConstants.data)
+#else
 // According to the GLSL ES 1.0 spec, uniform precision must match between stages,
 // but we can't guarantee that highp is always supported in fragment shaders...
 // We *really* don't want to use mediump for these in vertex shaders though.
@@ -95,6 +102,7 @@ uniform LOVE_HIGHP_OR_MEDIUMP vec4 love_UniformsPerDraw2[1];
 #else
 uniform LOVE_HIGHP_OR_MEDIUMP vec4 love_UniformsPerDraw[13];
 #endif
+#endif
 
 // Older GLSL doesn't support preprocessor line continuations...
 #define TransformMatrix mat4(love_UniformsPerDraw[0], love_UniformsPerDraw[1], love_UniformsPerDraw[2], love_UniformsPerDraw[3])
@@ -543,6 +551,9 @@ std::string Shader::createShaderStageCode(Graphics *gfx, ShaderStageType stage,
 	if (glsl1on3)
 		ss << "#define LOVE_GLSL1_ON_GLSL3 1\n";
 
+	if (gfx->getCapabilities().features[Graphics::FEATURE_BUILTIN_UNIFORM_PUSH_CONSTANT])
+		ss << "#define LOVE_BUILTIN_UNIFORM_PUSH_CONSTANT 1\n";
+
 	if (isGammaCorrect())
 		ss << "#define LOVE_GAMMA_CORRECT 1\n";
 	if (info.usesMRT)

+ 30 - 0
src/modules/graphics/ShaderStage.cpp

@@ -137,6 +137,21 @@ namespace love
 namespace graphics
 {
 
+static EShLanguage getGlslShaderType(ShaderStageType stage)
+{
+	switch (stage)
+	{
+	case SHADERSTAGE_VERTEX:
+		return EShLangVertex;
+	case SHADERSTAGE_PIXEL:
+		return EShLangFragment;
+	case SHADERSTAGE_COMPUTE:
+		return EShLangCompute;
+	default:
+		throw love::Exception("unkonwn shader stage type");
+	}
+}
+
 ShaderStage::ShaderStage(Graphics *gfx, ShaderStageType stage, const std::string &glsl, bool gles, const std::string &cachekey)
 	: stageType(stage)
 	, source(glsl)
@@ -169,6 +184,21 @@ ShaderStage::ShaderStage(Graphics *gfx, ShaderStageType stage, const std::string
 
 	bool forwardcompat = supportsGLSL3 && !forcedefault;
 
+	if (gfx->getCapabilities().features[Graphics::FEATURE_BUILTIN_UNIFORM_PUSH_CONSTANT]) {
+		forcedefault = true;
+		defaultversion = 450;
+		defaultprofile = ECoreProfile;
+
+		glslangShader->setEnvInput(glslang::EShSourceGlsl, getGlslShaderType(stageType), glslang::EShClientVulkan, 450);
+		glslangShader->setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_2);
+		glslangShader->setEnvTarget(glslang::EshTargetSpv, glslang::EShTargetSpv_1_0);
+		glslangShader->setEnvInputVulkanRulesRelaxed();
+		glslangShader->setAutoMapLocations(true);
+		glslangShader->setAutoMapBindings(true);
+		glslangShader->setGlobalUniformBinding(0);
+		glslangShader->setGlobalUniformSet(0);
+	}
+
 	if (!glslangShader->parse(&defaultTBuiltInResource, defaultversion, defaultprofile, forcedefault, forwardcompat, EShMsgSuppressWarnings))
 	{
 		const char *stagename = "unknown";

+ 2 - 1
src/modules/graphics/metal/Graphics.mm

@@ -2123,6 +2123,7 @@ void Graphics::initCapabilities()
 	capabilities.features[FEATURE_FULL_NPOT] = true;
 	capabilities.features[FEATURE_PIXEL_SHADER_HIGHP] = true;
 	capabilities.features[FEATURE_SHADER_DERIVATIVES] = true;
+	capabilities.features[FEATURE_BUILTIN_UNIFORM_PUSH_CONSTANT] = false;
 	capabilities.features[FEATURE_GLSL3] = true;
 	capabilities.features[FEATURE_GLSL4] = true;
 	capabilities.features[FEATURE_INSTANCING] = true;
@@ -2132,7 +2133,7 @@ void Graphics::initCapabilities()
 	capabilities.features[FEATURE_COPY_BUFFER_TO_TEXTURE] = true;
 	capabilities.features[FEATURE_COPY_TEXTURE_TO_BUFFER] = true;
 	capabilities.features[FEATURE_COPY_RENDER_TARGET_TO_BUFFER] = true;
-	static_assert(FEATURE_MAX_ENUM == 17, "Graphics::initCapabilities must be updated when adding a new graphics feature!");
+	static_assert(FEATURE_MAX_ENUM == 18, "Graphics::initCapabilities must be updated when adding a new graphics feature!");
 
 	// https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
 	capabilities.limits[LIMIT_POINT_SIZE] = 511;

+ 2 - 1
src/modules/graphics/opengl/Graphics.cpp

@@ -1649,6 +1649,7 @@ void Graphics::initCapabilities()
 	capabilities.features[FEATURE_FULL_NPOT] = GLAD_VERSION_2_0 || GLAD_ES_VERSION_3_0 || GLAD_OES_texture_npot;
 	capabilities.features[FEATURE_PIXEL_SHADER_HIGHP] = gl.isPixelShaderHighpSupported();
 	capabilities.features[FEATURE_SHADER_DERIVATIVES] = GLAD_VERSION_2_0 || GLAD_ES_VERSION_3_0 || GLAD_OES_standard_derivatives;
+	capabilities.features[FEATURE_BUILTIN_UNIFORM_PUSH_CONSTANT] = false;
 	capabilities.features[FEATURE_GLSL3] = GLAD_ES_VERSION_3_0 || gl.isCoreProfile();
 	capabilities.features[FEATURE_GLSL4] = GLAD_ES_VERSION_3_1 || (gl.isCoreProfile() && GLAD_VERSION_4_3);
 	capabilities.features[FEATURE_INSTANCING] = gl.isInstancingSupported();
@@ -1658,7 +1659,7 @@ void Graphics::initCapabilities()
 	capabilities.features[FEATURE_COPY_BUFFER_TO_TEXTURE] = gl.isCopyBufferToTextureSupported();
 	capabilities.features[FEATURE_COPY_TEXTURE_TO_BUFFER] = gl.isCopyTextureToBufferSupported();
 	capabilities.features[FEATURE_COPY_RENDER_TARGET_TO_BUFFER] = gl.isCopyRenderTargetToBufferSupported();
-	static_assert(FEATURE_MAX_ENUM == 17, "Graphics::initCapabilities must be updated when adding a new graphics feature!");
+	static_assert(FEATURE_MAX_ENUM == 18, "Graphics::initCapabilities must be updated when adding a new graphics feature!");
 
 	capabilities.limits[LIMIT_POINT_SIZE] = gl.getMaxPointSize();
 	capabilities.limits[LIMIT_TEXTURE_SIZE] = gl.getMax2DTextureSize();

+ 16 - 17
src/modules/graphics/vulkan/Graphics.cpp

@@ -226,7 +226,7 @@ void Graphics::discard(const std::vector<bool>& colorbuffers, bool depthstencil)
 	startRenderPass();
 }
 
-void Graphics::submitGpuCommands(bool present, void* screenshotCallbackData)
+void Graphics::submitGpuCommands(bool present, void *screenshotCallbackData)
 {
 	flushBatchedDraws();
 
@@ -255,17 +255,6 @@ void Graphics::submitGpuCommands(bool present, void* screenshotCallbackData)
 				VK_IMAGE_LAYOUT_UNDEFINED,
 				VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
 
-			VkImageCopy imageCopy{};
-			imageCopy.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
-			imageCopy.srcSubresource.layerCount = 1;
-			imageCopy.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
-			imageCopy.dstSubresource.layerCount = 1;
-			imageCopy.extent = {
-				swapChainExtent.width,
-				swapChainExtent.height,
-				1
-			};
-
 			VkImageBlit blit{};
 			blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
 			blit.srcSubresource.layerCount = 1;
@@ -407,7 +396,7 @@ void Graphics::present(void *screenshotCallbackdata)
 
 	deprecations.draw(this);
 
-	submitGpuCommands(true);
+	submitGpuCommands(true, screenshotCallbackdata);
 
 	VkPresentInfoKHR presentInfo{};
 	presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
@@ -533,6 +522,7 @@ void Graphics::initCapabilities()
 	capabilities.features[FEATURE_FULL_NPOT] = false;
 	capabilities.features[FEATURE_PIXEL_SHADER_HIGHP] = true;
 	capabilities.features[FEATURE_SHADER_DERIVATIVES] = true;
+	capabilities.features[FEATURE_BUILTIN_UNIFORM_PUSH_CONSTANT] = canUsePushConstants;
 	capabilities.features[FEATURE_GLSL3] = true;
 	capabilities.features[FEATURE_GLSL4] = true;
 	capabilities.features[FEATURE_INSTANCING] = true;
@@ -542,7 +532,7 @@ void Graphics::initCapabilities()
 	capabilities.features[FEATURE_COPY_BUFFER_TO_TEXTURE] = true;
 	capabilities.features[FEATURE_COPY_TEXTURE_TO_BUFFER] = true;
 	capabilities.features[FEATURE_COPY_RENDER_TARGET_TO_BUFFER] = true;
-	static_assert(FEATURE_MAX_ENUM == 17, "Graphics::initCapabilities must be updated when adding a new graphics feature!");
+	static_assert(FEATURE_MAX_ENUM == 18, "Graphics::initCapabilities must be updated when adding a new graphics feature!");
 
 	VkPhysicalDeviceProperties properties;
 	vkGetPhysicalDeviceProperties(physicalDevice, &properties);
@@ -577,6 +567,12 @@ void Graphics::unSetMode()
 {
 	created = false;
 	vkDeviceWaitIdle(device);
+
+	for (const auto &readbacks : readbackCallbacks)
+		for (const auto &readback : readbacks)
+			readback();
+	readbackCallbacks.clear();
+
 	Volatile::unloadAll();
 	cleanup();
 }
@@ -599,7 +595,7 @@ int Graphics::getBackbufferMSAA() const
 
 void Graphics::setFrontFaceWinding(Winding winding)
 {
-	const auto& currentState = states.back();
+	const auto &currentState = states.back();
 
 	if (currentState.winding == winding)
 		return;
@@ -1141,7 +1137,7 @@ graphics::Shader::BuiltinUniformData Graphics::getCurrentBuiltinUniformData()
 	}
 
 	// Store DPI scale in an unused component of another vector.
-	data.normalMatrix[0].w = (float)getCurrentDPIScale();
+	data.normalMatrix[0].w = static_cast<float>(getCurrentDPIScale());
 
 	// Same with point size.
 	data.normalMatrix[1].w = getPointSize();
@@ -1296,6 +1292,9 @@ void Graphics::pickPhysicalDevice()
 	vkGetPhysicalDeviceProperties(physicalDevice, &properties);
 	minUniformBufferOffsetAlignment = properties.limits.minUniformBufferOffsetAlignment;
 
+	if (sizeof(graphics::Shader::BuiltinUniformData) <= properties.limits.maxPushConstantsSize)
+		canUsePushConstants = true;
+
 	msaaSamples = getMsaaCount(requestedMsaa);
 }
 
@@ -2813,7 +2812,7 @@ void Graphics::cleanup()
 
 void Graphics::cleanupSwapChain()
 {
-	for (const auto& readbackBuffer : screenshotReadbackBuffers)
+	for (const auto &readbackBuffer : screenshotReadbackBuffers)
 	{
 		vmaDestroyBuffer(vmaAllocator, readbackBuffer.buffer, readbackBuffer.allocation);
 		vmaDestroyImage(vmaAllocator, readbackBuffer.image, readbackBuffer.imageAllocation);

+ 1 - 0
src/modules/graphics/vulkan/Graphics.h

@@ -373,6 +373,7 @@ private:
 	uint32_t vulkanApiVersion = VK_VERSION_1_0;
 	VkInstance instance = VK_NULL_HANDLE;
 	VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
+	bool canUsePushConstants = false;
 	int requestedMsaa = 0;
 	VkDevice device = VK_NULL_HANDLE; 
 	OptionalInstanceExtensions optionalInstanceExtensions;

+ 159 - 60
src/modules/graphics/vulkan/Shader.cpp

@@ -4,7 +4,7 @@
 #include "libraries/glslang/glslang/Public/ShaderLang.h"
 #include "libraries/glslang/SPIRV/GlslangToSpv.h"
 
-
+#include <set>
 #include <vector>
 
 namespace love
@@ -327,10 +327,24 @@ void Shader::newFrame(uint32_t frameIndex)
 			vkUpdateDescriptorSets(device, 1, &write, 0, nullptr);
 		}
 	}
+
+	if (useBuiltinUniformPushConstant)
+	{
+		if (!localUniformData.empty())
+		{
+
+		}
+	}
 }
 
 void Shader::cmdPushDescriptorSets(VkCommandBuffer commandBuffer, VkPipelineBindPoint bindPoint)
 {
+	if (useBuiltinUniformPushConstant)
+	{
+		auto builtinData = vgfx->getCurrentBuiltinUniformData();
+		vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(BuiltinUniformData), &builtinData);
+	}
+	
 	if (!localUniformData.empty())
 	{
 		auto usedStreamBufferMemory = currentUsedUniformStreamBuffersCount * uniformBufferSizeAligned;
@@ -525,6 +539,7 @@ void Shader::calculateUniformBufferSizeAligned()
 	uniformBufferSizeAligned = factor * minAlignment;
 }
 
+
 void Shader::buildLocalUniforms(spirv_cross::Compiler &comp, const spirv_cross::SPIRType &type, size_t baseoff, const std::string &basename)
 {
 	using namespace spirv_cross;
@@ -601,6 +616,40 @@ void Shader::buildLocalUniforms(spirv_cross::Compiler &comp, const spirv_cross::
 	}
 }
 
+static void parse(glslang::TShader* tshader, EShLanguage stage, const std::string &glsl, bool spirv14)
+{
+	using namespace glslang;
+
+	tshader->setEnvInput(EShSourceGlsl, stage, EShClientVulkan, 450);
+	tshader->setEnvClient(EShClientVulkan, EShTargetVulkan_1_2);
+	if (spirv14)
+		tshader->setEnvTarget(EshTargetSpv, EShTargetSpv_1_4);
+	else
+		tshader->setEnvTarget(EshTargetSpv, EShTargetSpv_1_0);
+	tshader->setAutoMapLocations(true);
+	tshader->setAutoMapBindings(true);
+	tshader->setEnvInputVulkanRulesRelaxed();
+	tshader->setGlobalUniformBinding(0);
+	tshader->setGlobalUniformSet(0);
+
+	const char* csrc = glsl.c_str();
+	const int sourceLength = static_cast<int>(glsl.length());
+	tshader->setStringsWithLengths(&csrc, &sourceLength, 1);
+
+	int defaultVersion = 450;
+	EProfile defaultProfile = ECoreProfile;
+	bool forceDefault = false;
+	bool forwardCompat = true;
+
+	if (!tshader->parse(&defaultTBuiltInResource, defaultVersion, defaultProfile, forceDefault, forwardCompat, EShMsgSuppressWarnings))
+	{
+		const char* msg1 = tshader->getInfoLog();
+		const char* msg2 = tshader->getInfoDebugLog();
+
+		throw love::Exception("error while parsing shader");
+	}
+}
+
 void Shader::compileShaders()
 {
 	using namespace glslang;
@@ -613,6 +662,7 @@ void Shader::compileShaders()
 	device = vgfx->getDevice();
 
 	const auto &enabledExtensions = vgfx->getEnabledOptionalDeviceExtensions();
+	auto useSpirv14 = enabledExtensions.spirv14;
 
 	for (int i = 0; i < SHADERSTAGE_MAX_ENUM; i++)
 	{
@@ -627,35 +677,7 @@ void Shader::compileShaders()
 		auto glslangShaderStage = getGlslShaderType(stage);
 		auto tshader = new TShader(glslangShaderStage);
 
-		tshader->setEnvInput(EShSourceGlsl, glslangShaderStage, EShClientVulkan, 450);
-		tshader->setEnvClient(EShClientVulkan, EShTargetVulkan_1_2);
-		if (enabledExtensions.spirv14)
-			tshader->setEnvTarget(EshTargetSpv, EShTargetSpv_1_4);
-		else
-			tshader->setEnvTarget(EshTargetSpv, EShTargetSpv_1_0);
-		tshader->setAutoMapLocations(true);
-		tshader->setAutoMapBindings(true);
-		tshader->setEnvInputVulkanRulesRelaxed();
-		tshader->setGlobalUniformBinding(0);
-		tshader->setGlobalUniformSet(0);
-
-		auto &glsl = stages[i]->getSource();
-		const char *csrc = glsl.c_str();
-		const int sourceLength = static_cast<int>(glsl.length());
-		tshader->setStringsWithLengths(&csrc, &sourceLength, 1);
-
-		int defaultVersion = 450;
-		EProfile defaultProfile = ECoreProfile;
-		bool forceDefault = false;
-		bool forwardCompat = true;
-
-		if (!tshader->parse(&defaultTBuiltInResource, defaultVersion, defaultProfile, forceDefault, forwardCompat, EShMsgSuppressWarnings))
-		{
-			const char *msg1 = tshader->getInfoLog();
-			const char *msg2 = tshader->getInfoDebugLog();
-
-			throw love::Exception("error while parsing shader");
-		}
+		parse(tshader, glslangShaderStage, stages[i]->getSource(), useSpirv14);
 
 		program->addShader(tshader);
 		glslangShaders.push_back(tshader);
@@ -669,6 +691,8 @@ void Shader::compileShaders()
 
 	uniformInfos.clear();
 
+	std::set<uint32_t> usedBufferBindings;
+
 	for (int i = 0; i < SHADERSTAGE_MAX_ENUM; i++)
 	{
 		auto shaderStage = (ShaderStageType)i;
@@ -678,6 +702,26 @@ void Shader::compileShaders()
 			continue;
 		}
 
+		bool recompile = false;
+
+		auto getFreeBinding = [&](uint32_t binding) {
+			if (usedBufferBindings.find(binding) == usedBufferBindings.end())
+			{
+				usedBufferBindings.insert(binding);
+				return binding;
+			}
+
+			recompile = true;
+			for (uint32_t i = 0; ; i++)
+			{
+				if (usedBufferBindings.find(i) == usedBufferBindings.end())
+				{
+					usedBufferBindings.insert(i);
+					return i;
+				}
+			}
+		};
+
 		spv::SpvBuildLogger logger;
 		glslang::SpvOptions opt;
 		opt.validate = true;
@@ -687,29 +731,6 @@ void Shader::compileShaders()
 
 		std::string msgs = logger.getAllMessages();
 
-		VkShaderModuleCreateInfo createInfo{};
-		createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
-		createInfo.codeSize = spirv.size() * sizeof(uint32_t);
-		createInfo.pCode = spirv.data();
-
-		auto device = vgfx->getDevice();
-
-		VkShaderModule shaderModule;
-
-		if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) {
-			throw love::Exception("failed to create shader module");
-		}
-
-		shaderModules.push_back(shaderModule);
-
-		VkPipelineShaderStageCreateInfo shaderStageInfo{};
-		shaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
-		shaderStageInfo.stage = getStageBit((ShaderStageType)i);
-		shaderStageInfo.module = shaderModule;
-		shaderStageInfo.pName = "main";
-
-		shaderStages.push_back(shaderStageInfo);
-
 		spirv_cross::CompilerGLSL comp(spirv);
 
 		// we only care about variables that are actually getting used.
@@ -717,16 +738,25 @@ void Shader::compileShaders()
 		auto shaderResources = comp.get_shader_resources(active);
 		comp.set_enabled_interface_variables(std::move(active));
 
-		for (const auto  &resource : shaderResources.uniform_buffers)
+		for (const auto &resource : shaderResources.push_constant_buffers)
+		{
+			useBuiltinUniformPushConstant = true;
+		}
+
+		for (const auto &resource : shaderResources.uniform_buffers)
 		{
+			if (uniformInfos.find(resource.name) != uniformInfos.end())
+				continue;
+
 			if (resource.name == "gl_DefaultUniformBlock")
 			{
-				const auto& type = comp.get_type(resource.base_type_id);
+				const auto &type = comp.get_type(resource.base_type_id);
 				size_t uniformBufferObjectSize = comp.get_declared_struct_size(type);
 				auto defaultUniformBlockSize = comp.get_declared_struct_size(type);
 				localUniformStagingData.resize(defaultUniformBlockSize);
 				localUniformData.resize(defaultUniformBlockSize);
-				uniformLocation = comp.get_decoration(resource.id, spv::DecorationBinding);
+				uniformLocation = getFreeBinding(comp.get_decoration(resource.id, spv::DecorationBinding));
+				comp.set_decoration(resource.id, spv::DecorationBinding, uniformLocation);
 
 				memset(localUniformStagingData.data(), 0, defaultUniformBlockSize);
 				memset(localUniformData.data(), 0, defaultUniformBlockSize);
@@ -742,12 +772,16 @@ void Shader::compileShaders()
 
 		for (const auto &r : shaderResources.sampled_images)
 		{
+			if (uniformInfos.find(r.name) != uniformInfos.end())
+				continue;
+
 			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 = comp.get_decoration(r.id, spv::DecorationBinding);
+			info.location = getFreeBinding(comp.get_decoration(r.id, spv::DecorationBinding));
+			comp.set_decoration(r.id, spv::DecorationBinding, info.location);
 			info.baseType = UNIFORM_SAMPLER;
 			info.name = r.name;
 			info.count = type.array.empty() ? 1 : type.array[0];
@@ -812,6 +846,9 @@ void Shader::compileShaders()
 
 		for (const auto &r : shaderResources.storage_buffers)
 		{
+			if (uniformInfos.find(r.name) != uniformInfos.end())
+				continue;
+
 			const auto &type = comp.get_type(r.type_id);
 
 			UniformInfo u{};
@@ -819,8 +856,9 @@ void Shader::compileShaders()
 			u.components = 1;
 			u.name = r.name;
 			u.count = type.array.empty() ? 1 : type.array[0];
-			u.location = comp.get_decoration(r.id, spv::DecorationBinding);
-			
+			u.location = getFreeBinding(comp.get_decoration(r.id, spv::DecorationBinding));
+			comp.set_decoration(r.id, spv::DecorationBinding, u.location);
+
 			const auto reflectionit = validationReflection.storageBuffers.find(u.name);
 			if (reflectionit != validationReflection.storageBuffers.end())
 			{
@@ -844,6 +882,9 @@ void Shader::compileShaders()
 
 		for (const auto &r : shaderResources.storage_images)
 		{
+			if (uniformInfos.find(r.name) != uniformInfos.end())
+				continue;
+
 			const auto &type = comp.get_type(r.type_id);
 
 			UniformInfo u{};
@@ -851,8 +892,10 @@ void Shader::compileShaders()
 			u.components = 1;
 			u.name = r.name;
 			u.count = type.array.empty() ? 1 : type.array[0];
+			// fixme: memory leak
 			u.textures = new love::graphics::Texture *[u.count];
 			u.location = comp.get_decoration(r.id, spv::DecorationBinding);
+			comp.set_decoration(r.id, spv::DecorationBinding, u.location);
 
 			for (int i = 0; i < u.count; i++)
 				u.textures[i] = nullptr;
@@ -869,6 +912,50 @@ void Shader::compileShaders()
 				const int attributeLocation = static_cast<int>(comp.get_decoration(r.id, spv::DecorationLocation));
 				attributes[name] = attributeLocation;
 			}
+
+		if (recompile)
+		{
+			auto glslLanguage = getGlslShaderType((ShaderStageType)i);
+			TShader* shader = new TShader(glslLanguage);
+
+			parse(shader, glslLanguage, comp.compile(), useSpirv14);
+
+			auto intermediate = shader->getIntermediate();
+
+			spv::SpvBuildLogger logger;
+			glslang::SpvOptions opt;
+
+			std::vector<uint32_t> code;
+
+			GlslangToSpv(*intermediate, code, &logger, &opt);
+
+			spirv = std::move(code);
+
+			delete shader;
+		}
+
+		VkShaderModuleCreateInfo createInfo{};
+		createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+		createInfo.codeSize = spirv.size() * sizeof(uint32_t);
+		createInfo.pCode = spirv.data();
+
+		auto device = vgfx->getDevice();
+
+		VkShaderModule shaderModule;
+
+		if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) {
+			throw love::Exception("failed to create shader module");
+		}
+
+		shaderModules.push_back(shaderModule);
+
+		VkPipelineShaderStageCreateInfo shaderStageInfo{};
+		shaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+		shaderStageInfo.stage = getStageBit((ShaderStageType)i);
+		shaderStageInfo.module = shaderModule;
+		shaderStageInfo.pName = "main";
+
+		shaderStages.push_back(shaderStageInfo);
 	}
 
 	delete program;
@@ -927,7 +1014,19 @@ void Shader::createPipelineLayout()
 	pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
 	pipelineLayoutInfo.setLayoutCount = 1;
 	pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout;
-	pipelineLayoutInfo.pushConstantRangeCount = 0;
+
+	static_assert(sizeof(BuiltinUniformData) % 4 == 0, "size of push constant has to be a multiple of 4.");
+
+	VkPushConstantRange range{};
+	range.offset = 0;
+	range.size = sizeof(BuiltinUniformData);
+	range.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
+
+	if (useBuiltinUniformPushConstant)
+	{
+		pipelineLayoutInfo.pushConstantRangeCount = 1;
+		pipelineLayoutInfo.pPushConstantRanges = &range;
+	}
 
 	if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS)
 		throw love::Exception("failed to create pipeline layout");

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

@@ -88,6 +88,7 @@ private:
 
 	VkPipeline computePipeline;
 
+	bool useBuiltinUniformPushConstant = false;
 	VkDescriptorSetLayout descriptorSetLayout;
 	VkPipelineLayout pipelineLayout;
 	std::vector<VkDescriptorPoolSize> descriptorPoolSizes;