Latte: Partial support for mips in texture readback

This commit is contained in:
Exzap committed 2026-09-10 13:27:23 +02:00
1 parent 8a471c8933
commit c25dbdf86d
8 files changed
+38 -36

No files matched your search

+1 -1
View File
@@ -333,7 +333,7 @@ float* LatteTexture_getEffectiveTextureScale(LatteConst::ShaderType shaderType,
LatteTextureView* LatteTexture_CreateTexture(Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth);
void LatteTexture_Delete(LatteTexture* texture);
void LatteTextureLoader_writeReadbackTextureToMemory(LatteTextureDefinition* textureData, uint32 sliceIndex, uint32 mipIndex, uint8* linearPixelData);
void LatteTextureLoader_writeReadbackTextureToMemory(LatteTextureDefinition* textureData, uint32 sliceIndex, uint32 mipIndex, uint8* linearPixelData, uint32 sourceRowPitch);
sint32 LatteTexture_getEffectiveWidth(LatteTexture* texture);
bool LatteTexture_doesEffectiveRescaleRatioMatch(LatteTexture* texture1, sint32 mipLevel1, LatteTexture* texture2, sint32 mipLevel2);
+12 -16
View File
@@ -695,15 +695,12 @@ void LatteTextureLoader_UpdateTextureSliceData(LatteTexture* tex, uint32 sliceIn
}
template<typename copyType>
void optimizedLinearReadbackWriteLoop(LatteTextureLoaderCtx* textureLoader, uint8* linearPixelData)
void optimizedLinearReadbackWriteLoop(LatteTextureLoaderCtx* textureLoader, uint8* linearPixelData, uint32 sourcePitch)
{
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* rowPixelData = (copyType*)(linearPixelData + y * sourcePitch);
copyType* blockData = (copyType*)LatteTextureLoader_getInputLinearOptimized_(textureLoader, 0, y, 1, 1, sizeof(copyType) * 8, textureLoader->sliceIndex, 1, 0, textureLoader->pitch, textureLoader->surfaceInfoHeight);
if constexpr (sizeof(copyType) == 4)
{
@@ -721,7 +718,7 @@ void optimizedLinearReadbackWriteLoop(LatteTextureLoaderCtx* textureLoader, uint
}
}
void LatteTextureLoader_writeReadbackTextureToMemory(LatteTextureDefinition* textureData, uint32 sliceIndex, uint32 mipIndex, uint8* linearPixelData)
void LatteTextureLoader_writeReadbackTextureToMemory(LatteTextureDefinition* textureData, uint32 sliceIndex, uint32 mipIndex, uint8* linearPixelData, uint32 sourceRowPitch)
{
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);
@@ -736,22 +733,21 @@ void LatteTextureLoader_writeReadbackTextureToMemory(LatteTextureDefinition* tex
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<uint32>(&textureLoader, linearPixelData);
optimizedLinearReadbackWriteLoop<uint32>(&textureLoader, linearPixelData, sourceRowPitch);
}
else if (textureData->format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_UNORM)
{
optimizedLinearReadbackWriteLoop<uint64>(&textureLoader, linearPixelData);
optimizedLinearReadbackWriteLoop<uint64>(&textureLoader, linearPixelData, sourceRowPitch);
}
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;
sint32 pixelOffset = yc * sourceRowPitch;
for (sint32 x = 0; x < textureLoader.width; x += textureLoader.stepX)
{
uint8* blockData = LatteTextureLoader_getInputLinearOptimized(&textureLoader, x, y);
@@ -771,7 +767,7 @@ void LatteTextureLoader_writeReadbackTextureToMemory(LatteTextureDefinition* tex
for (sint32 x = 0; x < textureLoader.width; x += textureLoader.stepX)
{
uint8* blockData = LatteTextureLoader_getInputLinearOptimized(&textureLoader, x, y);
sint32 pixelOffset = (x + yc * pitch) * 4;
sint32 pixelOffset = yc * sourceRowPitch + x * 4;
(*(uint32*)(blockData + 0)) = *(uint32*)(linearPixelData + pixelOffset + 0);
}
}
@@ -784,7 +780,7 @@ void LatteTextureLoader_writeReadbackTextureToMemory(LatteTextureDefinition* tex
for (sint32 x = 0; x < textureLoader.width; x += textureLoader.stepX)
{
uint8* blockData = LatteTextureLoader_getInputLinearOptimized(&textureLoader, x, y);
sint32 pixelOffset = (x + yc * pitch) * 8;
sint32 pixelOffset = yc * sourceRowPitch + x * 8;
(*(uint32*)(blockData + 0)) = *(uint32*)(linearPixelData + pixelOffset + 0);
(*(uint32*)(blockData + 4)) = *(uint32*)(linearPixelData + pixelOffset + 4);
}
@@ -792,7 +788,7 @@ void LatteTextureLoader_writeReadbackTextureToMemory(LatteTextureDefinition* tex
}
else if (textureData->format == Latte::E_GX2SURFFMT::R8_G8_UNORM)
{
optimizedLinearReadbackWriteLoop<uint16>(&textureLoader, linearPixelData);
optimizedLinearReadbackWriteLoop<uint16>(&textureLoader, linearPixelData, sourceRowPitch);
}
else if (textureData->format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_UNORM)
{
@@ -800,7 +796,7 @@ void LatteTextureLoader_writeReadbackTextureToMemory(LatteTextureDefinition* tex
}
else if (textureData->format == Latte::E_GX2SURFFMT::R16_UNORM)
{
optimizedLinearReadbackWriteLoop<uint16>(&textureLoader, linearPixelData);
optimizedLinearReadbackWriteLoop<uint16>(&textureLoader, linearPixelData, sourceRowPitch);
}
else
{
@@ -816,7 +812,7 @@ void LatteTextureLoader_writeReadbackTextureToMemory(LatteTextureDefinition* tex
// used in Bayonetta 2
for (sint32 y = 0; y < textureLoader.height; y++)
{
uint8* pixelInput = linearPixelData + (y * textureLoader.width) * 4;
uint8* pixelInput = linearPixelData + y * sourceRowPitch;
for (sint32 x = 0; x < textureLoader.width; x++)
{
uint8* outputData = LatteTextureLoader_GetInput(&textureLoader, x, y);
@@ -831,7 +827,7 @@ void LatteTextureLoader_writeReadbackTextureToMemory(LatteTextureDefinition* tex
// 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;
uint8* pixelInput = linearPixelData + y * sourceRowPitch;
for (sint32 x = 0; x < textureLoader.width; x++)
{
uint8* outputData = LatteTextureLoader_GetInput(&textureLoader, x, y);
@@ -145,7 +145,7 @@ void LatteTextureReadback_UpdateFinishedTransfers(bool forceFinish)
}
#endif
uint8* pixelData = readbackInfo->GetData();
LatteTextureLoader_writeReadbackTextureToMemory(&readbackInfo->hostTextureCopy, readbackInfo->m_firstSlice, 0, pixelData);
LatteTextureLoader_writeReadbackTextureToMemory(&readbackInfo->hostTextureCopy, readbackInfo->m_firstSlice, readbackInfo->m_firstMip, pixelData, readbackInfo->m_rowPitch);
readbackInfo->ReleaseData();
// get the original texture if it still exists and invalidate the current data hash
LatteTextureView* origTexView = LatteTextureViewLookupCache::lookupSlice(readbackInfo->hostTextureCopy.physAddress, readbackInfo->hostTextureCopy.width, readbackInfo->hostTextureCopy.height, readbackInfo->hostTextureCopy.pitch, 0, 0, readbackInfo->hostTextureCopy.format);
@@ -172,7 +172,7 @@ bool LatteTextureReadback_ReadbackToLinearBlocking(LatteTextureView* sourceView,
uint8* data = info->GetData(); // returned pixel format should match Latte format
uint32 bpp = Latte::GetFormatBits(sourceView->baseTexture->format) / 8;
uint32 srcRowBytes = sourceView->baseTexture->width * bpp;
uint32 srcRowBytes = info->m_rowPitch;
uint32 dstRowBytes = dstWidth * bpp;
for (uint32 y = 0; y < dstHeight; y++)
memcpy(dstPtr + y * dstPitch * bpp, data + y * srcRowBytes, dstRowBytes);
@@ -6,8 +6,8 @@
class LatteTextureReadbackInfo
{
public:
LatteTextureReadbackInfo(LatteTextureView* textureView)
: hostTextureCopy(textureView->baseTexture), m_firstSlice(textureView->firstSlice), m_textureView(textureView)
LatteTextureReadbackInfo(LatteTextureView* textureView, sint32 mipIndex = 0)
: hostTextureCopy(textureView->baseTexture), m_firstSlice(textureView->firstSlice), m_firstMip(mipIndex), m_textureView(textureView)
{}
virtual ~LatteTextureReadbackInfo() = default;
@@ -25,8 +25,10 @@ public:
// texture info
LatteTextureDefinition hostTextureCopy{};
sint32 m_firstSlice{};
sint32 m_firstMip{}; // mip level transferred by the backend
uint32 m_rowPitch = 0; // bytes between rows in GetData(). Set by the backend
protected:
LatteTextureView* m_textureView;
uint32 m_image_size = 0;
};
};
@@ -19,6 +19,7 @@ void LatteTextureReadbackInfoMtl::StartTransfer()
cemu_assert_debug(m_textureView->baseTexture->dim != Latte::E_DIM::DIM_3D);
size_t bytesPerRow = GetMtlTextureBytesPerRow(baseTexture->format, baseTexture->isDepth, baseTexture->width);
m_rowPitch = static_cast<uint32>(bytesPerRow);
size_t bytesPerImage = GetMtlTextureBytesPerImage(baseTexture->format, baseTexture->isDepth, baseTexture->height, bytesPerRow);
auto blitCommandEncoder = m_mtlr->GetBlitCommandEncoder();
@@ -542,7 +542,6 @@ void OpenGLRenderer::HandleScreenshotRequest(LatteTextureView* texView, bool pad
texture_bindAndActivate(texView, 0);
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &screenshotWidth);
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &screenshotHeight);
glPixelStorei(GL_PACK_ALIGNMENT, 1); // set alignment to 1
const sint32 pixelDataSize = screenshotWidth * screenshotHeight * 3;
std::vector<uint8> rgb_data(pixelDataSize);
@@ -2,25 +2,26 @@
#include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.h"
LatteTextureReadbackInfoGL::LatteTextureReadbackInfoGL(LatteTextureView* textureView)
: LatteTextureReadbackInfo(textureView)
: LatteTextureReadbackInfo(textureView, textureView->firstMip)
{
LatteTexture* baseTexture = textureView->baseTexture;
uint32 width = baseTexture->GetMipWidth(m_firstMip);
// handle format
if (textureView->format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM)
{
m_image_size = baseTexture->width*baseTexture->height * 4;
m_rowPitch = width * 4;
m_texFormatGL = GL_RGBA;
m_texDataTypeGL = GL_UNSIGNED_BYTE;
}
else if (textureView->format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_SRGB)
{
m_image_size = baseTexture->width*baseTexture->height * 4;
m_rowPitch = width * 4;
m_texFormatGL = GL_RGBA;
m_texDataTypeGL = GL_UNSIGNED_BYTE;
}
else if (textureView->format == Latte::E_GX2SURFFMT::R32_G32_B32_A32_FLOAT)
{
m_image_size = baseTexture->width*baseTexture->height * 16;
m_rowPitch = width * 16;
m_texFormatGL = GL_RGBA;
m_texDataTypeGL = GL_FLOAT;
}
@@ -28,13 +29,13 @@ LatteTextureReadbackInfoGL::LatteTextureReadbackInfoGL(LatteTextureView* texture
{
if (baseTexture->isDepth)
{
m_image_size = baseTexture->width*baseTexture->height * 4;
m_rowPitch = width * 4;
m_texFormatGL = GL_DEPTH_COMPONENT;
m_texDataTypeGL = GL_FLOAT;
}
else
{
m_image_size = baseTexture->width*baseTexture->height * 4;
m_rowPitch = width * 4;
m_texFormatGL = GL_RED;
m_texDataTypeGL = GL_FLOAT;
}
@@ -43,33 +44,33 @@ LatteTextureReadbackInfoGL::LatteTextureReadbackInfoGL(LatteTextureView* texture
{
if (baseTexture->isDepth)
{
m_image_size = baseTexture->width*baseTexture->height * 2;
m_rowPitch = width * 2;
m_texFormatGL = GL_DEPTH_COMPONENT;
m_texDataTypeGL = GL_UNSIGNED_SHORT;
cemu_assert_unimplemented();
}
else
{
m_image_size = baseTexture->width*baseTexture->height * 2;
m_rowPitch = width * 2;
m_texFormatGL = GL_RED;
m_texDataTypeGL = GL_UNSIGNED_SHORT;
}
}
else if (textureView->format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT)
{
m_image_size = baseTexture->width*baseTexture->height * 8;
m_rowPitch = width * 8;
m_texFormatGL = GL_RGBA;
m_texDataTypeGL = GL_HALF_FLOAT;
}
else if (textureView->format == Latte::E_GX2SURFFMT::R8_G8_UNORM)
{
m_image_size = baseTexture->width*baseTexture->height * 2;
m_rowPitch = width * 2;
m_texFormatGL = GL_RG;
m_texDataTypeGL = GL_UNSIGNED_BYTE;
}
else if (textureView->format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_UNORM)
{
m_image_size = baseTexture->width*baseTexture->height * 8;
m_rowPitch = width * 8;
m_texFormatGL = GL_RGBA;
m_texDataTypeGL = GL_UNSIGNED_SHORT;
}
@@ -78,6 +79,9 @@ LatteTextureReadbackInfoGL::LatteTextureReadbackInfoGL(LatteTextureView* texture
cemuLog_logDebug(LogType::Force, "Unsupported texture readback format {:04x}", (uint32)textureView->format);
return;
}
// OpenGL uses the default 4 byte pack alignment because it gives us slightly better row copy performance
m_rowPitch = (m_rowPitch + 3) & ~3u;
m_image_size = m_rowPitch * baseTexture->GetMipHeight(m_firstMip);
}
LatteTextureReadbackInfoGL::~LatteTextureReadbackInfoGL()
@@ -93,14 +97,13 @@ void LatteTextureReadbackInfoGL::StartTransfer()
{
cemu_assert(m_textureView);
LatteTextureGL* baseTexture = (LatteTextureGL*)m_textureView->baseTexture;
cemu_assert_debug(m_textureView->firstMip == 0);
cemu_assert_debug(m_textureView->baseTexture->dim != Latte::E_DIM::DIM_3D);
// create unsynchronized buffer
glGenBuffers(1, &texImageBufferGL);
glBindBuffer(GL_PIXEL_PACK_BUFFER, texImageBufferGL);
glBufferData(GL_PIXEL_PACK_BUFFER, m_image_size, NULL, GL_DYNAMIC_READ);
// request texture read into buffer
glGetTextureSubImage(baseTexture->glId_texture, 0, 0, 0, m_firstSlice, baseTexture->width, baseTexture->height, 1, m_texFormatGL, m_texDataTypeGL, m_image_size, NULL);
glGetTextureSubImage(baseTexture->glId_texture, m_firstMip, 0, 0, m_firstSlice, baseTexture->GetMipWidth(m_firstMip), baseTexture->GetMipHeight(m_firstMip), 1, m_texFormatGL, m_texDataTypeGL, m_image_size, NULL);
glFlush();
// create fence sync (so we can check if the image copy operation finished)
imageCopyFinSync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
@@ -6,6 +6,7 @@ LatteTextureReadbackInfoVk::LatteTextureReadbackInfoVk(VkDevice device, LatteTex
: LatteTextureReadbackInfo(textureView), m_device(device)
{
m_image_size = GetImageSize(textureView);
m_rowPitch = m_image_size / textureView->baseTexture->height;
}
LatteTextureReadbackInfoVk::~LatteTextureReadbackInfoVk()