From a57d0e12b3d955555d8e53b2394cd015fc00ac57 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Mon, 27 Jan 2020 20:35:38 -0500 Subject: [PATCH] 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. --- core/src/lib.rs | 2 +- web/src/render.rs | 97 +++++++++++++++++++++++++++++++++-------------- 2 files changed, 70 insertions(+), 29 deletions(-) diff --git a/core/src/lib.rs b/core/src/lib.rs index 4e2e66f8e..b23c5faaa 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -9,7 +9,7 @@ extern crate smallvec; mod avm1; mod bounding_box; mod character; -mod color_transform; +pub mod color_transform; mod context; pub mod events; mod font; diff --git a/web/src/render.rs b/web/src/render.rs index 464ce2ebf..43379f472 100644 --- a/web/src/render.rs +++ b/web/src/render.rs @@ -3,6 +3,7 @@ use ruffle_core::backend::render::{ swf, swf::CharacterId, BitmapHandle, BitmapInfo, Color, Letterbox, RenderBackend, ShapeHandle, Transform, }; +use ruffle_core::color_transform::ColorTransform; use ruffle_core::shape_utils::DrawCommand; use std::collections::HashMap; use std::convert::TryInto; @@ -19,7 +20,6 @@ pub struct WebCanvasRenderBackend { render_targets: Vec<(HtmlCanvasElement, CanvasRenderingContext2d)>, cur_render_target: usize, color_matrix: Element, - last_matrix_str: Option, shapes: Vec, bitmaps: Vec, id_to_bitmap: HashMap, @@ -32,13 +32,28 @@ pub struct WebCanvasRenderBackend { /// Canvas-drawable shape data extracted from an SWF file. struct ShapeData(Vec); +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. enum CanvasDrawCommand { /// A command to draw a path stroke with a given style. Stroke { path: Path2d, line_width: f64, - stroke_style: String, + stroke_style: CanvasColor, line_cap: String, line_join: String, miter_limit: f64, @@ -59,12 +74,22 @@ enum CanvasDrawCommand { } enum CanvasFillStyle { - Color(String), + Color(CanvasColor), #[allow(dead_code)] Gradient(CanvasGradient), Pattern(CanvasPattern), } +impl CanvasFillStyle { + /// Attempt to apply a color transformation to this fill style. + fn color_transform(&self, cxform: &ColorTransform) -> Option { + match self { + Self::Color(cc) => Some(Self::Color(cc.color_transform(cxform))), + _ => None, + } + } +} + #[allow(dead_code)] struct BitmapData { image: HtmlImageElement, @@ -166,7 +191,6 @@ impl WebCanvasRenderBackend { render_targets, cur_render_target: 0, color_matrix, - last_matrix_str: None, context, shapes: vec![], bitmaps: vec![], @@ -274,7 +298,11 @@ impl WebCanvasRenderBackend { f64::from(matrix.ty) / 20.0, ) .unwrap(); + } + #[allow(clippy::float_cmp)] + #[inline] + fn set_color_filter(&self, transform: &Transform) { let color_transform = &transform.color_transform; if color_transform.r_mult == 1.0 && color_transform.g_mult == 1.0 @@ -306,22 +334,17 @@ impl WebCanvasRenderBackend { a_add ); - if self.last_matrix_str.as_ref() != Some(&matrix_str) { - self.color_matrix - .set_attribute("values", &matrix_str) - .unwrap(); + self.color_matrix + .set_attribute("values", &matrix_str) + .unwrap(); - self.context.set_filter("url('#_cm')"); - - self.last_matrix_str = Some(matrix_str); - } + self.context.set_filter("url('#_cm')"); } } #[inline] - fn clear_transform(&mut self) { + fn clear_color_filter(&self) { self.context.set_filter("none"); - self.last_matrix_str = None; self.context.set_global_alpha(1.0); } } @@ -507,12 +530,13 @@ impl RenderBackend for WebCanvasRenderBackend { fn render_bitmap(&mut self, bitmap: BitmapHandle, transform: &Transform) { self.set_transform(transform); + self.set_color_filter(transform); if let Some(bitmap) = self.bitmaps.get(bitmap.0) { let _ = self .context .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) { @@ -521,8 +545,14 @@ impl RenderBackend for WebCanvasRenderBackend { for command in shape.0.iter() { match command { CanvasDrawCommand::Fill { path, fill_style } => { - match fill_style { - CanvasFillStyle::Color(color) => { + let xformed_fill_style = + 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)) } CanvasFillStyle::Gradient(grad) => self.context.set_fill_style(grad), @@ -530,6 +560,10 @@ impl RenderBackend for WebCanvasRenderBackend { }; self.context.fill_with_path_2d(&path); + + if xformed_fill_style.is_none() { + self.clear_color_filter(); + } } CanvasDrawCommand::Stroke { path, @@ -539,12 +573,14 @@ impl RenderBackend for WebCanvasRenderBackend { line_join, miter_limit, } => { + let xformed_stroke_style = + stroke_style.color_transform(&transform.color_transform); self.context.set_line_width(*line_width); self.context.set_line_cap(&line_cap); self.context.set_line_join(&line_join); self.context.set_miter_limit(*miter_limit); 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); } CanvasDrawCommand::DrawImage { @@ -559,7 +595,6 @@ impl RenderBackend for WebCanvasRenderBackend { } } } - self.clear_transform(); } fn draw_pause_overlay(&mut self) { @@ -1122,12 +1157,12 @@ fn swf_shape_to_canvas_commands( match path { DrawPath::Fill { style, commands } => { let fill_style = match style { - FillStyle::Color(Color { r, g, b, a }) => CanvasFillStyle::Color(format!( - "rgba({},{},{},{})", - r, - g, - b, - f32::from(*a) / 255.0 + FillStyle::Color(Color { r, g, b, a }) => CanvasFillStyle::Color(CanvasColor( + format!("rgba({},{},{},{})", r, g, b, f32::from(*a) / 255.0), + *r, + *g, + *b, + *a, )), FillStyle::LinearGradient(_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 // 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 stroke_style = format!( - "rgba({},{},{},{})", - style.color.r, style.color.g, style.color.b, style.color.a + let stroke_style = CanvasColor( + format!( + "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 { LineCapStyle::Round => "round",