denoland/deno · error · DOMExceptionOperationError

validation error occurred

Error message

validation error occurred

What it means

GPUBuffer.mapAsync() in Deno's WebGPU maps the buffer via wgpu_core::buffer_map_async; if that call returns a validation error, buffer.rs maps it to BufferError::Operation("validation error occurred") and rejects the mapAsync promise (the error is also pushed to the device's uncaptured-error handler). Typical validation failures: offset/size not aligned, size out of bounds, buffer created without MAP usage, or the buffer/device state being invalid.

Source

Thrown at ext/webgpu/buffer.rs:235

        sender.send(status).unwrap();
      });

      let err = self
        .instance
        .buffer_map_async(
          self.id,
          offset,
          size,
          wgpu_core::resource::BufferMapOperation {
            host: mode,
            callback: Some(callback),
          },
        )
        .err();

      if err.is_some() {
        self.error_handler.push_error(err);
        return Err(BufferError::Operation("validation error occurred"));
      }
    }

    let done = Rc::new(RefCell::new(false));
    let done_ = done.clone();
    let device_poll_fut = async move {
      while !*done.borrow() {
        {
          self
            .instance
            .device_poll(self.device, wgpu_types::PollType::wait_indefinitely())
            .unwrap();
        }
        tokio::time::sleep(Duration::from_millis(10)).await;
      }
      Ok::<(), BufferError>(())
    };

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Create the buffer with the right flags: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST for readback, GPUBufferUsage.MAP_WRITE | GPUBufferUsage.COPY_SRC for upload.
  2. Align offset and size to 8 bytes (round down/up) and clamp size to buffer.byteLength - offset.
  3. Ensure the buffer is unmapped (getMappedRange + unmap finished) before calling mapAsync again.
  4. Catch the rejection, and also register device.onuncapturederror / device.lost handling to surface the underlying wgpu validation message.

Example fix

// before
const buf = device.createBuffer({
  size: 16,
  usage: GPUBufferUsage.COPY_DST, // no MAP_READ -> "validation error occurred"
});
await buf.mapAsync(GPUMapMode.READ, 0);

// after
const buf = device.createBuffer({
  size: 16,
  usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
});
await buf.mapAsync(GPUMapMode.READ, 0, 16);
Defensive patterns

Strategy: try-catch

Validate before calling

const MAP_ALIGNMENT = 8;
function assertMappable(buffer, offset, size) {
  const usage = buffer.usage ?? (buffer._creationUsage as GPUBufferUsageFlags);
  const s = size ?? buffer.byteLength - offset;
  if (offset % MAP_ALIGNMENT !== 0 || s % MAP_ALIGNMENT !== 0) {
    throw new Error(`mapAsync offset/size must be ${MAP_ALIGNMENT}-byte aligned`);
  }
  if (offset + s > buffer.byteLength) throw new Error("mapAsync range out of bounds");
  return s;
}

Type guard

function isMappableBuffer(buffer: GPUBuffer, mode: GPUMapModeFlags): boolean {
  const u = buffer.usage;
  return mode === GPUMapMode.READ
    ? (u & GPUBufferUsage.MAP_READ) !== 0
    : (u & GPUBufferUsage.MAP_WRITE) !== 0;
}

Try / catch

try {
  await buffer.mapAsync(GPUMapMode.READ, alignedOffset, alignedSize);
} catch (err) {
  console.error("mapAsync failed:", err.message); // "validation error occurred"
  // recreate buffer with MAP flags / fix alignment, or bail
}
device.onuncapturederror = (e) => console.error("uncaptured:", e.error.message);

Prevention

When it happens

Trigger: `await buffer.mapAsync(GPUMapMode.READ, offset, size)` where offset or size is not a multiple of 8, offset+size exceeds buffer.byteLength, the GPUBuffer was created without GPUBufferUsage.MAP_READ/MAP_WRITE, mapAsync is called while already mapped/pending, or the device was lost/destroyed. Example: creating a buffer with COPY_DST only and then trying to map it for reading.

Common situations: Forgetting MAP_READ|MAP_WRITE usage flags and mapping anyway (most common); passing byte offsets computed from unaligned struct sizes; reusing offset math meant for copyBufferToBuffer (4-byte alignment) where mapping requires 8; racing two mapAsync calls on the same buffer; continuing after device loss in error-handling tests.

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/89206f72ab0b561e. Report an issue: GitHub.