fix(render): #459 — enable shaderDemoteToHelperInvocation, the feature the SPIR-V 1.6 fragment modules declare

The shader compiler targets Vulkan 1.3 / SPIR-V 1.6, where glslang lowers a
fragment discard to OpDemoteToHelperInvocation; five committed modules
(sky, mesh_detail, mesh_atmospheric, both directional-shadow cutouts)
declare the capability, and vkCreateShaderModule reported
VUID-VkShaderModuleCreateInfo-pCode-08740 twice per launch under the
Khronos validation layer because the device never enabled the feature.
The feature is core 1.3 (the device gate already requires 1.3), so it is
read through the existing Vulkan13Features chain, enabled at device
creation next to dynamicRendering/synchronization2/maintenance4, and
gated as required in VulkanCapabilityRecord with its own failure text.
Found by the first validation-layer self-gate of Campaign OVERHAUL v2
(S3 chunk 4 round 2); no register row, no behavior change on a
conformant driver.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-09-03 17:10:00 +02:00
parent c64ecbde8b
commit e33fd24724
2 changed files with 22 additions and 0 deletions

View file

@ -84,6 +84,17 @@ internal sealed record VulkanDeviceFeatureSupport
/// <summary>Relaxed shader interface rules for the dual-legal GLSL sources.</summary>
public required bool Maintenance4 { get; init; }
/// <summary>
/// #459: the shader compiler targets Vulkan 1.3 / SPIR-V 1.6, where glslang
/// lowers every fragment <c>discard</c> to <c>OpDemoteToHelperInvocation</c>
/// (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 (<c>VUID-VkShaderModuleCreateInfo-pCode-08740</c>), so the feature
/// is required and enabled, never optional.
/// </summary>
public required bool ShaderDemoteToHelperInvocation { get; init; }
/// <summary>
/// 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,
};
/// <summary>
@ -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)

View file

@ -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
{