enki/enki_api/context/
instance.rs1use 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
13pub fn set_active_enki(enki: Arc<Enki>) {
15 let _ = ACTIVE_ENKI.set(enki);
16}
17
18pub 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
26pub fn active_engine() -> Arc<EnkiEngine> {
28 active_enki().engine.clone()
29}
30
31pub struct EnkiBuilder {
33 config: EngineConfig,
34}
35
36impl EnkiBuilder {
37 pub fn new() -> Self {
38 Self {
39 config: EngineConfig::default(),
40 }
41 }
42
43 pub fn app_name(mut self, name: impl Into<String>) -> Self {
45 self.config.app_name = name.into();
46 self
47 }
48
49 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 #[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 #[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 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
121pub 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 pub fn builder() -> EnkiBuilder {
134 EnkiBuilder::new()
135 }
136
137 pub fn init() -> Arc<Self> {
139 Self::builder().init()
140 }
141
142 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 pub fn active() -> Arc<Self> {
235 active_enki()
236 }
237
238 pub fn begin_flow(&self) -> Flow<'_> {
239 Flow::new(self)
240 }
241
242 #[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 #[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 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 pub fn wait_idle(&self) -> Result<()> {
288 self.engine.wait_idle()
289 }
290}