1use super::*;
2
3impl WebGpuChartBackend {
5 pub fn is_supported() -> bool {
19 let Some(window) = web_sys::window() else {
20 return false;
21 };
22 let navigator: Navigator = window.navigator();
23 match js_sys::Reflect::get(navigator.as_ref(), &JsValue::from_str(WEBGPU_PROPERTY_GPU)) {
24 Ok(value) => !value.is_undefined() && !value.is_null(),
25 Err(_) => false,
26 }
27 }
28
29 pub async fn create(canvas: &HtmlCanvasElement) -> Option<Self> {
49 let gpu: JsValue = Self::gpu_object()?;
50 let adapter: JsValue = Self::request_adapter(&gpu).await?;
51 let device: JsValue = Self::request_device(&adapter).await?;
52 let queue: JsValue = gpu_queue(&device)?;
53 let context: JsValue = Self::canvas_context(canvas)?;
54 let format: String = Self::preferred_format(&gpu)?;
55 Self::configure_context(&context, &device, &format)?;
56 let api: WebGpuApi = WebGpuApi::new(&device, &queue, &context)?;
57 let uniform_layout: JsValue = Self::create_uniform_layout(&device)?;
58 let texture_layout: JsValue = Self::create_texture_layout(&device)?;
59 let solid_layout: JsValue =
60 Self::create_pipeline_layout(&device, from_ref(&uniform_layout))?;
61 let textured_layout: JsValue = Self::create_pipeline_layout(
62 &device,
63 &[uniform_layout.clone(), texture_layout.clone()],
64 )?;
65 let solid_shader: JsValue = Self::create_shader_module(&device, WEBGPU_SOLID_SHADER)?;
66 let textured_shader: JsValue = Self::create_shader_module(&device, WEBGPU_TEXTURED_SHADER)?;
67 let solid_pipeline: JsValue =
68 Self::create_render_pipeline(&device, &solid_layout, &solid_shader, &format, false)?;
69 let textured_pipeline: JsValue = Self::create_render_pipeline(
70 &device,
71 &textured_layout,
72 &textured_shader,
73 &format,
74 true,
75 )?;
76 let uniform_buffer: JsValue = Self::create_buffer(
77 &device,
78 WEBGPU_UNIFORM_SIZE,
79 WEBGPU_BUFFER_USAGE_UNIFORM | WEBGPU_BUFFER_USAGE_COPY_DST,
80 )?;
81 let uniform_bind_group: JsValue = Self::create_bind_group(
82 &device,
83 &uniform_layout,
84 &[gpu_object(&[
85 (WEBGPU_KEY_BINDING, &JsValue::from_f64(0.0)),
86 (
87 WEBGPU_KEY_RESOURCE,
88 &gpu_object(&[(WEBGPU_KEY_BUFFER, &uniform_buffer)]),
89 ),
90 ])],
91 )?;
92 let sampler: JsValue = Self::create_sampler(&device)?;
93 let (pass_descriptor, pass_attachment) = Self::create_pass_descriptor();
94 Some(Self {
95 canvas: canvas.clone(),
96 device,
97 queue,
98 context,
99 format,
100 api,
101 encoder_api: None,
102 pass_api: None,
103 solid_pipeline,
104 textured_pipeline,
105 texture_layout,
106 sampler,
107 uniform_buffer,
108 uniform_bind_group,
109 pass_descriptor,
110 pass_attachment,
111 atlas: WebGpuAtlas {
112 texture: None,
113 bind_group: None,
114 width: 0,
115 height: 0,
116 },
117 solid_buffer: WebGpuBuffer {
118 buffer: None,
119 capacity: 0,
120 },
121 solid_index_buffer: WebGpuBuffer {
122 buffer: None,
123 capacity: 0,
124 },
125 textured_buffer: WebGpuBuffer {
126 buffer: None,
127 capacity: 0,
128 },
129 textured_index_buffer: WebGpuBuffer {
130 buffer: None,
131 capacity: 0,
132 },
133 batches: Vec::new(),
134 images: Vec::new(),
135 next_image_id: 1,
136 atlas_glyphs: 0,
137 fingerprint: None,
138 tessellator: Tessellator::new(),
139 width: 0.0,
140 height: 0.0,
141 dpr: 1.0,
142 solid_vertices: Vec::new(),
143 solid_indices: Vec::new(),
144 textured_vertices: Vec::new(),
145 textured_indices: Vec::new(),
146 clips: Vec::new(),
147 })
148 }
149
150 pub fn canvas(&self) -> &HtmlCanvasElement {
156 self.get_canvas()
157 }
158
159 pub fn dpr(&self) -> f64 {
165 self.get_dpr()
166 }
167
168 pub fn format(&self) -> &str {
174 self.get_format()
175 }
176
177 pub fn batch_count(&self) -> usize {
186 self.get_batches()
187 .iter()
188 .filter(|batch: &&WebGpuBatch| batch.index_count > 0)
189 .count()
190 }
191
192 pub fn invalidate(&mut self) {
199 self.set_fingerprint(None);
200 }
201
202 fn gpu_object() -> Option<JsValue> {
208 let window: Window = web_sys::window()?;
209 let navigator: Navigator = window.navigator();
210 let gpu: JsValue =
211 js_sys::Reflect::get(navigator.as_ref(), &JsValue::from_str(WEBGPU_PROPERTY_GPU))
212 .ok()?;
213 if gpu.is_undefined() || gpu.is_null() {
214 return None;
215 }
216 Some(gpu)
217 }
218
219 async fn request_adapter(gpu: &JsValue) -> Option<JsValue> {
229 let method: Function = gpu_method(gpu, WEBGPU_METHOD_REQUEST_ADAPTER)?;
230 let options: JsValue = gpu_object(&[(
231 "powerPreference",
232 &JsValue::from_str(WEBGPU_POWER_PREFERENCE),
233 )]);
234 let promise: js_sys::Promise = method.call1(gpu, &options).ok()?.unchecked_into();
235 let adapter: JsValue = JsFuture::from(promise).await.ok()?;
236 if adapter.is_undefined() || adapter.is_null() {
237 return None;
238 }
239 Some(adapter)
240 }
241
242 async fn request_device(adapter: &JsValue) -> Option<JsValue> {
252 let method: Function = gpu_method(adapter, WEBGPU_METHOD_REQUEST_DEVICE)?;
253 let promise: js_sys::Promise = method.call0(adapter).ok()?.unchecked_into();
254 let device: JsValue = JsFuture::from(promise).await.ok()?;
255 if device.is_undefined() || device.is_null() {
256 return None;
257 }
258 Some(device)
259 }
260
261 fn canvas_context(canvas: &HtmlCanvasElement) -> Option<JsValue> {
271 let context: Object = canvas.get_context(WEBGPU_CONTEXT_ID).ok()??;
272 Some(context.into())
273 }
274
275 fn preferred_format(gpu: &JsValue) -> Option<String> {
285 let method: Function = gpu_method(gpu, WEBGPU_METHOD_GET_PREFERRED_CANVAS_FORMAT)?;
286 let format: JsValue = method.call0(gpu).ok()?;
287 format.as_string()
288 }
289
290 fn configure_context(context: &JsValue, device: &JsValue, format: &str) -> Option<()> {
301 let method: Function = gpu_method(context, WEBGPU_METHOD_CONFIGURE)?;
302 let descriptor: JsValue = gpu_object(&[
303 ("device", device),
304 (WEBGPU_KEY_FORMAT, &JsValue::from_str(format)),
305 ("alphaMode", &JsValue::from_str(WEBGPU_ALPHA_MODE)),
306 ]);
307 method.call1(context, &descriptor).ok()?;
308 Some(())
309 }
310
311 fn create_shader_module(device: &JsValue, code: &str) -> Option<JsValue> {
322 let method: Function = gpu_method(device, WEBGPU_METHOD_CREATE_SHADER_MODULE)?;
323 let descriptor = gpu_object(&[("code", &JsValue::from_str(code))]);
324 method.call1(device, &descriptor).ok()
325 }
326
327 fn create_buffer(device: &JsValue, size: u32, usage: u32) -> Option<JsValue> {
338 let method: Function = gpu_method(device, WEBGPU_METHOD_CREATE_BUFFER)?;
339 let descriptor: JsValue = gpu_object(&[
340 (WEBGPU_KEY_SIZE, &JsValue::from_f64(f64::from(size))),
341 (WEBGPU_KEY_USAGE, &JsValue::from_f64(f64::from(usage))),
342 ]);
343 method.call1(device, &descriptor).ok()
344 }
345
346 fn create_texture(
358 device: &JsValue,
359 width: u32,
360 height: u32,
361 format: &str,
362 usage: u32,
363 ) -> Option<JsValue> {
364 let method: Function = gpu_method(device, WEBGPU_METHOD_CREATE_TEXTURE)?;
365 let descriptor: JsValue = gpu_object(&[
366 (
367 WEBGPU_KEY_SIZE,
368 &gpu_object(&[
369 (WEBGPU_KEY_WIDTH, &JsValue::from_f64(f64::from(width))),
370 (WEBGPU_KEY_HEIGHT, &JsValue::from_f64(f64::from(height))),
371 (WEBGPU_KEY_DEPTH_OR_ARRAY_LAYERS, &JsValue::from_f64(1.0)),
372 ]),
373 ),
374 (WEBGPU_KEY_FORMAT, &JsValue::from_str(format)),
375 (WEBGPU_KEY_USAGE, &JsValue::from_f64(f64::from(usage))),
376 ]);
377 method.call1(device, &descriptor).ok()
378 }
379
380 fn create_view(texture: &JsValue) -> Option<JsValue> {
390 gpu_method(texture, WEBGPU_METHOD_CREATE_VIEW)?
391 .call0(texture)
392 .ok()
393 }
394
395 fn destroy(resource: &JsValue) {
401 if let Some(method) = gpu_method(resource, WEBGPU_METHOD_DESTROY) {
402 let _: Result<JsValue, JsValue> = method.call0(resource);
403 }
404 }
405
406 fn create_sampler(device: &JsValue) -> Option<JsValue> {
416 let method: Function = gpu_method(device, WEBGPU_METHOD_CREATE_SAMPLER)?;
417 let descriptor: JsValue = gpu_object(&[
418 ("magFilter", &JsValue::from_str(WEBGPU_FILTER_LINEAR)),
419 ("minFilter", &JsValue::from_str(WEBGPU_FILTER_LINEAR)),
420 (
421 "addressModeU",
422 &JsValue::from_str(WEBGPU_ADDRESS_MODE_CLAMP_TO_EDGE),
423 ),
424 (
425 "addressModeV",
426 &JsValue::from_str(WEBGPU_ADDRESS_MODE_CLAMP_TO_EDGE),
427 ),
428 ]);
429 method.call1(device, &descriptor).ok()
430 }
431
432 fn create_uniform_layout(device: &JsValue) -> Option<JsValue> {
442 let method: Function = gpu_method(device, WEBGPU_METHOD_CREATE_BIND_GROUP_LAYOUT)?;
443 let entries: JsValue = gpu_array(&[gpu_object(&[
444 (WEBGPU_KEY_BINDING, &JsValue::from_f64(0.0)),
445 (
446 WEBGPU_KEY_VISIBILITY,
447 &JsValue::from_f64(f64::from(WEBGPU_SHADER_STAGE_VERTEX)),
448 ),
449 (
450 WEBGPU_KEY_BUFFER,
451 &gpu_object(&[(WEBGPU_KEY_TYPE, &JsValue::from_str("uniform"))]),
452 ),
453 ])]);
454 let descriptor: JsValue = gpu_object(&[(WEBGPU_KEY_ENTRIES, &entries)]);
455 method.call1(device, &descriptor).ok()
456 }
457
458 fn create_texture_layout(device: &JsValue) -> Option<JsValue> {
468 let method: Function = gpu_method(device, WEBGPU_METHOD_CREATE_BIND_GROUP_LAYOUT)?;
469 let entries: JsValue = gpu_array(&[
470 gpu_object(&[
471 (WEBGPU_KEY_BINDING, &JsValue::from_f64(0.0)),
472 (
473 WEBGPU_KEY_VISIBILITY,
474 &JsValue::from_f64(f64::from(WEBGPU_SHADER_STAGE_FRAGMENT)),
475 ),
476 (
477 "sampler",
478 &gpu_object(&[(WEBGPU_KEY_TYPE, &JsValue::from_str("filtering"))]),
479 ),
480 ]),
481 gpu_object(&[
482 (WEBGPU_KEY_BINDING, &JsValue::from_f64(1.0)),
483 (
484 WEBGPU_KEY_VISIBILITY,
485 &JsValue::from_f64(f64::from(WEBGPU_SHADER_STAGE_FRAGMENT)),
486 ),
487 (
488 WEBGPU_KEY_TEXTURE,
489 &gpu_object(&[("sampleType", &JsValue::from_str("float"))]),
490 ),
491 ]),
492 ]);
493 let descriptor: JsValue = gpu_object(&[(WEBGPU_KEY_ENTRIES, &entries)]);
494 method.call1(device, &descriptor).ok()
495 }
496
497 fn create_pipeline_layout(device: &JsValue, layouts: &[JsValue]) -> Option<JsValue> {
508 let method: Function = gpu_method(device, WEBGPU_METHOD_CREATE_PIPELINE_LAYOUT)?;
509 let descriptor = gpu_object(&[("bindGroupLayouts", &gpu_array(layouts))]);
510 method.call1(device, &descriptor).ok()
511 }
512
513 fn create_bind_group(
525 device: &JsValue,
526 layout: &JsValue,
527 entries: &[JsValue],
528 ) -> Option<JsValue> {
529 let method: Function = gpu_method(device, WEBGPU_METHOD_CREATE_BIND_GROUP)?;
530 let descriptor: JsValue = gpu_object(&[
531 (WEBGPU_KEY_LAYOUT, layout),
532 (WEBGPU_KEY_ENTRIES, &gpu_array(entries)),
533 ]);
534 method.call1(device, &descriptor).ok()
535 }
536
537 fn create_render_pipeline(
555 device: &JsValue,
556 layout: &JsValue,
557 shader: &JsValue,
558 format: &str,
559 textured: bool,
560 ) -> Option<JsValue> {
561 let attributes: JsValue = if textured {
562 gpu_array(&[
563 gpu_object(&[
564 (WEBGPU_KEY_SHADER_LOCATION, &JsValue::from_f64(0.0)),
565 (WEBGPU_KEY_OFFSET, &JsValue::from_f64(0.0)),
566 (
567 WEBGPU_KEY_FORMAT,
568 &JsValue::from_str(WEBGPU_VERTEX_FORMAT_FLOAT32X2),
569 ),
570 ]),
571 gpu_object(&[
572 (WEBGPU_KEY_SHADER_LOCATION, &JsValue::from_f64(1.0)),
573 (WEBGPU_KEY_OFFSET, &JsValue::from_f64(8.0)),
574 (
575 WEBGPU_KEY_FORMAT,
576 &JsValue::from_str(WEBGPU_VERTEX_FORMAT_FLOAT32X2),
577 ),
578 ]),
579 gpu_object(&[
580 (WEBGPU_KEY_SHADER_LOCATION, &JsValue::from_f64(2.0)),
581 (WEBGPU_KEY_OFFSET, &JsValue::from_f64(16.0)),
582 (
583 WEBGPU_KEY_FORMAT,
584 &JsValue::from_str(WEBGPU_VERTEX_FORMAT_UNORM8X4),
585 ),
586 ]),
587 ])
588 } else {
589 gpu_array(&[
590 gpu_object(&[
591 (WEBGPU_KEY_SHADER_LOCATION, &JsValue::from_f64(0.0)),
592 (WEBGPU_KEY_OFFSET, &JsValue::from_f64(0.0)),
593 (
594 WEBGPU_KEY_FORMAT,
595 &JsValue::from_str(WEBGPU_VERTEX_FORMAT_FLOAT32X2),
596 ),
597 ]),
598 gpu_object(&[
599 (WEBGPU_KEY_SHADER_LOCATION, &JsValue::from_f64(1.0)),
600 (WEBGPU_KEY_OFFSET, &JsValue::from_f64(8.0)),
601 (
602 WEBGPU_KEY_FORMAT,
603 &JsValue::from_str(WEBGPU_VERTEX_FORMAT_UNORM8X4),
604 ),
605 ]),
606 ])
607 };
608 let stride: u32 = if textured {
609 WEBGPU_TEXTURED_VERTEX_STRIDE
610 } else {
611 WEBGPU_SOLID_VERTEX_STRIDE
612 };
613 let vertex_buffer: JsValue = gpu_object(&[
614 ("arrayStride", &JsValue::from_f64(f64::from(stride))),
615 ("stepMode", &JsValue::from_str(WEBGPU_STEP_MODE_VERTEX)),
616 ("attributes", &attributes),
617 ]);
618 let target: JsValue = gpu_object(&[
619 (WEBGPU_KEY_FORMAT, &JsValue::from_str(format)),
620 (
621 "blend",
622 &gpu_object(&[
623 (
624 "color",
625 &gpu_object(&[
626 (
627 WEBGPU_KEY_SRC_FACTOR,
628 &JsValue::from_str(WEBGPU_BLEND_FACTOR_ONE),
629 ),
630 (
631 WEBGPU_KEY_DST_FACTOR,
632 &JsValue::from_str(WEBGPU_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA),
633 ),
634 (
635 WEBGPU_KEY_OPERATION,
636 &JsValue::from_str(WEBGPU_BLEND_OPERATION_ADD),
637 ),
638 ]),
639 ),
640 (
641 "alpha",
642 &gpu_object(&[
643 (
644 WEBGPU_KEY_SRC_FACTOR,
645 &JsValue::from_str(WEBGPU_BLEND_FACTOR_ONE),
646 ),
647 (
648 WEBGPU_KEY_DST_FACTOR,
649 &JsValue::from_str(WEBGPU_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA),
650 ),
651 (
652 WEBGPU_KEY_OPERATION,
653 &JsValue::from_str(WEBGPU_BLEND_OPERATION_ADD),
654 ),
655 ]),
656 ),
657 ]),
658 ),
659 ]);
660 let descriptor: JsValue = gpu_object(&[
661 (WEBGPU_KEY_LAYOUT, layout),
662 (
663 WEBGPU_KEY_VERTEX,
664 &gpu_object(&[
665 (WEBGPU_KEY_MODULE, shader),
666 (
667 WEBGPU_KEY_ENTRY_POINT,
668 &JsValue::from_str(WEBGPU_VERTEX_ENTRY_POINT),
669 ),
670 ("buffers", &gpu_array(&[vertex_buffer])),
671 ]),
672 ),
673 (
674 "fragment",
675 &gpu_object(&[
676 (WEBGPU_KEY_MODULE, shader),
677 (
678 WEBGPU_KEY_ENTRY_POINT,
679 &JsValue::from_str(WEBGPU_FRAGMENT_ENTRY_POINT),
680 ),
681 ("targets", &gpu_array(&[target])),
682 ]),
683 ),
684 (
685 "primitive",
686 &gpu_object(&[
687 ("topology", &JsValue::from_str("triangle-list")),
688 ("cullMode", &JsValue::from_str("none")),
689 ]),
690 ),
691 (
692 "multisample",
693 &gpu_object(&[("count", &JsValue::from_f64(1.0))]),
694 ),
695 ]);
696 gpu_method(device, WEBGPU_METHOD_CREATE_RENDER_PIPELINE)?
697 .call1(device, &descriptor)
698 .ok()
699 }
700
701 fn create_pass_descriptor() -> (JsValue, JsValue) {
711 let attachment: JsValue = gpu_object(&[
712 ("view", &JsValue::UNDEFINED),
713 (
714 "clearValue",
715 &gpu_object(&[
716 ("r", &JsValue::from_f64(0.0)),
717 ("g", &JsValue::from_f64(0.0)),
718 ("b", &JsValue::from_f64(0.0)),
719 ("a", &JsValue::from_f64(0.0)),
720 ]),
721 ),
722 ("loadOp", &JsValue::from_str("clear")),
723 ("storeOp", &JsValue::from_str("store")),
724 ]);
725 let descriptor: JsValue =
726 gpu_object(&[("colorAttachments", &gpu_array(from_ref(&attachment)))]);
727 (descriptor, attachment)
728 }
729
730 fn update_uniform(&mut self) {
739 let width: f64 = if self.get_width() > 0.0 {
740 self.get_width()
741 } else {
742 1.0
743 };
744 let height: f64 = if self.get_height() > 0.0 {
745 self.get_height()
746 } else {
747 1.0
748 };
749 let data: [f32; 4] = [
750 (2.0 / width) as f32,
751 (-2.0 / height) as f32,
752 -1.0_f32,
753 1.0_f32,
754 ];
755 let view: Float32Array = js_sys::Float32Array::from(data.as_slice());
756 let _: Result<JsValue, JsValue> = self.get_api().write_buffer.call3(
757 self.get_queue(),
758 self.get_uniform_buffer(),
759 &JsValue::from_f64(0.0),
760 &view,
761 );
762 }
763
764 fn sync_atlas(&mut self, atlas: &GlyphAtlas) {
771 let width: u32 = atlas.width();
772 let height: u32 = atlas.height();
773 if self.get_atlas().texture.is_some()
774 && self.get_atlas().width == width
775 && self.get_atlas().height == height
776 {
777 return;
778 }
779 if let Some(texture) = &self.get_atlas().texture {
780 Self::destroy(texture);
781 }
782 let usage: u32 = WEBGPU_TEXTURE_USAGE_COPY_DST
783 | WEBGPU_TEXTURE_USAGE_TEXTURE_BINDING
784 | WEBGPU_TEXTURE_USAGE_RENDER_ATTACHMENT;
785 let Some(texture) = Self::create_texture(
786 self.get_device(),
787 width,
788 height,
789 WEBGPU_TEXTURE_FORMAT,
790 usage,
791 ) else {
792 return;
793 };
794 let Some(view) = Self::create_view(&texture) else {
795 return;
796 };
797 let Some(bind_group) = Self::create_bind_group(
798 self.get_device(),
799 self.get_texture_layout(),
800 &[
801 gpu_object(&[
802 (WEBGPU_KEY_BINDING, &JsValue::from_f64(0.0)),
803 (WEBGPU_KEY_RESOURCE, self.get_sampler()),
804 ]),
805 gpu_object(&[
806 (WEBGPU_KEY_BINDING, &JsValue::from_f64(1.0)),
807 (WEBGPU_KEY_RESOURCE, &view),
808 ]),
809 ],
810 ) else {
811 return;
812 };
813 self.get_mut_atlas().texture = Some(texture);
814 self.get_mut_atlas().bind_group = Some(bind_group);
815 self.get_mut_atlas().width = width;
816 self.get_mut_atlas().height = height;
817 self.set_fingerprint(None);
819 }
820
821 fn upload_atlas(&mut self, atlas: &GlyphAtlas) {
827 let Some(texture) = self.get_atlas().texture.clone() else {
828 return;
829 };
830 let canvas: &HtmlCanvasElement = atlas.canvas();
831 let source: JsValue = gpu_object(&[
832 (WEBGPU_KEY_SOURCE, canvas.as_ref()),
833 (WEBGPU_KEY_FLIP_Y, &JsValue::FALSE),
834 ]);
835 let destination: JsValue = gpu_object(&[
836 (WEBGPU_KEY_TEXTURE, &texture),
837 (WEBGPU_KEY_PREMULTIPLIED_ALPHA, &JsValue::TRUE),
838 ]);
839 let size: JsValue = gpu_object(&[
840 (
841 WEBGPU_KEY_WIDTH,
842 &JsValue::from_f64(f64::from(atlas.width())),
843 ),
844 (
845 WEBGPU_KEY_HEIGHT,
846 &JsValue::from_f64(f64::from(atlas.height())),
847 ),
848 (WEBGPU_KEY_DEPTH_OR_ARRAY_LAYERS, &JsValue::from_f64(1.0)),
849 ]);
850 let _: Result<JsValue, JsValue> = self.get_api().copy_external_image.call3(
851 self.get_queue(),
852 &source,
853 &destination,
854 &size,
855 );
856 }
857
858 fn list_fingerprint(&mut self, list: &DisplayList) -> u64 {
873 let mut hasher: GpuHasher = GpuHasher::new();
874 list.len().hash(&mut hasher);
875 for item in list.items() {
876 item.kind().hash(&mut hasher);
877 item.z.hash(&mut hasher);
878 item.element_id.hash(&mut hasher);
879 item.series_index.hash(&mut hasher);
880 item.data_index.hash(&mut hasher);
881 match &item.cmd {
882 DrawCmd::Fill {
883 path,
884 paint,
885 opacity,
886 } => {
887 hash_path(&mut hasher, path);
888 hash_fill(&mut hasher, paint);
889 hasher.write_f64(*opacity);
890 }
891 DrawCmd::Stroke {
892 path,
893 paint,
894 style,
895 opacity,
896 } => {
897 hash_path(&mut hasher, path);
898 hash_stroke(&mut hasher, paint);
899 hash_line_style(&mut hasher, style);
900 hasher.write_f64(*opacity);
901 }
902 DrawCmd::Strip {
903 points,
904 baseline,
905 paint,
906 opacity,
907 } => {
908 hasher.write_u32(points.len() as u32);
909 for point in points {
910 hasher.write_f32(point.x);
911 hasher.write_f32(point.y);
912 }
913 hasher.write_f32(*baseline);
914 hash_fill(&mut hasher, paint);
915 hasher.write_f64(*opacity);
916 }
917 DrawCmd::Text { text, x, y, style } => {
918 text.hash(&mut hasher);
919 hasher.write_f64(*x);
920 hasher.write_f64(*y);
921 hash_text_style(&mut hasher, style);
922 }
923 DrawCmd::Image {
924 image,
925 x,
926 y,
927 width,
928 height,
929 } => {
930 let id: u32 = self.image_id(image.as_ref());
931 id.hash(&mut hasher);
932 hasher.write_f64(*x);
933 hasher.write_f64(*y);
934 hasher.write_f64(*width);
935 hasher.write_f64(*height);
936 }
937 DrawCmd::Clip { path } => hash_path(&mut hasher, path),
938 DrawCmd::PopClip => {}
939 }
940 }
941 hasher.finish()
942 }
943
944 fn rebuild(&mut self, list: &DisplayList, shaper: &mut TextShaper) {
956 self.get_mut_solid_vertices().clear();
957 self.get_mut_solid_indices().clear();
958 self.get_mut_textured_vertices().clear();
959 self.get_mut_textured_indices().clear();
960 self.get_mut_batches().clear();
961 self.get_mut_clips().clear();
962 for item in list.items() {
963 match &item.cmd {
964 DrawCmd::Fill {
965 path,
966 paint,
967 opacity,
968 } => self.push_fill(path, paint, *opacity),
969 DrawCmd::Stroke {
970 path,
971 paint,
972 style,
973 opacity,
974 } => self.push_stroke(path, paint, style, *opacity),
975 DrawCmd::Strip {
976 points,
977 baseline,
978 paint,
979 opacity,
980 } => self.push_strip(points, *baseline, paint, *opacity),
981 DrawCmd::Text { text, x, y, style } => {
982 self.push_text(text, *x, *y, style, shaper);
983 }
984 DrawCmd::Image {
985 image,
986 x,
987 y,
988 width,
989 height,
990 } => self.push_image(image, *x, *y, *width, *height),
991 DrawCmd::Clip { path } => self.push_clip(path),
992 DrawCmd::PopClip => self.pop_clip(),
993 }
994 }
995 self.upload_buffers();
996 }
997
998 fn push_fill(&mut self, path: &Path, paint: &Fill, opacity: f64) {
1006 if matches!(paint, Fill::None) {
1007 return;
1008 }
1009 let mesh: SolidMesh = self.get_mut_tessellator().fill(path, 0xffff_ffff);
1010 if mesh.indices.is_empty() {
1011 return;
1012 }
1013 let base: u32 =
1014 (self.get_solid_vertices().len() / WEBGPU_SOLID_VERTEX_STRIDE as usize) as u32;
1015 let first_index: u32 = self.get_solid_indices().len() as u32;
1016 self.open_batch(WebGpuBatchKind::Solid, first_index, None);
1017 for vertex in &mesh.vertices {
1018 let color: Color = self.resolve_fill_color(paint, vertex.x, vertex.y);
1019 let alpha: u8 = scaled_alpha(color, opacity);
1020 push_solid_vertex(
1021 self.get_mut_solid_vertices(),
1022 vertex.x,
1023 vertex.y,
1024 color,
1025 alpha,
1026 );
1027 }
1028 for index in &mesh.indices {
1029 self.get_mut_solid_indices().push(base + index);
1030 }
1031 self.close_batch(mesh.indices.len() as u32);
1032 }
1033
1034 fn push_stroke(&mut self, path: &Path, paint: &Stroke, style: &LineStyle, opacity: f64) {
1043 if matches!(paint, Stroke::None) || style.width <= 0.0 {
1044 return;
1045 }
1046 let stroke_style: StrokeStyle = StrokeStyle {
1047 width: style.width as f32,
1048 cap: style.cap,
1049 join: style.join,
1050 dash: style.dash.clone(),
1051 };
1052 let mesh: SolidMesh = self
1053 .get_mut_tessellator()
1054 .stroke(path, &stroke_style, 0xffff_ffff);
1055 if mesh.indices.is_empty() {
1056 return;
1057 }
1058 let base: u32 =
1059 (self.get_solid_vertices().len() / WEBGPU_SOLID_VERTEX_STRIDE as usize) as u32;
1060 let first_index: u32 = self.get_solid_indices().len() as u32;
1061 self.open_batch(WebGpuBatchKind::Solid, first_index, None);
1062 for vertex in &mesh.vertices {
1063 let color: Color = self.resolve_stroke_color(paint, vertex.x, vertex.y);
1064 let alpha: u8 = scaled_alpha(color, opacity * style.opacity);
1065 push_solid_vertex(
1066 self.get_mut_solid_vertices(),
1067 vertex.x,
1068 vertex.y,
1069 color,
1070 alpha,
1071 );
1072 }
1073 for index in &mesh.indices {
1074 self.get_mut_solid_indices().push(base + index);
1075 }
1076 self.close_batch(mesh.indices.len() as u32);
1077 }
1078
1079 fn push_strip(&mut self, points: &[Point], baseline: f32, paint: &Fill, opacity: f64) {
1091 if matches!(paint, Fill::None) || points.len() < 2 {
1092 return;
1093 }
1094 let mesh: SolidMesh = self
1095 .get_mut_tessellator()
1096 .fill_strip(points, baseline, 0xffff_ffff);
1097 if mesh.indices.is_empty() {
1098 return;
1099 }
1100 let base: u32 =
1101 (self.get_solid_vertices().len() / WEBGPU_SOLID_VERTEX_STRIDE as usize) as u32;
1102 let first_index: u32 = self.get_solid_indices().len() as u32;
1103 self.open_batch(WebGpuBatchKind::Solid, first_index, None);
1104 for vertex in &mesh.vertices {
1105 let color: Color = self.resolve_fill_color(paint, vertex.x, vertex.y);
1106 let alpha: u8 = scaled_alpha(color, opacity);
1107 push_solid_vertex(
1108 self.get_mut_solid_vertices(),
1109 vertex.x,
1110 vertex.y,
1111 color,
1112 alpha,
1113 );
1114 }
1115 for index in &mesh.indices {
1116 self.get_mut_solid_indices().push(base + index);
1117 }
1118 self.close_batch(mesh.indices.len() as u32);
1119 }
1120
1121 fn resolve_fill_color(&self, paint: &Fill, x: f32, y: f32) -> Color {
1132 match paint {
1133 Fill::Solid(color) => *color,
1134 Fill::Linear(gradient) => linear_gradient_color(gradient, f64::from(x), f64::from(y)),
1135 Fill::Radial(gradient) => radial_gradient_color(gradient, f64::from(x), f64::from(y)),
1136 Fill::None => Color::default(),
1137 }
1138 }
1139
1140 fn resolve_stroke_color(&self, paint: &Stroke, x: f32, y: f32) -> Color {
1151 match paint {
1152 Stroke::Solid(color) => *color,
1153 Stroke::Linear(gradient) => linear_gradient_color(gradient, f64::from(x), f64::from(y)),
1154 Stroke::Radial(gradient) => radial_gradient_color(gradient, f64::from(x), f64::from(y)),
1155 Stroke::None => Color::default(),
1156 }
1157 }
1158
1159 fn push_text(
1172 &mut self,
1173 text: &str,
1174 x: f64,
1175 y: f64,
1176 style: &TextStyle,
1177 shaper: &mut TextShaper,
1178 ) {
1179 let run: TextRun = shaper.shape(text, style, x, y);
1180 if let Some(background) = style.background {
1181 for line in &run.lines {
1182 let left: f64 = line.left - style.padding.3;
1183 let top: f64 = line.baseline - run.ascent - style.padding.0;
1184 let width: f64 = line.width + style.padding.1 + style.padding.3;
1185 let height: f64 = run.ascent + run.descent + style.padding.0 + style.padding.2;
1186 self.push_solid_quad(
1187 left as f32,
1188 top as f32,
1189 width as f32,
1190 height as f32,
1191 background,
1192 1.0,
1193 );
1194 }
1195 }
1196 if run.quads.is_empty() {
1197 return;
1198 }
1199 let bind_group: Option<JsValue> = self.get_atlas().bind_group.clone();
1200 let base: u32 =
1201 (self.get_textured_vertices().len() / WEBGPU_TEXTURED_VERTEX_STRIDE as usize) as u32;
1202 let first_index: u32 = self.get_textured_indices().len() as u32;
1203 self.open_batch(WebGpuBatchKind::Textured, first_index, bind_group);
1204 let color: Color = style.color;
1205 let alpha: u8 = style.color.alpha;
1206 for quad in &run.quads {
1207 push_textured_vertex(
1208 self.get_mut_textured_vertices(),
1209 quad.x,
1210 quad.y,
1211 quad.u,
1212 quad.v,
1213 color,
1214 alpha,
1215 );
1216 push_textured_vertex(
1217 self.get_mut_textured_vertices(),
1218 quad.x + quad.width,
1219 quad.y,
1220 quad.u + quad.u_size,
1221 quad.v,
1222 color,
1223 alpha,
1224 );
1225 push_textured_vertex(
1226 self.get_mut_textured_vertices(),
1227 quad.x + quad.width,
1228 quad.y + quad.height,
1229 quad.u + quad.u_size,
1230 quad.v + quad.v_size,
1231 color,
1232 alpha,
1233 );
1234 push_textured_vertex(
1235 self.get_mut_textured_vertices(),
1236 quad.x,
1237 quad.y + quad.height,
1238 quad.u,
1239 quad.v + quad.v_size,
1240 color,
1241 alpha,
1242 );
1243 }
1244 for quad in 0..run.quads.len() as u32 {
1245 let vertex: u32 = base + quad * 4;
1246 self.get_mut_textured_indices().extend_from_slice(&[
1247 vertex,
1248 vertex + 1,
1249 vertex + 2,
1250 vertex,
1251 vertex + 2,
1252 vertex + 3,
1253 ]);
1254 }
1255 self.close_batch((run.quads.len() * 6) as u32);
1256 }
1257
1258 fn push_image(&mut self, image: &HtmlImageElement, x: f64, y: f64, width: f64, height: f64) {
1265 if !image.complete() {
1266 return;
1267 }
1268 let natural_width: u32 = image.natural_width();
1269 let natural_height: u32 = image.natural_height();
1270 if natural_width == 0 || natural_height == 0 {
1271 return;
1272 }
1273 let source: String = image.src();
1274 let id: u32 = self.image_id(image.as_ref());
1275 let Some(bind_group) =
1276 self.prepare_image(id, image, &source, natural_width, natural_height)
1277 else {
1278 return;
1279 };
1280 let base: u32 =
1281 (self.get_textured_vertices().len() / WEBGPU_TEXTURED_VERTEX_STRIDE as usize) as u32;
1282 let first_index: u32 = self.get_textured_indices().len() as u32;
1283 self.open_batch(WebGpuBatchKind::Textured, first_index, Some(bind_group));
1284 let color: Color = Color::rgb(255, 255, 255);
1285 push_textured_vertex(
1286 self.get_mut_textured_vertices(),
1287 x as f32,
1288 y as f32,
1289 0.0,
1290 0.0,
1291 color,
1292 255,
1293 );
1294 push_textured_vertex(
1295 self.get_mut_textured_vertices(),
1296 (x + width) as f32,
1297 y as f32,
1298 1.0,
1299 0.0,
1300 color,
1301 255,
1302 );
1303 push_textured_vertex(
1304 self.get_mut_textured_vertices(),
1305 (x + width) as f32,
1306 (y + height) as f32,
1307 1.0,
1308 1.0,
1309 color,
1310 255,
1311 );
1312 push_textured_vertex(
1313 self.get_mut_textured_vertices(),
1314 x as f32,
1315 (y + height) as f32,
1316 0.0,
1317 1.0,
1318 color,
1319 255,
1320 );
1321 self.get_mut_textured_indices().extend_from_slice(&[
1322 base,
1323 base + 1,
1324 base + 2,
1325 base,
1326 base + 2,
1327 base + 3,
1328 ]);
1329 self.close_batch(6);
1330 }
1331
1332 fn push_solid_quad(
1340 &mut self,
1341 x: f32,
1342 y: f32,
1343 width: f32,
1344 height: f32,
1345 color: Color,
1346 opacity: f64,
1347 ) {
1348 let base: u32 =
1349 (self.get_solid_vertices().len() / WEBGPU_SOLID_VERTEX_STRIDE as usize) as u32;
1350 let first_index: u32 = self.get_solid_indices().len() as u32;
1351 self.open_batch(WebGpuBatchKind::Solid, first_index, None);
1352 let alpha: u8 = scaled_alpha(color, opacity);
1353 push_solid_vertex(self.get_mut_solid_vertices(), x, y, color, alpha);
1354 push_solid_vertex(self.get_mut_solid_vertices(), x + width, y, color, alpha);
1355 push_solid_vertex(
1356 self.get_mut_solid_vertices(),
1357 x + width,
1358 y + height,
1359 color,
1360 alpha,
1361 );
1362 push_solid_vertex(self.get_mut_solid_vertices(), x, y + height, color, alpha);
1363 self.get_mut_solid_indices().extend_from_slice(&[
1364 base,
1365 base + 1,
1366 base + 2,
1367 base,
1368 base + 2,
1369 base + 3,
1370 ]);
1371 self.close_batch(6);
1372 }
1373
1374 fn push_clip(&mut self, path: &Path) {
1384 let bounds: BBox = path.bounds();
1385 let scissor: WebGpuScissor = WebGpuScissor {
1386 x: (bounds.min_x * self.get_dpr()).floor().max(0.0) as u32,
1387 y: (bounds.min_y * self.get_dpr()).floor().max(0.0) as u32,
1388 width: ((bounds.max_x - bounds.min_x) * self.get_dpr())
1389 .ceil()
1390 .max(0.0) as u32,
1391 height: ((bounds.max_y - bounds.min_y) * self.get_dpr())
1392 .ceil()
1393 .max(0.0) as u32,
1394 };
1395 self.get_mut_clips().push(scissor);
1396 }
1397
1398 fn pop_clip(&mut self) {
1400 self.get_mut_clips().pop();
1401 }
1402
1403 fn current_scissor(&self) -> Option<WebGpuScissor> {
1411 let mut result: Option<WebGpuScissor> = None;
1412 for clip in self.get_clips() {
1413 result = Some(match result {
1414 None => *clip,
1415 Some(current) => {
1416 let left: u32 = current.x.max(clip.x);
1417 let top: u32 = current.y.max(clip.y);
1418 let right: u32 = (current.x + current.width).min(clip.x + clip.width);
1419 let bottom: u32 = (current.y + current.height).min(clip.y + clip.height);
1420 WebGpuScissor {
1421 x: left,
1422 y: top,
1423 width: right.saturating_sub(left),
1424 height: bottom.saturating_sub(top),
1425 }
1426 }
1427 });
1428 }
1429 result
1430 }
1431
1432 fn open_batch(&mut self, kind: WebGpuBatchKind, first_index: u32, bind_group: Option<JsValue>) {
1445 let compatible: bool = match self.get_batches().last() {
1446 Some(last) => last.kind == kind && last.bind_group == bind_group,
1447 None => false,
1448 };
1449 if compatible {
1450 return;
1451 }
1452 let scissor: Option<WebGpuScissor> = self.current_scissor();
1453 self.get_mut_batches().push(WebGpuBatch {
1454 kind,
1455 first_index,
1456 index_count: 0,
1457 bind_group,
1458 scissor,
1459 });
1460 }
1461
1462 fn close_batch(&mut self, index_count: u32) {
1468 if let Some(last) = self.get_mut_batches().last_mut() {
1469 last.index_count += index_count;
1470 }
1471 }
1472
1473 fn image_id(&mut self, element: &JsValue) -> u32 {
1484 for image in self.get_images() {
1485 if image.element == *element {
1486 return image.id;
1487 }
1488 }
1489 let id: u32 = self.get_next_image_id();
1490 self.set_next_image_id(self.get_next_image_id() + 1);
1491 self.get_mut_images().push(WebGpuImage {
1492 element: element.clone(),
1493 id,
1494 texture: None,
1495 bind_group: None,
1496 width: 0,
1497 height: 0,
1498 source: String::new(),
1499 });
1500 id
1501 }
1502
1503 fn prepare_image(
1515 &mut self,
1516 id: u32,
1517 image: &HtmlImageElement,
1518 source: &str,
1519 width: u32,
1520 height: u32,
1521 ) -> Option<JsValue> {
1522 let position: usize = self
1523 .get_images()
1524 .iter()
1525 .position(|entry: &WebGpuImage| entry.id == id)?;
1526 let stale: bool = {
1527 let entry: &_ = &self.get_images()[position];
1528 entry.width != width || entry.height != height || entry.source != source
1529 };
1530 if stale {
1531 let usage: u32 = WEBGPU_TEXTURE_USAGE_COPY_DST
1532 | WEBGPU_TEXTURE_USAGE_TEXTURE_BINDING
1533 | WEBGPU_TEXTURE_USAGE_RENDER_ATTACHMENT;
1534 let texture: JsValue = Self::create_texture(
1535 self.get_device(),
1536 width,
1537 height,
1538 WEBGPU_TEXTURE_FORMAT,
1539 usage,
1540 )?;
1541 let view: JsValue = Self::create_view(&texture)?;
1542 let bind_group: JsValue = Self::create_bind_group(
1543 self.get_device(),
1544 self.get_texture_layout(),
1545 &[
1546 gpu_object(&[
1547 (WEBGPU_KEY_BINDING, &JsValue::from_f64(0.0)),
1548 (WEBGPU_KEY_RESOURCE, self.get_sampler()),
1549 ]),
1550 gpu_object(&[
1551 (WEBGPU_KEY_BINDING, &JsValue::from_f64(1.0)),
1552 (WEBGPU_KEY_RESOURCE, &view),
1553 ]),
1554 ],
1555 )?;
1556 let images: &mut Vec<WebGpuImage> = self.get_mut_images();
1557 let entry: &mut WebGpuImage = &mut images[position];
1558 entry.texture = Some(texture);
1559 entry.bind_group = Some(bind_group);
1560 entry.width = width;
1561 entry.height = height;
1562 entry.source = source.to_string();
1563 }
1564 let texture: JsValue = self.get_images()[position].texture.clone()?;
1565 let copy_source: JsValue = gpu_object(&[
1566 (WEBGPU_KEY_SOURCE, image.as_ref()),
1567 (WEBGPU_KEY_FLIP_Y, &JsValue::FALSE),
1568 ]);
1569 let copy_destination: JsValue = gpu_object(&[
1570 (WEBGPU_KEY_TEXTURE, &texture),
1571 (WEBGPU_KEY_PREMULTIPLIED_ALPHA, &JsValue::TRUE),
1572 ]);
1573 let size: JsValue = gpu_object(&[
1574 (WEBGPU_KEY_WIDTH, &JsValue::from_f64(f64::from(width))),
1575 (WEBGPU_KEY_HEIGHT, &JsValue::from_f64(f64::from(height))),
1576 (WEBGPU_KEY_DEPTH_OR_ARRAY_LAYERS, &JsValue::from_f64(1.0)),
1577 ]);
1578 let _: Result<JsValue, JsValue> = self.get_api().copy_external_image.call3(
1579 self.get_queue(),
1580 ©_source,
1581 ©_destination,
1582 &size,
1583 );
1584 self.get_images()[position].bind_group.clone()
1585 }
1586
1587 fn upload_buffers(&mut self) {
1592 let device: JsValue = self.get_device().clone();
1593 let queue: JsValue = self.get_queue().clone();
1594 let write_buffer: Function = self.get_api().write_buffer.clone();
1595 let Self {
1596 solid_buffer,
1597 solid_index_buffer,
1598 solid_vertices,
1599 solid_indices,
1600 textured_buffer,
1601 textured_index_buffer,
1602 textured_vertices,
1603 textured_indices,
1604 ..
1605 } = &mut *self;
1606 WebGpuBuffer::upload_bytes(
1607 &device,
1608 &queue,
1609 &write_buffer,
1610 solid_buffer,
1611 solid_vertices,
1612 WEBGPU_BUFFER_USAGE_VERTEX | WEBGPU_BUFFER_USAGE_COPY_DST,
1613 );
1614 WebGpuBuffer::upload_indices(
1615 &device,
1616 &queue,
1617 &write_buffer,
1618 solid_index_buffer,
1619 solid_indices,
1620 WEBGPU_BUFFER_USAGE_INDEX | WEBGPU_BUFFER_USAGE_COPY_DST,
1621 );
1622 WebGpuBuffer::upload_bytes(
1623 &device,
1624 &queue,
1625 &write_buffer,
1626 textured_buffer,
1627 textured_vertices,
1628 WEBGPU_BUFFER_USAGE_VERTEX | WEBGPU_BUFFER_USAGE_COPY_DST,
1629 );
1630 WebGpuBuffer::upload_indices(
1631 &device,
1632 &queue,
1633 &write_buffer,
1634 textured_index_buffer,
1635 textured_indices,
1636 WEBGPU_BUFFER_USAGE_INDEX | WEBGPU_BUFFER_USAGE_COPY_DST,
1637 );
1638 }
1639
1640 fn encode(&mut self) {
1642 let Some(texture) = self
1643 .get_api()
1644 .get_current_texture
1645 .call0(self.get_context())
1646 .ok()
1647 else {
1648 return;
1649 };
1650 let Some(view) = Self::create_view(&texture) else {
1651 return;
1652 };
1653 gpu_set(self.get_pass_attachment(), WEBGPU_PROPERTY_VIEW, &view);
1654 let Some(encoder) = self
1655 .get_api()
1656 .create_command_encoder
1657 .call0(self.get_device())
1658 .ok()
1659 else {
1660 return;
1661 };
1662 if self.try_get_encoder_api().is_none() {
1663 self.set_encoder_api(WebGpuEncoderApi::new(&encoder));
1664 }
1665 let Some(pass) =
1666 self.try_get_encoder_api()
1667 .as_ref()
1668 .and_then(|encoder_api: &WebGpuEncoderApi| {
1669 encoder_api
1670 .begin_render_pass
1671 .call1(&encoder, self.get_pass_descriptor())
1672 .ok()
1673 })
1674 else {
1675 return;
1676 };
1677 if self.try_get_pass_api().is_none() {
1678 self.set_pass_api(WebGpuPassApi::new(&pass));
1679 }
1680 let Some(pass_api) = self.try_get_pass_api().as_ref() else {
1681 return;
1682 };
1683 let _: Result<JsValue, JsValue> = pass_api.set_bind_group.call2(
1684 &pass,
1685 &JsValue::from_f64(0.0),
1686 self.get_uniform_bind_group(),
1687 );
1688 self.draw_batches(&pass, pass_api);
1689 let _: Result<JsValue, JsValue> = pass_api.end_pass.call0(&pass);
1690 let Some(command) =
1691 self.try_get_encoder_api()
1692 .as_ref()
1693 .and_then(|encoder_api: &WebGpuEncoderApi| {
1694 encoder_api.finish_encoder.call0(&encoder).ok()
1695 })
1696 else {
1697 return;
1698 };
1699 let commands: JsValue = gpu_array(&[command]);
1700 let _: Result<JsValue, JsValue> = self.get_api().submit.call1(self.get_queue(), &commands);
1701 }
1702
1703 fn draw_batches(&self, pass: &JsValue, pass_api: &WebGpuPassApi) {
1714 let mut current_kind: Option<WebGpuBatchKind> = None;
1715 let mut current_bind_group: Option<JsValue> = None;
1716 let mut current_scissor: Option<WebGpuScissor> = None;
1717 for batch in self.get_batches() {
1718 if batch.index_count == 0 {
1719 continue;
1720 }
1721 if current_kind != Some(batch.kind) {
1722 let (pipeline, vertex_buffer, index_buffer) = match batch.kind {
1723 WebGpuBatchKind::Solid => (
1724 self.get_solid_pipeline(),
1725 self.get_solid_buffer(),
1726 self.get_solid_index_buffer(),
1727 ),
1728 WebGpuBatchKind::Textured => (
1729 self.get_textured_pipeline(),
1730 self.get_textured_buffer(),
1731 self.get_textured_index_buffer(),
1732 ),
1733 };
1734 let _: Result<JsValue, JsValue> = pass_api.set_pipeline.call1(pass, pipeline);
1735 if let Some(buffer) = &vertex_buffer.buffer {
1736 let _: Result<JsValue, JsValue> =
1737 pass_api
1738 .set_vertex_buffer
1739 .call2(pass, &JsValue::from_f64(0.0), buffer);
1740 }
1741 if let Some(buffer) = &index_buffer.buffer {
1742 let _: Result<JsValue, JsValue> = pass_api.set_index_buffer.call3(
1743 pass,
1744 buffer,
1745 &JsValue::from_str("uint32"),
1746 &JsValue::from_f64(0.0),
1747 );
1748 }
1749 current_kind = Some(batch.kind);
1750 current_bind_group = None;
1751 }
1752 if batch.kind == WebGpuBatchKind::Textured {
1753 let group: Option<JsValue> = batch
1754 .bind_group
1755 .clone()
1756 .or_else(|| self.get_atlas().bind_group.clone());
1757 if let Some(group) = group
1758 && current_bind_group.as_ref() != Some(&group)
1759 {
1760 let _: Result<JsValue, JsValue> =
1761 pass_api
1762 .set_bind_group
1763 .call2(pass, &JsValue::from_f64(1.0), &group);
1764 current_bind_group = Some(group);
1765 }
1766 }
1767 if batch.scissor != current_scissor {
1768 match &batch.scissor {
1769 Some(scissor) => {
1770 let _: Result<JsValue, JsValue> = pass_api.set_scissor_rect.call4(
1771 pass,
1772 &JsValue::from_f64(f64::from(scissor.x)),
1773 &JsValue::from_f64(f64::from(scissor.y)),
1774 &JsValue::from_f64(f64::from(scissor.width)),
1775 &JsValue::from_f64(f64::from(scissor.height)),
1776 );
1777 }
1778 None => {
1779 let _: Result<JsValue, JsValue> = pass_api.set_scissor_rect.call4(
1780 pass,
1781 &JsValue::from_f64(0.0),
1782 &JsValue::from_f64(0.0),
1783 &JsValue::from_f64(f64::from(self.get_canvas().width())),
1784 &JsValue::from_f64(f64::from(self.get_canvas().height())),
1785 );
1786 }
1787 }
1788 current_scissor = batch.scissor;
1789 }
1790 let _: Result<JsValue, JsValue> = pass_api.draw_indexed.call5(
1791 pass,
1792 &JsValue::from_f64(f64::from(batch.index_count)),
1793 &JsValue::from_f64(1.0),
1794 &JsValue::from_f64(f64::from(batch.first_index)),
1795 &JsValue::from_f64(0.0),
1796 &JsValue::from_f64(0.0),
1797 );
1798 }
1799 }
1800}
1801
1802impl ChartBackend for WebGpuChartBackend {
1804 fn kind(&self) -> BackendKind {
1810 BackendKind::WebGpu
1811 }
1812
1813 fn resize(&mut self, width: f64, height: f64, dpr: f64) {
1819 if (dpr - self.get_dpr()).abs() > f64::EPSILON {
1823 self.set_fingerprint(None);
1824 }
1825 self.set_dpr(dpr);
1826 self.set_width(width);
1827 self.set_height(height);
1828 let physical_width: u32 = (width * dpr).round().max(1.0) as u32;
1829 let physical_height: u32 = (height * dpr).round().max(1.0) as u32;
1830 if self.get_canvas().width() != physical_width {
1831 self.get_canvas().set_width(physical_width);
1832 }
1833 if self.get_canvas().height() != physical_height {
1834 self.get_canvas().set_height(physical_height);
1835 }
1836 self.update_uniform();
1837 }
1838
1839 fn render(&mut self, list: &DisplayList, shaper: &mut TextShaper) {
1846 self.sync_atlas(shaper.atlas());
1847 let glyphs: usize = shaper.atlas().len();
1852 if glyphs < self.get_atlas_glyphs() {
1853 self.set_fingerprint(None);
1854 }
1855 self.set_atlas_glyphs(glyphs);
1856 let fingerprint: u64 = self.list_fingerprint(list);
1857 if *self.try_get_fingerprint() != Some(fingerprint) {
1858 self.rebuild(list, shaper);
1859 self.set_fingerprint(Some(fingerprint));
1860 }
1861 if shaper.atlas().is_dirty() {
1862 self.upload_atlas(shaper.atlas());
1863 shaper.atlas_mut().mark_clean();
1864 }
1865 self.encode();
1866 }
1867
1868 fn dispose(&mut self) {
1870 let Self {
1871 solid_buffer,
1872 solid_index_buffer,
1873 textured_buffer,
1874 textured_index_buffer,
1875 ..
1876 } = &mut *self;
1877 for buffer in [
1878 solid_buffer,
1879 solid_index_buffer,
1880 textured_buffer,
1881 textured_index_buffer,
1882 ] {
1883 if let Some(handle) = &buffer.buffer {
1884 Self::destroy(handle);
1885 }
1886 buffer.buffer = None;
1887 buffer.capacity = 0;
1888 }
1889 if let Some(texture) = &self.get_atlas().texture {
1890 Self::destroy(texture);
1891 }
1892 self.get_mut_atlas().texture = None;
1893 self.get_mut_atlas().bind_group = None;
1894 self.get_mut_atlas().width = 0;
1895 self.get_mut_atlas().height = 0;
1896 for image in self.get_mut_images().iter_mut() {
1897 if let Some(texture) = &image.texture {
1898 Self::destroy(texture);
1899 }
1900 image.texture = None;
1901 image.bind_group = None;
1902 }
1903 self.get_mut_batches().clear();
1904 self.set_fingerprint(None);
1905 }
1906}
1907
1908impl Drop for WebGpuChartBackend {
1910 fn drop(&mut self) {
1916 <Self as ChartBackend>::dispose(self);
1917 }
1918}
1919
1920impl WebGpuApi {
1922 fn new(device: &JsValue, queue: &JsValue, context: &JsValue) -> Option<Self> {
1933 Some(Self {
1934 create_command_encoder: gpu_method(device, WEBGPU_METHOD_CREATE_COMMAND_ENCODER)?,
1935 submit: gpu_method(queue, WEBGPU_METHOD_SUBMIT)?,
1936 write_buffer: gpu_method(queue, WEBGPU_METHOD_WRITE_BUFFER)?,
1937 copy_external_image: gpu_method(queue, WEBGPU_METHOD_COPY_EXTERNAL_IMAGE)?,
1938 get_current_texture: gpu_method(context, WEBGPU_METHOD_GET_CURRENT_TEXTURE)?,
1939 })
1940 }
1941}
1942
1943impl WebGpuEncoderApi {
1945 fn new(encoder: &JsValue) -> Option<Self> {
1955 Some(Self {
1956 begin_render_pass: gpu_method(encoder, WEBGPU_METHOD_BEGIN_RENDER_PASS)?,
1957 finish_encoder: gpu_method(encoder, WEBGPU_METHOD_FINISH)?,
1958 })
1959 }
1960}
1961
1962impl WebGpuPassApi {
1964 fn new(pass: &JsValue) -> Option<Self> {
1974 Some(Self {
1975 set_pipeline: gpu_method(pass, WEBGPU_METHOD_SET_PIPELINE)?,
1976 set_vertex_buffer: gpu_method(pass, WEBGPU_METHOD_SET_VERTEX_BUFFER)?,
1977 set_index_buffer: gpu_method(pass, WEBGPU_METHOD_SET_INDEX_BUFFER)?,
1978 set_bind_group: gpu_method(pass, WEBGPU_METHOD_SET_BIND_GROUP)?,
1979 set_scissor_rect: gpu_method(pass, WEBGPU_METHOD_SET_SCISSOR_RECT)?,
1980 draw_indexed: gpu_method(pass, WEBGPU_METHOD_DRAW_INDEXED)?,
1981 end_pass: gpu_method(pass, WEBGPU_METHOD_END)?,
1982 })
1983 }
1984}
1985
1986impl WebGpuBuffer {
1988 fn ensure(&mut self, device: &JsValue, size: u32, usage: u32) -> Option<JsValue> {
2002 if size == 0 {
2003 return None;
2004 }
2005 if self.get_capacity() < size {
2006 let capacity: u32 = size
2007 .checked_next_power_of_two()
2008 .unwrap_or(u32::MAX)
2009 .max(1024);
2010 let buffer: JsValue = WebGpuChartBackend::create_buffer(device, capacity, usage)?;
2011 self.set_buffer(Some(buffer));
2012 self.set_capacity(capacity);
2013 }
2014 self.try_get_buffer().clone()
2015 }
2016
2017 fn upload_bytes(
2031 device: &JsValue,
2032 queue: &JsValue,
2033 write_buffer: &Function,
2034 buffer: &mut Self,
2035 bytes: &[u8],
2036 usage: u32,
2037 ) {
2038 if bytes.is_empty() {
2039 return;
2040 }
2041 let Some(handle) = buffer.ensure(device, bytes.len() as u32, usage) else {
2042 return;
2043 };
2044 let data: Uint8Array = js_sys::Uint8Array::from(bytes);
2045 let _: Result<JsValue, JsValue> =
2046 write_buffer.call3(queue, &handle, &JsValue::from_f64(0.0), &data);
2047 }
2048
2049 fn upload_indices(
2059 device: &JsValue,
2060 queue: &JsValue,
2061 write_buffer: &Function,
2062 buffer: &mut Self,
2063 indices: &[u32],
2064 usage: u32,
2065 ) {
2066 if indices.is_empty() {
2067 return;
2068 }
2069 let Some(handle) = buffer.ensure(device, (indices.len() * 4) as u32, usage) else {
2070 return;
2071 };
2072 let data: Uint32Array = js_sys::Uint32Array::from(indices);
2073 let _: Result<JsValue, JsValue> =
2074 write_buffer.call3(queue, &handle, &JsValue::from_f64(0.0), &data);
2075 }
2076}
2077
2078impl GpuHasher {
2080 const OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
2082
2083 const PRIME: u64 = 0x0000_0100_0000_01b3;
2085
2086 pub(crate) fn new() -> Self {
2088 Self {
2089 state: Self::OFFSET,
2090 }
2091 }
2092
2093 pub(crate) fn write_f32(&mut self, value: f32) {
2102 self.write_u32(value.to_bits());
2103 }
2104
2105 pub(crate) fn write_f64(&mut self, value: f64) {
2111 self.write_u64(value.to_bits());
2112 }
2113}
2114
2115impl Hasher for GpuHasher {
2117 fn finish(&self) -> u64 {
2123 self.get_state()
2124 }
2125
2126 fn write(&mut self, bytes: &[u8]) {
2132 for byte in bytes {
2133 self.write_u8(*byte);
2134 }
2135 }
2136
2137 fn write_u8(&mut self, value: u8) {
2143 self.set_state(self.get_state() ^ u64::from(value));
2144 self.set_state(self.get_state().wrapping_mul(Self::PRIME));
2145 }
2146
2147 fn write_u32(&mut self, value: u32) {
2153 for byte in value.to_le_bytes() {
2154 self.write_u8(byte);
2155 }
2156 }
2157
2158 fn write_u64(&mut self, value: u64) {
2164 for byte in value.to_le_bytes() {
2165 self.write_u8(byte);
2166 }
2167 }
2168
2169 fn write_i32(&mut self, value: i32) {
2175 self.write_u32(value as u32);
2176 }
2177}