Implement OffscreenCanvas support
This commit is contained in:
parent
68ec5a52d3
commit
6c78268502
2 changed files with 124 additions and 28 deletions
137
src/web.rs
137
src/web.rs
|
|
@ -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> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue