From 2841cce2f86a2f528085da9d09bf109dfeb95015 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Mon, 1 Feb 2016 19:23:53 -0800 Subject: [PATCH] Reimplement arbitrary rotation angles for linear gradients by clipping in the fragment shader. This reintroduces the clip-in rect in the FS. Since we'll need it anyway when we have arbitrary matrix/clip stacks, this seems harmless. Color strips for non-axis-aligned linear gradients are submitted as axis-aligned rects centered at the appropriate point, just as axis-aligned ones are. However, we reuse one of the vertex fields to store the rotation angle. The vertex shader detects this condition, rotates the rectangle around its midpoint, and passes the clip rect on to the fragment shader. Additionally, this patch eliminates the ugly (-10000,10000) spans for linear gradient strips that we had before. It was necessary to tighten this up in order to avoid needless fragment shader invocations in the non-axis-aligned case, so I went ahead and fixed it everywhere. Closes #161. --- res/gl3_common.vs.glsl | 1 + res/quad.fs.glsl | 7 ++- res/quad.vs.glsl | 26 ++++++++++-- src/batch.rs | 4 ++ src/batch_builder.rs | 96 ++++++++++++++++++++---------------------- src/node_compiler.rs | 2 + src/util.rs | 9 ++++ 7 files changed, 90 insertions(+), 55 deletions(-) diff --git a/res/gl3_common.vs.glsl b/res/gl3_common.vs.glsl index b7f61826d1..e347d5dd23 100644 --- a/res/gl3_common.vs.glsl +++ b/res/gl3_common.vs.glsl @@ -45,6 +45,7 @@ out vec2 vDestTextureSize; out vec2 vSourceTextureSize; out float vBlurRadius; out vec4 vTileParams; +out vec4 vClipInRect; out vec4 vClipOutRect; int Bottom7Bits(int value) { diff --git a/res/quad.fs.glsl b/res/quad.fs.glsl index 7a8619b335..73e9bf6f24 100644 --- a/res/quad.fs.glsl +++ b/res/quad.fs.glsl @@ -8,11 +8,16 @@ bool PointInRect(vec2 p, vec2 p0, vec2 p1) void main(void) { - // Clip out rect + // Clip out. if (PointInRect(vPosition, vClipOutRect.xy, vClipOutRect.zw)) { discard; } + // Clip in. + if (vClipInRect != vec4(0.0) && !PointInRect(vPosition, vClipInRect.xy, vClipInRect.zw)) { + discard; + } + // Apply image tiling parameters (offset and scale) to color UVs. vec2 colorTexCoord = vTileParams.xy + fract(vColorTexCoord.xy) * vTileParams.zw; vec2 maskTexCoord = vMaskTexCoord.xy; diff --git a/res/quad.vs.glsl b/res/quad.vs.glsl index 1f18bb923e..bbca4ef1c4 100644 --- a/res/quad.vs.glsl +++ b/res/quad.vs.glsl @@ -70,11 +70,31 @@ void main(void) } } - // Clip and compute varyings. - localPos.xy = clamp(localPos.xy, clipInRect.xy, clipInRect.zw); vec2 localST = (localPos.xy - rect_origin) / rect_size; + + // Rotate or clip as necessary. If there is no rotation, we can clip here in the vertex shader + // and save a whole bunch of fragment shader invocations. If there is a rotation, we fall back + // to FS clipping. + // + // The rotation angle is encoded as a negative bottom left u coordinate. (uv coordinates should + // always be nonnegative normally, and gradients don't use color textures, so this is fine.) + vec4 colorTexCoordRectBottom = aColorTexCoordRectBottom; + if (colorTexCoordRectBottom.z < 0.0) { + float angle = -colorTexCoordRectBottom.z; + vec2 center = rect_origin + rect_size / 2.0; + vec2 translatedPos = localPos.xy - center; + localPos.xy = vec2(translatedPos.x * cos(angle) - translatedPos.y * sin(angle), + translatedPos.x * sin(angle) + translatedPos.y * cos(angle)) + center; + colorTexCoordRectBottom.z = aColorTexCoordRectTop.x; + vClipInRect = clipInRect; + } else { + localPos.x = clamp(localPos.x, clipInRect.x, clipInRect.z); + localPos.y = clamp(localPos.y, clipInRect.y, clipInRect.w); + vClipInRect = vec4(0.0); + } + vColorTexCoord = Bilerp2(aColorTexCoordRectTop.xy, aColorTexCoordRectTop.zw, - aColorTexCoordRectBottom.xy, aColorTexCoordRectBottom.zw, + colorTexCoordRectBottom.xy, colorTexCoordRectBottom.zw, localST); vMaskTexCoord = Bilerp2(aMaskTexCoordRectTop.xy, aMaskTexCoordRectTop.zw, aMaskTexCoordRectBottom.xy, aMaskTexCoordRectBottom.zw, diff --git a/src/batch.rs b/src/batch.rs index 1f202dce3f..aacf87768b 100644 --- a/src/batch.rs +++ b/src/batch.rs @@ -252,6 +252,10 @@ impl<'a> BatchBuilder<'a> { self.current_matrix_index += 1; } + pub fn clip_in_rect(&self) -> Rect { + self.cached_clip_in_rect.unwrap_or(MAX_RECT) + } + // TODO(gw): This is really inefficient to call this every push/pop... fn update_clip_in_rect(&mut self) { self.cached_clip_in_rect = Some(MAX_RECT); diff --git a/src/batch_builder.rs b/src/batch_builder.rs index 1b81ba191b..8d69baf5da 100644 --- a/src/batch_builder.rs +++ b/src/batch_builder.rs @@ -515,11 +515,10 @@ impl<'a> BatchBuilder<'a> { resource_cache: &ResourceCache, frame_id: FrameId) { // Fast paths for axis-aligned gradients: - // - // FIXME(pcwalton): Determine the start and end points properly! + let clip_rect = self.clip_in_rect(); if start_point.x == end_point.x { - let rect = Rect::new(Point2D::new(-10000.0, start_point.y), - Size2D::new(20000.0, end_point.y - start_point.y)); + let rect = Rect::new(Point2D::new(clip_rect.origin.x, start_point.y), + Size2D::new(clip_rect.size.width, end_point.y - start_point.y)); self.add_axis_aligned_gradient_with_stops(&rect, AxisDirection::Vertical, stops, @@ -528,8 +527,8 @@ impl<'a> BatchBuilder<'a> { return } if start_point.y == end_point.y { - let rect = Rect::new(Point2D::new(start_point.x, -10000.0), - Size2D::new(end_point.x - start_point.x, 20000.0)); + let rect = Rect::new(Point2D::new(start_point.x, clip_rect.origin.y), + Size2D::new(end_point.x - start_point.x, clip_rect.size.height)); self.add_axis_aligned_gradient_with_stops(&rect, AxisDirection::Horizontal, stops, @@ -542,60 +541,55 @@ impl<'a> BatchBuilder<'a> { let dummy_mask_image = resource_cache.get_dummy_mask_image(); debug_assert!(stops.len() >= 2); + let distance = util::distance(start_point, end_point); - let dir_x = end_point.x - start_point.x; - let dir_y = end_point.y - start_point.y; - let dir_len = (dir_x * dir_x + dir_y * dir_y).sqrt(); - let dir_xn = dir_x / dir_len; - let dir_yn = dir_y / dir_len; - let perp_xn = -dir_yn; - let perp_yn = dir_xn; - - for i in 0..stops.len()-1 { - let stop0 = &stops[i]; - let stop1 = &stops[i+1]; - - if stop0.offset == stop1.offset { - continue; - } - - let color0 = &stop0.color; - let color1 = &stop1.color; - - let start_x = start_point.x + stop0.offset * (end_point.x - start_point.x); - let start_y = start_point.y + stop0.offset * (end_point.y - start_point.y); - - //let end_x = start_point.x + stop1.offset * (end_point.x - start_point.x); - //let end_y = start_point.y + stop1.offset * (end_point.y - start_point.y); - - let len_scale = 1000.0; // todo: determine this properly!! - - let x0 = start_x - perp_xn * len_scale; - let y0 = start_y - perp_yn * len_scale; - - //let x1 = end_x - perp_xn * len_scale; - //let y1 = end_y - perp_yn * len_scale; - - //let x2 = end_x + perp_xn * len_scale; - //let y2 = end_y + perp_yn * len_scale; - - let x3 = start_x + perp_xn * len_scale; - let y3 = start_y + perp_yn * len_scale; + let mut angle = ((end_point.y - start_point.y) / (end_point.x - start_point.x)).atan() + + f32::consts::FRAC_PI_2; + if angle < 0.0 { + angle += 2.0 * f32::consts::PI + } - // TODO(gw): Non-axis-aligned gradients are still added via rotated rectangles. - // This means they can't currently be clipped by complex clip regions. - // To fix this, use a bit of trigonometry to supply the rectangles as - // axis-aligned, and then the complex clipping will just work! + // A simple way to estimate the length of each strip we'll need. Providing a good estimate + // saves fragment shader invocations. + let length_0 = clip_rect.size.width * angle.sin() + clip_rect.size.height * angle.cos(); + let length_1 = clip_rect.size.width * angle.cos() + clip_rect.size.height * angle.sin(); + let length = if length_0 > length_1 { + length_0 + } else { + length_1 + }; - let rect = Rect::new(Point2D::new(x0, y0), Size2D::new(x3 - x0, y3 - y0)); + let mut prev = &stops[0]; + for next in &stops[1..] { + let prev_point = util::lerp_points(start_point, end_point, prev.offset); + let next_point = util::lerp_points(start_point, end_point, next.offset); + let midpoint = util::lerp_points(&prev_point, &next_point, 0.5); + + let height = util::distance(&prev_point, &next_point); + let rect = + Rect::new(Point2D::new(-length / 2.0 + midpoint.x, midpoint.y - height / 2.0), + Size2D::new(length, height)); + let mut rect_uv = white_image.uv_rect; + println!("start point={:?} end point={:?} prev point={:?} next point={:?} height={:?} \ + angle={:?} rect={:?}", + start_point, + end_point, + prev_point, + next_point, + height, + angle, + rect); + rect_uv.bottom_left.x = -angle; self.add_rectangle(white_image.texture_id, dummy_mask_image.texture_id, &rect, - &white_image.uv_rect, + &rect_uv, &dummy_mask_image.pixel_rect, - &[*color0, *color1, *color0, *color1], + &[next.color, next.color, prev.color, prev.color], PackedVertexColorMode::Gradient, None); + + prev = next } } diff --git a/src/node_compiler.rs b/src/node_compiler.rs index 5995b40875..12a8393114 100644 --- a/src/node_compiler.rs +++ b/src/node_compiler.rs @@ -65,6 +65,8 @@ impl NodeCompiler for AABBTreeNode { builder.push_clip_in_rect(clip_rect); builder.push_complex_clip(&display_item.clip.complex); + println!("clip rect={:?}", clip_rect); + match display_item.item { SpecificDisplayItem::WebGL(ref info) => { builder.add_webgl_rectangle(&display_item.rect, diff --git a/src/util.rs b/src/util.rs index 60871e31bb..4625393e16 100644 --- a/src/util.rs +++ b/src/util.rs @@ -190,3 +190,12 @@ pub fn rect_center(rect: &Rect) -> Point2D { Point2D::new(rect.origin.x + rect.size.width / 2.0, rect.origin.y + rect.size.height / 2.0) } +pub fn distance(a: &Point2D, b: &Point2D) -> f32 { + let (x, y) = (b.x - a.x, b.y - a.y); + (x * x + y * y).sqrt() +} + +pub fn lerp_points(a: &Point2D, b: &Point2D, t: f32) -> Point2D { + Point2D::new(lerp(a.x, b.x, t), lerp(a.y, b.y, t)) +} +