docs: Add an example of a fully GPU genetic algorithm#2250
docs: Add an example of a fully GPU genetic algorithm#2250
Conversation
reczkok
commented
Mar 5, 2026
|
pkg.pr.new packages benchmark commit |
📊 Bundle Size Comparison
👀 Notable resultsStatic test results:No major changes. Dynamic test results:No major changes. 📋 All resultsClick to reveal the results table (344 entries).
If you wish to run a comparison for other, slower bundlers, run the 'Tree-shake test' from the GitHub Actions menu. |
There was a problem hiding this comment.
Pull request overview
This PR adds a new interactive example demonstrating a fully GPU-driven genetic algorithm for 2D car racing simulation. Cars navigate procedurally generated tracks using sensor-based inputs processed through evolved genomes, with tournament selection, crossover, and mutation all running in GPU compute shaders.
Changes:
- Added a complete genetic racing example with procedural track generation (
track.ts), GPU-based genetic algorithm logic (ga.ts), and the main simulation/rendering loop (index.ts) - Included interactive controls for grid size, population, mutation parameters, and simulation speed, along with a car sprite asset and HTML overlay for stats display
- Added example metadata (
meta.json) and thumbnail for the docs example gallery
Reviewed changes
Copilot reviewed 5 out of 7 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
apps/typegpu-docs/src/examples/algorithms/genetic-racing/meta.json |
Example metadata with title, category, and tags |
apps/typegpu-docs/src/examples/algorithms/genetic-racing/index.html |
Canvas element with stats overlay div and styling |
apps/typegpu-docs/src/examples/algorithms/genetic-racing/index.ts |
Main example: GPU pipeline setup, simulation loop, rendering, and user controls |
apps/typegpu-docs/src/examples/algorithms/genetic-racing/ga.ts |
Genetic algorithm: data schemas, init/evolve compute shaders, population management |
apps/typegpu-docs/src/examples/algorithms/genetic-racing/track.ts |
Procedural track generation with random walk paths and Catmull-Rom spline smoothing |
apps/typegpu-docs/public/assets/genetic-car/car.png |
Car sprite image asset |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
b235654 to
22abee6
Compare
| if (hasChampion) { | ||
| const src = root.unwrap(championGenomeBuffer); | ||
| const encoder = root.device.createCommandEncoder(); | ||
| encoder.copyBufferToBuffer(src, 0, root.unwrap(ga.genomeBuffers[ga.current]), 0, src.size); | ||
| root.device.queue.submit([encoder.finish()]); | ||
| } |
There was a problem hiding this comment.
I'm not a fan of this but I think i prefer it to dispatching an entire compute shader to copy a couple of bytes
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 7 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
apps/typegpu-docs/src/examples/algorithms/genetic-racing/track.ts
Outdated
Show resolved
Hide resolved
apps/typegpu-docs/src/examples/algorithms/genetic-racing/index.ts
Outdated
Show resolved
Hide resolved
Split Genome into InputLayer, DenseLayer and OutputLayer and replace flat weight vectors with mat4x4 matrices. Add FitnessArray and a fit compute shader plus a reduction to select the champion; wire the new fitness buffer into init/evolve flows. Update mutation/evolution helpers and simulation to evaluate the layered network and use the reworked sensor inputs.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
9c5c3e4 to
c9fd29c
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 9 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
apps/typegpu-docs/src/examples/algorithms/genetic-racing/index.ts
Outdated
Show resolved
Hide resolved
iwoplaza
left a comment
There was a problem hiding this comment.
Left a few nits, awesome example! And SO FAST 🚗🚗🚗🚗🚗🚗
| ); | ||
| }; | ||
|
|
||
| const evolveInputLayer = (a: d.Infer<typeof InputLayer>, b: d.Infer<typeof InputLayer>) => { |
There was a problem hiding this comment.
| const evolveInputLayer = (a: d.Infer<typeof InputLayer>, b: d.Infer<typeof InputLayer>) => { | |
| const evolveInputLayer = (a: d.InferGPU<typeof InputLayer>, b: d.InferGPU<typeof InputLayer>) => { |
| }); | ||
| }; | ||
|
|
||
| const evolveDenseLayer = (a: d.Infer<typeof DenseLayer>, b: d.Infer<typeof DenseLayer>) => { |
There was a problem hiding this comment.
| const evolveDenseLayer = (a: d.Infer<typeof DenseLayer>, b: d.Infer<typeof DenseLayer>) => { | |
| const evolveDenseLayer = (a: d.InferGPU<typeof DenseLayer>, b: d.InferGPU<typeof DenseLayer>) => { |
| return DenseLayer({ w: evolveMat4x4(a.w, b.w), bias: evolveVec(a.bias, b.bias) }); | ||
| }; | ||
|
|
||
| const evolveOutputLayer = (a: d.Infer<typeof OutputLayer>, b: d.Infer<typeof OutputLayer>) => { |
There was a problem hiding this comment.
| const evolveOutputLayer = (a: d.Infer<typeof OutputLayer>, b: d.Infer<typeof OutputLayer>) => { | |
| const evolveOutputLayer = (a: d.InferGPU<typeof OutputLayer>, b: d.InferGPU<typeof OutputLayer>) => { |
| initLayout.$.genome[i] = Genome({ | ||
| h1: InputLayer({ | ||
| wA: randSignedMat4x4(), | ||
| wB: randSignedMat4x4(), | ||
| wC: randSignedMat4x4(), | ||
| bias: d.vec4f(), | ||
| }), | ||
| h2: DenseLayer({ w: randSignedMat4x4(), bias: d.vec4f() }), | ||
| out: OutputLayer({ steer: randSignedVec4(), throttle: randSignedVec4(), bias: d.vec2f() }), | ||
| }); |
There was a problem hiding this comment.
Is that really necessary? I thought we handled nested struct props
| initLayout.$.genome[i] = Genome({ | |
| h1: InputLayer({ | |
| wA: randSignedMat4x4(), | |
| wB: randSignedMat4x4(), | |
| wC: randSignedMat4x4(), | |
| bias: d.vec4f(), | |
| }), | |
| h2: DenseLayer({ w: randSignedMat4x4(), bias: d.vec4f() }), | |
| out: OutputLayer({ steer: randSignedVec4(), throttle: randSignedVec4(), bias: d.vec2f() }), | |
| }); | |
| initLayout.$.genome[i] = Genome({ | |
| h1: { | |
| wA: randSignedMat4x4(), | |
| wB: randSignedMat4x4(), | |
| wC: randSignedMat4x4(), | |
| bias: d.vec4f(), | |
| }, | |
| h2: { w: randSignedMat4x4(), bias: d.vec4f() }, | |
| out: { steer: randSignedVec4(), throttle: randSignedVec4(), bias: d.vec2f() }, | |
| }); |
| return hitT; | ||
| }; | ||
|
|
||
| const evalNetwork = (genome: d.Infer<typeof Genome>, a: d.v4f, b: d.v4f, c: d.v4f) => { |
There was a problem hiding this comment.
| const evalNetwork = (genome: d.Infer<typeof Genome>, a: d.v4f, b: d.v4f, c: d.v4f) => { | |
| const evalNetwork = (genome: d.InferGPU<typeof Genome>, a: d.v4f, b: d.v4f, c: d.v4f) => { |
| std.atomicMax(reductionLayout.$.packed, packed); | ||
| }); | ||
|
|
||
| const finalizeReductionPipeline = root.createGuardedComputePipeline((_x) => { |
There was a problem hiding this comment.
Why not a 0-dimension pipeline?
| const finalizeReductionPipeline = root.createGuardedComputePipeline((_x) => { | |
| const finalizeReductionPipeline = root.createGuardedComputePipeline(() => { |
There was a problem hiding this comment.
I recall having type errors when dispatching - will check
| const trackPipeline = root.createRenderPipeline({ | ||
| vertex: common.fullScreenTriangle, | ||
| fragment: trackFragment, | ||
| targets: { format: presentationFormat }, |
There was a problem hiding this comment.
Default
| targets: { format: presentationFormat }, |
| fragment: carFragment, | ||
| primitive: { topology: 'triangle-strip' }, | ||
| targets: { | ||
| format: presentationFormat, |
There was a problem hiding this comment.
Default
| format: presentationFormat, |
| attribs: { | ||
| position: instanceLayout.attrib.position, | ||
| angle: instanceLayout.attrib.angle, | ||
| alive: instanceLayout.attrib.alive, | ||
| progress: instanceLayout.attrib.progress, | ||
| }, |
There was a problem hiding this comment.
| attribs: { | |
| position: instanceLayout.attrib.position, | |
| angle: instanceLayout.attrib.angle, | |
| alive: instanceLayout.attrib.alive, | |
| progress: instanceLayout.attrib.progress, | |
| }, | |
| attribs: instanceLayout.attrib, |
| const reductionEncoder = root.device.createCommandEncoder(); | ||
| reductionEncoder.clearBuffer(root.unwrap(reductionPackedBuffer)); | ||
| reductionPipeline.with(bg).with(reductionEncoder).dispatchThreads(population); | ||
| finalizeReductionPipeline.with(bg).with(reductionEncoder).dispatchThreads(1); |
There was a problem hiding this comment.
| finalizeReductionPipeline.with(bg).with(reductionEncoder).dispatchThreads(1); | |
| finalizeReductionPipeline.with(bg).with(reductionEncoder).dispatchThreads(); |