Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
FROM ghcr.io/wiiu-env/devkitppc:20241128
FROM ghcr.io/wiiu-env/devkitppc:20260225

WORKDIR project
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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 -lwebp -lwut -lz

ifeq ($(DEBUG),1)
CXXFLAGS += -DDEBUG -g
Expand Down
38 changes: 24 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,42 @@ 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.tga`
3. A random image from the directory `[ENVIRONMENT]/splashes/`.

If no splash screen 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

### 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-libjpeg-turbo
- ppc-libpng
- ppc-libwebp

Then use the `make` command.

## Building using the Dockerfile

Expand Down
7 changes: 2 additions & 5 deletions source/crt.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ void __init_wut_malloc();

void __init_wut_newlib();

void __init_wut_stdcpp();
void __init_wut_thread();

void __init_wut_devoptab();

Expand All @@ -12,8 +12,6 @@ void __fini_wut_malloc();

void __fini_wut_newlib();

void __fini_wut_stdcpp();

void __fini_wut_devoptab();

void __fini();
Expand All @@ -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();
}
Expand All @@ -33,7 +31,6 @@ void __attribute__((weak))
__fini_wut_() {
__fini();
__fini_wut_devoptab();
__fini_wut_stdcpp();
__fini_wut_newlib();
__fini_wut_malloc();
}
90 changes: 90 additions & 0 deletions source/gfx/JPEGTexture.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#include "JPEGTexture.h"
#include "utils/logger.h"
#include <cstdlib>
#include <cstring>
#include <gx2/mem.h>
#include <turbojpeg.h>

GX2Texture *JPEG_LoadTexture(std::span<uint8_t> data) {
GX2Texture *texture = nullptr;
int height;
int width;

tjhandle handle = tj3Init(TJINIT_DECOMPRESS);
if (!handle) {
goto error;
}

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

texture = static_cast<GX2Texture *>(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 (tj3Decompress8(handle,
data.data(), data.size(),
static_cast<unsigned char *>(texture->surface.image),
texture->surface.pitch * 4,
TJPF_RGBA)) {
DEBUG_FUNCTION_LINE_ERR("Failed to read JPEG image: %s\n", tj3GetErrorStr(handle));
goto error;
}

tj3Destroy(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);
tj3Destroy(handle);
return nullptr;
}
7 changes: 7 additions & 0 deletions source/gfx/JPEGTexture.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#pragma once

#include <cstdint>
#include <gx2/texture.h>
#include <span>

GX2Texture *JPEG_LoadTexture(std::span<uint8_t> data);
113 changes: 40 additions & 73 deletions source/gfx/PNGTexture.cpp
Original file line number Diff line number Diff line change
@@ -1,67 +1,33 @@
#include "PNGTexture.h"
#include "utils/logger.h"
#include <cstdlib>
#include <cstring>
#include <gx2/mem.h>
#include <malloc.h>
#include <png.h>
#include <utils/logger.h>

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<uint8_t> 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<GX2Texture *>(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;
Expand All @@ -79,34 +45,35 @@ GX2Texture *PNG_LoadTexture(std::span<uint8_t> 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;
}

error:
if (texture) {
std::free(texture->surface.image);
}
std::free(texture);
png_image_free(&image);
return nullptr;
}
Loading
Loading