[GPU] Materialize guest DCC clears in color and sampled images

This commit is contained in:
ParantezTech 2026-10-01 17:18:09 +03:00
parent 8df819acc2
commit dccdc2d6fd
3 changed files with 142 additions and 56 deletions

View File

@ -46,18 +46,20 @@ public static partial class ImageRequestBuilders
{
ClearColorValue value = default;
var supported = hasDcc && PackedClearValue.TryDecodeColor(format, packedClear, out value);
var fixedSupported = hasDcc && format switch
{
Format.R8Unorm or Format.R8G8Unorm or Format.R8G8B8A8Unorm or Format.R8G8B8A8Srgb or Format.B8G8R8A8Unorm or Format.B8G8R8A8Srgb or
Format.A2B10G10R10UnormPack32 or Format.A2R10G10B10UnormPack32 or Format.R5G6B5UnormPack16 or Format.A1R5G5B5UnormPack16 or
Format.R4G4B4A4UnormPack16 or Format.R16Unorm or Format.R16G16Unorm or Format.R16G16B16A16Unorm or Format.R16Sfloat or
Format.R16G16Sfloat or Format.R16G16B16A16Sfloat or Format.R32Sfloat or Format.R32G32Sfloat or Format.R32G32B32A32Sfloat or
Format.B10G11R11UfloatPack32 => true,
_ => false,
};
var fixedSupported = hasDcc && SupportsDccFixedClear(format);
return (supported, fixedSupported, supported ? value : default);
}
public static bool SupportsDccFixedClear(Format format) => format switch
{
Format.R8Unorm or Format.R8G8Unorm or Format.R8G8B8A8Unorm or Format.R8G8B8A8Srgb or Format.B8G8R8A8Unorm or Format.B8G8R8A8Srgb or
Format.A2B10G10R10UnormPack32 or Format.A2R10G10B10UnormPack32 or Format.R5G6B5UnormPack16 or Format.A1R5G5B5UnormPack16 or
Format.R4G4B4A4UnormPack16 or Format.R16Unorm or Format.R16G16Unorm or Format.R16G16B16A16Unorm or Format.R16Sfloat or
Format.R16G16Sfloat or Format.R16G16B16A16Sfloat or Format.R32Sfloat or Format.R32G32Sfloat or Format.R32G32B32A32Sfloat or
Format.B10G11R11UfloatPack32 => true,
_ => false,
};
// Builds the request for a bound color target. Null when the slot carries no target.
public static ColorTargetResolution? ColorTarget(in ColorTargetWords words, uint targetMask, uint drawLayerOffset, bool ignoreTargetMask)
{

View File

@ -7,6 +7,7 @@ using System.Collections.Concurrent;
using SharpEmu.Libs.Gpu;
using SharpEmu.Libs.Gpu.Buffers;
using SharpEmu.Libs.Gpu.Images;
using SharpEmu.Libs.Gpu.Rendering;
using SharpEmu.Libs.Gpu.Scheduling;
using SharpEmu.Libs.Gpu.Vulkan;
using Silk.NET.Vulkan;
@ -287,53 +288,14 @@ internal static unsafe partial class VulkanVideoPresenter
var metadataAddress = target.Request.Description.Metadata.Range.Address;
if (!_imageCache.IsMetadataCleared(metadataAddress, view.BaseLayer, out var metadataValue))
{
return false;
return ResolveGuestDccAttachmentClear(target, out clearValue);
}
var resolution = target.Resolution;
switch ((byte)metadataValue)
if (!TryDecodeDccClear((byte)metadataValue, resolution.MetadataClearSupported, resolution.MetadataFixedClearSupported,
resolution.ColorClearValue, out clearValue))
{
case 0x00:
break;
case 0x20:
if (!resolution.MetadataClearSupported)
{
return false;
}
clearValue = resolution.ColorClearValue;
break;
case 0x40:
if (!resolution.MetadataFixedClearSupported)
{
return false;
}
clearValue.Float32_3 = 1f;
break;
case 0x80:
if (!resolution.MetadataFixedClearSupported)
{
return false;
}
clearValue.Float32_0 = 1f;
clearValue.Float32_1 = 1f;
clearValue.Float32_2 = 1f;
break;
case 0xc0:
if (!resolution.MetadataFixedClearSupported)
{
return false;
}
clearValue.Float32_0 = 1f;
clearValue.Float32_1 = 1f;
clearValue.Float32_2 = 1f;
clearValue.Float32_3 = 1f;
break;
default:
return false;
return false;
}
for (uint layer = 1; layer < view.LayerCount; layer++)
@ -353,9 +315,104 @@ internal static unsafe partial class VulkanVideoPresenter
}
}
ConsumeGuestDccClears(target.Request.Description, view.BaseLayer, view.LayerCount);
return true;
}
private bool ResolveGuestDccAttachmentClear(ColorAttachment target, out ClearColorValue clearValue)
{
clearValue = default;
var description = target.Request.Description;
var sliceSize = description.DccSliceSize;
var view = target.Request.View;
if (sliceSize == 0 || view.LayerCount == 0)
{
return false;
}
var metadataAddress = description.Metadata.Range.Address;
var slices = new ulong[view.LayerCount];
byte clearCode = 0;
for (uint layer = 0; layer < view.LayerCount; layer++)
{
if (!_imageCache.TryReadGuestDccClear(metadataAddress, sliceSize, view.BaseLayer + layer, out slices[layer], out var code) ||
(layer != 0 && code != clearCode))
{
return false;
}
clearCode = code;
}
var resolution = target.Resolution;
if (!TryDecodeDccClear(clearCode, resolution.MetadataClearSupported, resolution.MetadataFixedClearSupported,
resolution.ColorClearValue, out clearValue))
{
return false;
}
foreach (var slice in slices)
{
_bufferCache.FillBuffer(slice, sliceSize, uint.MaxValue, false);
}
if (RenderTrace.Enabled && RenderTrace.MetadataClear())
{
RenderTrace.Write(
$"Materialized a guest DCC clear on a color target: metadata=0x{metadataAddress:X16} code=0x{clearCode:X2} " +
$"layers={view.BaseLayer}+{view.LayerCount} slice=0x{sliceSize:X} format={description.PixelFormat}");
}
return true;
}
private void ConsumeGuestDccClears(in ImageDescription description, uint baseLayer, uint layerCount)
{
var sliceSize = description.DccSliceSize;
if (sliceSize == 0)
{
return;
}
for (uint layer = 0; layer < layerCount; layer++)
{
if (_imageCache.TryReadGuestDccClear(description.Metadata.Range.Address, sliceSize, baseLayer + layer, out var slice, out _))
{
_bufferCache.FillBuffer(slice, sliceSize, uint.MaxValue, false);
}
}
}
private static bool TryDecodeDccClear(byte code, bool registerClearSupported, bool fixedClearSupported, ClearColorValue registerClear,
out ClearColorValue clearValue)
{
clearValue = default;
switch (code)
{
case 0x00:
return true;
case 0x20:
clearValue = registerClear;
return registerClearSupported;
case 0x40:
clearValue.Float32_3 = 1f;
return fixedClearSupported;
case 0x80:
clearValue.Float32_0 = 1f;
clearValue.Float32_1 = 1f;
clearValue.Float32_2 = 1f;
return fixedClearSupported;
case 0xc0:
clearValue.Float32_0 = 1f;
clearValue.Float32_1 = 1f;
clearValue.Float32_2 = 1f;
clearValue.Float32_3 = 1f;
return fixedClearSupported;
default:
return false;
}
}
private DepthAttachment? DiscoverDepthTarget(GuestDepthTarget target)
{
if (target.Registers is not { } words || ImageRequestBuilders.DepthTarget(words, _deviceInfo) is not { } resolution)

View File

@ -236,6 +236,12 @@ internal static unsafe partial class VulkanVideoPresenter
public ResourceSlotIdentifier FindImage(ref ImageRequest request, bool exactFormat)
{
_ = BeginBatchedGuestCommands();
if (request.Role == ImageRole.ColorTarget && request.Description.DccSliceSize is var sliceSize and not 0)
{
_imageCache.SynchronizeGuestDccMetadata(request.Description.Metadata.Range.Address, sliceSize,
request.View.BaseLayer, request.View.LayerCount);
}
return _imageCache.FindImage(ref request, exactFormat);
}
@ -534,8 +540,8 @@ internal static unsafe partial class VulkanVideoPresenter
// A DCC fast clear stays pending until the surface binds as a color target. A shader that
// samples or writes the surface first must see the cleared contents, and the later bind must
// not clear over its writes: Astro Bot's save-slot cards were drawn by compute into a
// fast-cleared target, then wiped black by the deferred clear. Only the zero clear code is
// materialized here; the register clear color is known only when the surface is a target.
// fast-cleared target, then wiped black by the deferred clear. The register clear color is
// known only when the surface is a target.
private void RecordSampledColorMetadataClears(TextureResource[] bindings)
{
const byte DccClearToZero = 0x00;
@ -547,10 +553,17 @@ internal static unsafe partial class VulkanVideoPresenter
}
var metadataAddress = image.Description.Metadata.Range.Address;
var sliceSize = image.Description.DccSliceSize;
var fixedClearSupported = ImageRequestBuilders.SupportsDccFixedClear(image.Description.PixelFormat);
var view = binding.Request.View;
for (var layer = view.BaseLayer; layer < view.BaseLayer + view.LayerCount; layer++)
{
if (!_imageCache.IsMetadataCleared(metadataAddress, layer, out var metadataValue) || (byte)metadataValue != DccClearToZero)
var tracked = _imageCache.IsMetadataCleared(metadataAddress, layer, out var metadataValue) && (byte)metadataValue == DccClearToZero;
var clearValue = default(ClearColorValue);
ulong guestSlice = 0;
if (!tracked &&
(sliceSize == 0 || !_imageCache.TryReadGuestDccClear(metadataAddress, sliceSize, layer, out guestSlice, out var code) ||
!TryDecodeDccClear(code, false, fixedClearSupported, default, out clearValue)))
{
continue;
}
@ -560,12 +573,26 @@ internal static unsafe partial class VulkanVideoPresenter
var range = new SubresourceRange(0, 1, layer, 1);
image.Transition(ImageLayout.TransferDstOptimal, AccessFlags.TransferWriteBit, range, command);
var vkRange = new ImageSubresourceRange(ImageAspectFlags.ColorBit, 0, 1, layer, 1);
var clearValue = default(ClearColorValue);
_vk.CmdClearColorImage(command, image.Backing.Handle, ImageLayout.TransferDstOptimal, &clearValue, 1, &vkRange);
if (!tracked)
{
_bufferCache.FillBuffer(guestSlice, sliceSize, uint.MaxValue, false);
if (RenderTrace.Enabled && RenderTrace.MetadataClear())
{
RenderTrace.Write(
$"Materialized a guest DCC clear on a sampled image: metadata=0x{metadataAddress:X16} layer={layer} " +
$"slice=0x{sliceSize:X} format={image.Description.PixelFormat}");
}
continue;
}
if (!_imageCache.SetMetadataSlice(metadataAddress, layer, false))
{
throw SubmissionScheduler.Fatal($"The DCC clear state could not be consumed: metadata=0x{metadataAddress:X16} layer={layer}.");
}
ConsumeGuestDccClears(image.Description, layer, 1);
}
}
}