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