stride3d/stride · error · InvalidOperationException

importing stage-only methods/variables is only possible at…

Error message

importing stage-only methods/variables is only possible at the root mixin

What it means

Importing stage-only methods/variables (shaderClass.ImportStageOnly, SDSL `import stage`) is only legal at the root mixin — the node with no Stage. MergeClassInBuffers enforces this and throws InvalidOperationException when such an import appears in a nested/staged mixin, because stage-only symbols cannot be replicated into a non-root stage context.

Solutions

  1. Move the `import stage` shader to the root of the mixin tree
  2. Remove `import stage` (use regular import) if stage-only symbols aren't required
  3. Restructure composition so stage-only symbols are only referenced from the root mixin

Example fix

// before
var nested = new ShaderMixinSource { Mixins = { importStageShader } }; // used as composition of root
// after
var root = new ShaderMixinSource { Mixins = { importStageShader, otherMixin } }; // import stage at root
Defensive patterns

Strategy: try-catch

Validate before calling

if (shaderClass.ImportStageOnly && mixinNode.Stage != null)
    throw new InvalidOperationException("import stage shader must be at root mixin");

Try / catch

try { result = mixer.MergeSDSL(tree); }
catch (InvalidOperationException ex) when (ex.Message.Contains("importing stage-only")) {
    moveShaderToRootMixin();
}

Prevention

When it happens

Trigger: A shader using `import stage` (or ImportStageOnly=true) is mixed at a non-root node (mixinNode.Stage != null) during MergeClassInBuffers via shaderInfo; composing an `import stage` shader into a nested composition path.

Common situations: Applying an `import stage` utility shader as a nested composition instead of the root; inheritance chains where a mid-level shader imports stage-only symbols; refactor moving an `import stage` shader deeper into the tree.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14). Data as JSON: /api/errors/62bfbe8fac1ca133. Report an issue: GitHub.

Appendix: source

Thrown at sources/shaders/Stride.Shaders.Compilers/SDSL/ShaderMixer.cs:421

    }

    private static string ComposeLinkName(string linkName, string? compositionPath = null)
    {
        if (compositionPath != null)
            linkName += $".{compositionPath}";
        return linkName;
    }

    // Append CompositionPath to "Link" for any non-stage variable
    // Also force-emit the missing "Link" decorations

    private ShaderInfo MergeClassInBuffers(MixinGlobalContext globalContext, SpirvContext context, SpirvBuffer buffer, MixinNode mixinNode, ShaderClassInstantiation shaderClass, TypeDuplicateHelper typeDuplicateInserter)
    {
        var isRootMixin = mixinNode.Stage == null;
        if (shaderClass.ImportStageOnly)
        {
            if (!isRootMixin)
                throw new InvalidOperationException("importing stage-only methods/variables is only possible at the root mixin");
        }

        var shaderBuffers = shaderClass.Buffer ?? throw new InvalidOperationException($"Shader buffers not loaded for {shaderClass.ClassName}");
        var offset = context.Bound;
        var resourceGroupOffset = context.ResourceGroupBound;

        // Remember when we started to add instructions in both context and main buffer
        var shaderStart = buffer.Count;
        var contextStart = context.Count;
        var names = new Dictionary<int, string>();

        var forbiddenIds = new HashSet<int>();
        var remapIds = new Dictionary<int, int>();
        var removedIds = new HashSet<int>();

        bool isContext = true;

        // Note: FunctionType is only required when looking for stage function

View on GitHub (pinned to 96fad776d2)