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