Implement OffscreenCanvas support
This commit is contained in:
parent
68ec5a52d3
commit
6c78268502
2 changed files with 124 additions and 28 deletions
15
Cargo.toml
15
Cargo.toml
|
|
@ -51,12 +51,21 @@ foreign-types = "0.3.0"
|
||||||
objc = "0.2.7"
|
objc = "0.2.7"
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
js-sys = "0.3.55"
|
js-sys = "0.3.63"
|
||||||
wasm-bindgen = "0.2.78"
|
wasm-bindgen = "0.2.86"
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies.web-sys]
|
[target.'cfg(target_arch = "wasm32")'.dependencies.web-sys]
|
||||||
version = "0.3.55"
|
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]
|
[target.'cfg(target_os = "redox")'.dependencies]
|
||||||
redox_syscall = "0.3"
|
redox_syscall = "0.3"
|
||||||
|
|
|
||||||
137
src/web.rs
137
src/web.rs
|
|
@ -2,11 +2,12 @@
|
||||||
|
|
||||||
#![allow(clippy::uninlined_format_args)]
|
#![allow(clippy::uninlined_format_args)]
|
||||||
|
|
||||||
|
use js_sys::Object;
|
||||||
use raw_window_handle::WebWindowHandle;
|
use raw_window_handle::WebWindowHandle;
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::{JsCast, JsValue};
|
||||||
use web_sys::CanvasRenderingContext2d;
|
|
||||||
use web_sys::HtmlCanvasElement;
|
|
||||||
use web_sys::ImageData;
|
use web_sys::ImageData;
|
||||||
|
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement};
|
||||||
|
use web_sys::{OffscreenCanvas, OffscreenCanvasRenderingContext2d};
|
||||||
|
|
||||||
use crate::error::SwResultExt;
|
use crate::error::SwResultExt;
|
||||||
use crate::{Rect, SoftBufferError};
|
use crate::{Rect, SoftBufferError};
|
||||||
|
|
@ -33,11 +34,8 @@ impl WebDisplayImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct WebImpl {
|
pub struct WebImpl {
|
||||||
/// The handle to the canvas that we're drawing to.
|
/// The handle and context to the canvas that we're drawing to.
|
||||||
canvas: HtmlCanvasElement,
|
canvas: Canvas,
|
||||||
|
|
||||||
/// The 2D rendering context for the canvas.
|
|
||||||
ctx: CanvasRenderingContext2d,
|
|
||||||
|
|
||||||
/// The buffer that we're drawing to.
|
/// The buffer that we're drawing to.
|
||||||
buffer: Vec<u32>,
|
buffer: Vec<u32>,
|
||||||
|
|
@ -49,6 +47,19 @@ pub struct WebImpl {
|
||||||
size: Option<(NonZeroU32, NonZeroU32)>,
|
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 {
|
impl WebImpl {
|
||||||
pub fn new(display: &WebDisplayImpl, handle: WebWindowHandle) -> Result<Self, SoftBufferError> {
|
pub fn new(display: &WebDisplayImpl, handle: WebWindowHandle) -> Result<Self, SoftBufferError> {
|
||||||
let canvas: HtmlCanvasElement = display
|
let canvas: HtmlCanvasElement = display
|
||||||
|
|
@ -64,25 +75,46 @@ impl WebImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_canvas(canvas: HtmlCanvasElement) -> Result<Self, SoftBufferError> {
|
fn from_canvas(canvas: HtmlCanvasElement) -> Result<Self, SoftBufferError> {
|
||||||
let ctx = canvas
|
let ctx = Self::resolve_ctx(canvas.get_context("2d").ok(), "CanvasRenderingContext2d")?;
|
||||||
.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`");
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
canvas,
|
canvas: Canvas::Canvas { canvas, ctx },
|
||||||
ctx,
|
|
||||||
buffer: Vec::new(),
|
buffer: Vec::new(),
|
||||||
buffer_presented: false,
|
buffer_presented: false,
|
||||||
size: None,
|
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.
|
/// Resize the canvas to the given dimensions.
|
||||||
pub(crate) fn resize(
|
pub(crate) fn resize(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
@ -121,7 +153,6 @@ impl WebImpl {
|
||||||
let result = {
|
let result = {
|
||||||
use js_sys::{Uint8Array, Uint8ClampedArray};
|
use js_sys::{Uint8Array, Uint8ClampedArray};
|
||||||
use wasm_bindgen::prelude::wasm_bindgen;
|
use wasm_bindgen::prelude::wasm_bindgen;
|
||||||
use wasm_bindgen::JsValue;
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
@ -147,13 +178,11 @@ impl WebImpl {
|
||||||
|
|
||||||
for rect in damage {
|
for rect in damage {
|
||||||
// This can only throw an error if `data` is detached, which is impossible.
|
// This can only throw an error if `data` is detached, which is impossible.
|
||||||
self.ctx
|
self.canvas
|
||||||
.put_image_data_with_dirty_x_and_dirty_y_and_dirty_width_and_dirty_height(
|
.put_image_data(
|
||||||
&image_data,
|
&image_data,
|
||||||
rect.x.into(),
|
rect.x.into(),
|
||||||
rect.y.into(),
|
rect.y.into(),
|
||||||
rect.x.into(),
|
|
||||||
rect.y.into(),
|
|
||||||
rect.width.get().into(),
|
rect.width.get().into(),
|
||||||
rect.height.get().into(),
|
rect.height.get().into(),
|
||||||
)
|
)
|
||||||
|
|
@ -172,7 +201,7 @@ impl WebImpl {
|
||||||
.expect("Must set size of surface before calling `fetch()`");
|
.expect("Must set size of surface before calling `fetch()`");
|
||||||
|
|
||||||
let image_data = self
|
let image_data = self
|
||||||
.ctx
|
.canvas
|
||||||
.get_image_data(0., 0., width.get().into(), height.get().into())
|
.get_image_data(0., 0., width.get().into(), height.get().into())
|
||||||
.ok()
|
.ok()
|
||||||
// TODO: Can also error if width or height are 0.
|
// 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 the canvas was already controlled by an `OffscreenCanvas`.
|
||||||
/// - If a another context then "2d" was already created for this canvas.
|
/// - If a another context then "2d" was already created for this canvas.
|
||||||
fn from_canvas(canvas: HtmlCanvasElement) -> Result<Self, SoftBufferError>;
|
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 {
|
impl SurfaceExtWeb for crate::Surface {
|
||||||
|
|
@ -206,6 +241,58 @@ impl SurfaceExtWeb for crate::Surface {
|
||||||
_marker: PhantomData,
|
_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> {
|
pub struct BufferImpl<'a> {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue