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 _ = Reflect::set(
176 context,
177 &JsValue::from_str(RENDERER_PROPERTY_IMAGE_SMOOTHING_QUALITY),
178 &JsValue::from_str(quality_value),
179 );
180 let _ = 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 _ = 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 _ = context.draw_image_with_html_image_element_and_sw_and_sh_and_dx_and_dy_and_dw_and_dh(
233 image,
234 source.get_x(),
235 source.get_y(),
236 source.get_width(),
237 source.get_height(),
238 -source.get_width() * 0.5,
239 -source.get_height() * 0.5,
240 source.get_width(),
241 source.get_height(),
242 );
243 let _ = context.set_transform(1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
244}
245
246impl DrawList {
249 pub fn create() -> DrawList {
255 DrawList::new(Vec::new())
256 }
257
258 pub fn is_empty(&self) -> bool {
264 self.get_commands().is_empty()
265 }
266
267 pub fn len(&self) -> usize {
273 self.get_commands().len()
274 }
275
276 pub fn clear(&mut self) {
279 self.get_mut_commands().clear();
280 }
281
282 pub fn fill_rect(&mut self, position: Vector2D, width: f64, height: f64, color: Color) {
284 self.get_mut_commands().push(DrawCommand::FillRect {
285 position,
286 width,
287 height,
288 color,
289 });
290 }
291
292 pub fn stroke_rect(
294 &mut self,
295 position: Vector2D,
296 width: f64,
297 height: f64,
298 color: Color,
299 line_width: f64,
300 ) {
301 self.get_mut_commands().push(DrawCommand::StrokeRect {
302 position,
303 width,
304 height,
305 color,
306 line_width,
307 });
308 }
309
310 pub fn fill_circle(&mut self, center: Vector2D, radius: f64, color: Color) {
312 self.get_mut_commands().push(DrawCommand::FillCircle {
313 center,
314 radius,
315 color,
316 });
317 }
318
319 pub fn stroke_circle(&mut self, center: Vector2D, radius: f64, color: Color, line_width: f64) {
321 self.get_mut_commands().push(DrawCommand::StrokeCircle {
322 center,
323 radius,
324 color,
325 line_width,
326 });
327 }
328
329 pub fn draw_line(&mut self, start: Vector2D, end: Vector2D, color: Color, line_width: f64) {
331 self.get_mut_commands().push(DrawCommand::Line {
332 start,
333 end,
334 color,
335 line_width,
336 });
337 }
338
339 pub fn fill_text<T, F>(&mut self, text: T, position: Vector2D, color: Color, font: F)
341 where
342 T: AsRef<str>,
343 F: AsRef<str>,
344 {
345 self.get_mut_commands().push(DrawCommand::FillText {
346 text: text.as_ref().to_string(),
347 position,
348 color,
349 font: font.as_ref().to_string(),
350 });
351 }
352
353 pub fn draw_sprite(&mut self, image: &HtmlImageElement, source: Rect, transform: Transform2D) {
355 self.get_mut_commands().push(DrawCommand::DrawSprite {
356 image: image.clone(),
357 source,
358 transform,
359 });
360 }
361
362 pub fn draw_image_rect(
364 &mut self,
365 image: &HtmlImageElement,
366 source: Rect,
367 dest_position: Vector2D,
368 dest_width: f64,
369 dest_height: f64,
370 ) {
371 self.get_mut_commands().push(DrawCommand::DrawImageRect {
372 image: image.clone(),
373 source,
374 dest_position,
375 dest_width,
376 dest_height,
377 });
378 }
379
380 pub fn set_global_alpha(&mut self, alpha: f64) {
382 self.get_mut_commands()
383 .push(DrawCommand::SetGlobalAlpha { alpha });
384 }
385
386 pub fn set_blend_mode(&mut self, mode: BlendMode) {
388 self.get_mut_commands()
389 .push(DrawCommand::SetBlendMode { mode });
390 }
391}
392
393impl CanvasRenderer {
394 pub fn from_selector<S>(
406 canvas_selector: S,
407 viewport_width: f64,
408 viewport_height: f64,
409 ) -> Option<CanvasRenderer>
410 where
411 S: AsRef<str>,
412 {
413 let window_value: Window = window().expect("no global window exists");
414 let document_value: Document = window_value.document().expect("should have a document");
415 let element: Element = document_value
416 .query_selector(canvas_selector.as_ref())
417 .ok()
418 .flatten()?;
419 let canvas_element: HtmlCanvasElement = element.unchecked_into();
420 let context_object: Object = canvas_element
421 .get_context(RENDERER_CONTEXT_TYPE_2D)
422 .ok()
423 .flatten()?;
424 let context: CanvasRenderingContext2d = context_object.unchecked_into();
425 let renderer: CanvasRenderer = CanvasRenderer::new(
426 context,
427 Camera2D::create(viewport_width, viewport_height),
428 RenderQuality::default(),
429 );
430 renderer.enable_smoothing();
431 Some(renderer)
432 }
433
434 pub fn enable_smoothing(&self) {
440 Self::apply_quality(self.get_context(), self.get_quality());
441 }
442
443 pub fn clear(&self) {
445 self.get_context().clear_rect(
446 0.0,
447 0.0,
448 self.get_camera().get_viewport_width(),
449 self.get_camera().get_viewport_height(),
450 );
451 }
452
453 pub fn clear_color<C>(&self, color: C)
459 where
460 C: AsRef<str>,
461 {
462 self.get_context().set_fill_style_str(color.as_ref());
463 self.get_context().fill_rect(
464 0.0,
465 0.0,
466 self.get_camera().get_viewport_width(),
467 self.get_camera().get_viewport_height(),
468 );
469 }
470
471 pub fn save(&self) {
473 self.get_context().save();
474 }
475
476 pub fn restore(&self) {
478 self.get_context().restore();
479 }
480
481 pub fn replay(&self, list: &DrawList) {
489 Self::replay_context(self.get_context(), list);
490 }
491
492 pub fn replay_context(context: &CanvasRenderingContext2d, list: &DrawList) {
510 let mut current_fill: Option<Color> = None;
511 let mut current_stroke: Option<Color> = None;
512 let mut current_line_width: f64 = f64::NAN;
513 let mut run_open: bool = false;
515 let mut run_is_fill: bool = true;
516 let mut run_key: Option<(u8, Color, f64)> = None;
517
518 fn batch_key(command: &DrawCommand) -> Option<(u8, Color, f64)> {
521 match command {
522 DrawCommand::FillRect { color, .. } | DrawCommand::FillCircle { color, .. } => {
523 Some((0, *color, 0.0))
524 }
525 DrawCommand::StrokeRect {
526 color, line_width, ..
527 }
528 | DrawCommand::StrokeCircle {
529 color, line_width, ..
530 }
531 | DrawCommand::Line {
532 color, line_width, ..
533 } => Some((1, *color, *line_width)),
534 _ => None,
535 }
536 }
537
538 fn emit_geometry(context: &CanvasRenderingContext2d, command: &DrawCommand) {
540 match command {
541 DrawCommand::FillRect {
542 position,
543 width,
544 height,
545 ..
546 }
547 | DrawCommand::StrokeRect {
548 position,
549 width,
550 height,
551 ..
552 } => {
553 context.rect(position.get_x(), position.get_y(), *width, *height);
554 }
555 DrawCommand::FillCircle { center, radius, .. }
556 | DrawCommand::StrokeCircle { center, radius, .. } => {
557 context.move_to(center.get_x() + radius, center.get_y());
558 let _ = context.arc(center.get_x(), center.get_y(), *radius, 0.0, TWO_PI);
559 }
560 DrawCommand::Line { start, end, .. } => {
561 context.move_to(start.get_x(), start.get_y());
562 context.line_to(end.get_x(), end.get_y());
563 }
564 _ => {}
565 }
566 }
567
568 for command in self_commands(list) {
569 let key: Option<(u8, Color, f64)> = batch_key(command);
570 if run_open && key != run_key {
572 if run_is_fill {
573 context.fill();
574 } else {
575 context.stroke();
576 }
577 run_open = false;
578 }
579 if let Some(current_key) = key {
580 if !run_open {
582 let (kind, color, line_width) = current_key;
583 if kind == 0 {
584 if current_fill != Some(color) {
585 context.set_fill_style_str(&Color::to_css(&color));
586 current_fill = Some(color);
587 }
588 run_is_fill = true;
589 } else {
590 if current_stroke != Some(color) {
591 context.set_stroke_style_str(&Color::to_css(&color));
592 current_stroke = Some(color);
593 }
594 if current_line_width != line_width {
595 context.set_line_width(line_width);
596 current_line_width = line_width;
597 }
598 run_is_fill = false;
599 }
600 context.begin_path();
601 run_open = true;
602 run_key = Some(current_key);
603 }
604 emit_geometry(context, command);
605 continue;
606 }
607 match command {
609 DrawCommand::FillText {
610 text,
611 position,
612 color,
613 font,
614 } => {
615 if current_fill != Some(*color) {
616 context.set_fill_style_str(&Color::to_css(color));
617 current_fill = Some(*color);
618 }
619 context.set_font(font);
620 let _ = context.fill_text(text, position.get_x(), position.get_y());
621 }
622 DrawCommand::DrawSprite {
623 image,
624 source,
625 transform,
626 } => {
627 draw_sprite_immediate(context, image, source, transform);
628 }
629 DrawCommand::DrawImageRect {
630 image,
631 source,
632 dest_position,
633 dest_width,
634 dest_height,
635 } => {
636 let _ = context
637 .draw_image_with_html_image_element_and_sw_and_sh_and_dx_and_dy_and_dw_and_dh(
638 image,
639 source.get_x(),
640 source.get_y(),
641 source.get_width(),
642 source.get_height(),
643 dest_position.get_x(),
644 dest_position.get_y(),
645 *dest_width,
646 *dest_height,
647 );
648 }
649 DrawCommand::SetGlobalAlpha { alpha } => {
650 context.set_global_alpha(Numeric::clamp(*alpha, 0.0, 1.0));
651 }
652 DrawCommand::SetBlendMode { mode } => {
653 let _ = context.set_global_composite_operation(mode.to_css());
654 }
655 _ => {}
656 }
657 }
658 if run_open {
660 if run_is_fill {
661 context.fill();
662 } else {
663 context.stroke();
664 }
665 }
666 let _ = context.set_transform(1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
667 context.set_global_alpha(1.0);
668 }
669
670 pub fn apply_camera(&self) {
675 let camera: Camera2D = self.get_camera();
676 let _ = self.get_context().translate(
677 camera.get_viewport_width() * 0.5,
678 camera.get_viewport_height() * 0.5,
679 );
680 let _ = self
681 .get_context()
682 .scale(camera.get_zoom(), camera.get_zoom());
683 let _ = self.get_context().rotate(camera.get_rotation());
684 let _ = self.get_context().translate(
685 -camera.get_position().get_x(),
686 -camera.get_position().get_y(),
687 );
688 }
689
690 pub fn set_fill_color<C>(&self, color: C)
696 where
697 C: AsRef<str>,
698 {
699 self.get_context().set_fill_style_str(color.as_ref());
700 }
701
702 pub fn set_stroke_color<C>(&self, color: C)
708 where
709 C: AsRef<str>,
710 {
711 self.get_context().set_stroke_style_str(color.as_ref());
712 }
713
714 pub fn set_line_width(&self, width: f64) {
720 self.get_context().set_line_width(width);
721 }
722
723 pub fn set_global_alpha(&self, alpha: f64) {
729 self.get_context()
730 .set_global_alpha(Numeric::clamp(alpha, 0.0, 1.0));
731 }
732
733 pub fn fill_rect(&self, position: Vector2D, width: f64, height: f64) {
741 self.get_context()
742 .fill_rect(position.get_x(), position.get_y(), width, height);
743 }
744
745 pub fn stroke_rect(&self, position: Vector2D, width: f64, height: f64) {
753 self.get_context()
754 .stroke_rect(position.get_x(), position.get_y(), width, height);
755 }
756
757 pub fn fill_circle(&self, center: Vector2D, radius: f64) {
764 self.get_context().begin_path();
765 self.get_context()
766 .arc(center.get_x(), center.get_y(), radius, 0.0, TWO_PI)
767 .unwrap_or(());
768 self.get_context().fill();
769 }
770
771 pub fn stroke_circle(&self, center: Vector2D, radius: f64) {
778 self.get_context().begin_path();
779 self.get_context()
780 .arc(center.get_x(), center.get_y(), radius, 0.0, TWO_PI)
781 .unwrap_or(());
782 self.get_context().stroke();
783 }
784
785 pub fn draw_line(&self, start: Vector2D, end: Vector2D) {
792 self.get_context().begin_path();
793 self.get_context().move_to(start.get_x(), start.get_y());
794 self.get_context().line_to(end.get_x(), end.get_y());
795 self.get_context().stroke();
796 }
797
798 pub fn fill_text<T>(&self, text: T, position: Vector2D)
805 where
806 T: AsRef<str>,
807 {
808 self.get_context()
809 .fill_text(text.as_ref(), position.get_x(), position.get_y())
810 .unwrap_or(());
811 }
812
813 pub fn set_font<F>(&self, font: F)
819 where
820 F: AsRef<str>,
821 {
822 self.get_context().set_font(font.as_ref());
823 }
824
825 pub fn draw_image(
834 &self,
835 image: &HtmlImageElement,
836 position: Vector2D,
837 width: f64,
838 height: f64,
839 ) {
840 let _ = self
841 .get_context()
842 .draw_image_with_html_image_element_and_dw_and_dh(
843 image,
844 position.get_x(),
845 position.get_y(),
846 width,
847 height,
848 );
849 }
850
851 pub fn draw_image_rect(
861 &self,
862 image: &HtmlImageElement,
863 source: Rect,
864 dest_position: Vector2D,
865 dest_width: f64,
866 dest_height: f64,
867 ) {
868 let _ = self
869 .get_context()
870 .draw_image_with_html_image_element_and_sw_and_sh_and_dx_and_dy_and_dw_and_dh(
871 image,
872 source.get_x(),
873 source.get_y(),
874 source.get_width(),
875 source.get_height(),
876 dest_position.get_x(),
877 dest_position.get_y(),
878 dest_width,
879 dest_height,
880 );
881 }
882}
883
884impl Camera3D {
886 pub fn create(
899 position: Vector3D,
900 target: Vector3D,
901 viewport_width: f64,
902 viewport_height: f64,
903 ) -> Camera3D {
904 let mut camera: Camera3D = Camera3D::new(position, target, viewport_width, viewport_height);
905 camera.set_up(Vector3D::up());
906 camera.set_fov(DEFAULT_CAMERA_FOV);
907 camera.set_near(DEFAULT_CAMERA_NEAR);
908 camera.set_far(DEFAULT_CAMERA_FAR);
909 camera
910 }
911
912 pub fn aspect(&self) -> f64 {
918 if self.get_viewport_height() < EPSILON {
919 return 1.0;
920 }
921 self.get_viewport_width() / self.get_viewport_height()
922 }
923
924 pub fn forward(&self) -> Vector3D {
930 (self.get_target() - self.get_position()).normalized()
931 }
932
933 pub fn right(&self) -> Vector3D {
939 self.forward().cross(self.get_up()).normalized()
940 }
941
942 pub fn view_matrix(&self) -> Matrix4x4 {
948 Matrix4x4::look_at(self.get_position(), self.get_target(), self.get_up())
949 }
950
951 pub fn projection_matrix(&self) -> Matrix4x4 {
957 Matrix4x4::perspective(
958 self.get_fov(),
959 self.aspect(),
960 self.get_near(),
961 self.get_far(),
962 )
963 }
964
965 pub fn view_proj_matrix(&self) -> Matrix4x4 {
971 self.projection_matrix().multiply(self.view_matrix())
972 }
973
974 pub fn world_to_screen(&self, world: Vector3D) -> Vector3D {
984 let clip: Vector3D = self.view_proj_matrix().transform_point(world);
985 Vector3D::new(
986 (clip.get_x() + 1.0) * 0.5 * self.get_viewport_width(),
987 (1.0 - clip.get_y()) * 0.5 * self.get_viewport_height(),
988 clip.get_z(),
989 )
990 }
991
992 pub fn in_frustum(&self, world: Vector3D) -> bool {
1002 let clip: Vector3D = self.view_proj_matrix().transform_point(world);
1003 clip.get_x() >= -1.0
1004 && clip.get_x() <= 1.0
1005 && clip.get_y() >= -1.0
1006 && clip.get_y() <= 1.0
1007 && clip.get_z() >= -1.0
1008 && clip.get_z() <= 1.0
1009 }
1010
1011 pub fn translate(&mut self, offset: Vector3D) {
1017 self.set_position(self.get_position() + offset);
1018 self.set_target(self.get_target() + offset);
1019 }
1020
1021 pub fn zoom(&mut self, distance: f64) {
1027 let direction: Vector3D = self.forward();
1028 self.set_position(self.get_position() + direction.scaled(distance));
1029 }
1030
1031 pub fn orbit(&mut self, yaw_delta: f64, pitch_delta: f64) {
1038 let offset: Vector3D = self.get_position() - self.get_target();
1039 let current_distance: f64 = offset.magnitude();
1040 let current_yaw: f64 = offset.get_x().atan2(offset.get_z());
1041 let horizontal_dist: f64 =
1042 (offset.get_x() * offset.get_x() + offset.get_z() * offset.get_z()).sqrt();
1043 let current_pitch: f64 = (offset.get_y() / horizontal_dist.max(EPSILON)).asin();
1044 let new_yaw: f64 = current_yaw + yaw_delta;
1045 let new_pitch: f64 = Numeric::clamp(
1046 current_pitch + pitch_delta,
1047 -HALF_PI + EPSILON,
1048 HALF_PI - EPSILON,
1049 );
1050 let cos_pitch: f64 = new_pitch.cos();
1051 self.set_position(
1052 self.get_target()
1053 + Vector3D::new(
1054 new_yaw.sin() * cos_pitch * current_distance,
1055 new_pitch.sin() * current_distance,
1056 new_yaw.cos() * cos_pitch * current_distance,
1057 ),
1058 );
1059 }
1060}
1061
1062impl Default for Camera3D {
1064 fn default() -> Camera3D {
1065 Camera3D::create(Vector3D::new(0.0, 0.0, 5.0), Vector3D::zero(), 800.0, 600.0)
1066 }
1067}
1068
1069impl SsaaCanvas {
1071 pub fn from_selector<S>(canvas_selector: S, width: f64, height: f64) -> Option<SsaaCanvas>
1083 where
1084 S: AsRef<str>,
1085 {
1086 Self::from_selector_with_scale(
1087 canvas_selector,
1088 width,
1089 height,
1090 RENDERER_DEFAULT_SSAA_SCALE_FACTOR,
1091 )
1092 }
1093
1094 pub fn from_selector_with_scale<S>(
1110 canvas_selector: S,
1111 width: f64,
1112 height: f64,
1113 scale_factor: f64,
1114 ) -> Option<SsaaCanvas>
1115 where
1116 S: AsRef<str>,
1117 {
1118 let window_value: Window = window().expect("no global window exists");
1119 let document_value: Document = window_value.document().expect("should have a document");
1120 let element: Element = document_value
1121 .query_selector(canvas_selector.as_ref())
1122 .ok()
1123 .flatten()?;
1124 let display_canvas: HtmlCanvasElement = element.unchecked_into();
1125 let device_pixel_ratio: f64 = CanvasRenderer::detect_dpr();
1126 let physical_width: u32 = (width * device_pixel_ratio).round() as u32;
1127 let physical_height: u32 = (height * device_pixel_ratio).round() as u32;
1128 display_canvas.set_width(physical_width);
1129 display_canvas.set_height(physical_height);
1130 let display_context_object: Object = display_canvas
1131 .get_context(RENDERER_CONTEXT_TYPE_2D)
1132 .ok()
1133 .flatten()?;
1134 let display_context: CanvasRenderingContext2d = display_context_object.unchecked_into();
1135 let _ = display_context.scale(device_pixel_ratio, device_pixel_ratio);
1136 let offscreen_canvas: HtmlCanvasElement = document_value
1137 .create_element(RENDERER_ELEMENT_CANVAS)
1138 .ok()?
1139 .unchecked_into();
1140 let scaled_width: u32 = (width * scale_factor * device_pixel_ratio).round() as u32;
1141 let scaled_height: u32 = (height * scale_factor * device_pixel_ratio).round() as u32;
1142 offscreen_canvas.set_width(scaled_width);
1143 offscreen_canvas.set_height(scaled_height);
1144 let offscreen_context_object: Object = offscreen_canvas
1145 .get_context(RENDERER_CONTEXT_TYPE_2D)
1146 .ok()
1147 .flatten()?;
1148 let offscreen_context: CanvasRenderingContext2d = offscreen_context_object.unchecked_into();
1149 let _ = offscreen_context.scale(
1150 scale_factor * device_pixel_ratio,
1151 scale_factor * device_pixel_ratio,
1152 );
1153 let ssaa_canvas: SsaaCanvas = SsaaCanvas::new(
1154 display_canvas,
1155 display_context,
1156 offscreen_canvas,
1157 offscreen_context,
1158 scale_factor,
1159 width,
1160 height,
1161 );
1162 ssaa_canvas.enable_smoothing();
1163 Some(ssaa_canvas)
1164 }
1165
1166 pub fn present(&self) {
1173 CanvasRenderer::apply_quality(self.get_display_context(), self.get_quality());
1174 self.get_display_context()
1175 .clear_rect(0.0, 0.0, self.get_width(), self.get_height());
1176 let _ = self
1177 .get_display_context()
1178 .draw_image_with_html_canvas_element_and_dw_and_dh(
1179 self.get_offscreen_canvas(),
1180 0.0,
1181 0.0,
1182 self.get_width(),
1183 self.get_height(),
1184 );
1185 }
1186
1187 pub fn clear(&self) {
1189 self.get_offscreen_context()
1190 .clear_rect(0.0, 0.0, self.get_width(), self.get_height());
1191 }
1192
1193 pub fn clear_color<C>(&self, color: C)
1199 where
1200 C: AsRef<str>,
1201 {
1202 self.get_offscreen_context()
1203 .set_fill_style_str(color.as_ref());
1204 self.get_offscreen_context()
1205 .fill_rect(0.0, 0.0, self.get_width(), self.get_height());
1206 }
1207
1208 pub fn enable_smoothing(&self) {
1213 let quality: RenderQuality = self.get_quality();
1214 CanvasRenderer::apply_quality(self.get_display_context(), quality);
1215 CanvasRenderer::apply_quality(self.get_offscreen_context(), quality);
1216 }
1217}
1218
1219impl BlendMode {
1221 pub fn to_css(&self) -> &str {
1227 match self {
1228 BlendMode::Normal => BLEND_MODE_NORMAL,
1229 BlendMode::Multiply => BLEND_MODE_MULTIPLY,
1230 BlendMode::Screen => BLEND_MODE_SCREEN,
1231 BlendMode::Lighter => BLEND_MODE_LIGHTER,
1232 BlendMode::Overlay => BLEND_MODE_OVERLAY,
1233 BlendMode::Darken => BLEND_MODE_DARKEN,
1234 BlendMode::Lighten => BLEND_MODE_LIGHTEN,
1235 BlendMode::ColorDodge => BLEND_MODE_COLOR_DODGE,
1236 BlendMode::ColorBurn => BLEND_MODE_COLOR_BURN,
1237 BlendMode::HardLight => BLEND_MODE_HARD_LIGHT,
1238 BlendMode::SoftLight => BLEND_MODE_SOFT_LIGHT,
1239 BlendMode::Difference => BLEND_MODE_DIFFERENCE,
1240 BlendMode::Exclusion => BLEND_MODE_EXCLUSION,
1241 BlendMode::Hue => BLEND_MODE_HUE,
1242 BlendMode::Saturation => BLEND_MODE_SATURATION,
1243 BlendMode::Color => BLEND_MODE_COLOR,
1244 BlendMode::Luminosity => BLEND_MODE_LUMINOSITY,
1245 }
1246 }
1247}
1248
1249impl LinearGradient {
1251 pub fn create(start: Vector2D, end: Vector2D, stops: Vec<(f64, String)>) -> LinearGradient {
1263 LinearGradient::new(start, end, stops)
1264 }
1265
1266 pub fn to_gradient(&self, context: &CanvasRenderingContext2d) -> Option<CanvasGradient> {
1276 let canvas_gradient: CanvasGradient = context.create_linear_gradient(
1277 self.get_start().get_x(),
1278 self.get_start().get_y(),
1279 self.get_end().get_x(),
1280 self.get_end().get_y(),
1281 );
1282 for (position, color) in self.get_stops() {
1283 let _ = canvas_gradient.add_color_stop(*position as f32, color);
1284 }
1285 Some(canvas_gradient)
1286 }
1287}
1288
1289impl RadialGradient {
1291 pub fn create(
1305 inner_center: Vector2D,
1306 inner_radius: f64,
1307 outer_center: Vector2D,
1308 outer_radius: f64,
1309 stops: Vec<(f64, String)>,
1310 ) -> RadialGradient {
1311 RadialGradient::new(
1312 inner_center,
1313 inner_radius,
1314 outer_center,
1315 outer_radius,
1316 stops,
1317 )
1318 }
1319
1320 pub fn to_gradient(&self, context: &CanvasRenderingContext2d) -> Option<CanvasGradient> {
1330 let canvas_gradient: CanvasGradient = context
1331 .create_radial_gradient(
1332 self.get_inner_center().get_x(),
1333 self.get_inner_center().get_y(),
1334 self.get_inner_radius(),
1335 self.get_outer_center().get_x(),
1336 self.get_outer_center().get_y(),
1337 self.get_outer_radius(),
1338 )
1339 .ok()?;
1340 for (position, color) in self.get_stops() {
1341 let _ = canvas_gradient.add_color_stop(*position as f32, color);
1342 }
1343 Some(canvas_gradient)
1344 }
1345}
1346
1347impl ShadowConfig {
1349 pub fn create() -> ShadowConfig {
1355 ShadowConfig::new(
1356 RENDERER_DEFAULT_SHADOW_COLOR.to_string(),
1357 RENDERER_DEFAULT_SHADOW_BLUR,
1358 0.0,
1359 0.0,
1360 )
1361 }
1362}
1363
1364impl Default for ShadowConfig {
1366 fn default() -> ShadowConfig {
1367 ShadowConfig::create()
1368 }
1369}
1370
1371impl RenderLayer {
1373 pub fn create(z_index: i32, visible: bool) -> RenderLayer {
1384 RenderLayer::new(z_index, visible)
1385 }
1386
1387 pub fn background() -> RenderLayer {
1393 RenderLayer::new(RENDERER_LAYER_BACKGROUND, true)
1394 }
1395
1396 pub fn foreground() -> RenderLayer {
1402 RenderLayer::new(RENDERER_LAYER_FOREGROUND, true)
1403 }
1404
1405 pub fn ui() -> RenderLayer {
1411 RenderLayer::new(RENDERER_LAYER_UI, true)
1412 }
1413}
1414
1415impl CanvasRenderer {
1417 pub fn set_blend_mode(&self, mode: BlendMode) {
1423 let _ = self
1424 .get_context()
1425 .set_global_composite_operation(mode.to_css());
1426 }
1427
1428 pub fn set_shadow(&self, config: &ShadowConfig) {
1434 self.get_context()
1435 .set_shadow_color(config.get_color().as_str());
1436 self.get_context().set_shadow_blur(config.get_blur());
1437 self.get_context()
1438 .set_shadow_offset_x(config.get_offset_x());
1439 self.get_context()
1440 .set_shadow_offset_y(config.get_offset_y());
1441 }
1442
1443 pub fn clear_shadow(&self) {
1445 self.get_context().set_shadow_color("rgba(0, 0, 0, 0)");
1446 self.get_context().set_shadow_blur(0.0);
1447 self.get_context().set_shadow_offset_x(0.0);
1448 self.get_context().set_shadow_offset_y(0.0);
1449 }
1450
1451 pub fn set_linear_gradient_fill(&self, gradient: &LinearGradient) {
1457 if let Some(canvas_gradient) = gradient.to_gradient(self.get_context()) {
1458 self.get_context()
1459 .set_fill_style_canvas_gradient(&canvas_gradient);
1460 }
1461 }
1462
1463 pub fn set_radial_gradient_fill(&self, gradient: &RadialGradient) {
1469 if let Some(canvas_gradient) = gradient.to_gradient(self.get_context()) {
1470 self.get_context()
1471 .set_fill_style_canvas_gradient(&canvas_gradient);
1472 }
1473 }
1474
1475 pub fn set_linear_gradient_stroke(&self, gradient: &LinearGradient) {
1481 if let Some(canvas_gradient) = gradient.to_gradient(self.get_context()) {
1482 self.get_context()
1483 .set_stroke_style_canvas_gradient(&canvas_gradient);
1484 }
1485 }
1486
1487 pub fn set_radial_gradient_stroke(&self, gradient: &RadialGradient) {
1493 if let Some(canvas_gradient) = gradient.to_gradient(self.get_context()) {
1494 self.get_context()
1495 .set_stroke_style_canvas_gradient(&canvas_gradient);
1496 }
1497 }
1498}
1499
1500impl RenderBackend for CanvasRenderer {
1508 fn clear(&self) {
1510 self.clear();
1511 }
1512
1513 fn clear_color<C>(&self, color: C)
1515 where
1516 C: AsRef<str>,
1517 {
1518 self.clear_color(color);
1519 }
1520
1521 fn save(&self) {
1523 self.save();
1524 }
1525
1526 fn restore(&self) {
1528 self.restore();
1529 }
1530
1531 fn set_fill_color(&self, color: &str) {
1533 self.set_fill_color(color);
1534 }
1535
1536 fn set_stroke_color(&self, color: &str) {
1538 self.set_stroke_color(color);
1539 }
1540
1541 fn set_line_width(&self, width: f64) {
1543 self.set_line_width(width);
1544 }
1545
1546 fn set_global_alpha(&self, alpha: f64) {
1548 self.set_global_alpha(alpha);
1549 }
1550
1551 fn set_blend_mode(&self, mode: BlendMode) {
1553 self.set_blend_mode(mode);
1554 }
1555
1556 fn set_shadow(&self, config: &ShadowConfig) {
1558 self.set_shadow(config);
1559 }
1560
1561 fn clear_shadow(&self) {
1563 self.clear_shadow();
1564 }
1565
1566 fn fill_rect(&self, position: Vector2D, width: f64, height: f64) {
1568 self.fill_rect(position, width, height);
1569 }
1570
1571 fn stroke_rect(&self, position: Vector2D, width: f64, height: f64) {
1573 self.stroke_rect(position, width, height);
1574 }
1575
1576 fn fill_circle(&self, center: Vector2D, radius: f64) {
1578 self.fill_circle(center, radius);
1579 }
1580
1581 fn stroke_circle(&self, center: Vector2D, radius: f64) {
1583 self.stroke_circle(center, radius);
1584 }
1585
1586 fn draw_line(&self, start: Vector2D, end: Vector2D) {
1588 self.draw_line(start, end);
1589 }
1590
1591 fn fill_text(&self, text: &str, position: Vector2D) {
1593 self.fill_text(text, position);
1594 }
1595
1596 fn set_font(&self, font: &str) {
1598 self.set_font(font);
1599 }
1600
1601 fn draw_image(&self, image: &HtmlImageElement, position: Vector2D, width: f64, height: f64) {
1603 self.draw_image(image, position, width, height);
1604 }
1605
1606 fn set_linear_gradient_fill(&self, gradient: &LinearGradient) {
1608 self.set_linear_gradient_fill(gradient);
1609 }
1610
1611 fn set_radial_gradient_fill(&self, gradient: &RadialGradient) {
1613 self.set_radial_gradient_fill(gradient);
1614 }
1615}
1616
1617impl WebGpuRenderer {
1619 fn timeout_promise() -> Promise {
1646 let window_value: Window = window().expect("no global window exists");
1647 Promise::new(&mut |_resolve: Function, reject: Function| {
1648 let reject_fn: Function = reject.clone();
1649 let timer: Closure<dyn FnMut()> = Closure::wrap(Box::new(move || {
1650 let _ = reject_fn.call1(
1651 &JsValue::UNDEFINED,
1652 &JsValue::from_str(RENDERER_TIMEOUT_ERROR_MESSAGE),
1653 );
1654 }));
1655 let _ = window_value.set_timeout_with_callback_and_timeout_and_arguments_0(
1656 timer.as_ref().unchecked_ref(),
1657 INIT_PROMISE_TIMEOUT_MILLIS,
1658 );
1659 timer.forget();
1660 })
1661 }
1662
1663 fn race_with_timeout(promise: Promise) -> Promise {
1669 let array: Array = Array::of2(&promise, &Self::timeout_promise());
1670 Promise::race(&array)
1671 }
1672
1673 pub async fn init(config: &RenderConfig) -> Result<WebGpuRenderer, WebGpuInitError> {
1696 let window: Window = window().expect("no global window exists");
1697 let navigator: Navigator = window.navigator();
1698 let gpu_result: Result<JsValue, JsValue> =
1699 Reflect::get(navigator.as_ref(), &JsValue::from_str(WEBGPU_CONTEXT_TYPE));
1700 let gpu: JsValue = match gpu_result {
1701 Ok(value) => value,
1702 Err(err) => return Err(WebGpuInitError::NavigatorLookup(err)),
1703 };
1704 if gpu.is_undefined() || gpu.is_null() {
1705 return Err(WebGpuInitError::NavigatorGpuMissing);
1706 }
1707 let adapter_options: Object = Object::new();
1708 let _ = Reflect::set(
1709 &adapter_options,
1710 &JsValue::from_str(WEBGPU_PROPERTY_POWER_PREFERENCE),
1711 &JsValue::from_str(config.power_preference.to_web_sys_string()),
1712 );
1713 let request_adapter_fn: Function =
1714 match Reflect::get(&gpu, &JsValue::from_str(WEBGPU_METHOD_REQUEST_ADAPTER)) {
1715 Ok(value) => value.unchecked_into(),
1716 Err(err) => return Err(WebGpuInitError::RequestAdapterLookup(err)),
1717 };
1718 let adapter_promise: Promise = match request_adapter_fn.call1(&gpu, &adapter_options) {
1719 Ok(value) => value.unchecked_into(),
1720 Err(err) => return Err(WebGpuInitError::RequestAdapterCall(err)),
1721 };
1722 let adapter_value: JsValue =
1723 match JsFuture::from(Self::race_with_timeout(adapter_promise)).await {
1724 Ok(value) => value,
1725 Err(err) => return Err(WebGpuInitError::AdapterPromise(err)),
1726 };
1727 if adapter_value.is_null() || adapter_value.is_undefined() {
1728 return Err(WebGpuInitError::AdapterUnavailable);
1729 }
1730 let device_descriptor: Object = Object::new();
1731 let request_device_fn: Function = match Reflect::get(
1732 &adapter_value,
1733 &JsValue::from_str(WEBGPU_METHOD_REQUEST_DEVICE),
1734 ) {
1735 Ok(value) => value.unchecked_into(),
1736 Err(err) => return Err(WebGpuInitError::RequestDeviceLookup(err)),
1737 };
1738 let device_promise: Promise =
1739 match request_device_fn.call1(&adapter_value, &device_descriptor) {
1740 Ok(value) => value.unchecked_into(),
1741 Err(err) => return Err(WebGpuInitError::RequestDeviceCall(err)),
1742 };
1743 let device_value: JsValue =
1744 match JsFuture::from(Self::race_with_timeout(device_promise)).await {
1745 Ok(value) => value,
1746 Err(err) => return Err(WebGpuInitError::DevicePromise(err)),
1747 };
1748 if device_value.is_null() || device_value.is_undefined() {
1749 return Err(WebGpuInitError::DeviceUnavailable);
1750 }
1751 let document: Document = window.document().expect("should have a document");
1752 let element: Element = match document.query_selector(&config.canvas_selector) {
1753 Ok(Some(el)) => el,
1754 Ok(None) => {
1755 return Err(WebGpuInitError::CanvasNotFound(
1756 config.canvas_selector.clone(),
1757 ));
1758 }
1759 Err(err) => return Err(WebGpuInitError::CanvasQuery(err)),
1760 };
1761 let canvas: HtmlCanvasElement = element.unchecked_into();
1762 let context_object: Option<Object> = canvas.get_context(WEBGPU_CONTEXT_TYPE).ok().flatten();
1763 let context_object: Object = match context_object {
1764 Some(c) => c,
1765 None => return Err(WebGpuInitError::CanvasContextUnavailable),
1766 };
1767 let context: JsValue = context_object.into();
1768 let get_format_fn: Function =
1769 match Reflect::get(&gpu, &JsValue::from_str(WEBGPU_METHOD_GET_PREFERRED_FORMAT)) {
1770 Ok(value) => value.unchecked_into(),
1771 Err(err) => return Err(WebGpuInitError::PreferredFormatLookup(err)),
1772 };
1773 let format_value: JsValue = match get_format_fn.call0(&gpu) {
1774 Ok(value) => value,
1775 Err(err) => return Err(WebGpuInitError::PreferredFormatCall(err)),
1776 };
1777 let format: String = match format_value.as_string() {
1778 Some(s) => s,
1779 None => return Err(WebGpuInitError::PreferredFormatType(format_value)),
1780 };
1781 let dpr: f64 = CanvasRenderer::detect_dpr();
1785 let physical_width: u32 = (config.width * dpr).round() as u32;
1786 let physical_height: u32 = (config.height * dpr).round() as u32;
1787 canvas.set_width(physical_width);
1788 canvas.set_height(physical_height);
1789 let canvas_config: Object = Object::new();
1790 let _ = Reflect::set(
1791 &canvas_config,
1792 &JsValue::from_str(WEBGPU_PROPERTY_DEVICE),
1793 &device_value,
1794 );
1795 let _ = Reflect::set(
1796 &canvas_config,
1797 &JsValue::from_str(WEBGPU_PROPERTY_FORMAT),
1798 &format_value,
1799 );
1800 let configure_fn: Function =
1801 match Reflect::get(&context, &JsValue::from_str(WEBGPU_METHOD_CONFIGURE)) {
1802 Ok(value) => value.unchecked_into(),
1803 Err(err) => return Err(WebGpuInitError::ConfigureLookup(err)),
1804 };
1805 let _ = configure_fn.call1(&context, &canvas_config);
1806 let queue: JsValue =
1807 match Reflect::get(&device_value, &JsValue::from_str(WEBGPU_PROPERTY_QUEUE)) {
1808 Ok(value) => value,
1809 Err(err) => return Err(WebGpuInitError::QueueLookup(err)),
1810 };
1811 Ok(WebGpuRenderer {
1812 device: device_value,
1813 queue,
1814 context,
1815 canvas,
1816 format,
1817 width: physical_width,
1818 height: physical_height,
1819 antialias: config.antialias,
1820 })
1821 }
1822
1823 pub fn resize(&mut self, physical_width: u32, physical_height: u32) -> bool {
1848 if self.get_canvas().is_null()
1849 || self.get_context().is_null()
1850 || self.get_device().is_undefined()
1851 {
1852 return false;
1853 }
1854 self.get_canvas().set_width(physical_width);
1855 self.get_canvas().set_height(physical_height);
1856 let format_value: JsValue = JsValue::from_str(&self.get_format());
1857 let canvas_config: Object = Object::new();
1858 let _ = Reflect::set(
1859 &canvas_config,
1860 &JsValue::from_str(WEBGPU_PROPERTY_DEVICE),
1861 self.get_device(),
1862 );
1863 let _ = Reflect::set(
1864 &canvas_config,
1865 &JsValue::from_str(WEBGPU_PROPERTY_FORMAT),
1866 &format_value,
1867 );
1868 let configure_fn: Function = Reflect::get(
1869 self.get_context(),
1870 &JsValue::from_str(WEBGPU_METHOD_CONFIGURE),
1871 )
1872 .ok()
1873 .and_then(|value: JsValue| value.dyn_into::<Function>().ok())
1874 .unwrap_or_else(|| Function::new_no_args(""));
1875 if configure_fn
1876 .call1(self.get_context(), &canvas_config)
1877 .is_err()
1878 {
1879 return false;
1880 }
1881 self.set_width(physical_width);
1882 self.set_height(physical_height);
1883 true
1884 }
1885
1886 pub(crate) fn create_shader_module<S>(&self, code: S) -> JsValue
1896 where
1897 S: AsRef<str>,
1898 {
1899 let descriptor: Object = Object::new();
1900 let _ = Reflect::set(
1901 &descriptor,
1902 &JsValue::from_str(WEBGPU_PROPERTY_CODE),
1903 &JsValue::from_str(code.as_ref()),
1904 );
1905 let create_fn: Function = Reflect::get(
1906 self.get_device(),
1907 &JsValue::from_str(WEBGPU_METHOD_CREATE_SHADER_MODULE),
1908 )
1909 .unwrap_or(JsValue::UNDEFINED)
1910 .unchecked_into();
1911 create_fn
1912 .call1(self.get_device(), &descriptor)
1913 .unwrap_or(JsValue::UNDEFINED)
1914 }
1915
1916 pub(crate) fn create_command_encoder(&self) -> JsValue {
1922 let create_fn: Function = Reflect::get(
1923 self.get_device(),
1924 &JsValue::from_str(WEBGPU_METHOD_CREATE_COMMAND_ENCODER),
1925 )
1926 .unwrap_or(JsValue::UNDEFINED)
1927 .unchecked_into();
1928 create_fn
1929 .call0(self.get_device())
1930 .unwrap_or(JsValue::UNDEFINED)
1931 }
1932
1933 pub(crate) fn get_current_texture_view(&self) -> JsValue {
1943 let get_texture_fn: Function = Reflect::get(
1944 self.get_context(),
1945 &JsValue::from_str(WEBGPU_METHOD_GET_CURRENT_TEXTURE),
1946 )
1947 .unwrap_or(JsValue::UNDEFINED)
1948 .unchecked_into();
1949 let texture: JsValue = get_texture_fn
1950 .call0(self.get_context())
1951 .unwrap_or(JsValue::UNDEFINED);
1952 let create_view_fn: Function =
1953 Reflect::get(&texture, &JsValue::from_str(WEBGPU_METHOD_CREATE_VIEW))
1954 .unwrap_or(JsValue::UNDEFINED)
1955 .unchecked_into();
1956 create_view_fn.call0(&texture).unwrap_or(JsValue::UNDEFINED)
1957 }
1958
1959 pub(crate) fn begin_render_pass(
1975 &self,
1976 encoder: &JsValue,
1977 clear_color: (f64, f64, f64, f64),
1978 ) -> JsValue {
1979 let view: JsValue = self.get_current_texture_view();
1980 let color_dict: Object = Object::new();
1981 let _ = Reflect::set(
1982 &color_dict,
1983 &JsValue::from_str(WEBGPU_PROPERTY_R),
1984 &JsValue::from_f64(clear_color.0),
1985 );
1986 let _ = Reflect::set(
1987 &color_dict,
1988 &JsValue::from_str(WEBGPU_PROPERTY_G),
1989 &JsValue::from_f64(clear_color.1),
1990 );
1991 let _ = Reflect::set(
1992 &color_dict,
1993 &JsValue::from_str(WEBGPU_PROPERTY_B),
1994 &JsValue::from_f64(clear_color.2),
1995 );
1996 let _ = Reflect::set(
1997 &color_dict,
1998 &JsValue::from_str(WEBGPU_PROPERTY_A),
1999 &JsValue::from_f64(clear_color.3),
2000 );
2001 let attachment: Object = Object::new();
2002 let _ = Reflect::set(&attachment, &JsValue::from_str(WEBGPU_PROPERTY_VIEW), &view);
2003 let _ = Reflect::set(
2004 &attachment,
2005 &JsValue::from_str(WEBGPU_PROPERTY_LOAD_OP),
2006 &JsValue::from_str(WEBGPU_LOAD_OP_CLEAR),
2007 );
2008 let _ = Reflect::set(
2009 &attachment,
2010 &JsValue::from_str(WEBGPU_PROPERTY_STORE_OP),
2011 &JsValue::from_str(WEBGPU_STORE_OP_STORE),
2012 );
2013 let _ = Reflect::set(
2014 &attachment,
2015 &JsValue::from_str(WEBGPU_PROPERTY_CLEAR_VALUE),
2016 &color_dict,
2017 );
2018 let color_attachments: Array = Array::new();
2019 color_attachments.push(&attachment);
2020 let descriptor: Object = Object::new();
2021 let _ = Reflect::set(
2022 &descriptor,
2023 &JsValue::from_str(WEBGPU_PROPERTY_COLOR_ATTACHMENTS),
2024 &color_attachments,
2025 );
2026 let begin_fn: Function =
2027 Reflect::get(encoder, &JsValue::from_str(WEBGPU_METHOD_BEGIN_RENDER_PASS))
2028 .unwrap_or(JsValue::UNDEFINED)
2029 .unchecked_into();
2030 begin_fn
2031 .call1(encoder, &descriptor)
2032 .unwrap_or(JsValue::UNDEFINED)
2033 }
2034
2035 pub(crate) fn submit(&self, command_buffers: &[JsValue]) {
2041 let array: Array = Array::new();
2042 for buffer in command_buffers {
2043 array.push(buffer);
2044 }
2045 let submit_fn: Function =
2046 Reflect::get(self.get_queue(), &JsValue::from_str(WEBGPU_METHOD_SUBMIT))
2047 .unwrap_or(JsValue::UNDEFINED)
2048 .unchecked_into();
2049 let _ = submit_fn.call1(self.get_queue(), &array);
2050 }
2051
2052 pub fn create_render_pipeline<S>(&self, shader_code: S) -> JsValue
2068 where
2069 S: AsRef<str>,
2070 {
2071 let module: JsValue = self.create_shader_module(shader_code);
2072 let vertex_state: Object = Object::new();
2073 let _ = Reflect::set(
2074 &vertex_state,
2075 &JsValue::from_str(WEBGPU_PROPERTY_MODULE),
2076 &module,
2077 );
2078 let _ = Reflect::set(
2079 &vertex_state,
2080 &JsValue::from_str(WEBGPU_PROPERTY_ENTRY_POINT),
2081 &JsValue::from_str(WEBGPU_VERTEX_ENTRY_POINT),
2082 );
2083 let _ = Reflect::set(
2084 &vertex_state,
2085 &JsValue::from_str(WEBGPU_PROPERTY_BUFFERS),
2086 &Array::new(),
2087 );
2088 let target: Object = Object::new();
2089 let _ = Reflect::set(
2090 &target,
2091 &JsValue::from_str(WEBGPU_PROPERTY_FORMAT),
2092 &JsValue::from_str(&self.get_format()),
2093 );
2094 let targets: Array = Array::new();
2095 targets.push(&target);
2096 let fragment_state: Object = Object::new();
2097 let _ = Reflect::set(
2098 &fragment_state,
2099 &JsValue::from_str(WEBGPU_PROPERTY_MODULE),
2100 &module,
2101 );
2102 let _ = Reflect::set(
2103 &fragment_state,
2104 &JsValue::from_str(WEBGPU_PROPERTY_ENTRY_POINT),
2105 &JsValue::from_str(WEBGPU_FRAGMENT_ENTRY_POINT),
2106 );
2107 let _ = Reflect::set(
2108 &fragment_state,
2109 &JsValue::from_str(WEBGPU_PROPERTY_TARGETS),
2110 &targets,
2111 );
2112 let primitive: Object = Object::new();
2113 let _ = Reflect::set(
2114 &primitive,
2115 &JsValue::from_str(WEBGPU_PROPERTY_TOPOLOGY),
2116 &JsValue::from_str(WEBGPU_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST),
2117 );
2118 let descriptor: Object = Object::new();
2119 let _ = Reflect::set(
2120 &descriptor,
2121 &JsValue::from_str(WEBGPU_PROPERTY_LAYOUT),
2122 &JsValue::null(),
2123 );
2124 let _ = Reflect::set(
2125 &descriptor,
2126 &JsValue::from_str(WEBGPU_PROPERTY_VERTEX),
2127 &vertex_state,
2128 );
2129 let _ = Reflect::set(
2130 &descriptor,
2131 &JsValue::from_str(WEBGPU_PROPERTY_FRAGMENT),
2132 &fragment_state,
2133 );
2134 let _ = Reflect::set(
2135 &descriptor,
2136 &JsValue::from_str(WEBGPU_PROPERTY_PRIMITIVE),
2137 &primitive,
2138 );
2139 let create_fn: Function = Reflect::get(
2140 self.get_device(),
2141 &JsValue::from_str(WEBGPU_METHOD_CREATE_RENDER_PIPELINE),
2142 )
2143 .unwrap_or(JsValue::UNDEFINED)
2144 .unchecked_into();
2145 create_fn
2146 .call1(self.get_device(), &descriptor)
2147 .unwrap_or(JsValue::UNDEFINED)
2148 }
2149
2150 pub(crate) fn set_pipeline(&self, pass: &JsValue, pipeline: &JsValue) {
2157 let set_fn: Function = Reflect::get(pass, &JsValue::from_str(WEBGPU_METHOD_SET_PIPELINE))
2158 .unwrap_or(JsValue::UNDEFINED)
2159 .unchecked_into();
2160 let _ = set_fn.call1(pass, pipeline);
2161 }
2162
2163 pub(crate) fn draw(&self, pass: &JsValue, vertex_count: u32, instance_count: u32) {
2171 let draw_fn: Function = Reflect::get(pass, &JsValue::from_str(WEBGPU_METHOD_DRAW))
2172 .unwrap_or(JsValue::UNDEFINED)
2173 .unchecked_into();
2174 let _ = draw_fn.call2(
2175 pass,
2176 &JsValue::from_f64(f64::from(vertex_count)),
2177 &JsValue::from_f64(f64::from(instance_count)),
2178 );
2179 }
2180
2181 pub(crate) fn end_render_pass(&self, pass: &JsValue) {
2187 let end_fn: Function = Reflect::get(pass, &JsValue::from_str(WEBGPU_METHOD_END))
2188 .unwrap_or(JsValue::UNDEFINED)
2189 .unchecked_into();
2190 let _ = end_fn.call0(pass);
2191 }
2192
2193 pub(crate) fn finish_command_encoder(&self, encoder: &JsValue) -> JsValue {
2203 let finish_fn: Function = Reflect::get(encoder, &JsValue::from_str(WEBGPU_METHOD_FINISH))
2204 .unwrap_or(JsValue::UNDEFINED)
2205 .unchecked_into();
2206 finish_fn.call0(encoder).unwrap_or(JsValue::UNDEFINED)
2207 }
2208
2209 pub fn render_frame(
2222 &self,
2223 pipeline: &JsValue,
2224 clear_color: (f64, f64, f64, f64),
2225 vertex_count: u32,
2226 ) {
2227 let encoder: JsValue = self.create_command_encoder();
2228 let pass: JsValue = self.begin_render_pass(&encoder, clear_color);
2229 self.set_pipeline(&pass, pipeline);
2230 self.draw(&pass, vertex_count, 1);
2231 self.end_render_pass(&pass);
2232 let command_buffer: JsValue = self.finish_command_encoder(&encoder);
2233 self.submit(&[command_buffer]);
2234 }
2235}
2236
2237impl WebGpuInitError {
2243 pub fn code(&self) -> &'static str {
2252 match self {
2253 Self::NavigatorLookup(_) => "WEBGPU_NAVIGATOR_LOOKUP",
2254 Self::NavigatorGpuMissing => "WEBGPU_NAVIGATOR_GPU_MISSING",
2255 Self::RequestAdapterLookup(_) => "WEBGPU_REQUEST_ADAPTER_LOOKUP",
2256 Self::RequestAdapterCall(_) => "WEBGPU_REQUEST_ADAPTER_CALL",
2257 Self::AdapterPromise(_) => "WEBGPU_ADAPTER_PROMISE",
2258 Self::AdapterUnavailable => "WEBGPU_ADAPTER_UNAVAILABLE",
2259 Self::RequestDeviceLookup(_) => "WEBGPU_REQUEST_DEVICE_LOOKUP",
2260 Self::RequestDeviceCall(_) => "WEBGPU_REQUEST_DEVICE_CALL",
2261 Self::DevicePromise(_) => "WEBGPU_DEVICE_PROMISE",
2262 Self::DeviceUnavailable => "WEBGPU_DEVICE_UNAVAILABLE",
2263 Self::CanvasNotFound(_) => "WEBGPU_CANVAS_NOT_FOUND",
2264 Self::CanvasQuery(_) => "WEBGPU_CANVAS_QUERY",
2265 Self::CanvasContextUnavailable => "WEBGPU_CANVAS_CONTEXT_UNAVAILABLE",
2266 Self::PreferredFormatLookup(_) => "WEBGPU_PREFERRED_FORMAT_LOOKUP",
2267 Self::PreferredFormatCall(_) => "WEBGPU_PREFERRED_FORMAT_CALL",
2268 Self::PreferredFormatType(_) => "WEBGPU_PREFERRED_FORMAT_TYPE",
2269 Self::ConfigureLookup(_) => "WEBGPU_CONFIGURE_LOOKUP",
2270 Self::QueueLookup(_) => "WEBGPU_QUEUE_LOOKUP",
2271 }
2272 }
2273
2274 pub fn js_error(&self) -> Option<&JsValue> {
2284 match self {
2285 Self::NavigatorLookup(err)
2286 | Self::RequestAdapterLookup(err)
2287 | Self::RequestAdapterCall(err)
2288 | Self::AdapterPromise(err)
2289 | Self::RequestDeviceLookup(err)
2290 | Self::RequestDeviceCall(err)
2291 | Self::DevicePromise(err)
2292 | Self::CanvasQuery(err)
2293 | Self::PreferredFormatLookup(err)
2294 | Self::PreferredFormatCall(err)
2295 | Self::PreferredFormatType(err)
2296 | Self::ConfigureLookup(err)
2297 | Self::QueueLookup(err) => Some(err),
2298 Self::NavigatorGpuMissing
2299 | Self::AdapterUnavailable
2300 | Self::DeviceUnavailable
2301 | Self::CanvasContextUnavailable
2302 | Self::CanvasNotFound(_) => None,
2303 }
2304 }
2305}
2306
2307fn js_error_to_string(value: &JsValue) -> String {
2309 if let Some(s) = value.as_string() {
2310 s
2311 } else if value.is_undefined() {
2312 "<undefined>".to_string()
2313 } else if value.is_null() {
2314 "<null>".to_string()
2315 } else {
2316 format!("{:?}", value)
2317 }
2318}
2319
2320impl std::fmt::Display for WebGpuInitError {
2327 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2328 match self {
2329 Self::NavigatorLookup(err) => write!(
2330 formatter,
2331 "[{}] Reflect::get(navigator, webgpu) failed: {}",
2332 self.code(),
2333 js_error_to_string(err),
2334 ),
2335 Self::NavigatorGpuMissing => write!(
2336 formatter,
2337 "[{}] navigator.gpu is missing - browser does not expose WebGPU on this origin",
2338 self.code(),
2339 ),
2340 Self::RequestAdapterLookup(err) => write!(
2341 formatter,
2342 "[{}] Reflect::get(gpu, requestAdapter) failed: {}",
2343 self.code(),
2344 js_error_to_string(err),
2345 ),
2346 Self::RequestAdapterCall(err) => write!(
2347 formatter,
2348 "[{}] gpu.requestAdapter() threw: {}",
2349 self.code(),
2350 js_error_to_string(err),
2351 ),
2352 Self::AdapterPromise(err) => write!(
2353 formatter,
2354 "[{}] adapter promise rejected or timed out: {}",
2355 self.code(),
2356 js_error_to_string(err),
2357 ),
2358 Self::AdapterUnavailable => write!(
2359 formatter,
2360 "[{}] requestAdapter returned null - no compatible GPU adapter for the requested powerPreference",
2361 self.code(),
2362 ),
2363 Self::RequestDeviceLookup(err) => write!(
2364 formatter,
2365 "[{}] Reflect::get(adapter, requestDevice) failed: {}",
2366 self.code(),
2367 js_error_to_string(err),
2368 ),
2369 Self::RequestDeviceCall(err) => write!(
2370 formatter,
2371 "[{}] adapter.requestDevice() threw: {}",
2372 self.code(),
2373 js_error_to_string(err),
2374 ),
2375 Self::DevicePromise(err) => write!(
2376 formatter,
2377 "[{}] device promise rejected or timed out: {}",
2378 self.code(),
2379 js_error_to_string(err),
2380 ),
2381 Self::DeviceUnavailable => write!(
2382 formatter,
2383 "[{}] requestDevice returned null - adapter could not allocate a device (possibly device-lost)",
2384 self.code(),
2385 ),
2386 Self::CanvasNotFound(selector) => write!(
2387 formatter,
2388 "[{}] canvas element {:?} not found in DOM",
2389 self.code(),
2390 selector,
2391 ),
2392 Self::CanvasQuery(err) => write!(
2393 formatter,
2394 "[{}] querySelector threw: {}",
2395 self.code(),
2396 js_error_to_string(err),
2397 ),
2398 Self::CanvasContextUnavailable => write!(
2399 formatter,
2400 "[{}] canvas.get_context('webgpu') returned null - the canvas may already be using another context type or WebGPU is disabled",
2401 self.code(),
2402 ),
2403 Self::PreferredFormatLookup(err) => write!(
2404 formatter,
2405 "[{}] Reflect::get(gpu, getPreferredCanvasFormat) failed: {}",
2406 self.code(),
2407 js_error_to_string(err),
2408 ),
2409 Self::PreferredFormatCall(err) => write!(
2410 formatter,
2411 "[{}] gpu.getPreferredCanvasFormat() threw: {}",
2412 self.code(),
2413 js_error_to_string(err),
2414 ),
2415 Self::PreferredFormatType(value) => write!(
2416 formatter,
2417 "[{}] getPreferredCanvasFormat returned non-string: {}",
2418 self.code(),
2419 js_error_to_string(value),
2420 ),
2421 Self::ConfigureLookup(err) => write!(
2422 formatter,
2423 "[{}] Reflect::get(context, configure) failed: {}",
2424 self.code(),
2425 js_error_to_string(err),
2426 ),
2427 Self::QueueLookup(err) => write!(
2428 formatter,
2429 "[{}] Reflect::get(device, queue) failed: {}",
2430 self.code(),
2431 js_error_to_string(err),
2432 ),
2433 }
2434 }
2435}
2436
2437impl std::error::Error for WebGpuInitError {}