web: Initial masking support
Also add web/src/utils.rs for some methods to ease management of errors for wasm_bindgen.
This commit is contained in:
parent
28f0ce3c83
commit
ce9a0a8ef6
|
@ -1,6 +1,7 @@
|
||||||
//! Ruffle web frontend.
|
//! Ruffle web frontend.
|
||||||
mod audio;
|
mod audio;
|
||||||
mod render;
|
mod render;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
use crate::{audio::WebAudioBackend, render::WebCanvasRenderBackend};
|
use crate::{audio::WebAudioBackend, render::WebCanvasRenderBackend};
|
||||||
use generational_arena::{Arena, Index};
|
use generational_arena::{Arena, Index};
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::utils::JsResult;
|
||||||
use ruffle_core::backend::render::{
|
use ruffle_core::backend::render::{
|
||||||
swf, swf::CharacterId, BitmapHandle, Color, Letterbox, RenderBackend, ShapeHandle, Transform,
|
swf, swf::CharacterId, BitmapHandle, Color, Letterbox, RenderBackend, ShapeHandle, Transform,
|
||||||
};
|
};
|
||||||
|
@ -8,6 +9,9 @@ use web_sys::{CanvasRenderingContext2d, Element, HtmlCanvasElement, HtmlImageEle
|
||||||
pub struct WebCanvasRenderBackend {
|
pub struct WebCanvasRenderBackend {
|
||||||
canvas: HtmlCanvasElement,
|
canvas: HtmlCanvasElement,
|
||||||
context: CanvasRenderingContext2d,
|
context: CanvasRenderingContext2d,
|
||||||
|
root_canvas: HtmlCanvasElement,
|
||||||
|
render_targets: Vec<(HtmlCanvasElement, CanvasRenderingContext2d)>,
|
||||||
|
cur_render_target: usize,
|
||||||
color_matrix: Element,
|
color_matrix: Element,
|
||||||
shapes: Vec<ShapeData>,
|
shapes: Vec<ShapeData>,
|
||||||
bitmaps: Vec<BitmapData>,
|
bitmaps: Vec<BitmapData>,
|
||||||
|
@ -45,7 +49,7 @@ impl WebCanvasRenderBackend {
|
||||||
);
|
);
|
||||||
let context: CanvasRenderingContext2d = canvas
|
let context: CanvasRenderingContext2d = canvas
|
||||||
.get_context_with_context_options("2d", &context_options)
|
.get_context_with_context_options("2d", &context_options)
|
||||||
.map_err(|_| "Could not create context")?
|
.into_js_result()?
|
||||||
.ok_or("Could not create context")?
|
.ok_or("Could not create context")?
|
||||||
.dyn_into()
|
.dyn_into()
|
||||||
.map_err(|_| "Expected CanvasRenderingContext2d")?;
|
.map_err(|_| "Expected CanvasRenderingContext2d")?;
|
||||||
|
@ -119,8 +123,12 @@ impl WebCanvasRenderBackend {
|
||||||
.map(|s| s.contains("Firefox"))
|
.map(|s| s.contains("Firefox"))
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
let render_targets = vec![(canvas.clone(), context.clone())];
|
||||||
let renderer = Self {
|
let renderer = Self {
|
||||||
canvas: canvas.clone(),
|
canvas: canvas.clone(),
|
||||||
|
root_canvas: canvas.clone(),
|
||||||
|
render_targets,
|
||||||
|
cur_render_target: 0,
|
||||||
color_matrix,
|
color_matrix,
|
||||||
context,
|
context,
|
||||||
shapes: vec![],
|
shapes: vec![],
|
||||||
|
@ -161,6 +169,57 @@ impl WebCanvasRenderBackend {
|
||||||
&base64::encode(&png_data[..])
|
&base64::encode(&png_data[..])
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pushes a fresh canvas onto the stack to use as a render target.
|
||||||
|
fn push_render_target(&mut self) {
|
||||||
|
self.cur_render_target += 1;
|
||||||
|
if self.cur_render_target >= self.render_targets.len() {
|
||||||
|
// Create offscreen canvas to use as the render target.
|
||||||
|
let window = web_sys::window().unwrap();
|
||||||
|
let document = window.document().unwrap();
|
||||||
|
let canvas: HtmlCanvasElement = document
|
||||||
|
.create_element("canvas")
|
||||||
|
.unwrap()
|
||||||
|
.dyn_into()
|
||||||
|
.unwrap();
|
||||||
|
let context: CanvasRenderingContext2d = canvas
|
||||||
|
.get_context("2d")
|
||||||
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
|
.dyn_into()
|
||||||
|
.unwrap();
|
||||||
|
canvas
|
||||||
|
.style()
|
||||||
|
.set_property("display", "none")
|
||||||
|
.warn_on_error();
|
||||||
|
self.root_canvas.append_child(&canvas).warn_on_error();
|
||||||
|
self.render_targets.push((canvas, context));
|
||||||
|
}
|
||||||
|
|
||||||
|
let (canvas, context) = &self.render_targets[self.cur_render_target];
|
||||||
|
canvas.set_width(self.viewport_width);
|
||||||
|
canvas.set_height(self.viewport_height);
|
||||||
|
self.canvas = canvas.clone();
|
||||||
|
self.context = context.clone();
|
||||||
|
let width = self.canvas.width();
|
||||||
|
let height = self.canvas.height();
|
||||||
|
self.context
|
||||||
|
.clear_rect(0.0, 0.0, width.into(), height.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pop_render_target(&mut self) -> (HtmlCanvasElement, CanvasRenderingContext2d) {
|
||||||
|
if self.cur_render_target > 0 {
|
||||||
|
let out = (self.canvas.clone(), self.context.clone());
|
||||||
|
self.cur_render_target -= 1;
|
||||||
|
let (canvas, context) = &self.render_targets[self.cur_render_target];
|
||||||
|
self.canvas = canvas.clone();
|
||||||
|
self.context = context.clone();
|
||||||
|
out
|
||||||
|
} else {
|
||||||
|
log::error!("Render target stack underflow");
|
||||||
|
(self.canvas.clone(), self.context.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderBackend for WebCanvasRenderBackend {
|
impl RenderBackend for WebCanvasRenderBackend {
|
||||||
|
@ -441,9 +500,42 @@ impl RenderBackend for WebCanvasRenderBackend {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_mask(&mut self) {}
|
fn push_mask(&mut self) {
|
||||||
fn activate_mask(&mut self) {}
|
// In the canvas backend, masks are implemented using two render targets.
|
||||||
fn pop_mask(&mut self) {}
|
// We render the masker clips to the first render target.
|
||||||
|
self.push_render_target();
|
||||||
|
}
|
||||||
|
fn activate_mask(&mut self) {
|
||||||
|
// We render the maskee clips to the second render target.
|
||||||
|
self.push_render_target();
|
||||||
|
}
|
||||||
|
fn pop_mask(&mut self) {
|
||||||
|
let (maskee_canvas, _maskee_context) = self.pop_render_target();
|
||||||
|
let (masker_canvas, masker_context) = self.pop_render_target();
|
||||||
|
|
||||||
|
// We have to be sure to reset the transforms here so that
|
||||||
|
// the texture is drawn starting from the upper-left corner.
|
||||||
|
masker_context.reset_transform().warn_on_error();
|
||||||
|
self.context.reset_transform().warn_on_error();
|
||||||
|
|
||||||
|
// We draw the maskee onto the masker using the "source-in" blend mode.
|
||||||
|
// This will draw the clips in pixels only where the masker alpha > 0.
|
||||||
|
masker_context
|
||||||
|
.set_global_composite_operation("source-in")
|
||||||
|
.unwrap();
|
||||||
|
masker_context
|
||||||
|
.draw_image_with_html_canvas_element(&maskee_canvas, 0.0, 0.0)
|
||||||
|
.unwrap();
|
||||||
|
masker_context
|
||||||
|
.set_global_composite_operation("source-over")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Finally, we draw the finalized masked onto the main canvas.
|
||||||
|
self.context.reset_transform().warn_on_error();
|
||||||
|
self.context
|
||||||
|
.draw_image_with_html_canvas_element(&masker_canvas, 0.0, 0.0)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn swf_shape_to_svg(
|
fn swf_shape_to_svg(
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
//! Utility functions for the web backend.
|
||||||
|
use wasm_bindgen::JsValue;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct JsError {
|
||||||
|
value: JsValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for JsError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
|
||||||
|
write!(f, "JS exception: {:?}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for JsError {}
|
||||||
|
|
||||||
|
pub trait JsResult<T> {
|
||||||
|
/// Converts a `JsValue` into a standard `Error`.
|
||||||
|
fn warn_on_error(&self);
|
||||||
|
fn into_js_result(self) -> Result<T, JsError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> JsResult<T> for Result<T, JsValue> {
|
||||||
|
#[inline]
|
||||||
|
fn warn_on_error(&self) {
|
||||||
|
if let Err(value) = self {
|
||||||
|
log::warn!("Unexpected JavaScript error: {:?}", value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn into_js_result(self) -> Result<T, JsError> {
|
||||||
|
self.map_err(|value| JsError { value })
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue