mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-22 19:06:15 +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(
|
textures.Add(new TranslatedImageBinding(
|
||||||
texture,
|
texture,
|
||||||
Gen5ShaderTranslator.IsStorageImageOperation(binding.Opcode),
|
Gen5ShaderTranslator.RequiresStorageImage(
|
||||||
|
binding,
|
||||||
|
exportEvaluation.ImageBindings),
|
||||||
binding.MipLevel ?? 0,
|
binding.MipLevel ?? 0,
|
||||||
binding.SamplerDescriptor));
|
binding.SamplerDescriptor));
|
||||||
}
|
}
|
||||||
@@ -6102,17 +6104,22 @@ public static partial class AgcExports
|
|||||||
_graphicsShaderCache.TryAdd(shaderKey, compiled);
|
_graphicsShaderCache.TryAdd(shaderKey, compiled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var imageBindings = pixelEvaluation.ImageBindings
|
||||||
|
.Concat(exportEvaluation.ImageBindings)
|
||||||
|
.ToArray();
|
||||||
var textures = new List<TranslatedImageBinding>(
|
var textures = new List<TranslatedImageBinding>(
|
||||||
pixelEvaluation.ImageBindings.Count +
|
pixelEvaluation.ImageBindings.Count +
|
||||||
exportEvaluation.ImageBindings.Count);
|
exportEvaluation.ImageBindings.Count);
|
||||||
if (!TryAppendTranslatedImageBindings(
|
if (!TryAppendTranslatedImageBindings(
|
||||||
pixelEvaluation.ImageBindings,
|
pixelEvaluation.ImageBindings,
|
||||||
|
imageBindings,
|
||||||
textures,
|
textures,
|
||||||
pixelShaderAddress,
|
pixelShaderAddress,
|
||||||
exportShaderAddress,
|
exportShaderAddress,
|
||||||
out error) ||
|
out error) ||
|
||||||
!TryAppendTranslatedImageBindings(
|
!TryAppendTranslatedImageBindings(
|
||||||
exportEvaluation.ImageBindings,
|
exportEvaluation.ImageBindings,
|
||||||
|
imageBindings,
|
||||||
textures,
|
textures,
|
||||||
pixelShaderAddress,
|
pixelShaderAddress,
|
||||||
exportShaderAddress,
|
exportShaderAddress,
|
||||||
@@ -6191,6 +6198,7 @@ public static partial class AgcExports
|
|||||||
|
|
||||||
private static bool TryAppendTranslatedImageBindings(
|
private static bool TryAppendTranslatedImageBindings(
|
||||||
IReadOnlyList<Gen5ImageBinding> bindings,
|
IReadOnlyList<Gen5ImageBinding> bindings,
|
||||||
|
IReadOnlyList<Gen5ImageBinding> stageBindings,
|
||||||
List<TranslatedImageBinding> textures,
|
List<TranslatedImageBinding> textures,
|
||||||
ulong pixelShaderAddress,
|
ulong pixelShaderAddress,
|
||||||
ulong exportShaderAddress,
|
ulong exportShaderAddress,
|
||||||
@@ -6215,8 +6223,9 @@ public static partial class AgcExports
|
|||||||
0, 1, 1, Gen5TextureFormatR8G8B8A8Unorm, 0, 0, 0, 0, 0, 1, 0xFAC);
|
0, 1, 1, Gen5TextureFormatR8G8B8A8Unorm, 0, 0, 0, 0, 0, 1, 0xFAC);
|
||||||
}
|
}
|
||||||
|
|
||||||
var isStorage =
|
var isStorage = Gen5ShaderTranslator.RequiresStorageImage(
|
||||||
Gen5ShaderTranslator.IsStorageImageOperation(binding.Opcode);
|
binding,
|
||||||
|
stageBindings);
|
||||||
if (_traceAgcShader || _tracePixelShaderAddress == pixelShaderAddress)
|
if (_traceAgcShader || _tracePixelShaderAddress == pixelShaderAddress)
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine(
|
Console.Error.WriteLine(
|
||||||
@@ -7374,9 +7383,11 @@ public static partial class AgcExports
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private static void ReturnPooledEvaluationArrays(Gen5ShaderEvaluation evaluation)
|
private static void ReturnPooledEvaluationArrays(Gen5ShaderEvaluation evaluation)
|
||||||
{
|
{
|
||||||
|
var returned = new HashSet<byte[]>(
|
||||||
|
System.Collections.Generic.ReferenceEqualityComparer.Instance);
|
||||||
foreach (var binding in evaluation.GlobalMemoryBindings)
|
foreach (var binding in evaluation.GlobalMemoryBindings)
|
||||||
{
|
{
|
||||||
if (binding.DataPooled)
|
if (binding.DataPooled && returned.Add(binding.Data))
|
||||||
{
|
{
|
||||||
VulkanVideoPresenter.GuestDataPool.Return(binding.Data);
|
VulkanVideoPresenter.GuestDataPool.Return(binding.Data);
|
||||||
}
|
}
|
||||||
@@ -7386,7 +7397,7 @@ public static partial class AgcExports
|
|||||||
{
|
{
|
||||||
foreach (var binding in vertexInputs)
|
foreach (var binding in vertexInputs)
|
||||||
{
|
{
|
||||||
if (binding.DataPooled)
|
if (binding.DataPooled && returned.Add(binding.Data))
|
||||||
{
|
{
|
||||||
VulkanVideoPresenter.GuestDataPool.Return(binding.Data);
|
VulkanVideoPresenter.GuestDataPool.Return(binding.Data);
|
||||||
}
|
}
|
||||||
@@ -7406,11 +7417,13 @@ public static partial class AgcExports
|
|||||||
bool vertex,
|
bool vertex,
|
||||||
bool index)
|
bool index)
|
||||||
{
|
{
|
||||||
|
var returned = new HashSet<byte[]>(
|
||||||
|
System.Collections.Generic.ReferenceEqualityComparer.Instance);
|
||||||
if (globals)
|
if (globals)
|
||||||
{
|
{
|
||||||
foreach (var binding in draw.GlobalMemoryBindings)
|
foreach (var binding in draw.GlobalMemoryBindings)
|
||||||
{
|
{
|
||||||
if (binding.DataPooled)
|
if (binding.DataPooled && returned.Add(binding.Data))
|
||||||
{
|
{
|
||||||
VulkanVideoPresenter.GuestDataPool.Return(binding.Data);
|
VulkanVideoPresenter.GuestDataPool.Return(binding.Data);
|
||||||
}
|
}
|
||||||
@@ -7421,14 +7434,15 @@ public static partial class AgcExports
|
|||||||
{
|
{
|
||||||
foreach (var binding in draw.VertexInputs)
|
foreach (var binding in draw.VertexInputs)
|
||||||
{
|
{
|
||||||
if (binding.DataPooled)
|
if (binding.DataPooled && returned.Add(binding.Data))
|
||||||
{
|
{
|
||||||
VulkanVideoPresenter.GuestDataPool.Return(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);
|
VulkanVideoPresenter.GuestDataPool.Return(indexBuffer.Data);
|
||||||
}
|
}
|
||||||
@@ -8522,7 +8536,8 @@ public static partial class AgcExports
|
|||||||
var hasStorageBinding = false;
|
var hasStorageBinding = false;
|
||||||
foreach (var binding in bindings)
|
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);
|
var descriptorValid = TryDecodeTextureDescriptor(binding.ResourceDescriptor, out var texture);
|
||||||
if (!descriptorValid)
|
if (!descriptorValid)
|
||||||
{
|
{
|
||||||
@@ -8543,7 +8558,7 @@ public static partial class AgcExports
|
|||||||
$"0x{texture.Address:X16}:{texture.Width}x{texture.Height}:" +
|
$"0x{texture.Address:X16}:{texture.Width}x{texture.Height}:" +
|
||||||
$"fmt{texture.Format}/num{texture.NumberType}/tile{texture.TileMode}" +
|
$"fmt{texture.Format}/num{texture.NumberType}/tile{texture.TileMode}" +
|
||||||
$"{descriptorState}/{ProbeTexture(ctx, texture)}");
|
$"{descriptorState}/{ProbeTexture(ctx, texture)}");
|
||||||
if (isStorage && descriptorValid && texture.Address != 0)
|
if (writesStorage && descriptorValid && texture.Address != 0)
|
||||||
{
|
{
|
||||||
gpuState.ComputeImageWriters[texture.Address] = new ComputeImageWriter(
|
gpuState.ComputeImageWriters[texture.Address] = new ComputeImageWriter(
|
||||||
sequence,
|
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.KHR;
|
||||||
using Silk.NET.Vulkan.Extensions.EXT;
|
using Silk.NET.Vulkan.Extensions.EXT;
|
||||||
using Silk.NET.Windowing;
|
using Silk.NET.Windowing;
|
||||||
|
using System.Buffers.Binary;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
@@ -104,6 +105,233 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
// always takes its dimensions from the native child control instead.
|
// always takes its dimensions from the native child control instead.
|
||||||
private const uint DefaultWindowWidth = 1920;
|
private const uint DefaultWindowWidth = 1920;
|
||||||
private const uint DefaultWindowHeight = 1080;
|
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
|
// Vulkan's portable upper bound for minStorageBufferOffsetAlignment is
|
||||||
// 256 bytes. Using that fixed power of two (instead of racing the render
|
// 256 bytes. Using that fixed power of two (instead of racing the render
|
||||||
// thread's physical-device query) gives shader translation and descriptor
|
// 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,
|
// trims and repartitions those large arrays aggressively under GC load,
|
||||||
// causing hundreds of MiB/s of replacement byte[] allocations. Keep a
|
// causing hundreds of MiB/s of replacement byte[] allocations. Keep a
|
||||||
// bounded, non-shared pool for AGC-to-presenter ownership transfers.
|
// bounded, non-shared pool for AGC-to-presenter ownership transfers.
|
||||||
internal static System.Buffers.ArrayPool<byte> GuestDataPool { get; } =
|
internal static BoundedByteArrayPool GuestDataPool { get; } = new(
|
||||||
System.Buffers.ArrayPool<byte>.Create(
|
maxArrayLength: 16 * 1024 * 1024,
|
||||||
maxArrayLength: 16 * 1024 * 1024,
|
maxCachedBytes: 256UL * 1024 * 1024,
|
||||||
maxArraysPerBucket: 96);
|
maxArraysPerBucket: 8);
|
||||||
// The pending queue and per-render drain budget bound how much guest GPU
|
// 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
|
// work can be buffered ahead of the presenter. Draws are batched into
|
||||||
// shared command buffers, so draining a large batch per render tick is
|
// 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 InitialUploadPending;
|
||||||
public bool IsCpuBacked;
|
public bool IsCpuBacked;
|
||||||
public ulong CpuContentFingerprint;
|
public ulong CpuContentFingerprint;
|
||||||
|
public bool SupportsStorageUsage;
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed record PendingGuestSubmission(
|
private sealed record PendingGuestSubmission(
|
||||||
@@ -5112,9 +5341,11 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private static void ReturnPooledGuestData(VulkanTranslatedGuestDraw draw)
|
private static void ReturnPooledGuestData(VulkanTranslatedGuestDraw draw)
|
||||||
{
|
{
|
||||||
|
var returned = new HashSet<byte[]>(
|
||||||
|
System.Collections.Generic.ReferenceEqualityComparer.Instance);
|
||||||
foreach (var buffer in draw.GlobalMemoryBuffers)
|
foreach (var buffer in draw.GlobalMemoryBuffers)
|
||||||
{
|
{
|
||||||
if (buffer.Pooled)
|
if (buffer.Pooled && returned.Add(buffer.Data))
|
||||||
{
|
{
|
||||||
GuestDataPool.Return(buffer.Data);
|
GuestDataPool.Return(buffer.Data);
|
||||||
}
|
}
|
||||||
@@ -5122,13 +5353,14 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
|
|
||||||
foreach (var buffer in draw.VertexBuffers)
|
foreach (var buffer in draw.VertexBuffers)
|
||||||
{
|
{
|
||||||
if (buffer.Pooled)
|
if (buffer.Pooled && returned.Add(buffer.Data))
|
||||||
{
|
{
|
||||||
GuestDataPool.Return(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);
|
GuestDataPool.Return(indexBuffer.Data);
|
||||||
}
|
}
|
||||||
@@ -7030,9 +7262,16 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
}
|
}
|
||||||
|
|
||||||
var guestImage = ResolveStorageGuestImage(texture);
|
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 selectedMipLevel = GetStorageMipLevel(texture);
|
||||||
var view = GetOrCreateGuestImageView(
|
var view = GetOrCreateGuestImageIdentityView(
|
||||||
guestImage,
|
guestImage,
|
||||||
vkFormat,
|
vkFormat,
|
||||||
selectedMipLevel,
|
selectedMipLevel,
|
||||||
@@ -7109,7 +7348,14 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
{
|
{
|
||||||
var width = Math.Max(texture.Width, 1);
|
var width = Math.Max(texture.Width, 1);
|
||||||
var height = Math.Max(texture.Height, 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
|
var imageInfo = new ImageCreateInfo
|
||||||
{
|
{
|
||||||
SType = StructureType.ImageCreateInfo,
|
SType = StructureType.ImageCreateInfo,
|
||||||
@@ -7177,6 +7423,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
Image = image,
|
Image = image,
|
||||||
Memory = memory,
|
Memory = memory,
|
||||||
View = view,
|
View = view,
|
||||||
|
SupportsStorageUsage = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
return new TextureResource
|
return new TextureResource
|
||||||
@@ -7203,7 +7450,8 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
throw new InvalidOperationException("Storage image has no guest address.");
|
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(
|
var guestImage = GetOrCreateGuestImage(
|
||||||
new GuestRenderTarget(
|
new GuestRenderTarget(
|
||||||
texture.Address,
|
texture.Address,
|
||||||
@@ -7212,7 +7460,8 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
texture.Format,
|
texture.Format,
|
||||||
texture.NumberType,
|
texture.NumberType,
|
||||||
texture.ResourceMipLevels),
|
texture.ResourceMipLevels),
|
||||||
format);
|
format,
|
||||||
|
requiresStorage: true);
|
||||||
var selectedMipLevel = GetStorageMipLevel(texture);
|
var selectedMipLevel = GetStorageMipLevel(texture);
|
||||||
if (selectedMipLevel >= guestImage.MipLevels)
|
if (selectedMipLevel >= guestImage.MipLevels)
|
||||||
{
|
{
|
||||||
@@ -7284,6 +7533,8 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
$"{TextureDebugName(texture, vkFormat)} staging");
|
$"{TextureDebugName(texture, vkFormat)} staging");
|
||||||
|
|
||||||
var supportsAttachmentUsage = !IsBlockCompressedFormat(vkFormat);
|
var supportsAttachmentUsage = !IsBlockCompressedFormat(vkFormat);
|
||||||
|
var supportsStorageUsage = supportsAttachmentUsage &&
|
||||||
|
SupportsStorageImage(vkFormat);
|
||||||
var imageInfo = new ImageCreateInfo
|
var imageInfo = new ImageCreateInfo
|
||||||
{
|
{
|
||||||
SType = StructureType.ImageCreateInfo,
|
SType = StructureType.ImageCreateInfo,
|
||||||
@@ -7301,7 +7552,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
? ImageUsageFlags.TransferDstBit |
|
? ImageUsageFlags.TransferDstBit |
|
||||||
ImageUsageFlags.SampledBit |
|
ImageUsageFlags.SampledBit |
|
||||||
ImageUsageFlags.ColorAttachmentBit |
|
ImageUsageFlags.ColorAttachmentBit |
|
||||||
ImageUsageFlags.StorageBit |
|
(supportsStorageUsage ? ImageUsageFlags.StorageBit : (ImageUsageFlags)0) |
|
||||||
ImageUsageFlags.TransferSrcBit
|
ImageUsageFlags.TransferSrcBit
|
||||||
: ImageUsageFlags.TransferDstBit | ImageUsageFlags.SampledBit,
|
: ImageUsageFlags.TransferDstBit | ImageUsageFlags.SampledBit,
|
||||||
SharingMode = SharingMode.Exclusive,
|
SharingMode = SharingMode.Exclusive,
|
||||||
@@ -7370,6 +7621,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
InitialUploadPending = true,
|
InitialUploadPending = true,
|
||||||
IsCpuBacked = true,
|
IsCpuBacked = true,
|
||||||
CpuContentFingerprint = contentFingerprint,
|
CpuContentFingerprint = contentFingerprint,
|
||||||
|
SupportsStorageUsage = supportsStorageUsage,
|
||||||
};
|
};
|
||||||
_guestImages.Add(texture.Address, guestImage);
|
_guestImages.Add(texture.Address, guestImage);
|
||||||
resource.OwnsStorage = false;
|
resource.OwnsStorage = false;
|
||||||
@@ -8802,6 +9054,12 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
return (properties.OptimalTilingFeatures & FormatFeatureFlags.ColorAttachmentBit) != 0;
|
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) =>
|
internal static Format GetTextureFormat(uint format, uint numberType) =>
|
||||||
(format, numberType) switch
|
(format, numberType) switch
|
||||||
{
|
{
|
||||||
@@ -8882,6 +9140,19 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
_ => Format.R8G8B8A8Unorm,
|
_ => 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) =>
|
private static Format GetRenderTargetFormat(uint format, uint numberType) =>
|
||||||
(format, numberType) switch
|
(format, numberType) switch
|
||||||
{
|
{
|
||||||
@@ -9029,6 +9300,12 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!TryValidateStorageImageBindings(work, out validationError))
|
||||||
|
{
|
||||||
|
LogRejectedComputeDispatch(work, validationError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
TranslatedDrawResources? resources = null;
|
TranslatedDrawResources? resources = null;
|
||||||
CommandBuffer commandBuffer = default;
|
CommandBuffer commandBuffer = default;
|
||||||
var submitted = false;
|
var submitted = false;
|
||||||
@@ -9295,6 +9572,59 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
return true;
|
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(
|
private void LogRejectedComputeDispatch(
|
||||||
VulkanComputeGuestDispatch work,
|
VulkanComputeGuestDispatch work,
|
||||||
string reason)
|
string reason)
|
||||||
@@ -9776,24 +10106,23 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var index = 0; index < targetFormats.Length; index++)
|
var normalizedBlends = GuestBlendStateNormalizer.NormalizeIntegerAttachments(
|
||||||
{
|
work.Draw.RenderState.Blends,
|
||||||
if (targetFormats[index].IsInteger &&
|
targetFormats.Select(static format => format.IsInteger).ToArray(),
|
||||||
work.Draw.RenderState.Blends[index].Enable)
|
out var normalizedBlendCount);
|
||||||
|
var draw = normalizedBlendCount == 0
|
||||||
|
? work.Draw
|
||||||
|
: work.Draw with
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine(
|
RenderState = work.Draw.RenderState with { Blends = normalizedBlends },
|
||||||
"[LOADER][WARN] Vulkan skipped MRT draw with blending enabled for an integer attachment.");
|
};
|
||||||
ReturnPooledGuestData(work.Draw);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_supportsIndependentBlend)
|
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] !=
|
if (draw.RenderState.Blends[index] !=
|
||||||
work.Draw.RenderState.Blends[0])
|
draw.RenderState.Blends[0])
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine(
|
Console.Error.WriteLine(
|
||||||
"[LOADER][WARN] Vulkan skipped MRT draw requiring unsupported independentBlend.");
|
"[LOADER][WARN] Vulkan skipped MRT draw requiring unsupported independentBlend.");
|
||||||
@@ -9870,7 +10199,6 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var extent = new Extent2D(firstTarget.Width, firstTarget.Height);
|
var extent = new Extent2D(firstTarget.Width, firstTarget.Height);
|
||||||
var draw = work.Draw;
|
|
||||||
var clearDepthForDraw = draw.RenderState.Depth.ClearEnable;
|
var clearDepthForDraw = draw.RenderState.Depth.ClearEnable;
|
||||||
if (work.DepthTarget?.ReadOnly == true && draw.RenderState.Depth.WriteEnable)
|
if (work.DepthTarget?.ReadOnly == true && draw.RenderState.Depth.WriteEnable)
|
||||||
{
|
{
|
||||||
@@ -9889,48 +10217,20 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
draw.RenderState.Depth) &&
|
draw.RenderState.Depth) &&
|
||||||
work.DepthTarget is { } depthTarget)
|
work.DepthTarget is { } depthTarget)
|
||||||
{
|
{
|
||||||
var effectiveDepthTarget = depthTarget;
|
var resolution = GuestDepthExtentResolver.Resolve(
|
||||||
if (depthTarget.Width < firstTarget.Width || depthTarget.Height < firstTarget.Height)
|
depthTarget,
|
||||||
{
|
firstTarget.Width,
|
||||||
var matchingTexture = work.Draw.Textures.FirstOrDefault(texture =>
|
firstTarget.Height,
|
||||||
texture.Address == depthTarget.Address &&
|
draw.Textures);
|
||||||
texture.Width >= firstTarget.Width &&
|
var effectiveDepthTarget = resolution.IsUsable &&
|
||||||
texture.Height >= firstTarget.Height);
|
(resolution.Width != depthTarget.Width ||
|
||||||
if (matchingTexture is not null)
|
resolution.Height != depthTarget.Height)
|
||||||
{
|
? depthTarget with
|
||||||
effectiveDepthTarget = depthTarget with
|
|
||||||
{
|
{
|
||||||
Width = matchingTexture.Width,
|
Width = resolution.Width,
|
||||||
Height = matchingTexture.Height,
|
Height = resolution.Height,
|
||||||
};
|
}
|
||||||
}
|
: depthTarget;
|
||||||
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}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
depth = GetOrCreateGuestDepth(effectiveDepthTarget);
|
depth = GetOrCreateGuestDepth(effectiveDepthTarget);
|
||||||
PrepareFirstUseDepth(depth, draw.RenderState.Depth);
|
PrepareFirstUseDepth(depth, draw.RenderState.Depth);
|
||||||
@@ -10558,8 +10858,17 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
|
|
||||||
private GuestImageResource GetOrCreateGuestImage(
|
private GuestImageResource GetOrCreateGuestImage(
|
||||||
GuestRenderTarget target,
|
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 mipLevels = ClampMipLevels(target.Width, target.Height, target.MipLevels);
|
||||||
var guestFormat = GetGuestTextureFormat(target.Format, target.NumberType);
|
var guestFormat = GetGuestTextureFormat(target.Format, target.NumberType);
|
||||||
var requestedKey = new GuestImageVariantKey(
|
var requestedKey = new GuestImageVariantKey(
|
||||||
@@ -10577,9 +10886,15 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
existing.GuestFormat == guestFormat &&
|
existing.GuestFormat == guestFormat &&
|
||||||
existing.Format == format)
|
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.IsCpuBacked = false;
|
||||||
existing.CpuContentFingerprint = 0;
|
existing.CpuContentFingerprint = 0;
|
||||||
if (existing.RenderPass.Handle == 0)
|
if (existing.RenderPass.Handle == 0 && !requiresStorage)
|
||||||
{
|
{
|
||||||
var attachmentView = existing.MipViews.Length > 0
|
var attachmentView = existing.MipViews.Length > 0
|
||||||
? existing.MipViews[0]
|
? existing.MipViews[0]
|
||||||
@@ -10639,6 +10954,12 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
|
|
||||||
if (_guestImageVariants.Remove(requestedKey, out var retained))
|
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.IsCpuBacked = false;
|
||||||
retained.CpuContentFingerprint = 0;
|
retained.CpuContentFingerprint = 0;
|
||||||
_guestImages.Add(target.Address, retained);
|
_guestImages.Add(target.Address, retained);
|
||||||
@@ -10686,7 +11007,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
Usage =
|
Usage =
|
||||||
ImageUsageFlags.ColorAttachmentBit |
|
ImageUsageFlags.ColorAttachmentBit |
|
||||||
ImageUsageFlags.SampledBit |
|
ImageUsageFlags.SampledBit |
|
||||||
ImageUsageFlags.StorageBit |
|
(supportsStorageUsage ? ImageUsageFlags.StorageBit : (ImageUsageFlags)0) |
|
||||||
ImageUsageFlags.TransferSrcBit |
|
ImageUsageFlags.TransferSrcBit |
|
||||||
ImageUsageFlags.TransferDstBit,
|
ImageUsageFlags.TransferDstBit,
|
||||||
SharingMode = SharingMode.Exclusive,
|
SharingMode = SharingMode.Exclusive,
|
||||||
@@ -10742,12 +11063,18 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
mipViews[mipLevel] = mipView;
|
mipViews[mipLevel] = mipView;
|
||||||
}
|
}
|
||||||
|
|
||||||
var (renderPass, initialRenderPass, framebuffer) =
|
RenderPass renderPass = default;
|
||||||
CreateRenderPassAndFramebuffer(
|
RenderPass initialRenderPass = default;
|
||||||
format,
|
Framebuffer framebuffer = default;
|
||||||
mipViews[0],
|
if (!requiresStorage)
|
||||||
target.Width,
|
{
|
||||||
target.Height);
|
(renderPass, initialRenderPass, framebuffer) =
|
||||||
|
CreateRenderPassAndFramebuffer(
|
||||||
|
format,
|
||||||
|
mipViews[0],
|
||||||
|
target.Width,
|
||||||
|
target.Height);
|
||||||
|
}
|
||||||
|
|
||||||
var resource = new GuestImageResource
|
var resource = new GuestImageResource
|
||||||
{
|
{
|
||||||
@@ -10764,6 +11091,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
RenderPass = renderPass,
|
RenderPass = renderPass,
|
||||||
InitialRenderPass = initialRenderPass,
|
InitialRenderPass = initialRenderPass,
|
||||||
Framebuffer = framebuffer,
|
Framebuffer = framebuffer,
|
||||||
|
SupportsStorageUsage = supportsStorageUsage,
|
||||||
};
|
};
|
||||||
var debugName = GuestImageDebugName(target, format);
|
var debugName = GuestImageDebugName(target, format);
|
||||||
SetDebugName(ObjectType.Image, image.Handle, $"{debugName} image");
|
SetDebugName(ObjectType.Image, image.Handle, $"{debugName} image");
|
||||||
@@ -10775,9 +11103,12 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
mipViews[mipLevel].Handle,
|
mipViews[mipLevel].Handle,
|
||||||
$"{debugName} mip{mipLevel}");
|
$"{debugName} mip{mipLevel}");
|
||||||
}
|
}
|
||||||
SetDebugName(ObjectType.RenderPass, renderPass.Handle, $"{debugName} renderpass");
|
if (renderPass.Handle != 0)
|
||||||
SetDebugName(ObjectType.RenderPass, initialRenderPass.Handle, $"{debugName} initial-renderpass");
|
{
|
||||||
SetDebugName(ObjectType.Framebuffer, framebuffer.Handle, $"{debugName} framebuffer");
|
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);
|
_guestImages.Add(target.Address, resource);
|
||||||
lock (_gate)
|
lock (_gate)
|
||||||
{
|
{
|
||||||
@@ -11476,6 +11807,18 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
return view;
|
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)
|
internal static bool IsCompatibleViewFormat(Format imageFormat, Format viewFormat)
|
||||||
{
|
{
|
||||||
if (imageFormat == viewFormat)
|
if (imageFormat == viewFormat)
|
||||||
@@ -14435,6 +14778,10 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
_descriptorLayouts.Clear();
|
_descriptorLayouts.Clear();
|
||||||
|
while (_recycledDescriptorPools.TryPop(out var recycledDescriptorPool))
|
||||||
|
{
|
||||||
|
_vk.DestroyDescriptorPool(_device, recycledDescriptorPool, null);
|
||||||
|
}
|
||||||
foreach (var sampler in _samplers.Values)
|
foreach (var sampler in _samplers.Values)
|
||||||
{
|
{
|
||||||
_vk.DestroySampler(_device, sampler, null);
|
_vk.DestroySampler(_device, sampler, null);
|
||||||
@@ -14499,6 +14846,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
_vk.DestroyInstance(_instance, null);
|
_vk.DestroyInstance(_instance, null);
|
||||||
_instance = default;
|
_instance = default;
|
||||||
}
|
}
|
||||||
|
GuestDataPool.Trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RecreateSwapchainResources(string operation, Result result)
|
private void RecreateSwapchainResources(string operation, Result result)
|
||||||
|
|||||||
@@ -162,6 +162,11 @@ public static partial class Gen5SpirvTranslator
|
|||||||
return context.TryCompile(out shader, out error);
|
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 sealed partial class CompilationContext
|
||||||
{
|
{
|
||||||
private const uint ImageDescriptorDwords = 8;
|
private const uint ImageDescriptorDwords = 8;
|
||||||
@@ -279,6 +284,7 @@ public static partial class Gen5SpirvTranslator
|
|||||||
private uint _workGroupIdInput;
|
private uint _workGroupIdInput;
|
||||||
private uint _computeDispatchLimit;
|
private uint _computeDispatchLimit;
|
||||||
private uint _pushConstantUintPointer;
|
private uint _pushConstantUintPointer;
|
||||||
|
private uint _subgroupSizeInput;
|
||||||
private uint _subgroupInvocationIdInput;
|
private uint _subgroupInvocationIdInput;
|
||||||
private uint _waveMaskScratch;
|
private uint _waveMaskScratch;
|
||||||
private uint _waveMaskScratchElementPointer;
|
private uint _waveMaskScratchElementPointer;
|
||||||
@@ -961,8 +967,9 @@ public static partial class Gen5SpirvTranslator
|
|||||||
{
|
{
|
||||||
var binding = _evaluation.ImageBindings[index];
|
var binding = _evaluation.ImageBindings[index];
|
||||||
_imageBindingByPc.TryAdd(binding.Pc, index);
|
_imageBindingByPc.TryAdd(binding.Pc, index);
|
||||||
var isStorage =
|
var isStorage = Gen5ShaderTranslator.RequiresStorageImage(
|
||||||
Gen5ShaderTranslator.IsStorageImageOperation(binding.Opcode);
|
binding,
|
||||||
|
_evaluation.ImageBindings);
|
||||||
var (format, componentKind) =
|
var (format, componentKind) =
|
||||||
DecodeImageFormat(binding.ResourceDescriptor);
|
DecodeImageFormat(binding.ResourceDescriptor);
|
||||||
var componentType = componentKind switch
|
var componentType = componentKind switch
|
||||||
@@ -1044,58 +1051,62 @@ public static partial class Gen5SpirvTranslator
|
|||||||
return (SpirvImageFormat.Unknown, ImageComponentKind.Float);
|
return (SpirvImageFormat.Unknown, ImageComponentKind.Float);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (dataFormat, numberType) switch
|
var kind = numberType switch
|
||||||
{
|
{
|
||||||
(1, _) => (SpirvImageFormat.R8, ImageComponentKind.Float),
|
4 => ImageComponentKind.Uint,
|
||||||
(2, _) => (SpirvImageFormat.R16f, ImageComponentKind.Float),
|
5 => ImageComponentKind.Sint,
|
||||||
(3, _) => (SpirvImageFormat.Rg8, ImageComponentKind.Float),
|
_ => 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),
|
|
||||||
};
|
};
|
||||||
|
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()
|
private void DeclareStageInterface()
|
||||||
{
|
{
|
||||||
if (UsesSubgroupOperations())
|
if (UsesSubgroupOperations())
|
||||||
@@ -1111,6 +1122,18 @@ public static partial class Gen5SpirvTranslator
|
|||||||
(uint)SpirvBuiltIn.SubgroupLocalInvocationId);
|
(uint)SpirvBuiltIn.SubgroupLocalInvocationId);
|
||||||
_interfaces.Add(_subgroupInvocationIdInput);
|
_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)
|
if (_waveLaneCount == 64)
|
||||||
{
|
{
|
||||||
_localInvocationIndexInput = _module.AddGlobalVariable(
|
_localInvocationIndexInput = _module.AddGlobalVariable(
|
||||||
@@ -3091,12 +3114,14 @@ public static partial class Gen5SpirvTranslator
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var imageLoad = Gen5ShaderTranslator.IsImageLoadOperation(instruction.Opcode);
|
||||||
var storage = Gen5ShaderTranslator.IsStorageImageOperation(instruction.Opcode);
|
var storage = Gen5ShaderTranslator.IsStorageImageOperation(instruction.Opcode);
|
||||||
for (var index = 0; index < _evaluation.ImageBindings.Count; index++)
|
for (var index = 0; index < _evaluation.ImageBindings.Count; index++)
|
||||||
{
|
{
|
||||||
var candidate = _evaluation.ImageBindings[index];
|
var candidate = _evaluation.ImageBindings[index];
|
||||||
if (candidate.Control.ScalarResource != control.ScalarResource ||
|
if (candidate.Control.ScalarResource != control.ScalarResource ||
|
||||||
candidate.Control.ScalarSampler != control.ScalarSampler ||
|
candidate.Control.ScalarSampler != control.ScalarSampler ||
|
||||||
|
Gen5ShaderTranslator.IsImageLoadOperation(candidate.Opcode) != imageLoad ||
|
||||||
Gen5ShaderTranslator.IsStorageImageOperation(candidate.Opcode) != storage ||
|
Gen5ShaderTranslator.IsStorageImageOperation(candidate.Opcode) != storage ||
|
||||||
!HasSameScalarDefinitions(
|
!HasSameScalarDefinitions(
|
||||||
candidate.Pc,
|
candidate.Pc,
|
||||||
@@ -5097,12 +5122,14 @@ public static partial class Gen5SpirvTranslator
|
|||||||
SpirvOp.UConvert,
|
SpirvOp.UConvert,
|
||||||
_ulongType,
|
_ulongType,
|
||||||
maskedLane));
|
maskedLane));
|
||||||
return _module.AddInstruction(
|
return _emulateWave64
|
||||||
SpirvOp.Select,
|
? shifted
|
||||||
_ulongType,
|
: _module.AddInstruction(
|
||||||
IsCurrentLaneInRdnaWave(),
|
SpirvOp.Select,
|
||||||
shifted,
|
_ulongType,
|
||||||
_module.Constant64(_ulongType, 0));
|
IsCurrentLaneInRdnaWave(),
|
||||||
|
shifted,
|
||||||
|
_module.Constant64(_ulongType, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
private uint IsCurrentLaneInRdnaWave() =>
|
private uint IsCurrentLaneInRdnaWave() =>
|
||||||
@@ -5139,6 +5166,11 @@ public static partial class Gen5SpirvTranslator
|
|||||||
0);
|
0);
|
||||||
if (_emulateWave64)
|
if (_emulateWave64)
|
||||||
{
|
{
|
||||||
|
var high = _module.AddInstruction(
|
||||||
|
SpirvOp.CompositeExtract,
|
||||||
|
_uintType,
|
||||||
|
ballot,
|
||||||
|
1);
|
||||||
var subgroupLane =
|
var subgroupLane =
|
||||||
Load(_uintType, _subgroupInvocationIdInput);
|
Load(_uintType, _subgroupInvocationIdInput);
|
||||||
var firstLane = _module.AddInstruction(
|
var firstLane = _module.AddInstruction(
|
||||||
@@ -5150,6 +5182,16 @@ public static partial class Gen5SpirvTranslator
|
|||||||
EmitConditional(firstLane, () =>
|
EmitConditional(firstLane, () =>
|
||||||
{
|
{
|
||||||
Store(WaveMaskScratchPointer(half), low);
|
Store(WaveMaskScratchPointer(half), low);
|
||||||
|
|
||||||
|
var nativeWave64 = _module.AddInstruction(
|
||||||
|
SpirvOp.UGreaterThanEqual,
|
||||||
|
_boolType,
|
||||||
|
Load(_uintType, _subgroupSizeInput),
|
||||||
|
UInt(64));
|
||||||
|
EmitConditional(nativeWave64, () =>
|
||||||
|
{
|
||||||
|
Store(WaveMaskScratchPointer(UInt(1)), high);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
EmitWave64Barrier();
|
EmitWave64Barrier();
|
||||||
var lowMask = Load(
|
var lowMask = Load(
|
||||||
|
|||||||
@@ -251,6 +251,7 @@ public enum SpirvBuiltIn : uint
|
|||||||
LocalInvocationId = 27,
|
LocalInvocationId = 27,
|
||||||
GlobalInvocationId = 28,
|
GlobalInvocationId = 28,
|
||||||
LocalInvocationIndex = 29,
|
LocalInvocationIndex = 29,
|
||||||
|
SubgroupSize = 36,
|
||||||
SubgroupLocalInvocationId = 41,
|
SubgroupLocalInvocationId = 41,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1576,11 +1576,37 @@ public static class Gen5ShaderTranslator
|
|||||||
private static bool IsMimgInstruction(string name) =>
|
private static bool IsMimgInstruction(string name) =>
|
||||||
name.StartsWith("Image", StringComparison.Ordinal);
|
name.StartsWith("Image", StringComparison.Ordinal);
|
||||||
|
|
||||||
|
public static bool IsImageLoadOperation(string name) =>
|
||||||
|
name.StartsWith("ImageLoad", StringComparison.Ordinal);
|
||||||
|
|
||||||
public static bool IsStorageImageOperation(string name) =>
|
public static bool IsStorageImageOperation(string name) =>
|
||||||
name.StartsWith("ImageLoad", StringComparison.Ordinal) ||
|
|
||||||
name.StartsWith("ImageStore", StringComparison.Ordinal) ||
|
name.StartsWith("ImageStore", StringComparison.Ordinal) ||
|
||||||
name.StartsWith("ImageAtomic", 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
|
public static bool IsDataShareAtomic(string name) => name switch
|
||||||
{
|
{
|
||||||
"DsAddU32" or "DsSubU32" or "DsIncU32" or "DsDecU32" or
|
"DsAddU32" or "DsSubU32" or "DsIncU32" or "DsDecU32" or
|
||||||
|
|||||||
Reference in New Issue
Block a user