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::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));
}
}

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),
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());

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 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;

View File

@ -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,

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,
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,

View File

@ -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;

View File

@ -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

View File

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

View File

@ -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 = &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)
{
if (!texture)

View File

@ -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();

View File

@ -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()
{
}

View File

@ -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; }

View File

@ -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))

View File

@ -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;
};

View File

@ -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

View File

@ -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);

View File

@ -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());

View File

@ -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()))