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