Browse Source

vulkan: implement graphics readback

niki 3 years ago
parent
commit
ec5a3a50f9

+ 2 - 0
CMakeLists.txt

@@ -599,6 +599,8 @@ set(LOVE_SRC_MODULE_GRAPHICS_OPENGL
 set(LOVE_SRC_MODULE_GRAPHICS_VULKAN
 set(LOVE_SRC_MODULE_GRAPHICS_VULKAN
 	src/modules/graphics/vulkan/Graphics.h
 	src/modules/graphics/vulkan/Graphics.h
 	src/modules/graphics/vulkan/Graphics.cpp
 	src/modules/graphics/vulkan/Graphics.cpp
+	src/modules/graphics/vulkan/GraphicsReadback.h
+	src/modules/graphics/vulkan/GraphicsReadback.cpp
 	src/modules/graphics/vulkan/Shader.h
 	src/modules/graphics/vulkan/Shader.h
 	src/modules/graphics/vulkan/Shader.cpp
 	src/modules/graphics/vulkan/Shader.cpp
 	src/modules/graphics/vulkan/ShaderStage.h
 	src/modules/graphics/vulkan/ShaderStage.h

+ 7 - 2
src/modules/graphics/vulkan/Buffer.cpp

@@ -38,7 +38,12 @@ bool Buffer::loadVolatile() {
 	VkBufferCreateInfo bufferInfo{};
 	VkBufferCreateInfo bufferInfo{};
 	bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
 	bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
 	bufferInfo.size = getSize();
 	bufferInfo.size = getSize();
-	bufferInfo.usage = getVulkanUsageFlags(usageFlags);
+	if (dataUsage == BUFFERDATAUSAGE_READBACK) {
+		bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
+	}
+	else {
+		bufferInfo.usage = getVulkanUsageFlags(usageFlags);
+	}
 
 
 	VmaAllocationCreateInfo allocCreateInfo = {};
 	VmaAllocationCreateInfo allocCreateInfo = {};
 	allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
 	allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
@@ -88,7 +93,7 @@ void Buffer::unmap(size_t usedoffset, size_t usedsize) {
 void Buffer::copyTo(love::graphics::Buffer* dest, size_t sourceoffset, size_t destoffset, size_t size) {
 void Buffer::copyTo(love::graphics::Buffer* dest, size_t sourceoffset, size_t destoffset, size_t size) {
 	Graphics* vgfx = (Graphics*)gfx;
 	Graphics* vgfx = (Graphics*)gfx;
 
 
-	auto commandBuffer = vgfx->getDataTransferCommandBuffer();
+	auto commandBuffer = vgfx->getReadbackCommandBuffer();
 
 
 	VkBufferCopy bufferCopy{};
 	VkBufferCopy bufferCopy{};
 	bufferCopy.srcOffset = sourceoffset;
 	bufferCopy.srcOffset = sourceoffset;

+ 155 - 42
src/modules/graphics/vulkan/Graphics.cpp

@@ -1,5 +1,6 @@
 #include "Graphics.h"
 #include "Graphics.h"
 #include "Buffer.h"
 #include "Buffer.h"
+#include "GraphicsReadback.h"
 #include "SDL_vulkan.h"
 #include "SDL_vulkan.h"
 #include "window/Window.h"
 #include "window/Window.h"
 #include "common/Exception.h"
 #include "common/Exception.h"
@@ -147,50 +148,81 @@ void Graphics::clear(const std::vector<OptionalColorD>& colors, OptionalInt sten
 	vkCmdClearAttachments(commandBuffers[currentFrame], static_cast<uint32_t>(attachments.size()), attachments.data(), 1, &rect);
 	vkCmdClearAttachments(commandBuffers[currentFrame], static_cast<uint32_t>(attachments.size()), attachments.data(), 1, &rect);
 }
 }
 
 
-void Graphics::present(void* screenshotCallbackdata) {
-	if (!isActive()) {
-		return;
-	}
-
+void Graphics::submitGpuCommands(bool present) {
 	flushBatchedDraws();
 	flushBatchedDraws();
 
 
-	endRecordingGraphicsCommands();
+	endRecordingGraphicsCommands(present);
 
 
 	if (imagesInFlight[imageIndex] != VK_NULL_HANDLE) {
 	if (imagesInFlight[imageIndex] != VK_NULL_HANDLE) {
 		vkWaitForFences(device, 1, &imagesInFlight.at(imageIndex), VK_TRUE, UINT64_MAX);
 		vkWaitForFences(device, 1, &imagesInFlight.at(imageIndex), VK_TRUE, UINT64_MAX);
 	}
 	}
 	imagesInFlight[imageIndex] = inFlightFences[currentFrame];
 	imagesInFlight[imageIndex] = inFlightFences[currentFrame];
 
 
-	// all data transfers should happen before any draw calls.
-	std::vector<VkCommandBuffer> submitCommandbuffers = { dataTransferCommandBuffers.at(currentFrame), commandBuffers.at(currentFrame) };
+	std::vector<VkCommandBuffer> submitCommandbuffers = { 
+		dataTransferCommandBuffers.at(currentFrame), 
+		commandBuffers.at(currentFrame), 
+		readbackCommandBuffers.at(currentFrame)};
 
 
 	VkSubmitInfo submitInfo{};
 	VkSubmitInfo submitInfo{};
 	submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
 	submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
 
 
 	VkSemaphore waitSemaphores[] = { imageAvailableSemaphores.at(currentFrame) };
 	VkSemaphore waitSemaphores[] = { imageAvailableSemaphores.at(currentFrame) };
 	VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT };
 	VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT };
-	submitInfo.waitSemaphoreCount = 1;
-	submitInfo.pWaitSemaphores = waitSemaphores;
-	submitInfo.pWaitDstStageMask = waitStages;
+
+	if (imageRequested) {
+		submitInfo.waitSemaphoreCount = 1;
+		submitInfo.pWaitSemaphores = waitSemaphores;
+		submitInfo.pWaitDstStageMask = waitStages;
+		imageRequested = false;
+	}
 
 
 	submitInfo.commandBufferCount = static_cast<uint32_t>(submitCommandbuffers.size());
 	submitInfo.commandBufferCount = static_cast<uint32_t>(submitCommandbuffers.size());
 	submitInfo.pCommandBuffers = submitCommandbuffers.data();
 	submitInfo.pCommandBuffers = submitCommandbuffers.data();
 
 
 	VkSemaphore signalSemaphores[] = { renderFinishedSemaphores.at(currentFrame) };
 	VkSemaphore signalSemaphores[] = { renderFinishedSemaphores.at(currentFrame) };
-	submitInfo.signalSemaphoreCount = 1;
-	submitInfo.pSignalSemaphores = signalSemaphores;
 
 
-	vkResetFences(device, 1, &inFlightFences[currentFrame]);
+	VkFence fence = VK_NULL_HANDLE;
+
+	if (present) {
+		submitInfo.signalSemaphoreCount = 1;
+		submitInfo.pSignalSemaphores = signalSemaphores;
+
+		vkResetFences(device, 1, &inFlightFences[currentFrame]);
+		fence = inFlightFences[currentFrame];
+	}
 
 
-	if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences.at(currentFrame)) != VK_SUCCESS) {
+	if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, fence) != VK_SUCCESS) {
 		throw love::Exception("failed to submit draw command buffer");
 		throw love::Exception("failed to submit draw command buffer");
 	}
 	}
+	
+	if (!present) {
+		vkQueueWaitIdle(graphicsQueue);
+
+		for (auto& callbacks : readbackCallbacks) {
+			for (const auto& callback : callbacks) {
+				callback();
+			}
+			callbacks.clear();
+		}
+
+		startRecordingGraphicsCommands(false);
+	}
+}
+
+void Graphics::present(void* screenshotCallbackdata) {
+	if (!isActive()) {
+		return;
+	}
+
+	submitGpuCommands(true);
 
 
 	VkPresentInfoKHR presentInfo{};
 	VkPresentInfoKHR presentInfo{};
 	presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
 	presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
 
 
+	VkSemaphore waitSemaphores[] = { renderFinishedSemaphores.at(currentFrame) };
+
 	presentInfo.waitSemaphoreCount = 1;
 	presentInfo.waitSemaphoreCount = 1;
-	presentInfo.pWaitSemaphores = signalSemaphores;
+	presentInfo.pWaitSemaphores = waitSemaphores;
 
 
 	VkSwapchainKHR swapChains[] = { swapChain };
 	VkSwapchainKHR swapChains[] = { swapChain };
 	presentInfo.swapchainCount = 1;
 	presentInfo.swapchainCount = 1;
@@ -210,8 +242,9 @@ void Graphics::present(void* screenshotCallbackdata) {
 
 
 	currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
 	currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
 
 
+	beginFrame();
+
 	updatedBatchedDrawBuffers();
 	updatedBatchedDrawBuffers();
-	startRecordingGraphicsCommands();
 }
 }
 
 
 void Graphics::setViewportSize(int width, int height, int pixelwidth, int pixelheight) {
 void Graphics::setViewportSize(int width, int height, int pixelwidth, int pixelheight) {
@@ -229,6 +262,9 @@ bool Graphics::setMode(void* context, int width, int height, int pixelwidth, int
 	cleanUpFunctions.clear();
 	cleanUpFunctions.clear();
 	cleanUpFunctions.resize(MAX_FRAMES_IN_FLIGHT);
 	cleanUpFunctions.resize(MAX_FRAMES_IN_FLIGHT);
 
 
+	readbackCallbacks.clear();
+	readbackCallbacks.resize(MAX_FRAMES_IN_FLIGHT);
+
 	createVulkanInstance();
 	createVulkanInstance();
 	createSurface();
 	createSurface();
 	pickPhysicalDevice();
 	pickPhysicalDevice();
@@ -244,13 +280,6 @@ bool Graphics::setMode(void* context, int width, int height, int pixelwidth, int
 	createDefaultFramebuffers();
 	createDefaultFramebuffers();
 	createCommandPool();
 	createCommandPool();
 	createCommandBuffers();
 	createCommandBuffers();
-	startRecordingGraphicsCommands();
-	createQuadIndexBuffer();
-	createDefaultTexture();
-	createDefaultShaders();
-	currentFrame = 0;
-
-	created = true;
 
 
 	float whiteColor[] = { 1.0f, 1.0f, 1.0f, 1.0f };
 	float whiteColor[] = { 1.0f, 1.0f, 1.0f, 1.0f };
 
 
@@ -271,18 +300,27 @@ bool Graphics::setMode(void* context, int width, int height, int pixelwidth, int
 		batchedDrawBuffers[i].constantColorBuffer->unmap(sizeof(whiteColor));
 		batchedDrawBuffers[i].constantColorBuffer->unmap(sizeof(whiteColor));
 		batchedDrawBuffers[i].constantColorBuffer->markUsed(sizeof(whiteColor));
 		batchedDrawBuffers[i].constantColorBuffer->markUsed(sizeof(whiteColor));
 	}
 	}
-
 	updatedBatchedDrawBuffers();
 	updatedBatchedDrawBuffers();
 
 
+	beginFrame();
+
+	createDefaultTexture();
+	createDefaultShaders();
 	Shader::current = Shader::standardShaders[graphics::Shader::StandardShader::STANDARD_DEFAULT];
 	Shader::current = Shader::standardShaders[graphics::Shader::StandardShader::STANDARD_DEFAULT];
+	createQuadIndexBuffer();
+
 	restoreState(states.back());
 	restoreState(states.back());
 
 
 	setViewportSize(width, height, pixelwidth, pixelheight);
 	setViewportSize(width, height, pixelwidth, pixelheight);
-	currentViewportWidth = 0.0f;
-	currentViewportHeight = 0.0f;
+	currentViewportWidth = 1.0f;
+	currentViewportHeight = 1.0f;
 
 
 	Vulkan::resetShaderSwitches();
 	Vulkan::resetShaderSwitches();
 
 
+	currentFrame = 0;
+
+	created = true;
+
 	return true;
 	return true;
 }
 }
 
 
@@ -301,10 +339,10 @@ void Graphics::initCapabilities() {
 	capabilities.features[FEATURE_INSTANCING] = true;
 	capabilities.features[FEATURE_INSTANCING] = true;
 	capabilities.features[FEATURE_TEXEL_BUFFER] = false;
 	capabilities.features[FEATURE_TEXEL_BUFFER] = false;
 	capabilities.features[FEATURE_INDEX_BUFFER_32BIT] = true;
 	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;
+	capabilities.features[FEATURE_COPY_BUFFER] = true;
+	capabilities.features[FEATURE_COPY_BUFFER_TO_TEXTURE] = true;
+	capabilities.features[FEATURE_COPY_TEXTURE_TO_BUFFER] = true;
+	capabilities.features[FEATURE_COPY_RENDER_TARGET_TO_BUFFER] = true;
 	static_assert(FEATURE_MAX_ENUM == 17, "Graphics::initCapabilities must be updated when adding a new graphics feature!");
 	static_assert(FEATURE_MAX_ENUM == 17, "Graphics::initCapabilities must be updated when adding a new graphics feature!");
 
 
 	VkPhysicalDeviceProperties properties;
 	VkPhysicalDeviceProperties properties;
@@ -584,6 +622,22 @@ Renderer Graphics::getRenderer() const {
 	return RENDERER_VULKAN;
 	return RENDERER_VULKAN;
 }
 }
 
 
+graphics::GraphicsReadback* Graphics::newReadbackInternal(ReadbackMethod method, love::graphics::Buffer* buffer, size_t offset, size_t size, data::ByteData* dest, size_t destoffset) { 
+	return new GraphicsReadback(this, method, buffer, offset, size, dest, destoffset);
+}
+
+graphics::GraphicsReadback* Graphics::newReadbackInternal(ReadbackMethod method, love::graphics::Texture* texture, int slice, int mipmap, const Rect& rect, image::ImageData* dest, int destx, int desty) { 
+	return new GraphicsReadback(this, method, texture, slice, mipmap, rect, dest, destx, desty);
+}
+
+graphics::ShaderStage* Graphics::newShaderStageInternal(ShaderStageType stage, const std::string& cachekey, const std::string& source, bool gles) {
+	return new ShaderStage(this, stage, source, gles, cachekey);
+}
+
+graphics::Shader* Graphics::newShaderInternal(StrongRef<love::graphics::ShaderStage> stages[SHADERSTAGE_MAX_ENUM]) {
+	return new Shader(stages);
+}
+
 graphics::StreamBuffer* Graphics::newStreamBuffer(BufferUsage type, size_t size) {
 graphics::StreamBuffer* Graphics::newStreamBuffer(BufferUsage type, size_t size) {
 	return new StreamBuffer(this, type, size);
 	return new StreamBuffer(this, type, size);
 }
 }
@@ -645,7 +699,7 @@ void Graphics::initDynamicState() {
 	}
 	}
 }
 }
 
 
