runmat_runtime/builtins/plotting/core/
context.rs1use runmat_accelerate_api::{AccelContextHandle, AccelContextKind, WgpuContextHandle};
11#[cfg(target_arch = "wasm32")]
12use runmat_thread_local::runmat_thread_local;
13#[cfg(target_arch = "wasm32")]
14use std::cell::RefCell;
15#[cfg(not(target_arch = "wasm32"))]
16use std::sync::{OnceLock, RwLock};
17
18use crate::{build_runtime_error, BuiltinResult, RuntimeError};
19
20#[cfg(not(target_arch = "wasm32"))]
22static SHARED_WGPU_CONTEXT: OnceLock<RwLock<Option<WgpuContextHandle>>> = OnceLock::new();
23
24#[cfg(target_arch = "wasm32")]
25runmat_thread_local! {
26 static SHARED_WGPU_CONTEXT: RefCell<Option<WgpuContextHandle>> = RefCell::new(None);
27}
28
29#[cfg(not(target_arch = "wasm32"))]
30fn shared_context_slot() -> &'static RwLock<Option<WgpuContextHandle>> {
31 SHARED_WGPU_CONTEXT.get_or_init(|| RwLock::new(None))
32}
33
34pub fn shared_wgpu_context() -> Option<WgpuContextHandle> {
36 #[cfg(not(target_arch = "wasm32"))]
37 {
38 shared_context_slot()
39 .read()
40 .ok()
41 .and_then(|context| context.clone())
42 }
43 #[cfg(target_arch = "wasm32")]
44 {
45 SHARED_WGPU_CONTEXT.with(|cell| cell.borrow().clone())
46 }
47}
48
49pub fn install_wgpu_context(context: &WgpuContextHandle) {
52 #[cfg(not(target_arch = "wasm32"))]
53 {
54 if let Ok(mut slot) = shared_context_slot().write() {
55 *slot = Some(context.clone());
56 }
57 }
58 #[cfg(target_arch = "wasm32")]
59 {
60 SHARED_WGPU_CONTEXT.with(|cell| {
61 *cell.borrow_mut() = Some(context.clone());
62 });
63 }
64 propagate_to_plot_crate(context);
65}
66
67pub fn ensure_context_from_provider() -> BuiltinResult<WgpuContextHandle> {
70 if let Some(handle) = runmat_accelerate_api::export_context(AccelContextKind::Plotting) {
74 return match handle {
75 AccelContextHandle::Wgpu(ctx) => {
76 install_wgpu_context(&ctx);
77 Ok(ctx)
78 }
79 };
80 }
81
82 if let Some(ctx) = shared_wgpu_context() {
83 return Ok(ctx);
84 }
85
86 Err(context_error(
87 "plotting context unavailable (GPU provider did not export a shared device)",
88 ))
89}
90
91fn context_error(message: impl Into<String>) -> RuntimeError {
92 build_runtime_error(message)
93 .with_identifier("RunMat:plot:ContextUnavailable")
94 .build()
95}
96
97fn propagate_to_plot_crate(context: &WgpuContextHandle) {
98 #[cfg(any(
107 feature = "gui",
108 feature = "plot-core",
109 all(target_arch = "wasm32", feature = "plot-web")
110 ))]
111 {
112 use runmat_plot::context::{
113 install_shared_wgpu_context as install_plot_context, SharedWgpuContext,
114 };
115 use runmat_plot::gpu::tuning as plot_tuning;
116 install_plot_context(SharedWgpuContext {
117 instance: context.instance.clone(),
118 device: context.device.clone(),
119 queue: context.queue.clone(),
120 adapter: context.adapter.clone(),
121 adapter_info: context.adapter_info.clone(),
122 limits: context.limits.clone(),
123 features: context.features,
124 });
125 if let Some(wg) = runmat_accelerate_api::workgroup_size_hint() {
126 plot_tuning::set_effective_workgroup_size(wg);
127 }
128 }
129
130 #[cfg(not(any(feature = "gui", all(target_arch = "wasm32", feature = "plot-web"))))]
131 {
132 let _ = context;
133 }
134}
135
136#[cfg(all(test, feature = "plot-core"))]
137pub(crate) mod tests {
138 use super::*;
139 use pollster::FutureExt;
140 use std::sync::Arc;
141
142 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
143 #[test]
144 fn install_context_propagates_to_plot_crate() {
145 if std::env::var("RUNMAT_PLOT_SKIP_GPU_TESTS").is_ok() {
146 return;
147 }
148 let instance = Arc::new(wgpu::Instance::default());
149 let adapter = match instance
150 .request_adapter(&wgpu::RequestAdapterOptions {
151 power_preference: wgpu::PowerPreference::HighPerformance,
152 compatible_surface: None,
153 force_fallback_adapter: false,
154 })
155 .block_on()
156 {
157 Some(adapter) => adapter,
158 None => return,
159 };
160 let adapter_info = adapter.get_info();
161 let adapter_limits = adapter.limits();
162 let adapter_features = adapter.features();
163 let adapter = Arc::new(adapter);
164 let (device, queue) = match adapter
165 .request_device(
166 &wgpu::DeviceDescriptor {
167 label: Some("plotting-context-test-device"),
168 required_features: adapter_features,
169 required_limits: adapter_limits.clone(),
170 },
171 None,
172 )
173 .block_on()
174 {
175 Ok(pair) => pair,
176 Err(_) => return,
177 };
178 let handle = WgpuContextHandle {
179 instance: instance.clone(),
180 device: Arc::new(device),
181 queue: Arc::new(queue),
182 adapter: adapter.clone(),
183 adapter_info,
184 limits: adapter_limits.clone(),
185 features: adapter_features,
186 };
187 install_wgpu_context(&handle);
188 assert!(shared_wgpu_context().is_some());
189 assert!(runmat_plot::shared_wgpu_context().is_some());
190 }
191}