(This is a post in a series of blog articles about current VTK Innovation efforts. See the prior, introductory posts here and here.)

This blog post explains how to design a VTK.wasm command module. In simple terms, WASM command modules are a type of WASM module designed to behave like traditional command-line applications. They are designed for a “run-to-completion” execution model, meaning they start, perform a specific task, and then exit without further interaction. They contain a main function, write to stdout, and stderr, and export a special _start symbol. In Emscripten, the _start implementation calls __wasm_call_ctors() to initialize all static members in your program, runs the entry point main() with command-line arguments, and finally calls __wasm_call_dtors(). The diagram below illustrates the lifecycle of a typical wasm command module written in C++, and built with Emscripten:

Figure 1: Lifecycle of a WebAssembly command module compiled from C++ source code.

The Problem with Event Loop

Interactive applications built with VTK for WebAssembly (via Emscripten) face a unique lifecycle challenge compared to their native desktop counterparts. A native application can block inside main() with an infinite event loop to process user input and render frames. However, this model is incompatible with web browsers, as a blocking main thread would freeze the user interface of the browser making the entire page unresponsive.

To solve this, Emscripten emulates an infinite loop. This is where the execution model departs from that of a standard WASM command module.

When vtkRenderWindowInteractor::Start() is called, control is yielded back to the browser’s event loop and the browser then calls a C++ callback function that processes mouse, keyboard and timer events on the VTK interactor after every refresh interval. This means the C++ main() function runs to completion and exits. Consequently, the stack unwinds, and any local objects created within main() are cleaned up. Note that in this model, global destructors are not run and at exit handlers are not called. See emscripten_set_main_loop for full details. 

Let’s examine how this impacts a standard VTK application in the next section.

Code Example

Consider this minimal VTK application, where all core components are declared as local variables within main() using the vtkNew smart pointer.

int main(int argc, char* argv[])

{
  // Scoped VTK objects created on the stack
  vtkNew<vtkRenderer> renderer;
  vtkNew<vtkRenderWindow> renderWindow;
  renderWindow->AddRenderer(renderer);
  vtkNew<vtkRenderWindowInteractor> renderWindowInteractor;
  renderWindowInteractor->SetRenderWindow(renderWindow);

  vtkNew<vtkInteractorStyleTrackballCamera> style;
  renderWindowInteractor->SetInteractorStyle(style);
  style->SetDefaultRenderer(renderer);

  // Create visualization pipeline
  vtkNew<vtkConeSource> coneSource;
  vtkNew<vtkPolyDataMapper> mapper;
  vtkNew<vtkActor> actor;
  mapper->SetInputConnection(coneSource->GetOutputPort());
  actor->SetMapper(mapper);
  renderer->AddActor(actor);

  // Configure and render the scene once
  renderer->SetBackground(0.2, 0.3, 0.4);
  renderWindow->Render();

  // Initialize the browser's event loop and exit main()
  renderWindowInteractor->Start();
  return 0; // The stack unwinds after this point
}

The Problem of Object Lifetime

Based on standard C++ RAII (Resource Acquisition Is Initialization) principles, the stack unwinding should destroy renderWindowInteractor and all other vtkNew-managed objects as soon as main() returns. This would tear down the entire application before the first user interaction could be processed. So, how does the application persist?

VTK’s Internal Reference Counting Solution

VTK is designed to handle this specific problem. The vtkRenderWindowInteractor::Start() method contains a crucial safeguard: it internally increments the reference count of the interactor object before setting up the event loop. Technically, this is implemented in the factory override class method vtkWebAssemblyRenderWindowInteractor::StartEventLoop().

This single, persistent reference prevents the interactor’s destruction when the vtkNew smart pointer goes out of scope. Because the interactor holds references to the render window, which in turn holds references to the renderers and their actors, this one action is sufficient to keep the entire visualization pipeline alive in memory, ready to be driven by browser events on demand. Don’t fear if your debugger takes you straight to return 0; immediately after calling the event loop start method. All vtkNew objects that are indirectly referenced by the interactor will survive the stack unwind. Just keep in mind that variables that are not derived from vtkObject and allocated on the stack will be destroyed. As an example, this would apply to vtkBoundingBox, and your own structs or classes.

For a full example project, check out the VTK/Examples/Emscripten/Cxx/Cone subdirectory.

Conclusion

The lifecycle of a VTK.wasm command module presents a fascinating departure from traditional C++ development. While the non-blocking nature of web browsers means the main() function runs to completion, VTK’s thoughtful design prevents your visualization from exiting prematurely.

This means you can write clean, modern C++ for WASM using features like vtkNew and vtkSmartPointer without fear. Your objects won’t disappear. To see this principle in action, your next step is clear: dive into the VTK/Examples/Emscripten/Cxx projects, compile any one, and see for yourself how a compiled C++ program powers a web application.

Acknowledgments

VTK is a creative work produced from an extended community. Refer to VTK’s GitLab repository for a detailed capture of contributions and enhancements. Much of this work is supported under an NIH R01 VTK Innovation grant 2R01EB014955-09, Accelerating Community-Driven Medical Innovation with VTK.

Leave a Reply