Image: Refactor to a more generic class
This commit is contained in:
parent
3ff1b04576
commit
24dfd30839
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
131
src/core/gpu.cpp
131
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<u32> 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<u32> 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<Image> 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<u8*>(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<u32>(m_display_texture_view_y);
|
||||
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 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<u32> texture_data((texture_data_stride * read_height) / sizeof(u32));
|
||||
|
||||
Image image(read_width, read_height, read_format);
|
||||
std::unique_ptr<GPUDownloadTexture> 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<u32>* 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<GPUDownloadTexture> 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<u32> 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<const char*>(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;
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include <vector>
|
||||
|
||||
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<u32>* 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,
|
||||
|
|
|
@ -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<const u16*>(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<u32*>(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<u32*>(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<GPUTexture> 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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -128,7 +128,7 @@ struct SaveStateBuffer
|
|||
std::string media_path;
|
||||
u32 media_subimage_index;
|
||||
u32 version;
|
||||
RGBA8Image screenshot;
|
||||
Image screenshot;
|
||||
DynamicHeapArray<u8> 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<SAVE_STATE_HEADER::CompressionType>(header.screenshot_compression_type) :
|
||||
SAVE_STATE_HEADER::CompressionType::None;
|
||||
if (!ReadAndDecompressStateData(
|
||||
fp, std::span<u8>(reinterpret_cast<u8*>(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<u32> 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<u8*>(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<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
|
||||
{
|
||||
buffer->screenshot = std::move(screenshot_rgba8.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
|
@ -74,7 +74,7 @@ struct ExtendedSaveStateInfo
|
|||
std::string media_path;
|
||||
std::time_t timestamp;
|
||||
|
||||
RGBA8Image screenshot;
|
||||
Image screenshot;
|
||||
};
|
||||
|
||||
namespace System {
|
||||
|
|
|
@ -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<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 = ℑ
|
||||
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)
|
||||
{
|
||||
if (!texture)
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include <vector>
|
||||
|
||||
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<GPUTexture> FetchAndUploadTextureImage(const Image& image,
|
||||
GPUTexture::Flags flags = GPUTexture::Flags::None,
|
||||
Error* error = nullptr);
|
||||
void RecycleTexture(std::unique_ptr<GPUTexture> texture);
|
||||
void PurgeTexturePool();
|
||||
|
||||
|
|
|
@ -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<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
|
||||
{
|
||||
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<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()
|
||||
{
|
||||
}
|
||||
|
|
|
@ -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<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 GetHeight() const { return m_height; }
|
||||
ALWAYS_INLINE u32 GetLayers() const { return m_layers; }
|
||||
|
|
|
@ -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<const u8> data, Error* error);
|
||||
static bool PNGBufferSaver(const RGBA8Image& image, DynamicHeapArray<u8>* 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<const u8> data, Error* error);
|
||||
static bool PNGBufferSaver(const Image& image, DynamicHeapArray<u8>* 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<const u8> data, Error* error);
|
||||
static bool JPEGBufferSaver(const RGBA8Image& image, DynamicHeapArray<u8>* 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<const u8> data, Error* error);
|
||||
static bool JPEGBufferSaver(const Image& image, DynamicHeapArray<u8>* 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<const u8> data, Error* error);
|
||||
static bool WebPBufferSaver(const RGBA8Image& image, DynamicHeapArray<u8>* 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<const u8> data, Error* error);
|
||||
static bool WebPBufferSaver(const Image& image, DynamicHeapArray<u8>* 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<const u8>, Error*);
|
||||
bool (*buffer_saver)(const RGBA8Image&, DynamicHeapArray<u8>*, 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<const u8>, Error*);
|
||||
bool (*buffer_saver)(const Image&, DynamicHeapArray<u8>*, 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<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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
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<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 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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
|
@ -181,13 +395,15 @@ bool RGBA8Image::RasterizeSVG(const std::span<const u8> 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<DynamicHeapArray<u8>> RGBA8Image::SaveToBuffer(std::string_view filename,
|
||||
u8 quality /* = DEFAULT_SAVE_QUALITY */,
|
||||
Error* error /* = nullptr */) const
|
||||
std::optional<DynamicHeapArray<u8>> Image::SaveToBuffer(std::string_view filename,
|
||||
u8 quality /* = DEFAULT_SAVE_QUALITY */,
|
||||
Error* error /* = nullptr */) const
|
||||
{
|
||||
std::optional<DynamicHeapArray<u8>> ret;
|
||||
|
||||
|
@ -230,81 +446,154 @@ std::optional<DynamicHeapArray<u8>> 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<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++)
|
||||
{
|
||||
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<false>(row_pixels_ptr, GSVector4i::load<false>(row_pixels_ptr).shuffle8(mask));
|
||||
row_pixels_ptr += sizeof(GSVector4i);
|
||||
GSVector4i::store<false>(row_pixels_out_ptr, GSVector4i::load<false>(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> Image::ConvertToRGBA8(Error* error) const
|
||||
{
|
||||
if (m_width == new_width && m_height == new_height)
|
||||
return;
|
||||
std::optional<Image> ret;
|
||||
|
||||
std::vector<u32> resized_texture_data(new_width * new_height);
|
||||
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))
|
||||
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<const u8*>(src_image->m_pixels.data()), src_image->m_width,
|
||||
src_image->m_height, src_image->GetPitch(), reinterpret_cast<u8*>(m_pixels.data()), new_width,
|
||||
new_height, GetPitch(), 4))
|
||||
PixelStorage temp = Common::make_unique_aligned_for_overwrite<u8[]>(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<u32>& new_data,
|
||||
std::vector<png_bytep>& row_pointers)
|
||||
static bool PNGCommonLoader(Image* image, png_structp png_ptr, png_infop info_ptr, std::vector<png_bytep>& 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<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());
|
||||
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<u32> new_data;
|
||||
std::vector<png_bytep> 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<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");
|
||||
});
|
||||
|
||||
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);
|
||||
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); });
|
||||
|
||||
std::vector<u32> new_data;
|
||||
std::vector<png_bytep> 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<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_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<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_infop info_ptr = nullptr;
|
||||
|
@ -570,7 +861,7 @@ struct JPEGErrorHandler
|
|||
} // namespace
|
||||
|
||||
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;
|
||||
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<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) {
|
||||
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;
|
||||
|
||||
|
@ -713,7 +1006,7 @@ bool JPEGFileLoader(RGBA8Image* image, std::string_view filename, std::FILE* fp,
|
|||
}
|
||||
|
||||
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;
|
||||
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<u8>* buffer, u8 quality, Error* error)
|
||||
bool JPEGBufferSaver(const Image& image, DynamicHeapArray<u8>* 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<u8>* 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<const u8> data, Error* error)
|
||||
bool WebPBufferLoader(Image* image, std::span<const u8> 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<const u8> data, Error* error)
|
|||
return false;
|
||||
}
|
||||
|
||||
std::vector<u32> pixels;
|
||||
pixels.resize(static_cast<u32>(width) * static_cast<u32>(height));
|
||||
if (!WebPDecodeRGBAInto(data.data(), data.size(), reinterpret_cast<u8*>(pixels.data()), sizeof(u32) * pixels.size(),
|
||||
sizeof(u32) * static_cast<u32>(width)))
|
||||
image->Resize(static_cast<u32>(width), static_cast<u32>(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<u32>(width), static_cast<u32>(height), std::move(pixels));
|
||||
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;
|
||||
const size_t encoded_size =
|
||||
|
@ -904,7 +1197,7 @@ bool WebPBufferSaver(const RGBA8Image& image, DynamicHeapArray<u8>* 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<DynamicHeapArray<u8>> 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<u8> buffer;
|
||||
if (!WebPBufferSaver(image, &buffer, quality, error))
|
||||
|
@ -926,4 +1219,4 @@ bool WebPFileSaver(const RGBA8Image& image, std::string_view filename, std::FILE
|
|||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
204
src/util/image.h
204
src/util/image.h
|
@ -3,154 +3,87 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "common/align.h"
|
||||
#include "common/heap_array.h"
|
||||
#include "common/types.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
class Error;
|
||||
|
||||
template<typename PixelType>
|
||||
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<PixelType> 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<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;
|
||||
None,
|
||||
RGBA8,
|
||||
BGRA8,
|
||||
RGB565,
|
||||
RGBA5551,
|
||||
BC1,
|
||||
BC2,
|
||||
BC3,
|
||||
BC7,
|
||||
MaxCount,
|
||||
};
|
||||
|
||||
class RGBA8Image : public Image<u32>
|
||||
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<u32> pixels);
|
||||
RGBA8Image(const RGBA8Image& copy);
|
||||
RGBA8Image(RGBA8Image&& move);
|
||||
public:
|
||||
using PixelStorage = Common::unique_aligned_ptr<u8[]>;
|
||||
|
||||
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<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(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,
|
||||
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;
|
||||
};
|
||||
|
|
|
@ -43,8 +43,8 @@ using MessageDialogCallbackVariant = std::variant<InfoMessageDialogCallback, Con
|
|||
static constexpr float MENU_BACKGROUND_ANIMATION_TIME = 0.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::shared_ptr<GPUTexture> UploadTexture(std::string_view path, const RGBA8Image& image);
|
||||
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 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<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 SmallString s_fullscreen_footer_text;
|
||||
|
@ -288,19 +288,9 @@ const std::shared_ptr<GPUTexture>& ImGuiFullscreen::GetPlaceholderTexture()
|
|||
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(
|
||||
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;
|
||||
std::optional<Image> image;
|
||||
Error error;
|
||||
|
||||
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())
|
||||
{
|
||||
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<RGBA8Image> 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<RGBA8Image> ImGuiFullscreen::LoadTextureImage(std::string_view pat
|
|||
std::optional<DynamicHeapArray<u8>> 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<RGBA8Image> ImGuiFullscreen::LoadTextureImage(std::string_view pat
|
|||
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 =
|
||||
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<GPUTexture> 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<GPUTexture> ImGuiFullscreen::UploadTexture(std::string_view path
|
|||
|
||||
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())
|
||||
{
|
||||
std::shared_ptr<GPUTexture> 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<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();
|
||||
lock.unlock();
|
||||
|
||||
|
@ -480,7 +469,7 @@ void ImGuiFullscreen::TextureLoaderThread()
|
|||
s_texture_load_queue.pop_front();
|
||||
|
||||
lock.unlock();
|
||||
std::optional<RGBA8Image> image(LoadTextureImage(path.c_str(), 0, 0));
|
||||
std::optional<Image> image(LoadTextureImage(path.c_str(), 0, 0));
|
||||
lock.lock();
|
||||
|
||||
// don't bother queuing back if it doesn't exist
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
class RGBA8Image;
|
||||
class Image;
|
||||
class GPUTexture;
|
||||
class SmallStringBase;
|
||||
|
||||
|
@ -129,7 +129,6 @@ void Shutdown();
|
|||
|
||||
/// Texture cache.
|
||||
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);
|
||||
GPUTexture* GetCachedTexture(std::string_view name);
|
||||
GPUTexture* GetCachedTexture(std::string_view name, u32 svg_width, u32 svg_height);
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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()))
|
||||
|
|
Loading…
Reference in New Issue