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 alignment: 0x40, // Vulkan `minStorageBufferOffsetAlignment` on Intel Xe
255 });
256 let atlas = Arc::new(BladeAtlas::new(&gpu));
257 let atlas_sampler = gpu.create_sampler(gpu::SamplerDesc {
258 name: "atlas",
259 mag_filter: gpu::FilterMode::Linear,
260 min_filter: gpu::FilterMode::Linear,
261 ..Default::default()
262 });
263
264 Self {
265 gpu,
266 command_encoder,
267 last_sync_point: None,
268 pipelines,
269 instance_belt,
270 viewport_size: size,
271 path_tiles: HashMap::default(),
272 atlas,
273 atlas_sampler,
274 }
275 }
276
277 fn wait_for_gpu(&mut self) {
278 if let Some(last_sp) = self.last_sync_point.take() {
279 if !self.gpu.wait_for(&last_sp, MAX_FRAME_TIME_MS) {
280 panic!("GPU hung");
281 }
282 }
283 }
284
285 pub fn destroy(&mut self) {
286 self.wait_for_gpu();
287 self.atlas.destroy();
288 self.instance_belt.destroy(&self.gpu);
289 self.gpu.destroy_command_encoder(&mut self.command_encoder);
290 }
291
292 pub fn resize(&mut self, size: gpu::Extent) {
293 self.wait_for_gpu();
294 self.gpu.resize(gpu::SurfaceConfig {
295 size,
296 usage: gpu::TextureUsage::TARGET,
297 frame_count: SURFACE_FRAME_COUNT,
298 });
299 self.viewport_size = size;
300 }
301
302 pub fn atlas(&self) -> &Arc<BladeAtlas> {
303 &self.atlas
304 }
305
306 fn rasterize_paths(&mut self, paths: &[Path<ScaledPixels>]) {
307 self.path_tiles.clear();
308 let mut vertices_by_texture_id = HashMap::default();
309
310 for path in paths {
311 let clipped_bounds = path.bounds.intersect(&path.content_mask.bounds);
312 let tile = self
313 .atlas
314 .allocate(clipped_bounds.size.map(Into::into), AtlasTextureKind::Path);
315 vertices_by_texture_id
316 .entry(tile.texture_id)
317 .or_insert(Vec::new())
318 .extend(path.vertices.iter().map(|vertex| PathVertex {
319 xy_position: vertex.xy_position - clipped_bounds.origin
320 + tile.bounds.origin.map(Into::into),
321 st_position: vertex.st_position,
322 content_mask: ContentMask {
323 bounds: tile.bounds.map(Into::into),
324 },
325 }));
326 self.path_tiles.insert(path.id, tile);
327 }
328
329 for (texture_id, vertices) in vertices_by_texture_id {
330 let tex_info = self.atlas.get_texture_info(texture_id);
331 let globals = GlobalParams {
332 viewport_size: [tex_info.size.width as f32, tex_info.size.height as f32],
333 pad: [0; 2],
334 };
335
336 let vertex_buf = self.instance_belt.alloc_data(&vertices, &self.gpu);
337 let mut pass = self.command_encoder.render(gpu::RenderTargetSet {
338 colors: &[gpu::RenderTarget {
339 view: tex_info.raw_view,
340 init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
341 finish_op: gpu::FinishOp::Store,
342 }],
343 depth_stencil: None,
344 });
345
346 let mut encoder = pass.with(&self.pipelines.path_rasterization);
347 encoder.bind(
348 0,
349 &ShaderPathRasterizationData {
350 globals,
351 b_path_vertices: vertex_buf,
352 },
353 );
354 encoder.draw(0, vertices.len() as u32, 0, 1);
355 }
356 }
357
358 pub fn draw(&mut self, scene: &Scene) {
359 let frame = self.gpu.acquire_frame();
360 self.command_encoder.start();
361 self.command_encoder.init_texture(frame.texture());
362
363 self.atlas.before_frame(&mut self.command_encoder);
364 self.rasterize_paths(scene.paths());
365
366 let globals = GlobalParams {
367 viewport_size: [
368 self.viewport_size.width as f32,
369 self.viewport_size.height as f32,
370 ],
371 pad: [0; 2],
372 };
373
374 if let mut pass = self.command_encoder.render(gpu::RenderTargetSet {
375 colors: &[gpu::RenderTarget {
376 view: frame.texture_view(),
377 init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
378 finish_op: gpu::FinishOp::Store,
379 }],
380 depth_stencil: None,
381 }) {
382 for batch in scene.batches() {
383 match batch {
384 PrimitiveBatch::Quads(quads) => {
385 let instance_buf = self.instance_belt.alloc_data(quads, &self.gpu);
386 let mut encoder = pass.with(&self.pipelines.quads);
387 encoder.bind(
388 0,
389 &ShaderQuadsData {
390 globals,
391 b_quads: instance_buf,
392 },
393 );
394 encoder.draw(0, 4, 0, quads.len() as u32);
395 }
396 PrimitiveBatch::Shadows(shadows) => {
397 let instance_buf = self.instance_belt.alloc_data(shadows, &self.gpu);
398 let mut encoder = pass.with(&self.pipelines.shadows);
399 encoder.bind(
400 0,
401 &ShaderShadowsData {
402 globals,
403 b_shadows: instance_buf,
404 },
405 );
406 encoder.draw(0, 4, 0, shadows.len() as u32);
407 }
408 PrimitiveBatch::Paths(paths) => {
409 let mut encoder = pass.with(&self.pipelines.paths);
410 //TODO: group by texture ID
411 for path in paths {
412 let tile = &self.path_tiles[&path.id];
413 let tex_info = self.atlas.get_texture_info(tile.texture_id);
414 let origin = path.bounds.intersect(&path.content_mask.bounds).origin;
415 let sprites = [PathSprite {
416 bounds: Bounds {
417 origin: origin.map(|p| p.floor()),
418 size: tile.bounds.size.map(Into::into),
419 },
420 color: path.color,
421 tile: (*tile).clone(),
422 }];
423
424 let instance_buf = self.instance_belt.alloc_data(&sprites, &self.gpu);
425 encoder.bind(
426 0,
427 &ShaderPathsData {
428 globals,
429 t_sprite: tex_info.raw_view,
430 s_sprite: self.atlas_sampler,
431 b_path_sprites: instance_buf,
432 },
433 );
434 encoder.draw(0, 4, 0, sprites.len() as u32);
435 }
436 }
437 PrimitiveBatch::Underlines(underlines) => {
438 let instance_buf = self.instance_belt.alloc_data(underlines, &self.gpu);
439 let mut encoder = pass.with(&self.pipelines.underlines);
440 encoder.bind(
441 0,
442 &ShaderUnderlinesData {
443 globals,
444 b_underlines: instance_buf,
445 },
446 );
447 encoder.draw(0, 4, 0, underlines.len() as u32);
448 }
449 PrimitiveBatch::MonochromeSprites {
450 texture_id,
451 sprites,
452 } => {
453 let tex_info = self.atlas.get_texture_info(texture_id);
454 let instance_buf = self.instance_belt.alloc_data(&sprites, &self.gpu);
455 let mut encoder = pass.with(&self.pipelines.mono_sprites);
456 encoder.bind(
457 0,
458 &ShaderMonoSpritesData {
459 globals,
460 t_sprite: tex_info.raw_view,
461 s_sprite: self.atlas_sampler,
462 b_mono_sprites: instance_buf,
463 },
464 );
465 encoder.draw(0, 4, 0, sprites.len() as u32);
466 }
467 PrimitiveBatch::PolychromeSprites {
468 texture_id,
469 sprites,
470 } => {
471 let tex_info = self.atlas.get_texture_info(texture_id);
472 let instance_buf = self.instance_belt.alloc_data(&sprites, &self.gpu);
473 let mut encoder = pass.with(&self.pipelines.poly_sprites);
474 encoder.bind(
475 0,
476 &ShaderPolySpritesData {
477 globals,
478 t_sprite: tex_info.raw_view,
479 s_sprite: self.atlas_sampler,
480 b_poly_sprites: instance_buf,
481 },
482 );
483 encoder.draw(0, 4, 0, sprites.len() as u32);
484 }
485 PrimitiveBatch::Surfaces { .. } => {
486 unimplemented!()
487 }
488 }
489 }
490 }
491
492 self.command_encoder.present(frame);
493 let sync_point = self.gpu.submit(&mut self.command_encoder);
494
495 self.instance_belt.flush(&sync_point);
496 self.atlas.after_frame(&sync_point);
497 self.atlas.clear_textures(AtlasTextureKind::Path);
498
499 self.wait_for_gpu();
500 self.last_sync_point = Some(sync_point);
501 }
502}