Skip to content

GPUBuffer, binding sub-regions, drawIndexed with baseVertex and firstInstance #2236

@jerzakm

Description

@jerzakm

In project I'm working on I ran into a case that TypeGPU api doesn't natively support. I'm mostly sharing this out of curiosity and in case it's useful, the unwrap escape hatch is great and everything works.

1. GPUBuffer

I need to use a raw GPUBuffer as a vertex source to get sub-buffer writes at specified byte offsets, like this:

device.queue.writeBuffer(page, alloc.offset, someVertexData);

if I understand correctly, TypeGPU write() replaces the entire buffer, and for my case it would possibly have support something like
buffer.writeAt(offset,data). Alternatively, a way to use or wrap GPUBuffer so it can be accessed directly without unwrapping

2. Binding sub region of a buffer - draw indexed with baseVertex and firstInstance

My buffers hold many objects' geometry packed together, but I need each draw call to read only one objects vertices and indices - not the whole buffer.

setIndexBuffer has built-in offset and size params:

pass.setIndexBuffer(
        fullBuffer,
        "uint16",
        indexOffset,
        indexSize,
      );

For vertex buffer I have to set the entire buffer in the pass:

pass.setVertexBuffer(0, fullBuffer);

but then I can use drawIndexed like this:

pass.drawIndexed(
  draw.indexCount,   // 36 indices for a cube
  1,                 // 1 instance
  0,                 // firstIndex
  draw.baseVertex,   // added to every index before vertex fetch acts as an offset
  draw.modelSlot,    // firstInstance - becomes instance_index in shader
);

Now when the GPU processes index 0, it actually fetches vertex 0 + 24 = 24. Index 1 fetches vertex 25. And so on. The indices stay [0,..,23] but they're shifted to read from the right region of the buffer.

This is the other half of why I have to unwrap. TypeGPU's draw abstractions don't expose baseVertex or firstInstance.

My draw code currently looks like this:

const encoder = device.createCommandEncoder();
  const pass = encoder.beginRenderPass({
    colorAttachments: [
      {
        view: context.getCurrentTexture().createView(),
        clearValue: [0.04, 0.04, 0.07, 1],
        loadOp: "clear" as const,
        storeOp: "store" as const,
      },
    ],
    depthStencilAttachment: {
      view: depthTexture.createView(),
      depthClearValue: 1,
      depthLoadOp: "clear" as const,
      depthStoreOp: "store" as const,
    },
  });
  const rawPipeline = root.unwrap(renderPipeline);
  pass.setPipeline(rawPipeline);
  pass.setBindGroup(0, rawCameraBindGroup);
  pass.setBindGroup(1, rawModelsBindGroup);

  for (const [vertPage, draws] of drawsByPage) {
    pass.setVertexBuffer(0, vertexPool.getBuffer(vertPage));
    for (const draw of draws) {
      pass.setIndexBuffer(
        indexPool.getBuffer(draw.idxPage),
        "uint16",
        draw.idxOffset,
        draw.idxSize,
      );
      pass.drawIndexed(draw.indexCount, 1, 0, draw.baseVertex, draw.modelSlot);
    }
  }

  pass.end();
  device.queue.submit([encoder.finish()]);

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions