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, ContentMask, Path, PathId, PathVertex, PrimitiveBatch,
7 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
43struct BladePipelines {
44 quads: gpu::RenderPipeline,
45 shadows: gpu::RenderPipeline,
46 path_rasterization: gpu::RenderPipeline,
47}
48
49impl BladePipelines {
50 fn new(gpu: &gpu::Context, surface_format: gpu::TextureFormat) -> Self {
51 let shader = gpu.create_shader(gpu::ShaderDesc {
52 source: include_str!("shaders.wgsl"),
53 });
54 shader.check_struct_size::<Quad>();
55 shader.check_struct_size::<Shadow>();
56 assert_eq!(
57 mem::size_of::<PathVertex<ScaledPixels>>(),
58 shader.get_struct_size("PathVertex") as usize,
59 );
60 let quads_layout = <ShaderQuadsData as gpu::ShaderData>::layout();
61 let shadows_layout = <ShaderShadowsData as gpu::ShaderData>::layout();
62 let path_rasterization_layout = <ShaderPathRasterizationData as gpu::ShaderData>::layout();
63
64 Self {
65 quads: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
66 name: "quads",
67 data_layouts: &[&quads_layout],
68 vertex: shader.at("vs_quad"),
69 primitive: gpu::PrimitiveState {
70 topology: gpu::PrimitiveTopology::TriangleStrip,
71 ..Default::default()
72 },
73 depth_stencil: None,
74 fragment: shader.at("fs_quad"),
75 color_targets: &[gpu::ColorTargetState {
76 format: surface_format,
77 blend: Some(gpu::BlendState::ALPHA_BLENDING),
78 write_mask: gpu::ColorWrites::default(),
79 }],
80 }),
81 shadows: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
82 name: "shadows",
83 data_layouts: &[&shadows_layout],
84 vertex: shader.at("vs_shadow"),
85 primitive: gpu::PrimitiveState {
86 topology: gpu::PrimitiveTopology::TriangleStrip,
87 ..Default::default()
88 },
89 depth_stencil: None,
90 fragment: shader.at("fs_shadow"),
91 color_targets: &[gpu::ColorTargetState {
92 format: surface_format,
93 blend: Some(gpu::BlendState::ALPHA_BLENDING),
94 write_mask: gpu::ColorWrites::default(),
95 }],
96 }),
97 path_rasterization: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
98 name: "path_rasterization",
99 data_layouts: &[&path_rasterization_layout],
100 vertex: shader.at("vs_path_rasterization"),
101 primitive: gpu::PrimitiveState {
102 topology: gpu::PrimitiveTopology::TriangleStrip,
103 ..Default::default()
104 },
105 depth_stencil: None,
106 fragment: shader.at("fs_path_rasterization"),
107 color_targets: &[gpu::ColorTargetState {
108 format: PATH_TEXTURE_FORMAT,
109 blend: Some(gpu::BlendState::ALPHA_BLENDING),
110 write_mask: gpu::ColorWrites::default(),
111 }],
112 }),
113 }
114 }
115}
116
117pub struct BladeRenderer {
118 gpu: Arc<gpu::Context>,
119 command_encoder: gpu::CommandEncoder,
120 last_sync_point: Option<gpu::SyncPoint>,
121 pipelines: BladePipelines,
122 instance_belt: BladeBelt,
123 viewport_size: gpu::Extent,
124 path_tiles: HashMap<PathId, AtlasTile>,
125 atlas: Arc<BladeAtlas>,
126}
127
128impl BladeRenderer {
129 pub fn new(gpu: Arc<gpu::Context>, size: gpu::Extent) -> Self {
130 let surface_format = gpu.resize(gpu::SurfaceConfig {
131 size,
132 usage: gpu::TextureUsage::TARGET,
133 frame_count: SURFACE_FRAME_COUNT,
134 });
135 let command_encoder = gpu.create_command_encoder(gpu::CommandEncoderDesc {
136 name: "main",
137 buffer_count: 2,
138 });
139 let pipelines = BladePipelines::new(&gpu, surface_format);
140 let instance_belt = BladeBelt::new(BladeBeltDescriptor {
141 memory: gpu::Memory::Shared,
142 min_chunk_size: 0x1000,
143 });
144 let atlas = Arc::new(BladeAtlas::new(&gpu));
145
146 Self {
147 gpu,
148 command_encoder,
149 last_sync_point: None,
150 pipelines,
151 instance_belt,
152 viewport_size: size,
153 path_tiles: HashMap::default(),
154 atlas,
155 }
156 }
157
158 fn wait_for_gpu(&mut self) {
159 if let Some(last_sp) = self.last_sync_point.take() {
160 if !self.gpu.wait_for(&last_sp, MAX_FRAME_TIME_MS) {
161 panic!("GPU hung");
162 }
163 }
164 }
165
166 pub fn destroy(&mut self) {
167 self.wait_for_gpu();
168 self.atlas.destroy();
169 self.instance_belt.destroy(&self.gpu);
170 self.gpu.destroy_command_encoder(&mut self.command_encoder);
171 }
172
173 pub fn resize(&mut self, size: gpu::Extent) {
174 self.wait_for_gpu();
175 self.gpu.resize(gpu::SurfaceConfig {
176 size,
177 usage: gpu::TextureUsage::TARGET,
178 frame_count: SURFACE_FRAME_COUNT,
179 });
180 self.viewport_size = size;
181 }
182
183 pub fn atlas(&self) -> &Arc<BladeAtlas> {
184 &self.atlas
185 }
186
187 fn rasterize_paths(&mut self, paths: &[Path<ScaledPixels>]) {
188 self.path_tiles.clear();
189 let mut vertices_by_texture_id = HashMap::default();
190
191 for path in paths {
192 let clipped_bounds = path.bounds.intersect(&path.content_mask.bounds);
193 let tile = self
194 .atlas
195 .allocate(clipped_bounds.size.map(Into::into), AtlasTextureKind::Path);
196 vertices_by_texture_id
197 .entry(tile.texture_id)
198 .or_insert(Vec::new())
199 .extend(path.vertices.iter().map(|vertex| PathVertex {
200 xy_position: vertex.xy_position - clipped_bounds.origin
201 + tile.bounds.origin.map(Into::into),
202 st_position: vertex.st_position,
203 content_mask: ContentMask {
204 bounds: tile.bounds.map(Into::into),
205 },
206 }));
207 self.path_tiles.insert(path.id, tile);
208 }
209
210 for (texture_id, vertices) in vertices_by_texture_id {
211 let tex_info = self.atlas.get_texture_info(texture_id);
212 let globals = GlobalParams {
213 viewport_size: [tex_info.size.width as f32, tex_info.size.height as f32],
214 pad: [0; 2],
215 };
216
217 let vertex_buf = self.instance_belt.alloc_data(&vertices, &self.gpu);
218 let mut pass = self.command_encoder.render(gpu::RenderTargetSet {
219 colors: &[gpu::RenderTarget {
220 view: tex_info.raw_view.unwrap(),
221 init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
222 finish_op: gpu::FinishOp::Store,
223 }],
224 depth_stencil: None,
225 });
226
227 let mut encoder = pass.with(&self.pipelines.path_rasterization);
228 encoder.bind(
229 0,
230 &ShaderPathRasterizationData {
231 globals,
232 b_path_vertices: vertex_buf,
233 },
234 );
235 encoder.draw(0, vertices.len() as u32, 0, 1);
236 }
237 }
238
239 pub fn draw(&mut self, scene: &Scene) {
240 let frame = self.gpu.acquire_frame();
241 self.command_encoder.start();
242 self.command_encoder.init_texture(frame.texture());
243
244 self.rasterize_paths(scene.paths());
245
246 let globals = GlobalParams {
247 viewport_size: [
248 self.viewport_size.width as f32,
249 self.viewport_size.height as f32,
250 ],
251 pad: [0; 2],
252 };
253
254 if let mut pass = self.command_encoder.render(gpu::RenderTargetSet {
255 colors: &[gpu::RenderTarget {
256 view: frame.texture_view(),
257 init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
258 finish_op: gpu::FinishOp::Store,
259 }],
260 depth_stencil: None,
261 }) {
262 for batch in scene.batches() {
263 match batch {
264 PrimitiveBatch::Quads(quads) => {
265 let instance_buf = self.instance_belt.alloc_data(quads, &self.gpu);
266 let mut encoder = pass.with(&self.pipelines.quads);
267 encoder.bind(
268 0,
269 &ShaderQuadsData {
270 globals,
271 b_quads: instance_buf,
272 },
273 );
274 encoder.draw(0, 4, 0, quads.len() as u32);
275 }
276 PrimitiveBatch::Shadows(shadows) => {
277 let instance_buf = self.instance_belt.alloc_data(shadows, &self.gpu);
278 let mut encoder = pass.with(&self.pipelines.shadows);
279 encoder.bind(
280 0,
281 &ShaderShadowsData {
282 globals,
283 b_shadows: instance_buf,
284 },
285 );
286 encoder.draw(0, 4, 0, shadows.len() as u32);
287 }
288 PrimitiveBatch::Paths(paths) => {}
289 _ => continue,
290 }
291 }
292 }
293
294 self.command_encoder.present(frame);
295 let sync_point = self.gpu.submit(&mut self.command_encoder);
296 self.instance_belt.flush(&sync_point);
297 self.wait_for_gpu();
298 self.last_sync_point = Some(sync_point);
299 }
300}