Skip to content

Commit 6f816f8

Browse files
committed
simplify and review comments
1 parent c4de23e commit 6f816f8

File tree

2 files changed

+34
-30
lines changed
  • apps/typegpu-docs/src/examples/algorithms/genetic-racing

2 files changed

+34
-30
lines changed

apps/typegpu-docs/src/examples/algorithms/genetic-racing/ga.ts

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ const evolveLayout = tgpu.bindGroupLayout({
8484
genome: { storage: GenomeArray },
8585
nextState: { storage: CarStateArray, access: 'mutable' },
8686
nextGenome: { storage: GenomeArray, access: 'mutable' },
87+
bestIdx: { storage: d.u32 },
8788
});
8889

8990
const randSignedVec4 = () => {
@@ -155,7 +156,7 @@ const evolveMat4x4 = (a: d.m4x4f, b: d.m4x4f) => {
155156
);
156157
};
157158

158-
const evolveInputLayer = (a: d.Infer<typeof InputLayer>, b: d.Infer<typeof InputLayer>) => {
159+
const evolveInputLayer = (a: d.InferGPU<typeof InputLayer>, b: d.InferGPU<typeof InputLayer>) => {
159160
'use gpu';
160161
return InputLayer({
161162
wA: evolveMat4x4(a.wA, b.wA),
@@ -165,12 +166,15 @@ const evolveInputLayer = (a: d.Infer<typeof InputLayer>, b: d.Infer<typeof Input
165166
});
166167
};
167168

168-
const evolveDenseLayer = (a: d.Infer<typeof DenseLayer>, b: d.Infer<typeof DenseLayer>) => {
169+
const evolveDenseLayer = (a: d.InferGPU<typeof DenseLayer>, b: d.InferGPU<typeof DenseLayer>) => {
169170
'use gpu';
170171
return DenseLayer({ w: evolveMat4x4(a.w, b.w), bias: evolveVec(a.bias, b.bias) });
171172
};
172173

173-
const evolveOutputLayer = (a: d.Infer<typeof OutputLayer>, b: d.Infer<typeof OutputLayer>) => {
174+
const evolveOutputLayer = (
175+
a: d.InferGPU<typeof OutputLayer>,
176+
b: d.InferGPU<typeof OutputLayer>,
177+
) => {
174178
'use gpu';
175179
return OutputLayer({
176180
steer: evolveVec(a.steer, b.steer),
@@ -196,21 +200,31 @@ const initShader = (i: number) => {
196200
randf.seed2(d.vec2f(d.f32(i) + 1, paramsAccess.$.generation + 11));
197201

198202
initLayout.$.genome[i] = Genome({
199-
h1: InputLayer({
203+
h1: {
200204
wA: randSignedMat4x4(),
201205
wB: randSignedMat4x4(),
202206
wC: randSignedMat4x4(),
203207
bias: d.vec4f(),
204-
}),
205-
h2: DenseLayer({ w: randSignedMat4x4(), bias: d.vec4f() }),
206-
out: OutputLayer({ steer: randSignedVec4(), throttle: randSignedVec4(), bias: d.vec2f() }),
208+
},
209+
h2: { w: randSignedMat4x4(), bias: d.vec4f() },
210+
out: { steer: randSignedVec4(), throttle: randSignedVec4(), bias: d.vec2f() },
207211
});
208212
initLayout.$.state[i] = makeSpawnState();
209213
};
210214

211215
const evolveShader = (i: number) => {
212216
'use gpu';
213-
if (d.u32(i) >= paramsAccess.$.population) return;
217+
if (d.u32(i) >= paramsAccess.$.population) {
218+
return;
219+
}
220+
221+
// Elitism: champion always lives at index 0, copied unchanged
222+
if (d.u32(i) === 0) {
223+
evolveLayout.$.nextGenome[0] = Genome(evolveLayout.$.genome[evolveLayout.$.bestIdx]);
224+
evolveLayout.$.nextState[0] = makeSpawnState();
225+
return;
226+
}
227+
214228
randf.seed2(d.vec2f(d.f32(i) + 3, paramsAccess.$.generation + 19));
215229

216230
const parentA = Genome(evolveLayout.$.genome[tournamentSelect()]);
@@ -231,6 +245,7 @@ export function createGeneticPopulation(root: TgpuRoot, params: TgpuUniform<type
231245
);
232246
const genomeBuffers = [0, 1].map(() => root.createBuffer(GenomeArray).$usage('storage'));
233247
const fitnessBuffer = root.createBuffer(FitnessArray).$usage('storage');
248+
const bestIdxBuffer = root.createBuffer(d.u32).$usage('storage');
234249

235250
const initBindGroups = [0, 1].map((i) =>
236251
root.createBindGroup(initLayout, {
@@ -252,6 +267,7 @@ export function createGeneticPopulation(root: TgpuRoot, params: TgpuUniform<type
252267
genome: genomeBuffers[i],
253268
nextState: stateBuffers[1 - i],
254269
nextGenome: genomeBuffers[1 - i],
270+
bestIdx: bestIdxBuffer,
255271
}),
256272
);
257273

@@ -266,6 +282,7 @@ export function createGeneticPopulation(root: TgpuRoot, params: TgpuUniform<type
266282
stateBuffers,
267283
genomeBuffers,
268284
fitnessBuffer,
285+
bestIdxBuffer,
269286
get current() {
270287
return current;
271288
},

apps/typegpu-docs/src/examples/algorithms/genetic-racing/index.ts

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ const senseRaycast = (pos: d.v2f, angle: number, offset: number) => {
127127
return hitT;
128128
};
129129

130-
const evalNetwork = (genome: d.Infer<typeof Genome>, a: d.v4f, b: d.v4f, c: d.v4f) => {
130+
const evalNetwork = (genome: d.InferGPU<typeof Genome>, a: d.v4f, b: d.v4f, c: d.v4f) => {
131131
'use gpu';
132132
const h1 = std.tanh(
133133
std.transpose(genome.h1.wA) * a +
@@ -257,23 +257,22 @@ const simulatePipeline = root.createGuardedComputePipeline((i) => {
257257

258258
// upper 16 bits = quantized fitness [0,65535], lower 16 bits = car index
259259
const reductionPackedBuffer = root.createBuffer(d.atomic(d.u32), 0).$usage('storage');
260-
const championGenomeBuffer = root.createBuffer(Genome).$usage('storage');
261260
const bestFitnessBuffer = root.createBuffer(d.f32).$usage('storage');
262261

263262
const reductionLayout = tgpu.bindGroupLayout({
264263
fitness: { storage: FitnessArray },
265264
genome: { storage: GenomeArray },
266265
packed: { storage: d.atomic(d.u32), access: 'mutable' },
267-
championGenome: { storage: Genome, access: 'mutable' },
266+
bestIdx: { storage: d.u32, access: 'mutable' },
268267
bestFitness: { storage: d.f32, access: 'mutable' },
269268
});
270269

271270
const reductionBindGroups = [0, 1].map((i) =>
272271
root.createBindGroup(reductionLayout, {
273272
fitness: ga.fitnessBuffer,
274273
genome: ga.genomeBuffers[i],
274+
bestIdx: ga.bestIdxBuffer,
275275
packed: reductionPackedBuffer,
276-
championGenome: championGenomeBuffer,
277276
bestFitness: bestFitnessBuffer,
278277
}),
279278
);
@@ -289,11 +288,10 @@ const reductionPipeline = root.createGuardedComputePipeline((i) => {
289288
std.atomicMax(reductionLayout.$.packed, packed);
290289
});
291290

292-
const finalizeReductionPipeline = root.createGuardedComputePipeline((_x) => {
291+
const finalizeReductionPipeline = root.createGuardedComputePipeline(() => {
293292
'use gpu';
294293
const packed = std.atomicLoad(reductionLayout.$.packed);
295-
const bestIdx = packed & 0xffff;
296-
reductionLayout.$.championGenome = Genome(reductionLayout.$.genome[bestIdx]);
294+
reductionLayout.$.bestIdx = packed & 0xffff;
297295
reductionLayout.$.bestFitness = (d.f32(packed >> 16) / 65535) * 64;
298296
});
299297

@@ -396,7 +394,6 @@ let population = DEFAULT_POP;
396394
let rafHandle = 0;
397395
let pendingEvolve = false;
398396
let showBestOnly = false;
399-
let hasChampion = false;
400397
let displayedBestFitness = 0;
401398

402399
const statsDiv = document.querySelector('.stats') as HTMLDivElement;
@@ -429,12 +426,6 @@ function frame() {
429426
if (!paused) {
430427
if (pendingEvolve) {
431428
ga.evolve(population);
432-
if (hasChampion) {
433-
const src = root.unwrap(championGenomeBuffer);
434-
const encoder = root.device.createCommandEncoder();
435-
encoder.copyBufferToBuffer(src, 0, root.unwrap(ga.genomeBuffers[ga.current]), 0, src.size);
436-
root.device.queue.submit([encoder.finish()]);
437-
}
438429
steps = 0;
439430
params.writePartial({ generation: ga.generation });
440431
pendingEvolve = false;
@@ -449,11 +440,9 @@ function frame() {
449440
const dispatchCount = Math.ceil(stepsToRun / innerSteps);
450441

451442
const simEncoder = root.device.createCommandEncoder();
443+
const encoderPipeline = simulatePipeline.with(simBindGroups[ga.current]).with(simEncoder);
452444
for (let dispatch = 0; dispatch < dispatchCount; dispatch++) {
453-
simulatePipeline
454-
.with(simBindGroups[ga.current])
455-
.with(simEncoder)
456-
.dispatchThreads(population);
445+
encoderPipeline.dispatchThreads(population);
457446
}
458447
root.device.queue.submit([simEncoder.finish()]);
459448

@@ -468,10 +457,9 @@ function frame() {
468457
const reductionEncoder = root.device.createCommandEncoder();
469458
reductionEncoder.clearBuffer(root.unwrap(reductionPackedBuffer));
470459
reductionPipeline.with(bg).with(reductionEncoder).dispatchThreads(population);
471-
finalizeReductionPipeline.with(bg).with(reductionEncoder).dispatchThreads(1);
460+
finalizeReductionPipeline.with(bg).with(reductionEncoder).dispatchThreads();
472461
root.device.queue.submit([reductionEncoder.finish()]);
473462

474-
hasChampion = true;
475463
void bestFitnessBuffer.read().then((fitness) => {
476464
displayedBestFitness = fitness;
477465
});
@@ -488,7 +476,7 @@ function frame() {
488476
carPipeline
489477
.withColorAttachment({ view: context, loadOp: 'load', storeOp: 'store' })
490478
.with(instanceLayout, ga.currentStateBuffer)
491-
.draw(4, showBestOnly && hasChampion ? 1 : population);
479+
.draw(4, showBestOnly ? 1 : population);
492480

493481
rafHandle = requestAnimationFrame(frame);
494482
}
@@ -527,7 +515,6 @@ let gridSizeKey = 'S';
527515
function startSimulation() {
528516
steps = 0;
529517
pendingEvolve = false;
530-
hasChampion = false;
531518
displayedBestFitness = 0;
532519
params.writePartial({ generation: 0 });
533520
ga.init();

0 commit comments

Comments
 (0)