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