Add a defringing shader for subpixel antialiasing, untested as of yet

This commit is contained in:
Patrick Walton 2019-02-04 16:04:13 -08:00
parent a5234e6695
commit f25682f0f1
9 changed files with 271 additions and 39 deletions

View File

@ -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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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);
}

View File

@ -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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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);
}

View File

@ -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 {

View File

@ -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<f32>) {
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<f32>) {
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) {
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,20 +363,82 @@ 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<f32>) {
// 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.
// 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);
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<Rect<f32>>) {
*bounds = Some(match *bounds {
None => self.bounds,

View File

@ -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()

View File

@ -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 {

View File

@ -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<TimerQuery>,
free_timer_queries: Vec<TimerQuery>,
pub debug_renderer: DebugRenderer,
// Extra info
main_framebuffer_size: Size2D<u32>,
}
@ -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);
}
}
}

View File

@ -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);
}
PreparedBuildTransform::None => {}
}
outline.clip_against_rect(&self.view_box);
outline.make_monotonic();
}
PreparedBuildTransform::None => {
outline.clip_against_rect(&self.view_box);
}
}
outline.prepare_for_tiling(&self.view_box);
outline
}
}

View File

@ -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;
}