Browse Source

Merge pull request #1902 from nikeinikei/12.0-development

Improve vk render passes
Sasha Szpakowski 2 years ago
parent
commit
11c92ece33

+ 298 - 244
src/modules/graphics/vulkan/Graphics.cpp

@@ -110,50 +110,87 @@ void Graphics::clear(OptionalColorD color, OptionalInt stencil, OptionalDouble d
 
 	flushBatchedDraws();
 
-	if (!renderPassState.active)
-		startRenderPass();
+	if (renderPassState.active)
+	{
+		VkClearAttachment attachment{};
 
-	VkClearAttachment attachment{};
+		if (color.hasValue)
+		{
+			Colorf cf((float)color.value.r, (float)color.value.g, (float)color.value.b, (float)color.value.a);
+			gammaCorrectColor(cf);
 
-	if (color.hasValue)
-	{
-		Colorf cf((float)color.value.r, (float)color.value.g, (float)color.value.b, (float)color.value.a);
-		gammaCorrectColor(cf);
+			attachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			attachment.clearValue.color.float32[0] = static_cast<float>(cf.r);
+			attachment.clearValue.color.float32[1] = static_cast<float>(cf.g);
+			attachment.clearValue.color.float32[2] = static_cast<float>(cf.b);
+			attachment.clearValue.color.float32[3] = static_cast<float>(cf.a);
+		}
 
-		attachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
-		attachment.clearValue.color.float32[0] = static_cast<float>(cf.r);
-		attachment.clearValue.color.float32[1] = static_cast<float>(cf.g);
-		attachment.clearValue.color.float32[2] = static_cast<float>(cf.b);
-		attachment.clearValue.color.float32[3] = static_cast<float>(cf.a);
-	}
+		VkClearAttachment depthStencilAttachment{};
 
-	VkClearAttachment depthStencilAttachment{};
+		if (stencil.hasValue)
+		{
+			depthStencilAttachment.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT;
+			depthStencilAttachment.clearValue.depthStencil.stencil = static_cast<uint32_t>(stencil.value);
+		}
+		if (depth.hasValue)
+		{
+			depthStencilAttachment.aspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT;
+			depthStencilAttachment.clearValue.depthStencil.depth = static_cast<float>(depth.value);
+		}
 
-	if (stencil.hasValue)
-	{
-		depthStencilAttachment.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT;
-		depthStencilAttachment.clearValue.depthStencil.stencil = static_cast<uint32_t>(stencil.value);
+		std::array<VkClearAttachment, 2> attachments = {
+			attachment,
+			depthStencilAttachment
+		};
+
+		VkClearRect rect{};
+		rect.layerCount = 1;
+		rect.rect.extent.width = static_cast<uint32_t>(renderPassState.width);
+		rect.rect.extent.height = static_cast<uint32_t>(renderPassState.height);
+
+		vkCmdClearAttachments(
+			commandBuffers[currentFrame], 
+			static_cast<uint32_t>(attachments.size()), attachments.data(),
+			1, &rect);
 	}
-	if (depth.hasValue)
+	else
 	{
-		depthStencilAttachment.aspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT;
-		depthStencilAttachment.clearValue.depthStencil.depth = static_cast<float>(depth.value);
-	}
+		if (color.hasValue)
+		{
+			renderPassState.renderPassConfiguration.colorAttachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
 
-	std::array<VkClearAttachment, 2> attachments = {
-		attachment,
-		depthStencilAttachment
-	};
+			Colorf cf((float)color.value.r, (float)color.value.g, (float)color.value.b, (float)color.value.a);
+			gammaCorrectColor(cf);
+
+			renderPassState.clearColors[0].color.float32[0] = static_cast<float>(cf.r);
+			renderPassState.clearColors[0].color.float32[1] = static_cast<float>(cf.g);
+			renderPassState.clearColors[0].color.float32[2] = static_cast<float>(cf.b);
+			renderPassState.clearColors[0].color.float32[3] = static_cast<float>(cf.a);
+		}
+
+		if (depth.hasValue)
+		{
+			renderPassState.renderPassConfiguration.staticData.depthStencilAttachment.depthLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+			renderPassState.clearColors[1].depthStencil.depth = static_cast<float>(depth.value);
+		}
 
-	VkClearRect rect{};
-	rect.layerCount = 1;
-	rect.rect.extent.width = static_cast<uint32_t>(renderPassState.width);
-	rect.rect.extent.height = static_cast<uint32_t>(renderPassState.height);
+		if (stencil.hasValue)
+		{
+			renderPassState.renderPassConfiguration.staticData.depthStencilAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+			renderPassState.clearColors[1].depthStencil.stencil = static_cast<uint32_t>(stencil.value);
+		}
 
-	vkCmdClearAttachments(
-		commandBuffers[currentFrame], 
-		static_cast<uint32_t>(attachments.size()), attachments.data(),
-		1, &rect);
+		if (renderPassState.isWindow)
+		{
+			renderPassState.windowClearRequested = true;
+			renderPassState.mainWindowClearColorValue = color;
+			renderPassState.mainWindowClearDepthValue = depth;
+			renderPassState.mainWindowClearStencilValue = stencil;
+		}
+		else
+			startRenderPass();
+	}
 }
 
 void Graphics::clear(const std::vector<OptionalColorD> &colors, OptionalInt stencil, OptionalDouble depth)
@@ -163,51 +200,84 @@ void Graphics::clear(const std::vector<OptionalColorD> &colors, OptionalInt sten
 
 	flushBatchedDraws();
 
-	if (!renderPassState.active)
-		startRenderPass();
-
-	std::vector<VkClearAttachment> attachments;
-	for (const auto &color : colors)
+	if (renderPassState.active)
 	{
-		VkClearAttachment attachment{};
-		if (color.hasValue)
+		std::vector<VkClearAttachment> attachments;
+		for (const auto &color : colors)
 		{
-			Colorf cf((float)color.value.r, (float)color.value.g, (float)color.value.b, (float)color.value.a);
-			gammaCorrectColor(cf);
+			VkClearAttachment attachment{};
+			if (color.hasValue)
+			{
+				Colorf cf((float)color.value.r, (float)color.value.g, (float)color.value.b, (float)color.value.a);
+				gammaCorrectColor(cf);
+
+				attachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+				attachment.clearValue.color.float32[0] = static_cast<float>(cf.r);
+				attachment.clearValue.color.float32[1] = static_cast<float>(cf.g);
+				attachment.clearValue.color.float32[2] = static_cast<float>(cf.b);
+				attachment.clearValue.color.float32[3] = static_cast<float>(cf.a);
+			}
+			attachments.push_back(attachment);
+		}
 
-			attachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
-			attachment.clearValue.color.float32[0] = static_cast<float>(cf.r);
-			attachment.clearValue.color.float32[1] = static_cast<float>(cf.g);
-			attachment.clearValue.color.float32[2] = static_cast<float>(cf.b);
-			attachment.clearValue.color.float32[3] = static_cast<float>(cf.a);
+		VkClearAttachment depthStencilAttachment{};
+
+		if (stencil.hasValue)
+		{
+			depthStencilAttachment.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
+			depthStencilAttachment.clearValue.depthStencil.stencil = static_cast<uint32_t>(stencil.value);
+		}
+		if (depth.hasValue)
+		{
+			depthStencilAttachment.aspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT;
+			depthStencilAttachment.clearValue.depthStencil.depth = static_cast<float>(depth.value);
 		}
-		attachments.push_back(attachment);
-	}
 
-	VkClearAttachment depthStencilAttachment{};
+		attachments.push_back(depthStencilAttachment);
 
-	if (stencil.hasValue)
-	{
-		depthStencilAttachment.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
-		depthStencilAttachment.clearValue.depthStencil.stencil = static_cast<uint32_t>(stencil.value);
+		VkClearRect rect{};
+		rect.layerCount = 1;
+		rect.rect.extent.width = static_cast<uint32_t>(renderPassState.width);
+		rect.rect.extent.height = static_cast<uint32_t>(renderPassState.height);
+
+		vkCmdClearAttachments(
+			commandBuffers[currentFrame],
+			static_cast<uint32_t>(attachments.size()), attachments.data(),
+			1, &rect);
 	}
-	if (depth.hasValue)
+	else
 	{
-		depthStencilAttachment.aspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT;
-		depthStencilAttachment.clearValue.depthStencil.depth = static_cast<float>(depth.value);
-	}
+		for (size_t i = 0; i < colors.size(); i++)
+		{
+			if (colors[i].hasValue)
+			{
+				renderPassState.renderPassConfiguration.colorAttachments[i].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+
+				auto &color = colors[i];
+				Colorf cf((float)color.value.r, (float)color.value.g, (float)color.value.b, (float)color.value.a);
+				gammaCorrectColor(cf);
 
-	attachments.push_back(depthStencilAttachment);
+				renderPassState.clearColors[i].color.float32[0] = static_cast<float>(cf.r);
+				renderPassState.clearColors[i].color.float32[1] = static_cast<float>(cf.g);
+				renderPassState.clearColors[i].color.float32[2] = static_cast<float>(cf.b);
+				renderPassState.clearColors[i].color.float32[3] = static_cast<float>(cf.a);
+			}
+		}
+
+		if (depth.hasValue)
+		{
+			renderPassState.renderPassConfiguration.staticData.depthStencilAttachment.depthLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+			renderPassState.clearColors[colors.size()].depthStencil.depth = static_cast<float>(depth.value);
+		}
 
-	VkClearRect rect{};
-	rect.layerCount = 1;
-	rect.rect.extent.width = static_cast<uint32_t>(renderPassState.width);
-	rect.rect.extent.height = static_cast<uint32_t>(renderPassState.height);
+		if (stencil.hasValue)
+		{
+			renderPassState.renderPassConfiguration.staticData.depthStencilAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+			renderPassState.clearColors[colors.size()].depthStencil.stencil = static_cast<uint32_t>(stencil.value);
+		}
 
-	vkCmdClearAttachments(
-		commandBuffers[currentFrame],
-		static_cast<uint32_t>(attachments.size()), attachments.data(),
-		1, &rect);
+		startRenderPass();
+	}
 }
 
 void Graphics::discard(const std::vector<bool> &colorbuffers, bool depthstencil)
