1// Doing `if let` gives you nice scoping with passes/encoders
2#![allow(irrefutable_let_patterns)]
3
4use super::{BladeBelt, BladeBeltDescriptor};
5use crate::{
6 AtlasTextureKind, AtlasTile, BladeAtlas, Bounds, ContentMask, Hsla, MonochromeSprite, Path,
7 PathId, PathVertex, PolychromeSprite, PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow,
8 Underline, PATH_TEXTURE_FORMAT,
9};
10use bytemuck::{Pod, Zeroable};
11use collections::HashMap;
12
13use blade_graphics as gpu;
14use std::{mem, sync::Arc};
15
16const SURFACE_FRAME_COUNT: u32 = 3;
17const MAX_FRAME_TIME_MS: u32 = 1000;
18
19#[repr(C)]
20#[derive(Clone, Copy, Pod, Zeroable)]
21struct GlobalParams {
22 viewport_size: [f32; 2],
23 pad: [u32; 2],
24}
25
26#[derive(blade_macros::ShaderData)]
27struct ShaderQuadsData {
28 globals: GlobalParams,
29 b_quads: gpu::BufferPiece,
30}
31
32#[derive(blade_macros::ShaderData)]
33struct ShaderShadowsData {
34 globals: GlobalParams,
35 b_shadows: gpu::BufferPiece,
36}
37
38#[derive(blade_macros::ShaderData)]
39struct ShaderPathRasterizationData {
40 globals: GlobalParams,
41 b_path_vertices: gpu::BufferPiece,
42}
43
44#[derive(blade_macros::ShaderData)]
45struct ShaderPathsData {
46 globals: GlobalParams,
47 t_sprite: gpu::TextureView,
48 s_sprite: gpu::Sampler,
49 b_path_sprites: gpu::BufferPiece,
50}
51
52#[derive(blade_macros::ShaderData)]
53struct ShaderUnderlinesData {
54 globals: GlobalParams,
55 b_underlines: gpu::BufferPiece,
56}
57
58#[derive(blade_macros::ShaderData)]
59struct ShaderMonoSpritesData {
60 globals: GlobalParams,
61 t_sprite: gpu::TextureView,
62 s_sprite: gpu::Sampler,
63 b_mono_sprites: gpu::BufferPiece,
64}
65
66#[derive(blade_macros::ShaderData)]
67struct ShaderPolySpritesData {
68 globals: GlobalParams,
69 t_sprite: gpu::TextureView,
70 s_sprite: gpu::Sampler,
71 b_poly_sprites: gpu::BufferPiece,
72}
73
74#[derive(Clone, Debug, Eq, PartialEq)]
75#[repr(C)]
76struct PathSprite {
77 bounds: Bounds<ScaledPixels>,
78 color: Hsla,
79 tile: AtlasTile,
80}
81
82struct BladePipelines {
83 quads: gpu::RenderPipeline,
84 shadows: gpu::RenderPipeline,
85 path_rasterization: gpu::RenderPipeline,
86 paths: gpu::RenderPipeline,
87 underlines: gpu::RenderPipeline,
88 mono_sprites: gpu::RenderPipeline,
89 poly_sprites: gpu::RenderPipeline,
90}
91
92impl BladePipelines {
93 fn new(gpu: &gpu::Context, surface_format: gpu::TextureFormat) -> Self {
94 use gpu::ShaderData as _;
95
96 let shader = gpu.create_shader(gpu::ShaderDesc {
97 source: include_str!("shaders.wgsl"),
98 });
99 shader.check_struct_size::<Quad>();
100 shader.check_struct_size::<Shadow>();
101 assert_eq!(
102 mem::size_of::<PathVertex<ScaledPixels>>(),
103 shader.get_struct_size("PathVertex") as usize,
104 );
105 shader.check_struct_size::<PathSprite>();
106 shader.check_struct_size::<Underline>();
107 shader.check_struct_size::<MonochromeSprite>();
108 shader.check_struct_size::<PolychromeSprite>();
109
110 Self {
111 quads: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
112 name: "quads",
113 data_layouts: &[&ShaderQuadsData::layout()],
114 vertex: shader.at("vs_quad"),
115 primitive: gpu::PrimitiveState {
116 topology: gpu::PrimitiveTopology::TriangleStrip,
117 ..Default::default()
118 },
119 depth_stencil: None,
120 fragment: shader.at("fs_quad"),
121 color_targets: &[gpu::ColorTargetState {
122 format: surface_format,
123 blend: Some(gpu::BlendState::ALPHA_BLENDING),
124 write_mask: gpu::ColorWrites::default(),
125 }],
126 }),
127 shadows: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
128 name: "shadows",
129 data_layouts: &[&ShaderShadowsData::layout()],
130 vertex: shader.at("vs_shadow"),
131 primitive: gpu::PrimitiveState {
132 topology: gpu::PrimitiveTopology::TriangleStrip,
133 ..Default::default()
134 },
135 depth_stencil: None,
136 fragment: shader.at("fs_shadow"),
137 color_targets: &[gpu::ColorTargetState {
138 format: surface_format,
139 blend: Some(gpu::BlendState::ALPHA_BLENDING),
140 write_mask: gpu::ColorWrites::default(),
141 }],
142 }),
143 path_rasterization: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
144 name: "path_rasterization",
145 data_layouts: &[&ShaderPathRasterizationData::layout()],
146 vertex: shader.at("vs_path_rasterization"),
147 primitive: gpu::PrimitiveState {
148 topology: gpu::PrimitiveTopology::TriangleStrip,
149 ..Default::default()
150 },
151 depth_stencil: None,
152 fragment: shader.at("fs_path_rasterization"),
153 color_targets: &[gpu::ColorTargetState {
154 format: PATH_TEXTURE_FORMAT,
155 blend: Some(gpu::BlendState::ALPHA_BLENDING),
156 write_mask: gpu::ColorWrites::default(),
157 }],
158 }),
159 paths: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
160 name: "paths",
161 data_layouts: &[&ShaderPathsData::layout()],
162 vertex: shader.at("vs_path"),
163 primitive: gpu::PrimitiveState {
164 topology: gpu::PrimitiveTopology::TriangleStrip,
165 ..Default::default()
166 },
167 depth_stencil: None,
168 fragment: shader.at("fs_path"),
169 color_targets: &[gpu::ColorTargetState {
170 format: surface_format,
171 blend: Some(gpu::BlendState::ALPHA_BLENDING),
172 write_mask: gpu::ColorWrites::default(),
173 }],
174 }),
175 underlines: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
176 name: "underlines",
177 data_layouts: &[&ShaderUnderlinesData::layout()],
178 vertex: shader.at("vs_underline"),
179 primitive: gpu::PrimitiveState {
180 topology: gpu::PrimitiveTopology::TriangleStrip,
181 ..Default::default()
182 },
183 depth_stencil: None,
184 fragment: shader.at("fs_underline"),
185 color_targets: &[gpu::ColorTargetState {
186 format: surface_format,
187 blend: Some(gpu::BlendState::ALPHA_BLENDING),
188 write_mask: gpu::ColorWrites::default(),
189 }],
190 }),
191 mono_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
192 name: "mono-sprites",
193 data_layouts: &[&ShaderMonoSpritesData::layout()],
194 vertex: shader.at("vs_mono_sprite"),
195 primitive: gpu::PrimitiveState {
196 topology: gpu::PrimitiveTopology::TriangleStrip,
197 ..Default::default()
198 },
199 depth_stencil: None,
200 fragment: shader.at("fs_mono_sprite"),
201 color_targets: &[gpu::ColorTargetState {
202 format: surface_format,
203 blend: Some(gpu::BlendState::ALPHA_BLENDING),
204 write_mask: gpu::ColorWrites::default(),
205 }],
206 }),
207 poly_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
208 name: "poly-sprites",
209 data_layouts: &[&ShaderPolySpritesData::layout()],
210 vertex: shader.at("vs_poly_sprite"),
211 primitive: gpu::PrimitiveState {
212 topology: gpu::PrimitiveTopology::TriangleStrip,
213 ..Default::default()
214 },
215 depth_stencil: None,
216 fragment: shader.at("fs_poly_sprite"),
217 color_targets: &[gpu::ColorTargetState {
218 format: surface_format,
219 blend: Some(gpu::BlendState::ALPHA_BLENDING),
220 write_mask: gpu::ColorWrites::default(),
221 }],
222 }),
223 }
224 }
225}
226
227pub struct BladeRenderer {
228 gpu: Arc<gpu::Context>,
229 command_encoder: gpu::CommandEncoder,
230 last_sync_point: Option<gpu::SyncPoint>,
231 pipelines: BladePipelines,
232 instance_belt: BladeBelt,
233 viewport_size: gpu::Extent,
234 path_tiles: HashMap<PathId, AtlasTile>,
235 atlas: Arc<BladeAtlas>,
236 atlas_sampler: gpu::Sampler,
237}
238
239impl BladeRenderer {
240 pub fn new(gpu: Arc<gpu::Context>, size: gpu::Extent) -> Self {
241 let surface_format = gpu.resize(gpu::SurfaceConfig {
242 size,
243 usage: gpu::TextureUsage::TARGET,
244 frame_count: SURFACE_FRAME_COUNT,
245 });
246 let command_encoder = gpu.create_command_encoder(gpu::CommandEncoderDesc {
247 name: "main",
248 buffer_count: 2,
249 });
250 let pipelines = BladePipelines::new(&gpu, surface_format);
251 let instance_belt = BladeBelt::new(BladeBeltDescriptor {
252 memory: gpu::Memory::Shared,
253 min_chunk_size: 0x1000,
254 });
255 let atlas = Arc::new(BladeAtlas::new(&gpu));
256 let atlas_sampler = gpu.create_sampler(gpu::SamplerDesc {
257 name: "atlas",
258 mag_filter: gpu::FilterMode::Linear,
259 min_filter: gpu::FilterMode::Linear,
260 ..Default::default()
261 });
262
263 Self {
264 gpu,
265 command_encoder,
266 last_sync_point: None,
267 pipelines,
268 instance_belt,
269 viewport_size: size,
270 path_tiles: HashMap::default(),
271 atlas,
272 atlas_sampler,
273 }
274 }
275
276 fn wait_for_gpu(&mut self) {
277 if let Some(last_sp) = self.last_sync_point.take() {
278 if !self.gpu.wait_for(&last_sp, MAX_FRAME_TIME_MS) {
279 panic!("GPU hung");
280 }
281 }
282 }
283
284 pub fn destroy(&mut self) {
285 self.wait_for_gpu();
286 self.atlas.destroy();
287 self.instance_belt.destroy(&self.gpu);
288 self.gpu.destroy_command_encoder(&mut self.command_encoder);
289 }
290
291 pub fn resize(&mut self, size: gpu::Extent) {
292 self.wait_for_gpu();
293 self.gpu.resize(gpu::SurfaceConfig {
294 size,
295 usage: gpu::TextureUsage::TARGET,
296 frame_count: SURFACE_FRAME_COUNT,
297 });
298 self.viewport_size = size;
299 }
300
301 pub fn atlas(&self) -> &Arc<BladeAtlas> {
302 &self.atlas
303 }
304
305 fn rasterize_paths(&mut self, paths: &[Path<ScaledPixels>]) {
306 self.path_tiles.clear();
307 let mut vertices_by_texture_id = HashMap::default();
308
309 for path in paths {
310 let clipped_bounds = path.bounds.intersect(&path.content_mask.bounds);
311 let tile = self
312 .atlas
313 .allocate(clipped_bounds.size.map(Into::into), AtlasTextureKind::Path);
314 vertices_by_texture_id
315 .entry(tile.texture_id)
316 .or_insert(Vec::new())
317 .extend(path.vertices.iter().map(|vertex| PathVertex {
318 xy_position: vertex.xy_position - clipped_bounds.origin
319 + tile.bounds.origin.map(Into::into),
320 st_position: vertex.st_position,
321 content_mask: ContentMask {
322 bounds: tile.bounds.map(Into::into),
323 },
324 }));
325 self.path_tiles.insert(path.id, tile);
326 }
327
328 for (texture_id, vertices) in vertices_by_texture_id {
329 let tex_info = self.atlas.get_texture_info(texture_id);
330 let globals = GlobalParams {
331 viewport_size: [tex_info.size.width as f32, tex_info.size.height as f32],
332 pad: [0; 2],
333 };
334
335 let vertex_buf = self.instance_belt.alloc_data(&vertices, &self.gpu);
336 let mut pass = self.command_encoder.render(gpu::RenderTargetSet {
337 colors: &[gpu::RenderTarget {
338 view: tex_info.raw_view.unwrap(),
339 init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
340 finish_op: gpu::FinishOp::Store,
341 }],
342 depth_stencil: None,
343 });
344
345 let mut encoder = pass.with(&self.pipelines.path_rasterization);
346 encoder.bind(
347 0,
348 &ShaderPathRasterizationData {
349 globals,
350 b_path_vertices: vertex_buf,
351 },
352 );
353 encoder.draw(0, vertices.len() as u32, 0, 1);
354 }
355 }
356
357 pub fn draw(&mut self, scene: &Scene) {
358 let frame = self.gpu.acquire_frame();
359 self.command_encoder.start();
360 self.command_encoder.init_texture(frame.texture());
361
362 self.atlas.before_frame(&mut self.command_encoder);
363 self.rasterize_paths(scene.paths());
364
365 let globals = GlobalParams {
366 viewport_size: [
367 self.viewport_size.width as f32,
368 self.viewport_size.height as f32,
369 ],
370 pad: [0; 2],
371 };
372
373 if let mut pass = self.command_encoder.render(gpu::RenderTargetSet {
374 colors: &[gpu::RenderTarget {
375 view: frame.texture_view(),
376 init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
377 finish_op: gpu::FinishOp::Store,
378 }],
379 depth_stencil: None,
380 }) {
381 for batch in scene.batches() {
382 match batch {
383 PrimitiveBatch::Quads(quads) => {
384 let instance_buf = self.instance_belt.alloc_data(quads, &self.gpu);
385 let mut encoder = pass.with(&self.pipelines.quads);
386 encoder.bind(
387 0,
388 &ShaderQuadsData {
389 globals,
390 b_quads: instance_buf,
391 },
392 );
393 encoder.draw(0, 4, 0, quads.len() as u32);
394 }
395 PrimitiveBatch::Shadows(shadows) => {
396 let instance_buf = self.instance_belt.alloc_data(shadows, &self.gpu);
397 let mut encoder = pass.with(&self.pipelines.shadows);
398 encoder.bind(
399 0,
400 &ShaderShadowsData {
401 globals,
402 b_shadows: instance_buf,
403 },
404 );
405 encoder.draw(0, 4, 0, shadows.len() as u32);
406 }
407 PrimitiveBatch::Paths(paths) => {
408 let mut encoder = pass.with(&self.pipelines.paths);
409 //TODO: group by texture ID
410 for path in paths {
411 let tile = &self.path_tiles[&path.id];
412 let tex_info = self.atlas.get_texture_info(tile.texture_id);
413 let origin = path.bounds.intersect(&path.content_mask.bounds).origin;
414 let sprites = [PathSprite {
415 bounds: Bounds {
416 origin: origin.map(|p| p.floor()),
417 size: tile.bounds.size.map(Into::into),
418 },
419 color: path.color,
420 tile: (*tile).clone(),
421 }];
422
423 let instance_buf = self.instance_belt.alloc_data(&sprites, &self.gpu);
424 encoder.bind(
425 0,
426 &ShaderPathsData {
427 globals,
428 t_sprite: tex_info.raw_view.unwrap(),
429 s_sprite: self.atlas_sampler,
430 b_path_sprites: instance_buf,
431 },
432 );
433 encoder.draw(0, 4, 0, sprites.len() as u32);
434 }
435 }
436 PrimitiveBatch::Underlines(underlines) => {
437 let instance_buf = self.instance_belt.alloc_data(underlines, &self.gpu);
438 let mut encoder = pass.with(&self.pipelines.underlines);
439 encoder.bind(
440 0,
441 &ShaderUnderlinesData {
442 globals,
443 b_underlines: instance_buf,
444 },
445 );
446 encoder.draw(0, 4, 0, underlines.len() as u32);
447 }
448 PrimitiveBatch::MonochromeSprites {
449 texture_id,
450 sprites,
451 } => {
452 let tex_info = self.atlas.get_texture_info(texture_id);
453 let instance_buf = self.instance_belt.alloc_data(&sprites, &self.gpu);
454 let mut encoder = pass.with(&self.pipelines.mono_sprites);
455 encoder.bind(
456 0,
457 &ShaderMonoSpritesData {
458 globals,
459 t_sprite: tex_info.raw_view.unwrap(),
460 s_sprite: self.atlas_sampler,
461 b_mono_sprites: instance_buf,
462 },
463 );
464 encoder.draw(0, 4, 0, sprites.len() as u32);
465 }
466 PrimitiveBatch::PolychromeSprites {
467 texture_id,
468 sprites,
469 } => {
470 let tex_info = self.atlas.get_texture_info(texture_id);
471 let instance_buf = self.instance_belt.alloc_data(&sprites, &self.gpu);
472 let mut encoder = pass.with(&self.pipelines.poly_sprites);
473 encoder.bind(
474 0,
475 &ShaderPolySpritesData {
476 globals,
477 t_sprite: tex_info.raw_view.unwrap(),
478 s_sprite: self.atlas_sampler,
479 b_poly_sprites: instance_buf,
480 },
481 );
482 encoder.draw(0, 4, 0, sprites.len() as u32);
483 }
484 PrimitiveBatch::Surfaces { .. } => {
485 unimplemented!()
486 }
487 }
488 }
489 }
490
491 self.command_encoder.present(frame);
492 let sync_point = self.gpu.submit(&mut self.command_encoder);
493
494 self.instance_belt.flush(&sync_point);
495 self.atlas.after_frame(&sync_point);
496 self.atlas.clear_textures(AtlasTextureKind::Path);
497
498 self.wait_for_gpu();
499 self.last_sync_point = Some(sync_point);
500 }
501}