GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
src/vk/scene.rs (63.8K)
1 //! 3D scene stage: the ash port of the app's "3D canvas render pass". Draws
2 //! Vertex3D meshes (scene3d.wgsl: mvp transform, `SceneDraw::screen_space`
3 //! background quads, window-corner discard) into the full-size backdrop image with
4 //! a depth buffer, scissored to the viewport pane. The renderer then copies the
5 //! backdrop into the swapchain image and draws the UI pass over it — the same
6 //! image doubles as the blur-behind source for the 2D shader, replacing
7 //! milestone 1's 1x1 placeholder.
8 //!
9 //! Meshes are handle-based (`MeshId`); per-draw uniforms (mvp + window info) go
10 //! into one dynamic-offset uniform buffer per frame in flight, so a frame's
11 //! draws share a single descriptor set.
12
13 use ash::vk;
14 use gpu_allocator::vulkan::{Allocation, AllocationCreateDesc, AllocationScheme, Allocator};
15 use gpu_allocator::MemoryLocation;
16
17 use super::renderer::{create_cpu_buffer, destroy_cpu_buffer, AllocatedBuffer};
18
19 pub use crate::draw::scene::{MeshId, SceneDraw, SceneImage, Vertex3D};
20 use crate::draw::lit::{lit_uniforms, LitDraw, LitLight, LitMeshId, LitVertex, LIT_UNIFORM_SIZE};
21 use crate::draw::scene::{image_quads_3d, scene_uniforms, wire_base_bias, ImageVertex3D, SceneUniforms, DEFAULT_SCENE_LIGHT, UNIT_INSTANCE};
22
23 const UNIFORM_SIZE: vk::DeviceSize = std::mem::size_of::<SceneUniforms>() as vk::DeviceSize;
24 /// One uniform slot holds either a scene block or a lit one.
25 const SLOT_SIZE: vk::DeviceSize = if (LIT_UNIFORM_SIZE as vk::DeviceSize) > UNIFORM_SIZE {
26 LIT_UNIFORM_SIZE as vk::DeviceSize
27 } else {
28 UNIFORM_SIZE
29 };
30
31 struct Mesh {
32 buffer: AllocatedBuffer,
33 count: u32,
34 /// Buffers this mesh held before, each tagged with the frames that may
35 /// still read it: every frame submitted before the tag. An update takes
36 /// one no frame still reads in place of waiting for the device to go
37 /// idle ([`Mesh::replace`]).
38 spare: Vec<(AllocatedBuffer, u64)>,
39 }
40
41 impl Mesh {
42 fn new(buffer: AllocatedBuffer, count: u32) -> Self {
43 Mesh { buffer, count, spare: Vec::new() }
44 }
45
46 /// Put `bytes` (`count` vertices) in place of what the mesh holds,
47 /// without waiting for the GPU. The frames already submitted may still
48 /// read the mesh's buffer, so the bytes go into another — a spare no
49 /// submitted frame still reads (`tag <= complete`) and big enough, or a
50 /// new one — and the buffer they replace becomes a spare tagged `tag`,
51 /// the frames submitted so far. Until 2026-10-07 an update waited for
52 /// the device to go idle, reasoning that geometry updates are rare; a
53 /// playing simulation updates its meshes every frame, and the wait put
54 /// the CPU's work and the GPU's end to end, so a frame took both.
55 fn replace(
56 &mut self,
57 device: &ash::Device,
58 allocator: &mut Allocator,
59 bytes: &[u8],
60 count: u32,
61 (tag, complete): (u64, u64),
62 label: &'static str,
63 ) {
64 let needed = (bytes.len() as vk::DeviceSize).max(64);
65 let free = self.spare.iter().position(|(b, t)| *t <= complete && b.size >= needed);
66 let fresh = match free {
67 Some(i) => self.spare.swap_remove(i).0,
68 None => create_cpu_buffer(device, allocator, needed.next_power_of_two(), vk::BufferUsageFlags::VERTEX_BUFFER, label),
69 };
70 let old = std::mem::replace(&mut self.buffer, fresh);
71 self.spare.push((old, tag));
72 if !bytes.is_empty() {
73 self.buffer.allocation.as_mut().unwrap().mapped_slice_mut().unwrap()[..bytes.len()].copy_from_slice(bytes);
74 }
75 self.count = count;
76 }
77
78 /// Release the spares no frame still reads that are too small for the
79 /// mesh as it stands, and keep two of the rest: a steady playback reuses
80 /// them and allocates nothing.
81 fn reclaim(&mut self, device: &ash::Device, allocator: &mut Allocator, complete: u64) {
82 let current = self.buffer.size;
83 let mut kept = 0;
84 let mut i = 0;
85 while i < self.spare.len() {
86 let (size, tag) = (self.spare[i].0.size, self.spare[i].1);
87 let free = tag <= complete;
88 if free && (size < current || kept >= 2) {
89 let (mut buffer, _) = self.spare.swap_remove(i);
90 destroy_cpu_buffer(device, allocator, &mut buffer);
91 continue;
92 }
93 kept += free as usize;
94 i += 1;
95 }
96 }
97
98 fn destroy(&mut self, device: &ash::Device, allocator: &mut Allocator) {
99 let mut buffer = std::mem::replace(&mut self.buffer, AllocatedBuffer::null());
100 destroy_cpu_buffer(device, allocator, &mut buffer);
101 for (mut spare, _) in self.spare.drain(..) {
102 destroy_cpu_buffer(device, allocator, &mut spare);
103 }
104 }
105 }
106
107 struct StagedScene {
108 scissor: (u32, u32, u32, u32),
109 draws: Vec<SceneDraw>,
110 images: Vec<SceneImage>,
111 lit: Vec<LitDraw>,
112 }
113
114 struct SceneFrame {
115 uniforms: AllocatedBuffer,
116 /// Six vertices per staged `SceneImage`, in staged order.
117 image_verts: AllocatedBuffer,
118 descriptor_set: vk::DescriptorSet,
119 draw_count: u32,
120 }
121
122 pub(crate) struct SceneStage {
123 render_pass: vk::RenderPass,
124 pipeline: vk::Pipeline,
125 /// PolygonMode::LINE twin of `pipeline` — None when the device lacks
126 /// fillModeNonSolid (wireframe draws then fall back to the fill pipeline).
127 wireframe_pipeline: Option<vk::Pipeline>,
128 /// `pipeline` with culling off and depth writes off — the
129 /// `SceneDraw::see_through` fill.
130 see_through_pipeline: vk::Pipeline,
131 /// `wireframe_pipeline` WITH depth writes — the wires of a see-through
132 /// fill (`SceneDraw::see_through` on a wireframe draw).
133 wireframe_see_through_pipeline: vk::Pipeline,
134 /// Device cap for `SceneDraw::line_width` (1.0 without wideLines).
135 max_line_width: f32,
136 pipeline_layout: vk::PipelineLayout,
137 descriptor_set_layout: vk::DescriptorSetLayout,
138 descriptor_pool: vk::DescriptorPool,
139 shader_module: vk::ShaderModule,
140 uniform_stride: vk::DeviceSize,
141 /// The `SceneImage` pipeline: set 0 is the scene's uniforms, set 1 a
142 /// user image's descriptor set (`image::image_set_bindings`).
143 image_pipeline: vk::Pipeline,
144 image_pipeline_layout: vk::PipelineLayout,
145 image_set_layout: vk::DescriptorSetLayout,
146 image_shader_module: vk::ShaderModule,
147 /// The `LitDraw` pipeline (`draw::lit`): set 0 the scene's uniforms
148 /// (a lit block in the slot), set 1 the base-colour image's set, as the
149 /// image pipeline has it. No culling: the shader lights a back face by
150 /// its flipped normal.
151 lit_pipeline: vk::Pipeline,
152 lit_shader_module: vk::ShaderModule,
153 lit_meshes: Vec<Mesh>,
154 pub(crate) lit_light: LitLight,
155 /// A 1x1 white image the renderer uploads, bound for an untextured lit
156 /// draw (and one whose texture is not resident): set 1 must be bound.
157 pub(crate) lit_fallback_image: Option<u32>,
158
159 format: vk::Format,
160 extent: vk::Extent2D,
161 pub(crate) backdrop_image: vk::Image,
162 pub(crate) backdrop_view: vk::ImageView,
163 backdrop_allocation: Option<Allocation>,
164 depth_image: vk::Image,
165 depth_view: vk::ImageView,
166 depth_allocation: Option<Allocation>,
167 framebuffer: vk::Framebuffer,
168
169 meshes: Vec<Mesh>,
170 /// Frames submitted so far, counted by the renderer as it submits them.
171 pub(crate) submitted: u64,
172 /// Every frame numbered below this one has finished on the GPU, as the
173 /// renderer learns by waiting on a frame slot's fence
174 /// ([`SceneStage::frame_waited`]).
175 complete_before: u64,
176 /// The one instance a draw without instances is drawn with
177 /// ([`UNIT_INSTANCE`]), bound in binding 1 in place of an instance mesh.
178 unit_instance: AllocatedBuffer,
179 frames: Vec<SceneFrame>,
180 staged: Option<StagedScene>,
181 /// True once the backdrop holds rendered content worth copying to screen.
182 pub(crate) backdrop_valid: bool,
183 /// Whether the app has ever staged a scene (or a traced one). Until it
184 /// has, the backdrop and depth targets are 1×1 — see
185 /// [`Scene::target_extent`].
186 pub(crate) wanted: bool,
187 /// Toward the flat shading's light, unit length — see
188 /// `VkRenderer::set_scene_light`.
189 pub(crate) light: [f32; 3],
190 }
191
192 impl SceneStage {
193 pub(crate) fn new(
194 device: &ash::Device,
195 allocator: &mut Allocator,
196 format: vk::Format,
197 extent: vk::Extent2D,
198 frames_in_flight: usize,
199 min_uniform_align: vk::DeviceSize,
200 max_line_width: f32,
201 ) -> Self {
202 unsafe {
203 // Offscreen pass: color -> TRANSFER_SRC (copied to the swapchain
204 // right after), depth is transient.
205 let attachments = [
206 vk::AttachmentDescription::default()
207 .format(format)
208 .samples(vk::SampleCountFlags::TYPE_1)
209 .load_op(vk::AttachmentLoadOp::CLEAR)
210 .store_op(vk::AttachmentStoreOp::STORE)
211 .stencil_load_op(vk::AttachmentLoadOp::DONT_CARE)
212 .stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
213 .initial_layout(vk::ImageLayout::UNDEFINED)
214 .final_layout(vk::ImageLayout::TRANSFER_SRC_OPTIMAL),
215 vk::AttachmentDescription::default()
216 .format(vk::Format::D32_SFLOAT)
217 .samples(vk::SampleCountFlags::TYPE_1)
218 .load_op(vk::AttachmentLoadOp::CLEAR)
219 .store_op(vk::AttachmentStoreOp::DONT_CARE)
220 .stencil_load_op(vk::AttachmentLoadOp::DONT_CARE)
221 .stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
222 .initial_layout(vk::ImageLayout::UNDEFINED)
223 .final_layout(vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL),
224 ];
225 let color_refs = [vk::AttachmentReference::default()
226 .attachment(0)
227 .layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL)];
228 let depth_ref = vk::AttachmentReference::default()
229 .attachment(1)
230 .layout(vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
231 let subpasses = [vk::SubpassDescription::default()
232 .pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS)
233 .color_attachments(&color_refs)
234 .depth_stencil_attachment(&depth_ref)];
235 let dependencies = [
236 // Prior frame sampled the backdrop (blur plates) and used the depth
237 // image; execution dependency before we overwrite from UNDEFINED.
238 vk::SubpassDependency::default()
239 .src_subpass(vk::SUBPASS_EXTERNAL)
240 .dst_subpass(0)
241 .src_stage_mask(
242 vk::PipelineStageFlags::FRAGMENT_SHADER
243 | vk::PipelineStageFlags::LATE_FRAGMENT_TESTS,
244 )
245 .src_access_mask(vk::AccessFlags::empty())
246 .dst_stage_mask(
247 vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT
248 | vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS,
249 )
250 .dst_access_mask(
251 vk::AccessFlags::COLOR_ATTACHMENT_WRITE
252 | vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE,
253 ),
254 // The copy to the swapchain reads the color attachment right after.
255 vk::SubpassDependency::default()
256 .src_subpass(0)
257 .dst_subpass(vk::SUBPASS_EXTERNAL)
258 .src_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT)
259 .src_access_mask(vk::AccessFlags::COLOR_ATTACHMENT_WRITE)
260 .dst_stage_mask(vk::PipelineStageFlags::TRANSFER)
261 .dst_access_mask(vk::AccessFlags::TRANSFER_READ),
262 ];
263 let render_pass = device
264 .create_render_pass(
265 &vk::RenderPassCreateInfo::default()
266 .attachments(&attachments)
267 .subpasses(&subpasses)
268 .dependencies(&dependencies),
269 None,
270 )
271 .expect("Failed to create scene render pass");
272
273 let bindings = [vk::DescriptorSetLayoutBinding::default()
274 .binding(0)
275 .descriptor_type(vk::DescriptorType::UNIFORM_BUFFER_DYNAMIC)
276 .descriptor_count(1)
277 .stage_flags(vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT)];
278 let descriptor_set_layout = device
279 .create_descriptor_set_layout(
280 &vk::DescriptorSetLayoutCreateInfo::default().bindings(&bindings),
281 None,
282 )
283 .expect("Failed to create scene descriptor set layout");
284 let set_layouts_one = [descriptor_set_layout];
285 let pipeline_layout = device
286 .create_pipeline_layout(
287 &vk::PipelineLayoutCreateInfo::default().set_layouts(&set_layouts_one),
288 None,
289 )
290 .expect("Failed to create scene pipeline layout");
291
292 let spirv = super::renderer::scene3d_spirv();
293 let shader_module = device
294 .create_shader_module(&vk::ShaderModuleCreateInfo::default().code(spirv), None)
295 .expect("Failed to create 3D shader module");
296 let stages = [
297 vk::PipelineShaderStageCreateInfo::default()
298 .stage(vk::ShaderStageFlags::VERTEX)
299 .module(shader_module)
300 .name(c"vs_main"),
301 vk::PipelineShaderStageCreateInfo::default()
302 .stage(vk::ShaderStageFlags::FRAGMENT)
303 .module(shader_module)
304 .name(c"fs_main"),
305 ];
306 // Binding 0 the mesh's vertices, binding 1 the instances it is
307 // drawn for (`SceneDraw::instances`), both `Vertex3D`s: the
308 // instance's position is the offset, its colour the multiplier.
309 let vertex_bindings = [
310 vk::VertexInputBindingDescription::default()
311 .binding(0)
312 .stride(std::mem::size_of::<Vertex3D>() as u32)
313 .input_rate(vk::VertexInputRate::VERTEX),
314 vk::VertexInputBindingDescription::default()
315 .binding(1)
316 .stride(std::mem::size_of::<Vertex3D>() as u32)
317 .input_rate(vk::VertexInputRate::INSTANCE),
318 ];
319 let vertex_attributes = [
320 vk::VertexInputAttributeDescription::default()
321 .location(0)
322 .binding(0)
323 .format(vk::Format::R32G32B32_SFLOAT)
324 .offset(0),
325 vk::VertexInputAttributeDescription::default()
326 .location(1)
327 .binding(0)
328 .format(vk::Format::R32G32B32_SFLOAT)
329 .offset(12),
330 vk::VertexInputAttributeDescription::default()
331 .location(2)
332 .binding(1)
333 .format(vk::Format::R32G32B32_SFLOAT)
334 .offset(0),
335 vk::VertexInputAttributeDescription::default()
336 .location(3)
337 .binding(1)
338 .format(vk::Format::R32G32B32_SFLOAT)
339 .offset(12),
340 ];
341 let vertex_input = vk::PipelineVertexInputStateCreateInfo::default()
342 .vertex_binding_descriptions(&vertex_bindings)
343 .vertex_attribute_descriptions(&vertex_attributes);
344 let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default()
345 .topology(vk::PrimitiveTopology::TRIANGLE_LIST);
346 let viewport_state = vk::PipelineViewportStateCreateInfo::default()
347 .viewport_count(1)
348 .scissor_count(1);
349 // wgpu pipeline_3d: CCW front, back-face culling. Winding survives
350 // because the renderer flips Y via negative viewport height (like
351 // wgpu-hal), not in the shader. Depth bias is enabled but DYNAMIC
352 // (zero for ordinary fills): fills carrying a wire overlay are
353 // pushed back per `SceneDraw::wire_base_width`.
354 let rasterization = vk::PipelineRasterizationStateCreateInfo::default()
355 .polygon_mode(vk::PolygonMode::FILL)
356 .cull_mode(vk::CullModeFlags::BACK)
357 .front_face(vk::FrontFace::COUNTER_CLOCKWISE)
358 .depth_bias_enable(true)
359 .line_width(1.0);
360 let multisample = vk::PipelineMultisampleStateCreateInfo::default()
361 .rasterization_samples(vk::SampleCountFlags::TYPE_1);
362 let depth_stencil = vk::PipelineDepthStencilStateCreateInfo::default()
363 .depth_test_enable(true)
364 .depth_write_enable(true)
365 .depth_compare_op(vk::CompareOp::LESS);
366 let blend_attachments = [vk::PipelineColorBlendAttachmentState::default()
367 .blend_enable(true)
368 .src_color_blend_factor(vk::BlendFactor::SRC_ALPHA)
369 .dst_color_blend_factor(vk::BlendFactor::ONE_MINUS_SRC_ALPHA)
370 .color_blend_op(vk::BlendOp::ADD)
371 .src_alpha_blend_factor(vk::BlendFactor::ONE)
372 .dst_alpha_blend_factor(vk::BlendFactor::ONE_MINUS_SRC_ALPHA)
373 .alpha_blend_op(vk::BlendOp::ADD)
374 .color_write_mask(vk::ColorComponentFlags::RGBA)];
375 let color_blend = vk::PipelineColorBlendStateCreateInfo::default()
376 .attachments(&blend_attachments);
377 let dynamic_states = [
378 vk::DynamicState::VIEWPORT,
379 vk::DynamicState::SCISSOR,
380 vk::DynamicState::DEPTH_BIAS,
381 ];
382 let dynamic_state =
383 vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_states);
384 let pipeline = device
385 .create_graphics_pipelines(
386 vk::PipelineCache::null(),
387 &[vk::GraphicsPipelineCreateInfo::default()
388 .stages(&stages)
389 .vertex_input_state(&vertex_input)
390 .input_assembly_state(&input_assembly)
391 .viewport_state(&viewport_state)
392 .rasterization_state(&rasterization)
393 .multisample_state(&multisample)
394 .depth_stencil_state(&depth_stencil)
395 .color_blend_state(&color_blend)
396 .dynamic_state(&dynamic_state)
397 .layout(pipeline_layout)
398 .render_pass(render_pass)
399 .subpass(0)],
400 None,
401 )
402 .expect("Failed to create 3D pipeline")[0];
403
404 // The see-through twin: the fill pipeline with no culling (a
405 // translucent closed mesh shows its far wall) and no depth
406 // WRITES (its near layers must not hide its far ones, nor the
407 // wires riding it). The depth test stays: an opaque thing drawn
408 // earlier still occludes it.
409 let see_through_pipeline = {
410 let rasterization_st = vk::PipelineRasterizationStateCreateInfo::default()
411 .polygon_mode(vk::PolygonMode::FILL)
412 .cull_mode(vk::CullModeFlags::NONE)
413 .front_face(vk::FrontFace::COUNTER_CLOCKWISE)
414 .depth_bias_enable(true)
415 .line_width(1.0);
416 let depth_stencil_st = vk::PipelineDepthStencilStateCreateInfo::default()
417 .depth_test_enable(true)
418 .depth_write_enable(false)
419 .depth_compare_op(vk::CompareOp::LESS);
420 device
421 .create_graphics_pipelines(
422 vk::PipelineCache::null(),
423 &[vk::GraphicsPipelineCreateInfo::default()
424 .stages(&stages)
425 .vertex_input_state(&vertex_input)
426 .input_assembly_state(&input_assembly)
427 .viewport_state(&viewport_state)
428 .rasterization_state(&rasterization_st)
429 .multisample_state(&multisample)
430 .depth_stencil_state(&depth_stencil_st)
431 .color_blend_state(&color_blend)
432 .dynamic_state(&dynamic_state)
433 .layout(pipeline_layout)
434 .render_pass(render_pass)
435 .subpass(0)],
436 None,
437 )
438 .expect("Failed to create 3D see-through pipeline")[0]
439 };
440
441 // The wireframe twin draws LINE_LIST edge meshes, NOT the fill
442 // mesh through PolygonMode::LINE. Polygon-mode lines proved
443 // driver-broken twice on Mesa ANV with the negative-height
444 // viewport: triangle winding is evaluated without the
445 // framebuffer Y-mirror (CCW-front selected the FAR facet set —
446 // wireframe spheres drew only the far hemisphere's interior,
447 // pole-fan forensics), and vertex-attribute sourcing fetches
448 // from the wrong vertices (wires aligned to the mesh but carried
449 // colors from a rotated region — the sphere's symmetry masked
450 // the misplacement geometrically). Real line primitives take the
451 // ordinary, well-tested raster path: no facet culling exists, so
452 // hidden-wire removal is the DEPTH test against the fill, which
453 // `SceneDraw::wire_base_width` pushes back.
454 // The compare is LESS_OR_EQUAL with writes off, and the wires
455 // carry NO bias — the tiebreak lives on the FILL side
456 // (`SceneDraw::wire_base_width` pushes the fill back by its own
457 // slope-scaled polygon offset). Biasing the line cannot work for
458 // wide wires: a w-px line's fragments sample the fill's plane up
459 // to (w/2 + 0.5) px off the true edge, but the hardware scales a
460 // line's slope bias by its ALONG-AXIS depth slope — near zero
461 // for contour-following wires — while strong constant terms
462 // punch FAR-side wires through the near fill (the old -4/-1 did
463 // exactly that; with the culled-facet flip above those far wires
464 // were the only wires, and the overlay's whole lattice was the
465 // back side showing through, which read as the mesh
466 // counter-rotating during orbits).
467 // `depth_write` builds the see-through twin (`SceneDraw::
468 // see_through` on a WIRE draw): identical but for the writes.
469 let make_lines_pipeline = |depth_write: bool| {
470 let depth_stencil_lines = vk::PipelineDepthStencilStateCreateInfo::default()
471 .depth_test_enable(true)
472 .depth_write_enable(depth_write)
473 .depth_compare_op(vk::CompareOp::LESS_OR_EQUAL);
474 let input_assembly_lines = vk::PipelineInputAssemblyStateCreateInfo::default()
475 .topology(vk::PrimitiveTopology::LINE_LIST);
476 let rasterization_lines = vk::PipelineRasterizationStateCreateInfo::default()
477 .polygon_mode(vk::PolygonMode::FILL)
478 .cull_mode(vk::CullModeFlags::NONE)
479 .front_face(vk::FrontFace::COUNTER_CLOCKWISE)
480 .depth_bias_enable(true)
481 .line_width(1.0);
482 // Line width is dynamic (SceneDraw::line_width); depth bias
483 // is dynamic on both pipelines and set to zero for wires —
484 // see the comment above.
485 let dynamic_states_lines = [
486 vk::DynamicState::VIEWPORT,
487 vk::DynamicState::SCISSOR,
488 vk::DynamicState::LINE_WIDTH,
489 vk::DynamicState::DEPTH_BIAS,
490 ];
491 let dynamic_state_lines = vk::PipelineDynamicStateCreateInfo::default()
492 .dynamic_states(&dynamic_states_lines);
493 device
494 .create_graphics_pipelines(
495 vk::PipelineCache::null(),
496 &[vk::GraphicsPipelineCreateInfo::default()
497 .stages(&stages)
498 .vertex_input_state(&vertex_input)
499 .input_assembly_state(&input_assembly_lines)
500 .viewport_state(&viewport_state)
501 .rasterization_state(&rasterization_lines)
502 .multisample_state(&multisample)
503 .depth_stencil_state(&depth_stencil_lines)
504 .color_blend_state(&color_blend)
505 .dynamic_state(&dynamic_state_lines)
506 .layout(pipeline_layout)
507 .render_pass(render_pass)
508 .subpass(0)],
509 None,
510 )
511 .expect("Failed to create 3D wireframe pipeline")[0]
512 };
513 let wireframe_pipeline = Some(make_lines_pipeline(false));
514 let wireframe_see_through_pipeline = make_lines_pipeline(true);
515
516 // The image pipeline: the fill's pass, blend and depth state,
517 // with no culling (a picture is seen from behind, mirrored) and
518 // a vertex that carries a uv where a mesh's carries a colour.
519 let image_bindings = super::image::image_set_bindings();
520 let image_set_layout = device
521 .create_descriptor_set_layout(
522 &vk::DescriptorSetLayoutCreateInfo::default().bindings(&image_bindings),
523 None,
524 )
525 .expect("Failed to create scene image set layout");
526 let image_set_layouts = [descriptor_set_layout, image_set_layout];
527 let image_pipeline_layout = device
528 .create_pipeline_layout(
529 &vk::PipelineLayoutCreateInfo::default().set_layouts(&image_set_layouts),
530 None,
531 )
532 .expect("Failed to create scene image pipeline layout");
533 let image_shader_module = device
534 .create_shader_module(
535 &vk::ShaderModuleCreateInfo::default()
536 .code(super::renderer::scene3d_image_spirv()),
537 None,
538 )
539 .expect("Failed to create 3D image shader module");
540 let image_pipeline = {
541 let stages = [
542 vk::PipelineShaderStageCreateInfo::default()
543 .stage(vk::ShaderStageFlags::VERTEX)
544 .module(image_shader_module)
545 .name(c"vs_main"),
546 vk::PipelineShaderStageCreateInfo::default()
547 .stage(vk::ShaderStageFlags::FRAGMENT)
548 .module(image_shader_module)
549 .name(c"fs_main"),
550 ];
551 let vertex_bindings = [vk::VertexInputBindingDescription::default()
552 .binding(0)
553 .stride(std::mem::size_of::<ImageVertex3D>() as u32)
554 .input_rate(vk::VertexInputRate::VERTEX)];
555 let vertex_attributes = [
556 vk::VertexInputAttributeDescription::default()
557 .location(0)
558 .binding(0)
559 .format(vk::Format::R32G32B32_SFLOAT)
560 .offset(0),
561 vk::VertexInputAttributeDescription::default()
562 .location(1)
563 .binding(0)
564 .format(vk::Format::R32G32_SFLOAT)
565 .offset(12),
566 ];
567 let vertex_input = vk::PipelineVertexInputStateCreateInfo::default()
568 .vertex_binding_descriptions(&vertex_bindings)
569 .vertex_attribute_descriptions(&vertex_attributes);
570 let rasterization = vk::PipelineRasterizationStateCreateInfo::default()
571 .polygon_mode(vk::PolygonMode::FILL)
572 .cull_mode(vk::CullModeFlags::NONE)
573 .front_face(vk::FrontFace::COUNTER_CLOCKWISE)
574 .line_width(1.0);
575 let dynamic_states = [vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR];
576 let dynamic_state = vk::PipelineDynamicStateCreateInfo::default()
577 .dynamic_states(&dynamic_states);
578 device
579 .create_graphics_pipelines(
580 vk::PipelineCache::null(),
581 &[vk::GraphicsPipelineCreateInfo::default()
582 .stages(&stages)
583 .vertex_input_state(&vertex_input)
584 .input_assembly_state(&input_assembly)
585 .viewport_state(&viewport_state)
586 .rasterization_state(&rasterization)
587 .multisample_state(&multisample)
588 .depth_stencil_state(&depth_stencil)
589 .color_blend_state(&color_blend)
590 .dynamic_state(&dynamic_state)
591 .layout(image_pipeline_layout)
592 .render_pass(render_pass)
593 .subpass(0)],
594 None,
595 )
596 .expect("Failed to create 3D image pipeline")[0]
597 };
598
599 // The lit pipeline: the fill's pass, blend, depth and dynamic
600 // depth-bias state, the image pipeline's layout, no culling, and
601 // a `LitVertex`.
602 let lit_shader_module = device
603 .create_shader_module(
604 &vk::ShaderModuleCreateInfo::default().code(super::renderer::scene3d_lit_spirv()),
605 None,
606 )
607 .expect("Failed to create 3D lit shader module");
608 let lit_pipeline = {
609 let stages = [
610 vk::PipelineShaderStageCreateInfo::default()
611 .stage(vk::ShaderStageFlags::VERTEX)
612 .module(lit_shader_module)
613 .name(c"vs_main"),
614 vk::PipelineShaderStageCreateInfo::default()
615 .stage(vk::ShaderStageFlags::FRAGMENT)
616 .module(lit_shader_module)
617 .name(c"fs_main"),
618 ];
619 let vertex_bindings = [vk::VertexInputBindingDescription::default()
620 .binding(0)
621 .stride(std::mem::size_of::<LitVertex>() as u32)
622 .input_rate(vk::VertexInputRate::VERTEX)];
623 let attribute = |location: u32, format: vk::Format, offset: u32| {
624 vk::VertexInputAttributeDescription::default()
625 .location(location)
626 .binding(0)
627 .format(format)
628 .offset(offset)
629 };
630 let vertex_attributes = [
631 attribute(0, vk::Format::R32G32B32_SFLOAT, 0),
632 attribute(1, vk::Format::R32G32B32_SFLOAT, 12),
633 attribute(2, vk::Format::R32G32_SFLOAT, 24),
634 attribute(3, vk::Format::R32G32B32_SFLOAT, 32),
635 ];
636 let vertex_input = vk::PipelineVertexInputStateCreateInfo::default()
637 .vertex_binding_descriptions(&vertex_bindings)
638 .vertex_attribute_descriptions(&vertex_attributes);
639 let rasterization = vk::PipelineRasterizationStateCreateInfo::default()
640 .polygon_mode(vk::PolygonMode::FILL)
641 .cull_mode(vk::CullModeFlags::NONE)
642 .front_face(vk::FrontFace::COUNTER_CLOCKWISE)
643 .depth_bias_enable(true)
644 .line_width(1.0);
645 device
646 .create_graphics_pipelines(
647 vk::PipelineCache::null(),
648 &[vk::GraphicsPipelineCreateInfo::default()
649 .stages(&stages)
650 .vertex_input_state(&vertex_input)
651 .input_assembly_state(&input_assembly)
652 .viewport_state(&viewport_state)
653 .rasterization_state(&rasterization)
654 .multisample_state(&multisample)
655 .depth_stencil_state(&depth_stencil)
656 .color_blend_state(&color_blend)
657 .dynamic_state(&dynamic_state)
658 .layout(image_pipeline_layout)
659 .render_pass(render_pass)
660 .subpass(0)],
661 None,
662 )
663 .expect("Failed to create 3D lit pipeline")[0]
664 };
665
666 let uniform_stride = SLOT_SIZE.next_multiple_of(min_uniform_align.max(1));
667
668 let pool_sizes = [vk::DescriptorPoolSize::default()
669 .ty(vk::DescriptorType::UNIFORM_BUFFER_DYNAMIC)
670 .descriptor_count(frames_in_flight as u32)];
671 let descriptor_pool = device
672 .create_descriptor_pool(
673 &vk::DescriptorPoolCreateInfo::default()
674 .max_sets(frames_in_flight as u32)
675 .pool_sizes(&pool_sizes),
676 None,
677 )
678 .expect("Failed to create scene descriptor pool");
679 let set_layouts: Vec<vk::DescriptorSetLayout> =
680 vec![descriptor_set_layout; frames_in_flight];
681 let sets = device
682 .allocate_descriptor_sets(
683 &vk::DescriptorSetAllocateInfo::default()
684 .descriptor_pool(descriptor_pool)
685 .set_layouts(&set_layouts),
686 )
687 .expect("Failed to allocate scene descriptor sets");
688 let frames: Vec<SceneFrame> = sets
689 .into_iter()
690 .map(|descriptor_set| {
691 let uniforms = create_cpu_buffer(
692 device,
693 allocator,
694 uniform_stride * 16,
695 vk::BufferUsageFlags::UNIFORM_BUFFER,
696 "scene-uniforms",
697 );
698 let image_verts = create_cpu_buffer(
699 device,
700 allocator,
701 1024,
702 vk::BufferUsageFlags::VERTEX_BUFFER,
703 "scene-image-quads",
704 );
705 SceneFrame { uniforms, image_verts, descriptor_set, draw_count: 0 }
706 })
707 .collect();
708 for frame in &frames {
709 Self::write_descriptor(device, frame);
710 }
711 let mut unit_instance = create_cpu_buffer(
712 device,
713 allocator,
714 64,
715 vk::BufferUsageFlags::VERTEX_BUFFER,
716 "scene-unit-instance",
717 );
718 let unit: &[u8] = bytemuck::bytes_of(&UNIT_INSTANCE);
719 unit_instance.allocation.as_mut().unwrap().mapped_slice_mut().unwrap()[..unit.len()].copy_from_slice(unit);
720
721 let mut stage = SceneStage {
722 render_pass,
723 pipeline,
724 wireframe_pipeline,
725 see_through_pipeline,
726 wireframe_see_through_pipeline,
727 max_line_width,
728 pipeline_layout,
729 descriptor_set_layout,
730 descriptor_pool,
731 shader_module,
732 uniform_stride,
733 image_pipeline,
734 image_pipeline_layout,
735 image_set_layout,
736 image_shader_module,
737 lit_pipeline,
738 lit_shader_module,
739 lit_meshes: Vec::new(),
740 lit_light: LitLight::default(),
741 lit_fallback_image: None,
742 format,
743 extent: vk::Extent2D { width: 0, height: 0 },
744 backdrop_image: vk::Image::null(),
745 backdrop_view: vk::ImageView::null(),
746 backdrop_allocation: None,
747 depth_image: vk::Image::null(),
748 depth_view: vk::ImageView::null(),
749 depth_allocation: None,
750 framebuffer: vk::Framebuffer::null(),
751 meshes: Vec::new(),
752 submitted: 0,
753 complete_before: 0,
754 unit_instance,
755 frames,
756 staged: None,
757 backdrop_valid: false,
758 wanted: false,
759 light: glam::Vec3::from_array(DEFAULT_SCENE_LIGHT).normalize().to_array(),
760 };
761 let extent = stage.target_extent(extent);
762 stage.resize(device, allocator, extent);
763 stage
764 }
765 }
766
767 fn write_descriptor(device: &ash::Device, frame: &SceneFrame) {
768 let buffer_infos = [vk::DescriptorBufferInfo::default()
769 .buffer(frame.uniforms.buffer)
770 .offset(0)
771 .range(UNIFORM_SIZE)];
772 unsafe {
773 device.update_descriptor_sets(
774 &[vk::WriteDescriptorSet::default()
775 .dst_set(frame.descriptor_set)
776 .dst_binding(0)
777 .descriptor_type(vk::DescriptorType::UNIFORM_BUFFER_DYNAMIC)
778 .buffer_info(&buffer_infos)],
779 &[],
780 );
781 }
782 }
783
784 /// The size the backdrop and depth targets are made at for a surface of
785 /// `surface`: the surface's, once a scene has been staged; 1×1 before.
786 ///
787 /// Most windows never draw a 3D scene, and full-surface targets cost
788 /// ~33 MiB a window at 2560×1600 (D32 depth plus the RGBA backdrop). A
789 /// 2D frame still samples the backdrop — the frosted root plate reads it,
790 /// zeroed — and a zeroed 1×1 image reads the same under the UI's
791 /// clamp-to-edge sampler at normalized coordinates. The first
792 /// `stage_scene` / `stage_rt` grows them (`VkRenderer::want_scene_targets`).
793 pub(crate) fn target_extent(&self, surface: vk::Extent2D) -> vk::Extent2D {
794 if self.wanted {
795 surface
796 } else {
797 vk::Extent2D { width: 1, height: 1 }
798 }
799 }
800
801 fn destroy_targets(&mut self, device: &ash::Device, allocator: &mut Allocator) {
802 unsafe {
803 if self.framebuffer != vk::Framebuffer::null() {
804 device.destroy_framebuffer(self.framebuffer, None);
805 self.framebuffer = vk::Framebuffer::null();
806 }
807 if self.backdrop_view != vk::ImageView::null() {
808 device.destroy_image_view(self.backdrop_view, None);
809 device.destroy_image(self.backdrop_image, None);
810 self.backdrop_view = vk::ImageView::null();
811 self.backdrop_image = vk::Image::null();
812 }
813 if self.depth_view != vk::ImageView::null() {
814 device.destroy_image_view(self.depth_view, None);
815 device.destroy_image(self.depth_image, None);
816 self.depth_view = vk::ImageView::null();
817 self.depth_image = vk::Image::null();
818 }
819 }
820 if let Some(a) = self.backdrop_allocation.take() {
821 let _ = allocator.free(a);
822 }
823 if let Some(a) = self.depth_allocation.take() {
824 let _ = allocator.free(a);
825 }
826 }
827
828 /// (Re)create the backdrop + depth targets at `extent`. Caller must have the
829 /// device idle (the renderer's swapchain-rebuild path guarantees it) and must
830 /// re-point the UI descriptor at the new `backdrop_view` and re-init its layout.
831 ///
832 /// Returns whether the targets were recreated. At an unchanged extent they
833 /// are kept, CONTENTS included: the backdrop still holds the last staged
834 /// scene and `backdrop_valid` still says so, and an app that stages only
835 /// when its scene changes will not stage again to repair it.
836 pub(crate) fn resize(
837 &mut self,
838 device: &ash::Device,
839 allocator: &mut Allocator,
840 extent: vk::Extent2D,
841 ) -> bool {
842 if extent == self.extent && self.framebuffer != vk::Framebuffer::null() {
843 return false;
844 }
845 self.destroy_targets(device, allocator);
846 self.extent = extent;
847 self.backdrop_valid = false;
848 unsafe {
849 let backdrop_image = device
850 .create_image(
851 &vk::ImageCreateInfo::default()
852 .image_type(vk::ImageType::TYPE_2D)
853 .format(self.format)
854 .extent(vk::Extent3D {
855 width: extent.width,
856 height: extent.height,
857 depth: 1,
858 })
859 .mip_levels(1)
860 .array_layers(1)
861 .samples(vk::SampleCountFlags::TYPE_1)
862 .tiling(vk::ImageTiling::OPTIMAL)
863 .usage(
864 vk::ImageUsageFlags::COLOR_ATTACHMENT
865 | vk::ImageUsageFlags::SAMPLED
866 | vk::ImageUsageFlags::TRANSFER_SRC
867 | vk::ImageUsageFlags::TRANSFER_DST,
868 )
869 .initial_layout(vk::ImageLayout::UNDEFINED),
870 None,
871 )
872 .expect("Failed to create backdrop image");
873 let requirements = device.get_image_memory_requirements(backdrop_image);
874 let allocation = allocator
875 .allocate(&AllocationCreateDesc {
876 name: "backdrop",
877 requirements,
878 location: MemoryLocation::GpuOnly,
879 linear: false,
880 allocation_scheme: AllocationScheme::GpuAllocatorManaged,
881 })
882 .expect("Failed to allocate backdrop memory");
883 device
884 .bind_image_memory(backdrop_image, allocation.memory(), allocation.offset())
885 .expect("Failed to bind backdrop memory");
886 let backdrop_view = device
887 .create_image_view(
888 &vk::ImageViewCreateInfo::default()
889 .image(backdrop_image)
890 .view_type(vk::ImageViewType::TYPE_2D)
891 .format(self.format)
892 .subresource_range(
893 vk::ImageSubresourceRange::default()
894 .aspect_mask(vk::ImageAspectFlags::COLOR)
895 .level_count(1)
896 .layer_count(1),
897 ),
898 None,
899 )
900 .expect("Failed to create backdrop view");
901 self.backdrop_image = backdrop_image;
902 self.backdrop_view = backdrop_view;
903 self.backdrop_allocation = Some(allocation);
904
905 let depth_image = device
906 .create_image(
907 &vk::ImageCreateInfo::default()
908 .image_type(vk::ImageType::TYPE_2D)
909 .format(vk::Format::D32_SFLOAT)
910 .extent(vk::Extent3D {
911 width: extent.width,
912 height: extent.height,
913 depth: 1,
914 })
915 .mip_levels(1)
916 .array_layers(1)
917 .samples(vk::SampleCountFlags::TYPE_1)
918 .tiling(vk::ImageTiling::OPTIMAL)
919 .usage(vk::ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT)
920 .initial_layout(vk::ImageLayout::UNDEFINED),
921 None,
922 )
923 .expect("Failed to create depth image");
924 let requirements = device.get_image_memory_requirements(depth_image);
925 let allocation = allocator
926 .allocate(&AllocationCreateDesc {
927 name: "depth",
928 requirements,
929 location: MemoryLocation::GpuOnly,
930 linear: false,
931 allocation_scheme: AllocationScheme::GpuAllocatorManaged,
932 })
933 .expect("Failed to allocate depth memory");
934 device
935 .bind_image_memory(depth_image, allocation.memory(), allocation.offset())
936 .expect("Failed to bind depth memory");
937 let depth_view = device
938 .create_image_view(
939 &vk::ImageViewCreateInfo::default()
940 .image(depth_image)
941 .view_type(vk::ImageViewType::TYPE_2D)
942 .format(vk::Format::D32_SFLOAT)
943 .subresource_range(
944 vk::ImageSubresourceRange::default()
945 .aspect_mask(vk::ImageAspectFlags::DEPTH)
946 .level_count(1)
947 .layer_count(1),
948 ),
949 None,
950 )
951 .expect("Failed to create depth view");
952 self.depth_image = depth_image;
953 self.depth_view = depth_view;
954 self.depth_allocation = Some(allocation);
955
956 let attachments = [self.backdrop_view, self.depth_view];
957 self.framebuffer = device
958 .create_framebuffer(
959 &vk::FramebufferCreateInfo::default()
960 .render_pass(self.render_pass)
961 .attachments(&attachments)
962 .width(extent.width)
963 .height(extent.height)
964 .layers(1),
965 None,
966 )
967 .expect("Failed to create scene framebuffer");
968 }
969 true
970 }
971
972 pub(crate) fn create_mesh(
973 &mut self,
974 device: &ash::Device,
975 allocator: &mut Allocator,
976 verts: &[Vertex3D],
977 ) -> MeshId {
978 let bytes: &[u8] = bytemuck::cast_slice(verts);
979 let mut buffer = create_cpu_buffer(
980 device,
981 allocator,
982 (bytes.len() as vk::DeviceSize).max(64),
983 vk::BufferUsageFlags::VERTEX_BUFFER,
984 "mesh",
985 );
986 if !bytes.is_empty() {
987 buffer.allocation.as_mut().unwrap().mapped_slice_mut().unwrap()[..bytes.len()]
988 .copy_from_slice(bytes);
989 }
990 self.meshes.push(Mesh::new(buffer, verts.len() as u32));
991 MeshId(self.meshes.len() - 1)
992 }
993
994 /// Replace a mesh's vertices without waiting for the GPU: the frames in
995 /// flight keep the buffer they read ([`Mesh::replace`]).
996 pub(crate) fn update_mesh(
997 &mut self,
998 device: &ash::Device,
999 allocator: &mut Allocator,
1000 id: MeshId,
1001 verts: &[Vertex3D],
1002 ) {
1003 let frames = (self.submitted, self.complete_before);
1004 self.meshes[id.0].replace(device, allocator, bytemuck::cast_slice(verts), verts.len() as u32, frames, "mesh");
1005 }
1006
1007 /// After the renderer has waited on the fence of the slot the next frame
1008 /// will use: the frame that slot last carried has finished, and every
1009 /// frame before it, so the spares those frames read may be reused.
1010 pub(crate) fn frame_waited(&mut self, device: &ash::Device, allocator: &mut Allocator, frames_in_flight: u64) {
1011 self.complete_before = (self.submitted + 1).saturating_sub(frames_in_flight);
1012 let complete = self.complete_before;
1013 for mesh in self.meshes.iter_mut().chain(self.lit_meshes.iter_mut()) {
1014 mesh.reclaim(device, allocator, complete);
1015 }
1016 }
1017
1018 pub(crate) fn stage(&mut self, scissor: (u32, u32, u32, u32), draws: Vec<SceneDraw>) {
1019 self.staged = Some(StagedScene { scissor, draws, images: Vec::new(), lit: Vec::new() });
1020 }
1021
1022 /// The staged scene's lit draws; nothing when no scene is staged.
1023 pub(crate) fn stage_lit(&mut self, draws: Vec<LitDraw>) {
1024 if let Some(staged) = &mut self.staged {
1025 staged.lit = draws;
1026 }
1027 }
1028
1029 pub(crate) fn create_lit_mesh(&mut self, device: &ash::Device, allocator: &mut Allocator, verts: &[LitVertex]) -> LitMeshId {
1030 let bytes: &[u8] = bytemuck::cast_slice(verts);
1031 let mut buffer = create_cpu_buffer(
1032 device,
1033 allocator,
1034 (bytes.len() as vk::DeviceSize).max(64),
1035 vk::BufferUsageFlags::VERTEX_BUFFER,
1036 "lit-mesh",
1037 );
1038 if !bytes.is_empty() {
1039 buffer.allocation.as_mut().unwrap().mapped_slice_mut().unwrap()[..bytes.len()].copy_from_slice(bytes);
1040 }
1041 self.lit_meshes.push(Mesh::new(buffer, verts.len() as u32));
1042 LitMeshId(self.lit_meshes.len() - 1)
1043 }
1044
1045 /// Replace a lit mesh's vertices without waiting for the GPU, as
1046 /// `update_mesh` does.
1047 pub(crate) fn update_lit_mesh(&mut self, device: &ash::Device, allocator: &mut Allocator, id: LitMeshId, verts: &[LitVertex]) {
1048 let frames = (self.submitted, self.complete_before);
1049 self.lit_meshes[id.0].replace(device, allocator, bytemuck::cast_slice(verts), verts.len() as u32, frames, "lit-mesh");
1050 }
1051
1052 /// The staged scene's images; nothing when no scene is staged.
1053 pub(crate) fn stage_images(&mut self, images: Vec<SceneImage>) {
1054 if let Some(staged) = &mut self.staged {
1055 staged.images = images;
1056 }
1057 }
1058
1059 /// After the frame fence: write this frame's per-draw uniforms (mvp + the
1060 /// window-corner info shader_3d shares with the 2D shader).
1061 pub(crate) fn write_frame_uniforms(
1062 &mut self,
1063 device: &ash::Device,
1064 allocator: &mut Allocator,
1065 frame_index: usize,
1066 corner_radius_px: f32,
1067 ) {
1068 let Some(staged) = &self.staged else {
1069 self.frames[frame_index].draw_count = 0;
1070 return;
1071 };
1072 let frame = &mut self.frames[frame_index];
1073 // One slot per mesh draw, then one per image, then one per lit draw.
1074 let slots = staged.draws.len() + staged.images.len() + staged.lit.len();
1075 let needed = self.uniform_stride * slots.max(1) as vk::DeviceSize;
1076 if needed > frame.uniforms.size {
1077 let mut old = std::mem::replace(&mut frame.uniforms, AllocatedBuffer::null());
1078 destroy_cpu_buffer(device, allocator, &mut old);
1079 frame.uniforms = create_cpu_buffer(
1080 device,
1081 allocator,
1082 needed.next_power_of_two(),
1083 vk::BufferUsageFlags::UNIFORM_BUFFER,
1084 "scene-uniforms",
1085 );
1086 Self::write_descriptor(device, frame);
1087 }
1088 let window_size = [self.extent.width as f32, self.extent.height as f32];
1089 let mapped = frame.uniforms.allocation.as_mut().unwrap().mapped_slice_mut().unwrap();
1090 let blocks = scene_uniforms(&staged.draws, &staged.images, window_size, corner_radius_px, self.light);
1091 for (i, uniforms) in blocks.iter().enumerate() {
1092 let offset = (self.uniform_stride as usize) * i;
1093 mapped[offset..offset + UNIFORM_SIZE as usize].copy_from_slice(bytemuck::bytes_of(uniforms));
1094 }
1095 let lit = lit_uniforms(&staged.lit, &self.lit_light, window_size, corner_radius_px);
1096 for (k, uniforms) in lit.iter().enumerate() {
1097 let offset = (self.uniform_stride as usize) * (blocks.len() + k);
1098 mapped[offset..offset + LIT_UNIFORM_SIZE].copy_from_slice(bytemuck::bytes_of(uniforms));
1099 }
1100 frame.draw_count = staged.draws.len() as u32;
1101
1102 let verts = image_quads_3d(&staged.images);
1103 let bytes: &[u8] = bytemuck::cast_slice(&verts);
1104 if bytes.len() as vk::DeviceSize > frame.image_verts.size {
1105 let mut old = std::mem::replace(&mut frame.image_verts, AllocatedBuffer::null());
1106 destroy_cpu_buffer(device, allocator, &mut old);
1107 frame.image_verts = create_cpu_buffer(
1108 device,
1109 allocator,
1110 (bytes.len() as vk::DeviceSize).next_power_of_two(),
1111 vk::BufferUsageFlags::VERTEX_BUFFER,
1112 "scene-image-quads",
1113 );
1114 }
1115 if !bytes.is_empty() {
1116 frame.image_verts.allocation.as_mut().unwrap().mapped_slice_mut().unwrap()
1117 [..bytes.len()]
1118 .copy_from_slice(bytes);
1119 }
1120 }
1121
1122 /// Record the offscreen scene pass. Consumes the staged scene; afterwards the
1123 /// backdrop is in TRANSFER_SRC layout, ready for the swapchain copy. Returns
1124 /// false if nothing was staged.
1125 pub(crate) fn record(
1126 &mut self,
1127 device: &ash::Device,
1128 cmd: vk::CommandBuffer,
1129 frame_index: usize,
1130 images: &super::image::ImageStage,
1131 ) -> bool {
1132 let Some(staged) = self.staged.take() else {
1133 return false;
1134 };
1135 let frame = &self.frames[frame_index];
1136 unsafe {
1137 let clear_values = [
1138 vk::ClearValue { color: vk::ClearColorValue { float32: [0.0, 0.0, 0.0, 0.0] } },
1139 vk::ClearValue {
1140 depth_stencil: vk::ClearDepthStencilValue { depth: 1.0, stencil: 0 },
1141 },
1142 ];
1143 device.cmd_begin_render_pass(
1144 cmd,
1145 &vk::RenderPassBeginInfo::default()
1146 .render_pass(self.render_pass)
1147 .framebuffer(self.framebuffer)
1148 .render_area(vk::Rect2D {
1149 offset: vk::Offset2D { x: 0, y: 0 },
1150 extent: self.extent,
1151 })
1152 .clear_values(&clear_values),
1153 vk::SubpassContents::INLINE,
1154 );
1155 // Negative-height viewport: wgpu's Y-up NDC without touching winding.
1156 device.cmd_set_viewport(
1157 cmd,
1158 0,
1159 &[vk::Viewport {
1160 x: 0.0,
1161 y: self.extent.height as f32,
1162 width: self.extent.width as f32,
1163 height: -(self.extent.height as f32),
1164 min_depth: 0.0,
1165 max_depth: 1.0,
1166 }],
1167 );
1168 let (sx, sy, sw, sh) = staged.scissor;
1169 let sx = sx.min(self.extent.width);
1170 let sy = sy.min(self.extent.height);
1171 device.cmd_set_scissor(
1172 cmd,
1173 0,
1174 &[vk::Rect2D {
1175 offset: vk::Offset2D { x: sx as i32, y: sy as i32 },
1176 extent: vk::Extent2D {
1177 width: sw.min(self.extent.width - sx),
1178 height: sh.min(self.extent.height - sy),
1179 },
1180 }],
1181 );
1182 let mut bound = self.pipeline;
1183 device.cmd_bind_pipeline(cmd, vk::PipelineBindPoint::GRAPHICS, bound);
1184 // The images due before mesh draw `at` (or, past the last draw,
1185 // every one left), in staged order.
1186 let draw_images = |at: usize, bound: &mut vk::Pipeline| {
1187 for (j, image) in staged.images.iter().enumerate() {
1188 let due = (image.before as usize).min(staged.draws.len());
1189 if due != at {
1190 continue;
1191 }
1192 let Some(set) = images.descriptor_set(image.image) else { continue };
1193 if *bound != self.image_pipeline {
1194 device.cmd_bind_pipeline(
1195 cmd,
1196 vk::PipelineBindPoint::GRAPHICS,
1197 self.image_pipeline,
1198 );
1199 *bound = self.image_pipeline;
1200 }
1201 device.cmd_bind_descriptor_sets(
1202 cmd,
1203 vk::PipelineBindPoint::GRAPHICS,
1204 self.image_pipeline_layout,
1205 0,
1206 &[frame.descriptor_set],
1207 &[(self.uniform_stride as u32) * (staged.draws.len() + j) as u32],
1208 );
1209 device.cmd_bind_descriptor_sets(
1210 cmd,
1211 vk::PipelineBindPoint::GRAPHICS,
1212 self.image_pipeline_layout,
1213 1,
1214 &[set],
1215 &[],
1216 );
1217 device.cmd_bind_vertex_buffers(cmd, 0, &[frame.image_verts.buffer], &[0]);
1218 device.cmd_draw(cmd, 6, 1, (j * 6) as u32, 0);
1219 }
1220 };
1221 // The lit draws due before mesh draw `at`, likewise.
1222 let lit_base = staged.draws.len() + staged.images.len();
1223 let fallback = self.lit_fallback_image.and_then(|id| images.descriptor_set(id));
1224 let draw_lit = |at: usize, bound: &mut vk::Pipeline| {
1225 for (k, lit) in staged.lit.iter().enumerate() {
1226 let due = (lit.before as usize).min(staged.draws.len());
1227 if due != at {
1228 continue;
1229 }
1230 let mesh = &self.lit_meshes[lit.mesh.0];
1231 if mesh.count == 0 {
1232 continue;
1233 }
1234 let set = lit.material.texture.and_then(|id| images.descriptor_set(id)).or(fallback);
1235 let Some(set) = set else { continue };
1236 if *bound != self.lit_pipeline {
1237 device.cmd_bind_pipeline(cmd, vk::PipelineBindPoint::GRAPHICS, self.lit_pipeline);
1238 *bound = self.lit_pipeline;
1239 }
1240 if lit.wire_base_width > 0.0 {
1241 let w = lit.wire_base_width.clamp(1.0, self.max_line_width);
1242 let (constant, slope) = wire_base_bias(w);
1243 device.cmd_set_depth_bias(cmd, constant, 0.0, slope);
1244 } else {
1245 device.cmd_set_depth_bias(cmd, 0.0, 0.0, 0.0);
1246 }
1247 device.cmd_bind_descriptor_sets(
1248 cmd,
1249 vk::PipelineBindPoint::GRAPHICS,
1250 self.image_pipeline_layout,
1251 0,
1252 &[frame.descriptor_set],
1253 &[(self.uniform_stride as u32) * (lit_base + k) as u32],
1254 );
1255 device.cmd_bind_descriptor_sets(
1256 cmd,
1257 vk::PipelineBindPoint::GRAPHICS,
1258 self.image_pipeline_layout,
1259 1,
1260 &[set],
1261 &[],
1262 );
1263 device.cmd_bind_vertex_buffers(cmd, 0, &[mesh.buffer.buffer], &[0]);
1264 device.cmd_draw(cmd, mesh.count, 1, 0, 0);
1265 }
1266 };
1267 for (i, draw) in staged.draws.iter().enumerate() {
1268 draw_lit(i, &mut bound);
1269 draw_images(i, &mut bound);
1270 let mesh = &self.meshes[draw.mesh.0];
1271 if mesh.count == 0 {
1272 continue;
1273 }
1274 // What it is drawn for: an instance mesh, or the one unit
1275 // instance that leaves it as it is.
1276 let (instance_buffer, instance_count) = match draw.instances {
1277 Some(id) => {
1278 let instances = &self.meshes[id.0];
1279 (instances.buffer.buffer, instances.count)
1280 }
1281 None => (self.unit_instance.buffer, 1),
1282 };
1283 if instance_count == 0 {
1284 continue;
1285 }
1286 let wanted = if draw.wireframe && draw.see_through {
1287 self.wireframe_see_through_pipeline
1288 } else if draw.wireframe {
1289 self.wireframe_pipeline.unwrap_or(self.pipeline)
1290 } else if draw.see_through {
1291 self.see_through_pipeline
1292 } else {
1293 self.pipeline
1294 };
1295 if wanted != bound {
1296 device.cmd_bind_pipeline(cmd, vk::PipelineBindPoint::GRAPHICS, wanted);
1297 bound = wanted;
1298 }
1299 if draw.wireframe && (draw.see_through || self.wireframe_pipeline.is_some()) {
1300 device.cmd_set_line_width(cmd, draw.line_width.clamp(1.0, self.max_line_width));
1301 device.cmd_set_depth_bias(cmd, 0.0, 0.0, 0.0);
1302 } else if draw.wire_base_width > 0.0 {
1303 // Push this fill behind its coming wire overlay (see
1304 // `wire_base_bias`).
1305 let w = draw.wire_base_width.clamp(1.0, self.max_line_width);
1306 let (constant, slope) = wire_base_bias(w);
1307 device.cmd_set_depth_bias(cmd, constant, 0.0, slope);
1308 } else {
1309 device.cmd_set_depth_bias(cmd, 0.0, 0.0, 0.0);
1310 }
1311 device.cmd_bind_descriptor_sets(
1312 cmd,
1313 vk::PipelineBindPoint::GRAPHICS,
1314 self.pipeline_layout,
1315 0,
1316 &[frame.descriptor_set],
1317 &[(self.uniform_stride as u32) * i as u32],
1318 );
1319 device.cmd_bind_vertex_buffers(cmd, 0, &[mesh.buffer.buffer, instance_buffer], &[0, 0]);
1320 device.cmd_draw(cmd, mesh.count, instance_count, 0, 0);
1321 }
1322 draw_lit(staged.draws.len(), &mut bound);
1323 draw_images(staged.draws.len(), &mut bound);
1324 device.cmd_end_render_pass(cmd);
1325 }
1326 self.backdrop_valid = true;
1327 true
1328 }
1329
1330 pub(crate) fn destroy(&mut self, device: &ash::Device, allocator: &mut Allocator) {
1331 self.destroy_targets(device, allocator);
1332 unsafe {
1333 for frame in &mut self.frames {
1334 let mut uniforms = std::mem::replace(&mut frame.uniforms, AllocatedBuffer::null());
1335 destroy_cpu_buffer(device, allocator, &mut uniforms);
1336 let mut quads = std::mem::replace(&mut frame.image_verts, AllocatedBuffer::null());
1337 destroy_cpu_buffer(device, allocator, &mut quads);
1338 }
1339 for mesh in self.meshes.iter_mut().chain(self.lit_meshes.iter_mut()) {
1340 mesh.destroy(device, allocator);
1341 }
1342 let mut unit = std::mem::replace(&mut self.unit_instance, AllocatedBuffer::null());
1343 destroy_cpu_buffer(device, allocator, &mut unit);
1344 device.destroy_descriptor_pool(self.descriptor_pool, None);
1345 device.destroy_descriptor_set_layout(self.descriptor_set_layout, None);
1346 if let Some(p) = self.wireframe_pipeline.take() {
1347 device.destroy_pipeline(p, None);
1348 }
1349 device.destroy_pipeline(self.lit_pipeline, None);
1350 device.destroy_shader_module(self.lit_shader_module, None);
1351 device.destroy_pipeline(self.image_pipeline, None);
1352 device.destroy_pipeline_layout(self.image_pipeline_layout, None);
1353 device.destroy_descriptor_set_layout(self.image_set_layout, None);
1354 device.destroy_shader_module(self.image_shader_module, None);
1355 device.destroy_pipeline(self.see_through_pipeline, None);
1356 device.destroy_pipeline(self.wireframe_see_through_pipeline, None);
1357 device.destroy_pipeline(self.pipeline, None);
1358 device.destroy_pipeline_layout(self.pipeline_layout, None);
1359 device.destroy_shader_module(self.shader_module, None);
1360 device.destroy_render_pass(self.render_pass, None);
1361 }
1362 }
1363 }