Skip to main content

rotex_vulkan/bridge/
render.rs

1use ash::vk;
2
3use super::{VulkanBridge, surface_not_attached_error};
4use super::types::DepthMode;
5use crate::backend::vulkan::{
6    Device, Framebuffer, FramebufferBuilder, ImageDescriptor, RenderPass, RenderPassBuilder,
7    RotexImage, Swapchain, SubpassBlueprint,
8};
9use crate::core::Instance;
10use crate::error::{Error, ErrorKind, vk_error};
11use rotex_types::{
12    FrameDescriptor as FrontendFrameDescriptor, MeshInstanceDescriptor,
13    SceneDescriptor as FrontendSceneDescriptor,
14};
15
16impl VulkanBridge {
17    pub fn render(
18        &mut self,
19        scene: &FrontendSceneDescriptor,
20        frame: &FrontendFrameDescriptor,
21    ) -> Result<(), Error> {
22        if frame.passes.is_empty() {
23            return Err(Error::fatal(ErrorKind::NoCompatibleDevice));
24        }
25        if self.surface_state.is_none() {
26            return Err(surface_not_attached_error());
27        }
28
29        self.in_flight_fence.wait(self.device.raw(), u64::MAX)?;
30        let image_index = match self
31            .surface_state
32            .as_ref()
33            .expect("checked")
34            .swapchain
35            .raw()
36            .acquire_next_image(&self.surface_state.as_ref().expect("checked").image_available)
37        {
38            Ok((index, _)) => index,
39            Err(err) if is_swapchain_outdated(&err) => {
40                self.recreate_swapchain()?;
41                return Ok(());
42            }
43            Err(err) => return Err(err),
44        };
45        self.in_flight_fence.reset(self.device.raw())?;
46
47        unsafe {
48            self.device.raw().logical_device().reset_command_buffer(
49                self.command_buffer.handle(),
50                vk::CommandBufferResetFlags::empty(),
51            )
52        }
53        .map_err(vk_error)?;
54        self.command_buffer.begin(
55            self.device.raw(),
56            vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT,
57        )?;
58
59        for pass in &frame.passes {
60            let draw_list: Vec<usize> = if pass.instance_indices.is_empty() {
61                (0..scene.instances.len()).collect()
62            } else {
63                pass.instance_indices
64                    .iter()
65                    .copied()
66                    .filter(|idx| *idx < scene.instances.len())
67                    .collect()
68            };
69
70            let pass_uses_depth = pass.clear_depth.is_some()
71                || draw_list.iter().any(|idx| {
72                    let inst = scene.instances[*idx];
73                    self.materials
74                        .get(&inst.material)
75                        .map(|m| m.descriptor.enable_depth)
76                        .unwrap_or(false)
77                });
78
79            if pass_uses_depth {
80                self.ensure_depth_targets()?;
81            }
82            let mut clear_values = vec![vk::ClearValue {
83                color: vk::ClearColorValue {
84                    float32: pass.clear_color,
85                },
86            }];
87            if pass_uses_depth {
88                clear_values.push(vk::ClearValue {
89                    depth_stencil: vk::ClearDepthStencilValue {
90                        depth: pass.clear_depth.unwrap_or(1.0),
91                        stencil: 0,
92                    },
93                });
94            }
95
96            let render_pass_handle = {
97                let state = self.surface_state.as_ref().expect("checked");
98                let targets = if pass_uses_depth {
99                    state
100                        .depth_targets
101                        .as_ref()
102                        .ok_or(Error::fatal(ErrorKind::NoCompatibleDevice))?
103                } else {
104                    &state.color_targets
105                };
106
107                if (image_index as usize) >= targets.framebuffers.len() {
108                    return Err(Error::fatal(ErrorKind::NoCompatibleDevice));
109                }
110
111                self.command_buffer.begin_render_pass(
112                    self.device.raw(),
113                    &targets.render_pass,
114                    &targets.framebuffers[image_index as usize],
115                    &clear_values,
116                );
117                targets.render_pass.handle()
118            };
119
120            for idx in draw_list {
121                let instance = scene.instances[idx];
122                self.record_instance_draw(instance, pass_uses_depth, render_pass_handle)?;
123            }
124
125            self.command_buffer.end_render_pass(self.device.raw());
126        }
127
128        self.command_buffer.end(self.device.raw())?;
129        let queue = self.device.raw().get_queue(self.graphics_queue_index, 0);
130        let signal = self
131            .surface_state
132            .as_ref()
133            .expect("checked")
134            .render_finished[image_index as usize]
135            .handle();
136        let wait = self
137            .surface_state
138            .as_ref()
139            .expect("checked")
140            .image_available
141            .handle();
142        let wait_semaphores = [wait];
143        let wait_stages = [vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT];
144        let command_buffers = [self.command_buffer.handle()];
145        let signal_semaphores = [signal];
146        let submit = vk::SubmitInfo::default()
147            .wait_semaphores(&wait_semaphores)
148            .wait_dst_stage_mask(&wait_stages)
149            .command_buffers(&command_buffers)
150            .signal_semaphores(&signal_semaphores);
151        unsafe {
152            self.device.raw().logical_device().queue_submit(
153                queue,
154                &[submit],
155                self.in_flight_fence.handle(),
156            )
157        }
158        .map_err(vk_error)?;
159
160        let present_result = {
161            let state = self.surface_state.as_ref().expect("checked");
162            state
163                .swapchain
164                .raw()
165                .present(queue, image_index, &state.render_finished[image_index as usize])
166        };
167        match present_result {
168            Ok(_) => Ok(()),
169            Err(err) if is_swapchain_outdated(&err) => self.recreate_swapchain(),
170            Err(err) => Err(err),
171        }
172    }
173
174    pub(super) fn record_instance_draw(
175        &mut self,
176        instance: MeshInstanceDescriptor,
177        pass_uses_depth: bool,
178        render_pass: vk::RenderPass,
179    ) -> Result<(), Error> {
180        let (material_depth_enabled, material_texture) = {
181            let material = self
182                .materials
183                .get(&instance.material)
184                .ok_or(Error::fatal(ErrorKind::NoCompatibleDevice))?;
185            (material.descriptor.enable_depth, material.descriptor.texture)
186        };
187        let depth_for_material = pass_uses_depth && material_depth_enabled;
188        let mesh_layout_id = self
189            .meshes
190            .get(&instance.mesh)
191            .ok_or(Error::fatal(ErrorKind::NoCompatibleDevice))?
192            .vertex_layout_id;
193        let depth_mode = if depth_for_material {
194            DepthMode::Enabled
195        } else {
196            DepthMode::Disabled
197        };
198        let (pipeline, pipeline_layout) = self.pipeline_handle_for(
199            instance.material,
200            mesh_layout_id,
201            depth_mode,
202            render_pass,
203        )?;
204        self.command_buffer
205            .bind_graphics_pipeline(self.device.raw(), pipeline);
206        let descriptor_set = if let Some(texture_id) = material_texture {
207            if let Some(texture) = self.textures.get(&texture_id) {
208                texture.descriptor_set.handle()
209            } else {
210                self.ensure_default_texture()?.descriptor_set.handle()
211            }
212        } else {
213            self.ensure_default_texture()?.descriptor_set.handle()
214        };
215        self.command_buffer.bind_graphics_descriptor_sets(
216            self.device.raw(),
217            pipeline_layout,
218            0,
219            &[descriptor_set],
220        );
221        let mesh = self
222            .meshes
223            .get(&instance.mesh)
224            .ok_or(Error::fatal(ErrorKind::NoCompatibleDevice))?;
225        self.command_buffer
226            .bind_vertex_buffer(self.device.raw(), mesh.vertex_buffer.handle());
227        self.command_buffer.bind_index_buffer(
228            self.device.raw(),
229            &mesh.index_buffer,
230            0,
231            mesh.index_type,
232        );
233        self.command_buffer
234            .draw_indexed(self.device.raw(), mesh.index_count, 1, 0, 0, 0);
235        Ok(())
236    }
237}
238
239pub(super) fn create_render_pass(
240    device: &Device,
241    format: vk::Format,
242    depth_format: Option<vk::Format>,
243) -> Result<RenderPass, Error> {
244    let mut builder = RenderPassBuilder::new().with_attachment(
245        vk::AttachmentDescription::default()
246            .format(format)
247            .samples(vk::SampleCountFlags::TYPE_1)
248            .load_op(vk::AttachmentLoadOp::CLEAR)
249            .store_op(vk::AttachmentStoreOp::STORE)
250            .initial_layout(vk::ImageLayout::UNDEFINED)
251            .final_layout(vk::ImageLayout::PRESENT_SRC_KHR),
252    );
253
254    let subpass = if let Some(depth_format) = depth_format {
255        builder = builder.with_attachment(
256            vk::AttachmentDescription::default()
257                .format(depth_format)
258                .samples(vk::SampleCountFlags::TYPE_1)
259                .load_op(vk::AttachmentLoadOp::CLEAR)
260                .store_op(vk::AttachmentStoreOp::DONT_CARE)
261                .stencil_load_op(vk::AttachmentLoadOp::DONT_CARE)
262                .stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
263                .initial_layout(vk::ImageLayout::UNDEFINED)
264                .final_layout(vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL),
265        );
266        SubpassBlueprint {
267            color_attachments: vec![0],
268            depth_attachment: Some(1),
269        }
270    } else {
271        SubpassBlueprint {
272            color_attachments: vec![0],
273            depth_attachment: None,
274        }
275    };
276
277    builder.with_subpass(subpass).build(device).map_err(vk_error)
278}
279
280pub(super) fn build_framebuffers(
281    device: &Device,
282    swapchain: &Swapchain,
283    render_pass: vk::RenderPass,
284    depth_image: Option<&RotexImage>,
285) -> Result<Vec<Framebuffer>, Error> {
286    swapchain
287        .image_views()
288        .iter()
289        .map(|view| {
290            let mut builder = FramebufferBuilder::new().with_attachment(*view);
291            if let Some(depth_image) = depth_image {
292                builder = builder.with_attachment(depth_image.view());
293            }
294            builder
295                .with_extent(swapchain.extent().width, swapchain.extent().height)
296                .build(device, render_pass)
297        })
298        .collect()
299}
300
301pub(super) fn create_depth_image(
302    instance: &Instance,
303    device: &Device,
304    extent: vk::Extent2D,
305    format: vk::Format,
306) -> Result<RotexImage, Error> {
307    RotexImage::new(
308        instance,
309        device,
310        ImageDescriptor::default(
311            format,
312            vk::Extent3D {
313                width: extent.width.max(1),
314                height: extent.height.max(1),
315                depth: 1,
316            },
317            vk::ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT,
318            vk::MemoryPropertyFlags::DEVICE_LOCAL,
319        ),
320    )
321}
322
323pub(super) fn find_depth_format(instance: &Instance, device: &Device) -> Result<vk::Format, Error> {
324    let candidates = [
325        vk::Format::D32_SFLOAT,
326        vk::Format::D32_SFLOAT_S8_UINT,
327        vk::Format::D24_UNORM_S8_UINT,
328    ];
329    for format in candidates {
330        let props = unsafe {
331            instance
332                .instance()
333                .get_physical_device_format_properties(device.physical_device(), format)
334        };
335        if props
336            .optimal_tiling_features
337            .contains(vk::FormatFeatureFlags::DEPTH_STENCIL_ATTACHMENT)
338        {
339            return Ok(format);
340        }
341    }
342    Err(Error::fatal(ErrorKind::NoCompatibleDevice))
343}
344
345pub(super) fn is_swapchain_outdated(err: &Error) -> bool {
346    matches!(
347        err.vk_result_code(),
348        Some(code)
349            if code == vk::Result::ERROR_OUT_OF_DATE_KHR.as_raw()
350                || code == vk::Result::SUBOPTIMAL_KHR.as_raw()
351    )
352}