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}