Image: Refactor to a more generic class

This commit is contained in:
Stenzek 2024-11-24 16:36:28 +10:00
parent 3ff1b04576
commit 24dfd30839
No known key found for this signature in database
18 changed files with 698 additions and 520 deletions

View File

@ -112,7 +112,6 @@ using ImGuiFullscreen::BeginNavBar;
using ImGuiFullscreen::CenterImage; using ImGuiFullscreen::CenterImage;
using ImGuiFullscreen::CloseChoiceDialog; using ImGuiFullscreen::CloseChoiceDialog;
using ImGuiFullscreen::CloseFileSelector; using ImGuiFullscreen::CloseFileSelector;
using ImGuiFullscreen::CreateTextureFromImage;
using ImGuiFullscreen::DefaultActiveButton; using ImGuiFullscreen::DefaultActiveButton;
using ImGuiFullscreen::DrawShadowedText; using ImGuiFullscreen::DrawShadowedText;
using ImGuiFullscreen::EndFullscreenColumns; using ImGuiFullscreen::EndFullscreenColumns;
@ -5966,7 +5965,7 @@ bool FullscreenUI::InitializeSaveStateListEntryFromPath(SaveStateListEntry* li,
li->path = std::move(path); li->path = std::move(path);
li->global = global; li->global = global;
if (ssi->screenshot.IsValid()) if (ssi->screenshot.IsValid())
li->preview_texture = CreateTextureFromImage(ssi->screenshot); li->preview_texture = g_gpu_device->FetchAndUploadTextureImage(ssi->screenshot);
return true; return true;
} }
@ -5994,7 +5993,7 @@ u32 FullscreenUI::PopulateSaveStateListEntries(const std::string& title, const s
li.title = FSUI_STR("Undo Load State"); li.title = FSUI_STR("Undo Load State");
li.summary = FSUI_STR("Restores the state of the system prior to the last state loaded."); li.summary = FSUI_STR("Restores the state of the system prior to the last state loaded.");
if (ssi->screenshot.IsValid()) 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)); s_save_state_selector_slots.push_back(std::move(li));
} }
} }

View File

@ -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), INFO_LOG("Extracting memory card icon from {} ({}) to {}", fi.filename, Path::GetFileTitle(memcard_path),
Path::GetFileTitle(ret)); 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, std::memcpy(image.GetPixels(), &fi.icon_frames.front().pixels,
MemoryCardImage::ICON_WIDTH * MemoryCardImage::ICON_HEIGHT * sizeof(u32)); MemoryCardImage::ICON_WIDTH * MemoryCardImage::ICON_HEIGHT * sizeof(u32));
serial_entry->icon_was_extracted = image.SaveToFile(ret.c_str()); serial_entry->icon_was_extracted = image.SaveToFile(ret.c_str());

View File

