diff --git a/demo3/shaders/defringe.fs.glsl b/demo3/shaders/defringe.fs.glsl new file mode 100644 index 00000000..1cee45a7 --- /dev/null +++ b/demo3/shaders/defringe.fs.glsl @@ -0,0 +1,59 @@ +#version 330 + +// pathfinder/demo3/shaders/defringe.fs.glsl +// +// Copyright © 2019 The Pathfinder Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// TODO(pcwalton): This could be significantly optimized by operating on a +// sparse per-tile basis. + +precision highp float; + +uniform sampler2D uSource; +uniform vec2 uFramebufferSize; +uniform vec4 uKernel; + +in vec2 vTexCoord; + +out vec4 oFragColor; + +float sample1Tap(float offset) { + return texture(uSource, vec2(vTexCoord.x + offset, vTexCoord.y)).r; +} + +void sample9Tap(out vec4 outAlphaLeft, + out float outAlphaCenter, + out vec4 outAlphaRight, + float onePixel) { + outAlphaLeft = vec4(uKernel.x > 0.0 ? sample1Tap(-4.0 * onePixel) : 0.0, + sample1Tap(-3.0 * onePixel), + sample1Tap(-2.0 * onePixel), + sample1Tap(-1.0 * onePixel)); + outAlphaCenter = sample1Tap(0.0); + outAlphaRight = vec4(sample1Tap(1.0 * onePixel), + sample1Tap(2.0 * onePixel), + sample1Tap(3.0 * onePixel), + uKernel.x > 0.0 ? sample1Tap(4.0 * onePixel) : 0.0); +} + +float convolve7Tap(vec4 alpha0, vec3 alpha1) { + return dot(alpha0, uKernel) + dot(alpha1, uKernel.zyx); +} + +void main() { + vec4 alphaLeft, alphaRight; + float alphaCenter; + sample9Tap(alphaLeft, alphaCenter, alphaRight, 1.0 / uFramebufferSize.x); + + vec3 alpha = vec3(convolve7Tap(alphaLeft, vec3(alphaCenter, alphaRight.xy)), + convolve7Tap(vec4(alphaLeft.yzw, alphaCenter), alphaRight.xyz), + convolve7Tap(vec4(alphaLeft.zw, alphaCenter, alphaRight.x), alphaRight.yzw)); + + oFragColor = vec4(alpha, 1.0); +} diff --git a/demo3/shaders/defringe.vs.glsl b/demo3/shaders/defringe.vs.glsl new file mode 100644 index 00000000..608cb421 --- /dev/null +++ b/demo3/shaders/defringe.vs.glsl @@ -0,0 +1,24 @@ +#version 330 + +// pathfinder/demo3/shaders/defringe.vs.glsl +// +// Copyright © 2019 The Pathfinder Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +precision highp float; + +uniform vec2 uFramebufferSize; + +in vec2 aPosition; + +out vec2 vTexCoord; + +void main() { + vTexCoord = aPosition; + gl_Position = vec4(aPosition / uFramebufferSize * 2.0 - 1.0, 0.0, 1.0); +} diff --git a/geometry/src/clip.rs b/geometry/src/clip.rs index 3f55b2ac..2ed9f042 100644 --- a/geometry/src/clip.rs +++ b/geometry/src/clip.rs @@ -343,11 +343,11 @@ trait ContourClipper where Self::Edge: TEdge { if let Some(last_position) = contour.last_position() { if last_position != segment.baseline.from() { // Add a line to join up segments. - contour.push_point(segment.baseline.from(), PointFlags::empty()); + contour.push_point(segment.baseline.from(), PointFlags::empty(), true); } } - contour.push_segment(*segment); + contour.push_segment(*segment, true); } fn check_for_fast_clip(&mut self, edge: &Self::Edge) -> FastClipResult { diff --git a/geometry/src/outline.rs b/geometry/src/outline.rs index b11108d7..bfaabe5b 100644 --- a/geometry/src/outline.rs +++ b/geometry/src/outline.rs @@ -17,6 +17,7 @@ use crate::point::Point2DF32; use crate::segment::{Segment, SegmentFlags, SegmentKind}; use crate::transform3d::Perspective; use crate::transform::Transform2DF32; +use crate::util; use euclid::{Point2D, Rect, Size2D}; use std::fmt::{self, Debug, Formatter}; use std::mem; @@ -66,7 +67,7 @@ impl Outline { .contours .push(mem::replace(&mut current_contour, Contour::new())); } - current_contour.push_point(segment.baseline.from(), PointFlags::empty()); + current_contour.push_point(segment.baseline.from(), PointFlags::empty(), true); } if segment.flags.contains(SegmentFlags::CLOSES_SUBPATH) { @@ -83,13 +84,15 @@ impl Outline { } if !segment.is_line() { - current_contour.push_point(segment.ctrl.from(), PointFlags::CONTROL_POINT_0); + current_contour.push_point(segment.ctrl.from(), PointFlags::CONTROL_POINT_0, true); if !segment.is_quadratic() { - current_contour.push_point(segment.ctrl.to(), PointFlags::CONTROL_POINT_1); + current_contour.push_point(segment.ctrl.to(), + PointFlags::CONTROL_POINT_1, + true); } } - current_contour.push_point(segment.baseline.to(), PointFlags::empty()); + current_contour.push_point(segment.baseline.to(), PointFlags::empty(), true); } if !current_contour.is_empty() { @@ -109,12 +112,6 @@ impl Outline { &self.bounds } - #[inline] - pub fn make_monotonic(&mut self) { - self.contours.iter_mut().for_each(|contour| contour.make_monotonic()); - } - - #[inline] pub fn transform(&mut self, transform: &Transform2DF32) { let mut new_bounds = None; for contour in &mut self.contours { @@ -124,7 +121,6 @@ impl Outline { self.bounds = new_bounds.unwrap_or_else(|| Rect::zero()); } - #[inline] pub fn apply_perspective(&mut self, perspective: &Perspective) { let mut new_bounds = None; for contour in &mut self.contours { @@ -134,7 +130,13 @@ impl Outline { self.bounds = new_bounds.unwrap_or_else(|| Rect::zero()); } - #[inline] + pub fn prepare_for_tiling(&mut self, view_box: &Rect) { + for contour in &mut self.contours { + contour.prepare_for_tiling(view_box); + } + self.bounds = self.bounds.intersection(view_box).unwrap_or_else(|| Rect::zero()); + } + pub fn clip_against_polygon(&mut self, clip_polygon: &[Point2DF32]) { let mut new_bounds = None; for contour in mem::replace(&mut self.contours, vec![]) { @@ -148,6 +150,10 @@ impl Outline { } pub fn clip_against_rect(&mut self, clip_rect: &Rect) { + if clip_rect.contains_rect(&self.bounds) { + return; + } + let mut new_bounds = None; for contour in mem::replace(&mut self.contours, vec![]) { let contour = ContourRectClipper::new(clip_rect, contour).clip(); @@ -221,31 +227,36 @@ impl Contour { // TODO(pcwalton): SIMD. #[inline] - pub(crate) fn push_point(&mut self, point: Point2DF32, flags: PointFlags) { - let first = self.is_empty(); - union_rect(&mut self.bounds, point, first); + pub(crate) fn push_point(&mut self, + point: Point2DF32, + flags: PointFlags, + update_bounds: bool) { + if update_bounds { + let first = self.is_empty(); + union_rect(&mut self.bounds, point, first); + } self.points.push(point); self.flags.push(flags); } - pub(crate) fn push_segment(&mut self, segment: Segment) { + pub(crate) fn push_segment(&mut self, segment: Segment, update_bounds: bool) { if segment.is_none() { return } if self.is_empty() { - self.push_point(segment.baseline.from(), PointFlags::empty()); + self.push_point(segment.baseline.from(), PointFlags::empty(), update_bounds); } if !segment.is_line() { - self.push_point(segment.ctrl.from(), PointFlags::CONTROL_POINT_0); + self.push_point(segment.ctrl.from(), PointFlags::CONTROL_POINT_0, update_bounds); if !segment.is_quadratic() { - self.push_point(segment.ctrl.to(), PointFlags::CONTROL_POINT_1); + self.push_point(segment.ctrl.to(), PointFlags::CONTROL_POINT_1, update_bounds); } } - self.push_point(segment.baseline.to(), PointFlags::empty()); + self.push_point(segment.baseline.to(), PointFlags::empty(), update_bounds); } #[inline] @@ -338,7 +349,6 @@ impl Contour { } } - #[inline] pub fn transform(&mut self, transform: &Transform2DF32) { for (point_index, point) in self.points.iter_mut().enumerate() { *point = transform.transform_point(point); @@ -346,7 +356,6 @@ impl Contour { } } - #[inline] pub fn apply_perspective(&mut self, perspective: &Perspective) { for (point_index, point) in self.points.iter_mut().enumerate() { *point = perspective.transform_point_2d(point); @@ -354,18 +363,80 @@ impl Contour { } } - #[inline] - pub fn make_monotonic(&mut self) { - // Fast path. - if self.iter().all(|segment| segment.is_monotonic()) { - return; + fn prepare_for_tiling(&mut self, view_box: &Rect) { + // Snap points to the view box bounds. This mops up floating point error from the clipping + // process. + let origin_upper_left = Point2DF32::from_euclid(view_box.origin); + let origin_lower_right = Point2DF32::from_euclid(view_box.bottom_right()); + let (mut last_endpoint_index, mut contour_is_monotonic) = (None, true); + for point_index in 0..(self.points.len() as u32) { + let position = &mut self.points[point_index as usize]; + *position = position.clamp(origin_upper_left, origin_lower_right); + + if contour_is_monotonic { + if self.point_is_endpoint(point_index) { + if let Some(last_endpoint_index) = last_endpoint_index { + if !self.curve_with_endpoints_is_monotonic(last_endpoint_index, + point_index) { + contour_is_monotonic = false; + } + } + last_endpoint_index = Some(point_index); + } + } } - // Slow path. - let contour = self.take(); - for segment in MonotonicConversionIter::new(contour.iter()) { - self.push_segment(segment); + // Convert to monotonic, if necessary. + if !contour_is_monotonic { + let contour = self.take(); + self.bounds = contour.bounds; + for segment in MonotonicConversionIter::new(contour.iter()) { + self.push_segment(segment, false); + } } + + // Update bounds. + self.bounds = self.bounds.intersection(view_box).unwrap_or_else(|| Rect::zero()); + } + + fn curve_with_endpoints_is_monotonic(&self, start_endpoint_index: u32, end_endpoint_index: u32) + -> bool { + let start_position = self.points[start_endpoint_index as usize]; + let end_position = self.points[end_endpoint_index as usize]; + + if start_position.x() <= end_position.x() { + for point_index in start_endpoint_index..end_endpoint_index { + if self.points[point_index as usize].x() > + self.points[point_index as usize + 1].x() { + return false; + } + } + } else { + for point_index in start_endpoint_index..end_endpoint_index { + if self.points[point_index as usize].x() < + self.points[point_index as usize + 1].x() { + return false; + } + } + } + + if start_position.y() <= end_position.y() { + for point_index in start_endpoint_index..end_endpoint_index { + if self.points[point_index as usize].y() > + self.points[point_index as usize + 1].y() { + return false; + } + } + } else { + for point_index in start_endpoint_index..end_endpoint_index { + if self.points[point_index as usize].y() < + self.points[point_index as usize + 1].y() { + return false; + } + } + } + + true } fn update_bounds(&self, bounds: &mut Option>) { diff --git a/geometry/src/point.rs b/geometry/src/point.rs index ebf90db6..76cc4d92 100644 --- a/geometry/src/point.rs +++ b/geometry/src/point.rs @@ -76,6 +76,11 @@ impl Point2DF32 { Point2DF32(self.0.max(other.0)) } + #[inline] + pub fn clamp(&self, min_val: Point2DF32, max_val: Point2DF32) -> Point2DF32 { + self.max(min_val).min(max_val) + } + #[inline] pub fn det(&self, other: Point2DF32) -> f32 { self.x() * other.y() - self.y() * other.x() diff --git a/geometry/src/util.rs b/geometry/src/util.rs index 59aeb112..56429c2e 100644 --- a/geometry/src/util.rs +++ b/geometry/src/util.rs @@ -10,12 +10,20 @@ //! Various utilities. +use std::f32; + /// Linear interpolation. #[inline] pub fn lerp(a: f32, b: f32, t: f32) -> f32 { a + (b - a) * t } +/// Clamping. +#[inline] +pub fn clamp(x: f32, min_val: f32, max_val: f32) -> f32 { + f32::min(max_val, f32::max(min_val, x)) +} + /// Divides `a` by `b`, rounding up. #[inline] pub fn alignup_i32(a: i32, b: i32) -> i32 { diff --git a/gl/src/renderer.rs b/gl/src/renderer.rs index 4782a558..ef22ca75 100644 --- a/gl/src/renderer.rs +++ b/gl/src/renderer.rs @@ -33,6 +33,7 @@ const FILL_COLORS_TEXTURE_WIDTH: u32 = 256; const FILL_COLORS_TEXTURE_HEIGHT: u32 = 256; pub struct Renderer { + // Core shaders fill_program: FillProgram, solid_tile_program: SolidTileProgram, mask_tile_program: MaskTileProgram, @@ -45,11 +46,16 @@ pub struct Renderer { mask_framebuffer: Framebuffer, fill_colors_texture: Texture, + // Postprocessing shaders + defringe_program: DefringeProgram, + defringe_vertex_array: DefringeVertexArray, + + // Debug pending_timer_queries: VecDeque, free_timer_queries: Vec, - pub debug_renderer: DebugRenderer, + // Extra info main_framebuffer_size: Size2D, } @@ -59,6 +65,8 @@ impl Renderer { let solid_tile_program = SolidTileProgram::new(); let mask_tile_program = MaskTileProgram::new(); + let defringe_program = DefringeProgram::new(); + let area_lut_texture = Texture::from_png("area-lut"); let quad_vertex_positions_buffer = Buffer::new(); @@ -72,6 +80,9 @@ impl Renderer { let solid_tile_vertex_array = SolidTileVertexArray::new(&solid_tile_program, &quad_vertex_positions_buffer); + let defringe_vertex_array = DefringeVertexArray::new(&defringe_program, + &quad_vertex_positions_buffer); + let mask_framebuffer = Framebuffer::new(&Size2D::new(MASK_FRAMEBUFFER_WIDTH, MASK_FRAMEBUFFER_HEIGHT)); @@ -92,6 +103,9 @@ impl Renderer { mask_framebuffer, fill_colors_texture, + defringe_program, + defringe_vertex_array, + pending_timer_queries: VecDeque::new(), free_timer_queries: vec![], @@ -452,3 +466,51 @@ impl MaskTileProgram { } } } + +struct DefringeProgram { + program: Program, + source_uniform: Uniform, + framebuffer_size_uniform: Uniform, + kernel_uniform: Uniform, +} + +impl DefringeProgram { + fn new() -> DefringeProgram { + let program = Program::new("defringe"); + let source_uniform = Uniform::new(&program, "Source"); + let framebuffer_size_uniform = Uniform::new(&program, "FramebufferSize"); + let kernel_uniform = Uniform::new(&program, "Kernel"); + DefringeProgram { program, source_uniform, framebuffer_size_uniform, kernel_uniform } + } +} + +struct DefringeVertexArray { + gl_vertex_array: GLuint, +} + +impl DefringeVertexArray { + fn new(defringe_program: &DefringeProgram, quad_vertex_positions_buffer: &Buffer) + -> DefringeVertexArray { + let mut gl_vertex_array = 0; + unsafe { + let position_attr = VertexAttr::new(&defringe_program.program, "Position"); + + gl::GenVertexArrays(1, &mut gl_vertex_array); + gl::BindVertexArray(gl_vertex_array); + gl::UseProgram(defringe_program.program.gl_program); + gl::BindBuffer(gl::ARRAY_BUFFER, quad_vertex_positions_buffer.gl_buffer); + position_attr.configure_float(2, gl::UNSIGNED_BYTE, false, 0, 0, 0); + } + + DefringeVertexArray { gl_vertex_array } + } +} + +impl Drop for DefringeVertexArray { + #[inline] + fn drop(&mut self) { + unsafe { + gl::DeleteVertexArrays(1, &mut self.gl_vertex_array); + } + } +} diff --git a/renderer/src/scene.rs b/renderer/src/scene.rs index d4e7c226..7b165ae2 100644 --- a/renderer/src/scene.rs +++ b/renderer/src/scene.rs @@ -116,14 +116,17 @@ impl Scene { PreparedBuildTransform::Perspective(ref perspective, ref quad) => { outline.clip_against_polygon(quad); outline.apply_perspective(perspective); + outline.prepare_for_tiling(&self.view_box); } PreparedBuildTransform::Transform2D(ref transform) => { outline.transform(transform); + outline.clip_against_rect(&self.view_box); + } + PreparedBuildTransform::None => { + outline.clip_against_rect(&self.view_box); } - PreparedBuildTransform::None => {} } - outline.clip_against_rect(&self.view_box); - outline.make_monotonic(); + outline.prepare_for_tiling(&self.view_box); outline } } diff --git a/renderer/src/tiles.rs b/renderer/src/tiles.rs index 5efcc1d4..54440307 100644 --- a/renderer/src/tiles.rs +++ b/renderer/src/tiles.rs @@ -91,10 +91,10 @@ impl<'o, 'z> Tiler<'o, 'z> { // Add new active edges. let strip_max_y = ((i32::from(strip_origin_y) + 1) * TILE_HEIGHT as i32) as f32; while let Some(queued_endpoint) = self.point_queue.peek() { - // We're done when we see an endpoint that belongs to the next tile. + // We're done when we see an endpoint that belongs to the next tile strip. // // Note that this test must be `>`, not `>=`, in order to make sure we don't miss - // active edges that lie precisely on the tile boundary. + // active edges that lie precisely on the tile strip boundary. if queued_endpoint.y > strip_max_y { break; }