[GPU] Refresh the stencil plane through the depth image (#989)

This commit is contained in:
T 2026-09-30 19:14:49 +02:00 committed by GitHub
parent b480391a7b
commit a424a67c1c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 140 additions and 15 deletions

View File

@ -586,7 +586,7 @@ public sealed partial class GuestImageCache
return replacementImageIdentifier;
}
private void AssociateStencilRange(ResourceSlotIdentifier depthImageIdentifier, GuestSpan stencil)
private ResourceSlotIdentifier AssociateStencilRange(ResourceSlotIdentifier depthImageIdentifier, GuestSpan stencil)
{
if (!ImageDescription.IsValidRange(stencil))
{
@ -620,5 +620,6 @@ public sealed partial class GuestImageCache
var record = _slots[association];
TouchImage(record);
record.AssociateDepth(depthImageIdentifier);
return association;
}
}

View File

@ -424,7 +424,7 @@ public sealed partial class GuestImageCache
foreach (var imageIdentifier in FindImagesInRange(address, size, pageOverlap: true))
{
var owner = _slots.TryGet(imageIdentifier);
if (owner == null || owner.DepthOwner.IsValid)
if (owner == null)
{
continue;
}
@ -467,7 +467,7 @@ public sealed partial class GuestImageCache
foreach (var imageIdentifier in FindImagesInRange(address, size, pageOverlap: true))
{
var image = _slots[imageIdentifier];
if (image.DepthOwner.IsValid || !image.Overlaps(address, size))
if (!image.Overlaps(address, size))
{
continue;
}
@ -496,14 +496,9 @@ public sealed partial class GuestImageCache
foreach (var imageIdentifier in FindImagesInRange(address, size, pageOverlap: true))
{
var image = _slots[imageIdentifier];
if (image.DepthOwner.IsValid)
{
continue;
}
imagePages = true;
imageBytes |= image.Overlaps(address, size);
gpuImageBytes |= image.GpuOverlaps(address, size);
gpuImageBytes |= !image.DepthOwner.IsValid && image.GpuOverlaps(address, size);
}
return new ImageRegionInfo(imagePages, imageBytes, gpuImageBytes);

View File

@ -190,7 +190,7 @@ public sealed unsafe partial class GuestImageCache
return tiles;
}
private static BufferImageCopy[] DepthCopies(in ImageDescription info, ulong sliceSize)
private static BufferImageCopy[] DepthCopies(in ImageDescription info, ulong sliceSize, ImageAspectFlags aspect = ImageAspectFlags.DepthBit)
{
var copies = new BufferImageCopy[info.Resources.Layers];
for (uint layer = 0; layer < info.Resources.Layers; layer++)
@ -200,7 +200,7 @@ public sealed unsafe partial class GuestImageCache
BufferOffset = sliceSize * layer,
BufferRowLength = info.Pitch,
BufferImageHeight = info.Extent.Height,
ImageSubresource = new ImageSubresourceLayers(ImageAspectFlags.DepthBit, 0, layer, 1),
ImageSubresource = new ImageSubresourceLayers(aspect, 0, layer, 1),
ImageExtent = new Extent3D(info.Extent.Width, info.Extent.Height, 1),
};
}
@ -230,8 +230,44 @@ public sealed unsafe partial class GuestImageCache
image.UploadFromBuffer(System.Runtime.InteropServices.CollectionsMarshal.AsSpan(copies), linear.Buffer, linear.Offset, linear.Size);
}
private void UploadStencilPlane(CachedImage association, GpuBuffer source, ulong sourceOffset)
{
var destination = _slots[association.DepthOwner];
var info = destination.Description;
info.Data = association.Description.Data;
info.GuestFormat = GuestPixelFormat.Bits8UInt;
info.BytesPerBlock = 1;
if (info.IsTiled)
{
info.Pitch = TileGeometry.DepthPitch(info.Extent.Width, 1);
}
if (info.Samples != 1 || destination.Backing.Samples != 1 || info.Resources.Layers == 0 || info.Data.Size % info.Resources.Layers != 0)
{
throw SubmissionScheduler.Fatal(
$"The stencil upload is invalid: address=0x{info.Data.Address:X16} size=0x{info.Data.Size:X} layers={info.Resources.Layers} samples={info.Samples} backingSamples={destination.Backing.Samples}.");
}
var fullSliceSize = info.Data.Size / info.Resources.Layers;
var copies = DepthCopies(info, fullSliceSize, ImageAspectFlags.StencilBit);
var linear = new TilerBufferSpan(source.Handle, sourceOffset, source.Size - sourceOffset);
if (info.IsTiled)
{
var tiles = DepthTileTransfers(info, DepthBlock(info), fullSliceSize);
linear = _tiler.Detile(source.Handle, sourceOffset, info.Data.Size, info.Data.Size, System.Runtime.InteropServices.CollectionsMarshal.AsSpan(tiles));
}
UploadRegions(destination, copies.ToList(), linear);
}
private void UploadFromBuffer(CachedImage image, in ImageRequest request, GpuBuffer source, ulong sourceOffset)
{
if (image.DepthOwner.IsValid)
{
UploadStencilPlane(image, source, sourceOffset);
return;
}
ref readonly var info = ref image.Description;
if (request.Role != ImageRole.DepthTarget)
{
@ -830,6 +866,11 @@ public sealed unsafe partial class GuestImageCache
}
TakeGpuOwnership(image);
if (aspect == ImageAspectFlags.StencilBit)
{
TakeStencilOwnership(selected, image);
}
return true;
}
}

