renderer_vulkan: Fix screenshots under NVIDIA vulkan (#7082)

This commit is contained in:
GPUCode 2023-10-22 22:53:14 +03:00 committed by GitHub
parent 597297ffb4
commit 36146459f8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 66 additions and 135 deletions

View file

@ -669,9 +669,9 @@ void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_p
screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32); screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32);
system.Renderer().RequestScreenshot( system.Renderer().RequestScreenshot(
screenshot_image.bits(), screenshot_image.bits(),
[this, screenshot_path] { [this, screenshot_path](bool invert_y) {
const std::string std_screenshot_path = screenshot_path.toStdString(); const std::string std_screenshot_path = screenshot_path.toStdString();
if (screenshot_image.mirrored(false, true).save(screenshot_path)) { if (screenshot_image.mirrored(false, invert_y).save(screenshot_path)) {
LOG_INFO(Frontend, "Screenshot saved to \"{}\"", std_screenshot_path); LOG_INFO(Frontend, "Screenshot saved to \"{}\"", std_screenshot_path);
} else { } else {
LOG_ERROR(Frontend, "Failed to save screenshot to \"{}\"", std_screenshot_path); LOG_ERROR(Frontend, "Failed to save screenshot to \"{}\"", std_screenshot_path);

View file

@ -59,7 +59,7 @@ bool RendererBase::IsScreenshotPending() const {
return settings.screenshot_requested; return settings.screenshot_requested;
} }
void RendererBase::RequestScreenshot(void* data, std::function<void()> callback, void RendererBase::RequestScreenshot(void* data, std::function<void(bool)> callback,
const Layout::FramebufferLayout& layout) { const Layout::FramebufferLayout& layout) {
if (settings.screenshot_requested) { if (settings.screenshot_requested) {
LOG_ERROR(Render, "A screenshot is already requested or in progress, ignoring the request"); LOG_ERROR(Render, "A screenshot is already requested or in progress, ignoring the request");

View file

@ -28,7 +28,7 @@ struct RendererSettings {
// Screenshot // Screenshot
std::atomic_bool screenshot_requested{false}; std::atomic_bool screenshot_requested{false};
void* screenshot_bits{}; void* screenshot_bits{};
std::function<void()> screenshot_complete_callback; std::function<void(bool)> screenshot_complete_callback;
Layout::FramebufferLayout screenshot_framebuffer_layout; Layout::FramebufferLayout screenshot_framebuffer_layout;
// Renderer // Renderer
std::atomic_bool bg_color_update_requested{false}; std::atomic_bool bg_color_update_requested{false};
@ -103,7 +103,7 @@ public:
[[nodiscard]] bool IsScreenshotPending() const; [[nodiscard]] bool IsScreenshotPending() const;
/// Request a screenshot of the next frame /// Request a screenshot of the next frame
void RequestScreenshot(void* data, std::function<void()> callback, void RequestScreenshot(void* data, std::function<void(bool)> callback,
const Layout::FramebufferLayout& layout); const Layout::FramebufferLayout& layout);
protected: protected:

View file

@ -151,7 +151,7 @@ void RendererOpenGL::RenderScreenshot() {
state.Apply(); state.Apply();
glDeleteRenderbuffers(1, &renderbuffer); glDeleteRenderbuffers(1, &renderbuffer);
settings.screenshot_complete_callback(); settings.screenshot_complete_callback(true);
} }
} }

View file

@ -6,6 +6,7 @@
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/microprofile.h" #include "common/microprofile.h"
#include "common/settings.h" #include "common/settings.h"
#include "common/texture.h"
#include "core/core.h" #include "core/core.h"
#include "core/frontend/emu_window.h" #include "core/frontend/emu_window.h"
#include "core/hw/gpu.h" #include "core/hw/gpu.h"
@ -18,6 +19,7 @@
#include "video_core/host_shaders/vulkan_present_frag_spv.h" #include "video_core/host_shaders/vulkan_present_frag_spv.h"
#include "video_core/host_shaders/vulkan_present_interlaced_frag_spv.h" #include "video_core/host_shaders/vulkan_present_interlaced_frag_spv.h"
#include "video_core/host_shaders/vulkan_present_vert_spv.h" #include "video_core/host_shaders/vulkan_present_vert_spv.h"
#include "vulkan/vulkan_format_traits.hpp"
#include <vk_mem_alloc.h> #include <vk_mem_alloc.h>
@ -867,20 +869,9 @@ void RendererVulkan::RenderScreenshot() {
const u32 width = layout.width; const u32 width = layout.width;
const u32 height = layout.height; const u32 height = layout.height;
const vk::ImageCreateInfo staging_image_info = { const vk::BufferCreateInfo staging_buffer_info = {
.imageType = vk::ImageType::e2D, .size = width * height * 4,
.format = vk::Format::eB8G8R8A8Unorm, .usage = vk::BufferUsageFlagBits::eTransferDst,
.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 = { const VmaAllocationCreateInfo alloc_create_info = {
@ -893,18 +884,18 @@ void RendererVulkan::RenderScreenshot() {
.pUserData = nullptr, .pUserData = nullptr,
}; };
VkImage unsafe_image{}; VkBuffer unsafe_buffer{};
VmaAllocation allocation{}; VmaAllocation allocation{};
VmaAllocationInfo alloc_info; VmaAllocationInfo alloc_info;
VkImageCreateInfo unsafe_image_info = static_cast<VkImageCreateInfo>(staging_image_info); VkBufferCreateInfo unsafe_buffer_info = static_cast<VkBufferCreateInfo>(staging_buffer_info);
VkResult result = vmaCreateImage(instance.GetAllocator(), &unsafe_image_info, VkResult result = vmaCreateBuffer(instance.GetAllocator(), &unsafe_buffer_info,
&alloc_create_info, &unsafe_image, &allocation, &alloc_info); &alloc_create_info, &unsafe_buffer, &allocation, &alloc_info);
if (result != VK_SUCCESS) [[unlikely]] { if (result != VK_SUCCESS) [[unlikely]] {
LOG_CRITICAL(Render_Vulkan, "Failed allocating texture with error {}", result); LOG_CRITICAL(Render_Vulkan, "Failed allocating texture with error {}", result);
UNREACHABLE(); UNREACHABLE();
} }
vk::Image staging_image{unsafe_image}; vk::Buffer staging_buffer{unsafe_buffer};
Frame frame{}; Frame frame{};
main_window.RecreateFrame(&frame, width, height); main_window.RecreateFrame(&frame, width, height);
@ -912,9 +903,8 @@ void RendererVulkan::RenderScreenshot() {
DrawScreens(&frame, layout, false); DrawScreens(&frame, layout, false);
scheduler.Record( scheduler.Record(
[width, height, source_image = frame.image, staging_image](vk::CommandBuffer cmdbuf) { [width, height, source_image = frame.image, staging_buffer](vk::CommandBuffer cmdbuf) {
const std::array read_barriers = { const vk::ImageMemoryBarrier read_barrier = {
vk::ImageMemoryBarrier{
.srcAccessMask = vk::AccessFlagBits::eMemoryWrite, .srcAccessMask = vk::AccessFlagBits::eMemoryWrite,
.dstAccessMask = vk::AccessFlagBits::eTransferRead, .dstAccessMask = vk::AccessFlagBits::eTransferRead,
.oldLayout = vk::ImageLayout::eTransferSrcOptimal, .oldLayout = vk::ImageLayout::eTransferSrcOptimal,
@ -929,26 +919,8 @@ void RendererVulkan::RenderScreenshot() {
.baseArrayLayer = 0, .baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS, .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 = { const vk::ImageMemoryBarrier write_barrier = {
vk::ImageMemoryBarrier{
.srcAccessMask = vk::AccessFlagBits::eTransferRead, .srcAccessMask = vk::AccessFlagBits::eTransferRead,
.dstAccessMask = vk::AccessFlagBits::eMemoryWrite, .dstAccessMask = vk::AccessFlagBits::eMemoryWrite,
.oldLayout = vk::ImageLayout::eTransferSrcOptimal, .oldLayout = vk::ImageLayout::eTransferSrcOptimal,
@ -963,64 +935,35 @@ void RendererVulkan::RenderScreenshot() {
.baseArrayLayer = 0, .baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS, .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,
},
},
}; };
static constexpr vk::MemoryBarrier memory_write_barrier = { static constexpr vk::MemoryBarrier memory_write_barrier = {
.srcAccessMask = vk::AccessFlagBits::eMemoryWrite, .srcAccessMask = vk::AccessFlagBits::eMemoryWrite,
.dstAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite, .dstAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite,
}; };
const std::array src_offsets = { const vk::BufferImageCopy image_copy = {
vk::Offset3D{0, 0, 0}, .bufferOffset = 0,
vk::Offset3D{static_cast<s32>(width), static_cast<s32>(height), 1}, .bufferRowLength = 0,
}; .bufferImageHeight = 0,
.imageSubresource =
const std::array dst_offsets = { {
vk::Offset3D{0, static_cast<s32>(height), 0},
vk::Offset3D{static_cast<s32>(width), 0, 1},
};
const vk::ImageBlit blit_area = {
.srcSubresource{
.aspectMask = vk::ImageAspectFlagBits::eColor, .aspectMask = vk::ImageAspectFlagBits::eColor,
.mipLevel = 0, .mipLevel = 0,
.baseArrayLayer = 0, .baseArrayLayer = 0,
.layerCount = 1, .layerCount = 1,
}, },
.srcOffsets = src_offsets, .imageOffset = {0, 0, 0},
.dstSubresource{ .imageExtent = {width, height, 1},
.aspectMask = vk::ImageAspectFlagBits::eColor,
.mipLevel = 0,
.baseArrayLayer = 0,
.layerCount = 1,
},
.dstOffsets = dst_offsets,
}; };
cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands, cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands,
vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer,
vk::DependencyFlagBits::eByRegion, {}, {}, read_barriers); vk::DependencyFlagBits::eByRegion, {}, {}, read_barrier);
cmdbuf.blitImage(source_image, vk::ImageLayout::eTransferSrcOptimal, staging_image, cmdbuf.copyImageToBuffer(source_image, vk::ImageLayout::eTransferSrcOptimal,
vk::ImageLayout::eTransferDstOptimal, blit_area, vk::Filter::eNearest); staging_buffer, image_copy);
cmdbuf.pipelineBarrier( cmdbuf.pipelineBarrier(
vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eAllCommands, vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eAllCommands,
vk::DependencyFlagBits::eByRegion, memory_write_barrier, {}, write_barriers); vk::DependencyFlagBits::eByRegion, memory_write_barrier, {}, write_barrier);
}); });
// Ensure the copy is fully completed before saving the screenshot // Ensure the copy is fully completed before saving the screenshot
@ -1028,27 +971,16 @@ void RendererVulkan::RenderScreenshot() {
const vk::Device device = instance.GetDevice(); 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);
// Copy backing image data to the QImage screenshot buffer // Copy backing image data to the QImage screenshot buffer
const u8* data = reinterpret_cast<const u8*>(alloc_info.pMappedData); std::memcpy(settings.screenshot_bits, alloc_info.pMappedData, staging_buffer_info.size);
std::memcpy(settings.screenshot_bits, data + subresource_layout.offset,
subresource_layout.size);
// Destroy allocated resources // Destroy allocated resources
vmaDestroyBuffer(instance.GetAllocator(), unsafe_buffer, allocation);
vmaDestroyImage(instance.GetAllocator(), frame.image, frame.allocation); vmaDestroyImage(instance.GetAllocator(), frame.image, frame.allocation);
device.destroyFramebuffer(frame.framebuffer); device.destroyFramebuffer(frame.framebuffer);
device.destroyImageView(frame.image_view); device.destroyImageView(frame.image_view);
settings.screenshot_complete_callback(); settings.screenshot_complete_callback(false);
} }
} // namespace Vulkan } // namespace Vulkan

View file

@ -150,8 +150,7 @@ void Swapchain::FindPresentFormat() {
return; return;
} }
LOG_CRITICAL(Render_Vulkan, "Unable to find required swapchain format!"); UNREACHABLE_MSG("Unable to find required swapchain format!");
UNREACHABLE();
} }
void Swapchain::SetPresentMode() { void Swapchain::SetPresentMode() {