Browse Source

vulkan: remove redundant tabs

this makes reading the source files easier
and is more in line with the rest of the löve codebase
niki 3 years ago
parent
commit
7bbc042fce

+ 87 - 86
src/modules/graphics/vulkan/Buffer.cpp

@@ -2,91 +2,92 @@
 #include "Graphics.h"
 
 namespace love {
-	namespace graphics {
-		namespace vulkan {
-			static VkBufferUsageFlags getUsageBit(BufferUsage mode) {
-				switch (mode) {
-				case BUFFERUSAGE_VERTEX: return VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
-				case BUFFERUSAGE_INDEX: return VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
-				case BUFFERUSAGE_UNIFORM: return VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
-				default:
-					throw love::Exception("unsupported BufferUsage mode");
-				}
-			}
-
-			static VkBufferUsageFlags getVulkanUsageFlags(BufferUsageFlags flags) {
-				VkBufferUsageFlags vkFlags = 0;
-				for (int i = 0; i < BUFFERUSAGE_MAX_ENUM; i++) {
-					BufferUsageFlags flag = static_cast<BufferUsageFlags>(1u << i);
-					if (flags & flag) {
-						vkFlags |= getUsageBit((BufferUsage)i);
-					}
-				}
-				return vkFlags;
-			}
-
-			Buffer::Buffer(love::graphics::Graphics* gfx, const Settings& settings, const std::vector<DataDeclaration>& format, const void* data, size_t size, size_t arraylength)
-				: love::graphics::Buffer(gfx, settings, format, size, arraylength), usageFlags(settings.usageFlags), gfx(gfx) {
-				loadVolatile();
-			}
-
-			bool Buffer::loadVolatile() {
-				Graphics* vgfx = (Graphics*)gfx;
-				allocator = vgfx->getVmaAllocator();
-
-				VkBufferCreateInfo bufferInfo{};
-				bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
-				bufferInfo.size = getSize();
-				bufferInfo.usage = getVulkanUsageFlags(usageFlags);
-
-				VmaAllocationCreateInfo allocCreateInfo = {};
-				allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
-				allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT;
-
-				vmaCreateBuffer(allocator, &bufferInfo, &allocCreateInfo, &buffer, &allocation, &allocInfo);
-
-				return true;
-			}
-
-			void Buffer::unloadVolatile() {
-				if (buffer == VK_NULL_HANDLE)
-					return;
-
-				Graphics* vgfx = (Graphics*)gfx;
-				auto device = vgfx->getDevice();
-
-				vgfx->queueCleanUp(
-					[device=device, allocator=allocator, buffer=buffer, allocation=allocation](){
-					vkDeviceWaitIdle(device);
-					vmaDestroyBuffer(allocator, buffer, allocation);
-				});
-
-				buffer = VK_NULL_HANDLE;
-			}
-
-			Buffer::~Buffer() {
-				unloadVolatile();
-			}
-
-			void* Buffer::map(MapType map, size_t offset, size_t size) {
-				char* data = (char*)allocInfo.pMappedData;
-				return (void*) (data + offset);
-			}
-
-			bool Buffer::fill(size_t offset, size_t size, const void *data) {
-				void* dst = (void*)((char*)allocInfo.pMappedData + offset);
-				memcpy(dst, data, size);
-				return true;
-			}
-
-			void Buffer::unmap(size_t usedoffset, size_t usedsize) {
-				(void)usedoffset;
-				(void)usedsize;
-			}
-
-			void Buffer::copyTo(love::graphics::Buffer* dest, size_t sourceoffset, size_t destoffset, size_t size) {
-				throw love::Exception("not implemented yet");
-			}
+namespace graphics {
+namespace vulkan {
+
+static VkBufferUsageFlags getUsageBit(BufferUsage mode) {
+	switch (mode) {
+	case BUFFERUSAGE_VERTEX: return VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
+	case BUFFERUSAGE_INDEX: return VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
+	case BUFFERUSAGE_UNIFORM: return VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
+	default:
+		throw love::Exception("unsupported BufferUsage mode");
+	}
+}
+
+static VkBufferUsageFlags getVulkanUsageFlags(BufferUsageFlags flags) {
+	VkBufferUsageFlags vkFlags = 0;
+	for (int i = 0; i < BUFFERUSAGE_MAX_ENUM; i++) {
+		BufferUsageFlags flag = static_cast<BufferUsageFlags>(1u << i);
+		if (flags & flag) {
+			vkFlags |= getUsageBit((BufferUsage)i);
 		}
 	}
-}
+	return vkFlags;
+}
+
+Buffer::Buffer(love::graphics::Graphics* gfx, const Settings& settings, const std::vector<DataDeclaration>& format, const void* data, size_t size, size_t arraylength)
+	: love::graphics::Buffer(gfx, settings, format, size, arraylength), usageFlags(settings.usageFlags), gfx(gfx) {
+	loadVolatile();
+}
+
+bool Buffer::loadVolatile() {
+	Graphics* vgfx = (Graphics*)gfx;
+	allocator = vgfx->getVmaAllocator();
+
+	VkBufferCreateInfo bufferInfo{};
+	bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+	bufferInfo.size = getSize();
+	bufferInfo.usage = getVulkanUsageFlags(usageFlags);
+
+	VmaAllocationCreateInfo allocCreateInfo = {};
+	allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
+	allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT;
+
+	vmaCreateBuffer(allocator, &bufferInfo, &allocCreateInfo, &buffer, &allocation, &allocInfo);
+
+	return true;
+}
+
+void Buffer::unloadVolatile() {
+	if (buffer == VK_NULL_HANDLE)
+		return;
+
+	Graphics* vgfx = (Graphics*)gfx;
+	auto device = vgfx->getDevice();
+
+	vgfx->queueCleanUp(
+		[device=device, allocator=allocator, buffer=buffer, allocation=allocation](){
+		vkDeviceWaitIdle(device);
+		vmaDestroyBuffer(allocator, buffer, allocation);
+	});
+
+	buffer = VK_NULL_HANDLE;
+}
+
+Buffer::~Buffer() {
+	unloadVolatile();
+}
+
+void* Buffer::map(MapType map, size_t offset, size_t size) {
+	char* data = (char*)allocInfo.pMappedData;
+	return (void*) (data + offset);
+}
+
+bool Buffer::fill(size_t offset, size_t size, const void *data) {
+	void* dst = (void*)((char*)allocInfo.pMappedData + offset);
+	memcpy(dst, data, size);
+	return true;
+}
+
+void Buffer::unmap(size_t usedoffset, size_t usedsize) {
+	(void)usedoffset;
+	(void)usedsize;
+}
+
+void Buffer::copyTo(love::graphics::Buffer* dest, size_t sourceoffset, size_t destoffset, size_t size) {
+	throw love::Exception("not implemented yet");
+}
+} // vulkan
+} // graphics
+} // love

+ 36 - 31
src/modules/graphics/vulkan/Buffer.h

@@ -1,3 +1,6 @@
+#ifndef LOVE_GRAPHICS_VULKAN_BUFFER_H
+#define LOVE_GRAPHICS_VULKAN_BUFFER_H
+
 #include "graphics/Buffer.h"
 #include <vulkan/vulkan.h>
 #include "vk_mem_alloc.h"
@@ -5,37 +8,39 @@
 
 
 namespace love {
-	namespace graphics {
-		namespace vulkan {
-			class Buffer : public love::graphics::Buffer, public Volatile {
-			public:
-				Buffer(love::graphics::Graphics* gfx, const Settings& settings, const std::vector<DataDeclaration>& format, const void* data, size_t size, size_t arraylength);
-				virtual ~Buffer();
-
-				virtual bool loadVolatile() override;
-				virtual void unloadVolatile() override;
+namespace graphics {
+namespace vulkan {
+class Buffer : public love::graphics::Buffer, public Volatile {
+public:
+	Buffer(love::graphics::Graphics* gfx, const Settings& settings, const std::vector<DataDeclaration>& format, const void* data, size_t size, size_t arraylength);
+	virtual ~Buffer();
 
-				void* map(MapType map, size_t offset, size_t size) override;
-				void unmap(size_t usedoffset, size_t usedsize) override;
-				bool fill(size_t offset, size_t size, const void* data) override;
-				void copyTo(love::graphics::Buffer* dest, size_t sourceoffset, size_t destoffset, size_t size) override;
-				ptrdiff_t getHandle() const override {
-					return (ptrdiff_t) buffer;	// todo ?
-				}
-				ptrdiff_t getTexelBufferHandle() const override {
-					throw love::Exception("unimplemented Buffer::getTexelBufferHandle");
-					return (ptrdiff_t) nullptr;	// todo ?
-				}
+	virtual bool loadVolatile() override;
+	virtual void unloadVolatile() override;
 
-			private:
-				// todo use a staging buffer for improved performance
-				VkBuffer buffer = VK_NULL_HANDLE;
-				love::graphics::Graphics* gfx;
-				VmaAllocator allocator;
-				VmaAllocation allocation;
-				VmaAllocationInfo allocInfo;
-				BufferUsageFlags usageFlags;
-			};
-		}
+	void* map(MapType map, size_t offset, size_t size) override;
+	void unmap(size_t usedoffset, size_t usedsize) override;
+	bool fill(size_t offset, size_t size, const void* data) override;
+	void copyTo(love::graphics::Buffer* dest, size_t sourceoffset, size_t destoffset, size_t size) override;
+	ptrdiff_t getHandle() const override {
+		return (ptrdiff_t) buffer;	// todo ?
+	}
+	ptrdiff_t getTexelBufferHandle() const override {
+		throw love::Exception("unimplemented Buffer::getTexelBufferHandle");
+		return (ptrdiff_t) nullptr;	// todo ?
 	}
-}
+
+private:
+	// todo use a staging buffer for improved performance
+	VkBuffer buffer = VK_NULL_HANDLE;
+	love::graphics::Graphics* gfx;
+	VmaAllocator allocator;
+	VmaAllocation allocation;
+	VmaAllocationInfo allocInfo;
+	BufferUsageFlags usageFlags;
+};
+} // vulkan
+} // graphics
+} // love
+
+#endif

+ 1338 - 1338
src/modules/graphics/vulkan/Graphics.cpp

@@ -19,1609 +19,1609 @@
 
 
 namespace love {
-	namespace graphics {
-		namespace vulkan {
-			static VkIndexType getVulkanIndexBufferType(IndexDataType type) {
-				switch (type) {
-				case INDEX_UINT16: return VK_INDEX_TYPE_UINT16;
-				case INDEX_UINT32: return VK_INDEX_TYPE_UINT32;
-				default:
-					throw love::Exception("unknown Index Data type");
-				}
-			}
+namespace graphics {
+namespace vulkan {
+static VkIndexType getVulkanIndexBufferType(IndexDataType type) {
+	switch (type) {
+	case INDEX_UINT16: return VK_INDEX_TYPE_UINT16;
+	case INDEX_UINT32: return VK_INDEX_TYPE_UINT32;
+	default:
+		throw love::Exception("unknown Index Data type");
+	}
+}
 
-			const std::vector<const char*> validationLayers = {
-				"VK_LAYER_KHRONOS_validation"
-			};
+const std::vector<const char*> validationLayers = {
+	"VK_LAYER_KHRONOS_validation"
+};
 
-			const std::vector<const char*> deviceExtensions = {
-				VK_KHR_SWAPCHAIN_EXTENSION_NAME,
+const std::vector<const char*> deviceExtensions = {
+	VK_KHR_SWAPCHAIN_EXTENSION_NAME,
 
-				// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_push_descriptor.html
-				VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME
-			};
+	// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_push_descriptor.html
+	VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME
+};
 
 #ifdef NDEBUG
-			constexpr bool enableValidationLayers = false;
+constexpr bool enableValidationLayers = false;
 #else
-			constexpr bool enableValidationLayers = true;
+constexpr bool enableValidationLayers = true;
 #endif
 
-			constexpr int MAX_FRAMES_IN_FLIGHT = 2;
+constexpr int MAX_FRAMES_IN_FLIGHT = 2;
 
-			constexpr uint32_t vulkanApiVersion = VK_API_VERSION_1_3;
+constexpr uint32_t vulkanApiVersion = VK_API_VERSION_1_3;
 
-			const char* Graphics::getName() const {
-				return "love.graphics.vulkan";
-			}
+const char* Graphics::getName() const {
+	return "love.graphics.vulkan";
+}
 
-			const VkDevice Graphics::getDevice() const {
-				return device;
-			}
+const VkDevice Graphics::getDevice() const {
+	return device;
+}
 
-			const VkPhysicalDevice Graphics::getPhysicalDevice() const {
-				return physicalDevice;
-			}
+const VkPhysicalDevice Graphics::getPhysicalDevice() const {
+	return physicalDevice;
+}
 
-			const VmaAllocator Graphics::getVmaAllocator() const {
-				return vmaAllocator;
-			}
+const VmaAllocator Graphics::getVmaAllocator() const {
+	return vmaAllocator;
+}
 
-			Graphics::~Graphics() {
-				// We already cleaned those up by clearing out batchedDrawBuffers. 
-				// We set them to nullptr here so the base class doesn't crash
-				// when it tries to free this.
-				batchedDrawState.vb[0] = nullptr;
-				batchedDrawState.vb[1] = nullptr;
-				batchedDrawState.indexBuffer = nullptr;
-			}
+Graphics::~Graphics() {
+	// We already cleaned those up by clearing out batchedDrawBuffers. 
+	// We set them to nullptr here so the base class doesn't crash
+	// when it tries to free this.
+	batchedDrawState.vb[0] = nullptr;
+	batchedDrawState.vb[1] = nullptr;
+	batchedDrawState.indexBuffer = nullptr;
+}
 
-			// START OVERRIDEN FUNCTIONS
+// START OVERRIDEN FUNCTIONS
 
-			love::graphics::Texture* Graphics::newTexture(const love::graphics::Texture::Settings& settings, const love::graphics::Texture::Slices* data) {
-				return new Texture(this, settings, data);
-			}
+love::graphics::Texture* Graphics::newTexture(const love::graphics::Texture::Settings& settings, const love::graphics::Texture::Slices* data) {
+	return new Texture(this, settings, data);
+}
 
-			love::graphics::Buffer* Graphics::newBuffer(const love::graphics::Buffer::Settings& settings, const std::vector<love::graphics::Buffer::DataDeclaration>& format, const void* data, size_t size, size_t arraylength) {
-				return new Buffer(this, settings, format, data, size, arraylength);
-			}
+love::graphics::Buffer* Graphics::newBuffer(const love::graphics::Buffer::Settings& settings, const std::vector<love::graphics::Buffer::DataDeclaration>& format, const void* data, size_t size, size_t arraylength) {
+	return new Buffer(this, settings, format, data, size, arraylength);
+}
 
-			// FIXME: clear stencil and depth missing.
-			void Graphics::clear(OptionalColorD color, OptionalInt stencil, OptionalDouble depth) {
-				VkClearAttachment attachment{};
+// FIXME: clear stencil and depth missing.
+void Graphics::clear(OptionalColorD color, OptionalInt stencil, OptionalDouble depth) {
+	VkClearAttachment attachment{};
 
-				if (color.hasValue) {
-					attachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
-					attachment.clearValue.color.float32[0] = static_cast<float>(color.value.r);
-					attachment.clearValue.color.float32[1] = static_cast<float>(color.value.g);
-					attachment.clearValue.color.float32[2] = static_cast<float>(color.value.b);
-					attachment.clearValue.color.float32[3] = static_cast<float>(color.value.a);
-				}
+	if (color.hasValue) {
+		attachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		attachment.clearValue.color.float32[0] = static_cast<float>(color.value.r);
+		attachment.clearValue.color.float32[1] = static_cast<float>(color.value.g);
+		attachment.clearValue.color.float32[2] = static_cast<float>(color.value.b);
+		attachment.clearValue.color.float32[3] = static_cast<float>(color.value.a);
+	}
 
-				VkClearRect rect{};
-				rect.layerCount = 1;
-				rect.rect.extent.width = static_cast<uint32_t>(currentViewportWidth);
-				rect.rect.extent.height = static_cast<uint32_t>(currentViewportHeight);
+	VkClearRect rect{};
+	rect.layerCount = 1;
+	rect.rect.extent.width = static_cast<uint32_t>(currentViewportWidth);
+	rect.rect.extent.height = static_cast<uint32_t>(currentViewportHeight);
 
-				vkCmdClearAttachments(commandBuffers[imageIndex], 1, &attachment, 1, &rect);
-			}
+	vkCmdClearAttachments(commandBuffers[imageIndex], 1, &attachment, 1, &rect);
+}
 
-			void Graphics::clear(const std::vector<OptionalColorD>& colors, OptionalInt stencil, OptionalDouble depth) {
-				std::vector<VkClearAttachment> attachments;
-				for (const auto& color : colors) {
-					VkClearAttachment attachment{};
-					if (color.hasValue) {
-						attachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
-						attachment.clearValue.color.float32[0] = static_cast<float>(color.value.r);
-						attachment.clearValue.color.float32[1] = static_cast<float>(color.value.g);
-						attachment.clearValue.color.float32[2] = static_cast<float>(color.value.b);
-						attachment.clearValue.color.float32[3] = static_cast<float>(color.value.a);
-					}
-					attachments.push_back(attachment);
-				}
+void Graphics::clear(const std::vector<OptionalColorD>& colors, OptionalInt stencil, OptionalDouble depth) {
+	std::vector<VkClearAttachment> attachments;
+	for (const auto& color : colors) {
+		VkClearAttachment attachment{};
+		if (color.hasValue) {
+			attachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			attachment.clearValue.color.float32[0] = static_cast<float>(color.value.r);
+			attachment.clearValue.color.float32[1] = static_cast<float>(color.value.g);
+			attachment.clearValue.color.float32[2] = static_cast<float>(color.value.b);
+			attachment.clearValue.color.float32[3] = static_cast<float>(color.value.a);
+		}
+		attachments.push_back(attachment);
+	}
 
-				VkClearRect rect{};
-				rect.layerCount = 1;
-				rect.rect.extent.width = static_cast<uint32_t>(currentViewportWidth);
-				rect.rect.extent.height = static_cast<uint32_t>(currentViewportHeight);
+	VkClearRect rect{};
+	rect.layerCount = 1;
+	rect.rect.extent.width = static_cast<uint32_t>(currentViewportWidth);
+	rect.rect.extent.height = static_cast<uint32_t>(currentViewportHeight);
 
-				vkCmdClearAttachments(commandBuffers[imageIndex], static_cast<uint32_t>(attachments.size()), attachments.data(), 1, &rect);
-			}
+	vkCmdClearAttachments(commandBuffers[imageIndex], static_cast<uint32_t>(attachments.size()), attachments.data(), 1, &rect);
+}
 
-			void Graphics::present(void* screenshotCallbackdata) {
-				if (!isActive()) {
-					return;
-				}
+void Graphics::present(void* screenshotCallbackdata) {
+	if (!isActive()) {
+		return;
+	}
 
-				flushBatchedDraws();
+	flushBatchedDraws();
 
-				endRecordingGraphicsCommands();
+	endRecordingGraphicsCommands();
 
-				if (imagesInFlight[imageIndex] != VK_NULL_HANDLE) {
-					vkWaitForFences(device, 1, &imagesInFlight.at(imageIndex), VK_TRUE, UINT64_MAX);
-				}
-				imagesInFlight[imageIndex] = inFlightFences[currentFrame];
+	if (imagesInFlight[imageIndex] != VK_NULL_HANDLE) {
+		vkWaitForFences(device, 1, &imagesInFlight.at(imageIndex), VK_TRUE, UINT64_MAX);
+	}
+	imagesInFlight[imageIndex] = inFlightFences[currentFrame];
 
-				// all data transfers should happen before any draw calls.
-				std::vector<VkCommandBuffer> submitCommandbuffers = { dataTransferCommandBuffers.at(currentFrame), commandBuffers.at(imageIndex) };
+	// all data transfers should happen before any draw calls.
+	std::vector<VkCommandBuffer> submitCommandbuffers = { dataTransferCommandBuffers.at(currentFrame), commandBuffers.at(imageIndex) };
 
-				VkSubmitInfo submitInfo{};
-				submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+	VkSubmitInfo submitInfo{};
+	submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
 
-				VkSemaphore waitSemaphores[] = { imageAvailableSemaphores.at(currentFrame) };
-				VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
-				submitInfo.waitSemaphoreCount = 1;
-				submitInfo.pWaitSemaphores = waitSemaphores;
-				submitInfo.pWaitDstStageMask = waitStages;
+	VkSemaphore waitSemaphores[] = { imageAvailableSemaphores.at(currentFrame) };
+	VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
+	submitInfo.waitSemaphoreCount = 1;
+	submitInfo.pWaitSemaphores = waitSemaphores;
+	submitInfo.pWaitDstStageMask = waitStages;
 
-				submitInfo.commandBufferCount = static_cast<uint32_t>(submitCommandbuffers.size());
-				submitInfo.pCommandBuffers = submitCommandbuffers.data();
+	submitInfo.commandBufferCount = static_cast<uint32_t>(submitCommandbuffers.size());
+	submitInfo.pCommandBuffers = submitCommandbuffers.data();
 
-				VkSemaphore signalSemaphores[] = { renderFinishedSemaphores.at(currentFrame) };
-				submitInfo.signalSemaphoreCount = 1;
-				submitInfo.pSignalSemaphores = signalSemaphores;
+	VkSemaphore signalSemaphores[] = { renderFinishedSemaphores.at(currentFrame) };
+	submitInfo.signalSemaphoreCount = 1;
+	submitInfo.pSignalSemaphores = signalSemaphores;
 
-				vkResetFences(device, 1, &inFlightFences[currentFrame]);
+	vkResetFences(device, 1, &inFlightFences[currentFrame]);
 
-				if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences.at(currentFrame)) != VK_SUCCESS) {
-					throw love::Exception("failed to submit draw command buffer");
-				}
+	if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences.at(currentFrame)) != VK_SUCCESS) {
+		throw love::Exception("failed to submit draw command buffer");
+	}
 
-				VkPresentInfoKHR presentInfo{};
-				presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
+	VkPresentInfoKHR presentInfo{};
+	presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
 
-				presentInfo.waitSemaphoreCount = 1;
-				presentInfo.pWaitSemaphores = signalSemaphores;
+	presentInfo.waitSemaphoreCount = 1;
+	presentInfo.pWaitSemaphores = signalSemaphores;
 
-				VkSwapchainKHR swapChains[] = { swapChain };
-				presentInfo.swapchainCount = 1;
-				presentInfo.pSwapchains = swapChains;
+	VkSwapchainKHR swapChains[] = { swapChain };
+	presentInfo.swapchainCount = 1;
+	presentInfo.pSwapchains = swapChains;
 
-				presentInfo.pImageIndices = &imageIndex;
+	presentInfo.pImageIndices = &imageIndex;
 
-				VkResult result = vkQueuePresentKHR(presentQueue, &presentInfo);
+	VkResult result = vkQueuePresentKHR(presentQueue, &presentInfo);
 
-				if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) {
-					framebufferResized = false;
-					recreateSwapChain();
-				}
-				else if (result != VK_SUCCESS) {
-					throw love::Exception("failed to present swap chain image");
-				}
-				
-				currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
+	if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized) {
+		framebufferResized = false;
+		recreateSwapChain();
+	}
+	else if (result != VK_SUCCESS) {
+		throw love::Exception("failed to present swap chain image");
+	}
+	
+	currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
 
-				updatedBatchedDrawBuffers();
-				startRecordingGraphicsCommands();
-			}
+	updatedBatchedDrawBuffers();
+	startRecordingGraphicsCommands();
+}
 
-			void Graphics::setViewportSize(int width, int height, int pixelwidth, int pixelheight) {
-				this->width = width;
-				this->height = height;
-				this->pixelWidth = pixelwidth;
-				this->pixelHeight = pixelheight;
+void Graphics::setViewportSize(int width, int height, int pixelwidth, int pixelheight) {
+	this->width = width;
+	this->height = height;
+	this->pixelWidth = pixelwidth;
+	this->pixelHeight = pixelheight;
 
-				resetProjection();
-			}
+	resetProjection();
+}
 
-			bool Graphics::setMode(void* context, int width, int height, int pixelwidth, int pixelheight, bool windowhasstencil, int msaa) {
-				cleanUpFunctions.clear();
-				cleanUpFunctions.resize(MAX_FRAMES_IN_FLIGHT);
-
-				createVulkanInstance();
-				createSurface();
-				pickPhysicalDevice();
-				createLogicalDevice();
-				initVMA();
-				initCapabilities();
-				createSwapChain();
-				createImageViews();
-				createCommandPool();
-				createCommandBuffers();
-				createDefaultTexture();
-				createDefaultShaders();
-				createQuadIndexBuffer();
-				createSyncObjects();
-				startRecordingGraphicsCommands();
-				currentFrame = 0;
-
-				created = true;
-
-				float whiteColor[] = { 1.0f, 1.0f, 1.0f, 1.0f };
-
-				batchedDrawBuffers.clear();
-				batchedDrawBuffers.reserve(MAX_FRAMES_IN_FLIGHT);
-				for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
-					batchedDrawBuffers.emplace_back();
-					// Initial sizes that should be good enough for most cases. It will
-					// resize to fit if needed, later.
-					batchedDrawBuffers[i].vertexBuffer1 = new StreamBuffer(this, BUFFERUSAGE_VERTEX, 1024 * 1024 * 1);
-					batchedDrawBuffers[i].vertexBuffer2 = new StreamBuffer(this, BUFFERUSAGE_VERTEX, 256 * 1024 * 1);
-					batchedDrawBuffers[i].indexBuffer = new StreamBuffer(this, BUFFERUSAGE_INDEX, sizeof(uint16) * LOVE_UINT16_MAX);
-
-					// sometimes the VertexColor is not set, so we manually adjust it to white color
-					batchedDrawBuffers[i].constantColorBuffer = new StreamBuffer(this, BUFFERUSAGE_VERTEX, sizeof(whiteColor));
-					auto mapInfo = batchedDrawBuffers[i].constantColorBuffer->map(sizeof(whiteColor));
-					memcpy(mapInfo.data, whiteColor, sizeof(whiteColor));
-					batchedDrawBuffers[i].constantColorBuffer->unmap(sizeof(whiteColor));
-					batchedDrawBuffers[i].constantColorBuffer->markUsed(sizeof(whiteColor));
-				}
+bool Graphics::setMode(void* context, int width, int height, int pixelwidth, int pixelheight, bool windowhasstencil, int msaa) {
+	cleanUpFunctions.clear();
+	cleanUpFunctions.resize(MAX_FRAMES_IN_FLIGHT);
+
+	createVulkanInstance();
+	createSurface();
+	pickPhysicalDevice();
+	createLogicalDevice();
+	initVMA();
+	initCapabilities();
+	createSwapChain();
+	createImageViews();
+	createCommandPool();
+	createCommandBuffers();
+	createDefaultTexture();
+	createDefaultShaders();
+	createQuadIndexBuffer();
+	createSyncObjects();
+	startRecordingGraphicsCommands();
+	currentFrame = 0;
+
+	created = true;
+
+	float whiteColor[] = { 1.0f, 1.0f, 1.0f, 1.0f };
+
+	batchedDrawBuffers.clear();
+	batchedDrawBuffers.reserve(MAX_FRAMES_IN_FLIGHT);
+	for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
+		batchedDrawBuffers.emplace_back();
+		// Initial sizes that should be good enough for most cases. It will
+		// resize to fit if needed, later.
+		batchedDrawBuffers[i].vertexBuffer1 = new StreamBuffer(this, BUFFERUSAGE_VERTEX, 1024 * 1024 * 1);
+		batchedDrawBuffers[i].vertexBuffer2 = new StreamBuffer(this, BUFFERUSAGE_VERTEX, 256 * 1024 * 1);
+		batchedDrawBuffers[i].indexBuffer = new StreamBuffer(this, BUFFERUSAGE_INDEX, sizeof(uint16) * LOVE_UINT16_MAX);
+
+		// sometimes the VertexColor is not set, so we manually adjust it to white color
+		batchedDrawBuffers[i].constantColorBuffer = new StreamBuffer(this, BUFFERUSAGE_VERTEX, sizeof(whiteColor));
+		auto mapInfo = batchedDrawBuffers[i].constantColorBuffer->map(sizeof(whiteColor));
+		memcpy(mapInfo.data, whiteColor, sizeof(whiteColor));
+		batchedDrawBuffers[i].constantColorBuffer->unmap(sizeof(whiteColor));
+		batchedDrawBuffers[i].constantColorBuffer->markUsed(sizeof(whiteColor));
+	}
 
-				updatedBatchedDrawBuffers();
+	updatedBatchedDrawBuffers();
 
-				Shader::current = Shader::standardShaders[graphics::Shader::StandardShader::STANDARD_DEFAULT];
-				currentPolygonMode = VK_POLYGON_MODE_FILL;
-				restoreState(states.back());
-				
-				setViewportSize(width, height, pixelwidth, pixelheight);
-				renderTargetTexture = nullptr;
-				currentViewportWidth = 0.0f;
-				currentViewportHeight = 0.0f;
+	Shader::current = Shader::standardShaders[graphics::Shader::StandardShader::STANDARD_DEFAULT];
+	currentPolygonMode = VK_POLYGON_MODE_FILL;
+	restoreState(states.back());
+	
+	setViewportSize(width, height, pixelwidth, pixelheight);
+	renderTargetTexture = nullptr;
+	currentViewportWidth = 0.0f;
+	currentViewportHeight = 0.0f;
 
-				Vulkan::resetShaderSwitches();
+	Vulkan::resetShaderSwitches();
 
-				return true;
-			}
+	return true;
+}
 
-			void Graphics::initCapabilities() {
-				// todo
-				capabilities.features[FEATURE_MULTI_RENDER_TARGET_FORMATS] = false;
-				capabilities.features[FEATURE_CLAMP_ZERO] = false;
-				capabilities.features[FEATURE_CLAMP_ONE] = false;
-				capabilities.features[FEATURE_BLEND_MINMAX] = false;
-				capabilities.features[FEATURE_LIGHTEN] = false;
-				capabilities.features[FEATURE_FULL_NPOT] = false;
-				capabilities.features[FEATURE_PIXEL_SHADER_HIGHP] = true;
-				capabilities.features[FEATURE_SHADER_DERIVATIVES] = false;
-				capabilities.features[FEATURE_GLSL3] = true;
-				capabilities.features[FEATURE_GLSL4] = true;
-				capabilities.features[FEATURE_INSTANCING] = false;
-				capabilities.features[FEATURE_TEXEL_BUFFER] = false;
-				capabilities.features[FEATURE_INDEX_BUFFER_32BIT] = true;
-				capabilities.features[FEATURE_COPY_BUFFER] = false;
-				capabilities.features[FEATURE_COPY_BUFFER_TO_TEXTURE] = false;
-				capabilities.features[FEATURE_COPY_TEXTURE_TO_BUFFER] = false;
-				capabilities.features[FEATURE_COPY_RENDER_TARGET_TO_BUFFER] = false;
-				static_assert(FEATURE_MAX_ENUM == 17, "Graphics::initCapabilities must be updated when adding a new graphics feature!");
-
-				VkPhysicalDeviceProperties properties;
-				vkGetPhysicalDeviceProperties(physicalDevice, &properties);
-
-				capabilities.limits[LIMIT_POINT_SIZE] = properties.limits.pointSizeRange[1];
-				capabilities.limits[LIMIT_TEXTURE_SIZE] = properties.limits.maxImageDimension2D;
-				capabilities.limits[LIMIT_TEXTURE_LAYERS] = properties.limits.maxImageArrayLayers;
-				capabilities.limits[LIMIT_VOLUME_TEXTURE_SIZE] = properties.limits.maxImageDimension3D;
-				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] = 0;  // todo
-				capabilities.limits[LIMIT_THREADGROUPS_Y] = 0;  // todo
-				capabilities.limits[LIMIT_THREADGROUPS_Z] = 0;  // todo
-				capabilities.limits[LIMIT_RENDER_TARGETS] = 1;	// todo
-				capabilities.limits[LIMIT_TEXTURE_MSAA] = 1;	// todo
-				capabilities.limits[LIMIT_ANISOTROPY] = 1.0f;	// todo
-				static_assert(LIMIT_MAX_ENUM == 13, "Graphics::initCapabilities must be updated when adding a new system limit!");
-
-				capabilities.textureTypes[TEXTURE_2D] = true;
-				capabilities.textureTypes[TEXTURE_VOLUME] = false;
-				capabilities.textureTypes[TEXTURE_2D_ARRAY] = false;
-				capabilities.textureTypes[TEXTURE_CUBE] = false;
-			}
+void Graphics::initCapabilities() {
+	// todo
+	capabilities.features[FEATURE_MULTI_RENDER_TARGET_FORMATS] = false;
+	capabilities.features[FEATURE_CLAMP_ZERO] = false;
+	capabilities.features[FEATURE_CLAMP_ONE] = false;
+	capabilities.features[FEATURE_BLEND_MINMAX] = false;
+	capabilities.features[FEATURE_LIGHTEN] = false;
+	capabilities.features[FEATURE_FULL_NPOT] = false;
+	capabilities.features[FEATURE_PIXEL_SHADER_HIGHP] = true;
+	capabilities.features[FEATURE_SHADER_DERIVATIVES] = false;
+	capabilities.features[FEATURE_GLSL3] = true;
+	capabilities.features[FEATURE_GLSL4] = true;
+	capabilities.features[FEATURE_INSTANCING] = false;
+	capabilities.features[FEATURE_TEXEL_BUFFER] = false;
+	capabilities.features[FEATURE_INDEX_BUFFER_32BIT] = true;
+	capabilities.features[FEATURE_COPY_BUFFER] = false;
+	capabilities.features[FEATURE_COPY_BUFFER_TO_TEXTURE] = false;
+	capabilities.features[FEATURE_COPY_TEXTURE_TO_BUFFER] = false;
+	capabilities.features[FEATURE_COPY_RENDER_TARGET_TO_BUFFER] = false;
+	static_assert(FEATURE_MAX_ENUM == 17, "Graphics::initCapabilities must be updated when adding a new graphics feature!");
+
+	VkPhysicalDeviceProperties properties;
+	vkGetPhysicalDeviceProperties(physicalDevice, &properties);
+
+	capabilities.limits[LIMIT_POINT_SIZE] = properties.limits.pointSizeRange[1];
+	capabilities.limits[LIMIT_TEXTURE_SIZE] = properties.limits.maxImageDimension2D;
+	capabilities.limits[LIMIT_TEXTURE_LAYERS] = properties.limits.maxImageArrayLayers;
+	capabilities.limits[LIMIT_VOLUME_TEXTURE_SIZE] = properties.limits.maxImageDimension3D;
+	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] = 0;  // todo
+	capabilities.limits[LIMIT_THREADGROUPS_Y] = 0;  // todo
+	capabilities.limits[LIMIT_THREADGROUPS_Z] = 0;  // todo
+	capabilities.limits[LIMIT_RENDER_TARGETS] = 1;	// todo
+	capabilities.limits[LIMIT_TEXTURE_MSAA] = 1;	// todo
+	capabilities.limits[LIMIT_ANISOTROPY] = 1.0f;	// todo
+	static_assert(LIMIT_MAX_ENUM == 13, "Graphics::initCapabilities must be updated when adding a new system limit!");
+
+	capabilities.textureTypes[TEXTURE_2D] = true;
+	capabilities.textureTypes[TEXTURE_VOLUME] = false;
+	capabilities.textureTypes[TEXTURE_2D_ARRAY] = false;
+	capabilities.textureTypes[TEXTURE_CUBE] = false;
+}
 
