[GPU] Materialize DCC slice clears from guest metadata
Co-authored-by: Sekai <140012992+SekaixX@users.noreply.github.com>
This commit is contained in:
parent
ef10b4a1d8
commit
c0028d304b
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue