git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

src/vk/text.rs (25.1K)

  1 //! Text on ash: cosmic-text shaping + swash rasterization into a self-managed
  2 //! RGBA glyph atlas, drawn by the glyph.wgsl pipeline inside the renderer's
  3 //! render pass. (cosmic-text used to be reached through glyphon's re-export;
  4 //! the dependency is direct now that the wgpu path is gone, pinned to the same
  5 //! version, so shaping behavior and fonts are unchanged.)
  6 //!
  7 //! `TextSpan` mirrors what was `glyphon::TextArea` (buffer + position + scale +
  8 //! bounds + default color) — the shape the wgpu-era cutover was written against.
  9 //!
 10 //! Atlas strategy: shelf packing into a 1024² RGBA8 image with a CPU mirror.
 11 //! When new glyphs land, the whole mirror is re-uploaded before the next render
 12 //! pass (bounded 4 MiB, and only on glyph-miss frames); if the atlas fills, it is
 13 //! cleared and repacked with just the current frame's glyphs. Mask glyphs are
 14 //! stored white-with-alpha, color (emoji) glyphs as-is drawn with a white vertex
 15 //! color — glyph.wgsl multiplies either by the vertex color.
 16 
 17 
 18 use ash::vk;
 19 use gpu_allocator::vulkan::{
 20     Allocation, AllocationCreateDesc, AllocationScheme, Allocator,
 21 };
 22 use gpu_allocator::MemoryLocation;
 23 
 24 
 25 use cosmic_text::{FontSystem, SwashCache};
 26 
 27 use super::renderer::{create_cpu_buffer, destroy_cpu_buffer, AllocatedBuffer};
 28 pub use crate::draw::TextSpan;
 29 use crate::draw::glyphs::{AtlasChange, GlyphAtlas, GlyphVertex, ATLAS_SIZE};
 30 
 31 struct TextFrame {
 32     vertex: AllocatedBuffer,
 33     vertex_count: u32,
 34     /// Holds what this frame copies into the atlas image: one region's rows
 35     /// packed tight, or the whole atlas after a repack. Grown on demand —
 36     /// a full-size buffer per frame in flight was 8 MiB of mapped memory
 37     /// per window, held whether or not a glyph ever changed again.
 38     staging: AllocatedBuffer,
 39     /// What this frame's staging holds for the image and the atlas
 40     /// generation it brings the image to, recorded by `write_frame_buffers`
 41     /// and copied by `record_upload` (which is when the image counts as
 42     /// holding it).
 43     pending: Option<(AtlasChange, u64)>,
 44 }
 45 
 46 /// Staging a frame starts with: room for a run of new glyphs.
 47 const STAGING_START: vk::DeviceSize = 64 * 1024;
 48 
 49 pub(crate) struct TextStage {
 50     pipeline: vk::Pipeline,
 51     pipeline_layout: vk::PipelineLayout,
 52     descriptor_set_layout: vk::DescriptorSetLayout,
 53     descriptor_pool: vk::DescriptorPool,
 54     descriptor_set: vk::DescriptorSet,
 55     shader_module: vk::ShaderModule,
 56     sampler: vk::Sampler,
 57 
 58     atlas_image: vk::Image,
 59     atlas_view: vk::ImageView,
 60     atlas_allocation: Option<Allocation>,
 61     /// The atlas and glyph quads (CPU side, shared with every renderer).
 62     atlas: GlyphAtlas,
 63     atlas_initialized: bool,
 64     /// The atlas generation the GPU image holds (copied, or about to be by
 65     /// a recorded frame). One image serves every frame in flight, so this
 66     /// is one number, not one per frame. 1 is the cleared atlas, which the
 67     /// image is cleared to on first use rather than uploaded.
 68     image_generation: u64,
 69     frames: Vec<TextFrame>,
 70 }
 71 
 72 impl TextStage {
 73     pub(crate) fn new(
 74         device: &ash::Device,
 75         allocator: &mut Allocator,
 76         render_pass: vk::RenderPass,
 77         frames_in_flight: usize,
 78     ) -> Self {
 79         unsafe {
 80             let bindings = [
 81                 vk::DescriptorSetLayoutBinding::default()
 82                     .binding(0)
 83                     .descriptor_type(vk::DescriptorType::SAMPLED_IMAGE)
 84                     .descriptor_count(1)
 85                     .stage_flags(vk::ShaderStageFlags::FRAGMENT),
 86                 vk::DescriptorSetLayoutBinding::default()
 87                     .binding(1)
 88                     .descriptor_type(vk::DescriptorType::SAMPLER)
 89                     .descriptor_count(1)
 90                     .stage_flags(vk::ShaderStageFlags::FRAGMENT),
 91             ];
 92             let descriptor_set_layout = device
 93                 .create_descriptor_set_layout(
 94                     &vk::DescriptorSetLayoutCreateInfo::default().bindings(&bindings),
 95                     None,
 96                 )
 97                 .expect("Failed to create text descriptor set layout");
 98             let set_layouts = [descriptor_set_layout];
 99             let pipeline_layout = device
100                 .create_pipeline_layout(
101                     &vk::PipelineLayoutCreateInfo::default().set_layouts(&set_layouts),
102                     None,
103                 )
104                 .expect("Failed to create text pipeline layout");
105 
106             let spirv = super::renderer::glyph_spirv();
107             let shader_module = device
108                 .create_shader_module(&vk::ShaderModuleCreateInfo::default().code(spirv), None)
109                 .expect("Failed to create glyph shader module");
110 
111             let stages = [
112                 vk::PipelineShaderStageCreateInfo::default()
113                     .stage(vk::ShaderStageFlags::VERTEX)
114                     .module(shader_module)
115                     .name(c"vs_main"),
116                 vk::PipelineShaderStageCreateInfo::default()
117                     .stage(vk::ShaderStageFlags::FRAGMENT)
118                     .module(shader_module)
119                     .name(c"fs_main"),
120             ];
121             let vertex_bindings = [vk::VertexInputBindingDescription::default()
122                 .binding(0)
123                 .stride(std::mem::size_of::<GlyphVertex>() as u32)
124                 .input_rate(vk::VertexInputRate::VERTEX)];
125             let vertex_attributes = [
126                 vk::VertexInputAttributeDescription::default()
127                     .location(0)
128                     .binding(0)
129                     .format(vk::Format::R32G32_SFLOAT)
130                     .offset(0),
131                 vk::VertexInputAttributeDescription::default()
132                     .location(1)
133                     .binding(0)
134                     .format(vk::Format::R32G32_SFLOAT)
135                     .offset(8),
136                 vk::VertexInputAttributeDescription::default()
137                     .location(2)
138                     .binding(0)
139                     .format(vk::Format::R32G32B32A32_SFLOAT)
140                     .offset(16),
141                 vk::VertexInputAttributeDescription::default()
142                     .location(3)
143                     .binding(0)
144                     .format(vk::Format::R32G32B32_SFLOAT)
145                     .offset(32),
146                 vk::VertexInputAttributeDescription::default()
147                     .location(4)
148                     .binding(0)
149                     .format(vk::Format::R32G32_SFLOAT)
150                     .offset(44),
151             ];
152             let vertex_input = vk::PipelineVertexInputStateCreateInfo::default()
153                 .vertex_binding_descriptions(&vertex_bindings)
154                 .vertex_attribute_descriptions(&vertex_attributes);
155             let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default()
156                 .topology(vk::PrimitiveTopology::TRIANGLE_LIST);
157             let viewport_state = vk::PipelineViewportStateCreateInfo::default()
158                 .viewport_count(1)
159                 .scissor_count(1);
160             let rasterization = vk::PipelineRasterizationStateCreateInfo::default()
161                 .polygon_mode(vk::PolygonMode::FILL)
162                 .cull_mode(vk::CullModeFlags::NONE)
163                 .front_face(vk::FrontFace::COUNTER_CLOCKWISE)
164                 .line_width(1.0);
165             let multisample = vk::PipelineMultisampleStateCreateInfo::default()
166                 .rasterization_samples(vk::SampleCountFlags::TYPE_1);
167             let blend_attachments = [vk::PipelineColorBlendAttachmentState::default()
168                 .blend_enable(true)
169                 .src_color_blend_factor(vk::BlendFactor::SRC_ALPHA)
170                 .dst_color_blend_factor(vk::BlendFactor::ONE_MINUS_SRC_ALPHA)
171                 .color_blend_op(vk::BlendOp::ADD)
172                 .src_alpha_blend_factor(vk::BlendFactor::ONE)
173                 .dst_alpha_blend_factor(vk::BlendFactor::ONE_MINUS_SRC_ALPHA)
174                 .alpha_blend_op(vk::BlendOp::ADD)
175                 .color_write_mask(vk::ColorComponentFlags::RGBA)];
176             let color_blend = vk::PipelineColorBlendStateCreateInfo::default()
177                 .attachments(&blend_attachments);
178             let dynamic_states = [vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR];
179             let dynamic_state =
180                 vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_states);
181             let pipeline = device
182                 .create_graphics_pipelines(
183                     vk::PipelineCache::null(),
184                     &[vk::GraphicsPipelineCreateInfo::default()
185                         .stages(&stages)
186                         .vertex_input_state(&vertex_input)
187                         .input_assembly_state(&input_assembly)
188                         .viewport_state(&viewport_state)
189                         .rasterization_state(&rasterization)
190                         .multisample_state(&multisample)
191                         .color_blend_state(&color_blend)
192                         .dynamic_state(&dynamic_state)
193                         .layout(pipeline_layout)
194                         .render_pass(render_pass)
195                         .subpass(0)],
196                     None,
197                 )
198                 .expect("Failed to create glyph pipeline")[0];
199 
200             let atlas_image = device
201                 .create_image(
202                     &vk::ImageCreateInfo::default()
203                         .image_type(vk::ImageType::TYPE_2D)
204                         .format(vk::Format::R8G8B8A8_UNORM)
205                         .extent(vk::Extent3D { width: ATLAS_SIZE, height: ATLAS_SIZE, depth: 1 })
206                         .mip_levels(1)
207                         .array_layers(1)
208                         .samples(vk::SampleCountFlags::TYPE_1)
209                         .tiling(vk::ImageTiling::OPTIMAL)
210                         .usage(vk::ImageUsageFlags::SAMPLED | vk::ImageUsageFlags::TRANSFER_DST)
211                         .initial_layout(vk::ImageLayout::UNDEFINED),
212                     None,
213                 )
214                 .expect("Failed to create atlas image");
215             let requirements = device.get_image_memory_requirements(atlas_image);
216             let atlas_allocation = allocator
217                 .allocate(&AllocationCreateDesc {
218                     name: "glyph-atlas",
219                     requirements,
220                     location: MemoryLocation::GpuOnly,
221                     linear: false,
222                     allocation_scheme: AllocationScheme::GpuAllocatorManaged,
223                 })
224                 .expect("Failed to allocate atlas memory");
225             device
226                 .bind_image_memory(atlas_image, atlas_allocation.memory(), atlas_allocation.offset())
227                 .expect("Failed to bind atlas memory");
228             let atlas_view = device
229                 .create_image_view(
230                     &vk::ImageViewCreateInfo::default()
231                         .image(atlas_image)
232                         .view_type(vk::ImageViewType::TYPE_2D)
233                         .format(vk::Format::R8G8B8A8_UNORM)
234                         .subresource_range(
235                             vk::ImageSubresourceRange::default()
236                                 .aspect_mask(vk::ImageAspectFlags::COLOR)
237                                 .level_count(1)
238                                 .layer_count(1),
239                         ),
240                     None,
241                 )
242                 .expect("Failed to create atlas view");
243 
244             // Glyphs are sampled 1:1; NEAREST keeps them crisp.
245             let sampler = device
246                 .create_sampler(
247                     &vk::SamplerCreateInfo::default()
248                         .mag_filter(vk::Filter::NEAREST)
249                         .min_filter(vk::Filter::NEAREST)
250                         .mipmap_mode(vk::SamplerMipmapMode::NEAREST)
251                         .address_mode_u(vk::SamplerAddressMode::CLAMP_TO_EDGE)
252                         .address_mode_v(vk::SamplerAddressMode::CLAMP_TO_EDGE)
253                         .address_mode_w(vk::SamplerAddressMode::CLAMP_TO_EDGE),
254                     None,
255                 )
256                 .expect("Failed to create atlas sampler");
257 
258             let pool_sizes = [
259                 vk::DescriptorPoolSize::default()
260                     .ty(vk::DescriptorType::SAMPLED_IMAGE)
261                     .descriptor_count(1),
262                 vk::DescriptorPoolSize::default()
263                     .ty(vk::DescriptorType::SAMPLER)
264                     .descriptor_count(1),
265             ];
266             let descriptor_pool = device
267                 .create_descriptor_pool(
268                     &vk::DescriptorPoolCreateInfo::default()
269                         .max_sets(1)
270                         .pool_sizes(&pool_sizes),
271                     None,
272                 )
273                 .expect("Failed to create text descriptor pool");
274             let descriptor_set = device
275                 .allocate_descriptor_sets(
276                     &vk::DescriptorSetAllocateInfo::default()
277                         .descriptor_pool(descriptor_pool)
278                         .set_layouts(&set_layouts),
279                 )
280                 .expect("Failed to allocate text descriptor set")[0];
281             let image_infos = [vk::DescriptorImageInfo::default()
282                 .image_view(atlas_view)
283                 .image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)];
284             let sampler_infos = [vk::DescriptorImageInfo::default().sampler(sampler)];
285             device.update_descriptor_sets(
286                 &[
287                     vk::WriteDescriptorSet::default()
288                         .dst_set(descriptor_set)
289                         .dst_binding(0)
290                         .descriptor_type(vk::DescriptorType::SAMPLED_IMAGE)
291                         .image_info(&image_infos),
292                     vk::WriteDescriptorSet::default()
293                         .dst_set(descriptor_set)
294                         .dst_binding(1)
295                         .descriptor_type(vk::DescriptorType::SAMPLER)
296                         .image_info(&sampler_infos),
297                 ],
298                 &[],
299             );
300 
301             let frames = (0..frames_in_flight)
302                 .map(|_| TextFrame {
303                     vertex: create_cpu_buffer(
304                         device,
305                         allocator,
306                         64 * 1024,
307                         vk::BufferUsageFlags::VERTEX_BUFFER,
308                         "glyph-vertices",
309                     ),
310                     vertex_count: 0,
311                     staging: create_cpu_buffer(
312                         device,
313                         allocator,
314                         STAGING_START,
315                         vk::BufferUsageFlags::TRANSFER_SRC,
316                         "atlas-staging",
317                     ),
318                     pending: None,
319                 })
320                 .collect();
321 
322             TextStage {
323                 pipeline,
324                 pipeline_layout,
325                 descriptor_set_layout,
326                 descriptor_pool,
327                 descriptor_set,
328                 shader_module,
329                 sampler,
330                 atlas_image,
331                 atlas_view,
332                 atlas_allocation: Some(atlas_allocation),
333                 atlas: GlyphAtlas::new(),
334                 atlas_initialized: false,
335                 image_generation: 1,
336                 frames,
337             }
338         }
339     }
340 
341     /// Build this frame's glyph quads against `extent` (see [`GlyphAtlas::prepare`]).
342     pub(crate) fn prepare(
343         &mut self,
344         font_system: &mut FontSystem,
345         swash_cache: &mut SwashCache,
346         spans: &[TextSpan<'_>],
347         extent: vk::Extent2D,
348     ) {
349         self.atlas.prepare(font_system, swash_cache, spans, extent.width, extent.height);
350     }
351 
352     /// Called after this frame's fence has been waited: copy the current text
353     /// vertices into the frame's buffer and refresh its staging copy if the
354     /// atlas changed. `pending_vertices` is RETAINED — it is the staged text
355     /// state, replaced only by the next `prepare` — so frames rendered without
356     /// a re-prepare (progressive RT refinement, animation ticks) keep their
357     /// text instead of alternating to an empty buffer.
358     pub(crate) fn write_frame_buffers(
359         &mut self,
360         device: &ash::Device,
361         allocator: &mut Allocator,
362         frame_index: usize,
363     ) {
364         let frame = &mut self.frames[frame_index];
365 
366         let bytes: &[u8] = bytemuck::cast_slice(self.atlas.vertices());
367         let needed = bytes.len() as vk::DeviceSize;
368         if needed > frame.vertex.size {
369             let mut old = std::mem::replace(&mut frame.vertex, AllocatedBuffer::null());
370             destroy_cpu_buffer(device, allocator, &mut old);
371             frame.vertex = create_cpu_buffer(
372                 device,
373                 allocator,
374                 needed.next_power_of_two(),
375                 vk::BufferUsageFlags::VERTEX_BUFFER,
376                 "glyph-vertices",
377             );
378         }
379         if !bytes.is_empty() {
380             frame.vertex.allocation.as_mut().unwrap().mapped_slice_mut().unwrap()
381                 [..bytes.len()]
382                 .copy_from_slice(bytes);
383         }
384         frame.vertex_count = self.atlas.vertices().len() as u32;
385 
386         // Stage only what changed since the image was last brought up to
387         // date: the rows of one region, or everything after a repack. Until
388         // 2026-10-06 each frame in flight re-staged and re-copied the whole
389         // 4 MiB atlas for any new glyph.
390         let change = self.atlas.changes_since(self.image_generation);
391         let frame = &mut self.frames[frame_index];
392         frame.pending = None;
393         let (row_bytes, rows, x0, y0) = match change {
394             AtlasChange::None => return,
395             AtlasChange::Full => (ATLAS_SIZE * 4, ATLAS_SIZE, 0, 0),
396             AtlasChange::Region { x, y, w, h } => (w * 4, h, x, y),
397         };
398         let needed = (row_bytes * rows) as vk::DeviceSize;
399         if needed > frame.staging.size {
400             let mut old = std::mem::replace(&mut frame.staging, AllocatedBuffer::null());
401             destroy_cpu_buffer(device, allocator, &mut old);
402             frame.staging = create_cpu_buffer(
403                 device,
404                 allocator,
405                 needed.next_power_of_two(),
406                 vk::BufferUsageFlags::TRANSFER_SRC,
407                 "atlas-staging",
408             );
409         }
410         let pixels = self.atlas.pixels();
411         let mapped = frame.staging.allocation.as_mut().unwrap().mapped_slice_mut().unwrap();
412         let stride = (ATLAS_SIZE * 4) as usize;
413         for r in 0..rows as usize {
414             let src = (y0 as usize + r) * stride + x0 as usize * 4;
415             let dst = r * row_bytes as usize;
416             mapped[dst..dst + row_bytes as usize].copy_from_slice(&pixels[src..src + row_bytes as usize]);
417         }
418         frame.pending = Some((change, self.atlas.generation()));
419     }
420 
421     /// Record the atlas upload (if this frame's staging is newer than the image).
422     /// Must be called outside a render pass.
423     pub(crate) fn record_upload(&mut self, device: &ash::Device, cmd: vk::CommandBuffer, frame_index: usize) {
424         let frame = &mut self.frames[frame_index];
425         let pending = frame.pending.take();
426         if pending.is_none() && self.atlas_initialized {
427             return;
428         }
429         if let Some((_, generation)) = pending {
430             self.image_generation = generation;
431         }
432         let pending = pending.map(|(change, _)| change);
433 
434         let range = vk::ImageSubresourceRange::default()
435             .aspect_mask(vk::ImageAspectFlags::COLOR)
436             .level_count(1)
437             .layer_count(1);
438         let (old_layout, src_stage, src_access) = if self.atlas_initialized {
439             (
440                 vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
441                 vk::PipelineStageFlags::FRAGMENT_SHADER,
442                 vk::AccessFlags::SHADER_READ,
443             )
444         } else {
445             (
446                 vk::ImageLayout::UNDEFINED,
447                 vk::PipelineStageFlags::TOP_OF_PIPE,
448                 vk::AccessFlags::empty(),
449             )
450         };
451         self.atlas_initialized = true;
452 
453         unsafe {
454             device.cmd_pipeline_barrier(
455                 cmd,
456                 src_stage,
457                 vk::PipelineStageFlags::TRANSFER,
458                 vk::DependencyFlags::empty(),
459                 &[],
460                 &[],
461                 &[vk::ImageMemoryBarrier::default()
462                     .src_access_mask(src_access)
463                     .dst_access_mask(vk::AccessFlags::TRANSFER_WRITE)
464                     .old_layout(old_layout)
465                     .new_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL)
466                     .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
467                     .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
468                     .image(self.atlas_image)
469                     .subresource_range(range)],
470             );
471             // A fresh image is cleared to the empty atlas rather than
472             // uploaded from it (`image_generation` starts at the cleared one).
473             if old_layout == vk::ImageLayout::UNDEFINED {
474                 device.cmd_clear_color_image(
475                     cmd,
476                     self.atlas_image,
477                     vk::ImageLayout::TRANSFER_DST_OPTIMAL,
478                     &vk::ClearColorValue { float32: [0.0; 4] },
479                     &[range],
480                 );
481             }
482             let region = match pending {
483                 Some(AtlasChange::Full) => Some((0, 0, ATLAS_SIZE, ATLAS_SIZE)),
484                 Some(AtlasChange::Region { x, y, w, h }) => Some((x, y, w, h)),
485                 Some(AtlasChange::None) | None => None,
486             };
487             if let Some((x, y, w, h)) = region {
488                 device.cmd_copy_buffer_to_image(
489                     cmd,
490                     frame.staging.buffer,
491                     self.atlas_image,
492                     vk::ImageLayout::TRANSFER_DST_OPTIMAL,
493                     &[vk::BufferImageCopy::default()
494                         .buffer_offset(0)
495                         .buffer_row_length(w)
496                         .buffer_image_height(h)
497                         .image_subresource(
498                             vk::ImageSubresourceLayers::default()
499                                 .aspect_mask(vk::ImageAspectFlags::COLOR)
500                                 .layer_count(1),
501                         )
502                         .image_offset(vk::Offset3D { x: x as i32, y: y as i32, z: 0 })
503                         .image_extent(vk::Extent3D { width: w, height: h, depth: 1 })],
504                 );
505             }
506             device.cmd_pipeline_barrier(
507                 cmd,
508                 vk::PipelineStageFlags::TRANSFER,
509                 vk::PipelineStageFlags::FRAGMENT_SHADER,
510                 vk::DependencyFlags::empty(),
511                 &[],
512                 &[],
513                 &[vk::ImageMemoryBarrier::default()
514                     .src_access_mask(vk::AccessFlags::TRANSFER_WRITE)
515                     .dst_access_mask(vk::AccessFlags::SHADER_READ)
516                     .old_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL)
517                     .new_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
518                     .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
519                     .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
520                     .image(self.atlas_image)
521                     .subresource_range(range)],
522             );
523         }
524     }
525 
526     /// Record the glyph draw. Must be called inside the render pass, after the
527     /// 2D quads (text goes on top). Viewport/scissor are inherited (dynamic,
528     /// already set by the caller).
529     pub(crate) fn record_draw(&self, device: &ash::Device, cmd: vk::CommandBuffer, frame_index: usize) {
530         let frame = &self.frames[frame_index];
531         if frame.vertex_count == 0 || !self.atlas_initialized {
532             return;
533         }
534         unsafe {
535             device.cmd_bind_pipeline(cmd, vk::PipelineBindPoint::GRAPHICS, self.pipeline);
536             device.cmd_bind_descriptor_sets(
537                 cmd,
538                 vk::PipelineBindPoint::GRAPHICS,
539                 self.pipeline_layout,
540                 0,
541                 &[self.descriptor_set],
542                 &[],
543             );
544             device.cmd_bind_vertex_buffers(cmd, 0, &[frame.vertex.buffer], &[0]);
545             device.cmd_draw(cmd, frame.vertex_count, 1, 0, 0);
546         }
547     }
548 
549     pub(crate) fn destroy(&mut self, device: &ash::Device, allocator: &mut Allocator) {
550         unsafe {
551             for frame in &mut self.frames {
552                 let mut vertex = std::mem::replace(&mut frame.vertex, AllocatedBuffer::null());
553                 destroy_cpu_buffer(device, allocator, &mut vertex);
554                 let mut staging = std::mem::replace(&mut frame.staging, AllocatedBuffer::null());
555                 destroy_cpu_buffer(device, allocator, &mut staging);
556             }
557             device.destroy_sampler(self.sampler, None);
558             device.destroy_image_view(self.atlas_view, None);
559             device.destroy_image(self.atlas_image, None);
560             if let Some(allocation) = self.atlas_allocation.take() {
561                 let _ = allocator.free(allocation);
562             }
563             device.destroy_descriptor_pool(self.descriptor_pool, None);
564             device.destroy_descriptor_set_layout(self.descriptor_set_layout, None);
565             device.destroy_pipeline(self.pipeline, None);
566             device.destroy_pipeline_layout(self.pipeline_layout, None);
567             device.destroy_shader_module(self.shader_module, None);
568         }
569     }
570 }