-void Graphics::startRecordingGraphicsCommands() {
+void Graphics::beginFrame() {
 	vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX);
 	vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX);
 
 
 	while (true) {
 	while (true) {
@@ -661,11 +715,24 @@ void Graphics::startRecordingGraphicsCommands() {
 		break;
 		break;
 	}
 	}
 
 
+	imageRequested = true;
+
+	for (auto& readbackCallback : readbackCallbacks.at(currentFrame)) {
+		readbackCallback();
+	}
+	readbackCallbacks.at(currentFrame).clear();
+
 	for (auto& cleanUpFn : cleanUpFunctions.at(currentFrame)) {
 	for (auto& cleanUpFn : cleanUpFunctions.at(currentFrame)) {
 		cleanUpFn();
 		cleanUpFn();
 	}
 	}
 	cleanUpFunctions.at(currentFrame).clear();
 	cleanUpFunctions.at(currentFrame).clear();
 
 
+	startRecordingGraphicsCommands(true);
+
+	Vulkan::resetShaderSwitches();
+}
+
+void Graphics::startRecordingGraphicsCommands(bool newFrame) {
 	VkCommandBufferBeginInfo beginInfo{};
 	VkCommandBufferBeginInfo beginInfo{};
 	beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
 	beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
 	beginInfo.flags = 0;
 	beginInfo.flags = 0;
@@ -677,20 +744,25 @@ void Graphics::startRecordingGraphicsCommands() {
 	if (vkBeginCommandBuffer(dataTransferCommandBuffers.at(currentFrame), &beginInfo) != VK_SUCCESS) {
 	if (vkBeginCommandBuffer(dataTransferCommandBuffers.at(currentFrame), &beginInfo) != VK_SUCCESS) {
 		throw love::Exception("failed to begin recording data transfer command buffer");
 		throw love::Exception("failed to begin recording data transfer command buffer");
 	}
 	}
+	if (vkBeginCommandBuffer(readbackCommandBuffers.at(currentFrame), &beginInfo) != VK_SUCCESS) {
+		throw love::Exception("failed to begin recording readback command buffer");
+	}
 
 
 	initDynamicState();
 	initDynamicState();
 
 
-	Vulkan::cmdTransitionImageLayout(commandBuffers.at(currentFrame), swapChainImages[imageIndex], VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
+	if (newFrame) {
+		Vulkan::cmdTransitionImageLayout(commandBuffers.at(currentFrame), swapChainImages[imageIndex], VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
+	}
 
 
 	startDefaultRenderPass();
 	startDefaultRenderPass();
-
-	Vulkan::resetShaderSwitches();
 }
 }
 
 
-void Graphics::endRecordingGraphicsCommands() {
+void Graphics::endRecordingGraphicsCommands(bool present) {
 	endRenderPass();
 	endRenderPass();
 
 
-	Vulkan::cmdTransitionImageLayout(commandBuffers.at(currentFrame), swapChainImages[imageIndex], VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
+	if (present) {
+		Vulkan::cmdTransitionImageLayout(commandBuffers.at(currentFrame), swapChainImages[imageIndex], VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
+	}
 
 
 	if (vkEndCommandBuffer(commandBuffers.at(currentFrame)) != VK_SUCCESS) {
 	if (vkEndCommandBuffer(commandBuffers.at(currentFrame)) != VK_SUCCESS) {
 		throw love::Exception("failed to record command buffer");
 		throw love::Exception("failed to record command buffer");
@@ -698,6 +770,9 @@ void Graphics::endRecordingGraphicsCommands() {
 	if (vkEndCommandBuffer(dataTransferCommandBuffers.at(currentFrame)) != VK_SUCCESS) {
 	if (vkEndCommandBuffer(dataTransferCommandBuffers.at(currentFrame)) != VK_SUCCESS) {
 		throw love::Exception("failed to record data transfer command buffer");
 		throw love::Exception("failed to record data transfer command buffer");
 	}
 	}
+	if (vkEndCommandBuffer(readbackCommandBuffers.at(currentFrame)) != VK_SUCCESS) {
+		throw love::Exception("failed to record read back command buffer");
+	}
 }
 }
 
 
 void Graphics::updatedBatchedDrawBuffers() {
 void Graphics::updatedBatchedDrawBuffers() {
@@ -725,10 +800,18 @@ VkCommandBuffer Graphics::getDataTransferCommandBuffer() {
 	return dataTransferCommandBuffers.at(currentFrame);
 	return dataTransferCommandBuffers.at(currentFrame);
 }
 }
 
 
+VkCommandBuffer Graphics::getReadbackCommandBuffer() {
+	return readbackCommandBuffers.at(currentFrame);
+}
+
 void Graphics::queueCleanUp(std::function<void()> cleanUp) {
 void Graphics::queueCleanUp(std::function<void()> cleanUp) {
 	cleanUpFunctions.at(currentFrame).push_back(std::move(cleanUp));
 	cleanUpFunctions.at(currentFrame).push_back(std::move(cleanUp));
 }
 }
 
 
+void Graphics::addReadbackCallback(const std::function<void()>& callback) {
+	readbackCallbacks.at(currentFrame).push_back(std::move(callback));
+}
+
 graphics::Shader::BuiltinUniformData Graphics::getCurrentBuiltinUniformData() {
 graphics::Shader::BuiltinUniformData Graphics::getCurrentBuiltinUniformData() {
 	love::graphics::Shader::BuiltinUniformData data;
 	love::graphics::Shader::BuiltinUniformData data;
 
 
@@ -1036,7 +1119,6 @@ void Graphics::createLogicalDevice() {
 	createInfo.enabledExtensionCount = static_cast<uint32_t>(enabledExtensions.size());
 	createInfo.enabledExtensionCount = static_cast<uint32_t>(enabledExtensions.size());
 	createInfo.ppEnabledExtensionNames = enabledExtensions.data();
 	createInfo.ppEnabledExtensionNames = enabledExtensions.data();
 
 
-	// can this be removed?
 	if (enableValidationLayers) {
 	if (enableValidationLayers) {
 		createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
 		createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
 		createInfo.ppEnabledLayerNames = validationLayers.data();
 		createInfo.ppEnabledLayerNames = validationLayers.data();
@@ -1520,14 +1602,24 @@ VkRenderPass Graphics::createRenderPass(RenderPassConfiguration configuration) {
 	dependency.dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
 	dependency.dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
 	dependency.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
 	dependency.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
 
 
+	VkSubpassDependency readbackDependency{};
+	readbackDependency.srcSubpass = 0;
+	readbackDependency.dstSubpass = VK_SUBPASS_EXTERNAL;
+	readbackDependency.srcStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
+	readbackDependency.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
+	readbackDependency.dstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
+	readbackDependency.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
+
+	std::array<VkSubpassDependency, 2> dependencies = { dependency, readbackDependency };
+
     VkRenderPassCreateInfo createInfo{};
     VkRenderPassCreateInfo createInfo{};
     createInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
     createInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
     createInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
     createInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
     createInfo.pAttachments = attachments.data();
     createInfo.pAttachments = attachments.data();
     createInfo.subpassCount = 1;
     createInfo.subpassCount = 1;
     createInfo.pSubpasses = &subPass;
     createInfo.pSubpasses = &subPass;
-	createInfo.dependencyCount = 1;
-	createInfo.pDependencies = &dependency;
+	createInfo.dependencyCount = static_cast<uint32_t>(dependencies.size());
+	createInfo.pDependencies = dependencies.data();
 
 
     VkRenderPass renderPass;
     VkRenderPass renderPass;
     if (vkCreateRenderPass(device, &createInfo, nullptr, &renderPass) != VK_SUCCESS) {
     if (vkCreateRenderPass(device, &createInfo, nullptr, &renderPass) != VK_SUCCESS) {
@@ -1681,6 +1773,16 @@ void Graphics::startDefaultRenderPass() {
 	currentViewportHeight = (float)swapChainExtent.height;
 	currentViewportHeight = (float)swapChainExtent.height;
 	currentMsaaSamples = msaaSamples;
 	currentMsaaSamples = msaaSamples;
 	currentNumColorAttachments = 1;
 	currentNumColorAttachments = 1;
+
+	VkViewport viewport{};
+	viewport.x = 0.0f;
+	viewport.y = 0.0f;
+	viewport.width = currentViewportWidth;
+	viewport.height = currentViewportHeight;
+	viewport.minDepth = 0.0f;
+	viewport.maxDepth = 1.0f;
+
+	vkCmdSetViewport(commandBuffers.at(currentFrame), 0, 1, &viewport);
 }
 }
 
 
 void Graphics::startRenderPass(const RenderTargets& rts, int pixelw, int pixelh, bool hasSRGBtexture) {
 void Graphics::startRenderPass(const RenderTargets& rts, int pixelw, int pixelh, bool hasSRGBtexture) {
@@ -2151,12 +2253,13 @@ void Graphics::createCommandPool() {
 void Graphics::createCommandBuffers() {
 void Graphics::createCommandBuffers() {
 	commandBuffers.resize(MAX_FRAMES_IN_FLIGHT);
 	commandBuffers.resize(MAX_FRAMES_IN_FLIGHT);
 	dataTransferCommandBuffers.resize(MAX_FRAMES_IN_FLIGHT);
 	dataTransferCommandBuffers.resize(MAX_FRAMES_IN_FLIGHT);
+	readbackCommandBuffers.resize(MAX_FRAMES_IN_FLIGHT);
 
 
 	VkCommandBufferAllocateInfo allocInfo{};
 	VkCommandBufferAllocateInfo allocInfo{};
 	allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
 	allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
 	allocInfo.commandPool = commandPool;
 	allocInfo.commandPool = commandPool;
 	allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
 	allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
-	allocInfo.commandBufferCount = (uint32_t)MAX_FRAMES_IN_FLIGHT;
+	allocInfo.commandBufferCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);
 
 
 	if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) {
 	if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) {
 		throw love::Exception("failed to allocate command buffers");
 		throw love::Exception("failed to allocate command buffers");
@@ -2166,11 +2269,21 @@ void Graphics::createCommandBuffers() {
 	dataTransferAllocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
 	dataTransferAllocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
 	dataTransferAllocInfo.commandPool = commandPool;
 	dataTransferAllocInfo.commandPool = commandPool;
 	dataTransferAllocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
 	dataTransferAllocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
-	dataTransferAllocInfo.commandBufferCount = (uint32_t)MAX_FRAMES_IN_FLIGHT;
+	dataTransferAllocInfo.commandBufferCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);
 
 
 	if (vkAllocateCommandBuffers(device, &dataTransferAllocInfo, dataTransferCommandBuffers.data()) != VK_SUCCESS) {
 	if (vkAllocateCommandBuffers(device, &dataTransferAllocInfo, dataTransferCommandBuffers.data()) != VK_SUCCESS) {
 		throw love::Exception("failed to allocate data transfer command buffers");
 		throw love::Exception("failed to allocate data transfer command buffers");
 	}
 	}
+
+	VkCommandBufferAllocateInfo readbackAllocInfo{};
+	readbackAllocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+	readbackAllocInfo.commandPool = commandPool;
+	readbackAllocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+	readbackAllocInfo.commandBufferCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);
+
+	if (vkAllocateCommandBuffers(device, &readbackAllocInfo, readbackCommandBuffers.data()) != VK_SUCCESS) {
+		throw love::Exception("failed to allocate readback command buffers");
+	}
 }
 }
 
 
 void Graphics::createSyncObjects() {
 void Graphics::createSyncObjects() {

+ 16 - 11
src/modules/graphics/vulkan/Graphics.h

@@ -219,11 +219,16 @@ public:
 	void draw(const DrawIndexedCommand& cmd) override;
 	void draw(const DrawIndexedCommand& cmd) override;
 	void drawQuads(int start, int count, const VertexAttributes& attributes, const BufferBindings& buffers, graphics::Texture* texture) 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) override { return nullptr; }
+	graphics::GraphicsReadback* newReadbackInternal(ReadbackMethod method, love::graphics::Buffer* buffer, size_t offset, size_t size, data::ByteData* dest, size_t destoffset) override;
+	graphics::GraphicsReadback* newReadbackInternal(ReadbackMethod method, love::graphics::Texture* texture, int slice, int mipmap, const Rect& rect, image::ImageData* dest, int destx, int desty) override;
 
 
 	VkCommandBuffer getDataTransferCommandBuffer();
 	VkCommandBuffer getDataTransferCommandBuffer();
+	VkCommandBuffer getReadbackCommandBuffer();
+
 	void queueCleanUp(std::function<void()> cleanUp);
 	void queueCleanUp(std::function<void()> cleanUp);
+	void addReadbackCallback(const std::function<void()> &callback);
+
+	void submitGpuCommands(bool present);
 
 
 	uint32_t getNumImagesInFlight() const;
 	uint32_t getNumImagesInFlight() const;
 	const VkDeviceSize getMinUniformBufferOffsetAlignment() const;
 	const VkDeviceSize getMinUniformBufferOffsetAlignment() const;
@@ -231,12 +236,8 @@ public:
 	VkSampler getCachedSampler(const SamplerState&);
 	VkSampler getCachedSampler(const SamplerState&);
 
 
 protected:
 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::ShaderStage* newShaderStageInternal(ShaderStageType stage, const std::string& cachekey, const std::string& source, bool gles) override;
+	graphics::Shader* newShaderInternal(StrongRef<love::graphics::ShaderStage> stages[SHADERSTAGE_MAX_ENUM]) override;
 	graphics::StreamBuffer* newStreamBuffer(BufferUsage type, size_t size) override;
 	graphics::StreamBuffer* newStreamBuffer(BufferUsage type, size_t size) override;
 	bool dispatch(int x, int y, int z) override { return false; }
 	bool dispatch(int x, int y, int z) override { return false; }
 	void initCapabilities() override;
 	void initCapabilities() override;
@@ -280,8 +281,9 @@ private:
 	void cleanupSwapChain();
 	void cleanupSwapChain();
 	void recreateSwapChain();
 	void recreateSwapChain();
 	void initDynamicState();
 	void initDynamicState();
-	void startRecordingGraphicsCommands();
-	void endRecordingGraphicsCommands();
+	void beginFrame();
+	void startRecordingGraphicsCommands(bool newFrame);
+	void endRecordingGraphicsCommands(bool present);
 	void ensureGraphicsPipelineConfiguration(GraphicsPipelineConfiguration);
 	void ensureGraphicsPipelineConfiguration(GraphicsPipelineConfiguration);
 	graphics::Shader::BuiltinUniformData getCurrentBuiltinUniformData();
 	graphics::Shader::BuiltinUniformData getCurrentBuiltinUniformData();
 	void updatedBatchedDrawBuffers();
 	void updatedBatchedDrawBuffers();
@@ -329,13 +331,15 @@ private:
 	std::unordered_map<GraphicsPipelineConfiguration, VkPipeline, GraphicsPipelineConfigurationHasher> graphicsPipelines;
 	std::unordered_map<GraphicsPipelineConfiguration, VkPipeline, GraphicsPipelineConfigurationHasher> graphicsPipelines;
 	std::unordered_map<SamplerState, VkSampler, SamplerStateHasher> samplers;
 	std::unordered_map<SamplerState, VkSampler, SamplerStateHasher> samplers;
 	VkCommandPool commandPool = VK_NULL_HANDLE;
 	VkCommandPool commandPool = VK_NULL_HANDLE;
-	std::vector<VkCommandBuffer> commandBuffers;
 	std::vector<VkCommandBuffer> dataTransferCommandBuffers;
 	std::vector<VkCommandBuffer> dataTransferCommandBuffers;
+	std::vector<VkCommandBuffer> commandBuffers;
+	std::vector<VkCommandBuffer> readbackCommandBuffers;
 	std::vector<VkSemaphore> imageAvailableSemaphores;
 	std::vector<VkSemaphore> imageAvailableSemaphores;
 	std::vector<VkSemaphore> renderFinishedSemaphores;
 	std::vector<VkSemaphore> renderFinishedSemaphores;
 	std::vector<VkFence> inFlightFences;
 	std::vector<VkFence> inFlightFences;
 	std::vector<VkFence> imagesInFlight;
 	std::vector<VkFence> imagesInFlight;
 	VkDeviceSize minUniformBufferOffsetAlignment = 0;
 	VkDeviceSize minUniformBufferOffsetAlignment = 0;
+	bool imageRequested = false;
 	size_t currentFrame = 0;
 	size_t currentFrame = 0;
 	uint32_t imageIndex = 0;
 	uint32_t imageIndex = 0;
 	bool framebufferResized = false;
 	bool framebufferResized = false;
@@ -347,6 +351,7 @@ private:
 	// functions that need to be called to cleanup objects that were needed for rendering a frame.
 	// 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.
 	// just like batchedDrawBuffers we need a vector for each frame in flight.
 	std::vector<std::vector<std::function<void()>>> cleanUpFunctions;
 	std::vector<std::vector<std::function<void()>>> cleanUpFunctions;
+	std::vector<std::vector<std::function<void()>>> readbackCallbacks;
 
 
 	// render pass variables.
 	// render pass variables.
 	std::optional<std::function<void()>> postRenderPass;
 	std::optional<std::function<void()>> postRenderPass;

+ 73 - 0
src/modules/graphics/vulkan/GraphicsReadback.cpp

@@ -0,0 +1,73 @@
+#include "GraphicsReadback.h"
+#include "Buffer.h"
+#include "Texture.h"
+#include "Graphics.h"
+#include "data/ByteData.h"
+
+namespace love {
+namespace graphics {
+namespace vulkan {
+
+GraphicsReadback::GraphicsReadback(love::graphics::Graphics* gfx, ReadbackMethod method, love::graphics::Buffer* buffer, size_t offset, size_t size, data::ByteData* dest, size_t destoffset)
+	: graphics::GraphicsReadback(gfx, method, buffer, offset, size, dest, destoffset) {
+	vgfx = dynamic_cast<Graphics*>(gfx);
+
+	// Immediate readback of readback-type buffers doesn't need a staging buffer.
+	if (method != READBACK_IMMEDIATE || buffer->getDataUsage() != BUFFERDATAUSAGE_READBACK) {
+		stagingBuffer = gfx->getTemporaryBuffer(size, DATAFORMAT_FLOAT, 0, BUFFERDATAUSAGE_READBACK);
+		gfx->copyBuffer(buffer, stagingBuffer, offset, 0, size);
+	}
+
+	if (method == READBACK_IMMEDIATE) {
+		vgfx->submitGpuCommands(false);
+		if (stagingBuffer.get()) {
+			status = readbackBuffer(stagingBuffer, 0, size);
+			gfx->releaseTemporaryBuffer(stagingBuffer);
+		}
+		else
+			status = readbackBuffer(buffer, offset, size);
+	}
+	else
+		vgfx->addReadbackCallback([&]() {
+			status = readbackBuffer(stagingBuffer, 0, stagingBuffer->getSize());
+
+			vgfx->releaseTemporaryBuffer(stagingBuffer);
+			stagingBuffer.set(nullptr);
+		});
+}
+
+GraphicsReadback::GraphicsReadback(love::graphics::Graphics* gfx, ReadbackMethod method, love::graphics::Texture* texture, int slice, int mipmap, const Rect& rect, image::ImageData* dest, int destx, int desty)
+	: graphics::GraphicsReadback(gfx, method, texture, slice, mipmap, rect, dest, destx, desty) {
+	vgfx = dynamic_cast<Graphics*>(gfx);
+
+	size_t size = getPixelFormatSliceSize(textureFormat, rect.w, rect.h);
+
+	stagingBuffer = vgfx->getTemporaryBuffer(size, DATAFORMAT_FLOAT, 0, BUFFERDATAUSAGE_READBACK);
+
+	vgfx->copyTextureToBuffer(texture, stagingBuffer, slice, mipmap, rect, 0, 0);
+
+	vgfx->addReadbackCallback([&]() {
+		status = readbackBuffer(stagingBuffer, 0, stagingBuffer->getSize());
+
+		vgfx->releaseTemporaryBuffer(stagingBuffer);
+		stagingBuffer.set(nullptr);
+	});
+
+	if (method == READBACK_IMMEDIATE)
+		vgfx->submitGpuCommands(false);
+}
+
+GraphicsReadback::~GraphicsReadback() {
+}
+
+void GraphicsReadback::wait() {
+	if (status == STATUS_WAITING)
+		vgfx->submitGpuCommands(false);
+}
+
+void GraphicsReadback::update() {
+}
+
+} // vulkan
+} // graphics
+} // love

+ 31 - 0
src/modules/graphics/vulkan/GraphicsReadback.h

@@ -0,0 +1,31 @@
+#ifndef LOVE_GRAPHICS_VULKAN_GRAPHICS_READBACK_H
+#define LOVE_GRAPHICS_VULKAN_GRAPHICS_READBACK_H
+
+#include "graphics/GraphicsReadback.h"
+
+namespace love {
+namespace graphics {
+namespace vulkan {
+
+class Graphics;
+
+class GraphicsReadback : public graphics::GraphicsReadback {
+public:
+	GraphicsReadback(love::graphics::Graphics* gfx, ReadbackMethod method, love::graphics::Buffer* buffer, size_t offset, size_t size, data::ByteData* dest, size_t destoffset);
+	GraphicsReadback(love::graphics::Graphics* gfx, ReadbackMethod method, love::graphics::Texture* texture, int slice, int mipmap, const Rect& rect, image::ImageData* dest, int destx, int desty);
+	virtual ~GraphicsReadback();
+
+	void wait() override;
+	void update() override;
+
+private:
+
+	Graphics* vgfx;
+	StrongRef<love::graphics::Buffer> stagingBuffer;
+};
+
+} // vulkan
+} // graphics
+} // love
+
+#endif

+ 3 - 2
src/modules/graphics/vulkan/Texture.cpp

@@ -391,7 +391,7 @@ void Texture::copyFromBuffer(graphics::Buffer* source, size_t sourceoffset, int
 }
 }
 
 
 void Texture::copyToBuffer(graphics::Buffer* dest, int slice, int mipmap, const Rect& rect, size_t destoffset, int destwidth, size_t size) {
 void Texture::copyToBuffer(graphics::Buffer* dest, int slice, int mipmap, const Rect& rect, size_t destoffset, int destwidth, size_t size) {
-	auto commandBuffer = vgfx->getDataTransferCommandBuffer();
+	auto commandBuffer = vgfx->getReadbackCommandBuffer();
 
 
 	VkImageSubresourceLayers layers{};
 	VkImageSubresourceLayers layers{};
 	layers.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
 	layers.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
@@ -402,10 +402,11 @@ void Texture::copyToBuffer(graphics::Buffer* dest, int slice, int mipmap, const
 	VkBufferImageCopy region{};
 	VkBufferImageCopy region{};
 	region.bufferOffset = destoffset;
 	region.bufferOffset = destoffset;
 	region.bufferRowLength = destwidth;
 	region.bufferRowLength = destwidth;
-	region.bufferImageHeight = 1;
+	region.bufferImageHeight = 0;
 	region.imageSubresource = layers;
 	region.imageSubresource = layers;
 	region.imageExtent.width = static_cast<uint32_t>(rect.w);
 	region.imageExtent.width = static_cast<uint32_t>(rect.w);
 	region.imageExtent.height = static_cast<uint32_t>(rect.h);
 	region.imageExtent.height = static_cast<uint32_t>(rect.h);
+	region.imageExtent.depth = 1;
 
 
 	Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
 	Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);