mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-31 15:09:42 +08:00
[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:
@@ -7782,7 +7782,10 @@ public static partial class AgcExports
|
|||||||
TextureDescriptor descriptor,
|
TextureDescriptor descriptor,
|
||||||
uint sourceWidth,
|
uint sourceWidth,
|
||||||
int logicalByteCount,
|
int logicalByteCount,
|
||||||
byte[] source)
|
byte[] source,
|
||||||
|
bool baseMipInTail = false,
|
||||||
|
int tailElementX = 0,
|
||||||
|
int tailElementY = 0)
|
||||||
{
|
{
|
||||||
if (!GnmTiling.NeedsDetile(descriptor.TileMode) ||
|
if (!GnmTiling.NeedsDetile(descriptor.TileMode) ||
|
||||||
!TryGetTextureElementLayout(
|
!TryGetTextureElementLayout(
|
||||||
@@ -7795,6 +7798,48 @@ public static partial class AgcExports
|
|||||||
return null;
|
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];
|
var linear = new byte[logicalByteCount];
|
||||||
return GnmTiling.TryDetile(
|
return GnmTiling.TryDetile(
|
||||||
source,
|
source,
|
||||||
@@ -7869,13 +7914,17 @@ public static partial class AgcExports
|
|||||||
}
|
}
|
||||||
|
|
||||||
var physicalSourceByteCount = sourceByteCount;
|
var physicalSourceByteCount = sourceByteCount;
|
||||||
if (GnmTiling.NeedsDetile(descriptor.TileMode) &&
|
var elementsWide = 0;
|
||||||
|
var elementsHigh = 0;
|
||||||
|
var bytesPerElement = 0;
|
||||||
|
var hasElementLayout = GnmTiling.NeedsDetile(descriptor.TileMode) &&
|
||||||
TryGetTextureElementLayout(
|
TryGetTextureElementLayout(
|
||||||
descriptor,
|
descriptor,
|
||||||
sourceWidth,
|
sourceWidth,
|
||||||
out var elementsWide,
|
out elementsWide,
|
||||||
out var elementsHigh,
|
out elementsHigh,
|
||||||
out var bytesPerElement) &&
|
out bytesPerElement);
|
||||||
|
if (hasElementLayout &&
|
||||||
GnmTiling.TryGetTiledByteCount(
|
GnmTiling.TryGetTiledByteCount(
|
||||||
descriptor.TileMode,
|
descriptor.TileMode,
|
||||||
elementsWide,
|
elementsWide,
|
||||||
@@ -7893,6 +7942,27 @@ public static partial class AgcExports
|
|||||||
return true;
|
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
|
// Upload-known (not plain availability): the presenter's answer goes
|
||||||
// generation-stale when the guest CPU rewrites a CPU-backed image
|
// generation-stale when the guest CPU rewrites a CPU-backed image
|
||||||
// (video planes, streamed font atlases), which routes this draw back
|
// (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
|
// and run the same AddrLib-derived detile path used below for
|
||||||
// sampled textures before seeding the Vulkan image.
|
// sampled textures before seeding the Vulkan image.
|
||||||
var storageSource = new byte[(int)physicalSourceByteCount];
|
var storageSource = new byte[(int)physicalSourceByteCount];
|
||||||
if (ctx.Memory.TryRead(descriptor.Address, storageSource))
|
if (ctx.Memory.TryRead(descriptor.Address + baseMipByteOffset, storageSource))
|
||||||
{
|
{
|
||||||
readSucceeded = true;
|
readSucceeded = true;
|
||||||
var linearStorage = TryDetileTextureSource(
|
var linearStorage = TryDetileTextureSource(
|
||||||
descriptor,
|
descriptor,
|
||||||
sourceWidth,
|
sourceWidth,
|
||||||
checked((int)sourceByteCount),
|
checked((int)sourceByteCount),
|
||||||
storageSource) ?? storageSource
|
storageSource,
|
||||||
|
baseMipInTail,
|
||||||
|
mipTailElementX,
|
||||||
|
mipTailElementY) ?? storageSource
|
||||||
.AsSpan(0, checked((int)sourceByteCount))
|
.AsSpan(0, checked((int)sourceByteCount))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
if (linearStorage.AsSpan().IndexOfAnyExcept((byte)0) >= 0)
|
if (linearStorage.AsSpan().IndexOfAnyExcept((byte)0) >= 0)
|
||||||
@@ -8055,7 +8128,7 @@ public static partial class AgcExports
|
|||||||
}
|
}
|
||||||
|
|
||||||
var source = new byte[(int)physicalSourceByteCount];
|
var source = new byte[(int)physicalSourceByteCount];
|
||||||
if (!ctx.Memory.TryRead(descriptor.Address, source))
|
if (!ctx.Memory.TryRead(descriptor.Address + baseMipByteOffset, source))
|
||||||
{
|
{
|
||||||
TraceTextureFallback(
|
TraceTextureFallback(
|
||||||
descriptor,
|
descriptor,
|
||||||
@@ -8092,7 +8165,10 @@ public static partial class AgcExports
|
|||||||
descriptor,
|
descriptor,
|
||||||
sourceWidth,
|
sourceWidth,
|
||||||
checked((int)sourceByteCount),
|
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);
|
DumpLinearTextureIfRequested(descriptor, sourceWidth, rgba);
|
||||||
texture = new GuestDrawTexture(
|
texture = new GuestDrawTexture(
|
||||||
descriptor.Address,
|
descriptor.Address,
|
||||||
|
|||||||
@@ -194,6 +194,155 @@ internal static class GnmTiling
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool TryGetBlockElementDimensions(
|
||||||
|
uint swizzleMode,
|
||||||
|
int bytesPerElement,
|
||||||
|
out int blockWidth,
|
||||||
|
out int blockHeight)
|
||||||
|
{
|
||||||
|
blockWidth = 0;
|
||||||
|
blockHeight = 0;
|
||||||
|
if (bytesPerElement <= 0 ||
|
||||||
|
!TryGetSwizzleKind(swizzleMode, out _, out var blockBytes))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bppLog2 = BitLog2((uint)bytesPerElement);
|
||||||
|
if (bppLog2 < 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
(blockWidth, blockHeight) = SquareBlockDimensions(blockBytes >> bppLog2);
|
||||||
|
return blockWidth != 0 && blockHeight != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Locates mip 0 in a GFX10 mip chain, which AddrLib stores smallest-first
|
||||||
|
/// (Gfx10Lib::ComputeSurfaceInfoMacroTiled/MicroTiled).
|
||||||
|
/// </summary>
|
||||||
|
public static bool TryGetBaseMipPlacement(
|
||||||
|
uint swizzleMode,
|
||||||
|
int elementsWide,
|
||||||
|
int elementsHigh,
|
||||||
|
int bytesPerElement,
|
||||||
|
uint resourceMipLevels,
|
||||||
|
out ulong byteOffset,
|
||||||
|
out bool inMipTail,
|
||||||
|
out int tailElementX,
|
||||||
|
out int tailElementY)
|
||||||
|
{
|
||||||
|
byteOffset = 0;
|
||||||
|
inMipTail = false;
|
||||||
|
tailElementX = 0;
|
||||||
|
tailElementY = 0;
|
||||||
|
if (resourceMipLevels <= 1 ||
|
||||||
|
!ShouldDetile(swizzleMode) ||
|
||||||
|
elementsWide <= 0 ||
|
||||||
|
elementsHigh <= 0 ||
|
||||||
|
bytesPerElement <= 0 ||
|
||||||
|
!TryGetSwizzleKind(swizzleMode, out _, out var blockBytes))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bppLog2 = BitLog2((uint)bytesPerElement);
|
||||||
|
if (bppLog2 < 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var (blockWidth, blockHeight) = SquareBlockDimensions(blockBytes >> bppLog2);
|
||||||
|
var blockSizeLog2 = BitLog2((uint)blockBytes);
|
||||||
|
if (blockWidth == 0 || blockHeight == 0 || blockSizeLog2 < 8)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var mipLevels = (int)Math.Min(resourceMipLevels, 16u);
|
||||||
|
var maxMipsInTail = blockSizeLog2 <= 8 ? 0
|
||||||
|
: blockSizeLog2 <= 11
|
||||||
|
? 1 + (1 << (blockSizeLog2 - 9))
|
||||||
|
: blockSizeLog2 - 4;
|
||||||
|
var tailWidth = (blockSizeLog2 & 1) != 0 ? blockWidth >> 1 : blockWidth;
|
||||||
|
var tailHeight = (blockSizeLog2 & 1) != 0 ? blockHeight : blockHeight >> 1;
|
||||||
|
|
||||||
|
var firstMipInTail = mipLevels;
|
||||||
|
var mipSizes = new ulong[mipLevels];
|
||||||
|
for (var i = 0; i < mipLevels; i++)
|
||||||
|
{
|
||||||
|
var mipWidth = Math.Max(elementsWide >> i, 1);
|
||||||
|
var mipHeight = Math.Max(elementsHigh >> i, 1);
|
||||||
|
if (maxMipsInTail > 0 &&
|
||||||
|
mipWidth <= tailWidth &&
|
||||||
|
mipHeight <= tailHeight &&
|
||||||
|
mipLevels - i <= maxMipsInTail)
|
||||||
|
{
|
||||||
|
firstMipInTail = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var alignedWidth = (ulong)(mipWidth + blockWidth - 1) / (ulong)blockWidth * (ulong)blockWidth;
|
||||||
|
var alignedHeight = (ulong)(mipHeight + blockHeight - 1) / (ulong)blockHeight * (ulong)blockHeight;
|
||||||
|
mipSizes[i] = alignedWidth * alignedHeight * (ulong)bytesPerElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstMipInTail == 0)
|
||||||
|
{
|
||||||
|
var m = maxMipsInTail - 1;
|
||||||
|
var mipOffset = m > 6 ? 16 << m : m << 8;
|
||||||
|
var mipX = ((mipOffset >> 9) & 1) |
|
||||||
|
((mipOffset >> 10) & 2) |
|
||||||
|
((mipOffset >> 11) & 4) |
|
||||||
|
((mipOffset >> 12) & 8) |
|
||||||
|
((mipOffset >> 13) & 16) |
|
||||||
|
((mipOffset >> 14) & 32);
|
||||||
|
var mipY = ((mipOffset >> 8) & 1) |
|
||||||
|
((mipOffset >> 9) & 2) |
|
||||||
|
((mipOffset >> 10) & 4) |
|
||||||
|
((mipOffset >> 11) & 8) |
|
||||||
|
((mipOffset >> 12) & 16) |
|
||||||
|
((mipOffset >> 13) & 32);
|
||||||
|
if ((blockSizeLog2 & 1) != 0)
|
||||||
|
{
|
||||||
|
(mipX, mipY) = (mipY, mipX);
|
||||||
|
if ((bppLog2 & 1) != 0)
|
||||||
|
{
|
||||||
|
mipY = (mipY << 1) | (mipX & 1);
|
||||||
|
mipX >>= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (microWidth, microHeight) = SquareBlockDimensions(256 >> bppLog2);
|
||||||
|
if (microWidth == 0 || microHeight == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
tailElementX = mipX * microWidth;
|
||||||
|
tailElementY = mipY * microHeight;
|
||||||
|
if (tailElementX + elementsWide > blockWidth ||
|
||||||
|
tailElementY + elementsHigh > blockHeight)
|
||||||
|
{
|
||||||
|
tailElementX = 0;
|
||||||
|
tailElementY = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
inMipTail = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
byteOffset = firstMipInTail < mipLevels ? (ulong)blockBytes : 0;
|
||||||
|
for (var i = firstMipInTail - 1; i >= 1; i--)
|
||||||
|
{
|
||||||
|
byteOffset += mipSizes[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deswizzles <paramref name="tiled"/> into linear row-major order.
|
/// Deswizzles <paramref name="tiled"/> into linear row-major order.
|
||||||
/// Elements are pixels for uncompressed formats and 4x4 blocks for
|
/// Elements are pixels for uncompressed formats and 4x4 blocks for
|
||||||
|
|||||||
Reference in New Issue
Block a user