Skip to main content

enki/enki_api/context/
instance.rs

1use anyhow::{Context, Result};
2use std::sync::{Arc, Mutex, OnceLock};
3
4use anu::context::{EngineConfig, EnkiEngine, EnkiEngineBuilder};
5use utu::GpuWindow;
6
7use super::ambient::ActiveFlowGuard;
8use super::errors::handle_execution_error;
9use super::flow::Flow;
10
11static ACTIVE_ENKI: OnceLock<Arc<Enki>> = OnceLock::new();
12
13/// Sets the global active Enki context instance.
14pub fn set_active_enki(enki: Arc<Enki>) {
15    let _ = ACTIVE_ENKI.set(enki);
16}
17
18/// Retrieves a clone of the globally active Enki context.
19pub fn active_enki() -> Arc<Enki> {
20    ACTIVE_ENKI
21        .get()
22        .cloned()
23        .expect("[Enki] No active Enki context found. Did you call 'Enki::init()'?")
24}
25
26/// Retrieves a clone of the underlying Vulkan compute engine (`EnkiEngine`).
27pub fn active_engine() -> Arc<EnkiEngine> {
28    active_enki().engine.clone()
29}
30
31/// Builder for configuring engine settings before runtime initialization.
32pub struct EnkiBuilder {
33    config: EngineConfig,
34}
35
36impl EnkiBuilder {
37    pub fn new() -> Self {
38        Self {
39            config: EngineConfig::default(),
40        }
41    }
42
43    /// Sets the application name reported to the Vulkan driver.
44    pub fn app_name(mut self, name: impl Into<String>) -> Self {
45        self.config.app_name = name.into();
46        self
47    }
48
49    /// Sets the maximum capacity in bytes allocated for uniform parameter uploads (ParamArena).
50    pub fn param_arena_size(mut self, size_bytes: u64) -> Self {
51        self.config.max_param_arena_size = Some(size_bytes);
52        self
53    }
54
55    // /// Configures the maximum number of bindless sampled image descriptors.
56    // pub fn max_sampled_images(mut self, count: u32) -> Self {
57    //     self.config.max_sampled_images = count;
58    //     self
59    // }
60
61    // /// Configures the maximum number of bindless storage image descriptors.
62    // pub fn max_storage_images(mut self, count: u32) -> Self {
63    //     self.config.max_storage_images = count;
64    //     self
65    // }
66
67    // /// Configures the maximum number of bindless sampler descriptors.
68    // pub fn max_samplers(mut self, count: u32) -> Self {
69    //     self.config.max_samplers = count;
70    //     self
71    // }
72
73    /// Finalizes configuration and initializes a headless GPU context.
74    #[track_caller]
75    pub fn init(self) -> Arc<Enki> {
76        let caller = std::panic::Location::caller();
77        let mut config = self.config;
78        config.headless = true;
79        config.caller_location = Some((caller.file(), caller.line(), caller.column()));
80
81        match Enki::new_headless_with_config(config) {
82            Ok(enki) => enki,
83            Err(e) => handle_execution_error(&e),
84        }
85    }
86
87    /// Finalizes configuration and initializes a windowed presentation GPU context.
88    #[track_caller]
89    pub fn init_windowed<W>(self, window: Arc<W>, width: u32, height: u32) -> Arc<Enki>
90    where
91        W: raw_window_handle::HasWindowHandle
92            + raw_window_handle::HasDisplayHandle
93            + Send
94            + Sync
95            + 'static,
96    {
97        let caller = std::panic::Location::caller();
98        let mut config = self.config;
99        config.headless = false;
100        config.caller_location = Some((caller.file(), caller.line(), caller.column()));
101
102        match Enki::new_windowed_with_config(config, window, width, height) {
103            Ok(enki) => enki,
104            Err(e) => handle_execution_error(&e),
105        }
106    }
107
108    /// Sets the maximum number of hardware timestamp profiling queries per flow.
109    pub fn max_timestamp_queries(mut self, count: u32) -> Self {
110        self.config.max_timestamp_queries = count;
111        self
112    }
113}
114
115impl Default for EnkiBuilder {
116    fn default() -> Self {
117        Self::new()
118    }
119}
120
121/// The central handle to the Enki heterogeneous GPU runtime.
122///
123/// Manages Vulkan instance lifecycle, hardware devices, memory allocation heaps,
124/// and execution flows across host CPU and GPU silicon.
125pub struct Enki {
126    pub window_context: Option<Arc<dyn std::any::Any + Send + Sync>>,
127    pub gpu_window: Option<Mutex<GpuWindow>>,
128    pub engine: Arc<EnkiEngine>,
129}
130
131impl Enki {
132    /// Returns a new `EnkiBuilder` to configure runtime options before initialization.
133    pub fn builder() -> EnkiBuilder {
134        EnkiBuilder::new()
135    }
136
137    /// Initializes a headless GPU compute context with default settings.
138    pub fn init() -> Arc<Self> {
139        Self::builder().init()
140    }
141
142    /// Initializes a windowed GPU context with swapchain presentation support.
143    pub fn init_windowed<W>(window: Arc<W>, width: u32, height: u32) -> Arc<Self>
144    where
145        W: raw_window_handle::HasWindowHandle
146            + raw_window_handle::HasDisplayHandle
147            + Send
148            + Sync
149            + 'static,
150    {
151        Self::builder().init_windowed(window, width, height)
152    }
153
154    fn new_headless_with_config(config: EngineConfig) -> Result<Arc<Self>> {
155        let engine = EnkiEngineBuilder::new(config)
156            .build()
157            .context("[Enki Core] Failed to build headless EnkiEngine")?;
158
159        let enki = Arc::new(Self {
160            window_context: None,
161            gpu_window: None,
162            engine: Arc::from(engine),
163        });
164
165        set_active_enki(enki.clone());
166        Ok(enki)
167    }
168
169    fn new_windowed_with_config<W>(
170        mut config: EngineConfig,
171        window: Arc<W>,
172        width: u32,
173        height: u32,
174    ) -> Result<Arc<Self>>
175    where
176        W: raw_window_handle::HasWindowHandle
177            + raw_window_handle::HasDisplayHandle
178            + Send
179            + Sync
180            + 'static,
181    {
182        let display_handle = window
183            .display_handle()
184            .map_err(|_| {
185                let diag = anu::diagnostics::hw::headless_display_mismatch();
186                anyhow::anyhow!("{}", anu::diagnostics::emit_diagnostic(&diag))
187            })?
188            .as_raw();
189
190        let surface_extensions = ash_window::enumerate_required_extensions(display_handle)
191            .map_err(|_| {
192                let diag = anu::diagnostics::hw::headless_display_mismatch();
193                anyhow::anyhow!("{}", anu::diagnostics::emit_diagnostic(&diag))
194            })?;
195
196        for &ext_ptr in surface_extensions {
197            let cstr = unsafe { std::ffi::CStr::from_ptr(ext_ptr) };
198            config.required_instance_extensions.push(cstr.to_owned());
199        }
200
201        config
202            .required_device_extensions
203            .push(std::ffi::CString::from(ash::khr::swapchain::NAME));
204
205        let engine = EnkiEngineBuilder::new(config)
206            .build()
207            .context("[Enki Core] Failed to build windowed EnkiEngine")?;
208
209        let engine = Arc::new(engine);
210
211        let gpu_window = GpuWindow::new(
212            &engine.instance,
213            engine.raw_physical_device(),
214            &engine.device,
215            window.as_ref(),
216            window.as_ref(),
217            width,
218            height,
219            3,
220        )
221        .context("[Enki Core] Failed to create GpuWindow context")?;
222
223        let enki = Arc::new(Self {
224            window_context: Some(window as Arc<dyn std::any::Any + Send + Sync>),
225            gpu_window: Some(Mutex::new(gpu_window)),
226            engine,
227        });
228
229        set_active_enki(enki.clone());
230        Ok(enki)
231    }
232
233    /// Returns an `Arc` clone of the currently active global Enki runtime instance.
234    pub fn active() -> Arc<Self> {
235        active_enki()
236    }
237
238    pub fn begin_flow(&self) -> Flow<'_> {
239        Flow::new(self)
240    }
241
242    /// Records and executes an atomic compute and presentation flow, halting on execution error.
243    #[track_caller]
244    #[inline(always)]
245    pub fn flow<R, F>(&self, f: F) -> R
246    where
247        F: FnOnce(&mut Flow<'_>) -> R,
248    {
249        match self.try_flow(f) {
250            Ok(output) => output,
251            Err(e) => handle_execution_error(&e),
252        }
253    }
254
255    /// Records and executes an atomic flow, returning an explicit `Result` on failure.
256    #[track_caller]
257    pub fn try_flow<R, F>(&self, f: F) -> Result<R>
258    where
259        F: FnOnce(&mut Flow<'_>) -> R,
260    {
261        let mut flow = self.begin_flow();
262        let output = {
263            let _guard = ActiveFlowGuard::enter(&mut flow);
264            f(&mut flow)
265        };
266
267        if let Some(err) = flow.sticky_error.take() {
268            return Err(err);
269        }
270
271        flow.try_end_flow()?;
272        Ok(output)
273    }
274
275    /// Resizes the underlying presentation window swapchain dimensions.
276    pub fn resize(&self, width: u32, height: u32) -> Result<()> {
277        if let Some(window_mutex) = &self.gpu_window {
278            let mut window = window_mutex.lock().unwrap();
279            window
280                .recreate(self.engine.raw_physical_device(), width, height)
281                .context("[Enki Core] Swapchain recreation failed")?;
282        }
283        Ok(())
284    }
285
286    /// Blocks the host CPU until all in-flight GPU timeline tasks are completely idle.
287    pub fn wait_idle(&self) -> Result<()> {
288        self.engine.wait_idle()
289    }
290}