Skip to content

fix: keep one execution mode per entry point when a method overrides it - #3431

Merged
xen2 merged 2 commits into
stride3d:masterfrom
LazyWorksZA:fix/sdsl-entrypoint-override-execution-modes
Sep 13, 2026
Merged

xen2 merged 2 commits into
stride3d:masterfrom
LazyWorksZA:fix/sdsl-entrypoint-override-execution-modes

Conversation

@sasvdw

@sasvdw sasvdw commented Sep 13, 2026 •

Copy link
Copy Markdown
Contributor

PR Details

Each override of an entry point compiles with its own execution modes on its own function id. InterfaceProcessor.GenerateStreamWrapper moved only the modes of the most derived method to the wrapper. The base modes stayed on a function that is no longer an entry point, and spirv-val rejected the module while MergeSDSL returned true. A hull override without attributes failed earlier, because FindOutputPatchSize looked at the most derived method only.

Policy. The most derived declaration of each mode wins. An override that restates an attribute replaces the base value. An override that omits it keeps the base value. ComputeShaderBase.CSMain carries [numthreads(ThreadNumberX, ...)], so an override that only adds setup and calls base.CSMain() keeps its LocalSize. The stricter option, drop unless restated, is the same code without the walk to the base, so it is cheap to switch if preferred.

Change. ResolveOverrideChain returns the function ids of the entry point and the methods it overrides, base first. The method group also holds overloads and composition functions, so the chain keeps only members with the entry point's signature and skips composition functions, as ResolveEntryPoint does. GenerateStreamWrapper walks the chain from the most derived method down, moves the first declaration of each mode to the wrapper, and drops repeats. FindOutputPatchSize and ResolveHullPatchConstantEntryPoint use the same walk.

Tests. New cases in StrideShaderTests, each against a corpus shader:

  • CSNumThreadsOverride: the override restates [numthreads]. One LocalSize, the derived value.
  • CSNumThreadsInherit: the override omits it. One LocalSize, the base value.
  • HSOverride: the override declares no hull attributes. outputcontrolpoints and patchconstantfunc come from the base.
  • ComposeNumThreadsRoot and ComposeNumThreadsInheritRoot: the composed helper derives from the same base and carries its own [numthreads]. It stays out of the chain, so the root's value, or the base's, wins.

The override cases fail on master, two with the validator error and one in FindOutputPatchSize. The composition cases fail without the composition filter.

Related Issue

Fixes #3430

Types of changes

  • Bug fix (non-breaking change which fixes an issue)

Checklist

  • I have added tests to cover my changes.
  • All new and existing tests passed.
  • I have built and run the editor to try this change out.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VHYKv129fEUp6sxMbybZWf

@xen2

xen2 commented Sep 13, 2026

Copy link
Copy Markdown
Member

I think it needs a rebase (conflicts)

case Op.OpFunction when depth > 0:
result.Add(((OpFunction)i).ResultId);
break;
if ((FunctionType)member.Type == signature)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After the rebase, this should skip composition functions, like ResolveEntryPoint now does on master (#3382).

A composition that also derives from ComputeShaderBase has its own CSMain in the same group. Right now it would end up in the chain, and its [numthreads] could win over the real entry point's.

if ((FunctionType)member.Type == signature && !compositionFunctions.Contains(member.IdRef))

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, missed that. Done.

sasvdw and others added 2 commits September 13, 2026 14:52
Each override of an entry point compiles with its own execution modes on its
own function id. The interface processor moved only the modes of the most
derived method to the wrapper. The base modes stayed on a function that is no
longer an entry point, and spirv-val rejected the module. A hull override
without attributes failed earlier, because FindOutputPatchSize looked at the
most derived method only.

The processor now walks the override chain from the most derived method down.
The first declaration of each mode wins. Repeats from a base method are dropped.
The patch constant decoration and the output patch size use the same walk.

Fixes stride3d#3430

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VHYKv129fEUp6sxMbybZWf
A composition that derives from the same compute base has its own CSMain in
the method group. Without the filter it joined the chain and its [numthreads]
won over the entry point's. ResolveOverrideChain now skips the functions that
CollectCompositionFunctions found, as ResolveEntryPoint does.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VHYKv129fEUp6sxMbybZWf
@sasvdw
sasvdw force-pushed the fix/sdsl-entrypoint-override-execution-modes branch from 8556d6a to 09dc1c3 Compare September 13, 2026 12:57
@sasvdw

sasvdw commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author

I think it needs a rebase (conflicts)

rebased and ready for reveiw!

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Overriding an entry point keeps the base method's execution modes

2 participants