BunKit Metal
3D scenes and a typed GPU API for BunKit apps.
Scene3D is a scene graph of shapes, a camera and a light. Under it,
GPUView is a Metal surface where you write the passes yourself, and
gpu() is the device that hands out buffers, shaders and pipelines. All three
come from bunkit/metal.
A 3D scene
A Scene3D is a view, so it goes into a stack next to labels and buttons:
const scene = new Scene3D({
grow: 1,
camera: { position: [4, 2.6, 5], target: [0, 0.6, 0], fov: 50 },
light: { direction: [0.5, 0.85, 0.35], ambient: "#5d6478" },
});
scene.add(plane({ size: 40, color: "#20202a" }));
const cube = scene.add(box({ size: 1.1, position: [0, 0.55, 0], color: "#aa091b" }));
scene.onFrame(({ dt }) => {
cube.rotation.y += dt;
});
The shapes are box, sphere, plane,
cylinder and cone, and mesh(geometry(...)) takes
vertices of your own. Nodes that share a geometry and a material are drawn together in
one instanced draw call:
for (let i = 0; i < 200; i++) {
scene.add(box({ size: 0.2, position: [i % 20, 0, i / 20] }));
}
scene.batchCount; // 1
Drawing a frame
A GPUView calls onFrame once per display refresh, and you record
render passes into the frame it passes you:
const view = new GPUView({ grow: 1, sampleCount: 4 });
view.onFrame((frame) => {
frame.render(
{
color: { texture: frame.colorTexture, clear: [0, 0, 0, 1], resolve: frame.resolveTexture },
depth: frame.depthTexture,
},
(pass) => {
pass.pipeline(pipeline).bind({ globals, instances });
pass.drawIndexed(indices, count, { instances: 5000 });
},
);
});
bind matches inputs by the names the shader uses rather than by slot number,
and a misspelt name is an error that lists the real ones. At most 2 frames are on the GPU
at once, and when it falls behind the view skips a tick rather than block the run loop.
What else there is
struct()schemas, declared once for both JavaScript and the shadergpu().renderPipeline(), compiled from MSL sourcegpu().effect(), a full-screen pass written as one fragment bodygpu().bloom(), HDR rendering with bloom and tone mappinggpu().kernel(), compute that runs in the same frame as the drawingmaterial(), a custom look forScene3Dnodes- Textures, samplers and render targets
view.input, the keys and mouse, read once per frame- MSL snippets for noise, distance functions, tone mapping and lighting
bun run particles simulates 250,000 particles with a kernel, and
bun run playground is a live shader editor. The API follows WebGPU closely,
so porting from it is mostly renaming.