1use valo_dl::DisplayList;
2use valo_geometry::Color;
3
4use crate::contours::ContourCache;
5use crate::glyphs::GlyphStore;
6use crate::gpu_timer::GpuTimer;
7use crate::host_buffer::HostBuffer;
8use crate::images::ImageStore;
9use crate::pipelines::PipelineCache;
10use crate::plan::{FramePlan, PassColor, PlannedPass, Planner};
11use crate::pool::TargetPool;
12
13pub struct RenderTarget<'a> {
18 pub view: &'a wgpu::TextureView,
20 pub texture: &'a wgpu::Texture,
22 pub format: wgpu::TextureFormat,
24 pub size: [u32; 2],
26 pub clear: Option<Color>,
28}
29
30#[derive(Clone, Copy, Debug, Default)]
32pub struct RenderStats {
33 pub ops: u32,
35 pub draws: u32,
37 pub clips: u32,
39 pub culled: u32,
41 pub layers_rendered: u32,
43 pub layers_elided: u32,
45 pub snapshots: u32,
47 pub backdrops: u32,
49 pub shared_backdrops: u32,
51 pub filter_passes: u32,
53 pub text_tiers: [u32; 3],
55 pub glyph_rasters: u32,
57 pub raster_quads: u32,
59 pub raster_fills: u32,
61 pub atlas_gcs: u32,
63 pub held_rasters: u32,
65 pub opaque_reordered: u32,
67 pub gpu_ms: f32,
71 pub blocks_created: u32,
73 pub cpu_ms: f32,
75 pub draw_calls: u32,
80 pub render_passes: u32,
82 pub pipeline_switches: u32,
84 pub vertex_bytes: u64,
86 pub uniform_bytes: u64,
88 pub plan_ms: f32,
90 pub encode_ms: f32,
92}
93
94pub struct RendererCore {
100 device: wgpu::Device,
101 queue: wgpu::Queue,
102 host: HostBuffer,
103 pipelines: PipelineCache,
104 images: ImageStore,
105 pool: TargetPool,
106 glyphs: GlyphStore,
107 contours: ContourCache,
108 ramps: crate::ramps::RampCache,
109 rasters: crate::raster::ListRasterCache,
110 sampler: wgpu::Sampler,
113 timer: GpuTimer,
114}
115
116impl RendererCore {
117 pub fn new(device: wgpu::Device, queue: wgpu::Queue) -> Self {
119 let host = HostBuffer::new(&device);
120 let pipelines = PipelineCache::new(&device, host.bind_group_layout());
121 let images = ImageStore::new(&device, &queue);
122 let pool = TargetPool::new(&device);
123 let glyphs = GlyphStore::new(&device, &queue);
124 let timer = GpuTimer::new(&device, &queue);
125 let sampler = crate::plan::linear_sampler(&device);
126 Self {
127 device,
128 queue,
129 host,
130 pipelines,
131 images,
132 pool,
133 glyphs,
134 contours: ContourCache::new(),
135 ramps: crate::ramps::RampCache::new(),
136 rasters: crate::raster::ListRasterCache::new(),
137 sampler,
138 timer,
139 }
140 }
141
142 pub fn images(&mut self) -> &mut ImageStore {
144 &mut self.images
145 }
146
147 pub fn device(&self) -> &wgpu::Device {
149 &self.device
150 }
151
152 pub fn set_text_tiers(&mut self, tiers: crate::TextTiers) {
158 self.glyphs.tiers = tiers;
159 }
160
161 pub fn set_hide_missing_glyphs(&mut self, hide: bool) {
167 self.glyphs.set_hide_missing_glyphs(hide);
168 }
169
170 pub fn set_text_raster_hold(&mut self, held: bool) {
176 self.glyphs.set_text_raster_hold(held);
177 }
178
179 pub fn set_raster_hold(&mut self, held: bool) {
184 self.rasters.set_hold(held);
185 }
186
187 pub fn render(&mut self, dl: &DisplayList, target: &RenderTarget) -> RenderStats {
191 #[cfg(feature = "trace")]
192 let _span = tracing::info_span!("valo.render", draws = dl.draw_count()).entered();
193 let t0 = web_time::Instant::now();
194 let blocks_before = self.host.blocks_created;
195
196 self.host.begin_frame();
197 let plan = self.plan(dl, target);
198 let t_planned = web_time::Instant::now();
199
200 let mut stats = plan.stats;
201 self.glyphs.flush_uploads();
202 (stats.uniform_bytes, stats.vertex_bytes) = self.upload_and_compile(&plan);
203 self.encode_and_submit(&plan, target, &mut stats);
204 (stats.glyph_rasters, stats.atlas_gcs, stats.held_rasters) = self.glyphs.frame_counters();
205 self.pool.end_frame();
206 self.images.end_frame();
207 self.contours.end_frame();
208 self.glyphs.end_frame();
209 self.ramps.end_frame();
210 self.rasters.end_frame();
211
212 stats.render_passes = plan.passes.len() as u32;
213 stats.blocks_created = (self.host.blocks_created - blocks_before) as u32;
214 stats.plan_ms = (t_planned - t0).as_secs_f32() * 1000.0;
215 stats.encode_ms = t_planned.elapsed().as_secs_f32() * 1000.0;
216 stats.cpu_ms = t0.elapsed().as_secs_f32() * 1000.0;
217 stats.gpu_ms = self.timer.latest_ms(&self.device);
218 stats
219 }
220
221 pub fn memory_report(&self) -> crate::MemoryReport {
225 crate::MemoryReport {
226 images: self.images.report(),
227 atlas: self.glyphs.report_atlas(),
228 targets: self.pool.report(),
229 host_buffer: self.host.report(),
230 contours: self.contours.report(),
231 glyph_paths: self.glyphs.report_paths(),
232 ramps: self.ramps.report(),
233 raster_cache: self.rasters.report(),
234 wgpu: crate::report::wgpu_counters(&self.device),
235 }
236 }
237
238 fn plan(&mut self, dl: &DisplayList, target: &RenderTarget) -> FramePlan {
239 #[cfg(feature = "trace")]
240 let _span = tracing::info_span!("valo.plan").entered();
241 let Self {
244 device,
245 queue,
246 host,
247 images,
248 pool,
249 pipelines,
250 glyphs,
251 contours,
252 ramps,
253 rasters,
254 sampler,
255 ..
256 } = self;
257 Planner::new(
258 device, queue, host, images, pool, pipelines, glyphs, contours, ramps, rasters,
259 sampler, target, dl,
260 )
261 .run(dl)
262 }
263
264 fn upload_and_compile(&mut self, plan: &FramePlan) -> (u64, u64) {
265 let bytes = self.host.flush(&self.queue);
266 for pass in &plan.passes {
267 for step in &pass.steps {
268 self.pipelines.ensure(&self.device, step.key);
269 }
270 }
271 bytes
272 }
273
274 fn encode_and_submit(
275 &mut self,
276 plan: &FramePlan,
277 target: &RenderTarget,
278 stats: &mut RenderStats,
279 ) {
280 #[cfg(feature = "trace")]
281 let _span = tracing::info_span!("valo.encode", passes = plan.passes.len()).entered();
282 let mut encoder = self
283 .device
284 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
285 label: Some("valo.frame"),
286 });
287 for (index, pass) in plan.passes.iter().enumerate() {
288 self.encode_copies(&mut encoder, pass);
289 let timing = self.timer.pass_writes(index, plan.passes.len());
290 self.encode_pass(&mut encoder, pass, target, timing, stats);
291 }
292 self.timer.end_frame(&mut encoder);
293 self.queue.submit(std::iter::once(encoder.finish()));
294 self.timer.after_submit();
295 }
296
297 fn encode_copies(&self, encoder: &mut wgpu::CommandEncoder, pass: &PlannedPass) {
301 for copy in &pass.pre_copies {
302 let origin = wgpu::Origin3d {
303 x: copy.origin[0],
304 y: copy.origin[1],
305 z: 0,
306 };
307 encoder.copy_texture_to_texture(
308 copy_at(©.src, origin),
309 copy_at(©.dst, origin),
310 wgpu::Extent3d {
311 width: copy.size[0],
312 height: copy.size[1],
313 depth_or_array_layers: 1,
314 },
315 );
316 }
317 }
318
319 fn encode_pass(
320 &self,
321 encoder: &mut wgpu::CommandEncoder,
322 pass: &PlannedPass,
323 target: &RenderTarget,
324 timing: Option<wgpu::RenderPassTimestampWrites>,
325 stats: &mut RenderStats,
326 ) {
327 let color = match &pass.color {
328 PassColor::Main { msaa } => color_attachment(msaa, Some(target.view), pass),
329 PassColor::Layer { msaa, resolve } => color_attachment(msaa, Some(resolve), pass),
330 PassColor::Filter { view } => color_attachment(view, None, pass),
331 };
332 let color_attachments = [Some(color)];
333 let mut rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
334 label: Some("valo.pass"),
335 color_attachments: &color_attachments,
336 depth_stencil_attachment: pass
337 .depth
338 .as_ref()
339 .map(|depth| depth_attachment(depth, pass.clear_depth, pass.store)),
340 timestamp_writes: timing,
341 occlusion_query_set: None,
342 multiview_mask: None,
343 });
344 rp.set_stencil_reference(0);
345 let mut bound = None;
346 for step in &pass.steps {
347 if bound != Some(step.key) {
348 rp.set_pipeline(self.pipelines.get(&step.key));
349 bound = Some(step.key);
350 stats.pipeline_switches += 1;
351 }
352 stats.draw_calls += 1;
353 rp.set_bind_group(
354 0,
355 self.host.bind_group(step.uniforms.block),
356 &[step.uniforms.offset],
357 );
358 if let Some(texture) = &step.texture {
359 rp.set_bind_group(1, texture, &[]);
360 }
361 match step.mesh {
362 None => rp.draw(0..6, 0..1),
363 Some((slot, vertex_count)) => {
364 let buffer = self.host.vertex_buffer(slot.block);
365 rp.set_vertex_buffer(0, buffer.slice(slot.offset..slot.offset + slot.bytes));
366 rp.draw(0..vertex_count, 0..1);
367 }
368 }
369 }
370 }
371}
372
373fn copy_at(texture: &wgpu::Texture, origin: wgpu::Origin3d) -> wgpu::TexelCopyTextureInfo<'_> {
374 wgpu::TexelCopyTextureInfo {
375 texture,
376 mip_level: 0,
377 origin,
378 aspect: wgpu::TextureAspect::All,
379 }
380}
381
382fn color_attachment<'a>(
387 view: &'a wgpu::TextureView,
388 resolve: Option<&'a wgpu::TextureView>,
389 pass: &PlannedPass,
390) -> wgpu::RenderPassColorAttachment<'a> {
391 wgpu::RenderPassColorAttachment {
392 view,
393 depth_slice: None,
394 resolve_target: resolve,
395 ops: wgpu::Operations {
396 load: match pass.clear {
397 Some(c) => wgpu::LoadOp::Clear(wgpu::Color {
398 r: (c.r * c.a) as f64,
399 g: (c.g * c.a) as f64,
400 b: (c.b * c.a) as f64,
401 a: c.a as f64,
402 }),
403 None => wgpu::LoadOp::Load,
404 },
405 store: store_op(pass.store),
406 },
407 }
408}
409
410fn depth_attachment(
413 view: &wgpu::TextureView,
414 clear: bool,
415 store: bool,
416) -> wgpu::RenderPassDepthStencilAttachment<'_> {
417 wgpu::RenderPassDepthStencilAttachment {
418 view,
419 depth_ops: Some(wgpu::Operations {
420 load: if clear {
421 wgpu::LoadOp::Clear(0.0)
422 } else {
423 wgpu::LoadOp::Load
424 },
425 store: store_op(store),
426 }),
427 stencil_ops: Some(wgpu::Operations {
428 load: if clear {
429 wgpu::LoadOp::Clear(0)
430 } else {
431 wgpu::LoadOp::Load
432 },
433 store: store_op(store),
434 }),
435 }
436}
437
438fn store_op(store: bool) -> wgpu::StoreOp {
439 if store {
440 wgpu::StoreOp::Store
441 } else {
442 wgpu::StoreOp::Discard
443 }
444}