-			void Graphics::getAPIStats(int& shaderswitches) const {
-				shaderswitches = Vulkan::getNumShaderSwitches();
-			}
+void Graphics::getAPIStats(int& shaderswitches) const {
+	shaderswitches = Vulkan::getNumShaderSwitches();
+}
 
-			void Graphics::unSetMode() {
-				created = false;
-				vkDeviceWaitIdle(device);
-				Volatile::unloadAll();
-				cleanup();
-			}
+void Graphics::unSetMode() {
+	created = false;
+	vkDeviceWaitIdle(device);
+	Volatile::unloadAll();
+	cleanup();
+}
 
-			void Graphics::setActive(bool enable) {
-				flushBatchedDraws();
-				active = enable;
-			}
+void Graphics::setActive(bool enable) {
+	flushBatchedDraws();
+	active = enable;
+}
 
-			void Graphics::setFrontFaceWinding(Winding winding) {
-				const auto& currentState = states.back();
+void Graphics::setFrontFaceWinding(Winding winding) {
+	const auto& currentState = states.back();
 
-				if (currentState.winding == winding) {
-					return;
-				}
+	if (currentState.winding == winding) {
+		return;
+	}
 
-				flushBatchedDraws();
+	flushBatchedDraws();
 
-				states.back().winding = winding;
-			}
+	states.back().winding = winding;
+}
 
-			void Graphics::setColorMask(ColorChannelMask mask) {
-				flushBatchedDraws();
+void Graphics::setColorMask(ColorChannelMask mask) {
+	flushBatchedDraws();
 
-				states.back().colorMask = mask;
-			}
+	states.back().colorMask = mask;
+}
 
-			void Graphics::setBlendState(const BlendState& blend) {
-				flushBatchedDraws();
+void Graphics::setBlendState(const BlendState& blend) {
+	flushBatchedDraws();
 
-				states.back().blend = blend;
-			}
+	states.back().blend = blend;
+}
 
-			void Graphics::setPointSize(float size) {
-				if (size != states.back().pointSize)
-					flushBatchedDraws();
+void Graphics::setPointSize(float size) {
+	if (size != states.back().pointSize)
+		flushBatchedDraws();
 
-				states.back().pointSize = size;
-			}
+	states.back().pointSize = size;
+}
 
-			bool Graphics::usesGLSLES() const {
-				return false;
-			}
-			
-			Graphics::RendererInfo Graphics::getRendererInfo() const {
-				VkPhysicalDeviceProperties deviceProperties;
-				vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties);
-
-				Graphics::RendererInfo info;
-				info.device = deviceProperties.deviceName;
-				info.vendor = Vulkan::getVendorName(deviceProperties.vendorID);
-				info.version = Vulkan::getVulkanApiVersion(deviceProperties.apiVersion);
-				info.name = "Vulkan";
-
-				return info;
-			}
+bool Graphics::usesGLSLES() const {
+	return false;
+}
 
-			void Graphics::draw(const DrawCommand& cmd) {
-				prepareDraw(*cmd.attributes, *cmd.buffers, cmd.texture, cmd.primitiveType, cmd.cullMode);
+Graphics::RendererInfo Graphics::getRendererInfo() const {
+	VkPhysicalDeviceProperties deviceProperties;
+	vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties);
 
-				vkCmdDraw(commandBuffers.at(imageIndex), static_cast<uint32_t>(cmd.vertexCount), static_cast<uint32_t>(cmd.instanceCount), static_cast<uint32_t>(cmd.vertexStart), 0);
-			}
+	Graphics::RendererInfo info;
+	info.device = deviceProperties.deviceName;
+	info.vendor = Vulkan::getVendorName(deviceProperties.vendorID);
+	info.version = Vulkan::getVulkanApiVersion(deviceProperties.apiVersion);
+	info.name = "Vulkan";
 
-			void Graphics::draw(const DrawIndexedCommand& cmd) {
-				prepareDraw(*cmd.attributes, *cmd.buffers, cmd.texture, cmd.primitiveType, cmd.cullMode);
+	return info;
+}
 
-				vkCmdBindIndexBuffer(commandBuffers.at(imageIndex), (VkBuffer)cmd.indexBuffer->getHandle(), static_cast<VkDeviceSize>(cmd.indexBufferOffset), getVulkanIndexBufferType(cmd.indexType));
-				vkCmdDrawIndexed(commandBuffers.at(imageIndex), static_cast<uint32_t>(cmd.indexCount), static_cast<uint32_t>(cmd.instanceCount), 0, 0, 0);
-			}
+void Graphics::draw(const DrawCommand& cmd) {
+	prepareDraw(*cmd.attributes, *cmd.buffers, cmd.texture, cmd.primitiveType, cmd.cullMode);
 
-			void Graphics::drawQuads(int start, int count, const VertexAttributes& attributes, const BufferBindings& buffers, graphics::Texture* texture) {
-				const int MAX_VERTICES_PER_DRAW = LOVE_UINT16_MAX;
-				const int MAX_QUADS_PER_DRAW = MAX_VERTICES_PER_DRAW / 4;
+	vkCmdDraw(commandBuffers.at(imageIndex), static_cast<uint32_t>(cmd.vertexCount), static_cast<uint32_t>(cmd.instanceCount), static_cast<uint32_t>(cmd.vertexStart), 0);
+}
 
-				prepareDraw(attributes, buffers, texture, PRIMITIVE_TRIANGLES, CULL_BACK);
+void Graphics::draw(const DrawIndexedCommand& cmd) {
+	prepareDraw(*cmd.attributes, *cmd.buffers, cmd.texture, cmd.primitiveType, cmd.cullMode);
 
-				vkCmdBindIndexBuffer(commandBuffers.at(imageIndex), (VkBuffer)quadIndexBuffer->getHandle(), 0, getVulkanIndexBufferType(INDEX_UINT16));
+	vkCmdBindIndexBuffer(commandBuffers.at(imageIndex), (VkBuffer)cmd.indexBuffer->getHandle(), static_cast<VkDeviceSize>(cmd.indexBufferOffset), getVulkanIndexBufferType(cmd.indexType));
+	vkCmdDrawIndexed(commandBuffers.at(imageIndex), static_cast<uint32_t>(cmd.indexCount), static_cast<uint32_t>(cmd.instanceCount), 0, 0, 0);
+}
 
-				int baseVertex = start * 4;
+void Graphics::drawQuads(int start, int count, const VertexAttributes& attributes, const BufferBindings& buffers, graphics::Texture* texture) {
+	const int MAX_VERTICES_PER_DRAW = LOVE_UINT16_MAX;
+	const int MAX_QUADS_PER_DRAW = MAX_VERTICES_PER_DRAW / 4;
 
-				for (int quadindex = 0; quadindex < count; quadindex += MAX_QUADS_PER_DRAW) {
-					int quadcount = std::min(MAX_QUADS_PER_DRAW, count - quadindex);
+	prepareDraw(attributes, buffers, texture, PRIMITIVE_TRIANGLES, CULL_BACK);
 
-					vkCmdDrawIndexed(commandBuffers.at(imageIndex), static_cast<uint32_t>(quadcount * 6), 1, 0, baseVertex, 0);
-					baseVertex += quadcount * 4;
-				}
-			}
+	vkCmdBindIndexBuffer(commandBuffers.at(imageIndex), (VkBuffer)quadIndexBuffer->getHandle(), 0, getVulkanIndexBufferType(INDEX_UINT16));
 
-			void Graphics::setColor(Colorf c) {
-				c.r = std::min(std::max(c.r, 0.0f), 1.0f);
-				c.g = std::min(std::max(c.g, 0.0f), 1.0f);
-				c.b = std::min(std::max(c.b, 0.0f), 1.0f);
-				c.a = std::min(std::max(c.a, 0.0f), 1.0f);
+	int baseVertex = start * 4;
 
-				states.back().color = c;
-			}
+	for (int quadindex = 0; quadindex < count; quadindex += MAX_QUADS_PER_DRAW) {
+		int quadcount = std::min(MAX_QUADS_PER_DRAW, count - quadindex);
 
-			void Graphics::setScissor(const Rect& rect) {
-				flushBatchedDraws();
-				
-				states.back().scissor = true;
-				states.back().scissorRect = rect;
-			}
+		vkCmdDrawIndexed(commandBuffers.at(imageIndex), static_cast<uint32_t>(quadcount * 6), 1, 0, baseVertex, 0);
+		baseVertex += quadcount * 4;
+	}
+}
 
-			void Graphics::setScissor() {
-				flushBatchedDraws();
+void Graphics::setColor(Colorf c) {
+	c.r = std::min(std::max(c.r, 0.0f), 1.0f);
+	c.g = std::min(std::max(c.g, 0.0f), 1.0f);
+	c.b = std::min(std::max(c.b, 0.0f), 1.0f);
+	c.a = std::min(std::max(c.a, 0.0f), 1.0f);
 
-				states.back().scissor = false;
-			}
+	states.back().color = c;
+}
 
-			void Graphics::setWireframe(bool enable) {
-				flushBatchedDraws();
+void Graphics::setScissor(const Rect& rect) {
+	flushBatchedDraws();
+	
+	states.back().scissor = true;
+	states.back().scissorRect = rect;
+}
 
-				if (enable) {
-					currentPolygonMode = VK_POLYGON_MODE_LINE;
-				}
-				else {
-					currentPolygonMode = VK_POLYGON_MODE_FILL;
-				}
+void Graphics::setScissor() {
+	flushBatchedDraws();
 
-				states.back().wireframe = enable;
-			}
+	states.back().scissor = false;
+}
 
-			PixelFormat Graphics::getSizedFormat(PixelFormat format, bool rendertarget, bool readable) const { 
-				switch (format) {
-				case PIXELFORMAT_NORMAL:
-					if (isGammaCorrect()) {
-						return PIXELFORMAT_RGBA8_UNORM_sRGB;
-					}
-					else {
-						return PIXELFORMAT_RGBA8_UNORM;
-					}
-				case PIXELFORMAT_HDR:
-					return PIXELFORMAT_RGBA16_FLOAT;
-				default:
-					return format;
-				}
-			}
+void Graphics::setWireframe(bool enable) {
+	flushBatchedDraws();
 
-			bool Graphics::isPixelFormatSupported(PixelFormat format, uint32 usage, bool sRGB) { 
-				return true;
-			}
+	if (enable) {
+		currentPolygonMode = VK_POLYGON_MODE_LINE;
+	}
+	else {
+		currentPolygonMode = VK_POLYGON_MODE_FILL;
+	}
 
-			Renderer Graphics::getRenderer() const {
-				return RENDERER_VULKAN;
-			}
+	states.back().wireframe = enable;
+}
 
-			graphics::StreamBuffer* Graphics::newStreamBuffer(BufferUsage type, size_t size) {
-				return new StreamBuffer(this, type, size);
-			}
+PixelFormat Graphics::getSizedFormat(PixelFormat format, bool rendertarget, bool readable) const { 
+	switch (format) {
+	case PIXELFORMAT_NORMAL:
+		if (isGammaCorrect()) {
+			return PIXELFORMAT_RGBA8_UNORM_sRGB;
+		}
+		else {
+			return PIXELFORMAT_RGBA8_UNORM;
+		}
+	case PIXELFORMAT_HDR:
+		return PIXELFORMAT_RGBA16_FLOAT;
+	default:
+		return format;
+	}
+}
 
-			Matrix4 Graphics::computeDeviceProjection(const Matrix4& projection, bool rendertotexture) const { 
-				uint32 flags = DEVICE_PROJECTION_DEFAULT;
-				return calculateDeviceProjection(projection, flags);
-			}
-			
-			void Graphics::setRenderTargetsInternal(const RenderTargets& rts, int pixelw, int pixelh, bool hasSRGBtexture) {
-				endRenderPass();
-
-				if (rts.colors.size() == 0) {
-					startRenderPass(nullptr, swapChainExtent.width, swapChainExtent.height);
-				} else {
-					auto& firstRenderTarget = rts.getFirstTarget();
-					startRenderPass(static_cast<Texture*>(firstRenderTarget.texture), pixelw, pixelh);
-				}
-			}
+bool Graphics::isPixelFormatSupported(PixelFormat format, uint32 usage, bool sRGB) { 
+	return true;
+}
+
+Renderer Graphics::getRenderer() const {
+	return RENDERER_VULKAN;
+}
+
+graphics::StreamBuffer* Graphics::newStreamBuffer(BufferUsage type, size_t size) {
+	return new StreamBuffer(this, type, size);
+}
 
-			// END IMPLEMENTATION OVERRIDDEN FUNCTIONS
+Matrix4 Graphics::computeDeviceProjection(const Matrix4& projection, bool rendertotexture) const { 
+	uint32 flags = DEVICE_PROJECTION_DEFAULT;
+	return calculateDeviceProjection(projection, flags);
+}
 
-			void Graphics::startRecordingGraphicsCommands() {
-				vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX);
+void Graphics::setRenderTargetsInternal(const RenderTargets& rts, int pixelw, int pixelh, bool hasSRGBtexture) {
+	endRenderPass();
 
-				while (true) {
-					VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex);
-					if (result == VK_ERROR_OUT_OF_DATE_KHR) {
-						recreateSwapChain();
-						continue;
-					}
-					else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
-						throw love::Exception("failed to acquire swap chain image");
-					}
+	if (rts.colors.size() == 0) {
+		startRenderPass(nullptr, swapChainExtent.width, swapChainExtent.height);
+	} else {
+		auto& firstRenderTarget = rts.getFirstTarget();
+		startRenderPass(static_cast<Texture*>(firstRenderTarget.texture), pixelw, pixelh);
+	}
+}
 
-					break;
-				}
+// END IMPLEMENTATION OVERRIDDEN FUNCTIONS
 
-				for (auto cleanUpFn : cleanUpFunctions.at(currentFrame)) {
-					cleanUpFn();
-				}
-				cleanUpFunctions.at(currentFrame).clear();
+void Graphics::startRecordingGraphicsCommands() {
+	vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX);
 
-				VkCommandBufferBeginInfo beginInfo{};
-				beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
-				beginInfo.flags = 0;
-				beginInfo.pInheritanceInfo = nullptr;
+	while (true) {
+		VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex);
+		if (result == VK_ERROR_OUT_OF_DATE_KHR) {
+			recreateSwapChain();
+			continue;
+		}
+		else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
+			throw love::Exception("failed to acquire swap chain image");
+		}
 
-				if (vkBeginCommandBuffer(commandBuffers.at(imageIndex), &beginInfo) != VK_SUCCESS) {
-					throw love::Exception("failed to begin recording command buffer");
-				}
-				if (vkBeginCommandBuffer(dataTransferCommandBuffers.at(currentFrame), &beginInfo) != VK_SUCCESS) {
-					throw love::Exception("failed to begin recording data transfer command buffer");
-				}
+		break;
+	}
 
-				Vulkan::cmdTransitionImageLayout(commandBuffers.at(imageIndex), swapChainImages[imageIndex], VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
+	for (auto& cleanUpFn : cleanUpFunctions.at(currentFrame)) {
+		cleanUpFn();
+	}
+	cleanUpFunctions.at(currentFrame).clear();
 
-				startRenderPass(nullptr, swapChainExtent.width, swapChainExtent.height);
+	VkCommandBufferBeginInfo beginInfo{};
+	beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+	beginInfo.flags = 0;
+	beginInfo.pInheritanceInfo = nullptr;
 
-				Vulkan::resetShaderSwitches();
-			}
+	if (vkBeginCommandBuffer(commandBuffers.at(imageIndex), &beginInfo) != VK_SUCCESS) {
+		throw love::Exception("failed to begin recording command buffer");
+	}
+	if (vkBeginCommandBuffer(dataTransferCommandBuffers.at(currentFrame), &beginInfo) != VK_SUCCESS) {
+		throw love::Exception("failed to begin recording data transfer command buffer");
+	}
 
-			void Graphics::endRecordingGraphicsCommands() {
-				endRenderPass();
+	Vulkan::cmdTransitionImageLayout(commandBuffers.at(imageIndex), swapChainImages[imageIndex], VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
 
-				Vulkan::cmdTransitionImageLayout(commandBuffers.at(imageIndex), swapChainImages[imageIndex], VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
+	startRenderPass(nullptr, swapChainExtent.width, swapChainExtent.height);
 
-				if (vkEndCommandBuffer(commandBuffers.at(imageIndex)) != VK_SUCCESS) {
-					throw love::Exception("failed to record command buffer");
-				}
-				if (vkEndCommandBuffer(dataTransferCommandBuffers.at(currentFrame)) != VK_SUCCESS) {
-					throw love::Exception("failed to record data transfer command buffer");
-				}
-			}
+	Vulkan::resetShaderSwitches();
+}
 
-			void Graphics::updatedBatchedDrawBuffers() {
-				batchedDrawState.vb[0] = batchedDrawBuffers[currentFrame].vertexBuffer1;
-				batchedDrawState.vb[0]->nextFrame();
-				batchedDrawState.vb[1] = batchedDrawBuffers[currentFrame].vertexBuffer2; 
-				batchedDrawState.vb[1]->nextFrame();
-				batchedDrawState.indexBuffer = batchedDrawBuffers[currentFrame].indexBuffer;
-				batchedDrawState.indexBuffer->nextFrame();
-			}
+void Graphics::endRecordingGraphicsCommands() {
+	endRenderPass();
 
-			uint32_t Graphics::getNumImagesInFlight() const {
-				return MAX_FRAMES_IN_FLIGHT;
-			}
+	Vulkan::cmdTransitionImageLayout(commandBuffers.at(imageIndex), swapChainImages[imageIndex], VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
 
-			const VkDeviceSize Graphics::getMinUniformBufferOffsetAlignment() const {
-				return minUniformBufferOffsetAlignment;
-			}
+	if (vkEndCommandBuffer(commandBuffers.at(imageIndex)) != VK_SUCCESS) {
+		throw love::Exception("failed to record command buffer");
+	}
+	if (vkEndCommandBuffer(dataTransferCommandBuffers.at(currentFrame)) != VK_SUCCESS) {
+		throw love::Exception("failed to record data transfer command buffer");
+	}
+}
 
-			graphics::Texture* Graphics::getDefaultTexture() const {
-				return dynamic_cast<graphics::Texture*>(standardTexture.get());
-			}
+void Graphics::updatedBatchedDrawBuffers() {
+	batchedDrawState.vb[0] = batchedDrawBuffers[currentFrame].vertexBuffer1;
+	batchedDrawState.vb[0]->nextFrame();
+	batchedDrawState.vb[1] = batchedDrawBuffers[currentFrame].vertexBuffer2; 
+	batchedDrawState.vb[1]->nextFrame();
+	batchedDrawState.indexBuffer = batchedDrawBuffers[currentFrame].indexBuffer;
+	batchedDrawState.indexBuffer->nextFrame();
+}
 
-			const PFN_vkCmdPushDescriptorSetKHR Graphics::getVkCmdPushDescriptorSetKHRFunctionPointer() const {
-				return vkCmdPushDescriptorSet;
-			}
+uint32_t Graphics::getNumImagesInFlight() const {
+	return MAX_FRAMES_IN_FLIGHT;
+}
 
-			void Graphics::queueDatatransfer(std::function<void(VkCommandBuffer)> command, std::function<void()> cleanUp) {
-				command(dataTransferCommandBuffers.at(currentFrame));
-				cleanUpFunctions.at(currentFrame).push_back(std::move(cleanUp));
-			}
+const VkDeviceSize Graphics::getMinUniformBufferOffsetAlignment() const {
+	return minUniformBufferOffsetAlignment;
+}
 
-			void Graphics::queueCleanUp(std::function<void()> cleanUp) {
-				cleanUpFunctions.at(currentFrame).push_back(std::move(cleanUp));
-			}
+graphics::Texture* Graphics::getDefaultTexture() const {
+	return dynamic_cast<graphics::Texture*>(standardTexture.get());
+}
 
-			VkCommandBuffer Graphics::beginSingleTimeCommands() {
-				VkCommandBufferAllocateInfo allocInfo{};
-				allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
-				allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
-				allocInfo.commandPool = commandPool;
-				allocInfo.commandBufferCount = 1;
+const PFN_vkCmdPushDescriptorSetKHR Graphics::getVkCmdPushDescriptorSetKHRFunctionPointer() const {
+	return vkCmdPushDescriptorSet;
+}
 
-				VkCommandBuffer commandBuffer;
-				vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer);
+void Graphics::queueDatatransfer(std::function<void(VkCommandBuffer)> command, std::function<void()> cleanUp) {
+	command(dataTransferCommandBuffers.at(currentFrame));
+	cleanUpFunctions.at(currentFrame).push_back(std::move(cleanUp));
+}
 
-				VkCommandBufferBeginInfo beginInfo{};
-				beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
-				beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
+void Graphics::queueCleanUp(std::function<void()> cleanUp) {
+	cleanUpFunctions.at(currentFrame).push_back(std::move(cleanUp));
+}
 
-				vkBeginCommandBuffer(commandBuffer, &beginInfo);
+VkCommandBuffer Graphics::beginSingleTimeCommands() {
+	VkCommandBufferAllocateInfo allocInfo{};
+	allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+	allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+	allocInfo.commandPool = commandPool;
+	allocInfo.commandBufferCount = 1;
 
-				return commandBuffer;
-			}
+	VkCommandBuffer commandBuffer;
+	vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer);
 
-			void Graphics::endSingleTimeCommands(VkCommandBuffer commandBuffer) {
-				vkEndCommandBuffer(commandBuffer);
+	VkCommandBufferBeginInfo beginInfo{};
+	beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+	beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
 
-				VkSubmitInfo submitInfo{};
-				submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
-				submitInfo.commandBufferCount = 1;
-				submitInfo.pCommandBuffers = &commandBuffer;
+	vkBeginCommandBuffer(commandBuffer, &beginInfo);
 
-				vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);
-				vkQueueWaitIdle(graphicsQueue);
-				vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
-			}
+	return commandBuffer;
+}
 
-			graphics::Shader::BuiltinUniformData Graphics::getCurrentBuiltinUniformData() {
-				love::graphics::Shader::BuiltinUniformData data;
-
-				data.transformMatrix = getTransform();
-				data.projectionMatrix = getDeviceProjection();
-
-				// The normal matrix is the transpose of the inverse of the rotation portion
-				// (top-left 3x3) of the transform matrix.
-				{
-					Matrix3 normalmatrix = Matrix3(data.transformMatrix).transposedInverse();
-					const float* e = normalmatrix.getElements();
-					for (int i = 0; i < 3; i++)
-					{
-						data.normalMatrix[i].x = e[i * 3 + 0];
-						data.normalMatrix[i].y = e[i * 3 + 1];
-						data.normalMatrix[i].z = e[i * 3 + 2];
-						data.normalMatrix[i].w = 0.0f;
-					}
-				}
+void Graphics::endSingleTimeCommands(VkCommandBuffer commandBuffer) {
+	vkEndCommandBuffer(commandBuffer);
 
-				// Store DPI scale in an unused component of another vector.
-				data.normalMatrix[0].w = (float)getCurrentDPIScale();
+	VkSubmitInfo submitInfo{};
+	submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+	submitInfo.commandBufferCount = 1;
+	submitInfo.pCommandBuffers = &commandBuffer;
 
-				// Same with point size.
-				data.normalMatrix[1].w = getPointSize();
+	vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);
+	vkQueueWaitIdle(graphicsQueue);
+	vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
+}
 
-				data.screenSizeParams.x = static_cast<float>(swapChainExtent.width);
-				data.screenSizeParams.y = static_cast<float>(swapChainExtent.height);
+graphics::Shader::BuiltinUniformData Graphics::getCurrentBuiltinUniformData() {
+	love::graphics::Shader::BuiltinUniformData data;
+
+	data.transformMatrix = getTransform();
+	data.projectionMatrix = getDeviceProjection();
+
+	// The normal matrix is the transpose of the inverse of the rotation portion
+	// (top-left 3x3) of the transform matrix.
+	{
+		Matrix3 normalmatrix = Matrix3(data.transformMatrix).transposedInverse();
+		const float* e = normalmatrix.getElements();
+		for (int i = 0; i < 3; i++)
+		{
+			data.normalMatrix[i].x = e[i * 3 + 0];
+			data.normalMatrix[i].y = e[i * 3 + 1];
+			data.normalMatrix[i].z = e[i * 3 + 2];
+			data.normalMatrix[i].w = 0.0f;
+		}
+	}
 
-				data.screenSizeParams.z = 1.0f;
-				data.screenSizeParams.w = 0.0f;
+	// Store DPI scale in an unused component of another vector.
+	data.normalMatrix[0].w = (float)getCurrentDPIScale();
 
-				data.constantColor = getColor();
-				gammaCorrectColor(data.constantColor);
+	// Same with point size.
+	data.normalMatrix[1].w = getPointSize();
 
-				return data;
-			}
+	data.screenSizeParams.x = static_cast<float>(swapChainExtent.width);
+	data.screenSizeParams.y = static_cast<float>(swapChainExtent.height);
 
-			void Graphics::createVulkanInstance() {
-				if (enableValidationLayers && !checkValidationSupport()) {
-					throw love::Exception("validation layers requested, but not available");
-				}
+	data.screenSizeParams.z = 1.0f;
+	data.screenSizeParams.w = 0.0f;
 
-				VkApplicationInfo appInfo{};
-				appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
-				appInfo.pApplicationName = "LOVE";
-				appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);	//todo, get this version from somewhere else?
-				appInfo.pEngineName = "LOVE Engine";
-				appInfo.engineVersion = VK_MAKE_VERSION(VERSION_MAJOR, VERSION_MINOR, VERSION_REV);
-				appInfo.apiVersion = vulkanApiVersion;
-
-				VkInstanceCreateInfo createInfo{};
-				createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
-				createInfo.pApplicationInfo = &appInfo;
-				createInfo.pNext = nullptr;
-
-				auto window = Module::getInstance<love::window::Window>(M_WINDOW);
-				const void* handle = window->getHandle();
-
-				unsigned int count;
-				if (SDL_Vulkan_GetInstanceExtensions((SDL_Window*)handle, &count, nullptr) != SDL_TRUE) {
-					throw love::Exception("couldn't retrieve sdl vulkan extensions");
-				}
+	data.constantColor = getColor();
+	gammaCorrectColor(data.constantColor);
 
-				std::vector<const char*> extensions = {};	// can add more here
-				size_t addition_extension_count = extensions.size();
-				extensions.resize(addition_extension_count + count);
+	return data;
+}
 
-				if (SDL_Vulkan_GetInstanceExtensions((SDL_Window*)handle, &count, extensions.data() + addition_extension_count) != SDL_TRUE) {
-					throw love::Exception("couldn't retrieve sdl vulkan extensions");
-				}
+void Graphics::createVulkanInstance() {
+	if (enableValidationLayers && !checkValidationSupport()) {
+		throw love::Exception("validation layers requested, but not available");
+	}
 
-				createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
-				createInfo.ppEnabledExtensionNames = extensions.data();
+	VkApplicationInfo appInfo{};
+	appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
+	appInfo.pApplicationName = "LOVE";
+	appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);	//todo, get this version from somewhere else?
+	appInfo.pEngineName = "LOVE Engine";
+	appInfo.engineVersion = VK_MAKE_VERSION(VERSION_MAJOR, VERSION_MINOR, VERSION_REV);
+	appInfo.apiVersion = vulkanApiVersion;
+
+	VkInstanceCreateInfo createInfo{};
+	createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
+	createInfo.pApplicationInfo = &appInfo;
+	createInfo.pNext = nullptr;
+
+	auto window = Module::getInstance<love::window::Window>(M_WINDOW);
+	const void* handle = window->getHandle();
+
+	unsigned int count;
+	if (SDL_Vulkan_GetInstanceExtensions((SDL_Window*)handle, &count, nullptr) != SDL_TRUE) {
+		throw love::Exception("couldn't retrieve sdl vulkan extensions");
+	}
 
-				if (enableValidationLayers) {
-					createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
-					createInfo.ppEnabledLayerNames = validationLayers.data();
-				}
-				else {
-					createInfo.enabledLayerCount = 0;
-					createInfo.ppEnabledLayerNames = nullptr;
-				}
+	std::vector<const char*> extensions = {};	// can add more here
+	size_t addition_extension_count = extensions.size();
+	extensions.resize(addition_extension_count + count);
 
-				if (vkCreateInstance(
-					&createInfo, 
-					nullptr, 
-					&instance) != VK_SUCCESS) {
-					throw love::Exception("couldn't create vulkan instance");
-				}
-			}
+	if (SDL_Vulkan_GetInstanceExtensions((SDL_Window*)handle, &count, extensions.data() + addition_extension_count) != SDL_TRUE) {
+		throw love::Exception("couldn't retrieve sdl vulkan extensions");
+	}
 
-			bool Graphics::checkValidationSupport() {
-				uint32_t layerCount;
-				vkEnumerateInstanceLayerProperties(&layerCount, nullptr);
+	createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
+	createInfo.ppEnabledExtensionNames = extensions.data();
 
-				std::vector<VkLayerProperties> availableLayers(layerCount);
-				vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data());
+	if (enableValidationLayers) {
+		createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
+		createInfo.ppEnabledLayerNames = validationLayers.data();
+	}
+	else {
+		createInfo.enabledLayerCount = 0;
+		createInfo.ppEnabledLayerNames = nullptr;
+	}
 
-				for (const char* layerName : validationLayers) {
-					bool layerFound = false;
+	if (vkCreateInstance(
+		&createInfo, 
+		nullptr, 
+		&instance) != VK_SUCCESS) {
+		throw love::Exception("couldn't create vulkan instance");
+	}
+}
 
-					for (const auto& layerProperties : availableLayers) {
-						if (strcmp(layerName, layerProperties.layerName) == 0) {
-							layerFound = true;
-							break;
-						}
-					}
+bool Graphics::checkValidationSupport() {
+	uint32_t layerCount;
+	vkEnumerateInstanceLayerProperties(&layerCount, nullptr);
 
-					if (!layerFound) {
-						return false;
-					}
-				}
+	std::vector<VkLayerProperties> availableLayers(layerCount);
+	vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data());
+
+	for (const char* layerName : validationLayers) {
+		bool layerFound = false;
 
-				return true;
+		for (const auto& layerProperties : availableLayers) {
+			if (strcmp(layerName, layerProperties.layerName) == 0) {
+				layerFound = true;
+				break;
 			}
+		}
 
-			void Graphics::pickPhysicalDevice() {
-				uint32_t deviceCount = 0;
-				vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
+		if (!layerFound) {
+			return false;
+		}
+	}
 
-				if (deviceCount == 0) {
-					throw love::Exception("failed to find GPUs with Vulkan support");
-				}
+	return true;
+}
 
-				std::vector<VkPhysicalDevice> devices(deviceCount);
-				vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
+void Graphics::pickPhysicalDevice() {
+	uint32_t deviceCount = 0;
+	vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
 
-				std::multimap<int, VkPhysicalDevice> candidates;
+	if (deviceCount == 0) {
+		throw love::Exception("failed to find GPUs with Vulkan support");
+	}
 
-				for (const auto& device : devices) {
-					int score = rateDeviceSuitability(device);
-					candidates.insert(std::make_pair(score, device));
-				}
+	std::vector<VkPhysicalDevice> devices(deviceCount);
+	vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
 
-				if (candidates.rbegin()->first > 0) {
-					physicalDevice = candidates.rbegin()->second;
-				}
-				else {
-					throw love::Exception("failed to find a suitable gpu");
-				}
+	std::multimap<int, VkPhysicalDevice> candidates;
 
-				VkPhysicalDeviceProperties properties;
-				vkGetPhysicalDeviceProperties(physicalDevice, &properties);
-				minUniformBufferOffsetAlignment = properties.limits.minUniformBufferOffsetAlignment;
-			}
+	for (const auto& device : devices) {
+		int score = rateDeviceSuitability(device);
+		candidates.insert(std::make_pair(score, device));
+	}
 
-			bool Graphics::checkDeviceExtensionSupport(VkPhysicalDevice device) {
-				uint32_t extensionCount;
-				vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr);
+	if (candidates.rbegin()->first > 0) {
+		physicalDevice = candidates.rbegin()->second;
+	}
+	else {
+		throw love::Exception("failed to find a suitable gpu");
+	}
 
-				std::vector<VkExtensionProperties> availableExtensions(extensionCount);
-				vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data());
+	VkPhysicalDeviceProperties properties;
+	vkGetPhysicalDeviceProperties(physicalDevice, &properties);
+	minUniformBufferOffsetAlignment = properties.limits.minUniformBufferOffsetAlignment;
+}
 
-				std::set<std::string> requiredExtensions(deviceExtensions.begin(), deviceExtensions.end());
+bool Graphics::checkDeviceExtensionSupport(VkPhysicalDevice device) {
+	uint32_t extensionCount;
+	vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr);
 
-				for (const auto& extension : availableExtensions) {
-					requiredExtensions.erase(extension.extensionName);
-				}
+	std::vector<VkExtensionProperties> availableExtensions(extensionCount);
+	vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data());
 
-				return requiredExtensions.empty();
-			}
+	std::set<std::string> requiredExtensions(deviceExtensions.begin(), deviceExtensions.end());
 
-			// if the score is nonzero then the device is suitable.
-			// A higher rating means generally better performance
-			// if the score is 0 the device is unsuitable
-			int Graphics::rateDeviceSuitability(VkPhysicalDevice device) {
-				VkPhysicalDeviceProperties deviceProperties;
-				VkPhysicalDeviceFeatures deviceFeatures;
-				vkGetPhysicalDeviceProperties(device, &deviceProperties);
-				vkGetPhysicalDeviceFeatures(device, &deviceFeatures);
+	for (const auto& extension : availableExtensions) {
+		requiredExtensions.erase(extension.extensionName);
+	}
 
-				int score = 1;
+	return requiredExtensions.empty();
+}
 
-				// optional 
+// if the score is nonzero then the device is suitable.
+// A higher rating means generally better performance
+// if the score is 0 the device is unsuitable
+int Graphics::rateDeviceSuitability(VkPhysicalDevice device) {
+	VkPhysicalDeviceProperties deviceProperties;
+	VkPhysicalDeviceFeatures deviceFeatures;
+	vkGetPhysicalDeviceProperties(device, &deviceProperties);
+	vkGetPhysicalDeviceFeatures(device, &deviceFeatures);
 
-				if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
-					score += 1000;
-				}
-				if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU) {
-					score += 100;
-				}
-				if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU) {
-					score += 10;
-				}
+	int score = 1;
 
