From 9e742ed4c8a588469691f3ff2159cb51cb48fb6f Mon Sep 17 00:00:00 2001 From: Malachi Date: Sun, 31 May 2026 00:34:30 -0400 Subject: [PATCH] renderer_vulkan: Fix screenshot color channels on RGBA swapchain formats --- src/citra_libretro/libretro_vk.h | 4 ++++ .../renderer_vulkan/renderer_vulkan.cpp | 18 ++++++++++++++++++ .../renderer_vulkan/vk_present_window.h | 4 ++++ 3 files changed, 26 insertions(+) diff --git a/src/citra_libretro/libretro_vk.h b/src/citra_libretro/libretro_vk.h index c51075e21..335d813cf 100644 --- a/src/citra_libretro/libretro_vk.h +++ b/src/citra_libretro/libretro_vk.h @@ -126,6 +126,10 @@ public: return static_cast(frame_pool.size()); } + vk::Format GetSurfaceFormat() const noexcept { + return output_format; + } + private: /// Creates the render pass for LibRetro output vk::RenderPass CreateRenderpass(); diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index 0a25c2036..91be15222 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -1268,6 +1268,15 @@ void RendererVulkan::RenderScreenshotWithStagingCopy() { // Copy backing image data to the QImage screenshot buffer std::memcpy(settings.screenshot_bits, alloc_info.pMappedData, staging_buffer_info.size); + // QImage::Format_RGB32 expects BGRA byte order. If the swapchain format is RGBA, + // swap R and B channels so the screenshot colors are correct. + if (main_present_window.GetSurfaceFormat() == vk::Format::eR8G8B8A8Unorm) { + u8* pixels = static_cast(settings.screenshot_bits); + for (u32 i = 0; i < width * height; i++) { + std::swap(pixels[i * 4 + 0], pixels[i * 4 + 2]); + } + } + // Destroy allocated resources vmaDestroyBuffer(instance.GetAllocator(), staging_buffer, allocation); vmaDestroyImage(instance.GetAllocator(), frame.image, frame.allocation); @@ -1423,6 +1432,15 @@ bool RendererVulkan::TryRenderScreenshotWithHostMemory() { // Ensure the copy is fully completed before saving the screenshot scheduler.Finish(); + // QImage::Format_RGB32 expects BGRA byte order. If the swapchain format is RGBA, + // swap R and B channels so the screenshot colors are correct. + if (main_present_window.GetSurfaceFormat() == vk::Format::eR8G8B8A8Unorm) { + u8* pixels = static_cast(settings.screenshot_bits); + for (u32 i = 0; i < width * height; i++) { + std::swap(pixels[i * 4 + 0], pixels[i * 4 + 2]); + } + } + // Image data has been copied directly to host memory device.destroyFramebuffer(frame.framebuffer); device.destroyImageView(frame.image_view); diff --git a/src/video_core/renderer_vulkan/vk_present_window.h b/src/video_core/renderer_vulkan/vk_present_window.h index 012dbf81b..769ec02a1 100644 --- a/src/video_core/renderer_vulkan/vk_present_window.h +++ b/src/video_core/renderer_vulkan/vk_present_window.h @@ -63,6 +63,10 @@ public: return swapchain.GetImageCount(); } + vk::Format GetSurfaceFormat() const noexcept { + return swapchain.GetSurfaceFormat().format; + } + private: void PresentThread(std::stop_token token);