[AGC] Read mip 0 from its GFX10 mip-chain offset (#470)

GFX10 stores a mip chain smallest-first: the mip tail packs into the
first swizzle block, the remaining mips follow in decreasing size, and
mip 0 ends up at the end of the allocation. We read the base level
straight from the descriptor address, so every mipped sampled texture
decoded as a collage of its own smaller mips - in Demon's Souls that
showed up as scrambled menu text and repeated controller icons.

GnmTiling.TryGetBaseMipPlacement ports the AddrLib chain-offset math
from Gfx10Lib::ComputeSurfaceInfoMacroTiled/MicroTiled. It returns a
byte offset to mip 0, or, when the whole chain fits inside the tail
block, the element coordinates of mip 0 within that block.
TryCreateGuestDrawTexture applies the offset to the sampled and storage
guest reads, and TryDetileTextureSource lifts a tail-resident mip 0 out
of the detiled block as a sub-rectangle.

MAX_MIP is only decoded from extended descriptors, so resources without
one, and single-level resources, keep the current behaviour.

Tested on Demon's Souls (PPSA01342): menu text and icons decode
correctly instead of showing shrunken copies of themselves. Verified
offline by dumping the raw tiled bytes and the detiled output for a
4096x4096 UI atlas and checking the art lands at the sampled
coordinates.
This commit is contained in:
TarkusTK
2026-07-20 14:27:46 +02:00
committed by GitHub
parent 9d187dec55
commit 6ee445f0c2
2 changed files with 234 additions and 9 deletions
+85 -9
View File
@@ -7782,7 +7782,10 @@ public static partial class AgcExports
TextureDescriptor descriptor,
uint sourceWidth,
int logicalByteCount,
byte[] source)
byte[] source,
bool baseMipInTail = false,
int tailElementX = 0,
int tailElementY = 0)
{
if (!GnmTiling.NeedsDetile(descriptor.TileMode) ||
!TryGetTextureElementLayout(
@@ -7795,6 +7798,48 @@ public static partial class AgcExports
return null;
}
if (baseMipInTail)
{
if (!GnmTiling.TryGetBlockElementDimensions(
descriptor.TileMode,
bytesPerElement,
out var blockWidth,
out var blockHeight))
{
return null;
}
var blockByteCount = (long)blockWidth * blockHeight * bytesPerElement;
if (source.Length < blockByteCount ||
(long)elementsWide * elementsHigh * bytesPerElement > logicalByteCount)
{
return null;
}
var blockLinear = new byte[blockByteCount];
if (!GnmTiling.TryDetile(
source,
blockLinear,
descriptor.TileMode,
blockWidth,
blockHeight,
bytesPerElement))
{
return null;
}
var tailLinear = new byte[logicalByteCount];
var rowBytes = elementsWide * bytesPerElement;
for (var y = 0; y < elementsHigh; y++)
{
var sourceOffset = (((long)tailElementY + y) * blockWidth + tailElementX) * bytesPerElement;
blockLinear.AsSpan((int)sourceOffset, rowBytes)
.CopyTo(tailLinear.AsSpan(y * rowBytes, rowBytes));
}
return tailLinear;
}
var linear = new byte[logicalByteCount];
return GnmTiling.TryDetile(
source,
@@ -7869,13 +7914,17 @@ public static partial class AgcExports
}
var physicalSourceByteCount = sourceByteCount;
if (GnmTiling.NeedsDetile(descriptor.TileMode) &&
var elementsWide = 0;
var elementsHigh = 0;
var bytesPerElement = 0;
var hasElementLayout = GnmTiling.NeedsDetile(descriptor.TileMode) &&
TryGetTextureElementLayout(
descriptor,
sourceWidth,
out var elementsWide,
out var elementsHigh,
out var bytesPerElement) &&
out elementsWide,
out elementsHigh,
out bytesPerElement);
if (hasElementLayout &&
GnmTiling.TryGetTiledByteCount(
descriptor.TileMode,
elementsWide,
@@ -7893,6 +7942,27 @@ public static partial class AgcExports
return true;
}
var resourceMipLevels = descriptor.HasExtendedDescriptor
? descriptor.ResourceMipLevels
: 1u;
var baseMipByteOffset = 0UL;
var baseMipInTail = false;
var mipTailElementX = 0;
var mipTailElementY = 0;
if (hasElementLayout && resourceMipLevels > 1)
{
GnmTiling.TryGetBaseMipPlacement(
descriptor.TileMode,
elementsWide,
elementsHigh,
bytesPerElement,
resourceMipLevels,
out baseMipByteOffset,
out baseMipInTail,
out mipTailElementX,
out mipTailElementY);
}
// Upload-known (not plain availability): the presenter's answer goes
// generation-stale when the guest CPU rewrites a CPU-backed image
// (video planes, streamed font atlases), which routes this draw back
@@ -7943,14 +8013,17 @@ public static partial class AgcExports
// and run the same AddrLib-derived detile path used below for
// sampled textures before seeding the Vulkan image.
var storageSource = new byte[(int)physicalSourceByteCount];
if (ctx.Memory.TryRead(descriptor.Address, storageSource))
if (ctx.Memory.TryRead(descriptor.Address + baseMipByteOffset, storageSource))
{
readSucceeded = true;
var linearStorage = TryDetileTextureSource(
descriptor,
sourceWidth,
checked((int)sourceByteCount),
storageSource) ?? storageSource
storageSource,
baseMipInTail,
mipTailElementX,
mipTailElementY) ?? storageSource
.AsSpan(0, checked((int)sourceByteCount))
.ToArray();
if (linearStorage.AsSpan().IndexOfAnyExcept((byte)0) >= 0)
@@ -8055,7 +8128,7 @@ public static partial class AgcExports
}
var source = new byte[(int)physicalSourceByteCount];
if (!ctx.Memory.TryRead(descriptor.Address, source))
if (!ctx.Memory.TryRead(descriptor.Address + baseMipByteOffset, source))
{
TraceTextureFallback(
descriptor,
@@ -8092,7 +8165,10 @@ public static partial class AgcExports
descriptor,
sourceWidth,
checked((int)sourceByteCount),
source) ?? source.AsSpan(0, checked((int)sourceByteCount)).ToArray();
source,
baseMipInTail,
mipTailElementX,
mipTailElementY) ?? source.AsSpan(0, checked((int)sourceByteCount)).ToArray();
DumpLinearTextureIfRequested(descriptor, sourceWidth, rgba);
texture = new GuestDrawTexture(
descriptor.Address,