-				// definitely needed
+	// optional 
 
-				QueueFamilyIndices indices = findQueueFamilies(device);
-				if (!indices.isComplete()) {
-					score = 0;
-				}
+	if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
+		score += 1000;
+	}
+	if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU) {
+		score += 100;
+	}
+	if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU) {
+		score += 10;
+	}
 
-				bool extensionsSupported = checkDeviceExtensionSupport(device);
-				if (!extensionsSupported) {
-					score = 0;
-				}
+	// definitely needed
 
-				if (extensionsSupported) {
-					auto swapChainSupport = querySwapChainSupport(device);
-					bool swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty();
-					if (!swapChainAdequate) {
-						score = 0;
-					}
-				}
+	QueueFamilyIndices indices = findQueueFamilies(device);
+	if (!indices.isComplete()) {
+		score = 0;
+	}
 
-				if (!deviceFeatures.samplerAnisotropy) {
-					score = 0;
-				}
+	bool extensionsSupported = checkDeviceExtensionSupport(device);
+	if (!extensionsSupported) {
+		score = 0;
+	}
 
-				if (!deviceFeatures.fillModeNonSolid) {
-					score = 0;
-				}
+	if (extensionsSupported) {
+		auto swapChainSupport = querySwapChainSupport(device);
+		bool swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty();
+		if (!swapChainAdequate) {
+			score = 0;
+		}
+	}
 
-				return score;
-			}
+	if (!deviceFeatures.samplerAnisotropy) {
+		score = 0;
+	}
 
-			QueueFamilyIndices Graphics::findQueueFamilies(VkPhysicalDevice device) {
-				QueueFamilyIndices indices;
+	if (!deviceFeatures.fillModeNonSolid) {
+		score = 0;
+	}
 
-				uint32_t queueFamilyCount = 0;
-				vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);
+	return score;
+}
 
-				std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
-				vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());
+QueueFamilyIndices Graphics::findQueueFamilies(VkPhysicalDevice device) {
+	QueueFamilyIndices indices;
 
-				int i = 0;
-				for (const auto& queueFamily : queueFamilies) {
-					if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
-						indices.graphicsFamily = i;
-					}
+	uint32_t queueFamilyCount = 0;
+	vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);
 
-					VkBool32 presentSupport = false;
-					vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport);
+	std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
+	vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());
 
-					if (presentSupport) {
-						indices.presentFamily = i;
-					}
+	int i = 0;
+	for (const auto& queueFamily : queueFamilies) {
+		if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
+			indices.graphicsFamily = i;
+		}
 
-					if (indices.isComplete()) {
-						break;
-					}
+		VkBool32 presentSupport = false;
+		vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport);
 
-					i++;
-				}
+		if (presentSupport) {
+			indices.presentFamily = i;
+		}
 
-				return indices;
-			}
+		if (indices.isComplete()) {
+			break;
+		}
 
-			void Graphics::createLogicalDevice() {
-				QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
+		i++;
+	}
 
-				std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
-				std::set<uint32_t> uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() };
+	return indices;
+}
 
-				float queuePriority = 1.0f;
-				for (uint32_t queueFamily : uniqueQueueFamilies) {
-					VkDeviceQueueCreateInfo queueCreateInfo{};
-					queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
-					queueCreateInfo.queueFamilyIndex = queueFamily;
-					queueCreateInfo.queueCount = 1;
-					queueCreateInfo.pQueuePriorities = &queuePriority;
-					queueCreateInfos.push_back(queueCreateInfo);
-				}
+void Graphics::createLogicalDevice() {
+	QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
 
-				VkPhysicalDeviceDynamicRenderingFeatures dynamicRenderingFeature{};
-				dynamicRenderingFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES;
-				dynamicRenderingFeature.dynamicRendering = VK_TRUE;
+	std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
+	std::set<uint32_t> uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() };
 
-				VkPhysicalDeviceFeatures deviceFeatures{};
-				deviceFeatures.samplerAnisotropy = VK_TRUE;
+	float queuePriority = 1.0f;
+	for (uint32_t queueFamily : uniqueQueueFamilies) {
+		VkDeviceQueueCreateInfo queueCreateInfo{};
+		queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+		queueCreateInfo.queueFamilyIndex = queueFamily;
+		queueCreateInfo.queueCount = 1;
+		queueCreateInfo.pQueuePriorities = &queuePriority;
+		queueCreateInfos.push_back(queueCreateInfo);
+	}
 
-				VkDeviceCreateInfo createInfo{};
-				createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
-				createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());
-				createInfo.pQueueCreateInfos = queueCreateInfos.data();
-				createInfo.pEnabledFeatures = &deviceFeatures;
-				createInfo.pNext = &dynamicRenderingFeature;
+	VkPhysicalDeviceDynamicRenderingFeatures dynamicRenderingFeature{};
+	dynamicRenderingFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES;
+	dynamicRenderingFeature.dynamicRendering = VK_TRUE;
 
-				createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
-				createInfo.ppEnabledExtensionNames = deviceExtensions.data();
+	VkPhysicalDeviceFeatures deviceFeatures{};
+	deviceFeatures.samplerAnisotropy = VK_TRUE;
 
-				// can this be removed?
-				if (enableValidationLayers) {
-					createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
-					createInfo.ppEnabledLayerNames = validationLayers.data();
-				}
-				else {
-					createInfo.enabledLayerCount = 0;
-				}
+	VkDeviceCreateInfo createInfo{};
+	createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
+	createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());
+	createInfo.pQueueCreateInfos = queueCreateInfos.data();
+	createInfo.pEnabledFeatures = &deviceFeatures;
+	createInfo.pNext = &dynamicRenderingFeature;
 
-				if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {
-					throw love::Exception("failed to create logical device");
-				}
+	createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
+	createInfo.ppEnabledExtensionNames = deviceExtensions.data();
 
-				vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue);
-				vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue);
+	// can this be removed?
+	if (enableValidationLayers) {
+		createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
+		createInfo.ppEnabledLayerNames = validationLayers.data();
+	}
+	else {
+		createInfo.enabledLayerCount = 0;
+	}
 
-				vkCmdPushDescriptorSet = (PFN_vkCmdPushDescriptorSetKHR) vkGetDeviceProcAddr(device, "vkCmdPushDescriptorSetKHR");
-				if (!vkCmdPushDescriptorSet) {
-					// fixme: how widely adopted is this extension?
-					throw love::Exception("could not get a valid function pointer for vkCmdPushDescriptorSetKHR");
-				}
-			}
+	if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {
+		throw love::Exception("failed to create logical device");
+	}
 
-			void Graphics::initVMA() {
-				VmaVulkanFunctions vulkanFunctions = {};
-				vulkanFunctions.vkGetInstanceProcAddr = &vkGetInstanceProcAddr;
-				vulkanFunctions.vkGetDeviceProcAddr = &vkGetDeviceProcAddr;
+	vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue);
+	vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue);
 
-				VmaAllocatorCreateInfo allocatorCreateInfo = {};
-				allocatorCreateInfo.vulkanApiVersion = vulkanApiVersion;
-				allocatorCreateInfo.physicalDevice = physicalDevice;
-				allocatorCreateInfo.device = device;
-				allocatorCreateInfo.instance = instance;
-				allocatorCreateInfo.pVulkanFunctions = &vulkanFunctions;
+	vkCmdPushDescriptorSet = (PFN_vkCmdPushDescriptorSetKHR) vkGetDeviceProcAddr(device, "vkCmdPushDescriptorSetKHR");
+	if (!vkCmdPushDescriptorSet) {
+		// fixme: how widely adopted is this extension?
+		throw love::Exception("could not get a valid function pointer for vkCmdPushDescriptorSetKHR");
+	}
+}
 
-				if (vmaCreateAllocator(&allocatorCreateInfo, &vmaAllocator) != VK_SUCCESS) {
-					throw love::Exception("failed to create vma allocator");
-				}
-			}
+void Graphics::initVMA() {
+	VmaVulkanFunctions vulkanFunctions = {};
+	vulkanFunctions.vkGetInstanceProcAddr = &vkGetInstanceProcAddr;
+	vulkanFunctions.vkGetDeviceProcAddr = &vkGetDeviceProcAddr;
 
-			void Graphics::createSurface() {
-				auto window = Module::getInstance<love::window::Window>(M_WINDOW);
-				const void* handle = window->getHandle();
-				if (SDL_Vulkan_CreateSurface((SDL_Window*)handle, instance, &surface) != SDL_TRUE) {
-					throw love::Exception("failed to create window surface");
-				}
-			}
+	VmaAllocatorCreateInfo allocatorCreateInfo = {};
+	allocatorCreateInfo.vulkanApiVersion = vulkanApiVersion;
+	allocatorCreateInfo.physicalDevice = physicalDevice;
+	allocatorCreateInfo.device = device;
+	allocatorCreateInfo.instance = instance;
+	allocatorCreateInfo.pVulkanFunctions = &vulkanFunctions;
 
-			SwapChainSupportDetails Graphics::querySwapChainSupport(VkPhysicalDevice device) {
-				SwapChainSupportDetails details;
+	if (vmaCreateAllocator(&allocatorCreateInfo, &vmaAllocator) != VK_SUCCESS) {
+		throw love::Exception("failed to create vma allocator");
+	}
+}
 
-				vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities);
+void Graphics::createSurface() {
+	auto window = Module::getInstance<love::window::Window>(M_WINDOW);
+	const void* handle = window->getHandle();
+	if (SDL_Vulkan_CreateSurface((SDL_Window*)handle, instance, &surface) != SDL_TRUE) {
+		throw love::Exception("failed to create window surface");
+	}
+}
 
-				uint32_t formatCount;
-				vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr);
+SwapChainSupportDetails Graphics::querySwapChainSupport(VkPhysicalDevice device) {
+	SwapChainSupportDetails details;
 
-				if (formatCount != 0) {
-					details.formats.resize(formatCount);
-					vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data());
-				}
+	vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities);
 
-				uint32_t presentModeCount;
-				vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr);
+	uint32_t formatCount;
+	vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr);
 
-				if (presentModeCount != 0) {
-					details.presentModes.resize(presentModeCount);
-					vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data());
-				}
+	if (formatCount != 0) {
+		details.formats.resize(formatCount);
+		vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data());
+	}
 
-				return details;
-			}
+	uint32_t presentModeCount;
+	vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr);
 
-			void Graphics::createSwapChain() {
-				SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice);
+	if (presentModeCount != 0) {
+		details.presentModes.resize(presentModeCount);
+		vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data());
+	}
 
-				VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats);
-				VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes);
-				VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities);
+	return details;
+}
 
-				uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1;
-				if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) {
-					imageCount = swapChainSupport.capabilities.maxImageCount;
-				}
+void Graphics::createSwapChain() {
+	SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice);
 
-				VkSwapchainCreateInfoKHR createInfo{};
-				createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
-				createInfo.surface = surface;
+	VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats);
+	VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes);
+	VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities);
 
-				createInfo.minImageCount = imageCount;
-				createInfo.imageFormat = surfaceFormat.format;
-				createInfo.imageColorSpace = surfaceFormat.colorSpace;
-				createInfo.imageExtent = extent;
-				createInfo.imageArrayLayers = 1;
-				createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
+	uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1;
+	if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) {
+		imageCount = swapChainSupport.capabilities.maxImageCount;
+	}
 
-				QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
-				uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() };
+	VkSwapchainCreateInfoKHR createInfo{};
+	createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
+	createInfo.surface = surface;
 
-				if (indices.graphicsFamily != indices.presentFamily) {
-					createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
-					createInfo.queueFamilyIndexCount = 2;
-					createInfo.pQueueFamilyIndices = queueFamilyIndices;
-				}
-				else {
-					createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
-					createInfo.queueFamilyIndexCount = 0;
-					createInfo.pQueueFamilyIndices = nullptr;
-				}
+	createInfo.minImageCount = imageCount;
+	createInfo.imageFormat = surfaceFormat.format;
+	createInfo.imageColorSpace = surfaceFormat.colorSpace;
+	createInfo.imageExtent = extent;
+	createInfo.imageArrayLayers = 1;
+	createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
 
-				createInfo.preTransform = swapChainSupport.capabilities.currentTransform;
-				createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
-				createInfo.presentMode = presentMode;
-				createInfo.clipped = VK_TRUE;
-				createInfo.oldSwapchain = VK_NULL_HANDLE;
+	QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
+	uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(), indices.presentFamily.value() };
 
-				if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) {
-					throw love::Exception("failed to create swap chain");
-				}
+	if (indices.graphicsFamily != indices.presentFamily) {
+		createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
+		createInfo.queueFamilyIndexCount = 2;
+		createInfo.pQueueFamilyIndices = queueFamilyIndices;
+	}
+	else {
+		createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
+		createInfo.queueFamilyIndexCount = 0;
+		createInfo.pQueueFamilyIndices = nullptr;
+	}
 
-				vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr);
-				swapChainImages.resize(imageCount);
-				vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data());
+	createInfo.preTransform = swapChainSupport.capabilities.currentTransform;
+	createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
+	createInfo.presentMode = presentMode;
+	createInfo.clipped = VK_TRUE;
+	createInfo.oldSwapchain = VK_NULL_HANDLE;
 
-				swapChainImageFormat = surfaceFormat.format;
-				swapChainExtent = extent;
-			}
+	if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) {
+		throw love::Exception("failed to create swap chain");
+	}
 
-			VkSurfaceFormatKHR Graphics::chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats) {
-				for (const auto& availableFormat : availableFormats) {
-					// fixme: what if this format and colorspace is not available?
-					if (availableFormat.format == VK_FORMAT_B8G8R8A8_UNORM && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
-						return availableFormat;
-					}
-				}
+	vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr);
+	swapChainImages.resize(imageCount);
+	vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data());
 
-				return availableFormats[0];
-			}
+	swapChainImageFormat = surfaceFormat.format;
+	swapChainExtent = extent;
+}
 
-			VkPresentModeKHR Graphics::chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& availablePresentModes) {
-				// needed ?
-				for (const auto& availablePresentMode : availablePresentModes) {
-					if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
-						return availablePresentMode;
-					}
-				}
+VkSurfaceFormatKHR Graphics::chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats) {
+	for (const auto& availableFormat : availableFormats) {
+		// fixme: what if this format and colorspace is not available?
+		if (availableFormat.format == VK_FORMAT_B8G8R8A8_UNORM && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
+			return availableFormat;
+		}
+	}
 
-				return VK_PRESENT_MODE_FIFO_KHR;
-			}
+	return availableFormats[0];
+}
 
-			VkExtent2D Graphics::chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {
-				if (capabilities.currentExtent.width != UINT32_MAX) {
-					return capabilities.currentExtent;
-				}
-				else {
-					auto window = Module::getInstance<love::window::Window>(M_WINDOW);
-					const void* handle = window->getHandle();
+VkPresentModeKHR Graphics::chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& availablePresentModes) {
+	// needed ?
+	for (const auto& availablePresentMode : availablePresentModes) {
+		if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
+			return availablePresentMode;
+		}
+	}
 
-					int width, height;
-					// is this the equivalent of glfwGetFramebufferSize ?
-					SDL_Vulkan_GetDrawableSize((SDL_Window*)handle, &width, &height);
+	return VK_PRESENT_MODE_FIFO_KHR;
+}
 
-					VkExtent2D actualExtent = {
-						static_cast<uint32_t>(width),
-						static_cast<uint32_t>(height)
-					};
+VkExtent2D Graphics::chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {
+	if (capabilities.currentExtent.width != UINT32_MAX) {
+		return capabilities.currentExtent;
+	}
+	else {
+		auto window = Module::getInstance<love::window::Window>(M_WINDOW);
+		const void* handle = window->getHandle();
 
-					actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width);
-					actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height);
+		int width, height;
+		// is this the equivalent of glfwGetFramebufferSize ?
+		SDL_Vulkan_GetDrawableSize((SDL_Window*)handle, &width, &height);
 
-					return actualExtent;
-				}
-			}
+		VkExtent2D actualExtent = {
+			static_cast<uint32_t>(width),
+			static_cast<uint32_t>(height)
+		};
 
-			void Graphics::createImageViews() {
-				swapChainImageViews.resize(swapChainImages.size());
-
-				for (size_t i = 0; i < swapChainImages.size(); i++) {
-					VkImageViewCreateInfo createInfo{};
-					createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
-					createInfo.image = swapChainImages.at(i);
-					createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
-					createInfo.format = swapChainImageFormat;
-					createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
-					createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
-					createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
-					createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
-					createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
-					createInfo.subresourceRange.baseMipLevel = 0;
-					createInfo.subresourceRange.levelCount = 1;
-					createInfo.subresourceRange.baseArrayLayer = 0;
-					createInfo.subresourceRange.layerCount = 1;
-
-					if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews.at(i)) != VK_SUCCESS) {
-						throw love::Exception("failed to create image views");
-					}
-				}
-			}
+		actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width);
+		actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height);
 
-			void Graphics::createDefaultShaders() {
-				for (int i = 0; i < Shader::STANDARD_MAX_ENUM; i++) {
-					auto stype = (Shader::StandardShader)i;
+		return actualExtent;
+	}
+}
 
-					if (!Shader::standardShaders[i]) {
-						std::vector<std::string> stages;
-						stages.push_back(Shader::getDefaultCode(stype, SHADERSTAGE_VERTEX));
-						stages.push_back(Shader::getDefaultCode(stype, SHADERSTAGE_PIXEL));
-						Shader::standardShaders[i] = newShader(stages, {});
-					}
-				}
-			}
+void Graphics::createImageViews() {
+	swapChainImageViews.resize(swapChainImages.size());
+
+	for (size_t i = 0; i < swapChainImages.size(); i++) {
+		VkImageViewCreateInfo createInfo{};
+		createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+		createInfo.image = swapChainImages.at(i);
+		createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+		createInfo.format = swapChainImageFormat;
+		createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
+		createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
+		createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
+		createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
+		createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		createInfo.subresourceRange.baseMipLevel = 0;
+		createInfo.subresourceRange.levelCount = 1;
+		createInfo.subresourceRange.baseArrayLayer = 0;
+		createInfo.subresourceRange.layerCount = 1;
+
+		if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews.at(i)) != VK_SUCCESS) {
+			throw love::Exception("failed to create image views");
+		}
+	}
+}
 
-			void Graphics::createVulkanVertexFormat(
-				VertexAttributes vertexAttributes, 
-				bool& useConstantVertexColor,
-				GraphicsPipelineConfiguration& configuration) {
-				std::set<uint32_t> usedBuffers;
-				std::vector<VkVertexInputBindingDescription> bindingDescriptions;
-				std::vector<VkVertexInputAttributeDescription> attributeDescriptions;
-
-				auto allBits = vertexAttributes.enableBits;
-
-				bool usesColor = false;
-
-				uint8_t highestBufferBinding = 0;
-				
-				for (uint32_t i = 0; i < VertexAttributes::MAX; i++) {	// change to loop like in opengl implementation ?
-					uint32 bit = 1u << i;
-					if (allBits & bit) {
-						if (i == ATTRIB_COLOR) {
-							usesColor = true;
-						}
-
-						auto attrib = vertexAttributes.attribs[i];
-						auto bufferBinding = attrib.bufferIndex;
-						if (usedBuffers.find(bufferBinding) == usedBuffers.end()) {	// use .contains() when c++20 is enabled
-							usedBuffers.insert(bufferBinding);
-
-							VkVertexInputBindingDescription bindingDescription{};
-							bindingDescription.binding = bufferBinding;
-							if (vertexAttributes.instanceBits & (1u << bufferBinding)) {
-								bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_INSTANCE;
-							}
-							else {
-								bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
-							}
-							bindingDescription.stride = vertexAttributes.bufferLayouts[bufferBinding].stride;
-							bindingDescriptions.push_back(bindingDescription);
-
-							highestBufferBinding = std::max(highestBufferBinding, bufferBinding);
-						}
-
-						VkVertexInputAttributeDescription attributeDescription{};
-						attributeDescription.location = i;
-						attributeDescription.binding = bufferBinding;
-						attributeDescription.offset = attrib.offsetFromVertex;
-						attributeDescription.format = Vulkan::getVulkanVertexFormat(attrib.format);
-
-						attributeDescriptions.push_back(attributeDescription);
-					}
-				}
+void Graphics::createDefaultShaders() {
+	for (int i = 0; i < Shader::STANDARD_MAX_ENUM; i++) {
+		auto stype = (Shader::StandardShader)i;
 
-				// do we need to use a constant VertexColor?
-				if (!usesColor) {
-					// FIXME: is there a case where gaps happen between buffer bindings?
-					// then this doesn't work. We might need to enable null buffers again.
-					const auto constantColorBufferBinding = highestBufferBinding + 1;
+		if (!Shader::standardShaders[i]) {
+			std::vector<std::string> stages;
+			stages.push_back(Shader::getDefaultCode(stype, SHADERSTAGE_VERTEX));
+			stages.push_back(Shader::getDefaultCode(stype, SHADERSTAGE_PIXEL));
+			Shader::standardShaders[i] = newShader(stages, {});
+		}
+	}
+}
 
-					VkVertexInputBindingDescription bindingDescription{};
-					bindingDescription.binding = constantColorBufferBinding;
-					bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
-					bindingDescription.stride = 0;	// no stride, will always read the same color multiple times.
-					bindingDescriptions.push_back(bindingDescription);
+void Graphics::createVulkanVertexFormat(
+	VertexAttributes vertexAttributes, 
+	bool& useConstantVertexColor,
+	GraphicsPipelineConfiguration& configuration) {
+	std::set<uint32_t> usedBuffers;
+	std::vector<VkVertexInputBindingDescription> bindingDescriptions;
+	std::vector<VkVertexInputAttributeDescription> attributeDescriptions;
 
-					VkVertexInputAttributeDescription attributeDescription{};
-					attributeDescription.binding = constantColorBufferBinding;
-					attributeDescription.location = ATTRIB_COLOR;
-					attributeDescription.offset = 0;
-					attributeDescription.format = VK_FORMAT_R32G32B32A32_SFLOAT;
-					attributeDescriptions.push_back(attributeDescription);
+	auto allBits = vertexAttributes.enableBits;
 
-					useConstantVertexColor = true;
-				}
-				else {
-					useConstantVertexColor = false;
-				}
+	bool usesColor = false;
 
-				configuration.vertexInputBindingDescriptions = bindingDescriptions;
-				configuration.vertexInputAttributeDescriptions = attributeDescriptions;
+	uint8_t highestBufferBinding = 0;
+	
+	for (uint32_t i = 0; i < VertexAttributes::MAX; i++) {	// change to loop like in opengl implementation ?
+		uint32 bit = 1u << i;
+		if (allBits & bit) {
+			if (i == ATTRIB_COLOR) {
+				usesColor = true;
 			}
 
-			void Graphics::prepareDraw(const VertexAttributes& attributes, const BufferBindings& buffers, graphics::Texture* texture, PrimitiveType primitiveType, CullMode cullmode) {
-				GraphicsPipelineConfiguration configuration;
-				configuration.shader = (Shader*)Shader::current;
-				configuration.primitiveType = primitiveType;
-				configuration.polygonMode = currentPolygonMode;
-				configuration.blendState = states.back().blend;
-				configuration.winding = states.back().winding;
-				configuration.cullmode = cullmode;
-				configuration.framebufferFormat = currentFramebufferOutputFormat;
-				configuration.viewportWidth = currentViewportWidth;
-				configuration.viewportHeight = currentViewportHeight;
-				if (states.back().scissor) {
-					configuration.scissorRect = states.back().scissorRect;
+			auto attrib = vertexAttributes.attribs[i];
+			auto bufferBinding = attrib.bufferIndex;
+			if (usedBuffers.find(bufferBinding) == usedBuffers.end()) {	// use .contains() when c++20 is enabled
+				usedBuffers.insert(bufferBinding);
+
+				VkVertexInputBindingDescription bindingDescription{};
+				bindingDescription.binding = bufferBinding;
+				if (vertexAttributes.instanceBits & (1u << bufferBinding)) {
+					bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_INSTANCE;
 				}
 				else {
-					configuration.scissorRect = std::nullopt;
+					bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
 				}
-				std::vector<VkBuffer> bufferVector;
+				bindingDescription.stride = vertexAttributes.bufferLayouts[bufferBinding].stride;
+				bindingDescriptions.push_back(bindingDescription);
 
-				std::vector<VkDeviceSize> offsets;
+				highestBufferBinding = std::max(highestBufferBinding, bufferBinding);
+			}
 
-				bool useConstantColorBuffer;
-				createVulkanVertexFormat(attributes, useConstantColorBuffer, configuration);
+			VkVertexInputAttributeDescription attributeDescription{};
+			attributeDescription.location = i;
+			attributeDescription.binding = bufferBinding;
+			attributeDescription.offset = attrib.offsetFromVertex;
+			attributeDescription.format = Vulkan::getVulkanVertexFormat(attrib.format);
 
-				for (uint32_t i = 0; i < VertexAttributes::MAX; i++) {
-					if (buffers.useBits & (1u << i)) {
-						bufferVector.push_back((VkBuffer)buffers.info[i].buffer->getHandle());
-						offsets.push_back((VkDeviceSize)buffers.info[i].offset);
-					}
-				}
+			attributeDescriptions.push_back(attributeDescription);
+		}
+	}
 
-				if (useConstantColorBuffer) {
-					bufferVector.push_back((VkBuffer)batchedDrawBuffers[currentFrame].constantColorBuffer->getHandle());
-					offsets.push_back((VkDeviceSize)0);
-				}
+	// do we need to use a constant VertexColor?
+	if (!usesColor) {
+		// FIXME: is there a case where gaps happen between buffer bindings?
+		// then this doesn't work. We might need to enable null buffers again.
+		const auto constantColorBufferBinding = highestBufferBinding + 1;
+
+		VkVertexInputBindingDescription bindingDescription{};
+		bindingDescription.binding = constantColorBufferBinding;
+		bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
+		bindingDescription.stride = 0;	// no stride, will always read the same color multiple times.
+		bindingDescriptions.push_back(bindingDescription);
+
+		VkVertexInputAttributeDescription attributeDescription{};
+		attributeDescription.binding = constantColorBufferBinding;
+		attributeDescription.location = ATTRIB_COLOR;
+		attributeDescription.offset = 0;
+		attributeDescription.format = VK_FORMAT_R32G32B32A32_SFLOAT;
+		attributeDescriptions.push_back(attributeDescription);
+
+		useConstantVertexColor = true;
+	}
+	else {
+		useConstantVertexColor = false;
+	}
 
-				auto currentUniformData = getCurrentBuiltinUniformData();
-				configuration.shader->setUniformData(currentUniformData);
-				if (texture == nullptr) {
-					configuration.shader->setMainTex(standardTexture.get());
-				}
-				else {
-					configuration.shader->setMainTex(texture);
-				}
+	configuration.vertexInputBindingDescriptions = bindingDescriptions;
+	configuration.vertexInputAttributeDescriptions = attributeDescriptions;
+}
 
-				ensureGraphicsPipelineConfiguration(configuration);
+void Graphics::prepareDraw(const VertexAttributes& attributes, const BufferBindings& buffers, graphics::Texture* texture, PrimitiveType primitiveType, CullMode cullmode) {
+	GraphicsPipelineConfiguration configuration;
+	configuration.shader = (Shader*)Shader::current;
+	configuration.primitiveType = primitiveType;
+	configuration.polygonMode = currentPolygonMode;
+	configuration.blendState = states.back().blend;
+	configuration.winding = states.back().winding;
+	configuration.cullmode = cullmode;
+	configuration.framebufferFormat = currentFramebufferOutputFormat;
+	configuration.viewportWidth = currentViewportWidth;
+	configuration.viewportHeight = currentViewportHeight;
+	if (states.back().scissor) {
+		configuration.scissorRect = states.back().scissorRect;
+	}
+	else {
+		configuration.scissorRect = std::nullopt;
+	}
+	std::vector<VkBuffer> bufferVector;
 
-				configuration.shader->cmdPushDescriptorSets(commandBuffers.at(imageIndex), static_cast<uint32_t>(currentFrame));
-				vkCmdBindVertexBuffers(commandBuffers.at(imageIndex), 0, static_cast<uint32_t>(bufferVector.size()), bufferVector.data(), offsets.data());
-			}
+	std::vector<VkDeviceSize> offsets;
 
-			void Graphics::startRenderPass(Texture* texture, uint32_t w, uint32_t h) {
-				VkRenderingAttachmentInfo colorAttachmentInfo{};
-				colorAttachmentInfo.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
-				colorAttachmentInfo.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
-				colorAttachmentInfo.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
-				if (texture) {
-					colorAttachmentInfo.imageView = texture->getImageView();
-					auto vulkanFormat = Vulkan::getTextureFormat(texture->getPixelFormat());
-					currentFramebufferOutputFormat = vulkanFormat.internalFormat;
-
-					renderTargetTexture = texture;
-				} else {
-					colorAttachmentInfo.imageView = swapChainImageViews[imageIndex];
-					currentFramebufferOutputFormat = swapChainImageFormat;
-
-					renderTargetTexture = nullptr;
-				}
-				colorAttachmentInfo.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+	bool useConstantColorBuffer;
+	createVulkanVertexFormat(attributes, useConstantColorBuffer, configuration);
 
-				VkRenderingInfo renderingInfo{};
-				renderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
-				renderingInfo.renderArea.extent.width = w;
-				renderingInfo.renderArea.extent.height = h;
-				renderingInfo.layerCount = 1;
-				renderingInfo.colorAttachmentCount = 1;
-				renderingInfo.pColorAttachments = &colorAttachmentInfo;
+	for (uint32_t i = 0; i < VertexAttributes::MAX; i++) {
+		if (buffers.useBits & (1u << i)) {
+			bufferVector.push_back((VkBuffer)buffers.info[i].buffer->getHandle());
+			offsets.push_back((VkDeviceSize)buffers.info[i].offset);
+		}
+	}
 
-				currentViewportWidth = (float)w;
-				currentViewportHeight = (float)h;
+	if (useConstantColorBuffer) {
+		bufferVector.push_back((VkBuffer)batchedDrawBuffers[currentFrame].constantColorBuffer->getHandle());
+		offsets.push_back((VkDeviceSize)0);
+	}
 
-				if (renderTargetTexture) {
-					Vulkan::cmdTransitionImageLayout(commandBuffers.at(imageIndex), (VkImage)texture->getHandle(), VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
-				}
+	auto currentUniformData = getCurrentBuiltinUniformData();
+	configuration.shader->setUniformData(currentUniformData);
+	if (texture == nullptr) {
+		configuration.shader->setMainTex(standardTexture.get());
+	}
+	else {
+		configuration.shader->setMainTex(texture);
+	}
 
-				vkCmdBeginRendering(commandBuffers.at(imageIndex), &renderingInfo);
+	ensureGraphicsPipelineConfiguration(configuration);
 
-				currentGraphicsPipeline = VK_NULL_HANDLE;
-			}
+	configuration.shader->cmdPushDescriptorSets(commandBuffers.at(imageIndex), static_cast<uint32_t>(currentFrame));
+	vkCmdBindVertexBuffers(commandBuffers.at(imageIndex), 0, static_cast<uint32_t>(bufferVector.size()), bufferVector.data(), offsets.data());
+}
 
-			void Graphics::endRenderPass() {
-				vkCmdEndRendering(commandBuffers.at(imageIndex));
+void Graphics::startRenderPass(Texture* texture, uint32_t w, uint32_t h) {
+	VkRenderingAttachmentInfo colorAttachmentInfo{};
+	colorAttachmentInfo.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
+	colorAttachmentInfo.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+	colorAttachmentInfo.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+	if (texture) {
+		colorAttachmentInfo.imageView = texture->getImageView();
+		auto vulkanFormat = Vulkan::getTextureFormat(texture->getPixelFormat());
+		currentFramebufferOutputFormat = vulkanFormat.internalFormat;
+
+		renderTargetTexture = texture;
+	} else {
+		colorAttachmentInfo.imageView = swapChainImageViews[imageIndex];
+		currentFramebufferOutputFormat = swapChainImageFormat;
+
+		renderTargetTexture = nullptr;
+	}
+	colorAttachmentInfo.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
 
-				if (renderTargetTexture) {
-					Vulkan::cmdTransitionImageLayout(commandBuffers.at(imageIndex), (VkImage)renderTargetTexture->getHandle(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL);
-					renderTargetTexture = nullptr;
-				}
-			}
+	VkRenderingInfo renderingInfo{};
+	renderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
+	renderingInfo.renderArea.extent.width = w;
+	renderingInfo.renderArea.extent.height = h;
+	renderingInfo.layerCount = 1;
+	renderingInfo.colorAttachmentCount = 1;
+	renderingInfo.pColorAttachments = &colorAttachmentInfo;
 
-			VkPipeline Graphics::createGraphicsPipeline(GraphicsPipelineConfiguration configuration) {
-				auto &shaderStages = configuration.shader->getShaderStages();
-
-				VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
-				vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
-				vertexInputInfo.vertexBindingDescriptionCount = static_cast<uint32_t>(configuration.vertexInputBindingDescriptions.size());
-				vertexInputInfo.pVertexBindingDescriptions = configuration.vertexInputBindingDescriptions.data();
-				vertexInputInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(configuration.vertexInputAttributeDescriptions.size());
-				vertexInputInfo.pVertexAttributeDescriptions = configuration.vertexInputAttributeDescriptions.data();
-
-				VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
-				inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
-				inputAssembly.topology = Vulkan::getPrimitiveTypeTopology(configuration.primitiveType);
-				inputAssembly.primitiveRestartEnable = VK_FALSE;
-				
-				VkViewport viewport{};
-				viewport.x = 0.0f;
-				viewport.y = 0.0f;
-				viewport.width = configuration.viewportWidth;
-				viewport.height = configuration.viewportHeight;
-				viewport.minDepth = 0.0f;
-				viewport.maxDepth = 1.0f;
-
-				VkRect2D scissor{};
-				if (configuration.scissorRect.has_value()) {
-					scissor.offset.x = configuration.scissorRect.value().x;
-					scissor.offset.y = configuration.scissorRect.value().y;
-					scissor.extent.width = static_cast<uint32_t>(configuration.scissorRect.value().w);
-					scissor.extent.height = static_cast<uint32_t>(configuration.scissorRect.value().h);
-				}
-				else {
-					scissor.offset = { 0, 0 };
-					scissor.extent = swapChainExtent;
-				}
+	currentViewportWidth = (float)w;
+	currentViewportHeight = (float)h;
 
-				VkPipelineViewportStateCreateInfo viewportState{};
-				viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
-				viewportState.viewportCount = 1;
-				viewportState.pViewports = &viewport;
-				viewportState.scissorCount = 1;
-				viewportState.pScissors = &scissor;
-
-				VkPipelineRasterizationStateCreateInfo rasterizer{};
-				rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
-				rasterizer.depthClampEnable = VK_FALSE;
-				rasterizer.rasterizerDiscardEnable = VK_FALSE;
-				rasterizer.polygonMode = configuration.polygonMode;
-				rasterizer.lineWidth = 1.0f;
-				rasterizer.cullMode = Vulkan::getCullMode(configuration.cullmode);
-				rasterizer.frontFace = Vulkan::getFrontFace(configuration.winding);
-				rasterizer.depthBiasEnable = VK_FALSE;
-				rasterizer.depthBiasConstantFactor = 0.0f;
-				rasterizer.depthBiasClamp = 0.0f;
-				rasterizer.depthBiasSlopeFactor = 0.0f;
-
-				VkPipelineMultisampleStateCreateInfo multisampling{};
-				multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
-				multisampling.sampleShadingEnable = VK_FALSE;
-				multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
-				multisampling.minSampleShading = 1.0f; // Optional
-				multisampling.pSampleMask = nullptr; // Optional
-				multisampling.alphaToCoverageEnable = VK_FALSE; // Optional
-				multisampling.alphaToOneEnable = VK_FALSE; // Optional
-
-				VkPipelineColorBlendAttachmentState colorBlendAttachment{};
-				colorBlendAttachment.colorWriteMask = Vulkan::getColorMask(configuration.colorChannelMask);
-				colorBlendAttachment.blendEnable = Vulkan::getBool(configuration.blendState.enable);
-				colorBlendAttachment.srcColorBlendFactor = Vulkan::getBlendFactor(configuration.blendState.srcFactorRGB);
-				colorBlendAttachment.dstColorBlendFactor = Vulkan::getBlendFactor(configuration.blendState.dstFactorRGB);
-				colorBlendAttachment.colorBlendOp = Vulkan::getBlendOp(configuration.blendState.operationRGB);
-				colorBlendAttachment.srcAlphaBlendFactor = Vulkan::getBlendFactor(configuration.blendState.srcFactorA);
-				colorBlendAttachment.dstAlphaBlendFactor = Vulkan::getBlendFactor(configuration.blendState.dstFactorA);
-				colorBlendAttachment.alphaBlendOp = Vulkan::getBlendOp(configuration.blendState.operationA);
-
-				VkPipelineColorBlendStateCreateInfo colorBlending{};
-				colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
-				colorBlending.logicOpEnable = VK_FALSE;
-				colorBlending.logicOp = VK_LOGIC_OP_COPY;
-				colorBlending.attachmentCount = 1;
-				colorBlending.pAttachments = &colorBlendAttachment;
-				colorBlending.blendConstants[0] = 0.0f;
-				colorBlending.blendConstants[1] = 0.0f;
-				colorBlending.blendConstants[2] = 0.0f;
-				colorBlending.blendConstants[3] = 0.0f;
-
-				VkFormat framebufferOutputFormat = configuration.framebufferFormat;
-
-				VkPipelineRenderingCreateInfo pipelineRenderingCreateInfo{};
-				pipelineRenderingCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
-				pipelineRenderingCreateInfo.colorAttachmentCount = 1;
-				pipelineRenderingCreateInfo.pColorAttachmentFormats = &framebufferOutputFormat;
-
-				VkGraphicsPipelineCreateInfo pipelineInfo{};
-				pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
-				pipelineInfo.stageCount = static_cast<uint32_t>(shaderStages.size());
-				pipelineInfo.pStages = shaderStages.data();
-				pipelineInfo.pVertexInputState = &vertexInputInfo;
-				pipelineInfo.pInputAssemblyState = &inputAssembly;
-				pipelineInfo.pViewportState = &viewportState;
-				pipelineInfo.pRasterizationState = &rasterizer;
-				pipelineInfo.pMultisampleState = &multisampling;
-				pipelineInfo.pDepthStencilState = nullptr;
-				pipelineInfo.pColorBlendState = &colorBlending;
-				pipelineInfo.pDynamicState = nullptr;
-				pipelineInfo.layout = configuration.shader->getGraphicsPipelineLayout();
-				pipelineInfo.subpass = 0;
-				pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
-				pipelineInfo.basePipelineIndex = -1;
-				pipelineInfo.pNext = &pipelineRenderingCreateInfo;
-
-				VkPipeline graphicsPipeline;
-				if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) {
-					throw love::Exception("failed to create graphics pipeline");
-				}
-				return graphicsPipeline;
-			}
+	if (renderTargetTexture) {
+		Vulkan::cmdTransitionImageLayout(commandBuffers.at(imageIndex), (VkImage)texture->getHandle(), VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
+	}
 
-			void Graphics::ensureGraphicsPipelineConfiguration(GraphicsPipelineConfiguration configuration) {
-				VkPipeline pipeline = VK_NULL_HANDLE;
-				for (auto const& p : graphicsPipelines) {
-					if (p.first == configuration) {
-						pipeline = p.second;
-						break;
-					}
-				}
-				if (pipeline != VK_NULL_HANDLE) {
-					if (currentGraphicsPipeline != pipeline) {
-						vkCmdBindPipeline(commandBuffers.at(imageIndex), VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
-						currentGraphicsPipeline = pipeline;
-					}
-				} else {
-					VkPipeline newPipeLine = createGraphicsPipeline(configuration);
-					graphicsPipelines.push_back(std::make_pair(configuration, newPipeLine));
-					vkCmdBindPipeline(commandBuffers.at(imageIndex), VK_PIPELINE_BIND_POINT_GRAPHICS, newPipeLine);
-					currentGraphicsPipeline = newPipeLine;
-				}
-			}
+	vkCmdBeginRendering(commandBuffers.at(imageIndex), &renderingInfo);
 
-			void Graphics::createCommandPool() {
-				QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice);
+	currentGraphicsPipeline = VK_NULL_HANDLE;
+}
 
-				VkCommandPoolCreateInfo poolInfo{};
-				poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
-				poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value();
-				poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT | VK_COMMAND_POOL_CREATE_TRANSIENT_BIT;
+void Graphics::endRenderPass() {
+	vkCmdEndRendering(commandBuffers.at(imageIndex));
 
-				if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) {
-					throw love::Exception("failed to create command pool");
-				}
-			}
+	if (renderTargetTexture) {
+		Vulkan::cmdTransitionImageLayout(commandBuffers.at(imageIndex), (VkImage)renderTargetTexture->getHandle(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL);
+		renderTargetTexture = nullptr;
+	}
+}
 
-			void Graphics::createCommandBuffers() {
-				commandBuffers.resize(swapChainImages.size());
-				dataTransferCommandBuffers.resize(MAX_FRAMES_IN_FLIGHT);
+VkPipeline Graphics::createGraphicsPipeline(GraphicsPipelineConfiguration configuration) {
+	auto &shaderStages = configuration.shader->getShaderStages();
+
+	VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
+	vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
+	vertexInputInfo.vertexBindingDescriptionCount = static_cast<uint32_t>(configuration.vertexInputBindingDescriptions.size());
+	vertexInputInfo.pVertexBindingDescriptions = configuration.vertexInputBindingDescriptions.data();
+	vertexInputInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(configuration.vertexInputAttributeDescriptions.size());
+	vertexInputInfo.pVertexAttributeDescriptions = configuration.vertexInputAttributeDescriptions.data();
+
+	VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
+	inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
+	inputAssembly.topology = Vulkan::getPrimitiveTypeTopology(configuration.primitiveType);
+	inputAssembly.primitiveRestartEnable = VK_FALSE;
+	
+	VkViewport viewport{};
+	viewport.x = 0.0f;
+	viewport.y = 0.0f;
+	viewport.width = configuration.viewportWidth;
+	viewport.height = configuration.viewportHeight;
+	viewport.minDepth = 0.0f;
+	viewport.maxDepth = 1.0f;
+
+	VkRect2D scissor{};
+	if (configuration.scissorRect.has_value()) {
+		scissor.offset.x = configuration.scissorRect.value().x;
+		scissor.offset.y = configuration.scissorRect.value().y;
+		scissor.extent.width = static_cast<uint32_t>(configuration.scissorRect.value().w);
+		scissor.extent.height = static_cast<uint32_t>(configuration.scissorRect.value().h);
+	}
+	else {
+		scissor.offset = { 0, 0 };
+		scissor.extent = swapChainExtent;
+	}
 
-				VkCommandBufferAllocateInfo allocInfo{};
-				allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
-				allocInfo.commandPool = commandPool;
-				allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
-				allocInfo.commandBufferCount = (uint32_t)commandBuffers.size();
+	VkPipelineViewportStateCreateInfo viewportState{};
+	viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
+	viewportState.viewportCount = 1;
+	viewportState.pViewports = &viewport;
+	viewportState.scissorCount = 1;
+	viewportState.pScissors = &scissor;
+
+	VkPipelineRasterizationStateCreateInfo rasterizer{};
+	rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
+	rasterizer.depthClampEnable = VK_FALSE;
+	rasterizer.rasterizerDiscardEnable = VK_FALSE;
+	rasterizer.polygonMode = configuration.polygonMode;
+	rasterizer.lineWidth = 1.0f;
+	rasterizer.cullMode = Vulkan::getCullMode(configuration.cullmode);
+	rasterizer.frontFace = Vulkan::getFrontFace(configuration.winding);
+	rasterizer.depthBiasEnable = VK_FALSE;
+	rasterizer.depthBiasConstantFactor = 0.0f;
+	rasterizer.depthBiasClamp = 0.0f;
+	rasterizer.depthBiasSlopeFactor = 0.0f;
+
+	VkPipelineMultisampleStateCreateInfo multisampling{};
+	multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
+	multisampling.sampleShadingEnable = VK_FALSE;
+	multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
+	multisampling.minSampleShading = 1.0f; // Optional
+	multisampling.pSampleMask = nullptr; // Optional
+	multisampling.alphaToCoverageEnable = VK_FALSE; // Optional
+	multisampling.alphaToOneEnable = VK_FALSE; // Optional
+
+	VkPipelineColorBlendAttachmentState colorBlendAttachment{};
+	colorBlendAttachment.colorWriteMask = Vulkan::getColorMask(configuration.colorChannelMask);
+	colorBlendAttachment.blendEnable = Vulkan::getBool(configuration.blendState.enable);
+	colorBlendAttachment.srcColorBlendFactor = Vulkan::getBlendFactor(configuration.blendState.srcFactorRGB);
+	colorBlendAttachment.dstColorBlendFactor = Vulkan::getBlendFactor(configuration.blendState.dstFactorRGB);
+	colorBlendAttachment.colorBlendOp = Vulkan::getBlendOp(configuration.blendState.operationRGB);
+	colorBlendAttachment.srcAlphaBlendFactor = Vulkan::getBlendFactor(configuration.blendState.srcFactorA);
+	colorBlendAttachment.dstAlphaBlendFactor = Vulkan::getBlendFactor(configuration.blendState.dstFactorA);
+	colorBlendAttachment.alphaBlendOp = Vulkan::getBlendOp(configuration.blendState.operationA);
+
+	VkPipelineColorBlendStateCreateInfo colorBlending{};
+	colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
+	colorBlending.logicOpEnable = VK_FALSE;
+	colorBlending.logicOp = VK_LOGIC_OP_COPY;
+	colorBlending.attachmentCount = 1;
+	colorBlending.pAttachments = &colorBlendAttachment;
+	colorBlending.blendConstants[0] = 0.0f;
+	colorBlending.blendConstants[1] = 0.0f;
+	colorBlending.blendConstants[2] = 0.0f;
+	colorBlending.blendConstants[3] = 0.0f;
+
+	VkFormat framebufferOutputFormat = configuration.framebufferFormat;
+
+	VkPipelineRenderingCreateInfo pipelineRenderingCreateInfo{};
+	pipelineRenderingCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
+	pipelineRenderingCreateInfo.colorAttachmentCount = 1;
+	pipelineRenderingCreateInfo.pColorAttachmentFormats = &framebufferOutputFormat;
+
+	VkGraphicsPipelineCreateInfo pipelineInfo{};
+	pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
+	pipelineInfo.stageCount = static_cast<uint32_t>(shaderStages.size());
+	pipelineInfo.pStages = shaderStages.data();
+	pipelineInfo.pVertexInputState = &vertexInputInfo;
+	pipelineInfo.pInputAssemblyState = &inputAssembly;
+	pipelineInfo.pViewportState = &viewportState;
+	pipelineInfo.pRasterizationState = &rasterizer;
+	pipelineInfo.pMultisampleState = &multisampling;
+	pipelineInfo.pDepthStencilState = nullptr;
+	pipelineInfo.pColorBlendState = &colorBlending;
+	pipelineInfo.pDynamicState = nullptr;
+	pipelineInfo.layout = configuration.shader->getGraphicsPipelineLayout();
+	pipelineInfo.subpass = 0;
+	pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
+	pipelineInfo.basePipelineIndex = -1;
+	pipelineInfo.pNext = &pipelineRenderingCreateInfo;
+
+	VkPipeline graphicsPipeline;
+	if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) {
+		throw love::Exception("failed to create graphics pipeline");
+	}
+	return graphicsPipeline;
+}
 
-				if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) {
-					throw love::Exception("failed to allocate command buffers");
-				}
+void Graphics::ensureGraphicsPipelineConfiguration(GraphicsPipelineConfiguration configuration) {
+	VkPipeline pipeline = VK_NULL_HANDLE;
+	for (auto const& p : graphicsPipelines) {
+		if (p.first == configuration) {
+			pipeline = p.second;
+			break;
+		}
+	}
+	if (pipeline != VK_NULL_HANDLE) {
+		if (currentGraphicsPipeline != pipeline) {
+			vkCmdBindPipeline(commandBuffers.at(imageIndex), VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+			currentGraphicsPipeline = pipeline;
+		}
+	} else {
+		VkPipeline newPipeLine = createGraphicsPipeline(configuration);
+		graphicsPipelines.push_back(std::make_pair(configuration, newPipeLine));
+		vkCmdBindPipeline(commandBuffers.at(imageIndex), VK_PIPELINE_BIND_POINT_GRAPHICS, newPipeLine);
+		currentGraphicsPipeline = newPipeLine;
+	}
+}
 
-				VkCommandBufferAllocateInfo dataTransferAllocInfo{};
-				dataTransferAllocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
-				dataTransferAllocInfo.commandPool = commandPool;
-				dataTransferAllocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
-				dataTransferAllocInfo.commandBufferCount = (uint32_t)MAX_FRAMES_IN_FLIGHT;
+void Graphics::createCommandPool() {
+	QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice);
 
-				if (vkAllocateCommandBuffers(device, &dataTransferAllocInfo, dataTransferCommandBuffers.data()) != VK_SUCCESS) {
-					throw love::Exception("failed to allocate data transfer command buffers");
-				}
-			}
+	VkCommandPoolCreateInfo poolInfo{};
+	poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+	poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value();
+	poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT | VK_COMMAND_POOL_CREATE_TRANSIENT_BIT;
 
-			void Graphics::createSyncObjects() {
-				imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT);
-				renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT);
-				inFlightFences.resize(MAX_FRAMES_IN_FLIGHT);
-				imagesInFlight.resize(swapChainImages.size(), VK_NULL_HANDLE);
-
-				VkSemaphoreCreateInfo semaphoreInfo{};
-				semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
-
-				VkFenceCreateInfo fenceInfo{};
-				fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
-				fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
-
-				for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
-					if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores.at(i)) != VK_SUCCESS ||
-						vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores.at(i)) != VK_SUCCESS ||
-						vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences.at(i)) != VK_SUCCESS) {
-						throw love::Exception("failed to create synchronization objects for a frame!");
-					}
-				}
-			}
+	if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) {
+		throw love::Exception("failed to create command pool");
+	}
+}
 
