Skip to content
Closed
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 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 -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, TGA or JPEG) 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, something like 1280x720 is recommended.

## Path priority
The module will attempt to load a splash image from multiple places, in this order:
1. `[ENVIRONMENT]/splash.{png,jpg,jpeg,tga}`
2. `[ENVIRONMENT]/splashes/*.{png,jpg,jpeg,tga}` (selected randomly)
3. `SD:/wiiu/splash.{png,jpg,jpeg,tga}`
4. `SD:/wiiu/splashes/*.{png,jpg,jpeg,tga}` (selected randomly)

You should use the last path (`SD:/wiiu/splashes/`), and leave the others for when you
want to override the splash.

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

Then use the `make` command.

## Building using the Dockerfile

Expand Down
91 changes: 91 additions & 0 deletions source/gfx/JPEGTexture.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#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;

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)) {
DEBUG_FUNCTION_LINE_ERR("Failed to parse JPEG header\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 (tjDecompress2(handle,
data.data(), data.size(),
static_cast<unsigned char *>(texture->surface.image),
width,
texture->surface.pitch * 4,
height,
TJPF_RGBA,
0)) {
DEBUG_FUNCTION_LINE_ERR("Failed to read JPEG image\n");
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;
}
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