From 3bda5bbbe7b39cb6419b279ea619d2f9a89c9cb0 Mon Sep 17 00:00:00 2001 From: "Daniel K. O. (dkosmari)" Date: Fri, 13 Mar 2026 18:21:25 -0300 Subject: [PATCH 1/2] - Added error reporting when a splash image fails to load. - Some refactoring to reduce code duplication and explicit error handling. --- source/gfx/JPEGTexture.cpp | 123 +++++++++++++---------------- source/gfx/JPEGTexture.h | 6 +- source/gfx/PNGTexture.cpp | 103 ++++++++++-------------- source/gfx/PNGTexture.h | 6 +- source/gfx/SplashScreenDrawer.cpp | 125 +++++++++++++++++++++++++----- source/gfx/SplashScreenDrawer.h | 6 +- source/gfx/TGATexture.cpp | 113 +++++++++++++-------------- source/gfx/TGATexture.h | 24 ++---- source/gfx/Texture.cpp | 78 +++++++++++++++++++ source/gfx/Texture.h | 40 ++++++++++ source/gfx/WEBPTexture.cpp | 82 +++++--------------- source/gfx/WEBPTexture.h | 6 +- 12 files changed, 407 insertions(+), 305 deletions(-) create mode 100644 source/gfx/Texture.cpp create mode 100644 source/gfx/Texture.h diff --git a/source/gfx/JPEGTexture.cpp b/source/gfx/JPEGTexture.cpp index 2770085..76b8269 100644 --- a/source/gfx/JPEGTexture.cpp +++ b/source/gfx/JPEGTexture.cpp @@ -1,90 +1,73 @@ #include "JPEGTexture.h" -#include "utils/logger.h" -#include -#include -#include +#include #include -GX2Texture *JPEG_LoadTexture(std::span data) { - GX2Texture *texture = nullptr; - int height; - int width; +using namespace std::literals; - tjhandle handle = tj3Init(TJINIT_DECOMPRESS); - if (!handle) { - goto error; - } +struct TJError : std::runtime_error { + TJError(tjhandle handle) : std::runtime_error{tj3GetErrorStr(handle)} {} +}; - if (tj3DecompressHeader(handle, data.data(), data.size())) { - DEBUG_FUNCTION_LINE_ERR("Failed to parse JPEG header: %s\n", tj3GetErrorStr(handle)); - goto error; +// RAII wrapper for tjhandle +class JPEGImage { +public: + JPEGImage() : mHandle{tj3Init(TJINIT_DECOMPRESS)} { + if (!mHandle) { + throw TJError{mHandle}; + } } - width = tj3Get(handle, TJPARAM_JPEGWIDTH); - height = tj3Get(handle, TJPARAM_JPEGHEIGHT); - if (width == -1 || height == -1) { - DEBUG_FUNCTION_LINE_ERR("Unknown JPEG image size\n"); - goto error; + ~JPEGImage() noexcept { + if (mHandle) + tj3Destroy(mHandle); } - texture = static_cast(std::malloc(sizeof(GX2Texture))); - if (!texture) { - DEBUG_FUNCTION_LINE_ERR("Failed to allocate texture\n"); - goto error; + void decompressHeader(std::span data) { + if (tj3DecompressHeader(mHandle, data.data(), data.size())) { + throw TJError{mHandle}; + } } - std::memset(texture, 0, sizeof(GX2Texture)); - texture->surface.width = width; - texture->surface.height = height; - texture->surface.depth = 1; - texture->surface.mipLevels = 1; - texture->surface.format = GX2_SURFACE_FORMAT_UNORM_R8_G8_B8_A8; - texture->surface.aa = GX2_AA_MODE1X; - texture->surface.use = GX2_SURFACE_USE_TEXTURE; - texture->surface.dim = GX2_SURFACE_DIM_TEXTURE_2D; - texture->surface.tileMode = GX2_TILE_MODE_LINEAR_ALIGNED; - texture->surface.swizzle = 0; - texture->viewFirstMip = 0; - texture->viewNumMips = 1; - texture->viewFirstSlice = 0; - texture->viewNumSlices = 1; - texture->compMap = 0x0010203; - GX2CalcSurfaceSizeAndAlignment(&texture->surface); - GX2InitTextureRegs(texture); - - if (texture->surface.imageSize == 0) { - DEBUG_FUNCTION_LINE_ERR("Texture is empty\n"); - goto error; + void decompress8(std::span data, void *dst, + std::uint32_t rowStride, int pixelFormat) { + if (tj3Decompress8(mHandle, + data.data(), data.size(), + static_cast(dst), + rowStride, + pixelFormat)) { + throw TJError{mHandle}; + } } - texture->surface.image = std::aligned_alloc(texture->surface.alignment, - texture->surface.imageSize); - if (!texture->surface.image) { - DEBUG_FUNCTION_LINE_ERR("Failed to allocate surface for texture\n"); - goto error; + std::uint32_t getWidth() const { + int width = tj3Get(mHandle, TJPARAM_JPEGWIDTH); + if (width < 0) { + throw std::runtime_error{"Unknown JPEG width"}; + } + return width; } - if (tj3Decompress8(handle, - data.data(), data.size(), - static_cast(texture->surface.image), - texture->surface.pitch * 4, - TJPF_RGBA)) { - DEBUG_FUNCTION_LINE_ERR("Failed to read JPEG image: %s\n", tj3GetErrorStr(handle)); - goto error; + std::uint32_t getHeight() const { + int height = tj3Get(mHandle, TJPARAM_JPEGHEIGHT); + if (height < 0) { + throw std::runtime_error{"Unknown JPEG height"}; + } + return height; } - tj3Destroy(handle); - - GX2Invalidate(GX2_INVALIDATE_MODE_CPU | GX2_INVALIDATE_MODE_TEXTURE, - texture->surface.image, texture->surface.imageSize); - - return texture; +private: + tjhandle mHandle = nullptr; +}; // class JPEGImage -error: - if (texture) { - std::free(texture->surface.image); +std::expected JPEG_LoadTexture(std::span data) noexcept { + try { + JPEGImage jpeg; + jpeg.decompressHeader(data); + Texture texture{jpeg.getWidth(), jpeg.getHeight()}; + jpeg.decompress8(data, texture.getPixels(), texture.getRowStride(), TJPF_RGBA); + texture.flush(); + return texture; + } catch (std::exception &e) { + return std::unexpected{"[JPEGTexture] "s + e.what()}; } - std::free(texture); - tj3Destroy(handle); - return nullptr; } diff --git a/source/gfx/JPEGTexture.h b/source/gfx/JPEGTexture.h index d4cb56d..d7b26fd 100644 --- a/source/gfx/JPEGTexture.h +++ b/source/gfx/JPEGTexture.h @@ -1,7 +1,9 @@ #pragma once +#include "Texture.h" #include -#include +#include #include +#include -GX2Texture *JPEG_LoadTexture(std::span data); +std::expected JPEG_LoadTexture(std::span data) noexcept; diff --git a/source/gfx/PNGTexture.cpp b/source/gfx/PNGTexture.cpp index f7725d1..c7bcec3 100644 --- a/source/gfx/PNGTexture.cpp +++ b/source/gfx/PNGTexture.cpp @@ -1,79 +1,58 @@ #include "PNGTexture.h" -#include "utils/logger.h" -#include -#include -#include #include +#include -GX2Texture *PNG_LoadTexture(std::span data) { - GX2Texture *texture = nullptr; +using namespace std::literals; - png_image image{}; - image.version = PNG_IMAGE_VERSION; - - if (!png_image_begin_read_from_memory(&image, data.data(), data.size())) { - DEBUG_FUNCTION_LINE_ERR("Failed to parse PNG header: %s\n", image.message); - goto error; +// RAII wrapper for png_image +class PNGImage { +public: + PNGImage() { + mImage.version = PNG_IMAGE_VERSION; } - // Request the output to always be RGBA - image.format = PNG_FORMAT_RGBA; - - texture = static_cast(std::malloc(sizeof(GX2Texture))); - if (!texture) { - DEBUG_FUNCTION_LINE_ERR("Failed to allocate texture\n"); - goto error; + ~PNGImage() noexcept { + png_image_free(&mImage); } - std::memset(texture, 0, sizeof(GX2Texture)); - texture->surface.width = image.width; - texture->surface.height = image.height; - texture->surface.depth = 1; - texture->surface.mipLevels = 1; - texture->surface.format = GX2_SURFACE_FORMAT_UNORM_R8_G8_B8_A8; - texture->surface.aa = GX2_AA_MODE1X; - texture->surface.use = GX2_SURFACE_USE_TEXTURE; - texture->surface.dim = GX2_SURFACE_DIM_TEXTURE_2D; - texture->surface.tileMode = GX2_TILE_MODE_LINEAR_ALIGNED; - texture->surface.swizzle = 0; - texture->viewFirstMip = 0; - texture->viewNumMips = 1; - texture->viewFirstSlice = 0; - texture->viewNumSlices = 1; - texture->compMap = 0x0010203; - GX2CalcSurfaceSizeAndAlignment(&texture->surface); - GX2InitTextureRegs(texture); - - if (texture->surface.imageSize == 0) { - DEBUG_FUNCTION_LINE_ERR("Texture is empty\n"); - goto error; + void beginRead(std::span data) { + if (!png_image_begin_read_from_memory(&mImage, data.data(), data.size())) { + throw std::runtime_error{"Failed to parse PNG header: "s + mImage.message}; + } } - texture->surface.image = std::aligned_alloc(texture->surface.alignment, - texture->surface.imageSize); - if (!texture->surface.image) { - DEBUG_FUNCTION_LINE_ERR("Failed to allocate surface for texture\n"); - goto error; + void finishRead(png_const_colorp background, void *dst, + png_int_32 rowStride, void *colormap) { + // Request the output to always be RGBA + mImage.format = PNG_FORMAT_RGBA; + if (!png_image_finish_read(&mImage, background, dst, rowStride, nullptr)) { + throw std::runtime_error{"Failed to read PNG image: "s + mImage.message}; + } } - if (!png_image_finish_read(&image, nullptr, - texture->surface.image, - texture->surface.pitch * 4, - nullptr)) { - DEBUG_FUNCTION_LINE_ERR("Failed to read PNG image: %s\n", image.message); - goto error; + std::uint32_t + getWidth() const noexcept { + return mImage.width; } - GX2Invalidate(GX2_INVALIDATE_MODE_CPU | GX2_INVALIDATE_MODE_TEXTURE, - texture->surface.image, texture->surface.imageSize); - - return texture; + std::uint32_t getHeight() const noexcept { + return mImage.height; + } -error: - if (texture) { - std::free(texture->surface.image); +private: + png_image mImage{}; + +}; // class PNGImage + +std::expected PNG_LoadTexture(std::span data) noexcept { + try { + PNGImage png; + png.beginRead(data); + Texture texture{png.getWidth(), png.getHeight()}; + png.finishRead(nullptr, texture.getPixels(), texture.getRowStride(), nullptr); + texture.flush(); + return texture; + } catch (std::exception &e) { + return std::unexpected{"[PNGTexture] "s + e.what()}; } - std::free(texture); - png_image_free(&image); - return nullptr; } diff --git a/source/gfx/PNGTexture.h b/source/gfx/PNGTexture.h index 021faa7..fb01e91 100644 --- a/source/gfx/PNGTexture.h +++ b/source/gfx/PNGTexture.h @@ -1,7 +1,9 @@ #pragma once +#include "Texture.h" #include -#include +#include #include +#include -GX2Texture *PNG_LoadTexture(std::span data); +std::expected PNG_LoadTexture(std::span data) noexcept; diff --git a/source/gfx/SplashScreenDrawer.cpp b/source/gfx/SplashScreenDrawer.cpp index f237aad..c61f8ea 100644 --- a/source/gfx/SplashScreenDrawer.cpp +++ b/source/gfx/SplashScreenDrawer.cpp @@ -10,14 +10,24 @@ #include #include #include +#include +#include +#include +#include +#include +#include #include #include +#include #include #include #include #include +#include #include +using namespace std::literals; + /* constexpr const char *s_textureVertexShader = R"( #version 450 @@ -122,7 +132,56 @@ uint8_t empty_png[119] = { 0x0C, 0x0C, 0x00, 0x00, 0x0E, 0x00, 0x01, 0x7A, 0xB1, 0xB9, 0x30, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82}; -static GX2Texture *LoadImageAsTexture(const std::filesystem::path &filename) { +static void reportError(const std::string &msg) { + OSScreenInit(); + auto tvSize = OSScreenGetBufferSizeEx(SCREEN_TV); + auto drcSize = OSScreenGetBufferSizeEx(SCREEN_DRC); + + const auto tag = 0x000DECAF; + auto heap = MEMGetBaseHeapHandle(MEM_BASE_HEAP_MEM1); + MEMRecordStateForFrmHeap(heap, tag); + + auto tvBuffer = MEMAllocFromFrmHeapEx(heap, tvSize, 4); + auto drcBuffer = MEMAllocFromFrmHeapEx(heap, drcSize, 4); + + OSScreenSetBufferEx(SCREEN_TV, tvBuffer); + OSScreenSetBufferEx(SCREEN_DRC, drcBuffer); + + OSScreenEnableEx(SCREEN_TV, true); + OSScreenEnableEx(SCREEN_DRC, true); + + OSScreenClearBufferEx(SCREEN_TV, 0x80'00'00'ff); + OSScreenClearBufferEx(SCREEN_DRC, 0x80'00'00'ff); + + // break down the message if too long + const std::string::size_type max_line_length = 60; + if (msg.size() > max_line_length) { + int y = 0; + for (std::string::size_type i = 0; i < msg.size(); i += max_line_length, ++y) { + auto fragment = msg.substr(i, max_line_length); + OSScreenPutFontEx(SCREEN_TV, 0, y, fragment.c_str()); + OSScreenPutFontEx(SCREEN_DRC, 0, y, fragment.c_str()); + } + } else { + OSScreenPutFontEx(SCREEN_TV, 0, 0, msg.c_str()); + OSScreenPutFontEx(SCREEN_DRC, 0, 0, msg.c_str()); + } + + DCFlushRange(tvBuffer, tvSize); + DCFlushRange(drcBuffer, drcSize); + OSScreenFlipBuffersEx(SCREEN_TV); + OSScreenFlipBuffersEx(SCREEN_DRC); + + std::this_thread::sleep_for(15s); + + OSScreenEnableEx(SCREEN_TV, false); + OSScreenEnableEx(SCREEN_DRC, false); + + OSScreenShutdown(); + MEMFreeByStateToFrmHeap(heap, tag); +} + +static std::expected LoadImageAsTexture(const std::filesystem::path &filename) { std::vector buffer; if (LoadFileIntoBuffer(filename, buffer)) { auto ext = ToLower(filename.extension()); @@ -134,9 +193,10 @@ static GX2Texture *LoadImageAsTexture(const std::filesystem::path &filename) { return TGA_LoadTexture(buffer); } else if (ext == ".webp") { return WEBP_LoadTexture(buffer); - } - } - return nullptr; + } else + return std::unexpected{"Unknown file extension: "s + ext.string()}; + } else + return std::unexpected{"Unable to read file: "s + filename.string() + "\""s}; } SplashScreenDrawer::SplashScreenDrawer(const std::filesystem::path &envDir) { @@ -145,7 +205,15 @@ SplashScreenDrawer::SplashScreenDrawer(const std::filesystem::path &envDir) { // 2: Use general dir. if (!LoadTextureFrom("fs:/vol/external01/wiiu")) { // 3: Use fallback empty texture. - mTexture = PNG_LoadTexture(empty_png); + auto tex = PNG_LoadTexture(empty_png); + if (tex) { + mTexture = std::move(*tex); + } else { + // should never fail to load fallback texture, something is broken + DEBUG_FUNCTION_LINE_ERR("Could not load fallback texture: %s", + tex.error().c_str()); + abort(); + } } } @@ -205,17 +273,30 @@ bool SplashScreenDrawer::LoadTextureFrom(const std::filesystem::path &dir) { // First try the splash.* image. for (const auto &ext : extensions) { - auto fname = std::string{"splash"} + ext; - mTexture = LoadImageAsTexture(dir / fname); - if (mTexture) { + auto filename = dir / ("splash"s + ext); + if (!exists(filename)) + continue; + auto tex = LoadImageAsTexture(filename); + if (tex) { + mTexture = std::move(*tex); + return true; + } else { + DEBUG_FUNCTION_LINE_ERR("Failed to load texture \"%s\": %s\n", + filename.c_str(), + tex.error().c_str()); + reportError("Failed to load texture \"" + filename.string() + "\": " + tex.error()); return true; } } + // Second attempt, the "splashes" directory. try { + auto search_dir = dir / "splashes"; + if (!exists(search_dir)) + return false; // Make a list of all candidates in splashes/* to select one at random. std::vector candidates; - for (const auto &entry : std::filesystem::directory_iterator{dir / "splashes"}) { + for (const auto &entry : std::filesystem::directory_iterator{search_dir}) { if (!entry.is_regular_file()) { continue; } @@ -226,13 +307,23 @@ bool SplashScreenDrawer::LoadTextureFrom(const std::filesystem::path &dir) { } if (!candidates.empty()) { auto selected = GetRandomIndex(candidates.size()); - mTexture = LoadImageAsTexture(candidates[selected]); - if (mTexture) { + auto filename = candidates[selected]; + auto tex = LoadImageAsTexture(filename); + if (tex) { + mTexture = std::move(*tex); + return true; + } else { + DEBUG_FUNCTION_LINE_ERR("Failed to load texture \"%s\": %s\n", + filename.c_str(), + tex.error().c_str()); + reportError("Failed to load texture \"" + filename.string() + "\": " + tex.error()); return true; } } } catch (std::exception &e) { - DEBUG_FUNCTION_LINE_INFO("Loading texture failed: %s", e.what()); + DEBUG_FUNCTION_LINE_ERR("Loading texture failed: %s", e.what()); + reportError(e.what()); + return true; // stop trying to load a texture after this } return false; } @@ -253,7 +344,7 @@ void SplashScreenDrawer::Draw() { GX2RSetAttributeBuffer(&mPositionBuffer, 0, mPositionBuffer.elemSize, 0); GX2RSetAttributeBuffer(&mTexCoordBuffer, 1, mTexCoordBuffer.elemSize, 0); - GX2SetPixelTexture(mTexture, mShaderGroup.pixelShader->samplerVars[0].location); + GX2SetPixelTexture(mTexture.get(), mShaderGroup.pixelShader->samplerVars[0].location); GX2SetPixelSampler(&mSampler, mShaderGroup.pixelShader->samplerVars[0].location); GX2DrawEx(GX2_PRIMITIVE_MODE_QUADS, 4, 0, 1); @@ -264,12 +355,4 @@ void SplashScreenDrawer::Draw() { SplashScreenDrawer::~SplashScreenDrawer() { GX2RDestroyBufferEx(&mPositionBuffer, GX2R_RESOURCE_BIND_NONE); GX2RDestroyBufferEx(&mTexCoordBuffer, GX2R_RESOURCE_BIND_NONE); - if (mTexture) { - if (mTexture->surface.image != nullptr) { - std::free(mTexture->surface.image); - mTexture->surface.image = nullptr; - } - std::free(mTexture); - mTexture = nullptr; - } } diff --git a/source/gfx/SplashScreenDrawer.h b/source/gfx/SplashScreenDrawer.h index 5776ab8..9291ef0 100644 --- a/source/gfx/SplashScreenDrawer.h +++ b/source/gfx/SplashScreenDrawer.h @@ -1,11 +1,11 @@ #pragma once #include "ShaderSerializer.h" +#include "Texture.h" #include "gfx.h" #include #include #include -#include #include #include @@ -45,8 +45,8 @@ class SplashScreenDrawer { std::unique_ptr mPixelShaderWrapper; GX2RBuffer mPositionBuffer = {}; GX2RBuffer mTexCoordBuffer = {}; - GX2Texture *mTexture = nullptr; - GX2Sampler mSampler = {}; + Texture mTexture; + GX2Sampler mSampler = {}; void InitResources(); diff --git a/source/gfx/TGATexture.cpp b/source/gfx/TGATexture.cpp index 9989cd0..8771ee8 100644 --- a/source/gfx/TGATexture.cpp +++ b/source/gfx/TGATexture.cpp @@ -1,11 +1,9 @@ -#include -#include -#include -#include - #include "TGATexture.h" -#include "utils/logger.h" -#include +#include +#include +#include + +using namespace std::literals; /* * Based on @@ -14,71 +12,64 @@ * https://github.com/Crementif/WiiU-GX2-Shader-Examples/blob/5a88f861043dcb7666d4d25a6bab6bd271e76d5f/include/TGATexture.h */ -uint16_t inline _swapU16(uint16_t v) { - return (v >> 8) | (v << 8); -} +struct WUT_PACKED TGA_HEADER { + uint8_t identsize; // size of ID field that follows 18 byte header (0 usually) + uint8_t colourmaptype; // type of colour map 0=none, 1=has palette + uint8_t imagetype; // type of image 0=none,1=indexed,2=rgb,3=grey,+8=rle packed -GX2Texture *TGA_LoadTexture(std::span data) { - TGA_HEADER *tgaHeader = (TGA_HEADER *) data.data(); + uint8_t colourmapstart[2]; // first colour map entry in palette + uint8_t colourmaplength[2]; // number of colours in palette + uint8_t colourmapbits; // number of bits per palette entry 15,16,24,32 - uint32_t width = _swapU16(tgaHeader->width); - uint32_t height = _swapU16(tgaHeader->height); + uint16_t xstart; // image x origin + uint16_t ystart; // image y origin + uint16_t width; // image width in pixels + uint16_t height; // image height in pixels + uint8_t bits; // image bits per pixel 8,16,24,32 + uint8_t descriptor; // image descriptor bits (vh flip bits) +}; - if (tgaHeader->bits != 24) { - DEBUG_FUNCTION_LINE_INFO("Only 24bit TGA images are supported"); - return nullptr; - } - if (tgaHeader->imagetype != 2 && tgaHeader->imagetype != 3) { - DEBUG_FUNCTION_LINE_INFO("Only uncompressed TGA images are supported"); - return nullptr; - } +uint16_t inline _swapU16(uint16_t v) { + return (v >> 8) | (v << 8); +} - GX2Texture *texture = (GX2Texture *) malloc(sizeof(GX2Texture)); - *texture = {}; +std::expected TGA_LoadTexture(std::span data) noexcept { + try { + TGA_HEADER tgaHeader; + if (data.size() < sizeof tgaHeader) + throw std::runtime_error{"Truncated TGA image"}; + std::memcpy(&tgaHeader, data.data(), sizeof tgaHeader); - texture->surface.width = width; - texture->surface.height = height; - texture->surface.depth = 1; - texture->surface.mipLevels = 1; - texture->surface.format = GX2_SURFACE_FORMAT_UNORM_R8_G8_B8_A8; - texture->surface.aa = GX2_AA_MODE1X; - texture->surface.use = GX2_SURFACE_USE_TEXTURE; - texture->surface.dim = GX2_SURFACE_DIM_TEXTURE_2D; - texture->surface.tileMode = GX2_TILE_MODE_LINEAR_ALIGNED; - texture->surface.swizzle = 0; - texture->viewFirstMip = 0; - texture->viewNumMips = 1; - texture->viewFirstSlice = 0; - texture->viewNumSlices = 1; - texture->compMap = 0x0010203; - GX2CalcSurfaceSizeAndAlignment(&texture->surface); - GX2InitTextureRegs(texture); + uint32_t width = _swapU16(tgaHeader.width); + uint32_t height = _swapU16(tgaHeader.height); - if (texture->surface.imageSize == 0) { - return nullptr; - } + if (tgaHeader.bits != 24) { + throw std::runtime_error{"Only 24bit TGA images are supported"}; + } + if (tgaHeader.imagetype != 2 && tgaHeader.imagetype != 3) { + throw std::runtime_error{"Only uncompressed TGA images are supported"}; + } - texture->surface.image = memalign(texture->surface.alignment, texture->surface.imageSize); - if (!texture->surface.image) { - return nullptr; - } + Texture texture{width, height}; - for (uint32_t y = 0; y < height; y++) { - uint32_t *out_data = (uint32_t *) texture->surface.image + (y * texture->surface.pitch); - for (uint32_t x = 0; x < width; x++) { - int index = sizeof(TGA_HEADER) + (3 * width * (height - 1 - y)) + (3 * x); + for (uint32_t y = 0; y < height; y++) { + uint32_t *row = texture.getRow(y); + for (uint32_t x = 0; x < width; x++) { + size_t index = sizeof(TGA_HEADER) + (3 * width * (height - 1 - y)) + (3 * x); - int b = data[index + 0] & 0xFF; - int g = data[index + 1] & 0xFF; - int r = data[index + 2] & 0xFF; + int b = data[index + 0] & 0xFF; + int g = data[index + 1] & 0xFF; + int r = data[index + 2] & 0xFF; - *out_data = r << 24 | g << 16 | b << 8 | 0xFF; - out_data++; + row[x] = r << 24 | g << 16 | b << 8 | 0xFF; + } } - } - // todo: create texture with optimal tile format and use GX2CopySurface to convert from linear to tiled format - GX2Invalidate(GX2_INVALIDATE_MODE_CPU | GX2_INVALIDATE_MODE_TEXTURE, texture->surface.image, texture->surface.imageSize); + // todo: create texture with optimal tile format and use GX2CopySurface to convert from linear to tiled format - return texture; + texture.flush(); + return texture; + } catch (std::exception &e) { + return std::unexpected{"[TGATexture] "s + e.what()}; + } } diff --git a/source/gfx/TGATexture.h b/source/gfx/TGATexture.h index bcc0e94..9c89a8b 100644 --- a/source/gfx/TGATexture.h +++ b/source/gfx/TGATexture.h @@ -1,24 +1,10 @@ #pragma once +#include "Texture.h" #include -#include - -struct WUT_PACKED TGA_HEADER { - uint8_t identsize; // size of ID field that follows 18 byte header (0 usually) - uint8_t colourmaptype; // type of colour map 0=none, 1=has palette - uint8_t imagetype; // type of image 0=none,1=indexed,2=rgb,3=grey,+8=rle packed - - uint8_t colourmapstart[2]; // first colour map entry in palette - uint8_t colourmaplength[2]; // number of colours in palette - uint8_t colourmapbits; // number of bits per palette entry 15,16,24,32 - - uint16_t xstart; // image x origin - uint16_t ystart; // image y origin - uint16_t width; // image width in pixels - uint16_t height; // image height in pixels - uint8_t bits; // image bits per pixel 8,16,24,32 - uint8_t descriptor; // image descriptor bits (vh flip bits) -}; +#include +#include +#include // quick and dirty 24-bit TGA loader -GX2Texture *TGA_LoadTexture(std::span data); \ No newline at end of file +std::expected TGA_LoadTexture(std::span data) noexcept; diff --git a/source/gfx/Texture.cpp b/source/gfx/Texture.cpp new file mode 100644 index 0000000..e56bcd7 --- /dev/null +++ b/source/gfx/Texture.cpp @@ -0,0 +1,78 @@ +#include "Texture.h" +#include +#include +#include +#include + +Texture::Texture(std::uint32_t width, std::uint32_t height) : mTexture{std::make_unique()} { + std::memset(mTexture.get(), 0, sizeof *mTexture); + mTexture->surface.width = width; + mTexture->surface.height = height; + mTexture->surface.depth = 1; + mTexture->surface.mipLevels = 1; + mTexture->surface.format = GX2_SURFACE_FORMAT_UNORM_R8_G8_B8_A8; + mTexture->surface.aa = GX2_AA_MODE1X; + mTexture->surface.use = GX2_SURFACE_USE_TEXTURE; + mTexture->surface.dim = GX2_SURFACE_DIM_TEXTURE_2D; + mTexture->surface.tileMode = GX2_TILE_MODE_LINEAR_ALIGNED; + mTexture->surface.swizzle = 0; + mTexture->viewFirstMip = 0; + mTexture->viewNumMips = 1; + mTexture->viewFirstSlice = 0; + mTexture->viewNumSlices = 1; + mTexture->compMap = 0x0010203; + GX2CalcSurfaceSizeAndAlignment(&mTexture->surface); + GX2InitTextureRegs(mTexture.get()); + + if (mTexture->surface.imageSize == 0) { + throw std::runtime_error{"Texture is empty"}; + } + + mTexture->surface.image = std::aligned_alloc(mTexture->surface.alignment, + mTexture->surface.imageSize); + if (!mTexture->surface.image) { + throw std::runtime_error{"Failed to allocate surface for texture"}; + } +} + +Texture::~Texture() noexcept { + if (mTexture) { + std::free(mTexture->surface.image); + } +} + +GX2Texture * +Texture::get() noexcept { + return mTexture.get(); +} + +void Texture::flush() noexcept { + GX2Invalidate(GX2_INVALIDATE_MODE_CPU | GX2_INVALIDATE_MODE_TEXTURE, + getPixels(), + getSize()); +} + +void *Texture::getPixels() noexcept { + return mTexture->surface.image; +} + +std::size_t Texture::getSize() const noexcept { + return mTexture->surface.imageSize; +} + +std::uint32_t Texture::getRowPitch() noexcept { + return mTexture->surface.pitch; +} + +std::uint32_t Texture::getRowStride() noexcept { + return getRowPitch() * 4; +} + +uint32_t *Texture::getRow(uint32_t y) noexcept { + uint32_t *pixels = reinterpret_cast(getPixels()); + return &pixels[y * getRowPitch()]; +} + +Texture::operator bool() const noexcept { + return !!mTexture; +} diff --git a/source/gfx/Texture.h b/source/gfx/Texture.h new file mode 100644 index 0000000..d0e9b60 --- /dev/null +++ b/source/gfx/Texture.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include + +class Texture { +public: + // allow moving + Texture(Texture &&other) noexcept = default; + Texture &operator=(Texture &&other) noexcept = default; + + Texture() noexcept = default; + + Texture(std::uint32_t width, std::uint32_t height); + + ~Texture() noexcept; + + GX2Texture *get() noexcept; + + void flush() noexcept; + + void *getPixels() noexcept; + + std::size_t getSize() const noexcept; + + // size of a row, in pixels + std::uint32_t getRowPitch() noexcept; + + // size of a row, in bytes + std::uint32_t getRowStride() noexcept; + + uint32_t *getRow(uint32_t y) noexcept; + + explicit operator bool() const noexcept; + +private: + std::unique_ptr mTexture; + +}; // class Texture diff --git a/source/gfx/WEBPTexture.cpp b/source/gfx/WEBPTexture.cpp index 262045b..9e02f18 100644 --- a/source/gfx/WEBPTexture.cpp +++ b/source/gfx/WEBPTexture.cpp @@ -1,73 +1,29 @@ #include "WEBPTexture.h" -#include "utils/logger.h" -#include -#include -#include +#include #include -GX2Texture *WEBP_LoadTexture(std::span data) { - GX2Texture *texture = nullptr; - int width, height; +using namespace std::literals; - if (!WebPGetInfo(data.data(), data.size(), &width, &height)) { - DEBUG_FUNCTION_LINE_ERR("Failed to parse WEBP header\n"); - goto error; - } - - texture = static_cast(std::malloc(sizeof(GX2Texture))); - if (!texture) { - DEBUG_FUNCTION_LINE_ERR("Failed to allocate texture\n"); - goto error; - } +std::expected WEBP_LoadTexture(std::span data) noexcept { + try { + int width, height; - std::memset(texture, 0, sizeof(GX2Texture)); - texture->surface.width = width; - texture->surface.height = height; - texture->surface.depth = 1; - texture->surface.mipLevels = 1; - texture->surface.format = GX2_SURFACE_FORMAT_UNORM_R8_G8_B8_A8; - texture->surface.aa = GX2_AA_MODE1X; - texture->surface.use = GX2_SURFACE_USE_TEXTURE; - texture->surface.dim = GX2_SURFACE_DIM_TEXTURE_2D; - texture->surface.tileMode = GX2_TILE_MODE_LINEAR_ALIGNED; - texture->surface.swizzle = 0; - texture->viewFirstMip = 0; - texture->viewNumMips = 1; - texture->viewFirstSlice = 0; - texture->viewNumSlices = 1; - texture->compMap = 0x0010203; - GX2CalcSurfaceSizeAndAlignment(&texture->surface); - GX2InitTextureRegs(texture); - - if (texture->surface.imageSize == 0) { - DEBUG_FUNCTION_LINE_ERR("Texture is empty\n"); - goto error; - } - - texture->surface.image = std::aligned_alloc(texture->surface.alignment, - texture->surface.imageSize); - if (!texture->surface.image) { - DEBUG_FUNCTION_LINE_ERR("Failed to allocate surface for texture\n"); - goto error; - } - - if (!WebPDecodeRGBAInto(data.data(), data.size(), - reinterpret_cast(texture->surface.image), - texture->surface.imageSize, - texture->surface.pitch * 4)) { - DEBUG_FUNCTION_LINE_ERR("Failed to decode WEBP image\n"); - goto error; - } + if (!WebPGetInfo(data.data(), data.size(), &width, &height)) { + throw std::runtime_error{"Failed to parse WEBP header"}; + } - GX2Invalidate(GX2_INVALIDATE_MODE_CPU | GX2_INVALIDATE_MODE_TEXTURE, - texture->surface.image, texture->surface.imageSize); + Texture texture(width, height); - return texture; + if (!WebPDecodeRGBAInto(data.data(), data.size(), + reinterpret_cast(texture.getPixels()), + texture.getSize(), + texture.getRowStride())) { + throw std::runtime_error{"Failed to decode WEBP image"}; + } -error: - if (texture) { - std::free(texture->surface.image); + texture.flush(); + return texture; + } catch (std::exception &e) { + return std::unexpected{"[WEBPTexture] "s + e.what()}; } - std::free(texture); - return nullptr; } diff --git a/source/gfx/WEBPTexture.h b/source/gfx/WEBPTexture.h index b3180f0..2f35a83 100644 --- a/source/gfx/WEBPTexture.h +++ b/source/gfx/WEBPTexture.h @@ -1,7 +1,9 @@ #pragma once +#include "Texture.h" #include -#include +#include #include +#include -GX2Texture *WEBP_LoadTexture(std::span data); +std::expected WEBP_LoadTexture(std::span data) noexcept; From 3d8dd9c72c76b66f394a104504877b744a3c55c4 Mon Sep 17 00:00:00 2001 From: "Daniel K. O. (dkosmari)" Date: Fri, 13 Mar 2026 18:41:26 -0300 Subject: [PATCH 2/2] Fixed formatting for clang-format. --- source/gfx/Texture.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/gfx/Texture.h b/source/gfx/Texture.h index d0e9b60..eabec2e 100644 --- a/source/gfx/Texture.h +++ b/source/gfx/Texture.h @@ -7,7 +7,7 @@ class Texture { public: // allow moving - Texture(Texture &&other) noexcept = default; + Texture(Texture &&other) noexcept = default; Texture &operator=(Texture &&other) noexcept = default; Texture() noexcept = default;