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