Do not apply a color filter at all unless rendering a bitmap or gradient.

This significantly improves render times, as browsers appear to apply filters in the most general, inefficient way possible.
This commit is contained in:
David Wendt 2020-01-27 20:35:38 -05:00
parent 746971e38a
commit a57d0e12b3
2 changed files with 70 additions and 29 deletions

View File

@ -9,7 +9,7 @@ extern crate smallvec;
mod avm1; mod avm1;
mod bounding_box; mod bounding_box;
mod character; mod character;
mod color_transform; pub mod color_transform;
mod context; mod context;
pub mod events; pub mod events;
mod font; mod font;

View File

@ -3,6 +3,7 @@ use ruffle_core::backend::render::{
swf, swf::CharacterId, BitmapHandle, BitmapInfo, Color, Letterbox, RenderBackend, ShapeHandle, swf, swf::CharacterId, BitmapHandle, BitmapInfo, Color, Letterbox, RenderBackend, ShapeHandle,
Transform, Transform,
}; };
use ruffle_core::color_transform::ColorTransform;
use ruffle_core::shape_utils::DrawCommand; use ruffle_core::shape_utils::DrawCommand;
use std::collections::HashMap; use std::collections::HashMap;
use std::convert::TryInto; use std::convert::TryInto;
@ -19,7 +20,6 @@ pub struct WebCanvasRenderBackend {
render_targets: Vec<(HtmlCanvasElement, CanvasRenderingContext2d)>, render_targets: Vec<(HtmlCanvasElement, CanvasRenderingContext2d)>,
cur_render_target: usize, cur_render_target: usize,
color_matrix: Element, color_matrix: Element,
last_matrix_str: Option<String>,
shapes: Vec<ShapeData>, shapes: Vec<ShapeData>,
bitmaps: Vec<BitmapData>, bitmaps: Vec<BitmapData>,
id_to_bitmap: HashMap<CharacterId, BitmapHandle>, id_to_bitmap: HashMap<CharacterId, BitmapHandle>,
@ -32,13 +32,28 @@ pub struct WebCanvasRenderBackend {
/// Canvas-drawable shape data extracted from an SWF file. /// Canvas-drawable shape data extracted from an SWF file.
struct ShapeData(Vec<CanvasDrawCommand>); struct ShapeData(Vec<CanvasDrawCommand>);
struct CanvasColor(String, u8, u8, u8, u8);
impl CanvasColor {
/// Apply a color transformation to this color.
fn color_transform(&self, cxform: &ColorTransform) -> CanvasColor {
let CanvasColor(_, r, g, b, a) = self;
let r = (*r as f32 * cxform.r_mult + (cxform.r_add * 256.0)) as u8;
let g = (*g as f32 * cxform.g_mult + (cxform.g_add * 256.0)) as u8;
let b = (*b as f32 * cxform.b_mult + (cxform.b_add * 256.0)) as u8;
let a = (*a as f32 * cxform.a_mult + (cxform.a_add * 256.0)) as u8;
let colstring = format!("rgba({},{},{},{})", r, g, b, f32::from(a) / 255.0);
CanvasColor(colstring, r, g, b, a)
}
}
/// An individual command to be drawn to the canvas. /// An individual command to be drawn to the canvas.
enum CanvasDrawCommand { enum CanvasDrawCommand {
/// A command to draw a path stroke with a given style. /// A command to draw a path stroke with a given style.
Stroke { Stroke {
path: Path2d, path: Path2d,
line_width: f64, line_width: f64,
stroke_style: String, stroke_style: CanvasColor,
line_cap: String, line_cap: String,
line_join: String, line_join: String,
miter_limit: f64, miter_limit: f64,
@ -59,12 +74,22 @@ enum CanvasDrawCommand {
} }
enum CanvasFillStyle { enum CanvasFillStyle {
Color(String), Color(CanvasColor),
#[allow(dead_code)] #[allow(dead_code)]
Gradient(CanvasGradient), Gradient(CanvasGradient),
Pattern(CanvasPattern), Pattern(CanvasPattern),
} }
impl CanvasFillStyle {
/// Attempt to apply a color transformation to this fill style.
fn color_transform(&self, cxform: &ColorTransform) -> Option<CanvasFillStyle> {
match self {
Self::Color(cc) => Some(Self::Color(cc.color_transform(cxform))),
_ => None,
}
}
}
#[allow(dead_code)] #[allow(dead_code)]
struct BitmapData { struct BitmapData {
image: HtmlImageElement, image: HtmlImageElement,
@ -166,7 +191,6 @@ impl WebCanvasRenderBackend {
render_targets, render_targets,
cur_render_target: 0, cur_render_target: 0,
color_matrix, color_matrix,
last_matrix_str: None,
context, context,
shapes: vec![], shapes: vec![],
bitmaps: vec![], bitmaps: vec![],
@ -274,7 +298,11 @@ impl WebCanvasRenderBackend {
f64::from(matrix.ty) / 20.0, f64::from(matrix.ty) / 20.0,
) )
.unwrap(); .unwrap();
}
#[allow(clippy::float_cmp)]
#[inline]
fn set_color_filter(&self, transform: &Transform) {
let color_transform = &transform.color_transform; let color_transform = &transform.color_transform;
if color_transform.r_mult == 1.0 if color_transform.r_mult == 1.0
&& color_transform.g_mult == 1.0 && color_transform.g_mult == 1.0
@ -306,22 +334,17 @@ impl WebCanvasRenderBackend {
a_add a_add
); );
if self.last_matrix_str.as_ref() != Some(&matrix_str) { self.color_matrix
self.color_matrix .set_attribute("values", &matrix_str)
.set_attribute("values", &matrix_str) .unwrap();
.unwrap();
self.context.set_filter("url('#_cm')"); self.context.set_filter("url('#_cm')");
self.last_matrix_str = Some(matrix_str);
}
} }
} }
#[inline] #[inline]
fn clear_transform(&mut self) { fn clear_color_filter(&self) {
self.context.set_filter("none"); self.context.set_filter("none");
self.last_matrix_str = None;
self.context.set_global_alpha(1.0); self.context.set_global_alpha(1.0);
} }
} }
@ -507,12 +530,13 @@ impl RenderBackend for WebCanvasRenderBackend {
fn render_bitmap(&mut self, bitmap: BitmapHandle, transform: &Transform) { fn render_bitmap(&mut self, bitmap: BitmapHandle, transform: &Transform) {
self.set_transform(transform); self.set_transform(transform);
self.set_color_filter(transform);
if let Some(bitmap) = self.bitmaps.get(bitmap.0) { if let Some(bitmap) = self.bitmaps.get(bitmap.0) {
let _ = self let _ = self
.context .context
.draw_image_with_html_image_element(&bitmap.image, 0.0, 0.0); .draw_image_with_html_image_element(&bitmap.image, 0.0, 0.0);
} }
self.clear_transform(); self.clear_color_filter();
} }
fn render_shape(&mut self, shape: ShapeHandle, transform: &Transform) { fn render_shape(&mut self, shape: ShapeHandle, transform: &Transform) {
@ -521,8 +545,14 @@ impl RenderBackend for WebCanvasRenderBackend {
for command in shape.0.iter() { for command in shape.0.iter() {
match command { match command {
CanvasDrawCommand::Fill { path, fill_style } => { CanvasDrawCommand::Fill { path, fill_style } => {
match fill_style { let xformed_fill_style =
CanvasFillStyle::Color(color) => { fill_style.color_transform(&transform.color_transform);
if xformed_fill_style.is_none() {
self.set_color_filter(transform);
}
match xformed_fill_style.as_ref().unwrap_or(fill_style) {
CanvasFillStyle::Color(CanvasColor(color, ..)) => {
self.context.set_fill_style(&JsValue::from_str(&color)) self.context.set_fill_style(&JsValue::from_str(&color))
} }
CanvasFillStyle::Gradient(grad) => self.context.set_fill_style(grad), CanvasFillStyle::Gradient(grad) => self.context.set_fill_style(grad),
@ -530,6 +560,10 @@ impl RenderBackend for WebCanvasRenderBackend {
}; };
self.context.fill_with_path_2d(&path); self.context.fill_with_path_2d(&path);
if xformed_fill_style.is_none() {
self.clear_color_filter();
}
} }
CanvasDrawCommand::Stroke { CanvasDrawCommand::Stroke {
path, path,
@ -539,12 +573,14 @@ impl RenderBackend for WebCanvasRenderBackend {
line_join, line_join,
miter_limit, miter_limit,
} => { } => {
let xformed_stroke_style =
stroke_style.color_transform(&transform.color_transform);
self.context.set_line_width(*line_width); self.context.set_line_width(*line_width);
self.context.set_line_cap(&line_cap); self.context.set_line_cap(&line_cap);
self.context.set_line_join(&line_join); self.context.set_line_join(&line_join);
self.context.set_miter_limit(*miter_limit); self.context.set_miter_limit(*miter_limit);
self.context self.context
.set_stroke_style(&JsValue::from_str(&stroke_style)); .set_stroke_style(&JsValue::from_str(&xformed_stroke_style.0));
self.context.stroke_with_path(&path); self.context.stroke_with_path(&path);
} }
CanvasDrawCommand::DrawImage { CanvasDrawCommand::DrawImage {
@ -559,7 +595,6 @@ impl RenderBackend for WebCanvasRenderBackend {
} }
} }
} }
self.clear_transform();
} }
fn draw_pause_overlay(&mut self) { fn draw_pause_overlay(&mut self) {
@ -1122,12 +1157,12 @@ fn swf_shape_to_canvas_commands(
match path { match path {
DrawPath::Fill { style, commands } => { DrawPath::Fill { style, commands } => {
let fill_style = match style { let fill_style = match style {
FillStyle::Color(Color { r, g, b, a }) => CanvasFillStyle::Color(format!( FillStyle::Color(Color { r, g, b, a }) => CanvasFillStyle::Color(CanvasColor(
"rgba({},{},{},{})", format!("rgba({},{},{},{})", r, g, b, f32::from(*a) / 255.0),
r, *r,
g, *g,
b, *b,
f32::from(*a) / 255.0 *a,
)), )),
FillStyle::LinearGradient(_gradient) => return None, FillStyle::LinearGradient(_gradient) => return None,
FillStyle::RadialGradient(_gradient) => return None, FillStyle::RadialGradient(_gradient) => return None,
@ -1204,9 +1239,15 @@ fn swf_shape_to_canvas_commands(
// Therefore, we clamp the stroke width to 1 pixel (20 twips). This won't be 100% accurate // Therefore, we clamp the stroke width to 1 pixel (20 twips). This won't be 100% accurate
// if the shape is scaled, but it looks much closer to the Flash Player. // if the shape is scaled, but it looks much closer to the Flash Player.
let line_width = std::cmp::max(style.width.get(), 20); let line_width = std::cmp::max(style.width.get(), 20);
let stroke_style = format!( let stroke_style = CanvasColor(
"rgba({},{},{},{})", format!(
style.color.r, style.color.g, style.color.b, style.color.a "rgba({},{},{},{})",
style.color.r, style.color.g, style.color.b, style.color.a
),
style.color.r,
style.color.g,
style.color.b,
style.color.a,
); );
let line_cap = match style.start_cap { let line_cap = match style.start_cap {
LineCapStyle::Round => "round", LineCapStyle::Round => "round",