Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Engine Lifecycle

VisoEngine is the central facade for all rendering, input handling, scene management, and animation in viso. This chapter covers how to create it, what happens during initialization, and how to manage its lifetime.

Creation

The engine is created asynchronously because wgpu adapter and device initialization are async:

#![allow(unused)]
fn main() {
// With a structure file (standalone use)
let mut engine = VisoEngine::new_with_path(
    window.clone(),   // impl Into<wgpu::SurfaceTarget<'static>>
    (width, height),  // Physical pixel dimensions
    scale_factor,     // DPI scale (e.g. 2.0 on Retina)
    &cif_path,        // Path to mmCIF file
).await;

// Without a pre-loaded structure (library use)
let mut engine = VisoEngine::new(
    window.clone(),
    (width, height),
    scale_factor,
).await;
}

What Happens During Init

  1. GPU setup – wgpu instance, adapter, device, queue, and surface are configured via RenderContext
  2. Shader compilationShaderComposer loads and composes all WGSL shaders using naga_oil
  3. CameraCameraController is created with default orbital parameters (distance 150, FOV 45)
  4. Renderers – all molecular renderers are created (tube, ribbon, sidechain, ball-and-stick, band, pull, nucleic acid)
  5. Post-processing – SSAO, bloom, composite, and FXAA passes are initialized
  6. Picking – GPU picking system with offscreen render target and staging buffer
  7. Scene processor – background thread is spawned for mesh generation
  8. Structure loading – if a path was provided, the file is parsed and entities are added to the scene

Initial Scene Sync

After creation, the scene has entities but no GPU meshes. You must sync:

#![allow(unused)]
fn main() {
engine.sync_scene_to_renderers(None);
}

This submits the scene to the background processor thread. The None argument means no animation action (the initial load uses Snap behavior by default). The main thread continues immediately – mesh generation happens in the background.

On the next frame, apply_pending_scene() will detect the completed meshes and upload them to the GPU.

Resize and Scale Factor

Handle window resize events by forwarding to the engine:

#![allow(unused)]
fn main() {
engine.resize(new_width, new_height);
}

This resizes:

  • The wgpu surface
  • All post-processing textures (SSAO, bloom, composite, FXAA)
  • The picking render target
  • The camera aspect ratio

For DPI changes:

#![allow(unused)]
fn main() {
engine.set_scale_factor(new_scale);
let inner = window.inner_size();
engine.resize(inner.width, inner.height);
}

Shutdown

The engine cleans up automatically on drop. The background scene processor thread is joined:

#![allow(unused)]
fn main() {
engine.shutdown_scene_processor();
}

This sends a SceneRequest::Shutdown message and waits for the thread to finish. It’s also called automatically in the Drop implementation, so explicit shutdown is only needed if you want to control timing.

Ownership Model

VisoEngine owns:

ComponentTypePurpose
contextRenderContextwgpu device, queue, surface
sceneSceneEntity groups, focus state
camera_controllerCameraControllerCamera matrices, animation
animatorStructureAnimatorBackbone/sidechain animation
pickingPickingGPU picking system
scene_processorSceneProcessorBackground mesh thread
tube_rendererTubeRendererBackbone tubes/coils
ribbon_rendererRibbonRendererHelices and sheets
sidechain_rendererCapsuleSidechainRendererSidechain capsules
bns_rendererBallAndStickRendererLigands, ions, waters
band_rendererBandRendererConstraint bands
pull_rendererPullRendererActive pull visualization
na_rendererNucleicAcidRendererDNA/RNA backbones
post_processPostProcessStackSSAO, bloom, composite, FXAA

The engine is not thread-safe (!Send, !Sync) because it holds wgpu GPU resources. All engine access must happen on the main thread. The background scene processor communicates via channels and triple buffers.