stereokit_rust/tools/xr_comp_layers.rs
1//! XR_KHR_android_surface_swapchain extension implementation
2//!
3//! **This is a rust adaptation of <https://github.com/StereoKit/StereoKit/blob/develop/Examples/StereoKitTest/Tools/XrCompLayers.cs>**
4//!
5//! <https://registry.khronos.org/OpenXR/specs/1.1/html/xrspec.html#XR_KHR_android_surface_swapchain>
6
7use crate::{
8 maths::{Pose, Quat, Rect, Vec2},
9 prelude::*,
10 system::{Backend, BackendGraphics, BackendOpenXR, BackendXRType},
11 tex::{Tex, TexFormat, TexType},
12};
13#[cfg(target_os = "android")]
14use openxr_sys::{pfn::CreateSwapchainAndroidSurfaceKHR, platform::jobject};
15
16use openxr_sys::{
17 Duration, Session, Space, Swapchain, SwapchainImageWaitInfo,
18 pfn::{
19 AcquireSwapchainImage, CreateSwapchain, DestroySwapchain, EnumerateSwapchainImages, ReleaseSwapchainImage,
20 WaitSwapchainImage,
21 },
22};
23
24use openxr_sys::{
25 CompositionLayerFlags, CompositionLayerQuad, Extent2Df, Extent2Di, EyeVisibility, Offset2Di, Posef, Quaternionf,
26 Rect2Di, Result as XrResult, StructureType, SwapchainCreateFlags, SwapchainCreateInfo, SwapchainSubImage,
27 SwapchainUsageFlags, Vector3f,
28};
29
30use std::ptr::null_mut;
31
32#[derive(Debug)]
33
34/// `XrCompLayers` provides low-level OpenXR composition layer functionality, while `SwapchainSk`
35/// offers a high-level wrapper for creating and managing OpenXR swapchains with StereoKit integration.
36///
37/// ### Examples
38/// ## Basic Usage with SwapchainSk
39/// ```
40/// # stereokit_rust::test_init_sk!(); // !!!! Get a proper way to initialize sk !!!!
41/// use stereokit_rust::{ maths::{Vec3, Matrix, Pose, Vec2, Rect}, render_list::RenderList,
42/// util::{named_colors, Color128, Time}, tex::TexFormat, material::Material, mesh::Mesh,
43/// system::{Backend, BackendXRType, RenderClear}, tools::xr_comp_layers::* };
44///
45/// use openxr_sys::SwapchainUsageFlags;
46///
47/// // Check if OpenXR is available
48/// if Backend::xr_type() == BackendXRType::OpenXR {
49/// // Create XrCompLayers instance
50/// if let Some(mut swapchain) = SwapchainSk::new(
51/// TexFormat::RGBA32, 512, 512, None ){
52///
53/// // Set up rendering components
54/// let mut render_list = RenderList::new();
55/// let mut material = Material::default().copy();
56/// let projection = Matrix::orthographic(0.2, 0.2, 0.01, 10.0);
57///
58/// // Add a sphere to the scene
59/// render_list.add_mesh( Mesh::sphere(), material, Matrix::s(0.05 * Vec3::ONE),
60/// named_colors::WHITE, None);
61///
62/// // Render loop example
63/// test_steps!( // !!!! Get a proper main loop !!!!
64/// // Acquire the next swapchain image
65/// if let Ok(_image_index) = swapchain.acquire_image(None) {
66/// // Get the render target texture
67/// if let Some(render_tex) = swapchain.get_render_target() {
68/// // Render to the swapchain texture
69/// render_list.draw_now(
70/// render_tex,
71/// Matrix::look_at(Vec3::angle_xy(Time::get_totalf() * 90.0, 0.0), Vec3::ZERO, None),
72/// projection,
73/// Some(Color128::new(0.4, 0.3, 0.2, 1.0)),
74/// Some(RenderClear::Color),
75/// Rect::new(0.0, 0.0, 1.0, 1.0),
76/// None,
77/// );
78/// }
79///
80/// // Release the image back to the swapchain
81/// swapchain.release_image().expect("Failed to release image");
82///
83/// // Submit the quad layer to OpenXR
84/// XrCompLayers::submit_quad_layer(
85/// Pose::new(Vec3::new(0.0, 1.5, -1.0), None), // World position
86/// Vec2::new(0.3, 0.3), // Quad size
87/// swapchain.handle, // Swapchain handle
88/// Rect::new(0.0, 0.0, 512.0, 512.0), // Texture rectangle
89/// 0, // Array index
90/// 1, // Sort order
91/// None, // Eye visibility
92/// None, // XR space
93/// );
94/// }
95/// );
96///
97/// // Clean up
98/// swapchain.destroy();
99/// }
100/// }
101/// ```
102///
103pub struct XrCompLayers {
104 // OpenXR function pointers
105 #[cfg(target_os = "android")]
106 xr_create_swapchain_android: Option<CreateSwapchainAndroidSurfaceKHR>,
107 xr_create_swapchain: Option<CreateSwapchain>,
108 xr_destroy_swapchain: Option<DestroySwapchain>,
109 xr_enumerate_swaptchain_images: Option<EnumerateSwapchainImages>,
110 xr_acquire_swapchain_image: Option<AcquireSwapchainImage>,
111 xr_wait_swaptchain_image: Option<WaitSwapchainImage>,
112 xr_release_swaptchain_image: Option<ReleaseSwapchainImage>,
113}
114
115impl Default for XrCompLayers {
116 fn default() -> Self {
117 Self {
118 #[cfg(target_os = "android")]
119 xr_create_swapchain_android: BackendOpenXR::get_function::<CreateSwapchainAndroidSurfaceKHR>(
120 "xrCreateSwapchainAndroidSurfaceKHR",
121 ),
122 //#[cfg(not(target_os = "android"))]
123 xr_create_swapchain: BackendOpenXR::get_function::<CreateSwapchain>("xrCreateSwapchain"),
124
125 xr_destroy_swapchain: BackendOpenXR::get_function::<DestroySwapchain>("xrDestroySwapchain"),
126 xr_enumerate_swaptchain_images: BackendOpenXR::get_function::<EnumerateSwapchainImages>(
127 "xrEnumerateSwapchainImages",
128 ),
129 xr_acquire_swapchain_image: BackendOpenXR::get_function::<AcquireSwapchainImage>("xrAcquireSwapchainImage"),
130 xr_wait_swaptchain_image: BackendOpenXR::get_function::<WaitSwapchainImage>("xrWaitSwapchainImage"),
131 xr_release_swaptchain_image: BackendOpenXR::get_function::<ReleaseSwapchainImage>(
132 "xrReleaseSwapchainImage",
133 ),
134 }
135 }
136}
137
138impl XrCompLayers {
139 /// Initializes the composition layers helper by loading required OpenXR bindings.
140 /// Returns `Some(Self)` if the XR runtime type is OpenXR and all bindings are present.
141 pub fn new() -> Option<Self> {
142 let this = Self::default();
143
144 #[cfg(target_os = "android")]
145 {
146 if !BackendOpenXR::ext_enabled("XR_KHR_android_surface_swapchain") {
147 Log::warn(
148 "XrCompLayers: XR_KHR_android_surface_swapchain extension is not enabled. You have to enable it before sk::init.",
149 );
150 return None;
151 }
152 }
153
154 if !(Backend::xr_type() == BackendXRType::OpenXR && this.load_bindings()) {
155 Log::warn(format!("❌ XrCompLayers: some function bindings are missing : {this:?}"));
156 return None;
157 }
158
159 Some(this)
160 }
161
162 /// Checks presence of all required swapchain function bindings.
163 fn load_bindings(&self) -> bool {
164 let mut swapchain_func_present = true;
165 swapchain_func_present &= self.xr_create_swapchain.is_some();
166
167 #[cfg(target_os = "android")]
168 {
169 swapchain_func_present &= self.xr_create_swapchain_android.is_some();
170 }
171
172 swapchain_func_present
173 && self.xr_destroy_swapchain.is_some()
174 && self.xr_enumerate_swaptchain_images.is_some()
175 && self.xr_acquire_swapchain_image.is_some()
176 && self.xr_wait_swaptchain_image.is_some()
177 && self.xr_release_swaptchain_image.is_some()
178 }
179
180 /// Convert a StereoKit `TexFormat` into the corresponding native OpenXR format value.
181 pub fn to_native_format(format: TexFormat) -> i64 {
182 match Backend::graphics() {
183 BackendGraphics::D3D11 => match format {
184 TexFormat::RGBA32 => 29,
185 TexFormat::RGBA32Linear => 28,
186 TexFormat::BGRA32 => 91,
187 TexFormat::BGRA32Linear => 87,
188 TexFormat::RGB10A2 => 24,
189 TexFormat::RG11B10 => 26,
190 _ => panic!("Unsupported texture format"),
191 },
192 BackendGraphics::OpenGLESEGL => match format {
193 TexFormat::RGBA32 => 0x8C43,
194 TexFormat::RGBA32Linear => 0x8058,
195 TexFormat::RGB10A2 => 0x8059,
196 TexFormat::RG11B10 => 0x8C3A,
197 _ => panic!("Unsupported texture format"),
198 },
199 _ => panic!("Unsupported graphics backend"),
200 }
201 }
202
203 /// Submit a quad layer to the OpenXR composition.
204 ///
205 /// This method submits a rendered quad layer to the OpenXR compositor, which will
206 /// be displayed in the XR environment. The quad appears as a 2D surface in 3D space.
207 ///
208 /// # Parameters
209 /// - `world_pose`: Pose of the quad in world space (position and orientation).
210 /// - `size`: Dimensions of the quad in meters.
211 /// - `swapchain`: Swapchain handle to sample the texture from.
212 /// - `swapchain_rect`: Texture rectangle within the swapchain image (in pixel coordinates).
213 /// - `swapchain_array_index`: Array slice index for texture arrays (usually 0).
214 /// - `composition_sort_order`: Ordering for layer submission (higher values render on top).
215 /// - `visibility`: Optional eye visibility mask (None means both eyes).
216 /// - `xr_space`: Optional XR space handle (None uses default space).
217 ///
218 #[allow(clippy::too_many_arguments)]
219 pub fn submit_quad_layer(
220 world_pose: Pose,
221 size: Vec2,
222 swapchain: Swapchain,
223 swapchain_rect: Rect,
224 swapchain_array_index: u32,
225 composition_sort_order: i32,
226 visibility: Option<EyeVisibility>,
227 xr_space: Option<u64>,
228 ) {
229 let orientation = (world_pose.orientation * Quat::from_angles(180.0, 0.0, 0.0)).conjugate();
230 let xr_space = xr_space.unwrap_or_else(BackendOpenXR::space);
231 let mut quad_layer = CompositionLayerQuad {
232 ty: StructureType::COMPOSITION_LAYER_QUAD,
233 next: null_mut(),
234 layer_flags: CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA,
235 space: Space::from_raw(xr_space),
236 eye_visibility: visibility.unwrap_or(EyeVisibility::BOTH),
237 sub_image: SwapchainSubImage {
238 swapchain,
239 image_rect: Rect2Di {
240 offset: Offset2Di { x: swapchain_rect.x as i32, y: swapchain_rect.y as i32 },
241 extent: Extent2Di { width: swapchain_rect.width as i32, height: swapchain_rect.height as i32 },
242 },
243 image_array_index: swapchain_array_index,
244 },
245 pose: Posef {
246 orientation: Quaternionf { x: orientation.x, y: orientation.y, z: orientation.z, w: orientation.w },
247 position: Vector3f { x: world_pose.position.x, y: world_pose.position.y, z: world_pose.position.z },
248 },
249 size: Extent2Df { width: size.x, height: size.y },
250 };
251
252 BackendOpenXR::add_composition_layer(&mut quad_layer, composition_sort_order);
253 }
254
255 /// Create an Android surface swapchain via `XR_KHR_android_surface_swapchain`.
256 /// Returns the swapchain handle and raw `jobject` pointer on success.
257 /// /// ## Android Surface Swapchain (Android only)
258 ///
259 /// ```no_run
260 /// # #[cfg(target_os = "android")]
261 /// # {
262 /// use stereokit_rust::tools::xr_comp_layers::XrCompLayers;
263 /// use openxr_sys::SwapchainUsageFlags;
264 ///
265 /// if let Some(comp_layers) = XrCompLayers::new() {
266 /// if let Some((swapchain_handle, android_surface)) = comp_layers.try_make_android_swapchain(
267 /// 512, 512, SwapchainUsageFlags::COLOR_ATTACHMENT, false) {
268 ///
269 /// println!("Created Android surface swapchain: {:?}", android_surface);
270 ///
271 /// // Use the surface
272 ///
273 /// // Clean up
274 /// comp_layers.destroy_android_swapchain(swapchain_handle);
275 /// }
276 /// }
277 /// # }
278 #[cfg(target_os = "android")]
279 pub fn try_make_android_swapchain(
280 &self,
281 width: u32,
282 height: u32,
283 usage: SwapchainUsageFlags,
284 single_image: bool,
285 ) -> Option<(Swapchain, *mut jobject)> {
286 use openxr_sys::platform::jobject;
287
288 let mut swapchain = Swapchain::default();
289 let mut surface: *mut jobject = null_mut();
290
291 let create_flags = if single_image {
292 SwapchainCreateFlags::STATIC_IMAGE
293 } else {
294 SwapchainCreateFlags::PROTECTED_CONTENT
295 };
296
297 let info = SwapchainCreateInfo {
298 ty: StructureType::SWAPCHAIN_CREATE_INFO,
299 next: null_mut(),
300 create_flags: create_flags,
301 usage_flags: usage,
302 format: 0, // Required by spec to be zero for Android surface swapchains
303 sample_count: 0, // Required by spec to be zero for Android surface swapchains
304 width,
305 height,
306 face_count: 0, // Required by spec to be zero for Android surface swapchains
307 array_size: 0, // Required by spec to be zero for Android surface swapchains
308 mip_count: 0, // Required by spec to be zero for Android surface swapchains
309 };
310
311 if let Some(func) = self.xr_create_swapchain_android {
312 let res = unsafe {
313 func(
314 Session::from_raw(BackendOpenXR::session()),
315 &info,
316 &mut swapchain,
317 &mut surface as *mut _ as *mut *mut std::ffi::c_void,
318 )
319 };
320 match res {
321 XrResult::SUCCESS => Some((swapchain, surface)),
322 otherwise => {
323 Log::err(format!("❌ xrDestroySwapchain failed: {otherwise}"));
324 None
325 }
326 }
327 } else {
328 None
329 }
330 }
331
332 /// Destroy the given Android swapchain handle.
333 #[cfg(target_os = "android")]
334 pub fn destroy_android_swapchain(&self, handle: Swapchain) {
335 match unsafe { self.xr_destroy_swapchain.unwrap()(handle) } {
336 XrResult::SUCCESS => {}
337 otherwise => {
338 Log::err(format!("❌ xrDestroySwapchain failed: {otherwise}"));
339 }
340 }
341 }
342
343 /// Create a standard XR swapchain with the given parameters.
344 pub fn try_make_swapchain(
345 &self,
346 width: u32,
347 height: u32,
348 format: TexFormat,
349 usage: SwapchainUsageFlags,
350 single_image: bool,
351 ) -> Option<Swapchain> {
352 let mut swapchain = Swapchain::default();
353 let create_flags = if single_image {
354 SwapchainCreateFlags::STATIC_IMAGE
355 } else {
356 SwapchainCreateFlags::PROTECTED_CONTENT
357 };
358
359 let info = SwapchainCreateInfo {
360 ty: StructureType::SWAPCHAIN_CREATE_INFO,
361 next: null_mut(),
362 format: Self::to_native_format(format),
363 create_flags,
364 usage_flags: usage,
365 sample_count: 1,
366 width,
367 height,
368 face_count: 1,
369 array_size: 1,
370 mip_count: 1,
371 };
372
373 match unsafe {
374 self.xr_create_swapchain.unwrap()(Session::from_raw(BackendOpenXR::session()), &info, &mut swapchain)
375 } {
376 XrResult::SUCCESS => {}
377 otherwise => {
378 Log::err(format!("❌ xrCreateSwapchain failed: {otherwise}"));
379 return None;
380 }
381 }
382 Some(swapchain)
383 }
384
385 /// Destroy the given XR swapchain handle.
386 pub fn destroy_swapchain(swapchain: Swapchain) {
387 // We need to get the function pointer directly
388 let xr_destroy_swapchain = BackendOpenXR::get_function::<DestroySwapchain>("xrDestroySwapchain");
389
390 if let Some(func) = xr_destroy_swapchain {
391 unsafe {
392 func(swapchain);
393 }
394 }
395 }
396}
397
398/// High-level wrapper around an OpenXR swapchain.
399/// Manages creation of render-target textures, image acquisition and release.
400///
401/// `SwapchainSk` provides a convenient interface for working with OpenXR swapchains
402/// in StereoKit applications. It handles the complexity of swapchain image management
403/// and provides StereoKit `Tex` objects for rendering.
404///
405/// ### Examples
406/// ```
407/// # stereokit_rust::test_init_sk!(); // !!!! Get a proper way to initialize sk !!!!
408/// use stereokit_rust::{ maths::{Vec3, Matrix, Pose, Vec2, Rect}, render_list::RenderList,
409/// util::{named_colors, Color128, Time}, tex::TexFormat, material::Material, mesh::Mesh,
410/// system::{Backend, BackendXRType, RenderClear}, tools::xr_comp_layers::* };
411///
412/// // Create a swapchain
413/// if let Some(mut swapchain) = SwapchainSk::new(TexFormat::RGBA32, 512, 512, None) {
414///
415/// // Set up rendering
416/// let mut render_list = RenderList::new();
417/// let material = Material::default().copy();
418/// render_list.add_mesh(
419/// Mesh::cube(),
420/// material,
421/// Matrix::IDENTITY,
422/// named_colors::RED,
423/// None
424/// );
425///
426/// // Render to swapchain
427/// if let Ok(_) = swapchain.acquire_image(None) {
428/// if let Some(render_target) = swapchain.get_render_target() {
429/// render_list.draw_now(
430/// render_target,
431/// Matrix::look_at(Vec3::angle_xy(Time::get_totalf() * 90.0, 0.0), Vec3::ZERO, None),
432/// Matrix::orthographic(1.0, 1.0, 0.1, 10.0),
433/// None,
434/// Some(RenderClear::All),
435/// Rect::new(0.0, 0.0, 1.0, 1.0),
436/// None,
437/// );
438/// }
439/// swapchain.release_image().expect("Failed to release image");
440/// }
441///
442/// // Clean up
443/// swapchain.destroy();
444/// }
445/// ```
446pub struct SwapchainSk {
447 pub xr_comp_layers: XrCompLayers,
448 pub handle: Swapchain,
449 pub width: u32,
450 pub height: u32,
451 pub acquired: u32,
452 images: Vec<Tex>,
453 #[cfg(unix)]
454 gles_images: Vec<openxr_sys::SwapchainImageOpenGLESKHR>,
455 #[cfg(windows)]
456 d3d_images: Vec<openxr_sys::SwapchainImageD3D11KHR>,
457}
458
459impl SwapchainSk {
460 /// Create a new `SwapchainSk` for rendering into an OpenXR quad layer.
461 /// Returns `Some<Self>` if the XR runtime and swapchain creation succeed.
462 pub fn new(format: TexFormat, width: u32, height: u32, xr_comp_layers: Option<XrCompLayers>) -> Option<Self> {
463 let xr_comp_layers = get_xr_comp_layers(xr_comp_layers)?;
464 if Backend::xr_type() == BackendXRType::OpenXR {
465 if let Some(handle) =
466 xr_comp_layers.try_make_swapchain(width, height, format, SwapchainUsageFlags::COLOR_ATTACHMENT, false)
467 {
468 SwapchainSk::wrap(handle, format, width, height, Some(xr_comp_layers))
469 } else {
470 Log::warn("❌ Failed to create XR swapchain: Try_make_swapchain failed");
471 None
472 }
473 } else {
474 Log::warn("Swapchain: OpenXR backend is not available");
475 None
476 }
477 }
478
479 /// Return a reference to the currently acquired render-target texture, if any.
480 ///
481 /// This method provides access to the StereoKit `Tex` object that represents
482 /// the currently acquired swapchain image. The texture can be used as a render
483 /// target for drawing operations.
484 ///
485 /// # Returns
486 /// - `Some(&Tex)`: Reference to the current render target texture.
487 /// - `None`: No image is currently acquired or swapchain is empty.
488 ///
489 /// # Example
490 /// ```no_run
491 /// # use stereokit_rust::tools::xr_comp_layers::SwapchainSk;
492 /// # let mut swapchain: SwapchainSk = todo!();
493 /// if let Ok(_) = swapchain.acquire_image(None) {
494 /// if let Some(render_target) = swapchain.get_render_target() {
495 /// // Use render_target for drawing operations
496 /// println!("Render target size: {}x{}",
497 /// render_target.get_width().unwrap_or(0),
498 /// render_target.get_height().unwrap_or(0));
499 ///
500 /// // ... perform rendering to render_target ...
501 /// }
502 /// swapchain.release_image().expect("Failed to release");
503 /// }
504 /// ```
505 pub fn get_render_target(&self) -> Option<&Tex> {
506 if self.images.is_empty() {
507 return None;
508 }
509 Some(&self.images[self.acquired as usize])
510 }
511
512 /// Wrap OpenGL ES swapchain images into `Tex` objects for `unix` platforms.
513 #[cfg(unix)]
514 pub fn wrap(
515 handle: Swapchain,
516 format: TexFormat,
517 width: u32,
518 height: u32,
519 xr_comp_layers: Option<XrCompLayers>,
520 ) -> Option<Self> {
521 use openxr_sys::SwapchainImageOpenGLESKHR;
522
523 let xr_comp_layers = get_xr_comp_layers(xr_comp_layers)?;
524
525 let mut image_count = 0;
526 match unsafe { xr_comp_layers.xr_enumerate_swaptchain_images.unwrap()(handle, 0, &mut image_count, null_mut()) }
527 {
528 XrResult::SUCCESS => {}
529 otherwise => {
530 Log::err(format!("❌ xrEnumerateSwapchainImages failed: {otherwise}"));
531 return None;
532 }
533 }
534
535 if Backend::graphics() == BackendGraphics::OpenGLESEGL {
536 let mut gles_images: Vec<SwapchainImageOpenGLESKHR> = {
537 let images: Vec<SwapchainImageOpenGLESKHR> = vec![
538 SwapchainImageOpenGLESKHR {
539 image: 0,
540 ty: StructureType::SWAPCHAIN_IMAGE_OPENGL_ES_KHR,
541 next: null_mut()
542 };
543 image_count as usize
544 ];
545 images
546 };
547
548 let mut final_count = 0;
549 match unsafe {
550 xr_comp_layers.xr_enumerate_swaptchain_images.unwrap()(
551 handle,
552 image_count,
553 &mut final_count,
554 gles_images.as_mut_ptr() as *mut _,
555 )
556 } {
557 XrResult::SUCCESS => {}
558 otherwise => {
559 Log::err(format!("❌ xrEnumerateSwapchainImages failed: {otherwise}"));
560 return None;
561 }
562 }
563
564 assert_eq!(gles_images.len(), image_count as usize);
565 //assert_eq!(gles_images.len(), 3);
566
567 let mut this =
568 Self { xr_comp_layers, handle, width, height, acquired: 0, gles_images, images: Vec::with_capacity(0) };
569
570 for image in &this.gles_images {
571 Log::diag(format!("SwapchainSk: image: {image:#?}"));
572 // let mut image_sk =
573 // Tex::gen_color(named_colors::BLUE_VIOLET, width, height, TexType::Rendertarget, format);
574 //let mut image_sk = Tex::new(TexType::Rendertarget, format, None);
575 let mut image_sk =
576 Tex::render_target(width as usize, height as usize, Some(2), Some(format), None).unwrap();
577 unsafe {
578 image_sk.set_native_surface(
579 image.image as *mut std::ffi::c_void,
580 TexType::Rendertarget,
581 XrCompLayers::to_native_format(format),
582 width as i32,
583 height as i32,
584 1,
585 true,
586 )
587 };
588 this.images.push(image_sk);
589 }
590 Some(this)
591 } else {
592 Log::warn("❌ SwapchainSk: OpenGL ES backend is not available");
593 None
594 }
595 }
596
597 /// Wrap D3D11 swapchain images into `Tex` objects for `windows` platforms.
598 #[cfg(windows)]
599 pub fn wrap(
600 handle: Swapchain,
601 format: TexFormat,
602 width: u32,
603 height: u32,
604 xr_comp_layers: Option<XrCompLayers>,
605 ) -> Option<Self> {
606 use openxr_sys::SwapchainImageD3D11KHR;
607 use std::ptr::null_mut;
608
609 let xr_comp_layers = get_xr_comp_layers(xr_comp_layers)?;
610
611 // First, get the image count
612 let mut image_count = 0;
613 match unsafe { xr_comp_layers.xr_enumerate_swaptchain_images.unwrap()(handle, 0, &mut image_count, null_mut()) }
614 {
615 XrResult::SUCCESS => {}
616 err => {
617 Log::err(format!("❌ xrEnumerateSwapchainImages failed: {err}"));
618 return None;
619 }
620 }
621
622 // Only proceed for D3D11 backend
623 if Backend::graphics() == BackendGraphics::D3D11 {
624 // Prepare D3D11 image array
625 let mut d3d_images: Vec<SwapchainImageD3D11KHR> = vec![
626 SwapchainImageD3D11KHR {
627 texture: null_mut(),
628 ty: StructureType::SWAPCHAIN_IMAGE_D3D11_KHR,
629 next: null_mut(),
630 };
631 image_count as usize
632 ];
633 let mut final_count = 0;
634 match unsafe {
635 xr_comp_layers.xr_enumerate_swaptchain_images.unwrap()(
636 handle,
637 image_count,
638 &mut final_count,
639 d3d_images.as_mut_ptr() as *mut _,
640 )
641 } {
642 XrResult::SUCCESS => {}
643 err => {
644 Log::err(format!("❌ xrEnumerateSwapchainImages failed: {err}"));
645 return None;
646 }
647 }
648 let mut this =
649 Self { xr_comp_layers, handle, width, height, acquired: 0, d3d_images, images: Vec::with_capacity(0) };
650
651 // Wrap each D3D11 texture into a Tex object
652 for img in &this.d3d_images {
653 Log::diag(format!("SwapchainSk: image: {:#?}", img));
654 // let mut image_sk =
655 // Tex::gen_color(named_colors::BLUE_VIOLET, width, height, TexType::Rendertarget, format);
656 // let mut image_sk = Tex::new(TexType::Rendertarget, format, None);
657 let mut image_sk =
658 Tex::render_target(width as usize, height as usize, Some(1), Some(format), None).unwrap();
659
660 unsafe {
661 image_sk.set_native_surface(
662 img.texture,
663 TexType::Rendertarget,
664 XrCompLayers::to_native_format(format),
665 width as i32,
666 height as i32,
667 1,
668 true,
669 );
670 }
671 this.images.push(image_sk);
672 }
673 Some(this)
674 } else {
675 Log::warn("❌ SwapchainSk: D3D11 backend is not available");
676 None
677 }
678 }
679
680 /// Acquire the next image from the swapchain, waiting up to `timeout_ns` nanoseconds.
681 ///
682 /// This method must be called before rendering to the swapchain. It acquires an available
683 /// image from the swapchain and waits for it to be ready for rendering.
684 ///
685 /// # Parameters
686 /// - `timeout_ns`: Optional timeout in nanoseconds. If `None`, waits indefinitely.
687 ///
688 /// # Returns
689 /// - `Ok(image_index)`: The index of the acquired image on success.
690 /// - `Err(XrResult)`: OpenXR error code if acquisition fails.
691 ///
692 /// # Example
693 /// ```no_run
694 /// # use stereokit_rust::tools::xr_comp_layers::SwapchainSk;
695 /// # let mut swapchain: SwapchainSk = todo!();
696 /// // Acquire with default timeout
697 /// match swapchain.acquire_image(None) {
698 /// Ok(image_index) => {
699 /// println!("Acquired image {}", image_index);
700 /// // Render to swapchain.get_render_target()
701 /// // ... rendering code ...
702 /// swapchain.release_image().expect("Failed to release");
703 /// }
704 /// Err(e) => eprintln!("Failed to acquire image: {:?}", e),
705 /// }
706 /// ```
707 pub fn acquire_image(&mut self, timeout_ns: Option<i64>) -> std::result::Result<u32, XrResult> {
708 let timeout_ns = timeout_ns.unwrap_or(0x7fffffffffffffff);
709 let timeout = Duration::from_nanos(timeout_ns);
710 match unsafe {
711 self.xr_comp_layers.xr_acquire_swapchain_image.unwrap()(self.handle, null_mut(), &mut self.acquired)
712 } {
713 XrResult::SUCCESS => {}
714 otherwise => return Err(otherwise),
715 }
716
717 let swapchain_image_wait_info =
718 SwapchainImageWaitInfo { ty: StructureType::SWAPCHAIN_IMAGE_WAIT_INFO, next: null_mut(), timeout };
719
720 match unsafe { self.xr_comp_layers.xr_wait_swaptchain_image.unwrap()(self.handle, &swapchain_image_wait_info) }
721 {
722 XrResult::SUCCESS => Ok(self.acquired),
723 otherwise => Err(otherwise),
724 }
725 }
726
727 /// Release the currently held image back to the swapchain.
728 ///
729 /// This method must be called after finishing rendering to the acquired image.
730 /// It signals to the OpenXR runtime that rendering is complete and the image
731 /// can be used for composition.
732 ///
733 /// # Returns
734 /// - `Ok(())`: Successfully released the image.
735 /// - `Err(XrResult)`: OpenXR error code if release fails.
736 ///
737 /// # Example
738 /// ```no_run
739 /// # use stereokit_rust::tools::xr_comp_layers::SwapchainSk;
740 /// # let mut swapchain: SwapchainSk = todo!();
741 /// // After acquiring and rendering to the image
742 /// if let Ok(_) = swapchain.acquire_image(None) {
743 /// // ... render to swapchain.get_render_target() ...
744 ///
745 /// // Must release the image when done
746 /// swapchain.release_image().expect("Failed to release image");
747 /// }
748 /// ```
749 pub fn release_image(&mut self) -> std::result::Result<(), XrResult> {
750 match unsafe { self.xr_comp_layers.xr_release_swaptchain_image.unwrap()(self.handle, null_mut()) } {
751 XrResult::SUCCESS => Ok(()),
752 otherwise => Err(otherwise),
753 }
754 }
755
756 /// Destroy the swapchain and all associated resources.
757 pub fn destroy(&mut self) {
758 XrCompLayers::destroy_swapchain(self.handle);
759 self.handle = Swapchain::default();
760 }
761}
762
763/// Ensure an `XrCompLayers` instance is available, either using the provided one or creating a new one.
764pub fn get_xr_comp_layers(xr_comp_layers: Option<XrCompLayers>) -> Option<XrCompLayers> {
765 if let Some(comp_layers) = xr_comp_layers { Some(comp_layers) } else { XrCompLayers::new() }
766}