From 7b30868b03dad4748960429b6b9f7c6521107f8e Mon Sep 17 00:00:00 2001 From: ParantezTech <12572227+par274@users.noreply.github.com> Date: Thu, 8 Oct 2026 17:31:37 +0300 Subject: [PATCH] [SpirvTranslator] Preserve F32 signed zero, infinity and NaN when supported Co-authored-by: Sekai <140012992+SekaixX@users.noreply.github.com> --- .../Gpu/Pipelines/IShaderPipelineHost.cs | 1 + .../Gpu/Pipelines/ShaderProgramCache.cs | 14 ++++++++++---- .../VideoOut/VulkanVideoPresenter.Device.Setup.cs | 13 ++++++++++++- .../VideoOut/VulkanVideoPresenter.Pipelines.cs | 2 ++ .../VideoOut/VulkanVideoPresenter.ShaderPrewarm.cs | 3 ++- .../Gen5SpirvTranslator.cs | 12 ++++++++++++ .../SpirvModuleBuilder.cs | 2 ++ .../ShaderCompileRequest.cs | 1 + 8 files changed, 42 insertions(+), 6 deletions(-) diff --git a/src/SharpEmu.Libs/Gpu/Pipelines/IShaderPipelineHost.cs b/src/SharpEmu.Libs/Gpu/Pipelines/IShaderPipelineHost.cs index a120cba..2eff4e4 100644 --- a/src/SharpEmu.Libs/Gpu/Pipelines/IShaderPipelineHost.cs +++ b/src/SharpEmu.Libs/Gpu/Pipelines/IShaderPipelineHost.cs @@ -41,6 +41,7 @@ internal interface IShaderPipelineHost // The device supports shaderSharedInt64Atomics, so LDS 64-bit atomics can be // emitted as real 64-bit atomics instead of a non-atomic 32-bit pair. bool SharedInt64AtomicsEnabled { get; } + bool ShaderSignedZeroInfNanPreserveFloat32Supported => false; bool ExecGuardElisionEnabled => true; ShaderPrewarmList? ShaderPrewarm => null; bool PerVertexPixelInputsSupported => true; diff --git a/src/SharpEmu.Libs/Gpu/Pipelines/ShaderProgramCache.cs b/src/SharpEmu.Libs/Gpu/Pipelines/ShaderProgramCache.cs index 34f61c3..611b9c8 100644 --- a/src/SharpEmu.Libs/Gpu/Pipelines/ShaderProgramCache.cs +++ b/src/SharpEmu.Libs/Gpu/Pipelines/ShaderProgramCache.cs @@ -593,6 +593,7 @@ internal sealed class ShaderProgramCache { var enableGraphicsSubgroups = _host.GraphicsSubgroupOperationsEnabled; var sharedInt64Atomics = _host.SharedInt64AtomicsEnabled; + var signedZeroInfNanPreserve = _host.ShaderSignedZeroInfNanPreserveFloat32Supported; switch (source.Stage) { case ShaderStage.Vertex: @@ -605,6 +606,7 @@ internal sealed class ShaderProgramCache ScratchDwords = info.ScratchDwords, EnableGraphicsSubgroupOperations = enableGraphicsSubgroups, SupportsSharedInt64Atomics = sharedInt64Atomics, + ShaderSignedZeroInfNanPreserveFloat32Supported = signedZeroInfNanPreserve, RequiredVertexOutputCount = options.RequiredVertexOutputCount, VertexInputs = entry.VertexInputs, PositionExportControl = info.PositionExportControl, @@ -632,6 +634,7 @@ internal sealed class ShaderProgramCache ScratchDwords = info.ScratchDwords, EnableGraphicsSubgroupOperations = enableGraphicsSubgroups, SupportsSharedInt64Atomics = sharedInt64Atomics, + ShaderSignedZeroInfNanPreserveFloat32Supported = signedZeroInfNanPreserve, PixelOutputs = options.PixelOutputs, PixelInputEnable = options.PixelInputEnable, PixelCustomInterpolationMask = info.CustomInterpolationMask, @@ -643,7 +646,7 @@ internal sealed class ShaderProgramCache default: return BuildComputeRequest(entry.Plan, resources, layout, options.ComputeInfo!, options.ComputeSystemRegisters, - sharedInt64Atomics, _host.ExecGuardElisionEnabled); + sharedInt64Atomics, _host.ExecGuardElisionEnabled, signedZeroInfNanPreserve); } } @@ -659,7 +662,8 @@ internal sealed class ShaderProgramCache usesDispatchThreadLimits: usesDispatchThreadLimits); private static ShaderCompileRequest BuildComputeRequest(ShaderResourcePlan plan, SpecializedResourceInfo resources, BindingLayout layout, - ComputeInputInfo info, Gen5ComputeSystemRegisters? systemRegisters, bool sharedInt64Atomics, bool execGuardElision) => + ComputeInputInfo info, Gen5ComputeSystemRegisters? systemRegisters, bool sharedInt64Atomics, bool execGuardElision, + bool signedZeroInfNanPreserve) => new(plan, resources, layout) { WaveSize = info.WaveSize, @@ -667,6 +671,7 @@ internal sealed class ShaderProgramCache TraceDeviceAddressFaults = SharpEmu.HLE.GpuMemory.GuestGpuMemoryHook.TraceEnabled, ScratchDwords = info.ScratchDwords, SupportsSharedInt64Atomics = sharedInt64Atomics, + ShaderSignedZeroInfNanPreserveFloat32Supported = signedZeroInfNanPreserve, ComputeSystemRegisters = systemRegisters, LocalDataShareDwords = info.LocalDataShareDwords, LocalSizeX = Math.Max(info.ThreadsX, 1), @@ -675,7 +680,8 @@ internal sealed class ShaderProgramCache }; internal static bool TryCompilePrewarm(ComputePrewarmRecord record, ShaderCodeCapture code, IGuestGpuBackend compiler, - bool sharedInt64Atomics, bool execGuardElision, out IGuestCompiledShader? compiled, out BindingLayout? layout, out string error) + bool sharedInt64Atomics, bool execGuardElision, bool signedZeroInfNanPreserve, out IGuestCompiledShader? compiled, + out BindingLayout? layout, out string error) { compiled = null; layout = null; @@ -692,7 +698,7 @@ internal sealed class ShaderProgramCache layout = AllocateLayout(program, plan, resources, record.UserDataBase, record.UserDataCount, record.PushDataCursor, record.Info.DispatchThreadDimensions); var request = BuildComputeRequest(plan, resources, layout, record.Info, record.SystemRegisters, - sharedInt64Atomics, execGuardElision); + sharedInt64Atomics, execGuardElision, signedZeroInfNanPreserve); return compiler.TryCompileProgram(request, out compiled, out error) && compiled is not null; } catch (Exception exception) diff --git a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Device.Setup.cs b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Device.Setup.cs index 7f5333f..9d0d1d0 100644 --- a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Device.Setup.cs +++ b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Device.Setup.cs @@ -45,6 +45,7 @@ internal static unsafe partial class VulkanVideoPresenter private ulong _minStorageBufferOffsetAlignment = 1; private bool _supportsIndependentBlend; private bool _supportsDepthBiasClamp; + private bool _supportsShaderSignedZeroInfNanPreserveFloat32; private uint _maxColorAttachments; private Device _device; private PipelineCache _pipelineCache; @@ -583,10 +584,15 @@ internal static unsafe partial class VulkanVideoPresenter { SType = StructureType.PhysicalDevicePushDescriptorPropertiesKhr, }; + var floatControls = new PhysicalDeviceFloatControlsProperties + { + SType = StructureType.PhysicalDeviceFloatControlsProperties, + PNext = &pushDescriptorProperties, + }; var subgroupSizeControl = new PhysicalDeviceSubgroupSizeControlProperties { SType = StructureType.PhysicalDeviceSubgroupSizeControlProperties, - PNext = &pushDescriptorProperties, + PNext = &floatControls, }; var subgroup = new PhysicalDeviceSubgroupProperties { @@ -599,6 +605,8 @@ internal static unsafe partial class VulkanVideoPresenter PNext = &subgroup, }; _vk.GetPhysicalDeviceProperties2(_physicalDevice, &properties2); + _supportsShaderSignedZeroInfNanPreserveFloat32 = + floatControls.ShaderSignedZeroInfNanPreserveFloat32; SetNativeSubgroupCapabilities(subgroup.SubgroupSize, subgroup.SupportedStages); _canRequireComputeSubgroup32 = subgroup.SubgroupSize != RdnaSubgroupSize && @@ -657,6 +665,9 @@ internal static unsafe partial class VulkanVideoPresenter _ when GraphicsSubgroupOperationsEnabled => "auto-native-wave32", _ => "auto-fallback", }; + Console.Error.WriteLine( + $"[LOADER][INFO] Vulkan shaderSignedZeroInfNanPreserveFloat32 " + + $"enabled={_supportsShaderSignedZeroInfNanPreserveFloat32}"); Console.Error.WriteLine( $"[LOADER][INFO] Vulkan graphics subgroup operations " + $"enabled={GraphicsSubgroupOperationsEnabled} " + diff --git a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Pipelines.cs b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Pipelines.cs index caa42db..3b0e354 100644 --- a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Pipelines.cs +++ b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Pipelines.cs @@ -87,6 +87,8 @@ internal static unsafe partial class VulkanVideoPresenter bool IShaderPipelineHost.GraphicsSubgroupOperationsEnabled => GraphicsSubgroupOperationsEnabled; bool IShaderPipelineHost.SharedInt64AtomicsEnabled => SharedInt64AtomicsEnabled; + bool IShaderPipelineHost.ShaderSignedZeroInfNanPreserveFloat32Supported => + _supportsShaderSignedZeroInfNanPreserveFloat32; // NVIDIA's compiler rejects the elided-EXEC wave64 compute module with NVVM error 3. bool IShaderPipelineHost.ExecGuardElisionEnabled => _physicalDeviceVendorId != NvidiaVendorId; bool IShaderPipelineHost.PerVertexPixelInputsSupported => _supportsPerVertexPixelInputs; diff --git a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.ShaderPrewarm.cs b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.ShaderPrewarm.cs index 43a040b..d063eae 100644 --- a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.ShaderPrewarm.cs +++ b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.ShaderPrewarm.cs @@ -162,7 +162,8 @@ internal static unsafe partial class VulkanVideoPresenter bool execGuardElision) { if (!ShaderProgramCache.TryCompilePrewarm( - record, code, compiler, sharedInt64Atomics, execGuardElision, out var compiled, out var layout, out var error)) + record, code, compiler, sharedInt64Atomics, execGuardElision, + _supportsShaderSignedZeroInfNanPreserveFloat32, out var compiled, out var layout, out var error)) { NoteShaderPrewarmFailure(record, error); return; diff --git a/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.cs b/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.cs index 9eec5ba..73a8a16 100644 --- a/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.cs +++ b/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.cs @@ -518,6 +518,13 @@ public static partial class Gen5SpirvTranslator _ => SpirvExecutionModel.GLCompute, }; _module.AddEntryPoint(model, main, "main", _interfaces); + if (_request.ShaderSignedZeroInfNanPreserveFloat32Supported) + { + _module.AddExecutionMode( + main, + SpirvExecutionMode.SignedZeroInfNanPreserve, + 32); + } if (_stage == Gen5SpirvStage.Pixel) { _module.AddExecutionMode(main, SpirvExecutionMode.OriginUpperLeft); @@ -555,6 +562,11 @@ public static partial class Gen5SpirvTranslator private void DeclareModule() { _module.AddCapability(SpirvCapability.Shader); + if (_request.ShaderSignedZeroInfNanPreserveFloat32Supported) + { + _module.AddExtension("SPV_KHR_float_controls"); + _module.AddCapability(SpirvCapability.SignedZeroInfNanPreserve); + } _module.AddCapability(SpirvCapability.Int64); _module.AddCapability(SpirvCapability.ImageQuery); if (UsesSubgroupOperations()) diff --git a/src/SharpEmu.ShaderCompiler.Vulkan/SpirvModuleBuilder.cs b/src/SharpEmu.ShaderCompiler.Vulkan/SpirvModuleBuilder.cs index c3c240a..0768ec7 100644 --- a/src/SharpEmu.ShaderCompiler.Vulkan/SpirvModuleBuilder.cs +++ b/src/SharpEmu.ShaderCompiler.Vulkan/SpirvModuleBuilder.cs @@ -213,6 +213,7 @@ public enum SpirvCapability : uint ShaderViewportIndexLayerExt = 5254, RuntimeDescriptorArray = 5302, PhysicalStorageBufferAddresses = 5347, + SignedZeroInfNanPreserve = 4466, } public enum SpirvStorageClass : uint @@ -242,6 +243,7 @@ public enum SpirvExecutionMode : uint OriginUpperLeft = 7, DepthReplacing = 12, LocalSize = 17, + SignedZeroInfNanPreserve = 4461, } public enum SpirvDecoration : uint diff --git a/src/SharpEmu.ShaderCompiler/ShaderCompileRequest.cs b/src/SharpEmu.ShaderCompiler/ShaderCompileRequest.cs index 32d8b11..2e64c02 100644 --- a/src/SharpEmu.ShaderCompiler/ShaderCompileRequest.cs +++ b/src/SharpEmu.ShaderCompiler/ShaderCompileRequest.cs @@ -171,6 +171,7 @@ public sealed class ShaderCompileRequest // 64-bit atomics are emitted as real 64-bit atomics instead of a pair of // 32-bit ones, which is not atomic as a pair. public bool SupportsSharedInt64Atomics { get; init; } + public bool ShaderSignedZeroInfNanPreserveFloat32Supported { get; init; } public Gen5ComputeSystemRegisters? ComputeSystemRegisters { get; init; } public IReadOnlyList PixelOutputs { get; init; } = [];