-			void Graphics::createDefaultTexture() {
-				Texture::Settings settings;
-				standardTexture.reset((Texture*)newTexture(settings));
-			}
+void Graphics::createCommandBuffers() {
+	commandBuffers.resize(swapChainImages.size());
+	dataTransferCommandBuffers.resize(MAX_FRAMES_IN_FLIGHT);
 
-			void Graphics::createQuadIndexBuffer() {
-				if (quadIndexBuffer != nullptr)
-					return;
+	VkCommandBufferAllocateInfo allocInfo{};
+	allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+	allocInfo.commandPool = commandPool;
+	allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+	allocInfo.commandBufferCount = (uint32_t)commandBuffers.size();
 
-				size_t size = sizeof(uint16) * getIndexCount(TRIANGLEINDEX_QUADS, LOVE_UINT16_MAX);
-				quadIndexBuffer.reset((StreamBuffer*)newStreamBuffer(BUFFERUSAGE_INDEX, size));
-				auto map = quadIndexBuffer->map(size);
-				fillIndices(TRIANGLEINDEX_QUADS, 0, LOVE_UINT16_MAX, (uint16*)map.data);
-				quadIndexBuffer->unmap(size);
-			}
+	if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) {
+		throw love::Exception("failed to allocate command buffers");
+	}
 
-			bool operator==(const GraphicsPipelineConfiguration& first, const GraphicsPipelineConfiguration& other) {
-				if (first.scissorRect != other.scissorRect) {
-					return false;
-				}
-				if (first.viewportHeight != other.viewportHeight) {
-					return false;
-				}
-				if (first.viewportWidth != other.viewportWidth) {
-					return false;
-				}
-				if (first.framebufferFormat != other.framebufferFormat) {
-					return false;
-				}
-				if (first.cullmode != other.cullmode) {
-					return false;
-				}
-				if (first.winding != other.winding) {
-					return false;
-				}
-				if (first.colorChannelMask != other.colorChannelMask) {
-					return false;
-				}
-				if (!(first.blendState == other.blendState)) {	// not sure why != doesn't work
-					return false;
-				}
-				if (first.polygonMode != other.polygonMode) {
-					return false;
-				}
-				if (first.primitiveType != other.primitiveType) {
-					return false;
-				}
-				if (first.shader != other.shader) {
-					return false;
-				}
-				if (first.vertexInputAttributeDescriptions.size() != other.vertexInputAttributeDescriptions.size()) {
-					return false;
-				}
-				if (first.vertexInputBindingDescriptions.size() != other.vertexInputBindingDescriptions.size()) {
-					return false;
-				}
-				for (uint32_t i = 0; i < first.vertexInputAttributeDescriptions.size(); i++) {
-					const VkVertexInputAttributeDescription& x = first.vertexInputAttributeDescriptions[i];
-					const VkVertexInputAttributeDescription& y = other.vertexInputAttributeDescriptions[i];
-					if (x.binding != y.binding) {
-						return false;
-					}
-					if (x.location != y.location) {
-						return false;
-					}
-					if (x.offset != y.offset) {
-						return false;
-					}
-					if (x.format != y.format) {
-						return false;
-					}
-				}
-				for (uint32_t i = 0; i < first.vertexInputBindingDescriptions.size(); i++) {
-					const VkVertexInputBindingDescription& x = first.vertexInputBindingDescriptions[i];
-					const VkVertexInputBindingDescription& y = other.vertexInputBindingDescriptions[i];
-					if (x.binding != y.binding) {
-						return false;
-					}
-					if (x.inputRate != y.inputRate) {
-						return false;
-					}
-					if (x.stride != y.stride) {
-						return false;
-					}
-				}
-				return true;
-			}
+	VkCommandBufferAllocateInfo dataTransferAllocInfo{};
+	dataTransferAllocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+	dataTransferAllocInfo.commandPool = commandPool;
+	dataTransferAllocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+	dataTransferAllocInfo.commandBufferCount = (uint32_t)MAX_FRAMES_IN_FLIGHT;
 
-			void Graphics::cleanup() {
-				cleanupSwapChain();
+	if (vkAllocateCommandBuffers(device, &dataTransferAllocInfo, dataTransferCommandBuffers.data()) != VK_SUCCESS) {
+		throw love::Exception("failed to allocate data transfer command buffers");
+	}
+}
 
-				for (auto &cleanUpFns : cleanUpFunctions) {
-					for (auto cleanUpFn : cleanUpFns) {
-						cleanUpFn();
-					}
-				}
-				cleanUpFunctions.clear();
-
-				vmaDestroyAllocator(vmaAllocator);
-				batchedDrawBuffers.clear();
-				for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
-					vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr);
-					vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr);
-					vkDestroyFence(device, inFlightFences[i], nullptr);
-				}
+void Graphics::createSyncObjects() {
+	imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT);
+	renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT);
+	inFlightFences.resize(MAX_FRAMES_IN_FLIGHT);
+	imagesInFlight.resize(swapChainImages.size(), VK_NULL_HANDLE);
 
-				vkDestroyCommandPool(device, commandPool, nullptr);
-				vkDestroyDevice(device, nullptr);
-				vkDestroySurfaceKHR(instance, surface, nullptr);
-				vkDestroyInstance(instance, nullptr);
-			}
+	VkSemaphoreCreateInfo semaphoreInfo{};
+	semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
 
-			void Graphics::cleanupSwapChain() {
-				vkDestroyDescriptorPool(device, descriptorPool, nullptr);
-				vkFreeCommandBuffers(device, commandPool, static_cast<uint32_t>(commandBuffers.size()), commandBuffers.data());
-				vkFreeCommandBuffers(device, commandPool, MAX_FRAMES_IN_FLIGHT, dataTransferCommandBuffers.data());
-				for (auto const& p : graphicsPipelines) {
-					vkDestroyPipeline(device, p.second, nullptr);
-				}
-				graphicsPipelines.clear();
-				currentGraphicsPipeline = VK_NULL_HANDLE;
-				for (size_t i = 0; i < swapChainImageViews.size(); i++) {
-					vkDestroyImageView(device, swapChainImageViews[i], nullptr);
-				}
-				vkDestroySwapchainKHR(device, swapChain, nullptr);
-			}
+	VkFenceCreateInfo fenceInfo{};
+	fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
+	fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
 
-			void Graphics::recreateSwapChain() {
-				vkDeviceWaitIdle(device);
+	for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
+		if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores.at(i)) != VK_SUCCESS ||
+			vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores.at(i)) != VK_SUCCESS ||
+			vkCreateFence(device, &fenceInfo, nullptr, &inFlightFences.at(i)) != VK_SUCCESS) {
+			throw love::Exception("failed to create synchronization objects for a frame!");
+		}
+	}
+}
 
-				cleanupSwapChain();
+void Graphics::createDefaultTexture() {
+	Texture::Settings settings;
+	standardTexture.reset((Texture*)newTexture(settings));
+}
 
-				createSwapChain();
-				createImageViews();
-				createCommandBuffers();
-				startRecordingGraphicsCommands();
-			}
+void Graphics::createQuadIndexBuffer() {
+	if (quadIndexBuffer != nullptr)
+		return;
 
-			love::graphics::Graphics* createInstance() {
-				love::graphics::Graphics* instance = nullptr;
+	size_t size = sizeof(uint16) * getIndexCount(TRIANGLEINDEX_QUADS, LOVE_UINT16_MAX);
+	quadIndexBuffer.reset((StreamBuffer*)newStreamBuffer(BUFFERUSAGE_INDEX, size));
+	auto map = quadIndexBuffer->map(size);
+	fillIndices(TRIANGLEINDEX_QUADS, 0, LOVE_UINT16_MAX, (uint16*)map.data);
+	quadIndexBuffer->unmap(size);
+}
 
-				try {
-					instance = new Graphics();
-				}
-				catch (love::Exception& e) {
-					printf("Cannot create Vulkan renderer: %s\n", e.what());
-				}
+bool operator==(const GraphicsPipelineConfiguration& first, const GraphicsPipelineConfiguration& other) {
+	if (first.scissorRect != other.scissorRect) {
+		return false;
+	}
+	if (first.viewportHeight != other.viewportHeight) {
+		return false;
+	}
+	if (first.viewportWidth != other.viewportWidth) {
+		return false;
+	}
+	if (first.framebufferFormat != other.framebufferFormat) {
+		return false;
+	}
+	if (first.cullmode != other.cullmode) {
+		return false;
+	}
+	if (first.winding != other.winding) {
+		return false;
+	}
+	if (first.colorChannelMask != other.colorChannelMask) {
+		return false;
+	}
+	if (!(first.blendState == other.blendState)) {	// not sure why != doesn't work
+		return false;
+	}
+	if (first.polygonMode != other.polygonMode) {
+		return false;
+	}
+	if (first.primitiveType != other.primitiveType) {
+		return false;
+	}
+	if (first.shader != other.shader) {
+		return false;
+	}
+	if (first.vertexInputAttributeDescriptions.size() != other.vertexInputAttributeDescriptions.size()) {
+		return false;
+	}
+	if (first.vertexInputBindingDescriptions.size() != other.vertexInputBindingDescriptions.size()) {
+		return false;
+	}
+	for (uint32_t i = 0; i < first.vertexInputAttributeDescriptions.size(); i++) {
+		const VkVertexInputAttributeDescription& x = first.vertexInputAttributeDescriptions[i];
+		const VkVertexInputAttributeDescription& y = other.vertexInputAttributeDescriptions[i];
+		if (x.binding != y.binding) {
+			return false;
+		}
+		if (x.location != y.location) {
+			return false;
+		}
+		if (x.offset != y.offset) {
+			return false;
+		}
+		if (x.format != y.format) {
+			return false;
+		}
+	}
+	for (uint32_t i = 0; i < first.vertexInputBindingDescriptions.size(); i++) {
+		const VkVertexInputBindingDescription& x = first.vertexInputBindingDescriptions[i];
+		const VkVertexInputBindingDescription& y = other.vertexInputBindingDescriptions[i];
+		if (x.binding != y.binding) {
+			return false;
+		}
+		if (x.inputRate != y.inputRate) {
+			return false;
+		}
+		if (x.stride != y.stride) {
+			return false;
+		}
+	}
+	return true;
+}
 
-				return instance;
-			}
+void Graphics::cleanup() {
+	cleanupSwapChain();
+
+	for (auto &cleanUpFns : cleanUpFunctions) {
+		for (auto &cleanUpFn : cleanUpFns) {
+			cleanUpFn();
 		}
 	}
+	cleanUpFunctions.clear();
+
+	vmaDestroyAllocator(vmaAllocator);
+	batchedDrawBuffers.clear();
+	for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
+		vkDestroySemaphore(device, renderFinishedSemaphores[i], nullptr);
+		vkDestroySemaphore(device, imageAvailableSemaphores[i], nullptr);
+		vkDestroyFence(device, inFlightFences[i], nullptr);
+	}
+
+	vkDestroyCommandPool(device, commandPool, nullptr);
+	vkDestroyDevice(device, nullptr);
+	vkDestroySurfaceKHR(instance, surface, nullptr);
+	vkDestroyInstance(instance, nullptr);
+}
+
+void Graphics::cleanupSwapChain() {
+	vkDestroyDescriptorPool(device, descriptorPool, nullptr);
+	vkFreeCommandBuffers(device, commandPool, static_cast<uint32_t>(commandBuffers.size()), commandBuffers.data());
+	vkFreeCommandBuffers(device, commandPool, MAX_FRAMES_IN_FLIGHT, dataTransferCommandBuffers.data());
+	for (auto const& p : graphicsPipelines) {
+		vkDestroyPipeline(device, p.second, nullptr);
+	}
+	graphicsPipelines.clear();
+	currentGraphicsPipeline = VK_NULL_HANDLE;
+	for (size_t i = 0; i < swapChainImageViews.size(); i++) {
+		vkDestroyImageView(device, swapChainImageViews[i], nullptr);
+	}
+	vkDestroySwapchainKHR(device, swapChain, nullptr);
+}
+
+void Graphics::recreateSwapChain() {
+	vkDeviceWaitIdle(device);
+
+	cleanupSwapChain();
+
+	createSwapChain();
+	createImageViews();
+	createCommandBuffers();
+	startRecordingGraphicsCommands();
+}
+
+love::graphics::Graphics* createInstance() {
+	love::graphics::Graphics* instance = nullptr;
+
+	try {
+		instance = new Graphics();
+	}
+	catch (love::Exception& e) {
+		printf("Cannot create Vulkan renderer: %s\n", e.what());
+	}
+
+	return instance;
 }
+} // vulkan
+} // graphics
+} // love

+ 203 - 203
src/modules/graphics/vulkan/Graphics.h

