From 24dfd30839377f29d01e5034fc0bf3378df6e222 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sun, 24 Nov 2024 16:36:28 +1000 Subject: [PATCH] Image: Refactor to a more generic class --- src/core/fullscreen_ui.cpp | 5 +- src/core/game_list.cpp | 2 +- src/core/gpu.cpp | 131 +++---- src/core/gpu.h | 4 +- src/core/gpu_hw_texture_cache.cpp | 59 ++- src/core/gpu_hw_texture_cache.h | 4 +- src/core/system.cpp | 51 ++- src/core/system.h | 2 +- src/util/gpu_device.cpp | 22 ++ src/util/gpu_device.h | 4 + src/util/gpu_texture.cpp | 156 +++----- src/util/gpu_texture.h | 9 +- src/util/image.cpp | 517 ++++++++++++++++++++------ src/util/image.h | 204 ++++------ src/util/imgui_fullscreen.cpp | 41 +- src/util/imgui_fullscreen.h | 3 +- src/util/imgui_manager.cpp | 2 +- src/util/postprocessing_shader_fx.cpp | 2 +- 18 files changed, 698 insertions(+), 520 deletions(-) diff --git a/src/core/fullscreen_ui.cpp b/src/core/fullscreen_ui.cpp index 16e189aac..cdc45ab2d 100644 --- a/src/core/fullscreen_ui.cpp +++ b/src/core/fullscreen_ui.cpp @@ -112,7 +112,6 @@ using ImGuiFullscreen::BeginNavBar; using ImGuiFullscreen::CenterImage; using ImGuiFullscreen::CloseChoiceDialog; using ImGuiFullscreen::CloseFileSelector; -using ImGuiFullscreen::CreateTextureFromImage; using ImGuiFullscreen::DefaultActiveButton; using ImGuiFullscreen::DrawShadowedText; using ImGuiFullscreen::EndFullscreenColumns; @@ -5966,7 +5965,7 @@ bool FullscreenUI::InitializeSaveStateListEntryFromPath(SaveStateListEntry* li, li->path = std::move(path); li->global = global; if (ssi->screenshot.IsValid()) - li->preview_texture = CreateTextureFromImage(ssi->screenshot); + li->preview_texture = g_gpu_device->FetchAndUploadTextureImage(ssi->screenshot); return true; } @@ -5994,7 +5993,7 @@ u32 FullscreenUI::PopulateSaveStateListEntries(const std::string& title, const s li.title = FSUI_STR("Undo Load State"); li.summary = FSUI_STR("Restores the state of the system prior to the last state loaded."); if (ssi->screenshot.IsValid()) - li.preview_texture = CreateTextureFromImage(ssi->screenshot); + li.preview_texture = g_gpu_device->FetchAndUploadTextureImage(ssi->screenshot); s_save_state_selector_slots.push_back(std::move(li)); } } diff --git a/src/core/game_list.cpp b/src/core/game_list.cpp index 3db8ec8ea..0ed468509 100644 --- a/src/core/game_list.cpp +++ b/src/core/game_list.cpp @@ -1778,7 +1778,7 @@ std::string GameList::GetGameIconPath(std::string_view serial, std::string_view INFO_LOG("Extracting memory card icon from {} ({}) to {}", fi.filename, Path::GetFileTitle(memcard_path), Path::GetFileTitle(ret)); - RGBA8Image image(MemoryCardImage::ICON_WIDTH, MemoryCardImage::ICON_HEIGHT); + Image image(MemoryCardImage::ICON_WIDTH, MemoryCardImage::ICON_HEIGHT, ImageFormat::RGBA8); std::memcpy(image.GetPixels(), &fi.icon_frames.front().pixels, MemoryCardImage::ICON_WIDTH * MemoryCardImage::ICON_HEIGHT * sizeof(u32)); serial_entry->icon_was_extracted = image.SaveToFile(ret.c_str()); diff --git a/src/core/gpu.cpp b/src/core/gpu.cpp index 22946bd1b..284218a45 100644 --- a/src/core/gpu.cpp +++ b/src/core/gpu.cpp @@ -74,10 +74,8 @@ static u32 s_active_gpu_cycles_frames = 0; static constexpr GPUTexture::Format DISPLAY_INTERNAL_POSTFX_FORMAT = GPUTexture::Format::RGBA8; -static bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string filename, FileSystem::ManagedCFilePtr fp, - u8 quality, bool clear_alpha, bool flip_y, std::vector texture_data, - u32 texture_data_stride, GPUTexture::Format texture_format, - std::string osd_key); +static bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string path, FileSystem::ManagedCFilePtr fp, + u8 quality, bool clear_alpha, bool flip_y, Image image, std::string osd_key); GPU::GPU() { @@ -2423,48 +2421,38 @@ void GPU::CalculateDrawRect(s32 window_width, s32 window_height, bool apply_rota GSVector4(left_padding, top_padding, left_padding + display_width * scale, top_padding + display_height * scale)); } -bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string filename, FileSystem::ManagedCFilePtr fp, - u8 quality, bool clear_alpha, bool flip_y, std::vector texture_data, - u32 texture_data_stride, GPUTexture::Format texture_format, std::string osd_key) +bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string path, FileSystem::ManagedCFilePtr fp, u8 quality, + bool clear_alpha, bool flip_y, Image image, std::string osd_key) { - bool result; + Error error; - const char* extension = std::strrchr(filename.c_str(), '.'); - if (extension) + if (flip_y) + image.FlipY(); + + if (image.GetFormat() != ImageFormat::RGBA8) { - if (GPUTexture::ConvertTextureDataToRGBA8(width, height, texture_data, texture_data_stride, texture_format)) + std::optional convert_image = image.ConvertToRGBA8(&error); + if (!convert_image.has_value()) { - if (clear_alpha) - { - for (u32& pixel : texture_data) - pixel |= 0xFF000000u; - } - - if (flip_y) - GPUTexture::FlipTextureDataRGBA8(width, height, reinterpret_cast(texture_data.data()), - texture_data_stride); - - Assert(texture_data_stride == sizeof(u32) * width); - RGBA8Image image(width, height, std::move(texture_data)); - if (image.SaveToFile(filename.c_str(), fp.get(), quality)) - { - result = true; - } - else - { - ERROR_LOG("Unknown extension in filename '{}' or save error: '{}'", filename, extension); - result = false; - } + ERROR_LOG("Failed to convert {} screenshot to RGBA8: {}", Image::GetFormatName(image.GetFormat()), + error.GetDescription()); + image.Invalidate(); } else { - result = false; + image = std::move(convert_image.value()); } } - else + + bool result = false; + if (image.IsValid()) { - ERROR_LOG("Unable to determine file extension for '{}'", filename); - result = false; + if (clear_alpha) + image.SetAllPixelsOpaque(); + + result = image.SaveToFile(path.c_str(), fp.get(), quality, &error); + if (!result) + ERROR_LOG("Failed to save screenshot to '{}': '{}'", Path::GetFileName(path), error.GetDescription()); } if (!osd_key.empty()) @@ -2472,7 +2460,7 @@ bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string filename, Host::AddIconOSDMessage(std::move(osd_key), ICON_EMOJI_CAMERA, fmt::format(result ? TRANSLATE_FS("GPU", "Saved screenshot to '{}'.") : TRANSLATE_FS("GPU", "Failed to save screenshot to '{}'."), - Path::GetFileName(filename), + Path::GetFileName(path), result ? Host::OSD_INFO_DURATION : Host::OSD_ERROR_DURATION)); } @@ -2488,17 +2476,16 @@ bool GPU::WriteDisplayTextureToFile(std::string filename) const u32 read_y = static_cast(m_display_texture_view_y); const u32 read_width = static_cast(m_display_texture_view_width); const u32 read_height = static_cast(m_display_texture_view_height); + const ImageFormat read_format = GPUTexture::GetImageFormatForTextureFormat(m_display_texture->GetFormat()); + if (read_format == ImageFormat::None) + return false; - const u32 texture_data_stride = - Common::AlignUpPow2(GPUTexture::GetPixelSize(m_display_texture->GetFormat()) * read_width, 4); - std::vector texture_data((texture_data_stride * read_height) / sizeof(u32)); - + Image image(read_width, read_height, read_format); std::unique_ptr dltex; if (g_gpu_device->GetFeatures().memory_import) { - dltex = - g_gpu_device->CreateDownloadTexture(read_width, read_height, m_display_texture->GetFormat(), texture_data.data(), - texture_data.size() * sizeof(u32), texture_data_stride); + dltex = g_gpu_device->CreateDownloadTexture(read_width, read_height, m_display_texture->GetFormat(), + image.GetPixels(), image.GetStorageSize(), image.GetPitch()); } if (!dltex) { @@ -2511,7 +2498,7 @@ bool GPU::WriteDisplayTextureToFile(std::string filename) } dltex->CopyFromTexture(0, 0, m_display_texture, read_x, read_y, read_width, read_height, 0, 0, !dltex->IsImported()); - if (!dltex->ReadTexels(0, 0, read_width, read_height, texture_data.data(), texture_data_stride)) + if (!dltex->ReadTexels(0, 0, read_width, read_height, image.GetPixels(), image.GetPitch())) { RestoreDeviceContext(); return false; @@ -2530,17 +2517,19 @@ bool GPU::WriteDisplayTextureToFile(std::string filename) constexpr bool clear_alpha = true; const bool flip_y = g_gpu_device->UsesLowerLeftOrigin(); - return CompressAndWriteTextureToFile( - read_width, read_height, std::move(filename), std::move(fp), g_settings.display_screenshot_quality, clear_alpha, - flip_y, std::move(texture_data), texture_data_stride, m_display_texture->GetFormat(), std::string()); + return CompressAndWriteTextureToFile(read_width, read_height, std::move(filename), std::move(fp), + g_settings.display_screenshot_quality, clear_alpha, flip_y, std::move(image), + std::string()); } bool GPU::RenderScreenshotToBuffer(u32 width, u32 height, const GSVector4i display_rect, const GSVector4i draw_rect, - bool postfx, std::vector* out_pixels, u32* out_stride, - GPUTexture::Format* out_format) + bool postfx, Image* out_image) { const GPUTexture::Format hdformat = g_gpu_device->HasMainSwapChain() ? g_gpu_device->GetMainSwapChain()->GetFormat() : GPUTexture::Format::RGBA8; + const ImageFormat image_format = GPUTexture::GetImageFormatForTextureFormat(hdformat); + if (image_format == ImageFormat::None) + return false; auto render_texture = g_gpu_device->FetchAutoRecycleTexture(width, height, 1, 1, 1, GPUTexture::Type::RenderTarget, hdformat, GPUTexture::Flags::None); @@ -2552,34 +2541,33 @@ bool GPU::RenderScreenshotToBuffer(u32 width, u32 height, const GSVector4i displ // TODO: this should use copy shader instead. RenderDisplay(render_texture.get(), display_rect, draw_rect, postfx); - const u32 stride = Common::AlignUpPow2(GPUTexture::GetPixelSize(hdformat) * width, sizeof(u32)); - out_pixels->resize((height * stride) / sizeof(u32)); + Image image(width, height, image_format); + Error error; std::unique_ptr dltex; if (g_gpu_device->GetFeatures().memory_import) { - dltex = g_gpu_device->CreateDownloadTexture(width, height, hdformat, out_pixels->data(), - out_pixels->size() * sizeof(u32), stride); + dltex = g_gpu_device->CreateDownloadTexture(width, height, hdformat, image.GetPixels(), image.GetStorageSize(), + image.GetPitch(), &error); } if (!dltex) { - if (!(dltex = g_gpu_device->CreateDownloadTexture(width, height, hdformat))) + if (!(dltex = g_gpu_device->CreateDownloadTexture(width, height, hdformat, &error))) { - ERROR_LOG("Failed to create {}x{} download texture", width, height); + ERROR_LOG("Failed to create {}x{} download texture: {}", width, height, error.GetDescription()); return false; } } dltex->CopyFromTexture(0, 0, render_texture.get(), 0, 0, width, height, 0, 0, false); - if (!dltex->ReadTexels(0, 0, width, height, out_pixels->data(), stride)) + if (!dltex->ReadTexels(0, 0, width, height, image.GetPixels(), image.GetPitch())) { RestoreDeviceContext(); return false; } - *out_stride = stride; - *out_format = hdformat; RestoreDeviceContext(); + *out_image = std::move(image); return true; } @@ -2656,11 +2644,8 @@ bool GPU::RenderScreenshotToFile(std::string path, DisplayScreenshotMode mode, u if (width == 0 || height == 0) return false; - std::vector pixels; - u32 pixels_stride; - GPUTexture::Format pixels_format; - if (!RenderScreenshotToBuffer(width, height, display_rect, draw_rect, !internal_resolution, &pixels, &pixels_stride, - &pixels_format)) + Image image; + if (!RenderScreenshotToBuffer(width, height, display_rect, draw_rect, !internal_resolution, &image)) { ERROR_LOG("Failed to render {}x{} screenshot", width, height); return false; @@ -2687,10 +2672,10 @@ bool GPU::RenderScreenshotToFile(std::string path, DisplayScreenshotMode mode, u if (compress_on_thread) { System::QueueTaskOnThread([width, height, path = std::move(path), fp = fp.release(), quality, - flip_y = g_gpu_device->UsesLowerLeftOrigin(), pixels = std::move(pixels), pixels_stride, - pixels_format, osd_key = std::move(osd_key)]() mutable { + flip_y = g_gpu_device->UsesLowerLeftOrigin(), image = std::move(image), + osd_key = std::move(osd_key)]() mutable { CompressAndWriteTextureToFile(width, height, std::move(path), FileSystem::ManagedCFilePtr(fp), quality, true, - flip_y, std::move(pixels), pixels_stride, pixels_format, std::move(osd_key)); + flip_y, std::move(image), std::move(osd_key)); System::RemoveSelfFromTaskThreads(); }); @@ -2699,8 +2684,7 @@ bool GPU::RenderScreenshotToFile(std::string path, DisplayScreenshotMode mode, u else { return CompressAndWriteTextureToFile(width, height, std::move(path), std::move(fp), quality, true, - g_gpu_device->UsesLowerLeftOrigin(), std::move(pixels), pixels_stride, - pixels_format, std::move(osd_key)); + g_gpu_device->UsesLowerLeftOrigin(), std::move(image), std::move(osd_key)); } } @@ -2726,20 +2710,23 @@ bool GPU::DumpVRAMToFile(const char* filename) bool GPU::DumpVRAMToFile(const char* filename, u32 width, u32 height, u32 stride, const void* buffer, bool remove_alpha) { - RGBA8Image image(width, height); + Image image(width, height, ImageFormat::RGBA8); const char* ptr_in = static_cast(buffer); for (u32 row = 0; row < height; row++) { const char* row_ptr_in = ptr_in; - u32* ptr_out = image.GetRowPixels(row); + u8* ptr_out = image.GetRowPixels(row); for (u32 col = 0; col < width; col++) { u16 src_col; std::memcpy(&src_col, row_ptr_in, sizeof(u16)); row_ptr_in += sizeof(u16); - *(ptr_out++) = VRAMRGBA5551ToRGBA8888(remove_alpha ? (src_col | u16(0x8000)) : src_col); + + const u32 pixel32 = VRAMRGBA5551ToRGBA8888(remove_alpha ? (src_col | u16(0x8000)) : src_col); + std::memcpy(ptr_out, &pixel32, sizeof(pixel32)); + ptr_out += sizeof(pixel32); } ptr_in += stride; diff --git a/src/core/gpu.h b/src/core/gpu.h index a98b20cbb..5c18c9f98 100644 --- a/src/core/gpu.h +++ b/src/core/gpu.h @@ -24,6 +24,7 @@ #include class Error; +class Image; class SmallStringBase; class StateWrapper; @@ -233,8 +234,7 @@ public: /// Renders the display, optionally with postprocessing to the specified image. bool RenderScreenshotToBuffer(u32 width, u32 height, const GSVector4i display_rect, const GSVector4i draw_rect, - bool postfx, std::vector* out_pixels, u32* out_stride, - GPUTexture::Format* out_format); + bool postfx, Image* out_image); /// Helper function to save screenshot to PNG. bool RenderScreenshotToFile(std::string path, DisplayScreenshotMode mode, u8 quality, bool compress_on_thread, diff --git a/src/core/gpu_hw_texture_cache.cpp b/src/core/gpu_hw_texture_cache.cpp index 0f6ecc124..3c06e6f3f 100644 --- a/src/core/gpu_hw_texture_cache.cpp +++ b/src/core/gpu_hw_texture_cache.cpp @@ -303,7 +303,7 @@ static void FindTextureReplacements(bool load_vram_write_replacements, bool load static void LoadTextureReplacementAliases(const ryml::ConstNodeRef& root, bool load_vram_write_replacement_aliases, bool load_texture_replacement_aliases); -static const TextureReplacementImage* GetTextureReplacementImage(const std::string& filename); +static const TextureReplacementImage* GetTextureReplacementImage(const std::string& path); static void PreloadReplacementTextures(); static void PurgeUnreferencedTexturesFromCache(); @@ -2493,28 +2493,23 @@ void GPUTextureCache::DumpVRAMWrite(u32 width, u32 height, const void* pixels) if (filename.empty() || FileSystem::FileExists(filename.c_str())) return; - RGBA8Image image; - image.SetSize(width, height); + Image image(width, height, ImageFormat::RGBA8); const u16* src_pixels = reinterpret_cast(pixels); for (u32 y = 0; y < height; y++) { + u8* row_ptr = image.GetPixels(); for (u32 x = 0; x < width; x++) { - image.SetPixel(x, y, VRAMRGBA5551ToRGBA8888(*src_pixels)); - src_pixels++; + const u32 pixel32 = VRAMRGBA5551ToRGBA8888(*(src_pixels++)); + std::memcpy(row_ptr, &pixel32, sizeof(pixel32)); + row_ptr += sizeof(pixel32); } } if (s_state.config.dump_vram_write_force_alpha_channel) - { - for (u32 y = 0; y < height; y++) - { - for (u32 x = 0; x < width; x++) - image.SetPixel(x, y, image.GetPixel(x, y) | 0xFF000000u); - } - } + image.SetAllPixelsOpaque(); INFO_LOG("Dumping {}x{} VRAM write to '{}'", width, height, Path::GetFileName(filename)); if (!image.SaveToFile(filename.c_str())) [[unlikely]] @@ -2599,12 +2594,13 @@ void GPUTextureCache::DumpTexture(TextureReplacementType type, u32 offset_x, u32 DEV_LOG("Dumping VRAM write {:016X} [{}x{}] at {}", src_hash, width, height, rect); - RGBA8Image image(width, height); - GPUTextureCache::DecodeTexture(mode, &g_vram[rect.top * VRAM_WIDTH + rect.left], palette_data, image.GetPixels(), - image.GetPitch(), width, height); + Image image(width, height, ImageFormat::RGBA8); + GPUTextureCache::DecodeTexture(mode, &g_vram[rect.top * VRAM_WIDTH + rect.left], palette_data, + reinterpret_cast(image.GetPixels()), image.GetPitch(), width, height); - u32* image_pixels = image.GetPixels(); - const u32* image_pixels_end = image.GetPixels() + (width * height); + // TODO: Vectorize this. + u32* image_pixels = reinterpret_cast(image.GetPixels()); + const u32* image_pixels_end = image_pixels + (width * height); if (s_state.config.dump_texture_force_alpha_channel) { for (u32* pixel = image_pixels; pixel != image_pixels_end; pixel++) @@ -2970,21 +2966,23 @@ void GPUTextureCache::LoadTextureReplacementAliases(const ryml::ConstNodeRef& ro s_state.game_id); } -const GPUTextureCache::TextureReplacementImage* GPUTextureCache::GetTextureReplacementImage(const std::string& filename) +const GPUTextureCache::TextureReplacementImage* GPUTextureCache::GetTextureReplacementImage(const std::string& path) { - auto it = s_state.replacement_image_cache.find(filename); + auto it = s_state.replacement_image_cache.find(path); if (it != s_state.replacement_image_cache.end()) return &it->second; - RGBA8Image image; - if (!image.LoadFromFile(filename.c_str())) + Image image; + Error error; + if (!image.LoadFromFile(path.c_str(), &error)) { - ERROR_LOG("Failed to load '{}'", Path::GetFileName(filename)); + ERROR_LOG("Failed to load '{}': {}", Path::GetFileName(path), error.GetDescription()); return nullptr; } - VERBOSE_LOG("Loaded '{}': {}x{}", Path::GetFileName(filename), image.GetWidth(), image.GetHeight()); - it = s_state.replacement_image_cache.emplace(filename, std::move(image)).first; + VERBOSE_LOG("Loaded '{}': {}x{} {}", Path::GetFileName(path), image.GetWidth(), image.GetHeight(), + Image::GetFormatName(image.GetFormat())); + it = s_state.replacement_image_cache.emplace(path, std::move(image)).first; return &it->second; } @@ -3206,14 +3204,14 @@ void GPUTextureCache::ReloadTextureReplacements(bool show_info) void GPUTextureCache::PurgeUnreferencedTexturesFromCache() { TextureCache old_map = std::move(s_state.replacement_image_cache); - s_state.replacement_image_cache = {}; + s_state.replacement_image_cache = TextureCache(); for (const auto& it : s_state.vram_replacements) { const auto it2 = old_map.find(it.second); if (it2 != old_map.end()) { - s_state.replacement_image_cache[it.second] = std::move(it2->second); + s_state.replacement_image_cache.emplace(it.second, std::move(it2->second)); old_map.erase(it2); } } @@ -3225,7 +3223,7 @@ void GPUTextureCache::PurgeUnreferencedTexturesFromCache() const auto it2 = old_map.find(it.second.second); if (it2 != old_map.end()) { - s_state.replacement_image_cache[it.second.second] = std::move(it2->second); + s_state.replacement_image_cache.emplace(it.second.second, std::move(it2->second)); old_map.erase(it2); } } @@ -3319,9 +3317,8 @@ void GPUTextureCache::ApplyTextureReplacements(SourceKey key, HashType tex_hash, for (const TextureReplacementSubImage& si : subimages) { - const auto temp_texture = g_gpu_device->FetchAutoRecycleTexture( - si.image.GetWidth(), si.image.GetHeight(), 1, 1, 1, GPUTexture::Type::Texture, REPLACEMENT_TEXTURE_FORMAT, - GPUTexture::Flags::None, si.image.GetPixels(), si.image.GetPitch()); + std::unique_ptr temp_texture = + g_gpu_device->FetchAndUploadTextureImage(si.image, GPUTexture::Flags::None); if (!temp_texture) continue; @@ -3334,6 +3331,8 @@ void GPUTextureCache::ApplyTextureReplacements(SourceKey key, HashType tex_hash, g_gpu_device->SetPipeline(si.invert_alpha ? s_state.replacement_semitransparent_draw_pipeline.get() : s_state.replacement_draw_pipeline.get()); g_gpu_device->Draw(3, 0); + + g_gpu_device->RecycleTexture(std::move(temp_texture)); } g_gpu_device->CopyTextureRegion(replacement_tex.get(), 0, 0, 0, 0, s_state.replacement_texture_render_target.get(), 0, diff --git a/src/core/gpu_hw_texture_cache.h b/src/core/gpu_hw_texture_cache.h index 8fc601e64..1c071c920 100644 --- a/src/core/gpu_hw_texture_cache.h +++ b/src/core/gpu_hw_texture_cache.h @@ -5,8 +5,8 @@ #include "gpu_types.h" +class Image; class GPUTexture; -class RGBA8Image; class StateWrapper; struct Settings; @@ -29,7 +29,7 @@ enum class PaletteRecordFlags : u32 IMPLEMENT_ENUM_CLASS_BITWISE_OPERATORS(PaletteRecordFlags); using HashType = u64; -using TextureReplacementImage = RGBA8Image; +using TextureReplacementImage = Image; struct Source; struct HashCacheEntry; diff --git a/src/core/system.cpp b/src/core/system.cpp index 947ef9ac3..8ee6aa5e9 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -128,7 +128,7 @@ struct SaveStateBuffer std::string media_path; u32 media_subimage_index; u32 version; - RGBA8Image screenshot; + Image screenshot; DynamicHeapArray state_data; size_t state_size; }; @@ -2916,15 +2916,14 @@ bool System::LoadStateBufferFromFile(SaveStateBuffer* buffer, std::FILE* fp, Err // Read screenshot if requested. if (read_screenshot) { - buffer->screenshot.SetSize(header.screenshot_width, header.screenshot_height); - const u32 uncompressed_size = buffer->screenshot.GetPitch() * buffer->screenshot.GetHeight(); - const u32 compressed_size = (header.version >= 69) ? header.screenshot_compressed_size : uncompressed_size; + buffer->screenshot.Resize(header.screenshot_width, header.screenshot_height, ImageFormat::RGBA8, true); + const u32 compressed_size = + (header.version >= 69) ? header.screenshot_compressed_size : buffer->screenshot.GetStorageSize(); const SAVE_STATE_HEADER::CompressionType compression_type = (header.version >= 69) ? static_cast(header.screenshot_compression_type) : SAVE_STATE_HEADER::CompressionType::None; - if (!ReadAndDecompressStateData( - fp, std::span(reinterpret_cast(buffer->screenshot.GetPixels()), uncompressed_size), - header.offset_to_screenshot, compressed_size, compression_type, error)) [[unlikely]] + if (!ReadAndDecompressStateData(fp, buffer->screenshot.GetPixelsSpan(), header.offset_to_screenshot, + compressed_size, compression_type, error)) [[unlikely]] { return false; } @@ -3104,29 +3103,27 @@ bool System::SaveStateToBuffer(SaveStateBuffer* buffer, Error* error, u32 screen screenshot_display_rect = screenshot_display_rect.sub32(screenshot_display_rect.xyxy()); VERBOSE_LOG("Saving {}x{} screenshot for state", screenshot_width, screenshot_height); - std::vector screenshot_buffer; - u32 screenshot_stride; - GPUTexture::Format screenshot_format; if (g_gpu->RenderScreenshotToBuffer(screenshot_width, screenshot_height, screenshot_display_rect, - screenshot_draw_rect, false, &screenshot_buffer, &screenshot_stride, - &screenshot_format) && - GPUTexture::ConvertTextureDataToRGBA8(screenshot_width, screenshot_height, screenshot_buffer, screenshot_stride, - screenshot_format)) + screenshot_draw_rect, false, &buffer->screenshot)) { - if (screenshot_stride != (screenshot_width * sizeof(u32))) - { - WARNING_LOG("Failed to save {}x{} screenshot for save state due to incorrect stride({})", screenshot_width, - screenshot_height, screenshot_stride); - } - else - { - if (g_gpu_device->UsesLowerLeftOrigin()) - { - GPUTexture::FlipTextureDataRGBA8(screenshot_width, screenshot_height, - reinterpret_cast(screenshot_buffer.data()), screenshot_stride); - } + if (g_gpu_device->UsesLowerLeftOrigin()) + buffer->screenshot.FlipY(); - buffer->screenshot.SetPixels(screenshot_width, screenshot_height, std::move(screenshot_buffer)); + // Ensure it's RGBA8. + if (buffer->screenshot.GetFormat() != ImageFormat::RGBA8) + { + Error convert_error; + std::optional screenshot_rgba8 = buffer->screenshot.ConvertToRGBA8(&convert_error); + if (!screenshot_rgba8.has_value()) + { + ERROR_LOG("Failed to convert {} screenshot to RGBA8: {}", + Image::GetFormatName(buffer->screenshot.GetFormat()), convert_error.GetDescription()); + buffer->screenshot.Invalidate(); + } + else + { + buffer->screenshot = std::move(screenshot_rgba8.value()); + } } } else diff --git a/src/core/system.h b/src/core/system.h index a07350e22..c61a715c2 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -74,7 +74,7 @@ struct ExtendedSaveStateInfo std::string media_path; std::time_t timestamp; - RGBA8Image screenshot; + Image screenshot; }; namespace System { diff --git a/src/util/gpu_device.cpp b/src/util/gpu_device.cpp index 3c6c2fb25..44e3faf50 100644 --- a/src/util/gpu_device.cpp +++ b/src/util/gpu_device.cpp @@ -4,6 +4,7 @@ #include "gpu_device.h" #include "compress_helpers.h" #include "gpu_framebuffer_manager.h" +#include "image.h" #include "shadergen.h" #include "common/assert.h" @@ -1050,6 +1051,27 @@ GPUDevice::FetchAutoRecycleTexture(u32 width, u32 height, u32 layers, u32 levels return std::unique_ptr(ret.release()); } +std::unique_ptr GPUDevice::FetchAndUploadTextureImage(const Image& image, + GPUTexture::Flags flags /*= GPUTexture::Flags::None*/, + Error* error /*= nullptr*/) +{ + const Image* image_to_upload = ℑ + GPUTexture::Format gpu_format = GPUTexture::GetTextureFormatForImageFormat(image.GetFormat()); + std::optional converted_image; + if (!SupportsTextureFormat(gpu_format)) + { + converted_image = image.ConvertToRGBA8(error); + if (!converted_image.has_value()) + return nullptr; + + image_to_upload = &converted_image.value(); + gpu_format = GPUTexture::GetTextureFormatForImageFormat(converted_image->GetFormat()); + } + + return FetchTexture(image_to_upload->GetWidth(), image_to_upload->GetHeight(), 1, 1, 1, GPUTexture::Type::Texture, + gpu_format, flags, image_to_upload->GetPixels(), image_to_upload->GetPitch(), error); +} + void GPUDevice::RecycleTexture(std::unique_ptr texture) { if (!texture) diff --git a/src/util/gpu_device.h b/src/util/gpu_device.h index 04efc1fc6..02ae401c7 100644 --- a/src/util/gpu_device.h +++ b/src/util/gpu_device.h @@ -24,6 +24,7 @@ #include class Error; +class Image; enum class RenderAPI : u8 { @@ -707,6 +708,9 @@ public: FetchAutoRecycleTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, GPUTexture::Type type, GPUTexture::Format format, GPUTexture::Flags flags, const void* data = nullptr, u32 data_stride = 0, Error* error = nullptr); + std::unique_ptr FetchAndUploadTextureImage(const Image& image, + GPUTexture::Flags flags = GPUTexture::Flags::None, + Error* error = nullptr); void RecycleTexture(std::unique_ptr texture); void PurgeTexturePool(); diff --git a/src/util/gpu_texture.cpp b/src/util/gpu_texture.cpp index c1bad139b..a913d33b4 100644 --- a/src/util/gpu_texture.cpp +++ b/src/util/gpu_texture.cpp @@ -3,6 +3,7 @@ #include "gpu_texture.h" #include "gpu_device.h" +#include "image.h" #include "common/align.h" #include "common/assert.h" @@ -123,6 +124,56 @@ u32 GPUTexture::GetFullMipmapCount(u32 width, u32 height) return (std::countr_zero(max_dim) + 1); } +GPUTexture::Format GPUTexture::GetTextureFormatForImageFormat(ImageFormat format) +{ + static constexpr const std::array(ImageFormat::MaxCount)> mapping = {{ + Format::Unknown, // None + Format::RGBA8, // RGBA8 + Format::BGRA8, // BGRA8 + Format::RGB565, // RGB565 + Format::Unknown, // RGBA5551 + Format::Unknown, // BC1 + Format::Unknown, // BC2 + Format::Unknown, // BC3 + Format::Unknown, // BC7 + }}; + + return mapping[static_cast(format)]; +} + +ImageFormat GPUTexture::GetImageFormatForTextureFormat(Format format) +{ + static constexpr const std::array(Format::MaxCount)> mapping = {{ + ImageFormat::None, // Unknown + ImageFormat::RGBA8, // RGBA8 + ImageFormat::BGRA8, // BGRA8 + ImageFormat::RGB565, // RGB565 + ImageFormat::RGBA5551, // RGBA5551 + ImageFormat::None, // R8 + ImageFormat::None, // D16 + ImageFormat::None, // D24S8 + ImageFormat::None, // D32F + ImageFormat::None, // D32FS8 + ImageFormat::None, // R16 + ImageFormat::None, // R16I + ImageFormat::None, // R16U + ImageFormat::None, // R16F + ImageFormat::None, // R32I + ImageFormat::None, // R32U + ImageFormat::None, // R32F + ImageFormat::None, // RG8 + ImageFormat::None, // RG16 + ImageFormat::None, // RG16F + ImageFormat::None, // RG32F + ImageFormat::None, // RGBA16 + ImageFormat::None, // RGBA16F + ImageFormat::None, // RGBA32F + ImageFormat::None, // RGB10A2 + }}; + + return mapping[static_cast(format)]; +} + std::array GPUTexture::GetUNormClearColor() const { return GPUDevice::RGBA8ToFloat(m_clear_value.color); @@ -270,111 +321,6 @@ bool GPUTexture::ValidateConfig(u32 width, u32 height, u32 layers, u32 levels, u return true; } -bool GPUTexture::ConvertTextureDataToRGBA8(u32 width, u32 height, std::vector& texture_data, - u32& texture_data_stride, GPUTexture::Format format) -{ - switch (format) - { - case Format::BGRA8: - { - for (u32 y = 0; y < height; y++) - { - u8* pixels = reinterpret_cast(texture_data.data()) + (y * texture_data_stride); - for (u32 x = 0; x < width; x++) - { - u32 pixel; - std::memcpy(&pixel, pixels, sizeof(pixel)); - pixel = (pixel & 0xFF00FF00) | ((pixel & 0xFF) << 16) | ((pixel >> 16) & 0xFF); - std::memcpy(pixels, &pixel, sizeof(pixel)); - pixels += sizeof(pixel); - } - } - - return true; - } - - case Format::RGBA8: - return true; - - case Format::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); - u8* pixels_out = reinterpret_cast(temp.data()) + (y * width * sizeof(u32)); - - 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); - const u32 rgba8 = ZeroExtend32((r5 << 3) | (r5 & 7)) | (ZeroExtend32((g6 << 2) | (g6 & 3)) << 8) | - (ZeroExtend32((b5 << 3) | (b5 & 7)) << 16) | (0xFF000000u); - std::memcpy(pixels_out, &rgba8, sizeof(u32)); - pixels_out += sizeof(u32); - } - } - - texture_data = std::move(temp); - texture_data_stride = sizeof(u32) * width; - return true; - } - - case Format::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); - u8* pixels_out = reinterpret_cast(temp.data()) + (y * width * sizeof(u32)); - - 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); - const u32 rgba8 = ZeroExtend32((r5 << 3) | (r5 & 7)) | (ZeroExtend32((g6 << 3) | (g6 & 7)) << 8) | - (ZeroExtend32((b5 << 3) | (b5 & 7)) << 16) | (a1 ? 0xFF000000u : 0u); - std::memcpy(pixels_out, &rgba8, sizeof(u32)); - pixels_out += sizeof(u32); - } - } - - texture_data = std::move(temp); - texture_data_stride = sizeof(u32) * width; - return true; - } - - default: - return false; - } -} - -void GPUTexture::FlipTextureDataRGBA8(u32 width, u32 height, u8* texture_data, u32 texture_data_stride) -{ - std::unique_ptr temp = std::make_unique(texture_data_stride); - for (u32 flip_row = 0; flip_row < (height / 2); flip_row++) - { - u8* top_ptr = &texture_data[flip_row * texture_data_stride]; - u8* bottom_ptr = &texture_data[((height - 1) - flip_row) * texture_data_stride]; - std::memcpy(temp.get(), top_ptr, texture_data_stride); - std::memcpy(top_ptr, bottom_ptr, texture_data_stride); - std::memcpy(bottom_ptr, temp.get(), texture_data_stride); - } -} - void GPUTexture::MakeReadyForSampling() { } diff --git a/src/util/gpu_texture.h b/src/util/gpu_texture.h index 5139c74b5..c85113a91 100644 --- a/src/util/gpu_texture.h +++ b/src/util/gpu_texture.h @@ -13,6 +13,8 @@ class Error; +enum class ImageFormat : u8; + class GPUTexture { public: @@ -100,13 +102,12 @@ public: static u32 CalcUploadSize(Format format, u32 height, u32 pitch); static u32 GetFullMipmapCount(u32 width, u32 height); + static Format GetTextureFormatForImageFormat(ImageFormat format); + static ImageFormat GetImageFormatForTextureFormat(Format format); + static bool ValidateConfig(u32 width, u32 height, u32 layers, u32 levels, u32 samples, Type type, Format format, Flags flags, Error* error); - static bool ConvertTextureDataToRGBA8(u32 width, u32 height, std::vector& texture_data, u32& texture_data_stride, - Format format); - static void FlipTextureDataRGBA8(u32 width, u32 height, u8* texture_data, u32 texture_data_stride); - ALWAYS_INLINE u32 GetWidth() const { return m_width; } ALWAYS_INLINE u32 GetHeight() const { return m_height; } ALWAYS_INLINE u32 GetLayers() const { return m_layers; } diff --git a/src/util/image.cpp b/src/util/image.cpp index 5d31f607d..c3f5dc212 100644 --- a/src/util/image.cpp +++ b/src/util/image.cpp @@ -10,6 +10,7 @@ #include "common/file_system.h" #include "common/gsvector.h" #include "common/heap_array.h" +#include "common/intrin.h" #include "common/log.h" #include "common/path.h" #include "common/scoped_guard.h" @@ -30,28 +31,28 @@ LOG_CHANNEL(Image); -static bool PNGBufferLoader(RGBA8Image* image, std::span data, Error* error); -static bool PNGBufferSaver(const RGBA8Image& image, DynamicHeapArray* data, u8 quality, Error* error); -static bool PNGFileLoader(RGBA8Image* image, std::string_view filename, std::FILE* fp, Error* error); -static bool PNGFileSaver(const RGBA8Image& image, std::string_view filename, std::FILE* fp, u8 quality, Error* error); +static bool PNGBufferLoader(Image* image, std::span data, Error* error); +static bool PNGBufferSaver(const Image& image, DynamicHeapArray* data, u8 quality, Error* error); +static bool PNGFileLoader(Image* image, std::string_view filename, std::FILE* fp, Error* error); +static bool PNGFileSaver(const Image& image, std::string_view filename, std::FILE* fp, u8 quality, Error* error); -static bool JPEGBufferLoader(RGBA8Image* image, std::span data, Error* error); -static bool JPEGBufferSaver(const RGBA8Image& image, DynamicHeapArray* data, u8 quality, Error* error); -static bool JPEGFileLoader(RGBA8Image* image, std::string_view filename, std::FILE* fp, Error* error); -static bool JPEGFileSaver(const RGBA8Image& image, std::string_view filename, std::FILE* fp, u8 quality, Error* error); +static bool JPEGBufferLoader(Image* image, std::span data, Error* error); +static bool JPEGBufferSaver(const Image& image, DynamicHeapArray* data, u8 quality, Error* error); +static bool JPEGFileLoader(Image* image, std::string_view filename, std::FILE* fp, Error* error); +static bool JPEGFileSaver(const Image& image, std::string_view filename, std::FILE* fp, u8 quality, Error* error); -static bool WebPBufferLoader(RGBA8Image* image, std::span data, Error* error); -static bool WebPBufferSaver(const RGBA8Image& image, DynamicHeapArray* data, u8 quality, Error* error); -static bool WebPFileLoader(RGBA8Image* image, std::string_view filename, std::FILE* fp, Error* error); -static bool WebPFileSaver(const RGBA8Image& image, std::string_view filename, std::FILE* fp, u8 quality, Error* error); +static bool WebPBufferLoader(Image* image, std::span data, Error* error); +static bool WebPBufferSaver(const Image& image, DynamicHeapArray* data, u8 quality, Error* error); +static bool WebPFileLoader(Image* image, std::string_view filename, std::FILE* fp, Error* error); +static bool WebPFileSaver(const Image& image, std::string_view filename, std::FILE* fp, u8 quality, Error* error); struct FormatHandler { const char* extension; - bool (*buffer_loader)(RGBA8Image*, std::span, Error*); - bool (*buffer_saver)(const RGBA8Image&, DynamicHeapArray*, u8, Error*); - bool (*file_loader)(RGBA8Image*, std::string_view, std::FILE*, Error*); - bool (*file_saver)(const RGBA8Image&, std::string_view, std::FILE*, u8, Error*); + bool (*buffer_loader)(Image*, std::span, Error*); + bool (*buffer_saver)(const Image&, DynamicHeapArray*, u8, Error*); + bool (*file_loader)(Image*, std::string_view, std::FILE*, Error*); + bool (*file_saver)(const Image&, std::string_view, std::FILE*, u8, Error*); }; static constexpr FormatHandler s_format_handlers[] = { @@ -72,41 +73,254 @@ static const FormatHandler* GetFormatHandler(std::string_view extension) return nullptr; } -RGBA8Image::RGBA8Image() = default; +static void SwapBGRAToRGBA(void* pixels_out, u32 pixels_out_pitch, const void* pixels_in, u32 pixels_in_pitch, + u32 width, u32 height); -RGBA8Image::RGBA8Image(const RGBA8Image& copy) : Image(copy) +Image::Image() = default; + +Image::Image(const Image& copy) +{ + SetPixels(copy.m_width, copy.m_height, copy.m_format, copy.m_pixels.get(), copy.m_pitch); +} + +Image::Image(u32 width, u32 height, ImageFormat format, const void* pixels, u32 pitch) +{ + SetPixels(width, height, format, pixels, pitch); +} + +Image::Image(u32 width, u32 height, ImageFormat format, PixelStorage pixels, u32 pitch) + : m_width(width), m_height(height), m_pitch(pitch), m_format(format), m_pixels(std::move(pixels)) { } -RGBA8Image::RGBA8Image(u32 width, u32 height, const u32* pixels) : Image(width, height, pixels) +Image::Image(u32 width, u32 height, ImageFormat format) { + Resize(width, height, format, false); } -RGBA8Image::RGBA8Image(RGBA8Image&& move) : Image(move) +Image::Image(Image&& move) { + m_width = std::exchange(move.m_width, 0); + m_height = std::exchange(move.m_height, 0); + m_pitch = std::exchange(move.m_pitch, 0); + m_format = std::exchange(move.m_format, ImageFormat::None); + m_pixels = std::move(move.m_pixels); } -RGBA8Image::RGBA8Image(u32 width, u32 height) : Image(width, height) +void Image::Resize(u32 new_width, u32 new_height, bool preserve) { + Resize(new_width, new_height, m_format, preserve); } -RGBA8Image::RGBA8Image(u32 width, u32 height, std::vector pixels) : Image(width, height, std::move(pixels)) +void Image::Resize(u32 new_width, u32 new_height, ImageFormat format, bool preserve) { + if (m_width == new_width && m_height == new_height && m_format == format) + return; + + if (!preserve) + m_pixels.reset(); + + const u32 old_blocks_y = GetBlockYCount(); + const u32 old_pitch = m_pitch; + PixelStorage old_pixels = + std::exchange(m_pixels, Common::make_unique_aligned_for_overwrite( + VECTOR_ALIGNMENT, CalculateStorageSize(new_width, new_height, format))); + + m_width = new_width; + m_height = new_height; + m_format = format; + m_pitch = CalculatePitch(new_width, new_height, format); + if (preserve && old_pixels) + { + StringUtil::StrideMemCpy(m_pixels.get(), m_pitch, old_pixels.get(), old_pitch, std::min(old_pitch, m_pitch), + std::min(old_blocks_y, GetBlockYCount())); + } } -RGBA8Image& RGBA8Image::operator=(const RGBA8Image& copy) +Image& Image::operator=(const Image& copy) { - Image::operator=(copy); + SetPixels(copy.m_width, copy.m_height, copy.m_format, copy.m_pixels.get(), copy.m_pitch); return *this; } -RGBA8Image& RGBA8Image::operator=(RGBA8Image&& move) +Image& Image::operator=(Image&& move) { - Image::operator=(move); + m_width = std::exchange(move.m_width, 0); + m_height = std::exchange(move.m_height, 0); + m_pitch = std::exchange(move.m_pitch, 0); + m_format = std::exchange(move.m_format, ImageFormat::None); + m_pixels = std::move(move.m_pixels); return *this; } -bool RGBA8Image::LoadFromFile(const char* filename, Error* error /* = nullptr */) +const char* Image::GetFormatName(ImageFormat format) +{ + static constexpr std::array(ImageFormat::MaxCount)> names = { + "None", // None + "RGBA8", // RGBA8 + "BGRA8", // BGRA8 + "RGB565", // RGB565 + "RGB5551", // RGBA5551 + "BC1", // BC1 + "BC2", // BC2 + "BC3", // BC3 + "BC7", // BC7 + }; + + return names[static_cast(format)]; +} + +u32 Image::GetPixelSize(ImageFormat format) +{ + static constexpr std::array(ImageFormat::MaxCount)> sizes = {{ + 0, // Unknown + 4, // RGBA8 + 4, // BGRA8 + 2, // RGB565 + 2, // RGBA5551 + 8, // BC1 - 16 pixels in 64 bits + 16, // BC2 - 16 pixels in 128 bits + 16, // BC3 - 16 pixels in 128 bits + 16, // BC4 - 16 pixels in 128 bits + }}; + + return sizes[static_cast(format)]; +} + +bool Image::IsCompressedFormat(ImageFormat format) +{ + return (format >= ImageFormat::BC1); +} + +u32 Image::CalculatePitch(u32 width, u32 height, ImageFormat format) +{ + const u32 pixel_size = GetPixelSize(format); + if (!IsCompressedFormat(format)) + return Common::AlignUpPow2(width * pixel_size, 4); + + // All compressed formats use a block size of 4. + const u32 blocks_wide = Common::AlignUpPow2(width, 4) / 4; + return blocks_wide * pixel_size; +} + +u32 Image::CalculateStorageSize(u32 width, u32 height, ImageFormat format) +{ + const u32 pixel_size = GetPixelSize(format); + if (!IsCompressedFormat(format)) + return Common::AlignUpPow2(width * pixel_size, 4) * height; + + const u32 blocks_wide = Common::AlignUpPow2(width, 4) / 4; + const u32 blocks_high = Common::AlignUpPow2(height, 4) / 4; + return (blocks_wide * pixel_size) * blocks_high; +} + +u32 Image::CalculateStorageSize(u32 width, u32 height, u32 pitch, ImageFormat format) +{ + height = IsCompressedFormat(format) ? (Common::AlignUpPow2(height, 4) / 4) : height; + return pitch * height; +} + +u32 Image::GetBlockXCount() const +{ + return IsCompressedFormat(m_format) ? (Common::AlignUpPow2(m_width, 4) / 4) : m_width; +} + +u32 Image::GetBlockYCount() const +{ + return IsCompressedFormat(m_format) ? (Common::AlignUpPow2(m_height, 4) / 4) : m_height; +} + +u32 Image::GetStorageSize() const +{ + return GetBlockYCount() * m_pitch; +} + +std::span Image::GetPixelsSpan() const +{ + return std::span(m_pixels.get(), GetStorageSize()); +} + +std::span Image::GetPixelsSpan() +{ + return std::span(m_pixels.get(), GetStorageSize()); +} + +void Image::Clear() +{ + std::memset(m_pixels.get(), 0, CalculateStorageSize(m_width, m_height, m_pitch, m_format)); +} + +void Image::Invalidate() +{ + m_width = 0; + m_height = 0; + m_pitch = 0; + m_format = ImageFormat::None; + m_pixels.reset(); +} + +void Image::SetPixels(u32 width, u32 height, ImageFormat format, const void* pixels, u32 pitch) +{ + Resize(width, height, format, false); + if (m_pixels) + StringUtil::StrideMemCpy(m_pixels.get(), m_pitch, pixels, pitch, m_pitch, GetBlockYCount()); +} + +void Image::SetPixels(u32 width, u32 height, ImageFormat format, PixelStorage pixels, u32 pitch) +{ + m_width = width; + m_height = height; + m_format = format; + m_pitch = pitch; + m_pixels = std::move(pixels); +} + +bool Image::SetAllPixelsOpaque() +{ + if (m_format == ImageFormat::RGBA8 || m_format == ImageFormat::BGRA8) + { + for (u32 y = 0; y < m_height; y++) + { + u8* row = GetRowPixels(y); + for (u32 x = 0; x < m_width; x++, row += sizeof(u32)) + row[3] = 0xFF; + } + + return true; + } + else if (m_format == ImageFormat::RGBA5551) + { + for (u32 y = 0; y < m_height; y++) + { + u8* row = GetRowPixels(y); + for (u32 x = 0; x < m_width; x++, row += sizeof(u32)) + row[1] |= 0x80; + } + + return true; + } + else if (m_format == ImageFormat::RGB565) + { + // Already opaque + return true; + } + else + { + // Unhandled format + return false; + } +} + +Image::PixelStorage Image::TakePixels() +{ + m_width = 0; + m_height = 0; + m_format = ImageFormat::None; + m_pitch = 0; + return std::move(m_pixels); +} + +bool Image::LoadFromFile(const char* filename, Error* error /* = nullptr */) { auto fp = FileSystem::OpenManagedCFile(filename, "rb", error); if (!fp) @@ -115,8 +329,8 @@ bool RGBA8Image::LoadFromFile(const char* filename, Error* error /* = nullptr */ return LoadFromFile(filename, fp.get(), error); } -bool RGBA8Image::SaveToFile(const char* filename, u8 quality /* = DEFAULT_SAVE_QUALITY */, - Error* error /* = nullptr */) const +bool Image::SaveToFile(const char* filename, u8 quality /* = DEFAULT_SAVE_QUALITY */, + Error* error /* = nullptr */) const { auto fp = FileSystem::OpenManagedCFile(filename, "wb", error); if (!fp) @@ -131,7 +345,7 @@ bool RGBA8Image::SaveToFile(const char* filename, u8 quality /* = DEFAULT_SAVE_Q return false; } -bool RGBA8Image::LoadFromFile(std::string_view filename, std::FILE* fp, Error* error /* = nullptr */) +bool Image::LoadFromFile(std::string_view filename, std::FILE* fp, Error* error /* = nullptr */) { const std::string_view extension(Path::GetExtension(filename)); const FormatHandler* handler = GetFormatHandler(extension); @@ -144,7 +358,7 @@ bool RGBA8Image::LoadFromFile(std::string_view filename, std::FILE* fp, Error* e return handler->file_loader(this, filename, fp, error); } -bool RGBA8Image::LoadFromBuffer(std::string_view filename, std::span data, Error* error /* = nullptr */) +bool Image::LoadFromBuffer(std::string_view filename, std::span data, Error* error /* = nullptr */) { const std::string_view extension(Path::GetExtension(filename)); const FormatHandler* handler = GetFormatHandler(extension); @@ -157,7 +371,7 @@ bool RGBA8Image::LoadFromBuffer(std::string_view filename, std::span d return handler->buffer_loader(this, data, error); } -bool RGBA8Image::RasterizeSVG(const std::span data, u32 width, u32 height, Error* error) +bool Image::RasterizeSVG(const std::span data, u32 width, u32 height, Error* error) { if (width == 0 || height == 0) { @@ -181,13 +395,15 @@ bool RGBA8Image::RasterizeSVG(const std::span data, u32 width, u32 hei return false; } - SetPixels(width, height, lunasvg_bitmap_data(bitmap.get()), lunasvg_bitmap_stride(bitmap.get())); - SwapBGRAToRGBA(m_pixels.data(), m_width, m_height, GetPitch()); + // lunasvg works in BGRA, swap to RGBA + Resize(width, height, ImageFormat::RGBA8, false); + SwapBGRAToRGBA(m_pixels.get(), m_pitch, lunasvg_bitmap_data(bitmap.get()), lunasvg_bitmap_stride(bitmap.get()), width, + height); return true; } -bool RGBA8Image::SaveToFile(std::string_view filename, std::FILE* fp, u8 quality /* = DEFAULT_SAVE_QUALITY */, - Error* error /* = nullptr */) const +bool Image::SaveToFile(std::string_view filename, std::FILE* fp, u8 quality /* = DEFAULT_SAVE_QUALITY */, + Error* error /* = nullptr */) const { const std::string_view extension(Path::GetExtension(filename)); const FormatHandler* handler = GetFormatHandler(extension); @@ -209,9 +425,9 @@ bool RGBA8Image::SaveToFile(std::string_view filename, std::FILE* fp, u8 quality return true; } -std::optional> RGBA8Image::SaveToBuffer(std::string_view filename, - u8 quality /* = DEFAULT_SAVE_QUALITY */, - Error* error /* = nullptr */) const +std::optional> Image::SaveToBuffer(std::string_view filename, + u8 quality /* = DEFAULT_SAVE_QUALITY */, + Error* error /* = nullptr */) const { std::optional> ret; @@ -230,81 +446,154 @@ std::optional> RGBA8Image::SaveToBuffer(std::string_view fi return ret; } -void RGBA8Image::SwapBGRAToRGBA(void* pixels, u32 width, u32 height, u32 pitch) +void SwapBGRAToRGBA(void* pixels_out, u32 pixels_out_pitch, const void* pixels_in, u32 pixels_in_pitch, u32 width, + u32 height) { #ifdef GSVECTOR_HAS_FAST_INT_SHUFFLE8 constexpr u32 pixels_per_vec = sizeof(GSVector4i) / 4; const u32 aligned_width = Common::AlignDownPow2(width, pixels_per_vec); #endif - u8* pixels_ptr = static_cast(pixels); + const u8* pixels_in_ptr = static_cast(pixels_in); + u8* pixels_out_ptr = static_cast(pixels_out); for (u32 y = 0; y < height; y++) { - u8* row_pixels_ptr = pixels_ptr; - u32 x; + const u8* row_pixels_in_ptr = pixels_in_ptr; + u8* row_pixels_out_ptr = pixels_out_ptr; + u32 x = 0; #ifdef GSVECTOR_HAS_FAST_INT_SHUFFLE8 - for (x = 0; x < aligned_width; x += pixels_per_vec) + for (; x < aligned_width; x += pixels_per_vec) { static constexpr GSVector4i mask = GSVector4i::cxpr8(2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15); - GSVector4i::store(row_pixels_ptr, GSVector4i::load(row_pixels_ptr).shuffle8(mask)); - row_pixels_ptr += sizeof(GSVector4i); + GSVector4i::store(row_pixels_out_ptr, GSVector4i::load(row_pixels_in_ptr).shuffle8(mask)); + row_pixels_in_ptr += sizeof(GSVector4i); + row_pixels_out_ptr += sizeof(GSVector4i); } #endif - for (x = 0; x < width; x++) + for (; x < width; x++) { u32 pixel; - std::memcpy(&pixel, row_pixels_ptr, sizeof(pixel)); + std::memcpy(&pixel, row_pixels_in_ptr, sizeof(pixel)); pixel = (pixel & 0xFF00FF00) | ((pixel & 0xFF) << 16) | ((pixel >> 16) & 0xFF); - std::memcpy(row_pixels_ptr, &pixel, sizeof(pixel)); - row_pixels_ptr += sizeof(pixel); + std::memcpy(row_pixels_out_ptr, &pixel, sizeof(pixel)); + row_pixels_in_ptr += sizeof(pixel); + row_pixels_out_ptr += sizeof(pixel); } - pixels_ptr += pitch; + pixels_in_ptr += pixels_in_pitch; + pixels_out_ptr += pixels_out_pitch; } } -#if 0 - -void RGBA8Image::Resize(u32 new_width, u32 new_height) +std::optional Image::ConvertToRGBA8(Error* error) const { - if (m_width == new_width && m_height == new_height) - return; + std::optional ret; - std::vector resized_texture_data(new_width * new_height); - u32 resized_texture_stride = sizeof(u32) * new_width; - if (!stbir_resize_uint8(reinterpret_cast(m_pixels.data()), m_width, m_height, GetPitch(), - reinterpret_cast(resized_texture_data.data()), new_width, new_height, - resized_texture_stride, 4)) + if (!IsValid()) { - Panic("stbir_resize_uint8 failed"); - return; + Error::SetStringView(error, "Image is not valid."); + return ret; } - SetPixels(new_width, new_height, std::move(resized_texture_data)); + switch (m_format) + { + case ImageFormat::BGRA8: + { + ret = Image(m_width, m_height, ImageFormat::RGBA8); + SwapBGRAToRGBA(ret->GetPixels(), ret->GetPitch(), m_pixels.get(), m_pitch, m_width, m_height); + } + break; + + case ImageFormat::RGBA8: + { + ret = Image(m_width, m_height, m_format, m_pixels.get(), m_pitch); + } + break; + + case ImageFormat::RGB565: + { + ret = Image(m_width, m_height, ImageFormat::RGBA8); + for (u32 y = 0; y < m_height; y++) + { + const u8* pixels_in = GetRowPixels(y); + u8* pixels_out = ret->GetRowPixels(y); + + for (u32 x = 0; x < m_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); + const u32 rgba8 = ZeroExtend32((r5 << 3) | (r5 & 7)) | (ZeroExtend32((g6 << 2) | (g6 & 3)) << 8) | + (ZeroExtend32((b5 << 3) | (b5 & 7)) << 16) | (0xFF000000u); + std::memcpy(pixels_out, &rgba8, sizeof(u32)); + pixels_out += sizeof(u32); + } + } + } + break; + + case ImageFormat::RGBA5551: + { + ret = Image(m_width, m_height, ImageFormat::RGBA8); + for (u32 y = 0; y < m_height; y++) + { + const u8* pixels_in = GetRowPixels(y); + u8* pixels_out = ret->GetRowPixels(y); + + for (u32 x = 0; x < m_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); + const u32 rgba8 = ZeroExtend32((r5 << 3) | (r5 & 7)) | (ZeroExtend32((g6 << 3) | (g6 & 7)) << 8) | + (ZeroExtend32((b5 << 3) | (b5 & 7)) << 16) | (a1 ? 0xFF000000u : 0u); + std::memcpy(pixels_out, &rgba8, sizeof(u32)); + pixels_out += sizeof(u32); + } + } + } + break; + + // TODO: Block format decompression + + default: + { + Error::SetStringFmt(error, "Unhandled format {}", GetFormatName(m_format)); + } + break; + } + + return ret; } -void RGBA8Image::Resize(const RGBA8Image* src_image, u32 new_width, u32 new_height) +void Image::FlipY() { - if (src_image->m_width == new_width && src_image->m_height == new_height) - { - SetPixels(src_image->m_width, src_image->m_height, src_image->m_pixels.data()); + if (!IsValid()) return; - } - SetSize(new_width, new_height); - if (!stbir_resize_uint8(reinterpret_cast(src_image->m_pixels.data()), src_image->m_width, - src_image->m_height, src_image->GetPitch(), reinterpret_cast(m_pixels.data()), new_width, - new_height, GetPitch(), 4)) + PixelStorage temp = Common::make_unique_aligned_for_overwrite(VECTOR_ALIGNMENT, m_pitch); + const u32 half_height = m_height / 2; + for (u32 flip_row = 0; flip_row < half_height; flip_row++) { - Panic("stbir_resize_uint8 failed"); - return; + u8* top_ptr = &m_pixels[flip_row * m_pitch]; + u8* bottom_ptr = &m_pixels[((m_height - 1) - flip_row) * m_pitch]; + std::memcpy(temp.get(), top_ptr, m_pitch); + std::memcpy(top_ptr, bottom_ptr, m_pitch); + std::memcpy(bottom_ptr, temp.get(), m_pitch); } } -#endif - static void PNGSetErrorFunction(png_structp png_ptr, Error* error) { png_set_error_fn( @@ -316,8 +605,7 @@ static void PNGSetErrorFunction(png_structp png_ptr, Error* error) [](png_structp png_ptr, png_const_charp message) { WARNING_LOG("libpng warning: {}", message); }); } -static bool PNGCommonLoader(RGBA8Image* image, png_structp png_ptr, png_infop info_ptr, std::vector& new_data, - std::vector& row_pointers) +static bool PNGCommonLoader(Image* image, png_structp png_ptr, png_infop info_ptr, std::vector& row_pointers) { png_read_info(png_ptr, info_ptr); @@ -351,17 +639,16 @@ static bool PNGCommonLoader(RGBA8Image* image, png_structp png_ptr, png_infop in png_read_update_info(png_ptr, info_ptr); - new_data.resize(width * height); + image->Resize(width, height, ImageFormat::RGBA8, false); row_pointers.reserve(height); for (u32 y = 0; y < height; y++) - row_pointers.push_back(reinterpret_cast(new_data.data() + y * width)); + row_pointers.push_back(reinterpret_cast(image->GetRowPixels(y))); png_read_image(png_ptr, row_pointers.data()); - image->SetPixels(width, height, std::move(new_data)); return true; } -bool PNGFileLoader(RGBA8Image* image, std::string_view filename, std::FILE* fp, Error* error) +bool PNGFileLoader(Image* image, std::string_view filename, std::FILE* fp, Error* error) { png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (!png_ptr) @@ -380,12 +667,14 @@ bool PNGFileLoader(RGBA8Image* image, std::string_view filename, std::FILE* fp, ScopedGuard cleanup([&png_ptr, &info_ptr]() { png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); }); - std::vector new_data; std::vector row_pointers; PNGSetErrorFunction(png_ptr, error); if (setjmp(png_jmpbuf(png_ptr))) + { + image->Invalidate(); return false; + } png_set_read_fn(png_ptr, fp, [](png_structp png_ptr, png_bytep data_ptr, png_size_t size) { std::FILE* fp = static_cast(png_get_io_ptr(png_ptr)); @@ -393,10 +682,10 @@ bool PNGFileLoader(RGBA8Image* image, std::string_view filename, std::FILE* fp, png_error(png_ptr, "fread() failed"); }); - return PNGCommonLoader(image, png_ptr, info_ptr, new_data, row_pointers); + return PNGCommonLoader(image, png_ptr, info_ptr, row_pointers); } -bool PNGBufferLoader(RGBA8Image* image, std::span data, Error* error) +bool PNGBufferLoader(Image* image, std::span data, Error* error) { png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (!png_ptr) @@ -415,12 +704,14 @@ bool PNGBufferLoader(RGBA8Image* image, std::span data, Error* error) ScopedGuard cleanup([&png_ptr, &info_ptr]() { png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); }); - std::vector new_data; std::vector row_pointers; PNGSetErrorFunction(png_ptr, error); if (setjmp(png_jmpbuf(png_ptr))) + { + image->Invalidate(); return false; + } struct IOData { @@ -439,10 +730,10 @@ bool PNGBufferLoader(RGBA8Image* image, std::span data, Error* error) } }); - return PNGCommonLoader(image, png_ptr, info_ptr, new_data, row_pointers); + return PNGCommonLoader(image, png_ptr, info_ptr, row_pointers); } -static void PNGSaveCommon(const RGBA8Image& image, png_structp png_ptr, png_infop info_ptr, u8 quality) +static void PNGSaveCommon(const Image& image, png_structp png_ptr, png_infop info_ptr, u8 quality) { png_set_compression_level(png_ptr, std::clamp(quality / 10, 0, 9)); png_set_IHDR(png_ptr, info_ptr, image.GetWidth(), image.GetHeight(), 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, @@ -455,7 +746,7 @@ static void PNGSaveCommon(const RGBA8Image& image, png_structp png_ptr, png_info png_write_end(png_ptr, nullptr); } -bool PNGFileSaver(const RGBA8Image& image, std::string_view filename, std::FILE* fp, u8 quality, Error* error) +bool PNGFileSaver(const Image& image, std::string_view filename, std::FILE* fp, u8 quality, Error* error) { png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); png_infop info_ptr = nullptr; @@ -493,7 +784,7 @@ bool PNGFileSaver(const RGBA8Image& image, std::string_view filename, std::FILE* return true; } -bool PNGBufferSaver(const RGBA8Image& image, DynamicHeapArray* data, u8 quality, Error* error) +bool PNGBufferSaver(const Image& image, DynamicHeapArray* data, u8 quality, Error* error) { png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); png_infop info_ptr = nullptr; @@ -570,7 +861,7 @@ struct JPEGErrorHandler } // namespace template -static bool WrapJPEGDecompress(RGBA8Image* image, Error* error, T setup_func) +static bool WrapJPEGDecompress(Image* image, Error* error, T setup_func) { std::vector scanline; jpeg_decompress_struct info = {}; @@ -611,7 +902,7 @@ static bool WrapJPEGDecompress(RGBA8Image* image, Error* error, T setup_func) return false; } - image->SetSize(info.image_width, info.image_height); + image->Resize(info.image_width, info.image_height, ImageFormat::RGBA8, false); scanline.resize(info.image_width * 3); u8* scanline_buffer[1] = {scanline.data()}; @@ -627,11 +918,13 @@ static bool WrapJPEGDecompress(RGBA8Image* image, Error* error, T setup_func) // RGB -> RGBA const u8* src_ptr = scanline.data(); - u32* dst_ptr = image->GetRowPixels(y); + u8* dst_ptr = image->GetRowPixels(y); for (u32 x = 0; x < info.image_width; x++) { - *(dst_ptr++) = + const u32 pixel32 = (ZeroExtend32(src_ptr[0]) | (ZeroExtend32(src_ptr[1]) << 8) | (ZeroExtend32(src_ptr[2]) << 16) | 0xFF000000u); + std::memcpy(dst_ptr, &pixel32, sizeof(pixel32)); + dst_ptr += sizeof(pixel32); src_ptr += 3; } } @@ -641,14 +934,14 @@ static bool WrapJPEGDecompress(RGBA8Image* image, Error* error, T setup_func) return result; } -bool JPEGBufferLoader(RGBA8Image* image, std::span data, Error* error) +bool JPEGBufferLoader(Image* image, std::span data, Error* error) { return WrapJPEGDecompress(image, error, [data](jpeg_decompress_struct& info) { jpeg_mem_src(&info, static_cast(data.data()), static_cast(data.size())); }); } -bool JPEGFileLoader(RGBA8Image* image, std::string_view filename, std::FILE* fp, Error* error) +bool JPEGFileLoader(Image* image, std::string_view filename, std::FILE* fp, Error* error) { static constexpr u32 BUFFER_SIZE = 16384; @@ -713,7 +1006,7 @@ bool JPEGFileLoader(RGBA8Image* image, std::string_view filename, std::FILE* fp, } template -static bool WrapJPEGCompress(const RGBA8Image& image, u8 quality, Error* error, T setup_func) +static bool WrapJPEGCompress(const Image& image, u8 quality, Error* error, T setup_func) { std::vector scanline; jpeg_compress_struct info = {}; @@ -747,10 +1040,12 @@ static bool WrapJPEGCompress(const RGBA8Image& image, u8 quality, Error* error, { // RGBA -> RGB u8* dst_ptr = scanline.data(); - const u32* src_ptr = image.GetRowPixels(y); + const u8* src_ptr = image.GetRowPixels(y); for (u32 x = 0; x < info.image_width; x++) { - const u32 rgba = *(src_ptr++); + u32 rgba; + std::memcpy(&rgba, src_ptr, sizeof(rgba)); + src_ptr += sizeof(rgba); *(dst_ptr++) = Truncate8(rgba); *(dst_ptr++) = Truncate8(rgba >> 8); *(dst_ptr++) = Truncate8(rgba >> 16); @@ -769,7 +1064,7 @@ static bool WrapJPEGCompress(const RGBA8Image& image, u8 quality, Error* error, return result; } -bool JPEGBufferSaver(const RGBA8Image& image, DynamicHeapArray* buffer, u8 quality, Error* error) +bool JPEGBufferSaver(const Image& image, DynamicHeapArray* buffer, u8 quality, Error* error) { // give enough space to avoid reallocs buffer->resize(image.GetWidth() * image.GetHeight() * 2); @@ -807,7 +1102,7 @@ bool JPEGBufferSaver(const RGBA8Image& image, DynamicHeapArray* buffer, u8 q return WrapJPEGCompress(image, quality, error, [&cb](jpeg_compress_struct& info) { info.dest = &cb.mgr; }); } -bool JPEGFileSaver(const RGBA8Image& image, std::string_view filename, std::FILE* fp, u8 quality, Error* error) +bool JPEGFileSaver(const Image& image, std::string_view filename, std::FILE* fp, u8 quality, Error* error) { static constexpr u32 BUFFER_SIZE = 16384; @@ -864,7 +1159,7 @@ bool JPEGFileSaver(const RGBA8Image& image, std::string_view filename, std::FILE !cb.write_error); } -bool WebPBufferLoader(RGBA8Image* image, std::span data, Error* error) +bool WebPBufferLoader(Image* image, std::span data, Error* error) { int width, height; if (!WebPGetInfo(data.data(), data.size(), &width, &height) || width <= 0 || height <= 0) @@ -873,20 +1168,18 @@ bool WebPBufferLoader(RGBA8Image* image, std::span data, Error* error) return false; } - std::vector pixels; - pixels.resize(static_cast(width) * static_cast(height)); - if (!WebPDecodeRGBAInto(data.data(), data.size(), reinterpret_cast(pixels.data()), sizeof(u32) * pixels.size(), - sizeof(u32) * static_cast(width))) + image->Resize(static_cast(width), static_cast(height), ImageFormat::RGBA8, false); + if (!WebPDecodeRGBAInto(data.data(), data.size(), image->GetPixels(), image->GetStorageSize(), image->GetPitch())) { Error::SetStringView(error, "WebPDecodeRGBAInto() failed"); + image->Invalidate(); return false; } - image->SetPixels(static_cast(width), static_cast(height), std::move(pixels)); return true; } -bool WebPBufferSaver(const RGBA8Image& image, DynamicHeapArray* data, u8 quality, Error* error) +bool WebPBufferSaver(const Image& image, DynamicHeapArray* data, u8 quality, Error* error) { u8* encoded_data; const size_t encoded_size = @@ -904,7 +1197,7 @@ bool WebPBufferSaver(const RGBA8Image& image, DynamicHeapArray* data, u8 qua return true; } -bool WebPFileLoader(RGBA8Image* image, std::string_view filename, std::FILE* fp, Error* error) +bool WebPFileLoader(Image* image, std::string_view filename, std::FILE* fp, Error* error) { std::optional> data = FileSystem::ReadBinaryFile(fp, error); if (!data.has_value()) @@ -913,7 +1206,7 @@ bool WebPFileLoader(RGBA8Image* image, std::string_view filename, std::FILE* fp, return WebPBufferLoader(image, data->cspan(), error); } -bool WebPFileSaver(const RGBA8Image& image, std::string_view filename, std::FILE* fp, u8 quality, Error* error) +bool WebPFileSaver(const Image& image, std::string_view filename, std::FILE* fp, u8 quality, Error* error) { DynamicHeapArray buffer; if (!WebPBufferSaver(image, &buffer, quality, error)) @@ -926,4 +1219,4 @@ bool WebPFileSaver(const RGBA8Image& image, std::string_view filename, std::FILE } return true; -} \ No newline at end of file +} diff --git a/src/util/image.h b/src/util/image.h index efc272eda..49a7334a1 100644 --- a/src/util/image.h +++ b/src/util/image.h @@ -3,154 +3,87 @@ #pragma once +#include "common/align.h" #include "common/heap_array.h" #include "common/types.h" -#include #include -#include #include #include #include -#include class Error; -template -class Image +enum class ImageFormat : u8 { -public: - Image() = default; - Image(u32 width, u32 height) { SetSize(width, height); } - Image(u32 width, u32 height, const PixelType* pixels) { SetPixels(width, height, pixels); } - Image(u32 width, u32 height, std::vector pixels) { SetPixels(width, height, std::move(pixels)); } - Image(const Image& copy) - { - m_width = copy.m_width; - m_height = copy.m_height; - m_pixels = copy.m_pixels; - } - Image(Image&& move) - { - m_width = move.m_width; - m_height = move.m_height; - m_pixels = std::move(move.m_pixels); - move.m_width = 0; - move.m_height = 0; - } - - Image& operator=(const Image& copy) - { - m_width = copy.m_width; - m_height = copy.m_height; - m_pixels = copy.m_pixels; - return *this; - } - Image& operator=(Image&& move) - { - m_width = move.m_width; - m_height = move.m_height; - m_pixels = std::move(move.m_pixels); - move.m_width = 0; - move.m_height = 0; - return *this; - } - - ALWAYS_INLINE bool IsValid() const { return (m_width > 0 && m_height > 0); } - ALWAYS_INLINE u32 GetWidth() const { return m_width; } - ALWAYS_INLINE u32 GetHeight() const { return m_height; } - ALWAYS_INLINE u32 GetPitch() const { return (sizeof(PixelType) * m_width); } - ALWAYS_INLINE const PixelType* GetPixels() const { return m_pixels.data(); } - ALWAYS_INLINE PixelType* GetPixels() { return m_pixels.data(); } - ALWAYS_INLINE const PixelType* GetRowPixels(u32 y) const { return &m_pixels[y * m_width]; } - ALWAYS_INLINE PixelType* GetRowPixels(u32 y) { return &m_pixels[y * m_width]; } - ALWAYS_INLINE void SetPixel(u32 x, u32 y, PixelType pixel) { m_pixels[y * m_width + x] = pixel; } - ALWAYS_INLINE PixelType GetPixel(u32 x, u32 y) const { return m_pixels[y * m_width + x]; } - - void Clear(PixelType fill_value = static_cast(0)) - { - std::fill(m_pixels.begin(), m_pixels.end(), fill_value); - } - - void Invalidate() - { - m_width = 0; - m_height = 0; - m_pixels.clear(); - } - - void SetSize(u32 new_width, u32 new_height, PixelType fill_value = static_cast(0)) - { - m_width = new_width; - m_height = new_height; - m_pixels.resize(new_width * new_height); - Clear(fill_value); - } - - void SetPixels(u32 width, u32 height, const PixelType* pixels) - { - m_width = width; - m_height = height; - m_pixels.resize(width * height); - std::memcpy(m_pixels.data(), pixels, width * height * sizeof(PixelType)); - } - - void SetPixels(u32 width, u32 height, std::vector pixels) - { - m_width = width; - m_height = height; - m_pixels = std::move(pixels); - } - - void SetPixels(u32 width, u32 height, const void* data, u32 stride) - { - const u32 copy_width = width * sizeof(PixelType); - if (stride == copy_width) - { - SetPixels(width, height, static_cast(data)); - return; - } - - m_width = width; - m_height = height; - m_pixels.resize(width, height); - PixelType* out_ptr = m_pixels.data(); - const u8* in_ptr = static_cast(data); - for (u32 row = 0; row < height; row++) - { - std::memcpy(out_ptr, in_ptr, copy_width); - out_ptr += width; - in_ptr += stride; - } - } - - std::vector TakePixels() - { - m_width = 0; - m_height = 0; - return std::move(m_pixels); - } - -protected: - u32 m_width = 0; - u32 m_height = 0; - std::vector m_pixels; + None, + RGBA8, + BGRA8, + RGB565, + RGBA5551, + BC1, + BC2, + BC3, + BC7, + MaxCount, }; -class RGBA8Image : public Image +class Image { public: static constexpr u8 DEFAULT_SAVE_QUALITY = 85; - RGBA8Image(); - RGBA8Image(u32 width, u32 height); - RGBA8Image(u32 width, u32 height, const u32* pixels); - RGBA8Image(u32 width, u32 height, std::vector pixels); - RGBA8Image(const RGBA8Image& copy); - RGBA8Image(RGBA8Image&& move); +public: + using PixelStorage = Common::unique_aligned_ptr; - RGBA8Image& operator=(const RGBA8Image& copy); - RGBA8Image& operator=(RGBA8Image&& move); + Image(); + Image(u32 width, u32 height, ImageFormat format); + Image(u32 width, u32 height, ImageFormat format, const void* pixels, u32 pitch); + Image(u32 width, u32 height, ImageFormat format, PixelStorage pixels, u32 pitch); + Image(const Image& copy); + Image(Image&& move); + + Image& operator=(const Image& copy); + Image& operator=(Image&& move); + + static const char* GetFormatName(ImageFormat format); + static u32 GetPixelSize(ImageFormat format); + static bool IsCompressedFormat(ImageFormat format); + static u32 CalculatePitch(u32 width, u32 height, ImageFormat format); + static u32 CalculateStorageSize(u32 width, u32 height, ImageFormat format); + static u32 CalculateStorageSize(u32 width, u32 height, u32 pitch, ImageFormat format); + + ALWAYS_INLINE bool IsValid() const { return (m_width > 0 && m_height > 0); } + ALWAYS_INLINE u32 GetWidth() const { return m_width; } + ALWAYS_INLINE u32 GetHeight() const { return m_height; } + ALWAYS_INLINE u32 GetPitch() const { return m_pitch; } + ALWAYS_INLINE ImageFormat GetFormat() const { return m_format; } + ALWAYS_INLINE const u8* GetPixels() const { return m_pixels.get(); } + ALWAYS_INLINE u8* GetPixels() { return m_pixels.get(); } + ALWAYS_INLINE const u8* GetRowPixels(u32 y) const { return &m_pixels[y * m_pitch]; } + ALWAYS_INLINE u8* GetRowPixels(u32 y) { return &m_pixels[y * m_pitch]; } + // ALWAYS_INLINE void SetPixel(u32 x, u32 y, PixelType pixel) { m_pixels[y * m_width + x] = pixel; } + // ALWAYS_INLINE PixelType GetPixel(u32 x, u32 y) const { return m_pixels[y * m_width + x]; } + + u32 GetBlockXCount() const; + u32 GetBlockYCount() const; + u32 GetStorageSize() const; + + std::span GetPixelsSpan() const; + std::span GetPixelsSpan(); + + void Clear(); + void Invalidate(); + + void Resize(u32 new_width, u32 new_height, bool preserve); + void Resize(u32 new_width, u32 new_height, ImageFormat format, bool preserve); + + void SetPixels(u32 width, u32 height, ImageFormat format, const void* pixels, u32 pitch); + void SetPixels(u32 width, u32 height, ImageFormat format, PixelStorage pixels, u32 pitch); + + bool SetAllPixelsOpaque(); + + PixelStorage TakePixels(); bool LoadFromFile(const char* filename, Error* error = nullptr); bool LoadFromFile(std::string_view filename, std::FILE* fp, Error* error = nullptr); @@ -164,5 +97,14 @@ public: std::optional> SaveToBuffer(std::string_view filename, u8 quality = DEFAULT_SAVE_QUALITY, Error* error = nullptr) const; - static void SwapBGRAToRGBA(void* pixels, u32 width, u32 height, u32 pitch); + std::optional ConvertToRGBA8(Error* error) const; + + void FlipY(); + +protected: + u32 m_width = 0; + u32 m_height = 0; + u32 m_pitch = 0; + ImageFormat m_format = ImageFormat::None; + PixelStorage m_pixels; }; diff --git a/src/util/imgui_fullscreen.cpp b/src/util/imgui_fullscreen.cpp index b7a6b41ce..39b3705ef 100644 --- a/src/util/imgui_fullscreen.cpp +++ b/src/util/imgui_fullscreen.cpp @@ -43,8 +43,8 @@ using MessageDialogCallbackVariant = std::variant LoadTextureImage(std::string_view path, u32 svg_width, u32 svg_height); -static std::shared_ptr UploadTexture(std::string_view path, const RGBA8Image& image); +static std::optional LoadTextureImage(std::string_view path, u32 svg_width, u32 svg_height); +static std::shared_ptr UploadTexture(std::string_view path, const Image& image); static void TextureLoaderThread(); static void DrawFileSelector(); @@ -100,7 +100,7 @@ static std::atomic_bool s_texture_load_thread_quit{false}; static std::mutex s_texture_load_mutex; static std::condition_variable s_texture_load_cv; static std::deque s_texture_load_queue; -static std::deque> s_texture_upload_queue; +static std::deque> s_texture_upload_queue; static std::thread s_texture_load_thread; static SmallString s_fullscreen_footer_text; @@ -288,19 +288,9 @@ const std::shared_ptr& ImGuiFullscreen::GetPlaceholderTexture() return s_placeholder_texture; } -std::unique_ptr ImGuiFullscreen::CreateTextureFromImage(const RGBA8Image& image) +std::optional ImGuiFullscreen::LoadTextureImage(std::string_view path, u32 svg_width, u32 svg_height) { - std::unique_ptr ret = g_gpu_device->CreateTexture( - image.GetWidth(), image.GetHeight(), 1, 1, 1, GPUTexture::Type::Texture, GPUTexture::Format::RGBA8, - GPUTexture::Flags::None, image.GetPixels(), image.GetPitch()); - if (!ret) [[unlikely]] - ERROR_LOG("Failed to upload {}x{} RGBA8Image to GPU", image.GetWidth(), image.GetHeight()); - return ret; -} - -std::optional ImGuiFullscreen::LoadTextureImage(std::string_view path, u32 svg_width, u32 svg_height) -{ - std::optional image; + std::optional image; Error error; if (StringUtil::EqualNoCase(Path::GetExtension(path), "svg")) @@ -313,7 +303,7 @@ std::optional ImGuiFullscreen::LoadTextureImage(std::string_view pat if (svg_data.has_value()) { - image = RGBA8Image(); + image = Image(); if (!image->RasterizeSVG(svg_data->cspan(), svg_width, svg_height)) { ERROR_LOG("Failed to rasterize SVG texture file '{}': {}", path, error.GetDescription()); @@ -331,7 +321,7 @@ std::optional ImGuiFullscreen::LoadTextureImage(std::string_view pat auto fp = FileSystem::OpenManagedCFile(path_str.c_str(), "rb", &error); if (fp) { - image = RGBA8Image(); + image = Image(); if (!image->LoadFromFile(path_str.c_str(), fp.get(), &error)) { ERROR_LOG("Failed to read texture file '{}': {}", path, error.GetDescription()); @@ -348,7 +338,7 @@ std::optional ImGuiFullscreen::LoadTextureImage(std::string_view pat std::optional> data = Host::ReadResourceFile(path, true, &error); if (data.has_value()) { - image = RGBA8Image(); + image = Image(); if (!image->LoadFromBuffer(path, data->cspan(), &error)) { ERROR_LOG("Failed to read texture resource '{}': {}", path, error.GetDescription()); @@ -364,14 +354,13 @@ std::optional ImGuiFullscreen::LoadTextureImage(std::string_view pat return image; } -std::shared_ptr ImGuiFullscreen::UploadTexture(std::string_view path, const RGBA8Image& image) +std::shared_ptr ImGuiFullscreen::UploadTexture(std::string_view path, const Image& image) { - std::unique_ptr texture = - g_gpu_device->FetchTexture(image.GetWidth(), image.GetHeight(), 1, 1, 1, GPUTexture::Type::Texture, - GPUTexture::Format::RGBA8, GPUTexture::Flags::None, image.GetPixels(), image.GetPitch()); + Error error; + std::unique_ptr texture = g_gpu_device->FetchAndUploadTextureImage(image, GPUTexture::Flags::None, &error); if (!texture) { - ERROR_LOG("Failed to create {}x{} texture for resource", image.GetWidth(), image.GetHeight()); + ERROR_LOG("Failed to upload texture '{}': {}", Path::GetFileTitle(path), error.GetDescription()); return {}; } @@ -381,7 +370,7 @@ std::shared_ptr ImGuiFullscreen::UploadTexture(std::string_view path std::shared_ptr ImGuiFullscreen::LoadTexture(std::string_view path, u32 width_hint, u32 height_hint) { - std::optional image(LoadTextureImage(path, width_hint, height_hint)); + std::optional image(LoadTextureImage(path, width_hint, height_hint)); if (image.has_value()) { std::shared_ptr ret(UploadTexture(path, image.value())); @@ -447,7 +436,7 @@ void ImGuiFullscreen::UploadAsyncTextures() std::unique_lock lock(s_texture_load_mutex); while (!s_texture_upload_queue.empty()) { - std::pair it(std::move(s_texture_upload_queue.front())); + std::pair it(std::move(s_texture_upload_queue.front())); s_texture_upload_queue.pop_front(); lock.unlock(); @@ -480,7 +469,7 @@ void ImGuiFullscreen::TextureLoaderThread() s_texture_load_queue.pop_front(); lock.unlock(); - std::optional image(LoadTextureImage(path.c_str(), 0, 0)); + std::optional image(LoadTextureImage(path.c_str(), 0, 0)); lock.lock(); // don't bother queuing back if it doesn't exist diff --git a/src/util/imgui_fullscreen.h b/src/util/imgui_fullscreen.h index c28881642..8f1fecbfa 100644 --- a/src/util/imgui_fullscreen.h +++ b/src/util/imgui_fullscreen.h @@ -18,7 +18,7 @@ #include #include -class RGBA8Image; +class Image; class GPUTexture; class SmallStringBase; @@ -129,7 +129,6 @@ void Shutdown(); /// Texture cache. const std::shared_ptr& GetPlaceholderTexture(); -std::unique_ptr CreateTextureFromImage(const RGBA8Image& image); std::shared_ptr LoadTexture(std::string_view path, u32 svg_width = 0, u32 svg_height = 0); GPUTexture* GetCachedTexture(std::string_view name); GPUTexture* GetCachedTexture(std::string_view name, u32 svg_width, u32 svg_height); diff --git a/src/util/imgui_manager.cpp b/src/util/imgui_manager.cpp index 3e6a6cfd6..9a68bf6c2 100644 --- a/src/util/imgui_manager.cpp +++ b/src/util/imgui_manager.cpp @@ -1242,7 +1242,7 @@ void ImGuiManager::UpdateSoftwareCursorTexture(u32 index) } Error error; - RGBA8Image image; + Image image; if (!image.LoadFromFile(sc.image_path.c_str(), &error)) { ERROR_LOG("Failed to load software cursor {} image '{}': {}", index, sc.image_path, error.GetDescription()); diff --git a/src/util/postprocessing_shader_fx.cpp b/src/util/postprocessing_shader_fx.cpp index 2f3f945e6..1fdb70490 100644 --- a/src/util/postprocessing_shader_fx.cpp +++ b/src/util/postprocessing_shader_fx.cpp @@ -1121,7 +1121,7 @@ bool PostProcessing::ReShadeFXShader::CreatePasses(GPUTexture::Format backbuffer return false; } - RGBA8Image image; + Image image; if (const std::string image_path = Path::Combine(EmuFolders::Shaders, Path::Combine("reshade" FS_OSPATH_SEPARATOR_STR "Textures", source)); !image.LoadFromFile(image_path.c_str()))