Repository navigation
ogpu 0.18.0
The first public release since 0.8.0 on main (the last GitHub Release was 0.5.0). Ten
releases' worth of work land at once: meshes are now geometry + WGSL shader + a plain state
object, and the renderer derives every render pipeline. Every Mesh shader can read a small
engine uniform block (camera, object, time). Bind groups are built by WGSL name, static
scenery can replay from render bundles, and there's an MSDF text module with SplitText-style
reveals.
Still not an npm package: fork it, grow it, make it yours. The full per-release detail is in
CHANGELOG.md (0.9.0 → 0.18.0).
Highlights
- Shader + state meshes, derived pipelines.
new Mesh({ geometry, shader, state, bindGroups })
takes nogpuand builds no pipeline. The renderer'sPipelineRegistrybuilds one per
(shader, state, vertex layout, pass formats + sample count) on first draw and hands every
mesh that asks the same object, so one mesh draws into the canvas, an MSAA target or a
depth-only shadow map with no per-target setup.renderer.compile({ scene, target })
pre-warms them. - An engine block every Mesh shader can read.
camera.*(projection, view, position,
quaternion,viewport),object.*(model, normal, object matrices) at group(1) and
time.time/time.deltaat group(3). It lives in one renderer-owned buffer: a fixed slot
per mesh, rewritten only when the mesh moves, and a slot perrender()for the camera.
FullscreenRenderPipelines andComputeShaders don't get it. - Bind groups by WGSL name.
shader.bindGroupLayout(0).createBindGroup({ uniforms, tMap }).
BindGroupLayoutis interned per device: shaders or kernels with the same bindings share
one, and a hot reload that keeps the shape keeps your bind groups.UniformBufferbacks any
uniform struct. - Render bundles:
RenderBundleGroup. Parent a large static set under it, call
record(), andrender()replays the subtree from a GPURenderBundle. Children can still
move. 10 000 static meshes: render() CPU about 0.8 ms recorded vs 6.3 ms live (M1 Pro,
headless Chromium). - MSDF text (
MSDFFont,MSDFText,TEXT_PRESETS): instanced glyph quads on an MTSDF
atlas, SplitText-style reveals (chars/words/lines, masks, wipes, 12 easing curves)
evaluated on the GPU, outlines, awireframeview. Bundled font: Fira Sans Bold, as in
OGL's msdf-text example. - Faster draws. Redundant pipeline/bind-group/buffer sets are skipped, group 3 and the
empty group 2 are bound once per pass, and a static mesh costs no upload at all. - Per-node scissor (
node.scissor = { x, y, width, height }); hot reload now rebuilds a
Shaderin place and the registry rebuilds its pipelines, with bind groups surviving when
the binding shape doesn't change; aFloat16Arrayshim for Chrome/Edge 113–134; and
exact pins of webgpu-utils 2.1.1 (behind@utils/webgpu-utils) and wgpu-matrix
3.4.2 (behind@math/functions). - Compressed KTX textures now load: mip levels that aren't a whole number of compression
blocks upload as whole blocks, as WebGPU requires.
New and updated examples
?src=renderbundle: 6400 static meshes replayed from a bundle vs drawn live, with re-record,
a state swap and a render() timer.?src=msdftext: OGL's "don't panic", black on white, with every reveal preset, outlines and
a wireframe toggle.?src=highmeshcount&mode=recorded: up to 50 000 meshes, live or recorded.
Upgrading from 0.8.0
Every row maps 0.8.0 code straight to 0.18.0; the CHANGELOG has the stops in between.
| 0.8.0 | 0.18.0 |
|---|---|
new RenderPipeline(gpu, { code, constants, vertexBuffers, targets, sampleCount, cullMode, depthStencil, … }) + new Mesh(gpu, { pipeline, geometry, bindGroups }) |
const shader = new Shader(gpu, { code, constants }); new Mesh({ geometry, shader, state: { cullMode, depthWrite, depthCompare, depthBias, … }, bindGroups }). Drop targets, sampleCount, vertexBuffers and depth formats: they come from the pass. depthStencil becomes the flat state fields depthWrite / depthTest / depthCompare / depthBias / depthBiasSlopeScale / depthBiasClamp |
projectionMatrix, viewMatrix, modelMatrix, modelViewMatrix, normalMatrix, objectMatrix, cameraPosition, cameraQuaternion, resolution, time in your Uniforms struct |
delete them; read camera.*, object.*, time.*. Add // validate-prepend: <path>/src/core/engine.wgsl |
modelViewMatrix * p |
camera.viewMatrix * (object.modelMatrix * p) (or modelViewMatrix()) |
resolution |
camera.viewport |
time |
time.time; the frame delta is time.delta |
your own @group(1) or @group(3) bindings |
move them to group 2 (or 0); 1 and 3 are the engine's |
a module-scope camera, object, time, Camera, Object, Time or modelViewMatrix of your own |
rename it: they collide with the engine block |
mesh.uniforms.set(…), bindGroups: (uniformResource) => […] |
const uniforms = new UniformBuffer(gpu, shader.uniforms, values); bindGroups: [shader.bindGroupLayout(0).createBindGroup({ uniforms, … })]; uniforms.set(…) |
gui.uniform(mesh, …) |
gui.uniform(uniforms, …) |
device.createBindGroup({ layout: pipeline.bindGroupLayout(i), entries }) |
Mesh: shader.bindGroupLayout(i).createBindGroup({ name: resource }); fullscreen pass: pipeline.bindGroupLayout(i).createBindGroup({ … }). .layout is the raw GPUBindGroupLayout |
pass.setBindGroup(0, bindGroup, [0]) on a RenderPipeline |
pass.setBindGroup(0, bindGroup): no dynamic offset any more (hasDynamicUniform is gone) |
computeShader.bindGroupLayout(kernel) |
computeShader.bindGroupLayout('kernelName', group): both arguments required; it returns a BindGroupLayout, so use .createBindGroup({ … }) (or .layout for the raw one) |
createUniformBuffer(…) |
new UniformBuffer(…) for a struct, createBuffer(gpu, { usage: UNIFORM | COPY_DST, data }) for raw bytes (createUniformBuffer still works but warns) |
import { mat4, vec3 } from 'wgpu-matrix' |
from 'ogpu' or @math/functions. Only the functions ogpu uses are re-exported (mat4.create, vec3.sub, … may be missing): add what you need to src/math/functions/ |
import … from 'webgpu-utils' |
makeStructuredView from 'ogpu'; everything else from @utils/webgpu-utils (never the package directly) |
generateMipmap(device, tex) |
generateMipmap(gpu, tex) from @utils/mipmap.js |
| a pass drawing a mesh with another shader (shadow casters) | a second Mesh sharing the geometry, or swap mesh.shader and mesh.bindGroups together |
| one mesh, different uniform values per pass in one submit | one UniformBuffer + bind group per pass, swapped via mesh.bindGroups |
pbr.wgsl with a uniforms block at group(0) binding 0; loader.pipelines |
pbr.wgsl's group(0) starts at binding 1 (don't pass uniforms); loader.shader. GLTFLoader's targets / sampleCount options are ignored |
a Mesh subclass overriding draw({ camera, pass, time, resolution }), reading mesh.modelViewMatrix / uniformResource / structSize |
draw({ pass, passInfo, renderer, camera, cameraOffset, bound }); those fields are gone. mesh.normalMatrix / objectMatrix can be stale on the CPU: call mesh.updateDerived() first |
| after a device loss | rebuild your Shaders and their bind groups in renderer.addDeviceRestoredHandler(gpu => …) |
Browsers: Chrome/Edge 113+, plus Firefox and Safari where they ship WebGPU (see the
README). Optional features (timestamp queries, texture compression families,
float32-filterable) are feature-detected.