diff --git a/Examples/Emscripten/Cxx/ConeMultiBackend/CMakeLists.txt b/Examples/Emscripten/Cxx/ConeMultiBackend/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..905b5a590029b0f9eff5b022fe3ddb10b3131e4e --- /dev/null +++ b/Examples/Emscripten/Cxx/ConeMultiBackend/CMakeLists.txt @@ -0,0 +1,180 @@ +cmake_minimum_required(VERSION 3.13) +project(ConeMultiBackend) + +# ----------------------------------------------------------------------------- +# EMSCRIPTEN only +# ----------------------------------------------------------------------------- + +if (NOT EMSCRIPTEN) + message("Skipping example: This needs to run inside an Emscripten build environment") + return () +endif () + +# ----------------------------------------------------------------------------- +# Handle VTK dependency +# ----------------------------------------------------------------------------- + +find_package(VTK + COMPONENTS + FiltersSources # VTK pipeline + InteractionStyle # Mouse handling + RenderingOpenGL2 # For Rendering with OpenGL + RenderingWebGPU # For Rendering with WebGPU + RenderingUI # For SDL2 Window +) + +if (NOT VTK_FOUND) + message("Skipping example: ${VTK_NOT_FOUND_MESSAGE}") + return () +endif () + +# ----------------------------------------------------------------------------- +# Compile example code +# ----------------------------------------------------------------------------- + +add_executable(ConeMultiBackend ConeMultiBackend.cxx) + +target_link_libraries(ConeMultiBackend + PRIVATE + VTK::FiltersSources + VTK::InteractionStyle + VTK::RenderingOpenGL2 + VTK::RenderingWebGPU + VTK::RenderingUI +) + +# ----------------------------------------------------------------------------- +# WebAssembly build options +# ----------------------------------------------------------------------------- +set(emscripten_link_options) +set(emscripten_compile_options) + +list(APPEND emscripten_link_options + "-sWASM=1" + "-sMODULARIZE=1" + "-sALLOW_MEMORY_GROWTH=1" + "-sEXPORT_NAME=createConeMultiBackendModule" + "-sEXPORTED_RUNTIME_METHODS=['ENV']" # ENV holds the environment variables accessible by C getenv +) + +set(emscripten_debug_options) +set(DEBUGINFO "PROFILE" CACHE STRING "Type of debug info") +set_property(CACHE DEBUGINFO PROPERTY + STRINGS + NONE # -g0 + READABLE_JS # -g1 + PROFILE # -g2 + DEBUG_NATIVE # -g3 +) + +if(DEBUGINFO STREQUAL "NONE") + list(APPEND emscripten_debug_options + "-g0" + ) +elseif(DEBUGINFO STREQUAL "READABLE_JS") + list(APPEND emscripten_debug_options + "-g1" + ) + list(APPEND emscripten_link_options + "-sDEMANGLE_SUPPORT=1" + ) +elseif(DEBUGINFO STREQUAL "PROFILE") + list(APPEND emscripten_debug_options + "-g2" + ) + list(APPEND emscripten_link_options + "-sDEMANGLE_SUPPORT=1" + ) +elseif(DEBUGINFO STREQUAL "DEBUG_NATIVE") + list(APPEND emscripten_debug_options + "-g3" + ) + list(APPEND emscripten_link_options + "-sASSERTIONS=1" + "-sDEMANGLE_SUPPORT=1" + ) +endif() + +# ----------------------------------------------------------------------------- +# Build options +# ----------------------------------------------------------------------------- +set(emscripten_optimizations) +set(OPTIMIZE "SMALLEST_WITH_CLOSURE" CACHE STRING "Emscripten optimization") +set_property(CACHE OPTIMIZE PROPERTY + STRINGS + NO_OPTIMIZATION # -O0 + LITTLE # -O1 + MORE # -O2 + BEST # -O3 + SMALL # -Os + SMALLEST # -Oz + SMALLEST_WITH_CLOSURE # -Oz --closure 1 +) + +if(OPTIMIZE STREQUAL "NO_OPTIMIZATION") + list(APPEND emscripten_optimizations + "-O0" + ) +elseif(OPTIMIZE STREQUAL "LITTLE") + list(APPEND emscripten_optimizations + "-O1" + ) +elseif(OPTIMIZE STREQUAL "MORE") + list(APPEND emscripten_optimizations + "-O2" + ) +elseif(OPTIMIZE STREQUAL "BEST") + list(APPEND emscripten_optimizations + "-O3" + ) +elseif(OPTIMIZE STREQUAL "SMALL") + list(APPEND emscripten_optimizations + "-Os" + ) +elseif(OPTIMIZE STREQUAL "SMALLEST") + list(APPEND emscripten_optimizations + "-Oz" + ) +elseif(OPTIMIZE STREQUAL "SMALLEST_WITH_CLOSURE") + list(APPEND emscripten_optimizations + "-Oz" + ) + list(APPEND emscripten_link_options + "--closure 1" + ) +endif() + +target_compile_options(ConeMultiBackend + PUBLIC + ${emscripten_compile_options} + ${emscripten_optimizations} + ${emscripten_debug_options} +) + +target_link_options(ConeMultiBackend + PUBLIC + ${emscripten_link_options} + ${emscripten_optimizations} + ${emscripten_debug_options} +) + +# ----------------------------------------------------------------------------- +# VTK modules initialization +# ----------------------------------------------------------------------------- + +vtk_module_autoinit( + TARGETS ConeMultiBackend + MODULES ${VTK_LIBRARIES} +) + +# ----------------------------------------------------------------------------- +# Copy HTML to build directory +# ----------------------------------------------------------------------------- + +add_custom_command( + TARGET ConeMultiBackend + COMMAND + ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_CURRENT_SOURCE_DIR}/web" + "${CMAKE_CURRENT_BINARY_DIR}" +) diff --git a/Examples/Emscripten/Cxx/ConeMultiBackend/ConeMultiBackend.cxx b/Examples/Emscripten/Cxx/ConeMultiBackend/ConeMultiBackend.cxx new file mode 100644 index 0000000000000000000000000000000000000000..2e979c07e559b57f47c390655f92d187ecbea223 --- /dev/null +++ b/Examples/Emscripten/Cxx/ConeMultiBackend/ConeMultiBackend.cxx @@ -0,0 +1,113 @@ +/*========================================================================= + Program: Visualization Toolkit + Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen + All rights reserved. + See Copyright.txt or http://www.kitware.com/Copyright.htm for details. + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notice for more information. +=========================================================================*/ + +#include "vtkActor.h" +#include "vtkCellData.h" +#include "vtkConeSource.h" +#include "vtkInteractorStyleTrackballCamera.h" +#include "vtkMinimalStandardRandomSequence.h" +#include "vtkNew.h" +#include "vtkPolyData.h" +#include "vtkPolyDataMapper.h" +#include "vtkProperty.h" +#include "vtkRenderWindow.h" +#include "vtkRenderWindowInteractor.h" +#include "vtkRenderer.h" + +//------------------------------------------------------------------------------ +// Main +//------------------------------------------------------------------------------ + +int main(int argc, char* argv[]) +{ + // Create a renderer, render window, and interactor + vtkNew<vtkRenderer> renderer; + vtkNew<vtkRenderWindow> renderWindow; + renderWindow->SetMultiSamples(0); + renderWindow->AddRenderer(renderer); + + vtkNew<vtkRenderWindowInteractor> renderWindowInteractor; + renderWindowInteractor->SetRenderWindow(renderWindow); + + vtkNew<vtkInteractorStyleTrackballCamera> style; + renderWindowInteractor->SetInteractorStyle(style); + style->SetDefaultRenderer(renderer); + + vtkNew<vtkMinimalStandardRandomSequence> seq; + + double spacingX = 2.0, spacingY = 2.0, spacingZ = 2.0; + + const int nx = std::atoi(argv[1]); + const int ny = std::atoi(argv[2]); + const int nz = std::atoi(argv[3]); + const int mapperIsStatic = std::atoi(argv[4]); + + double x = 0.0, y = 0.0, z = 0.0; + for (int k = 0; k < nz; ++k) + { + for (int j = 0; j < ny; ++j) + { + for (int i = 0; i < nx; ++i) + { + vtkNew<vtkConeSource> coneSrc; + coneSrc->SetResolution(10); + // position the cone + coneSrc->SetCenter(x, y, z); + + coneSrc->Update(); + vtkPolyData* cone = coneSrc->GetOutput(); + + // generate random colors for each face of the cone. + vtkNew<vtkUnsignedCharArray> colors; + colors->SetNumberOfComponents(4); + seq->SetSeed(k * ny * nx + j * nx + i); + for (vtkIdType cellId = 0; cellId < cone->GetNumberOfPolys(); ++cellId) + { + double red = seq->GetNextRangeValue(0, 255.); + double green = seq->GetNextRangeValue(0, 255.); + double blue = seq->GetNextRangeValue(0, 255.); + colors->InsertNextTuple4(red, green, blue, 255); + } + cone->GetCellData()->SetScalars(colors); + + vtkNew<vtkPolyDataMapper> mapper; + mapper->SetInputData(cone); + mapper->Update(); + mapper->SetStatic(mapperIsStatic); + + vtkNew<vtkActor> actor; + actor->SetMapper(mapper); + actor->GetProperty()->SetEdgeVisibility(1); + actor->GetProperty()->SetEdgeColor(1.0, 1.0, 1.0); + mapper->Update(); + actor->SetOrigin(x, y, z); + actor->RotateZ(i * j); + renderer->AddActor(actor); + + x += spacingX; + } + x = 0.0; + y += spacingY; + } + y = 0.0; + z += spacingZ; + } + std::cout << "Created " << nx * ny * nz << " cones" << std::endl; + + // Start rendering app + renderer->SetBackground(0.2, 0.3, 0.4); + renderWindow->SetSize(300, 300); + renderWindow->Render(); + + // Start event loop + renderWindowInteractor->Start(); + + return 0; +} diff --git a/Examples/Emscripten/Cxx/ConeMultiBackend/README.md b/Examples/Emscripten/Cxx/ConeMultiBackend/README.md new file mode 100644 index 0000000000000000000000000000000000000000..eccba2845a6d258fd56d3c21b6bcd14bc576474c --- /dev/null +++ b/Examples/Emscripten/Cxx/ConeMultiBackend/README.md @@ -0,0 +1,48 @@ +# WebAssembly Cone Example + +This example aims to provide a base example on how to write a VTK viewer for +WebAssembly with both OpenGL and WebGPU backends. It shows how an application can +use a single wasm binary to serve using both the backends. In this example, the UI is very minimal. + +The additional steps required are: + +1. Update `CMakeLists.txt` so that the `VTK::RenderingWebGPU` component is requested when finding VTK. +2. Link your targets with `RenderingWebGPU`. There is no problem linking to both `RenderingWebGPU` and `RenderingOpenGL2`. +3. Preload environment variable with `VTK_GRAPHICS_BACKEND` set to 'WEBGPU'. + +## Compiling example against VTK + +We assume inside the `work/` directory to find the source of VTK under `src/` +and its build tree under `build-vtk-wasm`. + +If VTK is not built yet, please follow the guide `../README.md`. + +Let's create the build directory for our example + +``` +mkdir -p work/build +``` + +Start docker inside that working directory + +``` +docker run --rm --entrypoint /bin/bash -v $PWD:/work -p 8000:8000 -it dockcross/web-wasm:20230222-162287d + +cd /work/build + +emcmake cmake \ + -G Ninja \ + -DVTK_DIR=/work/build-vtk-wasm \ + /work/src/Examples/Emscripten/Cxx/Cone + +cmake --build . +``` + +## Serve and test generated code + +``` +cd work/build +python3 -m http.server 8000 +``` + +Open your browser to http://localhost:8000 and click on a backend. diff --git a/Examples/Emscripten/Cxx/ConeMultiBackend/web/config_base.js b/Examples/Emscripten/Cxx/ConeMultiBackend/web/config_base.js new file mode 100644 index 0000000000000000000000000000000000000000..fa80cf383cf81b6c643a80a9da57f130e21fc0ac --- /dev/null +++ b/Examples/Emscripten/Cxx/ConeMultiBackend/web/config_base.js @@ -0,0 +1,34 @@ +export var options = { + nx: '10', + ny: '10', + nz: '100', + mapperIsStatic: '1' +}; + +export const BaseConfig = { + 'canvas': (function () { + var canvas = document.getElementById('canvas'); + return canvas; + })(), + 'print': (function () { + return function (text) { + text = Array.prototype.slice.call(arguments).join(' '); + console.info(text); + }; + })(), + 'printErr': function (text) { + text = Array.prototype.slice.call(arguments).join(' '); + console.error(text); + } +}; + +export function initCanvas(canvas) { + // sends a resize event so that the render window fills up browser tab dimensions. + setTimeout(() => { + window.dispatchEvent(new Event('resize')); + }, 0); + // focus on the canvas to grab keyboard inputs. + canvas.setAttribute('tabindex', '0'); + // grab focus when the render window region receives mouse clicks. + canvas.addEventListener('click', () => canvas.focus()); +} diff --git a/Examples/Emscripten/Cxx/ConeMultiBackend/web/config_webgl.js b/Examples/Emscripten/Cxx/ConeMultiBackend/web/config_webgl.js new file mode 100644 index 0000000000000000000000000000000000000000..170b2ea90d1c350fc7af1db644f210f7e92e3d4f --- /dev/null +++ b/Examples/Emscripten/Cxx/ConeMultiBackend/web/config_webgl.js @@ -0,0 +1,30 @@ +import { BaseConfig, options, initCanvas } from "./config_base.js"; + +function makeWebGLConfig() { + var cfg = BaseConfig; + cfg.canvas = (function () { + var canvas = document.getElementById('canvas'); + canvas.addEventListener( + "webglcontextlost", + function (e) { + console.error('WebGL context lost. You will need to reload the page.'); + e.preventDefault(); + }, + false + ); + return canvas; + })() + cfg.arguments = [options.nx, options.ny, options.nz, options.mapperIsStatic]; + return cfg; +} + +var canvas = document.createElement('canvas'); +canvas.setAttribute('id', 'canvas'); +canvas.setAttribute('class', 'canvas'); +document.body.appendChild(canvas) + +let cfg = makeWebGLConfig(); + +let Runtime = await createConeMultiBackendModule(cfg); +console.log(`WASM runtime initialized with arguments ${Runtime.arguments}`); +initCanvas(canvas); diff --git a/Examples/Emscripten/Cxx/ConeMultiBackend/web/config_webgpu.js b/Examples/Emscripten/Cxx/ConeMultiBackend/web/config_webgpu.js new file mode 100644 index 0000000000000000000000000000000000000000..69e76f50dd3260a046fb91ef8b0287c2e01b1c85 --- /dev/null +++ b/Examples/Emscripten/Cxx/ConeMultiBackend/web/config_webgpu.js @@ -0,0 +1,44 @@ +import { BaseConfig, options, initCanvas } from "./config_base.js"; + +function makeWebGPUConfig() { + var cfg = BaseConfig; + cfg.canvas = (function () { + var canvas = document.getElementById('canvas'); + return canvas; + })() + cfg.preRun = [function (module) { + // select WEBGPU backend + module.ENV.VTK_GRAPHICS_BACKEND = 'WEBGPU'; + }]; + cfg.arguments = [options.nx, options.ny, options.nz, options.mapperIsStatic]; + return cfg; +} + +if (navigator.gpu === undefined) { + var webgpuSupportEl = document.createElement('h1'); + webgpuSupportEl.innerText = "Your browser does not support webgpu!"; + document.body.appendChild(webgpuSupportEl) + var explainEl = document.createElement('p'); + explainEl.innerText = "On supported browsers, maybe serve from localhost instead of ip?"; + document.body.appendChild(explainEl) +} else { + var canvas = document.createElement('canvas'); + canvas.setAttribute('id', 'canvas'); + canvas.setAttribute('class', 'canvas'); + document.body.appendChild(canvas) + + let cfg = makeWebGPUConfig(); + + let adapter = await navigator.gpu.requestAdapter(); + console.log("Found an adapter"); + let device = await adapter.requestDevice(); + console.log("Obtained a device"); + + // 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 + cfg.preinitializedWebGPUDevice = device; + + let Runtime = await createConeMultiBackendModule(cfg); + console.log(`WASM runtime initialized with arguments ${Runtime.arguments}`); + initCanvas(canvas); +} diff --git a/Examples/Emscripten/Cxx/ConeMultiBackend/web/index.html b/Examples/Emscripten/Cxx/ConeMultiBackend/web/index.html new file mode 100644 index 0000000000000000000000000000000000000000..0f459a355b197e24480261cea3d541b39ccec310 --- /dev/null +++ b/Examples/Emscripten/Cxx/ConeMultiBackend/web/index.html @@ -0,0 +1,20 @@ +<!doctype html> +<html> + +<head> + <meta charset='utf-8' /> + <link rel="stylesheet" href="style.css"> +</head> + +<body> + <script type='module' src='./config_base.js'></script> + <div class="container"> + <div class="center"> + <a href="webgl.html">VTK OpenGL (WebGL)</a> + | + <a href="webgpu.html">VTK WebGPU</a> + </div> + </div> +</body> + +</html> diff --git a/Examples/Emscripten/Cxx/ConeMultiBackend/web/style.css b/Examples/Emscripten/Cxx/ConeMultiBackend/web/style.css new file mode 100644 index 0000000000000000000000000000000000000000..45eb881480e3fb3f58803b6162440fb5627c9918 --- /dev/null +++ b/Examples/Emscripten/Cxx/ConeMultiBackend/web/style.css @@ -0,0 +1,22 @@ +.canvas { + left: 0; + top: 0; + position: absolute; + display: block; +} + +.container { + display: flex; + flex-direction: column; + width: 100%; + height: 100vh; +} + +.center { + margin: 0; + position: absolute; + top: 50%; + left: 50%; + -ms-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); +} diff --git a/Examples/Emscripten/Cxx/ConeMultiBackend/web/webgl.html b/Examples/Emscripten/Cxx/ConeMultiBackend/web/webgl.html new file mode 100644 index 0000000000000000000000000000000000000000..7fb99da8940d8647e2ce97c4b2a37570e6ecd659 --- /dev/null +++ b/Examples/Emscripten/Cxx/ConeMultiBackend/web/webgl.html @@ -0,0 +1,14 @@ +<!doctype html> +<html> + +<head> + <meta charset='utf-8' /> + <link rel="stylesheet" href="style.css"> +</head> + +<body> + <script type='text/javascript' src='./ConeMultiBackend.js'></script> + <script type='module' src='./config_webgl.js'></script> +</body> + +</html> diff --git a/Examples/Emscripten/Cxx/ConeMultiBackend/web/webgpu.html b/Examples/Emscripten/Cxx/ConeMultiBackend/web/webgpu.html new file mode 100644 index 0000000000000000000000000000000000000000..66c26f6e9b1b8c999e9de3303bea361ab7813577 --- /dev/null +++ b/Examples/Emscripten/Cxx/ConeMultiBackend/web/webgpu.html @@ -0,0 +1,15 @@ +<!doctype html> +<html> + +<head> + <meta charset='utf-8' /> + <link rel="stylesheet" href="style.css"> +</head> + +<body> + <script type='text/javascript' src='./ConeMultiBackend.js'></script> + <script type='module' src='./config_base.js'></script> + <script type='module' src='./config_webgpu.js'></script> +</body> + +</html>