diff --git a/Makefile b/Makefile index 17f9555..01f8b5f 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ WUMS_ROOT := $(DEVKITPRO)/wums #------------------------------------------------------------------------------- TARGET := 99_autoboot BUILD := build -SOURCES := source +SOURCES := source source/utils DATA := data INCLUDES := source include diff --git a/README.md b/README.md index 5e5e959..bebbb93 100644 --- a/README.md +++ b/README.md @@ -51,4 +51,5 @@ docker run -it --rm -v ${PWD}:/project autobootmodule-builder make clean ## Credits - GaryOderNichts -- Maschell \ No newline at end of file +- Maschell +- Crementif \ No newline at end of file diff --git a/source/QuickStartUtils.cpp b/source/QuickStartUtils.cpp index d7767d2..a524663 100644 --- a/source/QuickStartUtils.cpp +++ b/source/QuickStartUtils.cpp @@ -2,7 +2,9 @@ #include "BootUtils.h" #include "MenuUtils.h" #include "logger.h" - +#include "utils/SplashScreenDrawer.h" +#include "utils/SplashSoundPlayer.h" +#include "utils/gfx.h" #include #include #include @@ -351,9 +353,25 @@ bool launchQuickStartTitle() { MCP_Close(handle); if (err == 0) { DEBUG_FUNCTION_LINE("Launch %016llX", titleIdToLaunch); + char metaDir[256] = {}; + auto res = ACPGetTitleMetaDir(titleIdToLaunch, metaDir, sizeof(metaDir) - 1); + if (res == ACP_RESULT_SUCCESS) { + GfxInit(); + { + SplashScreenDrawer splashScreenDrawer(metaDir); + splashScreenDrawer.Draw(); + SplashSoundPlayer splashSound(metaDir); + splashSound.Play(); + } + GfxShutdown(); + } else { + DEBUG_FUNCTION_LINE_WARN("Failed to find assets"); + } ACPAssignTitlePatch(&titleInfo); - _SYSLaunchTitleWithStdArgsInNoSplash(titleIdToLaunch, nullptr); + _SYSLaunchTitleByPathFromLauncher(titleInfo.path, strlen(titleInfo.path)); return true; + } else { + DEBUG_FUNCTION_LINE_WARN("Failed to get title info"); } DEBUG_FUNCTION_LINE("Launch Wii U Menu!"); diff --git a/source/utils.cpp b/source/utils.cpp index 3d77dec..e8ed8ae 100644 --- a/source/utils.cpp +++ b/source/utils.cpp @@ -1,7 +1,13 @@ #include "logger.h" #include #include +#include +#include #include +#include +#include +#include +#include bool GetTitleIdOfDisc(uint64_t *titleId, bool *discPresent) { if (discPresent) { @@ -76,4 +82,27 @@ bool RestoreMLCUpdateDirectory() { DEBUG_FUNCTION_LINE_ERR("Failed to create FSA Client"); } return result; +} + + +bool LoadFileIntoBuffer(std::string_view path, std::vector &buffer) { + struct stat st {}; + if (stat(path.data(), &st) < 0 || !S_ISREG(st.st_mode)) { + DEBUG_FUNCTION_LINE_INFO("\"%s\" doesn't exists", path.data()); + return false; + } + + FILE *f = fopen(path.data(), "rb"); + if (!f) { + return false; + } + buffer.resize(st.st_size); + + if (fread(buffer.data(), 1, st.st_size, f) != st.st_size) { + DEBUG_FUNCTION_LINE_WARN("Failed load %s", path.data()); + return false; + } + + fclose(f); + return true; } \ No newline at end of file diff --git a/source/utils.h b/source/utils.h index 571c994..a776000 100644 --- a/source/utils.h +++ b/source/utils.h @@ -3,6 +3,8 @@ #include #include #include +#include +#include template std::unique_ptr make_unique_nothrow(Args &&...args) noexcept(noexcept(T(std::forward(args)...))) { @@ -32,4 +34,6 @@ bool GetTitleIdOfDisc(uint64_t *titleId, bool *discPresent); bool DeleteMLCUpdateDirectory(); -bool RestoreMLCUpdateDirectory(); \ No newline at end of file +bool RestoreMLCUpdateDirectory(); + +bool LoadFileIntoBuffer(std::string_view path, std::vector &buffer); \ No newline at end of file diff --git a/source/utils/ShaderSerializer.cpp b/source/utils/ShaderSerializer.cpp new file mode 100644 index 0000000..1bf3f53 --- /dev/null +++ b/source/utils/ShaderSerializer.cpp @@ -0,0 +1,446 @@ +#include "ShaderSerializer.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Based on https://github.com/Crementif/UntitledSandGame/blob/e752613ba54ac8f6767a8b37e9ac3f68ca180ad7/source/common/shader_serializer.h + */ + +template +static void writeAt(std::vector &fh, size_t pos, Type value) { + *reinterpret_cast(fh.data() + pos) = value; +} + +template +static void write(std::vector &fh, Type value) { + auto pos = fh.size(); + fh.resize(pos + sizeof(Type)); + *reinterpret_cast(fh.data() + pos) = value; +} + +static void writeString(std::vector &fh, const char *str) { + auto pos = fh.size(); + auto len = strlen(str) + 1; + fh.resize((static_cast(pos + len) + (4 - 1)) & ~(4 - 1)); + memcpy(fh.data() + pos, str, len); +} + +static void writeGX2RBuffer(std::vector &fh, GX2RBuffer *buffer) { + write(fh, buffer->flags); + write(fh, buffer->elemSize); + write(fh, buffer->elemCount); + auto pos = fh.size(); + fh.resize(pos + buffer->elemSize * buffer->elemCount); + memcpy(fh.data() + pos, buffer->buffer, buffer->elemSize * buffer->elemCount); +} + +std::vector SerializeVertexShader(GX2VertexShader *vertexShader) { + std::vector data; + + // write regs + write(data, vertexShader->regs.sq_pgm_resources_vs); + write(data, vertexShader->regs.vgt_primitiveid_en); + write(data, vertexShader->regs.spi_vs_out_config); + write(data, vertexShader->regs.num_spi_vs_out_id); + for (uint32_t spi_vs : vertexShader->regs.spi_vs_out_id) { + write(data, spi_vs); + } + write(data, vertexShader->regs.pa_cl_vs_out_cntl); + write(data, vertexShader->regs.sq_vtx_semantic_clear); + write(data, vertexShader->regs.num_sq_vtx_semantic); + for (uint32_t sq_vtx : vertexShader->regs.sq_vtx_semantic) { + write(data, sq_vtx); + } + write(data, vertexShader->regs.vgt_strmout_buffer_en); + write(data, vertexShader->regs.vgt_vertex_reuse_block_cntl); + write(data, vertexShader->regs.vgt_hos_reuse_depth); + + // write program + write(data, vertexShader->size); + for (uint32_t i = 0; i < vertexShader->size; i++) { + write(data, ((uint8_t *) vertexShader->program)[i]); + } + write(data, vertexShader->mode); + + // write uniform blocks + write(data, vertexShader->uniformBlockCount); + for (uint32_t i = 0; i < vertexShader->uniformBlockCount; i++) { + writeString(data, vertexShader->uniformBlocks[i].name); + write(data, vertexShader->uniformBlocks[i].offset); + write(data, vertexShader->uniformBlocks[i].size); + } + + // write uniform vars + write(data, vertexShader->uniformVarCount); + for (uint32_t i = 0; i < vertexShader->uniformVarCount; i++) { + writeString(data, vertexShader->uniformVars[i].name); + write(data, vertexShader->uniformVars[i].type); + write(data, vertexShader->uniformVars[i].count); + write(data, vertexShader->uniformVars[i].offset); + write(data, vertexShader->uniformVars[i].block); + } + + // write initial values + write(data, vertexShader->initialValueCount); + for (uint32_t i = 0; i < vertexShader->initialValueCount; i++) { + write(data, vertexShader->initialValues[i].value[0]); + write(data, vertexShader->initialValues[i].value[1]); + write(data, vertexShader->initialValues[i].value[2]); + write(data, vertexShader->initialValues[i].value[3]); + write(data, vertexShader->initialValues[i].offset); + } + + // write loop vars + write(data, vertexShader->loopVarCount); + for (uint32_t i = 0; i < vertexShader->loopVarCount; i++) { + write(data, vertexShader->loopVars[i].offset); + write(data, vertexShader->loopVars[i].value); + } + + // write sampler vars + write(data, vertexShader->samplerVarCount); + for (uint32_t i = 0; i < vertexShader->samplerVarCount; i++) { + writeString(data, vertexShader->samplerVars[i].name); + write(data, vertexShader->samplerVars[i].type); + write(data, vertexShader->samplerVars[i].location); + } + + // write attribute vars + write(data, vertexShader->attribVarCount); + for (uint32_t i = 0; i < vertexShader->attribVarCount; i++) { + writeString(data, vertexShader->attribVars[i].name); + write(data, vertexShader->attribVars[i].type); + write(data, vertexShader->attribVars[i].count); + write(data, vertexShader->attribVars[i].location); + } + + // write ring item size + write(data, vertexShader->ringItemsize); + + // write stream out + write(data, vertexShader->hasStreamOut); + for (uint32_t stride : vertexShader->streamOutStride) { + write(data, stride); + } + + // write gx2rBuffer + writeGX2RBuffer(data, &vertexShader->gx2rBuffer); + + return data; +} + +std::vector SerializePixelShader(GX2PixelShader *pixelShader) { + std::vector data; + + // write regs + write(data, pixelShader->regs.sq_pgm_resources_ps); + write(data, pixelShader->regs.sq_pgm_exports_ps); + write(data, pixelShader->regs.spi_ps_in_control_0); + write(data, pixelShader->regs.spi_ps_in_control_1); + write(data, pixelShader->regs.num_spi_ps_input_cntl); + for (uint32_t spi_ps : pixelShader->regs.spi_ps_input_cntls) { + write(data, spi_ps); + } + write(data, pixelShader->regs.cb_shader_mask); + write(data, pixelShader->regs.cb_shader_control); + write(data, pixelShader->regs.db_shader_control); + write(data, pixelShader->regs.spi_input_z); + + // write program + write(data, pixelShader->size); + for (uint32_t i = 0; i < pixelShader->size; i++) { + write(data, ((uint8_t *) pixelShader->program)[i]); + } + write(data, pixelShader->mode); + + // write uniform blocks + write(data, pixelShader->uniformBlockCount); + for (uint32_t i = 0; i < pixelShader->uniformBlockCount; i++) { + writeString(data, pixelShader->uniformBlocks[i].name); + write(data, pixelShader->uniformBlocks[i].offset); + write(data, pixelShader->uniformBlocks[i].size); + } + + // write uniform vars + write(data, pixelShader->uniformVarCount); + for (uint32_t i = 0; i < pixelShader->uniformVarCount; i++) { + writeString(data, pixelShader->uniformVars[i].name); + write(data, pixelShader->uniformVars[i].type); + write(data, pixelShader->uniformVars[i].count); + write(data, pixelShader->uniformVars[i].offset); + write(data, pixelShader->uniformVars[i].block); + } + + // write initial values + write(data, pixelShader->initialValueCount); + for (uint32_t i = 0; i < pixelShader->initialValueCount; i++) { + write(data, pixelShader->initialValues[i].value[0]); + write(data, pixelShader->initialValues[i].value[1]); + write(data, pixelShader->initialValues[i].value[2]); + write(data, pixelShader->initialValues[i].value[3]); + write(data, pixelShader->initialValues[i].offset); + } + + // write loop vars + write(data, pixelShader->loopVarCount); + for (uint32_t i = 0; i < pixelShader->loopVarCount; i++) { + write(data, pixelShader->loopVars[i].offset); + write(data, pixelShader->loopVars[i].value); + } + + // write sampler vars + write(data, pixelShader->samplerVarCount); + for (uint32_t i = 0; i < pixelShader->samplerVarCount; i++) { + writeString(data, pixelShader->samplerVars[i].name); + write(data, pixelShader->samplerVars[i].type); + write(data, pixelShader->samplerVars[i].location); + } + + // write gx2rBuffer + writeGX2RBuffer(data, &pixelShader->gx2rBuffer); + + return data; +} + +template +static Type readAt(const std::span &data, size_t &pos) { + Type value = *reinterpret_cast(data.data() + pos); + pos += sizeof(Type); + return value; +} + +static const char *readString(const std::span &data, size_t &pos) { + std::string str(reinterpret_cast(data.data() + pos)); + pos += str.size() + 1; + pos = (pos + 3) & ~3; // align to 4 bytes + + // Allocate memory for the string and copy the contents + char *result = static_cast(malloc(sizeof(char) * (str.size() + 1))); + strcpy(result, str.c_str()); + return result; +} + +static GX2RBuffer readGX2RBuffer(const std::span &data, size_t &pos) { + GX2RBuffer buffer; + buffer.flags = readAt(data, pos); + buffer.elemSize = readAt(data, pos); + buffer.elemCount = readAt(data, pos); + size_t bufferSize = buffer.elemSize * buffer.elemCount; + buffer.buffer = malloc(sizeof(uint8_t) * bufferSize); + memcpy(buffer.buffer, data.data() + pos, bufferSize); + pos += bufferSize; + return buffer; +} + +std::unique_ptr DeserializeVertexShader(const std::span &data) { + size_t pos = 0; + auto vertexShaderWrapper = std::make_unique(); + auto *vertexShader = vertexShaderWrapper->getVertexShader(); + *vertexShader = {}; + + // read regs + vertexShader->regs.sq_pgm_resources_vs = readAt(data, pos); + vertexShader->regs.vgt_primitiveid_en = readAt(data, pos); + vertexShader->regs.spi_vs_out_config = readAt(data, pos); + vertexShader->regs.num_spi_vs_out_id = readAt(data, pos); + for (uint32_t &spi_vs : vertexShader->regs.spi_vs_out_id) { + spi_vs = readAt(data, pos); + } + vertexShader->regs.pa_cl_vs_out_cntl = readAt(data, pos); + vertexShader->regs.sq_vtx_semantic_clear = readAt(data, pos); + vertexShader->regs.num_sq_vtx_semantic = readAt(data, pos); + for (uint32_t &sq_vtx : vertexShader->regs.sq_vtx_semantic) { + sq_vtx = readAt(data, pos); + } + vertexShader->regs.vgt_strmout_buffer_en = readAt(data, pos); + vertexShader->regs.vgt_vertex_reuse_block_cntl = readAt(data, pos); + vertexShader->regs.vgt_hos_reuse_depth = readAt(data, pos); + + // read program + vertexShader->size = readAt(data, pos); + vertexShader->program = memalign(256, vertexShader->size); + for (uint32_t i = 0; i < vertexShader->size; i++) { + static_cast(vertexShader->program)[i] = readAt(data, pos); + } + vertexShader->mode = readAt(data, pos); + + // read uniform blocks + vertexShader->uniformBlockCount = readAt(data, pos); + if (vertexShader->uniformBlockCount > 0) { + vertexShader->uniformBlocks = static_cast(malloc(sizeof(GX2UniformBlock) * vertexShader->uniformBlockCount)); + for (uint32_t i = 0; i < vertexShader->uniformBlockCount; i++) { + vertexShader->uniformBlocks[i].name = readString(data, pos); + vertexShader->uniformBlocks[i].offset = readAt(data, pos); + vertexShader->uniformBlocks[i].size = readAt(data, pos); + } + } + + // read uniform vars + vertexShader->uniformVarCount = readAt(data, pos); + if (vertexShader->uniformVarCount > 0) { + vertexShader->uniformVars = static_cast(malloc(sizeof(GX2UniformVar) * vertexShader->uniformVarCount)); + + for (uint32_t i = 0; i < vertexShader->uniformVarCount; i++) { + vertexShader->uniformVars[i].name = readString(data, pos); + vertexShader->uniformVars[i].type = readAt(data, pos); + vertexShader->uniformVars[i].count = readAt(data, pos); + vertexShader->uniformVars[i].offset = readAt(data, pos); + vertexShader->uniformVars[i].block = readAt(data, pos); + } + } + + // read initial values + vertexShader->initialValueCount = readAt(data, pos); + if (vertexShader->initialValueCount > 0) { + vertexShader->initialValues = static_cast(malloc(sizeof(GX2UniformInitialValue) * vertexShader->initialValueCount)); + for (uint32_t i = 0; i < vertexShader->initialValueCount; i++) { + vertexShader->initialValues[i].value[0] = readAt(data, pos); + vertexShader->initialValues[i].value[1] = readAt(data, pos); + vertexShader->initialValues[i].value[2] = readAt(data, pos); + vertexShader->initialValues[i].value[3] = readAt(data, pos); + vertexShader->initialValues[i].offset = readAt(data, pos); + } + } + + // read loop vars + vertexShader->loopVarCount = readAt(data, pos); + if (vertexShader->loopVarCount > 0) { + vertexShader->loopVars = static_cast(malloc(sizeof(GX2LoopVar) + vertexShader->loopVarCount)); + for (uint32_t i = 0; i < vertexShader->loopVarCount; i++) { + vertexShader->loopVars[i].offset = readAt(data, pos); + vertexShader->loopVars[i].value = readAt(data, pos); + } + } + + // read sampler vars + vertexShader->samplerVarCount = readAt(data, pos); + if (vertexShader->samplerVarCount > 0) { + vertexShader->samplerVars = static_cast(malloc(sizeof(GX2SamplerVar) * vertexShader->samplerVarCount)); + for (uint32_t i = 0; i < vertexShader->samplerVarCount; i++) { + vertexShader->samplerVars[i].name = readString(data, pos); + vertexShader->samplerVars[i].type = readAt(data, pos); + vertexShader->samplerVars[i].location = readAt(data, pos); + } + } + + // read attribute vars + vertexShader->attribVarCount = readAt(data, pos); + if (vertexShader->attribVarCount > 0) { + vertexShader->attribVars = static_cast(malloc(sizeof(GX2AttribVar) * vertexShader->attribVarCount)); + for (uint32_t i = 0; i < vertexShader->attribVarCount; i++) { + vertexShader->attribVars[i].name = readString(data, pos); + vertexShader->attribVars[i].type = readAt(data, pos); + vertexShader->attribVars[i].count = readAt(data, pos); + vertexShader->attribVars[i].location = readAt(data, pos); + } + } + + // read ring item size + vertexShader->ringItemsize = readAt(data, pos); + + // read stream out + vertexShader->hasStreamOut = readAt(data, pos); + for (uint32_t &stride : vertexShader->streamOutStride) { + stride = readAt(data, pos); + } + + // read gx2rBuffer + vertexShader->gx2rBuffer = readGX2RBuffer(data, pos); + + return vertexShaderWrapper; +} + +std::unique_ptr DeserializePixelShader(const std::span &data) { + size_t pos = 0; + auto pixelShaderWrapper = std::make_unique(); + auto *pixelShader = pixelShaderWrapper->getPixelShader(); + *pixelShader = {}; + // read regs + pixelShader->regs.sq_pgm_resources_ps = readAt(data, pos); + pixelShader->regs.sq_pgm_exports_ps = readAt(data, pos); + pixelShader->regs.spi_ps_in_control_0 = readAt(data, pos); + pixelShader->regs.spi_ps_in_control_1 = readAt(data, pos); + pixelShader->regs.num_spi_ps_input_cntl = readAt(data, pos); + for (uint32_t &spi_ps : pixelShader->regs.spi_ps_input_cntls) { + spi_ps = readAt(data, pos); + } + pixelShader->regs.cb_shader_mask = readAt(data, pos); + pixelShader->regs.cb_shader_control = readAt(data, pos); + pixelShader->regs.db_shader_control = readAt(data, pos); + pixelShader->regs.spi_input_z = readAt(data, pos); + + // read program + pixelShader->size = readAt(data, pos); + pixelShader->program = memalign(256, pixelShader->size); + for (uint32_t i = 0; i < pixelShader->size; i++) { + ((uint8_t *) pixelShader->program)[i] = readAt(data, pos); + } + pixelShader->mode = readAt(data, pos); + + // read uniform blocks + pixelShader->uniformBlockCount = readAt(data, pos); + if (pixelShader->uniformBlockCount > 0) { + pixelShader->uniformBlocks = static_cast(malloc(sizeof(GX2UniformBlock) * pixelShader->uniformBlockCount)); + for (uint32_t i = 0; i < pixelShader->uniformBlockCount; i++) { + pixelShader->uniformBlocks[i].name = readString(data, pos); + pixelShader->uniformBlocks[i].offset = readAt(data, pos); + pixelShader->uniformBlocks[i].size = readAt(data, pos); + } + } + + // read uniform vars + pixelShader->uniformVarCount = readAt(data, pos); + if (pixelShader->uniformVarCount > 0) { + pixelShader->uniformVars = static_cast(malloc(sizeof(GX2UniformVar) + pixelShader->uniformVarCount)); + for (uint32_t i = 0; i < pixelShader->uniformVarCount; i++) { + pixelShader->uniformVars[i].name = readString(data, pos); + pixelShader->uniformVars[i].type = readAt(data, pos); + pixelShader->uniformVars[i].count = readAt(data, pos); + pixelShader->uniformVars[i].offset = readAt(data, pos); + pixelShader->uniformVars[i].block = readAt(data, pos); + } + } + + // read initial values + pixelShader->initialValueCount = readAt(data, pos); + if (pixelShader->initialValueCount > 0) { + pixelShader->initialValues = static_cast(malloc(sizeof(GX2UniformInitialValue) * pixelShader->initialValueCount)); + for (uint32_t i = 0; i < pixelShader->initialValueCount; i++) { + pixelShader->initialValues[i].value[0] = readAt(data, pos); + pixelShader->initialValues[i].value[1] = readAt(data, pos); + pixelShader->initialValues[i].value[2] = readAt(data, pos); + pixelShader->initialValues[i].value[3] = readAt(data, pos); + pixelShader->initialValues[i].offset = readAt(data, pos); + } + } + + pixelShader->loopVarCount = readAt(data, pos); + if (pixelShader->loopVarCount > 0) { + pixelShader->loopVars = static_cast(malloc(sizeof(GX2LoopVar) * pixelShader->loopVarCount)); + for (uint32_t i = 0; i < pixelShader->loopVarCount; i++) { + pixelShader->loopVars[i].offset = readAt(data, pos); + pixelShader->loopVars[i].value = readAt(data, pos); + } + } + + pixelShader->samplerVarCount = readAt(data, pos); + if (pixelShader->samplerVarCount > 0) { + pixelShader->samplerVars = static_cast(malloc(sizeof(GX2SamplerVar) * pixelShader->samplerVarCount)); + for (uint32_t i = 0; i < pixelShader->samplerVarCount; i++) { + pixelShader->samplerVars[i].name = readString(data, pos); + pixelShader->samplerVars[i].type = readAt(data, pos); + pixelShader->samplerVars[i].location = readAt(data, pos); + } + } + + pixelShader->gx2rBuffer = readGX2RBuffer(data, pos); + + return pixelShaderWrapper; +} \ No newline at end of file diff --git a/source/utils/ShaderSerializer.h b/source/utils/ShaderSerializer.h new file mode 100644 index 0000000..bb2e50e --- /dev/null +++ b/source/utils/ShaderSerializer.h @@ -0,0 +1,120 @@ +#pragma once + +#include +#include +#include +#include + +class GX2PixelShaderWrapper { +public: + [[nodiscard]] GX2PixelShader *getPixelShader() { + return &pixelShader; + } + + ~GX2PixelShaderWrapper() { + if (pixelShader.program) { + free(pixelShader.program); + } + + if (pixelShader.uniformBlocks) { + for (uint32_t i = 0; i < pixelShader.uniformBlockCount; i++) { + free((void *) pixelShader.uniformBlocks[i].name); + } + + free(pixelShader.uniformBlocks); + } + + if (pixelShader.uniformVars) { + for (uint32_t i = 0; i < pixelShader.uniformVarCount; i++) { + free((void *) pixelShader.uniformVars[i].name); + } + free(pixelShader.uniformVars); + } + + if (pixelShader.initialValues) { + free(pixelShader.initialValues); + } + + if (pixelShader.samplerVars) { + for (uint32_t i = 0; i < pixelShader.samplerVarCount; i++) { + free((void *) pixelShader.samplerVars[i].name); + } + free(pixelShader.samplerVars); + } + + if (pixelShader.loopVars) { + free(pixelShader.loopVars); + } + + if (pixelShader.gx2rBuffer.buffer) { + free(pixelShader.gx2rBuffer.buffer); + } + } + +private: + alignas(0x40) GX2PixelShader pixelShader; +}; + +class GX2VertexShaderWrapper { +public: + [[nodiscard]] GX2VertexShader *getVertexShader() { + return &vertexShader; + } + + ~GX2VertexShaderWrapper() { + if (vertexShader.program) { + free(vertexShader.program); + } + + if (vertexShader.uniformBlocks) { + for (uint32_t i = 0; i < vertexShader.uniformBlockCount; i++) { + free((void *) vertexShader.uniformBlocks[i].name); + } + + free(vertexShader.uniformBlocks); + } + + if (vertexShader.uniformVars) { + for (uint32_t i = 0; i < vertexShader.uniformVarCount; i++) { + free((void *) vertexShader.uniformVars[i].name); + } + + free(vertexShader.uniformVars); + } + + if (vertexShader.initialValues) { + free(vertexShader.initialValues); + } + + if (vertexShader.loopVars) { + free(vertexShader.loopVars); + } + + if (vertexShader.samplerVars) { + for (uint32_t i = 0; i < vertexShader.samplerVarCount; i++) { + free((void *) vertexShader.samplerVars[i].name); + } + + free(vertexShader.samplerVars); + } + + if (vertexShader.attribVars) { + for (uint32_t i = 0; i < vertexShader.attribVarCount; i++) { + free((void *) vertexShader.attribVars[i].name); + } + + free(vertexShader.attribVars); + } + } + +private: + alignas(0x40) GX2VertexShader vertexShader; +}; + +std::vector SerializeVertexShader(GX2VertexShader *vertexShader); + +std::vector SerializePixelShader(GX2PixelShader *pixelShader); + +std::unique_ptr DeserializeVertexShader(const std::span &data); + +std::unique_ptr DeserializePixelShader(const std::span &data); diff --git a/source/utils/SplashScreenDrawer.cpp b/source/utils/SplashScreenDrawer.cpp new file mode 100644 index 0000000..6ca87a7 --- /dev/null +++ b/source/utils/SplashScreenDrawer.cpp @@ -0,0 +1,220 @@ +#include "SplashScreenDrawer.h" +#include "ShaderSerializer.h" +#include "TGATexture.h" +#include "gfx.h" +#include "logger.h" +#include "utils.h" +#include +#include +#include +#include +#include + +/* +constexpr const char *s_textureVertexShader = R"( +#version 450 + +layout(location = 0) in vec2 aPos; +layout(location = 1) in vec2 aTexCoord; + +layout(location = 0) out vec2 TexCoord; + +void main() +{ + TexCoord = aTexCoord; + gl_Position = vec4(aPos.x, aPos.y, 0.0f, 1.0f); +} +)"; + */ + +constexpr uint8_t s_textureVertexShaderCompiled[] = { + 0x00, 0x00, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0x8A, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x80, 0x89, 0x00, 0x40, 0x01, 0xC0, 0xC8, 0x0F, 0x00, 0x94, + 0x3C, 0xA0, 0x00, 0xC0, 0x08, 0x0B, 0x00, 0x94, 0x05, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x80, + 0x00, 0x00, 0x00, 0x80, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x0F, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x61, 0x50, 0x6F, 0x73, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x61, 0x54, 0x65, 0x78, 0x43, 0x6F, 0x6F, 0x72, + 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00}; + +/* +constexpr const char *s_texturePixelShader = R"( +#version 450 +#extension GL_ARB_shading_language_420pack: enable + +layout(location = 0) in vec2 TexCoord; + +layout(location = 0) out vec4 FragColor; + +layout(binding = 0) uniform sampler2D inTexture; + +void main() +{ + FragColor = texture(inTexture, TexCoord); +} +)";*/ +constexpr uint8_t s_texturePixelShaderCompiled[] = { + 0x00, 0x20, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x10, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x8A, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0xC0, + 0x88, 0x06, 0x20, 0x94, 0x10, 0x00, 0x00, 0x00, 0x00, 0x10, 0x0D, 0xF0, + 0x00, 0x00, 0x80, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x0F, 0xFF, + 0x00, 0x00, 0x00, 0x01, 0x69, 0x6E, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, + 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +static GX2Texture *LoadTGAAsTexture(std::string_view path) { + std::vector buffer; + if (!LoadFileIntoBuffer(path, buffer)) { + return nullptr; + } + + auto *texture = TGA_LoadTexture(buffer); + if (texture == nullptr) { + return nullptr; + } + return texture; +} + +SplashScreenDrawer::SplashScreenDrawer(std::string_view meta_dir) { + // create shader group + mVertexShaderWrapper = DeserializeVertexShader(s_textureVertexShaderCompiled); + mPixelShaderWrapper = DeserializePixelShader(s_texturePixelShaderCompiled); + + mShaderGroup = {}; + mShaderGroup.vertexShader = mVertexShaderWrapper->getVertexShader(); + mShaderGroup.pixelShader = mPixelShaderWrapper->getPixelShader(); + + GX2Invalidate(GX2_INVALIDATE_MODE_CPU_SHADER, mShaderGroup.vertexShader->program, mShaderGroup.vertexShader->size); + GX2Invalidate(GX2_INVALIDATE_MODE_CPU_SHADER, mShaderGroup.pixelShader->program, mShaderGroup.pixelShader->size); + + GX2SetShaderMode(GX2_SHADER_MODE_UNIFORM_BLOCK); + + GfxInitShaderAttribute(&mShaderGroup, "aPos", 0, 0, GX2_ATTRIB_FORMAT_FLOAT_32_32); + GfxInitShaderAttribute(&mShaderGroup, "aTexCoord", 1, 0, GX2_ATTRIB_FORMAT_FLOAT_32_32); + GfxInitFetchShader(&mShaderGroup); + + // upload vertex position + mPositionBuffer.flags = GX2R_RESOURCE_BIND_VERTEX_BUFFER | GX2R_RESOURCE_USAGE_CPU_READ | GX2R_RESOURCE_USAGE_CPU_WRITE | GX2R_RESOURCE_USAGE_GPU_READ; + mPositionBuffer.elemSize = 2 * sizeof(float); + mPositionBuffer.elemCount = 4; + GX2RCreateBuffer(&mPositionBuffer); + void *posUploadBuffer = GX2RLockBufferEx(&mPositionBuffer, GX2R_RESOURCE_BIND_NONE); + memcpy(posUploadBuffer, mPositionData, mPositionBuffer.elemSize * mPositionBuffer.elemCount); + GX2RUnlockBufferEx(&mPositionBuffer, GX2R_RESOURCE_BIND_NONE); + + // upload texture coords + mTexCoordBuffer.flags = GX2R_RESOURCE_BIND_VERTEX_BUFFER | GX2R_RESOURCE_USAGE_CPU_READ | GX2R_RESOURCE_USAGE_CPU_WRITE | GX2R_RESOURCE_USAGE_GPU_READ; + mTexCoordBuffer.elemSize = 2 * sizeof(float); + mTexCoordBuffer.elemCount = 4; + GX2RCreateBuffer(&mTexCoordBuffer); + void *coordsUploadBuffer = GX2RLockBufferEx(&mTexCoordBuffer, GX2R_RESOURCE_BIND_NONE); + memcpy(coordsUploadBuffer, mTexCoords, mTexCoordBuffer.elemSize * mTexCoordBuffer.elemCount); + GX2RUnlockBufferEx(&mTexCoordBuffer, GX2R_RESOURCE_BIND_NONE); + + std::string bootTvTex = std::string(meta_dir).append("/bootTvTex.tga"); + std::string bootDrcTex = std::string(meta_dir).append("/bootDrcTex.tga"); + mTextureTV = LoadTGAAsTexture(bootTvTex); + mTextureDRC = LoadTGAAsTexture(bootDrcTex); + + GX2Sampler sampler; + GX2InitSampler(&sampler, GX2_TEX_CLAMP_MODE_CLAMP, GX2_TEX_XY_FILTER_MODE_LINEAR); +} + +void SplashScreenDrawer::Draw() { + if (!mTextureTV || !mTextureDRC) { + DEBUG_FUNCTION_LINE_ERR("Textures are missing"); + return; + } + + GfxBeginRender(); + + GfxBeginRenderTV(); + GX2SetFetchShader(&mShaderGroup.fetchShader); + GX2SetVertexShader(mShaderGroup.vertexShader); + GX2SetPixelShader(mShaderGroup.pixelShader); + GX2SetShaderMode(GX2_SHADER_MODE_UNIFORM_BLOCK); + + GX2RSetAttributeBuffer(&mPositionBuffer, 0, mPositionBuffer.elemSize, 0); + GX2RSetAttributeBuffer(&mTexCoordBuffer, 1, mTexCoordBuffer.elemSize, 0); + GX2SetPixelTexture(mTextureTV, mShaderGroup.pixelShader->samplerVars[0].location); + GX2SetPixelSampler(&mSampler, mShaderGroup.pixelShader->samplerVars[0].location); + + GX2DrawEx(GX2_PRIMITIVE_MODE_QUADS, 4, 0, 1); + GfxFinishRenderTV(); + + GfxBeginRenderDRC(); + GX2SetFetchShader(&mShaderGroup.fetchShader); + GX2SetVertexShader(mShaderGroup.vertexShader); + GX2SetPixelShader(mShaderGroup.pixelShader); + GX2SetShaderMode(GX2_SHADER_MODE_UNIFORM_BLOCK); + + GX2RSetAttributeBuffer(&mPositionBuffer, 0, mPositionBuffer.elemSize, 0); + GX2RSetAttributeBuffer(&mTexCoordBuffer, 1, mTexCoordBuffer.elemSize, 0); + GX2SetPixelTexture(mTextureDRC, mShaderGroup.pixelShader->samplerVars[0].location); + GX2SetPixelSampler(&mSampler, mShaderGroup.pixelShader->samplerVars[0].location); + + GX2DrawEx(GX2_PRIMITIVE_MODE_QUADS, 4, 0, 1); + GfxFinishRenderDRC(); + + GfxFinishRender(); +} + +SplashScreenDrawer::~SplashScreenDrawer() { + GX2RDestroyBufferEx(&mPositionBuffer, GX2R_RESOURCE_BIND_NONE); + GX2RDestroyBufferEx(&mTexCoordBuffer, GX2R_RESOURCE_BIND_NONE); + if (mTextureTV) { + if (mTextureTV->surface.image != nullptr) { + free(mTextureTV->surface.image); + mTextureTV->surface.image = nullptr; + } + ::free(mTextureTV); + mTextureTV = nullptr; + } + if (mTextureDRC) { + if (mTextureDRC->surface.image != nullptr) { + free(mTextureDRC->surface.image); + mTextureDRC->surface.image = nullptr; + } + ::free(mTextureDRC); + mTextureDRC = nullptr; + } +} diff --git a/source/utils/SplashScreenDrawer.h b/source/utils/SplashScreenDrawer.h new file mode 100644 index 0000000..c002854 --- /dev/null +++ b/source/utils/SplashScreenDrawer.h @@ -0,0 +1,50 @@ +#pragma once + +#include "ShaderSerializer.h" +#include "gfx.h" +#include +#include +#include +#include +#include + +class SplashScreenDrawer { +public: + explicit SplashScreenDrawer(std::string_view meta_dir); + + void Draw(); + + virtual ~SplashScreenDrawer(); + +private: + const float mPositionData[8] = { + -1.0f, + -1.0f, + 1.0f, + -1.0f, + 1.0f, + 1.0f, + -1.0f, + 1.0f, + }; + + const float mTexCoords[8] = { + 0.0f, + 1.0f, + 1.0f, + 1.0f, + 1.0f, + 0.0f, + 0.0f, + 0.0f, + }; + + WHBGfxShaderGroup mShaderGroup = {}; + std::unique_ptr mVertexShaderWrapper; + std::unique_ptr mPixelShaderWrapper; + GX2RBuffer mPositionBuffer = {}; + GX2RBuffer mTexCoordBuffer = {}; + GX2Texture *mTextureTV = nullptr; + GX2Texture *mTextureDRC = nullptr; + GX2Sampler mSampler = {}; +}; diff --git a/source/utils/SplashSoundPlayer.cpp b/source/utils/SplashSoundPlayer.cpp new file mode 100644 index 0000000..31e81bc --- /dev/null +++ b/source/utils/SplashSoundPlayer.cpp @@ -0,0 +1,102 @@ +#include "SplashSoundPlayer.h" +#include "logger.h" +#include "utils.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +SplashSoundPlayer::SplashSoundPlayer(std::string_view meta_dir) { + std::string bootSound = std::string(meta_dir).append("/bootSound.btsnd"); + + if (!LoadFileIntoBuffer(bootSound, mBuffer)) { + mBuffer.clear(); + return; + } + if (mBuffer.size() < 8) { + return; + } + + auto target = ((uint32_t *) mBuffer.data())[0]; + if (target <= 2) { + mOutputTarget = static_cast(target); + } + + mLoopPoint = ((uint32_t *) mBuffer.data())[1]; +} + +void SplashSoundPlayer::Play() { + if (mBuffer.empty() || mBuffer.size() < 8) { + DEBUG_FUNCTION_LINE_WARN("mBuffed is empty or too small"); + return; + } + AXTransitionAudioBuffer transitionAudioBuffer = {}; + + AXInit(); + AXDeviceMode tvMode; + AXDeviceMode drcMode; + AXGetDeviceMode(0, &tvMode); + AXGetDeviceMode(1, &drcMode); + AXQuit(); + + WHBLogPrintf("TV mode before transition = %d\n", tvMode); + transitionAudioBuffer.tv.mode = tvMode; + + WHBLogPrintf("DRC mode before transition = %d\n", drcMode); + transitionAudioBuffer.drc.mode = drcMode; + + transitionAudioBuffer.unk1 = 1; + transitionAudioBuffer.unk2 = 0; + + transitionAudioBuffer.tv.unk1 = 0.99987207655f; + transitionAudioBuffer.tv.unk2 = 600; + + transitionAudioBuffer.drc.unk1 = transitionAudioBuffer.tv.unk1; + transitionAudioBuffer.tv.unk2 = transitionAudioBuffer.tv.unk2; + + switch (mOutputTarget) { + case TV_ONLY: + transitionAudioBuffer.tv.enabled = true; + transitionAudioBuffer.drc.enabled = false; + break; + case DRC_ONLY: + transitionAudioBuffer.tv.enabled = false; + transitionAudioBuffer.drc.enabled = true; + break; + case BOTH: + transitionAudioBuffer.tv.enabled = true; + transitionAudioBuffer.drc.enabled = true; + break; + } + + void *audioBuffer = (void *) nullptr; + uint32_t audioBufferLen = 0; + auto res = __OSGetTransitionAudioBuffer(&audioBuffer, &audioBufferLen); + if (res == 0) { + DEBUG_FUNCTION_LINE_ERR("Could not get access to audio buffer from foreground bucket\n"); + return; + } + + std::span audioBufferIn(mBuffer.data() + 8, mBuffer.size() - 8); + + DEBUG_FUNCTION_LINE("Got audio buffer from foreground bucket @ %8.8x len = %d\n", audioBuffer, audioBufferLen); + if (audioBufferLen < mBuffer.size()) { + DEBUG_FUNCTION_LINE_ERR("buffer not big enough"); + return; + } + + memcpy(audioBuffer, audioBufferIn.data(), audioBufferIn.size()); + __OSSetTransitionAudioSize(audioBufferIn.size()); + + transitionAudioBuffer.length = audioBufferIn.size(); + transitionAudioBuffer.loopPoint = mLoopPoint; + transitionAudioBuffer.audioBuffer = audioBuffer; + transitionAudioBuffer.audioBufferLen = audioBufferLen; + AXSetUpTransitionAudio((AXTransitionAudioBuffer *) &transitionAudioBuffer); + AXStartTransitionAudio(); +} diff --git a/source/utils/SplashSoundPlayer.h b/source/utils/SplashSoundPlayer.h new file mode 100644 index 0000000..2fa472d --- /dev/null +++ b/source/utils/SplashSoundPlayer.h @@ -0,0 +1,23 @@ +#pragma once +#include +#include +#include + +class SplashSoundPlayer { +public: + SplashSoundPlayer(std::string_view meta_dir); + + void Play(); + + virtual ~SplashSoundPlayer() = default; + +private: + enum TransitionAudioTarget { + TV_ONLY, + DRC_ONLY, + BOTH + }; + std::vector mBuffer; + TransitionAudioTarget mOutputTarget = BOTH; + uint32_t mLoopPoint = 0; +}; diff --git a/source/utils/TGATexture.cpp b/source/utils/TGATexture.cpp new file mode 100644 index 0000000..a045eb7 --- /dev/null +++ b/source/utils/TGATexture.cpp @@ -0,0 +1,84 @@ +#include +#include +#include +#include + +#include "TGATexture.h" +#include "logger.h" +#include + +/* + * Based on + * https://github.com/Xpl0itU/savemii/blob/70e3b63db52113519230e1e39bd56876cef12dc8/src/tga_reader.cpp + * and + * 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); +} + +GX2Texture *TGA_LoadTexture(std::span data) { + TGA_HEADER *tgaHeader = (TGA_HEADER *) data.data(); + + uint32_t width = _swapU16(tgaHeader->width); + uint32_t height = _swapU16(tgaHeader->height); + + if (tgaHeader->bits != 24) { + DEBUG_FUNCTION_LINE_WARN("Only 24bit TGA images are supported"); + return nullptr; + } + if (tgaHeader->imagetype != 2 && tgaHeader->imagetype != 3) { + DEBUG_FUNCTION_LINE_WARN("Only uncompressed TGA images are supported"); + return nullptr; + } + + GX2Texture *texture = (GX2Texture *) malloc(sizeof(GX2Texture)); + *texture = {}; + + 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) { + return nullptr; + } + + texture->surface.image = memalign(texture->surface.alignment, texture->surface.imageSize); + if (!texture->surface.image) { + return nullptr; + } + + 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); + + 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++; + } + } + + // 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); + + return texture; +} diff --git a/source/utils/TGATexture.h b/source/utils/TGATexture.h new file mode 100644 index 0000000..bcc0e94 --- /dev/null +++ b/source/utils/TGATexture.h @@ -0,0 +1,24 @@ +#pragma once + +#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) +}; + +// quick and dirty 24-bit TGA loader +GX2Texture *TGA_LoadTexture(std::span data); \ No newline at end of file diff --git a/source/utils/gfx.c b/source/utils/gfx.c new file mode 100644 index 0000000..34e73cd --- /dev/null +++ b/source/utils/gfx.c @@ -0,0 +1,549 @@ +/* + * Based on https://github.com/devkitPro/wut/blob/4933211d7ba86d4dd45c8525fc83747d799ecf31/libraries/libwhb/src/gfx.c + */ +#include "logger.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define WHB_GFX_COMMAND_BUFFER_POOL_SIZE (0x400000) + +static void *sCommandBufferPool = NULL; +static GX2DrcRenderMode sDrcRenderMode; +static void *sDrcScanBuffer = NULL; +static uint32_t sDrcScanBufferSize = 0; +static GX2SurfaceFormat sDrcSurfaceFormat; +static GX2TVRenderMode sTvRenderMode; +static void *sTvScanBuffer = NULL; +static uint32_t sTvScanBufferSize = 0; +static GX2SurfaceFormat sTvSurfaceFormat; +static GX2SurfaceFormat sDrcSurfaceFormat; +static GX2ColorBuffer sTvColourBuffer = {0}; +static GX2ColorBuffer sDrcColourBuffer = {0}; +static GX2ContextState *sTvContextState = NULL; +static GX2ContextState *sDrcContextState = NULL; +static BOOL sGpuTimedOut = FALSE; + +static void *sGfxHeapForeground = NULL; + +static void *AllocMEM2(uint32_t size, uint32_t alignment) { + void *block; + + if (alignment < 4) { + alignment = 4; + } + + block = MEMAllocFromDefaultHeapEx(size, alignment); + if (!block) { + OSFatal("AutobootModule: Failed to allocate memory from MEM2"); + } + return block; +} + +static void FreeMEM2(void *block) { + MEMFreeToDefaultHeap(block); +} + +static void *AllocBucket(uint32_t size, uint32_t alignment) { + void *block; + + if (!sGfxHeapForeground) { + DEBUG_FUNCTION_LINE_ERR("sGfxHeapForeground was NULL"); + return NULL; + } + + if (alignment < 4) { + alignment = 4; + } + + block = MEMAllocFromExpHeapEx(sGfxHeapForeground, size, alignment); + if (!block) { + DEBUG_FUNCTION_LINE_ERR("Failed to allocate memory from bucket"); + } + return block; +} + +static void FreeBucket(void *block) { + if (!sGfxHeapForeground) { + DEBUG_FUNCTION_LINE_ERR("sGfxHeapForeground was NULL"); + return; + } + + MEMFreeToExpHeap(sGfxHeapForeground, block); +} + +static void * +GfxGX2RAlloc(GX2RResourceFlags flags, uint32_t size, uint32_t alignment) { + return AllocMEM2(size, alignment); +} + +static void +GfxGX2RFree(GX2RResourceFlags flags, void *block) { + return FreeMEM2(block); +} + +static void +GfxInitTvColourBuffer(GX2ColorBuffer *cb, + uint32_t width, + uint32_t height, + GX2SurfaceFormat format, + GX2AAMode aa) { + memset(cb, 0, sizeof(GX2ColorBuffer)); + cb->surface.use = GX2_SURFACE_USE_TEXTURE_COLOR_BUFFER_TV; + cb->surface.dim = GX2_SURFACE_DIM_TEXTURE_2D; + cb->surface.width = width; + cb->surface.height = height; + cb->surface.depth = 1; + cb->surface.mipLevels = 1; + cb->surface.format = format; + cb->surface.aa = aa; + cb->surface.tileMode = GX2_TILE_MODE_DEFAULT; + cb->viewNumSlices = 1; + GX2CalcSurfaceSizeAndAlignment(&cb->surface); + GX2InitColorBufferRegs(cb); +} + +static uint32_t InitMemory() { + // Allocate TV scan buffer. + sTvScanBuffer = AllocBucket(sTvScanBufferSize, GX2_SCAN_BUFFER_ALIGNMENT); + if (!sTvScanBuffer) { + DEBUG_FUNCTION_LINE_INFO("%s: sTvScanBuffer = AllocBucket(0x%X, 0x%X) failed", + __FUNCTION__, + sTvScanBufferSize, + GX2_SCAN_BUFFER_ALIGNMENT); + goto error; + } + GX2Invalidate(GX2_INVALIDATE_MODE_CPU, sTvScanBuffer, sTvScanBufferSize); + GX2SetTVBuffer(sTvScanBuffer, sTvScanBufferSize, sTvRenderMode, sTvSurfaceFormat, GX2_BUFFERING_MODE_SINGLE); + + // Allocate TV colour buffer. + sTvColourBuffer.surface.image = AllocMEM2(sTvColourBuffer.surface.imageSize, sTvColourBuffer.surface.alignment); + if (!sTvColourBuffer.surface.image) { + DEBUG_FUNCTION_LINE_INFO("%s: sTvColourBuffer = AllocMEM2(0x%X, 0x%X) failed", + __FUNCTION__, + sTvColourBuffer.surface.imageSize, + sTvColourBuffer.surface.alignment); + goto error; + } + GX2Invalidate(GX2_INVALIDATE_MODE_CPU, sTvColourBuffer.surface.image, sTvColourBuffer.surface.imageSize); + + // Allocate DRC scan buffer. + sDrcScanBuffer = AllocBucket(sDrcScanBufferSize, GX2_SCAN_BUFFER_ALIGNMENT); + if (!sDrcScanBuffer) { + DEBUG_FUNCTION_LINE_INFO("%s: sDrcScanBuffer = AllocBucket(0x%X, 0x%X) failed", + __FUNCTION__, + sDrcScanBufferSize, + GX2_SCAN_BUFFER_ALIGNMENT); + goto error; + } + GX2Invalidate(GX2_INVALIDATE_MODE_CPU, sDrcScanBuffer, sDrcScanBufferSize); + GX2SetDRCBuffer(sDrcScanBuffer, sDrcScanBufferSize, sDrcRenderMode, sDrcSurfaceFormat, GX2_BUFFERING_MODE_SINGLE); + + // Allocate DRC colour buffer. + sDrcColourBuffer.surface.image = AllocMEM2(sDrcColourBuffer.surface.imageSize, sDrcColourBuffer.surface.alignment); + if (!sDrcColourBuffer.surface.image) { + DEBUG_FUNCTION_LINE_INFO("%s: sDrcColourBuffer = AllocMEM2(0x%X, 0x%X) failed", + __FUNCTION__, + sDrcColourBuffer.surface.imageSize, + sDrcColourBuffer.surface.alignment); + goto error; + } + GX2Invalidate(GX2_INVALIDATE_MODE_CPU, sDrcColourBuffer.surface.image, sDrcColourBuffer.surface.imageSize); + + return 0; + +error: + return -1; +} + +static uint32_t DeinitMemory() { + if (sTvScanBuffer) { + FreeBucket(sTvScanBuffer); + sTvScanBuffer = NULL; + } + + if (sTvColourBuffer.surface.image) { + FreeMEM2(sTvColourBuffer.surface.image); + sTvColourBuffer.surface.image = NULL; + } + + if (sDrcScanBuffer) { + FreeBucket(sDrcScanBuffer); + sDrcScanBuffer = NULL; + } + + if (sDrcColourBuffer.surface.image) { + FreeMEM2(sDrcColourBuffer.surface.image); + sDrcColourBuffer.surface.image = NULL; + } + + return 0; +} + +static BOOL initBucketHeap() { + MEMHeapHandle heap = MEMGetBaseHeapHandle(MEM_BASE_HEAP_FG); + uint32_t size; + void *base; + + size = MEMGetAllocatableSizeForFrmHeapEx(heap, 4); + if (!size) { + DEBUG_FUNCTION_LINE_WARN("%s: MEMAllocFromFrmHeapEx(heap, 0x%X, 4)", __FUNCTION__, size); + return FALSE; + } + + base = MEMAllocFromFrmHeapEx(heap, size, 4); + if (!base) { + DEBUG_FUNCTION_LINE_WARN("%s: MEMGetAllocatableSizeForFrmHeapEx == 0", __FUNCTION__); + return FALSE; + } + + sGfxHeapForeground = MEMCreateExpHeapEx(base, size, 0); + if (!sGfxHeapForeground) { + DEBUG_FUNCTION_LINE_WARN("%s: MEMCreateExpHeapEx(0x%08X, 0x%X, 0)", __FUNCTION__, base, size); + return FALSE; + } + + return TRUE; +} + +static BOOL deInitBucketHeap() { + MEMHeapHandle foreground = MEMGetBaseHeapHandle(MEM_BASE_HEAP_FG); + + if (sGfxHeapForeground) { + MEMDestroyExpHeap(sGfxHeapForeground); + sGfxHeapForeground = NULL; + } + + MEMFreeToFrmHeap(foreground, MEM_FRM_HEAP_FREE_ALL); + return TRUE; +} + +BOOL GfxInit() { + initBucketHeap(); + + uint32_t drcWidth, drcHeight; + uint32_t tvWidth, tvHeight; + uint32_t unk; + + sCommandBufferPool = AllocMEM2(WHB_GFX_COMMAND_BUFFER_POOL_SIZE, + GX2_COMMAND_BUFFER_ALIGNMENT); + if (!sCommandBufferPool) { + DEBUG_FUNCTION_LINE_INFO("%s: failed to allocate command buffer pool", __FUNCTION__); + goto error; + } + + uint32_t initAttribs[] = { + GX2_INIT_CMD_BUF_BASE, (uintptr_t) sCommandBufferPool, + GX2_INIT_CMD_BUF_POOL_SIZE, WHB_GFX_COMMAND_BUFFER_POOL_SIZE, + GX2_INIT_ARGC, 0, + GX2_INIT_ARGV, 0, + GX2_INIT_END}; + GX2Init(initAttribs); + + // Clear frame information in saved foreground to avoid screen corruption + __OSClearSavedFrame(OS_SAVED_FRAME_A, OS_SAVED_FRAME_SCREEN_TV); + __OSClearSavedFrame(OS_SAVED_FRAME_A, OS_SAVED_FRAME_SCREEN_DRC); + __OSClearSavedFrame(OS_SAVED_FRAME_B, OS_SAVED_FRAME_SCREEN_TV); + __OSClearSavedFrame(OS_SAVED_FRAME_B, OS_SAVED_FRAME_SCREEN_DRC); + + // Disable output until we have rendered something + GX2SetTVEnable(FALSE); + GX2SetDRCEnable(FALSE); + + sDrcRenderMode = GX2GetSystemDRCMode(); + sTvSurfaceFormat = GX2_SURFACE_FORMAT_UNORM_R8_G8_B8_A8; + sDrcSurfaceFormat = GX2_SURFACE_FORMAT_UNORM_R8_G8_B8_A8; + + switch (GX2GetSystemTVScanMode()) { + case GX2_TV_SCAN_MODE_480I: + case GX2_TV_SCAN_MODE_480P: + sTvRenderMode = GX2_TV_RENDER_MODE_WIDE_480P; + tvWidth = 854; + tvHeight = 480; + break; + case GX2_TV_SCAN_MODE_1080I: + case GX2_TV_SCAN_MODE_1080P: + sTvRenderMode = GX2_TV_RENDER_MODE_WIDE_1080P; + tvWidth = 1920; + tvHeight = 1080; + break; + case GX2_TV_SCAN_MODE_720P: + default: + sTvRenderMode = GX2_TV_RENDER_MODE_WIDE_720P; + tvWidth = 1280; + tvHeight = 720; + break; + } + + drcWidth = 854; + drcHeight = 480; + + // Setup TV and DRC buffers - they will be allocated in GfxProcCallbackAcquired. + GX2CalcTVSize(sTvRenderMode, sTvSurfaceFormat, GX2_BUFFERING_MODE_SINGLE, &sTvScanBufferSize, &unk); + GfxInitTvColourBuffer(&sTvColourBuffer, tvWidth, tvHeight, sTvSurfaceFormat, GX2_AA_MODE1X); + + GX2CalcDRCSize(sDrcRenderMode, sDrcSurfaceFormat, GX2_BUFFERING_MODE_DOUBLE, &sDrcScanBufferSize, &unk); + GfxInitTvColourBuffer(&sDrcColourBuffer, drcWidth, drcHeight, sDrcSurfaceFormat, GX2_AA_MODE1X); + + GX2CalcDRCSize(sDrcRenderMode, sDrcSurfaceFormat, GX2_BUFFERING_MODE_SINGLE, &sDrcScanBufferSize, &unk); + if (InitMemory() != 0) { + DEBUG_FUNCTION_LINE_INFO("%s: GfxProcCallbackAcquired failed", __FUNCTION__); + goto error; + } + + GX2RSetAllocator(&GfxGX2RAlloc, &GfxGX2RFree); + + // Initialise TV context state. + sTvContextState = AllocMEM2(sizeof(GX2ContextState), GX2_CONTEXT_STATE_ALIGNMENT); + if (!sTvContextState) { + DEBUG_FUNCTION_LINE_INFO("%s: failed to allocate sTvContextState", __FUNCTION__); + goto error; + } + GX2SetupContextStateEx(sTvContextState, TRUE); + GX2SetContextState(sTvContextState); + GX2SetColorBuffer(&sTvColourBuffer, GX2_RENDER_TARGET_0); + GX2SetViewport(0, 0, (float) sTvColourBuffer.surface.width, (float) sTvColourBuffer.surface.height, 0.0f, 1.0f); + GX2SetScissor(0, 0, (float) sTvColourBuffer.surface.width, (float) sTvColourBuffer.surface.height); + GX2SetTVScale((float) sTvColourBuffer.surface.width, (float) sTvColourBuffer.surface.height); + + // Initialise DRC context state. + sDrcContextState = AllocMEM2(sizeof(GX2ContextState), GX2_CONTEXT_STATE_ALIGNMENT); + if (!sDrcContextState) { + WHBLogPrintf("%s: failed to allocate sDrcContextState", __FUNCTION__); + goto error; + } + GX2SetupContextStateEx(sDrcContextState, TRUE); + GX2SetContextState(sDrcContextState); + GX2SetColorBuffer(&sDrcColourBuffer, GX2_RENDER_TARGET_0); + GX2SetViewport(0, 0, (float) sDrcColourBuffer.surface.width, (float) sDrcColourBuffer.surface.height, 0.0f, 1.0f); + GX2SetScissor(0, 0, (float) sDrcColourBuffer.surface.width, (float) sDrcColourBuffer.surface.height); + GX2SetDRCScale((float) sDrcColourBuffer.surface.width, (float) sDrcColourBuffer.surface.height); + + // Set 60fps VSync + GX2SetSwapInterval(1); + + return TRUE; + +error: + if (sCommandBufferPool) { + FreeMEM2(sCommandBufferPool); + sCommandBufferPool = NULL; + } + + if (sTvScanBuffer) { + FreeBucket(sTvScanBuffer); + sTvScanBuffer = NULL; + } + + if (sTvColourBuffer.surface.image) { + FreeMEM2(sTvColourBuffer.surface.image); + sTvColourBuffer.surface.image = NULL; + } + + if (sTvContextState) { + FreeMEM2(sTvContextState); + sTvContextState = NULL; + } + + if (sDrcContextState) { + FreeMEM2(sDrcContextState); + sDrcContextState = NULL; + } + + if (sDrcScanBuffer) { + FreeBucket(sDrcScanBuffer); + sDrcScanBuffer = NULL; + } + + if (sDrcColourBuffer.surface.image) { + FreeMEM2(sDrcColourBuffer.surface.image); + sDrcColourBuffer.surface.image = NULL; + } + + return FALSE; +} + +void GfxShutdown() { + if (sGpuTimedOut) { + GX2ResetGPU(0); + sGpuTimedOut = FALSE; + } + + GX2RSetAllocator(NULL, NULL); + + GX2Shutdown(); + + DeinitMemory(); + + if (sTvContextState) { + FreeMEM2(sTvContextState); + sTvContextState = NULL; + } + + if (sDrcContextState) { + FreeMEM2(sDrcContextState); + sDrcContextState = NULL; + } + + if (sCommandBufferPool) { + FreeMEM2(sCommandBufferPool); + sCommandBufferPool = NULL; + } + + deInitBucketHeap(); +} + +void GfxBeginRender() { + uint32_t swapCount, flipCount; + OSTime lastFlip, lastVsync; + uint32_t waitCount = 0; + + while (1) { + GX2GetSwapStatus(&swapCount, &flipCount, &lastFlip, &lastVsync); + + if (flipCount >= swapCount) { + break; + } + + if (waitCount >= 10) { + WHBLogPrint("WHBGfxBeginRender wait for swap timed out"); + sGpuTimedOut = TRUE; + break; + } + + waitCount++; + GX2WaitForVsync(); + } +} + +void GfxBeginRenderDRC() { + GX2SetContextState(sDrcContextState); +} + +void GfxFinishRenderDRC() { + GX2CopyColorBufferToScanBuffer(&sDrcColourBuffer, GX2_SCAN_TARGET_DRC); +} + + +void GfxBeginRenderTV() { + GX2SetContextState(sTvContextState); +} + +void GfxFinishRenderTV() { + GX2CopyColorBufferToScanBuffer(&sTvColourBuffer, GX2_SCAN_TARGET_TV); +} + +void GfxFinishRender() { + GX2SwapScanBuffers(); + GX2Flush(); + GX2DrawDone(); + GX2SetTVEnable(TRUE); + GX2SetDRCEnable(TRUE); +} + +BOOL GfxInitFetchShader(WHBGfxShaderGroup *group) { + uint32_t size = GX2CalcFetchShaderSizeEx(group->numAttributes, + GX2_FETCH_SHADER_TESSELLATION_NONE, + GX2_TESSELLATION_MODE_DISCRETE); + group->fetchShaderProgram = AllocMEM2(size, GX2_SHADER_PROGRAM_ALIGNMENT); + + GX2InitFetchShaderEx(&group->fetchShader, + group->fetchShaderProgram, + group->numAttributes, + group->attributes, + GX2_FETCH_SHADER_TESSELLATION_NONE, + GX2_TESSELLATION_MODE_DISCRETE); + + GX2Invalidate(GX2_INVALIDATE_MODE_CPU_SHADER, group->fetchShaderProgram, size); + return TRUE; +} + +static uint32_t +GfxGetAttribFormatSel(GX2AttribFormat format) { + switch (format) { + case GX2_ATTRIB_FORMAT_UNORM_8: + case GX2_ATTRIB_FORMAT_UINT_8: + case GX2_ATTRIB_FORMAT_SNORM_8: + case GX2_ATTRIB_FORMAT_SINT_8: + case GX2_ATTRIB_FORMAT_FLOAT_32: + return GX2_SEL_MASK(GX2_SQ_SEL_X, GX2_SQ_SEL_0, GX2_SQ_SEL_0, GX2_SQ_SEL_1); + case GX2_ATTRIB_FORMAT_UNORM_8_8: + case GX2_ATTRIB_FORMAT_UINT_8_8: + case GX2_ATTRIB_FORMAT_SNORM_8_8: + case GX2_ATTRIB_FORMAT_SINT_8_8: + case GX2_ATTRIB_FORMAT_FLOAT_32_32: + return GX2_SEL_MASK(GX2_SQ_SEL_X, GX2_SQ_SEL_Y, GX2_SQ_SEL_0, GX2_SQ_SEL_1); + case GX2_ATTRIB_FORMAT_FLOAT_32_32_32: + return GX2_SEL_MASK(GX2_SQ_SEL_X, GX2_SQ_SEL_Y, GX2_SQ_SEL_Z, GX2_SQ_SEL_1); + case GX2_ATTRIB_FORMAT_UNORM_8_8_8_8: + case GX2_ATTRIB_FORMAT_UINT_8_8_8_8: + case GX2_ATTRIB_FORMAT_SNORM_8_8_8_8: + case GX2_ATTRIB_FORMAT_SINT_8_8_8_8: + case GX2_ATTRIB_FORMAT_FLOAT_32_32_32_32: + return GX2_SEL_MASK(GX2_SQ_SEL_X, GX2_SQ_SEL_Y, GX2_SQ_SEL_Z, GX2_SQ_SEL_W); + break; + default: + return GX2_SEL_MASK(GX2_SQ_SEL_0, GX2_SQ_SEL_0, GX2_SQ_SEL_0, GX2_SQ_SEL_1); + } +} + +static int32_t +GfxGetVertexAttribVarLocation(const GX2VertexShader *shader, + const char *name) { + uint32_t i; + + for (i = 0; i < shader->attribVarCount; ++i) { + if (strcmp(shader->attribVars[i].name, name) == 0) { + return shader->attribVars[i].location; + } + } + + return -1; +} + +BOOL GfxInitShaderAttribute(WHBGfxShaderGroup *group, + const char *name, + uint32_t buffer, + uint32_t offset, + GX2AttribFormat format) { + GX2AttribStream *attrib; + int32_t location; + + location = GfxGetVertexAttribVarLocation(group->vertexShader, name); + if (location == -1) { + return FALSE; + } + + attrib = &group->attributes[group->numAttributes++]; + attrib->location = location; + attrib->buffer = buffer; + attrib->offset = offset; + attrib->format = format; + attrib->type = GX2_ATTRIB_INDEX_PER_VERTEX; + attrib->aluDivisor = 0; + attrib->mask = GfxGetAttribFormatSel(format); + attrib->endianSwap = GX2_ENDIAN_SWAP_DEFAULT; + return TRUE; +} \ No newline at end of file diff --git a/source/utils/gfx.h b/source/utils/gfx.h new file mode 100644 index 0000000..8c98dba --- /dev/null +++ b/source/utils/gfx.h @@ -0,0 +1,40 @@ +#pragma once +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +BOOL GfxInit(); + +void GfxShutdown(); + +void GfxBeginRender(); + +void GfxFinishRender(); + +void GfxBeginRenderDRC(); + +void GfxFinishRenderDRC(); + +void GfxBeginRenderTV(); + +void GfxFinishRenderTV(); + +BOOL GfxInitShaderAttribute(WHBGfxShaderGroup *group, + const char *name, + uint32_t buffer, + uint32_t offset, + GX2AttribFormat format); + +BOOL GfxInitFetchShader(WHBGfxShaderGroup *group); + +#ifdef __cplusplus +} +#endif + +/** @} */