[GPU] Materialize DCC slice clears from guest metadata

Co-authored-by: Sekai <140012992+SekaixX@users.noreply.github.com>
This commit is contained in:
ParantezTech 2026-10-08 17:32:01 +03:00
parent ef10b4a1d8
commit c0028d304b
4 changed files with 293 additions and 27 deletions

View File

@ -1,13 +1,271 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE.GpuMemory;
using SharpEmu.Libs.Gpu.Buffers;
using SharpEmu.Libs.Gpu.Scheduling;
using Silk.NET.Vulkan;
namespace SharpEmu.Libs.Gpu.Images;
// Surface metadata (HTile, DCC, CMask, FMask) keyed by its guest address.
public sealed partial class GuestImageCache
{
private unsafe void MaterializeDccClear(
ResourceSlotIdentifier imageIdentifier,
in ImageRequest request,
uint metadataBaseLayer)
{
if (request.Description.Metadata.Kind != MetadataKind.Dcc)
{
return;
}
var description = request.Description;
var range = description.Metadata.Range;
using (var held = _lock.Hold())
{
var image = _slots.TryGet(imageIdentifier);
if (image == null)
{
return;
}
image.Description.Metadata = description.Metadata;
if (range.Size == 0 || description.Resources.Levels != 1 ||
image.Description.Resources.Levels != 1)
{
return;
}
_surfaceMetadata.Remove(range.Address);
}
var layers = description.TransferLayers;
const ulong metadataBlockSize = 0x1000;
if (!ImageDescription.IsValidRange(range) ||
range.Address % metadataBlockSize != 0 ||
layers == 0 ||
range.Size % layers != 0 ||
(range.Size / layers) % metadataBlockSize != 0)
{
throw SubmissionScheduler.Fatal(
$"DCC slices must contain aligned 4 KiB blocks: address=0x{range.Address:X16} size=0x{range.Size:X} layers={layers}.");
}
var view = request.View;
var volume = description.IsVolume && view.Type == ImageViewType.Type3D;
var first = volume ? 0u : metadataBaseLayer;
var imageFirst = volume ? 0u : view.BaseLayer;
var count = volume ? description.Extent.Depth : view.LayerCount;
if (first >= layers || count == 0 || count > layers - first)
{
throw SubmissionScheduler.Fatal(
$"The DCC view exceeds its metadata slices: first={first} count={count} layers={layers}.");
}
var sliceSize = range.Size / layers;
var synchronizedAddress = range.Address + sliceSize * first;
var synchronizedSize = sliceSize * count;
if (_bufferCache.HasGpuDirtyBytes(synchronizedAddress, synchronizedSize))
{
_bufferCache.ReadMemory(synchronizedAddress, synchronizedSize, isWrite: false);
}
const int metadataScanChunkSize = 64 * 1024;
var bytes = System.Buffers.ArrayPool<byte>.Shared.Rent(
checked((int)Math.Min(sliceSize, (ulong)metadataScanChunkSize)));
try
{
Span<byte> firstByte = stackalloc byte[1];
for (uint slice = 0; slice < count; slice++)
{
var address = range.Address + sliceSize * (first + slice);
if (!_backing.TryReadBacking(address, firstByte))
{
throw SubmissionScheduler.Fatal(
$"Could not read DCC metadata key: address=0x{address:X16}.");
}
var code = firstByte[0];
if (!PackedClearValue.TryDecodeDccColor(
request.View.Format,
code * 0x01010101u,
description.Metadata,
allowClearToRegister: request.Role == ImageRole.ColorTarget,
out var clear))
{
continue;
}
var uniform = true;
for (ulong position = 0; position < sliceSize && uniform;)
{
var length = checked((int)Math.Min(
sliceSize - position,
(ulong)Math.Min(bytes.Length, metadataScanChunkSize)));
var chunk = bytes.AsSpan(0, length);
if (!_backing.TryReadBacking(address + position, chunk))
{
throw SubmissionScheduler.Fatal(
$"Could not read DCC metadata backing: address=0x{address + position:X16} size=0x{length:X}.");
}
for (var index = 0; index < chunk.Length; index++)
{
if (chunk[index] != code)
{
uniform = false;
break;
}
}
position += (ulong)length;
}
if (!uniform)
{
continue;
}
using (var held = _lock.Hold())
{
if (_slots.TryGet(imageIdentifier) == null)
{
return;
}
ClearDccImage(
imageIdentifier,
request,
new SubresourceRange(view.BaseLevel, view.LevelCount, imageFirst + slice, 1),
clear);
}
if (request.Role != ImageRole.DisplaySurface)
{
_bufferCache.FillBuffer(address, sliceSize, uint.MaxValue, isGds: false);
}
}
}
finally
{
System.Buffers.ArrayPool<byte>.Shared.Return(bytes);
}
}
private unsafe void ClearDccImage(
ResourceSlotIdentifier imageIdentifier,
in ImageRequest request,
in SubresourceRange range,
in ClearColorValue clear)
{
var image = _slots[imageIdentifier];
ref readonly var description = ref image.Description;
if (range.BaseLevel >= description.Resources.Levels || range.LevelCount == 0 ||
range.LevelCount > description.Resources.Levels - range.BaseLevel)
{
throw SubmissionScheduler.Fatal(
$"The DCC clear mip range is invalid: base={range.BaseLevel} count={range.LevelCount} levels={description.Resources.Levels}.");
}
var layers = description.IsVolume
? Math.Max(description.Extent.Depth >> (int)range.BaseLevel, 1)
: image.Backing.Layers;
if (range.LayerCount == 0 || range.BaseLayer >= layers || range.LayerCount > layers - range.BaseLayer)
{
throw SubmissionScheduler.Fatal(
$"The DCC clear layer range is invalid: base={range.BaseLayer} count={range.LayerCount} layers={layers}.");
}
var fullImage = range.BaseLevel == 0 && range.LevelCount == description.Resources.Levels &&
range.BaseLayer == 0 && range.LayerCount == layers;
WatchImage(imageIdentifier);
if (!fullImage && (image.IsBufferModified || image.IsCpuDirty))
{
PopulateFromGuest(imageIdentifier, RefreshRequest(image), "before-dcc-clear");
if (image.Description.Samples == 1 && (image.IsBufferModified || image.IsCpuDirty))
{
throw SubmissionScheduler.Fatal(
$"The DCC clear left guest ownership in place: address=0x{description.Data.Address:X16} bufferModified={image.IsBufferModified} cpuDirty={image.IsCpuDirty}.");
}
}
var scheduled = _scheduler.Current;
if (scheduled.IsInvalid)
{
throw SubmissionScheduler.Fatal("A DCC clear needs a command buffer that is recording.");
}
scheduled.EndRendering();
var command = new CommandBuffer(scheduled.Handle);
var needsAttachmentClear = request.View.Format != image.Backing.Format ||
(description.IsVolume && !fullImage);
if (needsAttachmentClear)
{
var viewDescription = new ImageViewDescription(
request.View.Format,
range.LayerCount == 1 ? ImageViewType.Type2D : ImageViewType.Type2DArray,
ImageAspectFlags.ColorBit,
range.BaseLevel,
range.LevelCount,
range.BaseLayer,
range.LayerCount,
default,
ImageUsageFlags.ColorAttachmentBit);
var view = image.GetOrCreateView(viewDescription);
image.Transition(
ImageLayout.ColorAttachmentOptimal,
AccessFlags.ColorAttachmentWriteBit,
range,
command);
var attachment = new RenderingAttachmentInfo
{
SType = StructureType.RenderingAttachmentInfo,
ImageView = view,
ImageLayout = ImageLayout.ColorAttachmentOptimal,
LoadOp = AttachmentLoadOp.Clear,
StoreOp = AttachmentStoreOp.Store,
ClearValue = new ClearValue { Color = clear },
};
var rendering = new RenderingInfo
{
SType = StructureType.RenderingInfo,
RenderArea = new Rect2D(
new Offset2D(0, 0),
new Extent2D(
Math.Max(description.Extent.Width >> (int)range.BaseLevel, 1),
Math.Max(description.Extent.Height >> (int)range.BaseLevel, 1))),
LayerCount = range.LayerCount,
ColorAttachmentCount = 1,
PColorAttachments = &attachment,
};
_device.Vk.CmdBeginRendering(command, &rendering);
_device.Vk.CmdEndRendering(command);
}
else
{
image.Transition(ImageLayout.TransferDstOptimal, AccessFlags.TransferWriteBit, range, command);
var nativeRange = new ImageSubresourceRange(
ImageAspectFlags.ColorBit,
range.BaseLevel,
range.LevelCount,
description.IsVolume ? 0u : range.BaseLayer,
description.IsVolume ? 1u : range.LayerCount);
var nativeClear = clear;
_device.Vk.CmdClearColorImage(
command,
image.Backing.Handle,
ImageLayout.TransferDstOptimal,
&nativeClear,
1,
&nativeRange);
}
TakeGpuOwnership(image);
}
public bool IsMetadata(ulong address)
{
using var held = _lock.Hold();
@ -57,7 +315,6 @@ public sealed partial class GuestImageCache
return true;
}
// True when registered DCC absorbed the fill and the guest dispatch can be skipped.
public bool TryAbsorbDccFill(ulong address, ulong size, uint fillValue)
{
if (!IsValidRange(address, size))

View File

@ -123,28 +123,48 @@ public sealed unsafe partial class GuestImageCache : IGuestImageCache, IGuestIma
return GetNullImage(request);
}
var found = LookUpImage(ref request, exactFormat);
var metadataBaseLayer = request.View.BaseLayer;
var result = LookUpImage(ref request, exactFormat);
if (request.Role is ImageRole.Texture or ImageRole.StorageImage)
{
if (request.Description.Metadata.Kind == MetadataKind.Dcc)
ulong metadataAddress;
ulong sliceSize;
using (var held = _lock.Hold())
{
using var held = _lock.Hold();
var image = _slots[found];
if (image.Description.Metadata.Kind == MetadataKind.None)
var image = _slots[result];
if (request.Description.Metadata.Kind == MetadataKind.Dcc && request.Description.Metadata.Range.Size == 0 &&
image.Description.Metadata.Kind == MetadataKind.None)
{
image.Description.Metadata.Kind = MetadataKind.Dcc;
image.Description.Metadata.Range = request.Description.Metadata.Range;
image.Description.Metadata = request.Description.Metadata;
}
ref readonly var description = ref image.Description;
metadataAddress = description.Metadata.Range.Address;
sliceSize = description.DccSliceSize;
}
ref readonly var description = ref _slots[found].Description;
if (description.DccSliceSize is var sliceSize and not 0)
if (sliceSize != 0)
{
SynchronizeGuestDccMetadata(description.Metadata.Range.Address, sliceSize, request.View.BaseLayer, request.View.LayerCount);
SynchronizeGuestDccMetadata(metadataAddress, sliceSize, request.View.BaseLayer, request.View.LayerCount);
}
}
return found;
MaterializeDccClear(result, request, metadataBaseLayer);
if (request.Role == ImageRole.DisplaySurface && request.Description.Metadata.Compression != DisplayCompression.Uncompressed)
{
using var held = _lock.Hold();
var image = _slots[result];
var guestDirty = image.IsBufferModified || image.IsCpuDirty;
var nativeCurrent = (image.Uses.RenderTarget || image.IsGpuModified) && !guestDirty;
if (!nativeCurrent)
{
throw SubmissionScheduler.Fatal(
$"A compressed display surface can only be read from clean native GPU contents: address=0x{image.Description.Data.Address:X16} bufferModified={image.IsBufferModified} cpuDirty={image.IsCpuDirty} gpuModified={image.IsGpuModified}.");
}
}
return result;
}
private ResourceSlotIdentifier LookUpImage(ref ImageRequest request, bool exactFormat)
@ -231,17 +251,6 @@ public sealed unsafe partial class GuestImageCache : IGuestImageCache, IGuestIma
}
var image = _slots[result];
if (request.Role == ImageRole.DisplaySurface && request.Description.Metadata.Compression != DisplayCompression.Uncompressed)
{
var guestDirty = image.IsBufferModified || image.IsCpuDirty;
var nativeCurrent = (image.Uses.RenderTarget || image.IsGpuModified) && !guestDirty;
if (!nativeCurrent)
{
throw SubmissionScheduler.Fatal(
$"A compressed display surface can only be read from clean native GPU contents: address=0x{image.Description.Data.Address:X16} bufferModified={image.IsBufferModified} cpuDirty={image.IsCpuDirty} gpuModified={image.IsGpuModified}.");
}
}
if (viewMip >= 0)
{
request.View = request.View with { BaseLevel = (uint)viewMip };
@ -353,8 +362,7 @@ public sealed unsafe partial class GuestImageCache : IGuestImageCache, IGuestIma
image.MarkGpuModified();
image.Uses.RenderTarget = true;
RefreshFromGuest(imageIdentifier, request);
// DCC lives in its own allocation; it is registered at bind time, keeping a pending fill.
if (request.Description.Metadata.Kind == MetadataKind.Dcc)
if (request.Description.Metadata.Kind == MetadataKind.Dcc && request.Description.Metadata.Range.Size == 0)
{
image.Description.Metadata = request.Description.Metadata;
var address = request.Description.Metadata.Range.Address;

View File

@ -276,7 +276,7 @@ internal static unsafe partial class VulkanVideoPresenter
private bool ResolveDccAttachmentClear(ColorAttachment target, out ClearColorValue clearValue)
{
clearValue = default;
if (target.Request.Description.Metadata.Kind != MetadataKind.Dcc)
if (target.Request.Description.Metadata.Kind != MetadataKind.Dcc || target.Request.Description.Metadata.Range.Size != 0)
{
return false;
}

View File

@ -574,7 +574,8 @@ internal static unsafe partial class VulkanVideoPresenter
const byte DccClearToZero = 0x00;
foreach (var binding in bindings)
{
if (binding.IsHostMovie || binding.CachedImage is not { } image || image.Description.Metadata.Kind != MetadataKind.Dcc)
if (binding.IsHostMovie || binding.CachedImage is not { } image || image.Description.Metadata.Kind != MetadataKind.Dcc ||
image.Description.Metadata.Range.Size != 0)
{
continue;
}