Another project
1use crate::gpu::Gpu;
2
3const ATLAS_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::R8Unorm;
4const INITIAL_INSTANCE_CAP: u64 = 256;
5
6#[repr(C, align(16))]
7#[derive(Copy, Clone, Debug, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
8pub struct SdfGlyphInstance {
9 pub rect_xywh_px: [f32; 4],
10 pub uv_min: [f32; 2],
11 pub uv_max: [f32; 2],
12 pub color_premul_rgba: [f32; 4],
13}
14
15const INSTANCE_STRIDE: u64 = core::mem::size_of::<SdfGlyphInstance>() as u64;
16
17const INSTANCE_ATTRS: [wgpu::VertexAttribute; 4] = wgpu::vertex_attr_array![
18 0 => Float32x4,
19 1 => Float32x2,
20 2 => Float32x2,
21 3 => Float32x4,
22];
23
24#[repr(C, align(16))]
25#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
26struct Frame {
27 viewport_px: [f32; 2],
28 _pad: [f32; 2],
29}
30
31const FRAME_SIZE: u64 = core::mem::size_of::<Frame>() as u64;
32
33pub struct ChromeTextPipeline {
34 device: wgpu::Device,
35 queue: wgpu::Queue,
36 pipeline: wgpu::RenderPipeline,
37 uniform_buffer: wgpu::Buffer,
38 bind_group: wgpu::BindGroup,
39 atlas: wgpu::Texture,
40 atlas_extent: u32,
41 atlas_version: Option<u64>,
42 instance_buffer: wgpu::Buffer,
43 instance_capacity: u64,
44}
45
46impl ChromeTextPipeline {
47 #[must_use]
48 pub fn new(gpu: &Gpu, color_format: wgpu::TextureFormat, atlas_extent: u32) -> Self {
49 let device = gpu.device().clone();
50 let queue = gpu.queue().clone();
51 let atlas = create_atlas_texture(&device, atlas_extent);
52 let atlas_view = atlas.create_view(&wgpu::TextureViewDescriptor::default());
53 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
54 label: Some("bone-render:chrome-text-sampler"),
55 address_mode_u: wgpu::AddressMode::ClampToEdge,
56 address_mode_v: wgpu::AddressMode::ClampToEdge,
57 address_mode_w: wgpu::AddressMode::ClampToEdge,
58 mag_filter: wgpu::FilterMode::Linear,
59 min_filter: wgpu::FilterMode::Linear,
60 mipmap_filter: wgpu::MipmapFilterMode::Nearest,
61 ..Default::default()
62 });
63 let bind_group_layout = create_bind_group_layout(&device);
64 let pipeline = create_pipeline(&device, &bind_group_layout, color_format);
65 let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
66 label: Some("bone-render:chrome-text-uniform"),
67 size: FRAME_SIZE,
68 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
69 mapped_at_creation: false,
70 });
71 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
72 label: Some("bone-render:chrome-text-bg"),
73 layout: &bind_group_layout,
74 entries: &[
75 wgpu::BindGroupEntry {
76 binding: 0,
77 resource: uniform_buffer.as_entire_binding(),
78 },
79 wgpu::BindGroupEntry {
80 binding: 1,
81 resource: wgpu::BindingResource::TextureView(&atlas_view),
82 },
83 wgpu::BindGroupEntry {
84 binding: 2,
85 resource: wgpu::BindingResource::Sampler(&sampler),
86 },
87 ],
88 });
89 let instance_buffer = create_instance_buffer(&device, INITIAL_INSTANCE_CAP);
90 Self {
91 device,
92 queue,
93 pipeline,
94 uniform_buffer,
95 bind_group,
96 atlas,
97 atlas_extent,
98 atlas_version: None,
99 instance_buffer,
100 instance_capacity: INITIAL_INSTANCE_CAP,
101 }
102 }
103
104 pub fn upload(
105 &mut self,
106 viewport_px: [f32; 2],
107 atlas_pixels: &[u8],
108 atlas_version: u64,
109 instances: &[SdfGlyphInstance],
110 ) {
111 if self.atlas_version != Some(atlas_version) {
112 self.upload_atlas(atlas_pixels);
113 self.atlas_version = Some(atlas_version);
114 }
115 let frame = Frame {
116 viewport_px,
117 _pad: [0.0, 0.0],
118 };
119 self.queue
120 .write_buffer(&self.uniform_buffer, 0, bytemuck::bytes_of(&frame));
121 if instances.is_empty() {
122 return;
123 }
124 let needed = instances.len() as u64;
125 if needed > self.instance_capacity {
126 let new_cap = needed.next_power_of_two().max(self.instance_capacity * 2);
127 self.instance_buffer = create_instance_buffer(&self.device, new_cap);
128 self.instance_capacity = new_cap;
129 }
130 self.queue
131 .write_buffer(&self.instance_buffer, 0, bytemuck::cast_slice(instances));
132 }
133
134 pub fn draw_range(
135 &self,
136 encoder: &mut wgpu::CommandEncoder,
137 color_view: &wgpu::TextureView,
138 range: core::ops::Range<u32>,
139 ) {
140 if range.start >= range.end {
141 return;
142 }
143 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
144 label: Some("bone-render:chrome-text-pass"),
145 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
146 view: color_view,
147 resolve_target: None,
148 depth_slice: None,
149 ops: wgpu::Operations {
150 load: wgpu::LoadOp::Load,
151 store: wgpu::StoreOp::Store,
152 },
153 })],
154 depth_stencil_attachment: None,
155 timestamp_writes: None,
156 occlusion_query_set: None,
157 multiview_mask: None,
158 });
159 pass.set_pipeline(&self.pipeline);
160 pass.set_bind_group(0, &self.bind_group, &[]);
161 let start_bytes = u64::from(range.start) * INSTANCE_STRIDE;
162 let end_bytes = u64::from(range.end) * INSTANCE_STRIDE;
163 pass.set_vertex_buffer(0, self.instance_buffer.slice(start_bytes..end_bytes));
164 pass.draw(0..6, 0..(range.end - range.start));
165 }
166
167 fn upload_atlas(&self, pixels: &[u8]) {
168 let bytes_per_row = self.atlas_extent;
169 self.queue.write_texture(
170 wgpu::TexelCopyTextureInfo {
171 texture: &self.atlas,
172 mip_level: 0,
173 origin: wgpu::Origin3d::ZERO,
174 aspect: wgpu::TextureAspect::All,
175 },
176 pixels,
177 wgpu::TexelCopyBufferLayout {
178 offset: 0,
179 bytes_per_row: Some(bytes_per_row),
180 rows_per_image: Some(self.atlas_extent),
181 },
182 wgpu::Extent3d {
183 width: self.atlas_extent,
184 height: self.atlas_extent,
185 depth_or_array_layers: 1,
186 },
187 );
188 }
189}
190
191impl core::fmt::Debug for ChromeTextPipeline {
192 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
193 f.debug_struct("ChromeTextPipeline")
194 .field("atlas_extent", &self.atlas_extent)
195 .field("atlas_version", &self.atlas_version)
196 .field("instance_capacity", &self.instance_capacity)
197 .finish_non_exhaustive()
198 }
199}
200
201fn create_atlas_texture(device: &wgpu::Device, extent: u32) -> wgpu::Texture {
202 device.create_texture(&wgpu::TextureDescriptor {
203 label: Some("bone-render:chrome-text-atlas"),
204 size: wgpu::Extent3d {
205 width: extent,
206 height: extent,
207 depth_or_array_layers: 1,
208 },
209 mip_level_count: 1,
210 sample_count: 1,
211 dimension: wgpu::TextureDimension::D2,
212 format: ATLAS_FORMAT,
213 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
214 view_formats: &[],
215 })
216}
217
218fn create_bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout {
219 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
220 label: Some("bone-render:chrome-text-bgl"),
221 entries: &[
222 wgpu::BindGroupLayoutEntry {
223 binding: 0,
224 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
225 ty: wgpu::BindingType::Buffer {
226 ty: wgpu::BufferBindingType::Uniform,
227 has_dynamic_offset: false,
228 min_binding_size: wgpu::BufferSize::new(FRAME_SIZE),
229 },
230 count: None,
231 },
232 wgpu::BindGroupLayoutEntry {
233 binding: 1,
234 visibility: wgpu::ShaderStages::FRAGMENT,
235 ty: wgpu::BindingType::Texture {
236 sample_type: wgpu::TextureSampleType::Float { filterable: true },
237 view_dimension: wgpu::TextureViewDimension::D2,
238 multisampled: false,
239 },
240 count: None,
241 },
242 wgpu::BindGroupLayoutEntry {
243 binding: 2,
244 visibility: wgpu::ShaderStages::FRAGMENT,
245 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
246 count: None,
247 },
248 ],
249 })
250}
251
252fn create_pipeline(
253 device: &wgpu::Device,
254 bind_group_layout: &wgpu::BindGroupLayout,
255 color_format: wgpu::TextureFormat,
256) -> wgpu::RenderPipeline {
257 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
258 label: Some("bone-render:chrome-text-shader"),
259 source: wgpu::ShaderSource::Wgsl(include_str!("chrome_text.wgsl").into()),
260 });
261 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
262 label: Some("bone-render:chrome-text-layout"),
263 bind_group_layouts: &[Some(bind_group_layout)],
264 immediate_size: 0,
265 });
266 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
267 label: Some("bone-render:chrome-text-pipeline"),
268 layout: Some(&pipeline_layout),
269 vertex: wgpu::VertexState {
270 module: &shader,
271 entry_point: Some("vs"),
272 compilation_options: wgpu::PipelineCompilationOptions::default(),
273 buffers: &[wgpu::VertexBufferLayout {
274 array_stride: INSTANCE_STRIDE,
275 step_mode: wgpu::VertexStepMode::Instance,
276 attributes: &INSTANCE_ATTRS,
277 }],
278 },
279 fragment: Some(wgpu::FragmentState {
280 module: &shader,
281 entry_point: Some("fs"),
282 compilation_options: wgpu::PipelineCompilationOptions::default(),
283 targets: &[Some(wgpu::ColorTargetState {
284 format: color_format,
285 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
286 write_mask: wgpu::ColorWrites::ALL,
287 })],
288 }),
289 primitive: wgpu::PrimitiveState {
290 topology: wgpu::PrimitiveTopology::TriangleList,
291 strip_index_format: None,
292 front_face: wgpu::FrontFace::Ccw,
293 cull_mode: None,
294 polygon_mode: wgpu::PolygonMode::Fill,
295 conservative: false,
296 unclipped_depth: false,
297 },
298 depth_stencil: None,
299 multisample: wgpu::MultisampleState::default(),
300 multiview_mask: None,
301 cache: None,
302 })
303}
304
305fn create_instance_buffer(device: &wgpu::Device, capacity: u64) -> wgpu::Buffer {
306 device.create_buffer(&wgpu::BufferDescriptor {
307 label: Some("bone-render:chrome-text-instances"),
308 size: capacity * INSTANCE_STRIDE,
309 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
310 mapped_at_creation: false,
311 })
312}