@@ -18,209 +18,209 @@
 
 
 namespace love {
-	namespace graphics {
-		namespace vulkan {
-			struct GraphicsPipelineConfiguration {
-				std::vector<VkVertexInputBindingDescription> vertexInputBindingDescriptions;
-				std::vector<VkVertexInputAttributeDescription> vertexInputAttributeDescriptions;
-				Shader* shader = nullptr;
-				PrimitiveType primitiveType = PRIMITIVE_MAX_ENUM;
-				VkPolygonMode polygonMode = VK_POLYGON_MODE_FILL;
-				BlendState blendState;
-				ColorChannelMask colorChannelMask;
-				Winding winding;
-				CullMode cullmode;
-				VkFormat framebufferFormat;
-				float viewportWidth;
-				float viewportHeight;
-				std::optional<Rect> scissorRect;
-
-				friend static bool operator==(const GraphicsPipelineConfiguration& first, const GraphicsPipelineConfiguration& other);
-			};
-
-			struct BatchedDrawBuffers {
-				StreamBuffer* vertexBuffer1;
-				StreamBuffer* vertexBuffer2;
-				StreamBuffer* indexBuffer;
-				StreamBuffer* constantColorBuffer;
-
-				~BatchedDrawBuffers() {
-					delete vertexBuffer1;
-					delete vertexBuffer2;
-					delete indexBuffer;
-					delete constantColorBuffer;
-				}
-			};
-
-			struct QueueFamilyIndices {
-				std::optional<uint32_t> graphicsFamily;
-				std::optional<uint32_t> presentFamily;
-
-				bool isComplete() {
-					return graphicsFamily.has_value() && presentFamily.has_value();
-				}
-			};
-
-			struct SwapChainSupportDetails {
-				VkSurfaceCapabilitiesKHR capabilities{};
-				std::vector<VkSurfaceFormatKHR> formats;
-				std::vector<VkPresentModeKHR> presentModes;
-			};
-
-			class Graphics final : public love::graphics::Graphics {
-			public:
-				Graphics() = default;
-
-				virtual ~Graphics();
-
-				const char* getName() const override;
-				const VkDevice getDevice() const;
-				const VkPhysicalDevice getPhysicalDevice() const;
-				const VmaAllocator getVmaAllocator() const;
-
-				// implementation for virtual functions
-				love::graphics::Texture* newTexture(const love::graphics::Texture::Settings& settings, const love::graphics::Texture::Slices* data = nullptr) override;
-				love::graphics::Buffer* newBuffer(const love::graphics::Buffer::Settings& settings, const std::vector<love::graphics::Buffer::DataDeclaration>& format, const void* data, size_t size, size_t arraylength) override;
-				void clear(OptionalColorD color, OptionalInt stencil, OptionalDouble depth) override;
-				void clear(const std::vector<OptionalColorD>& colors, OptionalInt stencil, OptionalDouble depth) override;
-				Matrix4 computeDeviceProjection(const Matrix4& projection, bool rendertotexture) const override;
-				void discard(const std::vector<bool>& colorbuffers, bool depthstencil) override { }
-				void present(void* screenshotCallbackdata) override;
-				void setViewportSize(int width, int height, int pixelwidth, int pixelheight) override;
-				bool setMode(void* context, int width, int height, int pixelwidth, int pixelheight, bool windowhasstencil, int msaa) override;
-				void unSetMode() override;
-				void setActive(bool active) override;
-				int getRequestedBackbufferMSAA() const override { return 0; }
-				int getBackbufferMSAA() const  override { return 0; }
-				void setColor(Colorf c) override;
-				void setScissor(const Rect& rect) override;
-				void setScissor() override;
-				void setStencilMode(StencilAction action, CompareMode compare, int value, love::uint32 readmask, love::uint32 writemask) override { }
-				void setDepthMode(CompareMode compare, bool write) override { }
-				void setFrontFaceWinding(Winding winding) override;
-				void setColorMask(ColorChannelMask mask) override;
-				void setBlendState(const BlendState& blend) override;
-				void setPointSize(float size) override;
-				void setWireframe(bool enable) override;
-				PixelFormat getSizedFormat(PixelFormat format, bool rendertarget, bool readable) const override;
-				bool isPixelFormatSupported(PixelFormat format, uint32 usage, bool sRGB = false) override;
-				Renderer getRenderer() const override;
-				bool usesGLSLES() const override;
-				RendererInfo getRendererInfo() const override;
-				void draw(const DrawCommand& cmd) override;
-				void draw(const DrawIndexedCommand& cmd) override;
-				void drawQuads(int start, int count, const VertexAttributes& attributes, const BufferBindings& buffers, graphics::Texture* texture) override;
-
-				GraphicsReadback* newReadbackInternal(ReadbackMethod method, love::graphics::Buffer* buffer, size_t offset, size_t size, data::ByteData* dest, size_t destoffset) override { return nullptr;  };
-				GraphicsReadback* newReadbackInternal(ReadbackMethod method, love::graphics::Texture* texture, int slice, int mipmap, const Rect& rect, image::ImageData* dest, int destx, int desty) { return nullptr; }
-
-				void queueDatatransfer(std::function<void(VkCommandBuffer)> command, std::function<void()> cleanUp);
-				void queueCleanUp(std::function<void()> cleanUp);
-
-				VkCommandBuffer beginSingleTimeCommands();
-				void endSingleTimeCommands(VkCommandBuffer);
-
-				uint32_t getNumImagesInFlight() const;
-				const PFN_vkCmdPushDescriptorSetKHR getVkCmdPushDescriptorSetKHRFunctionPointer() const;
-				const VkDeviceSize getMinUniformBufferOffsetAlignment() const;
-				graphics::Texture* getDefaultTexture() const;
-
-			protected:
-				graphics::ShaderStage* newShaderStageInternal(ShaderStageType stage, const std::string& cachekey, const std::string& source, bool gles) override { 
-					return new ShaderStage(this, stage, source, gles, cachekey); 
-				}
-				graphics::Shader* newShaderInternal(StrongRef<love::graphics::ShaderStage> stages[SHADERSTAGE_MAX_ENUM]) override { 
-					return new Shader(stages);
-				}
-				graphics::StreamBuffer* newStreamBuffer(BufferUsage type, size_t size) override;
-				bool dispatch(int x, int y, int z) override { return false; }
-				void initCapabilities() override;
-				void getAPIStats(int& shaderswitches) const override;
-				void setRenderTargetsInternal(const RenderTargets& rts, int pixelw, int pixelh, bool hasSRGBtexture) override;
-
-			private:
-				void createVulkanInstance();
-				bool checkValidationSupport();
-				void pickPhysicalDevice();
-				int rateDeviceSuitability(VkPhysicalDevice device);
-				QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device);
-				void createLogicalDevice();
-				void initVMA();
-				void createSurface();
-				bool checkDeviceExtensionSupport(VkPhysicalDevice device);
-				SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device);
-				VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats);
-				VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& availablePresentModes);
-				VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities);
-				void createSwapChain();
-				void createImageViews();
-				void createDefaultShaders();
-				VkPipeline createGraphicsPipeline(GraphicsPipelineConfiguration);
-				void createCommandPool();
-				void createCommandBuffers();
-				void createSyncObjects();
-				void createDefaultTexture();
-				void createQuadIndexBuffer();
-				void cleanup();
-				void cleanupSwapChain();
-				void recreateSwapChain();
-				void startRecordingGraphicsCommands();
-				void endRecordingGraphicsCommands();
-				void ensureGraphicsPipelineConfiguration(GraphicsPipelineConfiguration);
-				graphics::Shader::BuiltinUniformData getCurrentBuiltinUniformData();
-				void updatedBatchedDrawBuffers();
-				void createVulkanVertexFormat(VertexAttributes vertexAttributes, bool& useConstantVertexColor, GraphicsPipelineConfiguration& configuration);
-				void prepareDraw(const VertexAttributes& attributes, const BufferBindings& buffers, graphics::Texture* texture, PrimitiveType, CullMode);
-				void startRenderPass(Texture*, uint32_t w, uint32_t h);
-				void endRenderPass();
-
-				VkInstance instance = VK_NULL_HANDLE;
-				VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
-				VkDevice device = VK_NULL_HANDLE;
-				VkQueue graphicsQueue = VK_NULL_HANDLE;
-				VkQueue presentQueue = VK_NULL_HANDLE;
-				VkSurfaceKHR surface = VK_NULL_HANDLE;
-				VkSwapchainKHR swapChain = VK_NULL_HANDLE;
-				std::vector<VkImage> swapChainImages;
-				VkFormat swapChainImageFormat = VK_FORMAT_UNDEFINED;
-				VkExtent2D swapChainExtent = VkExtent2D();
-				std::vector<VkImageView> swapChainImageViews;
-				VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
-				VkPipeline currentGraphicsPipeline = VK_NULL_HANDLE;
-				std::vector<std::pair<GraphicsPipelineConfiguration, VkPipeline>> graphicsPipelines;	// FIXME improve performance by using a hash map
-				VkCommandPool commandPool = VK_NULL_HANDLE;
-				std::vector<VkCommandBuffer> commandBuffers;
-				std::vector<VkCommandBuffer> dataTransferCommandBuffers;
-				VkDescriptorPool descriptorPool = VK_NULL_HANDLE;
-				std::vector<VkSemaphore> imageAvailableSemaphores;
-				std::vector<VkSemaphore> renderFinishedSemaphores;
-				std::vector<VkFence> inFlightFences;
-				std::vector<VkFence> imagesInFlight;
-				VkDeviceSize minUniformBufferOffsetAlignment;
-				PFN_vkCmdPushDescriptorSetKHR vkCmdPushDescriptorSet;
-				size_t currentFrame = 0;
-				uint32_t imageIndex = 0;
-				bool framebufferResized = false;
-				VmaAllocator vmaAllocator = VK_NULL_HANDLE;
-				std::unique_ptr<Texture> standardTexture = nullptr;
-				std::unique_ptr<StreamBuffer> quadIndexBuffer = nullptr;
-				// we need an array of draw buffers, since the frames are being rendered asynchronously
-				// and we can't (or shouldn't) update the contents of the buffers while they're still in flight / being rendered.
-				std::vector<BatchedDrawBuffers> batchedDrawBuffers;
-				// functions that need to be called to cleanup objects that were needed for rendering a frame.
-				// just like batchedDrawBuffers we need a vector for each frame in flight.
-				std::vector<std::vector<std::function<void()>>> cleanUpFunctions;
-				graphics::Texture* currentTexture = nullptr;
-				VkPolygonMode currentPolygonMode = VK_POLYGON_MODE_FILL;
-
-				// render pass variables.
-				VkFormat currentFramebufferOutputFormat = VK_FORMAT_UNDEFINED;
-				Texture* renderTargetTexture;
-				float currentViewportWidth = 0;
-				float currentViewportHeight = 0;
-			};
-		}
+namespace graphics {
+namespace vulkan {
+struct GraphicsPipelineConfiguration {
+	std::vector<VkVertexInputBindingDescription> vertexInputBindingDescriptions;
+	std::vector<VkVertexInputAttributeDescription> vertexInputAttributeDescriptions;
+	Shader* shader = nullptr;
+	PrimitiveType primitiveType = PRIMITIVE_MAX_ENUM;
+	VkPolygonMode polygonMode = VK_POLYGON_MODE_FILL;
+	BlendState blendState;
+	ColorChannelMask colorChannelMask;
+	Winding winding;
+	CullMode cullmode;
+	VkFormat framebufferFormat;
+	float viewportWidth;
+	float viewportHeight;
+	std::optional<Rect> scissorRect;
+
+	friend static bool operator==(const GraphicsPipelineConfiguration& first, const GraphicsPipelineConfiguration& other);
+};
+
+struct BatchedDrawBuffers {
+	StreamBuffer* vertexBuffer1;
+	StreamBuffer* vertexBuffer2;
+	StreamBuffer* indexBuffer;
+	StreamBuffer* constantColorBuffer;
+
+	~BatchedDrawBuffers() {
+		delete vertexBuffer1;
+		delete vertexBuffer2;
+		delete indexBuffer;
+		delete constantColorBuffer;
 	}
-}
+};
+
+struct QueueFamilyIndices {
+	std::optional<uint32_t> graphicsFamily;
+	std::optional<uint32_t> presentFamily;
+
+	bool isComplete() {
+		return graphicsFamily.has_value() && presentFamily.has_value();
+	}
+};
+
+struct SwapChainSupportDetails {
+	VkSurfaceCapabilitiesKHR capabilities{};
+	std::vector<VkSurfaceFormatKHR> formats;
+	std::vector<VkPresentModeKHR> presentModes;
+};
+
+class Graphics final : public love::graphics::Graphics {
+public:
+	Graphics() = default;
+
+	virtual ~Graphics();
+
+	const char* getName() const override;
+	const VkDevice getDevice() const;
+	const VkPhysicalDevice getPhysicalDevice() const;
+	const VmaAllocator getVmaAllocator() const;
+
+	// implementation for virtual functions
+	love::graphics::Texture* newTexture(const love::graphics::Texture::Settings& settings, const love::graphics::Texture::Slices* data = nullptr) override;
+	love::graphics::Buffer* newBuffer(const love::graphics::Buffer::Settings& settings, const std::vector<love::graphics::Buffer::DataDeclaration>& format, const void* data, size_t size, size_t arraylength) override;
+	void clear(OptionalColorD color, OptionalInt stencil, OptionalDouble depth) override;
+	void clear(const std::vector<OptionalColorD>& colors, OptionalInt stencil, OptionalDouble depth) override;
+	Matrix4 computeDeviceProjection(const Matrix4& projection, bool rendertotexture) const override;
+	void discard(const std::vector<bool>& colorbuffers, bool depthstencil) override { }
+	void present(void* screenshotCallbackdata) override;
+	void setViewportSize(int width, int height, int pixelwidth, int pixelheight) override;
+	bool setMode(void* context, int width, int height, int pixelwidth, int pixelheight, bool windowhasstencil, int msaa) override;
+	void unSetMode() override;
+	void setActive(bool active) override;
+	int getRequestedBackbufferMSAA() const override { return 0; }
+	int getBackbufferMSAA() const  override { return 0; }
+	void setColor(Colorf c) override;
+	void setScissor(const Rect& rect) override;
+	void setScissor() override;
+	void setStencilMode(StencilAction action, CompareMode compare, int value, love::uint32 readmask, love::uint32 writemask) override { }
+	void setDepthMode(CompareMode compare, bool write) override { }
+	void setFrontFaceWinding(Winding winding) override;
+	void setColorMask(ColorChannelMask mask) override;
+	void setBlendState(const BlendState& blend) override;
+	void setPointSize(float size) override;
+	void setWireframe(bool enable) override;
+	PixelFormat getSizedFormat(PixelFormat format, bool rendertarget, bool readable) const override;
+	bool isPixelFormatSupported(PixelFormat format, uint32 usage, bool sRGB = false) override;
+	Renderer getRenderer() const override;
+	bool usesGLSLES() const override;
+	RendererInfo getRendererInfo() const override;
+	void draw(const DrawCommand& cmd) override;
+	void draw(const DrawIndexedCommand& cmd) override;
+	void drawQuads(int start, int count, const VertexAttributes& attributes, const BufferBindings& buffers, graphics::Texture* texture) override;
+
+	GraphicsReadback* newReadbackInternal(ReadbackMethod method, love::graphics::Buffer* buffer, size_t offset, size_t size, data::ByteData* dest, size_t destoffset) override { return nullptr;  };
+	GraphicsReadback* newReadbackInternal(ReadbackMethod method, love::graphics::Texture* texture, int slice, int mipmap, const Rect& rect, image::ImageData* dest, int destx, int desty) { return nullptr; }
+
+	void queueDatatransfer(std::function<void(VkCommandBuffer)> command, std::function<void()> cleanUp);
+	void queueCleanUp(std::function<void()> cleanUp);
+
+	VkCommandBuffer beginSingleTimeCommands();
+	void endSingleTimeCommands(VkCommandBuffer);
+
+	uint32_t getNumImagesInFlight() const;
+	const PFN_vkCmdPushDescriptorSetKHR getVkCmdPushDescriptorSetKHRFunctionPointer() const;
+	const VkDeviceSize getMinUniformBufferOffsetAlignment() const;
+	graphics::Texture* getDefaultTexture() const;
+
+protected:
+	graphics::ShaderStage* newShaderStageInternal(ShaderStageType stage, const std::string& cachekey, const std::string& source, bool gles) override { 
+		return new ShaderStage(this, stage, source, gles, cachekey); 
+	}
+	graphics::Shader* newShaderInternal(StrongRef<love::graphics::ShaderStage> stages[SHADERSTAGE_MAX_ENUM]) override { 
+		return new Shader(stages);
+	}
+	graphics::StreamBuffer* newStreamBuffer(BufferUsage type, size_t size) override;
+	bool dispatch(int x, int y, int z) override { return false; }
+	void initCapabilities() override;
+	void getAPIStats(int& shaderswitches) const override;
+	void setRenderTargetsInternal(const RenderTargets& rts, int pixelw, int pixelh, bool hasSRGBtexture) override;
+
+private:
+	void createVulkanInstance();
+	bool checkValidationSupport();
+	void pickPhysicalDevice();
+	int rateDeviceSuitability(VkPhysicalDevice device);
+	QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device);
+	void createLogicalDevice();
+	void initVMA();
+	void createSurface();
+	bool checkDeviceExtensionSupport(VkPhysicalDevice device);
+	SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device);
+	VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats);
+	VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& availablePresentModes);
+	VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities);
+	void createSwapChain();
+	void createImageViews();
+	void createDefaultShaders();
+	VkPipeline createGraphicsPipeline(GraphicsPipelineConfiguration);
+	void createCommandPool();
+	void createCommandBuffers();
+	void createSyncObjects();
+	void createDefaultTexture();
+	void createQuadIndexBuffer();
+	void cleanup();
+	void cleanupSwapChain();
+	void recreateSwapChain();
+	void startRecordingGraphicsCommands();
+	void endRecordingGraphicsCommands();
+	void ensureGraphicsPipelineConfiguration(GraphicsPipelineConfiguration);
+	graphics::Shader::BuiltinUniformData getCurrentBuiltinUniformData();
+	void updatedBatchedDrawBuffers();
+	void createVulkanVertexFormat(VertexAttributes vertexAttributes, bool& useConstantVertexColor, GraphicsPipelineConfiguration& configuration);
+	void prepareDraw(const VertexAttributes& attributes, const BufferBindings& buffers, graphics::Texture* texture, PrimitiveType, CullMode);
+	void startRenderPass(Texture*, uint32_t w, uint32_t h);
+	void endRenderPass();
+
+	VkInstance instance = VK_NULL_HANDLE;
+	VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
+	VkDevice device = VK_NULL_HANDLE;
+	VkQueue graphicsQueue = VK_NULL_HANDLE;
+	VkQueue presentQueue = VK_NULL_HANDLE;
+	VkSurfaceKHR surface = VK_NULL_HANDLE;
+	VkSwapchainKHR swapChain = VK_NULL_HANDLE;
+	std::vector<VkImage> swapChainImages;
+	VkFormat swapChainImageFormat = VK_FORMAT_UNDEFINED;
+	VkExtent2D swapChainExtent = VkExtent2D();
+	std::vector<VkImageView> swapChainImageViews;
+	VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
+	VkPipeline currentGraphicsPipeline = VK_NULL_HANDLE;
+	std::vector<std::pair<GraphicsPipelineConfiguration, VkPipeline>> graphicsPipelines;	// FIXME improve performance by using a hash map
+	VkCommandPool commandPool = VK_NULL_HANDLE;
+	std::vector<VkCommandBuffer> commandBuffers;
+	std::vector<VkCommandBuffer> dataTransferCommandBuffers;
+	VkDescriptorPool descriptorPool = VK_NULL_HANDLE;
+	std::vector<VkSemaphore> imageAvailableSemaphores;
+	std::vector<VkSemaphore> renderFinishedSemaphores;
+	std::vector<VkFence> inFlightFences;
+	std::vector<VkFence> imagesInFlight;
+	VkDeviceSize minUniformBufferOffsetAlignment;
+	PFN_vkCmdPushDescriptorSetKHR vkCmdPushDescriptorSet;
+	size_t currentFrame = 0;
+	uint32_t imageIndex = 0;
+	bool framebufferResized = false;
+	VmaAllocator vmaAllocator = VK_NULL_HANDLE;
+	std::unique_ptr<Texture> standardTexture = nullptr;
+	std::unique_ptr<StreamBuffer> quadIndexBuffer = nullptr;
+	// we need an array of draw buffers, since the frames are being rendered asynchronously
+	// and we can't (or shouldn't) update the contents of the buffers while they're still in flight / being rendered.
+	std::vector<BatchedDrawBuffers> batchedDrawBuffers;
+	// functions that need to be called to cleanup objects that were needed for rendering a frame.
+	// just like batchedDrawBuffers we need a vector for each frame in flight.
+	std::vector<std::vector<std::function<void()>>> cleanUpFunctions;
+	graphics::Texture* currentTexture = nullptr;
+	VkPolygonMode currentPolygonMode = VK_POLYGON_MODE_FILL;
+
+	// render pass variables.
+	VkFormat currentFramebufferOutputFormat = VK_FORMAT_UNDEFINED;
+	Texture* renderTargetTexture;
+	float currentViewportWidth = 0;
+	float currentViewportHeight = 0;
+};
+} // vulkan
+} // graphics
+} // love
 
 #endif

+ 480 - 480
src/modules/graphics/vulkan/Shader.cpp

@@ -8,486 +8,486 @@
 #include <vector>
 
 namespace love {
-	namespace graphics {
-		namespace vulkan {
-			static const TBuiltInResource defaultTBuiltInResource = {
-				/* .MaxLights = */ 32,
-				/* .MaxClipPlanes = */ 6,
-				/* .MaxTextureUnits = */ 32,
-				/* .MaxTextureCoords = */ 32,
-				/* .MaxVertexAttribs = */ 64,
-				/* .MaxVertexUniformComponents = */ 16384,
-				/* .MaxVaryingFloats = */ 128,
-				/* .MaxVertexTextureImageUnits = */ 32,
-				/* .MaxCombinedTextureImageUnits = */ 80,
-				/* .MaxTextureImageUnits = */ 32,
-				/* .MaxFragmentUniformComponents = */ 16384,
-				/* .MaxDrawBuffers = */ 8,
-				/* .MaxVertexUniformVectors = */ 4096,
-				/* .MaxVaryingVectors = */ 32,
-				/* .MaxFragmentUniformVectors = */ 4096,
-				/* .MaxVertexOutputVectors = */ 32,
-				/* .MaxFragmentInputVectors = */ 31,
-				/* .MinProgramTexelOffset = */ -8,
-				/* .MaxProgramTexelOffset = */ 7,
-				/* .MaxClipDistances = */ 8,
-				/* .MaxComputeWorkGroupCountX = */ 65535,
-				/* .MaxComputeWorkGroupCountY = */ 65535,
-				/* .MaxComputeWorkGroupCountZ = */ 65535,
-				/* .MaxComputeWorkGroupSizeX = */ 1024,
-				/* .MaxComputeWorkGroupSizeY = */ 1024,
-				/* .MaxComputeWorkGroupSizeZ = */ 64,
-				/* .MaxComputeUniformComponents = */ 1024,
-				/* .MaxComputeTextureImageUnits = */ 32,
-				/* .MaxComputeImageUniforms = */ 16,
-				/* .MaxComputeAtomicCounters = */ 4096,
-				/* .MaxComputeAtomicCounterBuffers = */ 8,
-				/* .MaxVaryingComponents = */ 128,
-				/* .MaxVertexOutputComponents = */ 128,
-				/* .MaxGeometryInputComponents = */ 128,
-				/* .MaxGeometryOutputComponents = */ 128,
-				/* .MaxFragmentInputComponents = */ 128,
-				/* .MaxImageUnits = */ 192,
-				/* .MaxCombinedImageUnitsAndFragmentOutputs = */ 144,
-				/* .MaxCombinedShaderOutputResources = */ 144,
-				/* .MaxImageSamples = */ 32,
-				/* .MaxVertexImageUniforms = */ 16,
-				/* .MaxTessControlImageUniforms = */ 16,
-				/* .MaxTessEvaluationImageUniforms = */ 16,
-				/* .MaxGeometryImageUniforms = */ 16,
-				/* .MaxFragmentImageUniforms = */ 16,
-				/* .MaxCombinedImageUniforms = */ 80,
-				/* .MaxGeometryTextureImageUnits = */ 16,
-				/* .MaxGeometryOutputVertices = */ 256,
-				/* .MaxGeometryTotalOutputComponents = */ 1024,
-				/* .MaxGeometryUniformComponents = */ 1024,
-				/* .MaxGeometryVaryingComponents = */ 64,
-				/* .MaxTessControlInputComponents = */ 128,
-				/* .MaxTessControlOutputComponents = */ 128,
-				/* .MaxTessControlTextureImageUnits = */ 16,
-				/* .MaxTessControlUniformComponents = */ 1024,
-				/* .MaxTessControlTotalOutputComponents = */ 4096,
-				/* .MaxTessEvaluationInputComponents = */ 128,
-				/* .MaxTessEvaluationOutputComponents = */ 128,
-				/* .MaxTessEvaluationTextureImageUnits = */ 16,
-				/* .MaxTessEvaluationUniformComponents = */ 1024,
-				/* .MaxTessPatchComponents = */ 120,
-				/* .MaxPatchVertices = */ 32,
-				/* .MaxTessGenLevel = */ 64,
-				/* .MaxViewports = */ 16,
-				/* .MaxVertexAtomicCounters = */ 4096,
-				/* .MaxTessControlAtomicCounters = */ 4096,
-				/* .MaxTessEvaluationAtomicCounters = */ 4096,
-				/* .MaxGeometryAtomicCounters = */ 4096,
-				/* .MaxFragmentAtomicCounters = */ 4096,
-				/* .MaxCombinedAtomicCounters = */ 4096,
-				/* .MaxAtomicCounterBindings = */ 8,
-				/* .MaxVertexAtomicCounterBuffers = */ 8,
-				/* .MaxTessControlAtomicCounterBuffers = */ 8,
-				/* .MaxTessEvaluationAtomicCounterBuffers = */ 8,
-				/* .MaxGeometryAtomicCounterBuffers = */ 8,
-				/* .MaxFragmentAtomicCounterBuffers = */ 8,
-				/* .MaxCombinedAtomicCounterBuffers = */ 8,
-				/* .MaxAtomicCounterBufferSize = */ 16384,
-				/* .MaxTransformFeedbackBuffers = */ 4,
-				/* .MaxTransformFeedbackInterleavedComponents = */ 64,
-				/* .MaxCullDistances = */ 8,
-				/* .MaxCombinedClipAndCullDistances = */ 8,
-				/* .MaxSamples = */ 32,
-				/* .maxMeshOutputVerticesNV = */ 256,
-				/* .maxMeshOutputPrimitivesNV = */ 512,
-				/* .maxMeshWorkGroupSizeX_NV = */ 32,
-				/* .maxMeshWorkGroupSizeY_NV = */ 1,
-				/* .maxMeshWorkGroupSizeZ_NV = */ 1,
-				/* .maxTaskWorkGroupSizeX_NV = */ 32,
-				/* .maxTaskWorkGroupSizeY_NV = */ 1,
-				/* .maxTaskWorkGroupSizeZ_NV = */ 1,
-				/* .maxMeshViewCountNV = */ 4,
-				/* .maxDualSourceDrawBuffersEXT = */ 1,
-				/* .limits = */ {
-					/* .nonInductiveForLoops = */ 1,
-					/* .whileLoops = */ 1,
-					/* .doWhileLoops = */ 1,
-					/* .generalUniformIndexing = */ 1,
-					/* .generalAttributeMatrixVectorIndexing = */ 1,
-					/* .generalVaryingIndexing = */ 1,
-					/* .generalSamplerIndexing = */ 1,
-					/* .generalVariableIndexing = */ 1,
-					/* .generalConstantMatrixVectorIndexing = */ 1,
-				}
-			};
-
-			static const uint32_t STREAMBUFFER_SIZE = 1024;
-
-			static VkShaderStageFlagBits getStageBit(ShaderStageType type) {
-				switch (type) {
-				case SHADERSTAGE_VERTEX:
-					return VK_SHADER_STAGE_VERTEX_BIT;
-				case SHADERSTAGE_PIXEL:
-					return VK_SHADER_STAGE_FRAGMENT_BIT;
-				case SHADERSTAGE_COMPUTE:
-					return VK_SHADER_STAGE_COMPUTE_BIT;
-				}
-				throw love::Exception("invalid type");
-			}
-
-			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");
-				}
-			}
-
-			Shader::Shader(StrongRef<love::graphics::ShaderStage> stages[])
-				: graphics::Shader(stages) {
-				loadVolatile();
-			}
-
-			bool Shader::loadVolatile() {
-				calculateUniformBufferSizeAligned();
-				compileShaders();
-				createDescriptorSetLayout();
-				createPipelineLayout();
-				createStreamBuffers();
-				currentImage = 0;
-				count = 0;
-
-				return true;
-			}
-
-			void Shader::unloadVolatile() {
-				if (shaderModules.size() == 0) {
-					return;
-				}
-
-				auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
-				gfx->queueCleanUp([shaderModules = std::move(shaderModules), device = device, descriptorSetLayout = descriptorSetLayout, pipelineLayout = pipelineLayout](){
-					for (const auto shaderModule : shaderModules) {
-						vkDestroyShaderModule(device, shaderModule, nullptr);
-					}
-					vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
-					vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
-				});
-				for (const auto streamBuffer : streamBuffers) {
-					delete streamBuffer;
-				}
-				shaderModules.clear();
-				shaderStages.clear();
-				streamBuffers.clear();
-			}
-
-			const std::vector<VkPipelineShaderStageCreateInfo>& Shader::getShaderStages() const {
-				return shaderStages;
-			}
-
-			const VkPipelineLayout Shader::getGraphicsPipelineLayout() const {
-				return pipelineLayout;
-			}
-
-			static VkDescriptorImageInfo createDescriptorImageInfo(graphics::Texture* texture) {
-				VkDescriptorImageInfo imageInfo{};
-				imageInfo.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
-				Texture* vkTexture = (Texture*)texture;
-				imageInfo.imageView = vkTexture->getImageView();
-				imageInfo.sampler = vkTexture->getSampler();
-				return imageInfo;
-			}
-
-			void Shader::cmdPushDescriptorSets(VkCommandBuffer commandBuffer, uint32_t imageIndex) {
-				if (currentImage != imageIndex) {
-					currentImage = imageIndex;
-					count = 0;
-					streamBuffers[currentImage]->nextFrame();
-				}
-				else {
-					if (count >= STREAMBUFFER_SIZE) {
-						throw love::Exception("uniform stream buffer: out of memory (fixme: resize)");
-					}
-				}
-
-				auto mapInfo = streamBuffers[currentImage]->map(uniformBufferSizeAligned);
-				memcpy(mapInfo.data, &uniformData, uniformBufferSizeAligned);
-				streamBuffers[currentImage]->unmap(uniformBufferSizeAligned);
-				streamBuffers[currentImage]->markUsed(uniformBufferSizeAligned);
-
-				VkDescriptorBufferInfo bufferInfo{};
-				bufferInfo.buffer = (VkBuffer)streamBuffers[currentImage]->getHandle();
-				bufferInfo.offset = count * uniformBufferSizeAligned;
-				bufferInfo.range = sizeof(BuiltinUniformData);
-				
-				auto mainTexImageInfo = createDescriptorImageInfo(mainTex);
-				auto ytextureImageInfo = createDescriptorImageInfo(ytexture);
-				auto cbtextureImageInfo = createDescriptorImageInfo(cbtexture);
-				auto crtextureImageInfo = createDescriptorImageInfo(crtexture);
-
-				std::array<VkWriteDescriptorSet, 5> descriptorWrite{};
-				descriptorWrite[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
-				descriptorWrite[0].dstSet = 0;
-				descriptorWrite[0].dstBinding = 0;
-				descriptorWrite[0].dstArrayElement = 0;
-				descriptorWrite[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
-				descriptorWrite[0].descriptorCount = 1;
-				descriptorWrite[0].pBufferInfo = &bufferInfo;
-
-				descriptorWrite[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
-				descriptorWrite[1].dstSet = 0;
-				descriptorWrite[1].dstBinding = 1;
-				descriptorWrite[1].dstArrayElement = 0;
-				descriptorWrite[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
-				descriptorWrite[1].descriptorCount = 1;
-				descriptorWrite[1].pImageInfo = &mainTexImageInfo;				
-
-				descriptorWrite[2].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
-				descriptorWrite[2].dstSet = 0;
-				descriptorWrite[2].dstBinding = 2;
-				descriptorWrite[2].dstArrayElement = 0;
-				descriptorWrite[2].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
-				descriptorWrite[2].descriptorCount = 1;
-				descriptorWrite[2].pImageInfo = &ytextureImageInfo;
-
-				descriptorWrite[3].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
-				descriptorWrite[3].dstSet = 0;
-				descriptorWrite[3].dstBinding = 3;
-				descriptorWrite[3].dstArrayElement = 0;
-				descriptorWrite[3].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
-				descriptorWrite[3].descriptorCount = 1;
-				descriptorWrite[3].pImageInfo = &cbtextureImageInfo;
-
-				descriptorWrite[4].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
-				descriptorWrite[4].dstSet = 0;
-				descriptorWrite[4].dstBinding = 4;
-				descriptorWrite[4].dstArrayElement = 0;
-				descriptorWrite[4].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
-				descriptorWrite[4].descriptorCount = 1;
-				descriptorWrite[4].pImageInfo = &crtextureImageInfo;
-
-				vkCmdPushDescriptorSet(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, static_cast<uint32_t>(descriptorWrite.size()), descriptorWrite.data());
-
-				count++;
-			}
-
-			Shader::~Shader() {
-				unloadVolatile();
-			}
-
-			void Shader::attach() {
-				if (Shader::current != this) {
-					Graphics::flushBatchedDrawsGlobal();
-					Shader::current = this;
-					Vulkan::shaderSwitch();
-				}
-			}
-
-			int Shader::getVertexAttributeIndex(const std::string& name) {
-				return vertexAttributeIndices.at(name);
-			}
-
-			void Shader::calculateUniformBufferSizeAligned() {
-				gfx = Module::getInstance<Graphics>(Module::ModuleType::M_GRAPHICS);
-				auto vgfx = (Graphics*)gfx;
-				auto minAlignment = vgfx->getMinUniformBufferOffsetAlignment();
-				uniformBufferSizeAligned = 
-					static_cast<VkDeviceSize>(
-						std::ceil(
-							static_cast<float>(sizeof(BuiltinUniformData)) / static_cast<float>(minAlignment)
-						)
-					)
-					* minAlignment;
-			}
-
-			void Shader::compileShaders() {
-				using namespace glslang;
-				using namespace spirv_cross;
-
-				TProgram* program = new TProgram();
-
-				gfx = Module::getInstance<Graphics>(Module::ModuleType::M_GRAPHICS);
-				auto vgfx = (Graphics*)gfx;
-				device = vgfx->getDevice();
-
-				mainTex = vgfx->getDefaultTexture();
-				ytexture = vgfx->getDefaultTexture();
-				crtexture = vgfx->getDefaultTexture();
-				cbtexture = vgfx->getDefaultTexture();
-
-				for (int i = 0; i < SHADERSTAGE_MAX_ENUM; i++) {
-					if (!stages[i])
-						continue;
-
-					auto stage = (ShaderStageType)i;
-					auto glslangShaderStage = getGlslShaderType(stage);
-					auto tshader = new TShader(glslangShaderStage);
-
-					tshader->setEnvInput(EShSourceGlsl, glslangShaderStage, EShClientVulkan, 450);
-					tshader->setEnvClient(EShClientVulkan, EShTargetVulkan_1_2);
-					tshader->setEnvTarget(EshTargetSpv, EShTargetSpv_1_5);
-					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 defaultVersio = 450;
-					EProfile defaultProfile = ECoreProfile;
-					bool forceDefault = false;
-					bool forwardCompat = true;
-
-					if (!tshader->parse(&defaultTBuiltInResource, defaultVersio, defaultProfile, forceDefault, forwardCompat, EShMsgSuppressWarnings)) {
-						const char* msg1 = tshader->getInfoLog();
-						const char* msg2 = tshader->getInfoDebugLog();
-
-						throw love::Exception("error while parsing shader");
-					}
-
-					program->addShader(tshader);
-				}
-
-				if (!program->link(EShMsgDefault)) {
-					throw love::Exception("link failed! %s\n", program->getInfoLog());
-				}
-
-				if (!program->mapIO()) {
-					throw love::Exception("mapIO failed");
-				}
-
-				for (int i = 0; i < SHADERSTAGE_MAX_ENUM; i++) {
-					auto glslangStage = getGlslShaderType((ShaderStageType)i);
-					auto intermediate = program->getIntermediate(glslangStage);
-					if (intermediate == nullptr) {
-						continue;
-					}
-
-					spv::SpvBuildLogger logger;
-					glslang::SpvOptions opt;
-					opt.validate = true;
-
-					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();
-
-					Graphics* vkGfx = (Graphics*)gfx;
-					auto device = vkGfx->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);
-				}
-			}
-
-			// fixme: should generate this dynamically.
-			void Shader::createDescriptorSetLayout() {
-				auto vgfx = (Graphics*)gfx;
-				vkCmdPushDescriptorSet = vgfx->getVkCmdPushDescriptorSetKHRFunctionPointer();
-
-				VkDescriptorSetLayoutBinding uboLayoutBinding{};
-				uboLayoutBinding.binding = 0;
-				uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
-				uboLayoutBinding.descriptorCount = 1;
-				uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
-
-				VkDescriptorSetLayoutBinding samplerLayoutBinding{};
-				samplerLayoutBinding.binding = 1;
-				samplerLayoutBinding.descriptorCount = 1;
-				samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
-				samplerLayoutBinding.pImmutableSamplers = nullptr;
-				samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
-
-				VkDescriptorSetLayoutBinding videoYBinding{};
-				videoYBinding.binding = 2;
-				videoYBinding.descriptorCount = 1;
-				videoYBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
-				videoYBinding.pImmutableSamplers = nullptr;
-				videoYBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
-
-				VkDescriptorSetLayoutBinding videoCBBinding{};
-				videoCBBinding.binding = 3;
-				videoCBBinding.descriptorCount = 1;
-				videoCBBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
-				videoCBBinding.pImmutableSamplers = nullptr;
-				videoCBBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
-
-				VkDescriptorSetLayoutBinding videoCRinding{};
-				videoCRinding.binding = 4;
-				videoCRinding.descriptorCount = 1;
-				videoCRinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
-				videoCRinding.pImmutableSamplers = nullptr;
-				videoCRinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
-
-				std::array<VkDescriptorSetLayoutBinding, 5> bindings = { uboLayoutBinding, samplerLayoutBinding, videoYBinding, videoCBBinding, videoCRinding };
-				VkDescriptorSetLayoutCreateInfo layoutInfo{};
-				layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
-				layoutInfo.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR;
-				layoutInfo.bindingCount = static_cast<uint32_t>(bindings.size());
-				layoutInfo.pBindings = bindings.data();
-
-				if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) {
-					throw love::Exception("failed to create descriptor set layout");
-				}
-			}
-
-			void Shader::createPipelineLayout() {
-				VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
-				pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
-				pipelineLayoutInfo.setLayoutCount = 1;
-				pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout;
-				pipelineLayoutInfo.pushConstantRangeCount = 0;
-
-				if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) {
-					throw love::Exception("failed to create pipeline layout");
-				}
-			}
-
-			void Shader::createStreamBuffers() {
-				auto vgfx = (Graphics*)gfx;
-				const auto numImagesInFlight = vgfx->getNumImagesInFlight();
-				streamBuffers.resize(numImagesInFlight);
-				for (uint32_t i = 0; i < numImagesInFlight; i++) {
-					streamBuffers[i] = new StreamBuffer(gfx, BUFFERUSAGE_UNIFORM, STREAMBUFFER_SIZE * uniformBufferSizeAligned);
-				}
-			}
-
-			void Shader::setVideoTextures(graphics::Texture* ytexture, graphics::Texture* cbtexture, graphics::Texture* crtexture) {
-				this->ytexture = ytexture;
-				this->cbtexture = cbtexture;
-				this->crtexture = crtexture;
-			}
-
-			void Shader::setUniformData(BuiltinUniformData& data) {
-				uniformData = data;
-			}
-
-			void Shader::setMainTex(graphics::Texture* texture) {
-				mainTex = texture;
-			}
+namespace graphics {
+namespace vulkan {
+static const TBuiltInResource defaultTBuiltInResource = {
+	/* .MaxLights = */ 32,
+	/* .MaxClipPlanes = */ 6,
+	/* .MaxTextureUnits = */ 32,
+	/* .MaxTextureCoords = */ 32,
+	/* .MaxVertexAttribs = */ 64,
+	/* .MaxVertexUniformComponents = */ 16384,
+	/* .MaxVaryingFloats = */ 128,
+	/* .MaxVertexTextureImageUnits = */ 32,
+	/* .MaxCombinedTextureImageUnits = */ 80,
+	/* .MaxTextureImageUnits = */ 32,
+	/* .MaxFragmentUniformComponents = */ 16384,
+	/* .MaxDrawBuffers = */ 8,
+	/* .MaxVertexUniformVectors = */ 4096,
+	/* .MaxVaryingVectors = */ 32,
+	/* .MaxFragmentUniformVectors = */ 4096,
+	/* .MaxVertexOutputVectors = */ 32,
+	/* .MaxFragmentInputVectors = */ 31,
+	/* .MinProgramTexelOffset = */ -8,
+	/* .MaxProgramTexelOffset = */ 7,
+	/* .MaxClipDistances = */ 8,
+	/* .MaxComputeWorkGroupCountX = */ 65535,
+	/* .MaxComputeWorkGroupCountY = */ 65535,
+	/* .MaxComputeWorkGroupCountZ = */ 65535,
+	/* .MaxComputeWorkGroupSizeX = */ 1024,
+	/* .MaxComputeWorkGroupSizeY = */ 1024,
+	/* .MaxComputeWorkGroupSizeZ = */ 64,
+	/* .MaxComputeUniformComponents = */ 1024,
+	/* .MaxComputeTextureImageUnits = */ 32,
+	/* .MaxComputeImageUniforms = */ 16,
+	/* .MaxComputeAtomicCounters = */ 4096,
+	/* .MaxComputeAtomicCounterBuffers = */ 8,
+	/* .MaxVaryingComponents = */ 128,
+	/* .MaxVertexOutputComponents = */ 128,
+	/* .MaxGeometryInputComponents = */ 128,
+	/* .MaxGeometryOutputComponents = */ 128,
+	/* .MaxFragmentInputComponents = */ 128,
+	/* .MaxImageUnits = */ 192,
+	/* .MaxCombinedImageUnitsAndFragmentOutputs = */ 144,
+	/* .MaxCombinedShaderOutputResources = */ 144,
+	/* .MaxImageSamples = */ 32,
+	/* .MaxVertexImageUniforms = */ 16,
+	/* .MaxTessControlImageUniforms = */ 16,
+	/* .MaxTessEvaluationImageUniforms = */ 16,
+	/* .MaxGeometryImageUniforms = */ 16,
+	/* .MaxFragmentImageUniforms = */ 16,
+	/* .MaxCombinedImageUniforms = */ 80,
+	/* .MaxGeometryTextureImageUnits = */ 16,
+	/* .MaxGeometryOutputVertices = */ 256,
+	/* .MaxGeometryTotalOutputComponents = */ 1024,
+	/* .MaxGeometryUniformComponents = */ 1024,
+	/* .MaxGeometryVaryingComponents = */ 64,
+	/* .MaxTessControlInputComponents = */ 128,
+	/* .MaxTessControlOutputComponents = */ 128,
+	/* .MaxTessControlTextureImageUnits = */ 16,
+	/* .MaxTessControlUniformComponents = */ 1024,
+	/* .MaxTessControlTotalOutputComponents = */ 4096,
+	/* .MaxTessEvaluationInputComponents = */ 128,
+	/* .MaxTessEvaluationOutputComponents = */ 128,
+	/* .MaxTessEvaluationTextureImageUnits = */ 16,
+	/* .MaxTessEvaluationUniformComponents = */ 1024,
+	/* .MaxTessPatchComponents = */ 120,
+	/* .MaxPatchVertices = */ 32,
+	/* .MaxTessGenLevel = */ 64,
+	/* .MaxViewports = */ 16,
+	/* .MaxVertexAtomicCounters = */ 4096,
+	/* .MaxTessControlAtomicCounters = */ 4096,
+	/* .MaxTessEvaluationAtomicCounters = */ 4096,
+	/* .MaxGeometryAtomicCounters = */ 4096,
+	/* .MaxFragmentAtomicCounters = */ 4096,
+	/* .MaxCombinedAtomicCounters = */ 4096,
+	/* .MaxAtomicCounterBindings = */ 8,
+	/* .MaxVertexAtomicCounterBuffers = */ 8,
+	/* .MaxTessControlAtomicCounterBuffers = */ 8,
+	/* .MaxTessEvaluationAtomicCounterBuffers = */ 8,
+	/* .MaxGeometryAtomicCounterBuffers = */ 8,
+	/* .MaxFragmentAtomicCounterBuffers = */ 8,
+	/* .MaxCombinedAtomicCounterBuffers = */ 8,
+	/* .MaxAtomicCounterBufferSize = */ 16384,
+	/* .MaxTransformFeedbackBuffers = */ 4,
+	/* .MaxTransformFeedbackInterleavedComponents = */ 64,
+	/* .MaxCullDistances = */ 8,
+	/* .MaxCombinedClipAndCullDistances = */ 8,
+	/* .MaxSamples = */ 32,
+	/* .maxMeshOutputVerticesNV = */ 256,
+	/* .maxMeshOutputPrimitivesNV = */ 512,
+	/* .maxMeshWorkGroupSizeX_NV = */ 32,
+	/* .maxMeshWorkGroupSizeY_NV = */ 1,
+	/* .maxMeshWorkGroupSizeZ_NV = */ 1,
+	/* .maxTaskWorkGroupSizeX_NV = */ 32,
+	/* .maxTaskWorkGroupSizeY_NV = */ 1,
+	/* .maxTaskWorkGroupSizeZ_NV = */ 1,
+	/* .maxMeshViewCountNV = */ 4,
+	/* .maxDualSourceDrawBuffersEXT = */ 1,
+	/* .limits = */ {
+		/* .nonInductiveForLoops = */ 1,
+		/* .whileLoops = */ 1,
+		/* .doWhileLoops = */ 1,
+		/* .generalUniformIndexing = */ 1,
+		/* .generalAttributeMatrixVectorIndexing = */ 1,
+		/* .generalVaryingIndexing = */ 1,
+		/* .generalSamplerIndexing = */ 1,
+		/* .generalVariableIndexing = */ 1,
+		/* .generalConstantMatrixVectorIndexing = */ 1,
+	}
+};
+
+static const uint32_t STREAMBUFFER_SIZE = 1024;
+
+static VkShaderStageFlagBits getStageBit(ShaderStageType type) {
+	switch (type) {
+	case SHADERSTAGE_VERTEX:
+		return VK_SHADER_STAGE_VERTEX_BIT;
+	case SHADERSTAGE_PIXEL:
+		return VK_SHADER_STAGE_FRAGMENT_BIT;
+	case SHADERSTAGE_COMPUTE:
+		return VK_SHADER_STAGE_COMPUTE_BIT;
+	}
+	throw love::Exception("invalid type");
+}
+
+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");
+	}
+}
+
+Shader::Shader(StrongRef<love::graphics::ShaderStage> stages[])
+	: graphics::Shader(stages) {
+	loadVolatile();
+}
+
+bool Shader::loadVolatile() {
+	calculateUniformBufferSizeAligned();
+	compileShaders();
+	createDescriptorSetLayout();
+	createPipelineLayout();
+	createStreamBuffers();
+	currentImage = 0;
+	count = 0;
+
+	return true;
+}
+
+void Shader::unloadVolatile() {
+	if (shaderModules.size() == 0) {
+		return;
+	}
+
+	auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
+	gfx->queueCleanUp([shaderModules = std::move(shaderModules), device = device, descriptorSetLayout = descriptorSetLayout, pipelineLayout = pipelineLayout](){
+		for (const auto shaderModule : shaderModules) {
+			vkDestroyShaderModule(device, shaderModule, nullptr);
+		}
+		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
+		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
+	});
+	for (const auto streamBuffer : streamBuffers) {
+		delete streamBuffer;
+	}
+	shaderModules.clear();
+	shaderStages.clear();
+	streamBuffers.clear();
+}
+
+const std::vector<VkPipelineShaderStageCreateInfo>& Shader::getShaderStages() const {
+	return shaderStages;
+}
+
+const VkPipelineLayout Shader::getGraphicsPipelineLayout() const {
+	return pipelineLayout;
+}
+
+static VkDescriptorImageInfo createDescriptorImageInfo(graphics::Texture* texture) {
+	VkDescriptorImageInfo imageInfo{};
+	imageInfo.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
+	Texture* vkTexture = (Texture*)texture;
+	imageInfo.imageView = vkTexture->getImageView();
+	imageInfo.sampler = vkTexture->getSampler();
+	return imageInfo;
+}
+
+void Shader::cmdPushDescriptorSets(VkCommandBuffer commandBuffer, uint32_t imageIndex) {
+	if (currentImage != imageIndex) {
+		currentImage = imageIndex;
+		count = 0;
+		streamBuffers[currentImage]->nextFrame();
+	}
+	else {
+		if (count >= STREAMBUFFER_SIZE) {
+			throw love::Exception("uniform stream buffer: out of memory (fixme: resize)");
+		}
+	}
+
+	auto mapInfo = streamBuffers[currentImage]->map(uniformBufferSizeAligned);
+	memcpy(mapInfo.data, &uniformData, uniformBufferSizeAligned);
+	streamBuffers[currentImage]->unmap(uniformBufferSizeAligned);
+	streamBuffers[currentImage]->markUsed(uniformBufferSizeAligned);
+
+	VkDescriptorBufferInfo bufferInfo{};
+	bufferInfo.buffer = (VkBuffer)streamBuffers[currentImage]->getHandle();
+	bufferInfo.offset = count * uniformBufferSizeAligned;
+	bufferInfo.range = sizeof(BuiltinUniformData);
+	
+	auto mainTexImageInfo = createDescriptorImageInfo(mainTex);
+	auto ytextureImageInfo = createDescriptorImageInfo(ytexture);
+	auto cbtextureImageInfo = createDescriptorImageInfo(cbtexture);
+	auto crtextureImageInfo = createDescriptorImageInfo(crtexture);
+
+	std::array<VkWriteDescriptorSet, 5> descriptorWrite{};
+	descriptorWrite[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+	descriptorWrite[0].dstSet = 0;
+	descriptorWrite[0].dstBinding = 0;
+	descriptorWrite[0].dstArrayElement = 0;
+	descriptorWrite[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+	descriptorWrite[0].descriptorCount = 1;
+	descriptorWrite[0].pBufferInfo = &bufferInfo;
+
+	descriptorWrite[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+	descriptorWrite[1].dstSet = 0;
+	descriptorWrite[1].dstBinding = 1;
+	descriptorWrite[1].dstArrayElement = 0;
+	descriptorWrite[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+	descriptorWrite[1].descriptorCount = 1;
+	descriptorWrite[1].pImageInfo = &mainTexImageInfo;				
+
+	descriptorWrite[2].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+	descriptorWrite[2].dstSet = 0;
+	descriptorWrite[2].dstBinding = 2;
+	descriptorWrite[2].dstArrayElement = 0;
+	descriptorWrite[2].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+	descriptorWrite[2].descriptorCount = 1;
+	descriptorWrite[2].pImageInfo = &ytextureImageInfo;
+
+	descriptorWrite[3].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+	descriptorWrite[3].dstSet = 0;
+	descriptorWrite[3].dstBinding = 3;
+	descriptorWrite[3].dstArrayElement = 0;
+	descriptorWrite[3].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+	descriptorWrite[3].descriptorCount = 1;
+	descriptorWrite[3].pImageInfo = &cbtextureImageInfo;
+
+	descriptorWrite[4].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+	descriptorWrite[4].dstSet = 0;
+	descriptorWrite[4].dstBinding = 4;
+	descriptorWrite[4].dstArrayElement = 0;
+	descriptorWrite[4].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+	descriptorWrite[4].descriptorCount = 1;
+	descriptorWrite[4].pImageInfo = &crtextureImageInfo;
+
+	vkCmdPushDescriptorSet(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, static_cast<uint32_t>(descriptorWrite.size()), descriptorWrite.data());
+
+	count++;
+}
+
+Shader::~Shader() {
+	unloadVolatile();
+}
+
+void Shader::attach() {
+	if (Shader::current != this) {
+		Graphics::flushBatchedDrawsGlobal();
+		Shader::current = this;
+		Vulkan::shaderSwitch();
+	}
+}
+
+int Shader::getVertexAttributeIndex(const std::string& name) {
+	return vertexAttributeIndices.at(name);
+}
+
+void Shader::calculateUniformBufferSizeAligned() {
+	gfx = Module::getInstance<Graphics>(Module::ModuleType::M_GRAPHICS);
+	auto vgfx = (Graphics*)gfx;
+	auto minAlignment = vgfx->getMinUniformBufferOffsetAlignment();
+	uniformBufferSizeAligned = 
+		static_cast<VkDeviceSize>(
+			std::ceil(
+				static_cast<float>(sizeof(BuiltinUniformData)) / static_cast<float>(minAlignment)
+			)
+		)
+		* minAlignment;
+}
+
+void Shader::compileShaders() {
+	using namespace glslang;
+	using namespace spirv_cross;
+
+	TProgram* program = new TProgram();
+
+	gfx = Module::getInstance<Graphics>(Module::ModuleType::M_GRAPHICS);
+	auto vgfx = (Graphics*)gfx;
+	device = vgfx->getDevice();
+
+	mainTex = vgfx->getDefaultTexture();
+	ytexture = vgfx->getDefaultTexture();
+	crtexture = vgfx->getDefaultTexture();
+	cbtexture = vgfx->getDefaultTexture();
+
+	for (int i = 0; i < SHADERSTAGE_MAX_ENUM; i++) {
+		if (!stages[i])
+			continue;
+
+		auto stage = (ShaderStageType)i;
+		auto glslangShaderStage = getGlslShaderType(stage);
+		auto tshader = new TShader(glslangShaderStage);
+
+		tshader->setEnvInput(EShSourceGlsl, glslangShaderStage, EShClientVulkan, 450);
+		tshader->setEnvClient(EShClientVulkan, EShTargetVulkan_1_2);
+		tshader->setEnvTarget(EshTargetSpv, EShTargetSpv_1_5);
+		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 defaultVersio = 450;
+		EProfile defaultProfile = ECoreProfile;
+		bool forceDefault = false;
+		bool forwardCompat = true;
+
+		if (!tshader->parse(&defaultTBuiltInResource, defaultVersio, defaultProfile, forceDefault, forwardCompat, EShMsgSuppressWarnings)) {
+			const char* msg1 = tshader->getInfoLog();
+			const char* msg2 = tshader->getInfoDebugLog();
+
+			throw love::Exception("error while parsing shader");
 		}
+
+		program->addShader(tshader);
+	}
+
+	if (!program->link(EShMsgDefault)) {
+		throw love::Exception("link failed! %s\n", program->getInfoLog());
 	}
+
+	if (!program->mapIO()) {
+		throw love::Exception("mapIO failed");
+	}
+
+	for (int i = 0; i < SHADERSTAGE_MAX_ENUM; i++) {
+		auto glslangStage = getGlslShaderType((ShaderStageType)i);
+		auto intermediate = program->getIntermediate(glslangStage);
+		if (intermediate == nullptr) {
+			continue;
+		}
+
+		spv::SpvBuildLogger logger;
+		glslang::SpvOptions opt;
+		opt.validate = true;
+
+		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();
+
+		Graphics* vkGfx = (Graphics*)gfx;
+		auto device = vkGfx->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);
+	}
+}
+
+// fixme: should generate this dynamically.
+void Shader::createDescriptorSetLayout() {
+	auto vgfx = (Graphics*)gfx;
+	vkCmdPushDescriptorSet = vgfx->getVkCmdPushDescriptorSetKHRFunctionPointer();
+
+	VkDescriptorSetLayoutBinding uboLayoutBinding{};
+	uboLayoutBinding.binding = 0;
+	uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+	uboLayoutBinding.descriptorCount = 1;
+	uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
+
+	VkDescriptorSetLayoutBinding samplerLayoutBinding{};
+	samplerLayoutBinding.binding = 1;
+	samplerLayoutBinding.descriptorCount = 1;
+	samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+	samplerLayoutBinding.pImmutableSamplers = nullptr;
+	samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
+
+	VkDescriptorSetLayoutBinding videoYBinding{};
+	videoYBinding.binding = 2;
+	videoYBinding.descriptorCount = 1;
+	videoYBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+	videoYBinding.pImmutableSamplers = nullptr;
+	videoYBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
+
+	VkDescriptorSetLayoutBinding videoCBBinding{};
+	videoCBBinding.binding = 3;
+	videoCBBinding.descriptorCount = 1;
+	videoCBBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+	videoCBBinding.pImmutableSamplers = nullptr;
+	videoCBBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
+
+	VkDescriptorSetLayoutBinding videoCRinding{};
+	videoCRinding.binding = 4;
+	videoCRinding.descriptorCount = 1;
+	videoCRinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+	videoCRinding.pImmutableSamplers = nullptr;
+	videoCRinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
+
+	std::array<VkDescriptorSetLayoutBinding, 5> bindings = { uboLayoutBinding, samplerLayoutBinding, videoYBinding, videoCBBinding, videoCRinding };
+	VkDescriptorSetLayoutCreateInfo layoutInfo{};
+	layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+	layoutInfo.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR;
+	layoutInfo.bindingCount = static_cast<uint32_t>(bindings.size());
+	layoutInfo.pBindings = bindings.data();
+
+	if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) {
+		throw love::Exception("failed to create descriptor set layout");
+	}
+}
+
+void Shader::createPipelineLayout() {
+	VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
+	pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
+	pipelineLayoutInfo.setLayoutCount = 1;
+	pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout;
+	pipelineLayoutInfo.pushConstantRangeCount = 0;
+
+	if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) {
+		throw love::Exception("failed to create pipeline layout");
+	}
+}
+
+void Shader::createStreamBuffers() {
+	auto vgfx = (Graphics*)gfx;
+	const auto numImagesInFlight = vgfx->getNumImagesInFlight();
+	streamBuffers.resize(numImagesInFlight);
+	for (uint32_t i = 0; i < numImagesInFlight; i++) {
+		streamBuffers[i] = new StreamBuffer(gfx, BUFFERUSAGE_UNIFORM, STREAMBUFFER_SIZE * uniformBufferSizeAligned);
+	}
+}
+
+void Shader::setVideoTextures(graphics::Texture* ytexture, graphics::Texture* cbtexture, graphics::Texture* crtexture) {
+	this->ytexture = ytexture;
+	this->cbtexture = cbtexture;
+	this->crtexture = crtexture;
+}
+
+void Shader::setUniformData(BuiltinUniformData& data) {
+	uniformData = data;
+}
+
+void Shader::setMainTex(graphics::Texture* texture) {
+	mainTex = texture;
 }
+} // vulkan
+} // graphics
+} // love

