Skip to main content

max / shop

4.3 KB · 132 lines History Blame Raw
1 //! Glyph atlas: guillotiere allocator over a single R8Unorm wgpu texture.
2
3 use guillotiere::{AllocId, Allocation, AtlasAllocator, size2};
4
5 pub(crate) struct GlyphAtlas {
6 pub(crate) texture: wgpu::Texture,
7 pub(crate) view: wgpu::TextureView,
8 pub(crate) size: u32,
9 /// UV coordinate of a pre-uploaded white pixel; solid-color quads sample
10 /// this to bypass the glyph atlas without needing a second pipeline.
11 pub(crate) solid_uv: [f32; 2],
12 allocator: AtlasAllocator,
13 }
14
15 #[derive(Copy, Clone)]
16 pub(crate) struct AtlasSlot {
17 pub(crate) _id: AllocId,
18 pub(crate) uv_min: [f32; 2],
19 pub(crate) uv_max: [f32; 2],
20 pub(crate) px: [u32; 2],
21 }
22
23 impl GlyphAtlas {
24 pub(crate) fn new(device: &wgpu::Device, queue: &wgpu::Queue, size: u32) -> Self {
25 let texture = device.create_texture(&wgpu::TextureDescriptor {
26 label: Some("shop.glyph_atlas"),
27 size: wgpu::Extent3d {
28 width: size,
29 height: size,
30 depth_or_array_layers: 1,
31 },
32 mip_level_count: 1,
33 sample_count: 1,
34 dimension: wgpu::TextureDimension::D2,
35 format: wgpu::TextureFormat::R8Unorm,
36 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
37 view_formats: &[],
38 });
39 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
40 let mut allocator = AtlasAllocator::new(size2(size as i32, size as i32));
41
42 // Reserve a 4x4 white block at the top-left and record its center UV.
43 // Solid-color quads sample this to reuse the glyph pipeline.
44 let solid_alloc = allocator
45 .allocate(size2(4, 4))
46 .expect("atlas must fit a 4x4 solid reservation");
47 let sx = solid_alloc.rectangle.min.x as u32;
48 let sy = solid_alloc.rectangle.min.y as u32;
49 queue.write_texture(
50 wgpu::TexelCopyTextureInfo {
51 texture: &texture,
52 mip_level: 0,
53 origin: wgpu::Origin3d { x: sx, y: sy, z: 0 },
54 aspect: wgpu::TextureAspect::All,
55 },
56 &[255u8; 16],
57 wgpu::TexelCopyBufferLayout {
58 offset: 0,
59 bytes_per_row: Some(4),
60 rows_per_image: Some(4),
61 },
62 wgpu::Extent3d {
63 width: 4,
64 height: 4,
65 depth_or_array_layers: 1,
66 },
67 );
68 let s = size as f32;
69 let solid_uv = [(sx as f32 + 2.0) / s, (sy as f32 + 2.0) / s];
70
71 Self {
72 texture,
73 view,
74 size,
75 solid_uv,
76 allocator,
77 }
78 }
79
80 /// Reserve a rectangle and upload the coverage bitmap. Returns `None` if
81 /// the atlas is full (caller should evict or grow — MVP just skips glyph).
82 pub(crate) fn upload(
83 &mut self,
84 queue: &wgpu::Queue,
85 width: u32,
86 height: u32,
87 bitmap: &[u8],
88 ) -> Option<AtlasSlot> {
89 if width == 0 || height == 0 {
90 return Some(AtlasSlot {
91 _id: AllocId::deserialize(0),
92 uv_min: [0.0, 0.0],
93 uv_max: [0.0, 0.0],
94 px: [0, 0],
95 });
96 }
97 let Allocation { id, rectangle } = self
98 .allocator
99 .allocate(size2(width as i32 + 1, height as i32 + 1))?;
100 let x = rectangle.min.x as u32;
101 let y = rectangle.min.y as u32;
102
103 queue.write_texture(
104 wgpu::TexelCopyTextureInfo {
105 texture: &self.texture,
106 mip_level: 0,
107 origin: wgpu::Origin3d { x, y, z: 0 },
108 aspect: wgpu::TextureAspect::All,
109 },
110 bitmap,
111 wgpu::TexelCopyBufferLayout {
112 offset: 0,
113 bytes_per_row: Some(width),
114 rows_per_image: Some(height),
115 },
116 wgpu::Extent3d {
117 width,
118 height,
119 depth_or_array_layers: 1,
120 },
121 );
122
123 let s = self.size as f32;
124 Some(AtlasSlot {
125 _id: id,
126 uv_min: [x as f32 / s, y as f32 / s],
127 uv_max: [(x + width) as f32 / s, (y + height) as f32 / s],
128 px: [width, height],
129 })
130 }
131 }
132