Skip to main content

runmat_runtime/builtins/plotting/core/
context.rs

1//! Shared plotting context registry for zero-copy rendering.
2//!
3//! This module tracks the GPU device/queue exported by the active acceleration
4//! provider so plotting backends (native GUI or wasm/web) can reuse the same
5//! `wgpu::Device` without creating duplicate adapters. Call
6//! [`ensure_context_from_provider`] or [`install_wgpu_context`] once a provider
7//! is initialized; subsequent callers can query [`shared_wgpu_context`] to
8//! determine whether zero-copy rendering is possible.
9
10#[cfg(not(target_arch = "wasm32"))]
11use once_cell::sync::OnceCell;
12use runmat_accelerate_api::{AccelContextHandle, AccelContextKind, WgpuContextHandle};
13#[cfg(target_arch = "wasm32")]
14use runmat_thread_local::runmat_thread_local;
15#[cfg(target_arch = "wasm32")]
16use std::cell::RefCell;
17
18use crate::{build_runtime_error, BuiltinResult, RuntimeError};
19
20/// Process-wide WGPU context exported by the acceleration provider (if any).
21#[cfg(not(target_arch = "wasm32"))]
22static SHARED_WGPU_CONTEXT: OnceCell<WgpuContextHandle> = OnceCell::new();
23
24#[cfg(target_arch = "wasm32")]
25runmat_thread_local! {
26    static SHARED_WGPU_CONTEXT: RefCell<Option<WgpuContextHandle>> = RefCell::new(None);
27}
28
29/// Returns the cached shared WGPU context, if one has been installed.
30pub fn shared_wgpu_context() -> Option<WgpuContextHandle> {
31    #[cfg(not(target_arch = "wasm32"))]
32    {
33        SHARED_WGPU_CONTEXT.get().cloned()
34    }
35    #[cfg(target_arch = "wasm32")]
36    {
37        SHARED_WGPU_CONTEXT.with(|cell| cell.borrow().clone())
38    }
39}
40
41/// Install a shared context that was exported out-of-band (e.g. from a host
42/// application that already called `export_context`).
43pub fn install_wgpu_context(context: &WgpuContextHandle) {
44    #[cfg(not(target_arch = "wasm32"))]
45    {
46        let _ = SHARED_WGPU_CONTEXT.set(context.clone());
47    }
48    #[cfg(target_arch = "wasm32")]
49    {
50        SHARED_WGPU_CONTEXT.with(|cell| {
51            *cell.borrow_mut() = Some(context.clone());
52        });
53    }
54    propagate_to_plot_crate(context);
55}
56
57/// Ensure the shared context is populated by calling back into the acceleration
58/// provider. Returns the cached context when available.
59pub fn ensure_context_from_provider() -> BuiltinResult<WgpuContextHandle> {
60    if let Some(ctx) = shared_wgpu_context() {
61        return Ok(ctx);
62    }
63
64    let handle =
65        runmat_accelerate_api::export_context(AccelContextKind::Plotting).ok_or_else(|| {
66            context_error(
67                "plotting context unavailable (GPU provider did not export a shared device)",
68            )
69        })?;
70    match handle {
71        AccelContextHandle::Wgpu(ctx) => {
72            install_wgpu_context(&ctx);
73            Ok(ctx)
74        }
75    }
76}
77
78fn context_error(message: impl Into<String>) -> RuntimeError {
79    build_runtime_error(message)
80        .with_identifier("RunMat:plot:ContextUnavailable")
81        .build()
82}
83
84fn propagate_to_plot_crate(context: &WgpuContextHandle) {
85    // Make the shared device available to the `runmat-plot` crate.
86    //
87    // This is required for zero-copy GPU-resident plotting:
88    // - plot builtins export provider buffers
89    // - plot GPU packers need an Arc<Device/Queue> to build vertex buffers
90    //
91    // Note: `runmat-plot` owns its own global context store; we install into it whenever
92    // a provider exports a plotting-capable WGPU context.
93    #[cfg(any(
94        feature = "gui",
95        feature = "plot-core",
96        all(target_arch = "wasm32", feature = "plot-web")
97    ))]
98    {
99        use runmat_plot::context::{
100            install_shared_wgpu_context as install_plot_context, SharedWgpuContext,
101        };
102        use runmat_plot::gpu::tuning as plot_tuning;
103        install_plot_context(SharedWgpuContext {
104            instance: context.instance.clone(),
105            device: context.device.clone(),
106            queue: context.queue.clone(),
107            adapter: context.adapter.clone(),
108            adapter_info: context.adapter_info.clone(),
109            limits: context.limits.clone(),
110            features: context.features,
111        });
112        if let Some(wg) = runmat_accelerate_api::workgroup_size_hint() {
113            plot_tuning::set_effective_workgroup_size(wg);
114        }
115    }
116
117    #[cfg(not(any(feature = "gui", all(target_arch = "wasm32", feature = "plot-web"))))]
118    {
119        let _ = context;
120    }
121}
122
123#[cfg(all(test, feature = "plot-core"))]
124pub(crate) mod tests {
125    use super::*;
126    use pollster::FutureExt;
127    use std::sync::Arc;
128
129    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
130    #[test]
131    fn install_context_propagates_to_plot_crate() {
132        if std::env::var("RUNMAT_PLOT_SKIP_GPU_TESTS").is_ok() {
133            return;
134        }
135        let instance = Arc::new(wgpu::Instance::default());
136        let adapter = match instance
137            .request_adapter(&wgpu::RequestAdapterOptions {
138                power_preference: wgpu::PowerPreference::HighPerformance,
139                compatible_surface: None,
140                force_fallback_adapter: false,
141            })
142            .block_on()
143        {
144            Some(adapter) => adapter,
145            None => return,
146        };
147        let adapter_info = adapter.get_info();
148        let adapter_limits = adapter.limits();
149        let adapter_features = adapter.features();
150        let adapter = Arc::new(adapter);
151        let (device, queue) = match adapter
152            .request_device(
153                &wgpu::DeviceDescriptor {
154                    label: Some("plotting-context-test-device"),
155                    required_features: adapter_features,
156                    required_limits: adapter_limits.clone(),
157                },
158                None,
159            )
160            .block_on()
161        {
162            Ok(pair) => pair,
163            Err(_) => return,
164        };
165        let handle = WgpuContextHandle {
166            instance: instance.clone(),
167            device: Arc::new(device),
168            queue: Arc::new(queue),
169            adapter: adapter.clone(),
170            adapter_info,
171            limits: adapter_limits.clone(),
172            features: adapter_features,
173        };
174        install_wgpu_context(&handle);
175        assert!(shared_wgpu_context().is_some());
176        assert!(runmat_plot::shared_wgpu_context().is_some());
177    }
178}