git.lucas.co / cce-ui
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 }