[GPU] Track per-slice guest DCC clear metadata

This commit is contained in:
ParantezTech 2026-10-01 17:17:45 +03:00
parent 597192c904
commit 8df819acc2
4 changed files with 134 additions and 0 deletions

View File

@ -84,6 +84,70 @@ public sealed partial class GuestImageCache
return false;
}
public static bool IsDccClearCode(byte code) => code is 0x00 or 0x20 or 0x40 or 0x80 or 0xc0;
public bool TryReadGuestDccClear(ulong metadataAddress, ulong sliceSize, uint slice, out ulong sliceAddress, out byte code)
{
sliceAddress = 0;
code = 0;
if (metadataAddress == 0 || sliceSize == 0 || sliceSize > int.MaxValue ||
(ulong)slice > (ulong.MaxValue - metadataAddress) / sliceSize)
{
return false;
}
var address = metadataAddress + (ulong)slice * sliceSize;
if (!IsValidRange(address, sliceSize) || _bufferCache.HasGpuDirtyBytes(address, sliceSize))
{
return false;
}
Span<byte> first = stackalloc byte[1];
if (!_backing.TryReadBacking(address, first) || !IsDccClearCode(first[0]))
{
return false;
}
var buffer = System.Buffers.ArrayPool<byte>.Shared.Rent((int)Math.Min(sliceSize, 64 * 1024));
try
{
for (ulong position = 0; position < sliceSize;)
{
var span = buffer.AsSpan(0, (int)Math.Min(sliceSize - position, (ulong)buffer.Length));
if (!_backing.TryReadBacking(address + position, span) || span.IndexOfAnyExcept(first[0]) >= 0)
{
return false;
}
position += (ulong)span.Length;
}
}
finally
{
System.Buffers.ArrayPool<byte>.Shared.Return(buffer);
}
sliceAddress = address;
code = first[0];
return true;
}
public void SynchronizeGuestDccMetadata(ulong metadataAddress, ulong sliceSize, uint baseLayer, uint layerCount)
{
if (metadataAddress == 0 || sliceSize == 0 || layerCount == 0 ||
(ulong)baseLayer + layerCount > (ulong.MaxValue - metadataAddress) / sliceSize)
{
return;
}
var address = metadataAddress + (ulong)baseLayer * sliceSize;
var size = (ulong)layerCount * sliceSize;
if (IsValidRange(address, size) && _bufferCache.HasGpuDirtyBytes(address, size))
{
_ = _bufferCache.TrySynchronizeCpuRead(address, size);
}
}
public bool SetMetadataSlice(ulong address, uint slice, bool isClear)
{
using var held = _lock.Hold();

View File

@ -245,6 +245,24 @@ public struct ImageDescription
public readonly bool IsTiledRenderTarget => TileMode == GuestTileMode.RenderTarget || IsSupportedStandard64RenderTarget;
public readonly ulong DccSliceSize
{
get
{
const ulong DccBlockBytes = 4096;
if (Metadata.Kind != MetadataKind.Dcc || TileMode != GuestTileMode.RenderTarget || Resources.Levels != 1 || Samples != 1 ||
Extent.Width == 0 || Extent.Height == 0 || BytesPerBlock is 0 or > 16 || !System.Numerics.BitOperations.IsPow2(BytesPerBlock))
{
return 0;
}
var coverageBits = 20 - System.Numerics.BitOperations.TrailingZeroCount(BytesPerBlock);
var blockWidth = 1UL << ((coverageBits + 1) / 2);
var blockHeight = 1UL << (coverageBits / 2);
return (Extent.Width + blockWidth - 1) / blockWidth * ((Extent.Height + blockHeight - 1) / blockHeight) * DccBlockBytes;
}
}
public static bool IsSupportedDisplayRenderTargetTileMode(GuestTileMode tileMode) => tileMode == GuestTileMode.RenderTarget;
public static bool CanUseDisplayNativeWithoutUpload(DisplayCompression compression, bool renderTarget, bool gpuModified, bool guestModified) =>

View File

@ -156,6 +156,36 @@ public sealed partial class GuestImageCacheTests
harness.Shutdown();
}
[Fact]
public void GuestDccClear_ReadsOnlyUniformClearSlices()
{
if (!GatePrerequisites.Ready(_vulkan)) return;
using var harness = new CacheHarness(_vulkan);
const ulong SliceSize = 0x2000;
var metadata = harness.MapBacked(0x10000, ReadWrite);
harness.Write(metadata, Enumerable.Repeat((byte)0x80, (int)SliceSize).ToArray());
harness.Write(metadata + SliceSize, Enumerable.Repeat((byte)0x00, (int)SliceSize).ToArray());
var mixed = Enumerable.Repeat((byte)0x40, (int)SliceSize).ToArray();
mixed[^1] = 0xff;
harness.Write(metadata + 2 * SliceSize, mixed);
harness.Write(metadata + 3 * SliceSize, Enumerable.Repeat((byte)0xff, (int)SliceSize).ToArray());
Assert.True(harness.Images.TryReadGuestDccClear(metadata, SliceSize, 0, out var slice, out var code));
Assert.Equal(metadata, slice);
Assert.Equal(0x80, code);
Assert.True(harness.Images.TryReadGuestDccClear(metadata, SliceSize, 1, out slice, out code));
Assert.Equal(metadata + SliceSize, slice);
Assert.Equal(0x00, code);
Assert.False(harness.Images.TryReadGuestDccClear(metadata, SliceSize, 2, out _, out _));
Assert.False(harness.Images.TryReadGuestDccClear(metadata, SliceSize, 3, out _, out _));
Assert.False(harness.Images.TryReadGuestDccClear(metadata, 0, 0, out _, out _));
harness.Worker.Run(() => harness.Cache.FillBuffer(metadata, SliceSize, uint.MaxValue, isGds: false));
Assert.False(harness.Images.TryReadGuestDccClear(metadata, SliceSize, 0, out _, out _));
Assert.True(harness.Images.TryReadGuestDccClear(metadata, SliceSize, 1, out _, out _));
harness.Shutdown();
}
[Fact]
public void Unregister_DropsMetadataInTheRange()
{

View File

@ -259,6 +259,28 @@ public sealed class ImageDescriptionTests
Reject(association, "stencil-association image", fatal);
}
[Theory]
[InlineData(2560u, 1440u, 8u, 1u, 1u, 0x1E000ul)]
[InlineData(1920u, 1080u, 4u, 1u, 1u, 0xC000ul)]
[InlineData(3840u, 2160u, 4u, 1u, 1u, 0x28000ul)]
[InlineData(640u, 360u, 16u, 1u, 1u, 0x6000ul)]
[InlineData(1920u, 1080u, 12u, 1u, 1u, 0ul)]
[InlineData(1920u, 1080u, 4u, 2u, 1u, 0ul)]
[InlineData(1920u, 1080u, 4u, 1u, 2u, 0ul)]
public void DccSliceSize_CoversOneMetadataBytePerColorBlock(uint width, uint height, uint bytesPerElement, uint levels, uint samples, ulong expected)
{
var description = Color(Base, 0x100, width, height, levels);
description.BytesPerBlock = bytesPerElement;
description.Samples = samples;
description.TileMode = GuestTileMode.RenderTarget;
description.Metadata.Kind = MetadataKind.Dcc;
description.Metadata.Range = new GuestSpan(0x200000, 0);
Assert.Equal(expected, description.DccSliceSize);
description.TileMode = GuestTileMode.Linear;
Assert.Equal(0ul, description.DccSliceSize);
}
[Fact]
public void Predicates_ReadTheDescription()
{