diff --git a/build.ps1 b/build.ps1 index 5e3bc3b..479e32f 100755 --- a/build.ps1 +++ b/build.ps1 @@ -1,6 +1,6 @@ $erroractionpreference = "stop" -$sdk_version="v9.3.0-2490-g97c85d6930-20240401" +$sdk_version="wasm32-v9.3.1-3934-g13405b1d99-20240824" $sdk_image="kitware/vtk-wasm-sdk" $sdk_config="Release" diff --git a/build.sh b/build.sh index 607185c..cc92e0f 100755 --- a/build.sh +++ b/build.sh @@ -3,7 +3,7 @@ set -e set -x -sdk_version="v9.3.0-2490-g97c85d6930-20240401" +sdk_version="wasm32-v9.3.1-3934-g13405b1d99-20240824" readonly sdk_version sdk_image="kitware/vtk-wasm-sdk" diff --git a/cpp/ConesViewer/CMakeLists.txt b/cpp/ConesViewer/CMakeLists.txt index 88eff6f..5fc4ed7 100644 --- a/cpp/ConesViewer/CMakeLists.txt +++ b/cpp/ConesViewer/CMakeLists.txt @@ -50,6 +50,8 @@ set(emscripten_compile_options) list(APPEND emscripten_link_options "-lembind" + "-sASYNCIFY=1" # for webgpu async APIs + "-sASYNCIFY_STACK_SIZE=81920" #~297 nesting levels "-sMODULARIZE=1" "-sEXPORT_ES6=1" "-sEXPORT_NAME=createConesViewerModule" diff --git a/cpp/GeometryViewer/CMakeLists.txt b/cpp/GeometryViewer/CMakeLists.txt index fd6d233..761fe27 100644 --- a/cpp/GeometryViewer/CMakeLists.txt +++ b/cpp/GeometryViewer/CMakeLists.txt @@ -55,6 +55,8 @@ set(emscripten_compile_options) list(APPEND emscripten_link_options "-lembind" + "-sASYNCIFY=1" # for webgpu async APIs + "-sASYNCIFY_STACK_SIZE=81920" #~297 nesting levels "-sMODULARIZE=1" "-sEXPORT_ES6=1" "-sEXPORT_NAME=createGeometryViewerModule" diff --git a/package-lock.json b/package-lock.json index 71ac534..01a243f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1209,12 +1209,13 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "devOptional": true, + "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -1746,10 +1747,11 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "devOptional": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -2014,6 +2016,7 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "devOptional": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -2175,12 +2178,13 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -2802,6 +2806,7 @@ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "devOptional": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -2893,10 +2898,11 @@ "dev": true }, "node_modules/vite": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.0.tgz", - "integrity": "sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==", + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz", + "integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==", "devOptional": true, + "license": "MIT", "dependencies": { "esbuild": "^0.18.10", "postcss": "^8.4.27", @@ -3049,10 +3055,11 @@ } }, "node_modules/vue-template-compiler": { - "version": "2.7.15", - "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.15.tgz", - "integrity": "sha512-yQxjxMptBL7UAog00O8sANud99C6wJF+7kgbcwqkvA38vCGF7HWE66w0ZFnS/kX5gSoJr/PQ4/oS3Ne2pW37Og==", + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz", + "integrity": "sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==", "dev": true, + "license": "MIT", "dependencies": { "de-indent": "^1.0.2", "he": "^1.2.0" diff --git a/src/components/ConesViewer/ConesViewer.vue b/src/components/ConesViewer/ConesViewer.vue index 78d3d98..f1075f2 100644 --- a/src/components/ConesViewer/ConesViewer.vue +++ b/src/components/ConesViewer/ConesViewer.vue @@ -2,7 +2,7 @@ import { onMounted, onUnmounted, ref } from "vue"; import { Properties } from "./Properties"; import { getConfiguration } from "@/utils/wasmConfigure" -import { hasWebGPU, getDevice } from "@/utils/wasmWebGPUInit"; +import { hasWebGPU } from "@/utils/wasmWebGPUInit"; import type { ConesViewerModule, ConesViewer } from "./ConesViewerFactory"; import createConesViewerModule from "./ConesViewer" @@ -27,7 +27,6 @@ const props = withDefaults(defineProps(), { var wasmModule: ConesViewerModule | null = null; var gui: GUI | null = null; var viewer: ConesViewer; -var animationRequestId: number = -1; var fpsScript: HTMLScriptElement | null = null; const supportsWebGPU = ref(false) @@ -43,27 +42,26 @@ const options = { animate: props.animate, } -function updateDatasets() { - viewer.createDatasets(options.nx, options.ny, options.nz, options.dx, options.dy, options.dz); - viewer.setMapperStatic(options.mapperIsStatic); - viewer.resetView(); - viewer.render(); +async function updateDatasets() { + await viewer.createDatasets(options.nx, options.ny, options.nz, options.dx, options.dy, options.dz); + await viewer.setMapperStatic(options.mapperIsStatic); + await viewer.resetView(); + await viewer.render(); } -function spinABit() { - viewer.azimuth(1); - viewer.render(); - animationRequestId = requestAnimationFrame(spinABit) +async function sleep() { + return new Promise(requestAnimationFrame); } -function updateAnimateState() { - if (options.animate) { - spinABit(); - } - else { - console.log("cancel " + animationRequestId) - cancelAnimationFrame(animationRequestId) - } +async function animate() { + do { + if (options.animate) + { + await viewer.azimuth(1); + await viewer.render(); + } + await sleep(); + } while (1) } function setupUI() { @@ -82,64 +80,57 @@ function setupUI() { /// show configuration parameters in a GUI. gui = new GUI(); const datasetFolder = gui!.addFolder("Dataset Dimensions") - datasetFolder.add(options, "nx", 1, 100).onChange(() => { - updateDatasets(); + datasetFolder.add(options, "nx", 1, 100).onChange(async () => { + await updateDatasets(); }); - datasetFolder.add(options, "ny", 1, 100).onChange(() => { - updateDatasets(); + datasetFolder.add(options, "ny", 1, 100).onChange(async () => { + await updateDatasets(); }); - datasetFolder.add(options, "nz", 1, 100).onChange(() => { - updateDatasets(); + datasetFolder.add(options, "nz", 1, 100).onChange(async () => { + await updateDatasets(); }); - datasetFolder.add(options, "dx", 1.0, 5.0).onChange(() => { - updateDatasets(); + datasetFolder.add(options, "dx", 1.0, 5.0).onChange(async () => { + await updateDatasets(); }); - datasetFolder.add(options, "dy", 1.0, 5.0).onChange(() => { - updateDatasets(); + datasetFolder.add(options, "dy", 1.0, 5.0).onChange(async () => { + await updateDatasets(); }); - datasetFolder.add(options, "dz", 1.0, 5.0).onChange(() => { - updateDatasets(); + datasetFolder.add(options, "dz", 1.0, 5.0).onChange(async () => { + await updateDatasets(); }); - gui!.add(options, "mapperIsStatic").onChange(() => { - viewer.setMapperStatic(options.mapperIsStatic); + gui!.add(options, "mapperIsStatic").onChange(async () => { + await viewer.setMapperStatic(options.mapperIsStatic); }); - gui!.add(options, "mouseWheelMotionFactor", 0.0, 1.0).onChange(() => { - viewer.setMouseWheelMotionFactor(options.mouseWheelMotionFactor); - }); - gui!.add(options, "animate").onChange(() => { - updateAnimateState(); + gui!.add(options, "mouseWheelMotionFactor", 0.0, 1.0).onChange(async () => { + await viewer.setMouseWheelMotionFactor(options.mouseWheelMotionFactor); }); + gui!.add(options, "animate"); } onMounted(async () => { console.debug("Mounted with properties ", props); - let webgpuDevice: GPUDevice | null = null; if (props.viewApi == "webgpu") { supportsWebGPU.value = await hasWebGPU(); if (!supportsWebGPU.value) { return; } - webgpuDevice = await getDevice(); } - let configuration: any = await getConfiguration(props.viewApi, webgpuDevice); + let configuration: any = await getConfiguration(props.viewApi); wasmModule = await createConesViewerModule(configuration); viewer = new wasmModule!.ConesViewer(); - viewer.initialize(); - viewer.resetView(); - viewer.render(); - updateDatasets() + await viewer.initialize(); + await viewer.resetView(); + await viewer.render(); + await updateDatasets() // starts processing events on browser main thread. - viewer.run(); + await viewer.run(); if (props.showControls) { setupUI(); } - updateAnimateState(); + await animate(); }); onUnmounted(async () => { - if (animationRequestId >= 0) { - cancelAnimationFrame(animationRequestId); - } if (fpsScript !== null) { document.body.removeChild(fpsScript); } diff --git a/src/components/ConesViewer/ConesViewerFactory.d.ts b/src/components/ConesViewer/ConesViewerFactory.d.ts index ad719b9..b94238f 100644 --- a/src/components/ConesViewer/ConesViewerFactory.d.ts +++ b/src/components/ConesViewer/ConesViewerFactory.d.ts @@ -1,15 +1,15 @@ /// export interface ConesViewer { - initialize(): void; - render(): void; - resetView(): void; - run(): void; - createDatasets(nx: number, ny: number, nz: number, dx: number, dy: number, dz: number): void; - setMapperStatic(value: boolean): void; - azimuth(value: number): void; - setMouseWheelMotionFactor(value: number): void; - delete(): void; + initialize(): Promise; + render(): Promise; + resetView(): Promise; + run(): Promise; + createDatasets(nx: number, ny: number, nz: number, dx: number, dy: number, dz: number): Promise; + setMapperStatic(value: boolean): Promise; + azimuth(value: number): Promise; + setMouseWheelMotionFactor(value: number): Promise; + delete(): Promise; } export interface ConesViewerModule extends EmscriptenModule { diff --git a/src/components/GeometryViewer/GeometryViewer.vue b/src/components/GeometryViewer/GeometryViewer.vue index ad7e277..ac30898 100644 --- a/src/components/GeometryViewer/GeometryViewer.vue +++ b/src/components/GeometryViewer/GeometryViewer.vue @@ -8,7 +8,7 @@ import { Properties } from "./Properties"; import { getConfiguration } from '../../utils/wasmConfigure' import { download } from "../../utils/fileDownload" import createGeometryViewerModule from './GeometryViewer' -import { hasWebGPU, getDevice } from "@/utils/wasmWebGPUInit"; +import { hasWebGPU } from "@/utils/wasmWebGPUInit"; import GUI, { Controller } from 'lil-gui' let props = withDefaults(defineProps(), { @@ -74,20 +74,19 @@ const options = { orthographic: props.orthographic, }; -function spinABit() { - viewer.azimuth(1); - viewer.render(); - animationRequestId = requestAnimationFrame(spinABit) +async function sleep() { + return new Promise(requestAnimationFrame); } -function updateAnimateState() { - if (options.animate) { - spinABit(); - } - else { - console.log("cancel " + animationRequestId) - cancelAnimationFrame(animationRequestId) - } +async function animate() { + do { + if (options.animate) + { + await viewer.azimuth(1); + await viewer.render(); + } + await sleep(); + } while (1) } var colorByArraysController: Controller; @@ -108,39 +107,41 @@ async function loadFile(file: File) { wasmModule.HEAPU8.set(data, ptr + offset); offset += data.byteLength; } - viewer.loadDataFileFromMemory(file.name, ptr, file.size); + await viewer.loadDataFileFromMemory(file.name, ptr, file.size); wasmModule._free(ptr); if (gui !== null) { - var colorArrays = ['Solid', ...viewer.getPointDataArrays().split(';'), ...viewer.getCellDataArrays().split(';')]; + let pointDataArrays = await viewer.getPointDataArrays(); + let cellDataArrays = await viewer.getCellDataArrays(); + let colorArrays = ['Solid', ...pointDataArrays.split(';'), ...cellDataArrays.split(';')]; colorByArraysController = colorByArraysController.options(colorArrays.filter((el) => { return el.length > 0; })); - colorByArraysController.onChange(() => { - viewer.setColorMapPreset(options.colorMapPreset); - viewer.setColorByArray(options.colorByArray); - viewer.render(); + colorByArraysController.onChange(async () => { + await viewer.setColorMapPreset(options.colorMapPreset); + await viewer.setColorByArray(options.colorByArray); + await viewer.render(); }); - viewer.setColorMapPreset(options.colorMapPreset); - viewer.setColorByArray(options.colorByArray); - viewer.render(); + await viewer.setColorMapPreset(options.colorMapPreset); + await viewer.setColorByArray(options.colorByArray); + await viewer.render(); } else { // set color map for the default file. - viewer.setColorMapPreset(options.colorMapPreset); - viewer.setColorByArray(options.colorByArray); - viewer.render(); + await viewer.setColorMapPreset(options.colorMapPreset); + await viewer.setColorByArray(options.colorByArray); + await viewer.render(); } - viewer.resetView(); - viewer.render(); + await viewer.resetView(); + await viewer.render(); } async function onFilesChanged() { - viewer.removeAllActors(); + await viewer.removeAllActors(); colorByArraysController.setValue('Solid'); let inputEl = document.getElementById('vtk-input') as HTMLInputElement; let files = inputEl.files as FileList; await loadFile(files[0]); } -function setupUI() { +async function setupUI() { /// setup fps counter fpsScript = document.createElement('script'); fpsScript.onload = () => { @@ -157,123 +158,120 @@ function setupUI() { gui = new GUI(); gui.add(options, 'simulateFileInput').name('Choose file'); const meshFolder = gui.addFolder('Mesh'); - meshFolder?.add(options, 'representation', { Points: 0, Wireframe: 1, Surface: 2, SurfaceWithEdges: 3 }).onChange(() => { - viewer.setRepresentation(options.representation); - viewer.render(); + meshFolder?.add(options, 'representation', { Points: 0, Wireframe: 1, Surface: 2, SurfaceWithEdges: 3 }).onChange(async () => { + await viewer.setRepresentation(options.representation); + await viewer.render(); }); - meshFolder?.add(options, 'vertexVisibility').onChange(() => { - viewer.setVertexVisibility(options.vertexVisibility); - viewer.render(); + meshFolder?.add(options, 'vertexVisibility').onChange(async () => { + await viewer.setVertexVisibility(options.vertexVisibility); + await viewer.render(); }); - meshFolder?.add(options, 'pointSize', 0.1, 10.0, 0.01).onChange(() => { - viewer.setPointSize(options.pointSize); - viewer.render(); + meshFolder?.add(options, 'pointSize', 0.1, 10.0, 0.01).onChange(async () => { + await viewer.setPointSize(options.pointSize); + await viewer.render(); }); - meshFolder?.add(options, 'lineWidth', 0.1, 5.0, 0.01).onChange(() => { - viewer.setLineWidth(options.lineWidth); - viewer.render(); + meshFolder?.add(options, 'lineWidth', 0.1, 5.0, 0.01).onChange(async () => { + await viewer.setLineWidth(options.lineWidth); + await viewer.render(); }); const colorFolder = gui.addFolder('Color'); const scalarMapFolder = colorFolder.addFolder('Scalar Mapping'); colorByArraysController = scalarMapFolder.add(options, 'colorByArray', options.colorArrays); - scalarMapFolder.add(options, 'colorMapPreset', viewer.getColorMapPresets().split(';')).onChange(() => { - viewer.setColorMapPreset(options.colorMapPreset); - viewer.render(); + let presets = await viewer.getColorMapPresets(); + scalarMapFolder.add(options, 'colorMapPreset', presets.split(';')).onChange(async () => { + await viewer.setColorMapPreset(options.colorMapPreset); + await viewer.render(); }); - scalarMapFolder?.add(options, 'interpolateScalarsBeforeMapping').onChange(() => { - viewer.setInterpolateScalarsBeforeMapping(options.interpolateScalarsBeforeMapping); - viewer.render(); + scalarMapFolder?.add(options, 'interpolateScalarsBeforeMapping').onChange(async () => { + await viewer.setInterpolateScalarsBeforeMapping(options.interpolateScalarsBeforeMapping); + await viewer.render(); }); - colorFolder.addColor(options, 'solidColor').onChange(() => { + colorFolder.addColor(options, 'solidColor').onChange(async () => { const rgb = hexToRgb(options.solidColor); - viewer.setColor(rgb[0], rgb[1], rgb[2]); - viewer.render(); + await viewer.setColor(rgb[0], rgb[1], rgb[2]); + await viewer.render(); }); - colorFolder.addColor(options, 'vertexColor').onChange(() => { + colorFolder.addColor(options, 'vertexColor').onChange(async () => { const rgb = hexToRgb(options.vertexColor); - viewer.setVertexColor(rgb[0], rgb[1], rgb[2]); - viewer.render(); + await viewer.setVertexColor(rgb[0], rgb[1], rgb[2]); + await viewer.render(); }); - colorFolder.addColor(options, 'edgeColor').onChange(() => { + colorFolder.addColor(options, 'edgeColor').onChange(async () => { const rgb = hexToRgb(options.edgeColor); - viewer.setEdgeColor(rgb[0], rgb[1], rgb[2]); - viewer.render(); + await viewer.setEdgeColor(rgb[0], rgb[1], rgb[2]); + await viewer.render(); }); - colorFolder.add(options, 'opacity', 0.0, 1.0, 0.01).onChange(() => { - viewer.setOpacity(options.opacity); - viewer.render(); + colorFolder.add(options, 'opacity', 0.0, 1.0, 0.01).onChange(async () => { + await viewer.setOpacity(options.opacity); + await viewer.render(); }) - colorFolder.add(options, 'edgeOpacity', 0.0, 1.0, 0.01).onChange(() => { - viewer.setEdgeOpacity(options.edgeOpacity); - viewer.render(); + colorFolder.add(options, 'edgeOpacity', 0.0, 1.0, 0.01).onChange(async () => { + await viewer.setEdgeOpacity(options.edgeOpacity); + await viewer.render(); }) const viewFolder = gui.addFolder('View'); - viewFolder.addColor(options, 'backgroundColor1').onChange(() => { + viewFolder.addColor(options, 'backgroundColor1').onChange(async () => { const rgb = hexToRgb(options.backgroundColor1); - viewer.setBackgroundColor1(rgb[0], rgb[1], rgb[2]); - viewer.render(); + await viewer.setBackgroundColor1(rgb[0], rgb[1], rgb[2]); + await viewer.render(); }); - viewFolder.addColor(options, 'backgroundColor2').onChange(() => { + viewFolder.addColor(options, 'backgroundColor2').onChange(async () => { const rgb = hexToRgb(options.backgroundColor2); - viewer.setBackgroundColor2(rgb[0], rgb[1], rgb[2]); - viewer.render(); + await viewer.setBackgroundColor2(rgb[0], rgb[1], rgb[2]); + await viewer.render(); }); - viewFolder.add(options, 'mouseWheelMotionFactor', 0.15, 1.0, 0.001).onChange(() => { - viewer.setMouseWheelMotionFactor(options.mouseWheelMotionFactor); + viewFolder.add(options, 'mouseWheelMotionFactor', 0.15, 1.0, 0.001).onChange(async () => { + await viewer.setMouseWheelMotionFactor(options.mouseWheelMotionFactor); }) - viewFolder?.add(options, 'highlightOnHover', { None: 0, Points: 1, Cells: 2 }).onChange(() => { + viewFolder?.add(options, 'highlightOnHover', { None: 0, Points: 1, Cells: 2 }).onChange(async () => { if (options.highlightOnHover == 0) { - viewer.setHighlightOnHover(false, false); + await viewer.setHighlightOnHover(false, false); } else if (options.highlightOnHover == 1) { - viewer.setHighlightOnHover(true, /*snapToPoint*/true); + await viewer.setHighlightOnHover(true, /*snapToPoint*/true); } else if (options.highlightOnHover == 2) { - viewer.setHighlightOnHover(true, /*snapToPoint*/false); + await viewer.setHighlightOnHover(true, /*snapToPoint*/false); } - viewer.render(); - }); - viewFolder.add(options, 'ditherGradient').onChange(() => { - viewer.setDitherGradient(options.ditherGradient); - viewer.render(); + await viewer.render(); }); - viewFolder.add(options, 'orthographic').onChange(() => { - viewer.setUseOrthographicProjection(options.orthographic); - viewer.render(); + viewFolder.add(options, 'ditherGradient').onChange(async () => { + await viewer.setDitherGradient(options.ditherGradient); + await viewer.render(); }); - viewFolder.add(options, 'mouseWheelMotionFactor', 0.0, 1.0).onChange(() => { - viewer.setMouseWheelMotionFactor(options.mouseWheelMotionFactor); + viewFolder.add(options, 'orthographic').onChange(async () => { + await viewer.setUseOrthographicProjection(options.orthographic); + await viewer.render(); }); - viewFolder.add(options, 'animate').onChange(() => { - updateAnimateState(); + viewFolder.add(options, 'mouseWheelMotionFactor', 0.0, 1.0).onChange(async () => { + await viewer.setMouseWheelMotionFactor(options.mouseWheelMotionFactor); }); + viewFolder.add(options, 'animate'); viewFolder.add( { - ResetView: () => { - viewer.resetView(); - viewer.render(); + ResetView: async () => { + await viewer.resetView(); + await viewer.render(); } }, 'ResetView'); } onMounted(async () => { console.debug("Mounted with properties ", props); - let webgpuDevice: GPUDevice | null = null; if (props.viewApi == "webgpu") { supportsWebGPU.value = await hasWebGPU(); if (!supportsWebGPU.value) { return; } - webgpuDevice = await getDevice(); } - let configuration: any = await getConfiguration(props.viewApi, webgpuDevice); + let configuration: any = getConfiguration(props.viewApi); wasmModule = await createGeometryViewerModule(configuration); viewer = new wasmModule!.GeometryViewer(); - viewer.initialize(); - viewer.resetView(); - viewer.render(); + await viewer.initialize(); + await viewer.resetView(); + await viewer.render(); // starts processing events on browser main thread. - viewer.start(); + await viewer.start(); /// connect drop events const dropDestination = document.getElementById('canvas') as HTMLElement; dropDestination!.addEventListener('dragover', (e: DragEvent) => { @@ -288,33 +286,30 @@ onMounted(async () => { dropDestination!.addEventListener('dragleave', () => { dropDestination!.classList.remove('drag-active'); }); - dropDestination!.addEventListener('drop', (e: DragEvent) => { + dropDestination!.addEventListener('drop', async (e: DragEvent) => { e.preventDefault(); dropDestination!.classList.remove('drag-active'); const dataTransfer = e.dataTransfer as DataTransfer; - loadFile(dataTransfer.files[0]); + await loadFile(dataTransfer.files[0]); }); if (props.showControls) { - setupUI(); + await setupUI(); } else { // white background when we're showing ourselves with other apps. // ideally this is exposed via a property. - viewer.setBackgroundColor1(255, 255, 255); - viewer.setBackgroundColor2(255, 255, 255); + await viewer.setBackgroundColor1(255, 255, 255); + await viewer.setBackgroundColor2(255, 255, 255); } // load the default file. if (props.url.length) { - let { blob, filename } = await download(props.url); - loadFile(new File([blob], filename)); + let {blob, filename} = await download(props.url); + await loadFile(new File([blob], filename)); + await animate(); } - updateAnimateState(); }) -onUnmounted(async () => { - if (animationRequestId >= 0) { - cancelAnimationFrame(animationRequestId); - } +onUnmounted(() => { if (fpsScript !== null) { document.body.removeChild(fpsScript); } @@ -330,7 +325,7 @@ onUnmounted(async () => { diff --git a/src/components/GeometryViewer/GeometryViewerFactory.d.ts b/src/components/GeometryViewer/GeometryViewerFactory.d.ts index a0dbea8..d5ebab8 100644 --- a/src/components/GeometryViewer/GeometryViewerFactory.d.ts +++ b/src/components/GeometryViewer/GeometryViewerFactory.d.ts @@ -1,42 +1,42 @@ /// export interface GeometryViewer { - initialize(): void; - render(): void; - resetView(): void; - removeAllActors(): void; - start(): void; - halt(): void; - resume(): void; - saveScreenshotAsPNG(): void; - setBackgroundColor1(_0: number, _1: number, _2: number): void; - setBackgroundColor2(_0: number, _1: number, _2: number): void; - setMouseWheelMotionFactor(_0: number): void; - setHighlightOnHover(_0: boolean, _0: boolean): void - setUseOrthographicProjection(_0: boolean): void; - setDitherGradient(_0: boolean): void; - setVertexVisibility(_0: boolean): void; - setInterpolateScalarsBeforeMapping(_0: boolean): void; - setRepresentation(_0: number): void; - setColor(_0: number, _1: number, _2: number): void; - setVertexColor(_0: number, _1: number, _2: number): void; - setEdgeColor(_0: number, _1: number, _2: number): void; - azimuth(_0: number): void; - setPointSize(_0: number): void; - setLineWidth(_0: number): void; - setOpacity(_0: number): void; - setEdgeOpacity(_0: number): void; - loadDataFileFromMemory(_0: ArrayBuffer | Uint8Array | Uint8ClampedArray | Int8Array | string, _1: number, _2: number): void; - setVertexShaderSource(_0: ArrayBuffer | Uint8Array | Uint8ClampedArray | Int8Array | string): ArrayBuffer | Uint8Array | Uint8ClampedArray | Int8Array | string; - setFragmentShaderSource(_0: ArrayBuffer | Uint8Array | Uint8ClampedArray | Int8Array | string): ArrayBuffer | Uint8Array | Uint8ClampedArray | Int8Array | string; - getVertexShaderSource(): ArrayBuffer | Uint8Array | Uint8ClampedArray | Int8Array | string; - getFragmentShaderSource(): ArrayBuffer | Uint8Array | Uint8ClampedArray | Int8Array | string; - setColorByArray(_0: ArrayBuffer | Uint8Array | Uint8ClampedArray | Int8Array | string): void; - setColorMapPreset(_0: ArrayBuffer | Uint8Array | Uint8ClampedArray | Int8Array | string): void; - getPointDataArrays(): string; - getCellDataArrays(): string; - getColorMapPresets(): string; - delete(): void; + initialize(): Promise; + render(): Promise; + resetView(): Promise; + removeAllActors(): Promise; + start(): Promise; + halt(): Promise; + resume(): Promise; + saveScreenshotAsPNG(): Promise; + setBackgroundColor1(_0: number, _1: number, _2: number): Promise; + setBackgroundColor2(_0: number, _1: number, _2: number): Promise; + setMouseWheelMotionFactor(_0: number): Promise; + setHighlightOnHover(_0: boolean, _0: boolean): Promise + setUseOrthographicProjection(_0: boolean): Promise; + setDitherGradient(_0: boolean): Promise; + setVertexVisibility(_0: boolean): Promise; + setInterpolateScalarsBeforeMapping(_0: boolean): Promise; + setRepresentation(_0: number): Promise; + setColor(_0: number, _1: number, _2: number): Promise; + setVertexColor(_0: number, _1: number, _2: number): Promise; + setEdgeColor(_0: number, _1: number, _2: number): Promise; + azimuth(_0: number): Promise; + setPointSize(_0: number): Promise; + setLineWidth(_0: number): Promise; + setOpacity(_0: number): Promise; + setEdgeOpacity(_0: number): Promise; + loadDataFileFromMemory(_0: ArrayBuffer | Uint8Array | Uint8ClampedArray | Int8Array | string, _1: number, _2: number): Promise; + setVertexShaderSource(_0: ArrayBuffer | Uint8Array | Uint8ClampedArray | Int8Array | string): Promise; + setFragmentShaderSource(_0: ArrayBuffer | Uint8Array | Uint8ClampedArray | Int8Array | string): Promise; + getVertexShaderSource(): Promise; + getFragmentShaderSource(): Promise; + setColorByArray(_0: ArrayBuffer | Uint8Array | Uint8ClampedArray | Int8Array | string): Promise; + setColorMapPreset(_0: ArrayBuffer | Uint8Array | Uint8ClampedArray | Int8Array | string): Promise; + getPointDataArrays(): Promise; + getCellDataArrays(): Promise; + getColorMapPresets(): Promise; + delete(): Promise; } export interface GeometryViewerModule extends EmscriptenModule { diff --git a/src/utils/wasmConfigure.ts b/src/utils/wasmConfigure.ts index aa562ac..188d46a 100644 --- a/src/utils/wasmConfigure.ts +++ b/src/utils/wasmConfigure.ts @@ -1,53 +1,49 @@ -export function getConfiguration(viewAPI: string, webgpuDevice: GPUDevice | null) { - return new Promise((resolve) => { - // Sets up a base configuration for VTK-wasm with WebGL2 rendering. - if (viewAPI === "webgl") { - const configuration = { - // Must be defined as 'canvas' and nothing else. Auto generated glue js expects .canvas to exist. - canvas: (function () { - const canvas = document.getElementById('canvas'); - canvas.oncontextmenu = (event) => { event.preventDefault(); }; - canvas.onclick = () => { canvas.focus(); }; // grab focus when the render window region receives mouse clicks. - canvas.tabIndex = -1; - canvas.addEventListener( - "webglcontextlost", - function (e) { - alert("WebGL context lost. You will need to reload the page."); - e.preventDefault(); - }, - false - ); - return canvas; - })(), - //Pipes std::cout and std::cerr into debug and error in dev console. - print: (text: string): void => console.debug(text), - printErr: (text: string): void => console.error(text), - }; - resolve(configuration) - } else if (viewAPI === "webgpu") { - // Sets up a base configuration for VTK-wasm with WebGPU rendering. - const configuration = { - // Must be defined as 'canvas' and nothing else. Auto generated glue js expects .canvas to exist. - canvas: (function () { - const canvas = document.getElementById('canvas'); - canvas.oncontextmenu = (event) => { event.preventDefault(); }; - canvas.onclick = () => { canvas.focus(); }; // grab focus when the render window region receives mouse clicks. - canvas.tabIndex = -1; - return canvas; - })(), - //Pipes std::cout and std::cerr into debug and error in dev console. - print: (text: string): void => console.debug(text), - printErr: (text: string): void => console.error(text), - preRun: [function (module: any) { - // select WEBGPU backend - module.ENV.VTK_GRAPHICS_BACKEND = 'WEBGPU'; - }], - preinitializedWebGPUDevice: null, - }; - // Set the device from JS. This can be done in C++ as well. - // See https://github.com/kainino0x/webgpu-cross-platform-demo/blob/main/main.cpp#L51 - configuration.preinitializedWebGPUDevice = webgpuDevice; - resolve(configuration) - } - }); +export function getConfiguration(viewAPI: string) { + // Sets up a base configuration for VTK-wasm with WebGL2 rendering. + if (viewAPI === "webgl") { + const configuration = { + // Must be defined as 'canvas' and nothing else. Auto generated glue js expects .canvas to exist. + canvas: (function () { + const canvas = document.getElementById('canvas'); + canvas.oncontextmenu = (event) => { event.preventDefault(); }; + canvas.onclick = () => { canvas.focus(); }; // grab focus when the render window region receives mouse clicks. + canvas.tabIndex = -1; + canvas.addEventListener( + "webglcontextlost", + function (e) { + alert("WebGL context lost. You will need to reload the page."); + e.preventDefault(); + }, + false + ); + return canvas; + })(), + //Pipes std::cout and std::cerr into debug and error in dev console. + print: (text: string): void => console.debug(text), + printErr: (text: string): void => console.error(text), + }; + return configuration; + } else if (viewAPI === "webgpu") { + // Sets up a base configuration for VTK-wasm with WebGPU rendering. + const configuration = { + // Must be defined as 'canvas' and nothing else. Auto generated glue js expects .canvas to exist. + canvas: (function () { + const canvas = document.getElementById('canvas'); + canvas.oncontextmenu = (event) => { event.preventDefault(); }; + canvas.onclick = () => { canvas.focus(); }; // grab focus when the render window region receives mouse clicks. + canvas.tabIndex = -1; + return canvas; + })(), + //Pipes std::cout and std::cerr into debug and error in dev console. + print: (text: string): void => console.debug(text), + printErr: (text: string): void => console.error(text), + preRun: [function (module: any) { + // select WEBGPU backend + module.ENV.VTK_GRAPHICS_BACKEND = 'WEBGPU'; + }], + }; + return configuration; + } else { + return null; + } } \ No newline at end of file diff --git a/src/utils/wasmWebGPUInit.ts b/src/utils/wasmWebGPUInit.ts index 8e246a9..b95ceda 100644 --- a/src/utils/wasmWebGPUInit.ts +++ b/src/utils/wasmWebGPUInit.ts @@ -8,26 +8,3 @@ export async function hasWebGPU(): Promise { } return true; } - -export async function getDevice(): Promise { - const adapter = await navigator.gpu.requestAdapter(); - if (adapter === null) { - return null; - } - const requiredLimits: any = {}; - // App ideally needs as much GPU memory it can get. - // when adapter limits are greater than default, copy adapter limits to device requirements. - // maxBufferSize - if (adapter.limits.maxBufferSize > 268435456) { - requiredLimits.maxBufferSize = adapter.limits.maxBufferSize; - } - // maxStorageBufferBindingSize - if (adapter.limits.maxStorageBufferBindingSize > 134217728) { - requiredLimits.maxStorageBufferBindingSize = adapter.limits.maxStorageBufferBindingSize; - } - // maxUniformBufferBindingSize - if (adapter.limits.maxUniformBufferBindingSize > 65536) { - requiredLimits.maxUniformBufferBindingSize = adapter.limits.maxUniformBufferBindingSize; - } - return await adapter.requestDevice({ requiredLimits }); -}