Vulkan: fix guest storage image and render-state handling (#332)

This commit is contained in:
lilp
2026-07-17 20:54:52 -03:00
committed by GitHub
parent b479dc0466
commit c9d018db8e
8 changed files with 806 additions and 147 deletions
+25 -10
View File
@@ -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