use super::*;
impl Camera2D {
pub fn create(viewport_width: f64, viewport_height: f64) -> Camera2D {
Camera2D::new(
Vector2D::zero(),
RENDERER_DEFAULT_CAMERA_ZOOM,
RENDERER_DEFAULT_CAMERA_ROTATION,
viewport_width,
viewport_height,
)
}
pub fn world_to_screen(&self, world: Vector2D) -> Vector2D {
let relative: Vector2D = world - self.get_position();
let rotated: Vector2D = relative.rotated(-self.get_rotation());
Vector2D::new(
rotated.get_x() * self.get_zoom() + self.get_viewport_width() * 0.5,
rotated.get_y() * self.get_zoom() + self.get_viewport_height() * 0.5,
)
}
pub fn screen_to_world(&self, screen: Vector2D) -> Vector2D {
let relative: Vector2D = Vector2D::new(
(screen.get_x() - self.get_viewport_width() * 0.5) / self.get_zoom(),
(screen.get_y() - self.get_viewport_height() * 0.5) / self.get_zoom(),
);
let rotated: Vector2D = relative.rotated(self.get_rotation());
rotated + self.get_position()
}
pub fn translate(&mut self, offset: Vector2D) {
self.set_position(self.get_position() + offset);
}
pub fn zoom_by(&mut self, factor: f64) {
self.set_zoom((self.get_zoom() * factor).max(EPSILON));
}
}
impl Default for Camera2D {
fn default() -> Camera2D {
Camera2D::create(800.0, 600.0)
}
}
impl CanvasRenderer {
pub fn font<F>(size: f64, family: F) -> String
where
F: AsRef<str>,
{
let family: &str = family.as_ref();
format!("{size}px {family}")
}
pub fn default_font() -> String {
Self::font(RENDERER_DEFAULT_FONT_SIZE, RENDERER_DEFAULT_FONT_FAMILY)
}
pub fn enable_smoothing_on(context: &CanvasRenderingContext2d) {
Self::apply_quality(context, RenderQuality::High);
}
pub fn detect_dpr() -> f64 {
let window_value: Window = window().expect("no global window exists");
let raw: Option<f64> = Reflect::get(
window_value.as_ref(),
&JsValue::from_str(RENDERER_PROPERTY_DEVICE_PIXEL_RATIO),
)
.ok()
.and_then(|value: JsValue| value.as_f64());
raw.filter(|value: &f64| value.is_finite() && *value >= 1.0)
.unwrap_or(RENDERER_DEFAULT_DEVICE_PIXEL_RATIO)
}
pub(crate) fn apply_quality(context: &CanvasRenderingContext2d, quality: RenderQuality) {
let smoothing_enabled: bool = !matches!(quality, RenderQuality::Low);
context.set_image_smoothing_enabled(smoothing_enabled);
let quality_value: &str = match quality {
RenderQuality::Low => RENDERER_IMAGE_SMOOTHING_QUALITY_LOW,
RenderQuality::Medium => RENDERER_IMAGE_SMOOTHING_QUALITY_MEDIUM,
RenderQuality::High => RENDERER_IMAGE_SMOOTHING_QUALITY_HIGH,
};
let _ = Reflect::set(
context,
&JsValue::from_str(RENDERER_PROPERTY_IMAGE_SMOOTHING_QUALITY),
&JsValue::from_str(quality_value),
);
let _ = Reflect::set(
context,
&JsValue::from_str(RENDERER_PROPERTY_TEXT_RENDERING),
&JsValue::from_str(RENDERER_TEXT_RENDERING_GEOMETRIC_PRECISION),
);
}
}
impl Color {
pub fn to_css(color: &Color) -> String {
color.to_css_rgba()
}
}
fn self_commands(list: &DrawList) -> &[DrawCommand] {
list.get_commands().as_slice()
}
fn draw_sprite_immediate(
context: &CanvasRenderingContext2d,
image: &HtmlImageElement,
source: &Rect,
transform: &Transform2D,
) {
let rotation: f64 = transform.get_rotation();
let cos: f64 = rotation.cos();
let sin: f64 = rotation.sin();
let scale_x: f64 = transform.get_scale().get_x();
let scale_y: f64 = transform.get_scale().get_y();
let _ = context.set_transform(
cos * scale_x,
sin * scale_x,
-sin * scale_y,
cos * scale_y,
transform.get_position().get_x(),
transform.get_position().get_y(),
);
let _ = context.draw_image_with_html_image_element_and_sw_and_sh_and_dx_and_dy_and_dw_and_dh(
image,
source.get_x(),
source.get_y(),
source.get_width(),
source.get_height(),
-source.get_width() * 0.5,
-source.get_height() * 0.5,
source.get_width(),
source.get_height(),
);
let _ = context.set_transform(1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
}
impl DrawList {
pub fn create() -> DrawList {
DrawList::new(Vec::new())
}
pub fn is_empty(&self) -> bool {
self.get_commands().is_empty()
}
pub fn len(&self) -> usize {
self.get_commands().len()
}
pub fn clear(&mut self) {
self.get_mut_commands().clear();
}
pub fn fill_rect(&mut self, position: Vector2D, width: f64, height: f64, color: Color) {
self.get_mut_commands().push(DrawCommand::FillRect {
position,
width,
height,
color,
});
}
pub fn stroke_rect(
&mut self,
position: Vector2D,
width: f64,
height: f64,
color: Color,
line_width: f64,
) {
self.get_mut_commands().push(DrawCommand::StrokeRect {
position,
width,
height,
color,
line_width,
});
}
pub fn fill_circle(&mut self, center: Vector2D, radius: f64, color: Color) {
self.get_mut_commands().push(DrawCommand::FillCircle {
center,
radius,
color,
});
}
pub fn stroke_circle(&mut self, center: Vector2D, radius: f64, color: Color, line_width: f64) {
self.get_mut_commands().push(DrawCommand::StrokeCircle {
center,
radius,
color,
line_width,
});
}
pub fn draw_line(&mut self, start: Vector2D, end: Vector2D, color: Color, line_width: f64) {
self.get_mut_commands().push(DrawCommand::Line {
start,
end,
color,
line_width,
});
}
pub fn fill_text<T, F>(&mut self, text: T, position: Vector2D, color: Color, font: F)
where
T: AsRef<str>,
F: AsRef<str>,
{
self.get_mut_commands().push(DrawCommand::FillText {
text: text.as_ref().to_string(),
position,
color,
font: font.as_ref().to_string(),
});
}
pub fn draw_sprite(&mut self, image: &HtmlImageElement, source: Rect, transform: Transform2D) {
self.get_mut_commands().push(DrawCommand::DrawSprite {
image: image.clone(),
source,
transform,
});
}
pub fn draw_image_rect(
&mut self,
image: &HtmlImageElement,
source: Rect,
dest_position: Vector2D,
dest_width: f64,
dest_height: f64,
) {
self.get_mut_commands().push(DrawCommand::DrawImageRect {
image: image.clone(),
source,
dest_position,
dest_width,
dest_height,
});
}
pub fn set_global_alpha(&mut self, alpha: f64) {
self.get_mut_commands()
.push(DrawCommand::SetGlobalAlpha { alpha });
}
pub fn set_blend_mode(&mut self, mode: BlendMode) {
self.get_mut_commands()
.push(DrawCommand::SetBlendMode { mode });
}
}
impl CanvasRenderer {
pub fn from_selector<S>(
canvas_selector: S,
viewport_width: f64,
viewport_height: f64,
) -> Option<CanvasRenderer>
where
S: AsRef<str>,
{
let window_value: Window = window().expect("no global window exists");
let document_value: Document = window_value.document().expect("should have a document");
let element: Element = document_value
.query_selector(canvas_selector.as_ref())
.ok()
.flatten()?;
let canvas_element: HtmlCanvasElement = element.unchecked_into();
let context_object: Object = canvas_element
.get_context(RENDERER_CONTEXT_TYPE_2D)
.ok()
.flatten()?;
let context: CanvasRenderingContext2d = context_object.unchecked_into();
let renderer: CanvasRenderer = CanvasRenderer::new(
context,
Camera2D::create(viewport_width, viewport_height),
RenderQuality::default(),
);
renderer.enable_smoothing();
Some(renderer)
}
pub fn enable_smoothing(&self) {
Self::apply_quality(self.get_context(), self.get_quality());
}
pub fn clear(&self) {
self.get_context().clear_rect(
0.0,
0.0,
self.get_camera().get_viewport_width(),
self.get_camera().get_viewport_height(),
);
}
pub fn clear_color<C>(&self, color: C)
where
C: AsRef<str>,
{
self.get_context().set_fill_style_str(color.as_ref());
self.get_context().fill_rect(
0.0,
0.0,
self.get_camera().get_viewport_width(),
self.get_camera().get_viewport_height(),
);
}
pub fn save(&self) {
self.get_context().save();
}
pub fn restore(&self) {
self.get_context().restore();
}
pub fn replay(&self, list: &DrawList) {
Self::replay_context(self.get_context(), list);
}
pub fn replay_context(context: &CanvasRenderingContext2d, list: &DrawList) {
let mut current_fill: Option<Color> = None;
let mut current_stroke: Option<Color> = None;
let mut current_line_width: f64 = f64::NAN;
let mut run_open: bool = false;
let mut run_is_fill: bool = true;
let mut run_key: Option<(u8, Color, f64)> = None;
fn batch_key(command: &DrawCommand) -> Option<(u8, Color, f64)> {
match command {
DrawCommand::FillRect { color, .. } | DrawCommand::FillCircle { color, .. } => {
Some((0, *color, 0.0))
}
DrawCommand::StrokeRect {
color, line_width, ..
}
| DrawCommand::StrokeCircle {
color, line_width, ..
}
| DrawCommand::Line {
color, line_width, ..
} => Some((1, *color, *line_width)),
_ => None,
}
}
fn emit_geometry(context: &CanvasRenderingContext2d, command: &DrawCommand) {
match command {
DrawCommand::FillRect {
position,
width,
height,
..
}
| DrawCommand::StrokeRect {
position,
width,
height,
..
} => {
context.rect(position.get_x(), position.get_y(), *width, *height);
}
DrawCommand::FillCircle { center, radius, .. }
| DrawCommand::StrokeCircle { center, radius, .. } => {
context.move_to(center.get_x() + radius, center.get_y());
let _ = context.arc(center.get_x(), center.get_y(), *radius, 0.0, TWO_PI);
}
DrawCommand::Line { start, end, .. } => {
context.move_to(start.get_x(), start.get_y());
context.line_to(end.get_x(), end.get_y());
}
_ => {}
}
}
for command in self_commands(list) {
let key: Option<(u8, Color, f64)> = batch_key(command);
if run_open && key != run_key {
if run_is_fill {
context.fill();
} else {
context.stroke();
}
run_open = false;
}
if let Some(current_key) = key {
if !run_open {
let (kind, color, line_width) = current_key;
if kind == 0 {
if current_fill != Some(color) {
context.set_fill_style_str(&Color::to_css(&color));
current_fill = Some(color);
}
run_is_fill = true;
} else {
if current_stroke != Some(color) {
context.set_stroke_style_str(&Color::to_css(&color));
current_stroke = Some(color);
}
if current_line_width != line_width {
context.set_line_width(line_width);
current_line_width = line_width;
}
run_is_fill = false;
}
context.begin_path();
run_open = true;
run_key = Some(current_key);
}
emit_geometry(context, command);
continue;
}
match command {
DrawCommand::FillText {
text,
position,
color,
font,
} => {
if current_fill != Some(*color) {
context.set_fill_style_str(&Color::to_css(color));
current_fill = Some(*color);
}
context.set_font(font);
let _ = context.fill_text(text, position.get_x(), position.get_y());
}
DrawCommand::DrawSprite {
image,
source,
transform,
} => {
draw_sprite_immediate(context, image, source, transform);
}
DrawCommand::DrawImageRect {
image,
source,
dest_position,
dest_width,
dest_height,
} => {
let _ = context
.draw_image_with_html_image_element_and_sw_and_sh_and_dx_and_dy_and_dw_and_dh(
image,
source.get_x(),
source.get_y(),
source.get_width(),
source.get_height(),
dest_position.get_x(),
dest_position.get_y(),
*dest_width,
*dest_height,
);
}
DrawCommand::SetGlobalAlpha { alpha } => {
context.set_global_alpha(Numeric::clamp(*alpha, 0.0, 1.0));
}
DrawCommand::SetBlendMode { mode } => {
let _ = context.set_global_composite_operation(mode.to_css());
}
_ => {}
}
}
if run_open {
if run_is_fill {
context.fill();
} else {
context.stroke();
}
}
let _ = context.set_transform(1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
context.set_global_alpha(1.0);
}
pub fn apply_camera(&self) {
let camera: Camera2D = self.get_camera();
let _ = self.get_context().translate(
camera.get_viewport_width() * 0.5,
camera.get_viewport_height() * 0.5,
);
let _ = self
.get_context()
.scale(camera.get_zoom(), camera.get_zoom());
let _ = self.get_context().rotate(camera.get_rotation());
let _ = self.get_context().translate(
-camera.get_position().get_x(),
-camera.get_position().get_y(),
);
}
pub fn set_fill_color<C>(&self, color: C)
where
C: AsRef<str>,
{
self.get_context().set_fill_style_str(color.as_ref());
}
pub fn set_stroke_color<C>(&self, color: C)
where
C: AsRef<str>,
{
self.get_context().set_stroke_style_str(color.as_ref());
}
pub fn set_line_width(&self, width: f64) {
self.get_context().set_line_width(width);
}
pub fn set_global_alpha(&self, alpha: f64) {
self.get_context()
.set_global_alpha(Numeric::clamp(alpha, 0.0, 1.0));
}
pub fn fill_rect(&self, position: Vector2D, width: f64, height: f64) {
self.get_context()
.fill_rect(position.get_x(), position.get_y(), width, height);
}
pub fn stroke_rect(&self, position: Vector2D, width: f64, height: f64) {
self.get_context()
.stroke_rect(position.get_x(), position.get_y(), width, height);
}
pub fn fill_circle(&self, center: Vector2D, radius: f64) {
self.get_context().begin_path();
self.get_context()
.arc(center.get_x(), center.get_y(), radius, 0.0, TWO_PI)
.unwrap_or(());
self.get_context().fill();
}
pub fn stroke_circle(&self, center: Vector2D, radius: f64) {
self.get_context().begin_path();
self.get_context()
.arc(center.get_x(), center.get_y(), radius, 0.0, TWO_PI)
.unwrap_or(());
self.get_context().stroke();
}
pub fn draw_line(&self, start: Vector2D, end: Vector2D) {
self.get_context().begin_path();
self.get_context().move_to(start.get_x(), start.get_y());
self.get_context().line_to(end.get_x(), end.get_y());
self.get_context().stroke();
}
pub fn fill_text<T>(&self, text: T, position: Vector2D)
where
T: AsRef<str>,
{
self.get_context()
.fill_text(text.as_ref(), position.get_x(), position.get_y())
.unwrap_or(());
}
pub fn set_font<F>(&self, font: F)
where
F: AsRef<str>,
{
self.get_context().set_font(font.as_ref());
}
pub fn draw_image(
&self,
image: &HtmlImageElement,
position: Vector2D,
width: f64,
height: f64,
) {
let _ = self
.get_context()
.draw_image_with_html_image_element_and_dw_and_dh(
image,
position.get_x(),
position.get_y(),
width,
height,
);
}
pub fn draw_image_rect(
&self,
image: &HtmlImageElement,
source: Rect,
dest_position: Vector2D,
dest_width: f64,
dest_height: f64,
) {
let _ = self
.get_context()
.draw_image_with_html_image_element_and_sw_and_sh_and_dx_and_dy_and_dw_and_dh(
image,
source.get_x(),
source.get_y(),
source.get_width(),
source.get_height(),
dest_position.get_x(),
dest_position.get_y(),
dest_width,
dest_height,
);
}
}
impl Camera3D {
pub fn create(
position: Vector3D,
target: Vector3D,
viewport_width: f64,
viewport_height: f64,
) -> Camera3D {
let mut camera: Camera3D = Camera3D::new(position, target, viewport_width, viewport_height);
camera.set_up(Vector3D::up());
camera.set_fov(DEFAULT_CAMERA_FOV);
camera.set_near(DEFAULT_CAMERA_NEAR);
camera.set_far(DEFAULT_CAMERA_FAR);
camera
}
pub fn aspect(&self) -> f64 {
if self.get_viewport_height() < EPSILON {
return 1.0;
}
self.get_viewport_width() / self.get_viewport_height()
}
pub fn forward(&self) -> Vector3D {
(self.get_target() - self.get_position()).normalized()
}
pub fn right(&self) -> Vector3D {
self.forward().cross(self.get_up()).normalized()
}
pub fn view_matrix(&self) -> Matrix4x4 {
Matrix4x4::look_at(self.get_position(), self.get_target(), self.get_up())
}
pub fn projection_matrix(&self) -> Matrix4x4 {
Matrix4x4::perspective(
self.get_fov(),
self.aspect(),
self.get_near(),
self.get_far(),
)
}
pub fn view_proj_matrix(&self) -> Matrix4x4 {
self.projection_matrix().multiply(self.view_matrix())
}
pub fn world_to_screen(&self, world: Vector3D) -> Vector3D {
let clip: Vector3D = self.view_proj_matrix().transform_point(world);
Vector3D::new(
(clip.get_x() + 1.0) * 0.5 * self.get_viewport_width(),
(1.0 - clip.get_y()) * 0.5 * self.get_viewport_height(),
clip.get_z(),
)
}
pub fn in_frustum(&self, world: Vector3D) -> bool {
let clip: Vector3D = self.view_proj_matrix().transform_point(world);
clip.get_x() >= -1.0
&& clip.get_x() <= 1.0
&& clip.get_y() >= -1.0
&& clip.get_y() <= 1.0
&& clip.get_z() >= -1.0
&& clip.get_z() <= 1.0
}
pub fn translate(&mut self, offset: Vector3D) {
self.set_position(self.get_position() + offset);
self.set_target(self.get_target() + offset);
}
pub fn zoom(&mut self, distance: f64) {
let direction: Vector3D = self.forward();
self.set_position(self.get_position() + direction.scaled(distance));
}
pub fn orbit(&mut self, yaw_delta: f64, pitch_delta: f64) {
let offset: Vector3D = self.get_position() - self.get_target();
let current_distance: f64 = offset.magnitude();
let current_yaw: f64 = offset.get_x().atan2(offset.get_z());
let horizontal_dist: f64 =
(offset.get_x() * offset.get_x() + offset.get_z() * offset.get_z()).sqrt();
let current_pitch: f64 = (offset.get_y() / horizontal_dist.max(EPSILON)).asin();
let new_yaw: f64 = current_yaw + yaw_delta;
let new_pitch: f64 = Numeric::clamp(
current_pitch + pitch_delta,
-HALF_PI + EPSILON,
HALF_PI - EPSILON,
);
let cos_pitch: f64 = new_pitch.cos();
self.set_position(
self.get_target()
+ Vector3D::new(
new_yaw.sin() * cos_pitch * current_distance,
new_pitch.sin() * current_distance,
new_yaw.cos() * cos_pitch * current_distance,
),
);
}
}
impl Default for Camera3D {
fn default() -> Camera3D {
Camera3D::create(Vector3D::new(0.0, 0.0, 5.0), Vector3D::zero(), 800.0, 600.0)
}
}
impl SsaaCanvas {
pub fn from_selector<S>(canvas_selector: S, width: f64, height: f64) -> Option<SsaaCanvas>
where
S: AsRef<str>,
{
Self::from_selector_with_scale(
canvas_selector,
width,
height,
RENDERER_DEFAULT_SSAA_SCALE_FACTOR,
)
}
pub fn from_selector_with_scale<S>(
canvas_selector: S,
width: f64,
height: f64,
scale_factor: f64,
) -> Option<SsaaCanvas>
where
S: AsRef<str>,
{
let window_value: Window = window().expect("no global window exists");
let document_value: Document = window_value.document().expect("should have a document");
let element: Element = document_value
.query_selector(canvas_selector.as_ref())
.ok()
.flatten()?;
let display_canvas: HtmlCanvasElement = element.unchecked_into();
let device_pixel_ratio: f64 = CanvasRenderer::detect_dpr();
let physical_width: u32 = (width * device_pixel_ratio).round() as u32;
let physical_height: u32 = (height * device_pixel_ratio).round() as u32;
display_canvas.set_width(physical_width);
display_canvas.set_height(physical_height);
let display_context_object: Object = display_canvas
.get_context(RENDERER_CONTEXT_TYPE_2D)
.ok()
.flatten()?;
let display_context: CanvasRenderingContext2d = display_context_object.unchecked_into();
let _ = display_context.scale(device_pixel_ratio, device_pixel_ratio);
let offscreen_canvas: HtmlCanvasElement = document_value
.create_element(RENDERER_ELEMENT_CANVAS)
.ok()?
.unchecked_into();
let scaled_width: u32 = (width * scale_factor * device_pixel_ratio).round() as u32;
let scaled_height: u32 = (height * scale_factor * device_pixel_ratio).round() as u32;
offscreen_canvas.set_width(scaled_width);
offscreen_canvas.set_height(scaled_height);
let offscreen_context_object: Object = offscreen_canvas
.get_context(RENDERER_CONTEXT_TYPE_2D)
.ok()
.flatten()?;
let offscreen_context: CanvasRenderingContext2d = offscreen_context_object.unchecked_into();
let _ = offscreen_context.scale(
scale_factor * device_pixel_ratio,
scale_factor * device_pixel_ratio,
);
let ssaa_canvas: SsaaCanvas = SsaaCanvas::new(
display_canvas,
display_context,
offscreen_canvas,
offscreen_context,
scale_factor,
width,
height,
);
ssaa_canvas.enable_smoothing();
Some(ssaa_canvas)
}
pub fn present(&self) {
CanvasRenderer::apply_quality(self.get_display_context(), self.get_quality());
self.get_display_context()
.clear_rect(0.0, 0.0, self.get_width(), self.get_height());
let _ = self
.get_display_context()
.draw_image_with_html_canvas_element_and_dw_and_dh(
self.get_offscreen_canvas(),
0.0,
0.0,
self.get_width(),
self.get_height(),
);
}
pub fn clear(&self) {
self.get_offscreen_context()
.clear_rect(0.0, 0.0, self.get_width(), self.get_height());
}
pub fn clear_color<C>(&self, color: C)
where
C: AsRef<str>,
{
self.get_offscreen_context()
.set_fill_style_str(color.as_ref());
self.get_offscreen_context()
.fill_rect(0.0, 0.0, self.get_width(), self.get_height());
}
pub fn enable_smoothing(&self) {
let quality: RenderQuality = self.get_quality();
CanvasRenderer::apply_quality(self.get_display_context(), quality);
CanvasRenderer::apply_quality(self.get_offscreen_context(), quality);
}
}
impl BlendMode {
pub fn to_css(&self) -> &str {
match self {
BlendMode::Normal => BLEND_MODE_NORMAL,
BlendMode::Multiply => BLEND_MODE_MULTIPLY,
BlendMode::Screen => BLEND_MODE_SCREEN,
BlendMode::Lighter => BLEND_MODE_LIGHTER,
BlendMode::Overlay => BLEND_MODE_OVERLAY,
BlendMode::Darken => BLEND_MODE_DARKEN,
BlendMode::Lighten => BLEND_MODE_LIGHTEN,
BlendMode::ColorDodge => BLEND_MODE_COLOR_DODGE,
BlendMode::ColorBurn => BLEND_MODE_COLOR_BURN,
BlendMode::HardLight => BLEND_MODE_HARD_LIGHT,
BlendMode::SoftLight => BLEND_MODE_SOFT_LIGHT,
BlendMode::Difference => BLEND_MODE_DIFFERENCE,
BlendMode::Exclusion => BLEND_MODE_EXCLUSION,
BlendMode::Hue => BLEND_MODE_HUE,
BlendMode::Saturation => BLEND_MODE_SATURATION,
BlendMode::Color => BLEND_MODE_COLOR,
BlendMode::Luminosity => BLEND_MODE_LUMINOSITY,
}
}
}
impl LinearGradient {
pub fn create(start: Vector2D, end: Vector2D, stops: Vec<(f64, String)>) -> LinearGradient {
LinearGradient::new(start, end, stops)
}
pub fn to_gradient(&self, context: &CanvasRenderingContext2d) -> Option<CanvasGradient> {
let canvas_gradient: CanvasGradient = context.create_linear_gradient(
self.get_start().get_x(),
self.get_start().get_y(),
self.get_end().get_x(),
self.get_end().get_y(),
);
for (position, color) in self.get_stops() {
let _ = canvas_gradient.add_color_stop(*position as f32, color);
}
Some(canvas_gradient)
}
}
impl RadialGradient {
pub fn create(
inner_center: Vector2D,
inner_radius: f64,
outer_center: Vector2D,
outer_radius: f64,
stops: Vec<(f64, String)>,
) -> RadialGradient {
RadialGradient::new(
inner_center,
inner_radius,
outer_center,
outer_radius,
stops,
)
}
pub fn to_gradient(&self, context: &CanvasRenderingContext2d) -> Option<CanvasGradient> {
let canvas_gradient: CanvasGradient = context
.create_radial_gradient(
self.get_inner_center().get_x(),
self.get_inner_center().get_y(),
self.get_inner_radius(),
self.get_outer_center().get_x(),
self.get_outer_center().get_y(),
self.get_outer_radius(),
)
.ok()?;
for (position, color) in self.get_stops() {
let _ = canvas_gradient.add_color_stop(*position as f32, color);
}
Some(canvas_gradient)
}
}
impl ShadowConfig {
pub fn create() -> ShadowConfig {
ShadowConfig::new(
RENDERER_DEFAULT_SHADOW_COLOR.to_string(),
RENDERER_DEFAULT_SHADOW_BLUR,
0.0,
0.0,
)
}
}
impl Default for ShadowConfig {
fn default() -> ShadowConfig {
ShadowConfig::create()
}
}
impl RenderLayer {
pub fn create(z_index: i32, visible: bool) -> RenderLayer {
RenderLayer::new(z_index, visible)
}
pub fn background() -> RenderLayer {
RenderLayer::new(RENDERER_LAYER_BACKGROUND, true)
}
pub fn foreground() -> RenderLayer {
RenderLayer::new(RENDERER_LAYER_FOREGROUND, true)
}
pub fn ui() -> RenderLayer {
RenderLayer::new(RENDERER_LAYER_UI, true)
}
}
impl CanvasRenderer {
pub fn set_blend_mode(&self, mode: BlendMode) {
let _ = self
.get_context()
.set_global_composite_operation(mode.to_css());
}
pub fn set_shadow(&self, config: &ShadowConfig) {
self.get_context()
.set_shadow_color(config.get_color().as_str());
self.get_context().set_shadow_blur(config.get_blur());
self.get_context()
.set_shadow_offset_x(config.get_offset_x());
self.get_context()
.set_shadow_offset_y(config.get_offset_y());
}
pub fn clear_shadow(&self) {
self.get_context().set_shadow_color("rgba(0, 0, 0, 0)");
self.get_context().set_shadow_blur(0.0);
self.get_context().set_shadow_offset_x(0.0);
self.get_context().set_shadow_offset_y(0.0);
}
pub fn set_linear_gradient_fill(&self, gradient: &LinearGradient) {
if let Some(canvas_gradient) = gradient.to_gradient(self.get_context()) {
self.get_context()
.set_fill_style_canvas_gradient(&canvas_gradient);
}
}
pub fn set_radial_gradient_fill(&self, gradient: &RadialGradient) {
if let Some(canvas_gradient) = gradient.to_gradient(self.get_context()) {
self.get_context()
.set_fill_style_canvas_gradient(&canvas_gradient);
}
}
pub fn set_linear_gradient_stroke(&self, gradient: &LinearGradient) {
if let Some(canvas_gradient) = gradient.to_gradient(self.get_context()) {
self.get_context()
.set_stroke_style_canvas_gradient(&canvas_gradient);
}
}
pub fn set_radial_gradient_stroke(&self, gradient: &RadialGradient) {
if let Some(canvas_gradient) = gradient.to_gradient(self.get_context()) {
self.get_context()
.set_stroke_style_canvas_gradient(&canvas_gradient);
}
}
}
impl RenderBackend for CanvasRenderer {
fn clear(&self) {
self.clear();
}
fn clear_color<C>(&self, color: C)
where
C: AsRef<str>,
{
self.clear_color(color);
}
fn save(&self) {
self.save();
}
fn restore(&self) {
self.restore();
}
fn set_fill_color(&self, color: &str) {
self.set_fill_color(color);
}
fn set_stroke_color(&self, color: &str) {
self.set_stroke_color(color);
}
fn set_line_width(&self, width: f64) {
self.set_line_width(width);
}
fn set_global_alpha(&self, alpha: f64) {
self.set_global_alpha(alpha);
}
fn set_blend_mode(&self, mode: BlendMode) {
self.set_blend_mode(mode);
}
fn set_shadow(&self, config: &ShadowConfig) {
self.set_shadow(config);
}
fn clear_shadow(&self) {
self.clear_shadow();
}
fn fill_rect(&self, position: Vector2D, width: f64, height: f64) {
self.fill_rect(position, width, height);
}
fn stroke_rect(&self, position: Vector2D, width: f64, height: f64) {
self.stroke_rect(position, width, height);
}
fn fill_circle(&self, center: Vector2D, radius: f64) {
self.fill_circle(center, radius);
}
fn stroke_circle(&self, center: Vector2D, radius: f64) {
self.stroke_circle(center, radius);
}
fn draw_line(&self, start: Vector2D, end: Vector2D) {
self.draw_line(start, end);
}
fn fill_text(&self, text: &str, position: Vector2D) {
self.fill_text(text, position);
}
fn set_font(&self, font: &str) {
self.set_font(font);
}
fn draw_image(&self, image: &HtmlImageElement, position: Vector2D, width: f64, height: f64) {
self.draw_image(image, position, width, height);
}
fn set_linear_gradient_fill(&self, gradient: &LinearGradient) {
self.set_linear_gradient_fill(gradient);
}
fn set_radial_gradient_fill(&self, gradient: &RadialGradient) {
self.set_radial_gradient_fill(gradient);
}
}
impl WebGpuRenderer {
fn timeout_promise() -> Promise {
let window_value: Window = window().expect("no global window exists");
Promise::new(&mut |_resolve: Function, reject: Function| {
let reject_fn: Function = reject.clone();
let timer: Closure<dyn FnMut()> = Closure::wrap(Box::new(move || {
let _ = reject_fn.call1(
&JsValue::UNDEFINED,
&JsValue::from_str(RENDERER_TIMEOUT_ERROR_MESSAGE),
);
}));
let _ = window_value.set_timeout_with_callback_and_timeout_and_arguments_0(
timer.as_ref().unchecked_ref(),
INIT_PROMISE_TIMEOUT_MILLIS,
);
timer.forget();
})
}
fn race_with_timeout(promise: Promise) -> Promise {
let array: Array = Array::of2(&promise, &Self::timeout_promise());
Promise::race(&array)
}
pub async fn init(config: &RenderConfig) -> Result<WebGpuRenderer, WebGpuInitError> {
let window: Window = window().expect("no global window exists");
let navigator: Navigator = window.navigator();
let gpu_result: Result<JsValue, JsValue> =
Reflect::get(navigator.as_ref(), &JsValue::from_str(WEBGPU_CONTEXT_TYPE));
let gpu: JsValue = match gpu_result {
Ok(value) => value,
Err(err) => return Err(WebGpuInitError::NavigatorLookup(err)),
};
if gpu.is_undefined() || gpu.is_null() {
return Err(WebGpuInitError::NavigatorGpuMissing);
}
let adapter_options: Object = Object::new();
let _ = Reflect::set(
&adapter_options,
&JsValue::from_str(WEBGPU_PROPERTY_POWER_PREFERENCE),
&JsValue::from_str(config.power_preference.to_web_sys_string()),
);
let request_adapter_fn: Function =
match Reflect::get(&gpu, &JsValue::from_str(WEBGPU_METHOD_REQUEST_ADAPTER)) {
Ok(value) => value.unchecked_into(),
Err(err) => return Err(WebGpuInitError::RequestAdapterLookup(err)),
};
let adapter_promise: Promise = match request_adapter_fn.call1(&gpu, &adapter_options) {
Ok(value) => value.unchecked_into(),
Err(err) => return Err(WebGpuInitError::RequestAdapterCall(err)),
};
let adapter_value: JsValue =
match JsFuture::from(Self::race_with_timeout(adapter_promise)).await {
Ok(value) => value,
Err(err) => return Err(WebGpuInitError::AdapterPromise(err)),
};
if adapter_value.is_null() || adapter_value.is_undefined() {
return Err(WebGpuInitError::AdapterUnavailable);
}
let device_descriptor: Object = Object::new();
let request_device_fn: Function = match Reflect::get(
&adapter_value,
&JsValue::from_str(WEBGPU_METHOD_REQUEST_DEVICE),
) {
Ok(value) => value.unchecked_into(),
Err(err) => return Err(WebGpuInitError::RequestDeviceLookup(err)),
};
let device_promise: Promise =
match request_device_fn.call1(&adapter_value, &device_descriptor) {
Ok(value) => value.unchecked_into(),
Err(err) => return Err(WebGpuInitError::RequestDeviceCall(err)),
};
let device_value: JsValue =
match JsFuture::from(Self::race_with_timeout(device_promise)).await {
Ok(value) => value,
Err(err) => return Err(WebGpuInitError::DevicePromise(err)),
};
if device_value.is_null() || device_value.is_undefined() {
return Err(WebGpuInitError::DeviceUnavailable);
}
let document: Document = window.document().expect("should have a document");
let element: Element = match document.query_selector(&config.canvas_selector) {
Ok(Some(el)) => el,
Ok(None) => {
return Err(WebGpuInitError::CanvasNotFound(
config.canvas_selector.clone(),
));
}
Err(err) => return Err(WebGpuInitError::CanvasQuery(err)),
};
let canvas: HtmlCanvasElement = element.unchecked_into();
let context_object: Option<Object> = canvas.get_context(WEBGPU_CONTEXT_TYPE).ok().flatten();
let context_object: Object = match context_object {
Some(c) => c,
None => return Err(WebGpuInitError::CanvasContextUnavailable),
};
let context: JsValue = context_object.into();
let get_format_fn: Function =
match Reflect::get(&gpu, &JsValue::from_str(WEBGPU_METHOD_GET_PREFERRED_FORMAT)) {
Ok(value) => value.unchecked_into(),
Err(err) => return Err(WebGpuInitError::PreferredFormatLookup(err)),
};
let format_value: JsValue = match get_format_fn.call0(&gpu) {
Ok(value) => value,
Err(err) => return Err(WebGpuInitError::PreferredFormatCall(err)),
};
let format: String = match format_value.as_string() {
Some(s) => s,
None => return Err(WebGpuInitError::PreferredFormatType(format_value)),
};
let dpr: f64 = CanvasRenderer::detect_dpr();
let physical_width: u32 = (config.width * dpr).round() as u32;
let physical_height: u32 = (config.height * dpr).round() as u32;
canvas.set_width(physical_width);
canvas.set_height(physical_height);
let canvas_config: Object = Object::new();
let _ = Reflect::set(
&canvas_config,
&JsValue::from_str(WEBGPU_PROPERTY_DEVICE),
&device_value,
);
let _ = Reflect::set(
&canvas_config,
&JsValue::from_str(WEBGPU_PROPERTY_FORMAT),
&format_value,
);
let configure_fn: Function =
match Reflect::get(&context, &JsValue::from_str(WEBGPU_METHOD_CONFIGURE)) {
Ok(value) => value.unchecked_into(),
Err(err) => return Err(WebGpuInitError::ConfigureLookup(err)),
};
let _ = configure_fn.call1(&context, &canvas_config);
let queue: JsValue =
match Reflect::get(&device_value, &JsValue::from_str(WEBGPU_PROPERTY_QUEUE)) {
Ok(value) => value,
Err(err) => return Err(WebGpuInitError::QueueLookup(err)),
};
Ok(WebGpuRenderer {
device: device_value,
queue,
context,
canvas,
format,
width: physical_width,
height: physical_height,
antialias: config.antialias,
})
}
pub fn resize(&mut self, physical_width: u32, physical_height: u32) -> bool {
if self.get_canvas().is_null()
|| self.get_context().is_null()
|| self.get_device().is_undefined()
{
return false;
}
self.get_canvas().set_width(physical_width);
self.get_canvas().set_height(physical_height);
let format_value: JsValue = JsValue::from_str(&self.get_format());
let canvas_config: Object = Object::new();
let _ = Reflect::set(
&canvas_config,
&JsValue::from_str(WEBGPU_PROPERTY_DEVICE),
self.get_device(),
);
let _ = Reflect::set(
&canvas_config,
&JsValue::from_str(WEBGPU_PROPERTY_FORMAT),
&format_value,
);
let configure_fn: Function = Reflect::get(
self.get_context(),
&JsValue::from_str(WEBGPU_METHOD_CONFIGURE),
)
.ok()
.and_then(|value: JsValue| value.dyn_into::<Function>().ok())
.unwrap_or_else(|| Function::new_no_args(""));
if configure_fn
.call1(self.get_context(), &canvas_config)
.is_err()
{
return false;
}
self.set_width(physical_width);
self.set_height(physical_height);
true
}
pub(crate) fn create_shader_module<S>(&self, code: S) -> JsValue
where
S: AsRef<str>,
{
let descriptor: Object = Object::new();
let _ = Reflect::set(
&descriptor,
&JsValue::from_str(WEBGPU_PROPERTY_CODE),
&JsValue::from_str(code.as_ref()),
);
let create_fn: Function = Reflect::get(
self.get_device(),
&JsValue::from_str(WEBGPU_METHOD_CREATE_SHADER_MODULE),
)
.unwrap_or(JsValue::UNDEFINED)
.unchecked_into();
create_fn
.call1(self.get_device(), &descriptor)
.unwrap_or(JsValue::UNDEFINED)
}
pub(crate) fn create_command_encoder(&self) -> JsValue {
let create_fn: Function = Reflect::get(
self.get_device(),
&JsValue::from_str(WEBGPU_METHOD_CREATE_COMMAND_ENCODER),
)
.unwrap_or(JsValue::UNDEFINED)
.unchecked_into();
create_fn
.call0(self.get_device())
.unwrap_or(JsValue::UNDEFINED)
}
pub(crate) fn get_current_texture_view(&self) -> JsValue {
let get_texture_fn: Function = Reflect::get(
self.get_context(),
&JsValue::from_str(WEBGPU_METHOD_GET_CURRENT_TEXTURE),
)
.unwrap_or(JsValue::UNDEFINED)
.unchecked_into();
let texture: JsValue = get_texture_fn
.call0(self.get_context())
.unwrap_or(JsValue::UNDEFINED);
let create_view_fn: Function =
Reflect::get(&texture, &JsValue::from_str(WEBGPU_METHOD_CREATE_VIEW))
.unwrap_or(JsValue::UNDEFINED)
.unchecked_into();
create_view_fn.call0(&texture).unwrap_or(JsValue::UNDEFINED)
}
pub(crate) fn begin_render_pass(
&self,
encoder: &JsValue,
clear_color: (f64, f64, f64, f64),
) -> JsValue {
let view: JsValue = self.get_current_texture_view();
let color_dict: Object = Object::new();
let _ = Reflect::set(
&color_dict,
&JsValue::from_str(WEBGPU_PROPERTY_R),
&JsValue::from_f64(clear_color.0),
);
let _ = Reflect::set(
&color_dict,
&JsValue::from_str(WEBGPU_PROPERTY_G),
&JsValue::from_f64(clear_color.1),
);
let _ = Reflect::set(
&color_dict,
&JsValue::from_str(WEBGPU_PROPERTY_B),
&JsValue::from_f64(clear_color.2),
);
let _ = Reflect::set(
&color_dict,
&JsValue::from_str(WEBGPU_PROPERTY_A),
&JsValue::from_f64(clear_color.3),
);
let attachment: Object = Object::new();
let _ = Reflect::set(&attachment, &JsValue::from_str(WEBGPU_PROPERTY_VIEW), &view);
let _ = Reflect::set(
&attachment,
&JsValue::from_str(WEBGPU_PROPERTY_LOAD_OP),
&JsValue::from_str(WEBGPU_LOAD_OP_CLEAR),
);
let _ = Reflect::set(
&attachment,
&JsValue::from_str(WEBGPU_PROPERTY_STORE_OP),
&JsValue::from_str(WEBGPU_STORE_OP_STORE),
);
let _ = Reflect::set(
&attachment,
&JsValue::from_str(WEBGPU_PROPERTY_CLEAR_VALUE),
&color_dict,
);
let color_attachments: Array = Array::new();
color_attachments.push(&attachment);
let descriptor: Object = Object::new();
let _ = Reflect::set(
&descriptor,
&JsValue::from_str(WEBGPU_PROPERTY_COLOR_ATTACHMENTS),
&color_attachments,
);
let begin_fn: Function =
Reflect::get(encoder, &JsValue::from_str(WEBGPU_METHOD_BEGIN_RENDER_PASS))
.unwrap_or(JsValue::UNDEFINED)
.unchecked_into();
begin_fn
.call1(encoder, &descriptor)
.unwrap_or(JsValue::UNDEFINED)
}
pub(crate) fn submit(&self, command_buffers: &[JsValue]) {
let array: Array = Array::new();
for buffer in command_buffers {
array.push(buffer);
}
let submit_fn: Function =
Reflect::get(self.get_queue(), &JsValue::from_str(WEBGPU_METHOD_SUBMIT))
.unwrap_or(JsValue::UNDEFINED)
.unchecked_into();
let _ = submit_fn.call1(self.get_queue(), &array);
}
pub fn create_render_pipeline<S>(&self, shader_code: S) -> JsValue
where
S: AsRef<str>,
{
let module: JsValue = self.create_shader_module(shader_code);
let vertex_state: Object = Object::new();
let _ = Reflect::set(
&vertex_state,
&JsValue::from_str(WEBGPU_PROPERTY_MODULE),
&module,
);
let _ = Reflect::set(
&vertex_state,
&JsValue::from_str(WEBGPU_PROPERTY_ENTRY_POINT),
&JsValue::from_str(WEBGPU_VERTEX_ENTRY_POINT),
);
let _ = Reflect::set(
&vertex_state,
&JsValue::from_str(WEBGPU_PROPERTY_BUFFERS),
&Array::new(),
);
let target: Object = Object::new();
let _ = Reflect::set(
&target,
&JsValue::from_str(WEBGPU_PROPERTY_FORMAT),
&JsValue::from_str(&self.get_format()),
);
let targets: Array = Array::new();
targets.push(&target);
let fragment_state: Object = Object::new();
let _ = Reflect::set(
&fragment_state,
&JsValue::from_str(WEBGPU_PROPERTY_MODULE),
&module,
);
let _ = Reflect::set(
&fragment_state,
&JsValue::from_str(WEBGPU_PROPERTY_ENTRY_POINT),
&JsValue::from_str(WEBGPU_FRAGMENT_ENTRY_POINT),
);
let _ = Reflect::set(
&fragment_state,
&JsValue::from_str(WEBGPU_PROPERTY_TARGETS),
&targets,
);
let primitive: Object = Object::new();
let _ = Reflect::set(
&primitive,
&JsValue::from_str(WEBGPU_PROPERTY_TOPOLOGY),
&JsValue::from_str(WEBGPU_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST),
);
let descriptor: Object = Object::new();
let _ = Reflect::set(
&descriptor,
&JsValue::from_str(WEBGPU_PROPERTY_LAYOUT),
&JsValue::null(),
);
let _ = Reflect::set(
&descriptor,
&JsValue::from_str(WEBGPU_PROPERTY_VERTEX),
&vertex_state,
);
let _ = Reflect::set(
&descriptor,
&JsValue::from_str(WEBGPU_PROPERTY_FRAGMENT),
&fragment_state,
);
let _ = Reflect::set(
&descriptor,
&JsValue::from_str(WEBGPU_PROPERTY_PRIMITIVE),
&primitive,
);
let create_fn: Function = Reflect::get(
self.get_device(),
&JsValue::from_str(WEBGPU_METHOD_CREATE_RENDER_PIPELINE),
)
.unwrap_or(JsValue::UNDEFINED)
.unchecked_into();
create_fn
.call1(self.get_device(), &descriptor)
.unwrap_or(JsValue::UNDEFINED)
}
pub(crate) fn set_pipeline(&self, pass: &JsValue, pipeline: &JsValue) {
let set_fn: Function = Reflect::get(pass, &JsValue::from_str(WEBGPU_METHOD_SET_PIPELINE))
.unwrap_or(JsValue::UNDEFINED)
.unchecked_into();
let _ = set_fn.call1(pass, pipeline);
}
pub(crate) fn draw(&self, pass: &JsValue, vertex_count: u32, instance_count: u32) {
let draw_fn: Function = Reflect::get(pass, &JsValue::from_str(WEBGPU_METHOD_DRAW))
.unwrap_or(JsValue::UNDEFINED)
.unchecked_into();
let _ = draw_fn.call2(
pass,
&JsValue::from_f64(f64::from(vertex_count)),
&JsValue::from_f64(f64::from(instance_count)),
);
}
pub(crate) fn end_render_pass(&self, pass: &JsValue) {
let end_fn: Function = Reflect::get(pass, &JsValue::from_str(WEBGPU_METHOD_END))
.unwrap_or(JsValue::UNDEFINED)
.unchecked_into();
let _ = end_fn.call0(pass);
}
pub(crate) fn finish_command_encoder(&self, encoder: &JsValue) -> JsValue {
let finish_fn: Function = Reflect::get(encoder, &JsValue::from_str(WEBGPU_METHOD_FINISH))
.unwrap_or(JsValue::UNDEFINED)
.unchecked_into();
finish_fn.call0(encoder).unwrap_or(JsValue::UNDEFINED)
}
pub fn render_frame(
&self,
pipeline: &JsValue,
clear_color: (f64, f64, f64, f64),
vertex_count: u32,
) {
let encoder: JsValue = self.create_command_encoder();
let pass: JsValue = self.begin_render_pass(&encoder, clear_color);
self.set_pipeline(&pass, pipeline);
self.draw(&pass, vertex_count, 1);
self.end_render_pass(&pass);
let command_buffer: JsValue = self.finish_command_encoder(&encoder);
self.submit(&[command_buffer]);
}
}
impl WebGpuInitError {
pub fn code(&self) -> &'static str {
match self {
Self::NavigatorLookup(_) => "WEBGPU_NAVIGATOR_LOOKUP",
Self::NavigatorGpuMissing => "WEBGPU_NAVIGATOR_GPU_MISSING",
Self::RequestAdapterLookup(_) => "WEBGPU_REQUEST_ADAPTER_LOOKUP",
Self::RequestAdapterCall(_) => "WEBGPU_REQUEST_ADAPTER_CALL",
Self::AdapterPromise(_) => "WEBGPU_ADAPTER_PROMISE",
Self::AdapterUnavailable => "WEBGPU_ADAPTER_UNAVAILABLE",
Self::RequestDeviceLookup(_) => "WEBGPU_REQUEST_DEVICE_LOOKUP",
Self::RequestDeviceCall(_) => "WEBGPU_REQUEST_DEVICE_CALL",
Self::DevicePromise(_) => "WEBGPU_DEVICE_PROMISE",
Self::DeviceUnavailable => "WEBGPU_DEVICE_UNAVAILABLE",
Self::CanvasNotFound(_) => "WEBGPU_CANVAS_NOT_FOUND",
Self::CanvasQuery(_) => "WEBGPU_CANVAS_QUERY",
Self::CanvasContextUnavailable => "WEBGPU_CANVAS_CONTEXT_UNAVAILABLE",
Self::PreferredFormatLookup(_) => "WEBGPU_PREFERRED_FORMAT_LOOKUP",
Self::PreferredFormatCall(_) => "WEBGPU_PREFERRED_FORMAT_CALL",
Self::PreferredFormatType(_) => "WEBGPU_PREFERRED_FORMAT_TYPE",
Self::ConfigureLookup(_) => "WEBGPU_CONFIGURE_LOOKUP",
Self::QueueLookup(_) => "WEBGPU_QUEUE_LOOKUP",
}
}
pub fn js_error(&self) -> Option<&JsValue> {
match self {
Self::NavigatorLookup(err)
| Self::RequestAdapterLookup(err)
| Self::RequestAdapterCall(err)
| Self::AdapterPromise(err)
| Self::RequestDeviceLookup(err)
| Self::RequestDeviceCall(err)
| Self::DevicePromise(err)
| Self::CanvasQuery(err)
| Self::PreferredFormatLookup(err)
| Self::PreferredFormatCall(err)
| Self::PreferredFormatType(err)
| Self::ConfigureLookup(err)
| Self::QueueLookup(err) => Some(err),
Self::NavigatorGpuMissing
| Self::AdapterUnavailable
| Self::DeviceUnavailable
| Self::CanvasContextUnavailable
| Self::CanvasNotFound(_) => None,
}
}
}
fn js_error_to_string(value: &JsValue) -> String {
if let Some(s) = value.as_string() {
s
} else if value.is_undefined() {
"<undefined>".to_string()
} else if value.is_null() {
"<null>".to_string()
} else {
format!("{:?}", value)
}
}
impl std::fmt::Display for WebGpuInitError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NavigatorLookup(err) => write!(
formatter,
"[{}] Reflect::get(navigator, webgpu) failed: {}",
self.code(),
js_error_to_string(err),
),
Self::NavigatorGpuMissing => write!(
formatter,
"[{}] navigator.gpu is missing - browser does not expose WebGPU on this origin",
self.code(),
),
Self::RequestAdapterLookup(err) => write!(
formatter,
"[{}] Reflect::get(gpu, requestAdapter) failed: {}",
self.code(),
js_error_to_string(err),
),
Self::RequestAdapterCall(err) => write!(
formatter,
"[{}] gpu.requestAdapter() threw: {}",
self.code(),
js_error_to_string(err),
),
Self::AdapterPromise(err) => write!(
formatter,
"[{}] adapter promise rejected or timed out: {}",
self.code(),
js_error_to_string(err),
),
Self::AdapterUnavailable => write!(
formatter,
"[{}] requestAdapter returned null - no compatible GPU adapter for the requested powerPreference",
self.code(),
),
Self::RequestDeviceLookup(err) => write!(
formatter,
"[{}] Reflect::get(adapter, requestDevice) failed: {}",
self.code(),
js_error_to_string(err),
),
Self::RequestDeviceCall(err) => write!(
formatter,
"[{}] adapter.requestDevice() threw: {}",
self.code(),
js_error_to_string(err),
),
Self::DevicePromise(err) => write!(
formatter,
"[{}] device promise rejected or timed out: {}",
self.code(),
js_error_to_string(err),
),
Self::DeviceUnavailable => write!(
formatter,
"[{}] requestDevice returned null - adapter could not allocate a device (possibly device-lost)",
self.code(),
),
Self::CanvasNotFound(selector) => write!(
formatter,
"[{}] canvas element {:?} not found in DOM",
self.code(),
selector,
),
Self::CanvasQuery(err) => write!(
formatter,
"[{}] querySelector threw: {}",
self.code(),
js_error_to_string(err),
),
Self::CanvasContextUnavailable => write!(
formatter,
"[{}] canvas.get_context('webgpu') returned null - the canvas may already be using another context type or WebGPU is disabled",
self.code(),
),
Self::PreferredFormatLookup(err) => write!(
formatter,
"[{}] Reflect::get(gpu, getPreferredCanvasFormat) failed: {}",
self.code(),
js_error_to_string(err),
),
Self::PreferredFormatCall(err) => write!(
formatter,
"[{}] gpu.getPreferredCanvasFormat() threw: {}",
self.code(),
js_error_to_string(err),
),
Self::PreferredFormatType(value) => write!(
formatter,
"[{}] getPreferredCanvasFormat returned non-string: {}",
self.code(),
js_error_to_string(value),
),
Self::ConfigureLookup(err) => write!(
formatter,
"[{}] Reflect::get(context, configure) failed: {}",
self.code(),
js_error_to_string(err),
),
Self::QueueLookup(err) => write!(
formatter,
"[{}] Reflect::get(device, queue) failed: {}",
self.code(),
js_error_to_string(err),
),
}
}
}
impl std::error::Error for WebGpuInitError {}