stride3d/stride · error · InvalidOperationException

Invalid state for

Error message

Invalid state for {resource.CommandListFenceValue}

What it means

After confirming no other unsubmitted command list owns the staging resource, Map expects a recorded CommandListFenceValue to wait on. If it is null, internal state is inconsistent (no fence value was recorded for the last submission), so the code throws InvalidOperationException with the interpolated value.

Solutions

  1. Ensure the staging resource was last written via CommandList.Update on a properly submitted command list so the fence value is recorded.
  2. Don't manually clear/reset resource.CommandListFenceValue or device fences in application code.
  3. Recreate the staging resource if its tracking state is suspected corrupt.
  4. If reproducible with straightforward API usage, report as an engine bug (this is an internal invariant).

Example fix

// before
resource.CommandListFenceValue = null; // manual clearing corrupts state
commandList.Map(resource, MapMode.Read, 0); // throws
// after
// let the engine manage fence values; update then map:
commandList.Update(resource, data);
commandList.Flush();
commandList.Map(resource, MapMode.Read, 0);
Defensive patterns

Strategy: try-catch

Validate before calling

if (resource.CommandListFenceValue is null && resource.UpdatingCommandList is null)
{
    // no tracked submission: refresh state by recreating or re-uploading the resource
}

Try / catch

try { commandList.Map(stagingResource, MapMode.Read, 0); }
catch (InvalidOperationException ex) when (ex.Message.StartsWith("Invalid state"))
{ /* fence bookkeeping corrupt: recreate resource or re-run Update path */ }

Prevention

When it happens

Trigger: Mapping a staging resource whose last updating command list was submitted but for which CommandListFenceValue was never set — typically after misuse of low-level flush APIs, resetting fences manually, or skipping the normal Update path that records the fence value.

Common situations: Custom render-loop code that submits command lists directly without going through the resource-tracking helpers; resource reused across device resets/recreation where fence bookkeeping was cleared; engine-level bugs.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

Thrown at sources/engine/Stride.Graphics/Direct3D12/CommandList.Direct3D12.cs:2289

                    // used in command list which hasn't be submitted yet? (only valid if our own, checked later)
                    resource.UpdatingCommandList is not null
                    // updated in a previous command list which hasn't be finished yet
                    || (resource.CommandListFenceValue is not null && !GraphicsDevice.CommandListFence.IsFenceCompleteInternal(resource.CommandListFenceValue.Value)))
                {
                    if (doNotWait)
                    {
                        return new MappedResource(resource, subResourceIndex, dataBox: default);
                    }

                    if (resource.UpdatingCommandList == this)
                        // Need to flush? (check if part of current Command List)
                        // resource.CommandListFenceValue should be set after
                        FlushInternal(wait: false);
                    else if (resource.UpdatingCommandList is not null)
                        throw new InvalidOperationException("CommandList updating the staging resource has not been submitted");

                    if (resource.CommandListFenceValue is null)
                        throw new InvalidOperationException($"Invalid state for {resource.CommandListFenceValue}");

                    GraphicsDevice.CommandListFence.WaitForFenceCPUInternal(resource.CommandListFenceValue.Value);

                    // We're now up to date, remove command list fence value (if any)
                    resource.CommandListFenceValue = null;
                }
            }

            // Also make sure all copy queues are done
            // (important for all cases, since it uploads initial data and also set resource barrier)
            if (resource.CopyFenceValue.HasValue)
            {
                GraphicsDevice.CopyFence.WaitForFenceCPUInternal(resource.CopyFenceValue.Value);
                resource.CopyFenceValue = null;
            }

            scoped ref var fullRange = ref NullRef<D3D12Range>();

View on GitHub (pinned to 96fad776d2)