diff --git a/src/core/host_display.cpp b/src/core/host_display.cpp index 740f33add..5a37f765d 100644 --- a/src/core/host_display.cpp +++ b/src/core/host_display.cpp @@ -1,4 +1,5 @@ #include "host_display.h" +#include "common/align.h" #include "common/assert.h" #include "common/file_system.h" #include "common/log.h" @@ -277,9 +278,93 @@ std::tuple HostDisplay::ConvertWindowCoordinatesToDisplayCoordinat return std::make_tuple(display_x, display_y); } +static bool ConvertTextureDataToRGBA8(u32 width, u32 height, std::vector& texture_data, u32& texture_data_stride, + HostDisplayPixelFormat format) +{ + switch (format) + { + case HostDisplayPixelFormat::BGRA8: + { + for (u32 y = 0; y < height; y++) + { + u32* pixels = reinterpret_cast(reinterpret_cast(texture_data.data()) + (y * texture_data_stride)); + for (u32 x = 0; x < width; x++) + pixels[x] = (pixels[x] & 0xFF00FF00) | ((pixels[x] & 0xFF) << 16) | ((pixels[x] >> 16) & 0xFF); + } + + return true; + } + + case HostDisplayPixelFormat::RGBA8: + return true; + + case HostDisplayPixelFormat::RGB565: + { + std::vector temp(width * height); + + for (u32 y = 0; y < height; y++) + { + const u8* pixels_in = reinterpret_cast(texture_data.data()) + (y * texture_data_stride); + u32* pixels_out = &temp[y * width]; + + for (u32 x = 0; x < width; x++) + { + // RGB565 -> RGBA8 + u16 pixel_in; + std::memcpy(&pixel_in, pixels_in, sizeof(u16)); + pixels_in += sizeof(u16); + const u8 r5 = Truncate8(pixel_in >> 11); + const u8 g6 = Truncate8((pixel_in >> 5) & 0x3F); + const u8 b5 = Truncate8(pixel_in & 0x1F); + *(pixels_out++) = ZeroExtend32((r5 << 3) | (r5 & 7)) | (ZeroExtend32((g6 << 2) | (g6 & 3)) << 8) | + (ZeroExtend32((b5 << 3) | (b5 & 7)) << 16) | (0xFF000000u); + } + } + + texture_data = std::move(temp); + texture_data_stride = sizeof(u32) * width; + return true; + } + + case HostDisplayPixelFormat::RGBA5551: + { + std::vector temp(width * height); + + for (u32 y = 0; y < height; y++) + { + const u8* pixels_in = reinterpret_cast(texture_data.data()) + (y * texture_data_stride); + u32* pixels_out = &temp[y * width]; + + for (u32 x = 0; x < width; x++) + { + // RGBA5551 -> RGBA8 + u16 pixel_in; + std::memcpy(&pixel_in, pixels_in, sizeof(u16)); + pixels_in += sizeof(u16); + const u8 a1 = Truncate8(pixel_in >> 15); + const u8 r5 = Truncate8((pixel_in >> 10) & 0x1F); + const u8 g6 = Truncate8((pixel_in >> 5) & 0x1F); + const u8 b5 = Truncate8(pixel_in & 0x1F); + *(pixels_out++) = ZeroExtend32((r5 << 3) | (r5 & 7)) | (ZeroExtend32((g6 << 3) | (g6 & 7)) << 8) | + (ZeroExtend32((b5 << 3) | (b5 & 7)) << 16) | (a1 ? 0xFF000000u : 0u); + } + } + + texture_data = std::move(temp); + texture_data_stride = sizeof(u32) * width; + return true; + } + + default: + Log_ErrorPrintf("Unknown pixel format %u", static_cast(format)); + return false; + } +} + static bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string filename, FileSystem::ManagedCFilePtr fp, bool clear_alpha, bool flip_y, u32 resize_width, u32 resize_height, - std::vector texture_data, u32 texture_data_stride) + std::vector texture_data, u32 texture_data_stride, + HostDisplayPixelFormat texture_format) { const char* extension = std::strrchr(filename.c_str(), '.'); @@ -289,6 +374,9 @@ static bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string fil return false; } + if (!ConvertTextureDataToRGBA8(width, height, texture_data, texture_data_stride, texture_format)) + return false; + if (clear_alpha) { for (u32& pixel : texture_data) @@ -359,13 +447,13 @@ static bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string fil } bool HostDisplay::WriteTextureToFile(const void* texture_handle, u32 x, u32 y, u32 width, u32 height, - std::string filename, bool clear_alpha /* = true */, bool flip_y /* = false */, - u32 resize_width /* = 0 */, u32 resize_height /* = 0 */, + HostDisplayPixelFormat format, std::string filename, bool clear_alpha /* = true */, + bool flip_y /* = false */, u32 resize_width /* = 0 */, u32 resize_height /* = 0 */, bool compress_on_thread /* = false */) { std::vector texture_data(width * height); - u32 texture_data_stride = sizeof(u32) * width; - if (!DownloadTexture(texture_handle, x, y, width, height, texture_data.data(), texture_data_stride)) + u32 texture_data_stride = Common::AlignUpPow2(GetDisplayPixelFormatSize(format) * width, 4); + if (!DownloadTexture(texture_handle, format, x, y, width, height, texture_data.data(), texture_data_stride)) { Log_ErrorPrintf("Texture download failed"); return false; @@ -381,12 +469,13 @@ bool HostDisplay::WriteTextureToFile(const void* texture_handle, u32 x, u32 y, u if (!compress_on_thread) { return CompressAndWriteTextureToFile(width, height, std::move(filename), std::move(fp), clear_alpha, flip_y, - resize_width, resize_height, std::move(texture_data), texture_data_stride); + resize_width, resize_height, std::move(texture_data), texture_data_stride, + format); } std::thread compress_thread(CompressAndWriteTextureToFile, width, height, std::move(filename), std::move(fp), clear_alpha, flip_y, resize_width, resize_height, std::move(texture_data), - texture_data_stride); + texture_data_stride, format); compress_thread.detach(); return true; } @@ -394,7 +483,7 @@ bool HostDisplay::WriteTextureToFile(const void* texture_handle, u32 x, u32 y, u bool HostDisplay::WriteDisplayTextureToFile(std::string filename, bool full_resolution /* = true */, bool apply_aspect_ratio /* = true */, bool compress_on_thread /* = false */) { - if (!m_display_texture_handle || m_display_texture_format != HostDisplayPixelFormat::RGBA8) + if (!m_display_texture_handle) return false; apply_aspect_ratio = (m_display_aspect_ratio > 0) ? apply_aspect_ratio : false; @@ -454,14 +543,14 @@ bool HostDisplay::WriteDisplayTextureToFile(std::string filename, bool full_reso } return WriteTextureToFile(m_display_texture_handle, m_display_texture_view_x, read_y, m_display_texture_view_width, - read_height, std::move(filename), true, flip_y, static_cast(resize_width), - static_cast(resize_height), compress_on_thread); + read_height, m_display_texture_format, std::move(filename), true, flip_y, + static_cast(resize_width), static_cast(resize_height), compress_on_thread); } bool HostDisplay::WriteDisplayTextureToBuffer(std::vector* buffer, u32 resize_width /* = 0 */, u32 resize_height /* = 0 */, bool clear_alpha /* = true */) { - if (!m_display_texture_handle || m_display_texture_format != HostDisplayPixelFormat::RGBA8) + if (!m_display_texture_handle) return false; const bool flip_y = (m_display_texture_view_height < 0); @@ -478,14 +567,17 @@ bool HostDisplay::WriteDisplayTextureToBuffer(std::vector* buffer, u32 resi u32 width = static_cast(read_width); u32 height = static_cast(read_height); std::vector texture_data(width * height); - u32 texture_data_stride = sizeof(u32) * width; - if (!DownloadTexture(m_display_texture_handle, read_x, read_y, width, height, texture_data.data(), - texture_data_stride)) + u32 texture_data_stride = Common::AlignUpPow2(GetDisplayPixelFormatSize(m_display_texture_format) * width, 4); + if (!DownloadTexture(m_display_texture_handle, m_display_texture_format, read_x, read_y, width, height, + texture_data.data(), texture_data_stride)) { Log_ErrorPrintf("Failed to download texture from GPU."); return false; } + if (!ConvertTextureDataToRGBA8(width, height, texture_data, texture_data_stride, m_display_texture_format)) + return false; + if (clear_alpha) { for (u32& pixel : texture_data) diff --git a/src/core/host_display.h b/src/core/host_display.h index 47b8ad372..a0f0ff18c 100644 --- a/src/core/host_display.h +++ b/src/core/host_display.h @@ -95,8 +95,8 @@ public: virtual void UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* data, u32 data_stride) = 0; - virtual bool DownloadTexture(const void* texture_handle, u32 x, u32 y, u32 width, u32 height, void* out_data, - u32 out_data_stride) = 0; + virtual bool DownloadTexture(const void* texture_handle, HostDisplayPixelFormat texture_format, u32 x, u32 y, + u32 width, u32 height, void* out_data, u32 out_data_stride) = 0; /// Returns false if the window was completely occluded. virtual bool Render() = 0; @@ -194,8 +194,9 @@ public: s32 window_height, s32 top_margin) const; /// Helper function to save texture data to a PNG. If flip_y is set, the image will be flipped aka OpenGL. - bool WriteTextureToFile(const void* texture_handle, u32 x, u32 y, u32 width, u32 height, std::string filename, - bool clear_alpha = true, bool flip_y = false, u32 resize_width = 0, u32 resize_height = 0, + bool WriteTextureToFile(const void* texture_handle, u32 x, u32 y, u32 width, u32 height, + HostDisplayPixelFormat format, std::string filename, bool clear_alpha = true, + bool flip_y = false, u32 resize_width = 0, u32 resize_height = 0, bool compress_on_thread = false); /// Helper function to save current display texture to PNG. diff --git a/src/duckstation-libretro/libretro_host_display.cpp b/src/duckstation-libretro/libretro_host_display.cpp index bf1108aff..0adbb61fe 100644 --- a/src/duckstation-libretro/libretro_host_display.cpp +++ b/src/duckstation-libretro/libretro_host_display.cpp @@ -156,8 +156,8 @@ void LibretroHostDisplay::UpdateTexture(HostDisplayTexture* texture, u32 x, u32 { } -bool LibretroHostDisplay::DownloadTexture(const void* texture_handle, u32 x, u32 y, u32 width, u32 height, - void* out_data, u32 out_data_stride) +bool LibretroHostDisplay::DownloadTexture(const void* texture_handle, HostDisplayPixelFormat texture_format, u32 x, + u32 y, u32 width, u32 height, void* out_data, u32 out_data_stride) { return false; } diff --git a/src/duckstation-libretro/libretro_host_display.h b/src/duckstation-libretro/libretro_host_display.h index 1198d182d..fa0def33e 100644 --- a/src/duckstation-libretro/libretro_host_display.h +++ b/src/duckstation-libretro/libretro_host_display.h @@ -38,8 +38,8 @@ public: bool dynamic) override; void UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* data, u32 data_stride) override; - bool DownloadTexture(const void* texture_handle, u32 x, u32 y, u32 width, u32 height, void* out_data, - u32 out_data_stride) override; + bool DownloadTexture(const void* texture_handle, HostDisplayPixelFormat texture_format, u32 x, u32 y, u32 width, + u32 height, void* out_data, u32 out_data_stride) override; void SetVSync(bool enabled) override; diff --git a/src/frontend-common/d3d11_host_display.cpp b/src/frontend-common/d3d11_host_display.cpp index be183dd70..4304e7455 100644 --- a/src/frontend-common/d3d11_host_display.cpp +++ b/src/frontend-common/d3d11_host_display.cpp @@ -155,8 +155,8 @@ void D3D11HostDisplay::UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, } } -bool D3D11HostDisplay::DownloadTexture(const void* texture_handle, u32 x, u32 y, u32 width, u32 height, void* out_data, - u32 out_data_stride) +bool D3D11HostDisplay::DownloadTexture(const void* texture_handle, HostDisplayPixelFormat texture_format, u32 x, u32 y, + u32 width, u32 height, void* out_data, u32 out_data_stride) { ID3D11ShaderResourceView* srv = const_cast(static_cast(texture_handle)); @@ -169,8 +169,17 @@ bool D3D11HostDisplay::DownloadTexture(const void* texture_handle, u32 x, u32 y, return false; m_readback_staging_texture.CopyFromTexture(m_context.Get(), srv_resource.Get(), 0, x, y, 0, 0, width, height); - return m_readback_staging_texture.ReadPixels(m_context.Get(), 0, 0, width, height, out_data_stride / sizeof(u32), - static_cast(out_data)); + + if (srv_desc.Format == DXGI_FORMAT_B5G6R5_UNORM || srv_desc.Format == DXGI_FORMAT_B5G5R5A1_UNORM) + { + return m_readback_staging_texture.ReadPixels(m_context.Get(), 0, 0, width, height, + out_data_stride / sizeof(u16), static_cast(out_data)); + } + else + { + return m_readback_staging_texture.ReadPixels(m_context.Get(), 0, 0, width, height, + out_data_stride / sizeof(u32), static_cast(out_data)); + } } static constexpr std::array(HostDisplayPixelFormat::Count)> diff --git a/src/frontend-common/d3d11_host_display.h b/src/frontend-common/d3d11_host_display.h index 5e48112e8..673018d31 100644 --- a/src/frontend-common/d3d11_host_display.h +++ b/src/frontend-common/d3d11_host_display.h @@ -55,8 +55,8 @@ public: u32 initial_data_stride, bool dynamic) override; void UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* texture_data, u32 texture_data_stride) override; - bool DownloadTexture(const void* texture_handle, u32 x, u32 y, u32 width, u32 height, void* out_data, - u32 out_data_stride) override; + bool DownloadTexture(const void* texture_handle, HostDisplayPixelFormat texture_format, u32 x, u32 y, u32 width, + u32 height, void* out_data, u32 out_data_stride) override; bool SupportsDisplayPixelFormat(HostDisplayPixelFormat format) const override; bool BeginSetDisplayPixels(HostDisplayPixelFormat format, u32 width, u32 height, void** out_buffer, u32* out_pitch) override; diff --git a/src/frontend-common/opengl_host_display.cpp b/src/frontend-common/opengl_host_display.cpp index b8eabedb2..792d50710 100644 --- a/src/frontend-common/opengl_host_display.cpp +++ b/src/frontend-common/opengl_host_display.cpp @@ -83,46 +83,6 @@ std::unique_ptr OpenGLHostDisplay::CreateTexture(u32 width, return OpenGLHostDisplayTexture::Create(width, height, initial_data, initial_data_stride); } -void OpenGLHostDisplay::UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, - const void* texture_data, u32 texture_data_stride) -{ - OpenGLHostDisplayTexture* tex = static_cast(texture); - Assert((texture_data_stride % sizeof(u32)) == 0); - - GLint old_texture_binding = 0, old_alignment = 0, old_row_length = 0; - glGetIntegerv(GL_TEXTURE_BINDING_2D, &old_texture_binding); - glGetIntegerv(GL_UNPACK_ALIGNMENT, &old_alignment); - glGetIntegerv(GL_UNPACK_ROW_LENGTH, &old_row_length); - - glBindTexture(GL_TEXTURE_2D, tex->GetGLID()); - glPixelStorei(GL_UNPACK_ALIGNMENT, 4); - glPixelStorei(GL_UNPACK_ROW_LENGTH, texture_data_stride / sizeof(u32)); - - glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, texture_data); - - glPixelStorei(GL_UNPACK_ALIGNMENT, old_alignment); - glPixelStorei(GL_UNPACK_ROW_LENGTH, old_row_length); - glBindTexture(GL_TEXTURE_2D, old_texture_binding); -} - -bool OpenGLHostDisplay::DownloadTexture(const void* texture_handle, u32 x, u32 y, u32 width, u32 height, void* out_data, - u32 out_data_stride) -{ - GLint old_alignment = 0, old_row_length = 0; - glGetIntegerv(GL_PACK_ALIGNMENT, &old_alignment); - glGetIntegerv(GL_PACK_ROW_LENGTH, &old_row_length); - glPixelStorei(GL_PACK_ALIGNMENT, sizeof(u32)); - glPixelStorei(GL_PACK_ROW_LENGTH, out_data_stride / sizeof(u32)); - - const GLuint texture = static_cast(reinterpret_cast(texture_handle)); - GL::Texture::GetTextureSubImage(texture, 0, x, y, 0, width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE, - height * out_data_stride, out_data); - - glPixelStorei(GL_PACK_ALIGNMENT, old_alignment); - glPixelStorei(GL_PACK_ROW_LENGTH, old_row_length); - return true; -} - static constexpr std::array, static_cast(HostDisplayPixelFormat::Count)> s_display_pixel_format_mapping = {{ {}, // Unknown @@ -132,6 +92,65 @@ static constexpr std::array, static_cast {GL_RGB5_A1, GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV} // RGBA5551 }}; +void OpenGLHostDisplay::UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, + const void* texture_data, u32 texture_data_stride) +{ + OpenGLHostDisplayTexture* tex = static_cast(texture); + const auto [gl_internal_format, gl_format, gl_type] = + s_display_pixel_format_mapping[static_cast(texture->GetFormat())]; + GLint alignment; + if (texture_data_stride & 1) + alignment = 1; + else if (texture_data_stride & 2) + alignment = 2; + else + alignment = 4; + + GLint old_texture_binding = 0, old_alignment = 0, old_row_length = 0; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &old_texture_binding); + glGetIntegerv(GL_UNPACK_ALIGNMENT, &old_alignment); + glGetIntegerv(GL_UNPACK_ROW_LENGTH, &old_row_length); + + glBindTexture(GL_TEXTURE_2D, tex->GetGLID()); + glPixelStorei(GL_UNPACK_ALIGNMENT, alignment); + glPixelStorei(GL_UNPACK_ROW_LENGTH, texture_data_stride / GetDisplayPixelFormatSize(texture->GetFormat())); + + glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, gl_format, gl_type, texture_data); + + glPixelStorei(GL_UNPACK_ALIGNMENT, old_alignment); + glPixelStorei(GL_UNPACK_ROW_LENGTH, old_row_length); + glBindTexture(GL_TEXTURE_2D, old_texture_binding); +} + +bool OpenGLHostDisplay::DownloadTexture(const void* texture_handle, HostDisplayPixelFormat texture_format, u32 x, u32 y, + u32 width, u32 height, void* out_data, u32 out_data_stride) +{ + GLint alignment; + if (out_data_stride & 1) + alignment = 1; + else if (out_data_stride & 2) + alignment = 2; + else + alignment = 4; + + GLint old_alignment = 0, old_row_length = 0; + glGetIntegerv(GL_PACK_ALIGNMENT, &old_alignment); + glGetIntegerv(GL_PACK_ROW_LENGTH, &old_row_length); + glPixelStorei(GL_PACK_ALIGNMENT, alignment); + glPixelStorei(GL_PACK_ROW_LENGTH, out_data_stride / GetDisplayPixelFormatSize(texture_format)); + + const GLuint texture = static_cast(reinterpret_cast(texture_handle)); + const auto [gl_internal_format, gl_format, gl_type] = + s_display_pixel_format_mapping[static_cast(texture_format)]; + + GL::Texture::GetTextureSubImage(texture, 0, x, y, 0, width, height, 1, gl_format, gl_type, height * out_data_stride, + out_data); + + glPixelStorei(GL_PACK_ALIGNMENT, old_alignment); + glPixelStorei(GL_PACK_ROW_LENGTH, old_row_length); + return true; +} + bool OpenGLHostDisplay::SupportsDisplayPixelFormat(HostDisplayPixelFormat format) const { return (std::get<0>(s_display_pixel_format_mapping[static_cast(format)]) != static_cast(0)); diff --git a/src/frontend-common/opengl_host_display.h b/src/frontend-common/opengl_host_display.h index c943b273c..b9b10fc0e 100644 --- a/src/frontend-common/opengl_host_display.h +++ b/src/frontend-common/opengl_host_display.h @@ -55,8 +55,8 @@ public: u32 initial_data_stride, bool dynamic) override; void UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* texture_data, u32 texture_data_stride) override; - bool DownloadTexture(const void* texture_handle, u32 x, u32 y, u32 width, u32 height, void* out_data, - u32 out_data_stride) override; + bool DownloadTexture(const void* texture_handle, HostDisplayPixelFormat texture_format, u32 x, u32 y, u32 width, + u32 height, void* out_data, u32 out_data_stride) override; bool SupportsDisplayPixelFormat(HostDisplayPixelFormat format) const override; bool BeginSetDisplayPixels(HostDisplayPixelFormat format, u32 width, u32 height, void** out_buffer, u32* out_pitch) override; diff --git a/src/frontend-common/vulkan_host_display.cpp b/src/frontend-common/vulkan_host_display.cpp index c4665acad..947b9ad37 100644 --- a/src/frontend-common/vulkan_host_display.cpp +++ b/src/frontend-common/vulkan_host_display.cpp @@ -230,14 +230,13 @@ void VulkanHostDisplay::UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, staging_texture->CopyToTexture(0, 0, vk_texture->GetTexture(), x, y, 0, 0, width, height); } -bool VulkanHostDisplay::DownloadTexture(const void* texture_handle, u32 x, u32 y, u32 width, u32 height, void* out_data, - u32 out_data_stride) +bool VulkanHostDisplay::DownloadTexture(const void* texture_handle, HostDisplayPixelFormat texture_format, u32 x, u32 y, + u32 width, u32 height, void* out_data, u32 out_data_stride) { Vulkan::Texture* texture = static_cast(const_cast(texture_handle)); if ((m_readback_staging_texture.GetWidth() < width || m_readback_staging_texture.GetHeight() < height) && - !m_readback_staging_texture.Create(Vulkan::StagingBuffer::Type::Readback, VK_FORMAT_R8G8B8A8_UNORM, width, - height)) + !m_readback_staging_texture.Create(Vulkan::StagingBuffer::Type::Readback, texture->GetFormat(), width, height)) { return false; } diff --git a/src/frontend-common/vulkan_host_display.h b/src/frontend-common/vulkan_host_display.h index 3ca21c232..31346f242 100644 --- a/src/frontend-common/vulkan_host_display.h +++ b/src/frontend-common/vulkan_host_display.h @@ -52,8 +52,8 @@ public: u32 initial_data_stride, bool dynamic) override; void UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* texture_data, u32 texture_data_stride) override; - bool DownloadTexture(const void* texture_handle, u32 x, u32 y, u32 width, u32 height, void* out_data, - u32 out_data_stride) override; + bool DownloadTexture(const void* texture_handle, HostDisplayPixelFormat texture_format, u32 x, u32 y, u32 width, + u32 height, void* out_data, u32 out_data_stride) override; bool SupportsDisplayPixelFormat(HostDisplayPixelFormat format) const override; bool BeginSetDisplayPixels(HostDisplayPixelFormat format, u32 width, u32 height, void** out_buffer,