diff --git a/app.js b/app.js index 57b03227..a6e439d2 100644 --- a/app.js +++ b/app.js @@ -578,6 +578,8 @@ function rewriteHtmlVersion(req, res, next) { if ([ "/kiri/", "/mesh/", + "/lib/gpu/raster.js", + "/lib/gpu/raster-worker.js", "/lib/mesh/work.js", "/lib/kiri/run/worker.js", "/lib/kiri/run/minion.js", diff --git a/package.json b/package.json index 54240d16..dfd0d1d1 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "@gridspace/app-server": "github:gridspace/app-server#v0.0.19", "@gridspace/basic-ftp": "github:gridspace/basic-ftp#v5.0.5-gridspace", "@gridspace/net-level-client": "^0.2.3", + "@gridspace/raster-path": "github:gridspace/raster-path#main", "@tracespace/parser": "^5.0.0-next.0", "@tweenjs/tween.js": "^16.6.0", "base64-js": "^1.5.1", diff --git a/src/gpu/raster-worker.js b/src/gpu/raster-worker.js new file mode 120000 index 00000000..3abe7863 --- /dev/null +++ b/src/gpu/raster-worker.js @@ -0,0 +1 @@ +../../node_modules/@gridspace/raster-path/src/web/webgpu-worker.js \ No newline at end of file diff --git a/src/gpu/raster.js b/src/gpu/raster.js new file mode 120000 index 00000000..5ebcfa64 --- /dev/null +++ b/src/gpu/raster.js @@ -0,0 +1 @@ +../../node_modules/@gridspace/raster-path/src/index.js \ No newline at end of file diff --git a/src/kiri/core/conf.js b/src/kiri/core/conf.js index f4fd6ccd..b8f5ffb4 100644 --- a/src/kiri/core/conf.js +++ b/src/kiri/core/conf.js @@ -943,6 +943,7 @@ export const conf = { threaded: true, units: "mm", view: null, + webGPU: false, zoomSpeed: 1.0, }, // default hidden ui groups diff --git a/src/kiri/core/init.js b/src/kiri/core/init.js index 02520c08..29dd46a2 100644 --- a/src/kiri/core/init.js +++ b/src/kiri/core/init.js @@ -142,26 +142,27 @@ function booleanSave() { if (control.antiAlias != ui.antiAlias.checked) { api.show.alert('Page Reload Required to Change Aliasing'); } - control.shiny = ui.shiny.checked; + control.antiAlias = ui.antiAlias.checked; + control.assembly = ui.assembly.checked; + control.autoLayout = ui.autoLayout.checked; + control.autoSave = ui.autoSave.checked; + control.dark = ui.dark.checked; + control.devel = ui.devel.checked; control.drawer = ui.drawer.checked; + control.exportOcto = ui.exportOcto.checked; + control.exportPreview = ui.exportPreview.checked; + control.exportThumb = ui.exportThumb.checked; + control.freeLayout = ui.freeLayout.checked; + control.healMesh = ui.healMesh.checked; + control.ortho = ui.ortho.checked; + control.reverseZoom = ui.reverseZoom.checked; control.scrolls = ui.scrolls.checked; + control.shiny = ui.shiny.checked; control.showOrigin = ui.showOrigin.checked; control.showRulers = ui.showRulers.checked; - control.autoLayout = ui.autoLayout.checked; - control.freeLayout = ui.freeLayout.checked; control.spaceRandoX = ui.spaceRandoX.checked; - control.autoSave = ui.autoSave.checked; - control.antiAlias = ui.antiAlias.checked; - control.reverseZoom = ui.reverseZoom.checked; - control.dark = ui.dark.checked; - control.exportOcto = ui.exportOcto.checked; - control.exportThumb = ui.exportThumb.checked; - control.exportPreview = ui.exportPreview.checked; - control.healMesh = ui.healMesh.checked; control.threaded = setThreaded(ui.threaded.checked); - control.assembly = ui.assembly.checked; - control.ortho = ui.ortho.checked; - control.devel = ui.devel.checked; + control.webGPU = ui.webGPU.checked; space.view.setZoom(control.reverseZoom, control.zoomSpeed); // platform.layout(); api.conf.save(); @@ -1063,6 +1064,7 @@ function init_one() { healMesh: newBoolean(LANG.pt_heal_s, booleanSave, {title: LANG.pt_heal_l}), threaded: newBoolean(LANG.pt_thrd_s, booleanSave, {title: LANG.pt_thrd_l, modes:THREED}), assembly: newBoolean(LANG.pt_assy_s, booleanSave, {title: LANG.pt_assy_l, modes:THREED}), + webGPU: newBoolean(LANG.pt_wgpu_s, booleanSave, {title: LANG.pt_wgpu_l, modes:THREED}), prefadd: uc.checkpoint($('prefs-add')), @@ -1630,20 +1632,22 @@ function init_two() { space.platform.setZOff(0.05); // restore UI state from settings + ui.antiAlias.checked = control.antiAlias; + ui.assembly.checked = control.assembly; + ui.autoLayout.checked = control.autoLayout; + ui.autoSave.checked = control.autoSave; + ui.devel.checked = control.devel; + ui.freeLayout.checked = control.freeLayout; + ui.healMesh.checked = control.healMesh; + ui.ortho.checked = control.ortho; + ui.reverseZoom.checked = control.reverseZoom; ui.showOrigin.checked = control.showOrigin; ui.showRulers.checked = control.showRulers; ui.showSpeeds.checked = control.showSpeeds; - ui.freeLayout.checked = control.freeLayout; - ui.autoLayout.checked = control.autoLayout; ui.spaceRandoX.checked = control.spaceRandoX; - ui.antiAlias.checked = control.antiAlias; - ui.reverseZoom.checked = control.reverseZoom; - ui.autoSave.checked = control.autoSave; - ui.healMesh.checked = control.healMesh; ui.threaded.checked = setThreaded(control.threaded); - ui.assembly.checked = control.assembly; - ui.ortho.checked = control.ortho; - ui.devel.checked = control.devel; + ui.webGPU.checked = control.webGPU; + lineTypeSave(); detailSave(); updateStats(); diff --git a/src/kiri/mode/cam/topo4.js b/src/kiri/mode/cam/topo4.js index f29934c2..171b93d0 100644 --- a/src/kiri/mode/cam/topo4.js +++ b/src/kiri/mode/cam/topo4.js @@ -7,6 +7,7 @@ import { sliceConnect } from '../../../geo/slicer.js'; import { newSlice } from '../../core/slice.js'; import { Tool } from './tool.js'; import { Slicer as topo_slicer } from './slicer_topo.js'; +import { RasterPath } from '../../../gpu/raster.js'; const RAD2DEG = 180 / Math.PI; const DEG2RAD = Math.PI / 180; @@ -24,6 +25,7 @@ export class Topo { let { state, op, onupdate, ondone } = opt; let { widget, settings, tabs } = opt.state; let { controller, process } = settings; + let { webGPU } = controller; let axis = op.axis.toLowerCase(), tool = new Tool(settings, op.tool), @@ -65,11 +67,14 @@ export class Topo { this.lineColor = state.settings.controller.dark ? 0xffff00 : 0x555500; this.offStart = op.offStart ?? 0; this.offEnd = op.offEnd ?? 0; + this.bounds = bounds; + this.gpu = webGPU ?? false; onupdate(0, "lathe"); const range = this.range = { min: Infinity, max: -Infinity }; const slices = this.sliced = await this.slice(scale(onupdate, 0.25, 0)); + for (let slice of slices) { range.min = Math.min(range.min, slice.z); range.max = Math.max(range.max, slice.z); @@ -91,7 +96,6 @@ export class Topo { const { vertices, resolution, tabverts, zoff, offStart, offEnd, tool, unit } = this; const { minions } = self.kiri_worker; const range = this.range = { min: Infinity, max: -Infinity }; - const box = this.box = new THREE.Box2(); // swap XZ in shared array for (let i = 0, l = vertices.length; i < l; i += 3) { @@ -119,6 +123,11 @@ export class Topo { // re-create shared vertex array for workers this.vertices = [].appendAll(vertices).appendAll(tabverts).toFloat32().toShared(); + // rp.rasterizeMesh(vertices, resolution).then(rpo => console.log({ rpo })); + if (this.gpu) { + return await this.sliceGPU(onupdate); + } + const zSpan = range.max - range.min; const shards = Math.ceil(Math.min(25, vertices.length / 27000)); const totalSteps = Math.ceil(zSpan / resolution); @@ -151,6 +160,73 @@ export class Topo { } } + async sliceGPU(onupdate) { + const { angle, linear, offStart, offEnd, resolution, tool, unit, vertices, zBottom } = this; + + let gpu = new RasterPath({ workerName: "/lib/gpu/raster-worker.js" }); + await gpu.init(); + await gpu.initWorkerPool(); + + let tickperstep = Math.round(this.step / resolution); + let bounds = this.bounds.clone(); + bounds.min.x += offStart * unit; + bounds.max.x -= offEnd * unit; + let output = await gpu.generateRadialToolpath( + vertices, + tool, + angle, + tickperstep, + zBottom, + resolution, + bounds, + (pct, msg) => { console.log({ pct, msg }) } + ); + + // console.log({ tool, output, tickperstep }); + // const axis = new THREE.Vector3(1, 0, 0); + let { numRotations, pointsPerLine, pathData } = output; + let degPerRow = 360 / numRotations; + let slices = this.gpu_slices = []; + let xmult = tickperstep * resolution; + let xoff = bounds.min.x; + let rows = []; + for (let i=0; i newPoint(j * xmult + xoff, 0, v).setA(-i * degPerRow)); + // let points = Array.from(lineData).map((v,i) => [ i * xmult + xoff, 0, v ]).flat().toFloat32(); + // let rot = new THREE.Matrix4().makeRotationAxis(axis, (Math.PI / numRotations * 2) * i); + // new THREE.BufferAttribute(points, 3).applyMatrix4(rot); + // points = Array.from(points).group(3).map((v,i) => newPoint(v[0], v[1], v[2])); + rows.push(points); + } + if (linear) { + for (let i=0; i row[i]); + // points.push(points[0].setA(points[0].a - 360)); + if (i % 2 === 1) points.reverse(); + slice.index = i; + slice.camLines = [ newPolygon(points).setOpen() ]; + slices.push(slice); + } + } + for (let slice of slices) { + slice.output() + .setLayer("lathe", { line: this.lineColor }) + .addPoly(slice.camLines[0].clone().applyRotations()); + } + // console.log({ webGPU: output, slices }); + return slices; + } + async sliceWorker(onupdate) { const { vertices, slices, resolution } = this; @@ -215,13 +291,19 @@ export class Topo { async lathe(onupdate) { const { minions } = self.kiri_worker; - if (minions?.running > 1) { + if (this.gpu) { + return await this.latheGPU(onupdate); + } else if (minions?.running > 1) { return await this.latheMinions(onupdate); } else { return await this.latheWorker(onupdate); } } + async latheGPU(onupdate) { + return this.gpu_slices; + } + lathePath(slices, tool) { const { resolution, step, zBottom, maxo } = this; diff --git a/web/kiri/lang/en.js b/web/kiri/lang/en.js index ea00c48b..ac673935 100644 --- a/web/kiri/lang/en.js +++ b/web/kiri/lang/en.js @@ -323,6 +323,8 @@ self.lang['en-us'] = { pt_thrd_l: ["use parallel processing when browser supports nested workers. may increase memory pressure on larger models. disable if you are getting out of memory errors"], pt_assy_s: "assembly", pt_assy_l: ["use web assembly when supported by browser. if you experience artifacts or memory errors, try disabling this."], + pt_wgpu_s: "web gpu", + pt_wgpu_l: ["use web gpu when supported by browser"], xp_menu: "exports",