+ 62 - 62
src/modules/graphics/vulkan/Shader.h

@@ -11,91 +11,91 @@
 
 
 namespace love {
-	namespace graphics {
-		namespace vulkan {
-			class Shader final : public graphics::Shader, public Volatile {
-			public:
-				Shader(StrongRef<love::graphics::ShaderStage> stages[]);
-				virtual ~Shader();
+namespace graphics {
+namespace vulkan {
+class Shader final : public graphics::Shader, public Volatile {
+public:
+	Shader(StrongRef<love::graphics::ShaderStage> stages[]);
+	virtual ~Shader();
 
-				bool loadVolatile() override;
-				void unloadVolatile() override;
+	bool loadVolatile() override;
+	void unloadVolatile() override;
 
-				const std::vector<VkPipelineShaderStageCreateInfo>& getShaderStages() const;
+	const std::vector<VkPipelineShaderStageCreateInfo>& getShaderStages() const;
 
-				const VkPipelineLayout getGraphicsPipelineLayout() const;
+	const VkPipelineLayout getGraphicsPipelineLayout() const;
 
-				void cmdPushDescriptorSets(VkCommandBuffer, uint32_t currentImage);
+	void cmdPushDescriptorSets(VkCommandBuffer, uint32_t currentImage);
 
-				void attach() override;
+	void attach() override;
 
-				ptrdiff_t getHandle() const { return 0; }
+	ptrdiff_t getHandle() const { return 0; }
 
-				std::string getWarnings() const override { return ""; }
+	std::string getWarnings() const override { return ""; }
 
-				int getVertexAttributeIndex(const std::string& name) override;
+	int getVertexAttributeIndex(const std::string& name) override;
 
-				const UniformInfo* getUniformInfo(const std::string& name) const override { return nullptr; }
-				const UniformInfo* getUniformInfo(BuiltinUniform builtin) const override { return nullptr;  }
+	const UniformInfo* getUniformInfo(const std::string& name) const override { return nullptr; }
+	const UniformInfo* getUniformInfo(BuiltinUniform builtin) const override { return nullptr;  }
 
-				void updateUniform(const UniformInfo* info, int count) override {}
+	void updateUniform(const UniformInfo* info, int count) override {}
 
-				void sendTextures(const UniformInfo* info, graphics::Texture** textures, int count) override {}
-				void sendBuffers(const UniformInfo* info, love::graphics::Buffer** buffers, int count) override {}
+	void sendTextures(const UniformInfo* info, graphics::Texture** textures, int count) override {}
+	void sendBuffers(const UniformInfo* info, love::graphics::Buffer** buffers, int count) override {}
 
-				bool hasUniform(const std::string& name) const override { return false; }
+	bool hasUniform(const std::string& name) const override { return false; }
 
-				void setVideoTextures(graphics::Texture* ytexture, graphics::Texture* cbtexture, graphics::Texture* crtexture) override;
+	void setVideoTextures(graphics::Texture* ytexture, graphics::Texture* cbtexture, graphics::Texture* crtexture) override;
 
-				// fixme: use normal methods for this in the future.
-				void setUniformData(BuiltinUniformData& data);
-				void setMainTex(graphics::Texture* texture);
+	// fixme: use normal methods for this in the future.
+	void setUniformData(BuiltinUniformData& data);
+	void setMainTex(graphics::Texture* texture);
 
-			private:
-				void calculateUniformBufferSizeAligned();
-				void compileShaders();
-				void createDescriptorSetLayout();
-				void createPipelineLayout();
-				void createStreamBuffers();
+private:
+	void calculateUniformBufferSizeAligned();
+	void compileShaders();
+	void createDescriptorSetLayout();
+	void createPipelineLayout();
+	void createStreamBuffers();
 
-				VkDeviceSize uniformBufferSizeAligned;
-				PFN_vkCmdPushDescriptorSetKHR vkCmdPushDescriptorSet;
+	VkDeviceSize uniformBufferSizeAligned;
+	PFN_vkCmdPushDescriptorSetKHR vkCmdPushDescriptorSet;
 
-				VkDescriptorSetLayout descriptorSetLayout;
-				VkPipelineLayout pipelineLayout;
+	VkDescriptorSetLayout descriptorSetLayout;
+	VkPipelineLayout pipelineLayout;
 
-				std::vector<StreamBuffer*> streamBuffers;
+	std::vector<StreamBuffer*> streamBuffers;
 
-				std::vector<VkPipelineShaderStageCreateInfo> shaderStages;
-				std::vector<VkShaderModule> shaderModules;
-				Graphics* gfx;
-				VkDevice device;
+	std::vector<VkPipelineShaderStageCreateInfo> shaderStages;
+	std::vector<VkShaderModule> shaderModules;
+	Graphics* gfx;
+	VkDevice device;
 
-				std::map<std::string, int> vertexAttributeIndices = {
-					{ "VertexPosition", 0 },
-					{ "VertexTexCoord", 1 },
-					{ "VertexColor", 2 }
-				};
+	std::map<std::string, int> vertexAttributeIndices = {
+		{ "VertexPosition", 0 },
+		{ "VertexTexCoord", 1 },
+		{ "VertexColor", 2 }
+	};
 
-				std::map<std::string, int> uniformBindings = {
-					{ "love_UniformsPerDraw", 0 },
-					{ "love_VideoYChannel", 1 },
-					{ "love_VideoCbChannel", 2 },
-					{ "love_VideoCrChannel", 3 },
-					{ "MainTex", 4 }
-				};
+	std::map<std::string, int> uniformBindings = {
+		{ "love_UniformsPerDraw", 0 },
+		{ "love_VideoYChannel", 1 },
+		{ "love_VideoCbChannel", 2 },
+		{ "love_VideoCrChannel", 3 },
+		{ "MainTex", 4 }
+	};
 
-				BuiltinUniformData uniformData;
-				graphics::Texture* mainTex;
-				graphics::Texture* ytexture;
-				graphics::Texture* cbtexture;
-				graphics::Texture* crtexture;
+	BuiltinUniformData uniformData;
+	graphics::Texture* mainTex;
+	graphics::Texture* ytexture;
+	graphics::Texture* cbtexture;
+	graphics::Texture* crtexture;
 
-				uint32_t currentImage;
-				uint32_t count;
-			};
-		}
-	}
+	uint32_t currentImage;
+	uint32_t count;
+};
+}
+}
 }
 
 #endif

+ 8 - 8
src/modules/graphics/vulkan/ShaderStage.cpp

@@ -12,12 +12,12 @@
 
 
 namespace love {
-	namespace graphics {
-		namespace vulkan {
-			ShaderStage::ShaderStage(love::graphics::Graphics* gfx, ShaderStageType stage, const std::string& glsl, bool gles, const std::string& cachekey)
-				: love::graphics::ShaderStage(gfx, stage, glsl, gles, cachekey) {
-				// the compilation is done in Shader.
-			}
-		}
-	}
+namespace graphics {
+namespace vulkan {
+ShaderStage::ShaderStage(love::graphics::Graphics* gfx, ShaderStageType stage, const std::string& glsl, bool gles, const std::string& cachekey)
+	: love::graphics::ShaderStage(gfx, stage, glsl, gles, cachekey) {
+	// the compilation is done in Shader.
 }
+} // love
+} // graphics
+} // vulkan

+ 10 - 10
src/modules/graphics/vulkan/ShaderStage.h

