Add a defringing shader for subpixel antialiasing, untested as of yet
This commit is contained in:
parent
a5234e6695
commit
f25682f0f1
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue