Skip to main content

lgpu_hal/vulkan/
swapchain.rs

1//! Vulkan swapchain presentation and surface integration.
2
3use super::{VulkanCommandRecorder, VulkanDevice, VulkanTexture};
4use crate::HalError;
5use ash::khr::surface;
6use ash::vk;
7
8pub struct VulkanSwapchain {
9    pub swapchain: vk::SwapchainKHR,
10    pub images: Vec<vk::Image>,
11    pub views: Vec<vk::ImageView>,
12    pub width: u32,
13    pub height: u32,
14    pub format: vk::Format,
15}
16
17impl VulkanDevice {
18    pub fn create_swapchain(
19        &self,
20        surface: vk::SurfaceKHR,
21        surface_loader: &surface::Instance,
22        width: u32,
23        height: u32,
24        old_swapchain: vk::SwapchainKHR,
25        preferred_format: Option<vk::Format>,
26        preferred_present_mode: Option<vk::PresentModeKHR>,
27    ) -> Result<VulkanSwapchain, HalError> {
28        let loader = self.swapchain_loader.as_ref().ok_or_else(|| {
29            HalError::BackendError("VK_KHR_swapchain is not supported or not enabled".to_string())
30        })?;
31
32        let caps = unsafe {
33            surface_loader
34                .get_physical_device_surface_capabilities(self.physical_device, surface)
35                .map_err(|e| {
36                    HalError::BackendError(format!("Failed to query surface capabilities: {e}"))
37                })?
38        };
39
40        let formats = unsafe {
41            surface_loader
42                .get_physical_device_surface_formats(self.physical_device, surface)
43                .map_err(|e| {
44                    HalError::BackendError(format!("Failed to query surface formats: {e}"))
45                })?
46        };
47
48        let present_modes = unsafe {
49            surface_loader
50                .get_physical_device_surface_present_modes(self.physical_device, surface)
51                .map_err(|e| {
52                    HalError::BackendError(format!("Failed to query surface present modes: {e}"))
53                })?
54        };
55
56        if formats.is_empty() {
57            return Err(HalError::BackendError(
58                "No surface formats supported".to_string(),
59            ));
60        }
61
62        let extent = if caps.current_extent.width != u32::MAX {
63            caps.current_extent
64        } else {
65            vk::Extent2D {
66                width: width.clamp(caps.min_image_extent.width, caps.max_image_extent.width),
67                height: height.clamp(caps.min_image_extent.height, caps.max_image_extent.height),
68            }
69        };
70
71        let mut image_count = caps.min_image_count + 1;
72        if caps.max_image_count > 0 && image_count > caps.max_image_count {
73            image_count = caps.max_image_count;
74        }
75
76        let selected_format = if let Some(pref) = preferred_format {
77            formats
78                .iter()
79                .find(|f| f.format == pref)
80                .cloned()
81                .unwrap_or(formats[0])
82        } else {
83            formats
84                .iter()
85                .find(|f| {
86                    f.format == vk::Format::B8G8R8A8_UNORM
87                        || f.format == vk::Format::R8G8B8A8_UNORM
88                        || f.format == vk::Format::B8G8R8A8_SRGB
89                        || f.format == vk::Format::R8G8B8A8_SRGB
90                })
91                .cloned()
92                .unwrap_or(formats[0])
93        };
94
95        let present_mode = match preferred_present_mode {
96            Some(mode) if present_modes.contains(&mode) => mode,
97            _ => {
98                if present_modes.contains(&vk::PresentModeKHR::MAILBOX) {
99                    vk::PresentModeKHR::MAILBOX
100                } else {
101                    vk::PresentModeKHR::FIFO
102                }
103            }
104        };
105
106        let create_info = vk::SwapchainCreateInfoKHR::default()
107            .surface(surface)
108            .min_image_count(image_count)
109            .image_format(selected_format.format)
110            .image_color_space(selected_format.color_space)
111            .image_extent(extent)
112            .image_array_layers(1)
113            .image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT | vk::ImageUsageFlags::TRANSFER_DST)
114            .image_sharing_mode(vk::SharingMode::EXCLUSIVE)
115            .pre_transform(caps.current_transform)
116            .composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE)
117            .present_mode(present_mode)
118            .clipped(true)
119            .old_swapchain(old_swapchain);
120
121        let swapchain = unsafe {
122            loader
123                .create_swapchain(&create_info, None)
124                .map_err(|e| HalError::BackendError(format!("Failed to create swapchain: {e}")))?
125        };
126
127        let images = match unsafe { loader.get_swapchain_images(swapchain) } {
128            Ok(imgs) => imgs,
129            Err(e) => {
130                unsafe {
131                    loader.destroy_swapchain(swapchain, None);
132                }
133                return Err(HalError::BackendError(format!(
134                    "Failed to get swapchain images: {e}"
135                )));
136            }
137        };
138
139        let mut views = Vec::with_capacity(images.len());
140        for &img in &images {
141            let view_create_info = vk::ImageViewCreateInfo::default()
142                .image(img)
143                .view_type(vk::ImageViewType::TYPE_2D)
144                .format(selected_format.format)
145                .subresource_range(vk::ImageSubresourceRange {
146                    aspect_mask: vk::ImageAspectFlags::COLOR,
147                    base_mip_level: 0,
148                    level_count: 1,
149                    base_array_layer: 0,
150                    layer_count: 1,
151                });
152
153            let view = match unsafe { self.device.create_image_view(&view_create_info, None) } {
154                Ok(v) => v,
155                Err(e) => {
156                    unsafe {
157                        for v in views {
158                            self.device.destroy_image_view(v, None);
159                        }
160                        loader.destroy_swapchain(swapchain, None);
161                    }
162                    return Err(HalError::BackendError(format!(
163                        "Failed to create swapchain image view: {e}"
164                    )));
165                }
166            };
167            views.push(view);
168        }
169
170        Ok(VulkanSwapchain {
171            swapchain,
172            images,
173            views,
174            width: extent.width,
175            height: extent.height,
176            format: selected_format.format,
177        })
178    }
179
180    pub fn destroy_swapchain(&self, swapchain: VulkanSwapchain) {
181        if let Some(loader) = &self.swapchain_loader {
182            unsafe {
183                for view in swapchain.views {
184                    self.device.destroy_image_view(view, None);
185                }
186                loader.destroy_swapchain(swapchain.swapchain, None);
187            }
188        }
189    }
190
191    pub fn acquire_next_image(
192        &self,
193        swapchain: &VulkanSwapchain,
194        semaphore: vk::Semaphore,
195        timeout_nanos: u64,
196    ) -> Result<(u32, bool), HalError> {
197        let loader = self
198            .swapchain_loader
199            .as_ref()
200            .ok_or_else(|| HalError::BackendError("VK_KHR_swapchain is not enabled".to_string()))?;
201        unsafe {
202            loader
203                .acquire_next_image(
204                    swapchain.swapchain,
205                    timeout_nanos,
206                    semaphore,
207                    vk::Fence::null(),
208                )
209                .map_err(|e| HalError::BackendError(format!("acquire_next_image failed: {e}")))
210        }
211    }
212
213    pub fn queue_present(
214        &self,
215        swapchain: &VulkanSwapchain,
216        image_index: u32,
217        wait_semaphores: &[vk::Semaphore],
218    ) -> Result<bool, HalError> {
219        self.queue_present_raw(swapchain.swapchain, image_index, wait_semaphores)
220    }
221
222    pub fn queue_present_raw(
223        &self,
224        swapchain: vk::SwapchainKHR,
225        image_index: u32,
226        wait_semaphores: &[vk::Semaphore],
227    ) -> Result<bool, HalError> {
228        let loader = self
229            .swapchain_loader
230            .as_ref()
231            .ok_or_else(|| HalError::BackendError("VK_KHR_swapchain is not enabled".to_string()))?;
232
233        let swapchains = [swapchain];
234        let image_indices = [image_index];
235        let present_info = vk::PresentInfoKHR::default()
236            .wait_semaphores(wait_semaphores)
237            .swapchains(&swapchains)
238            .image_indices(&image_indices);
239
240        let queue = self.queue.lock().unwrap();
241        unsafe {
242            loader
243                .queue_present(*queue, &present_info)
244                .map_err(|e| HalError::BackendError(format!("queue_present failed: {e}")))
245        }
246    }
247
248    pub fn cmd_transition_to_present(
249        &self,
250        recorder: &VulkanCommandRecorder,
251        texture: &VulkanTexture,
252    ) {
253        let old_layout = texture.transition_layout(vk::ImageLayout::PRESENT_SRC_KHR);
254        let new_layout = vk::ImageLayout::PRESENT_SRC_KHR;
255
256        let subresource_range = vk::ImageSubresourceRange::default()
257            .aspect_mask(vk::ImageAspectFlags::COLOR)
258            .base_mip_level(0)
259            .level_count(1)
260            .base_array_layer(0)
261            .layer_count(1);
262
263        let (src_stage, src_access) = match old_layout {
264            vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL => (
265                vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT,
266                vk::AccessFlags2::COLOR_ATTACHMENT_WRITE,
267            ),
268            vk::ImageLayout::TRANSFER_SRC_OPTIMAL => (
269                vk::PipelineStageFlags2::TRANSFER,
270                vk::AccessFlags2::TRANSFER_READ,
271            ),
272            _ => (
273                vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT
274                    | vk::PipelineStageFlags2::TRANSFER,
275                vk::AccessFlags2::COLOR_ATTACHMENT_WRITE | vk::AccessFlags2::TRANSFER_READ,
276            ),
277        };
278
279        let barrier = vk::ImageMemoryBarrier2::default()
280            .src_stage_mask(src_stage)
281            .src_access_mask(src_access)
282            .dst_stage_mask(vk::PipelineStageFlags2::BOTTOM_OF_PIPE)
283            .dst_access_mask(vk::AccessFlags2::empty())
284            .old_layout(old_layout)
285            .new_layout(new_layout)
286            .image(texture.image)
287            .subresource_range(subresource_range);
288
289        let barriers = [barrier];
290        let dep_info = vk::DependencyInfo::default().image_memory_barriers(&barriers);
291
292        unsafe {
293            self.device
294                .cmd_pipeline_barrier2(recorder.command_buffer, &dep_info);
295        }
296    }
297
298    pub fn submit_for_surface(
299        &self,
300        recorder: VulkanCommandRecorder,
301        wait_semaphore: vk::Semaphore,
302        signal_semaphore: vk::Semaphore,
303        fence: Option<vk::Fence>,
304    ) -> Result<(), HalError> {
305        unsafe {
306            self.device
307                .end_command_buffer(recorder.command_buffer)
308                .map_err(|e| HalError::CommandError(e.to_string()))?;
309
310            let cmd_buffers = [recorder.command_buffer];
311            let wait_sems = [wait_semaphore];
312            let wait_stages = [vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT];
313            let wait_values = [0u64];
314
315            let next_val = self
316                .internal_frame_counter
317                .fetch_add(1, std::sync::atomic::Ordering::AcqRel)
318                + 1;
319            let signal_sems = [signal_semaphore, self.internal_frame_semaphore];
320            let signal_values = [0u64, next_val];
321
322            let mut timeline_info = vk::TimelineSemaphoreSubmitInfo::default()
323                .wait_semaphore_values(&wait_values)
324                .signal_semaphore_values(&signal_values);
325
326            let submit_info = vk::SubmitInfo::default()
327                .command_buffers(&cmd_buffers)
328                .wait_semaphores(&wait_sems)
329                .wait_dst_stage_mask(&wait_stages)
330                .signal_semaphores(&signal_sems)
331                .push_next(&mut timeline_info);
332
333            let raw_fence = fence.unwrap_or(vk::Fence::null());
334            {
335                let queue = self.queue.lock().unwrap();
336                self.device
337                    .queue_submit(*queue, &[submit_info], raw_fence)
338                    .map_err(|e| HalError::CommandError(e.to_string()))?;
339            }
340
341            self.pending_async_recorders
342                .lock()
343                .unwrap()
344                .push((next_val, vec![recorder]));
345
346            Ok(())
347        }
348    }
349}