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