diff --git a/src/SharpEmu.Libs/Gpu/Images/ImageRequestBuilders.ColorTarget.cs b/src/SharpEmu.Libs/Gpu/Images/ImageRequestBuilders.ColorTarget.cs index 1be295f..783bef1 100644 --- a/src/SharpEmu.Libs/Gpu/Images/ImageRequestBuilders.ColorTarget.cs +++ b/src/SharpEmu.Libs/Gpu/Images/ImageRequestBuilders.ColorTarget.cs @@ -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) { diff --git a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.CachedImages.cs b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.CachedImages.cs index d7fe66e..dd2a625 100644 --- a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.CachedImages.cs +++ b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.CachedImages.cs @@ -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) diff --git a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Rendering.cs b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Rendering.cs index 8c924f5..7986e6d 100644 --- a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Rendering.cs +++ b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Rendering.cs @@ -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); } } }