#include "Cafe/HW/Latte/Renderer/Renderer.h" #include "Cafe/HW/Latte/LatteAddrLib/LatteAddrLib.h" #include "config/ActiveSettings.h" #include "Cafe/CafeSystem.h" //#define BENCHMARK_TEXTURE_DECODING // if defined, time it takes to decode textures will be measured and logged to log.txt #ifdef BENCHMARK_TEXTURE_DECODING uint64 textureDecodeBenchmark_perFormatSum[0x40] = { 0 }; // duration sum per texture format (hw format) - in microseconds uint64 textureDecodeBenchmark_totalSum = 0; #endif void LatteTextureLoader_begin(LatteTextureLoaderCtx* textureLoader, uint32 sliceIndex, uint32 mipIndex, MPTR physImagePtr, MPTR physMipPtr, Latte::E_GX2SURFFMT format, Latte::E_DIM dim, uint32 width, uint32 height, uint32 depth, uint32 mipLevels, uint32 pitch, Latte::E_HWTILEMODE tileMode, uint32 swizzle) { textureLoader->physAddress = physImagePtr; textureLoader->physMipAddress = physMipPtr; textureLoader->sliceIndex = sliceIndex; cemu_assert_debug(mipLevels != 0); textureLoader->mipLevels = std::max(1, mipLevels); textureLoader->tileMode = tileMode; textureLoader->bpp = Latte::GetFormatBits(format); textureLoader->stepX = 1; textureLoader->stepY = 1; if (Latte::IsCompressedFormat(format)) { textureLoader->stepX = 4; textureLoader->stepY = 4; } textureLoader->pipeSwizzle = (swizzle >> 8) & 1; textureLoader->bankSwizzle = ((swizzle >> 9) & 3); uint32 surfaceAA = 0; // todo if (mipIndex > 0 && Latte::TM_IsMacroTiled(tileMode)) { // separate swizzle from mip pointer if mip chain is not macro-tiled (and thus not swizzled) LatteAddrLib::AddrSurfaceInfo_OUT surfaceInfo; LatteAddrLib::GX2CalculateSurfaceInfo(format, width, height, depth, dim, Latte::MakeGX2TileMode(tileMode), surfaceAA, 1, &surfaceInfo); if (Latte::TM_IsMacroTiled(surfaceInfo.hwTileMode)) { uint32 mipSwizzle = physMipPtr&0x700; physMipPtr &= ~0x700; textureLoader->physMipAddress = physMipPtr; textureLoader->pipeSwizzle = (mipSwizzle >> 8) & 1; textureLoader->bankSwizzle = ((mipSwizzle >> 9) & 3); } } // calculate surface info uint32 level = mipIndex; LatteAddrLib::AddrSurfaceInfo_OUT surfaceInfo; LatteAddrLib::GX2CalculateSurfaceInfo(format, width, height, depth, dim, Latte::MakeGX2TileMode(tileMode), surfaceAA, level, &surfaceInfo); textureLoader->levelOffset = LatteAddrLib::CalculateMipOffset(format, width, height, depth, dim, (Latte::E_HWTILEMODE)tileMode, swizzle, surfaceAA, level); textureLoader->tileMode = surfaceInfo.hwTileMode; textureLoader->minOffsetOutdated = 0; textureLoader->maxOffsetOutdated = (sint32)surfaceInfo.surfSize; textureLoader->surfaceInfoHeight = surfaceInfo.height; textureLoader->surfaceInfoDepth = surfaceInfo.depth; // correct handling for LINEAR_ALIGNED pitch alignment is still not fully understood: //seems like sometimes there is a conditional pitch alignment to 0x40 OR there is no pitch alignment at all and we have a bug somewhere else uint64 titleId = CafeSystem::GetForegroundTitleId(); titleId &= ~0x300ULL; if (tileMode == Latte::E_HWTILEMODE::TM_LINEAR_ALIGNED && titleId == (0x000500301001200aULL)) { // examples of titles that use linear textures: // Minecraft - Uses sprite atlases with mips and linear tilemode. Expects padding of pitch for smaller mips to be 0x40 // Browser - Linear pitch must be used as-is, padding/alignment will break textures (uses a weird way to calculate pitch by using GX2CalcSurface on a texture with tileMode 0/4) // BotW - uses linear textures as render targets. With the smallest resolution being 3x3 with no pitch alignment expected at all (pitch = 3)? -> Not possible because both textures and rendertargets require a minimum alignment of 8 for pitch? surfaceInfo.pitch = std::max(1, pitch >> mipIndex); } textureLoader->width = width >> (mipIndex); textureLoader->width = std::max(textureLoader->width, 1); textureLoader->height = height >> (mipIndex); textureLoader->height = std::max(textureLoader->height, 1); textureLoader->pitch = surfaceInfo.pitch; // calculate start address if (level == 0) textureLoader->inputData = (uint8*)memory_getPointerFromPhysicalOffset(physImagePtr); else textureLoader->inputData = (uint8*)memory_getPointerFromPhysicalOffset(physMipPtr) + textureLoader->levelOffset; SetupCachedSurfaceAddrInfo(&textureLoader->computeAddrInfo, textureLoader->sliceIndex, 0, textureLoader->bpp, textureLoader->pitch, surfaceInfo.height, depth, 1 * 1, textureLoader->tileMode, false, textureLoader->pipeSwizzle, textureLoader->bankSwizzle); } uint8* LatteTextureLoader_GetInput(LatteTextureLoaderCtx* textureLoader, sint32 x, sint32 y) { // calculate address of input tile uint32 offset = 0; if (textureLoader->tileMode == Latte::E_HWTILEMODE::TM_LINEAR_GENERAL || textureLoader->tileMode == Latte::E_HWTILEMODE::TM_LINEAR_ALIGNED) offset = LatteAddrLib::ComputeSurfaceAddrFromCoordLinear(x / textureLoader->stepX, y / textureLoader->stepY, textureLoader->sliceIndex, 0, textureLoader->bpp, textureLoader->pitch, textureLoader->surfaceInfoHeight, textureLoader->surfaceInfoDepth); else if (textureLoader->tileMode == Latte::E_HWTILEMODE::TM_1D_TILED_THIN1 || textureLoader->tileMode == Latte::E_HWTILEMODE::TM_1D_TILED_THICK) offset = LatteAddrLib::ComputeSurfaceAddrFromCoordMicroTiled(x / textureLoader->stepX, y / textureLoader->stepY, textureLoader->sliceIndex, textureLoader->bpp, textureLoader->pitch, textureLoader->surfaceInfoHeight, (Latte::E_HWTILEMODE)textureLoader->tileMode, false); else offset = LatteAddrLib::ComputeSurfaceAddrFromCoordMacroTiledCached(x / textureLoader->stepX, y / textureLoader->stepY, &textureLoader->computeAddrInfo); uint8* blockData = textureLoader->inputData + offset; return blockData; } /* * Optimized version which assumes tileMode == 1 * Also does not do any min/max offset tracking */ uint8* LatteTextureLoader_getInputLinearOptimized(LatteTextureLoaderCtx* textureLoader, sint32 x, sint32 y) { // calculate address of input tile uint32 bitPos = 0; uint32 offset = 0; offset = LatteAddrLib::ComputeSurfaceAddrFromCoordLinear(x / textureLoader->stepX, y / textureLoader->stepY, textureLoader->sliceIndex, 0, textureLoader->bpp, textureLoader->pitch, textureLoader->surfaceInfoHeight, textureLoader->surfaceInfoDepth); return textureLoader->inputData + offset; } #define LatteTextureLoader_getInputLinearOptimized_(__textureLoader,__x,__y,__stepX,__stepY,__bpp,__sliceIndex,__numSlices,__sample,__pitch,__height) (textureLoader->inputData+((__x/__stepX) + __pitch * (__y/__stepY) + (__sliceIndex + __numSlices * __sample) * __height * __pitch)*(__bpp/8)) float SRGB_to_RGB(float cs) { float cl; if (cs <= 0.04045f) cl = cs / 12.92f; else cl = powf(((cs + 0.055f) / 1.055f), 2.4f); return cl; } void decodeBC1Block(uint8* inputData, float* output4x4RGBA) { // read colors uint16 c0 = *(uint16*)(inputData + 0); uint16 c1 = *(uint16*)(inputData + 2); // decode colors (RGB565 -> RGB888) float r[4]; float g[4]; float b[4]; float a[4]; b[0] = (float)((c0 >> 0) & 0x1F) / 31.0f; b[1] = (float)((c1 >> 0) & 0x1F) / 31.0f; g[0] = (float)((c0 >> 5) & 0x3F) / 63.0f; g[1] = (float)((c1 >> 5) & 0x3F) / 63.0f; r[0] = (float)((c0 >> 11) & 0x1F) / 31.0f; r[1] = (float)((c1 >> 11) & 0x1F) / 31.0f; a[0] = 1.0f; a[1] = 1.0f; a[2] = 1.0f; if (c0 > c1) { r[2] = (r[0] * 2.0f + r[1]) / 3.0f; r[3] = (r[0] * 1.0f + r[1] * 2.0f) / 3.0f; g[2] = (g[0] * 2.0f + g[1]) / 3.0f; g[3] = (g[0] * 1.0f + g[1] * 2.0f) / 3.0f; b[2] = (b[0] * 2.0f + b[1]) / 3.0f; b[3] = (b[0] * 1.0f + b[1] * 2.0f) / 3.0f; a[3] = 1.0f; } else { r[2] = (r[0] + r[1]) / 2.0f; r[3] = 0.0f; g[2] = (g[0] + g[1]) / 2.0f; g[3] = 0.0f; b[2] = (b[0] + b[1]) / 2.0f; b[3] = 0.0f; a[3] = 0.0f; } uint8* indexData = inputData + 4; float* colorOutputRGBA = output4x4RGBA; for (sint32 row = 0; row < 4; row++) { uint8 i0 = ((*indexData) >> 0) & 3; uint8 i1 = ((*indexData) >> 2) & 3; uint8 i2 = ((*indexData) >> 4) & 3; uint8 i3 = ((*indexData) >> 6) & 3; colorOutputRGBA[0] = r[i0]; colorOutputRGBA[1] = g[i0]; colorOutputRGBA[2] = b[i0]; colorOutputRGBA[3] = a[i0]; colorOutputRGBA += 4; colorOutputRGBA[0] = r[i1]; colorOutputRGBA[1] = g[i1]; colorOutputRGBA[2] = b[i1]; colorOutputRGBA[3] = a[i1]; colorOutputRGBA += 4; colorOutputRGBA[0] = r[i2]; colorOutputRGBA[1] = g[i2]; colorOutputRGBA[2] = b[i2]; colorOutputRGBA[3] = a[i2]; colorOutputRGBA += 4; colorOutputRGBA[0] = r[i3]; colorOutputRGBA[1] = g[i3]; colorOutputRGBA[2] = b[i3]; colorOutputRGBA[3] = a[i3]; colorOutputRGBA += 4; indexData++; } } void decodeBC2Block_UNORM(uint8* inputData, float* imageRGBA) { uint32 color0 = *(uint16*)(inputData + 8); uint32 color1 = *(uint16*)(inputData + 10); uint32 colorIndices = *(uint32*)(inputData + 12); uint8 r0 = (color0 >> 11) & 0x1F; uint8 g0 = (color0 >> 5) & 0x3F; uint8 b0 = (color0 >> 0) & 0x1F; uint8 r1 = (color1 >> 11) & 0x1F; uint8 g1 = (color1 >> 5) & 0x3F; uint8 b1 = (color1 >> 0) & 0x1F; float r[4]; float g[4]; float b[4]; r[0] = (float)r0 / 31.0f; r[1] = (float)r1 / 31.0f; r[2] = (r[0] * 2.0f + r[1]) / 3.0f; r[3] = (r[0] + r[1] * 2.0f) / 3.0f; g[0] = (float)g0 / 63.0f; g[1] = (float)g1 / 63.0f; g[2] = (g[0] * 2.0f + g[1]) / 3.0f; g[3] = (g[0] + g[1] * 2.0f) / 3.0f; b[0] = (float)b0 / 31.0f; b[1] = (float)b1 / 31.0f; b[2] = (b[0] * 2.0f + b[1]) / 3.0f; b[3] = (b[0] + b[1] * 2.0f) / 3.0f; for (sint32 py = 0; py < 4; py++) { for (sint32 px = 0; px < 4; px++) { uint8 colorIndex = (colorIndices >> (2 * (px + 4 * py))) & 0x03; sint32 pixelOffset = (px + py * 4) * 4; imageRGBA[pixelOffset + 0] = r[colorIndex]; imageRGBA[pixelOffset + 1] = g[colorIndex]; imageRGBA[pixelOffset + 2] = b[colorIndex]; } } // decode alpha uint8* alphaData = (uint8*)(inputData + 0); for (sint32 py = 0; py < 4; py++) { for (sint32 px = 0; px < 4; px++) { uint32 alphaIndex = (px + py * 4); uint8 alphaCode = (alphaData[alphaIndex / 2] >> ((alphaIndex & 1) * 4)) & 0xF; alphaCode |= (alphaCode << 4); sint32 pixelOffset = (px + py * 4) * 4; imageRGBA[pixelOffset + 3] = (float)alphaCode / 255.0f; // alpha } } } void decodeBC3Block_UNORM(uint8* inputData, float* imageRGBA) { uint32 color0 = *(uint16*)(inputData + 8); uint32 color1 = *(uint16*)(inputData + 10); uint32 colorIndices = *(uint32*)(inputData + 12); uint8 r0 = (color0 >> 11) & 0x1F; uint8 g0 = (color0 >> 5) & 0x3F; uint8 b0 = (color0 >> 0) & 0x1F; uint8 r1 = (color1 >> 11) & 0x1F; uint8 g1 = (color1 >> 5) & 0x3F; uint8 b1 = (color1 >> 0) & 0x1F; float r[4]; float g[4]; float b[4]; r[0] = (float)r0 / 31.0f; r[1] = (float)r1 / 31.0f; r[2] = (r[0] * 2.0f + r[1]) / 3.0f; r[3] = (r[0] + r[1] * 2.0f) / 3.0f; g[0] = (float)g0 / 63.0f; g[1] = (float)g1 / 63.0f; g[2] = (g[0] * 2.0f + g[1]) / 3.0f; g[3] = (g[0] + g[1] * 2.0f) / 3.0f; b[0] = (float)b0 / 31.0f; b[1] = (float)b1 / 31.0f; b[2] = (b[0] * 2.0f + b[1]) / 3.0f; b[3] = (b[0] + b[1] * 2.0f) / 3.0f; for (sint32 py = 0; py < 4; py++) { for (sint32 px = 0; px < 4; px++) { uint8 colorIndex = (colorIndices >> (2 * (px + 4 * py))) & 0x03; sint32 pixelOffset = (px + py * 4) * 4; imageRGBA[pixelOffset + 0] = r[colorIndex]; imageRGBA[pixelOffset + 1] = g[colorIndex]; imageRGBA[pixelOffset + 2] = b[colorIndex]; //imageRGBA[pixelOffset+3] = 1.0f; // alpha } } // decode alpha uint8 alpha0 = *(uint8*)(inputData + 0); uint8 alpha1 = *(uint8*)(inputData + 1); uint32 alphaCodeRow[2] = { 0 }; alphaCodeRow[0] |= ((*(uint8*)(inputData + 2)) << 0); alphaCodeRow[0] |= ((*(uint8*)(inputData + 3)) << 8); alphaCodeRow[0] |= ((*(uint8*)(inputData + 4)) << 16); alphaCodeRow[1] |= ((*(uint8*)(inputData + 5)) << 0); alphaCodeRow[1] |= ((*(uint8*)(inputData + 6)) << 8); alphaCodeRow[1] |= ((*(uint8*)(inputData + 7)) << 16); float a[8]; a[0] = (float)alpha0 / 255.0f; a[1] = (float)alpha1 / 255.0f; if (alpha0 > alpha1) { // 6 interpolated alpha values. a[2] = (a[0] * 6.0f + a[1] * 1.0f) / 7.0f; a[3] = (a[0] * 5.0f + a[1] * 2.0f) / 7.0f; a[4] = (a[0] * 4.0f + a[1] * 3.0f) / 7.0f; a[5] = (a[0] * 3.0f + a[1] * 4.0f) / 7.0f; a[6] = (a[0] * 2.0f + a[1] * 5.0f) / 7.0f; a[7] = (a[0] * 1.0f + a[1] * 6.0f) / 7.0f; } else { // 4 interpolated alpha values. a[2] = (a[0] * 4.0f + a[1] * 1.0f) / 5.0f; a[3] = (a[0] * 3.0f + a[1] * 2.0f) / 5.0f; a[4] = (a[0] * 2.0f + a[1] * 3.0f) / 5.0f; a[5] = (a[0] * 1.0f + a[1] * 4.0f) / 5.0f; a[6] = 0.0f; a[7] = 1.0f; } for (sint32 py = 0; py < 4; py++) { for (sint32 px = 0; px < 4; px++) { uint8 alphaCode = (alphaCodeRow[py / 2] >> 3 * (px + 4 * (py & 1))) & 0x07; sint32 pixelOffset = (px + py * 4) * 4; imageRGBA[pixelOffset + 3] = a[alphaCode]; // alpha } } } void decodeBC4Block_UNORM(uint8* blockStorage, float* rOutput) { uint8* blockInput = (uint8*)blockStorage; float red[8]; red[0] = ((float)(*(uint8*)(blockInput + 0))) / 255.0f; red[1] = ((float)(*(uint8*)(blockInput + 1))) / 255.0f; if (blockInput[0] > blockInput[1]) { // 6 interpolated color values red[2] = (6 * red[0] + 1 * red[1]) / 7.0f; // bit code 010 red[3] = (5 * red[0] + 2 * red[1]) / 7.0f; // bit code 011 red[4] = (4 * red[0] + 3 * red[1]) / 7.0f; // bit code 100 red[5] = (3 * red[0] + 4 * red[1]) / 7.0f; // bit code 101 red[6] = (2 * red[0] + 5 * red[1]) / 7.0f; // bit code 110 red[7] = (1 * red[0] + 6 * red[1]) / 7.0f; // bit code 111 } else { // 4 interpolated color values red[2] = (4 * red[0] + 1 * red[1]) / 5.0f; // bit code 010 red[3] = (3 * red[0] + 2 * red[1]) / 5.0f; // bit code 011 red[4] = (2 * red[0] + 3 * red[1]) / 5.0f; // bit code 100 red[5] = (1 * red[0] + 4 * red[1]) / 5.0f; // bit code 101 red[6] = 0.0f; // bit code 110 red[7] = 1.0f; // bit code 111 } uint8* bitIndices = blockInput + 2; uint32 redRow0 = (((uint32)bitIndices[2]) << 16) | (((uint32)bitIndices[1]) << 8) | (((uint32)bitIndices[0]) << 0); uint32 redRow1 = (((uint32)bitIndices[5]) << 16) | (((uint32)bitIndices[4]) << 8) | (((uint32)bitIndices[3]) << 0); uint8 pRed[16]; for (sint32 i = 0; i < 8; i++) { pRed[i] = (redRow0 >> (i * 3)) & 7; pRed[i + 8] = (redRow1 >> (i * 3)) & 7; } float* pixelOutput = rOutput; for (sint32 py = 0; py < 4; py++) { for (sint32 px = 0; px < 4; px++) { float c = red[pRed[px + py * 4]]; *pixelOutput = c; pixelOutput++; } } } void decodeBC5Block_UNORM(uint8* blockStorage, float* rgOutput) { uint8* blockInput = (uint8*)blockStorage; float red[8]; float green[8]; red[0] = ((float)(*(uint8*)(blockInput + 0))) / 255.0f; red[1] = ((float)(*(uint8*)(blockInput + 1))) / 255.0f; if (red[0] > red[1]) { // 6 interpolated color values red[2] = (6 * red[0] + 1 * red[1]) / 7.0f; // bit code 010 red[3] = (5 * red[0] + 2 * red[1]) / 7.0f; // bit code 011 red[4] = (4 * red[0] + 3 * red[1]) / 7.0f; // bit code 100 red[5] = (3 * red[0] + 4 * red[1]) / 7.0f; // bit code 101 red[6] = (2 * red[0] + 5 * red[1]) / 7.0f; // bit code 110 red[7] = (1 * red[0] + 6 * red[1]) / 7.0f; // bit code 111 } else { // 4 interpolated color values red[2] = (4 * red[0] + 1 * red[1]) / 5.0f; // bit code 010 red[3] = (3 * red[0] + 2 * red[1]) / 5.0f; // bit code 011 red[4] = (2 * red[0] + 3 * red[1]) / 5.0f; // bit code 100 red[5] = (1 * red[0] + 4 * red[1]) / 5.0f; // bit code 101 red[6] = 0.0f; // bit code 110 red[7] = 1.0f; // bit code 111 } green[0] = ((float)(*(uint8*)(blockInput + 8))) / 255.0f; green[1] = ((float)(*(uint8*)(blockInput + 9))) / 255.0f; if (green[0] > green[1]) { // 6 interpolated color values green[2] = (6 * green[0] + 1 * green[1]) / 7.0f; // bit code 010 green[3] = (5 * green[0] + 2 * green[1]) / 7.0f; // bit code 011 green[4] = (4 * green[0] + 3 * green[1]) / 7.0f; // bit code 100 green[5] = (3 * green[0] + 4 * green[1]) / 7.0f; // bit code 101 green[6] = (2 * green[0] + 5 * green[1]) / 7.0f; // bit code 110 green[7] = (1 * green[0] + 6 * green[1]) / 7.0f; // bit code 111 } else { // 4 interpolated color values green[2] = (4 * green[0] + 1 * green[1]) / 5.0f; // bit code 010 green[3] = (3 * green[0] + 2 * green[1]) / 5.0f; // bit code 011 green[4] = (2 * green[0] + 3 * green[1]) / 5.0f; // bit code 100 green[5] = (1 * green[0] + 4 * green[1]) / 5.0f; // bit code 101 green[6] = 0.0f; // bit code 110 green[7] = 1.0f; // bit code 111 } uint8* bitIndices = blockInput + 2; uint32 redRow0 = (((uint32)bitIndices[2]) << 16) | (((uint32)bitIndices[1]) << 8) | (((uint32)bitIndices[0]) << 0); uint32 redRow1 = (((uint32)bitIndices[5]) << 16) | (((uint32)bitIndices[4]) << 8) | (((uint32)bitIndices[3]) << 0); bitIndices = blockInput + 8 + 2; uint32 greenRow0 = (((uint32)bitIndices[2]) << 16) | (((uint32)bitIndices[1]) << 8) | (((uint32)bitIndices[0]) << 0); uint32 greenRow1 = (((uint32)bitIndices[5]) << 16) | (((uint32)bitIndices[4]) << 8) | (((uint32)bitIndices[3]) << 0); uint8 pRed[16]; uint8 pGreen[16]; for (sint32 i = 0; i < 8; i++) { pRed[i] = (redRow0 >> (i * 3)) & 7; pRed[i + 8] = (redRow1 >> (i * 3)) & 7; pGreen[i] = (greenRow0 >> (i * 3)) & 7; pGreen[i + 8] = (greenRow1 >> (i * 3)) & 7; } float* pixelOutput = rgOutput; for (sint32 py = 0; py < 4; py++) { for (sint32 px = 0; px < 4; px++) { float c = red[pRed[px + py * 4]]; *pixelOutput = c; pixelOutput++; c = green[pGreen[px + py * 4]]; *pixelOutput = c; pixelOutput++; } } } void decodeBC5Block_SNORM(uint8* blockStorage, float* rgOutput) // todo - can merge this with the UNORM implementation by using a template? { uint8* blockInput = (uint8*)blockStorage; float red[8]; float green[8]; red[0] = ((float)(*(sint8*)(blockInput + 0)) + 128.0f) / 255.0f; red[1] = ((float)(*(sint8*)(blockInput + 1)) + 128.0f) / 255.0f; red[0] = (red[0] * 2.0f - 1.0f); red[1] = (red[1] * 2.0f - 1.0f); if (red[0] > red[1]) { // 6 interpolated color values red[2] = (6 * red[0] + 1 * red[1]) / 7.0f; // bit code 010 red[3] = (5 * red[0] + 2 * red[1]) / 7.0f; // bit code 011 red[4] = (4 * red[0] + 3 * red[1]) / 7.0f; // bit code 100 red[5] = (3 * red[0] + 4 * red[1]) / 7.0f; // bit code 101 red[6] = (2 * red[0] + 5 * red[1]) / 7.0f; // bit code 110 red[7] = (1 * red[0] + 6 * red[1]) / 7.0f; // bit code 111 } else { // 4 interpolated color values red[2] = (4 * red[0] + 1 * red[1]) / 5.0f; // bit code 010 red[3] = (3 * red[0] + 2 * red[1]) / 5.0f; // bit code 011 red[4] = (2 * red[0] + 3 * red[1]) / 5.0f; // bit code 100 red[5] = (1 * red[0] + 4 * red[1]) / 5.0f; // bit code 101 red[6] = -1.0f; // bit code 110 red[7] = 1.0f; // bit code 111 } green[0] = ((float)(*(sint8*)(blockInput + 8)) + 128.0f) / 255.0f; green[1] = ((float)(*(sint8*)(blockInput + 9)) + 128.0f) / 255.0f; green[0] = (green[0] * 2.0f - 1.0f); green[1] = (green[1] * 2.0f - 1.0f); if (green[0] > green[1]) { // 6 interpolated color values green[2] = (6 * green[0] + 1 * green[1]) / 7.0f; // bit code 010 green[3] = (5 * green[0] + 2 * green[1]) / 7.0f; // bit code 011 green[4] = (4 * green[0] + 3 * green[1]) / 7.0f; // bit code 100 green[5] = (3 * green[0] + 4 * green[1]) / 7.0f; // bit code 101 green[6] = (2 * green[0] + 5 * green[1]) / 7.0f; // bit code 110 green[7] = (1 * green[0] + 6 * green[1]) / 7.0f; // bit code 111 } else { // 4 interpolated color values green[2] = (4 * green[0] + 1 * green[1]) / 5.0f; // bit code 010 green[3] = (3 * green[0] + 2 * green[1]) / 5.0f; // bit code 011 green[4] = (2 * green[0] + 3 * green[1]) / 5.0f; // bit code 100 green[5] = (1 * green[0] + 4 * green[1]) / 5.0f; // bit code 101 green[6] = -1.0f; // bit code 110 green[7] = 1.0f; // bit code 111 } uint8* bitIndices = blockInput + 2; uint32 redRow0 = (((uint32)bitIndices[2]) << 16) | (((uint32)bitIndices[1]) << 8) | (((uint32)bitIndices[0]) << 0); uint32 redRow1 = (((uint32)bitIndices[5]) << 16) | (((uint32)bitIndices[4]) << 8) | (((uint32)bitIndices[3]) << 0); bitIndices = blockInput + 8 + 2; uint32 greenRow0 = (((uint32)bitIndices[2]) << 16) | (((uint32)bitIndices[1]) << 8) | (((uint32)bitIndices[0]) << 0); uint32 greenRow1 = (((uint32)bitIndices[5]) << 16) | (((uint32)bitIndices[4]) << 8) | (((uint32)bitIndices[3]) << 0); uint8 pRed[16]; uint8 pGreen[16]; for (sint32 i = 0; i < 8; i++) { pRed[i] = (redRow0 >> (i * 3)) & 7; pRed[i + 8] = (redRow1 >> (i * 3)) & 7; pGreen[i] = (greenRow0 >> (i * 3)) & 7; pGreen[i + 8] = (greenRow1 >> (i * 3)) & 7; } for (sint32 py = 0; py < 4; py++) { float* pixelOutput = rgOutput + (py * 4) * 2; for (sint32 px = 0; px < 4; px++) { float c = red[pRed[px + py * 4]]; pixelOutput[0] = c; c = green[pGreen[px + py * 4]]; pixelOutput[1] = c; pixelOutput += 2; } } } void LatteTextureLoader_loadTextureDataIntoSlice(LatteTexture* hostTexture, sint32 width, sint32 height, sint32 depth, sint32 mipLevels, void* pixelData, sint32 sliceIndex, sint32 mipIndex, uint32 compressedImageSize) { if (mipIndex == 0) { cemu_assert_debug(width == hostTexture->width); cemu_assert_debug(height == hostTexture->height); cemu_assert_debug(depth == hostTexture->depth); } cemu_assert_debug(mipLevels == hostTexture->mipLevels); if (hostTexture->overwriteInfo.hasResolutionOverwrite || hostTexture->overwriteInfo.hasFormatOverwrite) { // todo - ideally, we should scale/convert the data to the new format and resolution g_renderer->texture_clearSlice(hostTexture, sliceIndex, mipIndex); } else { g_renderer->texture_loadSlice(hostTexture, width, height, depth, pixelData, sliceIndex, mipIndex, compressedImageSize); } } void LatteTextureLoader_UpdateTextureSliceData(LatteTexture* tex, uint32 sliceIndex, uint32 mipIndex, MPTR physImagePtr, MPTR physMipPtr, Latte::E_DIM dim, uint32 width, uint32 height, uint32 depth, uint32 mipLevels, uint32 pitch, Latte::E_HWTILEMODE tileMode, uint32 swizzle, bool dumpTex) { LatteTextureLoaderCtx textureLoader = { 0 }; Latte::E_GX2SURFFMT format = tex->format; LatteTextureLoader_begin(&textureLoader, sliceIndex, mipIndex, physImagePtr, physMipPtr, format, dim, width, height, depth, mipLevels, pitch, tileMode, swizzle); // enable texture dumping textureLoader.dump = ActiveSettings::DumpTexturesEnabled(); if (textureLoader.dump) { uint32 dumpSize = (((textureLoader.width + 4)&~4) * ((textureLoader.height + 4)&~4)) * 4; textureLoader.dumpRGBA = (uint8*)malloc(dumpSize); memset(textureLoader.dumpRGBA, 0x00, dumpSize); } // query texture decoder from renderer TextureDecoder* texDecoder = nullptr; texDecoder = g_renderer->texture_chooseDecodedFormat(format, tex->isDepth, dim, width, height); if (tex->isDataDefined == false) { tex->AllocateOnHost(); tex->isDataDefined = true; // if decoder is not set then clear texture // on Vulkan this is used to make sure the texture is no longer in UNDEFINED layout if (!texDecoder) { if(tex->isDepth) g_renderer->texture_clearDepthSlice(tex, 0, 0, true, tex->hasStencil, 0.0f, 0); else g_renderer->texture_clearColorSlice(tex, 0, 0, 0.0f, 0.0f, 0.0f, 0.0f); } } if (texDecoder == nullptr) return; textureLoader.decodedTexelCountX = texDecoder->getTexelCountX(&textureLoader); textureLoader.decodedTexelCountY = texDecoder->getTexelCountY(&textureLoader); // allocate memory for decoded texture uint32 imageSize = texDecoder->calculateImageSize(&textureLoader); uint8* pixelData = (uint8*)g_renderer->texture_acquireTextureUploadBuffer(imageSize); // decode texture (if data is required) #ifdef BENCHMARK_TEXTURE_DECODING LARGE_INTEGER benchmark_begin; LARGE_INTEGER benchmark_end; LARGE_INTEGER benchmark_freq; QueryPerformanceCounter(&benchmark_begin); #endif if (tex->overwriteInfo.hasFormatOverwrite == false && tex->overwriteInfo.hasResolutionOverwrite == false) { texDecoder->decode(&textureLoader, pixelData); } #ifdef BENCHMARK_TEXTURE_DECODING QueryPerformanceCounter(&benchmark_end); QueryPerformanceFrequency(&benchmark_freq); uint64 benchmarkResultMicroSeconds = (benchmark_end.QuadPart - benchmark_begin.QuadPart) * 1000000ULL / benchmark_freq.QuadPart; textureDecodeBenchmark_perFormatSum[(int)tex->format & 0x3F] += benchmarkResultMicroSeconds; textureDecodeBenchmark_totalSum += benchmarkResultMicroSeconds; cemuLog_log(LogType::Force, "TexDecode {:04}x{:04}x{:04} Fmt {:04x} Dim {} TileMode {:02x} Took {:03}.{:03}ms Sum(format) {:06}ms Sum(total) {:06}ms", textureLoader.width, textureLoader.height, textureLoader.surfaceInfoDepth, (int)tex->format, (int)tex->dim, textureLoader.tileMode, (uint32)(benchmarkResultMicroSeconds / 1000ULL), (uint32)(benchmarkResultMicroSeconds % 1000ULL), (uint32)(textureDecodeBenchmark_perFormatSum[tex->gx2Format & 0x3F] / 1000ULL), (uint32)(textureDecodeBenchmark_totalSum / 1000ULL)); #endif // convert texture to RGBA when dumping is enabled if (textureLoader.dump) { for (sint32 y = 0; y < textureLoader.height; y++) { sint32 pixelOffset = (y * textureLoader.width) * 4; uint8* pixelOutput = textureLoader.dumpRGBA + pixelOffset; for (sint32 x = 0; x < textureLoader.width; x++) { uint8* blockData = LatteTextureLoader_GetInput(&textureLoader, x, y); texDecoder->decodePixelToRGBA(blockData, pixelOutput, x % textureLoader.stepX, y % textureLoader.stepY); pixelOutput += 4; } } } // update texture data offsets and hashes // this has to be done before the texture data is decoded & uploaded to prevent a race condition where updates during upload are missed if (mipIndex == 0 || (tex->texDataPtrLow == 0 && tex->texDataPtrHigh == 0)) { tex->texDataPtrLow = physImagePtr + textureLoader.minOffsetOutdated; // always zero tex->texDataPtrHigh = physImagePtr + textureLoader.maxOffsetOutdated; // currently set to surface size LatteTC_ResetTextureChangeTracker(tex, true); } // load slice //debug_printf("[Load Slice] Addr: %08x MIP: %02d Slice: %02d Res %04x/%04x Texel Res %04x/%04x Fmt %04x Tm %d\n", textureLoader.physAddress, mipIndex, sliceIndex, textureLoader.width, textureLoader.height, textureLoader.texelCountX, textureLoader.texelCountY, (int)format, tileMode); LatteTextureLoader_loadTextureDataIntoSlice(tex, textureLoader.width, textureLoader.height, depth, mipLevels, pixelData, sliceIndex, mipIndex, imageSize); // write texture dump if (textureLoader.dump) { wchar_t path[1024]; swprintf(path, 1024, L"dump/textures/%08x_fmt%04x_slice%d_mip%02d_%dx%d_tm%02d.tga", physImagePtr, (uint32)tex->format, sliceIndex, mipIndex, tex->width, tex->height, tileMode); tga_write_rgba(path, textureLoader.width, textureLoader.height, textureLoader.dumpRGBA); free(textureLoader.dumpRGBA); } // clean up g_renderer->texture_releaseTextureUploadBuffer(pixelData); catchOpenGLError(); } template void optimizedLinearReadbackWriteLoop(LatteTextureLoaderCtx* textureLoader, uint8* linearPixelData) { uint32 pitch = textureLoader->width; // optimized for linear for (sint32 y = 0; y < textureLoader->height; y++) { sint32 yc = y; sint32 pixelOffset = yc * pitch; copyType* rowPixelData = (copyType*)(linearPixelData + pixelOffset * sizeof(copyType)); copyType* blockData = (copyType*)LatteTextureLoader_getInputLinearOptimized_(textureLoader, 0, y, 1, 1, sizeof(copyType) * 8, 0, 1, 0, textureLoader->pitch, textureLoader->height); if constexpr (sizeof(copyType) == 4) { memcpy_dwords(blockData, rowPixelData, textureLoader->width); } else { for (sint32 x = 0; x < textureLoader->width; x++) { *blockData = *rowPixelData; rowPixelData++; blockData++; } } } } void LatteTextureLoader_writeReadbackTextureToMemory(LatteTextureDefinition* textureData, uint32 sliceIndex, uint32 mipIndex, uint8* linearPixelData) { LatteTextureLoaderCtx textureLoader = { 0 }; LatteTextureLoader_begin(&textureLoader, sliceIndex, mipIndex, textureData->physAddress, textureData->physMipAddress, textureData->format, textureData->dim, textureData->width, textureData->height, textureData->depth, textureData->mipLevels, textureData->pitch, textureData->tileMode, textureData->swizzle); #ifdef CEMU_DEBUG_ASSERT if (textureData->depth != 1) cemuLog_log(LogType::Force, "_writeReadbackTextureToMemory(): Texture has multiple slices (not supported)"); #endif if (textureLoader.physAddress == MPTR_NULL) { cemuLog_log(LogType::Force, "_writeReadbackTextureToMemory(): Texture has invalid address"); return; } cemuLog_log(LogType::TextureReadback, "[TextureReadback-Write] PhysAddr {:08x} Res {}x{} Fmt {} Slice {} Mip {}", textureData->physAddress, textureData->width, textureData->height, textureData->format, sliceIndex, mipIndex); if (textureData->tileMode == Latte::E_HWTILEMODE::TM_LINEAR_ALIGNED) { uint32 pitch = textureLoader.width; if (textureData->format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM || textureData->format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_SRGB) { optimizedLinearReadbackWriteLoop(&textureLoader, linearPixelData); } else if (textureData->format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_UNORM) { optimizedLinearReadbackWriteLoop(&textureLoader, linearPixelData); } else if (textureData->format == Latte::E_GX2SURFFMT::R32_G32_B32_A32_FLOAT) { for (sint32 y = 0; y < textureLoader.height; y += textureLoader.stepY) { sint32 yc = y; sint32 pixelOffset = (0 + yc * pitch) * 16; for (sint32 x = 0; x < textureLoader.width; x += textureLoader.stepX) { uint8* blockData = LatteTextureLoader_getInputLinearOptimized(&textureLoader, x, y); (*(uint32*)(blockData + 0)) = *(uint32*)(linearPixelData + pixelOffset + 0); (*(uint32*)(blockData + 4)) = *(uint32*)(linearPixelData + pixelOffset + 4); (*(uint32*)(blockData + 8)) = *(uint32*)(linearPixelData + pixelOffset + 8); (*(uint32*)(blockData + 12)) = *(uint32*)(linearPixelData + pixelOffset + 12); pixelOffset += 16; } } } else if (textureData->format == Latte::E_GX2SURFFMT::R32_FLOAT) { for (sint32 y = 0; y < textureLoader.height; y += textureLoader.stepY) { sint32 yc = y; for (sint32 x = 0; x < textureLoader.width; x += textureLoader.stepX) { uint8* blockData = LatteTextureLoader_getInputLinearOptimized(&textureLoader, x, y); sint32 pixelOffset = (x + yc * pitch) * 4; (*(uint32*)(blockData + 0)) = *(uint32*)(linearPixelData + pixelOffset + 0); } } } else if (textureData->format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT) { for (sint32 y = 0; y < textureLoader.height; y += textureLoader.stepY) { sint32 yc = y; for (sint32 x = 0; x < textureLoader.width; x += textureLoader.stepX) { uint8* blockData = LatteTextureLoader_getInputLinearOptimized(&textureLoader, x, y); sint32 pixelOffset = (x + yc * pitch) * 8; (*(uint32*)(blockData + 0)) = *(uint32*)(linearPixelData + pixelOffset + 0); (*(uint32*)(blockData + 4)) = *(uint32*)(linearPixelData + pixelOffset + 4); } } } else if (textureData->format == Latte::E_GX2SURFFMT::R8_G8_UNORM) { optimizedLinearReadbackWriteLoop(&textureLoader, linearPixelData); } else if (textureData->format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_UNORM) { cemu_assert_unimplemented(); } else if (textureData->format == Latte::E_GX2SURFFMT::R16_UNORM) { optimizedLinearReadbackWriteLoop(&textureLoader, linearPixelData); } else { cemuLog_logDebug(LogType::Force, "Linear texture readback unsupported for format 0x{:04x}", (uint32)textureData->format); debugBreakpoint(); } return; } // generic and slow decode loops Latte::E_HWSURFFMT hwFormat = Latte::GetHWFormat(textureData->format); if (hwFormat == Latte::E_HWSURFFMT::HWFMT_8_8_8_8) { // used in Bayonetta 2 for (sint32 y = 0; y < textureLoader.height; y++) { uint8* pixelInput = linearPixelData + (y * textureLoader.width) * 4; for (sint32 x = 0; x < textureLoader.width; x++) { uint8* outputData = LatteTextureLoader_GetInput(&textureLoader, x, y); *(uint32*)(outputData + 0) = *(uint32*)pixelInput; pixelInput += 4; } } } else if (hwFormat == Latte::E_HWSURFFMT::HWFMT_32_FLOAT) { // required by Wind Waker for direct access to depth buffer // Bayonetta 2 also uses this but it converts the depth buffer to a color texture first for (sint32 y = 0; y < textureLoader.height; y++) { uint8* pixelInput = linearPixelData + (y * textureLoader.width) * 4; for (sint32 x = 0; x < textureLoader.width; x++) { uint8* outputData = LatteTextureLoader_GetInput(&textureLoader, x, y); *(uint32*)(outputData + 0) = *(uint32*)pixelInput; pixelInput += 4; } } } else { cemuLog_logDebug(LogType::Force, "Texture readback unsupported format {:04x} for tileMode 0x{:02x}", (uint32)textureData->format, textureData->tileMode); } } void LatteTextureLoader_estimateAccessedDataRange(LatteTexture* texture, sint32 sliceIndex, sint32 mipIndex, uint32& addrStart, uint32& addrEnd) { LatteTextureLoaderCtx textureLoader = { 0 }; LatteTextureLoader_begin(&textureLoader, sliceIndex, mipIndex, texture->physAddress, texture->physMipAddress, texture->format, texture->dim, texture->width, texture->height, texture->depth, texture->mipLevels, texture->pitch, texture->tileMode, texture->swizzle); cemu_assert_debug(textureLoader.width > 0); cemu_assert_debug(textureLoader.height > 0); // estimate data range by checking addresses of corner pixels // this isn't very reliable, find a better solution uint32 estimatedMinAddr = 0xFFFFFFFF; uint32 estimatedMaxAddr = 0x00000000; uint32 tempAddr; tempAddr = memory_getVirtualOffsetFromPointer(LatteTextureLoader_GetInput(&textureLoader, 0, 0)); estimatedMinAddr = std::min(estimatedMinAddr, tempAddr); estimatedMaxAddr = std::max(estimatedMaxAddr, tempAddr); tempAddr = memory_getVirtualOffsetFromPointer(LatteTextureLoader_GetInput(&textureLoader, textureLoader.width - 1, 0)); estimatedMinAddr = std::min(estimatedMinAddr, tempAddr); estimatedMaxAddr = std::max(estimatedMaxAddr, tempAddr); tempAddr = memory_getVirtualOffsetFromPointer(LatteTextureLoader_GetInput(&textureLoader, 0, textureLoader.height - 1)); estimatedMinAddr = std::min(estimatedMinAddr, tempAddr); estimatedMaxAddr = std::max(estimatedMaxAddr, tempAddr); tempAddr = memory_getVirtualOffsetFromPointer(LatteTextureLoader_GetInput(&textureLoader, textureLoader.width - 1, textureLoader.height - 1)); estimatedMinAddr = std::min(estimatedMinAddr, tempAddr); estimatedMaxAddr = std::max(estimatedMaxAddr, tempAddr); addrStart = estimatedMinAddr; addrEnd = estimatedMaxAddr; }