From ec5a3a50f98f576b27e8491a69106215021eebff Mon Sep 17 00:00:00 2001 From: niki Date: Tue, 30 Aug 2022 17:11:59 +0200 Subject: [PATCH] vulkan: implement graphics readback --- CMakeLists.txt | 2 + src/modules/graphics/vulkan/Buffer.cpp | 9 +- src/modules/graphics/vulkan/Graphics.cpp | 197 ++++++++++++++---- src/modules/graphics/vulkan/Graphics.h | 27 ++- .../graphics/vulkan/GraphicsReadback.cpp | 73 +++++++ .../graphics/vulkan/GraphicsReadback.h | 31 +++ src/modules/graphics/vulkan/Texture.cpp | 5 +- 7 files changed, 287 insertions(+), 57 deletions(-) create mode 100644 src/modules/graphics/vulkan/GraphicsReadback.cpp create mode 100644 src/modules/graphics/vulkan/GraphicsReadback.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d8f42756..94f403cea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -599,6 +599,8 @@ set(LOVE_SRC_MODULE_GRAPHICS_OPENGL set(LOVE_SRC_MODULE_GRAPHICS_VULKAN src/modules/graphics/vulkan/Graphics.h 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.cpp src/modules/graphics/vulkan/ShaderStage.h diff --git a/src/modules/graphics/vulkan/Buffer.cpp b/src/modules/graphics/vulkan/Buffer.cpp index 575b3b8d4..14480c8a3 100644 --- a/src/modules/graphics/vulkan/Buffer.cpp +++ b/src/modules/graphics/vulkan/Buffer.cpp @@ -38,7 +38,12 @@ bool Buffer::loadVolatile() { VkBufferCreateInfo bufferInfo{}; bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; 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 = {}; 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) { Graphics* vgfx = (Graphics*)gfx; - auto commandBuffer = vgfx->getDataTransferCommandBuffer(); + auto commandBuffer = vgfx->getReadbackCommandBuffer(); VkBufferCopy bufferCopy{}; bufferCopy.srcOffset = sourceoffset; diff --git a/src/modules/graphics/vulkan/Graphics.cpp b/src/modules/graphics/vulkan/Graphics.cpp index 67b6ba35e..bd14441d8 100644 --- a/src/modules/graphics/vulkan/Graphics.cpp +++ b/src/modules/graphics/vulkan/Graphics.cpp @@ -1,5 +1,6 @@ #include "Graphics.h" #include "Buffer.h" +#include "GraphicsReadback.h" #include "SDL_vulkan.h" #include "window/Window.h" #include "common/Exception.h" @@ -147,50 +148,81 @@ void Graphics::clear(const std::vector& colors, OptionalInt sten vkCmdClearAttachments(commandBuffers[currentFrame], static_cast(attachments.size()), attachments.data(), 1, &rect); } -void Graphics::present(void* screenshotCallbackdata) { - if (!isActive()) { - return; - } - +void Graphics::submitGpuCommands(bool present) { flushBatchedDraws(); - endRecordingGraphicsCommands(); + endRecordingGraphicsCommands(present); 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 submitCommandbuffers = { dataTransferCommandBuffers.at(currentFrame), commandBuffers.at(currentFrame) }; + std::vector submitCommandbuffers = { + dataTransferCommandBuffers.at(currentFrame), + commandBuffers.at(currentFrame), + readbackCommandBuffers.at(currentFrame)}; VkSubmitInfo submitInfo{}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; VkSemaphore waitSemaphores[] = { imageAvailableSemaphores.at(currentFrame) }; 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(submitCommandbuffers.size()); submitInfo.pCommandBuffers = submitCommandbuffers.data(); VkSemaphore signalSemaphores[] = { renderFinishedSemaphores.at(currentFrame) }; - submitInfo.signalSemaphoreCount = 1; - submitInfo.pSignalSemaphores = signalSemaphores; - vkResetFences(device, 1, &inFlightFences[currentFrame]); + VkFence fence = VK_NULL_HANDLE; - if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences.at(currentFrame)) != VK_SUCCESS) { + if (present) { + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = signalSemaphores; + + vkResetFences(device, 1, &inFlightFences[currentFrame]); + fence = inFlightFences[currentFrame]; + } + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, fence) != VK_SUCCESS) { 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{}; presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + VkSemaphore waitSemaphores[] = { renderFinishedSemaphores.at(currentFrame) }; + presentInfo.waitSemaphoreCount = 1; - presentInfo.pWaitSemaphores = signalSemaphores; + presentInfo.pWaitSemaphores = waitSemaphores; VkSwapchainKHR swapChains[] = { swapChain }; presentInfo.swapchainCount = 1; @@ -210,8 +242,9 @@ void Graphics::present(void* screenshotCallbackdata) { currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + beginFrame(); + updatedBatchedDrawBuffers(); - startRecordingGraphicsCommands(); } 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.resize(MAX_FRAMES_IN_FLIGHT); + readbackCallbacks.clear(); + readbackCallbacks.resize(MAX_FRAMES_IN_FLIGHT); + createVulkanInstance(); createSurface(); pickPhysicalDevice(); @@ -244,13 +280,6 @@ bool Graphics::setMode(void* context, int width, int height, int pixelwidth, int createDefaultFramebuffers(); createCommandPool(); createCommandBuffers(); - startRecordingGraphicsCommands(); - createQuadIndexBuffer(); - createDefaultTexture(); - createDefaultShaders(); - currentFrame = 0; - - created = true; 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->markUsed(sizeof(whiteColor)); } - updatedBatchedDrawBuffers(); + beginFrame(); + + createDefaultTexture(); + createDefaultShaders(); Shader::current = Shader::standardShaders[graphics::Shader::StandardShader::STANDARD_DEFAULT]; + createQuadIndexBuffer(); + restoreState(states.back()); setViewportSize(width, height, pixelwidth, pixelheight); - currentViewportWidth = 0.0f; - currentViewportHeight = 0.0f; + currentViewportWidth = 1.0f; + currentViewportHeight = 1.0f; Vulkan::resetShaderSwitches(); + currentFrame = 0; + + created = true; + return true; } @@ -301,10 +339,10 @@ void Graphics::initCapabilities() { capabilities.features[FEATURE_INSTANCING] = true; 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; + 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!"); VkPhysicalDeviceProperties properties; @@ -584,6 +622,22 @@ Renderer Graphics::getRenderer() const { 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 stages[SHADERSTAGE_MAX_ENUM]) { + return new Shader(stages); +} + graphics::StreamBuffer* Graphics::newStreamBuffer(BufferUsage type, size_t 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); while (true) { @@ -661,11 +715,24 @@ void Graphics::startRecordingGraphicsCommands() { break; } + imageRequested = true; + + for (auto& readbackCallback : readbackCallbacks.at(currentFrame)) { + readbackCallback(); + } + readbackCallbacks.at(currentFrame).clear(); + for (auto& cleanUpFn : cleanUpFunctions.at(currentFrame)) { cleanUpFn(); } cleanUpFunctions.at(currentFrame).clear(); + startRecordingGraphicsCommands(true); + + Vulkan::resetShaderSwitches(); +} + +void Graphics::startRecordingGraphicsCommands(bool newFrame) { VkCommandBufferBeginInfo beginInfo{}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = 0; @@ -677,20 +744,25 @@ void Graphics::startRecordingGraphicsCommands() { if (vkBeginCommandBuffer(dataTransferCommandBuffers.at(currentFrame), &beginInfo) != VK_SUCCESS) { 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(); - 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(); - - Vulkan::resetShaderSwitches(); } -void Graphics::endRecordingGraphicsCommands() { +void Graphics::endRecordingGraphicsCommands(bool present) { 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) { throw love::Exception("failed to record command buffer"); @@ -698,6 +770,9 @@ void Graphics::endRecordingGraphicsCommands() { if (vkEndCommandBuffer(dataTransferCommandBuffers.at(currentFrame)) != VK_SUCCESS) { 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() { @@ -725,10 +800,18 @@ VkCommandBuffer Graphics::getDataTransferCommandBuffer() { return dataTransferCommandBuffers.at(currentFrame); } +VkCommandBuffer Graphics::getReadbackCommandBuffer() { + return readbackCommandBuffers.at(currentFrame); +} + void Graphics::queueCleanUp(std::function cleanUp) { cleanUpFunctions.at(currentFrame).push_back(std::move(cleanUp)); } +void Graphics::addReadbackCallback(const std::function& callback) { + readbackCallbacks.at(currentFrame).push_back(std::move(callback)); +} + graphics::Shader::BuiltinUniformData Graphics::getCurrentBuiltinUniformData() { love::graphics::Shader::BuiltinUniformData data; @@ -1036,7 +1119,6 @@ void Graphics::createLogicalDevice() { createInfo.enabledExtensionCount = static_cast(enabledExtensions.size()); createInfo.ppEnabledExtensionNames = enabledExtensions.data(); - // can this be removed? if (enableValidationLayers) { createInfo.enabledLayerCount = static_cast(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); @@ -1520,14 +1602,24 @@ VkRenderPass Graphics::createRenderPass(RenderPassConfiguration configuration) { dependency.dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_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 dependencies = { dependency, readbackDependency }; + VkRenderPassCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; createInfo.attachmentCount = static_cast(attachments.size()); createInfo.pAttachments = attachments.data(); createInfo.subpassCount = 1; createInfo.pSubpasses = &subPass; - createInfo.dependencyCount = 1; - createInfo.pDependencies = &dependency; + createInfo.dependencyCount = static_cast(dependencies.size()); + createInfo.pDependencies = dependencies.data(); VkRenderPass renderPass; if (vkCreateRenderPass(device, &createInfo, nullptr, &renderPass) != VK_SUCCESS) { @@ -1681,6 +1773,16 @@ void Graphics::startDefaultRenderPass() { currentViewportHeight = (float)swapChainExtent.height; currentMsaaSamples = msaaSamples; 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) { @@ -2151,12 +2253,13 @@ void Graphics::createCommandPool() { void Graphics::createCommandBuffers() { commandBuffers.resize(MAX_FRAMES_IN_FLIGHT); dataTransferCommandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + readbackCommandBuffers.resize(MAX_FRAMES_IN_FLIGHT); 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)MAX_FRAMES_IN_FLIGHT; + allocInfo.commandBufferCount = static_cast(MAX_FRAMES_IN_FLIGHT); if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) { 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.commandPool = commandPool; dataTransferAllocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; - dataTransferAllocInfo.commandBufferCount = (uint32_t)MAX_FRAMES_IN_FLIGHT; + dataTransferAllocInfo.commandBufferCount = static_cast(MAX_FRAMES_IN_FLIGHT); if (vkAllocateCommandBuffers(device, &dataTransferAllocInfo, dataTransferCommandBuffers.data()) != VK_SUCCESS) { 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(MAX_FRAMES_IN_FLIGHT); + + if (vkAllocateCommandBuffers(device, &readbackAllocInfo, readbackCommandBuffers.data()) != VK_SUCCESS) { + throw love::Exception("failed to allocate readback command buffers"); + } } void Graphics::createSyncObjects() { diff --git a/src/modules/graphics/vulkan/Graphics.h b/src/modules/graphics/vulkan/Graphics.h index 80eff97a3..63282ee1a 100644 --- a/src/modules/graphics/vulkan/Graphics.h +++ b/src/modules/graphics/vulkan/Graphics.h @@ -219,11 +219,16 @@ public: 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) 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 getReadbackCommandBuffer(); + void queueCleanUp(std::function cleanUp); + void addReadbackCallback(const std::function &callback); + + void submitGpuCommands(bool present); uint32_t getNumImagesInFlight() const; const VkDeviceSize getMinUniformBufferOffsetAlignment() const; @@ -231,12 +236,8 @@ public: VkSampler getCachedSampler(const SamplerState&); 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 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 stages[SHADERSTAGE_MAX_ENUM]) override; graphics::StreamBuffer* newStreamBuffer(BufferUsage type, size_t size) override; bool dispatch(int x, int y, int z) override { return false; } void initCapabilities() override; @@ -280,8 +281,9 @@ private: void cleanupSwapChain(); void recreateSwapChain(); void initDynamicState(); - void startRecordingGraphicsCommands(); - void endRecordingGraphicsCommands(); + void beginFrame(); + void startRecordingGraphicsCommands(bool newFrame); + void endRecordingGraphicsCommands(bool present); void ensureGraphicsPipelineConfiguration(GraphicsPipelineConfiguration); graphics::Shader::BuiltinUniformData getCurrentBuiltinUniformData(); void updatedBatchedDrawBuffers(); @@ -329,13 +331,15 @@ private: std::unordered_map graphicsPipelines; std::unordered_map samplers; VkCommandPool commandPool = VK_NULL_HANDLE; - std::vector commandBuffers; std::vector dataTransferCommandBuffers; + std::vector commandBuffers; + std::vector readbackCommandBuffers; std::vector imageAvailableSemaphores; std::vector renderFinishedSemaphores; std::vector inFlightFences; std::vector imagesInFlight; VkDeviceSize minUniformBufferOffsetAlignment = 0; + bool imageRequested = false; size_t currentFrame = 0; uint32_t imageIndex = 0; bool framebufferResized = false; @@ -347,6 +351,7 @@ private: // 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>> cleanUpFunctions; + std::vector>> readbackCallbacks; // render pass variables. std::optional> postRenderPass; diff --git a/src/modules/graphics/vulkan/GraphicsReadback.cpp b/src/modules/graphics/vulkan/GraphicsReadback.cpp new file mode 100644 index 000000000..7c7489bd9 --- /dev/null +++ b/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(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(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 diff --git a/src/modules/graphics/vulkan/GraphicsReadback.h b/src/modules/graphics/vulkan/GraphicsReadback.h new file mode 100644 index 000000000..3148b5f57 --- /dev/null +++ b/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 stagingBuffer; +}; + +} // vulkan +} // graphics +} // love + +#endif diff --git a/src/modules/graphics/vulkan/Texture.cpp b/src/modules/graphics/vulkan/Texture.cpp index 2c0e11c5b..ffb9be8f9 100644 --- a/src/modules/graphics/vulkan/Texture.cpp +++ b/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) { - auto commandBuffer = vgfx->getDataTransferCommandBuffer(); + auto commandBuffer = vgfx->getReadbackCommandBuffer(); VkImageSubresourceLayers layers{}; layers.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; @@ -402,10 +402,11 @@ void Texture::copyToBuffer(graphics::Buffer* dest, int slice, int mipmap, const VkBufferImageCopy region{}; region.bufferOffset = destoffset; region.bufferRowLength = destwidth; - region.bufferImageHeight = 1; + region.bufferImageHeight = 0; region.imageSubresource = layers; region.imageExtent.width = static_cast(rect.w); region.imageExtent.height = static_cast(rect.h); + region.imageExtent.depth = 1; Vulkan::cmdTransitionImageLayout(commandBuffer, textureImage, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);