@@ -6,18 +6,18 @@
 #include <vulkan/vulkan.h>
 
 namespace love {
-	namespace graphics {
-		namespace vulkan {
-			class ShaderStage final : public graphics::ShaderStage {
-			public:
-				ShaderStage(love::graphics::Graphics* gfx, ShaderStageType stage, const std::string& glsl, bool gles, const std::string& cachekey);
+namespace graphics {
+namespace vulkan {
+class ShaderStage final : public graphics::ShaderStage {
+public:
+	ShaderStage(love::graphics::Graphics* gfx, ShaderStageType stage, const std::string& glsl, bool gles, const std::string& cachekey);
 
-				ptrdiff_t getHandle() const {
-					return 0;
-				}
-			};
-		}
+	ptrdiff_t getHandle() const {
+		return 0;
 	}
+};
+}
+}
 }
 
 #endif

+ 70 - 70
src/modules/graphics/vulkan/StreamBuffer.cpp

@@ -4,75 +4,75 @@
 
 
 namespace love {
-	namespace graphics {
-		namespace vulkan {
-			static VkBufferUsageFlags getUsageFlags(BufferUsage mode) {
-				switch (mode) {
-				case BUFFERUSAGE_VERTEX: return VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
-				case BUFFERUSAGE_INDEX: return VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
-				case BUFFERUSAGE_UNIFORM: return VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
-				default:
-					throw love::Exception("unsupported BufferUsage mode");
-				}
-			}
-
-			StreamBuffer::StreamBuffer(graphics::Graphics* gfx, BufferUsage mode, size_t size)
-				:	love::graphics::StreamBuffer(mode, size), gfx(gfx) {
-				loadVolatile();
-			}
-
-			bool StreamBuffer::loadVolatile() {
-				Graphics* vgfx = (Graphics*)gfx;
-				allocator = vgfx->getVmaAllocator();
-
-				VkBufferCreateInfo bufferInfo{};
-				bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
-				bufferInfo.size = getSize();
-				bufferInfo.usage = getUsageFlags(mode);
-				bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
-
-				VmaAllocationCreateInfo allocCreateInfo = {};
-				allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
-				allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT;	// always mapped
-
-				vmaCreateBuffer(allocator, &bufferInfo, &allocCreateInfo, &buffer, &allocation, &allocInfo);
-
-				usedGPUMemory = 0;
-
-				return true;
-			}
-
-			void StreamBuffer::unloadVolatile() {
-				if (buffer == VK_NULL_HANDLE)
-					return;
-
-				auto vgfx = (Graphics*)gfx;
-				vgfx->queueCleanUp([allocator=allocator, buffer=buffer, allocation=allocation](){
-					vmaDestroyBuffer(allocator, buffer, allocation);
-				});
-				buffer = VK_NULL_HANDLE;
-			}
-
-			StreamBuffer::~StreamBuffer() {
-				unloadVolatile();
-			}
-
-			love::graphics::StreamBuffer::MapInfo StreamBuffer::map(size_t minsize) {
-				(void)minsize;
-				return love::graphics::StreamBuffer::MapInfo((uint8*) allocInfo.pMappedData + usedGPUMemory, getSize());
-			}
-
-			size_t StreamBuffer::unmap(size_t usedSize) {
-				return usedGPUMemory;
-			}
-
-			void StreamBuffer::markUsed(size_t usedSize) {
-				usedGPUMemory += usedSize;
-			}
-
-			void StreamBuffer::nextFrame() {
-				usedGPUMemory = 0;
-			}
-		}
+namespace graphics {
+namespace vulkan {
+static VkBufferUsageFlags getUsageFlags(BufferUsage mode) {
+	switch (mode) {
+	case BUFFERUSAGE_VERTEX: return VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
+	case BUFFERUSAGE_INDEX: return VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
+	case BUFFERUSAGE_UNIFORM: return VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
+	default:
+		throw love::Exception("unsupported BufferUsage mode");
 	}
 }
+
+StreamBuffer::StreamBuffer(graphics::Graphics* gfx, BufferUsage mode, size_t size)
+	:	love::graphics::StreamBuffer(mode, size), gfx(gfx) {
+	loadVolatile();
+}
+
+bool StreamBuffer::loadVolatile() {
+	Graphics* vgfx = (Graphics*)gfx;
+	allocator = vgfx->getVmaAllocator();
+
+	VkBufferCreateInfo bufferInfo{};
+	bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+	bufferInfo.size = getSize();
+	bufferInfo.usage = getUsageFlags(mode);
+	bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+
+	VmaAllocationCreateInfo allocCreateInfo = {};
+	allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
+	allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT;	// always mapped
+
+	vmaCreateBuffer(allocator, &bufferInfo, &allocCreateInfo, &buffer, &allocation, &allocInfo);
+
+	usedGPUMemory = 0;
+
+	return true;
+}
+
+void StreamBuffer::unloadVolatile() {
+	if (buffer == VK_NULL_HANDLE)
+		return;
+
+	auto vgfx = (Graphics*)gfx;
+	vgfx->queueCleanUp([allocator=allocator, buffer=buffer, allocation=allocation](){
+		vmaDestroyBuffer(allocator, buffer, allocation);
+	});
+	buffer = VK_NULL_HANDLE;
+}
+
+StreamBuffer::~StreamBuffer() {
+	unloadVolatile();
+}
+
+love::graphics::StreamBuffer::MapInfo StreamBuffer::map(size_t minsize) {
+	(void)minsize;
+	return love::graphics::StreamBuffer::MapInfo((uint8*) allocInfo.pMappedData + usedGPUMemory, getSize());
+}
+
+size_t StreamBuffer::unmap(size_t usedSize) {
+	return usedGPUMemory;
+}
+
+void StreamBuffer::markUsed(size_t usedSize) {
+	usedGPUMemory += usedSize;
+}
+
+void StreamBuffer::nextFrame() {
+	usedGPUMemory = 0;
+}
+} // vulkan
+} // graphics
+} // love

+ 27 - 27
src/modules/graphics/vulkan/StreamBuffer.h

@@ -9,38 +9,38 @@
 #include "vk_mem_alloc.h"
 
 namespace love {
-	namespace graphics {
-		namespace vulkan {
-			class StreamBuffer : public love::graphics::StreamBuffer, public graphics::Volatile {
-			public:
-				StreamBuffer(graphics::Graphics* gfx, BufferUsage mode, size_t size);
-				virtual ~StreamBuffer();
+namespace graphics {
+namespace vulkan {
+class StreamBuffer : public love::graphics::StreamBuffer, public graphics::Volatile {
+public:
+	StreamBuffer(graphics::Graphics* gfx, BufferUsage mode, size_t size);
+	virtual ~StreamBuffer();
 
-				virtual bool loadVolatile() override;
+	virtual bool loadVolatile() override;
 
-				virtual void unloadVolatile() override;
+	virtual void unloadVolatile() override;
 
-				MapInfo map(size_t minsize) override;
-				size_t unmap(size_t usedSize) override;
-				void markUsed(size_t usedSize) override;
+	MapInfo map(size_t minsize) override;
+	size_t unmap(size_t usedSize) override;
+	void markUsed(size_t usedSize) override;
 
-				void nextFrame() override;
+	void nextFrame() override;
 
-				ptrdiff_t getHandle() const override {
-					return (ptrdiff_t) buffer;
-				}
-
-			private:
-				graphics::Graphics* gfx;
-				VmaAllocator allocator;
-				VmaAllocation allocation;
-				VmaAllocationInfo allocInfo;
-				VkBuffer buffer = VK_NULL_HANDLE;
-				size_t usedGPUMemory;
-
-			};
-		}
+	ptrdiff_t getHandle() const override {
+		return (ptrdiff_t) buffer;
 	}
-}
+
+private:
+	graphics::Graphics* gfx;
+	VmaAllocator allocator;
+	VmaAllocation allocation;
+	VmaAllocationInfo allocInfo;
+	VkBuffer buffer = VK_NULL_HANDLE;
+	size_t usedGPUMemory;
+
+};
+} // vulkan
+} // graphics
+} // love
 
 #endif

+ 266 - 266
src/modules/graphics/vulkan/Texture.cpp

@@ -8,272 +8,272 @@
 #define vgfx ((Graphics*)gfx)
 
 namespace love {
-	namespace graphics {
-		namespace vulkan {
-			Texture::Texture(love::graphics::Graphics* gfx, const Settings& settings, const Slices* data)
-				: love::graphics::Texture(gfx, settings, data), gfx(gfx), data(data) {
-				loadVolatile();
-			}
-
-			bool Texture::loadVolatile() {
-				allocator = vgfx->getVmaAllocator();
-				device = vgfx->getDevice();
-
-				auto vulkanFormat = Vulkan::getTextureFormat(format);
-
-				VkImageUsageFlags usageFlags = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
-
-				VkImageCreateInfo imageInfo{};
-				imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
-				imageInfo.imageType = VK_IMAGE_TYPE_2D;
-				imageInfo.extent.width = static_cast<uint32_t>(width);
-				imageInfo.extent.height = static_cast<uint32_t>(height);
-				imageInfo.extent.depth = 1;
-				imageInfo.mipLevels = 1;
-				imageInfo.arrayLayers = 1;
-				imageInfo.format = vulkanFormat.internalFormat;
-				imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
-				imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
-				imageInfo.usage = usageFlags;
-				imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
-				imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
-
-				VmaAllocationCreateInfo imageAllocationCreateInfo{};
-
-				if (vmaCreateImage(allocator, &imageInfo, &imageAllocationCreateInfo, &textureImage, &textureImageAllocation, nullptr) != VK_SUCCESS) {
-					throw love::Exception("failed to create image");
-				}
-				// fixme: we should use VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL as the default image layout instead of VK_IMAGE_LAYOUT_GENERAL.
-				transitionImageLayout(textureImage, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
-
-				if (data) {
-					auto sliceData = data->get(0, 0);
-					auto size = sliceData->getSize();
-					auto dataPtr = sliceData->getData();
-					Rect rect{};
-					rect.x = 0;
-					rect.y = 0;
-					rect.w = sliceData->getWidth();
-					rect.h = sliceData->getHeight();
-
-					uploadByteData(format, dataPtr, size, 0, 0, rect);
-				} else {
-					if (isRenderTarget()) {
-						clear(false);
-					}
-					else {
-						clear(true);
-					}
-				}
-				createTextureImageView();
-				createTextureSampler();
-
-				return true;
-			}
-
-			void Texture::unloadVolatile() {
-				if (textureImage == VK_NULL_HANDLE)
-					return;
-
-				vgfx->queueCleanUp([
-					device = device, 
-					textureSampler = textureSampler, 
-					textureImageView = textureImageView, 
-					allocator = allocator, 
-					textureImage = textureImage, 
-					textureImageAllocation = textureImageAllocation] () {
-					vkDestroySampler(device, textureSampler, nullptr);
-					vkDestroyImageView(device, textureImageView, nullptr);
-					vmaDestroyImage(allocator, textureImage, textureImageAllocation);
-				});
-
-				textureImage = VK_NULL_HANDLE;
-			}
-
-			Texture::~Texture() {
-				unloadVolatile();
-			}
-
-			void Texture::createTextureImageView() {
-				auto vulkanFormat = Vulkan::getTextureFormat(format);
-
-				VkImageViewCreateInfo viewInfo{};
-				viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
-				viewInfo.image = textureImage;
-				viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
-				viewInfo.format = vulkanFormat.internalFormat;
-				viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
-				viewInfo.subresourceRange.baseMipLevel = 0;
-				viewInfo.subresourceRange.levelCount = 1;
-				viewInfo.subresourceRange.baseArrayLayer = 0;
-				viewInfo.subresourceRange.layerCount = 1;
-				viewInfo.components.r = vulkanFormat.swizzleR;
-				viewInfo.components.g = vulkanFormat.swizzleG;
-				viewInfo.components.b = vulkanFormat.swizzleB;
-				viewInfo.components.a = vulkanFormat.swizzleA;
-
-				if (vkCreateImageView(device, &viewInfo, nullptr, &textureImageView) != VK_SUCCESS) {
-					throw love::Exception("could not create texture image view");
-				}
-			}
-
-			void Texture::createTextureSampler() {
-				auto physicalDevice = vgfx->getPhysicalDevice();
-				VkPhysicalDeviceProperties properties{};
-				vkGetPhysicalDeviceProperties(physicalDevice, &properties);
-
-				VkSamplerCreateInfo samplerInfo{};
-				samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
-				samplerInfo.magFilter = VK_FILTER_LINEAR;
-				samplerInfo.minFilter = VK_FILTER_LINEAR;
-				samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
-				samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
-				samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
-				samplerInfo.anisotropyEnable = VK_TRUE;
-				samplerInfo.maxAnisotropy = properties.limits.maxSamplerAnisotropy;
-				samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
-				samplerInfo.unnormalizedCoordinates = VK_FALSE;
-				samplerInfo.compareEnable = VK_FALSE;
-				samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
-				samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
-				samplerInfo.mipLodBias = 0.0f;
-				samplerInfo.minLod = 0.0f;
-				samplerInfo.maxLod = 0.0f;
-
-				if (vkCreateSampler(device, &samplerInfo, nullptr, &textureSampler) != VK_SUCCESS) {
-					throw love::Exception("failed to create texture sampler");
-				}
-			}
-
-			void Texture::clear(bool white) {
-				auto commandBuffer = vgfx->beginSingleTimeCommands();
-
-				auto clearColor = getClearValue(white);
-
-				VkImageSubresourceRange range{};
-				range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
-				range.layerCount = getMipmapCount();
-				range.levelCount = 1;
-
-				vkCmdClearColorImage(commandBuffer, textureImage, VK_IMAGE_LAYOUT_GENERAL, &clearColor, 1, &range);
-
-				vgfx->endSingleTimeCommands(commandBuffer);
-			}
-
-			VkClearColorValue Texture::getClearValue(bool white) {
-				auto vulkanFormat = Vulkan::getTextureFormat(format);
-
-				VkClearColorValue clearColor{};
-				if (white) {
-					switch (vulkanFormat.internalFormatRepresentation) {
-					case FORMATREPRESENTATION_FLOAT:
-						clearColor.float32[0] = 1.0f;
-						clearColor.float32[1] = 1.0f;
-						clearColor.float32[2] = 1.0f;
-						clearColor.float32[3] = 1.0f;
-						break;
-					case FORMATREPRESENTATION_SINT:
-						clearColor.int32[0] = std::numeric_limits<int32_t>::max();
-						clearColor.int32[1] = std::numeric_limits<int32_t>::max();
-						clearColor.int32[2] = std::numeric_limits<int32_t>::max();
-						clearColor.int32[3] = std::numeric_limits<int32_t>::max();
-						break;
-					case FORMATREPRESENTATION_UINT:
-						clearColor.uint32[0] = std::numeric_limits<uint32_t>::max();
-						clearColor.uint32[1] = std::numeric_limits<uint32_t>::max();
-						clearColor.uint32[2] = std::numeric_limits<uint32_t>::max();
-						clearColor.uint32[3] = std::numeric_limits<uint32_t>::max();
-						break;
-					}
-				}
-				else {
-					switch (vulkanFormat.internalFormatRepresentation) {
-					case FORMATREPRESENTATION_FLOAT:
-						clearColor.float32[0] = 0.0f;
-						clearColor.float32[1] = 0.0f;
-						clearColor.float32[2] = 0.0f;
-						clearColor.float32[3] = 0.0f;
-						break;
-					case FORMATREPRESENTATION_SINT:
-						clearColor.int32[0] = 0;
-						clearColor.int32[1] = 0;
-						clearColor.int32[2] = 0;
-						clearColor.int32[3] = 0;
-						break;
-					case FORMATREPRESENTATION_UINT:
-						clearColor.uint32[0] = 0;
-						clearColor.uint32[1] = 0;
-						clearColor.uint32[2] = 0;
-						clearColor.uint32[3] = 0;
-						break;
-					}
-				}
-				return clearColor;
-			}
-
-			void Texture::transitionImageLayout(VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout) {
-				auto commandBuffer = vgfx->beginSingleTimeCommands();
-
-				Vulkan::cmdTransitionImageLayout(commandBuffer, image, oldLayout, newLayout);
-
-				vgfx->endSingleTimeCommands(commandBuffer);
-			}
-
-			void Texture::uploadByteData(PixelFormat pixelformat, const void* data, size_t size, int level, int slice, const Rect& r) {
-				VkBuffer stagingBuffer;
-				VmaAllocation vmaAllocation;
-
-				VkBufferCreateInfo bufferCreateInfo{};
-				bufferCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
-				bufferCreateInfo.size = size;
-				bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
-
-				VmaAllocationCreateInfo allocCreateInfo = {};
-				allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
-				allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT;
-
-				VmaAllocationInfo allocInfo;
-				vmaCreateBuffer(allocator, &bufferCreateInfo, &allocCreateInfo, &stagingBuffer, &vmaAllocation, &allocInfo);
-
-				memcpy(allocInfo.pMappedData, data, size);
-
-				auto command = [buffer = stagingBuffer, image = textureImage, r = r](VkCommandBuffer commandBuffer) {
-					VkBufferImageCopy region{};
-					region.bufferOffset = 0;
-					region.bufferRowLength = 0;
-					region.bufferImageHeight = 0;
-
-					region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
-					region.imageSubresource.mipLevel = 0;
-					region.imageSubresource.baseArrayLayer = 0;
-					region.imageSubresource.layerCount = 1;
-
-					region.imageOffset = { r.x, r.y, 0 };
-					region.imageExtent = {
-						static_cast<uint32_t>(r.w),
-						static_cast<uint32_t>(r.h), 1
-					};
-
-					Vulkan::cmdTransitionImageLayout(commandBuffer, image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
-
-					vkCmdCopyBufferToImage(
-						commandBuffer,
-						buffer,
-						image,
-						VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
-						1,
-						&region
-					);
-
-					Vulkan::cmdTransitionImageLayout(commandBuffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL);
-				};
-
-				auto cleanUp = [allocator = allocator, stagingBuffer, vmaAllocation]() {
-					vmaDestroyBuffer(allocator, stagingBuffer, vmaAllocation);
-				};
-
-				vgfx->queueDatatransfer(command, cleanUp);
-			}
+namespace graphics {
+namespace vulkan {
+Texture::Texture(love::graphics::Graphics* gfx, const Settings& settings, const Slices* data)
+	: love::graphics::Texture(gfx, settings, data), gfx(gfx), data(data) {
+	loadVolatile();
+}
+
+bool Texture::loadVolatile() {
+	allocator = vgfx->getVmaAllocator();
+	device = vgfx->getDevice();
+
+	auto vulkanFormat = Vulkan::getTextureFormat(format);
+
+	VkImageUsageFlags usageFlags = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
+
+	VkImageCreateInfo imageInfo{};
+	imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
+	imageInfo.imageType = VK_IMAGE_TYPE_2D;
+	imageInfo.extent.width = static_cast<uint32_t>(width);
+	imageInfo.extent.height = static_cast<uint32_t>(height);
+	imageInfo.extent.depth = 1;
+	imageInfo.mipLevels = 1;
+	imageInfo.arrayLayers = 1;
+	imageInfo.format = vulkanFormat.internalFormat;
+	imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+	imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+	imageInfo.usage = usageFlags;
+	imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+	imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+
+	VmaAllocationCreateInfo imageAllocationCreateInfo{};
+
+	if (vmaCreateImage(allocator, &imageInfo, &imageAllocationCreateInfo, &textureImage, &textureImageAllocation, nullptr) != VK_SUCCESS) {
+		throw love::Exception("failed to create image");
+	}
+	// fixme: we should use VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL as the default image layout instead of VK_IMAGE_LAYOUT_GENERAL.
+	transitionImageLayout(textureImage, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
+
+	if (data) {
+		auto sliceData = data->get(0, 0);
+		auto size = sliceData->getSize();
+		auto dataPtr = sliceData->getData();
+		Rect rect{};
+		rect.x = 0;
+		rect.y = 0;
+		rect.w = sliceData->getWidth();
+		rect.h = sliceData->getHeight();
+
+		uploadByteData(format, dataPtr, size, 0, 0, rect);
+	} else {
+		if (isRenderTarget()) {
+			clear(false);
 		}
+		else {
+			clear(true);
+		}
+	}
+	createTextureImageView();
+	createTextureSampler();
+
+	return true;
+}
+
+void Texture::unloadVolatile() {
+	if (textureImage == VK_NULL_HANDLE)
+		return;
+
+	vgfx->queueCleanUp([
+		device = device, 
+		textureSampler = textureSampler, 
+		textureImageView = textureImageView, 
+		allocator = allocator, 
+		textureImage = textureImage, 
+		textureImageAllocation = textureImageAllocation] () {
+		vkDestroySampler(device, textureSampler, nullptr);
+		vkDestroyImageView(device, textureImageView, nullptr);
+		vmaDestroyImage(allocator, textureImage, textureImageAllocation);
+	});
+
+	textureImage = VK_NULL_HANDLE;
+}
+
+Texture::~Texture() {
+	unloadVolatile();
+}
+
+void Texture::createTextureImageView() {
+	auto vulkanFormat = Vulkan::getTextureFormat(format);
+
+	VkImageViewCreateInfo viewInfo{};
+	viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+	viewInfo.image = textureImage;
+	viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+	viewInfo.format = vulkanFormat.internalFormat;
+	viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+	viewInfo.subresourceRange.baseMipLevel = 0;
+	viewInfo.subresourceRange.levelCount = 1;
+	viewInfo.subresourceRange.baseArrayLayer = 0;
+	viewInfo.subresourceRange.layerCount = 1;
+	viewInfo.components.r = vulkanFormat.swizzleR;
+	viewInfo.components.g = vulkanFormat.swizzleG;
+	viewInfo.components.b = vulkanFormat.swizzleB;
+	viewInfo.components.a = vulkanFormat.swizzleA;
+
+	if (vkCreateImageView(device, &viewInfo, nullptr, &textureImageView) != VK_SUCCESS) {
+		throw love::Exception("could not create texture image view");
 	}
 }
+
+void Texture::createTextureSampler() {
+	auto physicalDevice = vgfx->getPhysicalDevice();
+	VkPhysicalDeviceProperties properties{};
+	vkGetPhysicalDeviceProperties(physicalDevice, &properties);
+
+	VkSamplerCreateInfo samplerInfo{};
+	samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
+	samplerInfo.magFilter = VK_FILTER_LINEAR;
+	samplerInfo.minFilter = VK_FILTER_LINEAR;
+	samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+	samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+	samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+	samplerInfo.anisotropyEnable = VK_TRUE;
+	samplerInfo.maxAnisotropy = properties.limits.maxSamplerAnisotropy;
+	samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
+	samplerInfo.unnormalizedCoordinates = VK_FALSE;
+	samplerInfo.compareEnable = VK_FALSE;
+	samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
+	samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+	samplerInfo.mipLodBias = 0.0f;
+	samplerInfo.minLod = 0.0f;
+	samplerInfo.maxLod = 0.0f;
+
+	if (vkCreateSampler(device, &samplerInfo, nullptr, &textureSampler) != VK_SUCCESS) {
+		throw love::Exception("failed to create texture sampler");
+	}
+}
+
+void Texture::clear(bool white) {
+	auto commandBuffer = vgfx->beginSingleTimeCommands();
+
+	auto clearColor = getClearValue(white);
+
+	VkImageSubresourceRange range{};
+	range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+	range.layerCount = getMipmapCount();
+	range.levelCount = 1;
+
+	vkCmdClearColorImage(commandBuffer, textureImage, VK_IMAGE_LAYOUT_GENERAL, &clearColor, 1, &range);
+
+	vgfx->endSingleTimeCommands(commandBuffer);
+}
+
+VkClearColorValue Texture::getClearValue(bool white) {
+	auto vulkanFormat = Vulkan::getTextureFormat(format);
+
+	VkClearColorValue clearColor{};
+	if (white) {
+		switch (vulkanFormat.internalFormatRepresentation) {
+		case FORMATREPRESENTATION_FLOAT:
+			clearColor.float32[0] = 1.0f;
+			clearColor.float32[1] = 1.0f;
+			clearColor.float32[2] = 1.0f;
+			clearColor.float32[3] = 1.0f;
+			break;
+		case FORMATREPRESENTATION_SINT:
+			clearColor.int32[0] = std::numeric_limits<int32_t>::max();
+			clearColor.int32[1] = std::numeric_limits<int32_t>::max();
+			clearColor.int32[2] = std::numeric_limits<int32_t>::max();
+			clearColor.int32[3] = std::numeric_limits<int32_t>::max();
+			break;
+		case FORMATREPRESENTATION_UINT:
+			clearColor.uint32[0] = std::numeric_limits<uint32_t>::max();
+			clearColor.uint32[1] = std::numeric_limits<uint32_t>::max();
+			clearColor.uint32[2] = std::numeric_limits<uint32_t>::max();
+			clearColor.uint32[3] = std::numeric_limits<uint32_t>::max();
+			break;
+		}
+	}
+	else {
+		switch (vulkanFormat.internalFormatRepresentation) {
+		case FORMATREPRESENTATION_FLOAT:
+			clearColor.float32[0] = 0.0f;
+			clearColor.float32[1] = 0.0f;
+			clearColor.float32[2] = 0.0f;
+			clearColor.float32[3] = 0.0f;
+			break;
+		case FORMATREPRESENTATION_SINT:
+			clearColor.int32[0] = 0;
+			clearColor.int32[1] = 0;
+			clearColor.int32[2] = 0;
+			clearColor.int32[3] = 0;
+			break;
+		case FORMATREPRESENTATION_UINT:
+			clearColor.uint32[0] = 0;
+			clearColor.uint32[1] = 0;
+			clearColor.uint32[2] = 0;
+			clearColor.uint32[3] = 0;
+			break;
+		}
+	}
+	return clearColor;
+}
+
+void Texture::transitionImageLayout(VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout) {
+	auto commandBuffer = vgfx->beginSingleTimeCommands();
+
+	Vulkan::cmdTransitionImageLayout(commandBuffer, image, oldLayout, newLayout);
+
+	vgfx->endSingleTimeCommands(commandBuffer);
+}
+
+void Texture::uploadByteData(PixelFormat pixelformat, const void* data, size_t size, int level, int slice, const Rect& r) {
+	VkBuffer stagingBuffer;
+	VmaAllocation vmaAllocation;
+
+	VkBufferCreateInfo bufferCreateInfo{};
+	bufferCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+	bufferCreateInfo.size = size;
+	bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
+
+	VmaAllocationCreateInfo allocCreateInfo = {};
+	allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
+	allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT;
+
+	VmaAllocationInfo allocInfo;
+	vmaCreateBuffer(allocator, &bufferCreateInfo, &allocCreateInfo, &stagingBuffer, &vmaAllocation, &allocInfo);
+
+	memcpy(allocInfo.pMappedData, data, size);
+
+	auto command = [buffer = stagingBuffer, image = textureImage, r = r](VkCommandBuffer commandBuffer) {
+		VkBufferImageCopy region{};
+		region.bufferOffset = 0;
+		region.bufferRowLength = 0;
+		region.bufferImageHeight = 0;
+
+		region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+		region.imageSubresource.mipLevel = 0;
+		region.imageSubresource.baseArrayLayer = 0;
+		region.imageSubresource.layerCount = 1;
+
+		region.imageOffset = { r.x, r.y, 0 };
+		region.imageExtent = {
+			static_cast<uint32_t>(r.w),
+			static_cast<uint32_t>(r.h), 1
+		};
+
+		Vulkan::cmdTransitionImageLayout(commandBuffer, image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
+
+		vkCmdCopyBufferToImage(
+			commandBuffer,
+			buffer,
+			image,
+			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+			1,
+			&region
+		);
+
+		Vulkan::cmdTransitionImageLayout(commandBuffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL);
+	};
+
+	auto cleanUp = [allocator = allocator, stagingBuffer, vmaAllocation]() {
+		vmaDestroyBuffer(allocator, stagingBuffer, vmaAllocation);
+	};
+
+	vgfx->queueDatatransfer(command, cleanUp);
+}
+} // vulkan
+} // graphics
+} // love

+ 45 - 45
src/modules/graphics/vulkan/Texture.h

@@ -10,50 +10,50 @@
 
 
 namespace love {
-	namespace graphics {
-		namespace vulkan {
-			class Texture : public graphics::Texture, public Volatile {
-			public:
-				Texture(love::graphics::Graphics* gfx, const Settings& settings, const Slices* data);
-				~Texture();
-
-				virtual bool loadVolatile() override;
-				virtual void unloadVolatile() override;
-
-				void copyFromBuffer(graphics::Buffer* source, size_t sourceoffset, int sourcewidth, size_t size, int slice, int mipmap, const Rect& rect) override { };
-				void copyToBuffer(graphics::Buffer* dest, int slice, int mipmap, const Rect& rect, size_t destoffset, int destwidth, size_t size) override { };
-
-				ptrdiff_t getRenderTargetHandle() const override { return (ptrdiff_t)textureImage; };
-				ptrdiff_t getSamplerHandle() const override { return (ptrdiff_t)textureSampler; };
-
-				void uploadByteData(PixelFormat pixelformat, const void* data, size_t size, int level, int slice, const Rect& r) override;
-
-				void generateMipmapsInternal()  override { };
-
-				int getMSAA() const override { return 0; };
-				ptrdiff_t getHandle() const override { return (ptrdiff_t)textureImage; }
-				VkImageView getImageView() const { return textureImageView; }
-				VkSampler getSampler() const { return textureSampler; }
-
-			private:
-				void transitionImageLayout(VkImage, VkImageLayout oldLayout, VkImageLayout newLayout);
-				void createTextureImageView();
-				void createTextureSampler();
-				void clear(bool white);
-
-				VkClearColorValue getClearValue(bool white);
-
-				graphics::Graphics* gfx;
-				VkDevice device;
-				VmaAllocator allocator;
-				VkImage textureImage = VK_NULL_HANDLE;
-				VmaAllocation textureImageAllocation;
-				VkImageView textureImageView;
-				VkSampler textureSampler;
-				const Slices* data;
-			};
-		}
-	}
-}
+namespace graphics {
+namespace vulkan {
+class Texture : public graphics::Texture, public Volatile {
+public:
+	Texture(love::graphics::Graphics* gfx, const Settings& settings, const Slices* data);
+	~Texture();
+
+	virtual bool loadVolatile() override;
+	virtual void unloadVolatile() override;
+
+	void copyFromBuffer(graphics::Buffer* source, size_t sourceoffset, int sourcewidth, size_t size, int slice, int mipmap, const Rect& rect) override { };
+	void copyToBuffer(graphics::Buffer* dest, int slice, int mipmap, const Rect& rect, size_t destoffset, int destwidth, size_t size) override { };
+
+	ptrdiff_t getRenderTargetHandle() const override { return (ptrdiff_t)textureImage; };
+	ptrdiff_t getSamplerHandle() const override { return (ptrdiff_t)textureSampler; };
+
+	void uploadByteData(PixelFormat pixelformat, const void* data, size_t size, int level, int slice, const Rect& r) override;
+
+	void generateMipmapsInternal()  override { };
+
+	int getMSAA() const override { return 0; };
+	ptrdiff_t getHandle() const override { return (ptrdiff_t)textureImage; }
+	VkImageView getImageView() const { return textureImageView; }
+	VkSampler getSampler() const { return textureSampler; }
+
+private:
+	void transitionImageLayout(VkImage, VkImageLayout oldLayout, VkImageLayout newLayout);
+	void createTextureImageView();
+	void createTextureSampler();
+	void clear(bool white);
+
+	VkClearColorValue getClearValue(bool white);
+
+	graphics::Graphics* gfx;
+	VkDevice device;
+	VmaAllocator allocator;
+	VkImage textureImage = VK_NULL_HANDLE;
+	VmaAllocation textureImageAllocation;
+	VkImageView textureImageView;
+	VkSampler textureSampler;
+	const Slices* data;
+};
+} // vulkan
+} // graphics
+} // love
 
 #endif

+ 543 - 543
src/modules/graphics/vulkan/Vulkan.cpp

@@ -4,548 +4,548 @@
 
 
 namespace love {
-	namespace graphics {
-		namespace vulkan {
-			static uint32_t numShaderSwitches;
-
-			void Vulkan::shaderSwitch() {
-				numShaderSwitches++;
-			}
-
-			uint32_t Vulkan::getNumShaderSwitches() {
-				return numShaderSwitches;
-			}
-			
-			void Vulkan::resetShaderSwitches() {
-				numShaderSwitches = 0;
-			}
-
-			VkFormat Vulkan::getVulkanVertexFormat(DataFormat format) {
-				switch (format) {
-				case DATAFORMAT_FLOAT:
-					return VK_FORMAT_R32_SFLOAT;
-				case DATAFORMAT_FLOAT_VEC2:
-					return VK_FORMAT_R32G32_SFLOAT;
-				case DATAFORMAT_FLOAT_VEC3:
-					return VK_FORMAT_R32G32B32_SFLOAT;
-				case DATAFORMAT_FLOAT_VEC4:
-					return VK_FORMAT_R32G32B32A32_SFLOAT;
-
-				case DATAFORMAT_FLOAT_MAT2X2:
-				case DATAFORMAT_FLOAT_MAT2X3:
-				case DATAFORMAT_FLOAT_MAT2X4:
-				case DATAFORMAT_FLOAT_MAT3X2:
-				case DATAFORMAT_FLOAT_MAT3X3:
-				case DATAFORMAT_FLOAT_MAT3X4:
-				case DATAFORMAT_FLOAT_MAT4X2:
-				case DATAFORMAT_FLOAT_MAT4X3:
-				case DATAFORMAT_FLOAT_MAT4X4:
-					throw love::Exception("unimplemented data format (matnxm)");
-
-				case DATAFORMAT_INT32:
-					return VK_FORMAT_R32_SINT;
-				case DATAFORMAT_INT32_VEC2:
-					return VK_FORMAT_R32G32_SINT;
-				case DATAFORMAT_INT32_VEC3:
-					return VK_FORMAT_R32G32B32_SINT;
-				case DATAFORMAT_INT32_VEC4:
-					return VK_FORMAT_R32G32B32A32_SINT;
-
-				case DATAFORMAT_UINT32:
-					return VK_FORMAT_R32_UINT;
-				case DATAFORMAT_UINT32_VEC2:
-					return VK_FORMAT_R32G32_UINT;
-				case DATAFORMAT_UINT32_VEC3:
-					return VK_FORMAT_R32G32B32_UINT;
-				case DATAFORMAT_UINT32_VEC4:
-					return VK_FORMAT_R32G32B32A32_UINT;
-
-				case DATAFORMAT_SNORM8_VEC4:
-					return VK_FORMAT_R8G8B8A8_SNORM;
-				case DATAFORMAT_UNORM8_VEC4:
-					return VK_FORMAT_R8G8B8A8_UNORM;
-				case DATAFORMAT_INT8_VEC4:
-					return VK_FORMAT_R8G8B8A8_SINT;
-				case DATAFORMAT_UINT8_VEC4:
-					return VK_FORMAT_R8G8B8A8_UINT;
-
-				case DATAFORMAT_SNORM16_VEC2:
-					return VK_FORMAT_R16G16_SNORM;
-				case DATAFORMAT_SNORM16_VEC4:
-					return VK_FORMAT_R16G16B16A16_SNORM;
-				case DATAFORMAT_UNORM16_VEC2:
-					return VK_FORMAT_R16G16_UNORM;
-				case DATAFORMAT_UNORM16_VEC4:
-					return VK_FORMAT_R16G16B16A16_UNORM;
-
-				case DATAFORMAT_INT16_VEC2:
-					return VK_FORMAT_R16G16_SINT;
-				case DATAFORMAT_INT16_VEC4:
-					return VK_FORMAT_R16G16B16A16_SINT;
-
-				case DATAFORMAT_UINT16:
-					return VK_FORMAT_R16_UINT;
-				case DATAFORMAT_UINT16_VEC2:
-					return VK_FORMAT_R16G16_UINT;
-				case DATAFORMAT_UINT16_VEC4:
-					return VK_FORMAT_R16G16B16A16_UINT;
-
-				case DATAFORMAT_BOOL:
-				case DATAFORMAT_BOOL_VEC2:
-				case DATAFORMAT_BOOL_VEC3:
-				case DATAFORMAT_BOOL_VEC4:
-					throw love::Exception("unimplemented data format (bool)");
-
-				default:
-					throw love::Exception("unknown data format");
-				}
-			}
-
-			TextureFormat Vulkan::getTextureFormat(PixelFormat format) {
-				TextureFormat textureFormat{};
-
-				switch (format) {
-					case PIXELFORMAT_UNKNOWN:
-						throw love::Exception("unknown pixel format");
-					case PIXELFORMAT_NORMAL:
-						textureFormat.internalFormat = VK_FORMAT_R8G8B8A8_SRGB;
-						textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_FLOAT;
-						break;
-					case PIXELFORMAT_HDR:
-						throw love::Exception("unimplemented pixel format: hdr");
-					case PIXELFORMAT_R8_UNORM:
-						textureFormat.internalFormat = VK_FORMAT_R8_UNORM;
-						textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_FLOAT;
-						break;
-					case PIXELFORMAT_R8_INT:
-						textureFormat.internalFormat = VK_FORMAT_R8_SINT;
-						textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_SINT;
-						break;
-					case PIXELFORMAT_R8_UINT:
-						textureFormat.internalFormat = VK_FORMAT_R8_UINT;
-						textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_UINT;
-						break;
-					case PIXELFORMAT_R16_UNORM:
-						textureFormat.internalFormat = VK_FORMAT_R16_UNORM;
-						textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_FLOAT;
-						break;
-					case PIXELFORMAT_R16_FLOAT:
-						textureFormat.internalFormat = VK_FORMAT_R16_SFLOAT;
-						textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_FLOAT;
-						break;
-					case PIXELFORMAT_R16_INT:
-						textureFormat.internalFormat = VK_FORMAT_R16_SINT;
-						textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_SINT;
-						break;
-					case PIXELFORMAT_R16_UINT:
-						textureFormat.internalFormat = VK_FORMAT_R16_UINT;
-						textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_UINT;
-						break;
-					case PIXELFORMAT_R32_FLOAT:
-						textureFormat.internalFormat = VK_FORMAT_R32_SFLOAT;
-						textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_FLOAT;
-						break;
-					case PIXELFORMAT_R32_INT:
-						textureFormat.internalFormat = VK_FORMAT_R32_SINT;
-						textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_SINT;
-						break;
-					case PIXELFORMAT_R32_UINT:
-						textureFormat.internalFormat = VK_FORMAT_R32_UINT;
-						textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_UINT;
-						break;
-					case PIXELFORMAT_RG8_UNORM:
-						textureFormat.internalFormat = VK_FORMAT_R8G8_UNORM;
-						textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_FLOAT;
-						break;
-					case PIXELFORMAT_RG8_INT:
-						textureFormat.internalFormat = VK_FORMAT_R8G8_SINT;
-						textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_SINT;
-						break;
-					case PIXELFORMAT_RG8_UINT:
-						textureFormat.internalFormat = VK_FORMAT_R8G8_UINT;
-						textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_UINT;
-						break;
-					case PIXELFORMAT_LA8_UNORM: // Same as RG8: but accessed as (L: L: L: A)
-						textureFormat.internalFormat = VK_FORMAT_R8G8_UNORM;
-						textureFormat.swizzleR = VK_COMPONENT_SWIZZLE_R;
-						textureFormat.swizzleG = VK_COMPONENT_SWIZZLE_R;
-						textureFormat.swizzleB = VK_COMPONENT_SWIZZLE_R;
-						textureFormat.swizzleA = VK_COMPONENT_SWIZZLE_G;
-						textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_FLOAT;
-						break;
-					case PIXELFORMAT_RG16_UNORM:
-						textureFormat.internalFormat = VK_FORMAT_R16G16_UNORM;
-						textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_FLOAT;
-						break;
-					case PIXELFORMAT_RG16_FLOAT:
-						textureFormat.internalFormat = VK_FORMAT_R16G16_SFLOAT;
-						textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_FLOAT;
-						break;
-					case PIXELFORMAT_RG16_INT:
-						textureFormat.internalFormat = VK_FORMAT_R16G16_SINT;
-						textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_SINT;
-						break;
-					case PIXELFORMAT_RG16_UINT:
-						textureFormat.internalFormat = VK_FORMAT_R16G16_UINT;
-						textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_UINT;
-						break;
-					case PIXELFORMAT_RG32_FLOAT:
-						textureFormat.internalFormat = VK_FORMAT_R32G32_SFLOAT;
-						textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_FLOAT;
-						break;
-					case PIXELFORMAT_RG32_INT:
-						textureFormat.internalFormat = VK_FORMAT_R32G32_SINT;
-						textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_SINT;
-						break;
-					case PIXELFORMAT_RG32_UINT:
-						textureFormat.internalFormat = VK_FORMAT_R32G32_UINT;
-						textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_UINT;
-						break;
-					case PIXELFORMAT_RGBA8_UNORM:
-						textureFormat.internalFormat = VK_FORMAT_R8G8B8A8_UNORM;
-						textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_FLOAT;
-						break;
-					case PIXELFORMAT_RGBA8_UNORM_sRGB:
-						textureFormat.internalFormat = VK_FORMAT_R8G8B8A8_SRGB;
-						textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_FLOAT;
-						break;
-					case PIXELFORMAT_BGRA8_UNORM:
-						textureFormat.internalFormat = VK_FORMAT_B8G8R8A8_UNORM;
-						textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_FLOAT;
-						break;
-					case PIXELFORMAT_BGRA8_UNORM_sRGB:
-						textureFormat.internalFormat = VK_FORMAT_B8G8R8A8_SRGB;
-						textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_FLOAT;
-						break;
-					case PIXELFORMAT_RGBA8_INT:
-						textureFormat.internalFormat = VK_FORMAT_R8G8B8A8_SINT;
-						textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_SINT;
-						break;
-					case PIXELFORMAT_RGBA8_UINT:
-						textureFormat.internalFormat = VK_FORMAT_R8G8B8A8_UINT;
-						textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_UINT;
-						break;
-					case PIXELFORMAT_RGBA16_UNORM:
-						textureFormat.internalFormat = VK_FORMAT_R16G16B16A16_UNORM;
-						textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_FLOAT;
-						break;
-					case PIXELFORMAT_RGBA16_FLOAT:
-						textureFormat.internalFormat = VK_FORMAT_R16G16B16A16_SFLOAT;
-						textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_FLOAT;
-						break;
-					case PIXELFORMAT_RGBA16_INT:
-						textureFormat.internalFormat = VK_FORMAT_R16G16B16A16_SINT;
-						textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_SINT;
-						break;
-					case PIXELFORMAT_RGBA16_UINT:
-						textureFormat.internalFormat = VK_FORMAT_R16G16B16A16_UINT;
-						textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_UINT;
-						break;
-					case PIXELFORMAT_RGBA32_FLOAT:
-						textureFormat.internalFormat = VK_FORMAT_R32G32B32A32_SFLOAT;
-						textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_FLOAT;
-						break;
-					case PIXELFORMAT_RGBA32_INT:
-						textureFormat.internalFormat = VK_FORMAT_R32G32B32A32_SINT;
-						textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_SINT;
-						break;
-					case PIXELFORMAT_RGBA32_UINT:
-						textureFormat.internalFormat = VK_FORMAT_R32G32B32A32_UINT;
-						textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_UINT;
-						break;
-					case PIXELFORMAT_RGBA4_UNORM:    // LSB->MSB: [a: b: g: r]
-					case PIXELFORMAT_RGB5A1_UNORM:   // LSB->MSB: [a: b: g: r]
-					case PIXELFORMAT_RGB565_UNORM:   // LSB->MSB: [b: g: r]
-					case PIXELFORMAT_RGB10A2_UNORM:  // LSB->MSB: [r: g: b: a]
-					case PIXELFORMAT_RG11B10_FLOAT:  // LSB->MSB: [r: g: b]
-					case PIXELFORMAT_STENCIL8:
-					case PIXELFORMAT_DEPTH16_UNORM:
-					case PIXELFORMAT_DEPTH24_UNORM:
-					case PIXELFORMAT_DEPTH32_FLOAT:
-					case PIXELFORMAT_DEPTH24_UNORM_STENCIL8:
-					case PIXELFORMAT_DEPTH32_FLOAT_STENCIL8:
-					case PIXELFORMAT_DXT1_UNORM:
-					case PIXELFORMAT_DXT3_UNORM:
-					case PIXELFORMAT_DXT5_UNORM:
-					case PIXELFORMAT_BC4_UNORM:
-					case PIXELFORMAT_BC4_SNORM:
-					case PIXELFORMAT_BC5_UNORM:
-					case PIXELFORMAT_BC5_SNORM:
-					case PIXELFORMAT_BC6H_UFLOAT:
-					case PIXELFORMAT_BC6H_FLOAT:
-					case PIXELFORMAT_BC7_UNORM:
-					case PIXELFORMAT_PVR1_RGB2_UNORM:
-					case PIXELFORMAT_PVR1_RGB4_UNORM:
-					case PIXELFORMAT_PVR1_RGBA2_UNORM:
-					case PIXELFORMAT_PVR1_RGBA4_UNORM:
-					case PIXELFORMAT_ETC1_UNORM:
-					case PIXELFORMAT_ETC2_RGB_UNORM:
-					case PIXELFORMAT_ETC2_RGBA_UNORM:
-					case PIXELFORMAT_ETC2_RGBA1_UNORM:
-					case PIXELFORMAT_EAC_R_UNORM:
-					case PIXELFORMAT_EAC_R_SNORM:
-					case PIXELFORMAT_EAC_RG_UNORM:
-					case PIXELFORMAT_EAC_RG_SNORM:
-					case PIXELFORMAT_ASTC_4x4:
-					case PIXELFORMAT_ASTC_5x4:
-					case PIXELFORMAT_ASTC_5x5:
-					case PIXELFORMAT_ASTC_6x5:
-					case PIXELFORMAT_ASTC_6x6:
-					case PIXELFORMAT_ASTC_8x5:
-					case PIXELFORMAT_ASTC_8x6:
-					case PIXELFORMAT_ASTC_8x8:
-					case PIXELFORMAT_ASTC_10x5:
-					case PIXELFORMAT_ASTC_10x6:
-					case PIXELFORMAT_ASTC_10x8:
-					case PIXELFORMAT_ASTC_10x10:
-					case PIXELFORMAT_ASTC_12x10:
-					case PIXELFORMAT_ASTC_12x12:
-						throw love::Exception("unimplemented pixel format");
-				}
-
-				return textureFormat;
-			}
-
-			// values taken from https://pcisig.com/membership/member-companies
-			// as specified at https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkPhysicalDeviceProperties.html
-			std::string Vulkan::getVendorName(uint32_t vendorId) {
-				switch (vendorId) {
-				case 4130:
-					return "AMD";
-				case 4318:
-					return "Nvidia";
-				case 8086:
-					return "Intel";
-				case 4203:
-					return "Apple";
-				case 5140:
-					return "Microsoft";
-				case 5045:
-					return "ARM";
-				case 20803:
-					return "Qualcomm";
-				case 5348:
-					return "Broadcom";
-				default:
-					return "unknown";
-				}
-			}
-
-			std::string Vulkan::getVulkanApiVersion(uint32_t version) {
-				std::stringstream ss;
-
-				ss << VK_API_VERSION_MAJOR(version) 
-				   << "." << VK_API_VERSION_MINOR(version) 
-				   << "." << VK_API_VERSION_PATCH(version);
-
-				return ss.str();
-			}
-
-			VkPrimitiveTopology Vulkan::getPrimitiveTypeTopology(graphics::PrimitiveType primitiveType) {
-				switch (primitiveType) {
-				case PRIMITIVE_POINTS:
-					return VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
-				case PRIMITIVE_TRIANGLES:
-					return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
-				case PRIMITIVE_TRIANGLE_FAN:
-					return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN;
-				case PRIMITIVE_TRIANGLE_STRIP:
-					return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
-				default:
-					throw love::Exception("unknown primitive type");
-				}
-			}
-
-			VkBlendFactor Vulkan::getBlendFactor(BlendFactor blendFactor) {
-				switch (blendFactor) {
-				case BLENDFACTOR_ZERO:
-					return VK_BLEND_FACTOR_ZERO;
-				case BLENDFACTOR_ONE:
-					return VK_BLEND_FACTOR_ONE;
-				case BLENDFACTOR_SRC_COLOR:
-					return VK_BLEND_FACTOR_SRC_COLOR;
-				case BLENDFACTOR_ONE_MINUS_SRC_COLOR:
-					return VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR;
-				case BLENDFACTOR_SRC_ALPHA:
-					return VK_BLEND_FACTOR_SRC_ALPHA;
-				case BLENDFACTOR_ONE_MINUS_SRC_ALPHA:
-					return VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
-				case BLENDFACTOR_DST_COLOR:
-					return VK_BLEND_FACTOR_DST_COLOR;
-				case BLENDFACTOR_ONE_MINUS_DST_COLOR:
-					return VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR;
-				case BLENDFACTOR_DST_ALPHA:
-					return VK_BLEND_FACTOR_DST_ALPHA;
-				case BLENDFACTOR_ONE_MINUS_DST_ALPHA:
-					return VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA;
-				case BLENDFACTOR_SRC_ALPHA_SATURATED:
-					return VK_BLEND_FACTOR_SRC_ALPHA_SATURATE;
-				default:
-					throw love::Exception("unknown blend factor");
-				}
-			}
-
-			VkBlendOp Vulkan::getBlendOp(BlendOperation op) {
-				switch (op) {
-				case BLENDOP_ADD:
-					return VK_BLEND_OP_ADD;
-				case BLENDOP_MAX:
-					return VK_BLEND_OP_MAX;
-				case BLENDOP_MIN:
-					return VK_BLEND_OP_MIN;
-				case BLENDOP_SUBTRACT:
-					return VK_BLEND_OP_SUBTRACT;
-				case BLENDOP_REVERSE_SUBTRACT:
-					return VK_BLEND_OP_REVERSE_SUBTRACT;
-				default:
-					throw love::Exception("unknown blend operation");
-				}
-			}
-
-			VkBool32 Vulkan::getBool(bool b) {
-				if (b) {
-					return VK_TRUE;
-				} else {
-					return VK_FALSE;
-				}
-			}
-
-			VkColorComponentFlags Vulkan::getColorMask(ColorChannelMask mask) {
-				VkColorComponentFlags flags = 0;
-
-				if (mask.r) {
-					flags |= VK_COLOR_COMPONENT_R_BIT;
-				}
-				if (mask.g) {
-					flags |= VK_COLOR_COMPONENT_G_BIT;
-				}
-				if (mask.b) {
-					flags |= VK_COLOR_COMPONENT_B_BIT;
-				}
-				if (mask.a) {
-					flags |= VK_COLOR_COMPONENT_A_BIT;
-				}
-
-				return flags;
-			}
-
-			VkFrontFace Vulkan::getFrontFace(Winding winding) {
-				switch (winding) {
-				case WINDING_CW:
-					return VK_FRONT_FACE_CLOCKWISE;
-				case WINDING_CCW:
-					return VK_FRONT_FACE_COUNTER_CLOCKWISE;
-				default:
-					throw love::Exception("unknown winding");
-				}
-			}
-
-			VkCullModeFlags Vulkan::getCullMode(CullMode cullmode) {
-				switch (cullmode) {
-				case CULL_BACK:
-					return VK_CULL_MODE_BACK_BIT;
-				case CULL_FRONT:
-					return VK_CULL_MODE_FRONT_BIT;
-				case CULL_NONE:
-					return VK_CULL_MODE_NONE;
-				default:
-					throw love::Exception("unknown cull mode");
-				}
-			}
-
-			void Vulkan::cmdTransitionImageLayout(VkCommandBuffer commandBuffer, VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout) {
-				VkImageMemoryBarrier barrier{};
-				barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
-				barrier.oldLayout = oldLayout;
-				barrier.newLayout = newLayout;
-				barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
-				barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
-				barrier.image = image;
-				barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
-				barrier.subresourceRange.baseMipLevel = 0;
-				barrier.subresourceRange.levelCount = 1;
-				barrier.subresourceRange.baseArrayLayer = 0;
-				barrier.subresourceRange.layerCount = 1;
-
-				VkPipelineStageFlags sourceStage;
-				VkPipelineStageFlags destinationStage;
-
-				if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
-					barrier.srcAccessMask = 0;
-					barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
-
-					sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
-					destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
-				}
-				else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
-					barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
-					barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
-
-					sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
-					destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
-				}
-				else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_GENERAL) {
-					barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
-					barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
-
-					sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
-					destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
-				}
-				else if (oldLayout == VK_IMAGE_LAYOUT_GENERAL && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
-					barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
-					barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
-
-					sourceStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
-					destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
-				}
-				else if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_GENERAL) {
-					barrier.srcAccessMask = 0;
-					barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT;
-
-					sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT;
-					destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
-				}
-				else if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) {
-					barrier.srcAccessMask = 0;
-					barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
-
-					sourceStage = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
-					destinationStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
-				}
-				else if (oldLayout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR) {
-					barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
-					barrier.dstAccessMask = 0;
-
-					sourceStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
-					destinationStage = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
-				}
-				else if (oldLayout == VK_IMAGE_LAYOUT_GENERAL && newLayout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) {
-					barrier.srcAccessMask = 0;
-					barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
-
-					sourceStage = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
-					destinationStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
-				}
-				else if (oldLayout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_GENERAL) {
-					barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
-					barrier.dstAccessMask = 0;
-
-					sourceStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
-					destinationStage = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
-				}
-				else {
-					throw std::invalid_argument("unsupported layout transition!");
-				}
-
-				vkCmdPipelineBarrier(
-					commandBuffer,
-					sourceStage, destinationStage,
-					0,
-					0, nullptr,
-					0, nullptr,
-					1, &barrier
-				);
-			}
-		}
+namespace graphics {
+namespace vulkan {
+static uint32_t numShaderSwitches;
+
+void Vulkan::shaderSwitch() {
+	numShaderSwitches++;
+}
+
+uint32_t Vulkan::getNumShaderSwitches() {
+	return numShaderSwitches;
+}
+
+void Vulkan::resetShaderSwitches() {
+	numShaderSwitches = 0;
+}
+
+VkFormat Vulkan::getVulkanVertexFormat(DataFormat format) {
+	switch (format) {
+	case DATAFORMAT_FLOAT:
+		return VK_FORMAT_R32_SFLOAT;
+	case DATAFORMAT_FLOAT_VEC2:
+		return VK_FORMAT_R32G32_SFLOAT;
+	case DATAFORMAT_FLOAT_VEC3:
+		return VK_FORMAT_R32G32B32_SFLOAT;
+	case DATAFORMAT_FLOAT_VEC4:
+		return VK_FORMAT_R32G32B32A32_SFLOAT;
+
+	case DATAFORMAT_FLOAT_MAT2X2:
+	case DATAFORMAT_FLOAT_MAT2X3:
+	case DATAFORMAT_FLOAT_MAT2X4:
+	case DATAFORMAT_FLOAT_MAT3X2:
+	case DATAFORMAT_FLOAT_MAT3X3:
+	case DATAFORMAT_FLOAT_MAT3X4:
+	case DATAFORMAT_FLOAT_MAT4X2:
+	case DATAFORMAT_FLOAT_MAT4X3:
+	case DATAFORMAT_FLOAT_MAT4X4:
+		throw love::Exception("unimplemented data format (matnxm)");
+
+	case DATAFORMAT_INT32:
+		return VK_FORMAT_R32_SINT;
+	case DATAFORMAT_INT32_VEC2:
+		return VK_FORMAT_R32G32_SINT;
+	case DATAFORMAT_INT32_VEC3:
+		return VK_FORMAT_R32G32B32_SINT;
+	case DATAFORMAT_INT32_VEC4:
+		return VK_FORMAT_R32G32B32A32_SINT;
+
+	case DATAFORMAT_UINT32:
+		return VK_FORMAT_R32_UINT;
+	case DATAFORMAT_UINT32_VEC2:
+		return VK_FORMAT_R32G32_UINT;
+	case DATAFORMAT_UINT32_VEC3:
+		return VK_FORMAT_R32G32B32_UINT;
+	case DATAFORMAT_UINT32_VEC4:
+		return VK_FORMAT_R32G32B32A32_UINT;
+
+	case DATAFORMAT_SNORM8_VEC4:
+		return VK_FORMAT_R8G8B8A8_SNORM;
+	case DATAFORMAT_UNORM8_VEC4:
+		return VK_FORMAT_R8G8B8A8_UNORM;
+	case DATAFORMAT_INT8_VEC4:
+		return VK_FORMAT_R8G8B8A8_SINT;
+	case DATAFORMAT_UINT8_VEC4:
+		return VK_FORMAT_R8G8B8A8_UINT;
+
+	case DATAFORMAT_SNORM16_VEC2:
+		return VK_FORMAT_R16G16_SNORM;
+	case DATAFORMAT_SNORM16_VEC4:
+		return VK_FORMAT_R16G16B16A16_SNORM;
+	case DATAFORMAT_UNORM16_VEC2:
+		return VK_FORMAT_R16G16_UNORM;
+	case DATAFORMAT_UNORM16_VEC4:
+		return VK_FORMAT_R16G16B16A16_UNORM;
+
+	case DATAFORMAT_INT16_VEC2:
+		return VK_FORMAT_R16G16_SINT;
+	case DATAFORMAT_INT16_VEC4:
+		return VK_FORMAT_R16G16B16A16_SINT;
+
+	case DATAFORMAT_UINT16:
+		return VK_FORMAT_R16_UINT;
+	case DATAFORMAT_UINT16_VEC2:
+		return VK_FORMAT_R16G16_UINT;
+	case DATAFORMAT_UINT16_VEC4:
+		return VK_FORMAT_R16G16B16A16_UINT;
+
+	case DATAFORMAT_BOOL:
+	case DATAFORMAT_BOOL_VEC2:
+	case DATAFORMAT_BOOL_VEC3:
+	case DATAFORMAT_BOOL_VEC4:
+		throw love::Exception("unimplemented data format (bool)");
+
+	default:
+		throw love::Exception("unknown data format");
 	}
 }
+
+TextureFormat Vulkan::getTextureFormat(PixelFormat format) {
+	TextureFormat textureFormat{};
+
+	switch (format) {
+		case PIXELFORMAT_UNKNOWN:
+			throw love::Exception("unknown pixel format");
+		case PIXELFORMAT_NORMAL:
+			textureFormat.internalFormat = VK_FORMAT_R8G8B8A8_SRGB;
+			textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_FLOAT;
+			break;
+		case PIXELFORMAT_HDR:
+			throw love::Exception("unimplemented pixel format: hdr");
+		case PIXELFORMAT_R8_UNORM:
+			textureFormat.internalFormat = VK_FORMAT_R8_UNORM;
+			textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_FLOAT;
+			break;
+		case PIXELFORMAT_R8_INT:
+			textureFormat.internalFormat = VK_FORMAT_R8_SINT;
+			textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_SINT;
+			break;
+		case PIXELFORMAT_R8_UINT:
+			textureFormat.internalFormat = VK_FORMAT_R8_UINT;
+			textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_UINT;
+			break;
+		case PIXELFORMAT_R16_UNORM:
+			textureFormat.internalFormat = VK_FORMAT_R16_UNORM;
+			textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_FLOAT;
+			break;
+		case PIXELFORMAT_R16_FLOAT:
+			textureFormat.internalFormat = VK_FORMAT_R16_SFLOAT;
+			textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_FLOAT;
+			break;
+		case PIXELFORMAT_R16_INT:
+			textureFormat.internalFormat = VK_FORMAT_R16_SINT;
+			textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_SINT;
+			break;
+		case PIXELFORMAT_R16_UINT:
+			textureFormat.internalFormat = VK_FORMAT_R16_UINT;
+			textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_UINT;
+			break;
+		case PIXELFORMAT_R32_FLOAT:
+			textureFormat.internalFormat = VK_FORMAT_R32_SFLOAT;
+			textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_FLOAT;
+			break;
+		case PIXELFORMAT_R32_INT:
+			textureFormat.internalFormat = VK_FORMAT_R32_SINT;
+			textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_SINT;
+			break;
+		case PIXELFORMAT_R32_UINT:
+			textureFormat.internalFormat = VK_FORMAT_R32_UINT;
+			textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_UINT;
+			break;
+		case PIXELFORMAT_RG8_UNORM:
+			textureFormat.internalFormat = VK_FORMAT_R8G8_UNORM;
+			textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_FLOAT;
+			break;
+		case PIXELFORMAT_RG8_INT:
+			textureFormat.internalFormat = VK_FORMAT_R8G8_SINT;
+			textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_SINT;
+			break;
+		case PIXELFORMAT_RG8_UINT:
+			textureFormat.internalFormat = VK_FORMAT_R8G8_UINT;
+			textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_UINT;
+			break;
+		case PIXELFORMAT_LA8_UNORM: // Same as RG8: but accessed as (L: L: L: A)
+			textureFormat.internalFormat = VK_FORMAT_R8G8_UNORM;
+			textureFormat.swizzleR = VK_COMPONENT_SWIZZLE_R;
+			textureFormat.swizzleG = VK_COMPONENT_SWIZZLE_R;
+			textureFormat.swizzleB = VK_COMPONENT_SWIZZLE_R;
+			textureFormat.swizzleA = VK_COMPONENT_SWIZZLE_G;
+			textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_FLOAT;
+			break;
+		case PIXELFORMAT_RG16_UNORM:
+			textureFormat.internalFormat = VK_FORMAT_R16G16_UNORM;
+			textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_FLOAT;
+			break;
+		case PIXELFORMAT_RG16_FLOAT:
+			textureFormat.internalFormat = VK_FORMAT_R16G16_SFLOAT;
+			textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_FLOAT;
+			break;
+		case PIXELFORMAT_RG16_INT:
+			textureFormat.internalFormat = VK_FORMAT_R16G16_SINT;
+			textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_SINT;
+			break;
+		case PIXELFORMAT_RG16_UINT:
+			textureFormat.internalFormat = VK_FORMAT_R16G16_UINT;
+			textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_UINT;
+			break;
+		case PIXELFORMAT_RG32_FLOAT:
+			textureFormat.internalFormat = VK_FORMAT_R32G32_SFLOAT;
+			textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_FLOAT;
+			break;
+		case PIXELFORMAT_RG32_INT:
+			textureFormat.internalFormat = VK_FORMAT_R32G32_SINT;
+			textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_SINT;
+			break;
+		case PIXELFORMAT_RG32_UINT:
+			textureFormat.internalFormat = VK_FORMAT_R32G32_UINT;
+			textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_UINT;
+			break;
+		case PIXELFORMAT_RGBA8_UNORM:
+			textureFormat.internalFormat = VK_FORMAT_R8G8B8A8_UNORM;
+			textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_FLOAT;
+			break;
+		case PIXELFORMAT_RGBA8_UNORM_sRGB:
+			textureFormat.internalFormat = VK_FORMAT_R8G8B8A8_SRGB;
+			textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_FLOAT;
+			break;
+		case PIXELFORMAT_BGRA8_UNORM:
+			textureFormat.internalFormat = VK_FORMAT_B8G8R8A8_UNORM;
+			textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_FLOAT;
+			break;
+		case PIXELFORMAT_BGRA8_UNORM_sRGB:
+			textureFormat.internalFormat = VK_FORMAT_B8G8R8A8_SRGB;
+			textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_FLOAT;
+			break;
+		case PIXELFORMAT_RGBA8_INT:
+			textureFormat.internalFormat = VK_FORMAT_R8G8B8A8_SINT;
+			textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_SINT;
+			break;
+		case PIXELFORMAT_RGBA8_UINT:
+			textureFormat.internalFormat = VK_FORMAT_R8G8B8A8_UINT;
+			textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_UINT;
+			break;
+		case PIXELFORMAT_RGBA16_UNORM:
+			textureFormat.internalFormat = VK_FORMAT_R16G16B16A16_UNORM;
+			textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_FLOAT;
+			break;
+		case PIXELFORMAT_RGBA16_FLOAT:
+			textureFormat.internalFormat = VK_FORMAT_R16G16B16A16_SFLOAT;
+			textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_FLOAT;
+			break;
+		case PIXELFORMAT_RGBA16_INT:
+			textureFormat.internalFormat = VK_FORMAT_R16G16B16A16_SINT;
+			textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_SINT;
+			break;
+		case PIXELFORMAT_RGBA16_UINT:
+			textureFormat.internalFormat = VK_FORMAT_R16G16B16A16_UINT;
+			textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_UINT;
+			break;
+		case PIXELFORMAT_RGBA32_FLOAT:
+			textureFormat.internalFormat = VK_FORMAT_R32G32B32A32_SFLOAT;
+			textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_FLOAT;
+			break;
+		case PIXELFORMAT_RGBA32_INT:
+			textureFormat.internalFormat = VK_FORMAT_R32G32B32A32_SINT;
+			textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_SINT;
+			break;
+		case PIXELFORMAT_RGBA32_UINT:
+			textureFormat.internalFormat = VK_FORMAT_R32G32B32A32_UINT;
+			textureFormat.internalFormatRepresentation = FORMATREPRESENTATION_UINT;
+			break;
+		case PIXELFORMAT_RGBA4_UNORM:    // LSB->MSB: [a: b: g: r]
+		case PIXELFORMAT_RGB5A1_UNORM:   // LSB->MSB: [a: b: g: r]
+		case PIXELFORMAT_RGB565_UNORM:   // LSB->MSB: [b: g: r]
+		case PIXELFORMAT_RGB10A2_UNORM:  // LSB->MSB: [r: g: b: a]
+		case PIXELFORMAT_RG11B10_FLOAT:  // LSB->MSB: [r: g: b]
+		case PIXELFORMAT_STENCIL8:
+		case PIXELFORMAT_DEPTH16_UNORM:
+		case PIXELFORMAT_DEPTH24_UNORM:
+		case PIXELFORMAT_DEPTH32_FLOAT:
+		case PIXELFORMAT_DEPTH24_UNORM_STENCIL8:
+		case PIXELFORMAT_DEPTH32_FLOAT_STENCIL8:
+		case PIXELFORMAT_DXT1_UNORM:
+		case PIXELFORMAT_DXT3_UNORM:
+		case PIXELFORMAT_DXT5_UNORM:
+		case PIXELFORMAT_BC4_UNORM:
+		case PIXELFORMAT_BC4_SNORM:
+		case PIXELFORMAT_BC5_UNORM:
+		case PIXELFORMAT_BC5_SNORM:
+		case PIXELFORMAT_BC6H_UFLOAT:
+		case PIXELFORMAT_BC6H_FLOAT:
+		case PIXELFORMAT_BC7_UNORM:
+		case PIXELFORMAT_PVR1_RGB2_UNORM:
+		case PIXELFORMAT_PVR1_RGB4_UNORM:
+		case PIXELFORMAT_PVR1_RGBA2_UNORM:
+		case PIXELFORMAT_PVR1_RGBA4_UNORM:
+		case PIXELFORMAT_ETC1_UNORM:
+		case PIXELFORMAT_ETC2_RGB_UNORM:
+		case PIXELFORMAT_ETC2_RGBA_UNORM:
+		case PIXELFORMAT_ETC2_RGBA1_UNORM:
+		case PIXELFORMAT_EAC_R_UNORM:
+		case PIXELFORMAT_EAC_R_SNORM:
+		case PIXELFORMAT_EAC_RG_UNORM:
+		case PIXELFORMAT_EAC_RG_SNORM:
+		case PIXELFORMAT_ASTC_4x4:
+		case PIXELFORMAT_ASTC_5x4:
+		case PIXELFORMAT_ASTC_5x5:
+		case PIXELFORMAT_ASTC_6x5:
+		case PIXELFORMAT_ASTC_6x6:
+		case PIXELFORMAT_ASTC_8x5:
+		case PIXELFORMAT_ASTC_8x6:
+		case PIXELFORMAT_ASTC_8x8:
+		case PIXELFORMAT_ASTC_10x5:
+		case PIXELFORMAT_ASTC_10x6:
+		case PIXELFORMAT_ASTC_10x8:
+		case PIXELFORMAT_ASTC_10x10:
+		case PIXELFORMAT_ASTC_12x10:
+		case PIXELFORMAT_ASTC_12x12:
+			throw love::Exception("unimplemented pixel format");
+	}
+
+	return textureFormat;
+}
+
+// values taken from https://pcisig.com/membership/member-companies
+// as specified at https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkPhysicalDeviceProperties.html
+std::string Vulkan::getVendorName(uint32_t vendorId) {
+	switch (vendorId) {
+	case 4130:
+		return "AMD";
+	case 4318:
+		return "Nvidia";
+	case 8086:
+		return "Intel";
+	case 4203:
+		return "Apple";
+	case 5140:
+		return "Microsoft";
+	case 5045:
+		return "ARM";
+	case 20803:
+		return "Qualcomm";
+	case 5348:
+		return "Broadcom";
+	default:
+		return "unknown";
+	}
+}
+
+std::string Vulkan::getVulkanApiVersion(uint32_t version) {
+	std::stringstream ss;
+
+	ss << VK_API_VERSION_MAJOR(version) 
+		<< "." << VK_API_VERSION_MINOR(version) 
+		<< "." << VK_API_VERSION_PATCH(version);
+
+	return ss.str();
+}
+
+VkPrimitiveTopology Vulkan::getPrimitiveTypeTopology(graphics::PrimitiveType primitiveType) {
+	switch (primitiveType) {
+	case PRIMITIVE_POINTS:
+		return VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
+	case PRIMITIVE_TRIANGLES:
+		return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
+	case PRIMITIVE_TRIANGLE_FAN:
+		return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN;
+	case PRIMITIVE_TRIANGLE_STRIP:
+		return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
+	default:
+		throw love::Exception("unknown primitive type");
+	}
+}
+
+VkBlendFactor Vulkan::getBlendFactor(BlendFactor blendFactor) {
+	switch (blendFactor) {
+	case BLENDFACTOR_ZERO:
+		return VK_BLEND_FACTOR_ZERO;
+	case BLENDFACTOR_ONE:
+		return VK_BLEND_FACTOR_ONE;
+	case BLENDFACTOR_SRC_COLOR:
+		return VK_BLEND_FACTOR_SRC_COLOR;
+	case BLENDFACTOR_ONE_MINUS_SRC_COLOR:
+		return VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR;
+	case BLENDFACTOR_SRC_ALPHA:
+		return VK_BLEND_FACTOR_SRC_ALPHA;
+	case BLENDFACTOR_ONE_MINUS_SRC_ALPHA:
+		return VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+	case BLENDFACTOR_DST_COLOR:
+		return VK_BLEND_FACTOR_DST_COLOR;
+	case BLENDFACTOR_ONE_MINUS_DST_COLOR:
+		return VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR;
+	case BLENDFACTOR_DST_ALPHA:
+		return VK_BLEND_FACTOR_DST_ALPHA;
+	case BLENDFACTOR_ONE_MINUS_DST_ALPHA:
+		return VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA;
+	case BLENDFACTOR_SRC_ALPHA_SATURATED:
+		return VK_BLEND_FACTOR_SRC_ALPHA_SATURATE;
+	default:
+		throw love::Exception("unknown blend factor");
+	}
+}
+
+VkBlendOp Vulkan::getBlendOp(BlendOperation op) {
+	switch (op) {
+	case BLENDOP_ADD:
+		return VK_BLEND_OP_ADD;
+	case BLENDOP_MAX:
+		return VK_BLEND_OP_MAX;
+	case BLENDOP_MIN:
+		return VK_BLEND_OP_MIN;
+	case BLENDOP_SUBTRACT:
+		return VK_BLEND_OP_SUBTRACT;
+	case BLENDOP_REVERSE_SUBTRACT:
+		return VK_BLEND_OP_REVERSE_SUBTRACT;
+	default:
+		throw love::Exception("unknown blend operation");
+	}
+}
+
+VkBool32 Vulkan::getBool(bool b) {
+	if (b) {
+		return VK_TRUE;
+	} else {
+		return VK_FALSE;
+	}
+}
+
+VkColorComponentFlags Vulkan::getColorMask(ColorChannelMask mask) {
+	VkColorComponentFlags flags = 0;
+
+	if (mask.r) {
+		flags |= VK_COLOR_COMPONENT_R_BIT;
+	}
+	if (mask.g) {
+		flags |= VK_COLOR_COMPONENT_G_BIT;
+	}
+	if (mask.b) {
+		flags |= VK_COLOR_COMPONENT_B_BIT;
+	}
+	if (mask.a) {
+		flags |= VK_COLOR_COMPONENT_A_BIT;
+	}
+
+	return flags;
+}
+
+VkFrontFace Vulkan::getFrontFace(Winding winding) {
+	switch (winding) {
+	case WINDING_CW:
+		return VK_FRONT_FACE_CLOCKWISE;
+	case WINDING_CCW:
+		return VK_FRONT_FACE_COUNTER_CLOCKWISE;
+	default:
+		throw love::Exception("unknown winding");
+	}
+}
+
+VkCullModeFlags Vulkan::getCullMode(CullMode cullmode) {
+	switch (cullmode) {
+	case CULL_BACK:
+		return VK_CULL_MODE_BACK_BIT;
+	case CULL_FRONT:
+		return VK_CULL_MODE_FRONT_BIT;
+	case CULL_NONE:
+		return VK_CULL_MODE_NONE;
+	default:
+		throw love::Exception("unknown cull mode");
+	}
+}
+
+void Vulkan::cmdTransitionImageLayout(VkCommandBuffer commandBuffer, VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout) {
+	VkImageMemoryBarrier barrier{};
+	barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+	barrier.oldLayout = oldLayout;
+	barrier.newLayout = newLayout;
+	barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+	barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+	barrier.image = image;
+	barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+	barrier.subresourceRange.baseMipLevel = 0;
+	barrier.subresourceRange.levelCount = 1;
+	barrier.subresourceRange.baseArrayLayer = 0;
+	barrier.subresourceRange.layerCount = 1;
+
+	VkPipelineStageFlags sourceStage;
+	VkPipelineStageFlags destinationStage;
+
+	if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
+		barrier.srcAccessMask = 0;
+		barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+
+		sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
+		destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
+	}
+	else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
+		barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+		barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+
+		sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
+		destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+	}
+	else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_GENERAL) {
+		barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+		barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+
+		sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
+		destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+	}
+	else if (oldLayout == VK_IMAGE_LAYOUT_GENERAL && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
+		barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
+		barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+
+		sourceStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+		destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
+	}
+	else if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_GENERAL) {
+		barrier.srcAccessMask = 0;
+		barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT;
+
+		sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT;
+		destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+	}
+	else if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) {
+		barrier.srcAccessMask = 0;
+		barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+
+		sourceStage = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+		destinationStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+	}
+	else if (oldLayout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR) {
+		barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		barrier.dstAccessMask = 0;
+
+		sourceStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+		destinationStage = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+	}
+	else if (oldLayout == VK_IMAGE_LAYOUT_GENERAL && newLayout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) {
+		barrier.srcAccessMask = 0;
+		barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+
+		sourceStage = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+		destinationStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+	}
+	else if (oldLayout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_GENERAL) {
+		barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+		barrier.dstAccessMask = 0;
+
+		sourceStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+		destinationStage = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+	}
+	else {
+		throw std::invalid_argument("unsupported layout transition!");
+	}
+
+	vkCmdPipelineBarrier(
+		commandBuffer,
+		sourceStage, destinationStage,
+		0,
+		0, nullptr,
+		0, nullptr,
+		1, &barrier
+	);
+}
+} // vulkan
+} // graphics
+} // love

