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..d957466630 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 (!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..a5d4410328 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(-1e-37, -1e-37, 1e38, 1e38); + } + 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..418acf2b1f 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,46 @@ 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; + 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/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)) +} +