From 994f04c40a6b23303018712fc3c7f66d7aca691b Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Tue, 3 Nov 2015 19:24:37 -0800 Subject: [PATCH] Properly blur box shadows, and correctly calculate the new texture coordinates when clipping rectangles. We use a closed form formula for computing Gaussian blurs of a box shadow. Assuming the blur radius is less than the size of the box, the maximum per-pixel error from this formula has been observed to be less than 3% in practice. It avoids the creation of intermediate textures. --- res/box-shadow-corner.fs.glsl | 11 - res/box_shadow.fs.glsl | 33 ++ ...adow-corner.vs.glsl => box_shadow.vs.glsl} | 0 src/clipper.rs | 127 ++---- src/internal_types.rs | 120 +++++- src/render_backend.rs | 400 ++++++++++-------- src/renderer.rs | 44 +- src/resource_cache.rs | 2 +- src/resource_list.rs | 23 +- src/texture_cache.rs | 15 +- src/util.rs | 9 + 11 files changed, 467 insertions(+), 317 deletions(-) delete mode 100644 res/box-shadow-corner.fs.glsl create mode 100644 res/box_shadow.fs.glsl rename res/{box-shadow-corner.vs.glsl => box_shadow.vs.glsl} (100%) diff --git a/res/box-shadow-corner.fs.glsl b/res/box-shadow-corner.fs.glsl deleted file mode 100644 index 443e2656b8..0000000000 --- a/res/box-shadow-corner.fs.glsl +++ /dev/null @@ -1,11 +0,0 @@ -void main(void) -{ - vec2 lPosition = vPosition - vBorderPosition.zw; - vec2 lArcCenter = vDestTextureSize; - float lArcRadius = vBorderRadii.x; - float lDistance = distance(lPosition, vec2(lArcCenter)); - float lValue = clamp(lDistance, lArcRadius - vBlurRadius, lArcRadius + vBlurRadius); - lValue = ((lValue - lArcRadius) / vBlurRadius + 1.0) / 2.0; - SetFragColor(vColor - vec4(lValue)); -} - diff --git a/res/box_shadow.fs.glsl b/res/box_shadow.fs.glsl new file mode 100644 index 0000000000..2d83b576f4 --- /dev/null +++ b/res/box_shadow.fs.glsl @@ -0,0 +1,33 @@ +float erf(float x) { + bool negative = x < 0.0; + if (negative) + x = -x; + float x2 = x * x; + float x3 = x2 * x; + float x4 = x2 * x2; + float denom = 1.0 + 0.278393 * x + 0.230389 * x2 + 0.000972 * x3 + 0.078108 * x4; + float result = 1.0 - 1.0 / (denom * denom * denom * denom); + return negative ? -result : result; +} + +void main(void) +{ + float range = int(vBlurRadius) * 3.0; + float sigma = vBlurRadius / 2.0; + float sigmaSqrt2 = sigma * 1.41421356237; + + vec2 position = vPosition - vBorderPosition.zw; + vec2 arcCenter = vDestTextureSize; + float arcRadius = vBorderRadii.x; + float distance = distance(position, vec2(arcCenter)); + float value = clamp(distance, arcRadius - vBlurRadius, arcRadius + vBlurRadius); + float minValue = min(value - range, arcRadius) - value; + float maxValue = min(value + range, arcRadius) - value; + if (minValue < maxValue) { + value = 1.0 - 0.5 * (erf(maxValue / sigmaSqrt2) - erf(minValue / sigmaSqrt2)); + } else { + value = 0.0; + } + SetFragColor(vColor - vec4(value)); +} + diff --git a/res/box-shadow-corner.vs.glsl b/res/box_shadow.vs.glsl similarity index 100% rename from res/box-shadow-corner.vs.glsl rename to res/box_shadow.vs.glsl diff --git a/src/clipper.rs b/src/clipper.rs index f6f59d111a..457b6de014 100644 --- a/src/clipper.rs +++ b/src/clipper.rs @@ -1,6 +1,6 @@ use euclid::{Point2D, Rect, Size2D}; use internal_types::{ClipRectToRegionMaskResult, ClipRectToRegionResult, PolygonPosColorUv}; -use internal_types::{RectPosUv, WorkVertex}; +use internal_types::{RectPosUv, RectUv, WorkVertex}; use render_backend::MAX_RECT; use simd::f32x4; use std::mem; @@ -380,63 +380,36 @@ pub trait Polygon : Sized { fn intersects_rect(&self, rect: &Rect) -> bool; } +impl RectPosUv { + fn push_clipped_rect(&self, clipped_rect: &Rect, output: &mut Vec) { + if util::rect_is_empty(&clipped_rect) { + return + } + + let uv_tl = util::bilerp(&clipped_rect.origin, &self.pos, &self.uv); + let uv_tr = util::bilerp(&clipped_rect.top_right(), &self.pos, &self.uv); + let uv_br = util::bilerp(&clipped_rect.bottom_right(), &self.pos, &self.uv); + let uv_bl = util::bilerp(&clipped_rect.bottom_left(), &self.pos, &self.uv); + + output.push(RectPosUv { + pos: *clipped_rect, + uv: RectUv { + top_left: uv_tl, + top_right: uv_tr, + bottom_left: uv_bl, + bottom_right: uv_br, + } + }); + } +} + impl Polygon for RectPosUv { fn clip_to_rect(&self, _: &mut ShClipBuffers, clip_rect: &Rect, output: &mut Vec) { - for clipped_rect in self.pos.intersection(clip_rect).into_iter() { - if util::rect_is_empty(&clipped_rect) { - continue - } - - // de-simd'd code: - // let cx0 = clipped_rect.origin.x; - // let cy0 = clipped_rect.origin.y; - // let cx1 = cx0 + clipped_rect.size.width; - // let cy1 = cy0 + clipped_rect.size.height; - - // let f0 = (cx0 - pos.origin.x) / pos.size.width; - // let f1 = (cy0 - pos.origin.y) / pos.size.height; - // let f2 = (cx1 - pos.origin.x) / pos.size.width; - // let f3 = (cy1 - pos.origin.y) / pos.size.height; - - // ClipRectResult { - // x0: cx0, - // y0: cy0, - // x1: cx1, - // y1: cy1, - // u0: uv.origin.x + f0 * uv.size.width, - // v0: uv.origin.y + f1 * uv.size.height, - // u1: uv.origin.x + f2 * uv.size.width, - // v1: uv.origin.y + f3 * uv.size.height, - // } - - let clip = f32x4::new(clipped_rect.origin.x, - clipped_rect.origin.y, - clipped_rect.origin.x + clipped_rect.size.width, - clipped_rect.origin.y + clipped_rect.size.height); - - let origins = f32x4::new(self.pos.origin.x, self.pos.origin.y, - self.pos.origin.x, self.pos.origin.y); - - let sizes = f32x4::new(self.pos.size.width, self.pos.size.height, - self.pos.size.width, self.pos.size.height); - - let uv_origins = f32x4::new(self.uv.origin.x, self.uv.origin.y, - self.uv.origin.x, self.uv.origin.y); - let uv_sizes = f32x4::new(self.uv.size.width, self.uv.size.height, - self.uv.size.width, self.uv.size.height); - let f = ((clip - origins) / sizes) * uv_sizes + uv_origins; - - output.push(RectPosUv { - pos: Rect::new(Point2D::new(clip.extract(0), clip.extract(1)), - Size2D::new(clip.extract(2) - clip.extract(0), - clip.extract(3) - clip.extract(1))), - uv: Rect::new(Point2D::new(f.extract(0), f.extract(1)), - Size2D::new(f.extract(2) - f.extract(0), - f.extract(3) - f.extract(1))), - }) + for clipped_rect in self.pos.intersection(clip_rect).iter() { + self.push_clipped_rect(clipped_rect, output) } } @@ -452,38 +425,22 @@ impl Polygon for RectPosUv { } }; - // FIXME(pcwalton): Clip the u and v too. - push(output, - &self.uv, - &self.pos.origin, - &Point2D::new(self.pos.max_x(), clip_rect.origin.y)); - push(output, - &self.uv, - &Point2D::new(self.pos.origin.x, clip_rect.origin.y), - &clip_rect.bottom_left()); - push(output, - &self.uv, - &clip_rect.top_right(), - &Point2D::new(self.pos.max_x(), clip_rect.max_y())); - push(output, - &self.uv, - &Point2D::new(self.pos.origin.x, clip_rect.max_y()), - &self.pos.bottom_right()); - - fn push(result: &mut Vec, - uv: &Rect, - top_left: &Point2D, - bottom_right: &Point2D) { - if top_left.x >= bottom_right.x || top_left.y >= bottom_right.y { - return - } - result.push(RectPosUv { - pos: Rect::new(*top_left, - Size2D::new(bottom_right.x - top_left.x, - bottom_right.y - top_left.y)), - uv: *uv, - }) - } + self.push_clipped_rect(&Rect::new(self.pos.origin, + Size2D::new(self.pos.size.width, + clip_rect.origin.y - self.pos.origin.y)), + output); + self.push_clipped_rect(&Rect::new(Point2D::new(self.pos.origin.x, clip_rect.origin.y), + Size2D::new(clip_rect.origin.x - self.pos.origin.x, + clip_rect.size.height)), + output); + self.push_clipped_rect(&Rect::new(Point2D::new(clip_rect.max_x(), clip_rect.origin.y), + Size2D::new(self.pos.max_x() - clip_rect.max_x(), + clip_rect.size.height)), + output); + self.push_clipped_rect(&Rect::new(Point2D::new(self.pos.origin.x, clip_rect.max_y()), + Size2D::new(self.pos.size.width, + self.pos.max_y() - clip_rect.max_y())), + output); } fn intersects_rect(&self, rect: &Rect) -> bool { diff --git a/src/internal_types.rs b/src/internal_types.rs index fd4d723fc2..da6ee590d2 100644 --- a/src/internal_types.rs +++ b/src/internal_types.rs @@ -195,12 +195,23 @@ pub enum TextureUpdateDetails { Blur(Vec, Size2D, Au, TextureImage, TextureImage), /// All four corners and whether inverted, respectively. BorderRadius(Au, Au, Au, Au, bool), - /// Blur radius border radius, and whether inverted, respectively. - BoxShadowCorner(Au, Au, bool), + /// Blur radius, box shadow part, and whether inverted, respectively. + BoxShadow(Au, BoxShadowPart, bool), /// Bytes, stretch size, and scratch texture image, respectively. Tile(Vec, Size2D, TextureImage), } +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub enum BoxShadowPart { + /// The edge. + Edge, + + /// A corner with a border radius. + /// + /// TODO(pcwalton): Elliptical radii. + Corner(Au), +} + #[derive(Clone, Copy, Debug)] pub struct TextureImage { pub texture_id: TextureId, @@ -460,22 +471,22 @@ impl ClipRectToRegionResult { -> [PackedVertex; 4] { [ self.make_packed_vertex(&self.rect_result.pos.origin, - &self.rect_result.uv.origin, + &self.rect_result.uv.top_left, &colors[0], mask, uv_index), self.make_packed_vertex(&self.rect_result.pos.top_right(), - &self.rect_result.uv.top_right(), + &self.rect_result.uv.top_right, &colors[1], mask, uv_index), self.make_packed_vertex(&self.rect_result.pos.bottom_left(), - &self.rect_result.uv.bottom_left(), + &self.rect_result.uv.bottom_left, &colors[3], mask, uv_index), self.make_packed_vertex(&self.rect_result.pos.bottom_right(), - &self.rect_result.uv.bottom_right(), + &self.rect_result.uv.bottom_right, &colors[2], mask, uv_index), @@ -541,7 +552,56 @@ impl CompiledNode { #[derive(Clone, Copy, Debug)] pub struct RectPosUv { pub pos: Rect, - pub uv: Rect, + pub uv: RectUv, +} + +#[derive(Clone, Copy, Debug)] +pub struct RectUv { + pub top_left: Point2D, + pub top_right: Point2D, + pub bottom_left: Point2D, + pub bottom_right: Point2D, +} + +impl RectUv { + pub fn from_image_and_rotation_angle(image: &TextureCacheItem, + rotation_angle: BasicRotationAngle) + -> RectUv { + match rotation_angle { + BasicRotationAngle::Upright => { + RectUv { + top_left: Point2D::new(image.u0, image.v0), + top_right: Point2D::new(image.u1, image.v0), + bottom_right: Point2D::new(image.u1, image.v1), + bottom_left: Point2D::new(image.u0, image.v1), + } + } + BasicRotationAngle::Clockwise90 => { + RectUv { + top_right: Point2D::new(image.u0, image.v0), + bottom_right: Point2D::new(image.u1, image.v0), + bottom_left: Point2D::new(image.u1, image.v1), + top_left: Point2D::new(image.u0, image.v1), + } + } + BasicRotationAngle::Clockwise180 => { + RectUv { + bottom_right: Point2D::new(image.u0, image.v0), + bottom_left: Point2D::new(image.u1, image.v0), + top_left: Point2D::new(image.u1, image.v1), + top_right: Point2D::new(image.u0, image.v1), + } + } + BasicRotationAngle::Clockwise270 => { + RectUv { + bottom_left: Point2D::new(image.u0, image.v0), + top_left: Point2D::new(image.u1, image.v0), + top_right: Point2D::new(image.u1, image.v1), + bottom_right: Point2D::new(image.u0, image.v1), + } + } + } + } } #[derive(Clone, Debug)] @@ -681,19 +741,41 @@ impl BorderRadiusRasterOp { } #[derive(Clone, Debug, Hash, Eq, PartialEq)] -pub struct BoxShadowCornerRasterOp { +pub struct BoxShadowRasterOp { pub blur_radius: Au, - pub border_radius: Au, + pub part: BoxShadowPart, + pub raster_size: Au, pub inverted: bool, } -impl BoxShadowCornerRasterOp { - pub fn create(blur_radius: f32, border_radius: f32, inverted: bool) - -> Option { +impl BoxShadowRasterOp { + pub fn raster_size(blur_radius: f32, border_radius: f32) -> f32 { + (3.0 * blur_radius).max(border_radius) + 3.0 * blur_radius + } + + pub fn create_corner(blur_radius: f32, border_radius: f32, inverted: bool) + -> Option { if blur_radius > 0.0 || border_radius > 0.0 { - Some(BoxShadowCornerRasterOp { + Some(BoxShadowRasterOp { blur_radius: Au::from_f32_px(blur_radius), - border_radius: Au::from_f32_px(border_radius), + part: BoxShadowPart::Corner(Au::from_f32_px(border_radius)), + raster_size: Au::from_f32_px(BoxShadowRasterOp::raster_size(blur_radius, + border_radius)), + inverted: inverted, + }) + } else { + None + } + } + + pub fn create_edge(blur_radius: f32, border_radius: f32, inverted: bool) + -> Option { + if blur_radius > 0.0 { + Some(BoxShadowRasterOp { + blur_radius: Au::from_f32_px(blur_radius), + part: BoxShadowPart::Edge, + raster_size: Au::from_f32_px(BoxShadowRasterOp::raster_size(blur_radius, + border_radius)), inverted: inverted, }) } else { @@ -724,7 +806,7 @@ impl GlyphKey { #[derive(Clone, Debug, Hash, Eq, PartialEq)] pub enum RasterItem { BorderRadius(BorderRadiusRasterOp), - BoxShadowCorner(BoxShadowCornerRasterOp), + BoxShadow(BoxShadowRasterOp), } #[derive(Clone, Debug, Hash, Eq, PartialEq)] @@ -736,3 +818,11 @@ pub struct TiledImageKey { pub stretch_height: u32, } +#[derive(Clone, Copy, Debug)] +pub enum BasicRotationAngle { + Upright, + Clockwise90, + Clockwise180, + Clockwise270, +} + diff --git a/src/render_backend.rs b/src/render_backend.rs index 02e510d532..c71fc444e6 100644 --- a/src/render_backend.rs +++ b/src/render_backend.rs @@ -6,13 +6,14 @@ use device::{ProgramId, TextureId}; use euclid::{Rect, Point2D, Size2D, Matrix4}; use fnv::FnvHasher; use internal_types::{ApiMsg, Frame, ResultMsg, DrawLayer, Primitive, ClearInfo}; -use internal_types::{BorderRadiusRasterOp, BoxShadowCornerRasterOp, DrawListID, RasterItem}; +use internal_types::{BorderRadiusRasterOp, BoxShadowRasterOp, DrawListID, RasterItem}; use internal_types::{BatchUpdateList, BatchId, BatchUpdate, BatchUpdateOp, CompiledNode}; use internal_types::{PackedVertex, WorkVertex, DisplayList, DrawCommand, DrawCommandInfo}; use internal_types::{ClipRectToRegionResult, DrawListIndex, DrawListItemIndex, DisplayItemKey}; use internal_types::{CompositeInfo, BorderEdgeDirection, RenderTargetIndex, GlyphKey}; use internal_types::{FontTemplate, Glyph, PolygonPosColorUv, RectPosUv, TextureTarget}; -use internal_types::{ResourceId, IdNamespace, TiledImageKey}; +use internal_types::{ResourceId, IdNamespace, TiledImageKey, BasicRotationAngle}; +use internal_types::{RectUv}; use layer::Layer; use optimizer; use render_api::RenderApi; @@ -1544,10 +1545,12 @@ impl DrawCommandBuilder { let image_info = resource_cache.get_image(image_key); if rect.size.width == stretch_size.width && rect.size.height == stretch_size.height { - let uv_origin = Point2D::new(image_info.u0, image_info.v0); - let uv_size = Size2D::new(image_info.u1 - image_info.u0, - image_info.v1 - image_info.v0); - let uv = Rect::new(uv_origin, uv_size); + let uv = RectUv { + top_left: Point2D::new(image_info.u0, image_info.v0), + top_right: Point2D::new(image_info.u1, image_info.v0), + bottom_left: Point2D::new(image_info.u0, image_info.v1), + bottom_right: Point2D::new(image_info.u1, image_info.v1), + }; self.push_image_rect(color, image_info, @@ -1572,10 +1575,12 @@ impl DrawCommandBuilder { None => (image_info, *stretch_size), }; - let uv_origin = Point2D::new(image_info.u0, image_info.v0); - let uv_size = Size2D::new(image_info.u1 - image_info.u0, - image_info.v1 - image_info.v0); - let uv = Rect::new(uv_origin, uv_size); + let uv = RectUv { + top_left: Point2D::new(image_info.u0, image_info.v0), + top_right: Point2D::new(image_info.u1, image_info.v0), + bottom_left: Point2D::new(image_info.u0, image_info.v1), + bottom_right: Point2D::new(image_info.u1, image_info.v1), + }; let mut y_offset = 0.0; while y_offset < rect.size.height { @@ -1610,7 +1615,7 @@ impl DrawCommandBuilder { resource_cache: &ResourceCache, clip_buffers: &mut ClipBuffers, rect: &Rect, - uv: &Rect) { + uv: &RectUv) { clipper::clip_rect_with_mode_and_to_region( RectPosUv { pos: *rect, @@ -1760,9 +1765,12 @@ impl DrawCommandBuilder { return } - let uv_origin = Point2D::new(image_info.u0, image_info.v0); - let uv_size = Size2D::new(image_info.u1 - image_info.u0, image_info.v1 - image_info.v0); - let uv = Rect::new(uv_origin, uv_size); + let uv = RectUv { + top_left: Point2D::new(image_info.u0, image_info.v0), + top_right: Point2D::new(image_info.u1, image_info.v0), + bottom_left: Point2D::new(image_info.u0, image_info.v1), + bottom_right: Point2D::new(image_info.u1, image_info.v1), + }; clipper::clip_rect_with_mode_and_to_region( RectPosUv { @@ -1947,13 +1955,10 @@ impl DrawCommandBuilder { match clip_mode { BoxShadowClipMode::None | BoxShadowClipMode::Outset => { // Fill the center area. - let metrics = BoxShadowMetrics::outset(&rect, border_radius, blur_radius); - let blur_diameter = blur_radius + blur_radius; - let twice_blur_diameter = blur_diameter + blur_diameter; - let center_rect = - Rect::new(metrics.tl_outer + Point2D::new(blur_diameter, blur_diameter), - Size2D::new(rect.size.width - twice_blur_diameter, - rect.size.height - twice_blur_diameter)); + let metrics = BoxShadowMetrics::new(&rect, border_radius, blur_radius); + let center_rect = Rect::new(metrics.tl_inner, + Size2D::new(metrics.br_inner.x - metrics.tl_inner.x, + metrics.br_inner.y - metrics.tl_inner.y)); self.add_color_rectangle(sort_key, ¢er_rect, box_bounds, @@ -2004,7 +2009,7 @@ impl DrawCommandBuilder { // +--+------------------+--+ let rect = compute_box_shadow_rect(box_bounds, box_offset, spread_radius); - let metrics = BoxShadowMetrics::new(clip_mode, &rect, border_radius, blur_radius); + let metrics = BoxShadowMetrics::new(&rect, border_radius, blur_radius); self.add_box_shadow_corner(sort_key, &metrics.tl_outer, &metrics.tl_inner, @@ -2014,37 +2019,41 @@ impl DrawCommandBuilder { border_radius, clip_mode, resource_cache, - clip_buffers); + clip_buffers, + BasicRotationAngle::Upright); self.add_box_shadow_corner(sort_key, - &metrics.tr_outer, - &metrics.tr_inner, + &Point2D::new(metrics.tr_inner.x, metrics.tr_outer.y), + &Point2D::new(metrics.tr_outer.x, metrics.tr_inner.y), box_bounds, &color, blur_radius, border_radius, clip_mode, resource_cache, - clip_buffers); + clip_buffers, + BasicRotationAngle::Clockwise90); self.add_box_shadow_corner(sort_key, - &metrics.bl_outer, - &metrics.bl_inner, + &metrics.br_inner, + &metrics.br_outer, box_bounds, &color, blur_radius, border_radius, clip_mode, resource_cache, - clip_buffers); + clip_buffers, + BasicRotationAngle::Clockwise180); self.add_box_shadow_corner(sort_key, - &metrics.br_outer, - &metrics.br_inner, + &Point2D::new(metrics.bl_outer.x, metrics.bl_inner.y), + &Point2D::new(metrics.bl_inner.x, metrics.bl_outer.y), box_bounds, &color, blur_radius, border_radius, clip_mode, resource_cache, - clip_buffers); + clip_buffers, + BasicRotationAngle::Clockwise270); } fn add_box_shadow_sides(&mut self, @@ -2060,7 +2069,7 @@ impl DrawCommandBuilder { resource_cache: &ResourceCache, clip_buffers: &mut ClipBuffers) { let rect = compute_box_shadow_rect(box_bounds, box_offset, spread_radius); - let metrics = BoxShadowMetrics::new(clip_mode, &rect, border_radius, blur_radius); + let metrics = BoxShadowMetrics::new(&rect, border_radius, blur_radius); // Draw the sides. // @@ -2074,62 +2083,69 @@ impl DrawCommandBuilder { // | |##################| | // +--+------------------+--+ - let transparent = ColorF { - a: 0.0, - ..*color - }; - let (start_color, end_color) = match clip_mode { - BoxShadowClipMode::None | BoxShadowClipMode::Outset => (transparent, *color), - BoxShadowClipMode::Inset => (*color, transparent), - }; - - let blur_diameter = blur_radius + blur_radius; - let twice_side_radius = metrics.side_radius + metrics.side_radius; - let horizontal_size = Size2D::new(rect.size.width - twice_side_radius, blur_diameter); - let vertical_size = Size2D::new(blur_diameter, rect.size.height - twice_side_radius); - let top_rect = Rect::new(metrics.tl_outer + Point2D::new(metrics.side_radius, 0.0), + let horizontal_size = Size2D::new(metrics.br_inner.x - metrics.tl_inner.x, + metrics.edge_size); + let vertical_size = Size2D::new(metrics.edge_size, + metrics.br_inner.y - metrics.tl_inner.y); + let top_rect = Rect::new(metrics.tl_outer + Point2D::new(metrics.edge_size, 0.0), horizontal_size); let right_rect = - Rect::new(metrics.tr_outer + Point2D::new(-blur_diameter, metrics.side_radius), + Rect::new(metrics.tr_outer + Point2D::new(-metrics.edge_size, metrics.edge_size), vertical_size); let bottom_rect = - Rect::new(metrics.bl_outer + Point2D::new(metrics.side_radius, -blur_diameter), + Rect::new(metrics.bl_outer + Point2D::new(metrics.edge_size, -metrics.edge_size), horizontal_size); - let left_rect = Rect::new(metrics.tl_outer + Point2D::new(0.0, metrics.side_radius), + let left_rect = Rect::new(metrics.tl_outer + Point2D::new(0.0, metrics.edge_size), vertical_size); - self.add_axis_aligned_gradient(sort_key, - &top_rect, - box_bounds, - clip_mode, - clip_region, - resource_cache, - clip_buffers, - &[start_color, start_color, end_color, end_color]); - self.add_axis_aligned_gradient(sort_key, - &right_rect, - box_bounds, - clip_mode, - clip_region, - resource_cache, - clip_buffers, - &[end_color, start_color, start_color, end_color]); - self.add_axis_aligned_gradient(sort_key, - &bottom_rect, - box_bounds, - clip_mode, - clip_region, - resource_cache, - clip_buffers, - &[end_color, end_color, start_color, start_color]); - self.add_axis_aligned_gradient(sort_key, - &left_rect, - box_bounds, - clip_mode, - clip_region, - resource_cache, - clip_buffers, - &[start_color, end_color, end_color, start_color]); + self.add_box_shadow_edge(sort_key, + &top_rect.origin, + &top_rect.bottom_right(), + box_bounds, + color, + blur_radius, + border_radius, + clip_mode, + clip_region, + resource_cache, + clip_buffers, + BasicRotationAngle::Clockwise270); + self.add_box_shadow_edge(sort_key, + &right_rect.origin, + &right_rect.bottom_right(), + box_bounds, + color, + blur_radius, + border_radius, + clip_mode, + clip_region, + resource_cache, + clip_buffers, + BasicRotationAngle::Upright); + self.add_box_shadow_edge(sort_key, + &bottom_rect.origin, + &bottom_rect.bottom_right(), + box_bounds, + color, + blur_radius, + border_radius, + clip_mode, + clip_region, + resource_cache, + clip_buffers, + BasicRotationAngle::Clockwise90); + self.add_box_shadow_edge(sort_key, + &left_rect.origin, + &left_rect.bottom_right(), + box_bounds, + color, + blur_radius, + border_radius, + clip_mode, + clip_region, + resource_cache, + clip_buffers, + BasicRotationAngle::Clockwise180); } fn fill_outside_area_of_inset_box_shadow(&mut self, @@ -2145,7 +2161,7 @@ impl DrawCommandBuilder { resource_cache: &ResourceCache, clip_buffers: &mut ClipBuffers) { let rect = compute_box_shadow_rect(box_bounds, box_offset, spread_radius); - let metrics = BoxShadowMetrics::new(clip_mode, &rect, border_radius, blur_radius); + let metrics = BoxShadowMetrics::new(&rect, border_radius, blur_radius); // Fill in the outside area of the box. // @@ -2402,13 +2418,13 @@ impl DrawCommandBuilder { fn add_border_corner(&mut self, sort_key: &DisplayItemKey, clip: &Rect, - v0: Point2D, - v1: Point2D, + vertices_rect: &Rect, color0: &ColorF, color1: &ColorF, outer_radius: &Size2D, inner_radius: &Size2D, - resource_cache: &ResourceCache) { + resource_cache: &ResourceCache, + rotation_angle: BasicRotationAngle) { if color0.a <= 0.0 && color1.a <= 0.0 { return } @@ -2428,41 +2444,70 @@ impl DrawCommandBuilder { } }; - let vmin = Point2D::new(v0.x.min(v1.x), v0.y.min(v1.y)); - let vmax = Point2D::new(v0.x.max(v1.x), v0.y.max(v1.y)); - let vertices_rect = Rect::new(vmin, Size2D::new(vmax.x - vmin.x, vmax.y - vmin.y)); + let mask_uv = RectUv::from_image_and_rotation_angle(mask_image, rotation_angle); if vertices_rect.intersects(clip) { + let v0; + let v1; + let muv0; + let muv1; + match rotation_angle { + BasicRotationAngle::Upright => { + v0 = vertices_rect.origin; + muv0 = mask_uv.top_left; + v1 = vertices_rect.bottom_right(); + muv1 = mask_uv.bottom_right; + } + BasicRotationAngle::Clockwise90 => { + v0 = vertices_rect.top_right(); + muv0 = mask_uv.top_right; + v1 = vertices_rect.bottom_left(); + muv1 = mask_uv.bottom_left; + } + BasicRotationAngle::Clockwise180 => { + v0 = vertices_rect.bottom_right(); + muv0 = mask_uv.bottom_right; + v1 = vertices_rect.origin; + muv1 = mask_uv.top_left; + } + BasicRotationAngle::Clockwise270 => { + v0 = vertices_rect.bottom_left(); + muv0 = mask_uv.bottom_left; + v1 = vertices_rect.top_right(); + muv1 = mask_uv.top_right; + } + } + let mut vertices = [ PackedVertex::from_components(v0.x, v0.y, color0, 0.0, 0.0, - mask_image.u0, mask_image.v0, + muv0.x, muv0.y, white_image.texture_index, mask_image.texture_index), PackedVertex::from_components(v1.x, v1.y, color0, 0.0, 0.0, - mask_image.u1, mask_image.v1, + muv1.x, muv1.y, white_image.texture_index, mask_image.texture_index), PackedVertex::from_components(v0.x, v1.y, color0, 0.0, 0.0, - mask_image.u0, mask_image.v1, + muv0.x, muv1.y, white_image.texture_index, mask_image.texture_index), PackedVertex::from_components(v0.x, v0.y, color1, 0.0, 0.0, - mask_image.u0, mask_image.v0, + muv0.x, muv0.y, white_image.texture_index, mask_image.texture_index), PackedVertex::from_components(v1.x, v0.y, color1, 0.0, 0.0, - mask_image.u1, mask_image.v0, + muv1.x, muv0.y, white_image.texture_index, mask_image.texture_index), PackedVertex::from_components(v1.x, v1.y, color1, 0.0, 0.0, - mask_image.u1, mask_image.v1, + muv1.x, muv1.y, white_image.texture_index, mask_image.texture_index), ]; @@ -2484,20 +2529,19 @@ impl DrawCommandBuilder { color1: &ColorF, mask_image: &TextureCacheItem, resource_cache: &ResourceCache, - clip_buffers: &mut ClipBuffers) { + clip_buffers: &mut ClipBuffers, + rotation_angle: BasicRotationAngle) { if color0.a <= 0.0 || color1.a <= 0.0 { return } let white_image = resource_cache.get_dummy_color_image(); let vertices_rect = Rect::new(*v0, Size2D::new(v1.x - v0.x, v1.y - v0.y)); - let mask_uv_rect = Rect::new(Point2D::new(mask_image.u0, mask_image.v0), - Size2D::new(mask_image.u1 - mask_image.u0, - mask_image.v1 - mask_image.v0)); + let mask_uv = RectUv::from_image_and_rotation_angle(mask_image, rotation_angle); clipper::clip_rect_with_mode(RectPosUv { pos: vertices_rect, - uv: mask_uv_rect, + uv: mask_uv, }, &mut clip_buffers.sh_clip_buffers, clip, @@ -2508,26 +2552,28 @@ impl DrawCommandBuilder { PackedVertex::from_components(clip_result.pos.origin.x, clip_result.pos.origin.y, color0, 0.0, 0.0, - clip_result.uv.origin.x, clip_result.uv.origin.y, + clip_result.uv.top_left.x, clip_result.uv.top_left.y, white_image.texture_index, mask_image.texture_index), PackedVertex::from_components(clip_result.pos.max_x(), clip_result.pos.origin.y, color0, 0.0, 0.0, - clip_result.uv.max_x(), - clip_result.uv.origin.y, + clip_result.uv.top_right.x, + clip_result.uv.top_right.y, white_image.texture_index, mask_image.texture_index), PackedVertex::from_components(clip_result.pos.origin.x, clip_result.pos.max_y(), color1, 0.0, 0.0, - clip_result.uv.origin.x, clip_result.uv.max_y(), + clip_result.uv.bottom_left.x, + clip_result.uv.bottom_left.y, white_image.texture_index, mask_image.texture_index), PackedVertex::from_components(clip_result.pos.max_x(), clip_result.pos.max_y(), color1, 0.0, 0.0, - clip_result.uv.max_x(), clip_result.uv.max_y(), + clip_result.uv.bottom_right.x, + clip_result.uv.bottom_right.y, white_image.texture_index, mask_image.texture_index), ]; @@ -2628,43 +2674,51 @@ impl DrawCommandBuilder { // Corners self.add_border_corner(sort_key, clip, - tl_outer, - tl_inner, + &Rect::new(tl_outer, + Size2D::new(tl_inner.x - tl_outer.x, + tl_inner.y - tl_outer.y)), &left_color, &top_color, &radius.top_left, &info.top_left_inner_radius(), - resource_cache); + resource_cache, + BasicRotationAngle::Upright); self.add_border_corner(sort_key, clip, - tr_outer, - tr_inner, + &Rect::new(Point2D::new(tr_inner.x, tr_outer.y), + Size2D::new(tr_outer.x - tr_inner.x, + tr_inner.y - tr_outer.y)), &right_color, &top_color, &radius.top_right, &info.top_right_inner_radius(), - resource_cache); + resource_cache, + BasicRotationAngle::Clockwise90); self.add_border_corner(sort_key, clip, - br_outer, - br_inner, + &Rect::new(br_inner, + Size2D::new(br_outer.x - br_inner.x, + br_outer.y - br_inner.y)), &right_color, &bottom_color, &radius.bottom_right, &info.bottom_right_inner_radius(), - resource_cache); + resource_cache, + BasicRotationAngle::Clockwise180); self.add_border_corner(sort_key, clip, - bl_outer, - bl_inner, + &Rect::new(Point2D::new(bl_outer.x, bl_inner.y), + Size2D::new(bl_inner.x - bl_outer.x, + bl_outer.y - bl_inner.y)), &left_color, &bottom_color, &radius.bottom_left, &info.bottom_left_inner_radius(), - resource_cache); + resource_cache, + BasicRotationAngle::Clockwise270); } // FIXME(pcwalton): Assumes rectangles are well-formed with origin in TL @@ -2678,18 +2732,61 @@ impl DrawCommandBuilder { border_radius: f32, clip_mode: BoxShadowClipMode, resource_cache: &ResourceCache, - clip_buffers: &mut ClipBuffers) { + clip_buffers: &mut ClipBuffers, + rotation_angle: BasicRotationAngle) { + let (inverted, clip_rect) = match clip_mode { + BoxShadowClipMode::Outset => (false, *box_bounds), + BoxShadowClipMode::Inset => (true, *box_bounds), + BoxShadowClipMode::None => (false, MAX_RECT), + }; + + let mask_image = match BoxShadowRasterOp::create_corner(blur_radius, + border_radius, + inverted) { + Some(raster_item) => { + let raster_item = RasterItem::BoxShadow(raster_item); + resource_cache.get_raster(&raster_item) + } + None => resource_cache.get_dummy_mask_image(), + }; + + self.add_masked_rectangle(sort_key, + top_left, + bottom_right, + &clip_rect, + clip_mode, + color, + color, + &mask_image, + resource_cache, + clip_buffers, + rotation_angle) + } + + fn add_box_shadow_edge(&mut self, + sort_key: &DisplayItemKey, + top_left: &Point2D, + bottom_right: &Point2D, + box_bounds: &Rect, + color: &ColorF, + blur_radius: f32, + border_radius: f32, + clip_mode: BoxShadowClipMode, + _: &ClipRegion, + resource_cache: &ResourceCache, + clip_buffers: &mut ClipBuffers, + rotation_angle: BasicRotationAngle) { let (inverted, clip_rect) = match clip_mode { BoxShadowClipMode::Outset => (false, *box_bounds), BoxShadowClipMode::Inset => (true, *box_bounds), BoxShadowClipMode::None => (false, MAX_RECT), }; - let mask_image = match BoxShadowCornerRasterOp::create(blur_radius, - border_radius, - inverted) { + let mask_image = match BoxShadowRasterOp::create_edge(blur_radius, + border_radius, + inverted) { Some(raster_item) => { - let raster_item = RasterItem::BoxShadowCorner(raster_item); + let raster_item = RasterItem::BoxShadow(raster_item); resource_cache.get_raster(&raster_item) } None => resource_cache.get_dummy_mask_image(), @@ -2704,7 +2801,8 @@ impl DrawCommandBuilder { color, &mask_image, resource_cache, - clip_buffers) + clip_buffers, + rotation_angle) } } @@ -2761,10 +2859,14 @@ impl BuildRequiredResources for AABBTreeNode { resource_list.add_box_shadow_corner(info.blur_radius, info.border_radius, false); + resource_list.add_box_shadow_edge(info.blur_radius, info.border_radius, false); if info.clip_mode == BoxShadowClipMode::Inset { resource_list.add_box_shadow_corner(info.blur_radius, info.border_radius, true); + resource_list.add_box_shadow_edge(info.blur_radius, + info.border_radius, + true); } } SpecificDisplayItem::Border(ref info) => { @@ -3058,8 +3160,9 @@ impl NodeCompiler for AABBTreeNode { } } +#[derive(Debug)] struct BoxShadowMetrics { - side_radius: f32, + edge_size: f32, tl_outer: Point2D, tl_inner: Point2D, tr_outer: Point2D, @@ -3071,35 +3174,15 @@ struct BoxShadowMetrics { } impl BoxShadowMetrics { - fn outset(rect: &Rect, border_radius: f32, blur_radius: f32) -> BoxShadowMetrics { - let side_radius = border_radius + blur_radius; - let tl_outer = rect.origin; - let tl_inner = tl_outer + Point2D::new(side_radius, side_radius); - let tr_outer = rect.top_right(); - let tr_inner = tr_outer + Point2D::new(-side_radius, side_radius); - let bl_outer = rect.bottom_left(); - let bl_inner = bl_outer + Point2D::new(side_radius, -side_radius); - let br_outer = rect.bottom_right(); - let br_inner = br_outer + Point2D::new(-side_radius, -side_radius); + fn new(box_bounds: &Rect, border_radius: f32, blur_radius: f32) -> BoxShadowMetrics { + let outside_edge_size = 3.0 * blur_radius; + let inside_edge_size = outside_edge_size.max(border_radius); + let edge_size = outside_edge_size + inside_edge_size; + let inner_rect = box_bounds.inflate(-inside_edge_size, -inside_edge_size); + let outer_rect = box_bounds.inflate(outside_edge_size, outside_edge_size); BoxShadowMetrics { - side_radius: side_radius, - tl_outer: tl_outer, - tl_inner: tl_inner, - tr_outer: tr_outer, - tr_inner: tr_inner, - bl_outer: bl_outer, - bl_inner: bl_inner, - br_outer: br_outer, - br_inner: br_inner, - } - } - - fn inset(inner_rect: &Rect, border_radius: f32, blur_radius: f32) -> BoxShadowMetrics { - let side_radius = border_radius + blur_radius; - let outer_rect = inner_rect.inflate(blur_radius, blur_radius); - BoxShadowMetrics { - side_radius: side_radius, + edge_size: edge_size, tl_outer: outer_rect.origin, tl_inner: inner_rect.origin, tr_outer: outer_rect.top_right(), @@ -3110,21 +3193,6 @@ impl BoxShadowMetrics { br_inner: inner_rect.bottom_right(), } } - - fn new(clip_mode: BoxShadowClipMode, - inner_rect: &Rect, - border_radius: f32, - blur_radius: f32) - -> BoxShadowMetrics { - match clip_mode { - BoxShadowClipMode::None | BoxShadowClipMode::Outset => { - BoxShadowMetrics::outset(inner_rect, border_radius, blur_radius) - } - BoxShadowClipMode::Inset => { - BoxShadowMetrics::inset(inner_rect, border_radius, blur_radius) - } - } - } } fn compute_box_shadow_rect(box_bounds: &Rect, diff --git a/src/renderer.rs b/src/renderer.rs index 6167b7229e..d5d3644614 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -6,7 +6,7 @@ use fnv::FnvHasher; use gleam::gl; use internal_types::{Frame, ResultMsg, TextureUpdateOp, BatchUpdateOp, BatchUpdateList}; use internal_types::{TextureUpdateDetails, TextureUpdateList, PackedVertex, RenderTargetMode}; -use internal_types::{BatchId, ORTHO_NEAR_PLANE, ORTHO_FAR_PLANE, DrawCommandInfo}; +use internal_types::{BatchId, ORTHO_NEAR_PLANE, ORTHO_FAR_PLANE, DrawCommandInfo, BoxShadowPart}; use internal_types::{PackedVertexForTextureCacheUpdate, TextureTarget, IdNamespace, ResourceId}; use render_api::RenderApi; use render_backend::RenderBackend; @@ -67,7 +67,7 @@ pub struct Renderer { u_filter_params: UniformLocation, u_filter_texture_size: UniformLocation, - box_shadow_corner_program_id: ProgramId, + shadow_corner_program_id: ProgramId, blur_program_id: ProgramId, u_direction: UniformLocation, @@ -94,8 +94,8 @@ impl Renderer { let border_program_id = device.create_program("border.vs.glsl", "border.fs.glsl"); let blend_program_id = device.create_program("blend.vs.glsl", "blend.fs.glsl"); let filter_program_id = device.create_program("filter.vs.glsl", "filter.fs.glsl"); - let box_shadow_corner_program_id = device.create_program("box-shadow-corner.vs.glsl", - "box-shadow-corner.fs.glsl"); + let shadow_corner_program_id = device.create_program("shadow_corner.vs.glsl", + "shadow_corner.fs.glsl"); let blur_program_id = device.create_program("blur.vs.glsl", "blur.fs.glsl"); let tile_program_id = device.create_program("tile.vs.glsl", "tile.fs.glsl"); @@ -170,7 +170,7 @@ impl Renderer { filter_program_id: filter_program_id, quad_program_id: quad_program_id, blit_program_id: blit_program_id, - box_shadow_corner_program_id: box_shadow_corner_program_id, + shadow_corner_program_id: shadow_corner_program_id, blur_program_id: blur_program_id, tile_program_id: tile_program_id, u_blend_params: u_blend_params, @@ -554,16 +554,14 @@ impl Renderer { None); batch.add_draw_item(update.id, TextureId(0), &vertices); } - TextureUpdateDetails::BoxShadowCorner(blur_radius, - border_radius, - inverted) => { - self.update_texture_cache_for_box_shadow_corner( + TextureUpdateDetails::BoxShadow(blur_radius, part, inverted) => { + self.update_texture_cache_for_box_shadow( update.id, update.index, &Rect::new(Point2D::new(x as f32, y as f32), Size2D::new(width as f32, height as f32)), blur_radius, - border_radius, + part, inverted) } TextureUpdateDetails::Tile(bytes, @@ -655,17 +653,16 @@ impl Renderer { self.flush_raster_batches(); } - fn update_texture_cache_for_box_shadow_corner(&mut self, - update_id: TextureId, - update_index: TextureIndex, - rect: &Rect, - blur_radius: Au, - border_radius: Au, - inverted: bool) { - let box_shadow_corner_program_id = self.box_shadow_corner_program_id; + fn update_texture_cache_for_box_shadow(&mut self, + update_id: TextureId, + update_index: TextureIndex, + rect: &Rect, + blur_radius: Au, + box_shadow_part: BoxShadowPart, + inverted: bool) { + let shadow_corner_program_id = self.shadow_corner_program_id; let blur_radius = blur_radius.to_f32_px(); - let border_radius = border_radius.to_f32_px(); let color = if inverted { ColorF::new(0.0, 0.0, 0.0, 0.0) @@ -676,7 +673,12 @@ impl Renderer { let zero_point = Point2D::new(0.0, 0.0); let zero_size = Size2D::new(0.0, 0.0); - let arc_radius = Point2D::new(border_radius, border_radius); + let arc_radius = match box_shadow_part { + BoxShadowPart::Edge => Point2D::new(rect.size.width, 0.0), + BoxShadowPart::Corner(border_radius) => { + Point2D::new(border_radius.to_f32_px(), border_radius.to_f32_px()) + } + }; let vertices: [PackedVertexForTextureCacheUpdate; 4] = [ PackedVertexForTextureCacheUpdate::new(&rect.origin, @@ -728,7 +730,7 @@ impl Renderer { let mut batch = self.get_or_create_raster_batch(update_id, update_index, TextureId(0), - box_shadow_corner_program_id, + shadow_corner_program_id, None); batch.add_draw_item(update_id, TextureId(0), &vertices); } diff --git a/src/resource_cache.rs b/src/resource_cache.rs index af2b0558b1..52ad796223 100644 --- a/src/resource_cache.rs +++ b/src/resource_cache.rs @@ -349,7 +349,7 @@ fn run_raster_jobs(thread_pool: &mut scoped_threadpool::Pool, (*native_font_handle).clone()); } } - job.result = font_context.get_glyph(job.glyph_key.font_key, + job.result = font_context.get_glyph(&job.glyph_key.font_key, job.glyph_key.size, job.glyph_key.index, device_pixel_ratio); diff --git a/src/resource_list.rs b/src/resource_list.rs index f1a0c58026..30f31e2db7 100644 --- a/src/resource_list.rs +++ b/src/resource_list.rs @@ -1,7 +1,7 @@ use app_units::Au; use euclid::Size2D; use fnv::FnvHasher; -use internal_types::{BorderRadiusRasterOp, BoxShadowCornerRasterOp}; +use internal_types::{BorderRadiusRasterOp, BoxShadowRasterOp}; use internal_types::{Glyph, GlyphKey, RasterItem, TiledImageKey}; use std::collections::{HashMap, HashSet}; use std::collections::hash_map::Entry::{Occupied, Vacant}; @@ -75,14 +75,19 @@ impl ResourceList { } } - pub fn add_box_shadow_corner(&mut self, - blur_radius: f32, - border_radius: f32, - inverted: bool) { - if let Some(raster_item) = BoxShadowCornerRasterOp::create(blur_radius, - border_radius, - inverted) { - self.required_rasters.insert(RasterItem::BoxShadowCorner(raster_item)); + pub fn add_box_shadow_corner(&mut self, blur_radius: f32, border_radius: f32, inverted: bool) { + if let Some(raster_item) = BoxShadowRasterOp::create_corner(blur_radius, + border_radius, + inverted) { + self.required_rasters.insert(RasterItem::BoxShadow(raster_item)); + } + } + + pub fn add_box_shadow_edge(&mut self, blur_radius: f32, border_radius: f32, inverted: bool) { + if let Some(raster_item) = BoxShadowRasterOp::create_edge(blur_radius, + border_radius, + inverted) { + self.required_rasters.insert(RasterItem::BoxShadow(raster_item)); } } diff --git a/src/texture_cache.rs b/src/texture_cache.rs index f35c171073..72c97f17ff 100644 --- a/src/texture_cache.rs +++ b/src/texture_cache.rs @@ -495,13 +495,12 @@ impl TextureCache { op.inverted)), } } - &RasterItem::BoxShadowCorner(ref op) => { - let size = op.border_radius + op.blur_radius; + &RasterItem::BoxShadow(ref op) => { let allocation = self.allocate(image_id, 0, 0, - size.to_nearest_px() as u32, - size.to_nearest_px() as u32, + op.raster_size.to_nearest_px() as u32, + op.raster_size.to_nearest_px() as u32, ImageFormat::A8, false); @@ -514,11 +513,9 @@ impl TextureCache { op: TextureUpdateOp::Update( allocation.uv.x, allocation.uv.y, - size.to_nearest_px() as u32, - size.to_nearest_px() as u32, - TextureUpdateDetails::BoxShadowCorner(op.blur_radius, - op.border_radius, - op.inverted)), + op.raster_size.to_nearest_px() as u32, + op.raster_size.to_nearest_px() as u32, + TextureUpdateDetails::BoxShadow(op.blur_radius, op.part, op.inverted)), } } }; diff --git a/src/util.rs b/src/util.rs index 276d73566b..719c18493e 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,4 +1,5 @@ use euclid::{Matrix4, Point2D, Rect, Size2D}; +use internal_types::RectUv; use std::num::Zero; use time::precise_time_ns; @@ -66,6 +67,14 @@ pub fn lerp(a: f32, b: f32, t: f32) -> f32 { (b - a) * t + a } +pub fn bilerp(point: &Point2D, quad: &Rect, uv: &RectUv) -> Point2D { + let (x1, y1, x2, y2) = (quad.origin.x, quad.origin.y, quad.max_x(), quad.max_y()); + (uv.top_left * (x2 - point.x) * (y2 - point.y) + + uv.top_right * (point.x - x1) * (y2 - point.y) + + uv.bottom_left * (x2 - point.x) * (point.y - y1) + + uv.bottom_right * (point.x - x1) * (point.y - y1)) / ((x2 - x1) * (y2 - y1)) +} + // Don't use `euclid`'s `is_empty` because that has effectively has an "and" in the conditional // below instead of an "or". pub fn rect_is_empty(rect: &Rect) -> bool {