1use super::*;
2
3impl Camera2D {
5 pub fn create(viewport_width: f64, viewport_height: f64) -> Camera2D {
16 Camera2D::new(
17 Vector2D::zero(),
18 RENDERER_DEFAULT_CAMERA_ZOOM,
19 RENDERER_DEFAULT_CAMERA_ROTATION,
20 viewport_width,
21 viewport_height,
22 )
23 }
24
25 pub fn world_to_screen(&self, world: Vector2D) -> Vector2D {
35 let relative: Vector2D = world - self.get_position();
36 let rotated: Vector2D = relative.rotated(-self.get_rotation());
37 Vector2D::new(
38 rotated.get_x() * self.get_zoom() + self.get_viewport_width() * 0.5,
39 rotated.get_y() * self.get_zoom() + self.get_viewport_height() * 0.5,
40 )
41 }
42
43 pub fn screen_to_world(&self, screen: Vector2D) -> Vector2D {
53 let relative: Vector2D = Vector2D::new(
54 (screen.get_x() - self.get_viewport_width() * 0.5) / self.get_zoom(),
55 (screen.get_y() - self.get_viewport_height() * 0.5) / self.get_zoom(),
56 );
57 let rotated: Vector2D = relative.rotated(self.get_rotation());
58 rotated + self.get_position()
59 }
60
61 pub fn translate(&mut self, offset: Vector2D) {
67 self.set_position(self.get_position() + offset);
68 }
69
70 pub fn zoom_by(&mut self, factor: f64) {
76 self.set_zoom((self.get_zoom() * factor).max(EPSILON));
77 }
78}
79
80impl Default for Camera2D {
82 fn default() -> Camera2D {
83 Camera2D::create(800.0, 600.0)
84 }
85}
86
87impl CanvasRenderer {
89 pub fn font<F>(size: f64, family: F) -> String
100 where
101 F: AsRef<str>,
102 {
103 let family: &str = family.as_ref();
104 format!("{size}px {family}")
105 }
106
107 pub fn default_font() -> String {
113 Self::font(RENDERER_DEFAULT_FONT_SIZE, RENDERER_DEFAULT_FONT_FAMILY)
114 }
115
116 pub fn enable_smoothing_on(context: &CanvasRenderingContext2d) {
130 Self::apply_quality(context, RenderQuality::High);
131 }
132
133 pub fn detect_dpr() -> f64 {
145 let window_value: Window = window().expect("no global window exists");
146 let raw: Option<f64> = Reflect::get(
147 window_value.as_ref(),
148 &JsValue::from_str(RENDERER_PROPERTY_DEVICE_PIXEL_RATIO),
149 )
150 .ok()
151 .and_then(|value: JsValue| value.as_f64());
152 raw.filter(|value: &f64| value.is_finite() && *value >= 1.0)
153 .unwrap_or(RENDERER_DEFAULT_DEVICE_PIXEL_RATIO)
154 }
155
156 pub(crate) fn apply_quality(context: &CanvasRenderingContext2d, quality: RenderQuality) {
168 let smoothing_enabled: bool = !matches!(quality, RenderQuality::Low);
169 context.set_image_smoothing_enabled(smoothing_enabled);
170 let quality_value: &str = match quality {
171 RenderQuality::Low => RENDERER_IMAGE_SMOOTHING_QUALITY_LOW,
172 RenderQuality::Medium => RENDERER_IMAGE_SMOOTHING_QUALITY_MEDIUM,
173 RenderQuality::High => RENDERER_IMAGE_SMOOTHING_QUALITY_HIGH,
174 };
175 let _: Result<bool, JsValue> = Reflect::set(
176 context,
177 &JsValue::from_str(RENDERER_PROPERTY_IMAGE_SMOOTHING_QUALITY),
178 &JsValue::from_str(quality_value),
179 );
180 let _: Result<bool, JsValue> = Reflect::set(
181 context,
182 &JsValue::from_str(RENDERER_PROPERTY_TEXT_RENDERING),
183 &JsValue::from_str(RENDERER_TEXT_RENDERING_GEOMETRIC_PRECISION),
184 );
185 }
186}
187
188impl Color {
190 pub fn to_css(color: &Color) -> String {
200 color.to_css_rgba()
201 }
202}
203
204fn self_commands(list: &DrawList) -> &[DrawCommand] {
206 list.get_commands().as_slice()
207}
208
209fn draw_sprite_immediate(
214 context: &CanvasRenderingContext2d,
215 image: &HtmlImageElement,
216 source: &Rect,
217 transform: &Transform2D,
218) {
219 let rotation: f64 = transform.get_rotation();
220 let cos: f64 = rotation.cos();
221 let sin: f64 = rotation.sin();
222 let scale_x: f64 = transform.get_scale().get_x();
223 let scale_y: f64 = transform.get_scale().get_y();
224 let _: Result<(), JsValue> = context.set_transform(
225 cos * scale_x,
226 sin * scale_x,
227 -sin * scale_y,
228 cos * scale_y,
229 transform.get_position().get_x(),
230 transform.get_position().get_y(),
231 );
232 let _: Result<(), JsValue> = context
233 .draw_image_with_html_image_element_and_sw_and_sh_and_dx_and_dy_and_dw_and_dh(
234 image,
235 source.get_x(),
236 source.get_y(),
237 source.get_width(),
238 source.get_height(),
239 -source.get_width() * 0.5,
240 -source.get_height() * 0.5,
241 source.get_width(),
242 source.get_height(),
243 );
244 let _: Result<(), JsValue> = context.set_transform(1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
245}
246
247impl DrawList {
250 pub fn create() -> DrawList {
256 DrawList::new(Vec::new())
257 }
258
259 pub fn is_empty(&self) -> bool {
265 self.get_commands().is_empty()
266 }
267
268 pub fn len(&self) -> usize {
274 self.get_commands().len()
275 }
276
277 pub fn clear(&mut self) {
280 self.get_mut_commands().clear();
281 }
282
283 pub fn fill_rect(&mut self, position: Vector2D, width: f64, height: f64, color: Color) {
285 self.get_mut_commands().push(DrawCommand::FillRect {
286 position,
287 width,
288 height,
289 color,
290 });
291 }
292
293 pub fn stroke_rect(
295 &mut self,
296 position: Vector2D,
297 width: f64,
298 height: f64,
299 color: Color,
300 line_width: f64,
301 ) {
302 self.get_mut_commands().push(DrawCommand::StrokeRect {
303 position,
304 width,
305 height,
306 color,
307 line_width,
308 });
309 }
310
311 pub fn fill_circle(&mut self, center: Vector2D, radius: f64, color: Color) {
313 self.get_mut_commands().push(DrawCommand::FillCircle {
314 center,
315 radius,
316 color,
317 });
318 }
319
320 pub fn stroke_circle(&mut self, center: Vector2D, radius: f64, color: Color, line_width: f64) {
322 self.get_mut_commands().push(DrawCommand::StrokeCircle {
323 center,
324 radius,
325 color,
326 line_width,
327 });
328 }
329
330 pub fn draw_line(&mut self, start: Vector2D, end: Vector2D, color: Color, line_width: f64) {
332 self.get_mut_commands().push(DrawCommand::Line {
333 start,
334 end,
335 color,
336 line_width,
337 });
338 }
339
340 pub fn fill_text<T, F>(&mut self, text: T, position: Vector2D, color: Color, font: F)
342 where
343 T: AsRef<str>,
344 F: AsRef<str>,
345 {
346 self.get_mut_commands().push(DrawCommand::FillText {
347 text: text.as_ref().to_string(),
348 position,
349 color,
350 font: font.as_ref().to_string(),
351 });
352 }
353
354 pub fn draw_sprite(&mut self, image: &HtmlImageElement, source: Rect, transform: Transform2D) {
356 self.get_mut_commands().push(DrawCommand::DrawSprite {
357 image: image.clone(),
358 source,
359 transform,
360 });
361 }
362
363 pub fn draw_image_rect(
365 &mut self,
366 image: &HtmlImageElement,
367 source: Rect,
368 dest_position: Vector2D,
369 dest_width: f64,
370 dest_height: f64,
371 ) {
372 self.get_mut_commands().push(DrawCommand::DrawImageRect {
373 image: image.clone(),
374 source,
375 dest_position,
376 dest_width,
377 dest_height,
378 });
379 }
380
381 pub fn set_global_alpha(&mut self, alpha: f64) {
383 self.get_mut_commands()
384 .push(DrawCommand::SetGlobalAlpha { alpha });
385 }
386
387 pub fn set_blend_mode(&mut self, mode: BlendMode) {
389 self.get_mut_commands()
390 .push(DrawCommand::SetBlendMode { mode });
391 }
392}
393
394impl CanvasRenderer {
395 pub fn from_selector<S>(
407 canvas_selector: S,
408 viewport_width: f64,
409 viewport_height: f64,
410 ) -> Option<CanvasRenderer>
411 where
412 S: AsRef<str>,
413 {
414 let window_value: Window = window().expect("no global window exists");
415 let document_value: Document = window_value.document().expect("should have a document");
416 let element: Element = document_value
417 .query_selector(canvas_selector.as_ref())
418 .ok()
419 .flatten()?;
420 let canvas_element: HtmlCanvasElement = element.unchecked_into();
421 let context_object: Object = canvas_element
422 .get_context(RENDERER_CONTEXT_TYPE_2D)
423 .ok()
424 .flatten()?;
425 let context: CanvasRenderingContext2d = context_object.unchecked_into();
426 let renderer: CanvasRenderer = CanvasRenderer::new(
427 context,
428 Camera2D::create(viewport_width, viewport_height),
429 RenderQuality::default(),
430 );
431 renderer.enable_smoothing();
432 Some(renderer)
433 }
434
435 pub fn enable_smoothing(&self) {
441 Self::apply_quality(self.get_context(), self.get_quality());
442 }
443
444 pub fn clear(&self) {
446 self.get_context().clear_rect(
447 0.0,
448 0.0,
449 self.get_camera().get_viewport_width(),
450 self.get_camera().get_viewport_height(),
451 );
452 }
453
454 pub fn clear_color<C>(&self, color: C)
460 where
461 C: AsRef<str>,
462 {
463 self.get_context().set_fill_style_str(color.as_ref());
464 self.get_context().fill_rect(
465 0.0,
466 0.0,
467 self.get_camera().get_viewport_width(),
468 self.get_camera().get_viewport_height(),
469 );
470 }
471
472 pub fn save(&self) {
474 self.get_context().save();
475 }
476
477 pub fn restore(&self) {
479 self.get_context().restore();
480 }
481
482 pub fn replay(&self, list: &DrawList) {
490 Self::replay_context(self.get_context(), list);
491 }
492
493 pub fn replay_context(context: &CanvasRenderingContext2d, list: &DrawList) {
511 let mut current_fill: Option<Color> = None;
512 let mut current_stroke: Option<Color> = None;
513 let mut current_line_width: f64 = f64::NAN;
514 let mut run_open: bool = false;
516 let mut run_is_fill: bool = true;
517 let mut run_key: Option<(u8, Color, f64)> = None;
518
519 fn batch_key(command: &DrawCommand) -> Option<(u8, Color, f64)> {
522 match command {
523 DrawCommand::FillRect { color, .. } | DrawCommand::FillCircle { color, .. } => {
524 Some((0, *color, 0.0))
525 }
526 DrawCommand::StrokeRect {
527 color, line_width, ..
528 }
529 | DrawCommand::StrokeCircle {
530 color, line_width, ..
531 }
532 | DrawCommand::Line {
533 color, line_width, ..
534 } => Some((1, *color, *line_width)),
535 _ => None,
536 }
537 }
538
539 fn emit_geometry(context: &CanvasRenderingContext2d, command: &DrawCommand) {
541 match command {
542 DrawCommand::FillRect {
543 position,
544 width,
545 height,
546 ..
547 }
548 | DrawCommand::StrokeRect {
549 position,
550 width,
551 height,
552 ..
553 } => {
554 context.rect(position.get_x(), position.get_y(), *width, *height);
555 }
556 DrawCommand::FillCircle { center, radius, .. }
557 | DrawCommand::StrokeCircle { center, radius, .. } => {
558 context.move_to(center.get_x() + radius, center.get_y());
559 let _: Result<(), JsValue> =
560 context.arc(center.get_x(), center.get_y(), *radius, 0.0, TWO_PI);
561 }
562 DrawCommand::Line { start, end, .. } => {
563 context.move_to(start.get_x(), start.get_y());
564 context.line_to(end.get_x(), end.get_y());
565 }
566 _ => {}
567 }
568 }
569
570 for command in self_commands(list) {
571 let key: Option<(u8, Color, f64)> = batch_key(command);
572 if run_open && key != run_key {
574 if run_is_fill {
575 context.fill();
576 } else {
577 context.stroke();
578 }
579 run_open = false;
580 }
581 if let Some(current_key) = key {
582 if !run_open {
584 let (kind, color, line_width) = current_key;
585 if kind == 0 {
586 if current_fill != Some(color) {
587 context.set_fill_style_str(&Color::to_css(&color));
588 current_fill = Some(color);
589 }
590 run_is_fill = true;
591 } else {
592 if current_stroke != Some(color) {
593 context.set_stroke_style_str(&Color::to_css(&color));
594 current_stroke = Some(color);
595 }
596 if current_line_width != line_width {
597 context.set_line_width(line_width);
598 current_line_width = line_width;
599 }
600 run_is_fill = false;
601 }
602 context.begin_path();
603 run_open = true;
604 run_key = Some(current_key);
605 }
606 emit_geometry(context, command);
607 continue;
608 }
609 match command {
611 DrawCommand::FillText {
612 text,
613 position,
614 color,
615 font,
616 } => {
617 if current_fill != Some(*color) {
618 context.set_fill_style_str(&Color::to_css(color));
619 current_fill = Some(*color);
620 }
621 context.set_font(font);
622 let _: Result<(), JsValue> =
623 context.fill_text(text, position.get_x(), position.get_y());
624 }
625 DrawCommand::DrawSprite {
626 image,
627 source,
628 transform,
629 } => {
630 draw_sprite_immediate(context, image, source, transform);
631 }
632 DrawCommand::DrawImageRect {
633 image,
634 source,
635 dest_position,
636 dest_width,
637 dest_height,
638 } => {
639 let _: Result<(), JsValue> = context
640 .draw_image_with_html_image_element_and_sw_and_sh_and_dx_and_dy_and_dw_and_dh(
641 image,
642 source.get_x(),
643 source.get_y(),
644 source.get_width(),
645 source.get_height(),
646 dest_position.get_x(),
647 dest_position.get_y(),
648 *dest_width,
649 *dest_height,
650 );
651 }
652 DrawCommand::SetGlobalAlpha { alpha } => {
653 context.set_global_alpha(Numeric::clamp(*alpha, 0.0, 1.0));
654 }
655 DrawCommand::SetBlendMode { mode } => {
656 let _: Result<(), JsValue> =
657 context.set_global_composite_operation(mode.to_css());
658 }
659 _ => {}
660 }
661 }
662 if run_open {
664 if run_is_fill {
665 context.fill();
666 } else {
667 context.stroke();
668 }
669 }
670 let _: Result<(), JsValue> = context.set_transform(1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
671 context.set_global_alpha(1.0);
672 }
673
674 pub fn apply_camera(&self) {
679 let camera: Camera2D = self.get_camera();
680 let _: Result<(), JsValue> = self.get_context().translate(
681 camera.get_viewport_width() * 0.5,
682 camera.get_viewport_height() * 0.5,
683 );
684 let _: Result<(), JsValue> = self
685 .get_context()
686 .scale(camera.get_zoom(), camera.get_zoom());
687 let _: Result<(), JsValue> = self.get_context().rotate(camera.get_rotation());
688 let _: Result<(), JsValue> = self.get_context().translate(
689 -camera.get_position().get_x(),
690 -camera.get_position().get_y(),
691 );
692 }
693
694 pub fn set_fill_color<C>(&self, color: C)
700 where
701 C: AsRef<str>,
702 {
703 self.get_context().set_fill_style_str(color.as_ref());
704 }
705
706 pub fn set_stroke_color<C>(&self, color: C)
712 where
713 C: AsRef<str>,
714 {
715 self.get_context().set_stroke_style_str(color.as_ref());
716 }
717
718 pub fn set_line_width(&self, width: f64) {
724 self.get_context().set_line_width(width);
725 }
726
727 pub fn set_global_alpha(&self, alpha: f64) {
733 self.get_context()
734 .set_global_alpha(Numeric::clamp(alpha, 0.0, 1.0));
735 }
736
737 pub fn fill_rect(&self, position: Vector2D, width: f64, height: f64) {
745 self.get_context()
746 .fill_rect(position.get_x(), position.get_y(), width, height);
747 }
748
749 pub fn stroke_rect(&self, position: Vector2D, width: f64, height: f64) {
757 self.get_context()
758 .stroke_rect(position.get_x(), position.get_y(), width, height);
759 }
760
761 pub fn fill_circle(&self, center: Vector2D, radius: f64) {
768 self.get_context().begin_path();
769 self.get_context()
770 .arc(center.get_x(), center.get_y(), radius, 0.0, TWO_PI)
771 .unwrap_or(());
772 self.get_context().fill();
773 }
774
775 pub fn stroke_circle(&self, center: Vector2D, radius: f64) {
782 self.get_context().begin_path();
783 self.get_context()
784 .arc(center.get_x(), center.get_y(), radius, 0.0, TWO_PI)
785 .unwrap_or(());
786 self.get_context().stroke();
787 }
788
789 pub fn draw_line(&self, start: Vector2D, end: Vector2D) {
796 self.get_context().begin_path();
797 self.get_context().move_to(start.get_x(), start.get_y());
798 self.get_context().line_to(end.get_x(), end.get_y());
799 self.get_context().stroke();
800 }
801
802 pub fn fill_text<T>(&self, text: T, position: Vector2D)
809 where
810 T: AsRef<str>,
811 {
812 self.get_context()
813 .fill_text(text.as_ref(), position.get_x(), position.get_y())
814 .unwrap_or(());
815 }
816
817 pub fn set_font<F>(&self, font: F)
823 where
824 F: AsRef<str>,
825 {
826 self.get_context().set_font(font.as_ref());
827 }
828
829 pub fn draw_image(
838 &self,
839 image: &HtmlImageElement,
840 position: Vector2D,
841 width: f64,
842 height: f64,
843 ) {
844 let _: Result<(), JsValue> = self
845 .get_context()
846 .draw_image_with_html_image_element_and_dw_and_dh(
847 image,
848 position.get_x(),
849 position.get_y(),
850 width,
851 height,
852 );
853 }
854
855 pub fn draw_image_rect(
865 &self,
866 image: &HtmlImageElement,
867 source: Rect,
868 dest_position: Vector2D,
869 dest_width: f64,
870 dest_height: f64,
871 ) {
872 let _: Result<(), JsValue> = self
873 .get_context()
874 .draw_image_with_html_image_element_and_sw_and_sh_and_dx_and_dy_and_dw_and_dh(
875 image,
876 source.get_x(),
877 source.get_y(),
878 source.get_width(),
879 source.get_height(),
880 dest_position.get_x(),
881 dest_position.get_y(),
882 dest_width,
883 dest_height,
884 );
885 }
886}
887
888impl Camera3D {
890 pub fn create(
903 position: Vector3D,
904 target: Vector3D,
905 viewport_width: f64,
906 viewport_height: f64,
907 ) -> Camera3D {
908 let mut camera: Camera3D = Camera3D::new(position, target, viewport_width, viewport_height);
909 camera.set_up(Vector3D::up());
910 camera.set_fov(DEFAULT_CAMERA_FOV);
911 camera.set_near(DEFAULT_CAMERA_NEAR);
912 camera.set_far(DEFAULT_CAMERA_FAR);
913 camera
914 }
915
916 pub fn aspect(&self) -> f64 {
922 if self.get_viewport_height() < EPSILON {
923 return 1.0;
924 }
925 self.get_viewport_width() / self.get_viewport_height()
926 }
927
928 pub fn forward(&self) -> Vector3D {
934 (self.get_target() - self.get_position()).normalized()
935 }
936
937 pub fn right(&self) -> Vector3D {
943 self.forward().cross(self.get_up()).normalized()
944 }
945
946 pub fn view_matrix(&self) -> Matrix4x4 {
952 Matrix4x4::look_at(self.get_position(), self.get_target(), self.get_up())
953 }
954
955 pub fn projection_matrix(&self) -> Matrix4x4 {
961 Matrix4x4::perspective(
962 self.get_fov(),
963 self.aspect(),
964 self.get_near(),
965 self.get_far(),
966 )
967 }
968
969 pub fn view_proj_matrix(&self) -> Matrix4x4 {
975 self.projection_matrix().multiply(self.view_matrix())
976 }
977
978 pub fn world_to_screen(&self, world: Vector3D) -> Vector3D {
988 let clip: Vector3D = self.view_proj_matrix().transform_point(world);
989 Vector3D::new(
990 (clip.get_x() + 1.0) * 0.5 * self.get_viewport_width(),
991 (1.0 - clip.get_y()) * 0.5 * self.get_viewport_height(),
992 clip.get_z(),
993 )
994 }
995
996 pub fn in_frustum(&self, world: Vector3D) -> bool {
1006 let clip: Vector3D = self.view_proj_matrix().transform_point(world);
1007 clip.get_x() >= -1.0
1008 && clip.get_x() <= 1.0
1009 && clip.get_y() >= -1.0
1010 && clip.get_y() <= 1.0
1011 && clip.get_z() >= -1.0
1012 && clip.get_z() <= 1.0
1013 }
1014
1015 pub fn translate(&mut self, offset: Vector3D) {
1021 self.set_position(self.get_position() + offset);
1022 self.set_target(self.get_target() + offset);
1023 }
1024
1025 pub fn zoom(&mut self, distance: f64) {
1031 let direction: Vector3D = self.forward();
1032 self.set_position(self.get_position() + direction.scaled(distance));
1033 }
1034
1035 pub fn orbit(&mut self, yaw_delta: f64, pitch_delta: f64) {
1042 let offset: Vector3D = self.get_position() - self.get_target();
1043 let current_distance: f64 = offset.magnitude();
1044 let current_yaw: f64 = offset.get_x().atan2(offset.get_z());
1045 let horizontal_dist: f64 =
1046 (offset.get_x() * offset.get_x() + offset.get_z() * offset.get_z()).sqrt();
1047 let current_pitch: f64 = (offset.get_y() / horizontal_dist.max(EPSILON)).asin();
1048 let new_yaw: f64 = current_yaw + yaw_delta;
1049 let new_pitch: f64 = Numeric::clamp(
1050 current_pitch + pitch_delta,
1051 -HALF_PI + EPSILON,
1052 HALF_PI - EPSILON,
1053 );
1054 let cos_pitch: f64 = new_pitch.cos();
1055 self.set_position(
1056 self.get_target()
1057 + Vector3D::new(
1058 new_yaw.sin() * cos_pitch * current_distance,
1059 new_pitch.sin() * current_distance,
1060 new_yaw.cos() * cos_pitch * current_distance,
1061 ),
1062 );
1063 }
1064}
1065
1066impl Default for Camera3D {
1068 fn default() -> Camera3D {
1069 Camera3D::create(Vector3D::new(0.0, 0.0, 5.0), Vector3D::zero(), 800.0, 600.0)
1070 }
1071}
1072
1073impl SsaaCanvas {
1075 pub fn from_selector<S>(canvas_selector: S, width: f64, height: f64) -> Option<SsaaCanvas>
1087 where
1088 S: AsRef<str>,
1089 {
1090 Self::from_selector_with_scale(
1091 canvas_selector,
1092 width,
1093 height,
1094 RENDERER_DEFAULT_SSAA_SCALE_FACTOR,
1095 )
1096 }
1097
1098 pub fn from_selector_with_scale<S>(
1114 canvas_selector: S,
1115 width: f64,
1116 height: f64,
1117 scale_factor: f64,
1118 ) -> Option<SsaaCanvas>
1119 where
1120 S: AsRef<str>,
1121 {
1122 let window_value: Window = window().expect("no global window exists");
1123 let document_value: Document = window_value.document().expect("should have a document");
1124 let element: Element = document_value
1125 .query_selector(canvas_selector.as_ref())
1126 .ok()
1127 .flatten()?;
1128 let display_canvas: HtmlCanvasElement = element.unchecked_into();
1129 let device_pixel_ratio: f64 = CanvasRenderer::detect_dpr();
1130 let physical_width: u32 = (width * device_pixel_ratio).round() as u32;
1131 let physical_height: u32 = (height * device_pixel_ratio).round() as u32;
1132 display_canvas.set_width(physical_width);
1133 display_canvas.set_height(physical_height);
1134 let display_context_object: Object = display_canvas
1135 .get_context(RENDERER_CONTEXT_TYPE_2D)
1136 .ok()
1137 .flatten()?;
1138 let display_context: CanvasRenderingContext2d = display_context_object.unchecked_into();
1139 let _: Result<(), JsValue> = display_context.scale(device_pixel_ratio, device_pixel_ratio);
1140 let offscreen_canvas: HtmlCanvasElement = document_value
1141 .create_element(RENDERER_ELEMENT_CANVAS)
1142 .ok()?
1143 .unchecked_into();
1144 let scaled_width: u32 = (width * scale_factor * device_pixel_ratio).round() as u32;
1145 let scaled_height: u32 = (height * scale_factor * device_pixel_ratio).round() as u32;
1146 offscreen_canvas.set_width(scaled_width);
1147 offscreen_canvas.set_height(scaled_height);
1148 let offscreen_context_object: Object = offscreen_canvas
1149 .get_context(RENDERER_CONTEXT_TYPE_2D)
1150 .ok()
1151 .flatten()?;
1152 let offscreen_context: CanvasRenderingContext2d = offscreen_context_object.unchecked_into();
1153 let _: Result<(), JsValue> = offscreen_context.scale(
1154 scale_factor * device_pixel_ratio,
1155 scale_factor * device_pixel_ratio,
1156 );
1157 let ssaa_canvas: SsaaCanvas = SsaaCanvas::new(
1158 display_canvas,
1159 display_context,
1160 offscreen_canvas,
1161 offscreen_context,
1162 scale_factor,
1163 width,
1164 height,
1165 );
1166 ssaa_canvas.enable_smoothing();
1167 Some(ssaa_canvas)
1168 }
1169
1170 pub fn present(&self) {
1177 CanvasRenderer::apply_quality(self.get_display_context(), self.get_quality());
1178 self.get_display_context()
1179 .clear_rect(0.0, 0.0, self.get_width(), self.get_height());
1180 let _: Result<(), JsValue> = self
1181 .get_display_context()
1182 .draw_image_with_html_canvas_element_and_dw_and_dh(
1183 self.get_offscreen_canvas(),
1184 0.0,
1185 0.0,
1186 self.get_width(),
1187 self.get_height(),
1188 );
1189 }
1190
1191 pub fn clear(&self) {
1193 self.get_offscreen_context()
1194 .clear_rect(0.0, 0.0, self.get_width(), self.get_height());
1195 }
1196
1197 pub fn clear_color<C>(&self, color: C)
1203 where
1204 C: AsRef<str>,
1205 {
1206 self.get_offscreen_context()
1207 .set_fill_style_str(color.as_ref());
1208 self.get_offscreen_context()
1209 .fill_rect(0.0, 0.0, self.get_width(), self.get_height());
1210 }
1211
1212 pub fn enable_smoothing(&self) {
1217 let quality: RenderQuality = self.get_quality();
1218 CanvasRenderer::apply_quality(self.get_display_context(), quality);
1219 CanvasRenderer::apply_quality(self.get_offscreen_context(), quality);
1220 }
1221}
1222
1223impl BlendMode {
1225 pub fn to_css(&self) -> &str {
1231 match self {
1232 BlendMode::Normal => BLEND_MODE_NORMAL,
1233 BlendMode::Multiply => BLEND_MODE_MULTIPLY,
1234 BlendMode::Screen => BLEND_MODE_SCREEN,
1235 BlendMode::Lighter => BLEND_MODE_LIGHTER,
1236 BlendMode::Overlay => BLEND_MODE_OVERLAY,
1237 BlendMode::Darken => BLEND_MODE_DARKEN,
1238 BlendMode::Lighten => BLEND_MODE_LIGHTEN,
1239 BlendMode::ColorDodge => BLEND_MODE_COLOR_DODGE,
1240 BlendMode::ColorBurn => BLEND_MODE_COLOR_BURN,
1241 BlendMode::HardLight => BLEND_MODE_HARD_LIGHT,
1242 BlendMode::SoftLight => BLEND_MODE_SOFT_LIGHT,
1243 BlendMode::Difference => BLEND_MODE_DIFFERENCE,
1244 BlendMode::Exclusion => BLEND_MODE_EXCLUSION,
1245 BlendMode::Hue => BLEND_MODE_HUE,
1246 BlendMode::Saturation => BLEND_MODE_SATURATION,
1247 BlendMode::Color => BLEND_MODE_COLOR,
1248 BlendMode::Luminosity => BLEND_MODE_LUMINOSITY,
1249 }
1250 }
1251}
1252
1253impl LinearGradient {
1255 pub fn create(start: Vector2D, end: Vector2D, stops: Vec<(f64, String)>) -> LinearGradient {
1267 LinearGradient::new(start, end, stops)
1268 }
1269
1270 pub fn to_gradient(&self, context: &CanvasRenderingContext2d) -> Option<CanvasGradient> {
1280 let canvas_gradient: CanvasGradient = context.create_linear_gradient(
1281 self.get_start().get_x(),
1282 self.get_start().get_y(),
1283 self.get_end().get_x(),
1284 self.get_end().get_y(),
1285 );
1286 for (position, color) in self.get_stops() {
1287 let _: Result<(), JsValue> = canvas_gradient.add_color_stop(*position as f32, color);
1288 }
1289 Some(canvas_gradient)
1290 }
1291}
1292
1293impl RadialGradient {
1295 pub fn create(
1309 inner_center: Vector2D,
1310 inner_radius: f64,
1311 outer_center: Vector2D,
1312 outer_radius: f64,
1313 stops: Vec<(f64, String)>,
1314 ) -> RadialGradient {
1315 RadialGradient::new(
1316 inner_center,
1317 inner_radius,
1318 outer_center,
1319 outer_radius,
1320 stops,
1321 )
1322 }
1323
1324 pub fn to_gradient(&self, context: &CanvasRenderingContext2d) -> Option<CanvasGradient> {
1334 let canvas_gradient: CanvasGradient = context
1335 .create_radial_gradient(
1336 self.get_inner_center().get_x(),
1337 self.get_inner_center().get_y(),
1338 self.get_inner_radius(),
1339 self.get_outer_center().get_x(),
1340 self.get_outer_center().get_y(),
1341 self.get_outer_radius(),
1342 )
1343 .ok()?;
1344 for (position, color) in self.get_stops() {
1345 let _: Result<(), JsValue> = canvas_gradient.add_color_stop(*position as f32, color);
1346 }
1347 Some(canvas_gradient)
1348 }
1349}
1350
1351impl ShadowConfig {
1353 pub fn create() -> ShadowConfig {
1359 ShadowConfig::new(
1360 RENDERER_DEFAULT_SHADOW_COLOR.to_string(),
1361 RENDERER_DEFAULT_SHADOW_BLUR,
1362 0.0,
1363 0.0,
1364 )
1365 }
1366}
1367
1368impl Default for ShadowConfig {
1370 fn default() -> ShadowConfig {
1371 ShadowConfig::create()
1372 }
1373}
1374
1375impl RenderLayer {
1377 pub fn create(z_index: i32, visible: bool) -> RenderLayer {
1388 RenderLayer::new(z_index, visible)
1389 }
1390
1391 pub fn background() -> RenderLayer {
1397 RenderLayer::new(RENDERER_LAYER_BACKGROUND, true)
1398 }
1399
1400 pub fn foreground() -> RenderLayer {
1406 RenderLayer::new(RENDERER_LAYER_FOREGROUND, true)
1407 }
1408
1409 pub fn ui() -> RenderLayer {
1415 RenderLayer::new(RENDERER_LAYER_UI, true)
1416 }
1417}
1418
1419impl CanvasRenderer {
1421 pub fn set_blend_mode(&self, mode: BlendMode) {
1427 let _: Result<(), JsValue> = self
1428 .get_context()
1429 .set_global_composite_operation(mode.to_css());
1430 }
1431
1432 pub fn set_shadow(&self, config: &ShadowConfig) {
1438 self.get_context()
1439 .set_shadow_color(config.get_color().as_str());
1440 self.get_context().set_shadow_blur(config.get_blur());
1441 self.get_context()
1442 .set_shadow_offset_x(config.get_offset_x());
1443 self.get_context()
1444 .set_shadow_offset_y(config.get_offset_y());
1445 }
1446
1447 pub fn clear_shadow(&self) {
1449 self.get_context().set_shadow_color("rgba(0, 0, 0, 0)");
1450 self.get_context().set_shadow_blur(0.0);
1451 self.get_context().set_shadow_offset_x(0.0);
1452 self.get_context().set_shadow_offset_y(0.0);
1453 }
1454
1455 pub fn set_linear_gradient_fill(&self, gradient: &LinearGradient) {
1461 if let Some(canvas_gradient) = gradient.to_gradient(self.get_context()) {
1462 self.get_context()
1463 .set_fill_style_canvas_gradient(&canvas_gradient);
1464 }
1465 }
1466
1467 pub fn set_radial_gradient_fill(&self, gradient: &RadialGradient) {
1473 if let Some(canvas_gradient) = gradient.to_gradient(self.get_context()) {
1474 self.get_context()
1475 .set_fill_style_canvas_gradient(&canvas_gradient);
1476 }
1477 }
1478
1479 pub fn set_linear_gradient_stroke(&self, gradient: &LinearGradient) {
1485 if let Some(canvas_gradient) = gradient.to_gradient(self.get_context()) {
1486 self.get_context()
1487 .set_stroke_style_canvas_gradient(&canvas_gradient);
1488 }
1489 }
1490
1491 pub fn set_radial_gradient_stroke(&self, gradient: &RadialGradient) {
1497 if let Some(canvas_gradient) = gradient.to_gradient(self.get_context()) {
1498 self.get_context()
1499 .set_stroke_style_canvas_gradient(&canvas_gradient);
1500 }
1501 }
1502}
1503
1504impl RenderBackend for CanvasRenderer {
1512 fn clear(&self) {
1514 self.clear();
1515 }
1516
1517 fn clear_color<C>(&self, color: C)
1519 where
1520 C: AsRef<str>,
1521 {
1522 self.clear_color(color);
1523 }
1524
1525 fn save(&self) {
1527 self.save();
1528 }
1529
1530 fn restore(&self) {
1532 self.restore();
1533 }
1534
1535 fn set_fill_color(&self, color: &str) {
1537 self.set_fill_color(color);
1538 }
1539
1540 fn set_stroke_color(&self, color: &str) {
1542 self.set_stroke_color(color);
1543 }
1544
1545 fn set_line_width(&self, width: f64) {
1547 self.set_line_width(width);
1548 }
1549
1550 fn set_global_alpha(&self, alpha: f64) {
1552 self.set_global_alpha(alpha);
1553 }
1554
1555 fn set_blend_mode(&self, mode: BlendMode) {
1557 self.set_blend_mode(mode);
1558 }
1559
1560 fn set_shadow(&self, config: &ShadowConfig) {
1562 self.set_shadow(config);
1563 }
1564
1565 fn clear_shadow(&self) {
1567 self.clear_shadow();
1568 }
1569
1570 fn fill_rect(&self, position: Vector2D, width: f64, height: f64) {
1572 self.fill_rect(position, width, height);
1573 }
1574
1575 fn stroke_rect(&self, position: Vector2D, width: f64, height: f64) {
1577 self.stroke_rect(position, width, height);
1578 }
1579
1580 fn fill_circle(&self, center: Vector2D, radius: f64) {
1582 self.fill_circle(center, radius);
1583 }
1584
1585 fn stroke_circle(&self, center: Vector2D, radius: f64) {
1587 self.stroke_circle(center, radius);
1588 }
1589
1590 fn draw_line(&self, start: Vector2D, end: Vector2D) {
1592 self.draw_line(start, end);
1593 }
1594
1595 fn fill_text(&self, text: &str, position: Vector2D) {
1597 self.fill_text(text, position);
1598 }
1599
1600 fn set_font(&self, font: &str) {
1602 self.set_font(font);
1603 }
1604
1605 fn draw_image(&self, image: &HtmlImageElement, position: Vector2D, width: f64, height: f64) {
1607 self.draw_image(image, position, width, height);
1608 }
1609
1610 fn set_linear_gradient_fill(&self, gradient: &LinearGradient) {
1612 self.set_linear_gradient_fill(gradient);
1613 }
1614
1615 fn set_radial_gradient_fill(&self, gradient: &RadialGradient) {
1617 self.set_radial_gradient_fill(gradient);
1618 }
1619}
1620
1621impl WebGpuRenderer {
1623 pub fn is_available() -> bool {
1644 let window_value: Window = match window() {
1645 Some(value) => value,
1646 None => return false,
1647 };
1648 let navigator: Navigator = window_value.navigator();
1649 let gpu_result: Result<JsValue, JsValue> = Reflect::get(
1650 navigator.as_ref(),
1651 &JsValue::from_str(WEBGPU_NAVIGATOR_GPU_KEY),
1652 );
1653 match gpu_result {
1654 Ok(value) => !value.is_undefined() && !value.is_null(),
1655 Err(_) => false,
1656 }
1657 }
1658
1659 pub async fn probe() -> bool {
1685 if !Self::is_available() {
1686 return false;
1687 }
1688 let window_value: Window = match window() {
1689 Some(value) => value,
1690 None => return false,
1691 };
1692 let navigator: Navigator = window_value.navigator();
1693 let gpu: JsValue = match Reflect::get(
1694 navigator.as_ref(),
1695 &JsValue::from_str(WEBGPU_NAVIGATOR_GPU_KEY),
1696 ) {
1697 Ok(value) => value,
1698 Err(_) => return false,
1699 };
1700 let request_adapter_fn: Function =
1701 match Reflect::get(&gpu, &JsValue::from_str(WEBGPU_METHOD_REQUEST_ADAPTER)) {
1702 Ok(value) => value.unchecked_into(),
1703 Err(_) => return false,
1704 };
1705 let adapter_promise: Promise = match request_adapter_fn.call0(&gpu) {
1706 Ok(value) => value.unchecked_into(),
1707 Err(_) => return false,
1708 };
1709 let adapter_value: JsValue =
1710 match JsFuture::from(Self::race_with_timeout(adapter_promise)).await {
1711 Ok(value) => value,
1712 Err(_) => return false,
1713 };
1714 !adapter_value.is_undefined() && !adapter_value.is_null()
1715 }
1716
1717 fn timeout_promise() -> Promise {
1744 let window_value: Window = window().expect("no global window exists");
1745 Promise::new(&mut |_resolve: Function, reject: Function| {
1746 let reject_fn: Function = reject.clone();
1747 let timer: Closure<dyn FnMut()> = Closure::wrap(Box::new(move || {
1748 let _: Result<JsValue, JsValue> = reject_fn.call1(
1749 &JsValue::UNDEFINED,
1750 &JsValue::from_str(RENDERER_TIMEOUT_ERROR_MESSAGE),
1751 );
1752 }));
1753 let _: Result<i32, JsValue> = window_value
1754 .set_timeout_with_callback_and_timeout_and_arguments_0(
1755 timer.as_ref().unchecked_ref(),
1756 INIT_PROMISE_TIMEOUT_MILLIS,
1757 );
1758 timer.forget();
1759 })
1760 }
1761
1762 fn race_with_timeout(promise: Promise) -> Promise {
1768 let array: Array = Array::of2(&promise, &Self::timeout_promise());
1769 Promise::race(&array)
1770 }
1771
1772 pub async fn init(config: &RenderConfig) -> Result<WebGpuRenderer, WebGpuInitError> {
1795 let window: Window = window().expect("no global window exists");
1796 let navigator: Navigator = window.navigator();
1797 let gpu_result: Result<JsValue, JsValue> = Reflect::get(
1798 navigator.as_ref(),
1799 &JsValue::from_str(WEBGPU_NAVIGATOR_GPU_KEY),
1800 );
1801 let gpu: JsValue = match gpu_result {
1802 Ok(value) => value,
1803 Err(err) => return Err(WebGpuInitError::NavigatorLookup(err)),
1804 };
1805 if gpu.is_undefined() || gpu.is_null() {
1806 return Err(WebGpuInitError::NavigatorGpuMissing);
1807 }
1808 let adapter_options: Object = Object::new();
1809 let _: Result<bool, JsValue> = Reflect::set(
1810 &adapter_options,
1811 &JsValue::from_str(WEBGPU_PROPERTY_POWER_PREFERENCE),
1812 &JsValue::from_str(config.power_preference.to_web_sys_string()),
1813 );
1814 let request_adapter_fn: Function =
1815 match Reflect::get(&gpu, &JsValue::from_str(WEBGPU_METHOD_REQUEST_ADAPTER)) {
1816 Ok(value) => value.unchecked_into(),
1817 Err(err) => return Err(WebGpuInitError::RequestAdapterLookup(err)),
1818 };
1819 let adapter_promise: Promise = match request_adapter_fn.call1(&gpu, &adapter_options) {
1820 Ok(value) => value.unchecked_into(),
1821 Err(err) => return Err(WebGpuInitError::RequestAdapterCall(err)),
1822 };
1823 let adapter_value: JsValue =
1824 match JsFuture::from(Self::race_with_timeout(adapter_promise)).await {
1825 Ok(value) => value,
1826 Err(err) => return Err(WebGpuInitError::AdapterPromise(err)),
1827 };
1828 if adapter_value.is_null() || adapter_value.is_undefined() {
1829 return Err(WebGpuInitError::AdapterUnavailable);
1830 }
1831 let device_descriptor: Object = Object::new();
1832 let request_device_fn: Function = match Reflect::get(
1833 &adapter_value,
1834 &JsValue::from_str(WEBGPU_METHOD_REQUEST_DEVICE),
1835 ) {
1836 Ok(value) => value.unchecked_into(),
1837 Err(err) => return Err(WebGpuInitError::RequestDeviceLookup(err)),
1838 };
1839 let device_promise: Promise =
1840 match request_device_fn.call1(&adapter_value, &device_descriptor) {
1841 Ok(value) => value.unchecked_into(),
1842 Err(err) => return Err(WebGpuInitError::RequestDeviceCall(err)),
1843 };
1844 let device_value: JsValue =
1845 match JsFuture::from(Self::race_with_timeout(device_promise)).await {
1846 Ok(value) => value,
1847 Err(err) => return Err(WebGpuInitError::DevicePromise(err)),
1848 };
1849 if device_value.is_null() || device_value.is_undefined() {
1850 return Err(WebGpuInitError::DeviceUnavailable);
1851 }
1852 let document: Document = window.document().expect("should have a document");
1853 let element: Element = match document.query_selector(&config.canvas_selector) {
1854 Ok(Some(el)) => el,
1855 Ok(None) => {
1856 return Err(WebGpuInitError::CanvasNotFound(
1857 config.canvas_selector.clone(),
1858 ));
1859 }
1860 Err(err) => return Err(WebGpuInitError::CanvasQuery(err)),
1861 };
1862 let canvas: HtmlCanvasElement = element.unchecked_into();
1863 let context_object: Option<Object> = canvas.get_context(WEBGPU_CONTEXT_TYPE).ok().flatten();
1864 let context_object: Object = match context_object {
1865 Some(c) => c,
1866 None => return Err(WebGpuInitError::CanvasContextUnavailable),
1867 };
1868 let context: JsValue = context_object.into();
1869 let get_format_fn: Function =
1870 match Reflect::get(&gpu, &JsValue::from_str(WEBGPU_METHOD_GET_PREFERRED_FORMAT)) {
1871 Ok(value) => value.unchecked_into(),
1872 Err(err) => return Err(WebGpuInitError::PreferredFormatLookup(err)),
1873 };
1874 let format_value: JsValue = match get_format_fn.call0(&gpu) {
1875 Ok(value) => value,
1876 Err(err) => return Err(WebGpuInitError::PreferredFormatCall(err)),
1877 };
1878 let format: String = match format_value.as_string() {
1879 Some(s) => s,
1880 None => return Err(WebGpuInitError::PreferredFormatType(format_value)),
1881 };
1882 let dpr: f64 = CanvasRenderer::detect_dpr();
1886 let physical_width: u32 = (config.width * dpr).round() as u32;
1887 let physical_height: u32 = (config.height * dpr).round() as u32;
1888 canvas.set_width(physical_width);
1889 canvas.set_height(physical_height);
1890 let canvas_config: Object = Object::new();
1891 let _: Result<bool, JsValue> = Reflect::set(
1892 &canvas_config,
1893 &JsValue::from_str(WEBGPU_PROPERTY_DEVICE),
1894 &device_value,
1895 );
1896 let _: Result<bool, JsValue> = Reflect::set(
1897 &canvas_config,
1898 &JsValue::from_str(WEBGPU_PROPERTY_FORMAT),
1899 &format_value,
1900 );
1901 let configure_fn: Function =
1902 match Reflect::get(&context, &JsValue::from_str(WEBGPU_METHOD_CONFIGURE)) {
1903 Ok(value) => value.unchecked_into(),
1904 Err(err) => return Err(WebGpuInitError::ConfigureLookup(err)),
1905 };
1906 let _: Result<JsValue, JsValue> = configure_fn.call1(&context, &canvas_config);
1907 let queue: JsValue =
1908 match Reflect::get(&device_value, &JsValue::from_str(WEBGPU_PROPERTY_QUEUE)) {
1909 Ok(value) => value,
1910 Err(err) => return Err(WebGpuInitError::QueueLookup(err)),
1911 };
1912 Ok(WebGpuRenderer {
1913 device: device_value,
1914 queue,
1915 context,
1916 canvas,
1917 format,
1918 width: physical_width,
1919 height: physical_height,
1920 antialias: config.antialias,
1921 multisample_texture: None,
1922 multisample_view: None,
1923 })
1924 }
1925
1926 fn create_multisample_texture(
1949 &self,
1950 physical_width: u32,
1951 physical_height: u32,
1952 ) -> (JsValue, JsValue) {
1953 let extent: Object = Object::new();
1954 let _: Result<bool, JsValue> = Reflect::set(
1955 &extent,
1956 &JsValue::from_str(WEBGPU_PROPERTY_EXTENT_WIDTH),
1957 &JsValue::from_f64(f64::from(physical_width)),
1958 );
1959 let _: Result<bool, JsValue> = Reflect::set(
1960 &extent,
1961 &JsValue::from_str(WEBGPU_PROPERTY_EXTENT_HEIGHT),
1962 &JsValue::from_f64(f64::from(physical_height)),
1963 );
1964 let _: Result<bool, JsValue> = Reflect::set(
1965 &extent,
1966 &JsValue::from_str(WEBGPU_PROPERTY_EXTENT_DEPTH),
1967 &JsValue::from_f64(1.0),
1968 );
1969 let descriptor: Object = Object::new();
1970 let _: Result<bool, JsValue> = Reflect::set(
1971 &descriptor,
1972 &JsValue::from_str(WEBGPU_PROPERTY_SIZE),
1973 &extent,
1974 );
1975 let _: Result<bool, JsValue> = Reflect::set(
1976 &descriptor,
1977 &JsValue::from_str(WEBGPU_PROPERTY_TEXTURE_FORMAT),
1978 &JsValue::from_str(&self.get_format()),
1979 );
1980 let _: Result<bool, JsValue> = Reflect::set(
1981 &descriptor,
1982 &JsValue::from_str(WEBGPU_PROPERTY_USAGE),
1983 &JsValue::from_f64(WEBGPU_TEXTURE_USAGE_RENDER_ATTACHMENT),
1984 );
1985 let _: Result<bool, JsValue> = Reflect::set(
1986 &descriptor,
1987 &JsValue::from_str(WEBGPU_PROPERTY_SAMPLE_COUNT),
1988 &JsValue::from_f64(4.0),
1989 );
1990 let create_texture_fn: Function = Reflect::get(
1991 self.get_device(),
1992 &JsValue::from_str(WEBGPU_METHOD_CREATE_TEXTURE),
1993 )
1994 .unwrap_or(JsValue::UNDEFINED)
1995 .unchecked_into();
1996 let texture: JsValue = create_texture_fn
1997 .call1(self.get_device(), &descriptor)
1998 .unwrap_or(JsValue::UNDEFINED);
1999 if texture.is_undefined() {
2000 return (JsValue::UNDEFINED, JsValue::UNDEFINED);
2001 }
2002 let create_view_fn: Function =
2003 Reflect::get(&texture, &JsValue::from_str(WEBGPU_METHOD_CREATE_VIEW))
2004 .unwrap_or(JsValue::UNDEFINED)
2005 .unchecked_into();
2006 let view: JsValue = create_view_fn.call0(&texture).unwrap_or(JsValue::UNDEFINED);
2007 if view.is_undefined() {
2008 return (texture, JsValue::UNDEFINED);
2009 }
2010 (texture, view)
2011 }
2012
2013 pub fn resize(&mut self, physical_width: u32, physical_height: u32) -> bool {
2038 if self.get_canvas().is_null()
2039 || self.get_context().is_null()
2040 || self.get_device().is_undefined()
2041 {
2042 return false;
2043 }
2044 self.get_canvas().set_width(physical_width);
2045 self.get_canvas().set_height(physical_height);
2046 let format_value: JsValue = JsValue::from_str(&self.get_format());
2047 let canvas_config: Object = Object::new();
2048 let _: Result<bool, JsValue> = Reflect::set(
2049 &canvas_config,
2050 &JsValue::from_str(WEBGPU_PROPERTY_DEVICE),
2051 self.get_device(),
2052 );
2053 let _: Result<bool, JsValue> = Reflect::set(
2054 &canvas_config,
2055 &JsValue::from_str(WEBGPU_PROPERTY_FORMAT),
2056 &format_value,
2057 );
2058 let configure_fn: Function = Reflect::get(
2059 self.get_context(),
2060 &JsValue::from_str(WEBGPU_METHOD_CONFIGURE),
2061 )
2062 .ok()
2063 .and_then(|value: JsValue| value.dyn_into::<Function>().ok())
2064 .unwrap_or_else(|| Function::new_no_args(""));
2065 if configure_fn
2066 .call1(self.get_context(), &canvas_config)
2067 .is_err()
2068 {
2069 return false;
2070 }
2071 self.set_width(physical_width);
2072 self.set_height(physical_height);
2073 if self.get_antialias() {
2081 let (texture, view) = self.create_multisample_texture(physical_width, physical_height);
2082 if !view.is_undefined() {
2083 self.set_multisample_texture(Some(texture));
2084 self.set_multisample_view(Some(view));
2085 } else {
2086 self.set_multisample_texture(None);
2087 self.set_multisample_view(None);
2088 }
2089 }
2090 true
2091 }
2092
2093 pub fn sync_to_current_canvas(&mut self) -> bool {
2111 let canvas_width: u32 = self.get_canvas().width();
2112 let canvas_height: u32 = self.get_canvas().height();
2113 let client_width: u32 = self.get_canvas().client_width().try_into().unwrap_or(0);
2114 let client_height: u32 = self.get_canvas().client_height().try_into().unwrap_or(0);
2115 let css_w: u32 = if client_width > 0 {
2120 client_width
2121 } else {
2122 canvas_width
2123 };
2124 let css_h: u32 = if client_height > 0 {
2125 client_height
2126 } else {
2127 canvas_height
2128 };
2129 if css_w == 0 || css_h == 0 {
2130 return false;
2131 }
2132 let dpr: f64 = CanvasRenderer::detect_dpr();
2133 let physical_width: u32 = (f64::from(css_w) * dpr).round() as u32;
2134 let physical_height: u32 = (f64::from(css_h) * dpr).round() as u32;
2135 self.resize(physical_width, physical_height)
2136 }
2137
2138 pub(crate) fn create_shader_module<S>(&self, code: S) -> JsValue
2148 where
2149 S: AsRef<str>,
2150 {
2151 let descriptor: Object = Object::new();
2152 let _: Result<bool, JsValue> = Reflect::set(
2153 &descriptor,
2154 &JsValue::from_str(WEBGPU_PROPERTY_CODE),
2155 &JsValue::from_str(code.as_ref()),
2156 );
2157 let create_fn: Function = Reflect::get(
2158 self.get_device(),
2159 &JsValue::from_str(WEBGPU_METHOD_CREATE_SHADER_MODULE),
2160 )
2161 .unwrap_or(JsValue::UNDEFINED)
2162 .unchecked_into();
2163 create_fn
2164 .call1(self.get_device(), &descriptor)
2165 .unwrap_or(JsValue::UNDEFINED)
2166 }
2167
2168 pub(crate) fn create_command_encoder(&self) -> JsValue {
2174 let create_fn: Function = Reflect::get(
2175 self.get_device(),
2176 &JsValue::from_str(WEBGPU_METHOD_CREATE_COMMAND_ENCODER),
2177 )
2178 .unwrap_or(JsValue::UNDEFINED)
2179 .unchecked_into();
2180 create_fn
2181 .call0(self.get_device())
2182 .unwrap_or(JsValue::UNDEFINED)
2183 }
2184
2185 pub(crate) fn get_current_texture_view(&self) -> JsValue {
2195 let get_texture_fn: Function = Reflect::get(
2196 self.get_context(),
2197 &JsValue::from_str(WEBGPU_METHOD_GET_CURRENT_TEXTURE),
2198 )
2199 .unwrap_or(JsValue::UNDEFINED)
2200 .unchecked_into();
2201 let texture: JsValue = get_texture_fn
2202 .call0(self.get_context())
2203 .unwrap_or(JsValue::UNDEFINED);
2204 let create_view_fn: Function =
2205 Reflect::get(&texture, &JsValue::from_str(WEBGPU_METHOD_CREATE_VIEW))
2206 .unwrap_or(JsValue::UNDEFINED)
2207 .unchecked_into();
2208 create_view_fn.call0(&texture).unwrap_or(JsValue::UNDEFINED)
2209 }
2210
2211 pub(crate) fn begin_render_pass(
2227 &mut self,
2228 encoder: &JsValue,
2229 clear_color: (f64, f64, f64, f64),
2230 ) -> JsValue {
2231 let swap_chain_view: JsValue = self.get_current_texture_view();
2232 let (color_view, resolve_view): (JsValue, Option<JsValue>) = if self.get_antialias() {
2238 let multisample_view: Option<JsValue> = self
2239 .get_multisample_view()
2240 .clone()
2241 .filter(|value: &JsValue| !value.is_undefined());
2242 let resolved: Option<JsValue> = match multisample_view {
2243 Some(view) => Some(view),
2244 None => {
2245 let width: u32 = self.get_width();
2250 let height: u32 = self.get_height();
2251 let (texture, view): (JsValue, JsValue) =
2252 self.create_multisample_texture(width, height);
2253 if !view.is_undefined() {
2254 self.set_multisample_texture(Some(texture));
2255 self.set_multisample_view(Some(view.clone()));
2256 Some(view)
2257 } else {
2258 self.set_multisample_texture(None);
2259 self.set_multisample_view(None);
2260 None
2261 }
2262 }
2263 };
2264 match resolved {
2265 Some(view) => (view, Some(swap_chain_view.clone())),
2266 None => (swap_chain_view.clone(), None),
2267 }
2268 } else {
2269 (swap_chain_view.clone(), None)
2270 };
2271 let color_dict: Object = Object::new();
2272 let _: Result<bool, JsValue> = Reflect::set(
2273 &color_dict,
2274 &JsValue::from_str(WEBGPU_PROPERTY_R),
2275 &JsValue::from_f64(clear_color.0),
2276 );
2277 let _: Result<bool, JsValue> = Reflect::set(
2278 &color_dict,
2279 &JsValue::from_str(WEBGPU_PROPERTY_G),
2280 &JsValue::from_f64(clear_color.1),
2281 );
2282 let _: Result<bool, JsValue> = Reflect::set(
2283 &color_dict,
2284 &JsValue::from_str(WEBGPU_PROPERTY_B),
2285 &JsValue::from_f64(clear_color.2),
2286 );
2287 let _: Result<bool, JsValue> = Reflect::set(
2288 &color_dict,
2289 &JsValue::from_str(WEBGPU_PROPERTY_A),
2290 &JsValue::from_f64(clear_color.3),
2291 );
2292 let attachment: Object = Object::new();
2293 let _: Result<bool, JsValue> = Reflect::set(
2294 &attachment,
2295 &JsValue::from_str(WEBGPU_PROPERTY_VIEW),
2296 &color_view,
2297 );
2298 let _: Result<bool, JsValue> = Reflect::set(
2299 &attachment,
2300 &JsValue::from_str(WEBGPU_PROPERTY_LOAD_OP),
2301 &JsValue::from_str(WEBGPU_LOAD_OP_CLEAR),
2302 );
2303 let _: Result<bool, JsValue> = Reflect::set(
2304 &attachment,
2305 &JsValue::from_str(WEBGPU_PROPERTY_STORE_OP),
2306 &JsValue::from_str(WEBGPU_STORE_OP_STORE),
2307 );
2308 let _: Result<bool, JsValue> = Reflect::set(
2309 &attachment,
2310 &JsValue::from_str(WEBGPU_PROPERTY_CLEAR_VALUE),
2311 &color_dict,
2312 );
2313 if let Some(target) = resolve_view.as_ref() {
2314 let _: Result<bool, JsValue> = Reflect::set(
2315 &attachment,
2316 &JsValue::from_str(WEBGPU_PROPERTY_RESOLVE_TARGET),
2317 target,
2318 );
2319 }
2320 let color_attachments: Array = Array::new();
2321 color_attachments.push(&attachment);
2322 let descriptor: Object = Object::new();
2323 let _: Result<bool, JsValue> = Reflect::set(
2324 &descriptor,
2325 &JsValue::from_str(WEBGPU_PROPERTY_COLOR_ATTACHMENTS),
2326 &color_attachments,
2327 );
2328 let begin_fn: Function =
2329 Reflect::get(encoder, &JsValue::from_str(WEBGPU_METHOD_BEGIN_RENDER_PASS))
2330 .unwrap_or(JsValue::UNDEFINED)
2331 .unchecked_into();
2332 begin_fn
2333 .call1(encoder, &descriptor)
2334 .unwrap_or(JsValue::UNDEFINED)
2335 }
2336
2337 pub(crate) fn submit(&self, command_buffers: &[JsValue]) {
2343 let array: Array = Array::new();
2344 for buffer in command_buffers {
2345 array.push(buffer);
2346 }
2347 let submit_fn: Function =
2348 Reflect::get(self.get_queue(), &JsValue::from_str(WEBGPU_METHOD_SUBMIT))
2349 .unwrap_or(JsValue::UNDEFINED)
2350 .unchecked_into();
2351 let _: Result<JsValue, JsValue> = submit_fn.call1(self.get_queue(), &array);
2352 }
2353
2354 pub fn create_render_pipeline<S>(&self, shader_code: S) -> JsValue
2370 where
2371 S: AsRef<str>,
2372 {
2373 let module: JsValue = self.create_shader_module(shader_code);
2374 let vertex_state: Object = Object::new();
2375 let _: Result<bool, JsValue> = Reflect::set(
2376 &vertex_state,
2377 &JsValue::from_str(WEBGPU_PROPERTY_MODULE),
2378 &module,
2379 );
2380 let _: Result<bool, JsValue> = Reflect::set(
2381 &vertex_state,
2382 &JsValue::from_str(WEBGPU_PROPERTY_ENTRY_POINT),
2383 &JsValue::from_str(WEBGPU_VERTEX_ENTRY_POINT),
2384 );
2385 let _: Result<bool, JsValue> = Reflect::set(
2386 &vertex_state,
2387 &JsValue::from_str(WEBGPU_PROPERTY_BUFFERS),
2388 &Array::new(),
2389 );
2390 let target: Object = Object::new();
2391 let _: Result<bool, JsValue> = Reflect::set(
2392 &target,
2393 &JsValue::from_str(WEBGPU_PROPERTY_FORMAT),
2394 &JsValue::from_str(&self.get_format()),
2395 );
2396 let targets: Array = Array::new();
2397 targets.push(&target);
2398 let fragment_state: Object = Object::new();
2399 let _: Result<bool, JsValue> = Reflect::set(
2400 &fragment_state,
2401 &JsValue::from_str(WEBGPU_PROPERTY_MODULE),
2402 &module,
2403 );
2404 let _: Result<bool, JsValue> = Reflect::set(
2405 &fragment_state,
2406 &JsValue::from_str(WEBGPU_PROPERTY_ENTRY_POINT),
2407 &JsValue::from_str(WEBGPU_FRAGMENT_ENTRY_POINT),
2408 );
2409 let _: Result<bool, JsValue> = Reflect::set(
2410 &fragment_state,
2411 &JsValue::from_str(WEBGPU_PROPERTY_TARGETS),
2412 &targets,
2413 );
2414 let primitive: Object = Object::new();
2415 let _: Result<bool, JsValue> = Reflect::set(
2416 &primitive,
2417 &JsValue::from_str(WEBGPU_PROPERTY_TOPOLOGY),
2418 &JsValue::from_str(WEBGPU_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST),
2419 );
2420 let multisample: Object = Object::new();
2428 let _: Result<bool, JsValue> = Reflect::set(
2429 &multisample,
2430 &JsValue::from_str(WEBGPU_PROPERTY_COUNT),
2431 &JsValue::from_f64(if self.get_antialias() { 4.0 } else { 1.0 }),
2432 );
2433 let descriptor: Object = Object::new();
2434 let _: Result<bool, JsValue> = Reflect::set(
2435 &descriptor,
2436 &JsValue::from_str(WEBGPU_PROPERTY_LAYOUT),
2437 &JsValue::from_str(WEBGPU_AUTO_LAYOUT),
2438 );
2439 let _: Result<bool, JsValue> = Reflect::set(
2440 &descriptor,
2441 &JsValue::from_str(WEBGPU_PROPERTY_VERTEX),
2442 &vertex_state,
2443 );
2444 let _: Result<bool, JsValue> = Reflect::set(
2445 &descriptor,
2446 &JsValue::from_str(WEBGPU_PROPERTY_FRAGMENT),
2447 &fragment_state,
2448 );
2449 let _: Result<bool, JsValue> = Reflect::set(
2450 &descriptor,
2451 &JsValue::from_str(WEBGPU_PROPERTY_PRIMITIVE),
2452 &primitive,
2453 );
2454 let _: Result<bool, JsValue> = Reflect::set(
2455 &descriptor,
2456 &JsValue::from_str(WEBGPU_PROPERTY_MULTISAMPLE),
2457 &multisample,
2458 );
2459 let create_fn: Function = Reflect::get(
2460 self.get_device(),
2461 &JsValue::from_str(WEBGPU_METHOD_CREATE_RENDER_PIPELINE),
2462 )
2463 .unwrap_or(JsValue::UNDEFINED)
2464 .unchecked_into();
2465 create_fn
2466 .call1(self.get_device(), &descriptor)
2467 .unwrap_or(JsValue::UNDEFINED)
2468 }
2469
2470 pub(crate) fn set_pipeline(&self, pass: &JsValue, pipeline: &JsValue) {
2477 let set_fn: Function = Reflect::get(pass, &JsValue::from_str(WEBGPU_METHOD_SET_PIPELINE))
2478 .unwrap_or(JsValue::UNDEFINED)
2479 .unchecked_into();
2480 let _: Result<JsValue, JsValue> = set_fn.call1(pass, pipeline);
2481 }
2482
2483 pub(crate) fn draw(&self, pass: &JsValue, vertex_count: u32, instance_count: u32) {
2491 let draw_fn: Function = Reflect::get(pass, &JsValue::from_str(WEBGPU_METHOD_DRAW))
2492 .unwrap_or(JsValue::UNDEFINED)
2493 .unchecked_into();
2494 let _: Result<JsValue, JsValue> = draw_fn.call2(
2495 pass,
2496 &JsValue::from_f64(f64::from(vertex_count)),
2497 &JsValue::from_f64(f64::from(instance_count)),
2498 );
2499 }
2500
2501 pub(crate) fn end_render_pass(&self, pass: &JsValue) {
2507 let end_fn: Function = Reflect::get(pass, &JsValue::from_str(WEBGPU_METHOD_END))
2508 .unwrap_or(JsValue::UNDEFINED)
2509 .unchecked_into();
2510 let _: Result<JsValue, JsValue> = end_fn.call0(pass);
2511 }
2512
2513 pub(crate) fn finish_command_encoder(&self, encoder: &JsValue) -> JsValue {
2523 let finish_fn: Function = Reflect::get(encoder, &JsValue::from_str(WEBGPU_METHOD_FINISH))
2524 .unwrap_or(JsValue::UNDEFINED)
2525 .unchecked_into();
2526 finish_fn.call0(encoder).unwrap_or(JsValue::UNDEFINED)
2527 }
2528
2529 pub fn render_frame(
2542 &mut self,
2543 pipeline: &JsValue,
2544 clear_color: (f64, f64, f64, f64),
2545 vertex_count: u32,
2546 ) {
2547 let encoder: JsValue = self.create_command_encoder();
2548 let pass: JsValue = self.begin_render_pass(&encoder, clear_color);
2549 self.set_pipeline(&pass, pipeline);
2550 self.draw(&pass, vertex_count, 1);
2551 self.end_render_pass(&pass);
2552 let command_buffer: JsValue = self.finish_command_encoder(&encoder);
2553 self.submit(&[command_buffer]);
2554 }
2555
2556 pub fn dispose(&self) {
2574 let context: &JsValue = self.get_context();
2575 if let Ok(unconfigure_fn) =
2576 Reflect::get(context, &JsValue::from_str(WEBGPU_METHOD_UNCONFIGURE))
2577 && let Ok(unconfigure_callable) = unconfigure_fn.dyn_into::<Function>()
2578 {
2579 let _: Result<JsValue, JsValue> = unconfigure_callable.call0(context);
2580 }
2581 let device: &JsValue = self.get_device();
2582 if let Ok(destroy_fn) = Reflect::get(device, &JsValue::from_str(WEBGPU_METHOD_DESTROY))
2583 && let Ok(destroy_callable) = destroy_fn.dyn_into::<Function>()
2584 {
2585 let _: Result<JsValue, JsValue> = destroy_callable.call0(device);
2586 }
2587 }
2588}
2589
2590impl WebGpuInitError {
2596 pub fn code(&self) -> &'static str {
2605 match self {
2606 Self::NavigatorLookup(_) => "WEBGPU_NAVIGATOR_LOOKUP",
2607 Self::NavigatorGpuMissing => "WEBGPU_NAVIGATOR_GPU_MISSING",
2608 Self::RequestAdapterLookup(_) => "WEBGPU_REQUEST_ADAPTER_LOOKUP",
2609 Self::RequestAdapterCall(_) => "WEBGPU_REQUEST_ADAPTER_CALL",
2610 Self::AdapterPromise(_) => "WEBGPU_ADAPTER_PROMISE",
2611 Self::AdapterUnavailable => "WEBGPU_ADAPTER_UNAVAILABLE",
2612 Self::RequestDeviceLookup(_) => "WEBGPU_REQUEST_DEVICE_LOOKUP",
2613 Self::RequestDeviceCall(_) => "WEBGPU_REQUEST_DEVICE_CALL",
2614 Self::DevicePromise(_) => "WEBGPU_DEVICE_PROMISE",
2615 Self::DeviceUnavailable => "WEBGPU_DEVICE_UNAVAILABLE",
2616 Self::CanvasNotFound(_) => "WEBGPU_CANVAS_NOT_FOUND",
2617 Self::CanvasQuery(_) => "WEBGPU_CANVAS_QUERY",
2618 Self::CanvasContextUnavailable => "WEBGPU_CANVAS_CONTEXT_UNAVAILABLE",
2619 Self::PreferredFormatLookup(_) => "WEBGPU_PREFERRED_FORMAT_LOOKUP",
2620 Self::PreferredFormatCall(_) => "WEBGPU_PREFERRED_FORMAT_CALL",
2621 Self::PreferredFormatType(_) => "WEBGPU_PREFERRED_FORMAT_TYPE",
2622 Self::ConfigureLookup(_) => "WEBGPU_CONFIGURE_LOOKUP",
2623 Self::QueueLookup(_) => "WEBGPU_QUEUE_LOOKUP",
2624 }
2625 }
2626
2627 pub fn js_error(&self) -> Option<&JsValue> {
2637 match self {
2638 Self::NavigatorLookup(err)
2639 | Self::RequestAdapterLookup(err)
2640 | Self::RequestAdapterCall(err)
2641 | Self::AdapterPromise(err)
2642 | Self::RequestDeviceLookup(err)
2643 | Self::RequestDeviceCall(err)
2644 | Self::DevicePromise(err)
2645 | Self::CanvasQuery(err)
2646 | Self::PreferredFormatLookup(err)
2647 | Self::PreferredFormatCall(err)
2648 | Self::PreferredFormatType(err)
2649 | Self::ConfigureLookup(err)
2650 | Self::QueueLookup(err) => Some(err),
2651 Self::NavigatorGpuMissing
2652 | Self::AdapterUnavailable
2653 | Self::DeviceUnavailable
2654 | Self::CanvasContextUnavailable
2655 | Self::CanvasNotFound(_) => None,
2656 }
2657 }
2658}
2659
2660fn js_error_to_string(value: &JsValue) -> String {
2662 if let Some(s) = value.as_string() {
2663 s
2664 } else if value.is_undefined() {
2665 "<undefined>".to_string()
2666 } else if value.is_null() {
2667 "<null>".to_string()
2668 } else {
2669 format!("{:?}", value)
2670 }
2671}
2672
2673impl std::fmt::Display for WebGpuInitError {
2680 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2681 match self {
2682 Self::NavigatorLookup(err) => write!(
2683 formatter,
2684 "[{}] Reflect::get(navigator, webgpu) failed: {}",
2685 self.code(),
2686 js_error_to_string(err),
2687 ),
2688 Self::NavigatorGpuMissing => write!(
2689 formatter,
2690 "[{}] navigator.gpu is missing - browser does not expose WebGPU on this origin",
2691 self.code(),
2692 ),
2693 Self::RequestAdapterLookup(err) => write!(
2694 formatter,
2695 "[{}] Reflect::get(gpu, requestAdapter) failed: {}",
2696 self.code(),
2697 js_error_to_string(err),
2698 ),
2699 Self::RequestAdapterCall(err) => write!(
2700 formatter,
2701 "[{}] gpu.requestAdapter() threw: {}",
2702 self.code(),
2703 js_error_to_string(err),
2704 ),
2705 Self::AdapterPromise(err) => write!(
2706 formatter,
2707 "[{}] adapter promise rejected or timed out: {}",
2708 self.code(),
2709 js_error_to_string(err),
2710 ),
2711 Self::AdapterUnavailable => write!(
2712 formatter,
2713 "[{}] requestAdapter returned null - no compatible GPU adapter for the requested powerPreference",
2714 self.code(),
2715 ),
2716 Self::RequestDeviceLookup(err) => write!(
2717 formatter,
2718 "[{}] Reflect::get(adapter, requestDevice) failed: {}",
2719 self.code(),
2720 js_error_to_string(err),
2721 ),
2722 Self::RequestDeviceCall(err) => write!(
2723 formatter,
2724 "[{}] adapter.requestDevice() threw: {}",
2725 self.code(),
2726 js_error_to_string(err),
2727 ),
2728 Self::DevicePromise(err) => write!(
2729 formatter,
2730 "[{}] device promise rejected or timed out: {}",
2731 self.code(),
2732 js_error_to_string(err),
2733 ),
2734 Self::DeviceUnavailable => write!(
2735 formatter,
2736 "[{}] requestDevice returned null - adapter could not allocate a device (possibly device-lost)",
2737 self.code(),
2738 ),
2739 Self::CanvasNotFound(selector) => write!(
2740 formatter,
2741 "[{}] canvas element {:?} not found in DOM",
2742 self.code(),
2743 selector,
2744 ),
2745 Self::CanvasQuery(err) => write!(
2746 formatter,
2747 "[{}] querySelector threw: {}",
2748 self.code(),
2749 js_error_to_string(err),
2750 ),
2751 Self::CanvasContextUnavailable => write!(
2752 formatter,
2753 "[{}] canvas.get_context('webgpu') returned null - the canvas may already be using another context type or WebGPU is disabled",
2754 self.code(),
2755 ),
2756 Self::PreferredFormatLookup(err) => write!(
2757 formatter,
2758 "[{}] Reflect::get(gpu, getPreferredCanvasFormat) failed: {}",
2759 self.code(),
2760 js_error_to_string(err),
2761 ),
2762 Self::PreferredFormatCall(err) => write!(
2763 formatter,
2764 "[{}] gpu.getPreferredCanvasFormat() threw: {}",
2765 self.code(),
2766 js_error_to_string(err),
2767 ),
2768 Self::PreferredFormatType(value) => write!(
2769 formatter,
2770 "[{}] getPreferredCanvasFormat returned non-string: {}",
2771 self.code(),
2772 js_error_to_string(value),
2773 ),
2774 Self::ConfigureLookup(err) => write!(
2775 formatter,
2776 "[{}] Reflect::get(context, configure) failed: {}",
2777 self.code(),
2778 js_error_to_string(err),
2779 ),
2780 Self::QueueLookup(err) => write!(
2781 formatter,
2782 "[{}] Reflect::get(device, queue) failed: {}",
2783 self.code(),
2784 js_error_to_string(err),
2785 ),
2786 }
2787 }
2788}
2789
2790impl std::error::Error for WebGpuInitError {}