Compare commits

...

4 Commits

Author SHA1 Message Date
ParantezTech 69d8490275 [shader-decoder-part1-hotfix] New format support, maintenance8 support, robustness improvements, and bug fixes. This commit includes updates to the AGC exports, Gen5 SPIR-V translator, and Vulkan video presenter to enhance compatibility and performance. 2026-07-07 20:54:48 +03:00
Foued Attar 38d94d8e54 [vulkan] enable required PhysicalDeviceFeatures for translated shaders (#29)
- Enable VertexPipelineStoresAndAtomics/FragmentStoresAndAtomics:
  fixes vkCreateGraphicsPipelines() rejecting guestBuffers storage
  descriptor as NonWritable in vertex/fragment stages.
- Enable ShaderInt64: fixes vkCreateShaderModule() rejecting SPIR-V
  using 64-bit integer capability.
- Query GetPhysicalDeviceFeatures first and only enable what the GPU
  actually reports as supported, with a warning fallback otherwise.

Also adds optional Vulkan Validation Layers (SHARPEMU_VK_VALIDATION=1)
to surface these VUID errors during development instead of silent
VK_ERROR_DEVICE_LOST.
2026-07-06 16:56:03 +03:00
ParantezTech 61a1abdb13 Merge remote-tracking branch 'refs/remotes/origin/main' 2026-07-06 09:53:04 +03:00
ParantezTech 88452218cb update readme 2026-07-06 09:52:46 +03:00
9 changed files with 563 additions and 123 deletions
+1 -1
View File
@@ -2,7 +2,6 @@
Copyright (C) 2026 SharpEmu Emulator Project
SPDX-License-Identifier: GPL-2.0-or-later
-->
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
@@ -10,6 +9,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
<ItemGroup>
<PackageVersion Include="Iced" Version="1.21.0" />
<PackageVersion Include="Silk.NET.Vulkan" Version="2.23.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.23.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.23.0" />
<PackageVersion Include="Silk.NET.Windowing" Version="2.23.0" />
</ItemGroup>
BIN
View File
Binary file not shown.
+11
View File
@@ -81,6 +81,7 @@
"dependencies": {
"SharpEmu.HLE": "[1.0.0, )",
"Silk.NET.Vulkan": "[2.23.0, )",
"Silk.NET.Vulkan.Extensions.EXT": "[2.23.0, )",
"Silk.NET.Vulkan.Extensions.KHR": "[2.23.0, )",
"Silk.NET.Windowing": "[2.23.0, )"
}
@@ -103,6 +104,16 @@
"Silk.NET.Core": "2.23.0"
}
},
"Silk.NET.Vulkan.Extensions.EXT": {
"type": "CentralTransitive",
"requested": "[2.23.0, )",
"resolved": "2.23.0",
"contentHash": "+Oth189ksRiL6HvGCwIdnsYHawqrbO8y49u1H61z3wsfcHhQZeVDYe/wF5LD7fk3NcdgDvwFD3mLm1QWhdZySw==",
"dependencies": {
"Silk.NET.Core": "2.23.0",
"Silk.NET.Vulkan": "2.23.0"
}
},
"Silk.NET.Vulkan.Extensions.KHR": {
"type": "CentralTransitive",
"requested": "[2.23.0, )",
+11
View File
@@ -72,6 +72,7 @@
"dependencies": {
"SharpEmu.HLE": "[1.0.0, )",
"Silk.NET.Vulkan": "[2.23.0, )",
"Silk.NET.Vulkan.Extensions.EXT": "[2.23.0, )",
"Silk.NET.Vulkan.Extensions.KHR": "[2.23.0, )",
"Silk.NET.Windowing": "[2.23.0, )"
}
@@ -88,6 +89,16 @@
"Silk.NET.Core": "2.23.0"
}
},
"Silk.NET.Vulkan.Extensions.EXT": {
"type": "CentralTransitive",
"requested": "[2.23.0, )",
"resolved": "2.23.0",
"contentHash": "+Oth189ksRiL6HvGCwIdnsYHawqrbO8y49u1H61z3wsfcHhQZeVDYe/wF5LD7fk3NcdgDvwFD3mLm1QWhdZySw==",
"dependencies": {
"Silk.NET.Core": "2.23.0",
"Silk.NET.Vulkan": "2.23.0"
}
},
"Silk.NET.Vulkan.Extensions.KHR": {
"type": "CentralTransitive",
"requested": "[2.23.0, )",
+15 -8
View File
@@ -3609,7 +3609,7 @@ public static class AgcExports
',',
draw.GlobalMemoryBindings.Select((binding, index) =>
$"{index}:0x{binding.BaseAddress:X16}:{binding.Data.Length}:" +
Convert.ToHexString(binding.Data.AsSpan(0, Math.Min(binding.Data.Length, 32)))));
Convert.ToHexString(binding.Data.AsSpan(0, Math.Min(binding.Data.Length, 256)))));
var indices = draw.IndexBuffer is { } indexBuffer
? $"{(indexBuffer.Is32Bit ? 32 : 16)}:" +
Convert.ToHexString(indexBuffer.Data.AsSpan(0, Math.Min(indexBuffer.Data.Length, 32)))
@@ -3744,7 +3744,7 @@ public static class AgcExports
descriptor.Width > 8192 ||
descriptor.Height > 8192)
{
texture = CreateFallbackGuestDrawTexture(isStorage);
texture = CreateFallbackGuestDrawTexture(isStorage, descriptor.Format, descriptor.NumberType);
return true;
}
@@ -3762,7 +3762,7 @@ public static class AgcExports
sourceByteCount > MaxPresentedTextureBytes ||
sourceByteCount > int.MaxValue)
{
texture = CreateFallbackGuestDrawTexture(isStorage);
texture = CreateFallbackGuestDrawTexture(isStorage, descriptor.Format, descriptor.NumberType);
return true;
}
@@ -3825,7 +3825,7 @@ public static class AgcExports
var source = new byte[(int)sourceByteCount];
if (!ctx.Memory.TryRead(descriptor.Address, source))
{
texture = CreateFallbackGuestDrawTexture(isStorage);
texture = CreateFallbackGuestDrawTexture(isStorage, descriptor.Format, descriptor.NumberType);
return true;
}
@@ -3868,18 +3868,25 @@ public static class AgcExports
return true;
}
private static VulkanGuestDrawTexture CreateFallbackGuestDrawTexture(bool isStorage = false) =>
new(
private static VulkanGuestDrawTexture CreateFallbackGuestDrawTexture(
bool isStorage,
uint format,
uint numberType)
{
var fallbackFormat = format == 0 ? 10u : format;
var fallbackNumberType = numberType;
return new(
0,
1,
1,
56,
NumberType: 0,
fallbackFormat,
fallbackNumberType,
[0, 0, 0, 255],
IsFallback: true,
IsStorage: isStorage,
MipLevels: 1,
MipLevel: 0);
}
private static VulkanGuestSampler ToVulkanSampler(IReadOnlyList<uint> descriptor) =>
descriptor.Count >= 4
+94 -38
View File
@@ -467,9 +467,6 @@ internal static partial class Gen5SpirvTranslator
_imageBindingByPc.TryAdd(binding.Pc, index);
var isStorage =
Gen5ShaderTranslator.IsStorageImageOperation(binding.Opcode);
var isDepth =
binding.Opcode.Contains("SampleC", StringComparison.Ordinal) ||
binding.Opcode.Contains("Gather4C", StringComparison.Ordinal);
var (format, componentKind) =
DecodeImageFormat(binding.ResourceDescriptor);
var componentType = componentKind switch
@@ -494,7 +491,7 @@ internal static partial class Gen5SpirvTranslator
var imageType = _module.TypeImage(
componentType,
SpirvImageDim.Dim2D,
depth: isDepth,
depth: false,
arrayed: false,
multisampled: false,
sampled: isStorage ? 2u : 1u,
@@ -1661,18 +1658,14 @@ internal static partial class Gen5SpirvTranslator
var offset = hasOffset ? BuildImageOffset(image, 0) : 0u;
var imageOperands =
(explicitLod ? 2u : 0u) | (hasOffset ? 0x10u : 0u);
var reference = hasCompare
? Bitcast(_floatType, LoadV(image.GetAddressRegister(hasOffset ? 1 : 0)))
: 0u;
var operands = new List<uint>
{
imageObject,
coordinates,
};
if (hasCompare)
{
operands.Add(
Bitcast(
_floatType,
LoadV(image.GetAddressRegister(hasOffset ? 1 : 0))));
}
if (imageOperands != 0)
{
@@ -1689,29 +1682,14 @@ internal static partial class Gen5SpirvTranslator
}
sampled = _module.AddInstruction(
hasCompare
? explicitLod
? SpirvOp.ImageSampleDrefExplicitLod
: SpirvOp.ImageSampleDrefImplicitLod
: explicitLod
? SpirvOp.ImageSampleExplicitLod
: SpirvOp.ImageSampleImplicitLod,
hasCompare ? resource.ComponentType : resource.VectorType,
explicitLod
? SpirvOp.ImageSampleExplicitLod
: SpirvOp.ImageSampleImplicitLod,
resource.VectorType,
[.. operands]);
if (hasCompare)
{
var scalar = sampled;
sampled = _module.AddInstruction(
SpirvOp.CompositeConstruct,
resource.VectorType,
scalar,
scalar,
scalar,
resource.ComponentKind == ImageComponentKind.Float
? Float(1)
: resource.ComponentKind == ImageComponentKind.Uint
? UInt(1)
: _module.Constant(_intType, 1));
sampled = EmitManualDepthCompare(resource, sampled, reference);
}
}
else if (instruction.Opcode.StartsWith(
@@ -1725,6 +1703,9 @@ internal static partial class Gen5SpirvTranslator
var start = (hasOffset ? 1 : 0) + (hasCompare ? 1 : 0);
var coordinates = BuildFloatCoordinates(image, start);
var offset = hasOffset ? BuildImageOffset(image, 0) : 0u;
var reference = hasCompare
? Bitcast(_floatType, LoadV(image.GetAddressRegister(hasOffset ? 1 : 0)))
: 0u;
var operands = new List<uint>
{
imageObject,
@@ -1732,10 +1713,7 @@ internal static partial class Gen5SpirvTranslator
};
if (hasCompare)
{
operands.Add(
Bitcast(
_floatType,
LoadV(image.GetAddressRegister(hasOffset ? 1 : 0))));
operands.Add(UInt(0));
}
else
{
@@ -1756,11 +1734,28 @@ internal static partial class Gen5SpirvTranslator
}
sampled = _module.AddInstruction(
hasCompare
? SpirvOp.ImageDrefGather
: SpirvOp.ImageGather,
SpirvOp.ImageGather,
resource.VectorType,
[.. operands]);
if (hasCompare)
{
var compared = new uint[4];
for (var component = 0u; component < 4; component++)
{
var texel = _module.AddInstruction(
SpirvOp.CompositeExtract,
resource.ComponentType,
sampled,
component);
compared[component] = EmitDepthCompareScalar(resource, texel, reference);
}
sampled = _module.AddInstruction(
SpirvOp.CompositeConstruct,
resource.VectorType,
compared);
}
writeAllComponents = true;
}
else
@@ -1794,6 +1789,67 @@ internal static partial class Gen5SpirvTranslator
return true;
}
private uint EmitDepthCompareScalar(
SpirvImageResource resource,
uint texel,
uint reference)
{
var texelAsFloat = resource.ComponentKind switch
{
ImageComponentKind.Uint => _module.AddInstruction(
SpirvOp.ConvertUToF, _floatType, texel),
ImageComponentKind.Sint => _module.AddInstruction(
SpirvOp.ConvertSToF, _floatType, texel),
_ => texel,
};
var passes = _module.AddInstruction(
SpirvOp.FOrdLessThanEqual,
_boolType,
reference,
texelAsFloat);
return _module.AddInstruction(
SpirvOp.Select,
resource.ComponentType,
passes,
resource.ComponentKind switch
{
ImageComponentKind.Uint => UInt(1),
ImageComponentKind.Sint => _module.Constant(_intType, 1),
_ => Float(1),
},
resource.ComponentKind switch
{
ImageComponentKind.Uint => UInt(0),
ImageComponentKind.Sint => _module.Constant(_intType, 0),
_ => Float(0),
});
}
private uint EmitManualDepthCompare(
SpirvImageResource resource,
uint sampledVector,
uint reference)
{
var texel = _module.AddInstruction(
SpirvOp.CompositeExtract,
resource.ComponentType,
sampledVector,
0u);
var scalar = EmitDepthCompareScalar(resource, texel, reference);
return _module.AddInstruction(
SpirvOp.CompositeConstruct,
resource.VectorType,
scalar,
scalar,
scalar,
resource.ComponentKind switch
{
ImageComponentKind.Uint => UInt(1),
ImageComponentKind.Sint => _module.Constant(_intType, 1),
_ => Float(1),
});
}
private uint BuildFloatCoordinates(Gen5ImageControl image, int start)
{
var x = Bitcast(
+1
View File
@@ -10,6 +10,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
<ItemGroup>
<PackageReference Include="Silk.NET.Vulkan" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" />
<PackageReference Include="Silk.NET.Windowing" />
</ItemGroup>
@@ -7,6 +7,7 @@ using Silk.NET.Maths;
using SharpEmu.Libs.Agc;
using Silk.NET.Vulkan;
using Silk.NET.Vulkan.Extensions.KHR;
using Silk.NET.Vulkan.Extensions.EXT;
using Silk.NET.Windowing;
using System.Numerics;
using System.Runtime.CompilerServices;
@@ -846,6 +847,8 @@ internal static unsafe class VulkanVideoPresenter
private delegate* unmanaged<CommandBuffer, void> _cmdEndDebugUtilsLabel;
private Instance _instance;
private SurfaceKHR _surface;
private DebugUtilsMessengerEXT _debugMessenger;
private ExtDebugUtils? _debugUtils;
private PhysicalDevice _physicalDevice;
private Device _device;
private Queue _queue;
@@ -1241,6 +1244,9 @@ internal static unsafe class VulkanVideoPresenter
private void CreateInstance()
{
var applicationName = (byte*)SilkMarshal.StringToPtr("SharpEmu");
var enableValidation = Environment.GetEnvironmentVariable("SHARPEMU_VK_VALIDATION") == "1";
byte* validationLayerName = null;
try
{
var applicationInfo = new ApplicationInfo
@@ -1268,12 +1274,29 @@ internal static unsafe class VulkanVideoPresenter
enabledExtensions[enabledExtensionCount++] = debugUtilsExtension;
}
if (enableValidation && IsInstanceLayerAvailable("VK_LAYER_KHRONOS_validation"))
{
validationLayerName = (byte*)SilkMarshal.StringToPtr("VK_LAYER_KHRONOS_validation");
}
else if (enableValidation)
{
Console.Error.WriteLine("[LOADER][WARN] SHARPEMU_VK_VALIDATION=1 but VK_LAYER_KHRONOS_validation not found (Vulkan SDK installed?).");
}
var layers = stackalloc byte*[1];
if (validationLayerName is not null)
{
layers[0] = validationLayerName;
}
var createInfo = new InstanceCreateInfo
{
SType = StructureType.InstanceCreateInfo,
PApplicationInfo = &applicationInfo,
EnabledExtensionCount = (uint)enabledExtensionCount,
PpEnabledExtensionNames = enabledExtensions,
EnabledLayerCount = validationLayerName is not null ? 1u : 0u,
PpEnabledLayerNames = validationLayerName is not null ? layers : null,
};
try
@@ -1283,6 +1306,13 @@ internal static unsafe class VulkanVideoPresenter
{
throw new InvalidOperationException("VK_KHR_surface is unavailable.");
}
if (validationLayerName is not null && _vk.TryGetInstanceExtension(_instance, out ExtDebugUtils debugUtils))
{
_debugUtils = debugUtils;
RegisterDebugMessenger(debugUtils);
Console.Error.WriteLine("[LOADER][INFO] Vulkan Validation Layers active (SHARPEMU_VK_VALIDATION=1).");
}
}
finally
{
@@ -1295,9 +1325,74 @@ internal static unsafe class VulkanVideoPresenter
finally
{
SilkMarshal.Free((nint)applicationName);
if (validationLayerName is not null)
{
SilkMarshal.Free((nint)validationLayerName);
}
}
}
private bool IsInstanceLayerAvailable(string layerName)
{
uint layerCount = 0;
if (_vk.EnumerateInstanceLayerProperties(&layerCount, null) != Result.Success || layerCount == 0)
{
return false;
}
var properties = new LayerProperties[layerCount];
fixed (LayerProperties* propertyPointer = properties)
{
if (_vk.EnumerateInstanceLayerProperties(&layerCount, propertyPointer) != Result.Success)
{
return false;
}
var expected = Encoding.UTF8.GetBytes(layerName);
for (var index = 0; index < layerCount; index++)
{
if (Utf8NullTerminatedEquals(propertyPointer[index].LayerName, expected))
{
return true;
}
}
}
return false;
}
private void RegisterDebugMessenger(ExtDebugUtils debugUtils)
{
var messengerInfo = new DebugUtilsMessengerCreateInfoEXT
{
SType = StructureType.DebugUtilsMessengerCreateInfoExt,
MessageSeverity = DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt
| DebugUtilsMessageSeverityFlagsEXT.WarningBitExt,
MessageType = DebugUtilsMessageTypeFlagsEXT.ValidationBitExt
| DebugUtilsMessageTypeFlagsEXT.PerformanceBitExt
| DebugUtilsMessageTypeFlagsEXT.GeneralBitExt,
PfnUserCallback = new PfnDebugUtilsMessengerCallbackEXT(DebugCallback),
};
Check(debugUtils.CreateDebugUtilsMessenger(_instance, &messengerInfo, null, out _debugMessenger),
"vkCreateDebugUtilsMessengerEXT");
}
private static unsafe uint DebugCallback(
DebugUtilsMessageSeverityFlagsEXT severity,
DebugUtilsMessageTypeFlagsEXT type,
DebugUtilsMessengerCallbackDataEXT* callbackData,
void* userData)
{
var message = SilkMarshal.PtrToString((nint)callbackData->PMessage);
var prefix = severity switch
{
DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt => "[VULKAN][ERROR]",
DebugUtilsMessageSeverityFlagsEXT.WarningBitExt => "[VULKAN][WARN]",
_ => "[VULKAN][INFO]",
};
Console.Error.WriteLine($"{prefix} {message}");
return Vk.False;
}
private void CreateSurface()
{
var instanceHandle = new VkHandle(_instance.Handle);
@@ -1358,17 +1453,129 @@ internal static unsafe class VulkanVideoPresenter
QueueCount = 1,
PQueuePriorities = &priority,
};
_vk.GetPhysicalDeviceFeatures(_physicalDevice, out var supportedFeatures);
var enabledFeatures = new PhysicalDeviceFeatures
{
VertexPipelineStoresAndAtomics = supportedFeatures.VertexPipelineStoresAndAtomics,
FragmentStoresAndAtomics = supportedFeatures.FragmentStoresAndAtomics,
ShaderInt64 = supportedFeatures.ShaderInt64,
ShaderImageGatherExtended = supportedFeatures.ShaderImageGatherExtended,
ShaderStorageImageExtendedFormats = supportedFeatures.ShaderStorageImageExtendedFormats,
ShaderStorageImageReadWithoutFormat = supportedFeatures.ShaderStorageImageReadWithoutFormat,
ShaderStorageImageWriteWithoutFormat = supportedFeatures.ShaderStorageImageWriteWithoutFormat,
RobustBufferAccess = supportedFeatures.RobustBufferAccess,
};
if (!supportedFeatures.RobustBufferAccess)
{
Console.Error.WriteLine(
"[LOADER][WARN] GPU does not support robustBufferAccess " +
"translated shaders performing out-of-bounds buffer access may cause device loss.");
}
if (!supportedFeatures.ShaderInt64)
{
Console.Error.WriteLine(
"[LOADER][WARN] GPU does not support shaderInt64 " +
"translated shaders using 64-bit integers will fail.");
}
if (!supportedFeatures.VertexPipelineStoresAndAtomics || !supportedFeatures.FragmentStoresAndAtomics)
{
Console.Error.WriteLine(
"[LOADER][WARN] GPU does not support vertexPipelineStoresAndAtomics/fragmentStoresAndAtomics " +
"translated shaders using storage buffers in vertex/fragment stages may fail.");
}
if (!supportedFeatures.ShaderImageGatherExtended)
{
Console.Error.WriteLine(
"[LOADER][WARN] GPU does not support shaderImageGatherExtended " +
"translated shaders using image gather with offsets/LOD/bias will fail.");
}
if (!supportedFeatures.ShaderStorageImageReadWithoutFormat ||
!supportedFeatures.ShaderStorageImageWriteWithoutFormat)
{
Console.Error.WriteLine(
"[LOADER][WARN] GPU does not support shaderStorageImage(Read|Write)WithoutFormat " +
"translated shaders using unformatted storage image load/store will fail.");
}
var maintenance8Features = new PhysicalDeviceMaintenance8FeaturesKHR
{
SType = StructureType.PhysicalDeviceMaintenance8FeaturesKhr,
};
var robustness2Features = new PhysicalDeviceRobustness2FeaturesEXT
{
SType = StructureType.PhysicalDeviceRobustness2FeaturesExt,
PNext = &maintenance8Features,
};
var featuresQuery = new PhysicalDeviceFeatures2
{
SType = StructureType.PhysicalDeviceFeatures2,
PNext = &robustness2Features,
};
_vk.GetPhysicalDeviceFeatures2(_physicalDevice, &featuresQuery);
var supportsMaintenance8 = maintenance8Features.Maintenance8;
var supportsRobustImageAccess2 = robustness2Features.RobustImageAccess2;
var supportsNullDescriptor = robustness2Features.NullDescriptor;
var supportsRobustness2 = supportsRobustImageAccess2 || supportsNullDescriptor;
if (!supportsMaintenance8)
{
Console.Error.WriteLine(
"[LOADER][WARN] GPU does not support VK_KHR_maintenance8 " +
"translated shaders using a dynamic texel offset on non-gather image samples will fail.");
}
if (!supportsRobustImageAccess2)
{
Console.Error.WriteLine(
"[LOADER][WARN] GPU does not support VK_EXT_robustness2 robustImageAccess2 " +
"translated shaders performing out-of-bounds image access may cause device loss.");
}
var swapchainExtension = (byte*)SilkMarshal.StringToPtr("VK_KHR_swapchain");
var maintenance8Extension = (byte*)SilkMarshal.StringToPtr("VK_KHR_maintenance8");
var robustness2Extension = (byte*)SilkMarshal.StringToPtr("VK_EXT_robustness2");
try
{
var extensions = stackalloc byte*[3];
var extensionCount = 0u;
extensions[extensionCount++] = swapchainExtension;
if (supportsMaintenance8)
{
extensions[extensionCount++] = maintenance8Extension;
}
if (supportsRobustness2)
{
extensions[extensionCount++] = robustness2Extension;
}
maintenance8Features.Maintenance8 = supportsMaintenance8;
maintenance8Features.PNext = null;
robustness2Features.RobustBufferAccess2 =
supportsRobustImageAccess2 && supportedFeatures.RobustBufferAccess;
robustness2Features.RobustImageAccess2 = supportsRobustImageAccess2;
robustness2Features.NullDescriptor = supportsNullDescriptor;
robustness2Features.PNext = supportsMaintenance8 ? &maintenance8Features : null;
var features2 = new PhysicalDeviceFeatures2
{
SType = StructureType.PhysicalDeviceFeatures2,
PNext = supportsRobustness2
? &robustness2Features
: (supportsMaintenance8 ? &maintenance8Features : null),
Features = enabledFeatures,
};
var createInfo = new DeviceCreateInfo
{
SType = StructureType.DeviceCreateInfo,
PNext = &features2,
QueueCreateInfoCount = 1,
PQueueCreateInfos = &queueInfo,
EnabledExtensionCount = 1,
PpEnabledExtensionNames = &swapchainExtension,
EnabledExtensionCount = extensionCount,
PpEnabledExtensionNames = extensions,
};
Check(_vk.CreateDevice(_physicalDevice, &createInfo, null, out _device), "vkCreateDevice");
@@ -1376,6 +1583,8 @@ internal static unsafe class VulkanVideoPresenter
finally
{
SilkMarshal.Free((nint)swapchainExtension);
SilkMarshal.Free((nint)maintenance8Extension);
SilkMarshal.Free((nint)robustness2Extension);
}
_vk.GetDeviceQueue(_device, _queueFamilyIndex, 0, out _queue);
@@ -1540,6 +1749,39 @@ internal static unsafe class VulkanVideoPresenter
resources.DebugName));
}
private void SubmitGuestCommandBufferAndWait(CommandBuffer commandBuffer)
{
var fenceInfo = new FenceCreateInfo
{
SType = StructureType.FenceCreateInfo,
};
Fence fence;
Check(
_vk.CreateFence(_device, &fenceInfo, null, out fence),
"vkCreateFence(guest chunk)");
try
{
var submitInfo = new SubmitInfo
{
SType = StructureType.SubmitInfo,
CommandBufferCount = 1,
PCommandBuffers = &commandBuffer,
};
Check(
_vk.QueueSubmit(_queue, 1, &submitInfo, fence),
"vkQueueSubmit(guest chunk)");
Check(
_vk.WaitForFences(_device, 1, &fence, true, ulong.MaxValue),
"vkWaitForFences(guest chunk)");
}
finally
{
_vk.DestroyFence(_device, fence, null);
}
_vk.FreeCommandBuffers(_device, _commandPool, 1, &commandBuffer);
}
private void EnsureGuestSubmissionCapacity()
{
CollectCompletedGuestSubmissions(waitForOldest: false);
@@ -2524,6 +2766,7 @@ internal static unsafe class VulkanVideoPresenter
var pipelineInfo = new ComputePipelineCreateInfo
{
SType = StructureType.ComputePipelineCreateInfo,
Flags = PipelineCreateFlags.CreateDispatchBaseBit,
Stage = stage,
Layout = resources.PipelineLayout,
};
@@ -2869,9 +3112,13 @@ internal static unsafe class VulkanVideoPresenter
}
_vk.UnmapMemory(_device, stagingMemory);
var supportsAttachmentUsage = !IsBlockCompressedFormat(vkFormat);
var imageInfo = new ImageCreateInfo
{
SType = StructureType.ImageCreateInfo,
Flags = supportsAttachmentUsage
? ImageCreateFlags.CreateMutableFormatBit | ImageCreateFlags.CreateExtendedUsageBit
: 0,
ImageType = ImageType.Type2D,
Format = vkFormat,
Extent = new Extent3D(width, height, 1),
@@ -2879,7 +3126,13 @@ internal static unsafe class VulkanVideoPresenter
ArrayLayers = 1,
Samples = SampleCountFlags.Count1Bit,
Tiling = ImageTiling.Optimal,
Usage = ImageUsageFlags.TransferDstBit | ImageUsageFlags.SampledBit,
Usage = supportsAttachmentUsage
? ImageUsageFlags.TransferDstBit |
ImageUsageFlags.SampledBit |
ImageUsageFlags.ColorAttachmentBit |
ImageUsageFlags.StorageBit |
ImageUsageFlags.TransferSrcBit
: ImageUsageFlags.TransferDstBit | ImageUsageFlags.SampledBit,
SharingMode = SharingMode.Exclusive,
InitialLayout = ImageLayout.Undefined,
};
@@ -3714,6 +3967,12 @@ internal static unsafe class VulkanVideoPresenter
_ => Format.Undefined,
};
private static bool IsBlockCompressedFormat(Format format) =>
format is Format.BC1RgbaUnormBlock or
Format.BC1RgbaSrgbBlock or
Format.BC7UnormBlock or
Format.BC7SrgbBlock;
private VkBuffer CreateBuffer(
ulong size,
BufferUsageFlags usage,
@@ -3767,8 +4026,19 @@ internal static unsafe class VulkanVideoPresenter
throw new InvalidOperationException("No compatible Vulkan host-visible memory type was found.");
}
private const uint MaxComputeZSlicesPerSubmission = 8;
private void ExecuteComputeDispatch(VulkanComputeGuestDispatch work)
{
if (AddressListContains("SHARPEMU_SKIP_COMPUTE_CS", work.ShaderAddress))
{
TraceVulkanShader(
$"vk.compute_skip cs=0x{work.ShaderAddress:X16} " +
$"groups={work.GroupCountX}x{work.GroupCountY}x{work.GroupCountZ} " +
$"textures={work.Textures.Count}");
return;
}
TranslatedDrawResources? resources = null;
CommandBuffer commandBuffer = default;
var submitted = false;
@@ -3776,59 +4046,95 @@ internal static unsafe class VulkanVideoPresenter
{
EnsureGuestSubmissionCapacity();
resources = CreateComputeDispatchResources(work);
commandBuffer = AllocateGuestCommandBuffer();
_commandBuffer = commandBuffer;
var beginInfo = new CommandBufferBeginInfo
{
SType = StructureType.CommandBufferBeginInfo,
Flags = CommandBufferUsageFlags.OneTimeSubmitBit,
};
Check(
_vk.BeginCommandBuffer(_commandBuffer, &beginInfo),
"vkBeginCommandBuffer(compute)");
BeginDebugLabel(_commandBuffer, resources.DebugName);
RecordTextureUploads(resources, PipelineStageFlags.ComputeShaderBit);
RecordStorageImagesForWrite(resources, PipelineStageFlags.ComputeShaderBit);
_vk.CmdBindPipeline(
_commandBuffer,
PipelineBindPoint.Compute,
resources.Pipeline);
if (resources.DescriptorSet.Handle != 0)
var batchCount = Math.Max(
1u,
(uint)Math.Ceiling(work.GroupCountZ / (double)MaxComputeZSlicesPerSubmission));
for (var batchIndex = 0u; batchIndex < batchCount; batchIndex++)
{
var descriptorSet = resources.DescriptorSet;
_vk.CmdBindDescriptorSets(
var zStart = batchIndex * MaxComputeZSlicesPerSubmission;
var zCount = Math.Min(MaxComputeZSlicesPerSubmission, work.GroupCountZ - zStart);
var isFirstBatch = batchIndex == 0;
var isLastBatch = batchIndex == batchCount - 1;
commandBuffer = AllocateGuestCommandBuffer();
_commandBuffer = commandBuffer;
var beginInfo = new CommandBufferBeginInfo
{
SType = StructureType.CommandBufferBeginInfo,
Flags = CommandBufferUsageFlags.OneTimeSubmitBit,
};
Check(
_vk.BeginCommandBuffer(_commandBuffer, &beginInfo),
"vkBeginCommandBuffer(compute)");
BeginDebugLabel(_commandBuffer, resources.DebugName);
if (isFirstBatch)
{
RecordTextureUploads(resources, PipelineStageFlags.ComputeShaderBit);
RecordStorageImagesForWrite(resources, PipelineStageFlags.ComputeShaderBit);
}
_vk.CmdBindPipeline(
_commandBuffer,
PipelineBindPoint.Compute,
resources.PipelineLayout,
0,
1,
&descriptorSet,
0,
null);
resources.Pipeline);
if (resources.DescriptorSet.Handle != 0)
{
var descriptorSet = resources.DescriptorSet;
_vk.CmdBindDescriptorSets(
_commandBuffer,
PipelineBindPoint.Compute,
resources.PipelineLayout,
0,
1,
&descriptorSet,
0,
null);
}
RecordChunkedComputeDispatch(_commandBuffer, work, zStart, zCount);
if (isLastBatch)
{
RecordStorageImagesForRead(resources, PipelineStageFlags.ComputeShaderBit);
}
EndDebugLabel(_commandBuffer);
Check(_vk.EndCommandBuffer(_commandBuffer), "vkEndCommandBuffer(compute)");
TraceVulkanShader(
$"vk.compute_submit cs=0x{work.ShaderAddress:X16} " +
$"batch={batchIndex}/{batchCount} z={zStart}..{zStart + zCount}");
if (isLastBatch)
{
SubmitGuestCommandBuffer(
commandBuffer,
resources,
GetTraceImages(resources));
submitted = true;
}
else
{
SubmitGuestCommandBufferAndWait(commandBuffer);
commandBuffer = default;
}
}
RecordChunkedComputeDispatch(_commandBuffer, work);
RecordStorageImagesForRead(resources, PipelineStageFlags.ComputeShaderBit);
EndDebugLabel(_commandBuffer);
Check(_vk.EndCommandBuffer(_commandBuffer), "vkEndCommandBuffer(compute)");
SubmitGuestCommandBuffer(
commandBuffer,
resources,
GetTraceImages(resources));
submitted = true;
MarkSampledImagesInitialized(resources);
MarkStorageImagesInitialized(resources, traceContents: false);
TraceVulkanShader(
$"vk.compute_dispatch groups={work.GroupCountX}x" +
$"{work.GroupCountY}x{work.GroupCountZ} " +
$"textures={work.Textures.Count} cs=0x{work.ShaderAddress:X16}");
$"textures={work.Textures.Count} cs=0x{work.ShaderAddress:X16} " +
$"batches={batchCount}");
}
catch (Exception exception)
{
Console.Error.WriteLine(
$"[LOADER][ERROR] Vulkan compute dispatch failed: {exception.Message}");
$"[LOADER][ERROR] Vulkan compute dispatch failed " +
$"cs=0x{work.ShaderAddress:X16}: {exception.Message}");
}
finally
{
@@ -3851,7 +4157,9 @@ internal static unsafe class VulkanVideoPresenter
private void RecordChunkedComputeDispatch(
CommandBuffer commandBuffer,
VulkanComputeGuestDispatch work)
VulkanComputeGuestDispatch work,
uint zStart,
uint zCount)
{
const uint maxWorkgroupsPerCommand = 4096;
var yChunk = Math.Max(
@@ -3861,7 +4169,7 @@ internal static unsafe class VulkanVideoPresenter
maxWorkgroupsPerCommand / Math.Max(work.GroupCountX, 1u)));
var commandCount = 0u;
for (var z = 0u; z < work.GroupCountZ; z++)
for (var z = zStart; z < zStart + zCount; z++)
{
for (var y = 0u; y < work.GroupCountY; y += yChunk)
{
@@ -3883,7 +4191,7 @@ internal static unsafe class VulkanVideoPresenter
TraceVulkanShader(
$"vk.compute_chunked cs=0x{work.ShaderAddress:X16} " +
$"groups={work.GroupCountX}x{work.GroupCountY}x{work.GroupCountZ} " +
$"commands={commandCount} y_chunk={yChunk}");
$"z_range={zStart}..{zStart + zCount} commands={commandCount} y_chunk={yChunk}");
}
}
@@ -4091,6 +4399,23 @@ internal static unsafe class VulkanVideoPresenter
existing.MipLevels == mipLevels &&
existing.Format == format)
{
if (existing.RenderPass.Handle == 0)
{
var attachmentView = existing.MipViews.Length > 0
? existing.MipViews[0]
: existing.View;
var (promotedRenderPass, promotedFramebuffer) = CreateRenderPassAndFramebuffer(
existing.Format,
attachmentView,
existing.Width,
existing.Height);
existing.RenderPass = promotedRenderPass;
existing.Framebuffer = promotedFramebuffer;
var promotedName = GuestImageDebugName(target, format);
SetDebugName(ObjectType.RenderPass, promotedRenderPass.Handle, $"{promotedName} renderpass");
SetDebugName(ObjectType.Framebuffer, promotedFramebuffer.Handle, $"{promotedName} framebuffer");
}
return existing;
}
@@ -4171,6 +4496,48 @@ internal static unsafe class VulkanVideoPresenter
mipViews[mipLevel] = mipView;
}
var (renderPass, framebuffer) = CreateRenderPassAndFramebuffer(
format,
mipViews[0],
target.Width,
target.Height);
var resource = new GuestImageResource
{
Address = target.Address,
Width = target.Width,
Height = target.Height,
MipLevels = mipLevels,
Format = format,
Image = image,
Memory = memory,
View = view,
MipViews = mipViews,
RenderPass = renderPass,
Framebuffer = framebuffer,
};
var debugName = GuestImageDebugName(target, format);
SetDebugName(ObjectType.Image, image.Handle, $"{debugName} image");
SetDebugName(ObjectType.ImageView, view.Handle, $"{debugName} view");
for (var mipLevel = 0; mipLevel < mipViews.Length; mipLevel++)
{
SetDebugName(
ObjectType.ImageView,
mipViews[mipLevel].Handle,
$"{debugName} mip{mipLevel}");
}
SetDebugName(ObjectType.RenderPass, renderPass.Handle, $"{debugName} renderpass");
SetDebugName(ObjectType.Framebuffer, framebuffer.Handle, $"{debugName} framebuffer");
_guestImages.Add(target.Address, resource);
return resource;
}
private (RenderPass RenderPass, Framebuffer Framebuffer) CreateRenderPassAndFramebuffer(
Format format,
ImageView attachmentView,
uint width,
uint height)
{
var colorAttachment = new AttachmentDescription
{
Format = format,
@@ -4205,49 +4572,22 @@ internal static unsafe class VulkanVideoPresenter
_vk.CreateRenderPass(_device, &renderPassInfo, null, out var renderPass),
"vkCreateRenderPass(offscreen)");
var attachment = mipViews[0];
var attachment = attachmentView;
var framebufferInfo = new FramebufferCreateInfo
{
SType = StructureType.FramebufferCreateInfo,
RenderPass = renderPass,
AttachmentCount = 1,
PAttachments = &attachment,
Width = target.Width,
Height = target.Height,
Width = width,
Height = height,
Layers = 1,
};
Check(
_vk.CreateFramebuffer(_device, &framebufferInfo, null, out var framebuffer),
"vkCreateFramebuffer(offscreen)");
var resource = new GuestImageResource
{
Address = target.Address,
Width = target.Width,
Height = target.Height,
MipLevels = mipLevels,
Format = format,
Image = image,
Memory = memory,
View = view,
MipViews = mipViews,
RenderPass = renderPass,
Framebuffer = framebuffer,
};
var debugName = GuestImageDebugName(target, format);
SetDebugName(ObjectType.Image, image.Handle, $"{debugName} image");
SetDebugName(ObjectType.ImageView, view.Handle, $"{debugName} view");
for (var mipLevel = 0; mipLevel < mipViews.Length; mipLevel++)
{
SetDebugName(
ObjectType.ImageView,
mipViews[mipLevel].Handle,
$"{debugName} mip{mipLevel}");
}
SetDebugName(ObjectType.RenderPass, renderPass.Handle, $"{debugName} renderpass");
SetDebugName(ObjectType.Framebuffer, framebuffer.Handle, $"{debugName} framebuffer");
_guestImages.Add(target.Address, resource);
return resource;
return (renderPass, framebuffer);
}
private static uint ClampMipLevels(uint width, uint height, uint requestedMipLevels)
@@ -5956,6 +6296,10 @@ internal static unsafe class VulkanVideoPresenter
return;
}
if (_debugUtils is not null && _debugMessenger.Handle != 0)
{
_debugUtils.DestroyDebugUtilsMessenger(_instance, _debugMessenger, null);
}
_vulkanReady = false;
_vk.DeviceWaitIdle(_device);
CollectCompletedGuestSubmissions(waitForOldest: false);
+10
View File
@@ -11,6 +11,16 @@
"Silk.NET.Core": "2.23.0"
}
},
"Silk.NET.Vulkan.Extensions.EXT": {
"type": "Direct",
"requested": "[2.23.0, )",
"resolved": "2.23.0",
"contentHash": "+Oth189ksRiL6HvGCwIdnsYHawqrbO8y49u1H61z3wsfcHhQZeVDYe/wF5LD7fk3NcdgDvwFD3mLm1QWhdZySw==",
"dependencies": {
"Silk.NET.Core": "2.23.0",
"Silk.NET.Vulkan": "2.23.0"
}
},
"Silk.NET.Vulkan.Extensions.KHR": {
"type": "Direct",
"requested": "[2.23.0, )",