Skip to content

Fix #11197: accept SV_PrimitiveID as input in intersection, any-hit, and closest-hit shaders#11894

Draft
jkwak-work wants to merge 1 commit into
masterfrom
intersection-shader-primitiveID
Draft

Fix #11197: accept SV_PrimitiveID as input in intersection, any-hit, and closest-hit shaders#11894
jkwak-work wants to merge 1 commit into
masterfrom
intersection-shader-primitiveID

Conversation

@jkwak-work

Copy link
Copy Markdown
Collaborator

Fixes #11197

Motivation

GLSL_EXT_ray_tracing allows gl_PrimitiveID as an input in the intersection, any-hit, and closest-hit stages, but Slang rejects the equivalent SV_PrimitiveID input. Consider this example (the reproducer from #11197):

struct Attributes {}

[shader("intersection")]
void main(int primitiveId : SV_PrimitiveID) {
    Attributes attr = {};
    ReportHit<Attributes>(1, 0, attr);
}

Compiling this to SPIR-V fails with:

error[E30702]: system value semantic 'SV_PrimitiveID' cannot be used as input in 'intersection' shader stage

The only workaround is the PrimitiveIndex() intrinsic, which maps to the same value but is hard to discover for users porting GLSL ray-tracing shaders that read gl_PrimitiveID.

Proposed solution

Fixing the reported error exposed two more issues cascading behind it; each is fixed at the layer that produces the wrong representation, so no consumer needs to patch around it:

  1. Front-end validation (the reported E30702): the sv_primitiveid semantic declaration in core.meta.slang is the source of truth for which stages may read a system value. Its get accessor only listed the rasterization stages, so the fix is to extend it with [require(intersection)], [require(anyhit)], and [require(closesthit)] — exactly the three stages where GLSL_EXT_ray_tracing permits gl_PrimitiveID. Stages with no current primitive (raygeneration, miss, callable) remain rejected.

  2. Parameter binding (cascading E39018): entry-point layout interpreted every ray-tracing entry-point parameter as a payload/hit-attributes parameter by direction and position, before the system-value classification could run. The fix teaches the ray-tracing interpretation that a parameter carrying an ordinary system-value semantic is not a ray-tracing-role parameter; it takes the regular system-value layout path that every other stage already uses.

  3. GLSL/SPIR-V legalization (invalid SPIR-V): the ray-tracing entry-point legalization turned every varying parameter into a plain global parameter (correct for payload/attributes, wrong for system values — it produced a Private-storage variable decorated BuiltIn PrimitiveId, which fails SPIR-V validation). The fix routes system-value parameters through the standard varying-input legalization, which maps them to a proper Input-class builtin — the same machinery a fragment-shader SV_PrimitiveID input uses.

With all three fixes, the downstream SPIR-V emitter needs no changes: maybeEmitSystemVal (slang-emit-spirv.cpp:7624) already maps sv_primitiveid to BuiltIn PrimitiveId and already exempts the intersection/any-hit/closest-hit stages from the Geometry capability requirement, so the emit side had anticipated this input all along.

Change summary

File Change
source/slang/core.meta.slang Added [require(intersection)], [require(anyhit)], [require(closesthit)] to the get : uint accessor of semantic sv_primitiveid.
source/slang/slang-parameter-binding.cpp New predicate isRayTracingPayloadOrAttributesParameter gates the ray-tracing payload/attributes interpretation in processEntryPointVaryingParameter; extracted isSystemValueSemanticName so the sv_/nv_ prefix rule has one source of truth (also used by processSimpleEntryPointParameter).
source/slang/slang-ir-glsl-legalize.cpp consolidateRayTracingParameters skips parameters whose IRVarLayout carries a system-value semantic; the ray-tracing early-return in legalizeEntryPointParameterForGLSL lets those parameters continue to the standard varying-input path.
tests/vkray/intersection-primitive-id.slang Positive test: all three stages read SV_PrimitiveID; checks SPIR-V BuiltIn PrimitiveId on an Input-storage-class variable and gl_PrimitiveID in GLSL output.
tests/diagnostics/execution-model/sv-primitive-id-invalid-rt-stage.slang Negative test: SV_PrimitiveID input in a miss shader still produces E30702.

Concepts and vocabulary

  • Semantic accessor table: system-value semantics are declared in core.meta.slang as semantic sv_* declarations whose get/set accessors carry [require(stage)] attributes; validateSystemValueSemanticForType (slang-check-shader.cpp) walks these accessors to decide whether a semantic is readable/writable in a given stage. This table — not the validation code — owns the per-stage policy.
  • Ray-tracing varying roles: ray-tracing entry-point parameters are identified by direction and position, not by semantic: an inout/out parameter is the ray payload (or callable payload), an in parameter of a hit shader is the hit attributes. SV_RayPayload and SV_IntersectionAttributes are SV-prefixed semantics that explicitly mark these same roles (see tests/spirv/c-layout-buffer-2.slang).
  • System-value layout: processSimpleEntryPointParameter gives a system-value parameter a layout that consumes no varying location slots and records the semantic in varLayout->systemValueSemantic, which lowers to IRSystemValueSemanticAttr on the parameter's IRVarLayout.

Process report

Change 1 — core.meta.slang accessor stages (the reported error).
validateSystemValueSemanticForType (slang-check-shader.cpp:116) looks up semantic sv_primitiveid and walks its accessors; for each get accessor it checks the merged [require(...)] capability set against the entry point's stage via capabilitySet->isIncompatibleWith(getAtomFromStage(stage)). The accessor listed only fragment, geometry, hull, and domain, so for an intersection shader no accessor matched and the E30702 diagnostic fired. The intersection/anyhit/closesthit capability aliases (slang-capabilities.capdef:1476-1484) expand to the _intersection/_anyhit/_closesthit stage atoms joined with the raytracing targets, so adding those three [require(...)] attributes makes the stage check pass exactly where the GLSL spec allows gl_PrimitiveID. The reproducer declares int while the accessor declares uint; isSemanticTypeCompatible (slang-check-shader.cpp:78) accepts this because int coerces to uint with matching scalar shape. No new stage is opened for writing (set remains geometry/mesh), and a new diagnostic test pins that miss/raygeneration inputs still fail.

Change 2 — parameter binding (cascading E39018).
With change 1 alone, the reproducer fails with error[E39018]: the 'intersection' stage does not support 'in' entry point parameters. The producer is processEntryPointVaryingParameter (slang-parameter-binding.cpp): for ray-tracing stages it classifies parameters by direction — input in a hit shader becomes hit attributes, input in intersection/raygeneration/miss/callable is diagnosed, output becomes payload — and this runs before processSimpleEntryPointParameter ever sees the SV_ prefix.

Input-shape check: is "an entry-point parameter with a system-value semantic in a ray-tracing stage" a shape this branch should claim? No. The ray-tracing interpretation exists to assign the payload/attributes roles, which are marked either implicitly (no semantic, or a user semantic, resolved by direction and position) or explicitly by SV_RayPayload/SV_IntersectionAttributes. A parameter carrying any other system-value semantic is a builtin input, and the per-stage policy for builtins is already owned by the accessor table from change 1. So the correct producer-side fix is for the layout code to leave such parameters to the regular system-value path — not for any downstream consumer to undo a payload/attributes misclassification.

The new isRayTracingPayloadOrAttributesParameter encodes exactly that rule and gates the existing if/else. Behavior is unchanged for every previously-accepted program: parameters without semantics or with user semantics keep the ray-tracing path (e.g. tests/diagnostics/execution-model/stage-incompatible-out-param.slang, where out float3 x : MY_SEMANTIC in an intersection shader still produces E39017), and SV_RayPayload/SV_IntersectionAttributes keep it too (e.g. tests/spirv/c-layout-buffer-2.slang). Non-ray-tracing stages are unaffected because both gated switches were no-ops for them. The sv_/nv_ prefix test previously appeared inline in processSimpleEntryPointParameter; it is extracted into isSystemValueSemanticName so the classification rule lives in one place.

Change 3 — GLSL/SPIR-V legalization (invalid SPIR-V).
With changes 1 and 2, compilation succeeds but SPIR-V validation fails: Vulkan spec allows BuiltIn PrimitiveId to be only used for variables with Input or Output storage class ... uses storage class Private, and the GLSL target emits a bare uint primitiveId_0; global instead of gl_PrimitiveID. The producer is the ray-tracing branch of the GLSL legalization pass: consolidateRayTracingParameters (slang-ir-glsl-legalize.cpp:3118) claims every varying parameter of a ray-tracing entry point and hands it to handleSingleParam, which creates a plain global parameter — correct for payload/attributes (their storage classes come from their own decorations) but wrong for a system value, which needs the builtin mapping. Meanwhile legalizeEntryPointParameterForGLSL early-returns for ray-tracing stages, so the standard varying path — createGLSLGlobalVaryings, whose getGLSLSystemValueInfo (slang-ir-glsl-legalize.cpp:730) maps sv_primitiveid to gl_PrimitiveID with required type int and an Input-class builtin variable — never runs.

Input-shape check: same answer as change 2, applied at the IR layer. The canonical representation of a system-value input is the varying layout with IRSystemValueSemanticAttr produced by parameter binding; the ray-tracing consolidation should not rewrite it into a plain global. Both edits consult that existing attribute (paramLayout->findSystemValueSemanticAttr()) rather than re-deriving anything: the consolidation skips such parameters, and the early-return lets them continue into the same default varying-input path a fragment-shader SV_PrimitiveID input takes. After this, the SPIR-V emitter's existing maybeEmitSystemVal handling produces %gl_PrimitiveID = OpVariable %_ptr_Input_int Input decorated BuiltIn PrimitiveId, and validation passes; the GLSL target emits gl_PrimitiveID with the int→uint adaptation.

Target notes.

  • CUDA/OptiX: the reproducer is rejected by a pre-existing capability diagnostic on ReportHit (E36107) independent of this change; PrimitiveIndex()optixGetPrimitiveIndex remains the OptiX route.
  • HLSL/DXIL: DXR forbids extra entry-point parameters, and Slang emits the parameter as-is (DXC rejects it downstream). This matches the existing behavior of Khronos-only semantics such as SV_PointCoord and SV_DrawIndex on D3D targets. Legalizing ray-tracing system-value parameters into PrimitiveIndex() calls on D3D would require a varying-parameter legalization pass for HLSL, which does not exist today; left as a follow-up.

Validation. Full slang-test run: 11150/11151 passed; the one failure (tests/dispatcher/smoke.slang) was a local build-scope artifact (the slang dispatcher binary had not been built) and passes after building it.

@jkwak-work jkwak-work added pr: non-breaking PRs without breaking changes CoPilot labels Jul 2, 2026
@jkwak-work jkwak-work self-assigned this Jul 2, 2026
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

An error occurred during the review process. Please try again later.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@jkwak-work

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CoPilot pr: non-breaking PRs without breaking changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Slang does not accept SV_PrimitiveID as input to intersection shaders

2 participants