From 2ad5d0fc29dd6b90debf5c9c9968d2ad236504ae Mon Sep 17 00:00:00 2001 From: Maschell Date: Sat, 22 Feb 2025 08:58:45 +0100 Subject: [PATCH 1/7] Bump version --- source/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/main.cpp b/source/main.cpp index bf5e13c..64fef0d 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -4,7 +4,7 @@ #include "utils/utils.h" #include "version.h" -#define MODULE_VERSION "v0.2" +#define MODULE_VERSION "v0.3" #define MODULE_VERSION_FULL MODULE_VERSION SPLASHSCREEN_MODULE_VERSION_EXTRA int32_t main(int32_t argc, char **argv) { From de9aff6d37b1008268ca67b7765f428f273cf40a Mon Sep 17 00:00:00 2001 From: "Daniel K. O." Date: Sat, 22 Feb 2025 05:03:50 -0300 Subject: [PATCH 2/7] Add JPEG support for loading splash screens (#7) * Added support for loading JPEG images. * Sorted headers. * Formatting for clang-format. --------- Co-authored-by: Daniel K. O. (dkosmari) --- Makefile | 2 +- README.md | 20 +++++--- source/gfx/JPEGTexture.cpp | 84 +++++++++++++++++++++++++++++++ source/gfx/JPEGTexture.h | 7 +++ source/gfx/SplashScreenDrawer.cpp | 16 ++++-- 5 files changed, 118 insertions(+), 11 deletions(-) create mode 100644 source/gfx/JPEGTexture.cpp create mode 100644 source/gfx/JPEGTexture.h diff --git a/Makefile b/Makefile index 1b10a26..3a59eae 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,7 @@ CXXFLAGS := $(CFLAGS) -std=c++23 -fno-rtti ASFLAGS := -g $(ARCH) LDFLAGS = -g $(ARCH) $(RPXSPECS) --entry=_start -Wl,-Map,$(notdir $*.map) -LIBS := -lpng -lwut -lz +LIBS := -lpng -lturbojpeg -lwut -lz ifeq ($(DEBUG),1) CXXFLAGS += -DDEBUG -g diff --git a/README.md b/README.md index b52e6f5..d2c4f61 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,11 @@ other modules of the environment are loading. Place the `01_splashscreen.rpx` in the `[ENVIRONMENT]/modules/setup` folder and run the EnvironmentLoader. The module will attempt to load the splash image, in this order: 1. `[ENVIRONMENT]/splash.png` - 2. `[ENVIRONMENT]/splash.tga` - 3. A random image from the directory `[ENVIRONMENT]/splashes/`. + 2. `[ENVIRONMENT]/splash.jpg` or `[ENVIRONMENT]/splash.jpeg` + 3. `[ENVIRONMENT]/splash.tga` + 4. A random image (PNG, JPEG or TGA) from the directory `[ENVIRONMENT]/splashes/`. -If no splash screen is found on the sd card, this module will effectively do nothing. +If no splash image is found on the sd card, this module will effectively do nothing. **Notes:** - `[ENVIRONMENT]` is the directory of the environment, for Aroma with would be `sd:/wiiu/enviroments/aroma/splash.png` @@ -22,14 +23,19 @@ If no splash screen is found on the sd card, this module will effectively do not ### Logging Building via `make` only logs errors (via OSReport). To enable logging via the [LoggingModule](https://github.com/wiiu-env/LoggingModule) set `DEBUG` to `1` or `VERBOSE`. -`make` Logs errors only (via OSReport). -`make DEBUG=1` Enables information and error logging via [LoggingModule](https://github.com/wiiu-env/LoggingModule). -`make DEBUG=VERBOSE` Enables verbose information and error logging via [LoggingModule](https://github.com/wiiu-env/LoggingModule). + - `make` Logs errors only (via OSReport). + - `make DEBUG=1` Enables information and error logging via [LoggingModule](https://github.com/wiiu-env/LoggingModule). + - `make DEBUG=VERBOSE` Enables verbose information and error logging via [LoggingModule](https://github.com/wiiu-env/LoggingModule). If the [LoggingModule](https://github.com/wiiu-env/LoggingModule) is not present, it'll fall back to UDP (Port 4405) and [CafeOS](https://github.com/wiiu-env/USBSerialLoggingModule) logging. ## Building -For building, you just need [wut](https://github.com/devkitPro/wut/) installed, then use the `make` command. +For building, you need to install (via devkitPro's `pacman`): + - [wut](https://github.com/devkitPro/wut/) + - ppc-libpng + - ppc-libjpeg-turbo + +Then use the `make` command. ## Building using the Dockerfile diff --git a/source/gfx/JPEGTexture.cpp b/source/gfx/JPEGTexture.cpp new file mode 100644 index 0000000..09ebf32 --- /dev/null +++ b/source/gfx/JPEGTexture.cpp @@ -0,0 +1,84 @@ +#include "JPEGTexture.h" +#include +#include +#include +#include + +GX2Texture *JPEG_LoadTexture(std::span data) { + GX2Texture *texture = nullptr; + + tjhandle handle = tjInitDecompress(); + if (!handle) { + return nullptr; + } + + int height; + int width; + int subsamp; + int colorspace; + if (tjDecompressHeader3(handle, + data.data(), data.size(), + &width, &height, + &subsamp, &colorspace)) { + goto error; + } + + texture = static_cast(std::malloc(sizeof(GX2Texture))); + if (!texture) { + goto error; + } + + 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) { + goto error; + } + + texture->surface.image = std::aligned_alloc(texture->surface.alignment, + texture->surface.imageSize); + if (!texture->surface.image) { + goto error; + } + + if (tjDecompress2(handle, + data.data(), data.size(), + static_cast(texture->surface.image), + width, + texture->surface.pitch * 4, + height, + TJPF_RGBA, + 0)) { + goto error; + } + + tjDestroy(handle); + + GX2Invalidate(GX2_INVALIDATE_MODE_CPU | GX2_INVALIDATE_MODE_TEXTURE, + texture->surface.image, texture->surface.imageSize); + return texture; + +error: + if (texture) { + std::free(texture->surface.image); + } + std::free(texture); + tjDestroy(handle); + return nullptr; +} diff --git a/source/gfx/JPEGTexture.h b/source/gfx/JPEGTexture.h new file mode 100644 index 0000000..d4cb56d --- /dev/null +++ b/source/gfx/JPEGTexture.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include +#include + +GX2Texture *JPEG_LoadTexture(std::span data); diff --git a/source/gfx/SplashScreenDrawer.cpp b/source/gfx/SplashScreenDrawer.cpp index 0cd1edf..e9f9793 100644 --- a/source/gfx/SplashScreenDrawer.cpp +++ b/source/gfx/SplashScreenDrawer.cpp @@ -1,4 +1,5 @@ #include "SplashScreenDrawer.h" +#include "JPEGTexture.h" #include "PNGTexture.h" #include "ShaderSerializer.h" #include "TGATexture.h" @@ -7,6 +8,7 @@ #include "utils/utils.h" #include #include +#include #include #include #include @@ -131,6 +133,8 @@ static GX2Texture *LoadImageAsTexture(const std::filesystem::path &filename) { auto ext = ToLower(filename.extension()); if (ext == ".png") { return PNG_LoadTexture(buffer); + } else if (ext == ".jpg" || ext == ".jpeg") { + return JPEG_LoadTexture(buffer); } else if (ext == ".tga") { return TGA_LoadTexture(buffer); } @@ -140,6 +144,12 @@ static GX2Texture *LoadImageAsTexture(const std::filesystem::path &filename) { SplashScreenDrawer::SplashScreenDrawer(const std::filesystem::path &splash_base_path) { mTexture = LoadImageAsTexture(splash_base_path / "splash.png"); + if (!mTexture) { + mTexture = LoadImageAsTexture(splash_base_path / "splash.jpg"); + } + if (!mTexture) { + mTexture = LoadImageAsTexture(splash_base_path / "splash.jpeg"); + } if (!mTexture) { mTexture = LoadImageAsTexture(splash_base_path / "splash.tga"); } @@ -152,7 +162,7 @@ SplashScreenDrawer::SplashScreenDrawer(const std::filesystem::path &splash_base_ continue; } auto ext = ToLower(entry.path().extension()); - if (ext == ".png" || ext == ".tga") { + if (ext == ".png" || ext == ".tga" || ext == ".jpg" || ext == ".jpeg") { candidates.push_back(entry.path()); } } @@ -241,10 +251,10 @@ SplashScreenDrawer::~SplashScreenDrawer() { GX2RDestroyBufferEx(&mTexCoordBuffer, GX2R_RESOURCE_BIND_NONE); if (mTexture) { if (mTexture->surface.image != nullptr) { - free(mTexture->surface.image); + std::free(mTexture->surface.image); mTexture->surface.image = nullptr; } - ::free(mTexture); + std::free(mTexture); mTexture = nullptr; } } From 7a3bba219e6fd43a07c7ae00895bcecfa0d818b4 Mon Sep 17 00:00:00 2001 From: "Daniel K. O." Date: Sat, 22 Feb 2025 15:05:02 -0300 Subject: [PATCH 3/7] Improved PNG reading (#8) * Use libpng's "simplified API", to automatically load all types of PNG as RGBA images. * Removed extra blank spaces. --------- Co-authored-by: Daniel K. O. (dkosmari) --- source/gfx/JPEGTexture.cpp | 7 +++ source/gfx/PNGTexture.cpp | 113 +++++++++++++------------------------ 2 files changed, 47 insertions(+), 73 deletions(-) diff --git a/source/gfx/JPEGTexture.cpp b/source/gfx/JPEGTexture.cpp index 09ebf32..5ef5afa 100644 --- a/source/gfx/JPEGTexture.cpp +++ b/source/gfx/JPEGTexture.cpp @@ -1,4 +1,5 @@ #include "JPEGTexture.h" +#include "utils/logger.h" #include #include #include @@ -20,11 +21,13 @@ GX2Texture *JPEG_LoadTexture(std::span data) { data.data(), data.size(), &width, &height, &subsamp, &colorspace)) { + DEBUG_FUNCTION_LINE_ERR("Failed to parse JPEG header\n"); goto error; } texture = static_cast(std::malloc(sizeof(GX2Texture))); if (!texture) { + DEBUG_FUNCTION_LINE_ERR("Failed to allocate texture\n"); goto error; } @@ -48,12 +51,14 @@ GX2Texture *JPEG_LoadTexture(std::span data) { 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; } @@ -65,6 +70,7 @@ GX2Texture *JPEG_LoadTexture(std::span data) { height, TJPF_RGBA, 0)) { + DEBUG_FUNCTION_LINE_ERR("Failed to read JPEG image\n"); goto error; } @@ -72,6 +78,7 @@ GX2Texture *JPEG_LoadTexture(std::span data) { GX2Invalidate(GX2_INVALIDATE_MODE_CPU | GX2_INVALIDATE_MODE_TEXTURE, texture->surface.image, texture->surface.imageSize); + return texture; error: diff --git a/source/gfx/PNGTexture.cpp b/source/gfx/PNGTexture.cpp index f2808ff..f7725d1 100644 --- a/source/gfx/PNGTexture.cpp +++ b/source/gfx/PNGTexture.cpp @@ -1,67 +1,33 @@ #include "PNGTexture.h" +#include "utils/logger.h" +#include #include #include -#include #include -#include - -static void png_read_data(png_structp png_ptr, png_bytep outBytes, png_size_t byteCountToRead) { - void **data = (void **) png_get_io_ptr(png_ptr); - - memcpy(outBytes, *data, byteCountToRead); - *((uint8_t **) data) += byteCountToRead; -} - -void my_png_error_fn(png_structp png_ptr, png_const_charp error_msg) { - DEBUG_FUNCTION_LINE_ERR("libpng error: %s\n", error_msg); - longjmp(png_jmpbuf(png_ptr), 1); -} - -void my_png_warning_fn(png_structp png_ptr, png_const_charp warning_msg) { - DEBUG_FUNCTION_LINE_ERR("libpng warning: %s\n", warning_msg); -} GX2Texture *PNG_LoadTexture(std::span data) { - png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); - if (png_ptr == nullptr) { - return nullptr; - } + GX2Texture *texture = nullptr; - png_infop info_ptr = png_create_info_struct(png_ptr); - if (info_ptr == nullptr) { - png_destroy_read_struct(&png_ptr, nullptr, nullptr); - return nullptr; - } + png_image image{}; + image.version = PNG_IMAGE_VERSION; - png_set_error_fn(png_ptr, nullptr, my_png_error_fn, my_png_warning_fn); - // Error handling using setjmp/longjmp - if (setjmp(png_jmpbuf(png_ptr))) { - DEBUG_FUNCTION_LINE_ERR("An error occurred while processing the PNG file\n"); - png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); - return nullptr; + 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; } - png_set_read_fn(png_ptr, (void *) &data, png_read_data); - - png_read_info(png_ptr, info_ptr); + // Request the output to always be RGBA + image.format = PNG_FORMAT_RGBA; - uint32_t width = 0; - uint32_t height = 0; - int bitDepth = 0; - int colorType = -1; - uint32_t retval = png_get_IHDR(png_ptr, info_ptr, &width, &height, &bitDepth, &colorType, nullptr, nullptr, nullptr); - if (retval != 1) { - return nullptr; + texture = static_cast(std::malloc(sizeof(GX2Texture))); + if (!texture) { + DEBUG_FUNCTION_LINE_ERR("Failed to allocate texture\n"); + goto error; } - uint32_t bytesPerRow = png_get_rowbytes(png_ptr, info_ptr); - auto *rowData = new uint8_t[bytesPerRow]; - - auto *texture = (GX2Texture *) malloc(sizeof(GX2Texture)); - *texture = {}; - - texture->surface.width = width; - texture->surface.height = height; + 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; @@ -79,34 +45,35 @@ GX2Texture *PNG_LoadTexture(std::span data) { GX2InitTextureRegs(texture); if (texture->surface.imageSize == 0) { - return nullptr; + DEBUG_FUNCTION_LINE_ERR("Texture is empty\n"); + goto error; } - texture->surface.image = memalign(texture->surface.alignment, texture->surface.imageSize); + texture->surface.image = std::aligned_alloc(texture->surface.alignment, + texture->surface.imageSize); if (!texture->surface.image) { - return nullptr; + DEBUG_FUNCTION_LINE_ERR("Failed to allocate surface for texture\n"); + goto error; } - for (uint32_t y = 0; y < height; y++) { - uint32_t *out_data = (uint32_t *) texture->surface.image + (y * texture->surface.pitch); - png_read_row(png_ptr, (png_bytep) rowData, nullptr); - for (uint32_t x = 0; x < width; x++) { - if (colorType == PNG_COLOR_TYPE_RGB_ALPHA) { - uint32_t i = (x) *4; - *out_data = rowData[i] << 24 | rowData[i + 1] << 16 | rowData[i + 2] << 8 | rowData[i + 3]; - } else if (colorType == PNG_COLOR_TYPE_RGB) { - uint32_t i = (x) *3; - *out_data = rowData[i] << 24 | rowData[i + 1] << 16 | rowData[i + 2] << 8 | 0xFF; - } - out_data++; - } + 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; } - delete[] rowData; - png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); - - // 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); + GX2Invalidate(GX2_INVALIDATE_MODE_CPU | GX2_INVALIDATE_MODE_TEXTURE, + texture->surface.image, texture->surface.imageSize); return texture; -} \ No newline at end of file + +error: + if (texture) { + std::free(texture->surface.image); + } + std::free(texture); + png_image_free(&image); + return nullptr; +} From d24e97e83d48ca40b25d8ef5359a0437693f1a82 Mon Sep 17 00:00:00 2001 From: "Daniel K. O." Date: Thu, 5 Feb 2026 07:57:59 -0300 Subject: [PATCH 4/7] Update to turbojpeg 3. (#17) * Update to turbojpeg 3. * Fixed formatting. * More formatting. * Fixed formatting. * Updated Dockerfile, crt for newlib and wut. * Add __init_wut_thread() call. --------- Co-authored-by: Daniel K. O. (dkosmari) --- Dockerfile | 2 +- source/crt.c | 7 ++----- source/gfx/JPEGTexture.cpp | 43 +++++++++++++++++++------------------- source/gfx/gfx.c | 4 ++-- 4 files changed, 26 insertions(+), 30 deletions(-) diff --git a/Dockerfile b/Dockerfile index ca3e689..0c26d05 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,3 @@ -FROM ghcr.io/wiiu-env/devkitppc:20241128 +FROM ghcr.io/wiiu-env/devkitppc:20260204 WORKDIR project diff --git a/source/crt.c b/source/crt.c index 25bf56f..fb47ab2 100644 --- a/source/crt.c +++ b/source/crt.c @@ -2,7 +2,7 @@ void __init_wut_malloc(); void __init_wut_newlib(); -void __init_wut_stdcpp(); +void __init_wut_thread(); void __init_wut_devoptab(); @@ -12,8 +12,6 @@ void __fini_wut_malloc(); void __fini_wut_newlib(); -void __fini_wut_stdcpp(); - void __fini_wut_devoptab(); void __fini(); @@ -22,9 +20,9 @@ void __attribute__((weak)) __fini_wut_socket(); void __attribute__((weak)) __init_wut_() { + __init_wut_thread(); __init_wut_malloc(); __init_wut_newlib(); - __init_wut_stdcpp(); __init_wut_devoptab(); if (&__init_wut_socket) __init_wut_socket(); } @@ -33,7 +31,6 @@ void __attribute__((weak)) __fini_wut_() { __fini(); __fini_wut_devoptab(); - __fini_wut_stdcpp(); __fini_wut_newlib(); __fini_wut_malloc(); } diff --git a/source/gfx/JPEGTexture.cpp b/source/gfx/JPEGTexture.cpp index 5ef5afa..2770085 100644 --- a/source/gfx/JPEGTexture.cpp +++ b/source/gfx/JPEGTexture.cpp @@ -7,21 +7,23 @@ GX2Texture *JPEG_LoadTexture(std::span data) { GX2Texture *texture = nullptr; + int height; + int width; - tjhandle handle = tjInitDecompress(); + tjhandle handle = tj3Init(TJINIT_DECOMPRESS); if (!handle) { - return nullptr; + goto error; } - int height; - int width; - int subsamp; - int colorspace; - if (tjDecompressHeader3(handle, - data.data(), data.size(), - &width, &height, - &subsamp, &colorspace)) { - DEBUG_FUNCTION_LINE_ERR("Failed to parse JPEG header\n"); + if (tj3DecompressHeader(handle, data.data(), data.size())) { + DEBUG_FUNCTION_LINE_ERR("Failed to parse JPEG header: %s\n", tj3GetErrorStr(handle)); + goto error; + } + + 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; } @@ -62,19 +64,16 @@ GX2Texture *JPEG_LoadTexture(std::span data) { goto error; } - if (tjDecompress2(handle, - data.data(), data.size(), - static_cast(texture->surface.image), - width, - texture->surface.pitch * 4, - height, - TJPF_RGBA, - 0)) { - DEBUG_FUNCTION_LINE_ERR("Failed to read JPEG image\n"); + 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; } - tjDestroy(handle); + tj3Destroy(handle); GX2Invalidate(GX2_INVALIDATE_MODE_CPU | GX2_INVALIDATE_MODE_TEXTURE, texture->surface.image, texture->surface.imageSize); @@ -86,6 +85,6 @@ GX2Texture *JPEG_LoadTexture(std::span data) { std::free(texture->surface.image); } std::free(texture); - tjDestroy(handle); + tj3Destroy(handle); return nullptr; } diff --git a/source/gfx/gfx.c b/source/gfx/gfx.c index 5bc9078..df33e02 100644 --- a/source/gfx/gfx.c +++ b/source/gfx/gfx.c @@ -200,7 +200,7 @@ static BOOL initBucketHeap() { sGfxHeapForeground = MEMCreateExpHeapEx(base, size, 0); if (!sGfxHeapForeground) { - WHBLogPrintf("%s: MEMCreateExpHeapEx(0x%08X, 0x%X, 0)", __FUNCTION__, base, size); + WHBLogPrintf("%s: MEMCreateExpHeapEx(%p, 0x%X, 0)", __FUNCTION__, base, size); return FALSE; } @@ -476,4 +476,4 @@ BOOL GfxInitShaderAttribute(WHBGfxShaderGroup *group, attrib->mask = GfxGetAttribFormatSel(format); attrib->endianSwap = GX2_ENDIAN_SWAP_DEFAULT; return TRUE; -} \ No newline at end of file +} From 3ca34b0bb1a9f2b063f812a66db19066d1db2845 Mon Sep 17 00:00:00 2001 From: "Daniel K. O." Date: Thu, 5 Feb 2026 08:00:23 -0300 Subject: [PATCH 5/7] Webp support (#16) * Fixed format string. * Added WEBP support. * Fixed formatting. * Fixed formatting again. * Updated Dockerfile, crt for newlib and wut. * Add __init_wut_thread() call. --------- Co-authored-by: Daniel K. O. (dkosmari) --- Makefile | 2 +- source/gfx/SplashScreenDrawer.cpp | 12 ++++- source/gfx/WEBPTexture.cpp | 73 +++++++++++++++++++++++++++++++ source/gfx/WEBPTexture.h | 7 +++ 4 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 source/gfx/WEBPTexture.cpp create mode 100644 source/gfx/WEBPTexture.h diff --git a/Makefile b/Makefile index 3a59eae..024ee56 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,7 @@ CXXFLAGS := $(CFLAGS) -std=c++23 -fno-rtti ASFLAGS := -g $(ARCH) LDFLAGS = -g $(ARCH) $(RPXSPECS) --entry=_start -Wl,-Map,$(notdir $*.map) -LIBS := -lpng -lturbojpeg -lwut -lz +LIBS := -lpng -lturbojpeg -lwebp -lwut -lz ifeq ($(DEBUG),1) CXXFLAGS += -DDEBUG -g diff --git a/source/gfx/SplashScreenDrawer.cpp b/source/gfx/SplashScreenDrawer.cpp index e9f9793..45e4aff 100644 --- a/source/gfx/SplashScreenDrawer.cpp +++ b/source/gfx/SplashScreenDrawer.cpp @@ -3,6 +3,7 @@ #include "PNGTexture.h" #include "ShaderSerializer.h" #include "TGATexture.h" +#include "WEBPTexture.h" #include "gfx.h" #include "utils/logger.h" #include "utils/utils.h" @@ -137,6 +138,8 @@ static GX2Texture *LoadImageAsTexture(const std::filesystem::path &filename) { return JPEG_LoadTexture(buffer); } else if (ext == ".tga") { return TGA_LoadTexture(buffer); + } else if (ext == ".webp") { + return WEBP_LoadTexture(buffer); } } return nullptr; @@ -153,6 +156,9 @@ SplashScreenDrawer::SplashScreenDrawer(const std::filesystem::path &splash_base_ if (!mTexture) { mTexture = LoadImageAsTexture(splash_base_path / "splash.tga"); } + if (!mTexture) { + mTexture = LoadImageAsTexture(splash_base_path / "splash.webp"); + } if (!mTexture) { // try to load a random one from "splashes/*" try { @@ -162,7 +168,11 @@ SplashScreenDrawer::SplashScreenDrawer(const std::filesystem::path &splash_base_ continue; } auto ext = ToLower(entry.path().extension()); - if (ext == ".png" || ext == ".tga" || ext == ".jpg" || ext == ".jpeg") { + if (ext == ".png" || + ext == ".tga" || + ext == ".jpg" || + ext == ".jpeg" || + ext == ".webp") { candidates.push_back(entry.path()); } } diff --git a/source/gfx/WEBPTexture.cpp b/source/gfx/WEBPTexture.cpp new file mode 100644 index 0000000..262045b --- /dev/null +++ b/source/gfx/WEBPTexture.cpp @@ -0,0 +1,73 @@ +#include "WEBPTexture.h" +#include "utils/logger.h" +#include +#include +#include +#include + +GX2Texture *WEBP_LoadTexture(std::span data) { + GX2Texture *texture = nullptr; + int width, height; + + 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::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; + } + + GX2Invalidate(GX2_INVALIDATE_MODE_CPU | GX2_INVALIDATE_MODE_TEXTURE, + texture->surface.image, texture->surface.imageSize); + + return texture; + +error: + if (texture) { + std::free(texture->surface.image); + } + std::free(texture); + return nullptr; +} diff --git a/source/gfx/WEBPTexture.h b/source/gfx/WEBPTexture.h new file mode 100644 index 0000000..b3180f0 --- /dev/null +++ b/source/gfx/WEBPTexture.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include +#include + +GX2Texture *WEBP_LoadTexture(std::span data); From 4d9d9e431fb51005a4969b38c5c6241b15da5891 Mon Sep 17 00:00:00 2001 From: "Daniel K. O." Date: Fri, 6 Feb 2026 03:15:00 -0300 Subject: [PATCH 6/7] General path (#19) * Fixed format string. * Try sd:/wiiu as a fallback path for loading splashes. * Updated README. * Better formatting. * Simpler wording. * Fixed typo. * Formatting code. * Avoid seeding RNG more than once. * Refactored to hide all path handling inside SplashScreenDrawer class. * Refactored some functions into utils.h, utils.cpp * Added missing include, some code cleanup. * Added a try-catch block for vector and filesystem exceptions. Hg: Enter commit message. Lines beginning with 'HG:' are removed. * Refactoring: make LoadTextureFrom return a bool. * Adjusted for clang-format checks. * Updated Dockerfile, crt for newlib and wut. * Add __init_wut_thread() call. * Updated README. * Some cleanup and remove unnecessary code changes. --------- Co-authored-by: Daniel K. O. (dkosmari) --- README.md | 28 ++++--- source/gfx/SplashScreenDrawer.cpp | 119 ++++++++++++++++-------------- source/gfx/SplashScreenDrawer.h | 6 +- source/main.cpp | 8 +- source/utils/utils.cpp | 27 +++++++ source/utils/utils.h | 5 ++ 6 files changed, 119 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index d2c4f61..d47dfb5 100644 --- a/README.md +++ b/README.md @@ -4,19 +4,22 @@ This module is supposed to be loaded with the [EnvironmentLoader](https://github other modules of the environment are loading. ## Usage -Place the `01_splashscreen.rpx` in the `[ENVIRONMENT]/modules/setup` folder and run the -EnvironmentLoader. The module will attempt to load the splash image, in this order: - 1. `[ENVIRONMENT]/splash.png` - 2. `[ENVIRONMENT]/splash.jpg` or `[ENVIRONMENT]/splash.jpeg` - 3. `[ENVIRONMENT]/splash.tga` - 4. A random image (PNG, JPEG or TGA) from the directory `[ENVIRONMENT]/splashes/`. - -If no splash image is found on the sd card, this module will effectively do nothing. + 1. Place the `01_splashscreen.rpx` in the `[ENVIRONMENT]/modules/setup` folder. + 2. Place your splash images (PNG, JPEG, TGA or WEBP) in the folder `SD:/wiiu/splashes/`. **Notes:** - - `[ENVIRONMENT]` is the directory of the environment, for Aroma with would be `sd:/wiiu/enviroments/aroma/splash.png` - - When using a `tga` make sure its 24 bit and uncompressed - - In theory any (reasonable) resolution is supported, something like 1280x720 is recommended + - `[ENVIRONMENT]` is the directory of the environment, for Aroma with would be `SD:/wiiu/enviroments/aroma`. + - When using a TGA image, make sure its 24 bit and uncompressed, + - In theory any (reasonable) resolution is supported, **1280x720** is recommended for best quality on both gamepad and TV screens. + +## Path priority +The module will attempt to load a splash image from multiple places, in this order: + 1. `[ENVIRONMENT]/`: an image named `splash.EXT` + 2. `[ENVIRONMENT]/splashes/`: a **random** image in that folder. + 3. `SD:/wiiu/`: an image named `splash.EXT` + 4. `SD:/wiiu/splashes/`: a **random** image in that folder. + +Where `EXT` can be `png`, `jpg`, `jpeg`, `tga` or `webp`. ## Buildflags @@ -32,8 +35,9 @@ If the [LoggingModule](https://github.com/wiiu-env/LoggingModule) is not present ## Building For building, you need to install (via devkitPro's `pacman`): - [wut](https://github.com/devkitPro/wut/) - - ppc-libpng - ppc-libjpeg-turbo + - ppc-libpng + - ppc-libwebp Then use the `make` command. diff --git a/source/gfx/SplashScreenDrawer.cpp b/source/gfx/SplashScreenDrawer.cpp index 45e4aff..f237aad 100644 --- a/source/gfx/SplashScreenDrawer.cpp +++ b/source/gfx/SplashScreenDrawer.cpp @@ -7,13 +7,15 @@ #include "gfx.h" #include "utils/logger.h" #include "utils/utils.h" +#include +#include #include -#include #include +#include #include #include #include -#include +#include #include /* @@ -120,14 +122,6 @@ 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 std::filesystem::path ToLower(const std::filesystem::path &p) { - std::string result; - for (auto c : p.string()) { - result.push_back(std::tolower(static_cast(c))); - } - return result; -} - static GX2Texture *LoadImageAsTexture(const std::filesystem::path &filename) { std::vector buffer; if (LoadFileIntoBuffer(filename, buffer)) { @@ -145,55 +139,20 @@ static GX2Texture *LoadImageAsTexture(const std::filesystem::path &filename) { return nullptr; } -SplashScreenDrawer::SplashScreenDrawer(const std::filesystem::path &splash_base_path) { - mTexture = LoadImageAsTexture(splash_base_path / "splash.png"); - if (!mTexture) { - mTexture = LoadImageAsTexture(splash_base_path / "splash.jpg"); - } - if (!mTexture) { - mTexture = LoadImageAsTexture(splash_base_path / "splash.jpeg"); - } - if (!mTexture) { - mTexture = LoadImageAsTexture(splash_base_path / "splash.tga"); - } - if (!mTexture) { - mTexture = LoadImageAsTexture(splash_base_path / "splash.webp"); - } - if (!mTexture) { - // try to load a random one from "splashes/*" - try { - std::vector candidates; - for (const auto &entry : std::filesystem::directory_iterator{splash_base_path / "splashes"}) { - if (!entry.is_regular_file()) { - continue; - } - auto ext = ToLower(entry.path().extension()); - if (ext == ".png" || - ext == ".tga" || - ext == ".jpg" || - ext == ".jpeg" || - ext == ".webp") { - candidates.push_back(entry.path()); - } - } - if (!candidates.empty()) { - auto t = static_cast(OSGetTime()); - std::seed_seq seed{static_cast(t), - static_cast(t >> 32)}; - std::minstd_rand eng{seed}; - std::uniform_int_distribution dist{0, candidates.size() - 1}; - auto selected = dist(eng); - mTexture = LoadImageAsTexture(candidates[selected]); - } - } catch (std::exception &) {} - } - if (!mTexture) { - mTexture = PNG_LoadTexture(empty_png); - } - if (!mTexture) { - return; +SplashScreenDrawer::SplashScreenDrawer(const std::filesystem::path &envDir) { + // 1: Use env dir. + if (!LoadTextureFrom(envDir)) { + // 2: Use general dir. + if (!LoadTextureFrom("fs:/vol/external01/wiiu")) { + // 3: Use fallback empty texture. + mTexture = PNG_LoadTexture(empty_png); + } } + InitResources(); +} + +void SplashScreenDrawer::InitResources() { // create shader group mVertexShaderWrapper = DeserializeVertexShader(s_textureVertexShaderCompiled); mPixelShaderWrapper = DeserializePixelShader(s_texturePixelShaderCompiled); @@ -232,6 +191,52 @@ SplashScreenDrawer::SplashScreenDrawer(const std::filesystem::path &splash_base_ GX2InitSampler(&mSampler, GX2_TEX_CLAMP_MODE_CLAMP, GX2_TEX_XY_FILTER_MODE_LINEAR); } +bool SplashScreenDrawer::LoadTextureFrom(const std::filesystem::path &dir) { + if (dir.empty()) { + return false; + } + + const std::array extensions = { + ".png", + ".jpg", + ".jpeg", + ".tga", + ".webp"}; + + // First try the splash.* image. + for (const auto &ext : extensions) { + auto fname = std::string{"splash"} + ext; + mTexture = LoadImageAsTexture(dir / fname); + if (mTexture) { + return true; + } + } + + try { + // 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"}) { + if (!entry.is_regular_file()) { + continue; + } + auto ext = ToLower(entry.path().extension()); + if (std::ranges::contains(extensions, ext)) { + candidates.push_back(entry.path()); + } + } + if (!candidates.empty()) { + auto selected = GetRandomIndex(candidates.size()); + mTexture = LoadImageAsTexture(candidates[selected]); + if (mTexture) { + return true; + } + } + } catch (std::exception &e) { + DEBUG_FUNCTION_LINE_INFO("Loading texture failed: %s", e.what()); + } + return false; +} + void SplashScreenDrawer::Draw() { if (!mTexture) { DEBUG_FUNCTION_LINE_INFO("Texture is missing"); diff --git a/source/gfx/SplashScreenDrawer.h b/source/gfx/SplashScreenDrawer.h index 2576fa3..5776ab8 100644 --- a/source/gfx/SplashScreenDrawer.h +++ b/source/gfx/SplashScreenDrawer.h @@ -11,7 +11,7 @@ class SplashScreenDrawer { public: - explicit SplashScreenDrawer(const std::filesystem::path &meta_dir); + explicit SplashScreenDrawer(const std::filesystem::path &envDir); void Draw(); @@ -47,4 +47,8 @@ class SplashScreenDrawer { GX2RBuffer mTexCoordBuffer = {}; GX2Texture *mTexture = nullptr; GX2Sampler mSampler = {}; + + void InitResources(); + + bool LoadTextureFrom(const std::filesystem::path &dir); }; diff --git a/source/main.cpp b/source/main.cpp index 64fef0d..86d90b8 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -7,18 +7,18 @@ #define MODULE_VERSION "v0.3" #define MODULE_VERSION_FULL MODULE_VERSION SPLASHSCREEN_MODULE_VERSION_EXTRA -int32_t main(int32_t argc, char **argv) { +int main(int argc, char **argv) { initLogging(); DEBUG_FUNCTION_LINE_INFO("Running SplashScreen Module " MODULE_VERSION_FULL ""); - std::filesystem::path basePath = "fs:/vol/external01/wiiu"; + std::filesystem::path envDir; if (argc >= 1) { - basePath = argv[0]; + envDir = argv[0]; } GfxInit(); { - SplashScreenDrawer splashScreenDrawer(basePath); + SplashScreenDrawer splashScreenDrawer(envDir); splashScreenDrawer.Draw(); } GfxShutdown(); diff --git a/source/utils/utils.cpp b/source/utils/utils.cpp index b657912..d8eba44 100644 --- a/source/utils/utils.cpp +++ b/source/utils/utils.cpp @@ -1,7 +1,11 @@ #include "utils.h" #include "logger.h" +#include #include +#include +#include #include +#include #include #include @@ -30,3 +34,26 @@ bool LoadFileIntoBuffer(const std::filesystem::path &filename, std::vector sRandomEngine; +} // namespace + +std::size_t GetRandomIndex(std::size_t size) { + if (!sRandomEngine) { + auto t = static_cast(OSGetTime()); + std::seed_seq seeder{static_cast(t), + static_cast(t >> 32)}; + sRandomEngine.emplace(seeder); + } + std::uniform_int_distribution dist{0, size - 1}; + return dist(*sRandomEngine); +} + +std::filesystem::path ToLower(const std::filesystem::path &p) { + std::string result; + for (auto c : p.string()) { + result.push_back(std::tolower(static_cast(c))); + } + return result; +} diff --git a/source/utils/utils.h b/source/utils/utils.h index ff36367..3af0438 100644 --- a/source/utils/utils.h +++ b/source/utils/utils.h @@ -1,7 +1,12 @@ #pragma once +#include #include #include #include bool LoadFileIntoBuffer(const std::filesystem::path &filename, std::vector &buffer); + +std::size_t GetRandomIndex(std::size_t size); + +std::filesystem::path ToLower(const std::filesystem::path &p); From 22f2528e5d70b016e77098f0a7265ea7c47c7147 Mon Sep 17 00:00:00 2001 From: Maschell Date: Sat, 18 Apr 2026 17:26:04 +0200 Subject: [PATCH 7/7] Update Dockerfile (#23) --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 0c26d05..ac62304 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,3 @@ -FROM ghcr.io/wiiu-env/devkitppc:20260204 +FROM ghcr.io/wiiu-env/devkitppc:20260225 WORKDIR project