Skip to main content

blade_graphics/vulkan/
mod.rs

1use ash::{
2    khr,
3    vk::{self},
4};
5use openxr as xr;
6use std::{mem, num::NonZeroU32, path::PathBuf, ptr, sync::Mutex};
7
8mod command;
9mod descriptor;
10mod init;
11mod pipeline;
12mod resource;
13mod surface;
14
15/// Shared Vulkan instance state used by both `Context::init` and `Context::enumerate`.
16struct VulkanInstance {
17    pub entry: ash::Entry,
18    pub instance: Instance,
19    pub driver_api_version: u32,
20}
21
22const QUERY_POOL_SIZE: usize = crate::limits::PASS_COUNT + 1;
23const MAX_XR_EYES: usize = 2;
24
25struct Instance {
26    core: ash::Instance,
27    _debug_utils: ash::ext::debug_utils::Instance,
28    get_physical_device_properties2: khr::get_physical_device_properties2::Instance,
29    cooperative_matrix: khr::cooperative_matrix::Instance,
30    get_surface_capabilities2: Option<khr::get_surface_capabilities2::Instance>,
31    surface: Option<khr::surface::Instance>,
32}
33
34#[derive(Clone)]
35struct RayTracingDevice {
36    acceleration_structure: khr::acceleration_structure::Device,
37    scratch_buffer_alignment: u64,
38}
39
40#[derive(Clone, Default)]
41struct CommandScopeDevice {}
42#[derive(Clone, Default)]
43struct TimingDevice {
44    period: f32,
45}
46
47#[derive(Clone)]
48struct Workarounds {
49    extra_sync_src_access: vk::AccessFlags,
50    extra_sync_dst_access: vk::AccessFlags,
51    extra_descriptor_pool_create_flags: vk::DescriptorPoolCreateFlags,
52}
53
54#[derive(Clone)]
55struct Device {
56    core: ash::Device,
57    device_information: crate::DeviceInformation,
58    swapchain: Option<khr::swapchain::Device>,
59    debug_utils: ash::ext::debug_utils::Device,
60    timeline_semaphore: khr::timeline_semaphore::Device,
61    dynamic_rendering: khr::dynamic_rendering::Device,
62    ray_tracing: Option<RayTracingDevice>,
63    buffer_device_address: bool,
64    max_inline_uniform_block_size: u32,
65    buffer_marker: Option<ash::amd::buffer_marker::Device>,
66    shader_info: Option<ash::amd::shader_info::Device>,
67    full_screen_exclusive: Option<ash::ext::full_screen_exclusive::Device>,
68    #[cfg(target_os = "windows")]
69    external_memory: Option<ash::khr::external_memory_win32::Device>,
70    #[cfg(not(target_os = "windows"))]
71    external_memory: Option<ash::khr::external_memory_fd::Device>,
72    command_scope: Option<CommandScopeDevice>,
73    timing: Option<TimingDevice>,
74    workarounds: Workarounds,
75}
76
77struct MemoryManager {
78    allocator: gpu_alloc::GpuAllocator<vk::DeviceMemory>,
79    slab: slab::Slab<gpu_alloc::MemoryBlock<vk::DeviceMemory>>,
80    valid_ash_memory_types: u32,
81}
82
83struct Queue {
84    raw: vk::Queue,
85    timeline_semaphore: vk::Semaphore,
86    last_progress: u64,
87}
88
89#[derive(Clone, Copy, Debug, Default, PartialEq)]
90struct InternalFrame {
91    acquire_semaphore: vk::Semaphore,
92    present_semaphore: vk::Semaphore,
93    image: vk::Image,
94    view: vk::ImageView,
95    xr_views: [vk::ImageView; MAX_XR_EYES],
96}
97
98#[derive(Clone, Copy, Debug, PartialEq)]
99struct Swapchain {
100    raw: vk::SwapchainKHR,
101    format: crate::TextureFormat,
102    alpha: crate::AlphaMode,
103    target_size: [u16; 2],
104}
105
106pub struct Surface {
107    device: khr::swapchain::Device,
108    raw: vk::SurfaceKHR,
109    frames: Vec<InternalFrame>,
110    next_semaphore: vk::Semaphore,
111    swapchain: Swapchain,
112    full_screen_exclusive: bool,
113}
114
115pub struct XrSurface {
116    raw: openxr::Swapchain<openxr::Vulkan>,
117    frames: Vec<InternalFrame>,
118    swapchain: Swapchain,
119    view_count: u32,
120}
121
122pub struct XrSessionState {
123    pub instance: xr::Instance,
124    pub system_id: xr::SystemId,
125    pub session: xr::Session<xr::Vulkan>,
126    pub frame_wait: xr::FrameWaiter,
127    pub frame_stream: xr::FrameStream<xr::Vulkan>,
128    pub view_type: xr::ViewConfigurationType,
129    pub environment_blend_mode: xr::EnvironmentBlendMode,
130    pub space: Option<xr::Space>,
131    pub predicted_display_time: Option<xr::Time>,
132}
133
134#[derive(Clone, Copy, Debug)]
135enum Presentation {
136    Window {
137        swapchain: vk::SwapchainKHR,
138        image_index: u32,
139        acquire_semaphore: vk::Semaphore,
140        present_semaphore: vk::Semaphore,
141    },
142    Xr {
143        swapchain: usize,
144        view_count: u32,
145        target_size: [u16; 2],
146        views: [XrView; MAX_XR_EYES],
147    },
148}
149
150#[derive(Clone, Copy, Debug, Default)]
151pub struct XrPose {
152    pub orientation: [f32; 4],
153    pub position: [f32; 3],
154}
155
156#[derive(Clone, Copy, Debug, Default)]
157pub struct XrFov {
158    pub angle_left: f32,
159    pub angle_right: f32,
160    pub angle_up: f32,
161    pub angle_down: f32,
162}
163
164#[derive(Clone, Copy, Debug, Default)]
165pub struct XrView {
166    pub pose: XrPose,
167    pub fov: XrFov,
168}
169
170#[derive(Clone, Copy, Debug)]
171pub struct Frame {
172    swapchain: Swapchain,
173    image_index: Option<u32>,
174    internal: InternalFrame,
175    xr_swapchain: usize,
176    xr_view_count: u32,
177    xr_views: [XrView; MAX_XR_EYES],
178}
179
180impl Frame {
181    pub fn texture(&self) -> Texture {
182        Texture {
183            raw: self.internal.image,
184            memory_handle: !0,
185            target_size: self.swapchain.target_size,
186            format: self.swapchain.format,
187            external: None,
188        }
189    }
190
191    pub fn texture_view(&self) -> TextureView {
192        TextureView {
193            raw: self.internal.view,
194            target_size: self.swapchain.target_size,
195            aspects: crate::TexelAspects::COLOR,
196        }
197    }
198
199    pub fn xr_texture_view(&self, eye: u32) -> TextureView {
200        let eye = eye as usize;
201        assert!(eye < MAX_XR_EYES, "XR eye {} is out of range", eye);
202        let raw = self.internal.xr_views[eye];
203        assert_ne!(
204            raw,
205            vk::ImageView::null(),
206            "XR eye {} view is not initialized",
207            eye
208        );
209        TextureView {
210            raw,
211            target_size: self.swapchain.target_size,
212            aspects: crate::TexelAspects::COLOR,
213        }
214    }
215
216    pub fn xr_view_count(&self) -> u32 {
217        self.xr_view_count
218    }
219
220    pub fn xr_view(&self, eye: u32) -> XrView {
221        let eye = eye as usize;
222        assert!(
223            eye < self.xr_view_count as usize,
224            "XR eye {} is out of range",
225            eye
226        );
227        self.xr_views[eye]
228    }
229}
230
231impl Context {
232    pub fn xr_session(&self) -> Option<xr::Session<xr::Vulkan>> {
233        self.xr
234            .as_ref()
235            .map(|xr| xr.lock().unwrap().session.clone())
236    }
237
238    /// Locate an action space (e.g. controller aim) relative to the XR reference space
239    /// at the last predicted display time.
240    pub fn xr_locate_space(&self, action_space: &xr::Space) -> Option<xr::Posef> {
241        let xr = self.xr.as_ref()?.lock().unwrap();
242        let time = xr.predicted_display_time?;
243        let ref_space = xr.space.as_ref()?;
244        let location = action_space.locate(ref_space, time).ok()?;
245        let flags = location.location_flags;
246        if flags.contains(
247            xr::SpaceLocationFlags::POSITION_VALID | xr::SpaceLocationFlags::ORIENTATION_VALID,
248        ) {
249            Some(location.pose)
250        } else {
251            None
252        }
253    }
254}
255
256fn map_timeout(millis: u32) -> u64 {
257    if millis == !0 {
258        !0
259    } else {
260        millis as u64 * 1_000_000
261    }
262}
263
264pub struct Context {
265    memory: Mutex<MemoryManager>,
266    device: Device,
267    queue_family_index: u32,
268    queue: Mutex<Queue>,
269    physical_device: vk::PhysicalDevice,
270    naga_flags: naga::back::spv::WriterFlags,
271    shader_debug_path: Option<PathBuf>,
272    min_buffer_alignment: u64,
273    min_uniform_buffer_offset_alignment: u64,
274    sample_count_flags: vk::SampleCountFlags,
275    dual_source_blending: bool,
276    shader_float16: bool,
277    cooperative_matrix: crate::CooperativeMatrix,
278    binding_array: bool,
279    memory_budget: bool,
280    inner: VulkanInstance,
281    xr: Option<Mutex<XrSessionState>>,
282}
283
284#[derive(Clone, Copy, Debug, Hash, PartialEq)]
285pub struct Buffer {
286    raw: vk::Buffer,
287    memory_handle: usize,
288    mapped_data: *mut u8,
289    size: u64,
290    external: Option<crate::ExternalMemorySource>,
291}
292
293impl Default for Buffer {
294    fn default() -> Self {
295        Self {
296            raw: vk::Buffer::null(),
297            memory_handle: !0,
298            mapped_data: ptr::null_mut(),
299            size: 0,
300            external: None,
301        }
302    }
303}
304
305impl Buffer {
306    pub fn data(&self) -> *mut u8 {
307        self.mapped_data
308    }
309
310    pub fn size(&self) -> u64 {
311        self.size
312    }
313}
314
315unsafe impl Send for Buffer {}
316unsafe impl Sync for Buffer {}
317
318#[derive(Clone, Copy, Debug, Hash, PartialEq)]
319pub struct Texture {
320    raw: vk::Image,
321    memory_handle: usize,
322    target_size: [u16; 2],
323    format: crate::TextureFormat,
324    external: Option<crate::ExternalMemorySource>,
325}
326
327impl Default for Texture {
328    fn default() -> Self {
329        Self {
330            raw: vk::Image::default(),
331            memory_handle: !0,
332            target_size: [0; 2],
333            format: crate::TextureFormat::Rgba8Unorm,
334            external: None,
335        }
336    }
337}
338
339#[derive(Clone, Copy, Debug, Default, Hash, PartialEq)]
340pub struct TextureView {
341    raw: vk::ImageView,
342    target_size: [u16; 2],
343    aspects: crate::TexelAspects,
344}
345
346#[derive(Clone, Copy, Debug, Hash, PartialEq)]
347pub struct Sampler {
348    raw: vk::Sampler,
349}
350
351#[derive(Clone, Copy, Debug, Default, Hash, PartialEq)]
352pub struct AccelerationStructure {
353    raw: vk::AccelerationStructureKHR,
354    buffer: vk::Buffer,
355    memory_handle: usize,
356}
357
358#[derive(Debug, Default)]
359struct DescriptorSetLayout {
360    raw: vk::DescriptorSetLayout,
361    update_template: vk::DescriptorUpdateTemplate,
362    template_size: u32,
363    template_offsets: Box<[u32]>,
364    /// Bitmask: bit N is set if binding N uses inline uniform blocks.
365    /// Clear bits use uniform buffer objects via the scratch buffer.
366    inline_uniform_mask: u64,
367}
368
369impl DescriptorSetLayout {
370    fn is_empty(&self) -> bool {
371        self.template_size == 0
372    }
373}
374
375#[derive(Debug)]
376struct PipelineLayout {
377    raw: vk::PipelineLayout,
378    descriptor_set_layouts: Vec<DescriptorSetLayout>,
379}
380
381#[derive(Debug)]
382struct ScratchBuffer {
383    raw: vk::Buffer,
384    memory_handle: usize,
385    mapped: *mut u8,
386    capacity: u64,
387    offset: u64,
388    alignment: u64,
389}
390
391pub struct PipelineContext<'a> {
392    update_data: &'a mut [u8],
393    template_offsets: &'a [u32],
394    scratch: Option<&'a mut ScratchBuffer>,
395    /// Bitmask: bit N is set if binding N uses inline uniform blocks.
396    inline_uniform_mask: u64,
397}
398
399#[derive(Debug)]
400pub struct ComputePipeline {
401    raw: vk::Pipeline,
402    layout: PipelineLayout,
403    wg_size: [u32; 3],
404}
405
406#[hidden_trait::expose]
407impl crate::traits::ComputePipelineBase for ComputePipeline {
408    fn get_workgroup_size(&self) -> [u32; 3] {
409        self.wg_size
410    }
411}
412
413#[derive(Debug)]
414pub struct RenderPipeline {
415    raw: vk::Pipeline,
416    layout: PipelineLayout,
417}
418
419#[derive(Debug)]
420struct CommandBuffer {
421    raw: vk::CommandBuffer,
422    descriptor_pool: descriptor::DescriptorPool,
423    query_pool: vk::QueryPool,
424    timed_pass_names: Vec<String>,
425    scratch: Option<ScratchBuffer>,
426}
427
428struct CrashHandler {
429    name: String,
430    marker_buf: Buffer,
431    raw_string: Box<[u8]>,
432    next_offset: usize,
433}
434
435pub struct CommandEncoder {
436    pool: vk::CommandPool,
437    buffers: Box<[CommandBuffer]>,
438    device: Device,
439    update_data: Vec<u8>,
440    present: Option<Presentation>,
441    crash_handler: Option<CrashHandler>,
442    temp_label: Vec<u8>,
443    timings: crate::Timings,
444}
445pub struct TransferCommandEncoder<'a> {
446    raw: vk::CommandBuffer,
447    device: &'a Device,
448}
449pub struct AccelerationStructureCommandEncoder<'a> {
450    raw: vk::CommandBuffer,
451    device: &'a Device,
452}
453pub struct ComputeCommandEncoder<'a> {
454    cmd_buf: &'a mut CommandBuffer,
455    device: &'a Device,
456    update_data: &'a mut Vec<u8>,
457}
458//Note: we aren't merging this with `ComputeCommandEncoder`
459// because the destructors are different, and they can't be specialized
460// https://github.com/rust-lang/rust/issues/46893
461pub struct RenderCommandEncoder<'a> {
462    cmd_buf: &'a mut CommandBuffer,
463    device: &'a Device,
464    update_data: &'a mut Vec<u8>,
465}
466
467pub struct PipelineEncoder<'a, 'p> {
468    cmd_buf: &'a mut CommandBuffer,
469    layout: &'p PipelineLayout,
470    bind_point: vk::PipelineBindPoint,
471    device: &'a Device,
472    update_data: &'a mut Vec<u8>,
473}
474
475#[derive(Clone, Debug)]
476pub struct SyncPoint {
477    progress: u64,
478}
479
480#[hidden_trait::expose]
481impl crate::traits::CommandDevice for Context {
482    type CommandEncoder = CommandEncoder;
483    type SyncPoint = SyncPoint;
484
485    fn create_command_encoder(&self, desc: super::CommandEncoderDesc) -> CommandEncoder {
486        let pool_info = vk::CommandPoolCreateInfo {
487            flags: vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER,
488            ..Default::default()
489        };
490        let pool = unsafe {
491            self.device
492                .core
493                .create_command_pool(&pool_info, None)
494                .unwrap()
495        };
496        let cmd_buf_info = vk::CommandBufferAllocateInfo {
497            command_pool: pool,
498            command_buffer_count: desc.buffer_count,
499            ..Default::default()
500        };
501        let cmd_buffers = unsafe {
502            self.device
503                .core
504                .allocate_command_buffers(&cmd_buf_info)
505                .unwrap()
506        };
507
508        let buffers = cmd_buffers
509            .into_iter()
510            .map(|raw| {
511                if !desc.name.is_empty() {
512                    self.set_object_name(raw, desc.name);
513                };
514                let descriptor_pool = self.device.create_descriptor_pool();
515                let query_pool = if self.device.timing.is_some() {
516                    let query_pool_info = vk::QueryPoolCreateInfo::default()
517                        .query_type(vk::QueryType::TIMESTAMP)
518                        .query_count(QUERY_POOL_SIZE as u32);
519                    unsafe {
520                        self.device
521                            .core
522                            .create_query_pool(&query_pool_info, None)
523                            .unwrap()
524                    }
525                } else {
526                    vk::QueryPool::null()
527                };
528                // Always create a scratch buffer for UBO bindings.
529                // Even when inline uniform blocks are supported, individual
530                // bindings that exceed the device limit fall back to UBOs.
531                const SCRATCH_SIZE: u64 = 1 << 20; // 1 MiB
532                let scratch_buf = self.create_buffer(crate::BufferDesc {
533                    name: "_scratch",
534                    size: SCRATCH_SIZE,
535                    memory: crate::Memory::Shared,
536                });
537                let scratch = Some(ScratchBuffer {
538                    raw: scratch_buf.raw,
539                    memory_handle: scratch_buf.memory_handle,
540                    mapped: scratch_buf.mapped_data,
541                    capacity: SCRATCH_SIZE,
542                    offset: 0,
543                    alignment: self.min_uniform_buffer_offset_alignment,
544                });
545                CommandBuffer {
546                    raw,
547                    descriptor_pool,
548                    query_pool,
549                    timed_pass_names: Vec::new(),
550                    scratch,
551                }
552            })
553            .collect();
554
555        let crash_handler = if self.device.buffer_marker.is_some() {
556            Some(CrashHandler {
557                name: desc.name.to_string(),
558                marker_buf: self.create_buffer(crate::BufferDesc {
559                    name: "_marker",
560                    size: 4,
561                    memory: crate::Memory::Shared,
562                }),
563                raw_string: vec![0; 0x1000].into_boxed_slice(),
564                next_offset: 0,
565            })
566        } else {
567            None
568        };
569
570        CommandEncoder {
571            pool,
572            buffers,
573            device: self.device.clone(),
574            update_data: Vec::new(),
575            present: None,
576            crash_handler,
577            temp_label: Vec::new(),
578            timings: Default::default(),
579        }
580    }
581
582    fn destroy_command_encoder(&self, command_encoder: &mut CommandEncoder) {
583        for cmd_buf in command_encoder.buffers.iter_mut() {
584            let raw_cmd_buffers = [cmd_buf.raw];
585            unsafe {
586                self.device
587                    .core
588                    .free_command_buffers(command_encoder.pool, &raw_cmd_buffers);
589            }
590            self.device
591                .destroy_descriptor_pool(&mut cmd_buf.descriptor_pool);
592            if self.device.timing.is_some() {
593                unsafe {
594                    self.device
595                        .core
596                        .destroy_query_pool(cmd_buf.query_pool, None);
597                }
598            }
599            if let Some(ref scratch) = cmd_buf.scratch {
600                self.destroy_buffer(super::Buffer {
601                    raw: scratch.raw,
602                    memory_handle: scratch.memory_handle,
603                    mapped_data: scratch.mapped,
604                    size: 0,
605                    external: None,
606                });
607            }
608        }
609        unsafe {
610            self.device
611                .core
612                .destroy_command_pool(mem::take(&mut command_encoder.pool), None)
613        };
614        if let Some(crash_handler) = command_encoder.crash_handler.take() {
615            self.destroy_buffer(crash_handler.marker_buf);
616        };
617    }
618
619    fn submit(&self, encoder: &mut CommandEncoder) -> SyncPoint {
620        let raw_cmd_buf = encoder.finish();
621        let mut queue = self.queue.lock().unwrap();
622        queue.last_progress += 1;
623        let progress = queue.last_progress;
624        let command_buffers = [raw_cmd_buf];
625        let wait_values_all = [0];
626        let mut wait_semaphores_all = [vk::Semaphore::null()];
627        let wait_stages = [vk::PipelineStageFlags::ALL_COMMANDS];
628        let mut signal_semaphores_all = [queue.timeline_semaphore, vk::Semaphore::null()];
629        let signal_values_all = [progress, 0];
630        let (num_wait_semaphores, num_signal_sepahores) = match encoder.present {
631            Some(Presentation::Window {
632                acquire_semaphore,
633                present_semaphore,
634                ..
635            }) => {
636                wait_semaphores_all[0] = acquire_semaphore;
637                signal_semaphores_all[1] = present_semaphore;
638                (1, 2)
639            }
640            Some(Presentation::Xr { .. }) | None => (0, 1),
641        };
642        let mut timeline_info = vk::TimelineSemaphoreSubmitInfo::default()
643            .wait_semaphore_values(&wait_values_all[..num_wait_semaphores])
644            .signal_semaphore_values(&signal_values_all[..num_signal_sepahores]);
645        let vk_info = vk::SubmitInfo::default()
646            .command_buffers(&command_buffers)
647            .wait_semaphores(&wait_semaphores_all[..num_wait_semaphores])
648            .wait_dst_stage_mask(&wait_stages[..num_wait_semaphores])
649            .signal_semaphores(&signal_semaphores_all[..num_signal_sepahores])
650            .push_next(&mut timeline_info);
651        let ret = unsafe {
652            self.device
653                .core
654                .queue_submit(queue.raw, &[vk_info], vk::Fence::null())
655        };
656        encoder.check_gpu_crash(ret);
657
658        if let Some(presentation) = encoder.present.take() {
659            match presentation {
660                Presentation::Window {
661                    swapchain,
662                    image_index,
663                    present_semaphore,
664                    ..
665                } => {
666                    let khr_swapchain = self.device.swapchain.as_ref().unwrap();
667                    let swapchains = [swapchain];
668                    let image_indices = [image_index];
669                    let wait_semaphores = [present_semaphore];
670                    let present_info = vk::PresentInfoKHR::default()
671                        .swapchains(&swapchains)
672                        .image_indices(&image_indices)
673                        .wait_semaphores(&wait_semaphores);
674                    let ret = unsafe { khr_swapchain.queue_present(queue.raw, &present_info) };
675                    let _ = encoder.check_gpu_crash(ret);
676                }
677                Presentation::Xr {
678                    swapchain,
679                    view_count,
680                    target_size,
681                    views,
682                } => {
683                    let semaphores = [queue.timeline_semaphore];
684                    let semaphore_values = [progress];
685                    let wait_info = vk::SemaphoreWaitInfoKHR::default()
686                        .semaphores(&semaphores)
687                        .values(&semaphore_values);
688                    unsafe {
689                        self.device
690                            .timeline_semaphore
691                            .wait_semaphores(&wait_info, !0)
692                            .unwrap();
693                    }
694                    let swapchain = unsafe { &mut *(swapchain as *mut xr::Swapchain<xr::Vulkan>) };
695                    swapchain.release_image().unwrap();
696
697                    let xr_state = self.xr.as_ref().expect("XR is not enabled in this context");
698                    let mut xr_state = xr_state.lock().unwrap();
699                    let environment_blend_mode = xr_state.environment_blend_mode;
700                    let space = xr_state.space.take().expect("XR space is not initialized");
701                    let predicted_display_time = xr_state
702                        .predicted_display_time
703                        .expect("XR frame timing is not initialized");
704                    let rect = xr::Rect2Di {
705                        offset: xr::Offset2Di { x: 0, y: 0 },
706                        extent: xr::Extent2Di {
707                            width: target_size[0] as _,
708                            height: target_size[1] as _,
709                        },
710                    };
711                    let projection_views = views[..view_count as usize]
712                        .iter()
713                        .enumerate()
714                        .map(|(i, view)| {
715                            xr::CompositionLayerProjectionView::new()
716                                .pose(xr::Posef {
717                                    orientation: xr::Quaternionf {
718                                        x: view.pose.orientation[0],
719                                        y: view.pose.orientation[1],
720                                        z: view.pose.orientation[2],
721                                        w: view.pose.orientation[3],
722                                    },
723                                    position: xr::Vector3f {
724                                        x: view.pose.position[0],
725                                        y: view.pose.position[1],
726                                        z: view.pose.position[2],
727                                    },
728                                })
729                                .fov(xr::Fovf {
730                                    angle_left: view.fov.angle_left,
731                                    angle_right: view.fov.angle_right,
732                                    angle_up: view.fov.angle_up,
733                                    angle_down: view.fov.angle_down,
734                                })
735                                .sub_image(
736                                    xr::SwapchainSubImage::new()
737                                        .swapchain(swapchain)
738                                        .image_array_index(i as u32)
739                                        .image_rect(rect),
740                                )
741                        })
742                        .collect::<Vec<_>>();
743                    match xr_state.frame_stream.end(
744                        predicted_display_time,
745                        environment_blend_mode,
746                        &[&xr::CompositionLayerProjection::new()
747                            .space(&space)
748                            .views(&projection_views)],
749                    ) {
750                        Ok(()) => {}
751                        Err(xr::sys::Result::ERROR_POSE_INVALID) => {
752                            // Tracking was lost between frame acquire and
753                            // present — transient, safe to ignore.
754                            log::warn!("XR frame end: pose invalid (tracking lost?)");
755                        }
756                        Err(e) => panic!("XR frame end failed: {e}"),
757                    }
758                    xr_state.space = Some(space);
759                }
760            }
761        }
762
763        SyncPoint { progress }
764    }
765
766    fn wait_for(&self, sp: &SyncPoint, timeout_ms: u32) -> Result<bool, crate::DeviceError> {
767        //Note: technically we could get away without locking the queue,
768        // but also this isn't time-sensitive, so it's fine.
769        let timeline_semaphore = self.queue.lock().unwrap().timeline_semaphore;
770        let semaphores = [timeline_semaphore];
771        let semaphore_values = [sp.progress];
772        let wait_info = vk::SemaphoreWaitInfoKHR::default()
773            .semaphores(&semaphores)
774            .values(&semaphore_values);
775        let timeout_ns = map_timeout(timeout_ms);
776        match unsafe {
777            self.device
778                .timeline_semaphore
779                .wait_semaphores(&wait_info, timeout_ns)
780        } {
781            Ok(()) => Ok(true),
782            Err(vk::Result::TIMEOUT) => Ok(false),
783            Err(vk::Result::ERROR_DEVICE_LOST) => Err(crate::DeviceError::DeviceLost),
784            Err(vk::Result::ERROR_OUT_OF_DEVICE_MEMORY)
785            | Err(vk::Result::ERROR_OUT_OF_HOST_MEMORY) => Err(crate::DeviceError::OutOfMemory),
786            Err(other) => {
787                log::error!("Unexpected wait_semaphores error: {:?}", other);
788                Err(crate::DeviceError::DeviceLost)
789            }
790        }
791    }
792}
793
794fn map_texture_format(format: crate::TextureFormat) -> vk::Format {
795    use crate::TextureFormat as Tf;
796    match format {
797        Tf::R8Unorm => vk::Format::R8_UNORM,
798        Tf::Rg8Unorm => vk::Format::R8G8_UNORM,
799        Tf::Rg8Snorm => vk::Format::R8G8_SNORM,
800        Tf::Rgba8Unorm => vk::Format::R8G8B8A8_UNORM,
801        Tf::Rgba8UnormSrgb => vk::Format::R8G8B8A8_SRGB,
802        Tf::Bgra8Unorm => vk::Format::B8G8R8A8_UNORM,
803        Tf::Bgra8UnormSrgb => vk::Format::B8G8R8A8_SRGB,
804        Tf::Rgba8Snorm => vk::Format::R8G8B8A8_SNORM,
805        Tf::R16Float => vk::Format::R16_SFLOAT,
806        Tf::Rg16Float => vk::Format::R16G16_SFLOAT,
807        Tf::Rgba16Float => vk::Format::R16G16B16A16_SFLOAT,
808        Tf::R32Float => vk::Format::R32_SFLOAT,
809        Tf::Rg32Float => vk::Format::R32G32_SFLOAT,
810        Tf::Rgba32Float => vk::Format::R32G32B32A32_SFLOAT,
811        Tf::R32Uint => vk::Format::R32_UINT,
812        Tf::Rg32Uint => vk::Format::R32G32_UINT,
813        Tf::Rgba32Uint => vk::Format::R32G32B32A32_UINT,
814        Tf::Depth32Float => vk::Format::D32_SFLOAT,
815        Tf::Depth32FloatStencil8Uint => vk::Format::D32_SFLOAT_S8_UINT,
816        Tf::Stencil8Uint => vk::Format::S8_UINT,
817        Tf::Bc1Unorm => vk::Format::BC1_RGBA_SRGB_BLOCK,
818        Tf::Bc1UnormSrgb => vk::Format::BC1_RGBA_UNORM_BLOCK,
819        Tf::Bc2Unorm => vk::Format::BC2_UNORM_BLOCK,
820        Tf::Bc2UnormSrgb => vk::Format::BC2_SRGB_BLOCK,
821        Tf::Bc3Unorm => vk::Format::BC3_UNORM_BLOCK,
822        Tf::Bc3UnormSrgb => vk::Format::BC3_SRGB_BLOCK,
823        Tf::Bc4Unorm => vk::Format::BC4_UNORM_BLOCK,
824        Tf::Bc4Snorm => vk::Format::BC4_SNORM_BLOCK,
825        Tf::Bc5Unorm => vk::Format::BC5_UNORM_BLOCK,
826        Tf::Bc5Snorm => vk::Format::BC5_SNORM_BLOCK,
827        Tf::Bc6hUfloat => vk::Format::BC6H_UFLOAT_BLOCK,
828        Tf::Bc6hFloat => vk::Format::BC6H_SFLOAT_BLOCK,
829        Tf::Bc7Unorm => vk::Format::BC7_UNORM_BLOCK,
830        Tf::Bc7UnormSrgb => vk::Format::BC7_SRGB_BLOCK,
831        Tf::Rgb10a2Unorm => vk::Format::A2B10G10R10_UNORM_PACK32,
832        Tf::Rg11b10Ufloat => vk::Format::B10G11R11_UFLOAT_PACK32,
833        Tf::Rgb9e5Ufloat => vk::Format::E5B9G9R9_UFLOAT_PACK32,
834    }
835}
836
837fn map_aspects(aspects: crate::TexelAspects) -> vk::ImageAspectFlags {
838    let mut flags = vk::ImageAspectFlags::empty();
839    if aspects.contains(crate::TexelAspects::COLOR) {
840        flags |= vk::ImageAspectFlags::COLOR;
841    }
842    if aspects.contains(crate::TexelAspects::DEPTH) {
843        flags |= vk::ImageAspectFlags::DEPTH;
844    }
845    if aspects.contains(crate::TexelAspects::STENCIL) {
846        flags |= vk::ImageAspectFlags::STENCIL;
847    }
848    flags
849}
850
851fn map_extent_3d(extent: &crate::Extent) -> vk::Extent3D {
852    vk::Extent3D {
853        width: extent.width,
854        height: extent.height,
855        depth: extent.depth,
856    }
857}
858
859fn map_subresource_range(
860    subresources: &crate::TextureSubresources,
861    aspects: crate::TexelAspects,
862) -> vk::ImageSubresourceRange {
863    vk::ImageSubresourceRange {
864        aspect_mask: map_aspects(aspects),
865        base_mip_level: subresources.base_mip_level,
866        level_count: subresources
867            .mip_level_count
868            .map_or(vk::REMAINING_MIP_LEVELS, NonZeroU32::get),
869        base_array_layer: subresources.base_array_layer,
870        layer_count: subresources
871            .array_layer_count
872            .map_or(vk::REMAINING_ARRAY_LAYERS, NonZeroU32::get),
873    }
874}
875
876fn map_comparison(fun: crate::CompareFunction) -> vk::CompareOp {
877    use crate::CompareFunction as Cf;
878    match fun {
879        Cf::Never => vk::CompareOp::NEVER,
880        Cf::Less => vk::CompareOp::LESS,
881        Cf::LessEqual => vk::CompareOp::LESS_OR_EQUAL,
882        Cf::Equal => vk::CompareOp::EQUAL,
883        Cf::GreaterEqual => vk::CompareOp::GREATER_OR_EQUAL,
884        Cf::Greater => vk::CompareOp::GREATER,
885        Cf::NotEqual => vk::CompareOp::NOT_EQUAL,
886        Cf::Always => vk::CompareOp::ALWAYS,
887    }
888}
889
890fn map_index_type(index_type: crate::IndexType) -> vk::IndexType {
891    match index_type {
892        crate::IndexType::U16 => vk::IndexType::UINT16,
893        crate::IndexType::U32 => vk::IndexType::UINT32,
894    }
895}
896
897fn map_vertex_format(vertex_format: crate::VertexFormat) -> vk::Format {
898    use crate::VertexFormat as Vf;
899    match vertex_format {
900        Vf::F32 => vk::Format::R32_SFLOAT,
901        Vf::F32Vec2 => vk::Format::R32G32_SFLOAT,
902        Vf::F32Vec3 => vk::Format::R32G32B32_SFLOAT,
903        Vf::F32Vec4 => vk::Format::R32G32B32A32_SFLOAT,
904        Vf::U32 => vk::Format::R32_UINT,
905        Vf::U32Vec2 => vk::Format::R32G32_UINT,
906        Vf::U32Vec3 => vk::Format::R32G32B32_UINT,
907        Vf::U32Vec4 => vk::Format::R32G32B32A32_UINT,
908        Vf::I32 => vk::Format::R32_SINT,
909        Vf::I32Vec2 => vk::Format::R32G32_SINT,
910        Vf::I32Vec3 => vk::Format::R32G32B32_SINT,
911        Vf::I32Vec4 => vk::Format::R32G32B32A32_SINT,
912    }
913}
914
915struct BottomLevelAccelerationStructureInput<'a> {
916    max_primitive_counts: Box<[u32]>,
917    build_range_infos: Box<[vk::AccelerationStructureBuildRangeInfoKHR]>,
918    _geometries: Box<[vk::AccelerationStructureGeometryKHR<'a>]>,
919    build_info: vk::AccelerationStructureBuildGeometryInfoKHR<'a>,
920}
921
922impl Device {
923    fn get_device_address(&self, piece: &crate::BufferPiece) -> u64 {
924        let vk_info = vk::BufferDeviceAddressInfo {
925            buffer: piece.buffer.raw,
926            ..Default::default()
927        };
928        let base = unsafe { self.core.get_buffer_device_address(&vk_info) };
929        base + piece.offset
930    }
931
932    fn map_acceleration_structure_meshes(
933        &self,
934        meshes: &[crate::AccelerationStructureMesh],
935    ) -> BottomLevelAccelerationStructureInput<'_> {
936        let mut total_primitive_count = 0;
937        let mut max_primitive_counts = Vec::with_capacity(meshes.len());
938        let mut build_range_infos = Vec::with_capacity(meshes.len());
939        let mut geometries = Vec::with_capacity(meshes.len());
940        for mesh in meshes {
941            total_primitive_count += mesh.triangle_count;
942            max_primitive_counts.push(mesh.triangle_count);
943            build_range_infos.push(vk::AccelerationStructureBuildRangeInfoKHR {
944                primitive_count: mesh.triangle_count,
945                primitive_offset: 0,
946                first_vertex: 0,
947                transform_offset: 0,
948            });
949
950            let mut triangles = vk::AccelerationStructureGeometryTrianglesDataKHR {
951                vertex_format: map_vertex_format(mesh.vertex_format),
952                vertex_data: {
953                    let device_address = self.get_device_address(&mesh.vertex_data);
954                    assert!(
955                        device_address & 0x3 == 0,
956                        "Vertex data address {device_address} is not aligned"
957                    );
958                    vk::DeviceOrHostAddressConstKHR { device_address }
959                },
960                vertex_stride: mesh.vertex_stride as u64,
961                max_vertex: mesh.vertex_count.saturating_sub(1),
962                ..Default::default()
963            };
964            if let Some(index_type) = mesh.index_type {
965                let device_address = self.get_device_address(&mesh.index_data);
966                assert!(
967                    device_address & 0x3 == 0,
968                    "Index data address {device_address} is not aligned"
969                );
970                triangles.index_type = map_index_type(index_type);
971                triangles.index_data = vk::DeviceOrHostAddressConstKHR { device_address };
972            }
973            if mesh.transform_data.buffer.raw != vk::Buffer::null() {
974                let device_address = self.get_device_address(&mesh.transform_data);
975                assert!(
976                    device_address & 0xF == 0,
977                    "Transform data address {device_address} is not aligned"
978                );
979                triangles.transform_data = vk::DeviceOrHostAddressConstKHR { device_address };
980            }
981
982            let geometry = vk::AccelerationStructureGeometryKHR {
983                geometry_type: vk::GeometryTypeKHR::TRIANGLES,
984                geometry: vk::AccelerationStructureGeometryDataKHR { triangles },
985                flags: if mesh.is_opaque {
986                    vk::GeometryFlagsKHR::OPAQUE
987                } else {
988                    vk::GeometryFlagsKHR::empty()
989                },
990                ..Default::default()
991            };
992            geometries.push(geometry);
993        }
994        let build_info = vk::AccelerationStructureBuildGeometryInfoKHR {
995            ty: vk::AccelerationStructureTypeKHR::BOTTOM_LEVEL,
996            flags: vk::BuildAccelerationStructureFlagsKHR::PREFER_FAST_TRACE,
997            mode: vk::BuildAccelerationStructureModeKHR::BUILD,
998            geometry_count: geometries.len() as u32,
999            p_geometries: geometries.as_ptr(),
1000            ..Default::default()
1001        };
1002
1003        log::debug!(
1004            "BLAS total {} primitives in {} geometries",
1005            total_primitive_count,
1006            geometries.len()
1007        );
1008        BottomLevelAccelerationStructureInput {
1009            max_primitive_counts: max_primitive_counts.into_boxed_slice(),
1010            build_range_infos: build_range_infos.into_boxed_slice(),
1011            _geometries: geometries.into_boxed_slice(),
1012            build_info,
1013        }
1014    }
1015}