Implement OffscreenCanvas support

This commit is contained in:
dAxpeDDa 2023-06-05 01:14:14 +02:00 committed by daxpedda
parent 68ec5a52d3
commit 6c78268502
2 changed files with 124 additions and 28 deletions

View file

@ -51,12 +51,21 @@ foreign-types = "0.3.0"
objc = "0.2.7"
[target.'cfg(target_arch = "wasm32")'.dependencies]
js-sys = "0.3.55"
wasm-bindgen = "0.2.78"
js-sys = "0.3.63"
wasm-bindgen = "0.2.86"
[target.'cfg(target_arch = "wasm32")'.dependencies.web-sys]
version = "0.3.55"
features = ["CanvasRenderingContext2d", "Document", "Element", "HtmlCanvasElement", "ImageData", "Window"]
features = [
"CanvasRenderingContext2d",
"Document",
"Element",
"HtmlCanvasElement",
"ImageData",
"OffscreenCanvas",
"OffscreenCanvasRenderingContext2d",
"Window",
]
[target.'cfg(target_os = "redox")'.dependencies]
redox_syscall = "0.3"

View file

@ -2,11 +2,12 @@
#![allow(clippy::uninlined_format_args)]
use js_sys::Object;
use raw_window_handle::WebWindowHandle;
use wasm_bindgen::JsCast;
use web_sys::CanvasRenderingContext2d;
use web_sys::HtmlCanvasElement;
use wasm_bindgen::{JsCast, JsValue};
use web_sys::ImageData;
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement};
use web_sys::{OffscreenCanvas, OffscreenCanvasRenderingContext2d};
use crate::error::SwResultExt;
use crate::{Rect, SoftBufferError};
@ -33,11 +34,8 @@ impl WebDisplayImpl {
}
pub struct WebImpl {
/// The handle to the canvas that we're drawing to.
canvas: HtmlCanvasElement,
/// The 2D rendering context for the canvas.
ctx: CanvasRenderingContext2d,
/// The handle and context to the canvas that we're drawing to.
canvas: Canvas,
/// The buffer that we're drawing to.
buffer: Vec<u32>,
@ -49,6 +47,19 @@ pub struct WebImpl {
size: Option<(NonZeroU32, NonZeroU32)>,
}
/// Holding canvas and context for [`HtmlCanvasElement`] or [`OffscreenCanvas`],
/// since they have different types.
enum Canvas {
Canvas {
canvas: HtmlCanvasElement,
ctx: CanvasRenderingContext2d,
},
OffscreenCanvas {
canvas: OffscreenCanvas,
ctx: OffscreenCanvasRenderingContext2d,
},
}
impl WebImpl {
pub fn new(display: &WebDisplayImpl, handle: WebWindowHandle) -> Result<Self, SoftBufferError> {
let canvas: HtmlCanvasElement = display
@ -64,25 +75,46 @@ impl WebImpl {
}
fn from_canvas(canvas: HtmlCanvasElement) -> Result<Self, SoftBufferError> {
let ctx = canvas
.get_context("2d")
.ok()
.swbuf_err("Canvas already controlled using `OffscreenCanvas`")?
.swbuf_err(
"A canvas context other than `CanvasRenderingContext2d` was already created",
)?
.dyn_into()
.expect("`getContext(\"2d\") didn't return a `CanvasRenderingContext2d`");
let ctx = Self::resolve_ctx(canvas.get_context("2d").ok(), "CanvasRenderingContext2d")?;
Ok(Self {
canvas,
ctx,
canvas: Canvas::Canvas { canvas, ctx },
buffer: Vec::new(),
buffer_presented: false,
size: None,
})
}
fn from_offscreen_canvas(canvas: OffscreenCanvas) -> Result<Self, SoftBufferError> {
let ctx = Self::resolve_ctx(
canvas.get_context("2d").ok(),
"OffscreenCanvasRenderingContext2d",
)?;
Ok(Self {
canvas: Canvas::OffscreenCanvas { canvas, ctx },
buffer: Vec::new(),
buffer_presented: false,
size: None,
})
}
/// De-duplicates the error handling between `HtmlCanvasElement` and `OffscreenCanvas`.
fn resolve_ctx<T: JsCast>(
result: Option<Option<Object>>,
name: &str,
) -> Result<T, SoftBufferError> {
let ctx = result
.swbuf_err("Canvas already controlled using `OffscreenCanvas`")?
.swbuf_err(format!(
"A canvas context other than `{name}` was already created"
))?
.dyn_into()
.unwrap_or_else(|_| panic!("`getContext(\"2d\") didn't return a `{name}`"));
Ok(ctx)
}
/// Resize the canvas to the given dimensions.
pub(crate) fn resize(
&mut self,
@ -121,7 +153,6 @@ impl WebImpl {
let result = {
use js_sys::{Uint8Array, Uint8ClampedArray};
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::JsValue;
#[wasm_bindgen]
extern "C" {
@ -147,13 +178,11 @@ impl WebImpl {
for rect in damage {
// This can only throw an error if `data` is detached, which is impossible.
self.ctx
.put_image_data_with_dirty_x_and_dirty_y_and_dirty_width_and_dirty_height(
self.canvas
.put_image_data(
&image_data,
rect.x.into(),
rect.y.into(),
rect.x.into(),
rect.y.into(),
rect.width.get().into(),
rect.height.get().into(),
)
@ -172,7 +201,7 @@ impl WebImpl {
.expect("Must set size of surface before calling `fetch()`");
let image_data = self
.ctx
.canvas
.get_image_data(0., 0., width.get().into(), height.get().into())
.ok()
// TODO: Can also error if width or height are 0.
@ -195,6 +224,12 @@ pub trait SurfaceExtWeb: Sized {
/// - If the canvas was already controlled by an `OffscreenCanvas`.
/// - If a another context then "2d" was already created for this canvas.
fn from_canvas(canvas: HtmlCanvasElement) -> Result<Self, SoftBufferError>;
/// Creates a new instance of this struct, using the provided [`HtmlCanvasElement`].
///
/// # Errors
/// If a another context then "2d" was already created for this canvas.
fn from_offscreen_canvas(offscreen_canvas: OffscreenCanvas) -> Result<Self, SoftBufferError>;
}
impl SurfaceExtWeb for crate::Surface {
@ -206,6 +241,58 @@ impl SurfaceExtWeb for crate::Surface {
_marker: PhantomData,
})
}
fn from_offscreen_canvas(offscreen_canvas: OffscreenCanvas) -> Result<Self, SoftBufferError> {
let imple = crate::SurfaceDispatch::Web(WebImpl::from_offscreen_canvas(offscreen_canvas)?);
Ok(Self {
surface_impl: Box::new(imple),
_marker: PhantomData,
})
}
}
impl Canvas {
fn set_width(&self, width: u32) {
match self {
Self::Canvas { canvas, .. } => canvas.set_width(width),
Self::OffscreenCanvas { canvas, .. } => canvas.set_width(width),
}
}
fn set_height(&self, height: u32) {
match self {
Self::Canvas { canvas, .. } => canvas.set_height(height),
Self::OffscreenCanvas { canvas, .. } => canvas.set_height(height),
}
}
fn get_image_data(&self, sx: f64, sy: f64, sw: f64, sh: f64) -> Result<ImageData, JsValue> {
match self {
Canvas::Canvas { ctx, .. } => ctx.get_image_data(sx, sy, sw, sh),
Canvas::OffscreenCanvas { ctx, .. } => ctx.get_image_data(sx, sy, sw, sh),
}
}
fn put_image_data(
&self,
imagedata: &ImageData,
dx: f64,
dy: f64,
widht: f64,
height: f64,
) -> Result<(), JsValue> {
match self {
Self::Canvas { ctx, .. } => ctx
.put_image_data_with_dirty_x_and_dirty_y_and_dirty_width_and_dirty_height(
imagedata, dx, dy, dx, dy, widht, height,
),
Self::OffscreenCanvas { ctx, .. } => ctx
.put_image_data_with_dirty_x_and_dirty_y_and_dirty_width_and_dirty_height(
imagedata, dx, dy, dx, dy, widht, height,
),
}
}
}
pub struct BufferImpl<'a> {