Skip to main content

polyscope_rs/
headless.rs

1//! Headless rendering API for polyscope-rs.
2//!
3//! Provides functions to render the current scene to an image buffer or file
4//! without opening a window. Useful for integration tests, batch processing,
5//! and automated screenshot generation.
6
7use crate::Result;
8use crate::app::App;
9use pollster::FutureExt;
10use polyscope_core::state::with_context_mut;
11use polyscope_render::RenderEngine;
12
13/// Renders the current scene to a file.
14///
15/// Creates a headless GPU context, renders one frame of the current scene
16/// (all registered structures and quantities), and saves the result as
17/// a PNG or JPEG image.
18///
19/// The camera is automatically fitted to the scene bounding box.
20///
21/// # Example
22/// ```no_run
23/// use polyscope_rs::*;
24///
25/// init().unwrap();
26/// register_point_cloud("pts", vec![Vec3::ZERO, Vec3::X, Vec3::Y]);
27/// render_to_file("output.png", 800, 600).unwrap();
28/// ```
29pub fn render_to_file(filename: &str, width: u32, height: u32) -> Result<()> {
30    let data = render_to_image(width, height)?;
31    polyscope_render::save_image(filename, &data, width, height)
32        .map_err(|e| crate::PolyscopeError::RenderError(format!("Failed to save image: {e}")))
33}
34
35/// Renders the current scene to a raw RGBA pixel buffer.
36///
37/// Creates a headless GPU context, renders one frame of the current scene,
38/// and returns the pixel data as `Vec<u8>` in RGBA format (4 bytes per pixel).
39///
40/// The returned buffer has dimensions `width * height * 4` bytes.
41/// Pixels are ordered row-by-row from top-left to bottom-right.
42///
43/// # Example
44/// ```no_run
45/// use polyscope_rs::*;
46///
47/// init().unwrap();
48/// register_point_cloud("pts", vec![Vec3::ZERO, Vec3::X, Vec3::Y]);
49/// let pixels = render_to_image(800, 600).unwrap();
50/// assert_eq!(pixels.len(), 800 * 600 * 4);
51/// ```
52pub fn render_to_image(width: u32, height: u32) -> Result<Vec<u8>> {
53    let mut app = App::new();
54
55    // Create headless render engine
56    let engine = RenderEngine::new_headless(width, height)
57        .block_on()
58        .map_err(|e| {
59            crate::PolyscopeError::RenderError(format!("Failed to create headless engine: {e}"))
60        })?;
61    app.engine = Some(engine);
62
63    // Clear stale GPU resources from all structures so they get re-initialized
64    // with the new wgpu device. This is necessary because each headless render
65    // call creates a fresh device, but structures in the global registry may
66    // retain buffers from a previous device.
67    with_context_mut(|ctx| {
68        for structure in ctx.registry.iter_mut() {
69            structure.clear_gpu_resources();
70        }
71    });
72
73    // Render one frame and capture
74    app.render_frame_headless();
75    app.capture_to_buffer()
76}