Table of Contents

Rendering and hosting

VTK draws with OpenGL, into a window of its own, a window the application gives it, or an offscreen buffer.

Off the screen

using var window = new vtkRenderWindow();
window.SetOffScreenRendering(1);
window.SetSize(640, 480);
window.AddRenderer(renderer);

// the camera looks at everything, from a little above
renderer.ResetCamera();
using (var camera = renderer.GetActiveCamera())
{
  camera.Elevation(20);
  camera.Zoom(1.2);
}

window.Render();

// the picture as image data: 640 x 480 points, an RGB tuple each
using var grab = new vtkWindowToImageFilter();
grab.SetInput(window);
grab.SetInputBufferTypeToRGB();
grab.ReadFrontBufferOff();
grab.Update();
using var image = grab.GetOutput();
int[] size = image.GetDimensions();

The image's scalars are the pixels: vtkUnsignedCharArray.ToArray() copies them, as in Getting started. Rendering needs OpenGL 3.2: a GPU's driver, or Mesa's llvmpipe (on Linux, under Xvfb).

Volumes

An image's values drawn as a volume, through transfer functions for opacity and colour:

// transfer functions: the value's opacity and colour, transparent black to opaque red
using var opacity = new vtkPiecewiseFunction();
opacity.AddPoint(0, 0);
opacity.AddPoint(31, 1);
using var color = new vtkColorTransferFunction();
color.AddRGBPoint(0, 0, 0, 0);
color.AddRGBPoint(31, 1, 0, 0);
using var property = new vtkVolumeProperty();
property.SetScalarOpacity(opacity);
property.SetColor(color);

// the mapper ray-casts on the GPU where it can, on the CPU otherwise
using var mapper = new vtkSmartVolumeMapper();
mapper.SetInputData(image);
using var volume = new vtkVolume();
volume.SetMapper(mapper);
volume.SetProperty(property);
renderer.AddVolume(volume);
renderer.ResetCamera();
window.Render();

vtkGPUVolumeRayCastMapper.New() and vtkFixedPointVolumeRayCastMapper pick one way to draw; vtkSmartVolumeMapper picks for the GPU at hand. Unstructured grids go through vtkUnstructuredGridVolumeRayCastMapper or vtkProjectedTetrahedraMapper.

In an application's window

The application makes a child window (an HWND on Windows); VTK's render window draws into it, and VTK's interactor gets the window's size, paint and mouse messages from C#:

A VTK view in an application's window A VTK view in an application's window
// VTK drawing into a child window the application made (an HWND on Windows), its mouse input fed
// from the window's messages; create, use and dispose it on the UI thread
public sealed class HostedView : IDisposable
{
  private readonly vtkRenderWindow _window = new();
  private readonly vtkGenericRenderWindowInteractor _interactor = new();
  private readonly vtkInteractorStyleTrackballCamera _style = new();

  public HostedView(IntPtr handle)
  {
    _window.SetWindowId(handle);
    _window.AddRenderer(Renderer);
    _interactor.SetRenderWindow(_window);
    _interactor.SetInteractorStyle(_style);
    _interactor.Initialize();
  }

  public vtkRenderer Renderer { get; } = new();

  // once, on the UI thread: VTK's messages to C#, the finalizer's releases to this thread
  public static void InitializeOnUiThread()
  {
    VtkOutput.Install();
    VtkFinalizer.Context = SynchronizationContext.Current;
  }

  // the window's size and paint messages
  public void Resize(int width, int height)
  {
    _window.SetSize(width, height);
    _interactor.UpdateSize(width, height);
  }

  public void Paint() => _window.Render();

  // its mouse messages, in the window's pixels (y down): the style rotates, pans and zooms
  public void LeftButtonDown(int x, int y)
  {
    _interactor.SetEventInformationFlipY(x, y);
    _interactor.LeftButtonPressEvent();
  }

  public void MouseMove(int x, int y)
  {
    _interactor.SetEventInformationFlipY(x, y);
    _interactor.MouseMoveEvent();
  }

  public void LeftButtonUp(int x, int y)
  {
    _interactor.SetEventInformationFlipY(x, y);
    _interactor.LeftButtonReleaseEvent();
  }

  // before the window goes: the render window last, its OpenGL context with it
  public void Dispose()
  {
    _interactor.Dispose();
    _style.Dispose();
    Renderer.Dispose();
    _window.Dispose();
  }
}
  • WPF: an HwndHost creates the child window in BuildWindowCore.
  • Avalonia: a NativeControlHost creates it in CreateNativeControlCore, after the window shows.
  • The window class has CS_OWNDC: OpenGL needs its own device context. Its procedure calls the view; VTK's own window procedure stays out, so an exception a C# observer throws comes back to C#.

The demos do all of this in a WPF and an Avalonia 12 viewer that open .vtu results; NetVtk.Viewer holds the shared part (VtkViewer, ViewerWindow).

Linux (X11): Avalonia's NativeControlHost gives an X11 window id. On a display connection the application opens and hands to VTK (SetDisplayId), VTK creates the child window there itself (SetParentId, Initialize, then GetGenericWindowId()), with the visual its OpenGL context needs; the application selects the window's mouse and structure events on that connection and passes them on to VTK's interactor, polling from a UI timer. The demos' X11ViewerWindow does this; its test draws into a host's X11 window under Xvfb. macOS (an NSView child) isn't done yet.

The rendering thread

Render windows, renderers, mappers, actors and textures release OpenGL resources when they go, which needs the thread that renders: create, use and dispose them there, the render window last. For what the garbage collector finalizes, set VtkFinalizer.Context on that thread, as InitializeOnUiThread above does: the finalizer then posts the releases to it. See Object lifetimes.