Latte: Handle texture copy edge-case

Fixes save preview images in BotW
This commit is contained in:
Exzap
2026-06-10 16:58:59 +02:00
parent d28c7654ec
commit 0a4f634eaf
3 changed files with 31 additions and 3 deletions

View File

@@ -125,6 +125,7 @@ void LatteTextureReadback_StartTransfer(LatteTextureView* textureView);
bool LatteTextureReadback_Update(bool forceStart = false);
void LatteTextureReadback_NotifyTextureDeletion(LatteTexture* texture);
void LatteTextureReadback_UpdateFinishedTransfers(bool forceFinish);
bool LatteTextureReadback_ReadbackToLinearBlocking(LatteTextureView* sourceView, uint8* dstPtr, uint32 dstWidth, uint32 dstHeight, uint32 dstPitch);
// query

View File

@@ -29,7 +29,6 @@ void LatteSurfaceCopy_CopyInRAM(const LatteSurfaceCopyParam& src, const LatteSur
copyWidth = (copyWidth + 3) / 4;
copyHeight = (copyHeight + 3) / 4;
}
uint32 dstBpp = Latte::GetFormatBits(dstHwFormat);
gx2SurfaceCopySoftware((uint8*)MEMPTR<void>(src.physDataAddr).GetPtr(), src.heightInTexels, src.pitch, 1, src.sliceIndex, src.swizzle, (uint32)src.tilemode,
@@ -44,10 +43,9 @@ void LatteSurfaceCopy_copySurfaceNew(const LatteSurfaceCopyParam& src, const Lat
cemu_assert_debug((rect.x + rect.width) <= dst.pitch * (Latte::IsCompressedFormat(dst.surfaceFormat)?4:1));
cemu_assert_debug((rect.x + rect.width) <= src.pitch * (Latte::IsCompressedFormat(src.surfaceFormat)?4:1));
if (src.tilemode == Latte::E_GX2TILEMODE::TM_LINEAR_SPECIAL || dst.tilemode == Latte::E_GX2TILEMODE::TM_LINEAR_SPECIAL)
if (src.tilemode == Latte::E_GX2TILEMODE::TM_LINEAR_SPECIAL)
{
// todo - it's technically possible for a matching linear texture to be in the texture cache already
// there is also a case of tiled to linear_special where we should trigger a readback without an actual destination texture
LatteSurfaceCopy_CopyInRAM(src, dst, rect);
return;
}
@@ -69,6 +67,13 @@ void LatteSurfaceCopy_copySurfaceNew(const LatteSurfaceCopyParam& src, const Lat
LatteTexture_UpdateCacheFromDynamicTextures(sourceTexture);
sourceTexture->reloadFromDynamicTextures = false;
}
// special case: RAM copy from cached texture to linear special format
if (dst.tilemode == Latte::E_GX2TILEMODE::TM_LINEAR_SPECIAL)
{
cemu_assert_debug(rect.x == 0 && rect.y == 0);
LatteTextureReadback_ReadbackToLinearBlocking(sourceView, MEMPTR<uint8>(dst.physDataAddr).GetPtr(), rect.width, rect.height, dst.pitch);
return;
}
// look up destination texture
LatteTexture* destinationTexture = nullptr;
LatteTextureView* destinationView = LatteTextureViewLookupCache::lookupSliceMinSize(dst.physDataAddr, rect.x + rect.width, rect.y + rect.height, dst.pitch, 0, dst.sliceIndex, dst.surfaceFormat);

View File

@@ -159,3 +159,25 @@ void LatteTextureReadback_UpdateFinishedTransfers(bool forceFinish)
}
performanceMonitor.gpuTime_waitForAsync.endMeasuring();
}
bool LatteTextureReadback_ReadbackToLinearBlocking(LatteTextureView* sourceView, uint8* dstPtr, uint32 dstWidth, uint32 dstHeight, uint32 dstPitch)
{
LatteTextureReadbackInfo* info = g_renderer->texture_createReadback(sourceView);
if (!info)
return false;
info->StartTransfer();
info->ForceFinish();
cemu_assert(info->IsFinished());
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 dstRowBytes = dstWidth * bpp;
for (uint32 y = 0; y < dstHeight; y++)
memcpy(dstPtr + y * dstPitch * bpp, data + y * srcRowBytes, dstRowBytes);
info->ReleaseData();
delete info;
return true;
}