1use std::sync::{Arc, Mutex};
2
3use anyhow::Result;
4
5use jag_draw::{
6 ColorLinPremul,
7 Command,
8 DisplayList,
9 ExternalTextureId,
10 HitIndex,
11 Painter,
12 PassManager,
13 Rect,
14 RenderAllocator,
15 Transform2D,
16 Viewport,
17 wgpu, };
19
20use crate::canvas::{Canvas, ImageFitMode};
21
22#[allow(clippy::type_complexity)]
27pub struct CachedFrameData {
28 pub gpu_scene: jag_draw::GpuScene,
30 pub transparent_gpu_scene: jag_draw::GpuScene,
32 pub transparent_batches: Vec<jag_draw::TransparentBatch>,
34 pub glyph_draws: Vec<(
36 [f32; 2],
37 jag_draw::RasterizedGlyph,
38 jag_draw::ColorLinPremul,
39 i32,
40 )>,
41 pub svg_draws: Vec<(
43 std::path::PathBuf,
44 [f32; 2],
45 [f32; 2],
46 Option<jag_draw::SvgStyle>,
47 i32,
48 Transform2D,
49 Option<jag_draw::Rect>,
50 )>,
51 pub image_draws: Vec<(
53 std::path::PathBuf,
54 [f32; 2],
55 [f32; 2],
56 i32,
57 Option<jag_draw::Rect>,
58 )>,
59 pub external_texture_draws: Vec<jag_draw::ExtractedExternalTextureDraw>,
61 pub clear: wgpu::Color,
63 pub direct: bool,
65 pub width: u32,
67 pub height: u32,
68 pub scroll_at_build: (f32, f32),
70 pub generation_at_build: u64,
72 pub viewport_size: (u32, u32),
74 pub hit_index: jag_draw::HitIndex,
76}
77
78fn apply_transform_to_point(point: [f32; 2], transform: Transform2D) -> [f32; 2] {
80 let [a, b, c, d, e, f] = transform.m;
81 let x = point[0];
82 let y = point[1];
83 [a * x + c * y + e, b * x + d * y + f]
84}
85
86static LAST_RAW_IMAGE_RECT: Mutex<Option<(f32, f32, f32, f32)>> = Mutex::new(None);
88
89fn set_last_raw_image_rect(x: f32, y: f32, w: f32, h: f32) {
91 if let Ok(mut guard) = LAST_RAW_IMAGE_RECT.lock() {
92 *guard = Some((x, y, w, h));
93 }
94}
95
96pub fn get_last_raw_image_rect() -> Option<(f32, f32, f32, f32)> {
98 if let Ok(guard) = LAST_RAW_IMAGE_RECT.lock() {
99 *guard
100 } else {
101 None
102 }
103}
104
105pub type OverlayCallback = Box<
108 dyn FnMut(
109 &mut PassManager,
110 &mut wgpu::CommandEncoder,
111 &wgpu::TextureView,
112 &wgpu::Queue,
113 u32,
114 u32,
115 ),
116>;
117
118fn calculate_image_fit(
121 origin: [f32; 2],
122 bounds: [f32; 2],
123 img_w: f32,
124 img_h: f32,
125 fit: ImageFitMode,
126) -> ([f32; 2], [f32; 2]) {
127 match fit {
128 ImageFitMode::Fill => {
129 (origin, bounds)
131 }
132 ImageFitMode::Contain => {
133 let bounds_aspect = bounds[0] / bounds[1];
135 let img_aspect = img_w / img_h;
136
137 let (render_w, render_h) = if img_aspect > bounds_aspect {
138 (bounds[0], bounds[0] / img_aspect)
140 } else {
141 (bounds[1] * img_aspect, bounds[1])
143 };
144
145 let offset_x = (bounds[0] - render_w) * 0.5;
147 let offset_y = (bounds[1] - render_h) * 0.5;
148
149 (
150 [origin[0] + offset_x, origin[1] + offset_y],
151 [render_w, render_h],
152 )
153 }
154 ImageFitMode::Cover => {
155 let bounds_aspect = bounds[0] / bounds[1];
157 let img_aspect = img_w / img_h;
158
159 let (render_w, render_h) = if img_aspect > bounds_aspect {
160 (bounds[1] * img_aspect, bounds[1])
162 } else {
163 (bounds[0], bounds[0] / img_aspect)
165 };
166
167 let offset_x = (bounds[0] - render_w) * 0.5;
169 let offset_y = (bounds[1] - render_h) * 0.5;
170
171 (
172 [origin[0] + offset_x, origin[1] + offset_y],
173 [render_w, render_h],
174 )
175 }
176 }
177}
178
179pub struct JagSurface {
187 device: Arc<wgpu::Device>,
188 queue: Arc<wgpu::Queue>,
189 surface_format: wgpu::TextureFormat,
190 pass: PassManager,
191 allocator: RenderAllocator,
192 direct: bool,
194 preserve_surface: bool,
196 use_intermediate: bool,
199 logical_pixels: bool,
201 dpi_scale: f32,
203 enable_smaa: bool,
205 ui_scale: f32,
207 overlay: Option<OverlayCallback>,
209 next_synthetic_external_texture_id: u64,
212 frame_cache: Option<CachedFrameData>,
215}
216
217impl JagSurface {
218 pub fn new(
220 device: Arc<wgpu::Device>,
221 queue: Arc<wgpu::Queue>,
222 surface_format: wgpu::TextureFormat,
223 ) -> Self {
224 let pass = PassManager::new(device.clone(), surface_format);
225 let allocator = RenderAllocator::new(device.clone());
226
227 Self {
228 device,
229 queue,
230 surface_format,
231 pass,
232 allocator,
233 direct: false,
234 preserve_surface: false,
235 use_intermediate: true,
236 logical_pixels: true,
237 dpi_scale: 1.0,
238 enable_smaa: false,
239 ui_scale: 1.0,
240 overlay: None,
241 next_synthetic_external_texture_id: 0x7000_0000_0000_0000,
242 frame_cache: None,
243 }
244 }
245
246 pub fn from_device_queue(
248 device: Arc<wgpu::Device>,
249 queue: Arc<wgpu::Queue>,
250 surface_format: wgpu::TextureFormat,
251 ) -> Self {
252 Self::new(device, queue, surface_format)
253 }
254
255 pub fn device(&self) -> Arc<wgpu::Device> {
256 self.device.clone()
257 }
258 pub fn queue(&self) -> Arc<wgpu::Queue> {
259 self.queue.clone()
260 }
261 pub fn surface_format(&self) -> wgpu::TextureFormat {
262 self.surface_format
263 }
264 pub fn pass_manager(&mut self) -> &mut PassManager {
265 &mut self.pass
266 }
267 pub fn allocator_mut(&mut self) -> &mut RenderAllocator {
268 &mut self.allocator
269 }
270
271 pub fn set_direct(&mut self, direct: bool) {
273 self.direct = direct;
274 }
275 pub fn set_preserve_surface(&mut self, preserve: bool) {
277 self.preserve_surface = preserve;
278 }
279 pub fn set_use_intermediate(&mut self, use_it: bool) {
281 self.use_intermediate = use_it;
282 }
283 pub fn set_enable_smaa(&mut self, enable: bool) {
285 self.enable_smaa = enable;
286 }
287 pub fn set_logical_pixels(&mut self, on: bool) {
289 self.logical_pixels = on;
290 }
291 pub fn set_dpi_scale(&mut self, scale: f32) {
293 self.dpi_scale = if scale.is_finite() && scale > 0.0 {
294 scale
295 } else {
296 1.0
297 };
298 }
299 pub fn set_ui_scale(&mut self, s: f32) {
301 self.ui_scale = if s.is_finite() { s } else { 1.0 };
302 }
303 pub fn set_overlay(&mut self, callback: OverlayCallback) {
305 self.overlay = Some(callback);
306 }
307 pub fn clear_overlay(&mut self) {
309 self.overlay = None;
310 }
311
312 pub fn set_scroll_offset(&mut self, offset: [f32; 2]) {
316 self.pass.set_scroll_offset(offset);
317 }
318
319 pub fn scroll_offset(&self) -> [f32; 2] {
321 self.pass.scroll_offset()
322 }
323
324 pub fn frame_cache(&self) -> Option<&CachedFrameData> {
326 self.frame_cache.as_ref()
327 }
328
329 pub fn clear_frame_cache(&mut self) {
331 self.frame_cache = None;
332 }
333
334 pub fn update_frame_cache_metadata(
338 &mut self,
339 scroll_at_build: (f32, f32),
340 generation: u64,
341 hit_index: HitIndex,
342 ) {
343 if let Some(ref mut cache) = self.frame_cache {
344 cache.scroll_at_build = scroll_at_build;
345 cache.generation_at_build = generation;
346 cache.hit_index = hit_index;
347 }
348 }
349
350 fn allocate_synthetic_external_texture_id(&mut self) -> ExternalTextureId {
351 let id = ExternalTextureId(self.next_synthetic_external_texture_id);
352 self.next_synthetic_external_texture_id =
353 self.next_synthetic_external_texture_id.wrapping_add(1);
354 id
355 }
356
357 fn opacity_group_z(commands: &[Command]) -> Option<i32> {
358 commands.iter().filter_map(Command::z_index).min()
359 }
360
361 fn collect_opacity_group(commands: &[Command], start_idx: usize) -> (Vec<Command>, usize) {
362 let mut depth = 1usize;
363 let mut i = start_idx;
364 let mut group = Vec::new();
365
366 while i < commands.len() {
367 match &commands[i] {
368 Command::PushOpacity(_) => {
369 depth += 1;
370 group.push(commands[i].clone());
371 }
372 Command::PopOpacity => {
373 depth = depth.saturating_sub(1);
374 if depth == 0 {
375 return (group, i + 1);
376 }
377 group.push(Command::PopOpacity);
378 }
379 _ => group.push(commands[i].clone()),
380 }
381 i += 1;
382 }
383
384 (group, i)
385 }
386
387 fn build_glyph_draws_from_text_draws(
388 &self,
389 text_draws: &[jag_draw::ExtractedTextDraw],
390 provider: Option<&Arc<dyn jag_draw::TextProvider + Send + Sync>>,
391 ) -> Vec<(
392 [f32; 2],
393 jag_draw::RasterizedGlyph,
394 jag_draw::ColorLinPremul,
395 i32,
396 )> {
397 let Some(provider) = provider else {
398 return Vec::new();
399 };
400
401 let sf = if self.dpi_scale.is_finite() && self.dpi_scale > 0.0 {
402 self.dpi_scale
403 } else {
404 1.0
405 };
406 let snap = |v: f32| -> f32 { (v * sf).round() / sf };
407
408 let mut glyph_draws = Vec::new();
409 for text_draw in text_draws {
410 let run = &text_draw.run;
411 let [a, b, c, d, e, f] = text_draw.transform.m;
412
413 let origin_x = a * run.pos[0] + c * run.pos[1] + e;
414 let origin_y = b * run.pos[0] + d * run.pos[1] + f;
415
416 let sx = (a * a + b * b).sqrt();
417 let sy = (c * c + d * d).sqrt();
418 let mut s = if sx.is_finite() && sy.is_finite() {
419 if sx > 0.0 && sy > 0.0 {
420 (sx + sy) * 0.5
421 } else {
422 sx.max(sy).max(1.0)
423 }
424 } else {
425 1.0
426 };
427 if !s.is_finite() || s <= 0.0 {
428 s = 1.0;
429 }
430
431 let logical_size = (run.size * s).max(1.0);
432 let physical_size = (logical_size * sf).max(1.0);
433 let run_for_provider = jag_draw::TextRun {
434 text: run.text.clone(),
435 pos: [0.0, 0.0],
436 size: physical_size,
437 color: run.color,
438 weight: run.weight,
439 style: run.style,
440 family: run.family.clone(),
441 };
442
443 let glyphs = jag_draw::rasterize_run_cached(provider.as_ref(), &run_for_provider);
444 for g in glyphs.iter() {
445 let mut origin = [origin_x + g.offset[0] / sf, origin_y + g.offset[1] / sf];
446 if logical_size <= 15.0 {
447 origin[0] = snap(origin[0]);
448 origin[1] = snap(origin[1]);
449 }
450 glyph_draws.push((
453 origin,
454 Self::grayscale_glyph_for_compositing(g),
455 run.color,
456 text_draw.z,
457 ));
458 }
459 }
460
461 glyph_draws
462 }
463
464 fn grayscale_glyph_for_compositing(
465 glyph: &jag_draw::RasterizedGlyph,
466 ) -> jag_draw::RasterizedGlyph {
467 use jag_draw::{GlyphMask, MaskFormat, SubpixelMask};
468
469 let mask = match &glyph.mask {
470 GlyphMask::Color(c) => GlyphMask::Color(c.clone()),
471 GlyphMask::Subpixel(m) => match m.format {
472 MaskFormat::Rgba8 => {
473 let mut out = Vec::with_capacity(m.data.len());
474 for px in m.data.chunks_exact(4) {
475 let gray =
476 ((u16::from(px[0]) + u16::from(px[1]) + u16::from(px[2])) / 3) as u8;
477 out.extend_from_slice(&[gray, gray, gray, 0]);
478 }
479 GlyphMask::Subpixel(SubpixelMask {
480 width: m.width,
481 height: m.height,
482 format: MaskFormat::Rgba8,
483 data: out,
484 })
485 }
486 MaskFormat::Rgba16 => {
487 let mut out = Vec::with_capacity(m.data.len());
488 for px in m.data.chunks_exact(8) {
489 let r = u16::from_le_bytes([px[0], px[1]]);
490 let g = u16::from_le_bytes([px[2], px[3]]);
491 let b = u16::from_le_bytes([px[4], px[5]]);
492 let gray = ((u32::from(r) + u32::from(g) + u32::from(b)) / 3) as u16;
493 let gb = gray.to_le_bytes();
494 out.extend_from_slice(&[gb[0], gb[1], gb[0], gb[1], gb[0], gb[1], 0, 0]);
495 }
496 GlyphMask::Subpixel(SubpixelMask {
497 width: m.width,
498 height: m.height,
499 format: MaskFormat::Rgba16,
500 data: out,
501 })
502 }
503 },
504 };
505
506 jag_draw::RasterizedGlyph {
507 offset: glyph.offset,
508 mask,
509 }
510 }
511
512 fn render_opacity_group_layer(
513 &mut self,
514 viewport: Viewport,
515 commands: Vec<Command>,
516 text_provider: Option<&Arc<dyn jag_draw::TextProvider + Send + Sync>>,
517 ) -> Result<ExternalTextureId> {
518 let mut group_list = DisplayList { viewport, commands };
519 group_list.sort_by_z();
520
521 let group_scene =
522 jag_draw::upload_display_list_unified(&mut self.allocator, &self.queue, &group_list)?;
523 let group_glyphs =
524 self.build_glyph_draws_from_text_draws(&group_scene.text_draws, text_provider);
525
526 let mut group_svgs: Vec<_> = group_scene
527 .svg_draws
528 .iter()
529 .map(|draw| {
530 (
531 crate::resolve_asset_path(&draw.path),
532 draw.origin,
533 draw.size,
534 None,
535 draw.z,
536 Transform2D::identity(),
537 None, )
539 })
540 .collect();
541 group_svgs.sort_by_key(|(_, _, _, _, z, _, _)| *z);
542
543 let mut group_images: Vec<(
544 std::path::PathBuf,
545 [f32; 2],
546 [f32; 2],
547 i32,
548 Option<jag_draw::Rect>,
549 )> = Vec::new();
550 for draw in &group_scene.image_draws {
551 let resolved_path = crate::resolve_asset_path(&draw.path);
552 if self
553 .pass
554 .load_image_to_view(&resolved_path, &self.queue)
555 .is_some()
556 {
557 group_images.push((resolved_path, draw.origin, draw.size, draw.z, None));
558 }
559 }
560 group_images.sort_by_key(|(_, _, _, z, _)| *z);
561
562 let width = viewport.width.max(1);
563 let height = viewport.height.max(1);
564 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
565 label: Some("opacity-group-layer"),
566 size: wgpu::Extent3d {
567 width,
568 height,
569 depth_or_array_layers: 1,
570 },
571 mip_level_count: 1,
572 sample_count: 1,
573 dimension: wgpu::TextureDimension::D2,
574 format: self.surface_format,
575 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
576 view_formats: &[],
577 });
578 let layer_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
579
580 let mut encoder = self
581 .device
582 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
583 label: Some("opacity-group-encoder"),
584 });
585 let saved_scroll = self.pass.scroll_offset();
587 self.pass.set_scroll_offset([0.0, 0.0]);
588 self.pass.render_unified(
589 &mut encoder,
590 &mut self.allocator,
591 &layer_view,
592 width,
593 height,
594 &group_scene.gpu_scene,
595 &group_scene.transparent_gpu_scene,
596 &group_scene.transparent_batches,
597 &group_glyphs,
598 &group_svgs,
599 &group_images,
600 &group_scene.external_texture_draws,
601 wgpu::Color::TRANSPARENT,
602 true,
603 &self.queue,
604 false,
605 );
606 self.pass.set_scroll_offset(saved_scroll);
607 self.queue.submit(std::iter::once(encoder.finish()));
608
609 let tex_id = self.allocate_synthetic_external_texture_id();
610 self.pass.register_external_texture(tex_id, layer_view);
611 Ok(tex_id)
612 }
613
614 fn flatten_opacity_groups(
615 &mut self,
616 commands: &[Command],
617 viewport: Viewport,
618 text_provider: Option<&Arc<dyn jag_draw::TextProvider + Send + Sync>>,
619 ) -> Result<Vec<Command>> {
620 let mut out: Vec<Command> = Vec::new();
621 let mut i = 0usize;
622 while i < commands.len() {
623 match &commands[i] {
624 Command::PushOpacity(opacity) => {
625 let (raw_group, next_i) = Self::collect_opacity_group(commands, i + 1);
626 let flattened_group =
627 self.flatten_opacity_groups(&raw_group, viewport, text_provider)?;
628
629 for cmd in flattened_group.iter() {
631 match cmd {
632 Command::HitRegionRect { .. }
633 | Command::HitRegionRoundedRect { .. }
634 | Command::HitRegionEllipse { .. } => out.push(cmd.clone()),
635 _ => {}
636 }
637 }
638
639 let layer_opacity = opacity.clamp(0.0, 1.0);
640 if layer_opacity > 0.0
641 && let Some(z) = Self::opacity_group_z(&flattened_group)
642 {
643 let logical_scale = jag_draw::logical_multiplier(
646 self.logical_pixels,
647 self.dpi_scale,
648 self.ui_scale,
649 );
650 let logical_w = (viewport.width as f32) / logical_scale;
651 let logical_h = (viewport.height as f32) / logical_scale;
652 let tex_id = self.render_opacity_group_layer(
653 viewport,
654 flattened_group,
655 text_provider,
656 )?;
657 out.push(Command::DrawExternalTexture {
658 rect: Rect {
659 x: 0.0,
660 y: 0.0,
661 w: logical_w,
662 h: logical_h,
663 },
664 texture_id: tex_id,
665 z,
666 transform: Transform2D::identity(),
667 opacity: layer_opacity,
668 premultiplied: true,
669 });
670 }
671 i = next_i;
672 }
673 Command::PopOpacity => {
674 i += 1;
676 }
677 _ => {
678 out.push(commands[i].clone());
679 i += 1;
680 }
681 }
682 }
683 Ok(out)
684 }
685
686 pub fn prepare_for_resize(&mut self, width: u32, height: u32) {
689 self.pass
690 .ensure_intermediate_texture(&mut self.allocator, width, height);
691 }
692
693 pub fn begin_frame(&self, width: u32, height: u32) -> Canvas {
695 let vp = Viewport { width, height };
696 Canvas {
697 viewport: vp,
698 painter: Painter::begin_frame(vp),
699 clear_color: None,
700 text_provider: None,
701 glyph_draws: Vec::new(),
702 svg_draws: Vec::new(),
703 image_draws: Vec::new(),
704 raw_image_draws: Vec::new(),
705 dpi_scale: self.dpi_scale,
706 clip_stack: vec![None],
707 overlay_draws: Vec::new(),
708 scrim_draws: Vec::new(),
709 }
710 }
711
712 pub fn end_frame(&mut self, frame: wgpu::SurfaceTexture, canvas: Canvas) -> Result<()> {
714 self.pass.set_scale_factor(self.dpi_scale);
716 self.pass.set_logical_pixels(self.logical_pixels);
717 self.pass.set_ui_scale(self.ui_scale);
718
719 let use_intermediate = self.enable_smaa || self.use_intermediate;
721
722 let text_provider = canvas.text_provider.clone();
723
724 let mut list = canvas.painter.finish();
726 let width = canvas.viewport.width.max(1);
727 let height = canvas.viewport.height.max(1);
728
729 if list
730 .commands
731 .iter()
732 .any(|cmd| matches!(cmd, Command::PushOpacity(_) | Command::PopOpacity))
733 {
734 let flattened =
735 self.flatten_opacity_groups(&list.commands, list.viewport, text_provider.as_ref())?;
736 list.commands = flattened;
737 }
738
739 list.sort_by_z();
741
742 let view = frame
744 .texture
745 .create_view(&wgpu::TextureViewDescriptor::default());
746 let scene_view = if use_intermediate {
747 self.pass
748 .ensure_intermediate_texture(&mut self.allocator, width, height);
749 let scene_target = self
750 .pass
751 .intermediate_texture
752 .as_ref()
753 .expect("intermediate render target not allocated");
754 scene_target
755 .texture
756 .create_view(&wgpu::TextureViewDescriptor::default())
757 } else {
758 frame
759 .texture
760 .create_view(&wgpu::TextureViewDescriptor::default())
761 };
762
763 let mut encoder = self
765 .device
766 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
767 label: Some("jag-surface-encoder"),
768 });
769
770 let clear = canvas.clear_color.unwrap_or(ColorLinPremul {
772 r: 0.0,
773 g: 0.0,
774 b: 0.0,
775 a: 0.0,
776 });
777 let clear_wgpu = wgpu::Color {
778 r: clear.r as f64,
779 g: clear.g as f64,
780 b: clear.b as f64,
781 a: clear.a as f64,
782 };
783
784 self.pass
786 .ensure_depth_texture(&mut self.allocator, width, height);
787
788 let unified_scene =
790 jag_draw::upload_display_list_unified(&mut self.allocator, &self.queue, &list)?;
791
792 let mut svg_draws: Vec<_> = canvas
794 .svg_draws
795 .iter()
796 .map(|(path, origin, max_size, style, z, transform, clip)| {
797 let resolved_path = crate::resolve_asset_path(path);
798 (
799 resolved_path,
800 *origin,
801 *max_size,
802 *style,
803 *z,
804 *transform,
805 *clip,
806 )
807 })
808 .collect();
809 svg_draws.sort_by_key(|(_, _, _, _, z, _, _)| *z);
810
811 let mut image_draws = canvas.image_draws.clone();
813 image_draws.sort_by_key(|(_, _, _, _, z, _, _)| *z);
814
815 let mut prepared_images: Vec<(
823 std::path::PathBuf,
824 [f32; 2],
825 [f32; 2],
826 i32,
827 Option<jag_draw::Rect>,
828 )> = Vec::new();
829 for (path, origin, size, fit, z, transform, clip) in image_draws.iter() {
830 let resolved_path = crate::resolve_asset_path(path);
832
833 if let Some((tex_view, img_w, img_h)) =
837 self.pass.load_image_to_view(&resolved_path, &self.queue)
838 {
839 drop(tex_view); let transformed_origin = apply_transform_to_point(*origin, *transform);
841 let (render_origin, render_size) = calculate_image_fit(
842 transformed_origin,
843 *size,
844 img_w as f32,
845 img_h as f32,
846 *fit,
847 );
848 prepared_images.push((
849 resolved_path.clone(),
850 render_origin,
851 render_size,
852 *z,
853 *clip,
854 ));
855 }
856 }
857
858 for (i, raw_draw) in canvas.raw_image_draws.iter().enumerate() {
864 if raw_draw.src_width == 0 || raw_draw.src_height == 0 {
865 continue;
866 }
867
868 let raw_path = std::path::PathBuf::from(format!("__webview_texture_{}__", i));
870
871 let has_new_pixels = !raw_draw.pixels.is_empty();
873
874 let need_new_texture =
876 if let Some((_, cached_w, cached_h)) = self.pass.try_get_image_view(&raw_path) {
877 cached_w != raw_draw.src_width || cached_h != raw_draw.src_height
879 } else {
880 true
881 };
882
883 if need_new_texture && has_new_pixels {
885 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
888 label: Some("cef-webview-texture"),
889 size: wgpu::Extent3d {
890 width: raw_draw.src_width,
891 height: raw_draw.src_height,
892 depth_or_array_layers: 1,
893 },
894 mip_level_count: 1,
895 sample_count: 1,
896 dimension: wgpu::TextureDimension::D2,
897 format: wgpu::TextureFormat::Bgra8UnormSrgb,
899 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
900 view_formats: &[],
901 });
902
903 self.pass.store_loaded_image(
905 &raw_path,
906 Arc::new(texture),
907 raw_draw.src_width,
908 raw_draw.src_height,
909 );
910 }
911
912 if has_new_pixels {
914 if let Some((tex, _, _)) = self.pass.get_cached_texture(&raw_path) {
915 self.queue.write_texture(
919 wgpu::ImageCopyTexture {
920 texture: &tex,
921 mip_level: 0,
922 origin: wgpu::Origin3d::ZERO,
923 aspect: wgpu::TextureAspect::All,
924 },
925 &raw_draw.pixels,
926 wgpu::ImageDataLayout {
927 offset: 0,
928 bytes_per_row: Some(raw_draw.src_width * 4),
929 rows_per_image: Some(raw_draw.src_height),
930 },
931 wgpu::Extent3d {
932 width: raw_draw.src_width,
933 height: raw_draw.src_height,
934 depth_or_array_layers: 1,
935 },
936 );
937 }
938 }
939
940 if self.pass.try_get_image_view(&raw_path).is_none() {
942 continue;
943 }
944
945 let transformed_origin = apply_transform_to_point(raw_draw.origin, raw_draw.transform);
949
950 set_last_raw_image_rect(
952 transformed_origin[0],
953 transformed_origin[1],
954 raw_draw.dst_size[0],
955 raw_draw.dst_size[1],
956 );
957
958 prepared_images.push((
959 raw_path,
960 transformed_origin,
961 raw_draw.dst_size,
962 raw_draw.z,
963 raw_draw.clip,
964 ));
965 }
966
967 let mut glyph_draws = canvas.glyph_draws.clone();
971
972 if let Some(ref provider) = canvas.text_provider {
973 let sf = if self.dpi_scale.is_finite() && self.dpi_scale > 0.0 {
976 self.dpi_scale
977 } else {
978 1.0
979 };
980 let snap = |v: f32| -> f32 { (v * sf).round() / sf };
981
982 for text_draw in &unified_scene.text_draws {
983 let run = &text_draw.run;
984 let [a, b, c, d, e, f] = text_draw.transform.m;
985
986 let origin_x = a * run.pos[0] + c * run.pos[1] + e;
988 let origin_y = b * run.pos[0] + d * run.pos[1] + f;
989
990 let sx = (a * a + b * b).sqrt();
993 let sy = (c * c + d * d).sqrt();
994 let mut s = if sx.is_finite() && sy.is_finite() {
995 if sx > 0.0 && sy > 0.0 {
996 (sx + sy) * 0.5
997 } else {
998 sx.max(sy).max(1.0)
999 }
1000 } else {
1001 1.0
1002 };
1003 if !s.is_finite() || s <= 0.0 {
1004 s = 1.0;
1005 }
1006
1007 let logical_size = (run.size * s).max(1.0);
1011 let physical_size = (logical_size * sf).max(1.0);
1012 let run_for_provider = jag_draw::TextRun {
1013 text: run.text.clone(),
1014 pos: [0.0, 0.0],
1015 size: physical_size,
1016 color: run.color,
1017 weight: run.weight,
1018 style: run.style,
1019 family: run.family.clone(),
1020 };
1021
1022 let glyphs = jag_draw::rasterize_run_cached(provider.as_ref(), &run_for_provider);
1027 for g in glyphs.iter() {
1028 let mut origin = [origin_x + g.offset[0] / sf, origin_y + g.offset[1] / sf];
1029 if logical_size <= 15.0 {
1030 origin[0] = snap(origin[0]);
1031 origin[1] = snap(origin[1]);
1032 }
1033 glyph_draws.push((origin, g.clone(), run.color, text_draw.z));
1034 }
1035 }
1036 }
1037
1038 let preserve_surface = self.preserve_surface;
1040 let direct = self.direct || !use_intermediate;
1041 self.pass.render_unified(
1042 &mut encoder,
1043 &mut self.allocator,
1044 &scene_view,
1045 width,
1046 height,
1047 &unified_scene.gpu_scene,
1048 &unified_scene.transparent_gpu_scene,
1049 &unified_scene.transparent_batches,
1050 &glyph_draws,
1051 &svg_draws,
1052 &prepared_images,
1053 &unified_scene.external_texture_draws,
1054 clear_wgpu,
1055 direct,
1056 &self.queue,
1057 preserve_surface,
1058 );
1059
1060 self.frame_cache = Some(CachedFrameData {
1065 gpu_scene: unified_scene.gpu_scene,
1066 transparent_gpu_scene: unified_scene.transparent_gpu_scene,
1067 transparent_batches: unified_scene.transparent_batches,
1068 glyph_draws,
1069 svg_draws,
1070 image_draws: prepared_images,
1071 external_texture_draws: unified_scene.external_texture_draws,
1072 clear: clear_wgpu,
1073 direct,
1074 width,
1075 height,
1076 scroll_at_build: (0.0, 0.0), generation_at_build: 0, viewport_size: (width, height),
1079 hit_index: HitIndex::default(), });
1081
1082 for scrim in &canvas.scrim_draws {
1084 match scrim {
1085 crate::ScrimDraw::Rect(rect, color) => {
1086 self.pass.draw_scrim_rect(
1087 &mut encoder,
1088 &scene_view,
1089 width,
1090 height,
1091 *rect,
1092 *color,
1093 &self.queue,
1094 );
1095 }
1096 crate::ScrimDraw::Cutout { hole, color } => {
1097 self.pass.draw_scrim_with_cutout(
1098 &mut encoder,
1099 &mut self.allocator,
1100 &scene_view,
1101 width,
1102 height,
1103 *hole,
1104 *color,
1105 &self.queue,
1106 );
1107 }
1108 }
1109 }
1110
1111 for (rect, color) in &canvas.overlay_draws {
1114 self.pass.draw_overlay_rect(
1115 &mut encoder,
1116 &scene_view,
1117 width,
1118 height,
1119 *rect,
1120 *color,
1121 &self.queue,
1122 );
1123 }
1124
1125 if let Some(ref mut overlay_fn) = self.overlay {
1128 overlay_fn(
1129 &mut self.pass,
1130 &mut encoder,
1131 &scene_view,
1132 &self.queue,
1133 width,
1134 height,
1135 );
1136 }
1137
1138 if use_intermediate {
1140 if self.enable_smaa {
1141 self.pass.apply_smaa(
1142 &mut encoder,
1143 &mut self.allocator,
1144 &scene_view,
1145 &view,
1146 width,
1147 height,
1148 &self.queue,
1149 );
1150 } else {
1151 self.pass.blit_to_surface(&mut encoder, &view);
1152 }
1153 }
1154
1155 let cb = encoder.finish();
1157 self.queue.submit(std::iter::once(cb));
1158 frame.present();
1159 Ok(())
1160 }
1161
1162 pub fn render_cached_frame_from_internal(
1168 &mut self,
1169 frame: wgpu::SurfaceTexture,
1170 scroll_delta: [f32; 2],
1171 ) -> Result<()> {
1172 let cache = self
1173 .frame_cache
1174 .as_ref()
1175 .ok_or_else(|| anyhow::anyhow!("no cached frame data"))?;
1176
1177 self.pass.set_scale_factor(self.dpi_scale);
1178 self.pass.set_logical_pixels(self.logical_pixels);
1179 self.pass.set_ui_scale(self.ui_scale);
1180
1181 let use_intermediate = self.enable_smaa || self.use_intermediate;
1182 let width = cache.width;
1183 let height = cache.height;
1184 let clear = cache.clear;
1185 let direct = cache.direct;
1186
1187 self.pass.set_scroll_offset(scroll_delta);
1189
1190 let view = frame
1191 .texture
1192 .create_view(&wgpu::TextureViewDescriptor::default());
1193 let scene_view = if use_intermediate {
1194 self.pass
1195 .ensure_intermediate_texture(&mut self.allocator, width, height);
1196 let scene_target = self
1197 .pass
1198 .intermediate_texture
1199 .as_ref()
1200 .expect("intermediate render target not allocated");
1201 scene_target
1202 .texture
1203 .create_view(&wgpu::TextureViewDescriptor::default())
1204 } else {
1205 frame
1206 .texture
1207 .create_view(&wgpu::TextureViewDescriptor::default())
1208 };
1209
1210 let mut encoder = self
1211 .device
1212 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1213 label: Some("jag-surface-cached-encoder"),
1214 });
1215
1216 self.pass
1217 .ensure_depth_texture(&mut self.allocator, width, height);
1218
1219 let cache = self.frame_cache.as_ref().unwrap();
1221 self.pass.render_unified(
1222 &mut encoder,
1223 &mut self.allocator,
1224 &scene_view,
1225 width,
1226 height,
1227 &cache.gpu_scene,
1228 &cache.transparent_gpu_scene,
1229 &cache.transparent_batches,
1230 &cache.glyph_draws,
1231 &cache.svg_draws,
1232 &cache.image_draws,
1233 &cache.external_texture_draws,
1234 clear,
1235 direct,
1236 &self.queue,
1237 false, );
1239
1240 if use_intermediate {
1242 if self.enable_smaa {
1243 self.pass.apply_smaa(
1244 &mut encoder,
1245 &mut self.allocator,
1246 &scene_view,
1247 &view,
1248 width,
1249 height,
1250 &self.queue,
1251 );
1252 } else {
1253 self.pass.blit_to_surface(&mut encoder, &view);
1254 }
1255 }
1256
1257 let cb = encoder.finish();
1258 self.queue.submit(std::iter::once(cb));
1259 frame.present();
1260
1261 self.pass.set_scroll_offset([0.0, 0.0]);
1263
1264 Ok(())
1265 }
1266
1267 pub fn end_frame_headless(&mut self, canvas: Canvas) -> Result<(u32, u32, Vec<u8>)> {
1274 self.pass.set_scale_factor(self.dpi_scale);
1276 self.pass.set_logical_pixels(self.logical_pixels);
1277 self.pass.set_ui_scale(self.ui_scale);
1278
1279 let text_provider = canvas.text_provider.clone();
1281
1282 let mut list = canvas.painter.finish();
1284 let width = canvas.viewport.width.max(1);
1285 let height = canvas.viewport.height.max(1);
1286
1287 if list
1288 .commands
1289 .iter()
1290 .any(|cmd| matches!(cmd, Command::PushOpacity(_) | Command::PopOpacity))
1291 {
1292 let flattened =
1293 self.flatten_opacity_groups(&list.commands, list.viewport, text_provider.as_ref())?;
1294 list.commands = flattened;
1295 }
1296
1297 list.sort_by_z();
1299
1300 let clear = canvas.clear_color.unwrap_or(ColorLinPremul {
1302 r: 0.0,
1303 g: 0.0,
1304 b: 0.0,
1305 a: 0.0,
1306 });
1307 let clear_wgpu = wgpu::Color {
1308 r: clear.r as f64,
1309 g: clear.g as f64,
1310 b: clear.b as f64,
1311 a: clear.a as f64,
1312 };
1313
1314 self.pass
1316 .ensure_depth_texture(&mut self.allocator, width, height);
1317
1318 let unified_scene =
1320 jag_draw::upload_display_list_unified(&mut self.allocator, &self.queue, &list)?;
1321
1322 let mut glyph_draws = canvas.glyph_draws.clone();
1324 if let Some(ref provider) = canvas.text_provider {
1325 let sf = if self.dpi_scale.is_finite() && self.dpi_scale > 0.0 {
1326 self.dpi_scale
1327 } else {
1328 1.0
1329 };
1330 let snap = |v: f32| -> f32 { (v * sf).round() / sf };
1331
1332 for text_draw in &unified_scene.text_draws {
1333 let run = &text_draw.run;
1334 let [a, b, c, d, e, f] = text_draw.transform.m;
1335
1336 let origin_x = a * run.pos[0] + c * run.pos[1] + e;
1337 let origin_y = b * run.pos[0] + d * run.pos[1] + f;
1338
1339 let sx = (a * a + b * b).sqrt();
1340 let sy = (c * c + d * d).sqrt();
1341 let mut s = if sx.is_finite() && sy.is_finite() {
1342 if sx > 0.0 && sy > 0.0 {
1343 (sx + sy) * 0.5
1344 } else {
1345 sx.max(sy).max(1.0)
1346 }
1347 } else {
1348 1.0
1349 };
1350 if !s.is_finite() || s <= 0.0 {
1351 s = 1.0;
1352 }
1353
1354 let logical_size = (run.size * s).max(1.0);
1355 let physical_size = (logical_size * sf).max(1.0);
1356 let run_for_provider = jag_draw::TextRun {
1357 text: run.text.clone(),
1358 pos: [0.0, 0.0],
1359 size: physical_size,
1360 color: run.color,
1361 weight: run.weight,
1362 style: run.style,
1363 family: run.family.clone(),
1364 };
1365
1366 let glyphs = jag_draw::rasterize_run_cached(provider.as_ref(), &run_for_provider);
1367 for g in glyphs.iter() {
1368 let mut origin = [origin_x + g.offset[0] / sf, origin_y + g.offset[1] / sf];
1369 if logical_size <= 15.0 {
1370 origin[0] = snap(origin[0]);
1371 origin[1] = snap(origin[1]);
1372 }
1373 glyph_draws.push((origin, g.clone(), run.color, text_draw.z));
1374 }
1375 }
1376 }
1377
1378 let mut svg_draws: Vec<_> = canvas
1380 .svg_draws
1381 .iter()
1382 .map(|(path, origin, max_size, style, z, transform, clip)| {
1383 let resolved_path = crate::resolve_asset_path(path);
1384 (
1385 resolved_path,
1386 *origin,
1387 *max_size,
1388 *style,
1389 *z,
1390 *transform,
1391 *clip,
1392 )
1393 })
1394 .collect();
1395 svg_draws.sort_by_key(|(_, _, _, _, z, _, _)| *z);
1396
1397 let mut image_draws = canvas.image_draws.clone();
1399 image_draws.sort_by_key(|(_, _, _, _, z, _, _)| *z);
1400
1401 let mut prepared_images: Vec<(
1402 std::path::PathBuf,
1403 [f32; 2],
1404 [f32; 2],
1405 i32,
1406 Option<jag_draw::Rect>,
1407 )> = Vec::new();
1408 for (path, origin, size, fit, z, transform, clip) in image_draws.iter() {
1409 let resolved_path = crate::resolve_asset_path(path);
1410 if let Some((tex_view, img_w, img_h)) =
1411 self.pass.load_image_to_view(&resolved_path, &self.queue)
1412 {
1413 drop(tex_view);
1414 let transformed_origin = apply_transform_to_point(*origin, *transform);
1415 let (render_origin, render_size) = calculate_image_fit(
1416 transformed_origin,
1417 *size,
1418 img_w as f32,
1419 img_h as f32,
1420 *fit,
1421 );
1422 prepared_images.push((
1423 resolved_path.clone(),
1424 render_origin,
1425 render_size,
1426 *z,
1427 *clip,
1428 ));
1429 }
1430 }
1431
1432 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
1434 label: Some("headless-render-target"),
1435 size: wgpu::Extent3d {
1436 width,
1437 height,
1438 depth_or_array_layers: 1,
1439 },
1440 mip_level_count: 1,
1441 sample_count: 1,
1442 dimension: wgpu::TextureDimension::D2,
1443 format: self.surface_format,
1444 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
1445 view_formats: &[],
1446 });
1447 let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
1448
1449 let mut encoder = self
1451 .device
1452 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1453 label: Some("headless-encoder"),
1454 });
1455
1456 self.pass.render_unified(
1458 &mut encoder,
1459 &mut self.allocator,
1460 &texture_view,
1461 width,
1462 height,
1463 &unified_scene.gpu_scene,
1464 &unified_scene.transparent_gpu_scene,
1465 &unified_scene.transparent_batches,
1466 &glyph_draws,
1467 &svg_draws,
1468 &prepared_images,
1469 &unified_scene.external_texture_draws,
1470 clear_wgpu,
1471 true, &self.queue,
1473 false, );
1475
1476 let bytes_per_pixel = 4u32;
1478 let unpadded_bytes_per_row = width * bytes_per_pixel;
1479 let padded_bytes_per_row = (unpadded_bytes_per_row + 255) & !255;
1480 let buffer_size = (padded_bytes_per_row * height) as u64;
1481
1482 let readback = self.device.create_buffer(&wgpu::BufferDescriptor {
1483 label: Some("headless-readback"),
1484 size: buffer_size,
1485 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
1486 mapped_at_creation: false,
1487 });
1488
1489 encoder.copy_texture_to_buffer(
1490 wgpu::ImageCopyTexture {
1491 texture: &texture,
1492 mip_level: 0,
1493 origin: wgpu::Origin3d::ZERO,
1494 aspect: wgpu::TextureAspect::All,
1495 },
1496 wgpu::ImageCopyBuffer {
1497 buffer: &readback,
1498 layout: wgpu::ImageDataLayout {
1499 offset: 0,
1500 bytes_per_row: Some(padded_bytes_per_row),
1501 rows_per_image: Some(height),
1502 },
1503 },
1504 wgpu::Extent3d {
1505 width,
1506 height,
1507 depth_or_array_layers: 1,
1508 },
1509 );
1510
1511 self.queue.submit(std::iter::once(encoder.finish()));
1513
1514 let (tx, rx) = std::sync::mpsc::channel();
1515 readback
1516 .slice(..)
1517 .map_async(wgpu::MapMode::Read, move |result| {
1518 result.expect("failed to map readback buffer");
1519 tx.send(()).expect("failed to signal readback");
1520 });
1521 self.device.poll(wgpu::Maintain::Wait);
1522 rx.recv()
1523 .map_err(|e| anyhow::anyhow!("readback recv: {}", e))?;
1524
1525 let mapped = readback.slice(..).get_mapped_range();
1527 let mut pixels = Vec::with_capacity((width * height * bytes_per_pixel) as usize);
1528 for row in 0..height {
1529 let start = (row * padded_bytes_per_row) as usize;
1530 let end = start + (width * bytes_per_pixel) as usize;
1531 pixels.extend_from_slice(&mapped[start..end]);
1532 }
1533 drop(mapped);
1534 readback.unmap();
1535
1536 Ok((width, height, pixels))
1537 }
1538}