use wgpu::*; use crate::{GlyphAtlas, UiData}; use super::{ atlas::PAGE, primitive::{ListDraw, PrimitiveRender}, texture::{default_sampler, sampled_group, sampled_layout, write_region}, }; /// Draws glyphs from the atlas, which it owns: one array texture bound once /// for a whole list, since every glyph in it reads the same pages. pub struct GlyphRender { pages: GpuPages, layout: BindGroupLayout, sampler: Sampler, } impl GlyphRender { pub fn new(device: &Device, queue: &Queue) -> Self { let layout = sampled_layout(device, TextureViewDimension::D2Array, "ui atlas"); let sampler = default_sampler(device); Self { pages: GpuPages::new(device, queue, &layout, &sampler), layout, sampler, } } } impl PrimitiveRender for GlyphRender { fn layout(&self) -> Option<&BindGroupLayout> { Some(&self.layout) } fn update(&mut self, ui: &mut UiData) { self.pages .update(&mut ui.text.atlas, &self.layout, &self.sampler); } fn draw<'a>(&'a self, pass: &mut RenderPass<'a>, list: ListDraw<'a>) { pass.set_bind_group(2, self.pages.group(), &[]); pass.draw(0..4, 0..list.instances); } } /// The glyph atlas on the GPU: one array texture whose layers are the pages /// `GlyphAtlas` packs. /// /// One array rather than a texture per page because a layer index is ordinary /// Vulkan 1.0 / GLES sampling, where a `binding_array` would need /// `VK_EXT_descriptor_indexing`, which a real share of Android GPUs lack. pub struct GpuPages { device: Device, queue: Queue, texture: Texture, group: BindGroup, } impl GpuPages { pub fn new( device: &Device, queue: &Queue, layout: &BindGroupLayout, sampler: &Sampler, ) -> Self { let texture = create_array(device, 1); Self { device: device.clone(), queue: queue.clone(), group: atlas_group(device, layout, &texture, sampler), texture, } } pub fn update(&mut self, atlas: &mut GlyphAtlas, layout: &BindGroupLayout, sampler: &Sampler) { if atlas.page_count() > self.texture.depth_or_array_layers() { self.grow(atlas.page_count(), layout, sampler); } for (upload, page) in atlas.uploads() { let dst = TexelCopyTextureInfo { texture: &self.texture, mip_level: 0, origin: Origin3d { x: upload.rect.x, y: upload.rect.y, z: upload.layer, }, aspect: TextureAspect::All, }; write_region(&self.queue, dst, page, upload.rect); } } pub fn group(&self) -> &BindGroup { &self.group } /// Doubles until `needed` fits and copies the old layers across GPU side. /// The new texture stales the group, so that is rebuilt here. fn grow(&mut self, needed: u32, layout: &BindGroupLayout, sampler: &Sampler) { let old = self.texture.depth_or_array_layers(); let mut layers = old; while layers < needed { layers *= 2; } let texture = create_array(&self.device, layers); let mut encoder = self .device .create_command_encoder(&CommandEncoderDescriptor { label: Some("atlas grow"), }); encoder.copy_texture_to_texture( self.texture.as_image_copy(), texture.as_image_copy(), Extent3d { width: PAGE, height: PAGE, depth_or_array_layers: old, }, ); self.queue.submit(std::iter::once(encoder.finish())); self.group = atlas_group(&self.device, layout, &texture, sampler); self.texture = texture; } } fn atlas_group( device: &Device, layout: &BindGroupLayout, texture: &Texture, sampler: &Sampler, ) -> BindGroup { let view = texture.create_view(&TextureViewDescriptor { dimension: Some(TextureViewDimension::D2Array), ..Default::default() }); sampled_group(device, layout, &view, sampler, "ui atlas") } fn create_array(device: &Device, layers: u32) -> Texture { device.create_texture(&TextureDescriptor { label: Some("glyph atlas"), size: Extent3d { width: PAGE, height: PAGE, depth_or_array_layers: layers, }, mip_level_count: 1, sample_count: 1, dimension: TextureDimension::D2, format: TextureFormat::Rgba8Unorm, usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST | TextureUsages::COPY_SRC, view_formats: &[], }) }