@@ -215,40 +285,18 @@ void Graphics::discard(const std::vector<bool> &colorbuffers, bool depthstencil)
 	if (renderPassState.active)
 		endRenderPass();
 
-	if (renderPassState.useConfigurations)
-	{
-		auto & renderPassConfiguration = renderPassState.renderPassConfiguration;
+	auto &renderPassConfiguration = renderPassState.renderPassConfiguration;
 
-		for (size_t i = 0; i < colorbuffers.size(); i++)
-			renderPassConfiguration.colorAttachments[i].discard = colorbuffers[i];
-		renderPassConfiguration.staticData.depthAttachment.discard = depthstencil;
-	}
-	else
+	for (size_t i = 0; i < colorbuffers.size(); i++)
 	{
-		RenderPassConfiguration renderPassConfiguration{};
-		renderPassConfiguration.colorAttachments.push_back({ swapChainImageFormat, colorbuffers[0], msaaSamples });
-		renderPassConfiguration.staticData.depthAttachment = { findDepthFormat(), depthstencil, msaaSamples };
-		if (msaaSamples & VK_SAMPLE_COUNT_1_BIT)
-			renderPassConfiguration.staticData.resolve = false;
-		else
-			renderPassConfiguration.staticData.resolve = true;
-
-		FramebufferConfiguration framebufferConfiguration{};
-		framebufferConfiguration.staticData.depthView = depthImageView;
-		if (msaaSamples & VK_SAMPLE_COUNT_1_BIT)
-		{
-			framebufferConfiguration.colorViews.push_back(swapChainImageViews.at(imageIndex));
-			framebufferConfiguration.staticData.resolveView = VK_NULL_HANDLE;
-		}
-		else
-		{
-			framebufferConfiguration.colorViews.push_back(colorImageView);
-			framebufferConfiguration.staticData.resolveView = swapChainImageViews.at(imageIndex);
-		}
+		if (colorbuffers[i])
+			renderPassConfiguration.colorAttachments[i].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+	}
 
-		renderPassState.useConfigurations = true;
-		renderPassState.renderPassConfiguration = renderPassConfiguration;
-		renderPassState.framebufferConfiguration = framebufferConfiguration;
+	if (depthstencil)
+	{
+		renderPassConfiguration.staticData.depthStencilAttachment.depthLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+		renderPassConfiguration.staticData.depthStencilAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
 	}
 
 	startRenderPass();
@@ -422,6 +470,9 @@ void Graphics::present(void *screenshotCallbackdata)
 	if (isRenderTargetActive())
 		throw love::Exception("present cannot be called while a render target is active.");
 
+	if (!renderPassState.active && renderPassState.windowClearRequested)
+		startRenderPass();
+
 	deprecations.draw(this);
 
 	submitGpuCommands(true, screenshotCallbackdata);
