diff --git a/src/AcDream.App/Rendering/Gpu/Vk/VulkanCapabilityRecord.cs b/src/AcDream.App/Rendering/Gpu/Vk/VulkanCapabilityRecord.cs index 5e9d5d42..e78874a0 100644 --- a/src/AcDream.App/Rendering/Gpu/Vk/VulkanCapabilityRecord.cs +++ b/src/AcDream.App/Rendering/Gpu/Vk/VulkanCapabilityRecord.cs @@ -84,6 +84,17 @@ internal sealed record VulkanDeviceFeatureSupport /// Relaxed shader interface rules for the dual-legal GLSL sources. public required bool Maintenance4 { get; init; } + /// + /// #459: the shader compiler targets Vulkan 1.3 / SPIR-V 1.6, where glslang + /// lowers every fragment discard to OpDemoteToHelperInvocation + /// (five committed modules declare the capability — sky, mesh_detail, + /// mesh_atmospheric and both directional-shadow cutout fragments). A module + /// that declares the capability without the feature enabled is invalid + /// usage (VUID-VkShaderModuleCreateInfo-pCode-08740), so the feature + /// is required and enabled, never optional. + /// + public required bool ShaderDemoteToHelperInvocation { get; init; } + /// /// Every feature present. The starting point for the forced-unsupported gate /// knob and for tests that assert one specific absence at a time. @@ -108,6 +119,7 @@ internal sealed record VulkanDeviceFeatureSupport DynamicRendering = true, Synchronization2 = true, Maintenance4 = true, + ShaderDemoteToHelperInvocation = true, }; /// @@ -139,6 +151,7 @@ internal sealed record VulkanDeviceFeatureSupport var n when Is(n, nameof(DynamicRendering)) => this with { DynamicRendering = false }, var n when Is(n, nameof(Synchronization2)) => this with { Synchronization2 = false }, var n when Is(n, nameof(Maintenance4)) => this with { Maintenance4 = false }, + var n when Is(n, nameof(ShaderDemoteToHelperInvocation)) => this with { ShaderDemoteToHelperInvocation = false }, _ => null, }; @@ -521,6 +534,8 @@ internal static class VulkanCapabilityRequirements failures.Add("synchronization2 is required; every barrier in the frame is a barrier2."); if (!features.Maintenance4) failures.Add("maintenance4 is required for the relaxed shader interface rules the shared GLSL relies on."); + if (!features.ShaderDemoteToHelperInvocation) + failures.Add("shaderDemoteToHelperInvocation is required; the SPIR-V 1.6 fragment modules lower discard to OpDemoteToHelperInvocation (#459)."); VulkanDeviceLimitSupport limits = capabilities.Limits; if (limits.MaxPushConstantsSize < GpuBindingModel.PushConstantBytes) diff --git a/src/AcDream.App/Rendering/Gpu/Vk/VulkanInterop.cs b/src/AcDream.App/Rendering/Gpu/Vk/VulkanInterop.cs index 3dae155b..5788a292 100644 --- a/src/AcDream.App/Rendering/Gpu/Vk/VulkanInterop.cs +++ b/src/AcDream.App/Rendering/Gpu/Vk/VulkanInterop.cs @@ -330,6 +330,7 @@ internal static unsafe class VulkanPhysicalDeviceInspector DynamicRendering = vulkan13.DynamicRendering, Synchronization2 = vulkan13.Synchronization2, Maintenance4 = vulkan13.Maintenance4, + ShaderDemoteToHelperInvocation = vulkan13.ShaderDemoteToHelperInvocation, }; } @@ -609,6 +610,12 @@ internal sealed unsafe class VulkanLogicalDeviceFactory DynamicRendering = true, Synchronization2 = true, Maintenance4 = true, + // #459: the SPIR-V 1.6 fragment modules declare + // DemoteToHelperInvocation (glslang's lowering of `discard` for a + // Vulkan 1.3 target); declaring it without the feature is invalid + // usage (VUID-VkShaderModuleCreateInfo-pCode-08740). Gated as + // required in VulkanCapabilityRecord, so this is never speculative. + ShaderDemoteToHelperInvocation = true, }; var vulkan12 = new PhysicalDeviceVulkan12Features {