mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-22 10:56:20 +08:00
Vulkan: fix guest storage image and render-state handling (#332)
This commit is contained in:
@@ -5779,7 +5779,9 @@ public static partial class AgcExports
|
||||
|
||||
textures.Add(new TranslatedImageBinding(
|
||||
texture,
|
||||
Gen5ShaderTranslator.IsStorageImageOperation(binding.Opcode),
|
||||
Gen5ShaderTranslator.RequiresStorageImage(
|
||||
binding,
|
||||
exportEvaluation.ImageBindings),
|
||||
binding.MipLevel ?? 0,
|
||||
binding.SamplerDescriptor));
|
||||
}
|
||||
@@ -6102,17 +6104,22 @@ public static partial class AgcExports
|
||||
_graphicsShaderCache.TryAdd(shaderKey, compiled);
|
||||
}
|
||||
|
||||
var imageBindings = pixelEvaluation.ImageBindings
|
||||
.Concat(exportEvaluation.ImageBindings)
|
||||
.ToArray();
|
||||
var textures = new List<TranslatedImageBinding>(
|
||||
pixelEvaluation.ImageBindings.Count +
|
||||
exportEvaluation.ImageBindings.Count);
|
||||
if (!TryAppendTranslatedImageBindings(
|
||||
pixelEvaluation.ImageBindings,
|
||||
imageBindings,
|
||||
textures,
|
||||
pixelShaderAddress,
|
||||
exportShaderAddress,
|
||||
out error) ||
|
||||
!TryAppendTranslatedImageBindings(
|
||||
exportEvaluation.ImageBindings,
|
||||
imageBindings,
|
||||
textures,
|
||||
pixelShaderAddress,
|
||||
exportShaderAddress,
|
||||
@@ -6191,6 +6198,7 @@ public static partial class AgcExports
|
||||
|
||||
private static bool TryAppendTranslatedImageBindings(
|
||||
IReadOnlyList<Gen5ImageBinding> bindings,
|
||||
IReadOnlyList<Gen5ImageBinding> stageBindings,
|
||||
List<TranslatedImageBinding> textures,
|
||||
ulong pixelShaderAddress,
|
||||
ulong exportShaderAddress,
|
||||
@@ -6215,8 +6223,9 @@ public static partial class AgcExports
|
||||
0, 1, 1, Gen5TextureFormatR8G8B8A8Unorm, 0, 0, 0, 0, 0, 1, 0xFAC);
|
||||
}
|
||||
|
||||
var isStorage =
|
||||
Gen5ShaderTranslator.IsStorageImageOperation(binding.Opcode);
|
||||
var isStorage = Gen5ShaderTranslator.RequiresStorageImage(
|
||||
binding,
|
||||
stageBindings);
|
||||
if (_traceAgcShader || _tracePixelShaderAddress == pixelShaderAddress)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
@@ -7374,9 +7383,11 @@ public static partial class AgcExports
|
||||
/// </summary>
|
||||
private static void ReturnPooledEvaluationArrays(Gen5ShaderEvaluation evaluation)
|
||||
{
|
||||
var returned = new HashSet<byte[]>(
|
||||
System.Collections.Generic.ReferenceEqualityComparer.Instance);
|
||||
foreach (var binding in evaluation.GlobalMemoryBindings)
|
||||
{
|
||||
if (binding.DataPooled)
|
||||
if (binding.DataPooled && returned.Add(binding.Data))
|
||||
{
|
||||
VulkanVideoPresenter.GuestDataPool.Return(binding.Data);
|
||||
}
|
||||
@@ -7386,7 +7397,7 @@ public static partial class AgcExports
|
||||
{
|
||||
foreach (var binding in vertexInputs)
|
||||
{
|
||||
if (binding.DataPooled)
|
||||
if (binding.DataPooled && returned.Add(binding.Data))
|
||||
{
|
||||
VulkanVideoPresenter.GuestDataPool.Return(binding.Data);
|
||||
}
|
||||
@@ -7406,11 +7417,13 @@ public static partial class AgcExports
|
||||
bool vertex,
|
||||
bool index)
|
||||
{
|
||||
var returned = new HashSet<byte[]>(
|
||||
System.Collections.Generic.ReferenceEqualityComparer.Instance);
|
||||
if (globals)
|
||||
{
|
||||
foreach (var binding in draw.GlobalMemoryBindings)
|
||||
{
|
||||
if (binding.DataPooled)
|
||||
if (binding.DataPooled && returned.Add(binding.Data))
|
||||
{
|
||||
VulkanVideoPresenter.GuestDataPool.Return(binding.Data);
|
||||
}
|
||||
@@ -7421,14 +7434,15 @@ public static partial class AgcExports
|
||||
{
|
||||
foreach (var binding in draw.VertexInputs)
|
||||
{
|
||||
if (binding.DataPooled)
|
||||
if (binding.DataPooled && returned.Add(binding.Data))
|
||||
{
|
||||
VulkanVideoPresenter.GuestDataPool.Return(binding.Data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (index && draw.IndexBuffer is { Pooled: true } indexBuffer)
|
||||
if (index && draw.IndexBuffer is { Pooled: true } indexBuffer &&
|
||||
returned.Add(indexBuffer.Data))
|
||||
{
|
||||
VulkanVideoPresenter.GuestDataPool.Return(indexBuffer.Data);
|
||||
}
|
||||
@@ -8522,7 +8536,8 @@ public static partial class AgcExports
|
||||
var hasStorageBinding = false;
|
||||
foreach (var binding in bindings)
|
||||
{
|
||||
var isStorage = Gen5ShaderTranslator.IsStorageImageOperation(binding.Opcode);
|
||||
var isStorage = Gen5ShaderTranslator.RequiresStorageImage(binding, bindings);
|
||||
var writesStorage = Gen5ShaderTranslator.IsStorageImageOperation(binding.Opcode);
|
||||
var descriptorValid = TryDecodeTextureDescriptor(binding.ResourceDescriptor, out var texture);
|
||||
if (!descriptorValid)
|
||||
{
|
||||
@@ -8543,7 +8558,7 @@ public static partial class AgcExports
|
||||
$"0x{texture.Address:X16}:{texture.Width}x{texture.Height}:" +
|
||||
$"fmt{texture.Format}/num{texture.NumberType}/tile{texture.TileMode}" +
|
||||
$"{descriptorState}/{ProbeTexture(ctx, texture)}");
|
||||
if (isStorage && descriptorValid && texture.Address != 0)
|
||||
if (writesStorage && descriptorValid && texture.Address != 0)
|
||||
{
|
||||
gpuState.ComputeImageWriters[texture.Address] = new ComputeImageWriter(
|
||||
sequence,
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System.Buffers;
|
||||
using System.Numerics;
|
||||
|
||||
namespace SharpEmu.Libs.VideoOut;
|
||||
|
||||
internal sealed class BoundedByteArrayPool : ArrayPool<byte>
|
||||
{
|
||||
private readonly object _gate = new();
|
||||
private readonly int _maxArrayLength;
|
||||
private readonly ulong _maxCachedBytes;
|
||||
private readonly int _maxArraysPerBucket;
|
||||
private readonly Dictionary<int, Stack<byte[]>> _cachedByBucket = [];
|
||||
private readonly HashSet<byte[]> _leases =
|
||||
new(System.Collections.Generic.ReferenceEqualityComparer.Instance);
|
||||
private ulong _cachedBytes;
|
||||
|
||||
public BoundedByteArrayPool(
|
||||
int maxArrayLength,
|
||||
ulong maxCachedBytes,
|
||||
int maxArraysPerBucket)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(maxArrayLength);
|
||||
ArgumentOutOfRangeException.ThrowIfZero(maxCachedBytes);
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(maxArraysPerBucket);
|
||||
_maxArrayLength = maxArrayLength;
|
||||
_maxCachedBytes = maxCachedBytes;
|
||||
_maxArraysPerBucket = maxArraysPerBucket;
|
||||
}
|
||||
|
||||
public override byte[] Rent(int minimumLength)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(minimumLength);
|
||||
var length = GetAllocationLength(minimumLength);
|
||||
byte[]? array = null;
|
||||
lock (_gate)
|
||||
{
|
||||
if (length <= _maxArrayLength &&
|
||||
_cachedByBucket.TryGetValue(length, out var bucket) &&
|
||||
bucket.TryPop(out array))
|
||||
{
|
||||
_cachedBytes -= (ulong)array.LongLength;
|
||||
}
|
||||
|
||||
array ??= new byte[length];
|
||||
_leases.Add(array);
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
public override void Return(byte[] array, bool clearArray = false)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(array);
|
||||
lock (_gate)
|
||||
{
|
||||
if (!_leases.Remove(array))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (clearArray)
|
||||
{
|
||||
Array.Clear(array);
|
||||
}
|
||||
|
||||
lock (_gate)
|
||||
{
|
||||
if (array.Length > _maxArrayLength ||
|
||||
!IsBucketLength(array.Length) ||
|
||||
(ulong)array.LongLength > _maxCachedBytes -
|
||||
Math.Min(_cachedBytes, _maxCachedBytes))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_cachedByBucket.TryGetValue(array.Length, out var bucket))
|
||||
{
|
||||
bucket = new Stack<byte[]>();
|
||||
_cachedByBucket.Add(array.Length, bucket);
|
||||
}
|
||||
|
||||
if (bucket.Count >= _maxArraysPerBucket)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bucket.Push(array);
|
||||
_cachedBytes += (ulong)array.LongLength;
|
||||
}
|
||||
}
|
||||
|
||||
public void Trim()
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
_cachedByBucket.Clear();
|
||||
_cachedBytes = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private int GetAllocationLength(int minimumLength)
|
||||
{
|
||||
if (minimumLength <= 16)
|
||||
{
|
||||
return 16;
|
||||
}
|
||||
|
||||
if (minimumLength > _maxArrayLength)
|
||||
{
|
||||
return minimumLength;
|
||||
}
|
||||
|
||||
return checked((int)BitOperations.RoundUpToPowerOf2((uint)minimumLength));
|
||||
}
|
||||
|
||||
private static bool IsBucketLength(int length) =>
|
||||
length >= 16 && (length & (length - 1)) == 0;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.Libs.Gpu;
|
||||
|
||||
namespace SharpEmu.Libs.VideoOut;
|
||||
|
||||
internal static class GuestBlendStateNormalizer
|
||||
{
|
||||
public static GuestBlendState[] NormalizeIntegerAttachments(
|
||||
IReadOnlyList<GuestBlendState> blends,
|
||||
IReadOnlyList<bool> integerAttachments,
|
||||
out int normalizedCount)
|
||||
{
|
||||
if (blends.Count != integerAttachments.Count)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"color attachment and blend-state counts must match",
|
||||
nameof(integerAttachments));
|
||||
}
|
||||
|
||||
var normalized = new GuestBlendState[blends.Count];
|
||||
normalizedCount = 0;
|
||||
for (var index = 0; index < blends.Count; index++)
|
||||
{
|
||||
var blend = blends[index];
|
||||
if (integerAttachments[index] && blend.Enable)
|
||||
{
|
||||
blend = blend with { Enable = false };
|
||||
normalizedCount++;
|
||||
}
|
||||
|
||||
normalized[index] = blend;
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.Libs.Gpu;
|
||||
|
||||
namespace SharpEmu.Libs.VideoOut;
|
||||
|
||||
internal enum GuestDepthExtentResolutionKind
|
||||
{
|
||||
Exact,
|
||||
TextureAlias,
|
||||
StaleOneByOne,
|
||||
Mismatch,
|
||||
}
|
||||
|
||||
internal readonly record struct GuestDepthExtentResolution(
|
||||
GuestDepthExtentResolutionKind Kind,
|
||||
uint Width,
|
||||
uint Height)
|
||||
{
|
||||
public bool IsUsable => Kind != GuestDepthExtentResolutionKind.Mismatch;
|
||||
}
|
||||
|
||||
internal static class GuestDepthExtentResolver
|
||||
{
|
||||
public static GuestDepthExtentResolution Resolve(
|
||||
GuestDepthTarget depth,
|
||||
uint colorWidth,
|
||||
uint colorHeight,
|
||||
IReadOnlyList<GuestDrawTexture> textures)
|
||||
{
|
||||
if (depth.Width >= colorWidth && depth.Height >= colorHeight)
|
||||
{
|
||||
return new GuestDepthExtentResolution(
|
||||
GuestDepthExtentResolutionKind.Exact,
|
||||
depth.Width,
|
||||
depth.Height);
|
||||
}
|
||||
|
||||
var matchingTexture = textures.FirstOrDefault(texture =>
|
||||
(texture.Address == depth.Address ||
|
||||
texture.Address == depth.ReadAddress ||
|
||||
texture.Address == depth.WriteAddress) &&
|
||||
texture.Width >= colorWidth &&
|
||||
texture.Height >= colorHeight);
|
||||
if (matchingTexture is not null)
|
||||
{
|
||||
return new GuestDepthExtentResolution(
|
||||
GuestDepthExtentResolutionKind.TextureAlias,
|
||||
matchingTexture.Width,
|
||||
matchingTexture.Height);
|
||||
}
|
||||
|
||||
if (depth.Width == 1 && depth.Height == 1)
|
||||
{
|
||||
return new GuestDepthExtentResolution(
|
||||
GuestDepthExtentResolutionKind.StaleOneByOne,
|
||||
colorWidth,
|
||||
colorHeight);
|
||||
}
|
||||
|
||||
return new GuestDepthExtentResolution(
|
||||
GuestDepthExtentResolutionKind.Mismatch,
|
||||
depth.Width,
|
||||
depth.Height);
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ using Silk.NET.Vulkan;
|
||||
using Silk.NET.Vulkan.Extensions.KHR;
|
||||
using Silk.NET.Vulkan.Extensions.EXT;
|
||||
using Silk.NET.Windowing;
|
||||
using System.Buffers.Binary;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
@@ -104,6 +105,233 @@ internal static unsafe class VulkanVideoPresenter
|
||||
// always takes its dimensions from the native child control instead.
|
||||
private const uint DefaultWindowWidth = 1920;
|
||||
private const uint DefaultWindowHeight = 1080;
|
||||
|
||||
internal enum StorageImageComponentKind
|
||||
{
|
||||
Float,
|
||||
Sint,
|
||||
Uint,
|
||||
}
|
||||
|
||||
internal readonly record struct SpirvStorageImageContract(
|
||||
SpirvImageFormat Format,
|
||||
StorageImageComponentKind ComponentKind);
|
||||
|
||||
internal static bool TryReadSpirvStorageImageContracts(
|
||||
ReadOnlySpan<byte> spirv,
|
||||
out SpirvStorageImageContract[] contracts,
|
||||
out string error)
|
||||
{
|
||||
contracts = [];
|
||||
error = string.Empty;
|
||||
if (spirv.Length < 5 * sizeof(uint) ||
|
||||
BinaryPrimitives.ReadUInt32LittleEndian(spirv) != 0x07230203u)
|
||||
{
|
||||
error = "invalid-spirv-header";
|
||||
return false;
|
||||
}
|
||||
|
||||
var componentTypes = new Dictionary<uint, StorageImageComponentKind>();
|
||||
var storageImageTypes = new Dictionary<uint, SpirvStorageImageContract>();
|
||||
var uniformConstantPointers = new Dictionary<uint, uint>();
|
||||
var uniformConstantVariables = new List<(uint Id, uint PointerType)>();
|
||||
var bindings = new Dictionary<uint, uint>();
|
||||
for (var offset = 5 * sizeof(uint); offset < spirv.Length;)
|
||||
{
|
||||
var instruction = BinaryPrimitives.ReadUInt32LittleEndian(
|
||||
spirv.Slice(offset, sizeof(uint)));
|
||||
var wordCount = checked((int)(instruction >> 16));
|
||||
var byteCount = checked(wordCount * sizeof(uint));
|
||||
if (wordCount == 0 || offset + byteCount > spirv.Length)
|
||||
{
|
||||
error = "invalid-spirv-instruction-size";
|
||||
return false;
|
||||
}
|
||||
|
||||
switch ((SpirvOp)(instruction & 0xFFFFu))
|
||||
{
|
||||
case SpirvOp.TypeInt when wordCount >= 4:
|
||||
componentTypes[ReadSpirvWord(spirv, offset, 1)] =
|
||||
ReadSpirvWord(spirv, offset, 3) != 0
|
||||
? StorageImageComponentKind.Sint
|
||||
: StorageImageComponentKind.Uint;
|
||||
break;
|
||||
case SpirvOp.TypeFloat when wordCount >= 3:
|
||||
componentTypes[ReadSpirvWord(spirv, offset, 1)] =
|
||||
StorageImageComponentKind.Float;
|
||||
break;
|
||||
case SpirvOp.TypeImage when wordCount >= 9 &&
|
||||
ReadSpirvWord(spirv, offset, 7) == 2:
|
||||
var imageType = ReadSpirvWord(spirv, offset, 1);
|
||||
var componentType = ReadSpirvWord(spirv, offset, 2);
|
||||
if (!componentTypes.TryGetValue(componentType, out var componentKind))
|
||||
{
|
||||
error = $"unknown-storage-component-type({componentType})";
|
||||
return false;
|
||||
}
|
||||
|
||||
storageImageTypes[imageType] = new SpirvStorageImageContract(
|
||||
(SpirvImageFormat)ReadSpirvWord(spirv, offset, 8),
|
||||
componentKind);
|
||||
break;
|
||||
case SpirvOp.TypePointer when wordCount >= 4 &&
|
||||
ReadSpirvWord(spirv, offset, 2) ==
|
||||
(uint)SpirvStorageClass.UniformConstant:
|
||||
uniformConstantPointers[ReadSpirvWord(spirv, offset, 1)] =
|
||||
ReadSpirvWord(spirv, offset, 3);
|
||||
break;
|
||||
case SpirvOp.Variable when wordCount >= 4 &&
|
||||
ReadSpirvWord(spirv, offset, 3) ==
|
||||
(uint)SpirvStorageClass.UniformConstant:
|
||||
uniformConstantVariables.Add((
|
||||
ReadSpirvWord(spirv, offset, 2),
|
||||
ReadSpirvWord(spirv, offset, 1)));
|
||||
break;
|
||||
case SpirvOp.Decorate when wordCount >= 4 &&
|
||||
ReadSpirvWord(spirv, offset, 2) ==
|
||||
(uint)SpirvDecoration.Binding:
|
||||
bindings[ReadSpirvWord(spirv, offset, 1)] =
|
||||
ReadSpirvWord(spirv, offset, 3);
|
||||
break;
|
||||
}
|
||||
|
||||
offset += byteCount;
|
||||
}
|
||||
|
||||
var result = new List<(uint Binding, SpirvStorageImageContract Contract)>();
|
||||
foreach (var variable in uniformConstantVariables)
|
||||
{
|
||||
if (!uniformConstantPointers.TryGetValue(variable.PointerType, out var imageType) ||
|
||||
!storageImageTypes.TryGetValue(imageType, out var contract))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!bindings.TryGetValue(variable.Id, out var binding) ||
|
||||
result.Any(entry => entry.Binding == binding))
|
||||
{
|
||||
error = $"invalid-storage-image-binding({variable.Id})";
|
||||
return false;
|
||||
}
|
||||
|
||||
result.Add((binding, contract));
|
||||
}
|
||||
|
||||
contracts = result
|
||||
.OrderBy(static entry => entry.Binding)
|
||||
.Select(static entry => entry.Contract)
|
||||
.ToArray();
|
||||
return true;
|
||||
}
|
||||
|
||||
private static uint ReadSpirvWord(
|
||||
ReadOnlySpan<byte> spirv,
|
||||
int instructionOffset,
|
||||
int wordIndex) =>
|
||||
BinaryPrimitives.ReadUInt32LittleEndian(
|
||||
spirv.Slice(
|
||||
instructionOffset + wordIndex * sizeof(uint),
|
||||
sizeof(uint)));
|
||||
|
||||
internal static bool TryValidateStorageImageContract(
|
||||
SpirvStorageImageContract shaderContract,
|
||||
uint guestFormat,
|
||||
uint guestNumberType,
|
||||
bool supportsStorage,
|
||||
out Format vulkanFormat,
|
||||
out string error)
|
||||
{
|
||||
vulkanFormat = Presenter.GetStorageImageFormat(
|
||||
Presenter.GetTextureFormat(guestFormat, guestNumberType));
|
||||
var guestComponentKind = guestNumberType switch
|
||||
{
|
||||
4 => StorageImageComponentKind.Uint,
|
||||
5 => StorageImageComponentKind.Sint,
|
||||
_ => StorageImageComponentKind.Float,
|
||||
};
|
||||
if (shaderContract.ComponentKind != guestComponentKind)
|
||||
{
|
||||
error = $"component-kind-mismatch(spirv={shaderContract.ComponentKind}," +
|
||||
$"guest={guestComponentKind})";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (shaderContract.Format != SpirvImageFormat.Unknown &&
|
||||
TryGetVulkanStorageImageFormat(shaderContract.Format, out var typedFormat) &&
|
||||
typedFormat != vulkanFormat)
|
||||
{
|
||||
error = $"typed-format-mismatch(spirv={shaderContract.Format}/{typedFormat}," +
|
||||
$"guest={guestFormat}/num={guestNumberType},vk={vulkanFormat})";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (shaderContract.Format != SpirvImageFormat.Unknown &&
|
||||
!TryGetVulkanStorageImageFormat(shaderContract.Format, out _))
|
||||
{
|
||||
error = $"unsupported-spirv-storage-format({shaderContract.Format})";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!supportsStorage)
|
||||
{
|
||||
error = $"vulkan-storage-feature-missing(vk={vulkanFormat})";
|
||||
return false;
|
||||
}
|
||||
|
||||
error = string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryGetVulkanStorageImageFormat(
|
||||
SpirvImageFormat format,
|
||||
out Format vulkanFormat)
|
||||
{
|
||||
vulkanFormat = format switch
|
||||
{
|
||||
SpirvImageFormat.Rgba32f => Format.R32G32B32A32Sfloat,
|
||||
SpirvImageFormat.Rgba16f => Format.R16G16B16A16Sfloat,
|
||||
SpirvImageFormat.R32f => Format.R32Sfloat,
|
||||
SpirvImageFormat.Rgba8 => Format.R8G8B8A8Unorm,
|
||||
SpirvImageFormat.Rgba8Snorm => Format.R8G8B8A8SNorm,
|
||||
SpirvImageFormat.Rg32f => Format.R32G32Sfloat,
|
||||
SpirvImageFormat.Rg16f => Format.R16G16Sfloat,
|
||||
SpirvImageFormat.R11fG11fB10f => Format.B10G11R11UfloatPack32,
|
||||
SpirvImageFormat.R16f => Format.R16Sfloat,
|
||||
SpirvImageFormat.Rgba16 => Format.R16G16B16A16Unorm,
|
||||
SpirvImageFormat.Rgb10A2 => Format.A2B10G10R10UnormPack32,
|
||||
SpirvImageFormat.Rg16 => Format.R16G16Unorm,
|
||||
SpirvImageFormat.Rg8 => Format.R8G8Unorm,
|
||||
SpirvImageFormat.R16 => Format.R16Unorm,
|
||||
SpirvImageFormat.R8 => Format.R8Unorm,
|
||||
SpirvImageFormat.Rgba16Snorm => Format.R16G16B16A16SNorm,
|
||||
SpirvImageFormat.Rg16Snorm => Format.R16G16SNorm,
|
||||
SpirvImageFormat.Rg8Snorm => Format.R8G8SNorm,
|
||||
SpirvImageFormat.R16Snorm => Format.R16SNorm,
|
||||
SpirvImageFormat.R8Snorm => Format.R8SNorm,
|
||||
SpirvImageFormat.Rgba32i => Format.R32G32B32A32Sint,
|
||||
SpirvImageFormat.Rgba16i => Format.R16G16B16A16Sint,
|
||||
SpirvImageFormat.Rgba8i => Format.R8G8B8A8Sint,
|
||||
SpirvImageFormat.R32i => Format.R32Sint,
|
||||
SpirvImageFormat.Rg32i => Format.R32G32Sint,
|
||||
SpirvImageFormat.Rg16i => Format.R16G16Sint,
|
||||
SpirvImageFormat.Rg8i => Format.R8G8Sint,
|
||||
SpirvImageFormat.R16i => Format.R16Sint,
|
||||
SpirvImageFormat.R8i => Format.R8Sint,
|
||||
SpirvImageFormat.Rgba32ui => Format.R32G32B32A32Uint,
|
||||
SpirvImageFormat.Rgba16ui => Format.R16G16B16A16Uint,
|
||||
SpirvImageFormat.Rgba8ui => Format.R8G8B8A8Uint,
|
||||
SpirvImageFormat.R32ui => Format.R32Uint,
|
||||
SpirvImageFormat.Rgb10A2ui => Format.A2B10G10R10UintPack32,
|
||||
SpirvImageFormat.Rg32ui => Format.R32G32Uint,
|
||||
SpirvImageFormat.Rg16ui => Format.R16G16Uint,
|
||||
SpirvImageFormat.Rg8ui => Format.R8G8Uint,
|
||||
SpirvImageFormat.R16ui => Format.R16Uint,
|
||||
SpirvImageFormat.R8ui => Format.R8Uint,
|
||||
_ => Format.Undefined,
|
||||
};
|
||||
return vulkanFormat != Format.Undefined;
|
||||
}
|
||||
|
||||
// Vulkan's portable upper bound for minStorageBufferOffsetAlignment is
|
||||
// 256 bytes. Using that fixed power of two (instead of racing the render
|
||||
// thread's physical-device query) gives shader translation and descriptor
|
||||
@@ -114,10 +342,10 @@ internal static unsafe class VulkanVideoPresenter
|
||||
// trims and repartitions those large arrays aggressively under GC load,
|
||||
// causing hundreds of MiB/s of replacement byte[] allocations. Keep a
|
||||
// bounded, non-shared pool for AGC-to-presenter ownership transfers.
|
||||
internal static System.Buffers.ArrayPool<byte> GuestDataPool { get; } =
|
||||
System.Buffers.ArrayPool<byte>.Create(
|
||||
maxArrayLength: 16 * 1024 * 1024,
|
||||
maxArraysPerBucket: 96);
|
||||
internal static BoundedByteArrayPool GuestDataPool { get; } = new(
|
||||
maxArrayLength: 16 * 1024 * 1024,
|
||||
maxCachedBytes: 256UL * 1024 * 1024,
|
||||
maxArraysPerBucket: 8);
|
||||
// The pending queue and per-render drain budget bound how much guest GPU
|
||||
// work can be buffered ahead of the presenter. Draws are batched into
|
||||
// shared command buffers, so draining a large batch per render tick is
|
||||
@@ -2813,6 +3041,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
public bool InitialUploadPending;
|
||||
public bool IsCpuBacked;
|
||||
public ulong CpuContentFingerprint;
|
||||
public bool SupportsStorageUsage;
|
||||
}
|
||||
|
||||
private sealed record PendingGuestSubmission(
|
||||
@@ -5112,9 +5341,11 @@ internal static unsafe class VulkanVideoPresenter
|
||||
/// </summary>
|
||||
private static void ReturnPooledGuestData(VulkanTranslatedGuestDraw draw)
|
||||
{
|
||||
var returned = new HashSet<byte[]>(
|
||||
System.Collections.Generic.ReferenceEqualityComparer.Instance);
|
||||
foreach (var buffer in draw.GlobalMemoryBuffers)
|
||||
{
|
||||
if (buffer.Pooled)
|
||||
if (buffer.Pooled && returned.Add(buffer.Data))
|
||||
{
|
||||
GuestDataPool.Return(buffer.Data);
|
||||
}
|
||||
@@ -5122,13 +5353,14 @@ internal static unsafe class VulkanVideoPresenter
|
||||
|
||||
foreach (var buffer in draw.VertexBuffers)
|
||||
{
|
||||
if (buffer.Pooled)
|
||||
if (buffer.Pooled && returned.Add(buffer.Data))
|
||||
{
|
||||
GuestDataPool.Return(buffer.Data);
|
||||
}
|
||||
}
|
||||
|
||||
if (draw.IndexBuffer is { Pooled: true } indexBuffer)
|
||||
if (draw.IndexBuffer is { Pooled: true } indexBuffer &&
|
||||
returned.Add(indexBuffer.Data))
|
||||
{
|
||||
GuestDataPool.Return(indexBuffer.Data);
|
||||
}
|
||||
@@ -7030,9 +7262,16 @@ internal static unsafe class VulkanVideoPresenter
|
||||
}
|
||||
|
||||
var guestImage = ResolveStorageGuestImage(texture);
|
||||
var vkFormat = GetTextureFormat(texture.Format, texture.NumberType);
|
||||
var vkFormat = GetStorageImageFormat(
|
||||
GetTextureFormat(texture.Format, texture.NumberType));
|
||||
if (!SupportsStorageImage(vkFormat))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Storage image format {vkFormat} is unsupported for guest " +
|
||||
$"format={texture.Format}/num={texture.NumberType}.");
|
||||
}
|
||||
var selectedMipLevel = GetStorageMipLevel(texture);
|
||||
var view = GetOrCreateGuestImageView(
|
||||
var view = GetOrCreateGuestImageIdentityView(
|
||||
guestImage,
|
||||
vkFormat,
|
||||
selectedMipLevel,
|
||||
@@ -7109,7 +7348,14 @@ internal static unsafe class VulkanVideoPresenter
|
||||
{
|
||||
var width = Math.Max(texture.Width, 1);
|
||||
var height = Math.Max(texture.Height, 1);
|
||||
var vkFormat = GetTextureFormat(texture.Format, texture.NumberType);
|
||||
var vkFormat = GetStorageImageFormat(
|
||||
GetTextureFormat(texture.Format, texture.NumberType));
|
||||
if (!SupportsStorageImage(vkFormat))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Storage scratch format {vkFormat} is unsupported for guest " +
|
||||
$"format={texture.Format}/num={texture.NumberType}.");
|
||||
}
|
||||
var imageInfo = new ImageCreateInfo
|
||||
{
|
||||
SType = StructureType.ImageCreateInfo,
|
||||
@@ -7177,6 +7423,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
Image = image,
|
||||
Memory = memory,
|
||||
View = view,
|
||||
SupportsStorageUsage = true,
|
||||
};
|
||||
|
||||
return new TextureResource
|
||||
@@ -7203,7 +7450,8 @@ internal static unsafe class VulkanVideoPresenter
|
||||
throw new InvalidOperationException("Storage image has no guest address.");
|
||||
}
|
||||
|
||||
var format = GetTextureFormat(texture.Format, texture.NumberType);
|
||||
var format = GetStorageImageFormat(
|
||||
GetTextureFormat(texture.Format, texture.NumberType));
|
||||
var guestImage = GetOrCreateGuestImage(
|
||||
new GuestRenderTarget(
|
||||
texture.Address,
|
||||
@@ -7212,7 +7460,8 @@ internal static unsafe class VulkanVideoPresenter
|
||||
texture.Format,
|
||||
texture.NumberType,
|
||||
texture.ResourceMipLevels),
|
||||
format);
|
||||
format,
|
||||
requiresStorage: true);
|
||||
var selectedMipLevel = GetStorageMipLevel(texture);
|
||||
if (selectedMipLevel >= guestImage.MipLevels)
|
||||
{
|
||||
@@ -7284,6 +7533,8 @@ internal static unsafe class VulkanVideoPresenter
|
||||
$"{TextureDebugName(texture, vkFormat)} staging");
|
||||
|
||||
var supportsAttachmentUsage = !IsBlockCompressedFormat(vkFormat);
|
||||
var supportsStorageUsage = supportsAttachmentUsage &&
|
||||
SupportsStorageImage(vkFormat);
|
||||
var imageInfo = new ImageCreateInfo
|
||||
{
|
||||
SType = StructureType.ImageCreateInfo,
|
||||
@@ -7301,7 +7552,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
? ImageUsageFlags.TransferDstBit |
|
||||
ImageUsageFlags.SampledBit |
|
||||
ImageUsageFlags.ColorAttachmentBit |
|
||||
ImageUsageFlags.StorageBit |
|
||||
(supportsStorageUsage ? ImageUsageFlags.StorageBit : (ImageUsageFlags)0) |
|
||||
ImageUsageFlags.TransferSrcBit
|
||||
: ImageUsageFlags.TransferDstBit | ImageUsageFlags.SampledBit,
|
||||
SharingMode = SharingMode.Exclusive,
|
||||
@@ -7370,6 +7621,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
InitialUploadPending = true,
|
||||
IsCpuBacked = true,
|
||||
CpuContentFingerprint = contentFingerprint,
|
||||
SupportsStorageUsage = supportsStorageUsage,
|
||||
};
|
||||
_guestImages.Add(texture.Address, guestImage);
|
||||
resource.OwnsStorage = false;
|
||||
@@ -8802,6 +9054,12 @@ internal static unsafe class VulkanVideoPresenter
|
||||
return (properties.OptimalTilingFeatures & FormatFeatureFlags.ColorAttachmentBit) != 0;
|
||||
}
|
||||
|
||||
private bool SupportsStorageImage(Format format)
|
||||
{
|
||||
_vk.GetPhysicalDeviceFormatProperties(_physicalDevice, format, out var properties);
|
||||
return (properties.OptimalTilingFeatures & FormatFeatureFlags.StorageImageBit) != 0;
|
||||
}
|
||||
|
||||
internal static Format GetTextureFormat(uint format, uint numberType) =>
|
||||
(format, numberType) switch
|
||||
{
|
||||
@@ -8882,6 +9140,19 @@ internal static unsafe class VulkanVideoPresenter
|
||||
_ => Format.R8G8B8A8Unorm,
|
||||
};
|
||||
|
||||
internal static Format GetStorageImageFormat(Format format) =>
|
||||
format switch
|
||||
{
|
||||
Format.R8Srgb => Format.R8Unorm,
|
||||
Format.R8G8Srgb => Format.R8G8Unorm,
|
||||
Format.R8G8B8A8Srgb => Format.R8G8B8A8Unorm,
|
||||
Format.BC1RgbaSrgbBlock => Format.BC1RgbaUnormBlock,
|
||||
Format.BC2SrgbBlock => Format.BC2UnormBlock,
|
||||
Format.BC3SrgbBlock => Format.BC3UnormBlock,
|
||||
Format.BC7SrgbBlock => Format.BC7UnormBlock,
|
||||
_ => format,
|
||||
};
|
||||
|
||||
private static Format GetRenderTargetFormat(uint format, uint numberType) =>
|
||||
(format, numberType) switch
|
||||
{
|
||||
@@ -9029,6 +9300,12 @@ internal static unsafe class VulkanVideoPresenter
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryValidateStorageImageBindings(work, out validationError))
|
||||
{
|
||||
LogRejectedComputeDispatch(work, validationError);
|
||||
return;
|
||||
}
|
||||
|
||||
TranslatedDrawResources? resources = null;
|
||||
CommandBuffer commandBuffer = default;
|
||||
var submitted = false;
|
||||
@@ -9295,6 +9572,59 @@ internal static unsafe class VulkanVideoPresenter
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryValidateStorageImageBindings(
|
||||
VulkanComputeGuestDispatch work,
|
||||
out string error)
|
||||
{
|
||||
var storageTextures = work.Textures
|
||||
.Where(static texture => texture.IsStorage)
|
||||
.ToArray();
|
||||
if (storageTextures.Length == 0)
|
||||
{
|
||||
error = string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!TryReadSpirvStorageImageContracts(
|
||||
work.ComputeSpirv,
|
||||
out var shaderContracts,
|
||||
out error))
|
||||
{
|
||||
error = $"storage-contract-parse-failed({error})";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (shaderContracts.Length != storageTextures.Length)
|
||||
{
|
||||
error = $"storage-binding-count-mismatch(spirv={shaderContracts.Length}," +
|
||||
$"guest={storageTextures.Length})";
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var index = 0; index < storageTextures.Length; index++)
|
||||
{
|
||||
var texture = storageTextures[index];
|
||||
var shaderContract = shaderContracts[index];
|
||||
var vulkanFormat = GetStorageImageFormat(
|
||||
GetTextureFormat(texture.Format, texture.NumberType));
|
||||
if (!TryValidateStorageImageContract(
|
||||
shaderContract,
|
||||
texture.Format,
|
||||
texture.NumberType,
|
||||
SupportsStorageImage(vulkanFormat),
|
||||
out _,
|
||||
out var bindingError))
|
||||
{
|
||||
error = $"storage-binding[{index}]-invalid(" +
|
||||
$"addr=0x{texture.Address:X16},reason={bindingError})";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
error = string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void LogRejectedComputeDispatch(
|
||||
VulkanComputeGuestDispatch work,
|
||||
string reason)
|
||||
@@ -9776,24 +10106,23 @@ internal static unsafe class VulkanVideoPresenter
|
||||
return;
|
||||
}
|
||||
|
||||
for (var index = 0; index < targetFormats.Length; index++)
|
||||
{
|
||||
if (targetFormats[index].IsInteger &&
|
||||
work.Draw.RenderState.Blends[index].Enable)
|
||||
var normalizedBlends = GuestBlendStateNormalizer.NormalizeIntegerAttachments(
|
||||
work.Draw.RenderState.Blends,
|
||||
targetFormats.Select(static format => format.IsInteger).ToArray(),
|
||||
out var normalizedBlendCount);
|
||||
var draw = normalizedBlendCount == 0
|
||||
? work.Draw
|
||||
: work.Draw with
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
"[LOADER][WARN] Vulkan skipped MRT draw with blending enabled for an integer attachment.");
|
||||
ReturnPooledGuestData(work.Draw);
|
||||
return;
|
||||
}
|
||||
}
|
||||
RenderState = work.Draw.RenderState with { Blends = normalizedBlends },
|
||||
};
|
||||
|
||||
if (!_supportsIndependentBlend)
|
||||
{
|
||||
for (var index = 1; index < work.Draw.RenderState.Blends.Count; index++)
|
||||
for (var index = 1; index < draw.RenderState.Blends.Count; index++)
|
||||
{
|
||||
if (work.Draw.RenderState.Blends[index] !=
|
||||
work.Draw.RenderState.Blends[0])
|
||||
if (draw.RenderState.Blends[index] !=
|
||||
draw.RenderState.Blends[0])
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
"[LOADER][WARN] Vulkan skipped MRT draw requiring unsupported independentBlend.");
|
||||
@@ -9870,7 +10199,6 @@ internal static unsafe class VulkanVideoPresenter
|
||||
try
|
||||
{
|
||||
var extent = new Extent2D(firstTarget.Width, firstTarget.Height);
|
||||
var draw = work.Draw;
|
||||
var clearDepthForDraw = draw.RenderState.Depth.ClearEnable;
|
||||
if (work.DepthTarget?.ReadOnly == true && draw.RenderState.Depth.WriteEnable)
|
||||
{
|
||||
@@ -9889,48 +10217,20 @@ internal static unsafe class VulkanVideoPresenter
|
||||
draw.RenderState.Depth) &&
|
||||
work.DepthTarget is { } depthTarget)
|
||||
{
|
||||
var effectiveDepthTarget = depthTarget;
|
||||
if (depthTarget.Width < firstTarget.Width || depthTarget.Height < firstTarget.Height)
|
||||
{
|
||||
var matchingTexture = work.Draw.Textures.FirstOrDefault(texture =>
|
||||
texture.Address == depthTarget.Address &&
|
||||
texture.Width >= firstTarget.Width &&
|
||||
texture.Height >= firstTarget.Height);
|
||||
if (matchingTexture is not null)
|
||||
{
|
||||
effectiveDepthTarget = depthTarget with
|
||||
var resolution = GuestDepthExtentResolver.Resolve(
|
||||
depthTarget,
|
||||
firstTarget.Width,
|
||||
firstTarget.Height,
|
||||
draw.Textures);
|
||||
var effectiveDepthTarget = resolution.IsUsable &&
|
||||
(resolution.Width != depthTarget.Width ||
|
||||
resolution.Height != depthTarget.Height)
|
||||
? depthTarget with
|
||||
{
|
||||
Width = matchingTexture.Width,
|
||||
Height = matchingTexture.Height,
|
||||
};
|
||||
}
|
||||
else if (depthTarget.Width == 1 && depthTarget.Height == 1)
|
||||
{
|
||||
// Some Gen5 streams leave DB_DEPTH_SIZE_XY at its
|
||||
// clear-state value while binding a full-size DB
|
||||
// surface. The color attachment and viewport still
|
||||
// define the render extent; treating that stale 1x1
|
||||
// value literally discards the entire draw.
|
||||
effectiveDepthTarget = depthTarget with
|
||||
{
|
||||
Width = firstTarget.Width,
|
||||
Height = firstTarget.Height,
|
||||
};
|
||||
}
|
||||
|
||||
if (effectiveDepthTarget != depthTarget &&
|
||||
_tracedDepthExtentFallbacks.Add(
|
||||
(depthTarget.Address,
|
||||
effectiveDepthTarget.Width,
|
||||
effectiveDepthTarget.Height)))
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][WARN] Vulkan repaired stale guest depth extent " +
|
||||
$"addr=0x{depthTarget.Address:X16} " +
|
||||
$"{depthTarget.Width}x{depthTarget.Height} -> " +
|
||||
$"{effectiveDepthTarget.Width}x{effectiveDepthTarget.Height}");
|
||||
}
|
||||
}
|
||||
Width = resolution.Width,
|
||||
Height = resolution.Height,
|
||||
}
|
||||
: depthTarget;
|
||||
|
||||
depth = GetOrCreateGuestDepth(effectiveDepthTarget);
|
||||
PrepareFirstUseDepth(depth, draw.RenderState.Depth);
|
||||
@@ -10558,8 +10858,17 @@ internal static unsafe class VulkanVideoPresenter
|
||||
|
||||
private GuestImageResource GetOrCreateGuestImage(
|
||||
GuestRenderTarget target,
|
||||
Format format)
|
||||
Format format,
|
||||
bool requiresStorage = false)
|
||||
{
|
||||
var supportsStorageUsage = SupportsStorageImage(format);
|
||||
if (requiresStorage && !supportsStorageUsage)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Storage image format {format} is unsupported for guest " +
|
||||
$"address 0x{target.Address:X16}.");
|
||||
}
|
||||
|
||||
var mipLevels = ClampMipLevels(target.Width, target.Height, target.MipLevels);
|
||||
var guestFormat = GetGuestTextureFormat(target.Format, target.NumberType);
|
||||
var requestedKey = new GuestImageVariantKey(
|
||||
@@ -10577,9 +10886,15 @@ internal static unsafe class VulkanVideoPresenter
|
||||
existing.GuestFormat == guestFormat &&
|
||||
existing.Format == format)
|
||||
{
|
||||
if (requiresStorage && !existing.SupportsStorageUsage)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Guest image 0x{target.Address:X16} was created without storage usage.");
|
||||
}
|
||||
|
||||
existing.IsCpuBacked = false;
|
||||
existing.CpuContentFingerprint = 0;
|
||||
if (existing.RenderPass.Handle == 0)
|
||||
if (existing.RenderPass.Handle == 0 && !requiresStorage)
|
||||
{
|
||||
var attachmentView = existing.MipViews.Length > 0
|
||||
? existing.MipViews[0]
|
||||
@@ -10639,6 +10954,12 @@ internal static unsafe class VulkanVideoPresenter
|
||||
|
||||
if (_guestImageVariants.Remove(requestedKey, out var retained))
|
||||
{
|
||||
if (requiresStorage && !retained.SupportsStorageUsage)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Retained guest image 0x{target.Address:X16} was created without storage usage.");
|
||||
}
|
||||
|
||||
retained.IsCpuBacked = false;
|
||||
retained.CpuContentFingerprint = 0;
|
||||
_guestImages.Add(target.Address, retained);
|
||||
@@ -10686,7 +11007,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
Usage =
|
||||
ImageUsageFlags.ColorAttachmentBit |
|
||||
ImageUsageFlags.SampledBit |
|
||||
ImageUsageFlags.StorageBit |
|
||||
(supportsStorageUsage ? ImageUsageFlags.StorageBit : (ImageUsageFlags)0) |
|
||||
ImageUsageFlags.TransferSrcBit |
|
||||
ImageUsageFlags.TransferDstBit,
|
||||
SharingMode = SharingMode.Exclusive,
|
||||
@@ -10742,12 +11063,18 @@ internal static unsafe class VulkanVideoPresenter
|
||||
mipViews[mipLevel] = mipView;
|
||||
}
|
||||
|
||||
var (renderPass, initialRenderPass, framebuffer) =
|
||||
CreateRenderPassAndFramebuffer(
|
||||
format,
|
||||
mipViews[0],
|
||||
target.Width,
|
||||
target.Height);
|
||||
RenderPass renderPass = default;
|
||||
RenderPass initialRenderPass = default;
|
||||
Framebuffer framebuffer = default;
|
||||
if (!requiresStorage)
|
||||
{
|
||||
(renderPass, initialRenderPass, framebuffer) =
|
||||
CreateRenderPassAndFramebuffer(
|
||||
format,
|
||||
mipViews[0],
|
||||
target.Width,
|
||||
target.Height);
|
||||
}
|
||||
|
||||
var resource = new GuestImageResource
|
||||
{
|
||||
@@ -10764,6 +11091,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
RenderPass = renderPass,
|
||||
InitialRenderPass = initialRenderPass,
|
||||
Framebuffer = framebuffer,
|
||||
SupportsStorageUsage = supportsStorageUsage,
|
||||
};
|
||||
var debugName = GuestImageDebugName(target, format);
|
||||
SetDebugName(ObjectType.Image, image.Handle, $"{debugName} image");
|
||||
@@ -10775,9 +11103,12 @@ internal static unsafe class VulkanVideoPresenter
|
||||
mipViews[mipLevel].Handle,
|
||||
$"{debugName} mip{mipLevel}");
|
||||
}
|
||||
SetDebugName(ObjectType.RenderPass, renderPass.Handle, $"{debugName} renderpass");
|
||||
SetDebugName(ObjectType.RenderPass, initialRenderPass.Handle, $"{debugName} initial-renderpass");
|
||||
SetDebugName(ObjectType.Framebuffer, framebuffer.Handle, $"{debugName} framebuffer");
|
||||
if (renderPass.Handle != 0)
|
||||
{
|
||||
SetDebugName(ObjectType.RenderPass, renderPass.Handle, $"{debugName} renderpass");
|
||||
SetDebugName(ObjectType.RenderPass, initialRenderPass.Handle, $"{debugName} initial-renderpass");
|
||||
SetDebugName(ObjectType.Framebuffer, framebuffer.Handle, $"{debugName} framebuffer");
|
||||
}
|
||||
_guestImages.Add(target.Address, resource);
|
||||
lock (_gate)
|
||||
{
|
||||
@@ -11476,6 +11807,18 @@ internal static unsafe class VulkanVideoPresenter
|
||||
return view;
|
||||
}
|
||||
|
||||
private ImageView GetOrCreateGuestImageIdentityView(
|
||||
GuestImageResource resource,
|
||||
Format format,
|
||||
uint mipLevel,
|
||||
uint levelCount) =>
|
||||
GetOrCreateGuestImageView(
|
||||
resource,
|
||||
format,
|
||||
mipLevel,
|
||||
levelCount,
|
||||
dstSelect: 0xFAC);
|
||||
|
||||
internal static bool IsCompatibleViewFormat(Format imageFormat, Format viewFormat)
|
||||
{
|
||||
if (imageFormat == viewFormat)
|
||||
@@ -14435,6 +14778,10 @@ internal static unsafe class VulkanVideoPresenter
|
||||
}
|
||||
}
|
||||
_descriptorLayouts.Clear();
|
||||
while (_recycledDescriptorPools.TryPop(out var recycledDescriptorPool))
|
||||
{
|
||||
_vk.DestroyDescriptorPool(_device, recycledDescriptorPool, null);
|
||||
}
|
||||
foreach (var sampler in _samplers.Values)
|
||||
{
|
||||
_vk.DestroySampler(_device, sampler, null);
|
||||
@@ -14499,6 +14846,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
_vk.DestroyInstance(_instance, null);
|
||||
_instance = default;
|
||||
}
|
||||
GuestDataPool.Trim();
|
||||
}
|
||||
|
||||
private void RecreateSwapchainResources(string operation, Result result)
|
||||
|
||||
@@ -162,6 +162,11 @@ public static partial class Gen5SpirvTranslator
|
||||
return context.TryCompile(out shader, out error);
|
||||
}
|
||||
|
||||
internal static SpirvImageFormat DecodeStorageImageFormat(
|
||||
uint dataFormat,
|
||||
uint numberType) =>
|
||||
CompilationContext.DecodeStorageImageFormat(dataFormat, numberType);
|
||||
|
||||
private sealed partial class CompilationContext
|
||||
{
|
||||
private const uint ImageDescriptorDwords = 8;
|
||||
@@ -279,6 +284,7 @@ public static partial class Gen5SpirvTranslator
|
||||
private uint _workGroupIdInput;
|
||||
private uint _computeDispatchLimit;
|
||||
private uint _pushConstantUintPointer;
|
||||
private uint _subgroupSizeInput;
|
||||
private uint _subgroupInvocationIdInput;
|
||||
private uint _waveMaskScratch;
|
||||
private uint _waveMaskScratchElementPointer;
|
||||
@@ -961,8 +967,9 @@ public static partial class Gen5SpirvTranslator
|
||||
{
|
||||
var binding = _evaluation.ImageBindings[index];
|
||||
_imageBindingByPc.TryAdd(binding.Pc, index);
|
||||
var isStorage =
|
||||
Gen5ShaderTranslator.IsStorageImageOperation(binding.Opcode);
|
||||
var isStorage = Gen5ShaderTranslator.RequiresStorageImage(
|
||||
binding,
|
||||
_evaluation.ImageBindings);
|
||||
var (format, componentKind) =
|
||||
DecodeImageFormat(binding.ResourceDescriptor);
|
||||
var componentType = componentKind switch
|
||||
@@ -1044,58 +1051,62 @@ public static partial class Gen5SpirvTranslator
|
||||
return (SpirvImageFormat.Unknown, ImageComponentKind.Float);
|
||||
}
|
||||
|
||||
return (dataFormat, numberType) switch
|
||||
var kind = numberType switch
|
||||
{
|
||||
(1, _) => (SpirvImageFormat.R8, ImageComponentKind.Float),
|
||||
(2, _) => (SpirvImageFormat.R16f, ImageComponentKind.Float),
|
||||
(3, _) => (SpirvImageFormat.Rg8, ImageComponentKind.Float),
|
||||
(4, 4) => (SpirvImageFormat.R32ui, ImageComponentKind.Uint),
|
||||
(4, 5) => (SpirvImageFormat.R32i, ImageComponentKind.Sint),
|
||||
(4, _) => (SpirvImageFormat.R32f, ImageComponentKind.Float),
|
||||
(5, 4) => (SpirvImageFormat.Rg16ui, ImageComponentKind.Uint),
|
||||
(5, 5) => (SpirvImageFormat.Rg16i, ImageComponentKind.Sint),
|
||||
(5, 0) => (SpirvImageFormat.Rg16, ImageComponentKind.Float),
|
||||
(5, _) => (SpirvImageFormat.Rg16f, ImageComponentKind.Float),
|
||||
(6 or 7, _) => (
|
||||
SpirvImageFormat.R11fG11fB10f,
|
||||
ImageComponentKind.Float),
|
||||
(9, 4) => (SpirvImageFormat.Rgb10A2ui, ImageComponentKind.Uint),
|
||||
(9, _) => (SpirvImageFormat.Rgb10A2, ImageComponentKind.Float),
|
||||
(10, 4) => (SpirvImageFormat.Rgba8ui, ImageComponentKind.Uint),
|
||||
(10, 5) => (SpirvImageFormat.Rgba8i, ImageComponentKind.Sint),
|
||||
(10, _) => (SpirvImageFormat.Rgba8, ImageComponentKind.Float),
|
||||
(11, 4) => (SpirvImageFormat.Rg32ui, ImageComponentKind.Uint),
|
||||
(11, 5) => (SpirvImageFormat.Rg32i, ImageComponentKind.Sint),
|
||||
(11, _) => (SpirvImageFormat.Rg32f, ImageComponentKind.Float),
|
||||
(12, 4) => (SpirvImageFormat.Rgba16ui, ImageComponentKind.Uint),
|
||||
(12, 5) => (SpirvImageFormat.Rgba16i, ImageComponentKind.Sint),
|
||||
(12, 0) => (SpirvImageFormat.Rgba16, ImageComponentKind.Float),
|
||||
(12, _) => (SpirvImageFormat.Rgba16f, ImageComponentKind.Float),
|
||||
(13 or 14, 4) => (
|
||||
SpirvImageFormat.Rgba32ui,
|
||||
ImageComponentKind.Uint),
|
||||
(13 or 14, 5) => (
|
||||
SpirvImageFormat.Rgba32i,
|
||||
ImageComponentKind.Sint),
|
||||
(13 or 14, _) => (
|
||||
SpirvImageFormat.Rgba32f,
|
||||
ImageComponentKind.Float),
|
||||
(20, _) => (SpirvImageFormat.R32ui, ImageComponentKind.Uint),
|
||||
(22, _) => (SpirvImageFormat.Rgba16f, ImageComponentKind.Float),
|
||||
(29, _) => (SpirvImageFormat.R32f, ImageComponentKind.Float),
|
||||
(36, _) => (SpirvImageFormat.R8, ImageComponentKind.Float),
|
||||
(49, _) => (SpirvImageFormat.R8ui, ImageComponentKind.Uint),
|
||||
(56 or 62 or 64, _) => (
|
||||
SpirvImageFormat.Rgba8,
|
||||
ImageComponentKind.Float),
|
||||
(71, _) => (SpirvImageFormat.Rgba16f, ImageComponentKind.Float),
|
||||
(75, _) => (SpirvImageFormat.Rg32f, ImageComponentKind.Float),
|
||||
(_, 4) => (SpirvImageFormat.Unknown, ImageComponentKind.Uint),
|
||||
(_, 5) => (SpirvImageFormat.Unknown, ImageComponentKind.Sint),
|
||||
_ => (SpirvImageFormat.Unknown, ImageComponentKind.Float),
|
||||
4 => ImageComponentKind.Uint,
|
||||
5 => ImageComponentKind.Sint,
|
||||
_ => ImageComponentKind.Float,
|
||||
};
|
||||
return (DecodeStorageImageFormat(dataFormat, numberType), kind);
|
||||
}
|
||||
|
||||
internal static SpirvImageFormat DecodeStorageImageFormat(
|
||||
uint dataFormat,
|
||||
uint numberType) =>
|
||||
(dataFormat, numberType) switch
|
||||
{
|
||||
(1, 0 or 9) => SpirvImageFormat.R8,
|
||||
(1, 1) => SpirvImageFormat.R8Snorm,
|
||||
(1, 4) => SpirvImageFormat.R8ui,
|
||||
(1, 5) => SpirvImageFormat.R8i,
|
||||
(2, 0) => SpirvImageFormat.R16,
|
||||
(2, 1) => SpirvImageFormat.R16Snorm,
|
||||
(2, 4) => SpirvImageFormat.R16ui,
|
||||
(2, 5) => SpirvImageFormat.R16i,
|
||||
(2, 7) => SpirvImageFormat.R16f,
|
||||
(3, 0 or 9) => SpirvImageFormat.Rg8,
|
||||
(3, 1) => SpirvImageFormat.Rg8Snorm,
|
||||
(3, 4) => SpirvImageFormat.Rg8ui,
|
||||
(3, 5) => SpirvImageFormat.Rg8i,
|
||||
(4, 4) => SpirvImageFormat.R32ui,
|
||||
(4, 5) => SpirvImageFormat.R32i,
|
||||
(4, 7) => SpirvImageFormat.R32f,
|
||||
(5, 0) => SpirvImageFormat.Rg16,
|
||||
(5, 1) => SpirvImageFormat.Rg16Snorm,
|
||||
(5, 4) => SpirvImageFormat.Rg16ui,
|
||||
(5, 5) => SpirvImageFormat.Rg16i,
|
||||
(5, 7) => SpirvImageFormat.Rg16f,
|
||||
(6 or 7, 7) => SpirvImageFormat.R11fG11fB10f,
|
||||
(8 or 9, 0) => SpirvImageFormat.Rgb10A2,
|
||||
(8 or 9, 4) => SpirvImageFormat.Rgb10A2ui,
|
||||
(10, 0 or 9) => SpirvImageFormat.Rgba8,
|
||||
(10, 1) => SpirvImageFormat.Rgba8Snorm,
|
||||
(10, 4) => SpirvImageFormat.Rgba8ui,
|
||||
(10, 5) => SpirvImageFormat.Rgba8i,
|
||||
(11, 4) => SpirvImageFormat.Rg32ui,
|
||||
(11, 5) => SpirvImageFormat.Rg32i,
|
||||
(11, 7) => SpirvImageFormat.Rg32f,
|
||||
(12, 0) => SpirvImageFormat.Rgba16,
|
||||
(12, 1) => SpirvImageFormat.Rgba16Snorm,
|
||||
(12, 4) => SpirvImageFormat.Rgba16ui,
|
||||
(12, 5) => SpirvImageFormat.Rgba16i,
|
||||
(12, 7) => SpirvImageFormat.Rgba16f,
|
||||
(13 or 14, 4) => SpirvImageFormat.Rgba32ui,
|
||||
(13 or 14, 5) => SpirvImageFormat.Rgba32i,
|
||||
(13 or 14, 7) => SpirvImageFormat.Rgba32f,
|
||||
_ => SpirvImageFormat.Unknown,
|
||||
};
|
||||
|
||||
private void DeclareStageInterface()
|
||||
{
|
||||
if (UsesSubgroupOperations())
|
||||
@@ -1111,6 +1122,18 @@ public static partial class Gen5SpirvTranslator
|
||||
(uint)SpirvBuiltIn.SubgroupLocalInvocationId);
|
||||
_interfaces.Add(_subgroupInvocationIdInput);
|
||||
|
||||
if (_emulateWave64)
|
||||
{
|
||||
_subgroupSizeInput = _module.AddGlobalVariable(
|
||||
subgroupPointer,
|
||||
SpirvStorageClass.Input);
|
||||
_module.AddDecoration(
|
||||
_subgroupSizeInput,
|
||||
SpirvDecoration.BuiltIn,
|
||||
(uint)SpirvBuiltIn.SubgroupSize);
|
||||
_interfaces.Add(_subgroupSizeInput);
|
||||
}
|
||||
|
||||
if (_waveLaneCount == 64)
|
||||
{
|
||||
_localInvocationIndexInput = _module.AddGlobalVariable(
|
||||
@@ -3091,12 +3114,14 @@ public static partial class Gen5SpirvTranslator
|
||||
return true;
|
||||
}
|
||||
|
||||
var imageLoad = Gen5ShaderTranslator.IsImageLoadOperation(instruction.Opcode);
|
||||
var storage = Gen5ShaderTranslator.IsStorageImageOperation(instruction.Opcode);
|
||||
for (var index = 0; index < _evaluation.ImageBindings.Count; index++)
|
||||
{
|
||||
var candidate = _evaluation.ImageBindings[index];
|
||||
if (candidate.Control.ScalarResource != control.ScalarResource ||
|
||||
candidate.Control.ScalarSampler != control.ScalarSampler ||
|
||||
Gen5ShaderTranslator.IsImageLoadOperation(candidate.Opcode) != imageLoad ||
|
||||
Gen5ShaderTranslator.IsStorageImageOperation(candidate.Opcode) != storage ||
|
||||
!HasSameScalarDefinitions(
|
||||
candidate.Pc,
|
||||
@@ -5097,12 +5122,14 @@ public static partial class Gen5SpirvTranslator
|
||||
SpirvOp.UConvert,
|
||||
_ulongType,
|
||||
maskedLane));
|
||||
return _module.AddInstruction(
|
||||
SpirvOp.Select,
|
||||
_ulongType,
|
||||
IsCurrentLaneInRdnaWave(),
|
||||
shifted,
|
||||
_module.Constant64(_ulongType, 0));
|
||||
return _emulateWave64
|
||||
? shifted
|
||||
: _module.AddInstruction(
|
||||
SpirvOp.Select,
|
||||
_ulongType,
|
||||
IsCurrentLaneInRdnaWave(),
|
||||
shifted,
|
||||
_module.Constant64(_ulongType, 0));
|
||||
}
|
||||
|
||||
private uint IsCurrentLaneInRdnaWave() =>
|
||||
@@ -5139,6 +5166,11 @@ public static partial class Gen5SpirvTranslator
|
||||
0);
|
||||
if (_emulateWave64)
|
||||
{
|
||||
var high = _module.AddInstruction(
|
||||
SpirvOp.CompositeExtract,
|
||||
_uintType,
|
||||
ballot,
|
||||
1);
|
||||
var subgroupLane =
|
||||
Load(_uintType, _subgroupInvocationIdInput);
|
||||
var firstLane = _module.AddInstruction(
|
||||
@@ -5150,6 +5182,16 @@ public static partial class Gen5SpirvTranslator
|
||||
EmitConditional(firstLane, () =>
|
||||
{
|
||||
Store(WaveMaskScratchPointer(half), low);
|
||||
|
||||
var nativeWave64 = _module.AddInstruction(
|
||||
SpirvOp.UGreaterThanEqual,
|
||||
_boolType,
|
||||
Load(_uintType, _subgroupSizeInput),
|
||||
UInt(64));
|
||||
EmitConditional(nativeWave64, () =>
|
||||
{
|
||||
Store(WaveMaskScratchPointer(UInt(1)), high);
|
||||
});
|
||||
});
|
||||
EmitWave64Barrier();
|
||||
var lowMask = Load(
|
||||
|
||||
@@ -251,6 +251,7 @@ public enum SpirvBuiltIn : uint
|
||||
LocalInvocationId = 27,
|
||||
GlobalInvocationId = 28,
|
||||
LocalInvocationIndex = 29,
|
||||
SubgroupSize = 36,
|
||||
SubgroupLocalInvocationId = 41,
|
||||
}
|
||||
|
||||
|
||||
@@ -1576,11 +1576,37 @@ public static class Gen5ShaderTranslator
|
||||
private static bool IsMimgInstruction(string name) =>
|
||||
name.StartsWith("Image", StringComparison.Ordinal);
|
||||
|
||||
public static bool IsImageLoadOperation(string name) =>
|
||||
name.StartsWith("ImageLoad", StringComparison.Ordinal);
|
||||
|
||||
public static bool IsStorageImageOperation(string name) =>
|
||||
name.StartsWith("ImageLoad", StringComparison.Ordinal) ||
|
||||
name.StartsWith("ImageStore", StringComparison.Ordinal) ||
|
||||
name.StartsWith("ImageAtomic", StringComparison.Ordinal);
|
||||
|
||||
public static bool RequiresStorageImage(
|
||||
Gen5ImageBinding binding,
|
||||
IReadOnlyList<Gen5ImageBinding> stageBindings)
|
||||
{
|
||||
if (IsStorageImageOperation(binding.Opcode))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!IsImageLoadOperation(binding.Opcode))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// IMAGE_LOAD itself is read-only and maps naturally to OpImageFetch,
|
||||
// including for block-compressed textures which Vulkan cannot expose
|
||||
// as storage images. Keep it as storage only when the same resolved
|
||||
// descriptor is also written in this shader stage, preserving coherent
|
||||
// read/write access through one storage-image representation.
|
||||
return stageBindings.Any(candidate =>
|
||||
IsStorageImageOperation(candidate.Opcode) &&
|
||||
binding.ResourceDescriptor.SequenceEqual(candidate.ResourceDescriptor));
|
||||
}
|
||||
|
||||
public static bool IsDataShareAtomic(string name) => name switch
|
||||
{
|
||||
"DsAddU32" or "DsSubU32" or "DsIncU32" or "DsDecU32" or
|
||||
|
||||
Reference in New Issue
Block a user