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 _: Result<bool, JsValue> = Reflect::set(
context,
&JsValue::from_str(RENDERER_PROPERTY_IMAGE_SMOOTHING_QUALITY),
&JsValue::from_str(quality_value),
);
let _: Result<bool, JsValue> = 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 _: Result<(), JsValue> = 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 _: Result<(), JsValue> = 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 _: Result<(), JsValue> = 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 _: Result<(), JsValue> =
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 _: Result<(), JsValue> =
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 _: Result<(), JsValue> = 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 _: Result<(), JsValue> =
context.set_global_composite_operation(mode.to_css());
}
_ => {}
}
}
if run_open {
if run_is_fill {
context.fill();
} else {
context.stroke();
}
}
let _: Result<(), JsValue> = 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 _: Result<(), JsValue> = self.get_context().translate(
camera.get_viewport_width() * 0.5,
camera.get_viewport_height() * 0.5,
);
let _: Result<(), JsValue> = self
.get_context()
.scale(camera.get_zoom(), camera.get_zoom());
let _: Result<(), JsValue> = self.get_context().rotate(camera.get_rotation());
let _: Result<(), JsValue> = 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 _: Result<(), JsValue> = 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 _: Result<(), JsValue> = 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 _: Result<(), JsValue> = 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 _: Result<(), JsValue> = 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 _: Result<(), JsValue> = 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 _: Result<(), JsValue> = 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 _: Result<(), JsValue> = 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 _: Result<(), JsValue> = 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 {
pub fn is_available() -> bool {
let window_value: Window = match window() {
Some(value) => value,
None => return false,
};
let navigator: Navigator = window_value.navigator();
let gpu_result: Result<JsValue, JsValue> = Reflect::get(
navigator.as_ref(),
&JsValue::from_str(WEBGPU_NAVIGATOR_GPU_KEY),
);
match gpu_result {
Ok(value) => !value.is_undefined() && !value.is_null(),
Err(_) => false,
}
}
pub async fn probe() -> bool {
if !Self::is_available() {
return false;
}
let window_value: Window = match window() {
Some(value) => value,
None => return false,
};
let navigator: Navigator = window_value.navigator();
let gpu: JsValue = match Reflect::get(
navigator.as_ref(),
&JsValue::from_str(WEBGPU_NAVIGATOR_GPU_KEY),
) {
Ok(value) => value,
Err(_) => return false,
};
let request_adapter_fn: Function =
match Reflect::get(&gpu, &JsValue::from_str(WEBGPU_METHOD_REQUEST_ADAPTER)) {
Ok(value) => value.unchecked_into(),
Err(_) => return false,
};
let adapter_promise: Promise = match request_adapter_fn.call0(&gpu) {
Ok(value) => value.unchecked_into(),
Err(_) => return false,
};
let adapter_value: JsValue =
match JsFuture::from(Self::race_with_timeout(adapter_promise)).await {
Ok(value) => value,
Err(_) => return false,
};
!adapter_value.is_undefined() && !adapter_value.is_null()
}
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 _: Result<JsValue, JsValue> = reject_fn.call1(
&JsValue::UNDEFINED,
&JsValue::from_str(RENDERER_TIMEOUT_ERROR_MESSAGE),
);
}));
let _: Result<i32, JsValue> = 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_NAVIGATOR_GPU_KEY),
);
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 _: Result<bool, JsValue> = 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 _: Result<bool, JsValue> = Reflect::set(
&canvas_config,
&JsValue::from_str(WEBGPU_PROPERTY_DEVICE),
&device_value,
);
let _: Result<bool, JsValue> = 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 _: Result<JsValue, JsValue> = 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,
multisample_texture: None,
multisample_view: None,
depth_texture: None,
depth_view: None,
depth_format: None,
device_lost_callback: None,
device_lost: false,
pending_error: Rc::new(PendingErrorCell::new()),
command_encoder: None,
})
}
fn create_multisample_texture(
&self,
physical_width: u32,
physical_height: u32,
) -> (JsValue, JsValue) {
let extent: Object = Object::new();
let _: Result<bool, JsValue> = Reflect::set(
&extent,
&JsValue::from_str(WEBGPU_PROPERTY_EXTENT_WIDTH),
&JsValue::from_f64(f64::from(physical_width)),
);
let _: Result<bool, JsValue> = Reflect::set(
&extent,
&JsValue::from_str(WEBGPU_PROPERTY_EXTENT_HEIGHT),
&JsValue::from_f64(f64::from(physical_height)),
);
let _: Result<bool, JsValue> = Reflect::set(
&extent,
&JsValue::from_str(WEBGPU_PROPERTY_EXTENT_DEPTH),
&JsValue::from_f64(1.0),
);
let descriptor: Object = Object::new();
let _: Result<bool, JsValue> = Reflect::set(
&descriptor,
&JsValue::from_str(WEBGPU_PROPERTY_SIZE),
&extent,
);
let _: Result<bool, JsValue> = Reflect::set(
&descriptor,
&JsValue::from_str(WEBGPU_PROPERTY_TEXTURE_FORMAT),
&JsValue::from_str(&self.get_format()),
);
let _: Result<bool, JsValue> = Reflect::set(
&descriptor,
&JsValue::from_str(WEBGPU_PROPERTY_USAGE),
&JsValue::from_f64(WEBGPU_TEXTURE_USAGE_RENDER_ATTACHMENT),
);
let _: Result<bool, JsValue> = Reflect::set(
&descriptor,
&JsValue::from_str(WEBGPU_PROPERTY_SAMPLE_COUNT),
&JsValue::from_f64(4.0),
);
let create_texture_fn: Function = Reflect::get(
self.get_device(),
&JsValue::from_str(WEBGPU_METHOD_CREATE_TEXTURE),
)
.unwrap_or(JsValue::UNDEFINED)
.unchecked_into();
let texture: JsValue = create_texture_fn
.call1(self.get_device(), &descriptor)
.unwrap_or(JsValue::UNDEFINED);
if texture.is_undefined() {
return (JsValue::UNDEFINED, JsValue::UNDEFINED);
}
let create_view_fn: Function =
Reflect::get(&texture, &JsValue::from_str(WEBGPU_METHOD_CREATE_VIEW))
.unwrap_or(JsValue::UNDEFINED)
.unchecked_into();
let view: JsValue = create_view_fn.call0(&texture).unwrap_or(JsValue::UNDEFINED);
if view.is_undefined() {
return (texture, JsValue::UNDEFINED);
}
(texture, view)
}
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 _: Result<bool, JsValue> = Reflect::set(
&canvas_config,
&JsValue::from_str(WEBGPU_PROPERTY_DEVICE),
self.get_device(),
);
let _: Result<bool, JsValue> = 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);
if self.get_antialias() {
let (texture, view) = self.create_multisample_texture(physical_width, physical_height);
if !view.is_undefined() {
self.set_multisample_texture(Some(texture));
self.set_multisample_view(Some(view));
} else {
self.set_multisample_texture(None);
self.set_multisample_view(None);
}
}
true
}
pub fn sync_to_current_canvas(&mut self) -> bool {
let canvas_width: u32 = self.get_canvas().width();
let canvas_height: u32 = self.get_canvas().height();
let client_width: u32 = self.get_canvas().client_width().try_into().unwrap_or(0);
let client_height: u32 = self.get_canvas().client_height().try_into().unwrap_or(0);
let css_w: u32 = if client_width > 0 {
client_width
} else {
canvas_width
};
let css_h: u32 = if client_height > 0 {
client_height
} else {
canvas_height
};
if css_w == 0 || css_h == 0 {
return false;
}
let dpr: f64 = CanvasRenderer::detect_dpr();
let physical_width: u32 = (f64::from(css_w) * dpr).round() as u32;
let physical_height: u32 = (f64::from(css_h) * dpr).round() as u32;
self.resize(physical_width, physical_height)
}
pub(crate) fn create_shader_module<S>(&self, code: S) -> JsValue
where
S: AsRef<str>,
{
let descriptor: Object = Object::new();
let _: Result<bool, JsValue> = 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(
&mut self,
encoder: &JsValue,
clear_color: (f64, f64, f64, f64),
) -> JsValue {
let mut color: RenderPassColorAttachment = RenderPassColorAttachment {
view: None,
resolve_target: None,
clear_value: Some(clear_color),
load_op: None,
store_op: None,
};
self.begin_render_pass_full(encoder, &mut color, None)
}
pub fn begin_render_pass_full(
&mut self,
encoder: &JsValue,
color: &mut RenderPassColorAttachment,
depth: Option<&RenderPassDepthStencilAttachment>,
) -> JsValue {
let swap_chain_view: JsValue = self.get_current_texture_view();
let (color_view, resolve_view): (JsValue, Option<JsValue>) = match color.view.take() {
Some(view) if !view.is_undefined() => (view, color.resolve_target.take()),
_ => {
if self.get_antialias() {
let multisample_view: Option<JsValue> = self
.get_multisample_view()
.clone()
.filter(|value: &JsValue| !value.is_undefined());
let resolved: Option<JsValue> = match multisample_view {
Some(view) => Some(view),
None => {
let width: u32 = self.get_width();
let height: u32 = self.get_height();
let (texture, view): (JsValue, JsValue) =
self.create_multisample_texture(width, height);
if !view.is_undefined() {
self.set_multisample_texture(Some(texture));
self.set_multisample_view(Some(view.clone()));
Some(view)
} else {
self.set_multisample_texture(None);
self.set_multisample_view(None);
None
}
}
};
match resolved {
Some(view) => (view, Some(swap_chain_view.clone())),
None => (swap_chain_view.clone(), None),
}
} else {
(swap_chain_view.clone(), None)
}
}
};
let attachment: Object = Object::new();
let _: Result<bool, JsValue> = Reflect::set(
&attachment,
&JsValue::from_str(WEBGPU_PROPERTY_VIEW),
&color_view,
);
let _: Result<bool, JsValue> = Reflect::set(
&attachment,
&JsValue::from_str(WEBGPU_PROPERTY_LOAD_OP),
&JsValue::from_str(color.effective_load_op()),
);
let _: Result<bool, JsValue> = Reflect::set(
&attachment,
&JsValue::from_str(WEBGPU_PROPERTY_STORE_OP),
&JsValue::from_str(color.effective_store_op()),
);
if let Some(cv) = color.clear_value {
let color_dict: Object = Object::new();
let _: Result<bool, JsValue> = Reflect::set(
&color_dict,
&JsValue::from_str(WEBGPU_PROPERTY_R),
&JsValue::from_f64(cv.0),
);
let _: Result<bool, JsValue> = Reflect::set(
&color_dict,
&JsValue::from_str(WEBGPU_PROPERTY_G),
&JsValue::from_f64(cv.1),
);
let _: Result<bool, JsValue> = Reflect::set(
&color_dict,
&JsValue::from_str(WEBGPU_PROPERTY_B),
&JsValue::from_f64(cv.2),
);
let _: Result<bool, JsValue> = Reflect::set(
&color_dict,
&JsValue::from_str(WEBGPU_PROPERTY_A),
&JsValue::from_f64(cv.3),
);
let _: Result<bool, JsValue> = Reflect::set(
&attachment,
&JsValue::from_str(WEBGPU_PROPERTY_CLEAR_VALUE),
&color_dict,
);
}
if let Some(target) = resolve_view.as_ref() {
let _: Result<bool, JsValue> = Reflect::set(
&attachment,
&JsValue::from_str(WEBGPU_PROPERTY_RESOLVE_TARGET),
target,
);
}
let color_attachments: Array = Array::new();
color_attachments.push(&attachment);
let descriptor: Object = Object::new();
let _: Result<bool, JsValue> = Reflect::set(
&descriptor,
&JsValue::from_str(WEBGPU_PROPERTY_COLOR_ATTACHMENTS),
&color_attachments,
);
if let Some(depth_desc) = depth {
let depth_view: JsValue = match depth_desc.view.clone() {
Some(v) if !v.is_undefined() => v,
_ => match self.create_depth_texture() {
Some(v) => v,
None => JsValue::UNDEFINED,
},
};
if !depth_view.is_undefined() {
let depth_attachment: Object = Object::new();
let _: Result<bool, JsValue> = Reflect::set(
&depth_attachment,
&JsValue::from_str(WEBGPU_PROPERTY_VIEW),
&depth_view,
);
let _: Result<bool, JsValue> = Reflect::set(
&depth_attachment,
&JsValue::from_str(WEBGPU_PROPERTY_DEPTH_LOAD_OP),
&JsValue::from_str(depth_desc.effective_depth_load_op()),
);
let _: Result<bool, JsValue> = Reflect::set(
&depth_attachment,
&JsValue::from_str(WEBGPU_PROPERTY_DEPTH_STORE_OP),
&JsValue::from_str(depth_desc.effective_depth_store_op()),
);
if let Some(clear) = depth_desc.depth_clear_value {
let _: Result<bool, JsValue> = Reflect::set(
&depth_attachment,
&JsValue::from_str(WEBGPU_PROPERTY_DEPTH_CLEAR_VALUE),
&JsValue::from_f64(f64::from(clear)),
);
}
if let Some(read_only) = depth_desc.depth_read_only {
let _: Result<bool, JsValue> = Reflect::set(
&depth_attachment,
&JsValue::from_str(WEBGPU_PROPERTY_DEPTH_READ_ONLY),
&JsValue::from_bool(read_only),
);
}
let _: Result<bool, JsValue> = Reflect::set(
&descriptor,
&JsValue::from_str(WEBGPU_PROPERTY_DEPTH_STENCIL_ATTACHMENT),
&depth_attachment,
);
}
}
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 _: Result<JsValue, JsValue> = submit_fn.call1(self.get_queue(), &array);
}
pub fn create_render_pipeline<S>(&self, shader_code: S) -> JsValue
where
S: AsRef<str>,
{
self.create_render_pipeline_full(
shader_code,
&[],
WEBGPU_VERTEX_ENTRY_POINT,
WEBGPU_FRAGMENT_ENTRY_POINT,
None,
)
}
pub fn create_render_pipeline_full<S>(
&self,
shader_code: S,
vertex_buffer_layouts: &[VertexBufferLayout],
vertex_entry: &str,
fragment_entry: &str,
depth_format: Option<&str>,
) -> JsValue
where
S: AsRef<str>,
{
let module: JsValue = self.create_shader_module(shader_code);
let vertex_state: Object = Object::new();
let _: Result<bool, JsValue> = Reflect::set(
&vertex_state,
&JsValue::from_str(WEBGPU_PROPERTY_MODULE),
&module,
);
let _: Result<bool, JsValue> = Reflect::set(
&vertex_state,
&JsValue::from_str(WEBGPU_PROPERTY_ENTRY_POINT),
&JsValue::from_str(vertex_entry),
);
let buffers: Array = Array::new();
for layout in vertex_buffer_layouts {
let layout_obj: Object = Object::new();
let _: Result<bool, JsValue> = Reflect::set(
&layout_obj,
&JsValue::from_str(WEBGPU_PROPERTY_ARRAY_STRIDE),
&JsValue::from_f64(layout.get_array_stride() as f64),
);
let _: Result<bool, JsValue> = Reflect::set(
&layout_obj,
&JsValue::from_str(WEBGPU_PROPERTY_STEP_MODE),
&JsValue::from_str(layout.get_step_mode().as_str()),
);
let attrs: Array = Array::new();
for attribute in layout.get_attributes() {
let attr: Object = Object::new();
let _: Result<bool, JsValue> = Reflect::set(
&attr,
&JsValue::from_str(WEBGPU_PROPERTY_FORMAT),
&JsValue::from_str(attribute.get_format()),
);
let _: Result<bool, JsValue> = Reflect::set(
&attr,
&JsValue::from_str(WEBGPU_PROPERTY_OFFSET),
&JsValue::from_f64(attribute.get_offset() as f64),
);
let _: Result<bool, JsValue> = Reflect::set(
&attr,
&JsValue::from_str(WEBGPU_PROPERTY_SHADER_LOCATION),
&JsValue::from_f64(f64::from(attribute.get_shader_location())),
);
attrs.push(&attr);
}
let _: Result<bool, JsValue> = Reflect::set(
&layout_obj,
&JsValue::from_str(WEBGPU_PROPERTY_ATTRIBUTES),
&attrs,
);
buffers.push(&layout_obj);
}
let _: Result<bool, JsValue> = Reflect::set(
&vertex_state,
&JsValue::from_str(WEBGPU_PROPERTY_BUFFERS),
&buffers,
);
let target: Object = Object::new();
let _: Result<bool, JsValue> = 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 _: Result<bool, JsValue> = Reflect::set(
&fragment_state,
&JsValue::from_str(WEBGPU_PROPERTY_MODULE),
&module,
);
let _: Result<bool, JsValue> = Reflect::set(
&fragment_state,
&JsValue::from_str(WEBGPU_PROPERTY_ENTRY_POINT),
&JsValue::from_str(fragment_entry),
);
let _: Result<bool, JsValue> = Reflect::set(
&fragment_state,
&JsValue::from_str(WEBGPU_PROPERTY_TARGETS),
&targets,
);
let primitive: Object = Object::new();
let _: Result<bool, JsValue> = Reflect::set(
&primitive,
&JsValue::from_str(WEBGPU_PROPERTY_TOPOLOGY),
&JsValue::from_str(WEBGPU_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST),
);
let multisample: Object = Object::new();
let _: Result<bool, JsValue> = Reflect::set(
&multisample,
&JsValue::from_str(WEBGPU_PROPERTY_COUNT),
&JsValue::from_f64(if self.get_antialias() { 4.0 } else { 1.0 }),
);
let descriptor: Object = Object::new();
let _: Result<bool, JsValue> = Reflect::set(
&descriptor,
&JsValue::from_str(WEBGPU_PROPERTY_LAYOUT),
&JsValue::from_str(WEBGPU_AUTO_LAYOUT),
);
let _: Result<bool, JsValue> = Reflect::set(
&descriptor,
&JsValue::from_str(WEBGPU_PROPERTY_VERTEX),
&vertex_state,
);
let _: Result<bool, JsValue> = Reflect::set(
&descriptor,
&JsValue::from_str(WEBGPU_PROPERTY_FRAGMENT),
&fragment_state,
);
let _: Result<bool, JsValue> = Reflect::set(
&descriptor,
&JsValue::from_str(WEBGPU_PROPERTY_PRIMITIVE),
&primitive,
);
let _: Result<bool, JsValue> = Reflect::set(
&descriptor,
&JsValue::from_str(WEBGPU_PROPERTY_MULTISAMPLE),
&multisample,
);
if let Some(format) = depth_format {
let depth_stencil: Object = Object::new();
let _: Result<bool, JsValue> = Reflect::set(
&depth_stencil,
&JsValue::from_str(WEBGPU_PROPERTY_FORMAT),
&JsValue::from_str(format),
);
let _: Result<bool, JsValue> = Reflect::set(
&depth_stencil,
&JsValue::from_str(WEBGPU_PROPERTY_DEPTH_WRITE_ENABLED),
&JsValue::from_bool(true),
);
let _: Result<bool, JsValue> = Reflect::set(
&depth_stencil,
&JsValue::from_str(WEBGPU_PROPERTY_DEPTH_COMPARE),
&JsValue::from_str(WEBGPU_COMPARE_LESS),
);
let _: Result<bool, JsValue> = Reflect::set(
&descriptor,
&JsValue::from_str(WEBGPU_PROPERTY_DEPTH_STENCIL),
&depth_stencil,
);
}
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 _: Result<JsValue, JsValue> = 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 _: Result<JsValue, JsValue> = 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 _: Result<JsValue, JsValue> = 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 create_uniform_buffer(&self, data: &[f32]) -> JsValue {
let byte_len: usize = data.len() * 4;
let size: f64 = byte_len.div_ceil(16).max(1) as f64 * 16.0;
let descriptor: Object = Object::new();
let _: Result<bool, JsValue> = Reflect::set(
&descriptor,
&JsValue::from_str(WEBGPU_PROPERTY_SIZE),
&JsValue::from_f64(size),
);
let _: Result<bool, JsValue> = Reflect::set(
&descriptor,
&JsValue::from_str(WEBGPU_PROPERTY_USAGE),
&JsValue::from_f64(WEBGPU_BUFFER_USAGE_UNIFORM + WEBGPU_BUFFER_USAGE_COPY_DST),
);
let create_fn: Function = Reflect::get(
self.get_device(),
&JsValue::from_str(WEBGPU_METHOD_CREATE_BUFFER),
)
.unwrap_or(JsValue::UNDEFINED)
.unchecked_into();
let buffer: JsValue = create_fn
.call1(self.get_device(), &descriptor)
.unwrap_or(JsValue::UNDEFINED);
self.update_uniform_buffer(&buffer, data);
buffer
}
pub fn update_uniform_buffer(&self, buffer: &JsValue, data: &[f32]) {
let view: js_sys::Float32Array = js_sys::Float32Array::from(data);
let write_fn: Function = Reflect::get(
self.get_queue(),
&JsValue::from_str(WEBGPU_METHOD_WRITE_BUFFER),
)
.unwrap_or(JsValue::UNDEFINED)
.unchecked_into();
let _: Result<JsValue, JsValue> =
write_fn.call3(self.get_queue(), buffer, &JsValue::from_f64(0.0), &view);
}
pub fn create_compute_pipeline<S>(&self, shader_code: S, entry_point: &str) -> JsValue
where
S: AsRef<str>,
{
let module: JsValue = self.create_shader_module(shader_code);
let compute_state: Object = Object::new();
let _: Result<bool, JsValue> = Reflect::set(
&compute_state,
&JsValue::from_str(WEBGPU_PROPERTY_MODULE),
&module,
);
let _: Result<bool, JsValue> = Reflect::set(
&compute_state,
&JsValue::from_str(WEBGPU_PROPERTY_ENTRY_POINT),
&JsValue::from_str(entry_point),
);
let descriptor: Object = Object::new();
let _: Result<bool, JsValue> = Reflect::set(
&descriptor,
&JsValue::from_str(WEBGPU_PROPERTY_LAYOUT),
&JsValue::from_str(WEBGPU_AUTO_LAYOUT),
);
let _: Result<bool, JsValue> = Reflect::set(
&descriptor,
&JsValue::from_str(WEBGPU_PROPERTY_COMPUTE),
&compute_state,
);
let create_fn: Function = Reflect::get(
self.get_device(),
&JsValue::from_str(WEBGPU_METHOD_CREATE_COMPUTE_PIPELINE),
)
.unwrap_or(JsValue::UNDEFINED)
.unchecked_into();
create_fn
.call1(self.get_device(), &descriptor)
.unwrap_or(JsValue::UNDEFINED)
}
pub fn begin_compute_pass(&self, encoder: &JsValue) -> JsValue {
let begin_fn: Function = Reflect::get(
encoder,
&JsValue::from_str(WEBGPU_METHOD_BEGIN_COMPUTE_PASS),
)
.unwrap_or(JsValue::UNDEFINED)
.unchecked_into();
let descriptor: Object = Object::new();
begin_fn
.call1(encoder, &descriptor)
.unwrap_or(JsValue::UNDEFINED)
}
pub fn dispatch(&self, pass: &JsValue, x: u32, y: u32, z: u32) {
let fn_: Function = Reflect::get(pass, &JsValue::from_str(WEBGPU_METHOD_DISPATCH))
.unwrap_or(JsValue::UNDEFINED)
.unchecked_into();
let _: Result<JsValue, JsValue> = fn_.call3(
pass,
&JsValue::from_f64(f64::from(x)),
&JsValue::from_f64(f64::from(y)),
&JsValue::from_f64(f64::from(z)),
);
}
pub fn push_error_scope(&self, filter: &str) {
let fn_: Function = Reflect::get(
self.get_device(),
&JsValue::from_str(WEBGPU_METHOD_PUSH_ERROR_SCOPE),
)
.unwrap_or(JsValue::UNDEFINED)
.unchecked_into();
let _: Result<JsValue, JsValue> = fn_.call1(self.get_device(), &JsValue::from_str(filter));
}
pub fn pop_error_sync(&self) -> Option<JsValue> {
let pop_fn: Function = Reflect::get(
self.get_device(),
&JsValue::from_str(WEBGPU_METHOD_POP_ERROR_SCOPE),
)
.ok()?
.unchecked_into();
let promise: JsValue = pop_fn.call0(self.get_device()).ok()?;
if !promise.is_object() {
return None;
}
let promise: js_sys::Promise = promise.unchecked_into();
let future = wasm_bindgen_futures::JsFuture::from(promise);
let slot: std::rc::Rc<PendingErrorCell> = self.pending_error.clone();
wasm_bindgen_futures::spawn_local(async move {
match future.await {
Ok(value) => {
let cell: &mut Option<JsValue> = unsafe { &mut *slot.as_ptr() };
if value.is_undefined() || value.is_null() {
*cell = None;
} else {
*cell = Some(value);
}
}
Err(_) => {
}
}
});
let cell: &mut Option<JsValue> = unsafe { &mut *self.pending_error.as_ptr() };
cell.take()
}
pub fn take_last_error(&self) -> Option<JsValue> {
let cell: &mut Option<JsValue> = unsafe { &mut *self.pending_error.as_ptr() };
cell.take()
}
pub fn begin_render_pass_to_texture(
&mut self,
encoder: &JsValue,
color_view: &JsValue,
clear_color: Option<(f64, f64, f64, f64)>,
depth_view: Option<&JsValue>,
depth_clear: Option<f32>,
) -> JsValue {
let mut color: RenderPassColorAttachment = RenderPassColorAttachment {
view: Some(color_view.clone()),
resolve_target: None,
clear_value: clear_color,
load_op: None,
store_op: None,
};
let depth: Option<RenderPassDepthStencilAttachment> =
depth_view.map(|v| RenderPassDepthStencilAttachment {
view: Some(v.clone()),
depth_clear_value: depth_clear,
depth_load_op: None,
depth_store_op: None,
depth_read_only: None,
});
let depth_ref: Option<&RenderPassDepthStencilAttachment> = depth.as_ref();
self.begin_render_pass_full(encoder, &mut color, depth_ref)
}
pub fn copy_texture_to_buffer(
&self,
source: &JsValue,
destination: &JsValue,
bytes_per_row: u32,
width: u32,
height: u32,
) {
let source_layout: Object = Object::new();
let _: Result<bool, JsValue> = Reflect::set(
&source_layout,
&JsValue::from_str(WEBGPU_PROPERTY_TEXTURE),
source,
);
let copy_size: Array = Array::new_with_length(3);
copy_size.set(0, JsValue::from_f64(f64::from(width)));
copy_size.set(1, JsValue::from_f64(f64::from(height)));
copy_size.set(2, JsValue::from_f64(1.0));
let destination_layout: Object = Object::new();
let _: Result<bool, JsValue> = Reflect::set(
&destination_layout,
&JsValue::from_str(WEBGPU_PROPERTY_BUFFER),
destination,
);
let _: Result<bool, JsValue> = Reflect::set(
&destination_layout,
&JsValue::from_str(WEBGPU_PROPERTY_BYTES_PER_ROW),
&JsValue::from_f64(f64::from(bytes_per_row)),
);
let _: Result<bool, JsValue> = Reflect::set(
&destination_layout,
&JsValue::from_str(WEBGPU_PROPERTY_ROWS_PER_IMAGE),
&JsValue::from_f64(f64::from(height)),
);
let info: Object = Object::new();
let _: Result<bool, JsValue> = Reflect::set(
&info,
&JsValue::from_str(WEBGPU_PROPERTY_SOURCE),
&source_layout,
);
let _: Result<bool, JsValue> = Reflect::set(
&info,
&JsValue::from_str(WEBGPU_PROPERTY_DESTINATION),
&destination_layout,
);
let _: Result<bool, JsValue> = Reflect::set(
&info,
&JsValue::from_str(WEBGPU_PROPERTY_COPY_SIZE),
©_size,
);
let encoder: JsValue = match self.get_command_encoder() {
Some(enc) => enc,
None => return,
};
let cmd_fn: Function = Reflect::get(
&encoder,
&JsValue::from_str(WEBGPU_METHOD_COPY_TEXTURE_TO_BUFFER),
)
.unwrap_or(JsValue::UNDEFINED)
.unchecked_into();
let _: Result<JsValue, JsValue> = cmd_fn.call1(&encoder, &info);
}
pub fn create_offline_render_target(
&self,
width: u32,
height: u32,
format: &str,
) -> (JsValue, JsValue) {
let descriptor: Object = Object::new();
let _: Result<bool, JsValue> = Reflect::set(
&descriptor,
&JsValue::from_str(WEBGPU_PROPERTY_SIZE),
&js_sys::Array::of3(
&JsValue::from_f64(f64::from(width)),
&JsValue::from_f64(f64::from(height)),
&JsValue::from_f64(1.0),
),
);
let _: Result<bool, JsValue> = Reflect::set(
&descriptor,
&JsValue::from_str(WEBGPU_PROPERTY_FORMAT),
&JsValue::from_str(format),
);
let _: Result<bool, JsValue> = Reflect::set(
&descriptor,
&JsValue::from_str(WEBGPU_PROPERTY_USAGE),
&JsValue::from_str("RENDER_ATTACHMENT | TEXTURE_BINDING | COPY_SRC"),
);
let create_fn: Function = Reflect::get(
self.get_device(),
&JsValue::from_str(WEBGPU_METHOD_CREATE_TEXTURE),
)
.unwrap_or(JsValue::UNDEFINED)
.unchecked_into();
let texture: JsValue = create_fn
.call1(self.get_device(), &descriptor)
.unwrap_or(JsValue::UNDEFINED);
if texture.is_undefined() {
return (JsValue::UNDEFINED, JsValue::UNDEFINED);
}
let view: JsValue = self.create_texture_view(&texture);
(texture, view)
}
pub fn create_texture_view(&self, texture: &JsValue) -> JsValue {
let fn_: Function = Reflect::get(texture, &JsValue::from_str(WEBGPU_METHOD_CREATE_VIEW))
.unwrap_or(JsValue::UNDEFINED)
.unchecked_into();
fn_.call0(texture).unwrap_or(JsValue::UNDEFINED)
}
pub fn on_device_lost(&mut self, callback: js_sys::Function) {
let lost_promise: Promise =
match Reflect::get(self.get_device(), &JsValue::from_str(WEBGPU_PROPERTY_LOST))
.ok()
.and_then(|v| v.dyn_into::<Promise>().ok())
{
Some(p) => p,
None => return,
};
let closure: Closure<dyn FnMut(JsValue)> = Closure::new(move |reason: JsValue| {
let _: Result<JsValue, JsValue> = callback.call1(&JsValue::NULL, &reason);
});
let _ = lost_promise.then(&closure);
closure.forget();
}
pub fn create_buffer(&self, size: u64, usage: u32) -> JsValue {
if size == 0 {
return JsValue::UNDEFINED;
}
let descriptor: Object = Object::new();
let _: Result<bool, JsValue> = Reflect::set(
&descriptor,
&JsValue::from_str(WEBGPU_PROPERTY_SIZE),
&JsValue::from_f64(size as f64),
);
let _: Result<bool, JsValue> = Reflect::set(
&descriptor,
&JsValue::from_str(WEBGPU_PROPERTY_USAGE),
&JsValue::from_f64(f64::from(usage)),
);
let create_fn: Function = Reflect::get(
self.get_device(),
&JsValue::from_str(WEBGPU_METHOD_CREATE_BUFFER),
)
.unwrap_or(JsValue::UNDEFINED)
.unchecked_into();
create_fn
.call1(self.get_device(), &descriptor)
.unwrap_or(JsValue::UNDEFINED)
}
pub fn create_vertex_buffer(&self, data: &[u8]) -> JsValue {
let buffer: JsValue = self.create_buffer(
data.len() as u64,
(WEBGPU_BUFFER_USAGE_VERTEX as u32) | (WEBGPU_BUFFER_USAGE_COPY_DST as u32),
);
if buffer.is_undefined() {
return JsValue::UNDEFINED;
}
self.write_buffer(&buffer, 0, data);
buffer
}
pub fn create_index_buffer(&self, data: &[u8]) -> JsValue {
let buffer: JsValue = self.create_buffer(
data.len() as u64,
(WEBGPU_BUFFER_USAGE_INDEX as u32) | (WEBGPU_BUFFER_USAGE_COPY_DST as u32),
);
if buffer.is_undefined() {
return JsValue::UNDEFINED;
}
self.write_buffer(&buffer, 0, data);
buffer
}
pub fn write_buffer(&self, buffer: &JsValue, offset: u64, data: &[u8]) {
if data.is_empty() {
return;
}
let view: js_sys::Uint8Array = js_sys::Uint8Array::from(data);
let write_fn: Function = Reflect::get(
self.get_queue(),
&JsValue::from_str(WEBGPU_METHOD_WRITE_BUFFER),
)
.unwrap_or(JsValue::UNDEFINED)
.unchecked_into();
let _: Result<JsValue, JsValue> = write_fn.call4(
self.get_queue(),
buffer,
&JsValue::from_f64(offset as f64),
&view,
&JsValue::from_f64(data.len() as f64),
);
}
pub fn create_depth_texture(&mut self) -> Option<JsValue> {
if let Some(view) = self.get_depth_view().clone() {
if !view.is_undefined() {
return Some(view);
}
}
let extent: Object = Object::new();
let _: Result<bool, JsValue> = Reflect::set(
&extent,
&JsValue::from_str(WEBGPU_PROPERTY_EXTENT_WIDTH),
&JsValue::from_f64(f64::from(self.get_width())),
);
let _: Result<bool, JsValue> = Reflect::set(
&extent,
&JsValue::from_str(WEBGPU_PROPERTY_EXTENT_HEIGHT),
&JsValue::from_f64(f64::from(self.get_height())),
);
let _: Result<bool, JsValue> = Reflect::set(
&extent,
&JsValue::from_str(WEBGPU_PROPERTY_EXTENT_DEPTH),
&JsValue::from_f64(1.0),
);
let descriptor: Object = Object::new();
let _: Result<bool, JsValue> = Reflect::set(
&descriptor,
&JsValue::from_str(WEBGPU_PROPERTY_SIZE),
&extent,
);
let format: &'static str = pick_depth_format(
false, true,
);
let _: Result<bool, JsValue> = Reflect::set(
&descriptor,
&JsValue::from_str(WEBGPU_PROPERTY_TEXTURE_FORMAT),
&JsValue::from_str(format),
);
let usage: u32 = texture_usage(
true, false,
false, false, false,
);
let _: Result<bool, JsValue> = Reflect::set(
&descriptor,
&JsValue::from_str(WEBGPU_PROPERTY_USAGE),
&JsValue::from_f64(usage as f64),
);
let create_fn: Function = Reflect::get(
self.get_device(),
&JsValue::from_str(WEBGPU_METHOD_CREATE_TEXTURE),
)
.unwrap_or(JsValue::UNDEFINED)
.unchecked_into();
let texture: JsValue = create_fn
.call1(self.get_device(), &descriptor)
.unwrap_or(JsValue::UNDEFINED);
if texture.is_undefined() {
return None;
}
let create_view_fn: Function =
Reflect::get(&texture, &JsValue::from_str(WEBGPU_METHOD_CREATE_VIEW))
.unwrap_or(JsValue::UNDEFINED)
.unchecked_into();
let view: JsValue = create_view_fn.call0(&texture).unwrap_or(JsValue::UNDEFINED);
if view.is_undefined() {
return None;
}
self.set_depth_texture(Some(texture));
self.set_depth_view(Some(view.clone()));
self.set_depth_format(Some(format.to_string()));
Some(view)
}
pub fn create_texture_2d(&self, descriptor: &Texture2DDescriptor) -> JsValue {
let width: u32 = descriptor.get_width();
let height: u32 = descriptor.get_height();
if width == 0 || height == 0 {
return JsValue::UNDEFINED;
}
let extent: Object = Object::new();
let _: Result<bool, JsValue> = Reflect::set(
&extent,
&JsValue::from_str(WEBGPU_PROPERTY_EXTENT_WIDTH),
&JsValue::from_f64(f64::from(width)),
);
let _: Result<bool, JsValue> = Reflect::set(
&extent,
&JsValue::from_str(WEBGPU_PROPERTY_EXTENT_HEIGHT),
&JsValue::from_f64(f64::from(height)),
);
let _: Result<bool, JsValue> = Reflect::set(
&extent,
&JsValue::from_str(WEBGPU_PROPERTY_EXTENT_DEPTH),
&JsValue::from_f64(1.0),
);
let desc: Object = Object::new();
let _: Result<bool, JsValue> =
Reflect::set(&desc, &JsValue::from_str(WEBGPU_PROPERTY_SIZE), &extent);
let mip_count: u32 = descriptor.get_mip_level_count().max(1);
let _: Result<bool, JsValue> = Reflect::set(
&desc,
&JsValue::from_str(WEBGPU_PROPERTY_MIP_LEVEL_COUNT),
&JsValue::from_f64(f64::from(mip_count)),
);
let sample_count: u32 = descriptor.get_sample_count().max(1);
let _: Result<bool, JsValue> = Reflect::set(
&desc,
&JsValue::from_str(WEBGPU_PROPERTY_SAMPLE_COUNT),
&JsValue::from_f64(f64::from(sample_count)),
);
let _: Result<bool, JsValue> = Reflect::set(
&desc,
&JsValue::from_str(WEBGPU_PROPERTY_TEXTURE_FORMAT),
&JsValue::from_str(descriptor.get_format()),
);
let _: Result<bool, JsValue> = Reflect::set(
&desc,
&JsValue::from_str(WEBGPU_PROPERTY_USAGE),
&JsValue::from_str(descriptor.get_usage()),
);
let create_fn: Function = Reflect::get(
self.get_device(),
&JsValue::from_str(WEBGPU_METHOD_CREATE_TEXTURE),
)
.unwrap_or(JsValue::UNDEFINED)
.unchecked_into();
create_fn
.call1(self.get_device(), &desc)
.unwrap_or(JsValue::UNDEFINED)
}
pub fn create_sampler(&self, descriptor: &GpuSamplerDescriptor) -> JsValue {
let desc: Object = Object::new();
let _: Result<bool, JsValue> = Reflect::set(
&desc,
&JsValue::from_str(WEBGPU_PROPERTY_MAG_FILTER),
&JsValue::from_str(descriptor.get_mag_filter()),
);
let _: Result<bool, JsValue> = Reflect::set(
&desc,
&JsValue::from_str(WEBGPU_PROPERTY_MIN_FILTER),
&JsValue::from_str(descriptor.get_min_filter()),
);
let _: Result<bool, JsValue> = Reflect::set(
&desc,
&JsValue::from_str(WEBGPU_PROPERTY_MIPMAP_FILTER),
&JsValue::from_str(descriptor.get_mipmap_filter()),
);
let _: Result<bool, JsValue> = Reflect::set(
&desc,
&JsValue::from_str(WEBGPU_PROPERTY_ADDRESS_MODE_U),
&JsValue::from_str(descriptor.get_address_mode_u()),
);
let _: Result<bool, JsValue> = Reflect::set(
&desc,
&JsValue::from_str(WEBGPU_PROPERTY_ADDRESS_MODE_V),
&JsValue::from_str(descriptor.get_address_mode_v()),
);
let _: Result<bool, JsValue> = Reflect::set(
&desc,
&JsValue::from_str(WEBGPU_PROPERTY_ADDRESS_MODE_W),
&JsValue::from_str(descriptor.get_address_mode_w()),
);
if descriptor.get_compare() {
let _: Result<bool, JsValue> = Reflect::set(
&desc,
&JsValue::from_str(WEBGPU_PROPERTY_COMPARE),
&JsValue::from_str(WEBGPU_COMPARE_LESS),
);
}
let create_fn: Function = Reflect::get(
self.get_device(),
&JsValue::from_str(WEBGPU_METHOD_CREATE_SAMPLER),
)
.unwrap_or(JsValue::UNDEFINED)
.unchecked_into();
create_fn
.call1(self.get_device(), &desc)
.unwrap_or(JsValue::UNDEFINED)
}
pub fn create_uniform_bind_group(&self, pipeline: &JsValue, buffer: &JsValue) -> JsValue {
self.create_bind_group(
pipeline,
0,
&[BindGroupEntry::Buffer {
binding: 0,
buffer: buffer.clone(),
offset: 0,
size: None,
}],
)
}
pub fn create_bind_group(
&self,
pipeline: &JsValue,
index: u32,
entries: &[BindGroupEntry],
) -> JsValue {
let layout_fn: Function = Reflect::get(
pipeline,
&JsValue::from_str(WEBGPU_METHOD_GET_BIND_GROUP_LAYOUT),
)
.unwrap_or(JsValue::UNDEFINED)
.unchecked_into();
let layout: JsValue = layout_fn
.call1(pipeline, &JsValue::from_f64(f64::from(index)))
.unwrap_or(JsValue::UNDEFINED);
let entries_array: Array = Array::new();
for entry in entries {
let entry_obj: Object = Object::new();
let _: Result<bool, JsValue> = Reflect::set(
&entry_obj,
&JsValue::from_str(WEBGPU_PROPERTY_BINDING),
&JsValue::from_f64(f64::from(entry.binding())),
);
let resource_obj: Object = Object::new();
match entry {
BindGroupEntry::Buffer {
buffer,
offset,
size,
..
} => {
let _: Result<bool, JsValue> = Reflect::set(
&resource_obj,
&JsValue::from_str(WEBGPU_PROPERTY_BUFFER),
buffer,
);
let _: Result<bool, JsValue> = Reflect::set(
&resource_obj,
&JsValue::from_str(WEBGPU_PROPERTY_OFFSET),
&JsValue::from_f64(*offset as f64),
);
if let Some(s) = size {
let _: Result<bool, JsValue> = Reflect::set(
&resource_obj,
&JsValue::from_str(WEBGPU_PROPERTY_SIZE),
&JsValue::from_f64(*s as f64),
);
}
}
BindGroupEntry::Texture { view, .. } => {
let _: Result<bool, JsValue> = Reflect::set(
&resource_obj,
&JsValue::from_str(WEBGPU_PROPERTY_TEXTURE_VIEW),
view,
);
}
BindGroupEntry::Sampler { sampler, .. } => {
let _: Result<bool, JsValue> = Reflect::set(
&resource_obj,
&JsValue::from_str(WEBGPU_PROPERTY_SAMPLER),
sampler,
);
}
}
let _: Result<bool, JsValue> = Reflect::set(
&entry_obj,
&JsValue::from_str(WEBGPU_PROPERTY_RESOURCE),
&resource_obj,
);
entries_array.push(&entry_obj);
}
let descriptor: Object = Object::new();
let _: Result<bool, JsValue> = Reflect::set(
&descriptor,
&JsValue::from_str(WEBGPU_PROPERTY_LAYOUT),
&layout,
);
let _: Result<bool, JsValue> = Reflect::set(
&descriptor,
&JsValue::from_str(WEBGPU_PROPERTY_ENTRIES),
&entries_array,
);
self.push_error_scope(WEBGPU_ERROR_FILTER_VALIDATION);
let create_fn: Function = Reflect::get(
self.get_device(),
&JsValue::from_str(WEBGPU_METHOD_CREATE_BIND_GROUP),
)
.unwrap_or(JsValue::UNDEFINED)
.unchecked_into();
let result: JsValue = create_fn
.call1(self.get_device(), &descriptor)
.unwrap_or(JsValue::UNDEFINED);
if let Some(error) = self.pop_error_sync() {
web_sys::console::error_1(&error);
}
result
}
pub(crate) fn set_bind_group(&self, pass: &JsValue, index: u32, bind_group: &JsValue) {
let set_fn: Function = Reflect::get(pass, &JsValue::from_str(WEBGPU_METHOD_SET_BIND_GROUP))
.unwrap_or(JsValue::UNDEFINED)
.unchecked_into();
let _: Result<JsValue, JsValue> =
set_fn.call2(pass, &JsValue::from_f64(f64::from(index)), bind_group);
}
pub fn render_frame(
&mut 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]);
}
pub fn render_frame_with_bind_group(
&mut self,
pipeline: &JsValue,
bind_group: &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.set_bind_group(&pass, 0, bind_group);
self.draw(&pass, vertex_count, 1);
self.end_render_pass(&pass);
let command_buffer: JsValue = self.finish_command_encoder(&encoder);
self.submit(&[command_buffer]);
}
pub fn dispose(&self) {
let context: &JsValue = self.get_context();
if let Ok(unconfigure_fn) =
Reflect::get(context, &JsValue::from_str(WEBGPU_METHOD_UNCONFIGURE))
&& let Ok(unconfigure_callable) = unconfigure_fn.dyn_into::<Function>()
{
let _: Result<JsValue, JsValue> = unconfigure_callable.call0(context);
}
let device: &JsValue = self.get_device();
if let Ok(destroy_fn) = Reflect::get(device, &JsValue::from_str(WEBGPU_METHOD_DESTROY))
&& let Ok(destroy_callable) = destroy_fn.dyn_into::<Function>()
{
let _: Result<JsValue, JsValue> = destroy_callable.call0(device);
}
}
pub fn set_viewport(
&self,
pass: &JsValue,
x: f32,
y: f32,
width: f32,
height: f32,
min_depth: f32,
max_depth: f32,
) {
let vp_dict: Object = Object::new();
let _ = Reflect::set(
&vp_dict,
&JsValue::from_str(WEBGPU_PROPERTY_X),
&JsValue::from_f64(x as f64),
);
let _ = Reflect::set(
&vp_dict,
&JsValue::from_str(WEBGPU_PROPERTY_Y),
&JsValue::from_f64(y as f64),
);
let _ = Reflect::set(
&vp_dict,
&JsValue::from_str(WEBGPU_PROPERTY_WIDTH),
&JsValue::from_f64(width as f64),
);
let _ = Reflect::set(
&vp_dict,
&JsValue::from_str(WEBGPU_PROPERTY_HEIGHT),
&JsValue::from_f64(height as f64),
);
let _ = Reflect::set(
&vp_dict,
&JsValue::from_str(WEBGPU_PROPERTY_MIN_DEPTH),
&JsValue::from_f64(min_depth as f64),
);
let _ = Reflect::set(
&vp_dict,
&JsValue::from_str(WEBGPU_PROPERTY_MAX_DEPTH),
&JsValue::from_f64(max_depth as f64),
);
let vp_js: JsValue = vp_dict.unchecked_into::<JsValue>();
if let Ok(set_fn) = Reflect::get(pass, &JsValue::from_str(WEBGPU_METHOD_SET_VIEWPORT))
&& let Ok(set_callable) = set_fn.dyn_into::<Function>()
{
let _: Result<JsValue, JsValue> = set_callable.call1(pass, &vp_js);
}
}
pub fn set_scissor_rect(&self, pass: &JsValue, x: u32, y: u32, width: u32, height: u32) {
let rect_dict: Object = Object::new();
let _ = Reflect::set(
&rect_dict,
&JsValue::from_str(WEBGPU_PROPERTY_X),
&JsValue::from_f64(x as f64),
);
let _ = Reflect::set(
&rect_dict,
&JsValue::from_str(WEBGPU_PROPERTY_Y),
&JsValue::from_f64(y as f64),
);
let _ = Reflect::set(
&rect_dict,
&JsValue::from_str(WEBGPU_PROPERTY_WIDTH),
&JsValue::from_f64(width as f64),
);
let _ = Reflect::set(
&rect_dict,
&JsValue::from_str(WEBGPU_PROPERTY_HEIGHT),
&JsValue::from_f64(height as f64),
);
let rect_js: JsValue = rect_dict.unchecked_into::<JsValue>();
if let Ok(set_fn) = Reflect::get(pass, &JsValue::from_str(WEBGPU_METHOD_SET_SCISSOR_RECT))
&& let Ok(set_callable) = set_fn.dyn_into::<Function>()
{
let _: Result<JsValue, JsValue> = set_callable.call1(pass, &rect_js);
}
}
pub fn set_blend_constant(&self, pass: &JsValue, r: f32, g: f32, b: f32, a: f32) {
let color_dict: Object = Object::new();
let _ = Reflect::set(
&color_dict,
&JsValue::from_str(WEBGPU_PROPERTY_R),
&JsValue::from_f64(r as f64),
);
let _ = Reflect::set(
&color_dict,
&JsValue::from_str(WEBGPU_PROPERTY_G),
&JsValue::from_f64(g as f64),
);
let _ = Reflect::set(
&color_dict,
&JsValue::from_str(WEBGPU_PROPERTY_B),
&JsValue::from_f64(b as f64),
);
let _ = Reflect::set(
&color_dict,
&JsValue::from_str(WEBGPU_PROPERTY_A),
&JsValue::from_f64(a as f64),
);
let color_js: JsValue = color_dict.unchecked_into::<JsValue>();
if let Ok(set_fn) = Reflect::get(pass, &JsValue::from_str(WEBGPU_METHOD_SET_BLEND_CONSTANT))
&& let Ok(set_callable) = set_fn.dyn_into::<Function>()
{
let _: Result<JsValue, JsValue> = set_callable.call1(pass, &color_js);
}
}
pub fn set_stencil_reference(&self, pass: &JsValue, reference: u32) {
if let Ok(set_fn) = Reflect::get(
pass,
&JsValue::from_str(WEBGPU_METHOD_SET_STENCIL_REFERENCE),
) && let Ok(set_callable) = set_fn.dyn_into::<Function>()
{
let _: Result<JsValue, JsValue> =
set_callable.call1(pass, &JsValue::from_f64(reference as f64));
}
}
pub fn set_bind_group_with_dynamic_offsets(
&self,
pass: &JsValue,
index: u32,
group: &JsValue,
dynamic_offsets: &[u32],
) {
if let Ok(set_fn) = Reflect::get(pass, &JsValue::from_str(WEBGPU_METHOD_SET_BIND_GROUP))
&& let Ok(set_callable) = set_fn.dyn_into::<Function>()
{
let offsets_array: Array = Array::new_with_length(dynamic_offsets.len() as u32);
for (i, off) in dynamic_offsets.iter().enumerate() {
let _ = offsets_array.set(i as u32, JsValue::from_f64(*off as f64));
}
let offsets_js: JsValue = offsets_array.unchecked_into::<JsValue>();
let _: Result<JsValue, JsValue> = set_callable.call4(
pass,
&JsValue::from_f64(index as f64),
group,
&offsets_js,
&JsValue::from_f64(0.0),
);
}
}
pub fn set_bind_group_compute_with_dynamic_offsets(
&self,
pass: &JsValue,
index: u32,
group: &JsValue,
dynamic_offsets: &[u32],
) {
if let Ok(set_fn) = Reflect::get(pass, &JsValue::from_str(WEBGPU_METHOD_SET_BIND_GROUP))
&& let Ok(set_callable) = set_fn.dyn_into::<Function>()
{
let offsets_array: Array = Array::new_with_length(dynamic_offsets.len() as u32);
for (i, off) in dynamic_offsets.iter().enumerate() {
let _ = offsets_array.set(i as u32, JsValue::from_f64(*off as f64));
}
let offsets_js: JsValue = offsets_array.unchecked_into::<JsValue>();
let _: Result<JsValue, JsValue> = set_callable.call4(
pass,
&JsValue::from_f64(index as f64),
group,
&offsets_js,
&JsValue::from_f64(0.0),
);
}
}
pub fn create_view(
&self,
texture: &JsValue,
descriptor: Option<&TextureViewDescriptor>,
) -> JsValue {
let create_view_fn: Function =
match Reflect::get(texture, &JsValue::from_str(WEBGPU_METHOD_CREATE_VIEW))
.ok()
.and_then(|v| v.dyn_into::<Function>().ok())
{
Some(f) => f,
None => return JsValue::UNDEFINED,
};
let desc_value: JsValue = match descriptor {
None => JsValue::UNDEFINED,
Some(d) => {
let dict: Object = Object::new();
if let Some(format) = d.get_format() {
let _ = Reflect::set(
&dict,
&JsValue::from_str(WEBGPU_PROPERTY_FORMAT),
&JsValue::from_str(format),
);
}
let _ = Reflect::set(
&dict,
&JsValue::from_str(WEBGPU_PROPERTY_DIMENSION),
&JsValue::from_str(d.effective_dimension()),
);
let _ = Reflect::set(
&dict,
&JsValue::from_str(WEBGPU_PROPERTY_ASPECT),
&JsValue::from_str(d.effective_aspect()),
);
let base_mip: u32 = d.get_base_mip_level();
if base_mip != 0 {
let _ = Reflect::set(
&dict,
&JsValue::from_str(WEBGPU_PROPERTY_BASE_MIP_LEVEL),
&JsValue::from_f64(base_mip as f64),
);
}
let mip_count: u32 = d.get_mip_level_count();
if mip_count != 0 {
let _ = Reflect::set(
&dict,
&JsValue::from_str(WEBGPU_PROPERTY_MIP_LEVEL_COUNT),
&JsValue::from_f64(mip_count as f64),
);
}
let base_array: u32 = d.get_base_array_layer();
if base_array != 0 {
let _ = Reflect::set(
&dict,
&JsValue::from_str(WEBGPU_PROPERTY_BASE_ARRAY_LAYER),
&JsValue::from_f64(base_array as f64),
);
}
let array_count: u32 = d.get_array_layer_count();
if array_count != 0 {
let _ = Reflect::set(
&dict,
&JsValue::from_str(WEBGPU_PROPERTY_ARRAY_LAYER_COUNT),
&JsValue::from_f64(array_count as f64),
);
}
dict.unchecked_into::<JsValue>()
}
};
create_view_fn
.call1(texture, &desc_value)
.unwrap_or(JsValue::UNDEFINED)
}
pub fn generate_mipmaps(&self, texture: &JsValue) {
if let Ok(gen_fn) = Reflect::get(texture, &JsValue::from_str(WEBGPU_METHOD_GENERATE_MIPMAP))
&& let Ok(gen_callable) = gen_fn.dyn_into::<Function>()
{
let _: Result<JsValue, JsValue> = gen_callable.call0(texture);
}
}
pub fn write_texture(&self, descriptor: &TextureWriteDescriptor) {
let queue: JsValue =
match Reflect::get(self.get_device(), &JsValue::from_str(WEBGPU_PROPERTY_QUEUE))
.ok()
.and_then(|v| v.dyn_into::<JsValue>().ok().into())
{
Some(q) => q,
None => return,
};
let layout_dict: Object = Object::new();
let _ = Reflect::set(
&layout_dict,
&JsValue::from_str(WEBGPU_PROPERTY_BYTES_PER_ROW),
&JsValue::from_f64(descriptor.get_bytes_per_row() as f64),
);
let _ = Reflect::set(
&layout_dict,
&JsValue::from_str(WEBGPU_PROPERTY_ROWS_PER_IMAGE),
&JsValue::from_f64(descriptor.get_rows_per_image() as f64),
);
let _ = Reflect::set(
&layout_dict,
&JsValue::from_str(WEBGPU_PROPERTY_OFFSET_BYTES),
&JsValue::from_f64(0.0),
);
let layout_js: JsValue = layout_dict.unchecked_into::<JsValue>();
let write_fn: Function =
match Reflect::get(&queue, &JsValue::from_str(WEBGPU_METHOD_WRITE_TEXTURE))
.ok()
.and_then(|v| v.dyn_into::<Function>().ok())
{
Some(f) => f,
None => return,
};
let dest_dict: Object = Object::new();
let _ = Reflect::set(
&dest_dict,
&JsValue::from_str(WEBGPU_PROPERTY_TEXTURE),
&descriptor.get_texture(),
);
let _ = Reflect::set(
&dest_dict,
&JsValue::from_str(WEBGPU_PROPERTY_MIP_LEVEL),
&JsValue::from_f64(descriptor.get_mip_level() as f64),
);
if let Some(origin) = descriptor.get_origin() {
let _ = Reflect::set(
&dest_dict,
&JsValue::from_str(WEBGPU_PROPERTY_ORIGIN),
&origin,
);
}
let dest_js: JsValue = dest_dict.unchecked_into::<JsValue>();
let data_js: JsValue = js_sys::Uint8Array::from(descriptor.get_data().as_slice()).into();
let size_value: JsValue = {
let bpr: u32 = descriptor.get_bytes_per_row();
let rows: u32 = if descriptor.get_rows_per_image() == 0 {
(descriptor.get_data().len() as u32) / bpr.max(1)
} else {
descriptor.get_rows_per_image()
};
let size_dict: Object = Object::new();
let _ = Reflect::set(
&size_dict,
&JsValue::from_str(WEBGPU_PROPERTY_WIDTH),
&JsValue::from_f64(bpr as f64),
);
let _ = Reflect::set(
&size_dict,
&JsValue::from_str(WEBGPU_PROPERTY_HEIGHT),
&JsValue::from_f64(rows as f64),
);
let _ = Reflect::set(
&size_dict,
&JsValue::from_str(WEBGPU_PROPERTY_DEPTH_OR_1),
&JsValue::from_f64(1.0),
);
size_dict.unchecked_into::<JsValue>()
};
let _: Result<JsValue, JsValue> =
write_fn.call4(&queue, &dest_js, &data_js, &layout_js, &size_value);
}
pub fn create_shader_module_with_label(&self, wgsl_source: &str, label: &str) -> JsValue {
let descriptor: Object = Object::new();
let _ = Reflect::set(
&descriptor,
&JsValue::from_str(WEBGPU_PROPERTY_CODE),
&JsValue::from_str(wgsl_source),
);
let _ = Reflect::set(
&descriptor,
&JsValue::from_str(WEBGPU_PROPERTY_LABEL),
&JsValue::from_str(label),
);
let desc_value: JsValue = descriptor.unchecked_into::<JsValue>();
if let Ok(create_fn) = Reflect::get(
self.get_device(),
&JsValue::from_str(WEBGPU_METHOD_CREATE_SHADER_MODULE),
) && let Ok(create_callable) = create_fn.dyn_into::<Function>()
{
return create_callable
.call1(self.get_device(), &desc_value)
.unwrap_or(JsValue::UNDEFINED);
}
JsValue::UNDEFINED
}
pub async fn read_buffer(&self, buffer: &JsValue, offset: u64, size: u64) -> Option<Vec<u8>> {
let map_fn: Function = Reflect::get(buffer, &JsValue::from_str(WEBGPU_METHOD_MAP_ASYNC))
.ok()
.and_then(|v| v.dyn_into::<Function>().ok())?;
let map_promise: js_sys::Promise = map_fn
.call3(
buffer,
&JsValue::from_f64(map_mode_for( true, false) as f64),
&JsValue::from_f64(offset as f64),
&JsValue::from_f64(size as f64),
)
.ok()?
.unchecked_into();
let _map_result = wasm_bindgen_futures::JsFuture::from(map_promise)
.await
.ok()?;
let get_range_fn: Function =
Reflect::get(buffer, &JsValue::from_str(WEBGPU_METHOD_GET_MAPPED_RANGE))
.ok()
.and_then(|v| v.dyn_into::<Function>().ok())?;
let array_buffer: js_sys::ArrayBuffer = get_range_fn
.call2(
buffer,
&JsValue::from_f64(offset as f64),
&JsValue::from_f64(size as f64),
)
.ok()?
.unchecked_into();
let u8_view: js_sys::Uint8Array = js_sys::Uint8Array::new(&array_buffer);
let mut out: Vec<u8> = vec![0u8; u8_view.length() as usize];
u8_view.copy_to(&mut out);
if let Ok(unmap_fn) = Reflect::get(buffer, &JsValue::from_str(WEBGPU_METHOD_UNMAP))
&& let Ok(unmap_callable) = unmap_fn.dyn_into::<Function>()
{
let _: Result<JsValue, JsValue> = unmap_callable.call0(buffer);
}
Some(out)
}
}
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 {}
impl WebGlRenderer {
pub fn is_available() -> bool {
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 = match document_value.create_element("canvas") {
Ok(element) => element,
Err(_) => return false,
};
let canvas: HtmlCanvasElement = element.unchecked_into();
canvas.get_context("webgl2").ok().flatten().is_some()
}
pub fn init(config: &RenderConfig) -> Result<WebGlRenderer, WebGlInitError> {
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(config.canvas_selector.as_ref())
.map_err(WebGlInitError::CanvasQuery)?
.ok_or_else(|| WebGlInitError::CanvasNotFound(config.canvas_selector.clone()))?;
let canvas: HtmlCanvasElement = element.unchecked_into();
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 context_object: Object = canvas
.get_context("webgl2")
.map_err(WebGlInitError::ContextLookup)?
.ok_or(WebGlInitError::ContextUnavailable)?;
let context: WebGl2RenderingContext = context_object
.dyn_into()
.map_err(|_| WebGlInitError::ContextCast)?;
context.viewport(0, 0, physical_width as i32, physical_height as i32);
Ok(WebGlRenderer {
context,
canvas,
width: physical_width,
height: physical_height,
})
}
pub fn create_program(
&self,
vertex_source: &str,
fragment_source: &str,
) -> Result<WebGlProgram, WebGlProgramError> {
let vertex_shader: WebGlShader =
self.compile_shader(WebGl2RenderingContext::VERTEX_SHADER, vertex_source)?;
let fragment_shader: WebGlShader =
self.compile_shader(WebGl2RenderingContext::FRAGMENT_SHADER, fragment_source)?;
let program: WebGlProgram = self.context.create_program().ok_or_else(|| {
WebGlProgramError::ProgramLink("createProgram returned null".to_string())
})?;
self.context.attach_shader(&program, &vertex_shader);
self.context.attach_shader(&program, &fragment_shader);
self.context.link_program(&program);
let linked: bool = self
.context
.get_program_parameter(&program, WebGl2RenderingContext::LINK_STATUS)
.as_bool()
.unwrap_or(false);
if !linked {
let log: String = self
.context
.get_program_info_log(&program)
.unwrap_or_default();
self.context.delete_program(Some(&program));
self.context.delete_shader(Some(&vertex_shader));
self.context.delete_shader(Some(&fragment_shader));
return Err(WebGlProgramError::ProgramLink(log));
}
self.context.delete_shader(Some(&vertex_shader));
self.context.delete_shader(Some(&fragment_shader));
Ok(program)
}
fn compile_shader(&self, kind: u32, source: &str) -> Result<WebGlShader, WebGlProgramError> {
let shader: WebGlShader = self.context.create_shader(kind).ok_or_else(|| {
WebGlProgramError::ShaderCompile("createShader returned null".to_string())
})?;
self.context.shader_source(&shader, source);
self.context.compile_shader(&shader);
let compiled: bool = self
.context
.get_shader_parameter(&shader, WebGl2RenderingContext::COMPILE_STATUS)
.as_bool()
.unwrap_or(false);
if !compiled {
let log: String = self
.context
.get_shader_info_log(&shader)
.unwrap_or_default();
self.context.delete_shader(Some(&shader));
return Err(WebGlProgramError::ShaderCompile(log));
}
Ok(shader)
}
pub fn set_uniform_2f(&self, program: &WebGlProgram, name: &str, x: f32, y: f32) {
let location: Option<WebGlUniformLocation> =
self.context.get_uniform_location(program, name);
self.context.uniform2f(location.as_ref(), x, y);
}
pub fn set_uniform_4fv(&self, program: &WebGlProgram, name: &str, data: &[f32]) {
let location: Option<WebGlUniformLocation> =
self.context.get_uniform_location(program, name);
self.context
.uniform4fv_with_f32_array(location.as_ref(), data);
}
pub fn render_frame(
&self,
program: &WebGlProgram,
clear_color: (f64, f64, f64, f64),
vertex_count: i32,
) {
let (r, g, b, a) = clear_color;
self.context
.viewport(0, 0, self.width as i32, self.height as i32);
self.context
.clear_color(r as f32, g as f32, b as f32, a as f32);
self.context.clear(WebGl2RenderingContext::COLOR_BUFFER_BIT);
self.context.use_program(Some(program));
self.context
.draw_arrays(WebGl2RenderingContext::TRIANGLES, 0, vertex_count);
}
pub fn resize(&mut self, physical_width: u32, physical_height: u32) {
self.canvas.set_width(physical_width);
self.canvas.set_height(physical_height);
self.width = physical_width;
self.height = physical_height;
self.context
.viewport(0, 0, physical_width as i32, physical_height as i32);
}
}
impl WebGlInitError {
pub fn code(&self) -> &'static str {
match self {
Self::CanvasNotFound(_) => "WEBGL_CANVAS_NOT_FOUND",
Self::CanvasQuery(_) => "WEBGL_CANVAS_QUERY",
Self::ContextUnavailable => "WEBGL_CONTEXT_UNAVAILABLE",
Self::ContextLookup(_) => "WEBGL_CONTEXT_LOOKUP",
Self::ContextCast => "WEBGL_CONTEXT_CAST",
}
}
pub fn js_error(&self) -> Option<&JsValue> {
match self {
Self::CanvasQuery(err) | Self::ContextLookup(err) => Some(err),
Self::CanvasNotFound(_) | Self::ContextUnavailable | Self::ContextCast => None,
}
}
}
impl std::fmt::Display for WebGlInitError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
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::ContextUnavailable => write!(
formatter,
"[{}] canvas.get_context('webgl2') returned null - the browser does not support WebGL 2 or the canvas already uses another context type",
self.code(),
),
Self::ContextLookup(err) => write!(
formatter,
"[{}] canvas.get_context('webgl2') threw: {}",
self.code(),
js_error_to_string(err),
),
Self::ContextCast => write!(
formatter,
"[{}] get_context('webgl2') result could not be cast to WebGl2RenderingContext",
self.code(),
),
}
}
}
impl std::error::Error for WebGlInitError {}
impl std::fmt::Display for WebGlProgramError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ShaderCompile(log) => write!(formatter, "shader compilation failed: {log}"),
Self::ProgramLink(log) => write!(formatter, "program link failed: {log}"),
}
}
}
impl std::error::Error for WebGlProgramError {}
impl Texture2DDescriptor {
pub fn default_for(width: u32, height: u32, format: &'static str) -> Self {
Self {
width,
height,
format,
mip_level_count: 1,
sample_count: 1,
usage: "TEXTURE_BINDING | COPY_DST | COPY_SRC",
}
}
}
impl GpuSamplerDescriptor {
pub fn default_sampler() -> Self {
Self {
mag_filter: WEBGPU_FILTER_MODE_NEAREST,
min_filter: WEBGPU_FILTER_MODE_NEAREST,
mipmap_filter: WEBGPU_FILTER_MODE_NEAREST,
address_mode_u: WEBGPU_ADDRESS_MODE_CLAMP_TO_EDGE,
address_mode_v: WEBGPU_ADDRESS_MODE_CLAMP_TO_EDGE,
address_mode_w: WEBGPU_ADDRESS_MODE_CLAMP_TO_EDGE,
compare: false,
}
}
}
impl RenderPassColorAttachment {
pub(crate) fn effective_load_op(&self) -> &'static str {
match (self.load_op, self.clear_value) {
(Some(op), _) => op,
(None, Some(_)) => WEBGPU_LOAD_OP_CLEAR,
(None, None) => WEBGPU_LOAD_OP_LOAD,
}
}
pub(crate) fn effective_store_op(&self) -> &'static str {
self.store_op.unwrap_or_else(|| {
default_color_store_op( false)
})
}
}
impl RenderPassDepthStencilAttachment {
pub(crate) fn effective_depth_load_op(&self) -> &'static str {
match (self.depth_load_op, self.depth_clear_value) {
(Some(op), _) => op,
(None, Some(_)) => WEBGPU_LOAD_OP_CLEAR,
(None, None) => WEBGPU_LOAD_OP_LOAD,
}
}
pub(crate) fn effective_depth_store_op(&self) -> &'static str {
self.depth_store_op.unwrap_or(WEBGPU_STORE_OP_STORE)
}
}
impl TextureViewDescriptor {
pub fn full() -> Self {
Self {
format: None,
dimension: None,
base_mip_level: 0,
mip_level_count: 0,
base_array_layer: 0,
array_layer_count: 0,
aspect: None,
}
}
pub(crate) fn effective_dimension(&self) -> &'static str {
self.dimension.unwrap_or(WEBGPU_TEXTURE_VIEW_DIMENSION_2D)
}
pub(crate) fn effective_aspect(&self) -> &'static str {
self.aspect.unwrap_or(WEBGPU_TEXTURE_ASPECT_ALL)
}
pub fn mip(level: u32) -> Self {
Self {
format: None,
dimension: None,
base_mip_level: level,
mip_level_count: 1,
base_array_layer: 0,
array_layer_count: 0,
aspect: None,
}
}
pub fn depth_only() -> Self {
Self {
format: None,
dimension: None,
base_mip_level: 0,
mip_level_count: 0,
base_array_layer: 0,
array_layer_count: 0,
aspect: Some(WEBGPU_TEXTURE_ASPECT_DEPTH_ONLY),
}
}
}
impl TextureWriteDescriptor {
pub fn for_2d(data: Vec<u8>, bytes_per_row: u32, texture: JsValue) -> Self {
Self {
data,
bytes_per_row,
rows_per_image: 0,
mip_level: 0,
texture,
origin: None,
flip_y: false,
}
}
}
impl VertexStepMode {
pub fn as_str(&self) -> &'static str {
match self {
Self::Vertex => "vertex",
Self::Instance => "instance",
}
}
}
impl BindGroupEntry {
pub(crate) fn binding(&self) -> u32 {
match self {
Self::Buffer { binding, .. }
| Self::Texture { binding, .. }
| Self::Sampler { binding, .. } => *binding,
}
}
}
pub(crate) fn pick_depth_format(high_precision: bool, with_stencil: bool) -> &'static str {
if with_stencil {
WEBGPU_DEPTH_FORMAT_DEPTH24_PLUS_STENCIL8
} else if high_precision {
WEBGPU_DEPTH_FORMAT_DEPTH32_FLOAT
} else if cfg!(target_arch = "wasm32") {
WEBGPU_DEPTH_FORMAT_DEPTH24_PLUS
} else {
WEBGPU_DEPTH_FORMAT_DEPTH16_UNORM
}
}
pub(crate) fn default_color_store_op(transient: bool) -> &'static str {
if transient {
WEBGPU_STORE_OP_DISCARD
} else {
WEBGPU_STORE_OP_STORE
}
}
pub(crate) fn map_mode_for(read: bool, write: bool) -> u32 {
let mut mode: u32 = 0;
if read {
mode |= WEBGPU_MAP_MODE_READ as u32;
}
if write {
mode |= WEBGPU_MAP_MODE_WRITE as u32;
}
mode
}
#[allow(dead_code)] pub(crate) fn primitive_topology_name(tag: u8) -> &'static str {
match tag {
0 => WEBGPU_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
1 => WEBGPU_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP,
2 => WEBGPU_PRIMITIVE_TOPOLOGY_LINE_LIST,
3 => WEBGPU_PRIMITIVE_TOPOLOGY_LINE_STRIP,
4 => WEBGPU_PRIMITIVE_TOPOLOGY_POINT_LIST,
_ => WEBGPU_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
}
}
pub(crate) fn texture_usage(
render_target: bool,
copy_src: bool,
copy_dst: bool,
sampled: bool,
storage: bool,
) -> u32 {
let mut usage: u32 = 0;
if render_target {
usage |= WEBGPU_TEXTURE_USAGE_RENDER_ATTACHMENT as u32;
}
if copy_src {
usage |= WEBGPU_TEXTURE_USAGE_COPY_SRC as u32;
}
if copy_dst {
usage |= WEBGPU_TEXTURE_USAGE_COPY_DST as u32;
}
if sampled {
usage |= WEBGPU_TEXTURE_USAGE_TEXTURE_BINDING as u32;
}
if storage {
usage |= WEBGPU_TEXTURE_USAGE_STORAGE_BINDING as u32;
}
usage
}
#[allow(dead_code)] pub(crate) fn buffer_usage(
vertex: bool,
index: bool,
uniform: bool,
storage: bool,
indirect: bool,
query_resolve: bool,
copy_src: bool,
copy_dst: bool,
) -> u32 {
let mut usage: u32 = 0;
if vertex {
usage |= WEBGPU_BUFFER_USAGE_VERTEX as u32;
}
if index {
usage |= WEBGPU_BUFFER_USAGE_INDEX as u32;
}
if uniform {
usage |= WEBGPU_BUFFER_USAGE_UNIFORM as u32;
}
if storage {
usage |= WEBGPU_BUFFER_USAGE_STORAGE as u32;
}
if indirect {
usage |= WEBGPU_BUFFER_USAGE_INDIRECT as u32;
}
if query_resolve {
usage |= WEBGPU_BUFFER_USAGE_QUERY_RESOLVE as u32;
}
if copy_src {
usage |= WEBGPU_BUFFER_USAGE_COPY_SRC as u32;
}
if copy_dst {
usage |= WEBGPU_BUFFER_USAGE_COPY_DST as u32;
}
usage
}
#[allow(dead_code)]
pub(crate) fn device_method_create_bind_group_layout() -> &'static str {
WEBGPU_METHOD_CREATE_BIND_GROUP_LAYOUT
}
#[allow(dead_code)]
pub(crate) fn device_method_create_pipeline_layout() -> &'static str {
WEBGPU_METHOD_CREATE_PIPELINE_LAYOUT
}
impl PendingErrorCell {
pub fn new() -> Self {
Self(UnsafeCell::new(None))
}
pub fn as_ptr(&self) -> *mut Option<JsValue> {
self.0.get()
}
}
impl Default for PendingErrorCell {
fn default() -> Self {
Self::new()
}
}
unsafe impl Sync for PendingErrorCell {}