diff --git a/Source/Renderer/Renderer/Renderer.h b/Source/Renderer/Renderer/Renderer.h index 073ddf34..a298afe3 100644 --- a/Source/Renderer/Renderer/Renderer.h +++ b/Source/Renderer/Renderer/Renderer.h @@ -229,6 +229,10 @@ namespace Renderer virtual void CopyBuffer(BufferID dstBuffer, u64 dstOffset, BufferID srcBuffer, u64 srcOffset, u64 range) = 0; // Immediately copies using an immediate command list virtual void CopyBufferImmediate(BufferID dstBuffer, u64 dstOffset, BufferID srcBuffer, u64 srcOffset, u64 range) = 0; + // Debug/automation readback. These calls wait for the graphics queue and + // should not be used in performance-sensitive runtime paths. + virtual bool ReadImageImmediate(ImageID image, void* destination, size_t destinationSize) = 0; + virtual bool ReadImageImmediate(DepthImageID image, void* destination, size_t destinationSize) = 0; void UploadToBuffer(BufferID dstBuffer, u64 dstOffset, void* srcData, u64 srcOffset, u64 srcSize); virtual void* MapBuffer(BufferID buffer) = 0; diff --git a/Source/Renderer/Renderer/Renderers/Vulkan/RendererVK.cpp b/Source/Renderer/Renderer/Renderers/Vulkan/RendererVK.cpp index d3c12941..6a7ba7d0 100644 --- a/Source/Renderer/Renderer/Renderers/Vulkan/RendererVK.cpp +++ b/Source/Renderer/Renderer/Renderers/Vulkan/RendererVK.cpp @@ -26,6 +26,8 @@ #include #include +#include + namespace Renderer { RendererVK::RendererVK(Novus::Window* window) @@ -2071,6 +2073,138 @@ namespace Renderer _device->EndSingleTimeCommands(commandList); } + bool RendererVK::ReadImageImmediate(ImageID imageID, void* destination, size_t destinationSize) + { + if (imageID == ImageID::Invalid() || destination == nullptr || destinationSize == 0) + return false; + + const uvec2 dimensions = _imageHandler->GetDimensions(imageID, 0); + if (dimensions.x == 0 || dimensions.y == 0) + return false; + + BufferDesc readbackDesc; + readbackDesc.name = "Immediate color image readback"; + readbackDesc.usage = BufferUsage::TRANSFER_DESTINATION; + readbackDesc.cpuAccess = BufferCPUAccess::ReadOnly; + readbackDesc.size = destinationSize; + const BufferID readbackBuffer = _bufferHandler->CreateBuffer(readbackDesc); + if (readbackBuffer == BufferID::Invalid()) + return false; + + VkImage image = _imageHandler->GetImage(imageID); + VkBuffer buffer = _bufferHandler->GetBuffer(readbackBuffer); + VkCommandBuffer commandBuffer = _device->BeginSingleTimeCommands(); + + _device->TransitionImageLayout( + commandBuffer, + image, + VK_IMAGE_ASPECT_COLOR_BIT, + VK_IMAGE_LAYOUT_GENERAL, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + 1, + 1); + + VkBufferImageCopy copyRegion = {}; + copyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + copyRegion.imageSubresource.layerCount = 1; + copyRegion.imageExtent = { dimensions.x, dimensions.y, 1 }; + vkCmdCopyImageToBuffer( + commandBuffer, + image, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + buffer, + 1, + ©Region); + + _device->TransitionImageLayout( + commandBuffer, + image, + VK_IMAGE_ASPECT_COLOR_BIT, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_IMAGE_LAYOUT_GENERAL, + 1, + 1); + _device->EndSingleTimeCommands(commandBuffer); + + void* mappedMemory = MapBuffer(readbackBuffer); + if (mappedMemory == nullptr) + { + _bufferHandler->DestroyBuffer(readbackBuffer); + return false; + } + + std::memcpy(destination, mappedMemory, destinationSize); + UnmapBuffer(readbackBuffer); + _bufferHandler->DestroyBuffer(readbackBuffer); + return true; + } + + bool RendererVK::ReadImageImmediate(DepthImageID imageID, void* destination, size_t destinationSize) + { + if (imageID == DepthImageID::Invalid() || destination == nullptr || destinationSize == 0) + return false; + + const uvec2 dimensions = _imageHandler->GetDimensions(imageID); + if (dimensions.x == 0 || dimensions.y == 0) + return false; + + BufferDesc readbackDesc; + readbackDesc.name = "Immediate depth image readback"; + readbackDesc.usage = BufferUsage::TRANSFER_DESTINATION; + readbackDesc.cpuAccess = BufferCPUAccess::ReadOnly; + readbackDesc.size = destinationSize; + const BufferID readbackBuffer = _bufferHandler->CreateBuffer(readbackDesc); + if (readbackBuffer == BufferID::Invalid()) + return false; + + VkImage image = _imageHandler->GetImage(imageID); + VkBuffer buffer = _bufferHandler->GetBuffer(readbackBuffer); + VkCommandBuffer commandBuffer = _device->BeginSingleTimeCommands(); + + _device->TransitionImageLayout( + commandBuffer, + image, + VK_IMAGE_ASPECT_DEPTH_BIT, + VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + 1, + 1); + + VkBufferImageCopy copyRegion = {}; + copyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + copyRegion.imageSubresource.layerCount = 1; + copyRegion.imageExtent = { dimensions.x, dimensions.y, 1 }; + vkCmdCopyImageToBuffer( + commandBuffer, + image, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + buffer, + 1, + ©Region); + + _device->TransitionImageLayout( + commandBuffer, + image, + VK_IMAGE_ASPECT_DEPTH_BIT, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL, + 1, + 1); + _device->EndSingleTimeCommands(commandBuffer); + + void* mappedMemory = MapBuffer(readbackBuffer); + if (mappedMemory == nullptr) + { + _bufferHandler->DestroyBuffer(readbackBuffer); + return false; + } + + std::memcpy(destination, mappedMemory, destinationSize); + UnmapBuffer(readbackBuffer); + _bufferHandler->DestroyBuffer(readbackBuffer); + return true; + } + void RendererVK::FillBuffer(CommandListID commandListID, BufferID dstBuffer, u64 dstOffset, u64 size, u32 data) { VkCommandBuffer commandBuffer = _commandListHandler->GetCommandBuffer(commandListID); diff --git a/Source/Renderer/Renderer/Renderers/Vulkan/RendererVK.h b/Source/Renderer/Renderer/Renderers/Vulkan/RendererVK.h index c0916002..d76dca2f 100644 --- a/Source/Renderer/Renderer/Renderers/Vulkan/RendererVK.h +++ b/Source/Renderer/Renderer/Renderers/Vulkan/RendererVK.h @@ -185,6 +185,8 @@ namespace Renderer void CopyBuffer(BufferID dstBuffer, u64 dstOffset, BufferID srcBuffer, u64 srcOffset, u64 range) override; // Immediately copies using an immediate command list void CopyBufferImmediate(BufferID dstBuffer, u64 dstOffset, BufferID srcBuffer, u64 srcOffset, u64 range) override; + bool ReadImageImmediate(ImageID image, void* destination, size_t destinationSize) override; + bool ReadImageImmediate(DepthImageID image, void* destination, size_t destinationSize) override; [[nodiscard]] void* MapBuffer(BufferID buffer) override; void UnmapBuffer(BufferID buffer) override;