From 306943532c1fb565a25fc9b0d8c2aa44c7f36dc5 Mon Sep 17 00:00:00 2001 From: GPUCode Date: Wed, 18 Jan 2023 22:45:52 +0200 Subject: [PATCH] renderer_vulkan: Preliminary screenshot support * It's broken for now but at least it doesn't crash --- .../renderer_vulkan/renderer_vulkan.cpp | 184 ++++++++++++++++++ .../renderer_vulkan/renderer_vulkan.h | 1 + .../renderer_vulkan/vk_swapchain.cpp | 3 +- src/video_core/renderer_vulkan/vk_swapchain.h | 5 + 4 files changed, 192 insertions(+), 1 deletion(-) diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index 941905c96..c6084f33e 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -25,6 +25,8 @@ #include "video_core/host_shaders/vulkan_present_interlaced_frag_spv.h" #include "video_core/host_shaders/vulkan_present_vert_spv.h" +#include + namespace Vulkan { /** @@ -900,6 +902,7 @@ void RendererVulkan::DrawScreens(const Layout::FramebufferLayout& layout, bool f void RendererVulkan::SwapBuffers() { const auto& layout = render_window.GetFramebufferLayout(); PrepareRendertarget(); + RenderScreenshot(); do { if (swapchain.NeedsRecreation()) { @@ -951,6 +954,187 @@ void RendererVulkan::SwapBuffers() { } } +void RendererVulkan::RenderScreenshot() { + if (!VideoCore::g_renderer_screenshot_requested) { + return; + } + + const Layout::FramebufferLayout layout{VideoCore::g_screenshot_framebuffer_layout}; + const vk::Extent2D extent = swapchain.GetExtent(); + const u32 width = std::min(layout.width, extent.width); + const u32 height = std::min(layout.height, extent.height); + + const vk::ImageCreateInfo staging_image_info = { + .imageType = vk::ImageType::e2D, + .format = vk::Format::eB8G8R8A8Unorm, + .extent = + { + .width = width, + .height = height, + .depth = 1, + }, + .mipLevels = 1, + .arrayLayers = 1, + .samples = vk::SampleCountFlagBits::e1, + .tiling = vk::ImageTiling::eLinear, + .usage = vk::ImageUsageFlagBits::eTransferDst, + .initialLayout = vk::ImageLayout::eUndefined, + }; + + const VmaAllocationCreateInfo alloc_create_info = { + .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT, + .usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST, + .requiredFlags = 0, + .preferredFlags = 0, + .pool = VK_NULL_HANDLE, + .pUserData = nullptr, + }; + + VkImage unsafe_image{}; + VmaAllocation allocation{}; + VmaAllocationInfo alloc_info; + VkImageCreateInfo unsafe_image_info = static_cast(staging_image_info); + + VkResult result = vmaCreateImage(instance.GetAllocator(), &unsafe_image_info, + &alloc_create_info, &unsafe_image, &allocation, &alloc_info); + if (result != VK_SUCCESS) [[unlikely]] { + LOG_CRITICAL(Render_Vulkan, "Failed allocating texture with error {}", result); + UNREACHABLE(); + } + + vk::Image staging_image{unsafe_image}; + + renderpass_cache.ExitRenderpass(); + scheduler.Record([width, height, swapchain_image = swapchain.Image(), + staging_image](vk::CommandBuffer cmdbuf) { + const std::array read_barriers = { + vk::ImageMemoryBarrier{ + .srcAccessMask = vk::AccessFlagBits::eMemoryWrite, + .dstAccessMask = vk::AccessFlagBits::eTransferRead, + .oldLayout = vk::ImageLayout::ePresentSrcKHR, + .newLayout = vk::ImageLayout::eTransferSrcOptimal, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapchain_image, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }, + vk::ImageMemoryBarrier{ + .srcAccessMask = vk::AccessFlagBits::eNone, + .dstAccessMask = vk::AccessFlagBits::eTransferWrite, + .oldLayout = vk::ImageLayout::eUndefined, + .newLayout = vk::ImageLayout::eTransferDstOptimal, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = staging_image, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }, + }; + const std::array write_barriers = { + vk::ImageMemoryBarrier{ + .srcAccessMask = vk::AccessFlagBits::eTransferRead, + .dstAccessMask = vk::AccessFlagBits::eMemoryWrite, + .oldLayout = vk::ImageLayout::eTransferSrcOptimal, + .newLayout = vk::ImageLayout::ePresentSrcKHR, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapchain_image, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }, + vk::ImageMemoryBarrier{ + .srcAccessMask = vk::AccessFlagBits::eTransferWrite, + .dstAccessMask = vk::AccessFlagBits::eMemoryRead, + .oldLayout = vk::ImageLayout::eTransferDstOptimal, + .newLayout = vk::ImageLayout::eGeneral, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = staging_image, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }, + }; + + const std::array offsets = { + vk::Offset3D{0, 0, 0}, + vk::Offset3D{static_cast(width), static_cast(height), 1}, + }; + + const vk::ImageBlit blit_area = { + .srcSubresource{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .mipLevel = 0, + .baseArrayLayer = 0, + .layerCount = 1, + }, + .srcOffsets = offsets, + .dstSubresource{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .mipLevel = 0, + .baseArrayLayer = 0, + .layerCount = 1, + }, + .dstOffsets = offsets, + }; + + cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands, + vk::PipelineStageFlagBits::eTransfer, + vk::DependencyFlagBits::eByRegion, {}, {}, read_barriers); + cmdbuf.blitImage(swapchain_image, vk::ImageLayout::eTransferSrcOptimal, staging_image, + vk::ImageLayout::eTransferDstOptimal, blit_area, vk::Filter::eNearest); + cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, + vk::PipelineStageFlagBits::eAllCommands, + vk::DependencyFlagBits::eByRegion, {}, {}, write_barriers); + }); + + // Ensure the copy is fully completed before saving the screenshot + scheduler.Finish(); + + const vk::Device device = instance.GetDevice(); + + // Get layout of the image (including row pitch) + const vk::ImageSubresource subresource = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .mipLevel = 0, + .arrayLayer = 0, + }; + + const vk::SubresourceLayout subresource_layout = + device.getImageSubresourceLayout(staging_image, subresource); + + // Map image memory so we can start copying from it + const u8* data = reinterpret_cast(alloc_info.pMappedData); + std::memcpy(VideoCore::g_screenshot_bits, data + subresource_layout.offset, + subresource_layout.size); + + // Destroy staging image + vmaDestroyImage(instance.GetAllocator(), unsafe_image, allocation); + + VideoCore::g_screenshot_complete_callback(); + VideoCore::g_renderer_screenshot_requested = false; +} + void RendererVulkan::NotifySurfaceChanged() { scheduler.Finish(); vk::SurfaceKHR new_surface = CreateSurface(instance.GetInstance(), render_window); diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h index 43e310759..9df270c63 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.h +++ b/src/video_core/renderer_vulkan/renderer_vulkan.h @@ -91,6 +91,7 @@ private: void LoadColorToActiveVkTexture(u8 color_r, u8 color_g, u8 color_b, const TextureInfo& texture); void ConfigureRenderPipeline(); void PrepareRendertarget(); + void RenderScreenshot(); void BeginRendering(); void DrawScreens(const Layout::FramebufferLayout& layout, bool flipped); diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index 61dbc0e87..0056d5dbc 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -69,7 +69,8 @@ void Swapchain::Create(vk::SurfaceKHR new_surface) { .imageColorSpace = surface_format.colorSpace, .imageExtent = extent, .imageArrayLayers = 1, - .imageUsage = vk::ImageUsageFlagBits::eColorAttachment, + .imageUsage = + vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferSrc, .imageSharingMode = sharing_mode, .queueFamilyIndexCount = queue_family_indices_count, .pQueueFamilyIndices = queue_family_indices.data(), diff --git a/src/video_core/renderer_vulkan/vk_swapchain.h b/src/video_core/renderer_vulkan/vk_swapchain.h index 6cfb83db8..21414a3a9 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.h +++ b/src/video_core/renderer_vulkan/vk_swapchain.h @@ -50,6 +50,11 @@ public: return framebuffers[image_index]; } + /// Returns the current image + [[nodiscard]] vk::Image Image() const { + return images[image_index]; + } + /// Returns the swapchain format [[nodiscard]] vk::SurfaceFormatKHR GetSurfaceFormat() const { return surface_format;