1use crate::*;
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 let _ = Reflect::set(
170 context,
171 &JsValue::from_str(RENDERER_PROPERTY_IMAGE_SMOOTHING_ENABLED),
172 &JsValue::from_bool(smoothing_enabled),
173 );
174 let quality_value: &str = match quality {
175 RenderQuality::Low => RENDERER_IMAGE_SMOOTHING_QUALITY_LOW,
176 RenderQuality::Medium => RENDERER_IMAGE_SMOOTHING_QUALITY_MEDIUM,
177 RenderQuality::High => RENDERER_IMAGE_SMOOTHING_QUALITY_HIGH,
178 };
179 let _ = Reflect::set(
180 context,
181 &JsValue::from_str(RENDERER_PROPERTY_IMAGE_SMOOTHING_QUALITY),
182 &JsValue::from_str(quality_value),
183 );
184 let _ = Reflect::set(
185 context,
186 &JsValue::from_str(RENDERER_PROPERTY_TEXT_RENDERING),
187 &JsValue::from_str(RENDERER_TEXT_RENDERING_GEOMETRIC_PRECISION),
188 );
189 }
190}
191
192impl Color {
194 pub fn to_css(color: &Color) -> String {
204 color.to_css_rgba()
205 }
206}
207
208impl CanvasRenderer {
210 pub fn from_selector<S>(
222 canvas_selector: S,
223 viewport_width: f64,
224 viewport_height: f64,
225 ) -> Option<CanvasRenderer>
226 where
227 S: AsRef<str>,
228 {
229 let window_value: Window = window().expect("no global window exists");
230 let document_value: Document = window_value.document().expect("should have a document");
231 let element: Element = document_value
232 .query_selector(canvas_selector.as_ref())
233 .ok()
234 .flatten()?;
235 let canvas_element: HtmlCanvasElement = element.unchecked_into();
236 let context_object: Object = canvas_element
237 .get_context(RENDERER_CONTEXT_TYPE_2D)
238 .ok()
239 .flatten()?;
240 let context: CanvasRenderingContext2d = context_object.unchecked_into();
241 let renderer: CanvasRenderer = CanvasRenderer::new(
242 context,
243 Camera2D::create(viewport_width, viewport_height),
244 RenderQuality::default(),
245 );
246 renderer.enable_smoothing();
247 Some(renderer)
248 }
249
250 pub fn enable_smoothing(&self) {
256 Self::apply_quality(self.get_context(), self.get_quality());
257 }
258
259 pub fn clear(&self) {
261 self.get_context().clear_rect(
262 0.0,
263 0.0,
264 self.get_camera().get_viewport_width(),
265 self.get_camera().get_viewport_height(),
266 );
267 }
268
269 pub fn clear_color<C>(&self, color: C)
275 where
276 C: AsRef<str>,
277 {
278 let _ = Reflect::set(
279 self.get_context(),
280 &JsValue::from_str(RENDERER_PROPERTY_FILL_STYLE),
281 &JsValue::from_str(color.as_ref()),
282 );
283 self.get_context().fill_rect(
284 0.0,
285 0.0,
286 self.get_camera().get_viewport_width(),
287 self.get_camera().get_viewport_height(),
288 );
289 }
290
291 pub fn save(&self) {
293 self.get_context().save();
294 }
295
296 pub fn restore(&self) {
298 self.get_context().restore();
299 }
300
301 pub fn apply_camera(&self) {
306 let camera: Camera2D = self.get_camera();
307 let _ = self.get_context().translate(
308 camera.get_viewport_width() * 0.5,
309 camera.get_viewport_height() * 0.5,
310 );
311 let _ = self
312 .get_context()
313 .scale(camera.get_zoom(), camera.get_zoom());
314 let _ = self.get_context().rotate(camera.get_rotation());
315 let _ = self.get_context().translate(
316 -camera.get_position().get_x(),
317 -camera.get_position().get_y(),
318 );
319 }
320
321 pub fn set_fill_color<C>(&self, color: C)
327 where
328 C: AsRef<str>,
329 {
330 let _ = Reflect::set(
331 self.get_context(),
332 &JsValue::from_str(RENDERER_PROPERTY_FILL_STYLE),
333 &JsValue::from_str(color.as_ref()),
334 );
335 }
336
337 pub fn set_stroke_color<C>(&self, color: C)
343 where
344 C: AsRef<str>,
345 {
346 let _ = Reflect::set(
347 self.get_context(),
348 &JsValue::from_str(RENDERER_PROPERTY_STROKE_STYLE),
349 &JsValue::from_str(color.as_ref()),
350 );
351 }
352
353 pub fn set_line_width(&self, width: f64) {
359 let _ = Reflect::set(
360 self.get_context(),
361 &JsValue::from_str(RENDERER_PROPERTY_LINE_WIDTH),
362 &JsValue::from_f64(width),
363 );
364 }
365
366 pub fn set_global_alpha(&self, alpha: f64) {
372 let _ = Reflect::set(
373 self.get_context(),
374 &JsValue::from_str(RENDERER_PROPERTY_GLOBAL_ALPHA),
375 &JsValue::from_f64(Numeric::clamp(alpha, 0.0, 1.0)),
376 );
377 }
378
379 pub fn fill_rect(&self, position: Vector2D, width: f64, height: f64) {
387 self.get_context()
388 .fill_rect(position.get_x(), position.get_y(), width, height);
389 }
390
391 pub fn stroke_rect(&self, position: Vector2D, width: f64, height: f64) {
399 self.get_context()
400 .stroke_rect(position.get_x(), position.get_y(), width, height);
401 }
402
403 pub fn fill_circle(&self, center: Vector2D, radius: f64) {
410 self.get_context().begin_path();
411 self.get_context()
412 .arc(center.get_x(), center.get_y(), radius, 0.0, TWO_PI)
413 .unwrap_or(());
414 self.get_context().fill();
415 }
416
417 pub fn stroke_circle(&self, center: Vector2D, radius: f64) {
424 self.get_context().begin_path();
425 self.get_context()
426 .arc(center.get_x(), center.get_y(), radius, 0.0, TWO_PI)
427 .unwrap_or(());
428 self.get_context().stroke();
429 }
430
431 pub fn draw_line(&self, start: Vector2D, end: Vector2D) {
438 self.get_context().begin_path();
439 self.get_context().move_to(start.get_x(), start.get_y());
440 self.get_context().line_to(end.get_x(), end.get_y());
441 self.get_context().stroke();
442 }
443
444 pub fn fill_text<T>(&self, text: T, position: Vector2D)
451 where
452 T: AsRef<str>,
453 {
454 self.get_context()
455 .fill_text(text.as_ref(), position.get_x(), position.get_y())
456 .unwrap_or(());
457 }
458
459 pub fn set_font<F>(&self, font: F)
465 where
466 F: AsRef<str>,
467 {
468 let _ = Reflect::set(
469 self.get_context(),
470 &JsValue::from_str(RENDERER_PROPERTY_FONT),
471 &JsValue::from_str(font.as_ref()),
472 );
473 }
474
475 pub fn draw_image(
484 &self,
485 image: &HtmlImageElement,
486 position: Vector2D,
487 width: f64,
488 height: f64,
489 ) {
490 let _ = self
491 .get_context()
492 .draw_image_with_html_image_element_and_dw_and_dh(
493 image,
494 position.get_x(),
495 position.get_y(),
496 width,
497 height,
498 );
499 }
500
501 pub fn draw_image_rect(
511 &self,
512 image: &HtmlImageElement,
513 source: Rect,
514 dest_position: Vector2D,
515 dest_width: f64,
516 dest_height: f64,
517 ) {
518 let _ = self
519 .get_context()
520 .draw_image_with_html_image_element_and_sw_and_sh_and_dx_and_dy_and_dw_and_dh(
521 image,
522 source.get_x(),
523 source.get_y(),
524 source.get_width(),
525 source.get_height(),
526 dest_position.get_x(),
527 dest_position.get_y(),
528 dest_width,
529 dest_height,
530 );
531 }
532}
533
534impl Camera3D {
536 pub fn create(
549 position: Vector3D,
550 target: Vector3D,
551 viewport_width: f64,
552 viewport_height: f64,
553 ) -> Camera3D {
554 let mut camera: Camera3D = Camera3D::new(position, target, viewport_width, viewport_height);
555 camera.set_up(Vector3D::up());
556 camera.set_fov(DEFAULT_CAMERA_FOV);
557 camera.set_near(DEFAULT_CAMERA_NEAR);
558 camera.set_far(DEFAULT_CAMERA_FAR);
559 camera
560 }
561
562 pub fn aspect(&self) -> f64 {
568 if self.get_viewport_height() < EPSILON {
569 return 1.0;
570 }
571 self.get_viewport_width() / self.get_viewport_height()
572 }
573
574 pub fn forward(&self) -> Vector3D {
580 (self.get_target() - self.get_position()).normalized()
581 }
582
583 pub fn right(&self) -> Vector3D {
589 self.forward().cross(self.get_up()).normalized()
590 }
591
592 pub fn view_matrix(&self) -> Matrix4x4 {
598 Matrix4x4::look_at(self.get_position(), self.get_target(), self.get_up())
599 }
600
601 pub fn projection_matrix(&self) -> Matrix4x4 {
607 Matrix4x4::perspective(
608 self.get_fov(),
609 self.aspect(),
610 self.get_near(),
611 self.get_far(),
612 )
613 }
614
615 pub fn view_proj_matrix(&self) -> Matrix4x4 {
621 self.projection_matrix().multiply(self.view_matrix())
622 }
623
624 pub fn world_to_screen(&self, world: Vector3D) -> Vector3D {
634 let clip: Vector3D = self.view_proj_matrix().transform_point(world);
635 Vector3D::new(
636 (clip.get_x() + 1.0) * 0.5 * self.get_viewport_width(),
637 (1.0 - clip.get_y()) * 0.5 * self.get_viewport_height(),
638 clip.get_z(),
639 )
640 }
641
642 pub fn in_frustum(&self, world: Vector3D) -> bool {
652 let clip: Vector3D = self.view_proj_matrix().transform_point(world);
653 clip.get_x() >= -1.0
654 && clip.get_x() <= 1.0
655 && clip.get_y() >= -1.0
656 && clip.get_y() <= 1.0
657 && clip.get_z() >= -1.0
658 && clip.get_z() <= 1.0
659 }
660
661 pub fn translate(&mut self, offset: Vector3D) {
667 self.set_position(self.get_position() + offset);
668 self.set_target(self.get_target() + offset);
669 }
670
671 pub fn zoom(&mut self, distance: f64) {
677 let direction: Vector3D = self.forward();
678 self.set_position(self.get_position() + direction.scaled(distance));
679 }
680
681 pub fn orbit(&mut self, yaw_delta: f64, pitch_delta: f64) {
688 let offset: Vector3D = self.get_position() - self.get_target();
689 let current_distance: f64 = offset.magnitude();
690 let current_yaw: f64 = offset.get_x().atan2(offset.get_z());
691 let horizontal_dist: f64 =
692 (offset.get_x() * offset.get_x() + offset.get_z() * offset.get_z()).sqrt();
693 let current_pitch: f64 = (offset.get_y() / horizontal_dist.max(EPSILON)).asin();
694 let new_yaw: f64 = current_yaw + yaw_delta;
695 let new_pitch: f64 = Numeric::clamp(
696 current_pitch + pitch_delta,
697 -HALF_PI + EPSILON,
698 HALF_PI - EPSILON,
699 );
700 let cos_pitch: f64 = new_pitch.cos();
701 self.set_position(
702 self.get_target()
703 + Vector3D::new(
704 new_yaw.sin() * cos_pitch * current_distance,
705 new_pitch.sin() * current_distance,
706 new_yaw.cos() * cos_pitch * current_distance,
707 ),
708 );
709 }
710}
711
712impl Default for Camera3D {
714 fn default() -> Camera3D {
715 Camera3D::create(Vector3D::new(0.0, 0.0, 5.0), Vector3D::zero(), 800.0, 600.0)
716 }
717}
718
719impl SsaaCanvas {
721 pub fn from_selector<S>(canvas_selector: S, width: f64, height: f64) -> Option<SsaaCanvas>
733 where
734 S: AsRef<str>,
735 {
736 Self::from_selector_with_scale(
737 canvas_selector,
738 width,
739 height,
740 RENDERER_DEFAULT_SSAA_SCALE_FACTOR,
741 )
742 }
743
744 pub fn from_selector_with_scale<S>(
760 canvas_selector: S,
761 width: f64,
762 height: f64,
763 scale_factor: f64,
764 ) -> Option<SsaaCanvas>
765 where
766 S: AsRef<str>,
767 {
768 let window_value: Window = window().expect("no global window exists");
769 let document_value: Document = window_value.document().expect("should have a document");
770 let element: Element = document_value
771 .query_selector(canvas_selector.as_ref())
772 .ok()
773 .flatten()?;
774 let display_canvas: HtmlCanvasElement = element.unchecked_into();
775 let device_pixel_ratio: f64 = CanvasRenderer::detect_dpr();
776 let physical_width: u32 = (width * device_pixel_ratio).round() as u32;
777 let physical_height: u32 = (height * device_pixel_ratio).round() as u32;
778 display_canvas.set_width(physical_width);
779 display_canvas.set_height(physical_height);
780 let display_context_object: Object = display_canvas
781 .get_context(RENDERER_CONTEXT_TYPE_2D)
782 .ok()
783 .flatten()?;
784 let display_context: CanvasRenderingContext2d = display_context_object.unchecked_into();
785 let _ = display_context.scale(device_pixel_ratio, device_pixel_ratio);
786 let offscreen_canvas: HtmlCanvasElement = document_value
787 .create_element(RENDERER_ELEMENT_CANVAS)
788 .ok()?
789 .unchecked_into();
790 let scaled_width: u32 = (width * scale_factor * device_pixel_ratio).round() as u32;
791 let scaled_height: u32 = (height * scale_factor * device_pixel_ratio).round() as u32;
792 offscreen_canvas.set_width(scaled_width);
793 offscreen_canvas.set_height(scaled_height);
794 let offscreen_context_object: Object = offscreen_canvas
795 .get_context(RENDERER_CONTEXT_TYPE_2D)
796 .ok()
797 .flatten()?;
798 let offscreen_context: CanvasRenderingContext2d = offscreen_context_object.unchecked_into();
799 let _ = offscreen_context.scale(
800 scale_factor * device_pixel_ratio,
801 scale_factor * device_pixel_ratio,
802 );
803 let ssaa_canvas: SsaaCanvas = SsaaCanvas::new(
804 display_canvas,
805 display_context,
806 offscreen_canvas,
807 offscreen_context,
808 scale_factor,
809 width,
810 height,
811 );
812 ssaa_canvas.enable_smoothing();
813 Some(ssaa_canvas)
814 }
815
816 pub fn present(&self) {
823 CanvasRenderer::apply_quality(self.get_display_context(), self.get_quality());
824 self.get_display_context()
825 .clear_rect(0.0, 0.0, self.get_width(), self.get_height());
826 let _ = self
827 .get_display_context()
828 .draw_image_with_html_canvas_element_and_dw_and_dh(
829 self.get_offscreen_canvas(),
830 0.0,
831 0.0,
832 self.get_width(),
833 self.get_height(),
834 );
835 }
836
837 pub fn clear(&self) {
839 self.get_offscreen_context()
840 .clear_rect(0.0, 0.0, self.get_width(), self.get_height());
841 }
842
843 pub fn clear_color<C>(&self, color: C)
849 where
850 C: AsRef<str>,
851 {
852 let _ = Reflect::set(
853 self.get_offscreen_context(),
854 &JsValue::from_str(RENDERER_PROPERTY_FILL_STYLE),
855 &JsValue::from_str(color.as_ref()),
856 );
857 self.get_offscreen_context()
858 .fill_rect(0.0, 0.0, self.get_width(), self.get_height());
859 }
860
861 pub fn enable_smoothing(&self) {
866 let quality: RenderQuality = self.get_quality();
867 CanvasRenderer::apply_quality(self.get_display_context(), quality);
868 CanvasRenderer::apply_quality(self.get_offscreen_context(), quality);
869 }
870}
871
872impl BlendMode {
874 pub fn to_css(&self) -> &str {
880 match self {
881 BlendMode::Normal => BLEND_MODE_NORMAL,
882 BlendMode::Multiply => BLEND_MODE_MULTIPLY,
883 BlendMode::Screen => BLEND_MODE_SCREEN,
884 BlendMode::Lighter => BLEND_MODE_LIGHTER,
885 BlendMode::Overlay => BLEND_MODE_OVERLAY,
886 BlendMode::Darken => BLEND_MODE_DARKEN,
887 BlendMode::Lighten => BLEND_MODE_LIGHTEN,
888 BlendMode::ColorDodge => BLEND_MODE_COLOR_DODGE,
889 BlendMode::ColorBurn => BLEND_MODE_COLOR_BURN,
890 BlendMode::HardLight => BLEND_MODE_HARD_LIGHT,
891 BlendMode::SoftLight => BLEND_MODE_SOFT_LIGHT,
892 BlendMode::Difference => BLEND_MODE_DIFFERENCE,
893 BlendMode::Exclusion => BLEND_MODE_EXCLUSION,
894 BlendMode::Hue => BLEND_MODE_HUE,
895 BlendMode::Saturation => BLEND_MODE_SATURATION,
896 BlendMode::Color => BLEND_MODE_COLOR,
897 BlendMode::Luminosity => BLEND_MODE_LUMINOSITY,
898 }
899 }
900}
901
902impl LinearGradient {
904 pub fn create(start: Vector2D, end: Vector2D, stops: Vec<(f64, String)>) -> LinearGradient {
916 LinearGradient::new(start, end, stops)
917 }
918
919 pub fn to_gradient(&self, context: &CanvasRenderingContext2d) -> Option<CanvasGradient> {
929 let canvas_gradient: CanvasGradient = context.create_linear_gradient(
930 self.get_start().get_x(),
931 self.get_start().get_y(),
932 self.get_end().get_x(),
933 self.get_end().get_y(),
934 );
935 for (position, color) in self.get_stops() {
936 let _ = canvas_gradient.add_color_stop(*position as f32, color);
937 }
938 Some(canvas_gradient)
939 }
940}
941
942impl RadialGradient {
944 pub fn create(
958 inner_center: Vector2D,
959 inner_radius: f64,
960 outer_center: Vector2D,
961 outer_radius: f64,
962 stops: Vec<(f64, String)>,
963 ) -> RadialGradient {
964 RadialGradient::new(
965 inner_center,
966 inner_radius,
967 outer_center,
968 outer_radius,
969 stops,
970 )
971 }
972
973 pub fn to_gradient(&self, context: &CanvasRenderingContext2d) -> Option<CanvasGradient> {
983 let canvas_gradient: CanvasGradient = context
984 .create_radial_gradient(
985 self.get_inner_center().get_x(),
986 self.get_inner_center().get_y(),
987 self.get_inner_radius(),
988 self.get_outer_center().get_x(),
989 self.get_outer_center().get_y(),
990 self.get_outer_radius(),
991 )
992 .ok()?;
993 for (position, color) in self.get_stops() {
994 let _ = canvas_gradient.add_color_stop(*position as f32, color);
995 }
996 Some(canvas_gradient)
997 }
998}
999
1000impl ShadowConfig {
1002 pub fn create() -> ShadowConfig {
1008 ShadowConfig::new(
1009 RENDERER_DEFAULT_SHADOW_COLOR.to_string(),
1010 RENDERER_DEFAULT_SHADOW_BLUR,
1011 0.0,
1012 0.0,
1013 )
1014 }
1015}
1016
1017impl Default for ShadowConfig {
1019 fn default() -> ShadowConfig {
1020 ShadowConfig::create()
1021 }
1022}
1023
1024impl RenderLayer {
1026 pub fn create(z_index: i32, visible: bool) -> RenderLayer {
1037 RenderLayer::new(z_index, visible)
1038 }
1039
1040 pub fn background() -> RenderLayer {
1046 RenderLayer::new(RENDERER_LAYER_BACKGROUND, true)
1047 }
1048
1049 pub fn foreground() -> RenderLayer {
1055 RenderLayer::new(RENDERER_LAYER_FOREGROUND, true)
1056 }
1057
1058 pub fn ui() -> RenderLayer {
1064 RenderLayer::new(RENDERER_LAYER_UI, true)
1065 }
1066}
1067
1068impl CanvasRenderer {
1070 pub fn set_blend_mode(&self, mode: BlendMode) {
1076 let _ = Reflect::set(
1077 self.get_context(),
1078 &JsValue::from_str(RENDERER_PROPERTY_GLOBAL_COMPOSITE_OPERATION),
1079 &JsValue::from_str(mode.to_css()),
1080 );
1081 }
1082
1083 pub fn set_shadow(&self, config: &ShadowConfig) {
1089 let _ = Reflect::set(
1090 self.get_context(),
1091 &JsValue::from_str(RENDERER_PROPERTY_SHADOW_COLOR),
1092 &JsValue::from_str(config.get_color().as_str()),
1093 );
1094 let _ = Reflect::set(
1095 self.get_context(),
1096 &JsValue::from_str(RENDERER_PROPERTY_SHADOW_BLUR),
1097 &JsValue::from_f64(config.get_blur()),
1098 );
1099 let _ = Reflect::set(
1100 self.get_context(),
1101 &JsValue::from_str(RENDERER_PROPERTY_SHADOW_OFFSET_X),
1102 &JsValue::from_f64(config.get_offset_x()),
1103 );
1104 let _ = Reflect::set(
1105 self.get_context(),
1106 &JsValue::from_str(RENDERER_PROPERTY_SHADOW_OFFSET_Y),
1107 &JsValue::from_f64(config.get_offset_y()),
1108 );
1109 }
1110
1111 pub fn clear_shadow(&self) {
1113 let _ = Reflect::set(
1114 self.get_context(),
1115 &JsValue::from_str(RENDERER_PROPERTY_SHADOW_COLOR),
1116 &JsValue::from_str("rgba(0, 0, 0, 0)"),
1117 );
1118 let _ = Reflect::set(
1119 self.get_context(),
1120 &JsValue::from_str(RENDERER_PROPERTY_SHADOW_BLUR),
1121 &JsValue::from_f64(0.0),
1122 );
1123 let _ = Reflect::set(
1124 self.get_context(),
1125 &JsValue::from_str(RENDERER_PROPERTY_SHADOW_OFFSET_X),
1126 &JsValue::from_f64(0.0),
1127 );
1128 let _ = Reflect::set(
1129 self.get_context(),
1130 &JsValue::from_str(RENDERER_PROPERTY_SHADOW_OFFSET_Y),
1131 &JsValue::from_f64(0.0),
1132 );
1133 }
1134
1135 pub fn set_linear_gradient_fill(&self, gradient: &LinearGradient) {
1141 if let Some(canvas_gradient) = gradient.to_gradient(self.get_context()) {
1142 let _ = Reflect::set(
1143 self.get_context(),
1144 &JsValue::from_str(RENDERER_PROPERTY_FILL_STYLE),
1145 &canvas_gradient,
1146 );
1147 }
1148 }
1149
1150 pub fn set_radial_gradient_fill(&self, gradient: &RadialGradient) {
1156 if let Some(canvas_gradient) = gradient.to_gradient(self.get_context()) {
1157 let _ = Reflect::set(
1158 self.get_context(),
1159 &JsValue::from_str(RENDERER_PROPERTY_FILL_STYLE),
1160 &canvas_gradient,
1161 );
1162 }
1163 }
1164
1165 pub fn set_linear_gradient_stroke(&self, gradient: &LinearGradient) {
1171 if let Some(canvas_gradient) = gradient.to_gradient(self.get_context()) {
1172 let _ = Reflect::set(
1173 self.get_context(),
1174 &JsValue::from_str(RENDERER_PROPERTY_STROKE_STYLE),
1175 &canvas_gradient,
1176 );
1177 }
1178 }
1179
1180 pub fn set_radial_gradient_stroke(&self, gradient: &RadialGradient) {
1186 if let Some(canvas_gradient) = gradient.to_gradient(self.get_context()) {
1187 let _ = Reflect::set(
1188 self.get_context(),
1189 &JsValue::from_str(RENDERER_PROPERTY_STROKE_STYLE),
1190 &canvas_gradient,
1191 );
1192 }
1193 }
1194}
1195
1196impl RenderBackend for CanvasRenderer {
1204 fn clear(&self) {
1206 self.clear();
1207 }
1208
1209 fn clear_color<C>(&self, color: C)
1211 where
1212 C: AsRef<str>,
1213 {
1214 self.clear_color(color);
1215 }
1216
1217 fn save(&self) {
1219 self.save();
1220 }
1221
1222 fn restore(&self) {
1224 self.restore();
1225 }
1226
1227 fn set_fill_color(&self, color: &str) {
1229 self.set_fill_color(color);
1230 }
1231
1232 fn set_stroke_color(&self, color: &str) {
1234 self.set_stroke_color(color);
1235 }
1236
1237 fn set_line_width(&self, width: f64) {
1239 self.set_line_width(width);
1240 }
1241
1242 fn set_global_alpha(&self, alpha: f64) {
1244 self.set_global_alpha(alpha);
1245 }
1246
1247 fn set_blend_mode(&self, mode: BlendMode) {
1249 self.set_blend_mode(mode);
1250 }
1251
1252 fn set_shadow(&self, config: &ShadowConfig) {
1254 self.set_shadow(config);
1255 }
1256
1257 fn clear_shadow(&self) {
1259 self.clear_shadow();
1260 }
1261
1262 fn fill_rect(&self, position: Vector2D, width: f64, height: f64) {
1264 self.fill_rect(position, width, height);
1265 }
1266
1267 fn stroke_rect(&self, position: Vector2D, width: f64, height: f64) {
1269 self.stroke_rect(position, width, height);
1270 }
1271
1272 fn fill_circle(&self, center: Vector2D, radius: f64) {
1274 self.fill_circle(center, radius);
1275 }
1276
1277 fn stroke_circle(&self, center: Vector2D, radius: f64) {
1279 self.stroke_circle(center, radius);
1280 }
1281
1282 fn draw_line(&self, start: Vector2D, end: Vector2D) {
1284 self.draw_line(start, end);
1285 }
1286
1287 fn fill_text(&self, text: &str, position: Vector2D) {
1289 self.fill_text(text, position);
1290 }
1291
1292 fn set_font(&self, font: &str) {
1294 self.set_font(font);
1295 }
1296
1297 fn draw_image(&self, image: &HtmlImageElement, position: Vector2D, width: f64, height: f64) {
1299 self.draw_image(image, position, width, height);
1300 }
1301
1302 fn set_linear_gradient_fill(&self, gradient: &LinearGradient) {
1304 self.set_linear_gradient_fill(gradient);
1305 }
1306
1307 fn set_radial_gradient_fill(&self, gradient: &RadialGradient) {
1309 self.set_radial_gradient_fill(gradient);
1310 }
1311}
1312
1313impl WebGpuRenderer {
1315 fn timeout_promise() -> Promise {
1342 let window_value: Window = window().expect("no global window exists");
1343 Promise::new(&mut |_resolve: Function, reject: Function| {
1344 let reject_fn: Function = reject.clone();
1345 let timer: Closure<dyn FnMut()> = Closure::wrap(Box::new(move || {
1346 let _ = reject_fn.call1(
1347 &JsValue::UNDEFINED,
1348 &JsValue::from_str(RENDERER_TIMEOUT_ERROR_MESSAGE),
1349 );
1350 }));
1351 let _ = window_value.set_timeout_with_callback_and_timeout_and_arguments_0(
1352 timer.as_ref().unchecked_ref(),
1353 INIT_PROMISE_TIMEOUT_MILLIS,
1354 );
1355 timer.forget();
1356 })
1357 }
1358
1359 fn race_with_timeout(promise: Promise) -> Promise {
1365 let array: Array = Array::of2(&promise, &Self::timeout_promise());
1366 Promise::race(&array)
1367 }
1368
1369 pub async fn init(config: &RenderConfig) -> Result<WebGpuRenderer, WebGpuInitError> {
1392 let window: Window = window().expect("no global window exists");
1393 let navigator: Navigator = window.navigator();
1394 let gpu_result: Result<JsValue, JsValue> =
1395 Reflect::get(navigator.as_ref(), &JsValue::from_str(WEBGPU_CONTEXT_TYPE));
1396 let gpu: JsValue = match gpu_result {
1397 Ok(value) => value,
1398 Err(err) => return Err(WebGpuInitError::NavigatorLookup(err)),
1399 };
1400 if gpu.is_undefined() || gpu.is_null() {
1401 return Err(WebGpuInitError::NavigatorGpuMissing);
1402 }
1403 let adapter_options: Object = Object::new();
1404 let _ = Reflect::set(
1405 &adapter_options,
1406 &JsValue::from_str(WEBGPU_PROPERTY_POWER_PREFERENCE),
1407 &JsValue::from_str(config.power_preference.to_web_sys_string()),
1408 );
1409 let request_adapter_fn: Function =
1410 match Reflect::get(&gpu, &JsValue::from_str(WEBGPU_METHOD_REQUEST_ADAPTER)) {
1411 Ok(value) => value.unchecked_into(),
1412 Err(err) => return Err(WebGpuInitError::RequestAdapterLookup(err)),
1413 };
1414 let adapter_promise: Promise = match request_adapter_fn.call1(&gpu, &adapter_options) {
1415 Ok(value) => value.unchecked_into(),
1416 Err(err) => return Err(WebGpuInitError::RequestAdapterCall(err)),
1417 };
1418 let adapter_value: JsValue =
1419 match JsFuture::from(Self::race_with_timeout(adapter_promise)).await {
1420 Ok(value) => value,
1421 Err(err) => return Err(WebGpuInitError::AdapterPromise(err)),
1422 };
1423 if adapter_value.is_null() || adapter_value.is_undefined() {
1424 return Err(WebGpuInitError::AdapterUnavailable);
1425 }
1426 let device_descriptor: Object = Object::new();
1427 let request_device_fn: Function = match Reflect::get(
1428 &adapter_value,
1429 &JsValue::from_str(WEBGPU_METHOD_REQUEST_DEVICE),
1430 ) {
1431 Ok(value) => value.unchecked_into(),
1432 Err(err) => return Err(WebGpuInitError::RequestDeviceLookup(err)),
1433 };
1434 let device_promise: Promise =
1435 match request_device_fn.call1(&adapter_value, &device_descriptor) {
1436 Ok(value) => value.unchecked_into(),
1437 Err(err) => return Err(WebGpuInitError::RequestDeviceCall(err)),
1438 };
1439 let device_value: JsValue =
1440 match JsFuture::from(Self::race_with_timeout(device_promise)).await {
1441 Ok(value) => value,
1442 Err(err) => return Err(WebGpuInitError::DevicePromise(err)),
1443 };
1444 if device_value.is_null() || device_value.is_undefined() {
1445 return Err(WebGpuInitError::DeviceUnavailable);
1446 }
1447 let document: Document = window.document().expect("should have a document");
1448 let element: Element = match document.query_selector(&config.canvas_selector) {
1449 Ok(Some(el)) => el,
1450 Ok(None) => {
1451 return Err(WebGpuInitError::CanvasNotFound(
1452 config.canvas_selector.clone(),
1453 ));
1454 }
1455 Err(err) => return Err(WebGpuInitError::CanvasQuery(err)),
1456 };
1457 let canvas: HtmlCanvasElement = element.unchecked_into();
1458 let context_object: Option<Object> = canvas.get_context(WEBGPU_CONTEXT_TYPE).ok().flatten();
1459 let context_object: Object = match context_object {
1460 Some(c) => c,
1461 None => return Err(WebGpuInitError::CanvasContextUnavailable),
1462 };
1463 let context: JsValue = context_object.into();
1464 let get_format_fn: Function =
1465 match Reflect::get(&gpu, &JsValue::from_str(WEBGPU_METHOD_GET_PREFERRED_FORMAT)) {
1466 Ok(value) => value.unchecked_into(),
1467 Err(err) => return Err(WebGpuInitError::PreferredFormatLookup(err)),
1468 };
1469 let format_value: JsValue = match get_format_fn.call0(&gpu) {
1470 Ok(value) => value,
1471 Err(err) => return Err(WebGpuInitError::PreferredFormatCall(err)),
1472 };
1473 let format: String = match format_value.as_string() {
1474 Some(s) => s,
1475 None => return Err(WebGpuInitError::PreferredFormatType(format_value)),
1476 };
1477 let dpr: f64 = CanvasRenderer::detect_dpr();
1481 let physical_width: u32 = (config.width * dpr).round() as u32;
1482 let physical_height: u32 = (config.height * dpr).round() as u32;
1483 canvas.set_width(physical_width);
1484 canvas.set_height(physical_height);
1485 let canvas_config: Object = Object::new();
1486 let _ = Reflect::set(
1487 &canvas_config,
1488 &JsValue::from_str(WEBGPU_PROPERTY_DEVICE),
1489 &device_value,
1490 );
1491 let _ = Reflect::set(
1492 &canvas_config,
1493 &JsValue::from_str(WEBGPU_PROPERTY_FORMAT),
1494 &format_value,
1495 );
1496 let configure_fn: Function =
1497 match Reflect::get(&context, &JsValue::from_str(WEBGPU_METHOD_CONFIGURE)) {
1498 Ok(value) => value.unchecked_into(),
1499 Err(err) => return Err(WebGpuInitError::ConfigureLookup(err)),
1500 };
1501 let _ = configure_fn.call1(&context, &canvas_config);
1502 let queue: JsValue =
1503 match Reflect::get(&device_value, &JsValue::from_str(WEBGPU_PROPERTY_QUEUE)) {
1504 Ok(value) => value,
1505 Err(err) => return Err(WebGpuInitError::QueueLookup(err)),
1506 };
1507 Ok(WebGpuRenderer {
1508 device: device_value,
1509 queue,
1510 context,
1511 canvas,
1512 format,
1513 width: physical_width,
1514 height: physical_height,
1515 antialias: config.antialias,
1516 })
1517 }
1518
1519 pub fn resize(&mut self, physical_width: u32, physical_height: u32) -> bool {
1544 if self.get_canvas().is_null()
1545 || self.get_context().is_null()
1546 || self.get_device().is_undefined()
1547 {
1548 return false;
1549 }
1550 self.get_canvas().set_width(physical_width);
1551 self.get_canvas().set_height(physical_height);
1552 let format_value: JsValue = JsValue::from_str(&self.get_format());
1553 let canvas_config: Object = Object::new();
1554 let _ = Reflect::set(
1555 &canvas_config,
1556 &JsValue::from_str(WEBGPU_PROPERTY_DEVICE),
1557 self.get_device(),
1558 );
1559 let _ = Reflect::set(
1560 &canvas_config,
1561 &JsValue::from_str(WEBGPU_PROPERTY_FORMAT),
1562 &format_value,
1563 );
1564 let configure_fn: Function = Reflect::get(
1565 self.get_context(),
1566 &JsValue::from_str(WEBGPU_METHOD_CONFIGURE),
1567 )
1568 .ok()
1569 .and_then(|value: JsValue| value.dyn_into::<Function>().ok())
1570 .unwrap_or_else(|| Function::new_no_args(""));
1571 if configure_fn
1572 .call1(self.get_context(), &canvas_config)
1573 .is_err()
1574 {
1575 return false;
1576 }
1577 self.set_width(physical_width);
1578 self.set_height(physical_height);
1579 true
1580 }
1581
1582 pub(crate) fn create_shader_module<S>(&self, code: S) -> JsValue
1592 where
1593 S: AsRef<str>,
1594 {
1595 let descriptor: Object = Object::new();
1596 let _ = Reflect::set(
1597 &descriptor,
1598 &JsValue::from_str(WEBGPU_PROPERTY_CODE),
1599 &JsValue::from_str(code.as_ref()),
1600 );
1601 let create_fn: Function = Reflect::get(
1602 self.get_device(),
1603 &JsValue::from_str(WEBGPU_METHOD_CREATE_SHADER_MODULE),
1604 )
1605 .unwrap_or(JsValue::UNDEFINED)
1606 .unchecked_into();
1607 create_fn
1608 .call1(self.get_device(), &descriptor)
1609 .unwrap_or(JsValue::UNDEFINED)
1610 }
1611
1612 pub(crate) fn create_command_encoder(&self) -> JsValue {
1618 let create_fn: Function = Reflect::get(
1619 self.get_device(),
1620 &JsValue::from_str(WEBGPU_METHOD_CREATE_COMMAND_ENCODER),
1621 )
1622 .unwrap_or(JsValue::UNDEFINED)
1623 .unchecked_into();
1624 create_fn
1625 .call0(self.get_device())
1626 .unwrap_or(JsValue::UNDEFINED)
1627 }
1628
1629 pub(crate) fn get_current_texture_view(&self) -> JsValue {
1639 let get_texture_fn: Function = Reflect::get(
1640 self.get_context(),
1641 &JsValue::from_str(WEBGPU_METHOD_GET_CURRENT_TEXTURE),
1642 )
1643 .unwrap_or(JsValue::UNDEFINED)
1644 .unchecked_into();
1645 let texture: JsValue = get_texture_fn
1646 .call0(self.get_context())
1647 .unwrap_or(JsValue::UNDEFINED);
1648 let create_view_fn: Function =
1649 Reflect::get(&texture, &JsValue::from_str(WEBGPU_METHOD_CREATE_VIEW))
1650 .unwrap_or(JsValue::UNDEFINED)
1651 .unchecked_into();
1652 create_view_fn.call0(&texture).unwrap_or(JsValue::UNDEFINED)
1653 }
1654
1655 pub(crate) fn begin_render_pass(
1671 &self,
1672 encoder: &JsValue,
1673 clear_color: (f64, f64, f64, f64),
1674 ) -> JsValue {
1675 let view: JsValue = self.get_current_texture_view();
1676 let color_dict: Object = Object::new();
1677 let _ = Reflect::set(
1678 &color_dict,
1679 &JsValue::from_str(WEBGPU_PROPERTY_R),
1680 &JsValue::from_f64(clear_color.0),
1681 );
1682 let _ = Reflect::set(
1683 &color_dict,
1684 &JsValue::from_str(WEBGPU_PROPERTY_G),
1685 &JsValue::from_f64(clear_color.1),
1686 );
1687 let _ = Reflect::set(
1688 &color_dict,
1689 &JsValue::from_str(WEBGPU_PROPERTY_B),
1690 &JsValue::from_f64(clear_color.2),
1691 );
1692 let _ = Reflect::set(
1693 &color_dict,
1694 &JsValue::from_str(WEBGPU_PROPERTY_A),
1695 &JsValue::from_f64(clear_color.3),
1696 );
1697 let attachment: Object = Object::new();
1698 let _ = Reflect::set(&attachment, &JsValue::from_str(WEBGPU_PROPERTY_VIEW), &view);
1699 let _ = Reflect::set(
1700 &attachment,
1701 &JsValue::from_str(WEBGPU_PROPERTY_LOAD_OP),
1702 &JsValue::from_str(WEBGPU_LOAD_OP_CLEAR),
1703 );
1704 let _ = Reflect::set(
1705 &attachment,
1706 &JsValue::from_str(WEBGPU_PROPERTY_STORE_OP),
1707 &JsValue::from_str(WEBGPU_STORE_OP_STORE),
1708 );
1709 let _ = Reflect::set(
1710 &attachment,
1711 &JsValue::from_str(WEBGPU_PROPERTY_CLEAR_VALUE),
1712 &color_dict,
1713 );
1714 let color_attachments: Array = Array::new();
1715 color_attachments.push(&attachment);
1716 let descriptor: Object = Object::new();
1717 let _ = Reflect::set(
1718 &descriptor,
1719 &JsValue::from_str(WEBGPU_PROPERTY_COLOR_ATTACHMENTS),
1720 &color_attachments,
1721 );
1722 let begin_fn: Function =
1723 Reflect::get(encoder, &JsValue::from_str(WEBGPU_METHOD_BEGIN_RENDER_PASS))
1724 .unwrap_or(JsValue::UNDEFINED)
1725 .unchecked_into();
1726 begin_fn
1727 .call1(encoder, &descriptor)
1728 .unwrap_or(JsValue::UNDEFINED)
1729 }
1730
1731 pub(crate) fn submit(&self, command_buffers: &[JsValue]) {
1737 let array: Array = Array::new();
1738 for buffer in command_buffers {
1739 array.push(buffer);
1740 }
1741 let submit_fn: Function =
1742 Reflect::get(self.get_queue(), &JsValue::from_str(WEBGPU_METHOD_SUBMIT))
1743 .unwrap_or(JsValue::UNDEFINED)
1744 .unchecked_into();
1745 let _ = submit_fn.call1(self.get_queue(), &array);
1746 }
1747
1748 pub fn create_render_pipeline<S>(&self, shader_code: S) -> JsValue
1764 where
1765 S: AsRef<str>,
1766 {
1767 let module: JsValue = self.create_shader_module(shader_code);
1768 let vertex_state: Object = Object::new();
1769 let _ = Reflect::set(
1770 &vertex_state,
1771 &JsValue::from_str(WEBGPU_PROPERTY_MODULE),
1772 &module,
1773 );
1774 let _ = Reflect::set(
1775 &vertex_state,
1776 &JsValue::from_str(WEBGPU_PROPERTY_ENTRY_POINT),
1777 &JsValue::from_str(WEBGPU_VERTEX_ENTRY_POINT),
1778 );
1779 let _ = Reflect::set(
1780 &vertex_state,
1781 &JsValue::from_str(WEBGPU_PROPERTY_BUFFERS),
1782 &Array::new(),
1783 );
1784 let target: Object = Object::new();
1785 let _ = Reflect::set(
1786 &target,
1787 &JsValue::from_str(WEBGPU_PROPERTY_FORMAT),
1788 &JsValue::from_str(&self.get_format()),
1789 );
1790 let targets: Array = Array::new();
1791 targets.push(&target);
1792 let fragment_state: Object = Object::new();
1793 let _ = Reflect::set(
1794 &fragment_state,
1795 &JsValue::from_str(WEBGPU_PROPERTY_MODULE),
1796 &module,
1797 );
1798 let _ = Reflect::set(
1799 &fragment_state,
1800 &JsValue::from_str(WEBGPU_PROPERTY_ENTRY_POINT),
1801 &JsValue::from_str(WEBGPU_FRAGMENT_ENTRY_POINT),
1802 );
1803 let _ = Reflect::set(
1804 &fragment_state,
1805 &JsValue::from_str(WEBGPU_PROPERTY_TARGETS),
1806 &targets,
1807 );
1808 let primitive: Object = Object::new();
1809 let _ = Reflect::set(
1810 &primitive,
1811 &JsValue::from_str(WEBGPU_PROPERTY_TOPOLOGY),
1812 &JsValue::from_str(WEBGPU_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST),
1813 );
1814 let descriptor: Object = Object::new();
1815 let _ = Reflect::set(
1816 &descriptor,
1817 &JsValue::from_str(WEBGPU_PROPERTY_LAYOUT),
1818 &JsValue::null(),
1819 );
1820 let _ = Reflect::set(
1821 &descriptor,
1822 &JsValue::from_str(WEBGPU_PROPERTY_VERTEX),
1823 &vertex_state,
1824 );
1825 let _ = Reflect::set(
1826 &descriptor,
1827 &JsValue::from_str(WEBGPU_PROPERTY_FRAGMENT),
1828 &fragment_state,
1829 );
1830 let _ = Reflect::set(
1831 &descriptor,
1832 &JsValue::from_str(WEBGPU_PROPERTY_PRIMITIVE),
1833 &primitive,
1834 );
1835 let create_fn: Function = Reflect::get(
1836 self.get_device(),
1837 &JsValue::from_str(WEBGPU_METHOD_CREATE_RENDER_PIPELINE),
1838 )
1839 .unwrap_or(JsValue::UNDEFINED)
1840 .unchecked_into();
1841 create_fn
1842 .call1(self.get_device(), &descriptor)
1843 .unwrap_or(JsValue::UNDEFINED)
1844 }
1845
1846 pub(crate) fn set_pipeline(&self, pass: &JsValue, pipeline: &JsValue) {
1853 let set_fn: Function = Reflect::get(pass, &JsValue::from_str(WEBGPU_METHOD_SET_PIPELINE))
1854 .unwrap_or(JsValue::UNDEFINED)
1855 .unchecked_into();
1856 let _ = set_fn.call1(pass, pipeline);
1857 }
1858
1859 pub(crate) fn draw(&self, pass: &JsValue, vertex_count: u32, instance_count: u32) {
1867 let draw_fn: Function = Reflect::get(pass, &JsValue::from_str(WEBGPU_METHOD_DRAW))
1868 .unwrap_or(JsValue::UNDEFINED)
1869 .unchecked_into();
1870 let _ = draw_fn.call2(
1871 pass,
1872 &JsValue::from_f64(f64::from(vertex_count)),
1873 &JsValue::from_f64(f64::from(instance_count)),
1874 );
1875 }
1876
1877 pub(crate) fn end_render_pass(&self, pass: &JsValue) {
1883 let end_fn: Function = Reflect::get(pass, &JsValue::from_str(WEBGPU_METHOD_END))
1884 .unwrap_or(JsValue::UNDEFINED)
1885 .unchecked_into();
1886 let _ = end_fn.call0(pass);
1887 }
1888
1889 pub(crate) fn finish_command_encoder(&self, encoder: &JsValue) -> JsValue {
1899 let finish_fn: Function = Reflect::get(encoder, &JsValue::from_str(WEBGPU_METHOD_FINISH))
1900 .unwrap_or(JsValue::UNDEFINED)
1901 .unchecked_into();
1902 finish_fn.call0(encoder).unwrap_or(JsValue::UNDEFINED)
1903 }
1904
1905 pub fn render_frame(
1918 &self,
1919 pipeline: &JsValue,
1920 clear_color: (f64, f64, f64, f64),
1921 vertex_count: u32,
1922 ) {
1923 let encoder: JsValue = self.create_command_encoder();
1924 let pass: JsValue = self.begin_render_pass(&encoder, clear_color);
1925 self.set_pipeline(&pass, pipeline);
1926 self.draw(&pass, vertex_count, 1);
1927 self.end_render_pass(&pass);
1928 let command_buffer: JsValue = self.finish_command_encoder(&encoder);
1929 self.submit(&[command_buffer]);
1930 }
1931}
1932
1933impl WebGpuInitError {
1939 pub fn code(&self) -> &'static str {
1948 match self {
1949 Self::NavigatorLookup(_) => "WEBGPU_NAVIGATOR_LOOKUP",
1950 Self::NavigatorGpuMissing => "WEBGPU_NAVIGATOR_GPU_MISSING",
1951 Self::RequestAdapterLookup(_) => "WEBGPU_REQUEST_ADAPTER_LOOKUP",
1952 Self::RequestAdapterCall(_) => "WEBGPU_REQUEST_ADAPTER_CALL",
1953 Self::AdapterPromise(_) => "WEBGPU_ADAPTER_PROMISE",
1954 Self::AdapterUnavailable => "WEBGPU_ADAPTER_UNAVAILABLE",
1955 Self::RequestDeviceLookup(_) => "WEBGPU_REQUEST_DEVICE_LOOKUP",
1956 Self::RequestDeviceCall(_) => "WEBGPU_REQUEST_DEVICE_CALL",
1957 Self::DevicePromise(_) => "WEBGPU_DEVICE_PROMISE",
1958 Self::DeviceUnavailable => "WEBGPU_DEVICE_UNAVAILABLE",
1959 Self::CanvasNotFound(_) => "WEBGPU_CANVAS_NOT_FOUND",
1960 Self::CanvasQuery(_) => "WEBGPU_CANVAS_QUERY",
1961 Self::CanvasContextUnavailable => "WEBGPU_CANVAS_CONTEXT_UNAVAILABLE",
1962 Self::PreferredFormatLookup(_) => "WEBGPU_PREFERRED_FORMAT_LOOKUP",
1963 Self::PreferredFormatCall(_) => "WEBGPU_PREFERRED_FORMAT_CALL",
1964 Self::PreferredFormatType(_) => "WEBGPU_PREFERRED_FORMAT_TYPE",
1965 Self::ConfigureLookup(_) => "WEBGPU_CONFIGURE_LOOKUP",
1966 Self::QueueLookup(_) => "WEBGPU_QUEUE_LOOKUP",
1967 }
1968 }
1969
1970 pub fn js_error(&self) -> Option<&JsValue> {
1980 match self {
1981 Self::NavigatorLookup(err)
1982 | Self::RequestAdapterLookup(err)
1983 | Self::RequestAdapterCall(err)
1984 | Self::AdapterPromise(err)
1985 | Self::RequestDeviceLookup(err)
1986 | Self::RequestDeviceCall(err)
1987 | Self::DevicePromise(err)
1988 | Self::CanvasQuery(err)
1989 | Self::PreferredFormatLookup(err)
1990 | Self::PreferredFormatCall(err)
1991 | Self::PreferredFormatType(err)
1992 | Self::ConfigureLookup(err)
1993 | Self::QueueLookup(err) => Some(err),
1994 Self::NavigatorGpuMissing
1995 | Self::AdapterUnavailable
1996 | Self::DeviceUnavailable
1997 | Self::CanvasContextUnavailable
1998 | Self::CanvasNotFound(_) => None,
1999 }
2000 }
2001}
2002
2003fn js_error_to_string(value: &JsValue) -> String {
2005 if let Some(s) = value.as_string() {
2006 s
2007 } else if value.is_undefined() {
2008 "<undefined>".to_string()
2009 } else if value.is_null() {
2010 "<null>".to_string()
2011 } else {
2012 format!("{:?}", value)
2013 }
2014}
2015
2016impl std::fmt::Display for WebGpuInitError {
2023 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2024 match self {
2025 Self::NavigatorLookup(err) => write!(
2026 formatter,
2027 "[{}] Reflect::get(navigator, webgpu) failed: {}",
2028 self.code(),
2029 js_error_to_string(err),
2030 ),
2031 Self::NavigatorGpuMissing => write!(
2032 formatter,
2033 "[{}] navigator.gpu is missing - browser does not expose WebGPU on this origin",
2034 self.code(),
2035 ),
2036 Self::RequestAdapterLookup(err) => write!(
2037 formatter,
2038 "[{}] Reflect::get(gpu, requestAdapter) failed: {}",
2039 self.code(),
2040 js_error_to_string(err),
2041 ),
2042 Self::RequestAdapterCall(err) => write!(
2043 formatter,
2044 "[{}] gpu.requestAdapter() threw: {}",
2045 self.code(),
2046 js_error_to_string(err),
2047 ),
2048 Self::AdapterPromise(err) => write!(
2049 formatter,
2050 "[{}] adapter promise rejected or timed out: {}",
2051 self.code(),
2052 js_error_to_string(err),
2053 ),
2054 Self::AdapterUnavailable => write!(
2055 formatter,
2056 "[{}] requestAdapter returned null - no compatible GPU adapter for the requested powerPreference",
2057 self.code(),
2058 ),
2059 Self::RequestDeviceLookup(err) => write!(
2060 formatter,
2061 "[{}] Reflect::get(adapter, requestDevice) failed: {}",
2062 self.code(),
2063 js_error_to_string(err),
2064 ),
2065 Self::RequestDeviceCall(err) => write!(
2066 formatter,
2067 "[{}] adapter.requestDevice() threw: {}",
2068 self.code(),
2069 js_error_to_string(err),
2070 ),
2071 Self::DevicePromise(err) => write!(
2072 formatter,
2073 "[{}] device promise rejected or timed out: {}",
2074 self.code(),
2075 js_error_to_string(err),
2076 ),
2077 Self::DeviceUnavailable => write!(
2078 formatter,
2079 "[{}] requestDevice returned null - adapter could not allocate a device (possibly device-lost)",
2080 self.code(),
2081 ),
2082 Self::CanvasNotFound(selector) => write!(
2083 formatter,
2084 "[{}] canvas element {:?} not found in DOM",
2085 self.code(),
2086 selector,
2087 ),
2088 Self::CanvasQuery(err) => write!(
2089 formatter,
2090 "[{}] querySelector threw: {}",
2091 self.code(),
2092 js_error_to_string(err),
2093 ),
2094 Self::CanvasContextUnavailable => write!(
2095 formatter,
2096 "[{}] canvas.get_context('webgpu') returned null - the canvas may already be using another context type or WebGPU is disabled",
2097 self.code(),
2098 ),
2099 Self::PreferredFormatLookup(err) => write!(
2100 formatter,
2101 "[{}] Reflect::get(gpu, getPreferredCanvasFormat) failed: {}",
2102 self.code(),
2103 js_error_to_string(err),
2104 ),
2105 Self::PreferredFormatCall(err) => write!(
2106 formatter,
2107 "[{}] gpu.getPreferredCanvasFormat() threw: {}",
2108 self.code(),
2109 js_error_to_string(err),
2110 ),
2111 Self::PreferredFormatType(value) => write!(
2112 formatter,
2113 "[{}] getPreferredCanvasFormat returned non-string: {}",
2114 self.code(),
2115 js_error_to_string(value),
2116 ),
2117 Self::ConfigureLookup(err) => write!(
2118 formatter,
2119 "[{}] Reflect::get(context, configure) failed: {}",
2120 self.code(),
2121 js_error_to_string(err),
2122 ),
2123 Self::QueueLookup(err) => write!(
2124 formatter,
2125 "[{}] Reflect::get(device, queue) failed: {}",
2126 self.code(),
2127 js_error_to_string(err),
2128 ),
2129 }
2130 }
2131}
2132
2133impl std::error::Error for WebGpuInitError {}