@gridspace/raster-path 1.0.3 → 1.0.5
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 +3 -5
- package/build/app.js +363 -39
- package/build/index.html +40 -2
- package/build/raster-path.js +16 -24
- package/build/raster-worker.js +2450 -0
- package/build/style.css +65 -0
- package/package.json +12 -4
- package/scripts/build-shaders.js +32 -8
- package/src/core/path-planar.js +788 -0
- package/src/core/path-radial.js +651 -0
- package/src/core/raster-config.js +185 -0
- package/src/{index.js → core/raster-path.js} +16 -24
- package/src/core/raster-planar.js +754 -0
- package/src/core/raster-tool.js +104 -0
- package/src/core/raster-worker.js +152 -0
- package/src/core/workload-calibrate.js +416 -0
- package/src/shaders/{radial-raster-v2.wgsl → radial-raster.wgsl} +8 -2
- package/src/shaders/workload-calibrate.wgsl +106 -0
- package/src/test/batch-divisor-benchmark.cjs +286 -0
- package/src/test/calibrate-test.cjs +136 -0
- package/src/test/extreme-work-test.cjs +167 -0
- package/src/test/lathe-cylinder-2-debug.cjs +334 -0
- package/src/test/lathe-cylinder-2-test.cjs +157 -0
- package/src/test/lathe-cylinder-test.cjs +198 -0
- package/src/test/radial-thread-limit-test.cjs +152 -0
- package/src/test/work-estimation-profile.cjs +406 -0
- package/src/test/workload-calculator-demo.cjs +113 -0
- package/src/test/workload-calibration.cjs +310 -0
- package/src/web/app.js +363 -39
- package/src/web/index.html +40 -2
- package/src/web/style.css +65 -0
- package/src/workload-calculator.js +318 -0
- package/build/webgpu-worker.js +0 -3011
- package/src/web/webgpu-worker.js +0 -2520
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ═══════════════════════════════════════════════════════════════════════════
|
|
3
|
+
* Raster Config - Shared WebGPU State and Configuration
|
|
4
|
+
* ═══════════════════════════════════════════════════════════════════════════
|
|
5
|
+
*
|
|
6
|
+
* Central module providing shared state, configuration, and utilities for all
|
|
7
|
+
* worker modules. All WebGPU resources and pipelines are initialized here.
|
|
8
|
+
*
|
|
9
|
+
* EXPORTS:
|
|
10
|
+
* ────────
|
|
11
|
+
* State Variables:
|
|
12
|
+
* - config - Worker configuration (memory limits, tiling, etc.)
|
|
13
|
+
* - device - WebGPU device handle
|
|
14
|
+
* - deviceCapabilities - Device limits and capabilities
|
|
15
|
+
* - isInitialized - Initialization status flag
|
|
16
|
+
*
|
|
17
|
+
* Cached Pipelines:
|
|
18
|
+
* - cachedRasterizePipeline - Planar rasterization compute pipeline
|
|
19
|
+
* - cachedToolpathPipeline - Planar toolpath generation pipeline
|
|
20
|
+
* - cachedRadialBatchPipeline - Radial rasterization pipeline
|
|
21
|
+
* - cachedRasterizeShaderModule - Compiled planar rasterize shader
|
|
22
|
+
* - cachedToolpathShaderModule - Compiled planar toolpath shader
|
|
23
|
+
* - cachedRadialBatchShaderModule - Compiled radial shader
|
|
24
|
+
*
|
|
25
|
+
* Constants:
|
|
26
|
+
* - EMPTY_CELL - Sentinel value for empty raster cells (-1e10)
|
|
27
|
+
* - log_pre - Log prefix string "[Worker]"
|
|
28
|
+
* - diagnostic - Debug mode flag
|
|
29
|
+
*
|
|
30
|
+
* Utilities:
|
|
31
|
+
* - debug - Logging object (error, warn, log, ok)
|
|
32
|
+
* - round(v, d) - Round number to d decimal places
|
|
33
|
+
*
|
|
34
|
+
* Functions:
|
|
35
|
+
* - initWebGPU() - Initialize WebGPU device and compile pipelines
|
|
36
|
+
* - setConfig(obj) - Replace entire config object
|
|
37
|
+
* - updateConfig(obj) - Merge updates into existing config
|
|
38
|
+
*
|
|
39
|
+
* ARCHITECTURE:
|
|
40
|
+
* ─────────────
|
|
41
|
+
* This module acts as a singleton state container. All worker modules import
|
|
42
|
+
* from here to access GPU resources. The initWebGPU() function is called once
|
|
43
|
+
* during worker initialization and pre-compiles all compute pipelines to avoid
|
|
44
|
+
* runtime compilation overhead.
|
|
45
|
+
*
|
|
46
|
+
* Shader code placeholders ('SHADER:xxx') are replaced during build by the
|
|
47
|
+
* build-shaders.js script, which bundles modules with esbuild then injects
|
|
48
|
+
* WGSL shader source code.
|
|
49
|
+
*
|
|
50
|
+
* ═══════════════════════════════════════════════════════════════════════════
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
// WebGPU state
|
|
54
|
+
export let config = {};
|
|
55
|
+
export let device = null;
|
|
56
|
+
export let deviceCapabilities = null;
|
|
57
|
+
export let isInitialized = false;
|
|
58
|
+
|
|
59
|
+
// Cached pipelines (created during initialization)
|
|
60
|
+
export let cachedRasterizePipeline = null;
|
|
61
|
+
export let cachedRasterizeShaderModule = null;
|
|
62
|
+
export let cachedToolpathPipeline = null;
|
|
63
|
+
export let cachedToolpathShaderModule = null;
|
|
64
|
+
export let cachedRadialBatchPipeline = null;
|
|
65
|
+
export let cachedRadialBatchShaderModule = null;
|
|
66
|
+
|
|
67
|
+
// Constants
|
|
68
|
+
export const EMPTY_CELL = -1e10;
|
|
69
|
+
export const log_pre = '[Worker]';
|
|
70
|
+
export const diagnostic = false;
|
|
71
|
+
|
|
72
|
+
// Logging utilities
|
|
73
|
+
let lastlog;
|
|
74
|
+
export const debug = {
|
|
75
|
+
error: function() { console.error(log_pre, ...arguments) },
|
|
76
|
+
warn: function() { console.warn(log_pre, ...arguments) },
|
|
77
|
+
log: function() {
|
|
78
|
+
if (!config.quiet) {
|
|
79
|
+
let now = performance.now();
|
|
80
|
+
let since = ((now - (lastlog ?? now)) | 0).toString().padStart(4,' ');
|
|
81
|
+
console.log(log_pre, `[${since}]`, ...arguments);
|
|
82
|
+
lastlog = now;
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
ok: function() { console.log(log_pre, '✅', ...arguments) },
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Utility functions
|
|
89
|
+
export function round(v, d = 1) {
|
|
90
|
+
return parseFloat(v.toFixed(d));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Shader code placeholders (replaced by build script)
|
|
94
|
+
const rasterizeShaderCode = 'SHADER:planar-rasterize';
|
|
95
|
+
const toolpathShaderCode = 'SHADER:planar-toolpath';
|
|
96
|
+
const radialRasterizeShaderCode = 'SHADER:radial-raster';
|
|
97
|
+
|
|
98
|
+
// Initialize WebGPU device in worker context
|
|
99
|
+
export async function initWebGPU() {
|
|
100
|
+
if (isInitialized) return true;
|
|
101
|
+
|
|
102
|
+
if (!navigator.gpu) {
|
|
103
|
+
debug.warn('WebGPU not supported');
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const adapter = await navigator.gpu.requestAdapter();
|
|
109
|
+
if (!adapter) {
|
|
110
|
+
debug.warn('WebGPU adapter not available');
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Request device with higher limits for large meshes
|
|
115
|
+
const adapterLimits = adapter.limits;
|
|
116
|
+
debug.log('Adapter limits:', {
|
|
117
|
+
maxStorageBufferBindingSize: adapterLimits.maxStorageBufferBindingSize,
|
|
118
|
+
maxBufferSize: adapterLimits.maxBufferSize
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
device = await adapter.requestDevice({
|
|
122
|
+
requiredLimits: {
|
|
123
|
+
maxStorageBufferBindingSize: Math.min(
|
|
124
|
+
adapterLimits.maxStorageBufferBindingSize,
|
|
125
|
+
1024 * 1024 * 1024 // Request up to 1GB
|
|
126
|
+
),
|
|
127
|
+
maxBufferSize: Math.min(
|
|
128
|
+
adapterLimits.maxBufferSize,
|
|
129
|
+
1024 * 1024 * 1024 // Request up to 1GB
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Pre-compile rasterize shader module (expensive operation)
|
|
135
|
+
cachedRasterizeShaderModule = device.createShaderModule({ code: rasterizeShaderCode });
|
|
136
|
+
|
|
137
|
+
// Pre-create rasterize pipeline (very expensive operation)
|
|
138
|
+
cachedRasterizePipeline = device.createComputePipeline({
|
|
139
|
+
layout: 'auto',
|
|
140
|
+
compute: { module: cachedRasterizeShaderModule, entryPoint: 'main' },
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Pre-compile toolpath shader module
|
|
144
|
+
cachedToolpathShaderModule = device.createShaderModule({ code: toolpathShaderCode });
|
|
145
|
+
|
|
146
|
+
// Pre-create toolpath pipeline
|
|
147
|
+
cachedToolpathPipeline = device.createComputePipeline({
|
|
148
|
+
layout: 'auto',
|
|
149
|
+
compute: { module: cachedToolpathShaderModule, entryPoint: 'main' },
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Pre-compile radial batch shader module
|
|
153
|
+
cachedRadialBatchShaderModule = device.createShaderModule({ code: radialRasterizeShaderCode });
|
|
154
|
+
|
|
155
|
+
// Pre-create radial batch pipeline
|
|
156
|
+
cachedRadialBatchPipeline = device.createComputePipeline({
|
|
157
|
+
layout: 'auto',
|
|
158
|
+
compute: { module: cachedRadialBatchShaderModule, entryPoint: 'main' },
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Store device capabilities
|
|
162
|
+
deviceCapabilities = {
|
|
163
|
+
maxStorageBufferBindingSize: device.limits.maxStorageBufferBindingSize,
|
|
164
|
+
maxBufferSize: device.limits.maxBufferSize,
|
|
165
|
+
maxComputeWorkgroupSizeX: device.limits.maxComputeWorkgroupSizeX,
|
|
166
|
+
maxComputeWorkgroupSizeY: device.limits.maxComputeWorkgroupSizeY,
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
isInitialized = true;
|
|
170
|
+
debug.log('Initialized (pipelines cached)');
|
|
171
|
+
return true;
|
|
172
|
+
} catch (error) {
|
|
173
|
+
debug.error('Failed to initialize:', error);
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Mutators for config (called from worker main)
|
|
179
|
+
export function setConfig(newConfig) {
|
|
180
|
+
config = newConfig;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function updateConfig(updates) {
|
|
184
|
+
Object.assign(config, updates);
|
|
185
|
+
}
|
|
@@ -48,13 +48,10 @@
|
|
|
48
48
|
* @property {'planar'|'radial'} mode - Rasterization mode (default: 'planar')
|
|
49
49
|
* @property {boolean} autoTiling - Automatically tile large datasets (default: true)
|
|
50
50
|
* @property {number} gpuMemorySafetyMargin - Safety margin as percentage (default: 0.8 = 80%)
|
|
51
|
-
* @property {number} maxConcurrentTiles - Max concurrent tiles for radial rasterization (default: 50)
|
|
52
51
|
* @property {number} maxGPUMemoryMB - Maximum GPU memory per tile (default: 256MB)
|
|
53
|
-
* @property {number} minTileSize - Minimum tile dimension (default: 50mm)
|
|
54
|
-
* @property {number} radialRotationOffset - Radial mode: rotation offset in degrees (default: 0, use 90 to start at Z-axis)
|
|
55
52
|
* @property {number} resolution - Grid step size in mm (required)
|
|
56
53
|
* @property {number} rotationStep - Radial mode only: degrees between rays (e.g., 1.0 = 360 rays)
|
|
57
|
-
* @property {number}
|
|
54
|
+
* @property {number} batchDivisor - Testing parameter to artificially divide batch size (default: 1)
|
|
58
55
|
* @property {boolean} debug - Enable debug logging (default: false)
|
|
59
56
|
* @property {boolean} quiet - Suppress log output (default: false)
|
|
60
57
|
*/
|
|
@@ -103,26 +100,22 @@ export class RasterPath {
|
|
|
103
100
|
this.deviceCapabilities = null;
|
|
104
101
|
|
|
105
102
|
// Configure debug output
|
|
106
|
-
let urlOpt = [];
|
|
107
103
|
if (config.quiet) {
|
|
108
104
|
debug.log = function() {};
|
|
109
|
-
urlOpt.push('quiet');
|
|
110
|
-
}
|
|
111
|
-
if (config.debug) {
|
|
112
|
-
urlOpt.push('debug');
|
|
113
105
|
}
|
|
114
106
|
|
|
115
107
|
// Configuration with defaults
|
|
116
108
|
this.config = {
|
|
117
|
-
workerName:
|
|
109
|
+
workerName: config.workerName ?? "raster-worker.js",
|
|
118
110
|
maxGPUMemoryMB: config.maxGPUMemoryMB ?? 256,
|
|
119
111
|
gpuMemorySafetyMargin: config.gpuMemorySafetyMargin ?? 0.8,
|
|
120
112
|
autoTiling: config.autoTiling ?? true,
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
radialRotationOffset: config.radialRotationOffset ?? 0, // degrees
|
|
113
|
+
batchDivisor: config.batchDivisor ?? 1, // For testing batching overhead
|
|
114
|
+
debug: config.debug,
|
|
115
|
+
quiet: config.quiet
|
|
125
116
|
};
|
|
117
|
+
|
|
118
|
+
debug.log('config', this.config);
|
|
126
119
|
}
|
|
127
120
|
|
|
128
121
|
/**
|
|
@@ -137,14 +130,14 @@ export class RasterPath {
|
|
|
137
130
|
|
|
138
131
|
return new Promise((resolve, reject) => {
|
|
139
132
|
try {
|
|
140
|
-
// Create worker from the
|
|
133
|
+
// Create worker from the raster-worker.js file
|
|
141
134
|
const workerName = this.config.workerName;
|
|
142
135
|
const isBuildVersion = import.meta.url.includes('/build/') || import.meta.url.includes('raster-path.js');
|
|
143
136
|
const workerPath = workerName
|
|
144
137
|
? new URL(workerName, import.meta.url)
|
|
145
138
|
: isBuildVersion
|
|
146
|
-
? new URL(`./
|
|
147
|
-
: new URL(
|
|
139
|
+
? new URL(`./raster-worker.js`, import.meta.url)
|
|
140
|
+
: new URL(`../core/raster-worker.js`, import.meta.url);
|
|
148
141
|
this.worker = new Worker(workerPath, { type: 'module' });
|
|
149
142
|
|
|
150
143
|
// Set up message handler
|
|
@@ -238,8 +231,8 @@ export class RasterPath {
|
|
|
238
231
|
const originalBounds = boundsOverride || this.#calculateBounds(triangles);
|
|
239
232
|
|
|
240
233
|
// Center model in YZ plane (required for radial rasterization)
|
|
241
|
-
// Radial mode casts rays from
|
|
242
|
-
//
|
|
234
|
+
// Radial mode casts rays from max_radius distance inward toward the X-axis,
|
|
235
|
+
// and centering ensures the geometry is symmetric around the rotation axis
|
|
243
236
|
const centerY = (originalBounds.min.y + originalBounds.max.y) / 2;
|
|
244
237
|
const centerZ = (originalBounds.min.z + originalBounds.max.z) / 2;
|
|
245
238
|
|
|
@@ -273,12 +266,10 @@ export class RasterPath {
|
|
|
273
266
|
* @param {number} params.xStep - Sample every Nth point in X direction
|
|
274
267
|
* @param {number} params.yStep - Sample every Nth point in Y direction
|
|
275
268
|
* @param {number} params.zFloor - Z floor value for out-of-bounds areas
|
|
276
|
-
* @param {number} params.radiusOffset - (Radial mode only) Distance from terrain surface to tool tip in mm.
|
|
277
|
-
* Used to calculate radial collision offset. Default: 20mm
|
|
278
269
|
* @param {function} params.onProgress - Optional progress callback (progress: number, info?: string) => void
|
|
279
270
|
* @returns {Promise<object>} Planar: {pathData, width, height} | Radial: {strips[], numStrips, totalPoints}
|
|
280
271
|
*/
|
|
281
|
-
async generateToolpaths({ xStep, yStep, zFloor,
|
|
272
|
+
async generateToolpaths({ xStep, yStep, zFloor, onProgress }) {
|
|
282
273
|
if (!this.isInitialized) {
|
|
283
274
|
throw new Error('RasterPath not initialized. Call init() first.');
|
|
284
275
|
}
|
|
@@ -287,6 +278,8 @@ export class RasterPath {
|
|
|
287
278
|
throw new Error('Tool not loaded. Call loadTool() first.');
|
|
288
279
|
}
|
|
289
280
|
|
|
281
|
+
debug.log('gen.paths', { xStep, yStep, zFloor });
|
|
282
|
+
|
|
290
283
|
if (this.mode === 'planar') {
|
|
291
284
|
if (!this.terrainData) {
|
|
292
285
|
throw new Error('Terrain not loaded. Call loadTerrain() first.');
|
|
@@ -444,8 +437,7 @@ export class RasterPath {
|
|
|
444
437
|
zFloor: zFloor,
|
|
445
438
|
bounds,
|
|
446
439
|
xStep,
|
|
447
|
-
yStep
|
|
448
|
-
gridStep: this.resolution
|
|
440
|
+
yStep
|
|
449
441
|
},
|
|
450
442
|
'radial-toolpaths-complete',
|
|
451
443
|
completionHandler
|