+ 42 - 42
src/modules/graphics/vulkan/Vulkan.h

@@ -5,47 +5,47 @@
 #include "vulkan/vulkan.h"
 
 namespace love {
-	namespace graphics {
-		namespace vulkan {
-			enum InternalFormatRepresentation {
-				FORMATREPRESENTATION_FLOAT,
-				FORMATREPRESENTATION_UINT,
-				FORMATREPRESENTATION_SINT,
-				FORMATREPRESENTATION_MAX_ENUM
-			};
-
-			struct TextureFormat {
-				InternalFormatRepresentation internalFormatRepresentation;
-				VkFormat internalFormat = VK_FORMAT_UNDEFINED;
-
-				VkComponentSwizzle swizzleR = VK_COMPONENT_SWIZZLE_IDENTITY;
-				VkComponentSwizzle swizzleG = VK_COMPONENT_SWIZZLE_IDENTITY;
-				VkComponentSwizzle swizzleB = VK_COMPONENT_SWIZZLE_IDENTITY;
-				VkComponentSwizzle swizzleA = VK_COMPONENT_SWIZZLE_IDENTITY;
-			};
-
-			class Vulkan {
-			public:
-				static void shaderSwitch();
-				static uint32_t getNumShaderSwitches();
-				static void resetShaderSwitches();
-
-				static VkFormat getVulkanVertexFormat(DataFormat format);
-				static TextureFormat getTextureFormat(PixelFormat);
-				static std::string getVendorName(uint32_t vendorId);
-				static std::string getVulkanApiVersion(uint32_t apiVersion);
-				static VkPrimitiveTopology getPrimitiveTypeTopology(graphics::PrimitiveType);
-				static VkBlendFactor getBlendFactor(BlendFactor);
-				static VkBlendOp getBlendOp(BlendOperation);
-				static VkBool32 getBool(bool);
-				static VkColorComponentFlags getColorMask(ColorChannelMask);
-				static VkFrontFace getFrontFace(Winding);
-				static VkCullModeFlags getCullMode(CullMode);
-
-				static void cmdTransitionImageLayout(VkCommandBuffer, VkImage, VkImageLayout oldLayout, VkImageLayout newLayout);
-			};
-		}
-	}
-}
+namespace graphics {
+namespace vulkan {
+enum InternalFormatRepresentation {
+	FORMATREPRESENTATION_FLOAT,
+	FORMATREPRESENTATION_UINT,
+	FORMATREPRESENTATION_SINT,
+	FORMATREPRESENTATION_MAX_ENUM
+};
+
+struct TextureFormat {
+	InternalFormatRepresentation internalFormatRepresentation;
+	VkFormat internalFormat = VK_FORMAT_UNDEFINED;
+
+	VkComponentSwizzle swizzleR = VK_COMPONENT_SWIZZLE_IDENTITY;
+	VkComponentSwizzle swizzleG = VK_COMPONENT_SWIZZLE_IDENTITY;
+	VkComponentSwizzle swizzleB = VK_COMPONENT_SWIZZLE_IDENTITY;
+	VkComponentSwizzle swizzleA = VK_COMPONENT_SWIZZLE_IDENTITY;
+};
+
+class Vulkan {
+public:
+	static void shaderSwitch();
+	static uint32_t getNumShaderSwitches();
+	static void resetShaderSwitches();
+
+	static VkFormat getVulkanVertexFormat(DataFormat format);
+	static TextureFormat getTextureFormat(PixelFormat);
+	static std::string getVendorName(uint32_t vendorId);
+	static std::string getVulkanApiVersion(uint32_t apiVersion);
+	static VkPrimitiveTopology getPrimitiveTypeTopology(graphics::PrimitiveType);
+	static VkBlendFactor getBlendFactor(BlendFactor);
+	static VkBlendOp getBlendOp(BlendOperation);
+	static VkBool32 getBool(bool);
+	static VkColorComponentFlags getColorMask(ColorChannelMask);
+	static VkFrontFace getFrontFace(Winding);
+	static VkCullModeFlags getCullMode(CullMode);
+
+	static void cmdTransitionImageLayout(VkCommandBuffer, VkImage, VkImageLayout oldLayout, VkImageLayout newLayout);
+};
+} // vulkan
+} // graphics
+} // love
 
 #endif