View File

@ -378,15 +378,27 @@ public sealed unsafe partial class GuestImageCache : IGuestImageCache, IGuestIma
}
}
TakeGpuOwnership(image);
if (request.Description.HasStencil)
{
AssociateStencilRange(imageIdentifier, request.Description.Stencil);
image.Description.Stencil = request.Description.Stencil;
RefreshStencilPlane(imageIdentifier, image, request.Description.Metadata.StencilCompressed);
}
TakeGpuOwnership(image);
return image.GetOrCreateView(request.View);
}
private void RefreshStencilPlane(ResourceSlotIdentifier depthIdentifier, CachedImage depth, bool stencilCompressed)
{
var association = AssociateStencilRange(depthIdentifier, depth.Description.Stencil);
if (stencilCompressed || depth.Description.Samples != 1)
{
return;
}
RefreshFromGuest(association, RefreshRequest(_slots[association]));
}
public void MarkGpuWritten(ResourceSlotIdentifier imageIdentifier)
{
using var held = _lock.Hold();
@ -398,13 +410,24 @@ public sealed unsafe partial class GuestImageCache : IGuestImageCache, IGuestIma
WatchImage(imageIdentifier);
TakeGpuOwnership(image);
if (image.Description.HasStencil)
{
TakeStencilOwnership(imageIdentifier, image);
}
}
private void TakeStencilOwnership(ResourceSlotIdentifier depthIdentifier, CachedImage depth)
{
var association = AssociateStencilRange(depthIdentifier, depth.Description.Stencil);
WatchImage(association);
TakeGpuOwnership(_slots[association]);
}
private static void TakeGpuOwnership(CachedImage image)
{
if (image.DepthOwner.IsValid || !image.Backing.Exists)
if (!image.DepthOwner.IsValid && !image.Backing.Exists)
{
throw SubmissionScheduler.Fatal($"A stencil association cannot own image contents: address=0x{image.Description.Data.Address:X16}.");
throw SubmissionScheduler.Fatal($"GPU ownership needs a native image or a stencil association: address=0x{image.Description.Data.Address:X16}.");
}
image.ClearBufferModified();

View File

@ -543,4 +543,69 @@ public sealed unsafe partial class GuestImageCacheTests
Assert.Equal(Bytes((ushort)0x0000, (ushort)0x2468, (ushort)0xabcd, (ushort)0xffff), harness.ReadImageBytes(harness.Image(storageId)));
harness.Shutdown();
}
[Fact]
public void StencilPlaneWrites_ReachTheCombinedAttachment()
{
if (!GatePrerequisites.Ready(_vulkan)) return;
var stencilFormat = SupportedStencilFormat(_vulkan, SampleCountFlags.Count1Bit);
if (stencilFormat == Format.Undefined) return;
using var harness = new CacheHarness(_vulkan);
var address = harness.MapBacked(0x10000, ReadWrite);
var depthAddress = address + 0xc000;
var stencilAddress = address + 0xd000;
harness.Write(depthAddress, Bytes((ushort)0x1111, (ushort)0x2222, (ushort)0x3333, (ushort)0x4444));
harness.Write(stencilAddress, [0x56, 0x56, 0x00, 0x56]);
var depth = LinearRequest(depthAddress, 8, stencilFormat, GuestPixelFormat.Bits16UNorm, GuestImageType.Color2D, new Extent3D(4, 1, 1), 1, 2, 1);
depth = AsDepthTarget(depth, stencilFormat);
depth.Description.Stencil = new GuestSpan(stencilAddress, 4);
depth.View = depth.View with { Aspect = ImageAspectFlags.DepthBit | ImageAspectFlags.StencilBit };
byte[] Stencil(ResourceSlotIdentifier id) => harness.ReadImageBytes(harness.Image(id), ImageAspectFlags.StencilBit)[..4];
var depthId = harness.Acquire(ref depth);
Assert.Equal(new byte[] { 0x56, 0x56, 0x00, 0x56 }, Stencil(depthId));
harness.Write(stencilAddress, [0x01, 0x02, 0x03, 0x04]);
Assert.True(harness.WriteFault(stencilAddress));
Assert.Equal(depthId, harness.Acquire(ref depth));
Assert.Equal(new byte[] { 0x01, 0x02, 0x03, 0x04 }, Stencil(depthId));
harness.Worker.Run(() => harness.Cache.FillBuffer(stencilAddress, 4, 0, false));
Assert.Equal(depthId, harness.Acquire(ref depth));
Assert.Equal(new byte[4], Stencil(depthId));
harness.Write(stencilAddress, [0x09, 0x09, 0x09, 0x09]);
Assert.True(harness.Worker.Run(() => harness.Images.TryClearImageFromBuffer(stencilAddress, 4, 0x07070707)));
Assert.Equal(depthId, harness.Acquire(ref depth));
Assert.Equal(new byte[] { 0x07, 0x07, 0x07, 0x07 }, Stencil(depthId));
harness.Shutdown();
}
[Fact]
public void TiledStencilPlaneWrites_ReachTheCombinedAttachment()
{
if (!GatePrerequisites.Ready(_vulkan)) return;
const ImageUsageFlags usage = ImageUsageFlags.TransferSrcBit | ImageUsageFlags.TransferDstBit | ImageUsageFlags.DepthStencilAttachmentBit;
if (!_vulkan.DeviceInfo.TryGetImageFormatProperties(Format.D32SfloatS8Uint, ImageType.Type2D, ImageTiling.Optimal, usage, 0, out _)) return;
using var harness = new CacheHarness(_vulkan);
const ulong planeSize = 0x10000;
const int texels = 128 * 128;
var address = harness.MapBacked(planeSize * 3, ReadWrite);
var stencilAddress = address + planeSize;
harness.Write(stencilAddress, Enumerable.Repeat((byte)0x5a, (int)planeSize).ToArray());
var depth = LinearRequest(address, planeSize, Format.D32SfloatS8Uint, GuestPixelFormat.Bits32Float, GuestImageType.Color2D, new Extent3D(128, 128, 1), 1, 4, 1);
depth.Description.TileMode = GuestTileMode.Depth;
depth = AsDepthTarget(depth, Format.D32SfloatS8Uint);
depth.Description.Stencil = new GuestSpan(stencilAddress, planeSize);
depth.View = depth.View with { Aspect = ImageAspectFlags.DepthBit | ImageAspectFlags.StencilBit };
byte[] Stencil(ResourceSlotIdentifier id) => harness.ReadImageBytes(harness.Image(id), ImageAspectFlags.StencilBit)[..texels];
var depthId = harness.Acquire(ref depth);
Assert.All(Stencil(depthId), value => Assert.Equal(0x5a, value));
harness.Worker.Run(() => harness.Cache.FillBuffer(stencilAddress, planeSize, 0, false));
Assert.Equal(depthId, harness.Acquire(ref depth));
Assert.All(Stencil(depthId), value => Assert.Equal(0, value));
harness.Shutdown();
}
}