software-mansion / software-mansion/TypeGPU
GPUBuffer, binding sub-regions, drawIndexed with baseVertex and firstInstance
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 3.2k
- Forks
- 122
- Avg merge
- 3d 5h
- Merged PRs (30d)
- 34
Description
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()]);
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with TypeGPU's buffer write API and draw abstractions, comparing them with the raw GPUBuffer, setVertexBuffer, setIndexBuffer, and drawIndexed calls shown here. Done means supporting sub-buffer writes and exposing the needed vertex-region, baseVertex, and firstInstance behavior without requiring unwrap.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, computer-graphics
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100