stride3d/stride · error · ArgumentException

Read / Write / ReadWrite is only supported for staging…

Error message

Read / Write / ReadWrite is only supported for staging resources

What it means

MapMode.Read, ReadWrite and Write on Direct3D12 require the resource to have been created with GraphicsResourceUsage.Staging, because they go through CPU-visible memory. Mapping a non-staging (e.g. Immutable/Default) resource with one of those modes throws ArgumentException naming the mapMode parameter.

Solutions

  1. Create the resource with GraphicsResourceUsage.Staging when you intend to Map it for Read/ReadWrite/Write.
  2. For readback, copy from the GPU resource to a staging resource with Copy first, then Map the staging resource in Read mode.
  3. Use MapMode.WriteDiscard on appropriate buffers instead of Write when rewriting whole contents (that path doesn't require staging).
  4. Check resource.Usage before mapping and branch accordingly.

Example fix

// before
var buf = Buffer.New(GraphicsDevice, size);
commandList.Map(buf, MapMode.Read, 0); // throws
// after
var staging = Buffer.New(GraphicsDevice, size, BufferFlags.None, GraphicsResourceUsage.Staging);
commandList.Copy(buf, staging);
var data = commandList.Map(staging, MapMode.Read, 0);
Defensive patterns

Strategy: validation

Validate before calling

if (mapMode is MapMode.Read or MapMode.ReadWrite or MapMode.Write
    && resource.Usage != GraphicsResourceUsage.Staging)
    throw new ArgumentException("Create the resource with Staging usage for Read/Write maps", nameof(resource));

Type guard

static bool SupportsMapMode(GraphicsResource r, MapMode m) =>
    m is not (MapMode.Read or MapMode.ReadWrite or MapMode.Write) || r.Usage == GraphicsResourceUsage.Staging;

Try / catch

try { var mapped = commandList.Map(resource, MapMode.Read, 0); }
catch (ArgumentException) { /* copy to staging then map the staging copy */ }

Prevention

When it happens

Trigger: Calling CommandList.Map with MapMode.Read/ReadWrite/Write on a Buffer or Texture whose Usage != GraphicsResourceUsage.Staging.

Common situations: Trying to read back GPU-written data from a default-usage buffer; forgetting to create the texture/buffer with GraphicsResourceUsage.Staging; porting D3D11 code where dynamic usage allowed similar mappings.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

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

                    // Internally it's a Buffer, so adapt resource index and offset
                    offsetInBytes = texture.ComputeBufferOffset(subResourceIndex, depthSlice: 0);
                    subResourceIndex = 0;
                }
            }
            else if (resource is Buffer buffer)
            {
                usage = buffer.Usage;

                if (lengthInBytes == 0)
                    lengthInBytes = buffer.SizeInBytes;
            }
            else throw new ArgumentException("Only Buffers and Textures can be mapped", nameof(resource));

            if (mapMode is MapMode.Read or MapMode.ReadWrite or MapMode.Write)
            {
                // Is non-staging even possible for Read/Write?
                if (usage != GraphicsResourceUsage.Staging)
                    throw new ArgumentException("Read / Write / ReadWrite is only supported for staging resources", nameof(mapMode));
            }

            // NOTE: This path is quite slow (it creates a new resource).
            //       Once we switch to D3D12 / Vulkan only, we should probably get rid of this use case
            //       by pooling and reusing buffers internally, or explicitly managed at the caller side
            if (mapMode == MapMode.WriteDiscard)
            {
                // Mark old resource for deletion once Command List are executed
                resource.OnDestroyed();

                // Create new resource
                resource.OnRecreate();
            }
            else if (mapMode != MapMode.WriteNoOverwrite)   // Write / Read / ReadWrite
            {
                // Need to wait?
                if (
                    // used in command list which hasn't be submitted yet? (only valid if our own, checked later)

View on GitHub (pinned to 96fad776d2)