@gridspace/raster-path 1.0.7 → 1.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +144 -8
- package/build/app.js +36 -0
- package/build/index.html +3 -0
- package/build/raster-path.js +10 -2
- package/build/raster-worker.js +657 -75
- package/package.json +3 -2
- package/src/core/path-radial-v3.js +405 -0
- package/src/core/path-tracing.js +206 -115
- package/src/core/raster-config.js +24 -0
- package/src/core/raster-path.js +10 -2
- package/src/core/raster-worker.js +10 -0
- package/src/shaders/radial-rasterize-batched.wgsl +164 -0
- package/src/shaders/radial-rotate-triangles.wgsl +70 -0
- package/src/test/radial-v3-benchmark.cjs +184 -0
- package/src/test/radial-v3-bucket-test.cjs +154 -0
- package/src/web/app.js +36 -0
- package/src/web/index.html +3 -0
package/src/core/path-tracing.js
CHANGED
|
@@ -272,70 +272,162 @@ export async function generateTracingToolpaths({
|
|
|
272
272
|
});
|
|
273
273
|
device.queue.writeBuffer(maxZBuffer, 0, maxZInitData);
|
|
274
274
|
|
|
275
|
-
//
|
|
276
|
-
|
|
275
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
276
|
+
// PHASE 1: Sample all paths and build unified buffer
|
|
277
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
278
|
+
debug.log('PHASE 1: Sampling all paths...');
|
|
279
|
+
const pathIndex = []; // Maps path ID → unified buffer offsets
|
|
280
|
+
const sampledSegments = [];
|
|
277
281
|
let totalSampledPoints = 0;
|
|
278
282
|
|
|
279
283
|
for (let pathIdx = 0; pathIdx < paths.length; pathIdx++) {
|
|
280
|
-
const pathStartTime = performance.now();
|
|
281
284
|
const inputPath = paths[pathIdx];
|
|
282
|
-
|
|
283
|
-
debug.log(`Processing path ${pathIdx + 1}/${paths.length}: ${inputPath.length / 2} input vertices`);
|
|
285
|
+
debug.log(`Path ${pathIdx + 1}/${paths.length}: ${inputPath.length / 2} input vertices`);
|
|
284
286
|
|
|
285
287
|
// Sample path at specified resolution
|
|
286
288
|
const sampledPath = samplePath(inputPath, step);
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
+
const numPoints = sampledPath.length / 2;
|
|
290
|
+
|
|
291
|
+
pathIndex.push({
|
|
292
|
+
startOffset: totalSampledPoints,
|
|
293
|
+
endOffset: totalSampledPoints + numPoints,
|
|
294
|
+
numPoints: numPoints
|
|
295
|
+
});
|
|
289
296
|
|
|
290
|
-
|
|
297
|
+
sampledSegments.push(sampledPath);
|
|
298
|
+
totalSampledPoints += numPoints;
|
|
291
299
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
300
|
+
debug.log(` Sampled to ${numPoints} points`);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Concatenate all sampled paths into unified buffer
|
|
304
|
+
const unifiedSampledXY = new Float32Array(totalSampledPoints * 2);
|
|
305
|
+
let writeOffset = 0;
|
|
306
|
+
|
|
307
|
+
for (let pathIdx = 0; pathIdx < sampledSegments.length; pathIdx++) {
|
|
308
|
+
const sampledPath = sampledSegments[pathIdx];
|
|
309
|
+
unifiedSampledXY.set(sampledPath, writeOffset * 2);
|
|
310
|
+
writeOffset += sampledPath.length / 2;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
debug.log(`Unified buffer: ${totalSampledPoints} total points from ${paths.length} paths`);
|
|
314
|
+
|
|
315
|
+
// Debug: Log first sampled point
|
|
316
|
+
if (totalSampledPoints > 0) {
|
|
317
|
+
const firstX = unifiedSampledXY[0];
|
|
318
|
+
const firstY = unifiedSampledXY[1];
|
|
295
319
|
const gridX = (firstX - terrainBounds.min.x) / gridStep;
|
|
296
320
|
const gridY = (firstY - terrainBounds.min.y) / gridStep;
|
|
297
|
-
debug.log(`
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
321
|
+
debug.log(`First point: world(${firstX.toFixed(2)}, ${firstY.toFixed(2)}) -> grid(${gridX.toFixed(2)}, ${gridY.toFixed(2)})`);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
325
|
+
// PHASE 2: Calculate memory budget and create chunks
|
|
326
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
327
|
+
debug.log('PHASE 2: Calculating memory budget and chunking...');
|
|
328
|
+
const bytesPerPoint = 8 + 4; // XY input (2 floats) + Z output (1 float)
|
|
329
|
+
const configuredLimit = config.maxGPUMemoryMB * 1024 * 1024;
|
|
330
|
+
const deviceLimit = deviceCapabilities.maxStorageBufferBindingSize;
|
|
331
|
+
const maxSafeSize = Math.min(configuredLimit, deviceLimit) * config.gpuMemorySafetyMargin;
|
|
332
|
+
|
|
333
|
+
// Fixed overhead: terrain, tool, maxZ, uniforms
|
|
334
|
+
const fixedOverhead = terrainPositions.byteLength +
|
|
335
|
+
(sparseToolData.count * 16) +
|
|
336
|
+
(paths.length * 4) +
|
|
337
|
+
48;
|
|
338
|
+
|
|
339
|
+
if (fixedOverhead > maxSafeSize) {
|
|
340
|
+
if (shouldCleanupBuffers) {
|
|
309
341
|
terrainBuffer.destroy();
|
|
310
342
|
toolBuffer.destroy();
|
|
311
|
-
throw new Error(
|
|
312
|
-
`Path ${pathIdx + 1} exceeds GPU memory limits: ` +
|
|
313
|
-
`${(estimatedMemory / 1024 / 1024).toFixed(1)}MB > ` +
|
|
314
|
-
`${(maxSafeSize / 1024 / 1024).toFixed(1)}MB safe limit. ` +
|
|
315
|
-
`Consider reducing step parameter or splitting path.`
|
|
316
|
-
);
|
|
317
343
|
}
|
|
344
|
+
throw new Error(
|
|
345
|
+
`Fixed buffers (terrain + tool) exceed GPU memory: ` +
|
|
346
|
+
`${(fixedOverhead / 1024 / 1024).toFixed(1)}MB > ` +
|
|
347
|
+
`${(maxSafeSize / 1024 / 1024).toFixed(1)}MB. ` +
|
|
348
|
+
`Try reducing terrain resolution or tool density.`
|
|
349
|
+
);
|
|
350
|
+
}
|
|
318
351
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
352
|
+
const availableForPaths = maxSafeSize - fixedOverhead;
|
|
353
|
+
const maxPointsPerChunk = Math.floor(availableForPaths / bytesPerPoint);
|
|
354
|
+
|
|
355
|
+
debug.log(`Memory budget: ${(maxSafeSize / 1024 / 1024).toFixed(1)}MB safe, ${(availableForPaths / 1024 / 1024).toFixed(1)}MB available for paths`);
|
|
356
|
+
debug.log(`Max points per chunk: ${maxPointsPerChunk.toLocaleString()}`);
|
|
357
|
+
|
|
358
|
+
// Create chunks
|
|
359
|
+
const chunks = [];
|
|
360
|
+
let currentStart = 0;
|
|
361
|
+
while (currentStart < totalSampledPoints) {
|
|
362
|
+
const currentEnd = Math.min(currentStart + maxPointsPerChunk, totalSampledPoints);
|
|
363
|
+
chunks.push({
|
|
364
|
+
startPoint: currentStart,
|
|
365
|
+
endPoint: currentEnd,
|
|
366
|
+
numPoints: currentEnd - currentStart
|
|
323
367
|
});
|
|
324
|
-
|
|
368
|
+
currentStart = currentEnd;
|
|
369
|
+
}
|
|
325
370
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
371
|
+
debug.log(`Created ${chunks.length} chunk(s) for processing`);
|
|
372
|
+
|
|
373
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
374
|
+
// PHASE 3: Create reusable GPU buffers (buffer pool pattern)
|
|
375
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
376
|
+
debug.log('PHASE 3: Creating reusable GPU buffers...');
|
|
377
|
+
|
|
378
|
+
// Input buffer: XY pairs for sampled points
|
|
379
|
+
const inputBuffer = device.createBuffer({
|
|
380
|
+
size: maxPointsPerChunk * 8, // 2 floats per point
|
|
381
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
// Output buffer: Z depths
|
|
385
|
+
const outputBuffer = device.createBuffer({
|
|
386
|
+
size: maxPointsPerChunk * 4, // 1 float per point
|
|
387
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// Uniform buffer
|
|
391
|
+
const uniformBuffer = device.createBuffer({
|
|
392
|
+
size: 48,
|
|
393
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
// Staging buffer for readback
|
|
397
|
+
const stagingBuffer = device.createBuffer({
|
|
398
|
+
size: maxPointsPerChunk * 4,
|
|
399
|
+
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
// Unified output array (filled chunk-by-chunk)
|
|
403
|
+
const unifiedOutputZ = new Float32Array(totalSampledPoints);
|
|
404
|
+
|
|
405
|
+
debug.log(`Buffers created for ${maxPointsPerChunk.toLocaleString()} points per chunk`);
|
|
330
406
|
|
|
331
|
-
|
|
332
|
-
|
|
407
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
408
|
+
// PHASE 4: Process each chunk with single GPU dispatch
|
|
409
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
410
|
+
debug.log('PHASE 4: Processing chunks...');
|
|
411
|
+
|
|
412
|
+
for (let chunkIdx = 0; chunkIdx < chunks.length; chunkIdx++) {
|
|
413
|
+
const chunk = chunks[chunkIdx];
|
|
414
|
+
const { startPoint, endPoint, numPoints } = chunk;
|
|
415
|
+
|
|
416
|
+
debug.log(`Processing chunk ${chunkIdx + 1}/${chunks.length}: points ${startPoint}-${endPoint} (${numPoints} points)`);
|
|
417
|
+
|
|
418
|
+
// Extract chunk slice from unified buffer
|
|
419
|
+
const chunkInputXY = unifiedSampledXY.subarray(startPoint * 2, endPoint * 2);
|
|
420
|
+
|
|
421
|
+
// Upload to GPU (reuse same buffers)
|
|
422
|
+
device.queue.writeBuffer(inputBuffer, 0, chunkInputXY);
|
|
423
|
+
|
|
424
|
+
// Update uniforms for this chunk
|
|
333
425
|
const uniformData = new Uint32Array(12); // 48 bytes
|
|
334
426
|
uniformData[0] = terrainData.width;
|
|
335
427
|
uniformData[1] = terrainData.height;
|
|
336
428
|
uniformData[2] = sparseToolData.count;
|
|
337
|
-
uniformData[3] =
|
|
338
|
-
uniformData[4] =
|
|
429
|
+
uniformData[3] = numPoints; // point_count for THIS CHUNK
|
|
430
|
+
uniformData[4] = 0; // path_index (unused, maxZ computed on CPU)
|
|
339
431
|
|
|
340
432
|
const uniformDataFloat = new Float32Array(uniformData.buffer);
|
|
341
433
|
uniformDataFloat[5] = terrainBounds.min.x;
|
|
@@ -343,16 +435,12 @@ export async function generateTracingToolpaths({
|
|
|
343
435
|
uniformDataFloat[7] = gridStep;
|
|
344
436
|
uniformDataFloat[8] = zFloor;
|
|
345
437
|
|
|
346
|
-
const uniformBuffer = device.createBuffer({
|
|
347
|
-
size: uniformData.byteLength,
|
|
348
|
-
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
349
|
-
});
|
|
350
438
|
device.queue.writeBuffer(uniformBuffer, 0, uniformData);
|
|
351
439
|
|
|
352
|
-
// Wait for
|
|
440
|
+
// Wait for uploads
|
|
353
441
|
await device.queue.onSubmittedWorkDone();
|
|
354
442
|
|
|
355
|
-
// Create bind group
|
|
443
|
+
// Create bind group (same bindings as before)
|
|
356
444
|
const bindGroup = device.createBindGroup({
|
|
357
445
|
layout: cachedTracingPipeline.getBindGroupLayout(0),
|
|
358
446
|
entries: [
|
|
@@ -360,28 +448,23 @@ export async function generateTracingToolpaths({
|
|
|
360
448
|
{ binding: 1, resource: { buffer: toolBuffer } },
|
|
361
449
|
{ binding: 2, resource: { buffer: inputBuffer } },
|
|
362
450
|
{ binding: 3, resource: { buffer: outputBuffer } },
|
|
363
|
-
{ binding: 4, resource: { buffer: maxZBuffer } },
|
|
451
|
+
{ binding: 4, resource: { buffer: maxZBuffer } }, // Keep for shader compatibility
|
|
364
452
|
{ binding: 5, resource: { buffer: uniformBuffer } },
|
|
365
453
|
],
|
|
366
454
|
});
|
|
367
455
|
|
|
368
|
-
//
|
|
456
|
+
// Single GPU dispatch for entire chunk
|
|
369
457
|
const commandEncoder = device.createCommandEncoder();
|
|
370
458
|
const passEncoder = commandEncoder.beginComputePass();
|
|
371
459
|
passEncoder.setPipeline(cachedTracingPipeline);
|
|
372
460
|
passEncoder.setBindGroup(0, bindGroup);
|
|
373
461
|
|
|
374
|
-
const workgroupsX = Math.ceil(
|
|
462
|
+
const workgroupsX = Math.ceil(numPoints / 64);
|
|
375
463
|
passEncoder.dispatchWorkgroups(workgroupsX);
|
|
376
464
|
passEncoder.end();
|
|
377
465
|
|
|
378
466
|
// Copy output to staging buffer
|
|
379
|
-
|
|
380
|
-
size: outputBufferSize,
|
|
381
|
-
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
commandEncoder.copyBufferToBuffer(outputBuffer, 0, stagingBuffer, 0, outputBufferSize);
|
|
467
|
+
commandEncoder.copyBufferToBuffer(outputBuffer, 0, stagingBuffer, 0, numPoints * 4);
|
|
385
468
|
device.queue.submit([commandEncoder.finish()]);
|
|
386
469
|
|
|
387
470
|
// Wait for GPU to finish
|
|
@@ -389,65 +472,79 @@ export async function generateTracingToolpaths({
|
|
|
389
472
|
|
|
390
473
|
// Read back results
|
|
391
474
|
await stagingBuffer.mapAsync(GPUMapMode.READ);
|
|
392
|
-
const
|
|
393
|
-
const depthsCopy = new Float32Array(outputDepths);
|
|
394
|
-
stagingBuffer.unmap();
|
|
395
|
-
|
|
396
|
-
// Build XYZ output array
|
|
397
|
-
const outputXYZ = new Float32Array(numSampledPoints * 3);
|
|
398
|
-
for (let i = 0; i < numSampledPoints; i++) {
|
|
399
|
-
outputXYZ[i * 3 + 0] = sampledPath[i * 2 + 0]; // X
|
|
400
|
-
outputXYZ[i * 3 + 1] = sampledPath[i * 2 + 1]; // Y
|
|
401
|
-
outputXYZ[i * 3 + 2] = depthsCopy[i]; // Z
|
|
402
|
-
}
|
|
475
|
+
const chunkOutputZ = new Float32Array(stagingBuffer.getMappedRange(), 0, numPoints);
|
|
403
476
|
|
|
404
|
-
|
|
477
|
+
// Copy to unified output array
|
|
478
|
+
unifiedOutputZ.set(chunkOutputZ, startPoint);
|
|
405
479
|
|
|
406
|
-
|
|
407
|
-
inputBuffer.destroy();
|
|
408
|
-
outputBuffer.destroy();
|
|
409
|
-
uniformBuffer.destroy();
|
|
410
|
-
stagingBuffer.destroy();
|
|
480
|
+
stagingBuffer.unmap();
|
|
411
481
|
|
|
412
|
-
|
|
413
|
-
debug.log(` Path ${pathIdx + 1} complete: ${numSampledPoints} points in ${pathTime.toFixed(1)}ms`);
|
|
482
|
+
debug.log(` Chunk ${chunkIdx + 1} complete: ${numPoints} points processed`);
|
|
414
483
|
|
|
415
|
-
// Report progress
|
|
484
|
+
// Report progress (point-based, not path-based)
|
|
416
485
|
if (onProgress) {
|
|
417
486
|
onProgress({
|
|
418
487
|
type: 'tracing-progress',
|
|
419
488
|
data: {
|
|
420
|
-
percent: Math.round((
|
|
421
|
-
current:
|
|
422
|
-
total:
|
|
423
|
-
|
|
489
|
+
percent: Math.round((endPoint / totalSampledPoints) * 100),
|
|
490
|
+
current: endPoint,
|
|
491
|
+
total: totalSampledPoints,
|
|
492
|
+
chunkIndex: chunkIdx + 1,
|
|
493
|
+
totalChunks: chunks.length
|
|
424
494
|
}
|
|
425
495
|
});
|
|
426
496
|
}
|
|
427
497
|
}
|
|
428
498
|
|
|
429
|
-
//
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
499
|
+
// Cleanup reusable buffers
|
|
500
|
+
inputBuffer.destroy();
|
|
501
|
+
outputBuffer.destroy();
|
|
502
|
+
uniformBuffer.destroy();
|
|
503
|
+
stagingBuffer.destroy();
|
|
504
|
+
|
|
505
|
+
debug.log('All chunks processed');
|
|
506
|
+
|
|
507
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
508
|
+
// PHASE 5: Remap unified output back to individual paths & compute maxZ
|
|
509
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
510
|
+
debug.log('PHASE 5: Remapping to individual paths and computing maxZ...');
|
|
511
|
+
|
|
512
|
+
const outputPaths = [];
|
|
513
|
+
const maxZValues = new Array(paths.length).fill(zFloor);
|
|
514
|
+
|
|
515
|
+
for (let pathIdx = 0; pathIdx < pathIndex.length; pathIdx++) {
|
|
516
|
+
const { startOffset, numPoints } = pathIndex[pathIdx];
|
|
517
|
+
|
|
518
|
+
if (numPoints === 0) {
|
|
519
|
+
outputPaths.push(new Float32Array(0)); // Empty path
|
|
520
|
+
debug.log(`Path ${pathIdx + 1}: empty`);
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Allocate XYZ output
|
|
525
|
+
const pathXYZ = new Float32Array(numPoints * 3);
|
|
434
526
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
527
|
+
// Copy from unified buffers + compute maxZ
|
|
528
|
+
for (let i = 0; i < numPoints; i++) {
|
|
529
|
+
const unifiedIdx = startOffset + i;
|
|
530
|
+
const x = unifiedSampledXY[unifiedIdx * 2 + 0];
|
|
531
|
+
const y = unifiedSampledXY[unifiedIdx * 2 + 1];
|
|
532
|
+
const z = unifiedOutputZ[unifiedIdx];
|
|
439
533
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
maxZStagingBuffer.unmap();
|
|
534
|
+
pathXYZ[i * 3 + 0] = x;
|
|
535
|
+
pathXYZ[i * 3 + 1] = y;
|
|
536
|
+
pathXYZ[i * 3 + 2] = z;
|
|
444
537
|
|
|
445
|
-
|
|
446
|
-
|
|
538
|
+
// Track max Z for this path (CPU-side)
|
|
539
|
+
maxZValues[pathIdx] = Math.max(maxZValues[pathIdx], z);
|
|
540
|
+
}
|
|
447
541
|
|
|
448
|
-
|
|
542
|
+
outputPaths.push(pathXYZ);
|
|
543
|
+
debug.log(`Path ${pathIdx + 1}: ${numPoints} points, maxZ=${maxZValues[pathIdx].toFixed(2)}`);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Cleanup maxZ buffer (was only used for shader compatibility)
|
|
449
547
|
maxZBuffer.destroy();
|
|
450
|
-
maxZStagingBuffer.destroy();
|
|
451
548
|
|
|
452
549
|
// Cleanup temporary buffers only (don't destroy cached buffers)
|
|
453
550
|
if (shouldCleanupBuffers) {
|
|
@@ -468,25 +565,19 @@ export async function generateTracingToolpaths({
|
|
|
468
565
|
}
|
|
469
566
|
|
|
470
567
|
/**
|
|
471
|
-
*
|
|
568
|
+
* IMPLEMENTATION NOTE: Unified Batching System
|
|
472
569
|
*
|
|
473
|
-
*
|
|
474
|
-
* Currently processes one path at a time. For better GPU utilization:
|
|
570
|
+
* This function uses a unified batching approach for optimal performance:
|
|
475
571
|
*
|
|
476
|
-
* 1.
|
|
477
|
-
* 2.
|
|
478
|
-
* 3.
|
|
479
|
-
* 4.
|
|
572
|
+
* 1. All paths are sampled and concatenated into a single unified buffer
|
|
573
|
+
* 2. Paths are chunked based on GPU memory limits (handles giant paths)
|
|
574
|
+
* 3. Each chunk is processed with a single GPU dispatch (reduces overhead)
|
|
575
|
+
* 4. Output is remapped back to individual path arrays
|
|
576
|
+
* 5. MaxZ is computed on CPU (avoids complex GPU atomic coordination)
|
|
480
577
|
*
|
|
481
578
|
* BENEFITS:
|
|
482
|
-
* -
|
|
483
|
-
* -
|
|
484
|
-
* -
|
|
485
|
-
*
|
|
486
|
-
* COMPLEXITY:
|
|
487
|
-
* - Need offset management in shader or CPU-side splitting
|
|
488
|
-
* - Memory limit checking becomes more complex
|
|
489
|
-
* - Progress reporting granularity reduced (can still report workgroup completion)
|
|
490
|
-
*
|
|
491
|
-
* ESTIMATE: 2-5x speedup for many small paths, minimal benefit for few large paths
|
|
579
|
+
* - Handles paths that exceed GPU memory limits (automatic chunking)
|
|
580
|
+
* - Reduces GPU dispatch overhead (10-100x for many small paths)
|
|
581
|
+
* - Better progress tracking (point-based instead of path-based)
|
|
582
|
+
* - Buffer pool pattern reduces allocation overhead
|
|
492
583
|
*/
|
|
@@ -65,6 +65,10 @@ export let cachedRadialBatchPipeline = null;
|
|
|
65
65
|
export let cachedRadialBatchShaderModule = null;
|
|
66
66
|
export let cachedTracingPipeline = null;
|
|
67
67
|
export let cachedTracingShaderModule = null;
|
|
68
|
+
export let cachedRadialV3RotatePipeline = null;
|
|
69
|
+
export let cachedRadialV3RotateShaderModule = null;
|
|
70
|
+
export let cachedRadialV3BatchedRasterizePipeline = null;
|
|
71
|
+
export let cachedRadialV3BatchedRasterizeShaderModule = null;
|
|
68
72
|
|
|
69
73
|
// Constants
|
|
70
74
|
export const EMPTY_CELL = -1e10;
|
|
@@ -97,6 +101,8 @@ const rasterizeShaderCode = 'SHADER:planar-rasterize';
|
|
|
97
101
|
const toolpathShaderCode = 'SHADER:planar-toolpath';
|
|
98
102
|
const radialRasterizeShaderCode = 'SHADER:radial-raster';
|
|
99
103
|
const tracingShaderCode = 'SHADER:tracing-toolpath';
|
|
104
|
+
const radialV3RotateShaderCode = 'SHADER:radial-rotate-triangles';
|
|
105
|
+
const radialV3BatchedRasterizeShaderCode = 'SHADER:radial-rasterize-batched';
|
|
100
106
|
|
|
101
107
|
// Initialize WebGPU device in worker context
|
|
102
108
|
export async function initWebGPU() {
|
|
@@ -167,6 +173,24 @@ export async function initWebGPU() {
|
|
|
167
173
|
compute: { module: cachedTracingShaderModule, entryPoint: 'main' },
|
|
168
174
|
});
|
|
169
175
|
|
|
176
|
+
// Pre-compile radial V3 rotate shader module
|
|
177
|
+
cachedRadialV3RotateShaderModule = device.createShaderModule({ code: radialV3RotateShaderCode });
|
|
178
|
+
|
|
179
|
+
// Pre-create radial V3 rotate pipeline
|
|
180
|
+
cachedRadialV3RotatePipeline = device.createComputePipeline({
|
|
181
|
+
layout: 'auto',
|
|
182
|
+
compute: { module: cachedRadialV3RotateShaderModule, entryPoint: 'main' },
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Pre-compile radial V3 batched rasterize shader module
|
|
186
|
+
cachedRadialV3BatchedRasterizeShaderModule = device.createShaderModule({ code: radialV3BatchedRasterizeShaderCode });
|
|
187
|
+
|
|
188
|
+
// Pre-create radial V3 batched rasterize pipeline
|
|
189
|
+
cachedRadialV3BatchedRasterizePipeline = device.createComputePipeline({
|
|
190
|
+
layout: 'auto',
|
|
191
|
+
compute: { module: cachedRadialV3BatchedRasterizeShaderModule, entryPoint: 'main' },
|
|
192
|
+
});
|
|
193
|
+
|
|
170
194
|
// Store device capabilities
|
|
171
195
|
deviceCapabilities = {
|
|
172
196
|
maxStorageBufferBindingSize: device.limits.maxStorageBufferBindingSize,
|
package/src/core/raster-path.js
CHANGED
|
@@ -111,6 +111,8 @@ export class RasterPath {
|
|
|
111
111
|
gpuMemorySafetyMargin: config.gpuMemorySafetyMargin ?? 0.8,
|
|
112
112
|
autoTiling: config.autoTiling ?? true,
|
|
113
113
|
batchDivisor: config.batchDivisor ?? 1, // For testing batching overhead
|
|
114
|
+
radialV3: config.radialV3 ?? false, // Use radial V3 pipeline (rotate-filter-toolpath)
|
|
115
|
+
radialV4: config.radialV4 ?? false, // Use radial V4 pipeline (slice-based lathe)
|
|
114
116
|
debug: config.debug,
|
|
115
117
|
quiet: config.quiet
|
|
116
118
|
};
|
|
@@ -531,9 +533,15 @@ export class RasterPath {
|
|
|
531
533
|
resolve(data);
|
|
532
534
|
};
|
|
533
535
|
|
|
534
|
-
// Send entire pipeline to worker
|
|
536
|
+
// Send entire pipeline to worker (use V3 or V4 if configured)
|
|
537
|
+
const messageType = this.config.radialV4
|
|
538
|
+
? 'radial-generate-toolpaths-v4'
|
|
539
|
+
: this.config.radialV3
|
|
540
|
+
? 'radial-generate-toolpaths-v3'
|
|
541
|
+
: 'radial-generate-toolpaths';
|
|
542
|
+
|
|
535
543
|
this.#sendMessage(
|
|
536
|
-
|
|
544
|
+
messageType,
|
|
537
545
|
{
|
|
538
546
|
triangles: triangles,
|
|
539
547
|
bucketData,
|
|
@@ -54,6 +54,7 @@ import { initWebGPU, setConfig, updateConfig, deviceCapabilities, debug, device
|
|
|
54
54
|
import { rasterizeMesh } from './raster-planar.js';
|
|
55
55
|
import { generateToolpath } from './path-planar.js';
|
|
56
56
|
import { generateRadialToolpaths } from './path-radial.js';
|
|
57
|
+
import { generateRadialToolpathsV3 } from './path-radial-v3.js';
|
|
57
58
|
import { generateTracingToolpaths, createReusableTracingBuffers, destroyReusableTracingBuffers } from './path-tracing.js';
|
|
58
59
|
import { calibrateGPU } from './workload-calibrate.js';
|
|
59
60
|
|
|
@@ -128,6 +129,15 @@ self.onmessage = async function(e) {
|
|
|
128
129
|
}, toolpathTransferBuffers);
|
|
129
130
|
break;
|
|
130
131
|
|
|
132
|
+
case 'radial-generate-toolpaths-v3':
|
|
133
|
+
const radialV3ToolpathResult = await generateRadialToolpathsV3(data);
|
|
134
|
+
const v3ToolpathTransferBuffers = radialV3ToolpathResult.strips.map(strip => strip.pathData.buffer);
|
|
135
|
+
self.postMessage({
|
|
136
|
+
type: 'radial-toolpaths-complete',
|
|
137
|
+
data: radialV3ToolpathResult
|
|
138
|
+
}, v3ToolpathTransferBuffers);
|
|
139
|
+
break;
|
|
140
|
+
|
|
131
141
|
case 'tracing-generate-toolpaths':
|
|
132
142
|
const tracingResult = await generateTracingToolpaths({
|
|
133
143
|
paths: data.paths,
|