422 lines
11 KiB
C
422 lines
11 KiB
C
/**
|
|
* Copyright (c) 2025 Dominic Masters
|
|
*
|
|
* This software is released under the MIT License.
|
|
* https://opensource.org/licenses/MIT
|
|
*/
|
|
|
|
#include "texture.h"
|
|
#include "assert/assert.h"
|
|
#include "util/memory.h"
|
|
#include "util/math.h"
|
|
#include "display/display.h"
|
|
|
|
const texture_t *TEXTURE_BOUND = NULL;
|
|
|
|
void textureInit(
|
|
texture_t *texture,
|
|
const int32_t width,
|
|
const int32_t height,
|
|
const textureformat_t format,
|
|
const texturedata_t data
|
|
) {
|
|
assertNotNull(texture, "Texture cannot be NULL");
|
|
assertTrue(width > 0 && height > 0, "width/height must be greater than 0");
|
|
|
|
memoryZero(texture, sizeof(texture_t));
|
|
texture->width = width;
|
|
texture->height = height;
|
|
texture->format = format;
|
|
|
|
assertTrue(width == mathNextPowTwo(width), "Width must be a power of 2.");
|
|
assertTrue(height == mathNextPowTwo(height), "Height must be a power of 2.");
|
|
|
|
#if DISPLAY_SDL2
|
|
glGenTextures(1, &texture->id);
|
|
glBindTexture(GL_TEXTURE_2D, texture->id);
|
|
|
|
switch(format) {
|
|
case TEXTURE_FORMAT_RGBA:
|
|
glTexImage2D(
|
|
GL_TEXTURE_2D, 0, format, width, height, 0,
|
|
format, GL_UNSIGNED_BYTE, (void*)data.rgbaColors
|
|
);
|
|
break;
|
|
|
|
case TEXTURE_FORMAT_PALETTE:
|
|
assertNotNull(data.paletteData, "Palette texture data cannot be NULL");
|
|
|
|
if(DISPLAY.usingShaderedPalettes) {
|
|
// Palette textures not supported, convert to GL_RED style texture
|
|
// so shader can perform the lookup.
|
|
uint8_t formatted[width * height];
|
|
for(int32_t i = 0; i < width * height; i++) {
|
|
uint8_t index = data.paletteData[i];
|
|
formatted[i] = index * 128;
|
|
}
|
|
glTexImage2D(
|
|
GL_TEXTURE_2D, 0, GL_R8, width, height, 0,
|
|
GL_RED, GL_UNSIGNED_BYTE, (void*)formatted
|
|
);
|
|
|
|
} else {
|
|
glTexImage2D(
|
|
GL_TEXTURE_2D,
|
|
0, GL_COLOR_INDEX8_EXT,
|
|
width, height,
|
|
0, GL_COLOR_INDEX8_EXT,
|
|
GL_UNSIGNED_BYTE, (void*)data.paletteData
|
|
);
|
|
// glColorTableEXT(
|
|
// GL_TEXTURE_2D, GL_RGBA, data.palette.palette->colorCount, GL_RGBA,
|
|
// GL_UNSIGNED_BYTE, (const void*)data.palette.palette->colors
|
|
// );
|
|
}
|
|
|
|
GLenum err = glGetError();
|
|
if(err != GL_NO_ERROR) {
|
|
printf("GL Error uploading palette texture: %d\n", err);
|
|
assertUnreachable("GL error uploading palette texture");
|
|
}
|
|
break;
|
|
|
|
default:
|
|
assertUnreachable("Unknown texture format");
|
|
break;
|
|
}
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
texture->ready = true;
|
|
|
|
#elif DOLPHIN
|
|
|
|
switch(format) {
|
|
case TEXTURE_FORMAT_RGBA:
|
|
assertTrue(
|
|
(width % 4) == 0 && (height % 4) == 0,
|
|
"RGB5A3 requires w/h multiple of 4 (or pad)"
|
|
);
|
|
|
|
// Convert to RGB5A3 format
|
|
size_t rgbaSize = width * height * sizeof(u16);
|
|
texture->rgba = (u16*)memalign(32, rgbaSize);
|
|
assertNotNull(texture->rgba, "Failed to allocate texture RGBA data");
|
|
|
|
for(uint32_t y = 0; y < height; ++y) {
|
|
for(uint32_t x = 0; x < width; ++x) {
|
|
const int src = y * width + x;
|
|
|
|
const int tileX = x >> 2;
|
|
const int tileY = y >> 2;
|
|
const int tilesPerRow = width >> 2;
|
|
const int tileIndex = tileY * tilesPerRow + tileX;
|
|
const int tileBaseWords = tileIndex * 16;
|
|
const int inTile = ((y & 3) << 2) + (x & 3);
|
|
const int dest = tileBaseWords + inTile;
|
|
|
|
color4b_t col = data.rgba.colors[src];
|
|
|
|
u16 outCol;
|
|
if(col.a < 255) {
|
|
// 0AAA RRRR GGGG BBBB
|
|
outCol = (
|
|
(0u << 15) |
|
|
((u16)(col.a >> 5) << 12) |
|
|
((u16)(col.r >> 4) << 8) |
|
|
((u16)(col.g >> 4) << 4) |
|
|
((u16)(col.b >> 4) << 0)
|
|
);
|
|
} else {
|
|
// 1RRRR RRGG GGGB BBBB
|
|
outCol = (
|
|
(1u << 15) |
|
|
((u16)(col.r >> 3) << 10) |
|
|
((u16)(col.g >> 3) << 5) |
|
|
((u16)(col.b >> 3) << 0)
|
|
);
|
|
}
|
|
texture->rgba[dest] = outCol;
|
|
}
|
|
}
|
|
|
|
DCFlushRange(texture->rgba, rgbaSize);
|
|
GX_InitTexObj(
|
|
&texture->texObj,
|
|
texture->rgba,
|
|
width, height,
|
|
GX_TF_RGB5A3,
|
|
GX_REPEAT, GX_REPEAT,
|
|
GX_FALSE
|
|
);
|
|
|
|
DCFlushRange(texture->rgba, rgbaSize);
|
|
GX_InvalidateTexAll();
|
|
|
|
GX_InitTexObjLOD(
|
|
&texture->texObj,
|
|
GX_NEAR, GX_NEAR,
|
|
0.0f, 0.0f, 0.0f,
|
|
GX_FALSE,
|
|
GX_FALSE,
|
|
GX_ANISO_1
|
|
);
|
|
break;
|
|
|
|
case TEXTURE_FORMAT_ALPHA: {
|
|
assertTrue(
|
|
(width % 4) == 0 && (height % 4) == 0,
|
|
"GX_TF_I8 requires w/h multiple of 4 (or pad)"
|
|
);
|
|
|
|
// 1 byte per pixel (I8), GX expects 4x4 tiled layout
|
|
const size_t alphaSize = (size_t)width * (size_t)height;
|
|
|
|
texture->alpha = (u8*)memalign(32, alphaSize);
|
|
assertNotNull(texture->alpha, "Failed to allocate alpha texture data");
|
|
|
|
const u32 tilesPerRow = ((u32)width) >> 3; // /8
|
|
|
|
for (u32 y = 0; y < (u32)height; ++y) {
|
|
const u32 tileY = y >> 2; // /4
|
|
const u32 inTileY = (y & 3) << 3; // (y%4)*8
|
|
|
|
for (u32 x = 0; x < (u32)width; ++x) {
|
|
const u32 srcI = y * (u32)width + x;
|
|
const u8 srcA = data.alpha.data[srcI]; // linear input
|
|
|
|
const u32 tileX = x >> 3; // /8
|
|
const u32 tileIndex = tileY * tilesPerRow + tileX;
|
|
|
|
const u32 tileBase = tileIndex * 32; // 8*4*1 = 32 bytes per tile
|
|
const u32 inTile = inTileY + (x & 7); // (y%4)*8 + (x%8)
|
|
|
|
texture->alpha[tileBase + inTile] = 0xFF - srcA;// Fixes inverted alpha.
|
|
}
|
|
}
|
|
|
|
// Flush CPU cache so GX sees the swizzled I8 texture data
|
|
DCFlushRange(texture->alpha, alphaSize);
|
|
|
|
// Initialize GX texture object with swizzled data
|
|
GX_InitTexObj(
|
|
&texture->texObj,
|
|
texture->alpha,
|
|
width, height,
|
|
GX_TF_I8,
|
|
GX_REPEAT, GX_REPEAT,
|
|
GX_FALSE
|
|
);
|
|
|
|
GX_InitTexObjLOD(
|
|
&texture->texObj,
|
|
GX_NEAR, GX_NEAR,
|
|
0.0f, 0.0f, 0.0f,
|
|
GX_FALSE,
|
|
GX_FALSE,
|
|
GX_ANISO_1
|
|
);
|
|
break;
|
|
}
|
|
|
|
case TEXTURE_FORMAT_PALETTE: {
|
|
// Not supported, convert to RGBA using lookup
|
|
color_t* formatted = memoryAllocate(width * height * sizeof(color_t));
|
|
for(int32_t i = 0; i < width * height; i++) {
|
|
uint8_t index = data.palette.data[i];
|
|
assertTrue(
|
|
index < data.palette.palette->colorCount,
|
|
"Palette index out of range"
|
|
);
|
|
formatted[i] = data.palette.palette->colors[index];
|
|
}
|
|
|
|
textureInit(
|
|
texture, width, height, TEXTURE_FORMAT_RGBA,
|
|
(texturedata_t){
|
|
.rgba = { .colors = formatted }
|
|
}
|
|
);
|
|
memoryFree(formatted);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
assertUnreachable("Unsupported texture format for Dolphin");
|
|
break;
|
|
}
|
|
|
|
texture->ready = true;
|
|
#endif
|
|
}
|
|
|
|
void textureBind(texture_t *texture) {
|
|
if(TEXTURE_BOUND == texture) return;
|
|
|
|
if(texture == NULL) {
|
|
#if DISPLAY_SDL2
|
|
glDisable(GL_TEXTURE_2D);
|
|
#elif DOLPHIN
|
|
GX_SetNumChans(0);
|
|
#endif
|
|
TEXTURE_BOUND = NULL;
|
|
return;
|
|
}
|
|
|
|
assertTrue(texture->ready, "Texture ID must be ready");
|
|
assertTrue(
|
|
texture->width > 0 && texture->height > 0,
|
|
"Texture width and height must be greater than 0"
|
|
);
|
|
|
|
#if DISPLAY_SDL2
|
|
glEnable(GL_TEXTURE_2D);
|
|
glBindTexture(GL_TEXTURE_2D, texture->id);
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
|
|
#elif DOLPHIN
|
|
GX_SetNumChans(1);
|
|
GX_LoadTexObj(&texture->texObj, GX_TEXMAP0);
|
|
#endif
|
|
TEXTURE_BOUND = texture;
|
|
}
|
|
|
|
void textureDispose(texture_t *texture) {
|
|
assertNotNull(texture, "Texture cannot be NULL");
|
|
assertTrue(texture->ready, "Texture ID must be ready");
|
|
|
|
if(TEXTURE_BOUND == texture) {
|
|
textureBind(NULL);
|
|
}
|
|
|
|
#if DISPLAY_SDL2
|
|
glDeleteTextures(1, &texture->id);
|
|
#elif DOLPHIN
|
|
switch(texture->format) {
|
|
case TEXTURE_FORMAT_RGBA:
|
|
free(texture->rgba);
|
|
break;
|
|
|
|
case TEXTURE_FORMAT_ALPHA:
|
|
free(texture->alpha);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
memoryZero(texture, sizeof(texture_t));
|
|
#endif
|
|
}
|
|
|
|
#if DOLPHIN
|
|
void textureDolphinUploadTEV() {
|
|
if(TEXTURE_BOUND == NULL) {
|
|
GX_SetNumTexGens(0);
|
|
GX_SetNumTevStages(1);
|
|
GX_SetTevOp(GX_TEVSTAGE0, GX_PASSCLR);
|
|
return;
|
|
}
|
|
|
|
// Add channel for vertex color
|
|
GX_SetNumChans(1);
|
|
GX_SetChanCtrl(
|
|
GX_COLOR0A0,// Store in color channel 0
|
|
GX_DISABLE,// Lighting disabled
|
|
GX_SRC_REG,// Ambient color?
|
|
GX_SRC_VTX,// Material color?
|
|
GX_LIGHTNULL,// Light Mask
|
|
GX_DF_NONE,// Diffuse function
|
|
GX_AF_NONE// Attenuation function
|
|
);
|
|
|
|
// One set of UVs
|
|
GX_SetNumTexGens(1);
|
|
GX_SetTexCoordGen(
|
|
GX_TEXCOORD0,
|
|
GX_TG_MTX2x4,
|
|
GX_TG_TEX0,
|
|
GX_IDENTITY
|
|
);
|
|
|
|
// Basically the shader setup
|
|
switch(TEXTURE_BOUND->format) {
|
|
case TEXTURE_FORMAT_RGBA:
|
|
// One TEV stage: vertex color * texture color
|
|
GX_SetNumTevStages(1);
|
|
GX_SetTevOp(GX_TEVSTAGE0, GX_MODULATE);
|
|
GX_SetTevOrder(
|
|
GX_TEVSTAGE0,
|
|
GX_TEXCOORD0,
|
|
GX_TEXMAP0,
|
|
GX_COLOR0A0
|
|
);
|
|
GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR);
|
|
GX_SetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0);
|
|
break;
|
|
|
|
case TEXTURE_FORMAT_ALPHA:
|
|
// One TEV stage: vertex color * texture color
|
|
GX_SetNumTevStages(1);
|
|
GX_SetTevOrder(
|
|
GX_TEVSTAGE0,
|
|
GX_TEXCOORD0,
|
|
GX_TEXMAP0,
|
|
GX_COLOR0A0
|
|
);
|
|
|
|
// Color = vertex color
|
|
GX_SetTevColorIn(
|
|
GX_TEVSTAGE0,
|
|
GX_CC_RASC,
|
|
GX_CC_ZERO,
|
|
GX_CC_ZERO,
|
|
GX_CC_ZERO
|
|
);
|
|
GX_SetTevColorOp(
|
|
GX_TEVSTAGE0,
|
|
GX_TEV_ADD,
|
|
GX_TB_ZERO,
|
|
GX_CS_SCALE_1,
|
|
GX_TRUE,
|
|
GX_TEVPREV
|
|
);
|
|
|
|
// Alpha = vertex alpha * I8 intensity
|
|
GX_SetTevAlphaIn(
|
|
GX_TEVSTAGE0,
|
|
GX_CA_RASA,
|
|
GX_CA_ZERO,
|
|
GX_CA_TEXA,
|
|
GX_CA_ZERO
|
|
);
|
|
GX_SetTevAlphaOp(
|
|
GX_TEVSTAGE0,
|
|
GX_TEV_ADD,
|
|
GX_TB_ZERO,
|
|
GX_CS_SCALE_1,
|
|
GX_TRUE,
|
|
GX_TEVPREV
|
|
);
|
|
|
|
GX_SetBlendMode(
|
|
GX_BM_BLEND,
|
|
GX_BL_SRCALPHA,
|
|
GX_BL_INVSRCALPHA,
|
|
GX_LO_CLEAR
|
|
);
|
|
|
|
GX_SetColorUpdate(GX_TRUE);
|
|
GX_SetAlphaUpdate(GX_TRUE);
|
|
|
|
break;
|
|
|
|
default:
|
|
assertUnreachable("Unknown texture format in meshDraw");
|
|
break;
|
|
}
|
|
}
|
|
#endif |