/** * 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/palette/palettelist.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; #if PSP assertTrue( width == mathNextPowTwo(width), "Width must be powers of 2 for PSP" ); assertTrue( height == mathNextPowTwo(height), "Height must be powers of 2 for PSP" ); #endif #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.rgba.colors ); break; case TEXTURE_FORMAT_ALPHA: glTexImage2D( GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, (void*)data.alpha.data ); break; case TEXTURE_FORMAT_PALETTE: assertNotNull(data.palette.data, "Palette texture data cannot be NULL"); assertTrue( data.palette.palette < PALETTE_LIST_COUNT, "Palette index out of range" ); // Get and validate the palette. const palette_t *pal = PALETTE_LIST[data.palette.palette]; assertNotNull(pal, "Palette cannot be NULL"); GLenum err = glGetError(); if(err != GL_NO_ERROR) { assertUnreachable("GL Error before setting color table"); } // Do we support paletted textures? bool_t havePalTex = false; #if PSP havePalTex = true; #else GLint mask = 0; glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &mask); if(mask & GL_CONTEXT_CORE_PROFILE_BIT) { GLint n=0; glGetIntegerv(GL_NUM_EXTENSIONS, &n); for(GLint i=0; icolorCount, "Palette index out of range" ); formatted[i] = pal->colors[index]; } glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (void*)formatted ); } else { // Supported. #if PSP // PSP requires the color table itself to be a power of two assertTrue( pal->colorCount == mathNextPowTwo(pal->colorCount), "Palette color count must be a power of 2 for PSP" ); #endif glTexImage2D( GL_TEXTURE_2D, 0, GL_COLOR_INDEX8_EXT, width, height, 0, GL_COLOR_INDEX8_EXT, GL_UNSIGNED_BYTE, (void*)data.palette.data ); glColorTableEXT( GL_TEXTURE_2D, GL_RGBA, pal->colorCount, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)pal->colors ); } 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_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glBindTexture(GL_TEXTURE_2D, 0); texture->ready = true; #elif DOLPHIN texture->format = format; 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_CLAMP, GX_CLAMP, 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_CLAMP, GX_CLAMP, GX_FALSE ); GX_InitTexObjLOD( &texture->texObj, GX_NEAR, GX_NEAR, 0.0f, 0.0f, 0.0f, GX_FALSE, GX_FALSE, GX_ANISO_1 ); 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); #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_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() { // 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 ); if(!TEXTURE_BOUND) { GX_SetNumTexGens(0); GX_SetNumTevStages(1); GX_SetTevOp(GX_TEVSTAGE0, GX_PASSCLR); return; } // 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_SetTevOrder( GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0 ); GX_SetTevOp(GX_TEVSTAGE0, GX_MODULATE); 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