@@ -493,6 +544,7 @@ bool Graphics::setMode(void *context, int width, int height, int pixelwidth, int
 	createSurface();
 	pickPhysicalDevice();
 	createLogicalDevice();
+	createPipelineCache();
 	initVMA();
 	initCapabilities();
 	createSwapChain();
@@ -502,8 +554,6 @@ bool Graphics::setMode(void *context, int width, int height, int pixelwidth, int
 	createColorResources();
 	createDepthResources();
 	transitionColorDepthLayouts = true;
-	createDefaultRenderPass();
-	createDefaultFramebuffers();
 	createCommandPool();
 	createCommandBuffers();
 
@@ -580,9 +630,9 @@ void Graphics::initCapabilities()
 	capabilities.limits[LIMIT_CUBE_TEXTURE_SIZE] = properties.limits.maxImageDimensionCube;
 	capabilities.limits[LIMIT_TEXEL_BUFFER_SIZE] = properties.limits.maxTexelBufferElements;
 	capabilities.limits[LIMIT_SHADER_STORAGE_BUFFER_SIZE] = properties.limits.maxStorageBufferRange;
-	capabilities.limits[LIMIT_THREADGROUPS_X] = properties.limits.maxComputeWorkGroupSize[0];
-	capabilities.limits[LIMIT_THREADGROUPS_Y] = properties.limits.maxComputeWorkGroupSize[1];
-	capabilities.limits[LIMIT_THREADGROUPS_Z] = properties.limits.maxComputeWorkGroupSize[2];
+	capabilities.limits[LIMIT_THREADGROUPS_X] = properties.limits.maxComputeWorkGroupCount[0];
+	capabilities.limits[LIMIT_THREADGROUPS_Y] = properties.limits.maxComputeWorkGroupCount[1];
+	capabilities.limits[LIMIT_THREADGROUPS_Z] = properties.limits.maxComputeWorkGroupCount[2];
 	capabilities.limits[LIMIT_RENDER_TARGETS] = properties.limits.maxColorAttachments;
 	capabilities.limits[LIMIT_TEXTURE_MSAA] = static_cast<double>(getMsaaCount(64));
 	capabilities.limits[LIMIT_ANISOTROPY] = properties.limits.maxSamplerAnisotropy;
@@ -638,7 +688,7 @@ void Graphics::setFrontFaceWinding(Winding winding)
 
 	states.back().winding = winding;
 
-	if (optionalDeviceFeatures.extendedDynamicState)
+	if (optionalDeviceExtensions.extendedDynamicState)
 		vkCmdSetFrontFaceEXT(
 			commandBuffers.at(currentFrame),
 			Vulkan::getFrontFace(winding));
@@ -875,7 +925,7 @@ void Graphics::setStencilMode(StencilAction action, CompareMode compare, int val
 	vkCmdSetStencilCompareMask(commandBuffers.at(currentFrame), VK_STENCIL_FRONT_AND_BACK, readmask);
 	vkCmdSetStencilReference(commandBuffers.at(currentFrame), VK_STENCIL_FRONT_AND_BACK, value);
 
-	if (optionalDeviceFeatures.extendedDynamicState)
+	if (optionalDeviceExtensions.extendedDynamicState)
 		vkCmdSetStencilOpEXT(
 			commandBuffers.at(currentFrame),
 			VK_STENCIL_FRONT_AND_BACK,
@@ -893,7 +943,7 @@ void Graphics::setDepthMode(CompareMode compare, bool write)
 {
 	flushBatchedDraws();
 
-	if (optionalDeviceFeatures.extendedDynamicState)
+	if (optionalDeviceExtensions.extendedDynamicState)
 	{
 		vkCmdSetDepthCompareOpEXT(
 			commandBuffers.at(currentFrame), Vulkan::getCompareOp(compare));
@@ -1035,6 +1085,8 @@ graphics::StreamBuffer *Graphics::newStreamBuffer(BufferUsage type, size_t size)
 
 bool Graphics::dispatch(love::graphics::Shader *shader, int x, int y, int z)
 {
+	usedShadersInFrame.insert(computeShader);
+
 	if (renderPassState.active)
 		endRenderPass();
 
@@ -1050,6 +1102,8 @@ bool Graphics::dispatch(love::graphics::Shader *shader, int x, int y, int z)
 
 bool Graphics::dispatch(love::graphics::Shader *shader, love::graphics::Buffer *indirectargs, size_t argsoffset)
 {
+	usedShadersInFrame.insert(computeShader);
+
 	if (renderPassState.active)
 		endRenderPass();
 
@@ -1094,7 +1148,7 @@ void Graphics::initDynamicState()
 	vkCmdSetStencilCompareMask(commandBuffers.at(currentFrame), VK_STENCIL_FRONT_AND_BACK, states.back().stencil.readMask);
 	vkCmdSetStencilReference(commandBuffers.at(currentFrame), VK_STENCIL_FRONT_AND_BACK, states.back().stencil.value);
 
-	if (optionalDeviceFeatures.extendedDynamicState)
+	if (optionalDeviceExtensions.extendedDynamicState)
 	{
 		 vkCmdSetStencilOpEXT(
 			commandBuffers.at(currentFrame),
@@ -1176,6 +1230,8 @@ void Graphics::beginFrame()
 
 	Vulkan::resetShaderSwitches();
 
+	for (const auto shader : usedShadersInFrame)
+		shader->newFrame();
 	usedShadersInFrame.clear();
 }
 
@@ -1269,9 +1325,9 @@ graphics::Shader::BuiltinUniformData Graphics::getCurrentBuiltinUniformData()
 	return data;
 }
 
-const OptionalDeviceFeatures &Graphics::getEnabledOptionalDeviceExtensions() const
+const OptionalDeviceExtensions &Graphics::getEnabledOptionalDeviceExtensions() const
 {
-	return optionalDeviceFeatures;
+	return optionalDeviceExtensions;
 }
 
 static void checkOptionalInstanceExtensions(OptionalInstanceExtensions &ext)
@@ -1401,6 +1457,7 @@ void Graphics::pickPhysicalDevice()
 	deviceApiVersion = properties.apiVersion;
 
 	msaaSamples = getMsaaCount(requestedMsaa);
+	depthStencilFormat = findDepthFormat();
 }
 
 bool Graphics::checkDeviceExtensionSupport(VkPhysicalDevice device)
@@ -1498,7 +1555,7 @@ QueueFamilyIndices Graphics::findQueueFamilies(VkPhysicalDevice device)
 	return indices;
 }
 
-static void findOptionalDeviceExtensions(VkPhysicalDevice physicalDevice, OptionalDeviceFeatures &optionalDeviceFeatures)
+static void findOptionalDeviceExtensions(VkPhysicalDevice physicalDevice, OptionalDeviceExtensions &optionalDeviceExtensions)
 {
 	uint32_t extensionCount;
 	vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &extensionCount, nullptr);
@@ -1509,19 +1566,17 @@ static void findOptionalDeviceExtensions(VkPhysicalDevice physicalDevice, Option
 	for (const auto &extension : availableExtensions)
 	{
 		if (strcmp(extension.extensionName, VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME) == 0)
-			optionalDeviceFeatures.extendedDynamicState = true;
+			optionalDeviceExtensions.extendedDynamicState = true;
 		if (strcmp(extension.extensionName, VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME) == 0)
-			optionalDeviceFeatures.memoryRequirements2 = true;
+			optionalDeviceExtensions.memoryRequirements2 = true;
 		if (strcmp(extension.extensionName, VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME) == 0)
-			optionalDeviceFeatures.dedicatedAllocation = true;
+			optionalDeviceExtensions.dedicatedAllocation = true;
 		if (strcmp(extension.extensionName, VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME) == 0)
-			optionalDeviceFeatures.bufferDeviceAddress = true;
-		if (strcmp(extension.extensionName, VK_EXT_MEMORY_BUDGET_EXTENSION_NAME) == 0)
-			optionalDeviceFeatures.memoryBudget = true;
+			optionalDeviceExtensions.memoryBudget = true;
 		if (strcmp(extension.extensionName, VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME) == 0)
-			optionalDeviceFeatures.shaderFloatControls = true;
+			optionalDeviceExtensions.shaderFloatControls = true;
 		if (strcmp(extension.extensionName, VK_KHR_SPIRV_1_4_EXTENSION_NAME) == 0)
-			optionalDeviceFeatures.spirv14 = true;
+			optionalDeviceExtensions.spirv14 = true;
 	}
 }
 
@@ -1546,22 +1601,20 @@ void Graphics::createLogicalDevice()
 		queueCreateInfos.push_back(queueCreateInfo);
 	}
 
-	findOptionalDeviceExtensions(physicalDevice, optionalDeviceFeatures);
+	findOptionalDeviceExtensions(physicalDevice, optionalDeviceExtensions);
 
 	// sanity check for dependencies.
 
-	if (optionalDeviceFeatures.extendedDynamicState && !optionalInstanceExtensions.physicalDeviceProperties2)
-		optionalDeviceFeatures.extendedDynamicState = false;
-	if (optionalDeviceFeatures.dedicatedAllocation && !optionalDeviceFeatures.memoryRequirements2)
-		optionalDeviceFeatures.dedicatedAllocation = false;
-	if (optionalDeviceFeatures.bufferDeviceAddress && !optionalInstanceExtensions.physicalDeviceProperties2)
-		optionalDeviceFeatures.bufferDeviceAddress = false;
-	if (optionalDeviceFeatures.memoryBudget && !optionalInstanceExtensions.physicalDeviceProperties2)
-		optionalDeviceFeatures.memoryBudget = false;
-	if (optionalDeviceFeatures.spirv14 && !optionalDeviceFeatures.shaderFloatControls)
-		optionalDeviceFeatures.spirv14 = false;
-	if (optionalDeviceFeatures.spirv14 && deviceApiVersion < VK_API_VERSION_1_1)
-		optionalDeviceFeatures.spirv14 = false;
+	if (optionalDeviceExtensions.extendedDynamicState && !optionalInstanceExtensions.physicalDeviceProperties2)
+		optionalDeviceExtensions.extendedDynamicState = false;
+	if (optionalDeviceExtensions.dedicatedAllocation && !optionalDeviceExtensions.memoryRequirements2)
+		optionalDeviceExtensions.dedicatedAllocation = false;
+	if (optionalDeviceExtensions.memoryBudget && !optionalInstanceExtensions.physicalDeviceProperties2)
+		optionalDeviceExtensions.memoryBudget = false;
+	if (optionalDeviceExtensions.spirv14 && !optionalDeviceExtensions.shaderFloatControls)
+		optionalDeviceExtensions.spirv14 = false;
+	if (optionalDeviceExtensions.spirv14 && deviceApiVersion < VK_API_VERSION_1_1)
+		optionalDeviceExtensions.spirv14 = false;
 
 	VkPhysicalDeviceFeatures deviceFeatures{};
 	deviceFeatures.samplerAnisotropy = VK_TRUE;
@@ -1574,19 +1627,17 @@ void Graphics::createLogicalDevice()
 	createInfo.pEnabledFeatures = &deviceFeatures;
 
 	std::vector<const char*> enabledExtensions(deviceExtensions.begin(), deviceExtensions.end());
-	if (optionalDeviceFeatures.extendedDynamicState)
+	if (optionalDeviceExtensions.extendedDynamicState)
 		enabledExtensions.push_back(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
-	if (optionalDeviceFeatures.memoryRequirements2)
+	if (optionalDeviceExtensions.memoryRequirements2)
 		enabledExtensions.push_back(VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME);
-	if (optionalDeviceFeatures.dedicatedAllocation)
+	if (optionalDeviceExtensions.dedicatedAllocation)
 		enabledExtensions.push_back(VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME);
-	if (optionalDeviceFeatures.bufferDeviceAddress)
-		enabledExtensions.push_back(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME);
-	if (optionalDeviceFeatures.memoryBudget)
+	if (optionalDeviceExtensions.memoryBudget)
 		enabledExtensions.push_back(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME);
-	if (optionalDeviceFeatures.shaderFloatControls)
+	if (optionalDeviceExtensions.shaderFloatControls)
 		enabledExtensions.push_back(VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME);
-	if (optionalDeviceFeatures.spirv14)
+	if (optionalDeviceExtensions.spirv14)
 		enabledExtensions.push_back(VK_KHR_SPIRV_1_4_EXTENSION_NAME);
 	if (deviceApiVersion >= VK_API_VERSION_1_1)
 		enabledExtensions.push_back(VK_KHR_BIND_MEMORY_2_EXTENSION_NAME);
@@ -1602,7 +1653,7 @@ void Graphics::createLogicalDevice()
 
 	VkPhysicalDeviceExtendedDynamicStateFeaturesEXT extendedDynamicStateFeatures{};
 	extendedDynamicStateFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT;
-	extendedDynamicStateFeatures.extendedDynamicState = Vulkan::getBool(optionalDeviceFeatures.extendedDynamicState);
+	extendedDynamicStateFeatures.extendedDynamicState = Vulkan::getBool(optionalDeviceExtensions.extendedDynamicState);
 	extendedDynamicStateFeatures.pNext = nullptr;
 
 	createInfo.pNext = &extendedDynamicStateFeatures;
@@ -1616,6 +1667,15 @@ void Graphics::createLogicalDevice()
 	vkGetDeviceQueue(device, indices.presentFamily.value, 0, &presentQueue);
 }
 
+void Graphics::createPipelineCache()
+{
+	VkPipelineCacheCreateInfo cacheInfo{};
+	cacheInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
+
+	if (vkCreatePipelineCache(device, &cacheInfo, nullptr, &pipelineCache) != VK_SUCCESS)
+		throw love::Exception("could not create pipeline cache");
+}
+
 void Graphics::initVMA()
 {
 	VmaAllocatorCreateInfo allocatorCreateInfo = {};
@@ -1657,9 +1717,9 @@ void Graphics::initVMA()
 	allocatorCreateInfo.pVulkanFunctions = &vulkanFunctions;
 
 	allocatorCreateInfo.flags |= VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT;
-	if (optionalDeviceFeatures.dedicatedAllocation)
+	if (optionalDeviceExtensions.dedicatedAllocation)
 		allocatorCreateInfo.flags |= VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT;
-	if (optionalDeviceFeatures.memoryBudget)
+	if (optionalDeviceExtensions.memoryBudget)
 		allocatorCreateInfo.flags |= VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT;
 
 	if (vmaCreateAllocator(&allocatorCreateInfo, &vmaAllocator) != VK_SUCCESS)
@@ -1966,40 +2026,6 @@ void Graphics::createScreenshotCallbackBuffers()
 	}
 }
 
-void Graphics::createDefaultRenderPass()
-{
-	RenderPassConfiguration renderPassConfiguration{};
-	renderPassConfiguration.colorAttachments.push_back({ swapChainImageFormat, false, msaaSamples });
-	renderPassConfiguration.staticData.depthAttachment = { findDepthFormat(), false, msaaSamples };
-	if (msaaSamples & VK_SAMPLE_COUNT_1_BIT)
-		renderPassConfiguration.staticData.resolve = false;
-	else
-		renderPassConfiguration.staticData.resolve = true;
-	defaultRenderPass = createRenderPass(renderPassConfiguration);
-}
-
-void Graphics::createDefaultFramebuffers()
-{
-	defaultFramebuffers.clear();
-
-	for (const auto view : swapChainImageViews)
-	{
-		FramebufferConfiguration configuration{};
-		configuration.staticData.renderPass = defaultRenderPass;
-		configuration.staticData.width = swapChainExtent.width;
-		configuration.staticData.height = swapChainExtent.height;
-		configuration.staticData.depthView = depthImageView;
-		if (msaaSamples & VK_SAMPLE_COUNT_1_BIT)
-			configuration.colorViews.push_back(view);
-		else
-		{
-			configuration.colorViews.push_back(colorImageView);
-			configuration.staticData.resolveView = view;
-		}
-		defaultFramebuffers.push_back(createFramebuffer(configuration));
-	}
-}
-
 VkFramebuffer Graphics::createFramebuffer(FramebufferConfiguration &configuration)
 {
 	std::vector<VkImageView> attachments;
@@ -2081,10 +2107,7 @@ VkRenderPass Graphics::createRenderPass(RenderPassConfiguration &configuration)
 		VkAttachmentDescription colorDescription{};
 		colorDescription.format = colorAttachment.format;
 		colorDescription.samples = colorAttachment.msaaSamples;
-		if (colorAttachment.discard)
-			colorDescription.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
-		else
-			colorDescription.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
+		colorDescription.loadOp = colorAttachment.loadOp;
 		colorDescription.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
 		colorDescription.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
 		colorDescription.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
@@ -2097,24 +2120,18 @@ VkRenderPass Graphics::createRenderPass(RenderPassConfiguration &configuration)
 	subPass.pColorAttachments = colorAttachmentRefs.data();
 
 	VkAttachmentReference depthStencilAttachmentRef{};
-	if (configuration.staticData.depthAttachment.format != VK_FORMAT_UNDEFINED)
+	if (configuration.staticData.depthStencilAttachment.format != VK_FORMAT_UNDEFINED)
 	{
 		depthStencilAttachmentRef.attachment = attachment++;
 		depthStencilAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
 		subPass.pDepthStencilAttachment = &depthStencilAttachmentRef;
 
 		VkAttachmentDescription depthStencilAttachment{};
-		depthStencilAttachment.format = configuration.staticData.depthAttachment.format;
-		depthStencilAttachment.samples = configuration.staticData.depthAttachment.msaaSamples;
-		if (configuration.staticData.depthAttachment.discard)
-			depthStencilAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
-		else
-			depthStencilAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
+		depthStencilAttachment.format = configuration.staticData.depthStencilAttachment.format;
+		depthStencilAttachment.samples = configuration.staticData.depthStencilAttachment.msaaSamples;
+		depthStencilAttachment.loadOp = configuration.staticData.depthStencilAttachment.depthLoadOp;
 		depthStencilAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
-		if (configuration.staticData.depthAttachment.discard)
-			depthStencilAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
-		else
-			depthStencilAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
+		depthStencilAttachment.stencilLoadOp = configuration.staticData.depthStencilAttachment.stencilLoadOp;
 		depthStencilAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE;
 		depthStencilAttachment.initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
 		depthStencilAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
@@ -2174,6 +2191,23 @@ VkRenderPass Graphics::createRenderPass(RenderPassConfiguration &configuration)
 	return renderPass;
 }
 
+VkRenderPass Graphics::getRenderPass(RenderPassConfiguration &configuration)
+{
+	VkRenderPass renderPass;
+	auto it = renderPasses.find(configuration);
+	if (it != renderPasses.end())
+		renderPass = it->second;
+	else
+	{
+		renderPass = createRenderPass(configuration);
+		renderPasses[configuration] = renderPass;
+	}
+
+	renderPassUsages[renderPass] = true;
+
+	return renderPass;
+}
+
 bool Graphics::usesConstantVertexColor(const VertexAttributes &vertexAttributes)
 {
 	return !!(vertexAttributes.enableBits & (1u << ATTRIB_COLOR));
@@ -2259,7 +2293,7 @@ void Graphics::prepareDraw(const VertexAttributes &attributes, const BufferBindi
 	if (!renderPassState.active)
 		startRenderPass();
 
-	Shader::current->attach();
+	usedShadersInFrame.insert((dynamic_cast<Shader*>(Shader::current)));
 
 	GraphicsPipelineConfiguration configuration{};
 
@@ -2273,7 +2307,7 @@ void Graphics::prepareDraw(const VertexAttributes &attributes, const BufferBindi
 	configuration.numColorAttachments = renderPassState.numColorAttachments;
 	configuration.primitiveType = primitiveType;
 
-	if (optionalDeviceFeatures.extendedDynamicState)
+	if (optionalDeviceExtensions.extendedDynamicState)
 		vkCmdSetCullModeEXT(commandBuffers.at(currentFrame), Vulkan::getCullMode(cullmode));
 	else
 	{
@@ -2316,20 +2350,54 @@ void Graphics::prepareDraw(const VertexAttributes &attributes, const BufferBindi
 
 void Graphics::setDefaultRenderPass()
 {
+	uint32_t numClearValues = 2;
+	renderPassState.clearColors.resize(numClearValues);
+
 	renderPassState.beginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
-	renderPassState.beginInfo.renderPass = defaultRenderPass;
-	renderPassState.beginInfo.framebuffer = defaultFramebuffers[imageIndex];
+	renderPassState.beginInfo.renderPass = VK_NULL_HANDLE;
+	renderPassState.beginInfo.framebuffer = VK_NULL_HANDLE;
 	renderPassState.beginInfo.renderArea.offset = { 0, 0 };
 	renderPassState.beginInfo.renderArea.extent = swapChainExtent;
-	renderPassState.beginInfo.clearValueCount = 0;
+	renderPassState.beginInfo.clearValueCount = numClearValues;
+	renderPassState.beginInfo.pClearValues = renderPassState.clearColors.data();
 
-	renderPassState.useConfigurations = false;
+	renderPassState.isWindow = true;
 	renderPassState.pipeline = VK_NULL_HANDLE;
 	renderPassState.width = static_cast<float>(swapChainExtent.width);
 	renderPassState.height = static_cast<float>(swapChainExtent.height);
 	renderPassState.msaa = msaaSamples;
 	renderPassState.numColorAttachments = 1;
 	renderPassState.transitionImages.clear();
+
+	RenderPassConfiguration renderPassConfiguration{};
+	renderPassConfiguration.colorAttachments.push_back({ swapChainImageFormat, VK_ATTACHMENT_LOAD_OP_LOAD, msaaSamples });
+	renderPassConfiguration.staticData.depthStencilAttachment = { depthStencilFormat, VK_ATTACHMENT_LOAD_OP_LOAD, VK_ATTACHMENT_LOAD_OP_LOAD, msaaSamples };
+	if (msaaSamples & VK_SAMPLE_COUNT_1_BIT)
+		renderPassConfiguration.staticData.resolve = false;
+	else
+		renderPassConfiguration.staticData.resolve = true;
+
+	FramebufferConfiguration framebufferConfiguration{};
+	framebufferConfiguration.staticData.depthView = depthImageView;
+	framebufferConfiguration.staticData.width = swapChainExtent.width;
+	framebufferConfiguration.staticData.height = swapChainExtent.height;
+
+	if (msaaSamples & VK_SAMPLE_COUNT_1_BIT)
+	{
+		framebufferConfiguration.colorViews.push_back(swapChainImageViews.at(imageIndex));
+		framebufferConfiguration.staticData.resolveView = VK_NULL_HANDLE;
+	}
+	else
+	{
+		framebufferConfiguration.colorViews.push_back(colorImageView);
+		framebufferConfiguration.staticData.resolveView = swapChainImageViews.at(imageIndex);
+	}
+
+	renderPassState.renderPassConfiguration = std::move(renderPassConfiguration);
+	renderPassState.framebufferConfiguration = std::move(framebufferConfiguration);
+
+	if (renderPassState.windowClearRequested)
+		clear(renderPassState.mainWindowClearColorValue, renderPassState.mainWindowClearStencilValue, renderPassState.mainWindowClearDepthValue);
 }
 
 void Graphics::setRenderPass(const RenderTargets &rts, int pixelw, int pixelh, bool hasSRGBtexture)
@@ -2341,14 +2409,14 @@ void Graphics::setRenderPass(const RenderTargets &rts, int pixelw, int pixelh, b
 	for (const auto &color : rts.colors)
 		renderPassConfiguration.colorAttachments.push_back({ 
 			Vulkan::getTextureFormat(color.texture->getPixelFormat(), isPixelFormatSRGB(color.texture->getPixelFormat())).internalFormat,
-			false, 
+			VK_ATTACHMENT_LOAD_OP_LOAD,
 			dynamic_cast<Texture*>(color.texture)->getMsaaSamples() });
 	if (rts.depthStencil.texture != nullptr)
-		if (rts.depthStencil.texture != nullptr)
-			renderPassConfiguration.staticData.depthAttachment = { 
-				Vulkan::getTextureFormat(rts.depthStencil.texture->getPixelFormat(), false).internalFormat,
-				false,
-				dynamic_cast<Texture*>(rts.depthStencil.texture)->getMsaaSamples() };
+		renderPassConfiguration.staticData.depthStencilAttachment = {
+			Vulkan::getTextureFormat(rts.depthStencil.texture->getPixelFormat(), false).internalFormat,
+			VK_ATTACHMENT_LOAD_OP_LOAD,
+			VK_ATTACHMENT_LOAD_OP_LOAD,
+			dynamic_cast<Texture*>(rts.depthStencil.texture)->getMsaaSamples() };
 
 	FramebufferConfiguration configuration{};
 
@@ -2365,15 +2433,19 @@ void Graphics::setRenderPass(const RenderTargets &rts, int pixelw, int pixelh, b
 	configuration.staticData.width = static_cast<uint32_t>(pixelw);
 	configuration.staticData.height = static_cast<uint32_t>(pixelh);
 
+	uint32_t numClearValues = static_cast<uint32_t>(rts.colors.size() + 1);
+	renderPassState.clearColors.resize(numClearValues);
+
 	renderPassState.beginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
 	renderPassState.beginInfo.renderPass = VK_NULL_HANDLE;
 	renderPassState.beginInfo.framebuffer = VK_NULL_HANDLE;
 	renderPassState.beginInfo.renderArea.offset = {0, 0};
 	renderPassState.beginInfo.renderArea.extent.width = static_cast<uint32_t>(pixelw);
 	renderPassState.beginInfo.renderArea.extent.height = static_cast<uint32_t>(pixelh);
-	renderPassState.beginInfo.clearValueCount = 0;
+	renderPassState.beginInfo.clearValueCount = numClearValues;
+	renderPassState.beginInfo.pClearValues = renderPassState.clearColors.data();
 
-	renderPassState.useConfigurations = true;
+	renderPassState.isWindow = false;
 	renderPassState.renderPassConfiguration = renderPassConfiguration;
 	renderPassState.framebufferConfiguration = configuration;
 	renderPassState.pipeline = VK_NULL_HANDLE;
@@ -2388,6 +2460,9 @@ void Graphics::startRenderPass()
 {
 	renderPassState.active = true;
 
+	if (renderPassState.isWindow && renderPassState.windowClearRequested)
+		renderPassState.windowClearRequested = false;
+
 	VkViewport viewport{};
 	viewport.x = 0.0f;
 	viewport.y = 0.0f;
@@ -2398,26 +2473,10 @@ void Graphics::startRenderPass()
 
 	vkCmdSetViewport(commandBuffers.at(currentFrame), 0, 1, &viewport);
 
-	if (renderPassState.useConfigurations)
-	{
-		auto &renderPassConfiguration = renderPassState.renderPassConfiguration;
-		VkRenderPass renderPass;
-		auto it = renderPasses.find(renderPassConfiguration);
-		if (it != renderPasses.end())
-			renderPass = it->second;
-		else
-		{
-			renderPass = createRenderPass(renderPassConfiguration);
-			renderPasses[renderPassConfiguration] = renderPass;
-		}
-		renderPassState.beginInfo.renderPass = renderPass;
-
-		renderPassUsages[renderPass] = true;
+	renderPassState.beginInfo.renderPass = getRenderPass(renderPassState.renderPassConfiguration);
 
-		auto &framebufferConfiguration = renderPassState.framebufferConfiguration;
-		framebufferConfiguration.staticData.renderPass = renderPass;
-		renderPassState.beginInfo.framebuffer = getFramebuffer(framebufferConfiguration);
-	}
+	renderPassState.framebufferConfiguration.staticData.renderPass = renderPassState.beginInfo.renderPass;
+	renderPassState.beginInfo.framebuffer = getFramebuffer(renderPassState.framebufferConfiguration);
 
 	for (const auto &image : renderPassState.transitionImages)
 		Vulkan::cmdTransitionImageLayout(commandBuffers.at(currentFrame), image, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
@@ -2433,6 +2492,12 @@ void Graphics::endRenderPass()
 
 	for (const auto &image : renderPassState.transitionImages)
 		Vulkan::cmdTransitionImageLayout(commandBuffers.at(currentFrame), image, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+
+	for (auto &colorAttachment : renderPassState.renderPassConfiguration.colorAttachments)
+		colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
+
+	renderPassState.renderPassConfiguration.staticData.depthStencilAttachment.depthLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
+	renderPassState.renderPassConfiguration.staticData.depthStencilAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
 }
 
 VkSampler Graphics::createSampler(const SamplerState &samplerState)
@@ -2523,11 +2588,6 @@ void Graphics::setComputeShader(Shader *shader)
 	computeShader = shader;
 }
 
-std::set<Shader*> &Graphics::getUsedShadersInFrame()
-{
-	return usedShadersInFrame;
-}
-
 VkSampler Graphics::getCachedSampler(const SamplerState &samplerState)
 {
 	auto samplerkey = samplerState.toKey();
@@ -2577,7 +2637,7 @@ VkPipeline Graphics::createGraphicsPipeline(GraphicsPipelineConfiguration &confi
 	rasterizer.rasterizerDiscardEnable = VK_FALSE;
 	rasterizer.polygonMode = Vulkan::getPolygonMode(configuration.wireFrame);
 	rasterizer.lineWidth = 1.0f;
-	if (!optionalDeviceFeatures.extendedDynamicState)
+	if (!optionalDeviceExtensions.extendedDynamicState)
 	{
 		rasterizer.cullMode = Vulkan::getCullMode(configuration.dynamicState.cullmode);
 		rasterizer.frontFace = Vulkan::getFrontFace(configuration.dynamicState.winding);
@@ -2596,7 +2656,7 @@ VkPipeline Graphics::createGraphicsPipeline(GraphicsPipelineConfiguration &confi
 	VkPipelineDepthStencilStateCreateInfo depthStencil{};
 	depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
 	depthStencil.depthTestEnable = VK_TRUE;
-	if (!optionalDeviceFeatures.extendedDynamicState)
+	if (!optionalDeviceExtensions.extendedDynamicState)
 	{
 		depthStencil.depthWriteEnable = Vulkan::getBool(configuration.dynamicState.depthState.write);
 		depthStencil.depthCompareOp = Vulkan::getCompareOp(configuration.dynamicState.depthState.compare);
@@ -2607,7 +2667,7 @@ VkPipeline Graphics::createGraphicsPipeline(GraphicsPipelineConfiguration &confi
 
 	depthStencil.stencilTestEnable = VK_TRUE;
 
-	if (!optionalDeviceFeatures.extendedDynamicState)
+	if (!optionalDeviceExtensions.extendedDynamicState)
 	{
 		depthStencil.front.failOp = VK_STENCIL_OP_KEEP;
 		depthStencil.front.passOp = Vulkan::getStencilOp(configuration.dynamicState.stencilAction);
@@ -2647,7 +2707,7 @@ VkPipeline Graphics::createGraphicsPipeline(GraphicsPipelineConfiguration &confi
 
 	std::vector<VkDynamicState> dynamicStates;
 
-	if (optionalDeviceFeatures.extendedDynamicState)
+	if (optionalDeviceExtensions.extendedDynamicState)
 		dynamicStates = {
 			VK_DYNAMIC_STATE_SCISSOR,
 			VK_DYNAMIC_STATE_VIEWPORT,
@@ -2691,7 +2751,7 @@ VkPipeline Graphics::createGraphicsPipeline(GraphicsPipelineConfiguration &confi
 	pipelineInfo.renderPass = configuration.renderPass;
 
 	VkPipeline graphicsPipeline;
-	if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS)
+	if (vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS)
 		throw love::Exception("failed to create graphics pipeline");
 	return graphicsPipeline;
 }
@@ -2839,12 +2899,10 @@ VkFormat Graphics::findDepthFormat()
 
 void Graphics::createDepthResources()
 {
-	VkFormat depthAttachment = findDepthFormat();
-
 	VkImageCreateInfo imageInfo{};
 	imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
 	imageInfo.imageType = VK_IMAGE_TYPE_2D;
-	imageInfo.format = depthAttachment;
+	imageInfo.format = depthStencilFormat;
 	imageInfo.extent.width = swapChainExtent.width;
 	imageInfo.extent.height = swapChainExtent.height;
 	imageInfo.extent.depth = 1;
@@ -2867,7 +2925,7 @@ void Graphics::createDepthResources()
 	imageViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
 	imageViewInfo.image = depthImage;
 	imageViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
-	imageViewInfo.format = depthAttachment;
+	imageViewInfo.format = depthStencilFormat;
 	imageViewInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
 	imageViewInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
 	imageViewInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
@@ -2977,6 +3035,7 @@ void Graphics::cleanup()
 	graphicsPipelines.clear();
 
 	vkDestroyCommandPool(device, commandPool, nullptr);
+	vkDestroyPipelineCache(device, pipelineCache, nullptr);
 	vkDestroyDevice(device, nullptr);
 	vkDestroySurfaceKHR(instance, surface, nullptr);
 	vkDestroyInstance(instance, nullptr);
@@ -2989,9 +3048,6 @@ void Graphics::cleanupSwapChain()
 		vmaDestroyBuffer(vmaAllocator, readbackBuffer.buffer, readbackBuffer.allocation);
 		vmaDestroyImage(vmaAllocator, readbackBuffer.image, readbackBuffer.imageAllocation);
 	}
-	for (const auto &framebuffer : defaultFramebuffers)
-		vkDestroyFramebuffer(device, framebuffer, nullptr);
-	vkDestroyRenderPass(device, defaultRenderPass, nullptr);
 	vkDestroyImageView(device, colorImageView, nullptr);
 	vmaDestroyImage(vmaAllocator, colorImage, colorImageAllocation);
 	vkDestroyImageView(device, depthImageView, nullptr);
@@ -3015,8 +3071,6 @@ void Graphics::recreateSwapChain()
 	createScreenshotCallbackBuffers();
 	createColorResources();
 	createDepthResources();
-	createDefaultRenderPass();
-	createDefaultFramebuffers();
 
 	transitionColorDepthLayouts = true;
 }

+ 37 - 19
src/modules/graphics/vulkan/Graphics.h

@@ -46,27 +46,43 @@ namespace graphics
 namespace vulkan
 {
 
-struct RenderPassAttachment
+struct ColorAttachment
 {
 	VkFormat format = VK_FORMAT_UNDEFINED;
-	bool discard = true;
+	VkAttachmentLoadOp loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
 	VkSampleCountFlagBits msaaSamples = VK_SAMPLE_COUNT_1_BIT;
 
-	bool operator==(const RenderPassAttachment &attachment) const
+	bool operator==(const ColorAttachment &attachment) const
 	{
 		return format == attachment.format && 
-			discard == attachment.discard && 
+			loadOp == attachment.loadOp &&
+			msaaSamples == attachment.msaaSamples;
+	}
+};
+
+struct DepthStencilAttachment
+{
+	VkFormat format = VK_FORMAT_UNDEFINED;
+	VkAttachmentLoadOp depthLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
+	VkAttachmentLoadOp stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
+	VkSampleCountFlagBits msaaSamples = VK_SAMPLE_COUNT_1_BIT;
+
+	bool operator==(const DepthStencilAttachment &attachment) const
+	{
+		return format == attachment.format &&
+			depthLoadOp == attachment.depthLoadOp &&
+			stencilLoadOp == attachment.stencilLoadOp &&
 			msaaSamples == attachment.msaaSamples;
 	}
 };
 
 struct RenderPassConfiguration
 {
-	std::vector<RenderPassAttachment> colorAttachments;
+	std::vector<ColorAttachment> colorAttachments;
 
 	struct StaticRenderPassConfiguration
 	{
-		RenderPassAttachment depthAttachment;
+		DepthStencilAttachment depthStencilAttachment;
 		bool resolve = false;
 	} staticData;
 
@@ -82,7 +98,7 @@ struct RenderPassConfigurationHasher
 	size_t operator()(const RenderPassConfiguration &configuration) const
 	{
 		size_t hashes[] = { 
-			XXH32(configuration.colorAttachments.data(), configuration.colorAttachments.size() * sizeof(VkFormat), 0),
+			XXH32(configuration.colorAttachments.data(), configuration.colorAttachments.size() * sizeof(ColorAttachment), 0),
 			XXH32(&configuration.staticData, sizeof(configuration.staticData), 0),
 		};
 		return XXH32(hashes, sizeof(hashes), 0);
@@ -130,7 +146,7 @@ struct OptionalInstanceExtensions
 	bool physicalDeviceProperties2 = false;
 };
 
-struct OptionalDeviceFeatures
+struct OptionalDeviceExtensions
 {
 	// VK_EXT_extended_dynamic_state
 	bool extendedDynamicState = false;
@@ -141,9 +157,6 @@ struct OptionalDeviceFeatures
 	// VK_KHR_dedicated_allocation
 	bool dedicatedAllocation = false;
 
-	// VK_KHR_buffer_device_address
-	bool bufferDeviceAddress = false;
-
 	// VK_EXT_memory_budget
 	bool memoryBudget = false;
 
@@ -216,7 +229,7 @@ struct RenderpassState
 {
 	bool active = false;
 	VkRenderPassBeginInfo beginInfo{};
-	bool useConfigurations = false;
+	bool isWindow = false;
 	RenderPassConfiguration renderPassConfiguration{};
 	FramebufferConfiguration framebufferConfiguration{};
 	VkPipeline pipeline = VK_NULL_HANDLE;
@@ -225,6 +238,12 @@ struct RenderpassState
 	float width = 0.0f;
 	float height = 0.0f;
 	VkSampleCountFlagBits msaa = VK_SAMPLE_COUNT_1_BIT;
+	std::vector<VkClearValue> clearColors;
+
+	bool windowClearRequested = false;
+	OptionalColorD mainWindowClearColorValue;
+	OptionalDouble mainWindowClearDepthValue;
+	OptionalInt mainWindowClearStencilValue;
 };
 
 struct ScreenshotReadbackBuffer
@@ -293,9 +312,8 @@ public:
 	graphics::Texture *getDefaultTexture() const;
 	VkSampler getCachedSampler(const SamplerState &sampler);
 	void setComputeShader(Shader *computeShader);
-	std::set<Shader*> &getUsedShadersInFrame();
 	graphics::Shader::BuiltinUniformData getCurrentBuiltinUniformData();
-	const OptionalDeviceFeatures &getEnabledOptionalDeviceExtensions() const;
+	const OptionalDeviceExtensions &getEnabledOptionalDeviceExtensions() const;
 	VkSampleCountFlagBits getMsaaCount(int requestedMsaa) const;
 	void setVsync(int vsync);
 	int getVsync() const;
@@ -317,6 +335,7 @@ private:
 	int rateDeviceSuitability(VkPhysicalDevice device);
 	QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device);
 	void createLogicalDevice();
+	void createPipelineCache();
 	void initVMA();
 	void createSurface();
 	bool checkDeviceExtensionSupport(VkPhysicalDevice device);
@@ -328,12 +347,11 @@ private:
 	void createSwapChain();
 	void createImageViews();
 	void createScreenshotCallbackBuffers();
-	void createDefaultRenderPass();
-	void createDefaultFramebuffers();
 	VkFramebuffer createFramebuffer(FramebufferConfiguration &configuration);
 	VkFramebuffer getFramebuffer(FramebufferConfiguration &configuration);
 	void createDefaultShaders();
 	VkRenderPass createRenderPass(RenderPassConfiguration &configuration);
+	VkRenderPass getRenderPass(RenderPassConfiguration &configuration);
 	VkPipeline createGraphicsPipeline(GraphicsPipelineConfiguration &configuration);
 	void createColorResources();
 	VkFormat findSupportedFormat(const std::vector<VkFormat> &candidates, VkImageTiling tiling, VkFormatFeatureFlags features);
@@ -375,7 +393,7 @@ private:
 	int requestedMsaa = 0;
 	VkDevice device = VK_NULL_HANDLE; 
 	OptionalInstanceExtensions optionalInstanceExtensions;
-	OptionalDeviceFeatures optionalDeviceFeatures;
+	OptionalDeviceExtensions optionalDeviceExtensions;
 	VkQueue graphicsQueue = VK_NULL_HANDLE;
 	VkQueue presentQueue = VK_NULL_HANDLE;
 	VkSurfaceKHR surface = VK_NULL_HANDLE;
@@ -384,6 +402,7 @@ private:
 	Matrix4 displayRotation;
 	std::vector<VkImage> swapChainImages;
 	VkFormat swapChainImageFormat = VK_FORMAT_UNDEFINED;
+	VkFormat depthStencilFormat = VK_FORMAT_UNDEFINED;
 	VkExtent2D swapChainExtent = VkExtent2D();
 	std::vector<VkImageView> swapChainImageViews;
 	VkSampleCountFlagBits msaaSamples = VK_SAMPLE_COUNT_1_BIT;
@@ -393,8 +412,7 @@ private:
 	VkImage depthImage = VK_NULL_HANDLE;
 	VkImageView depthImageView = VK_NULL_HANDLE;
 	VmaAllocation depthImageAllocation = VK_NULL_HANDLE;
-	VkRenderPass defaultRenderPass = VK_NULL_HANDLE;
-	std::vector<VkFramebuffer> defaultFramebuffers;
+	VkPipelineCache pipelineCache = VK_NULL_HANDLE;
 	std::unordered_map<RenderPassConfiguration, VkRenderPass, RenderPassConfigurationHasher> renderPasses;
 	std::unordered_map<FramebufferConfiguration, VkFramebuffer, FramebufferConfigurationHasher> framebuffers;
 	std::unordered_map<GraphicsPipelineConfiguration, VkPipeline, GraphicsPipelineConfigurationHasher> graphicsPipelines;

+ 110 - 57
src/modules/graphics/vulkan/Shader.cpp

@@ -25,8 +25,6 @@
 #include "libraries/glslang/SPIRV/GlslangToSpv.h"
 
 
-#include <vector>
-
 namespace love
 {
 namespace graphics
@@ -144,6 +142,72 @@ static const TBuiltInResource defaultTBuiltInResource = {
 static const uint32_t STREAMBUFFER_DEFAULT_SIZE = 16;
 static const uint32_t DESCRIPTOR_POOL_SIZE = 1;
 
+class BindingMapper
+{
+public:
+	uint32_t operator()(spirv_cross::CompilerGLSL &comp, std::vector<uint32_t> &spirv, const std::string &name, const spirv_cross::ID &id)
+	{
+		auto it = bindingMappings.find(name);
+		if (it == bindingMappings.end())
+		{
+			auto binding = comp.get_decoration(id, spv::DecorationBinding);
+
+			if (isFreeBinding(binding))
+			{
+				bindingMappings[name] = binding;
+				return binding;
+			}
+			else
+			{
+				uint32_t freeBinding = getFreeBinding();
+
+				uint32_t binaryBindingOffset;
+				if (!comp.get_binary_offset_for_decoration(id, spv::DecorationBinding, binaryBindingOffset))
+					throw love::Exception("could not get binary offset for binding");
+
+				spirv[binaryBindingOffset] = freeBinding;
+
+				bindingMappings[name] = freeBinding;
+
+				return freeBinding;
+			}
+		}
+		else
+			return it->second;
+	};
+
+
+private:
+	uint32_t getFreeBinding() {
+		for (uint32_t i = 0;; i++)
+		{
+			bool free = true;
+			for (const auto &entry : bindingMappings)
+			{
+				if (entry.second == i)
+				{
+					free = false;
+					break;
+				}
+			}
+			if (free)
+				return i;
+		}
+	}
+
+	bool isFreeBinding(uint32_t binding) {
+		for (const auto &entry : bindingMappings)
+		{
+			if (entry.second == binding)
+				return false;
+		}
+		return true;
+	}
+
+	std::map<std::string, uint32_t> bindingMappings;
+
+};
+
 static VkShaderStageFlagBits getStageBit(ShaderStageType type)
 {
 	switch (type)
@@ -185,6 +249,8 @@ Shader::Shader(StrongRef<love::graphics::ShaderStage> stages[])
 
 bool Shader::loadVolatile()
 {
+	device = vgfx->getDevice();
+
 	computePipeline = VK_NULL_HANDLE;
 
 	for (int i = 0; i < BUILTIN_MAX_ENUM; i++)
@@ -200,6 +266,7 @@ bool Shader::loadVolatile()
 	currentFrame = 0;
 	currentUsedUniformStreamBuffersCount = 0;
 	currentUsedDescriptorSetsCount = 0;
+	newFrame();
 
 	return true;
 }
@@ -384,7 +451,7 @@ void Shader::cmdPushDescriptorSets(VkCommandBuffer commandBuffer, VkPipelineBind
 	for (const auto &u : uniformInfos)
 	{
 		if (updatedUniforms.find(u.second.location) == updatedUniforms.end())
-			updateUniform(&u.second, u.second.count);
+			updateUniform(&u.second, u.second.count, true);
 	}
 
 	vkCmdBindDescriptorSets(commandBuffer, bindPoint, pipelineLayout, 0, 1, &currentDescriptorSet, 0, nullptr);
@@ -406,13 +473,6 @@ Shader::~Shader()
 
 void Shader::attach()
 {
-	auto &usedShadersInFrame = vgfx->getUsedShadersInFrame();
-	if (usedShadersInFrame.find(this) == usedShadersInFrame.end())
-	{
-		newFrame();
-		usedShadersInFrame.insert(this);
-	}
-
 	if (!isCompute)
 	{
 		if (Shader::current != this)
@@ -676,11 +736,9 @@ void Shader::compileShaders()
 	using namespace glslang;
 	using namespace spirv_cross;
 
-	std::vector<TShader*> glslangShaders;
+	std::vector<std::unique_ptr<TShader>> glslangShaders;
 
-	auto program = new TProgram();
-
-	device = vgfx->getDevice();
+	auto program = std::make_unique<TProgram>();
 
 	const auto &enabledExtensions = vgfx->getEnabledOptionalDeviceExtensions();
 
@@ -695,7 +753,7 @@ void Shader::compileShaders()
 			isCompute = true;
 
 		auto glslangShaderStage = getGlslShaderType(stage);
-		auto tshader = new TShader(glslangShaderStage);
+		auto tshader = std::make_unique<TShader>(glslangShaderStage);
 
 		tshader->setEnvInput(EShSourceGlsl, glslangShaderStage, EShClientVulkan, 450);
 		tshader->setEnvClient(EShClientVulkan, EShTargetVulkan_1_2);
@@ -721,14 +779,18 @@ void Shader::compileShaders()
 
 		if (!tshader->parse(&defaultTBuiltInResource, defaultVersion, defaultProfile, forceDefault, forwardCompat, EShMsgSuppressWarnings))
 		{
-			const char *msg1 = tshader->getInfoLog();
-			const char *msg2 = tshader->getInfoDebugLog();
+			const char *stageName = "unknown";
+			ShaderStage::getConstant(stage, stageName);
+
+			std::string err = "Error parsing " + std::string(stageName) + " shader:\n\n"
+				+ std::string(tshader->getInfoLog()) + "\n"
+				+ std::string(tshader->getInfoDebugLog());
 
-			throw love::Exception("error while parsing shader");
+			throw love::Exception("%s", err.c_str());
 		}
 
-		program->addShader(tshader);
-		glslangShaders.push_back(tshader);
+		program->addShader(tshader.get());
+		glslangShaders.push_back(std::move(tshader));
 	}
 
 	if (!program->link(EShMsgDefault))
@@ -739,14 +801,16 @@ void Shader::compileShaders()
 
 	uniformInfos.clear();
 
+	BindingMapper bindingMapper;
+
 	for (int i = 0; i < SHADERSTAGE_MAX_ENUM; i++)
 	{
 		auto shaderStage = (ShaderStageType)i;
 		auto glslangStage = getGlslShaderType(shaderStage);
 		auto intermediate = program->getIntermediate(glslangStage);
-		if (intermediate == nullptr) {
+
+		if (intermediate == nullptr)
 			continue;
-		}
 
 		spv::SpvBuildLogger logger;
 		glslang::SpvOptions opt;
@@ -755,31 +819,6 @@ void Shader::compileShaders()
 		std::vector<uint32_t> spirv;
 		GlslangToSpv(*intermediate, spirv, &logger, &opt);
 
-		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.
@@ -796,7 +835,7 @@ void Shader::compileShaders()
 				auto defaultUniformBlockSize = comp.get_declared_struct_size(type);
 				localUniformStagingData.resize(defaultUniformBlockSize);
 				localUniformData.resize(defaultUniformBlockSize);
-				localUniformLocation = comp.get_decoration(resource.id, spv::DecorationBinding);
+				localUniformLocation = bindingMapper(comp, spirv, resource.name, resource.id);
 
 				memset(localUniformStagingData.data(), 0, defaultUniformBlockSize);
 				memset(localUniformData.data(), 0, defaultUniformBlockSize);
@@ -817,7 +856,7 @@ void Shader::compileShaders()
 			const SPIRType &imagetype = comp.get_type(basetype.image.type);
 
 			graphics::Shader::UniformInfo info;
-			info.location = comp.get_decoration(r.id, spv::DecorationBinding);
+			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];
@@ -898,7 +937,7 @@ void Shader::compileShaders()
 			if (!fillUniformReflectionData(u))
 				continue;
 
-			u.location = comp.get_decoration(r.id, spv::DecorationBinding);
+			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++)
@@ -921,13 +960,11 @@ void Shader::compileShaders()
 				continue;
 
 			u.textures = new love::graphics::Texture *[u.count];
-			u.location = comp.get_decoration(r.id, spv::DecorationBinding);
+			u.location = bindingMapper(comp, spirv, r.name, r.id);
 
 			for (int i = 0; i < u.count; i++)
 				u.textures[i] = nullptr;
 
-			// some stuff missing ?
-
 			uniformInfos[u.name] = u;
 		}
 
@@ -940,11 +977,27 @@ void Shader::compileShaders()
 				attributes[name] = attributeLocation;
 			}
 		}
-	}
 
-	delete program;
-	for (auto shader : glslangShaders)
-		delete shader;
+		VkShaderModuleCreateInfo createInfo{};
+		createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+		createInfo.codeSize = spirv.size() * sizeof(uint32_t);
+		createInfo.pCode = spirv.data();
+
+		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);
+	}
 }
 
 void Shader::createDescriptorSetLayout()