Cores/Nds/Compute/ComputeRenderer3D.Texcache.cs
namespace Emulotl.Nds;
public sealed partial class ComputeRenderer3D
{
private static int TextureWidth( uint texParam ) => 8 << (int)((texParam >> 20) & 0x7);
private static int TextureHeight( uint texParam ) => 8 << (int)((texParam >> 23) & 0x7);
private ushort ReadTexU16( uint addr ) => (ushort)_gpu2d.ReadVRAM_Texture( addr, 2 );
private byte ReadTexU8( uint addr ) => (byte)_gpu2d.ReadVRAM_Texture( addr, 1 );
private uint ReadTexU32( uint addr ) => _gpu2d.ReadVRAM_Texture( addr, 4 );
private ushort ReadPalU16( uint addr ) => (ushort)_gpu2d.ReadVRAM_TexPal( addr, 2 );
private static uint ConvertRGB5ToRGB6( ushort val )
{
uint r = (uint)((val & 0x1F) << 1);
uint g = (uint)((val & 0x3E0) >> 4);
uint b = (uint)((val & 0x7C00) >> 9);
if ( r != 0 ) r++;
if ( g != 0 ) g++;
if ( b != 0 ) b++;
return r | (g << 8) | (b << 16);
}
private static ushort ColorAvg( ushort c0, ushort c1 )
{
uint r0 = (uint)(c0 & 0x001F), g0 = (uint)(c0 & 0x03E0), b0 = (uint)(c0 & 0x7C00);
uint r1 = (uint)(c1 & 0x001F), g1 = (uint)(c1 & 0x03E0), b1 = (uint)(c1 & 0x7C00);
uint r = (r0 + r1) >> 1;
uint g = ((g0 + g1) >> 1) & 0x03E0;
uint b = ((b0 + b1) >> 1) & 0x7C00;
return (ushort)(r | g | b);
}
private static ushort Color5of3( ushort c0, ushort c1 )
{
uint r0 = (uint)(c0 & 0x001F), g0 = (uint)(c0 & 0x03E0), b0 = (uint)(c0 & 0x7C00);
uint r1 = (uint)(c1 & 0x001F), g1 = (uint)(c1 & 0x03E0), b1 = (uint)(c1 & 0x7C00);
uint r = (r0 * 5 + r1 * 3) >> 3;
uint g = ((g0 * 5 + g1 * 3) >> 3) & 0x03E0;
uint b = ((b0 * 5 + b1 * 3) >> 3) & 0x7C00;
return (ushort)(r | g | b);
}
private static ushort Color3of5( ushort c0, ushort c1 )
{
uint r0 = (uint)(c0 & 0x001F), g0 = (uint)(c0 & 0x03E0), b0 = (uint)(c0 & 0x7C00);
uint r1 = (uint)(c1 & 0x001F), g1 = (uint)(c1 & 0x03E0), b1 = (uint)(c1 & 0x7C00);
uint r = (r0 * 3 + r1 * 5) >> 3;
uint g = ((g0 * 3 + g1 * 5) >> 3) & 0x03E0;
uint b = ((b0 * 3 + b1 * 5) >> 3) & 0x7C00;
return (ushort)(r | g | b);
}
private ulong HashTexRange( ulong h, uint addr, uint size )
{
for ( uint o = 0; o < size; o += 4 )
h = (h ^ ReadTexU32( addr + o )) * 1099511628211UL;
return h;
}
private ulong HashPalRange( ulong h, uint addr, uint size )
{
for ( uint o = 0; o < size; o += 4 )
h = (h ^ _gpu2d.ReadVRAM_TexPal( addr + o, 4 )) * 1099511628211UL;
return h;
}
private ulong ComputeTexSourceHash( uint texParam, uint texPalette )
{
int width = TextureWidth( texParam );
int height = TextureHeight( texParam );
uint fmt = (texParam >> 26) & 0x7;
uint addr = (texParam & 0xFFFF) * 8;
uint palBase = texPalette & 0x1FFF;
ulong h = 1469598103934665603UL;
if ( fmt == 7 )
{
h = HashTexRange( h, addr, (uint)(width * height * 2) );
}
else if ( fmt == 5 )
{
uint slot1addr = 0x20000 + ((addr & 0x1FFFC) >> 1);
if ( addr >= 0x40000 ) slot1addr += 0x10000;
h = HashTexRange( h, addr, (uint)(width * height / 16 * 4) );
h = HashTexRange( h, slot1addr, (uint)(width * height / 16 * 2) );
h = HashPalRange( h, palBase * 16, 0x10000 );
}
else
{
uint palAddr = palBase * 16;
uint texSize;
uint numPalEntries;
switch ( fmt )
{
case 1: texSize = (uint)(width * height); numPalEntries = 32; break;
case 6: texSize = (uint)(width * height); numPalEntries = 8; break;
case 2: texSize = (uint)(width * height / 4); numPalEntries = 4; palAddr >>= 1; break;
case 3: texSize = (uint)(width * height / 2); numPalEntries = 16; break;
default: texSize = (uint)(width * height); numPalEntries = 256; break;
}
palAddr &= 0x1FFFF;
h = HashTexRange( h, addr, texSize );
h = HashPalRange( h, palAddr, numPalEntries * 2 );
}
return h;
}
private void DecodeTexture( uint texParam, uint texPalette, uint[] output )
{
int width = TextureWidth( texParam );
int height = TextureHeight( texParam );
uint fmt = (texParam >> 26) & 0x7;
uint addr = (texParam & 0xFFFF) * 8;
uint palBase = texPalette & 0x1FFF;
if ( fmt == 7 )
{
for ( int i = 0; i < width * height; i++ )
{
ushort value = ReadTexU16( addr + (uint)i * 2 );
output[i] = ConvertRGB5ToRGB6( value ) | ((value & 0x8000) != 0 ? 0x1F000000u : 0);
}
}
else if ( fmt == 5 )
{
uint slot1addr = 0x20000 + ((addr & 0x1FFFC) >> 1);
if ( addr >= 0x40000 ) slot1addr += 0x10000;
uint palAddr = palBase * 16;
DecodeCompressed( width, height, output, addr, slot1addr, palAddr );
}
else if ( fmt == 1 || fmt == 6 )
{
uint palAddr = (palBase * 16) & 0x1FFFF;
int xbits = fmt == 1 ? 3 : 5;
int ybits = fmt == 1 ? 5 : 3;
for ( int y = 0; y < height; y++ )
{
for ( int x = 0; x < width; x++ )
{
byte val = ReadTexU8( addr + (uint)(x + y * width) );
uint idx = (uint)(val & ((1 << ybits) - 1));
ushort color = ReadPalU16( palAddr + idx * 2 );
uint alpha = (uint)((val >> ybits) & ((1 << xbits) - 1));
if ( xbits != 5 ) alpha = alpha * 4 + alpha / 2;
output[x + y * width] = ConvertRGB5ToRGB6( color ) | (alpha << 24);
}
}
}
else
{
uint palAddr = palBase * 16;
int colorBits = fmt switch { 2 => 2, 3 => 4, _ => 8 };
if ( fmt == 2 ) palAddr >>= 1;
palAddr &= 0x1FFFF;
bool color0Transparent = (texParam & (1 << 29)) != 0;
int per = 16 / colorBits;
for ( int y = 0; y < height; y++ )
{
for ( int x = 0; x < width / per; x++ )
{
ushort val = ReadTexU16( addr + (uint)(2 * (x + y * (width / per))) );
for ( int i = 0; i < per; i++ )
{
uint index = (uint)(val & ((1 << colorBits) - 1));
val >>= colorBits;
ushort color = ReadPalU16( palAddr + index * 2 );
bool transparent = color0Transparent && index == 0;
output[x * per + y * width + i] = ConvertRGB5ToRGB6( color ) | (transparent ? 0 : 0x1F000000u);
}
}
}
}
}
private void DecodeCompressed( int width, int height, uint[] output, uint addr, uint addrAux, uint palAddr )
{
for ( int y = 0; y < height / 4; y++ )
{
for ( int x = 0; x < width / 4; x++ )
{
uint data = ReadTexU32( addr + (uint)((x + y * (width / 4)) * 4) );
ushort auxData = ReadTexU16( addrAux + (uint)((x + y * (width / 4)) * 2) );
uint paletteOffset = palAddr + (uint)(auxData & 0x3FFF) * 4;
ushort color0 = (ushort)(ReadPalU16( paletteOffset ) | 0x8000);
ushort color1 = (ushort)(ReadPalU16( paletteOffset + 2 ) | 0x8000);
ushort color2 = (ushort)(ReadPalU16( paletteOffset + 4 ) | 0x8000);
ushort color3 = (ushort)(ReadPalU16( paletteOffset + 6 ) | 0x8000);
switch ( (auxData >> 14) & 0x3 )
{
case 0:
color3 = 0;
break;
case 1:
color2 = (ushort)(ColorAvg( color0, color1 ) | 0x8000);
color3 = 0;
break;
case 2:
break;
case 3:
color2 = (ushort)(Color5of3( color0, color1 ) | 0x8000);
color3 = (ushort)(Color3of5( color0, color1 ) | 0x8000);
break;
}
ulong packed = color0 | ((ulong)color1 << 16) | ((ulong)color2 << 32) | ((ulong)color3 << 48);
for ( int j = 0; j < 4; j++ )
{
for ( int i = 0; i < 4; i++ )
{
int colorIdx = 16 * (int)((data >> (2 * (i + j * 4))) & 0x3);
ushort color = (ushort)((packed >> colorIdx) & 0xFFFF);
output[x * 4 + i + (y * 4 + j) * width] = ConvertRGB5ToRGB6( color ) | ((color & 0x8000) != 0 ? 0x1F000000u : 0);
}
}
}
}
}
}