@ -74,10 +74,8 @@ static u32 s_active_gpu_cycles_frames = 0;
static constexpr GPUTexture::Format DISPLAY_INTERNAL_POSTFX_FORMAT = GPUTexture::Format::RGBA8; static constexpr GPUTexture::Format DISPLAY_INTERNAL_POSTFX_FORMAT = GPUTexture::Format::RGBA8;
static bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string filename, FileSystem::ManagedCFilePtr fp, static bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string path, FileSystem::ManagedCFilePtr fp,
u8 quality, bool clear_alpha, bool flip_y, std::vector<u32> texture_data, u8 quality, bool clear_alpha, bool flip_y, Image image, std::string osd_key);
u32 texture_data_stride, GPUTexture::Format texture_format,
std::string osd_key);
GPU::GPU() 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)); 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, bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string path, FileSystem::ManagedCFilePtr fp, u8 quality,
u8 quality, bool clear_alpha, bool flip_y, std::vector<u32> texture_data, bool clear_alpha, bool flip_y, Image image, std::string osd_key)
u32 texture_data_stride, GPUTexture::Format texture_format, std::string osd_key)
{ {
bool result; Error error;
const char* extension = std::strrchr(filename.c_str(), '.');
if (extension)
{
if (GPUTexture::ConvertTextureDataToRGBA8(width, height, texture_data, texture_data_stride, texture_format))
{
if (clear_alpha)
{
for (u32& pixel : texture_data)
pixel |= 0xFF000000u;
}
if (flip_y) if (flip_y)
GPUTexture::FlipTextureDataRGBA8(width, height, reinterpret_cast<u8*>(texture_data.data()), image.FlipY();
texture_data_stride);
Assert(texture_data_stride == sizeof(u32) * width); if (image.GetFormat() != ImageFormat::RGBA8)
RGBA8Image image(width, height, std::move(texture_data));
if (image.SaveToFile(filename.c_str(), fp.get(), quality))
{ {
result = true; std::optional<Image> convert_image = image.ConvertToRGBA8(&error);
if (!convert_image.has_value())
{
ERROR_LOG("Failed to convert {} screenshot to RGBA8: {}", Image::GetFormatName(image.GetFormat()),
error.GetDescription());
image.Invalidate();
} }
else else
{ {
ERROR_LOG("Unknown extension in filename '{}' or save error: '{}'", filename, extension); image = std::move(convert_image.value());
result = false;
} }
} }
else
bool result = false;
if (image.IsValid())
{ {
result = false; if (clear_alpha)
} image.SetAllPixelsOpaque();
}
else result = image.SaveToFile(path.c_str(), fp.get(), quality, &error);
{ if (!result)
ERROR_LOG("Unable to determine file extension for '{}'", filename); ERROR_LOG("Failed to save screenshot to '{}': '{}'", Path::GetFileName(path), error.GetDescription());
result = false;
} }
if (!osd_key.empty()) 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, Host::AddIconOSDMessage(std::move(osd_key), ICON_EMOJI_CAMERA,
fmt::format(result ? TRANSLATE_FS("GPU", "Saved screenshot to '{}'.") : fmt::format(result ? TRANSLATE_FS("GPU", "Saved screenshot to '{}'.") :
TRANSLATE_FS("GPU", "Failed to save 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)); 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<u32>(m_display_texture_view_y); const u32 read_y = static_cast<u32>(m_display_texture_view_y);
const u32 read_width = static_cast<u32>(m_display_texture_view_width); const u32 read_width = static_cast<u32>(m_display_texture_view_width);
const u32 read_height = static_cast<u32>(m_display_texture_view_height); const u32 read_height = static_cast<u32>(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 = Image image(read_width, read_height, read_format);
Common::AlignUpPow2(GPUTexture::GetPixelSize(m_display_texture->GetFormat()) * read_width, 4);
std::vector<u32> texture_data((texture_data_stride * read_height) / sizeof(u32));
std::unique_ptr<GPUDownloadTexture> dltex; std::unique_ptr<GPUDownloadTexture> dltex;
if (g_gpu_device->GetFeatures().memory_import) if (g_gpu_device->GetFeatures().memory_import)
{ {
dltex = dltex = g_gpu_device->CreateDownloadTexture(read_width, read_height, m_display_texture->GetFormat(),
g_gpu_device->CreateDownloadTexture(read_width, read_height, m_display_texture->GetFormat(), texture_data.data(), image.GetPixels(), image.GetStorageSize(), image.GetPitch());
texture_data.size() * sizeof(u32), texture_data_stride);
} }
if (!dltex) 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()); 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(); RestoreDeviceContext();
return false; return false;
@ -2530,17 +2517,19 @@ bool GPU::WriteDisplayTextureToFile(std::string filename)
constexpr bool clear_alpha = true; constexpr bool clear_alpha = true;
const bool flip_y = g_gpu_device->UsesLowerLeftOrigin(); const bool flip_y = g_gpu_device->UsesLowerLeftOrigin();
return CompressAndWriteTextureToFile( return CompressAndWriteTextureToFile(read_width, read_height, std::move(filename), std::move(fp),
read_width, read_height, std::move(filename), std::move(fp), g_settings.display_screenshot_quality, clear_alpha, g_settings.display_screenshot_quality, clear_alpha, flip_y, std::move(image),
flip_y, std::move(texture_data), texture_data_stride, m_display_texture->GetFormat(), std::string()); std::string());
} }
bool GPU::RenderScreenshotToBuffer(u32 width, u32 height, const GSVector4i display_rect, const GSVector4i draw_rect, bool GPU::RenderScreenshotToBuffer(u32 width, u32 height, const GSVector4i display_rect, const GSVector4i draw_rect,
bool postfx, std::vector<u32>* out_pixels, u32* out_stride, bool postfx, Image* out_image)
GPUTexture::Format* out_format)
{ {
const GPUTexture::Format hdformat = const GPUTexture::Format hdformat =
g_gpu_device->HasMainSwapChain() ? g_gpu_device->GetMainSwapChain()->GetFormat() : GPUTexture::Format::RGBA8; 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, auto render_texture = g_gpu_device->FetchAutoRecycleTexture(width, height, 1, 1, 1, GPUTexture::Type::RenderTarget,
hdformat, GPUTexture::Flags::None); 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. // TODO: this should use copy shader instead.
RenderDisplay(render_texture.get(), display_rect, draw_rect, postfx); RenderDisplay(render_texture.get(), display_rect, draw_rect, postfx);
const u32 stride = Common::AlignUpPow2(GPUTexture::GetPixelSize(hdformat) * width, sizeof(u32)); Image image(width, height, image_format);
out_pixels->resize((height * stride) / sizeof(u32));
Error error;
std::unique_ptr<GPUDownloadTexture> dltex; std::unique_ptr<GPUDownloadTexture> dltex;
if (g_gpu_device->GetFeatures().memory_import) if (g_gpu_device->GetFeatures().memory_import)
{ {
dltex = g_gpu_device->CreateDownloadTexture(width, height, hdformat, out_pixels->data(), dltex = g_gpu_device->CreateDownloadTexture(width, height, hdformat, image.GetPixels(), image.GetStorageSize(),
out_pixels->size() * sizeof(u32), stride); image.GetPitch(), &error);
} }
if (!dltex) 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; return false;
} }
} }
dltex->CopyFromTexture(0, 0, render_texture.get(), 0, 0, width, height, 0, 0, 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(); RestoreDeviceContext();
return false; return false;
} }
*out_stride = stride;
*out_format = hdformat;
RestoreDeviceContext(); RestoreDeviceContext();
*out_image = std::move(image);
return true; return true;
} }
@ -2656,11 +2644,8 @@ bool GPU::RenderScreenshotToFile(std::string path, DisplayScreenshotMode mode, u
if (width == 0 || height == 0) if (width == 0 || height == 0)
return false; return false;
std::vector<u32> pixels; Image image;
u32 pixels_stride; if (!RenderScreenshotToBuffer(width, height, display_rect, draw_rect, !internal_resolution, &image))
GPUTexture::Format pixels_format;
if (!RenderScreenshotToBuffer(width, height, display_rect, draw_rect, !internal_resolution, &pixels, &pixels_stride,
&pixels_format))
{ {
ERROR_LOG("Failed to render {}x{} screenshot", width, height); ERROR_LOG("Failed to render {}x{} screenshot", width, height);
return false; return false;
@ -2687,10 +2672,10 @@ bool GPU::RenderScreenshotToFile(std::string path, DisplayScreenshotMode mode, u
if (compress_on_thread) if (compress_on_thread)
{ {
System::QueueTaskOnThread([width, height, path = std::move(path), fp = fp.release(), quality, System::QueueTaskOnThread([width, height, path = std::move(path), fp = fp.release(), quality,
flip_y = g_gpu_device->UsesLowerLeftOrigin(), pixels = std::move(pixels), pixels_stride, flip_y = g_gpu_device->UsesLowerLeftOrigin(), image = std::move(image),
pixels_format, osd_key = std::move(osd_key)]() mutable { osd_key = std::move(osd_key)]() mutable {
CompressAndWriteTextureToFile(width, height, std::move(path), FileSystem::ManagedCFilePtr(fp), quality, true, 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(); System::RemoveSelfFromTaskThreads();
}); });
@ -2699,8 +2684,7 @@ bool GPU::RenderScreenshotToFile(std::string path, DisplayScreenshotMode mode, u
else else
{ {
return CompressAndWriteTextureToFile(width, height, std::move(path), std::move(fp), quality, true, return CompressAndWriteTextureToFile(width, height, std::move(path), std::move(fp), quality, true,
g_gpu_device->UsesLowerLeftOrigin(), std::move(pixels), pixels_stride, g_gpu_device->UsesLowerLeftOrigin(), std::move(image), std::move(osd_key));
pixels_format, 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) 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<const char*>(buffer); const char* ptr_in = static_cast<const char*>(buffer);
for (u32 row = 0; row < height; row++) for (u32 row = 0; row < height; row++)
{ {
const char* row_ptr_in = ptr_in; 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++) for (u32 col = 0; col < width; col++)
{ {
u16 src_col; u16 src_col;
std::memcpy(&src_col, row_ptr_in, sizeof(u16)); std::memcpy(&src_col, row_ptr_in, sizeof(u16));
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; ptr_in += stride;

View File

@ -24,6 +24,7 @@
#include <vector> #include <vector>
class Error; class Error;
class Image;
class SmallStringBase; class SmallStringBase;
class StateWrapper; class StateWrapper;
@ -233,8 +234,7 @@ public:
/// Renders the display, optionally with postprocessing to the specified image. /// 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 RenderScreenshotToBuffer(u32 width, u32 height, const GSVector4i display_rect, const GSVector4i draw_rect,
bool postfx, std::vector<u32>* out_pixels, u32* out_stride, bool postfx, Image* out_image);
GPUTexture::Format* out_format);
/// Helper function to save screenshot to PNG. /// Helper function to save screenshot to PNG.
bool RenderScreenshotToFile(std::string path, DisplayScreenshotMode mode, u8 quality, bool compress_on_thread, bool RenderScreenshotToFile(std::string path, DisplayScreenshotMode mode, u8 quality, bool compress_on_thread,

View File

@ -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, static void LoadTextureReplacementAliases(const ryml::ConstNodeRef& root, bool load_vram_write_replacement_aliases,
bool load_texture_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 PreloadReplacementTextures();
static void PurgeUnreferencedTexturesFromCache(); 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())) if (filename.empty() || FileSystem::FileExists(filename.c_str()))
return; return;
RGBA8Image image; Image image(width, height, ImageFormat::RGBA8);
image.SetSize(width, height);
const u16* src_pixels = reinterpret_cast<const u16*>(pixels); const u16* src_pixels = reinterpret_cast<const u16*>(pixels);
for (u32 y = 0; y < height; y++) for (u32 y = 0; y < height; y++)
{ {
u8* row_ptr = image.GetPixels();
for (u32 x = 0; x < width; x++) for (u32 x = 0; x < width; x++)
{ {
image.SetPixel(x, y, VRAMRGBA5551ToRGBA8888(*src_pixels)); const u32 pixel32 = VRAMRGBA5551ToRGBA8888(*(src_pixels++));
src_pixels++; std::memcpy(row_ptr, &pixel32, sizeof(pixel32));
row_ptr += sizeof(pixel32);
} }
} }
if (s_state.config.dump_vram_write_force_alpha_channel) if (s_state.config.dump_vram_write_force_alpha_channel)
{ image.SetAllPixelsOpaque();
for (u32 y = 0; y < height; y++)
{
for (u32 x = 0; x < width; x++)
image.SetPixel(x, y, image.GetPixel(x, y) | 0xFF000000u);
}
}
INFO_LOG("Dumping {}x{} VRAM write to '{}'", width, height, Path::GetFileName(filename)); INFO_LOG("Dumping {}x{} VRAM write to '{}'", width, height, Path::GetFileName(filename));
if (!image.SaveToFile(filename.c_str())) [[unlikely]] 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); DEV_LOG("Dumping VRAM write {:016X} [{}x{}] at {}", src_hash, width, height, rect);
RGBA8Image image(width, height); Image image(width, height, ImageFormat::RGBA8);
GPUTextureCache::DecodeTexture(mode, &g_vram[rect.top * VRAM_WIDTH + rect.left], palette_data, image.GetPixels(), GPUTextureCache::DecodeTexture(mode, &g_vram[rect.top * VRAM_WIDTH + rect.left], palette_data,
image.GetPitch(), width, height); reinterpret_cast<u32*>(image.GetPixels()), image.GetPitch(), width, height);
u32* image_pixels = image.GetPixels(); // TODO: Vectorize this.
const u32* image_pixels_end = image.GetPixels() + (width * height); u32* image_pixels = reinterpret_cast<u32*>(image.GetPixels());
const u32* image_pixels_end = image_pixels + (width * height);
if (s_state.config.dump_texture_force_alpha_channel) if (s_state.config.dump_texture_force_alpha_channel)
{ {
for (u32* pixel = image_pixels; pixel != image_pixels_end; pixel++) 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); 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()) if (it != s_state.replacement_image_cache.end())
return &it->second; return &it->second;
RGBA8Image image; Image image;
if (!image.LoadFromFile(filename.c_str())) 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; return nullptr;
} }
VERBOSE_LOG("Loaded '{}': {}x{}", Path::GetFileName(filename), image.GetWidth(), image.GetHeight()); VERBOSE_LOG("Loaded '{}': {}x{} {}", Path::GetFileName(path), image.GetWidth(), image.GetHeight(),
it = s_state.replacement_image_cache.emplace(filename, std::move(image)).first; Image::GetFormatName(image.GetFormat()));
it = s_state.replacement_image_cache.emplace(path, std::move(image)).first;
return &it->second; return &it->second;
} }
@ -3206,14 +3204,14 @@ void GPUTextureCache::ReloadTextureReplacements(bool show_info)
void GPUTextureCache::PurgeUnreferencedTexturesFromCache() void GPUTextureCache::PurgeUnreferencedTexturesFromCache()
{ {
TextureCache old_map = std::move(s_state.replacement_image_cache); 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) for (const auto& it : s_state.vram_replacements)
{ {
const auto it2 = old_map.find(it.second); const auto it2 = old_map.find(it.second);
if (it2 != old_map.end()) 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); old_map.erase(it2);
} }
} }
@ -3225,7 +3223,7 @@ void GPUTextureCache::PurgeUnreferencedTexturesFromCache()
const auto it2 = old_map.find(it.second.second); const auto it2 = old_map.find(it.second.second);
if (it2 != old_map.end()) 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); old_map.erase(it2);
} }
} }
@ -3319,9 +3317,8 @@ void GPUTextureCache::ApplyTextureReplacements(SourceKey key, HashType tex_hash,
for (const TextureReplacementSubImage& si : subimages) for (const TextureReplacementSubImage& si : subimages)
{ {
const auto temp_texture = g_gpu_device->FetchAutoRecycleTexture( std::unique_ptr<GPUTexture> temp_texture =
si.image.GetWidth(), si.image.GetHeight(), 1, 1, 1, GPUTexture::Type::Texture, REPLACEMENT_TEXTURE_FORMAT, g_gpu_device->FetchAndUploadTextureImage(si.image, GPUTexture::Flags::None);
GPUTexture::Flags::None, si.image.GetPixels(), si.image.GetPitch());
if (!temp_texture) if (!temp_texture)
continue; 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() : g_gpu_device->SetPipeline(si.invert_alpha ? s_state.replacement_semitransparent_draw_pipeline.get() :
s_state.replacement_draw_pipeline.get()); s_state.replacement_draw_pipeline.get());
g_gpu_device->Draw(3, 0); 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, g_gpu_device->CopyTextureRegion(replacement_tex.get(), 0, 0, 0, 0, s_state.replacement_texture_render_target.get(), 0,

View File

@ -5,8 +5,8 @@
#include "gpu_types.h" #include "gpu_types.h"
class Image;
class GPUTexture; class GPUTexture;
class RGBA8Image;
class StateWrapper; class StateWrapper;
struct Settings; struct Settings;
@ -29,7 +29,7 @@ enum class PaletteRecordFlags : u32
IMPLEMENT_ENUM_CLASS_BITWISE_OPERATORS(PaletteRecordFlags); IMPLEMENT_ENUM_CLASS_BITWISE_OPERATORS(PaletteRecordFlags);
using HashType = u64; using HashType = u64;
using TextureReplacementImage = RGBA8Image; using TextureReplacementImage = Image;
struct Source; struct Source;
struct HashCacheEntry; struct HashCacheEntry;

View File

@ -128,7 +128,7 @@ struct SaveStateBuffer
std::string media_path; std::string media_path;
u32 media_subimage_index; u32 media_subimage_index;
u32 version; u32 version;
RGBA8Image screenshot; Image screenshot;
DynamicHeapArray<u8> state_data; DynamicHeapArray<u8> state_data;
size_t state_size; size_t state_size;
}; };
@ -2916,15 +2916,14 @@ bool System::LoadStateBufferFromFile(SaveStateBuffer* buffer, std::FILE* fp, Err
// Read screenshot if requested. // Read screenshot if requested.
if (read_screenshot) if (read_screenshot)
{ {
buffer->screenshot.SetSize(header.screenshot_width, header.screenshot_height); buffer->screenshot.Resize(header.screenshot_width, header.screenshot_height, ImageFormat::RGBA8, true);
const u32 uncompressed_size = buffer->screenshot.GetPitch() * buffer->screenshot.GetHeight(); const u32 compressed_size =
const u32 compressed_size = (header.version >= 69) ? header.screenshot_compressed_size : uncompressed_size; (header.version >= 69) ? header.screenshot_compressed_size : buffer->screenshot.GetStorageSize();
const SAVE_STATE_HEADER::CompressionType compression_type = const SAVE_STATE_HEADER::CompressionType compression_type =
(header.version >= 69) ? static_cast<SAVE_STATE_HEADER::CompressionType>(header.screenshot_compression_type) : (header.version >= 69) ? static_cast<SAVE_STATE_HEADER::CompressionType>(header.screenshot_compression_type) :
SAVE_STATE_HEADER::CompressionType::None; SAVE_STATE_HEADER::CompressionType::None;
if (!ReadAndDecompressStateData( if (!ReadAndDecompressStateData(fp, buffer->screenshot.GetPixelsSpan(), header.offset_to_screenshot,
fp, std::span<u8>(reinterpret_cast<u8*>(buffer->screenshot.GetPixels()), uncompressed_size), compressed_size, compression_type, error)) [[unlikely]]
header.offset_to_screenshot, compressed_size, compression_type, error)) [[unlikely]]
{ {
return false; 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()); screenshot_display_rect = screenshot_display_rect.sub32(screenshot_display_rect.xyxy());
VERBOSE_LOG("Saving {}x{} screenshot for state", screenshot_width, screenshot_height); VERBOSE_LOG("Saving {}x{} screenshot for state", screenshot_width, screenshot_height);
std::vector<u32> screenshot_buffer;
u32 screenshot_stride;
GPUTexture::Format screenshot_format;
if (g_gpu->RenderScreenshotToBuffer(screenshot_width, screenshot_height, screenshot_display_rect, if (g_gpu->RenderScreenshotToBuffer(screenshot_width, screenshot_height, screenshot_display_rect,
screenshot_draw_rect, false, &screenshot_buffer, &screenshot_stride, screenshot_draw_rect, false, &buffer->screenshot))
&screenshot_format) &&
GPUTexture::ConvertTextureDataToRGBA8(screenshot_width, screenshot_height, screenshot_buffer, screenshot_stride,
screenshot_format))
{ {
if (screenshot_stride != (screenshot_width * sizeof(u32))) if (g_gpu_device->UsesLowerLeftOrigin())
buffer->screenshot.FlipY();
// Ensure it's RGBA8.
if (buffer->screenshot.GetFormat() != ImageFormat::RGBA8)
{ {
WARNING_LOG("Failed to save {}x{} screenshot for save state due to incorrect stride({})", screenshot_width, Error convert_error;
screenshot_height, screenshot_stride); std::optional<Image> 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 else
{ {
if (g_gpu_device->UsesLowerLeftOrigin()) buffer->screenshot = std::move(screenshot_rgba8.value());
{
GPUTexture::FlipTextureDataRGBA8(screenshot_width, screenshot_height,
reinterpret_cast<u8*>(screenshot_buffer.data()), screenshot_stride);
} }
buffer->screenshot.SetPixels(screenshot_width, screenshot_height, std::move(screenshot_buffer));
} }
} }
else else

View File

@ -74,7 +74,7 @@ struct ExtendedSaveStateInfo
std::string media_path; std::string media_path;
std::time_t timestamp; std::time_t timestamp;
RGBA8Image screenshot; Image screenshot;
}; };
namespace System { namespace System {

View File

@ -4,6 +4,7 @@
#include "gpu_device.h" #include "gpu_device.h"
#include "compress_helpers.h" #include "compress_helpers.h"
#include "gpu_framebuffer_manager.h" #include "gpu_framebuffer_manager.h"
#include "image.h"
#include "shadergen.h" #include "shadergen.h"
#include "common/assert.h" #include "common/assert.h"
@ -1050,6 +1051,27 @@ GPUDevice::FetchAutoRecycleTexture(u32 width, u32 height, u32 layers, u32 levels
return std::unique_ptr<GPUTexture, PooledTextureDeleter>(ret.release()); return std::unique_ptr<GPUTexture, PooledTextureDeleter>(ret.release());
} }
std::unique_ptr<GPUTexture> GPUDevice::FetchAndUploadTextureImage(const Image& image,
GPUTexture::Flags flags /*= GPUTexture::Flags::None*/,
Error* error /*= nullptr*/)
{
const Image* image_to_upload = &image;
GPUTexture::Format gpu_format = GPUTexture::GetTextureFormatForImageFormat(image.GetFormat());
std::optional<Image> 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<GPUTexture> texture) void GPUDevice::RecycleTexture(std::unique_ptr<GPUTexture> texture)
{ {
if (!texture) if (!texture)

View File

@ -24,6 +24,7 @@
#include <vector> #include <vector>
class Error; class Error;
class Image;
enum class RenderAPI : u8 enum class RenderAPI : u8
{ {
@ -707,6 +708,9 @@ public:
FetchAutoRecycleTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, GPUTexture::Type type, FetchAutoRecycleTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, GPUTexture::Type type,
GPUTexture::Format format, GPUTexture::Flags flags, const void* data = nullptr, GPUTexture::Format format, GPUTexture::Flags flags, const void* data = nullptr,
u32 data_stride = 0, Error* error = nullptr); u32 data_stride = 0, Error* error = nullptr);
std::unique_ptr<GPUTexture> FetchAndUploadTextureImage(const Image& image,
GPUTexture::Flags flags = GPUTexture::Flags::None,
Error* error = nullptr);
void RecycleTexture(std::unique_ptr<GPUTexture> texture); void RecycleTexture(std::unique_ptr<GPUTexture> texture);
void PurgeTexturePool(); void PurgeTexturePool();

View File

@ -3,6 +3,7 @@
#include "gpu_texture.h" #include "gpu_texture.h"
#include "gpu_device.h" #include "gpu_device.h"
#include "image.h"
#include "common/align.h" #include "common/align.h"
#include "common/assert.h" #include "common/assert.h"
@ -123,6 +124,56 @@ u32 GPUTexture::GetFullMipmapCount(u32 width, u32 height)
return (std::countr_zero(max_dim) + 1); return (std::countr_zero(max_dim) + 1);
} }
GPUTexture::Format GPUTexture::GetTextureFormatForImageFormat(ImageFormat format)
{
static constexpr const std::array<Format, static_cast<size_t>(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<size_t>(format)];
}
ImageFormat GPUTexture::GetImageFormatForTextureFormat(Format format)
{
static constexpr const std::array<ImageFormat, static_cast<size_t>(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<size_t>(format)];
}
std::array<float, 4> GPUTexture::GetUNormClearColor() const std::array<float, 4> GPUTexture::GetUNormClearColor() const
{ {
return GPUDevice::RGBA8ToFloat(m_clear_value.color); 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; return true;
} }
bool GPUTexture::ConvertTextureDataToRGBA8(u32 width, u32 height, std::vector<u32>& 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<u8*>(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<u32> temp(width * height);
for (u32 y = 0; y < height; y++)
{
const u8* pixels_in = reinterpret_cast<const u8*>(texture_data.data()) + (y * texture_data_stride);
u8* pixels_out = reinterpret_cast<u8*>(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<u32> temp(width * height);
for (u32 y = 0; y < height; y++)
{
const u8* pixels_in = reinterpret_cast<const u8*>(texture_data.data()) + (y * texture_data_stride);
u8* pixels_out = reinterpret_cast<u8*>(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<u8[]> temp = std::make_unique<u8[]>(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() void GPUTexture::MakeReadyForSampling()
{ {
} }

View File

@ -13,6 +13,8 @@
class Error; class Error;
enum class ImageFormat : u8;
class GPUTexture class GPUTexture
{ {
public: public:
@ -100,13 +102,12 @@ public:
static u32 CalcUploadSize(Format format, u32 height, u32 pitch); static u32 CalcUploadSize(Format format, u32 height, u32 pitch);
static u32 GetFullMipmapCount(u32 width, u32 height); 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, static bool ValidateConfig(u32 width, u32 height, u32 layers, u32 levels, u32 samples, Type type, Format format,
Flags flags, Error* error); Flags flags, Error* error);
static bool ConvertTextureDataToRGBA8(u32 width, u32 height, std::vector<u32>& 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 GetWidth() const { return m_width; }
ALWAYS_INLINE u32 GetHeight() const { return m_height; } ALWAYS_INLINE u32 GetHeight() const { return m_height; }
ALWAYS_INLINE u32 GetLayers() const { return m_layers; } ALWAYS_INLINE u32 GetLayers() const { return m_layers; }

View File

@ -10,6 +10,7 @@
#include "common/file_system.h" #include "common/file_system.h"
#include "common/gsvector.h" #include "common/gsvector.h"
#include "common/heap_array.h" #include "common/heap_array.h"
#include "common/intrin.h"
#include "common/log.h" #include "common/log.h"
#include "common/path.h" #include "common/path.h"
#include "common/scoped_guard.h" #include "common/scoped_guard.h"
@ -30,28 +31,28 @@
LOG_CHANNEL(Image); LOG_CHANNEL(Image);
static bool PNGBufferLoader(RGBA8Image* image, std::span<const u8> data, Error* error); static bool PNGBufferLoader(Image* image, std::span<const u8> data, Error* error);
static bool PNGBufferSaver(const RGBA8Image& image, DynamicHeapArray<u8>* data, u8 quality, Error* error); static bool PNGBufferSaver(const Image& image, DynamicHeapArray<u8>* data, u8 quality, Error* error);
static bool PNGFileLoader(RGBA8Image* image, std::string_view filename, std::FILE* fp, Error* error); static bool PNGFileLoader(Image* 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 PNGFileSaver(const Image& image, std::string_view filename, std::FILE* fp, u8 quality, Error* error);
static bool JPEGBufferLoader(RGBA8Image* image, std::span<const u8> data, Error* error); static bool JPEGBufferLoader(Image* image, std::span<const u8> data, Error* error);
static bool JPEGBufferSaver(const RGBA8Image& image, DynamicHeapArray<u8>* data, u8 quality, Error* error); static bool JPEGBufferSaver(const Image& image, DynamicHeapArray<u8>* data, u8 quality, Error* error);
static bool JPEGFileLoader(RGBA8Image* image, std::string_view filename, std::FILE* fp, Error* error); static bool JPEGFileLoader(Image* 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 JPEGFileSaver(const Image& image, std::string_view filename, std::FILE* fp, u8 quality, Error* error);
static bool WebPBufferLoader(RGBA8Image* image, std::span<const u8> data, Error* error); static bool WebPBufferLoader(Image* image, std::span<const u8> data, Error* error);
static bool WebPBufferSaver(const RGBA8Image& image, DynamicHeapArray<u8>* data, u8 quality, Error* error); static bool WebPBufferSaver(const Image& image, DynamicHeapArray<u8>* data, u8 quality, Error* error);
static bool WebPFileLoader(RGBA8Image* image, std::string_view filename, std::FILE* fp, Error* error); static bool WebPFileLoader(Image* 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 WebPFileSaver(const Image& image, std::string_view filename, std::FILE* fp, u8 quality, Error* error);
struct FormatHandler struct FormatHandler
{ {
const char* extension; const char* extension;
bool (*buffer_loader)(RGBA8Image*, std::span<const u8>, Error*); bool (*buffer_loader)(Image*, std::span<const u8>, Error*);
bool (*buffer_saver)(const RGBA8Image&, DynamicHeapArray<u8>*, u8, Error*); bool (*buffer_saver)(const Image&, DynamicHeapArray<u8>*, u8, Error*);
bool (*file_loader)(RGBA8Image*, std::string_view, std::FILE*, Error*); bool (*file_loader)(Image*, std::string_view, std::FILE*, Error*);
bool (*file_saver)(const RGBA8Image&, std::string_view, std::FILE*, u8, Error*); bool (*file_saver)(const Image&, std::string_view, std::FILE*, u8, Error*);
}; };
static constexpr FormatHandler s_format_handlers[] = { static constexpr FormatHandler s_format_handlers[] = {
@ -72,41 +73,254 @@ static const FormatHandler* GetFormatHandler(std::string_view extension)
return nullptr; 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<u32> 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<u8[]>(
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<u32>::operator=(copy); SetPixels(copy.m_width, copy.m_height, copy.m_format, copy.m_pixels.get(), copy.m_pitch);
return *this; return *this;
} }
RGBA8Image& RGBA8Image::operator=(RGBA8Image&& move) Image& Image::operator=(Image&& move)
{ {
Image<u32>::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; return *this;
} }
bool RGBA8Image::LoadFromFile(const char* filename, Error* error /* = nullptr */) const char* Image::GetFormatName(ImageFormat format)
{
static constexpr std::array<const char*, static_cast<size_t>(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<size_t>(format)];
}
u32 Image::GetPixelSize(ImageFormat format)
{
static constexpr std::array<u8, static_cast<size_t>(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<size_t>(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<const u8> Image::GetPixelsSpan() const
{
return std::span<const u8>(m_pixels.get(), GetStorageSize());
}
std::span<u8> Image::GetPixelsSpan()
{
return std::span<u8>(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); auto fp = FileSystem::OpenManagedCFile(filename, "rb", error);
if (!fp) if (!fp)
@ -115,7 +329,7 @@ bool RGBA8Image::LoadFromFile(const char* filename, Error* error /* = nullptr */
return LoadFromFile(filename, fp.get(), error); return LoadFromFile(filename, fp.get(), error);
} }
bool RGBA8Image::SaveToFile(const char* filename, u8 quality /* = DEFAULT_SAVE_QUALITY */, bool Image::SaveToFile(const char* filename, u8 quality /* = DEFAULT_SAVE_QUALITY */,
Error* error /* = nullptr */) const Error* error /* = nullptr */) const
{ {
auto fp = FileSystem::OpenManagedCFile(filename, "wb", error); auto fp = FileSystem::OpenManagedCFile(filename, "wb", error);
@ -131,7 +345,7 @@ bool RGBA8Image::SaveToFile(const char* filename, u8 quality /* = DEFAULT_SAVE_Q
return false; 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 std::string_view extension(Path::GetExtension(filename));
const FormatHandler* handler = GetFormatHandler(extension); 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); return handler->file_loader(this, filename, fp, error);
} }
bool RGBA8Image::LoadFromBuffer(std::string_view filename, std::span<const u8> data, Error* error /* = nullptr */) bool Image::LoadFromBuffer(std::string_view filename, std::span<const u8> data, Error* error /* = nullptr */)
{ {
const std::string_view extension(Path::GetExtension(filename)); const std::string_view extension(Path::GetExtension(filename));
const FormatHandler* handler = GetFormatHandler(extension); const FormatHandler* handler = GetFormatHandler(extension);
@ -157,7 +371,7 @@ bool RGBA8Image::LoadFromBuffer(std::string_view filename, std::span<const u8> d
return handler->buffer_loader(this, data, error); return handler->buffer_loader(this, data, error);
} }
bool RGBA8Image::RasterizeSVG(const std::span<const u8> data, u32 width, u32 height, Error* error) bool Image::RasterizeSVG(const std::span<const u8> data, u32 width, u32 height, Error* error)
{ {
if (width == 0 || height == 0) if (width == 0 || height == 0)
{ {
@ -181,12 +395,14 @@ bool RGBA8Image::RasterizeSVG(const std::span<const u8> data, u32 width, u32 hei
return false; return false;
} }
SetPixels(width, height, lunasvg_bitmap_data(bitmap.get()), lunasvg_bitmap_stride(bitmap.get())); // lunasvg works in BGRA, swap to RGBA
SwapBGRAToRGBA(m_pixels.data(), m_width, m_height, GetPitch()); 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; return true;
} }
bool RGBA8Image::SaveToFile(std::string_view filename, std::FILE* fp, u8 quality /* = DEFAULT_SAVE_QUALITY */, bool Image::SaveToFile(std::string_view filename, std::FILE* fp, u8 quality /* = DEFAULT_SAVE_QUALITY */,
Error* error /* = nullptr */) const Error* error /* = nullptr */) const
{ {
const std::string_view extension(Path::GetExtension(filename)); const std::string_view extension(Path::GetExtension(filename));
@ -209,7 +425,7 @@ bool RGBA8Image::SaveToFile(std::string_view filename, std::FILE* fp, u8 quality
return true; return true;
} }
std::optional<DynamicHeapArray<u8>> RGBA8Image::SaveToBuffer(std::string_view filename, std::optional<DynamicHeapArray<u8>> Image::SaveToBuffer(std::string_view filename,
u8 quality /* = DEFAULT_SAVE_QUALITY */, u8 quality /* = DEFAULT_SAVE_QUALITY */,
Error* error /* = nullptr */) const Error* error /* = nullptr */) const
{ {
@ -230,81 +446,154 @@ std::optional<DynamicHeapArray<u8>> RGBA8Image::SaveToBuffer(std::string_view fi
return ret; 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 #ifdef GSVECTOR_HAS_FAST_INT_SHUFFLE8
constexpr u32 pixels_per_vec = sizeof(GSVector4i) / 4; constexpr u32 pixels_per_vec = sizeof(GSVector4i) / 4;
const u32 aligned_width = Common::AlignDownPow2(width, pixels_per_vec); const u32 aligned_width = Common::AlignDownPow2(width, pixels_per_vec);
#endif #endif
u8* pixels_ptr = static_cast<u8*>(pixels); const u8* pixels_in_ptr = static_cast<const u8*>(pixels_in);
u8* pixels_out_ptr = static_cast<u8*>(pixels_out);
for (u32 y = 0; y < height; y++) for (u32 y = 0; y < height; y++)
{ {
u8* row_pixels_ptr = pixels_ptr; const u8* row_pixels_in_ptr = pixels_in_ptr;
u32 x; u8* row_pixels_out_ptr = pixels_out_ptr;
u32 x = 0;
#ifdef GSVECTOR_HAS_FAST_INT_SHUFFLE8 #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); static constexpr GSVector4i mask = GSVector4i::cxpr8(2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15);
GSVector4i::store<false>(row_pixels_ptr, GSVector4i::load<false>(row_pixels_ptr).shuffle8(mask)); GSVector4i::store<false>(row_pixels_out_ptr, GSVector4i::load<false>(row_pixels_in_ptr).shuffle8(mask));
row_pixels_ptr += sizeof(GSVector4i); row_pixels_in_ptr += sizeof(GSVector4i);
row_pixels_out_ptr += sizeof(GSVector4i);
} }
#endif #endif
for (x = 0; x < width; x++) for (; x < width; x++)
{ {
u32 pixel; 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); pixel = (pixel & 0xFF00FF00) | ((pixel & 0xFF) << 16) | ((pixel >> 16) & 0xFF);
std::memcpy(row_pixels_ptr, &pixel, sizeof(pixel)); std::memcpy(row_pixels_out_ptr, &pixel, sizeof(pixel));
row_pixels_ptr += 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 std::optional<Image> Image::ConvertToRGBA8(Error* error) const
void RGBA8Image::Resize(u32 new_width, u32 new_height)
{ {
if (m_width == new_width && m_height == new_height) std::optional<Image> ret;
return;
std::vector<u32> resized_texture_data(new_width * new_height); if (!IsValid())
u32 resized_texture_stride = sizeof(u32) * new_width;
if (!stbir_resize_uint8(reinterpret_cast<u8*>(m_pixels.data()), m_width, m_height, GetPitch(),
reinterpret_cast<u8*>(resized_texture_data.data()), new_width, new_height,
resized_texture_stride, 4))
{ {
Panic("stbir_resize_uint8 failed"); Error::SetStringView(error, "Image is not valid.");
return; 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) if (!IsValid())
{
SetPixels(src_image->m_width, src_image->m_height, src_image->m_pixels.data());
return; return;
}
SetSize(new_width, new_height); PixelStorage temp = Common::make_unique_aligned_for_overwrite<u8[]>(VECTOR_ALIGNMENT, m_pitch);
if (!stbir_resize_uint8(reinterpret_cast<const u8*>(src_image->m_pixels.data()), src_image->m_width, const u32 half_height = m_height / 2;
src_image->m_height, src_image->GetPitch(), reinterpret_cast<u8*>(m_pixels.data()), new_width, for (u32 flip_row = 0; flip_row < half_height; flip_row++)
new_height, GetPitch(), 4))
{ {
Panic("stbir_resize_uint8 failed"); u8* top_ptr = &m_pixels[flip_row * m_pitch];
return; 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) static void PNGSetErrorFunction(png_structp png_ptr, Error* error)
{ {
png_set_error_fn( 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); }); [](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<u32>& new_data, static bool PNGCommonLoader(Image* image, png_structp png_ptr, png_infop info_ptr, std::vector<png_bytep>& row_pointers)
std::vector<png_bytep>& row_pointers)
{ {
png_read_info(png_ptr, info_ptr); 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); png_read_update_info(png_ptr, info_ptr);
new_data.resize(width * height); image->Resize(width, height, ImageFormat::RGBA8, false);
row_pointers.reserve(height); row_pointers.reserve(height);
for (u32 y = 0; y < height; y++) for (u32 y = 0; y < height; y++)
row_pointers.push_back(reinterpret_cast<png_bytep>(new_data.data() + y * width)); row_pointers.push_back(reinterpret_cast<png_bytep>(image->GetRowPixels(y)));
png_read_image(png_ptr, row_pointers.data()); png_read_image(png_ptr, row_pointers.data());
image->SetPixels(width, height, std::move(new_data));
return true; 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); png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (!png_ptr) 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); }); ScopedGuard cleanup([&png_ptr, &info_ptr]() { png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); });
std::vector<u32> new_data;
std::vector<png_bytep> row_pointers; std::vector<png_bytep> row_pointers;
PNGSetErrorFunction(png_ptr, error); PNGSetErrorFunction(png_ptr, error);
if (setjmp(png_jmpbuf(png_ptr))) if (setjmp(png_jmpbuf(png_ptr)))
{
image->Invalidate();
return false; return false;
}
png_set_read_fn(png_ptr, fp, [](png_structp png_ptr, png_bytep data_ptr, png_size_t size) { png_set_read_fn(png_ptr, fp, [](png_structp png_ptr, png_bytep data_ptr, png_size_t size) {
std::FILE* fp = static_cast<std::FILE*>(png_get_io_ptr(png_ptr)); std::FILE* fp = static_cast<std::FILE*>(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"); 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<const u8> data, Error* error) bool PNGBufferLoader(Image* image, std::span<const u8> data, Error* error)
{ {
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (!png_ptr) if (!png_ptr)
@ -415,12 +704,14 @@ bool PNGBufferLoader(RGBA8Image* image, std::span<const u8> data, Error* error)
ScopedGuard cleanup([&png_ptr, &info_ptr]() { png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); }); ScopedGuard cleanup([&png_ptr, &info_ptr]() { png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); });
std::vector<u32> new_data;
std::vector<png_bytep> row_pointers; std::vector<png_bytep> row_pointers;
PNGSetErrorFunction(png_ptr, error); PNGSetErrorFunction(png_ptr, error);
if (setjmp(png_jmpbuf(png_ptr))) if (setjmp(png_jmpbuf(png_ptr)))
{
image->Invalidate();
return false; return false;
}
struct IOData struct IOData
{ {
@ -439,10 +730,10 @@ bool PNGBufferLoader(RGBA8Image* image, std::span<const u8> 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_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, 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); 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_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
png_infop info_ptr = nullptr; png_infop info_ptr = nullptr;
@ -493,7 +784,7 @@ bool PNGFileSaver(const RGBA8Image& image, std::string_view filename, std::FILE*
return true; return true;
} }
bool PNGBufferSaver(const RGBA8Image& image, DynamicHeapArray<u8>* data, u8 quality, Error* error) bool PNGBufferSaver(const Image& image, DynamicHeapArray<u8>* data, u8 quality, Error* error)
{ {
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
png_infop info_ptr = nullptr; png_infop info_ptr = nullptr;
@ -570,7 +861,7 @@ struct JPEGErrorHandler
} // namespace } // namespace
template<typename T> template<typename T>
static bool WrapJPEGDecompress(RGBA8Image* image, Error* error, T setup_func) static bool WrapJPEGDecompress(Image* image, Error* error, T setup_func)
{ {
std::vector<u8> scanline; std::vector<u8> scanline;
jpeg_decompress_struct info = {}; jpeg_decompress_struct info = {};
@ -611,7 +902,7 @@ static bool WrapJPEGDecompress(RGBA8Image* image, Error* error, T setup_func)
return false; 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); scanline.resize(info.image_width * 3);
u8* scanline_buffer[1] = {scanline.data()}; u8* scanline_buffer[1] = {scanline.data()};
@ -627,11 +918,13 @@ static bool WrapJPEGDecompress(RGBA8Image* image, Error* error, T setup_func)
// RGB -> RGBA // RGB -> RGBA
const u8* src_ptr = scanline.data(); 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++) 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); (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; src_ptr += 3;
} }
} }
@ -641,14 +934,14 @@ static bool WrapJPEGDecompress(RGBA8Image* image, Error* error, T setup_func)
return result; return result;
} }
bool JPEGBufferLoader(RGBA8Image* image, std::span<const u8> data, Error* error) bool JPEGBufferLoader(Image* image, std::span<const u8> data, Error* error)
{ {
return WrapJPEGDecompress(image, error, [data](jpeg_decompress_struct& info) { return WrapJPEGDecompress(image, error, [data](jpeg_decompress_struct& info) {
jpeg_mem_src(&info, static_cast<const unsigned char*>(data.data()), static_cast<unsigned long>(data.size())); jpeg_mem_src(&info, static_cast<const unsigned char*>(data.data()), static_cast<unsigned long>(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; static constexpr u32 BUFFER_SIZE = 16384;
@ -713,7 +1006,7 @@ bool JPEGFileLoader(RGBA8Image* image, std::string_view filename, std::FILE* fp,
} }
template<typename T> template<typename T>
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<u8> scanline; std::vector<u8> scanline;
jpeg_compress_struct info = {}; jpeg_compress_struct info = {};
@ -747,10 +1040,12 @@ static bool WrapJPEGCompress(const RGBA8Image& image, u8 quality, Error* error,
{ {
// RGBA -> RGB // RGBA -> RGB
u8* dst_ptr = scanline.data(); 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++) 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);
*(dst_ptr++) = Truncate8(rgba >> 8); *(dst_ptr++) = Truncate8(rgba >> 8);
*(dst_ptr++) = Truncate8(rgba >> 16); *(dst_ptr++) = Truncate8(rgba >> 16);
@ -769,7 +1064,7 @@ static bool WrapJPEGCompress(const RGBA8Image& image, u8 quality, Error* error,
return result; return result;
} }
bool JPEGBufferSaver(const RGBA8Image& image, DynamicHeapArray<u8>* buffer, u8 quality, Error* error) bool JPEGBufferSaver(const Image& image, DynamicHeapArray<u8>* buffer, u8 quality, Error* error)
{ {
// give enough space to avoid reallocs // give enough space to avoid reallocs
buffer->resize(image.GetWidth() * image.GetHeight() * 2); buffer->resize(image.GetWidth() * image.GetHeight() * 2);
@ -807,7 +1102,7 @@ bool JPEGBufferSaver(const RGBA8Image& image, DynamicHeapArray<u8>* buffer, u8 q
return WrapJPEGCompress(image, quality, error, [&cb](jpeg_compress_struct& info) { info.dest = &cb.mgr; }); 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; static constexpr u32 BUFFER_SIZE = 16384;
@ -864,7 +1159,7 @@ bool JPEGFileSaver(const RGBA8Image& image, std::string_view filename, std::FILE
!cb.write_error); !cb.write_error);
} }
bool WebPBufferLoader(RGBA8Image* image, std::span<const u8> data, Error* error) bool WebPBufferLoader(Image* image, std::span<const u8> data, Error* error)
{ {
int width, height; int width, height;
if (!WebPGetInfo(data.data(), data.size(), &width, &height) || width <= 0 || height <= 0) if (!WebPGetInfo(data.data(), data.size(), &width, &height) || width <= 0 || height <= 0)
@ -873,20 +1168,18 @@ bool WebPBufferLoader(RGBA8Image* image, std::span<const u8> data, Error* error)
return false; return false;
} }
std::vector<u32> pixels; image->Resize(static_cast<u32>(width), static_cast<u32>(height), ImageFormat::RGBA8, false);
pixels.resize(static_cast<u32>(width) * static_cast<u32>(height)); if (!WebPDecodeRGBAInto(data.data(), data.size(), image->GetPixels(), image->GetStorageSize(), image->GetPitch()))
if (!WebPDecodeRGBAInto(data.data(), data.size(), reinterpret_cast<u8*>(pixels.data()), sizeof(u32) * pixels.size(),
sizeof(u32) * static_cast<u32>(width)))
{ {
Error::SetStringView(error, "WebPDecodeRGBAInto() failed"); Error::SetStringView(error, "WebPDecodeRGBAInto() failed");
image->Invalidate();
return false; return false;
} }
image->SetPixels(static_cast<u32>(width), static_cast<u32>(height), std::move(pixels));
return true; return true;
} }
bool WebPBufferSaver(const RGBA8Image& image, DynamicHeapArray<u8>* data, u8 quality, Error* error) bool WebPBufferSaver(const Image& image, DynamicHeapArray<u8>* data, u8 quality, Error* error)
{ {
u8* encoded_data; u8* encoded_data;
const size_t encoded_size = const size_t encoded_size =
@ -904,7 +1197,7 @@ bool WebPBufferSaver(const RGBA8Image& image, DynamicHeapArray<u8>* data, u8 qua
return true; 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<DynamicHeapArray<u8>> data = FileSystem::ReadBinaryFile(fp, error); std::optional<DynamicHeapArray<u8>> data = FileSystem::ReadBinaryFile(fp, error);
if (!data.has_value()) 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); 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<u8> buffer; DynamicHeapArray<u8> buffer;
if (!WebPBufferSaver(image, &buffer, quality, error)) if (!WebPBufferSaver(image, &buffer, quality, error))

View File

@ -3,154 +3,87 @@
#pragma once #pragma once
#include "common/align.h"
#include "common/heap_array.h" #include "common/heap_array.h"
#include "common/types.h" #include "common/types.h"
#include <algorithm>
#include <cstdio> #include <cstdio>
#include <cstring>
#include <optional> #include <optional>
#include <span> #include <span>
#include <string_view> #include <string_view>
#include <vector>
class Error; class Error;
template<typename PixelType> enum class ImageFormat : u8
class Image
{ {
public: None,
Image() = default; RGBA8,
Image(u32 width, u32 height) { SetSize(width, height); } BGRA8,
Image(u32 width, u32 height, const PixelType* pixels) { SetPixels(width, height, pixels); } RGB565,
Image(u32 width, u32 height, std::vector<PixelType> pixels) { SetPixels(width, height, std::move(pixels)); } RGBA5551,
Image(const Image& copy) BC1,
{ BC2,
m_width = copy.m_width; BC3,
m_height = copy.m_height; BC7,
m_pixels = copy.m_pixels; MaxCount,
}
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<PixelType>(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<PixelType>(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<PixelType> 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<const PixelType*>(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<const u8*>(data);
for (u32 row = 0; row < height; row++)
{
std::memcpy(out_ptr, in_ptr, copy_width);
out_ptr += width;
in_ptr += stride;
}
}
std::vector<PixelType> TakePixels()
{
m_width = 0;
m_height = 0;
return std::move(m_pixels);
}
protected:
u32 m_width = 0;
u32 m_height = 0;
std::vector<PixelType> m_pixels;
}; };
class RGBA8Image : public Image<u32> class Image
{ {
public: public:
static constexpr u8 DEFAULT_SAVE_QUALITY = 85; static constexpr u8 DEFAULT_SAVE_QUALITY = 85;
RGBA8Image(); public:
RGBA8Image(u32 width, u32 height); using PixelStorage = Common::unique_aligned_ptr<u8[]>;
RGBA8Image(u32 width, u32 height, const u32* pixels);
RGBA8Image(u32 width, u32 height, std::vector<u32> pixels);
RGBA8Image(const RGBA8Image& copy);
RGBA8Image(RGBA8Image&& move);
RGBA8Image& operator=(const RGBA8Image& copy); Image();
RGBA8Image& operator=(RGBA8Image&& move); 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<const u8> GetPixelsSpan() const;
std::span<u8> 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(const char* filename, Error* error = nullptr);
bool LoadFromFile(std::string_view filename, std::FILE* fp, Error* error = nullptr); bool LoadFromFile(std::string_view filename, std::FILE* fp, Error* error = nullptr);
@ -164,5 +97,14 @@ public:
std::optional<DynamicHeapArray<u8>> SaveToBuffer(std::string_view filename, u8 quality = DEFAULT_SAVE_QUALITY, std::optional<DynamicHeapArray<u8>> SaveToBuffer(std::string_view filename, u8 quality = DEFAULT_SAVE_QUALITY,
Error* error = nullptr) const; Error* error = nullptr) const;
static void SwapBGRAToRGBA(void* pixels, u32 width, u32 height, u32 pitch); std::optional<Image> 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;
}; };

View File

@ -43,8 +43,8 @@ using MessageDialogCallbackVariant = std::variant<InfoMessageDialogCallback, Con
static constexpr float MENU_BACKGROUND_ANIMATION_TIME = 0.5f; static constexpr float MENU_BACKGROUND_ANIMATION_TIME = 0.5f;
static constexpr float SMOOTH_SCROLLING_SPEED = 3.5f; static constexpr float SMOOTH_SCROLLING_SPEED = 3.5f;
static std::optional<RGBA8Image> LoadTextureImage(std::string_view path, u32 svg_width, u32 svg_height); static std::optional<Image> LoadTextureImage(std::string_view path, u32 svg_width, u32 svg_height);
static std::shared_ptr<GPUTexture> UploadTexture(std::string_view path, const RGBA8Image& image); static std::shared_ptr<GPUTexture> UploadTexture(std::string_view path, const Image& image);
static void TextureLoaderThread(); static void TextureLoaderThread();
static void DrawFileSelector(); 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::mutex s_texture_load_mutex;
static std::condition_variable s_texture_load_cv; static std::condition_variable s_texture_load_cv;
static std::deque<std::string> s_texture_load_queue; static std::deque<std::string> s_texture_load_queue;
static std::deque<std::pair<std::string, RGBA8Image>> s_texture_upload_queue; static std::deque<std::pair<std::string, Image>> s_texture_upload_queue;
static std::thread s_texture_load_thread; static std::thread s_texture_load_thread;
static SmallString s_fullscreen_footer_text; static SmallString s_fullscreen_footer_text;
@ -288,19 +288,9 @@ const std::shared_ptr<GPUTexture>& ImGuiFullscreen::GetPlaceholderTexture()
return s_placeholder_texture; return s_placeholder_texture;
} }
std::unique_ptr<GPUTexture> ImGuiFullscreen::CreateTextureFromImage(const RGBA8Image& image) std::optional<Image> ImGuiFullscreen::LoadTextureImage(std::string_view path, u32 svg_width, u32 svg_height)
{ {
std::unique_ptr<GPUTexture> ret = g_gpu_device->CreateTexture( std::optional<Image> image;
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<RGBA8Image> ImGuiFullscreen::LoadTextureImage(std::string_view path, u32 svg_width, u32 svg_height)
{
std::optional<RGBA8Image> image;
Error error; Error error;
if (StringUtil::EqualNoCase(Path::GetExtension(path), "svg")) if (StringUtil::EqualNoCase(Path::GetExtension(path), "svg"))
@ -313,7 +303,7 @@ std::optional<RGBA8Image> ImGuiFullscreen::LoadTextureImage(std::string_view pat
if (svg_data.has_value()) if (svg_data.has_value())
{ {
image = RGBA8Image(); image = Image();
if (!image->RasterizeSVG(svg_data->cspan(), svg_width, svg_height)) if (!image->RasterizeSVG(svg_data->cspan(), svg_width, svg_height))
{ {
ERROR_LOG("Failed to rasterize SVG texture file '{}': {}", path, error.GetDescription()); ERROR_LOG("Failed to rasterize SVG texture file '{}': {}", path, error.GetDescription());
@ -331,7 +321,7 @@ std::optional<RGBA8Image> ImGuiFullscreen::LoadTextureImage(std::string_view pat
auto fp = FileSystem::OpenManagedCFile(path_str.c_str(), "rb", &error); auto fp = FileSystem::OpenManagedCFile(path_str.c_str(), "rb", &error);
if (fp) if (fp)
{ {
image = RGBA8Image(); image = Image();
if (!image->LoadFromFile(path_str.c_str(), fp.get(), &error)) if (!image->LoadFromFile(path_str.c_str(), fp.get(), &error))
{ {
ERROR_LOG("Failed to read texture file '{}': {}", path, error.GetDescription()); ERROR_LOG("Failed to read texture file '{}': {}", path, error.GetDescription());
@ -348,7 +338,7 @@ std::optional<RGBA8Image> ImGuiFullscreen::LoadTextureImage(std::string_view pat
std::optional<DynamicHeapArray<u8>> data = Host::ReadResourceFile(path, true, &error); std::optional<DynamicHeapArray<u8>> data = Host::ReadResourceFile(path, true, &error);
if (data.has_value()) if (data.has_value())
{ {
image = RGBA8Image(); image = Image();
if (!image->LoadFromBuffer(path, data->cspan(), &error)) if (!image->LoadFromBuffer(path, data->cspan(), &error))
{ {
ERROR_LOG("Failed to read texture resource '{}': {}", path, error.GetDescription()); ERROR_LOG("Failed to read texture resource '{}': {}", path, error.GetDescription());
@ -364,14 +354,13 @@ std::optional<RGBA8Image> ImGuiFullscreen::LoadTextureImage(std::string_view pat
return image; return image;
} }
std::shared_ptr<GPUTexture> ImGuiFullscreen::UploadTexture(std::string_view path, const RGBA8Image& image) std::shared_ptr<GPUTexture> ImGuiFullscreen::UploadTexture(std::string_view path, const Image& image)
{ {
std::unique_ptr<GPUTexture> texture = Error error;
g_gpu_device->FetchTexture(image.GetWidth(), image.GetHeight(), 1, 1, 1, GPUTexture::Type::Texture, std::unique_ptr<GPUTexture> texture = g_gpu_device->FetchAndUploadTextureImage(image, GPUTexture::Flags::None, &error);
GPUTexture::Format::RGBA8, GPUTexture::Flags::None, image.GetPixels(), image.GetPitch());
if (!texture) 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 {}; return {};
} }
@ -381,7 +370,7 @@ std::shared_ptr<GPUTexture> ImGuiFullscreen::UploadTexture(std::string_view path
std::shared_ptr<GPUTexture> ImGuiFullscreen::LoadTexture(std::string_view path, u32 width_hint, u32 height_hint) std::shared_ptr<GPUTexture> ImGuiFullscreen::LoadTexture(std::string_view path, u32 width_hint, u32 height_hint)
{ {
std::optional<RGBA8Image> image(LoadTextureImage(path, width_hint, height_hint)); std::optional<Image> image(LoadTextureImage(path, width_hint, height_hint));
if (image.has_value()) if (image.has_value())
{ {
std::shared_ptr<GPUTexture> ret(UploadTexture(path, image.value())); std::shared_ptr<GPUTexture> ret(UploadTexture(path, image.value()));
@ -447,7 +436,7 @@ void ImGuiFullscreen::UploadAsyncTextures()
std::unique_lock lock(s_texture_load_mutex); std::unique_lock lock(s_texture_load_mutex);
while (!s_texture_upload_queue.empty()) while (!s_texture_upload_queue.empty())
{ {
std::pair<std::string, RGBA8Image> it(std::move(s_texture_upload_queue.front())); std::pair<std::string, Image> it(std::move(s_texture_upload_queue.front()));
s_texture_upload_queue.pop_front(); s_texture_upload_queue.pop_front();
lock.unlock(); lock.unlock();
@ -480,7 +469,7 @@ void ImGuiFullscreen::TextureLoaderThread()
s_texture_load_queue.pop_front(); s_texture_load_queue.pop_front();
lock.unlock(); lock.unlock();
std::optional<RGBA8Image> image(LoadTextureImage(path.c_str(), 0, 0)); std::optional<Image> image(LoadTextureImage(path.c_str(), 0, 0));
lock.lock(); lock.lock();
// don't bother queuing back if it doesn't exist // don't bother queuing back if it doesn't exist

View File

@ -18,7 +18,7 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
class RGBA8Image; class Image;
class GPUTexture; class GPUTexture;
class SmallStringBase; class SmallStringBase;
@ -129,7 +129,6 @@ void Shutdown();
/// Texture cache. /// Texture cache.
const std::shared_ptr<GPUTexture>& GetPlaceholderTexture(); const std::shared_ptr<GPUTexture>& GetPlaceholderTexture();
std::unique_ptr<GPUTexture> CreateTextureFromImage(const RGBA8Image& image);
std::shared_ptr<GPUTexture> LoadTexture(std::string_view path, u32 svg_width = 0, u32 svg_height = 0); std::shared_ptr<GPUTexture> 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);
GPUTexture* GetCachedTexture(std::string_view name, u32 svg_width, u32 svg_height); GPUTexture* GetCachedTexture(std::string_view name, u32 svg_width, u32 svg_height);

View File

@ -1242,7 +1242,7 @@ void ImGuiManager::UpdateSoftwareCursorTexture(u32 index)
} }
Error error; Error error;
RGBA8Image image; Image image;
if (!image.LoadFromFile(sc.image_path.c_str(), &error)) if (!image.LoadFromFile(sc.image_path.c_str(), &error))
{ {
ERROR_LOG("Failed to load software cursor {} image '{}': {}", index, sc.image_path, error.GetDescription()); ERROR_LOG("Failed to load software cursor {} image '{}': {}", index, sc.image_path, error.GetDescription());

View File

@ -1121,7 +1121,7 @@ bool PostProcessing::ReShadeFXShader::CreatePasses(GPUTexture::Format backbuffer
return false; return false;
} }
RGBA8Image image; Image image;
if (const std::string image_path = if (const std::string image_path =
Path::Combine(EmuFolders::Shaders, Path::Combine("reshade" FS_OSPATH_SEPARATOR_STR "Textures", source)); Path::Combine(EmuFolders::Shaders, Path::Combine("reshade" FS_OSPATH_SEPARATOR_STR "Textures", source));
!image.LoadFromFile(image_path.c_str())) !image.LoadFromFile(image_path.c_str()))