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.display_context, self.get_quality());
824 self.display_context
825 .clear_rect(0.0, 0.0, self.width, self.height);
826 let _ = self
827 .display_context
828 .draw_image_with_html_canvas_element_and_dw_and_dh(
829 &self.offscreen_canvas,
830 0.0,
831 0.0,
832 self.width,
833 self.height,
834 );
835 }
836
837 pub fn clear(&self) {
839 self.offscreen_context
840 .clear_rect(0.0, 0.0, self.width, self.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.offscreen_context,
854 &JsValue::from_str(RENDERER_PROPERTY_FILL_STYLE),
855 &JsValue::from_str(color.as_ref()),
856 );
857 self.offscreen_context
858 .fill_rect(0.0, 0.0, self.width, self.height);
859 }
860
861 pub fn enable_smoothing(&self) {
866 let quality: RenderQuality = self.get_quality();
867 CanvasRenderer::apply_quality(&self.display_context, quality);
868 CanvasRenderer::apply_quality(&self.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.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.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) -> Option<WebGpuRenderer> {
1390 let window: Window = window().expect("no global window exists");
1391 let navigator: Navigator = window.navigator();
1392 let gpu_result: Result<JsValue, JsValue> =
1393 Reflect::get(navigator.as_ref(), &JsValue::from_str(WEBGPU_CONTEXT_TYPE));
1394 let gpu: JsValue = match gpu_result {
1395 Ok(value) => value,
1396 Err(err) => {
1397 web_sys::console::error_1(&JsValue::from_str(&format!(
1398 "[euv-engine][webgpu] init: Reflect::get(navigator, webgpu) failed: {:?}",
1399 err,
1400 )));
1401 return None;
1402 }
1403 };
1404 if gpu.is_undefined() || gpu.is_null() {
1405 web_sys::console::error_1(&JsValue::from_str(
1406 "[euv-engine][webgpu] init: navigator.gpu is missing - \
1407 browser does not expose WebGPU on this origin",
1408 ));
1409 return None;
1410 }
1411 let adapter_options: Object = Object::new();
1412 let _ = Reflect::set(
1413 &adapter_options,
1414 &JsValue::from_str(WEBGPU_PROPERTY_POWER_PREFERENCE),
1415 &JsValue::from_str(config.power_preference.to_web_sys_string()),
1416 );
1417 let request_adapter_fn: Function =
1418 match Reflect::get(&gpu, &JsValue::from_str(WEBGPU_METHOD_REQUEST_ADAPTER)) {
1419 Ok(value) => value.unchecked_into(),
1420 Err(err) => {
1421 web_sys::console::error_1(&JsValue::from_str(&format!(
1422 "[euv-engine][webgpu] init: Reflect::get(gpu, requestAdapter) failed: {:?}",
1423 err,
1424 )));
1425 return None;
1426 }
1427 };
1428 let adapter_promise: Promise = match request_adapter_fn.call1(&gpu, &adapter_options) {
1429 Ok(value) => value.unchecked_into(),
1430 Err(err) => {
1431 web_sys::console::error_1(&JsValue::from_str(&format!(
1432 "[euv-engine][webgpu] init: gpu.requestAdapter() threw: {:?}",
1433 err,
1434 )));
1435 return None;
1436 }
1437 };
1438 let adapter_value: JsValue =
1439 match JsFuture::from(Self::race_with_timeout(adapter_promise)).await {
1440 Ok(value) => value,
1441 Err(err) => {
1442 web_sys::console::error_1(&JsValue::from_str(&format!(
1443 "[euv-engine][webgpu] init: adapter promise rejected or timed out: {:?}",
1444 err,
1445 )));
1446 return None;
1447 }
1448 };
1449 if adapter_value.is_null() || adapter_value.is_undefined() {
1450 web_sys::console::error_1(&JsValue::from_str(
1451 "[euv-engine][webgpu] init: requestAdapter returned null - \
1452 no compatible GPU adapter for the requested powerPreference",
1453 ));
1454 return None;
1455 }
1456 let device_descriptor: Object = Object::new();
1457 let request_device_fn: Function = match Reflect::get(
1458 &adapter_value,
1459 &JsValue::from_str(WEBGPU_METHOD_REQUEST_DEVICE),
1460 ) {
1461 Ok(value) => value.unchecked_into(),
1462 Err(err) => {
1463 web_sys::console::error_1(&JsValue::from_str(&format!(
1464 "[euv-engine][webgpu] init: Reflect::get(adapter, requestDevice) failed: {:?}",
1465 err,
1466 )));
1467 return None;
1468 }
1469 };
1470 let device_promise: Promise =
1471 match request_device_fn.call1(&adapter_value, &device_descriptor) {
1472 Ok(value) => value.unchecked_into(),
1473 Err(err) => {
1474 web_sys::console::error_1(&JsValue::from_str(&format!(
1475 "[euv-engine][webgpu] init: adapter.requestDevice() threw: {:?}",
1476 err,
1477 )));
1478 return None;
1479 }
1480 };
1481 let device_value: JsValue =
1482 match JsFuture::from(Self::race_with_timeout(device_promise)).await {
1483 Ok(value) => value,
1484 Err(err) => {
1485 web_sys::console::error_1(&JsValue::from_str(&format!(
1486 "[euv-engine][webgpu] init: device promise rejected or timed out: {:?}",
1487 err,
1488 )));
1489 return None;
1490 }
1491 };
1492 if device_value.is_null() || device_value.is_undefined() {
1493 web_sys::console::error_1(&JsValue::from_str(
1494 "[euv-engine][webgpu] init: requestDevice returned null - \
1495 adapter could not allocate a device (possibly device-lost)",
1496 ));
1497 return None;
1498 }
1499 let document: Document = window.document().expect("should have a document");
1500 let element: Element = match document.query_selector(&config.canvas_selector) {
1501 Ok(Some(el)) => el,
1502 Ok(None) => {
1503 web_sys::console::error_1(&JsValue::from_str(&format!(
1504 "[euv-engine][webgpu] init: canvas element {:?} not found in DOM",
1505 &config.canvas_selector,
1506 )));
1507 return None;
1508 }
1509 Err(err) => {
1510 web_sys::console::error_1(&JsValue::from_str(&format!(
1511 "[euv-engine][webgpu] init: querySelector threw: {:?}",
1512 err,
1513 )));
1514 return None;
1515 }
1516 };
1517 let canvas: HtmlCanvasElement = element.unchecked_into();
1518 let context_object: Option<Object> = canvas.get_context(WEBGPU_CONTEXT_TYPE).ok().flatten();
1519 let context_object: Object = match context_object {
1520 Some(c) => c,
1521 None => {
1522 web_sys::console::error_1(&JsValue::from_str(
1523 "[euv-engine][webgpu] init: canvas.get_context('webgpu') returned null - \
1524 the canvas may already be using another context type or WebGPU is disabled",
1525 ));
1526 return None;
1527 }
1528 };
1529 let context: JsValue = context_object.into();
1530 let get_format_fn: Function = match Reflect::get(
1531 &gpu,
1532 &JsValue::from_str(WEBGPU_METHOD_GET_PREFERRED_FORMAT),
1533 ) {
1534 Ok(value) => value.unchecked_into(),
1535 Err(err) => {
1536 web_sys::console::error_1(&JsValue::from_str(&format!(
1537 "[euv-engine][webgpu] init: Reflect::get(gpu, getPreferredCanvasFormat) failed: {:?}",
1538 err,
1539 )));
1540 return None;
1541 }
1542 };
1543 let format_value: JsValue = match get_format_fn.call0(&gpu) {
1544 Ok(value) => value,
1545 Err(err) => {
1546 web_sys::console::error_1(&JsValue::from_str(&format!(
1547 "[euv-engine][webgpu] init: gpu.getPreferredCanvasFormat() threw: {:?}",
1548 err,
1549 )));
1550 return None;
1551 }
1552 };
1553 let format: String = match format_value.as_string() {
1554 Some(s) => s,
1555 None => {
1556 web_sys::console::error_1(&JsValue::from_str(&format!(
1557 "[euv-engine][webgpu] init: getPreferredCanvasFormat returned non-string: {:?}",
1558 format_value,
1559 )));
1560 return None;
1561 }
1562 };
1563 let dpr: f64 = CanvasRenderer::detect_dpr();
1567 let physical_width: u32 = (config.width * dpr).round() as u32;
1568 let physical_height: u32 = (config.height * dpr).round() as u32;
1569 canvas.set_width(physical_width);
1570 canvas.set_height(physical_height);
1571 let canvas_config: Object = Object::new();
1572 let _ = Reflect::set(
1573 &canvas_config,
1574 &JsValue::from_str(WEBGPU_PROPERTY_DEVICE),
1575 &device_value,
1576 );
1577 let _ = Reflect::set(
1578 &canvas_config,
1579 &JsValue::from_str(WEBGPU_PROPERTY_FORMAT),
1580 &format_value,
1581 );
1582 let configure_fn: Function =
1583 match Reflect::get(&context, &JsValue::from_str(WEBGPU_METHOD_CONFIGURE)) {
1584 Ok(value) => value.unchecked_into(),
1585 Err(err) => {
1586 web_sys::console::error_1(&JsValue::from_str(&format!(
1587 "[euv-engine][webgpu] init: Reflect::get(context, configure) failed: {:?}",
1588 err,
1589 )));
1590 return None;
1591 }
1592 };
1593 let _ = configure_fn.call1(&context, &canvas_config);
1594 let queue: JsValue =
1595 match Reflect::get(&device_value, &JsValue::from_str(WEBGPU_PROPERTY_QUEUE)) {
1596 Ok(value) => value,
1597 Err(err) => {
1598 web_sys::console::error_1(&JsValue::from_str(&format!(
1599 "[euv-engine][webgpu] init: Reflect::get(device, queue) failed: {:?}",
1600 err,
1601 )));
1602 return None;
1603 }
1604 };
1605 Some(WebGpuRenderer {
1606 device: device_value,
1607 queue,
1608 context,
1609 canvas,
1610 format,
1611 width: physical_width,
1612 height: physical_height,
1613 antialias: config.antialias,
1614 })
1615 }
1616
1617 pub fn resize(&mut self, physical_width: u32, physical_height: u32) -> bool {
1642 if self.canvas.is_null() || self.context.is_null() || self.device.is_undefined() {
1643 return false;
1644 }
1645 self.canvas.set_width(physical_width);
1646 self.canvas.set_height(physical_height);
1647 let format_value: JsValue = JsValue::from_str(&self.format);
1648 let canvas_config: Object = Object::new();
1649 let _ = Reflect::set(
1650 &canvas_config,
1651 &JsValue::from_str(WEBGPU_PROPERTY_DEVICE),
1652 &self.device,
1653 );
1654 let _ = Reflect::set(
1655 &canvas_config,
1656 &JsValue::from_str(WEBGPU_PROPERTY_FORMAT),
1657 &format_value,
1658 );
1659 let configure_fn: Function =
1660 Reflect::get(&self.context, &JsValue::from_str(WEBGPU_METHOD_CONFIGURE))
1661 .ok()
1662 .and_then(|value: JsValue| value.dyn_into::<Function>().ok())
1663 .unwrap_or_else(|| Function::new_no_args(""));
1664 if configure_fn.call1(&self.context, &canvas_config).is_err() {
1665 return false;
1666 }
1667 self.width = physical_width;
1668 self.height = physical_height;
1669 true
1670 }
1671
1672 pub(crate) fn create_shader_module<S>(&self, code: S) -> JsValue
1682 where
1683 S: AsRef<str>,
1684 {
1685 let descriptor: Object = Object::new();
1686 let _ = Reflect::set(
1687 &descriptor,
1688 &JsValue::from_str(WEBGPU_PROPERTY_CODE),
1689 &JsValue::from_str(code.as_ref()),
1690 );
1691 let create_fn: Function = Reflect::get(
1692 self.get_device(),
1693 &JsValue::from_str(WEBGPU_METHOD_CREATE_SHADER_MODULE),
1694 )
1695 .unwrap_or(JsValue::UNDEFINED)
1696 .unchecked_into();
1697 create_fn
1698 .call1(self.get_device(), &descriptor)
1699 .unwrap_or(JsValue::UNDEFINED)
1700 }
1701
1702 pub(crate) fn create_command_encoder(&self) -> JsValue {
1708 let create_fn: Function = Reflect::get(
1709 self.get_device(),
1710 &JsValue::from_str(WEBGPU_METHOD_CREATE_COMMAND_ENCODER),
1711 )
1712 .unwrap_or(JsValue::UNDEFINED)
1713 .unchecked_into();
1714 create_fn
1715 .call0(self.get_device())
1716 .unwrap_or(JsValue::UNDEFINED)
1717 }
1718
1719 pub(crate) fn get_current_texture_view(&self) -> JsValue {
1729 let get_texture_fn: Function = Reflect::get(
1730 self.get_context(),
1731 &JsValue::from_str(WEBGPU_METHOD_GET_CURRENT_TEXTURE),
1732 )
1733 .unwrap_or(JsValue::UNDEFINED)
1734 .unchecked_into();
1735 let texture: JsValue = get_texture_fn
1736 .call0(self.get_context())
1737 .unwrap_or(JsValue::UNDEFINED);
1738 let create_view_fn: Function =
1739 Reflect::get(&texture, &JsValue::from_str(WEBGPU_METHOD_CREATE_VIEW))
1740 .unwrap_or(JsValue::UNDEFINED)
1741 .unchecked_into();
1742 create_view_fn.call0(&texture).unwrap_or(JsValue::UNDEFINED)
1743 }
1744
1745 pub(crate) fn begin_render_pass(
1761 &self,
1762 encoder: &JsValue,
1763 clear_color: (f64, f64, f64, f64),
1764 ) -> JsValue {
1765 let view: JsValue = self.get_current_texture_view();
1766 let color_dict: Object = Object::new();
1767 let _ = Reflect::set(
1768 &color_dict,
1769 &JsValue::from_str(WEBGPU_PROPERTY_R),
1770 &JsValue::from_f64(clear_color.0),
1771 );
1772 let _ = Reflect::set(
1773 &color_dict,
1774 &JsValue::from_str(WEBGPU_PROPERTY_G),
1775 &JsValue::from_f64(clear_color.1),
1776 );
1777 let _ = Reflect::set(
1778 &color_dict,
1779 &JsValue::from_str(WEBGPU_PROPERTY_B),
1780 &JsValue::from_f64(clear_color.2),
1781 );
1782 let _ = Reflect::set(
1783 &color_dict,
1784 &JsValue::from_str(WEBGPU_PROPERTY_A),
1785 &JsValue::from_f64(clear_color.3),
1786 );
1787 let attachment: Object = Object::new();
1788 let _ = Reflect::set(&attachment, &JsValue::from_str(WEBGPU_PROPERTY_VIEW), &view);
1789 let _ = Reflect::set(
1790 &attachment,
1791 &JsValue::from_str(WEBGPU_PROPERTY_LOAD_OP),
1792 &JsValue::from_str(WEBGPU_LOAD_OP_CLEAR),
1793 );
1794 let _ = Reflect::set(
1795 &attachment,
1796 &JsValue::from_str(WEBGPU_PROPERTY_STORE_OP),
1797 &JsValue::from_str(WEBGPU_STORE_OP_STORE),
1798 );
1799 let _ = Reflect::set(
1800 &attachment,
1801 &JsValue::from_str(WEBGPU_PROPERTY_CLEAR_VALUE),
1802 &color_dict,
1803 );
1804 let color_attachments: Array = Array::new();
1805 color_attachments.push(&attachment);
1806 let descriptor: Object = Object::new();
1807 let _ = Reflect::set(
1808 &descriptor,
1809 &JsValue::from_str(WEBGPU_PROPERTY_COLOR_ATTACHMENTS),
1810 &color_attachments,
1811 );
1812 let begin_fn: Function =
1813 Reflect::get(encoder, &JsValue::from_str(WEBGPU_METHOD_BEGIN_RENDER_PASS))
1814 .unwrap_or(JsValue::UNDEFINED)
1815 .unchecked_into();
1816 begin_fn
1817 .call1(encoder, &descriptor)
1818 .unwrap_or(JsValue::UNDEFINED)
1819 }
1820
1821 pub(crate) fn submit(&self, command_buffers: &[JsValue]) {
1827 let array: Array = Array::new();
1828 for buffer in command_buffers {
1829 array.push(buffer);
1830 }
1831 let submit_fn: Function =
1832 Reflect::get(self.get_queue(), &JsValue::from_str(WEBGPU_METHOD_SUBMIT))
1833 .unwrap_or(JsValue::UNDEFINED)
1834 .unchecked_into();
1835 let _ = submit_fn.call1(self.get_queue(), &array);
1836 }
1837
1838 pub fn create_render_pipeline<S>(&self, shader_code: S) -> JsValue
1854 where
1855 S: AsRef<str>,
1856 {
1857 let module: JsValue = self.create_shader_module(shader_code);
1858 let vertex_state: Object = Object::new();
1859 let _ = Reflect::set(
1860 &vertex_state,
1861 &JsValue::from_str(WEBGPU_PROPERTY_MODULE),
1862 &module,
1863 );
1864 let _ = Reflect::set(
1865 &vertex_state,
1866 &JsValue::from_str(WEBGPU_PROPERTY_ENTRY_POINT),
1867 &JsValue::from_str(WEBGPU_VERTEX_ENTRY_POINT),
1868 );
1869 let _ = Reflect::set(
1870 &vertex_state,
1871 &JsValue::from_str(WEBGPU_PROPERTY_BUFFERS),
1872 &Array::new(),
1873 );
1874 let target: Object = Object::new();
1875 let _ = Reflect::set(
1876 &target,
1877 &JsValue::from_str(WEBGPU_PROPERTY_FORMAT),
1878 &JsValue::from_str(&self.format),
1879 );
1880 let targets: Array = Array::new();
1881 targets.push(&target);
1882 let fragment_state: Object = Object::new();
1883 let _ = Reflect::set(
1884 &fragment_state,
1885 &JsValue::from_str(WEBGPU_PROPERTY_MODULE),
1886 &module,
1887 );
1888 let _ = Reflect::set(
1889 &fragment_state,
1890 &JsValue::from_str(WEBGPU_PROPERTY_ENTRY_POINT),
1891 &JsValue::from_str(WEBGPU_FRAGMENT_ENTRY_POINT),
1892 );
1893 let _ = Reflect::set(
1894 &fragment_state,
1895 &JsValue::from_str(WEBGPU_PROPERTY_TARGETS),
1896 &targets,
1897 );
1898 let primitive: Object = Object::new();
1899 let _ = Reflect::set(
1900 &primitive,
1901 &JsValue::from_str(WEBGPU_PROPERTY_TOPOLOGY),
1902 &JsValue::from_str(WEBGPU_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST),
1903 );
1904 let descriptor: Object = Object::new();
1905 let _ = Reflect::set(
1906 &descriptor,
1907 &JsValue::from_str(WEBGPU_PROPERTY_LAYOUT),
1908 &JsValue::null(),
1909 );
1910 let _ = Reflect::set(
1911 &descriptor,
1912 &JsValue::from_str(WEBGPU_PROPERTY_VERTEX),
1913 &vertex_state,
1914 );
1915 let _ = Reflect::set(
1916 &descriptor,
1917 &JsValue::from_str(WEBGPU_PROPERTY_FRAGMENT),
1918 &fragment_state,
1919 );
1920 let _ = Reflect::set(
1921 &descriptor,
1922 &JsValue::from_str(WEBGPU_PROPERTY_PRIMITIVE),
1923 &primitive,
1924 );
1925 let create_fn: Function = Reflect::get(
1926 self.get_device(),
1927 &JsValue::from_str(WEBGPU_METHOD_CREATE_RENDER_PIPELINE),
1928 )
1929 .unwrap_or(JsValue::UNDEFINED)
1930 .unchecked_into();
1931 create_fn
1932 .call1(self.get_device(), &descriptor)
1933 .unwrap_or(JsValue::UNDEFINED)
1934 }
1935
1936 pub(crate) fn set_pipeline(&self, pass: &JsValue, pipeline: &JsValue) {
1943 let set_fn: Function = Reflect::get(pass, &JsValue::from_str(WEBGPU_METHOD_SET_PIPELINE))
1944 .unwrap_or(JsValue::UNDEFINED)
1945 .unchecked_into();
1946 let _ = set_fn.call1(pass, pipeline);
1947 }
1948
1949 pub(crate) fn draw(&self, pass: &JsValue, vertex_count: u32, instance_count: u32) {
1957 let draw_fn: Function = Reflect::get(pass, &JsValue::from_str(WEBGPU_METHOD_DRAW))
1958 .unwrap_or(JsValue::UNDEFINED)
1959 .unchecked_into();
1960 let _ = draw_fn.call2(
1961 pass,
1962 &JsValue::from_f64(f64::from(vertex_count)),
1963 &JsValue::from_f64(f64::from(instance_count)),
1964 );
1965 }
1966
1967 pub(crate) fn end_render_pass(&self, pass: &JsValue) {
1973 let end_fn: Function = Reflect::get(pass, &JsValue::from_str(WEBGPU_METHOD_END))
1974 .unwrap_or(JsValue::UNDEFINED)
1975 .unchecked_into();
1976 let _ = end_fn.call0(pass);
1977 }
1978
1979 pub(crate) fn finish_command_encoder(&self, encoder: &JsValue) -> JsValue {
1989 let finish_fn: Function = Reflect::get(encoder, &JsValue::from_str(WEBGPU_METHOD_FINISH))
1990 .unwrap_or(JsValue::UNDEFINED)
1991 .unchecked_into();
1992 finish_fn.call0(encoder).unwrap_or(JsValue::UNDEFINED)
1993 }
1994
1995 pub fn render_frame(
2008 &self,
2009 pipeline: &JsValue,
2010 clear_color: (f64, f64, f64, f64),
2011 vertex_count: u32,
2012 ) {
2013 let encoder: JsValue = self.create_command_encoder();
2014 let pass: JsValue = self.begin_render_pass(&encoder, clear_color);
2015 self.set_pipeline(&pass, pipeline);
2016 self.draw(&pass, vertex_count, 1);
2017 self.end_render_pass(&pass);
2018 let command_buffer: JsValue = self.finish_command_encoder(&encoder);
2019 self.submit(&[command_buffer]);
2020 }
2021}