Get subpixel AA and gamma correction working in 2D

This commit is contained in:
Patrick Walton 2019-03-25 16:13:56 -07:00
parent 29cedb96ef
commit db3851d754
8 changed files with 163 additions and 117 deletions

View File

@ -116,7 +116,7 @@ impl<W> DemoApp<W> where W: Window {
let resources = window.resource_loader();
let options = Options::get();
let view_box_size = view_box_size(options.mode, &window_size, false);
let view_box_size = view_box_size(options.mode, &window_size);
let built_svg = load_scene(resources, &options.input_path);
let message = get_svg_building_message(&built_svg);
@ -199,9 +199,7 @@ impl<W> DemoApp<W> where W: Window {
}
fn build_scene(&mut self) {
let view_box_size = view_box_size(self.ui.mode,
&self.window_size,
self.ui.subpixel_aa_effect_enabled);
let view_box_size = view_box_size(self.ui.mode, &self.window_size);
let render_transform = match self.camera {
Camera::ThreeD { ref mut transform, ref mut velocity } => {
@ -232,6 +230,7 @@ impl<W> DemoApp<W> where W: Window {
} else {
None
},
subpixel_aa_enabled: self.ui.subpixel_aa_effect_enabled,
barrel_distortion,
})).unwrap();
}
@ -254,9 +253,7 @@ impl<W> DemoApp<W> where W: Window {
}
Event::WindowResized(new_size) => {
self.window_size = new_size;
let view_box_size = view_box_size(self.ui.mode,
&self.window_size,
self.ui.subpixel_aa_effect_enabled);
let view_box_size = view_box_size(self.ui.mode, &self.window_size);
self.scene_thread_proxy.set_drawable_size(view_box_size);
self.renderer.set_main_framebuffer_size(self.window_size.device_size());
self.dirty = true;
@ -343,9 +340,7 @@ impl<W> DemoApp<W> where W: Window {
let built_svg = load_scene(self.window.resource_loader(), svg_path);
self.ui.message = get_svg_building_message(&built_svg);
let view_box_size = view_box_size(self.ui.mode,
&self.window_size,
self.ui.subpixel_aa_effect_enabled);
let view_box_size = view_box_size(self.ui.mode, &self.window_size);
self.scene_view_box = built_svg.scene.view_box;
self.monochrome_scene_color = built_svg.scene.monochrome_color();
@ -539,24 +534,20 @@ impl<W> DemoApp<W> where W: Window {
let render_msg = &self.current_frame.as_ref().unwrap().render_msg;
let built_scene = &render_msg.render_scenes[viewport_index as usize].built_scene;
let view_box_size = view_box_size(self.ui.mode,
&self.window_size,
self.ui.subpixel_aa_effect_enabled);
let view_box_size = view_box_size(self.ui.mode, &self.window_size);
let viewport_origin_x = viewport_index as i32 * view_box_size.x();
let viewport = RectI32::new(Point2DI32::new(viewport_origin_x, 0), view_box_size);
self.renderer.set_viewport(viewport);
match self.monochrome_scene_color {
None => self.renderer.set_render_mode(RenderMode::Multicolor),
Some(fill_color) => {
Some(fg_color) => {
self.renderer.set_render_mode(RenderMode::Monochrome {
fill_color: fill_color.to_f32(),
gamma_correction_bg_color: if self.ui.gamma_correction_effect_enabled {
Some(self.background_color())
} else {
None
},
fg_color: fg_color.to_f32(),
bg_color: self.background_color().to_f32(),
gamma_correction: self.ui.gamma_correction_effect_enabled,
defringing_kernel: if self.ui.subpixel_aa_effect_enabled {
// TODO(pcwalton): Select FreeType defringing kernel as necessary.
Some(DEFRINGING_KERNEL_CORE_GRAPHICS)
} else {
None
@ -709,6 +700,7 @@ struct BuildOptions {
render_transforms: Vec<RenderTransform>,
stem_darkening_font_size: Option<f32>,
barrel_distortion: Option<BarrelDistortionCoefficients>,
subpixel_aa_enabled: bool,
}
struct SceneToMainMsg {
@ -810,8 +802,6 @@ fn build_scene(scene: &Scene,
render_transform: RenderTransform,
jobs: Option<usize>)
-> BuiltScene {
let z_buffer = ZBuffer::new(scene.view_box);
let render_options = RenderOptions {
transform: render_transform,
dilation: match build_options.stem_darkening_font_size {
@ -822,9 +812,13 @@ fn build_scene(scene: &Scene,
}
},
barrel_distortion: build_options.barrel_distortion,
subpixel_aa_enabled: build_options.subpixel_aa_enabled,
};
let built_options = render_options.prepare(scene.bounds);
let effective_view_box = scene.effective_view_box(&built_options);
let z_buffer = ZBuffer::new(effective_view_box);
let quad = built_options.quad();
let built_objects = panic::catch_unwind(|| {
@ -846,7 +840,7 @@ fn build_scene(scene: &Scene,
let mut built_scene = BuiltScene::new(scene.view_box, &quad, scene.objects.len() as u32);
built_scene.shaders = scene.build_shaders();
let mut scene_builder = SceneBuilder::new(built_objects, z_buffer, scene.view_box);
let mut scene_builder = SceneBuilder::new(built_objects, z_buffer, effective_view_box);
built_scene.solid_tiles = scene_builder.build_solid_tiles();
while let Some(batch) = scene_builder.build_batch() {
built_scene.batches.push(batch);
@ -966,13 +960,12 @@ fn emit_message<W>(ui: &mut DemoUI<GLDevice>,
});
}
fn view_box_size(mode: Mode, window_size: &WindowSize, use_subpixel_aa: bool) -> Point2DI32 {
fn view_box_size(mode: Mode, window_size: &WindowSize) -> Point2DI32 {
let window_drawable_size = window_size.device_size();
let initial_size = match mode {
match mode {
Mode::TwoD | Mode::ThreeD => window_drawable_size,
Mode::VR => Point2DI32::new(window_drawable_size.x() / 2, window_drawable_size.y()),
};
if use_subpixel_aa { initial_size.scale_xy(Point2DI32::new(3, 1)) } else { initial_size }
}
}
struct Frame {

View File

@ -364,6 +364,9 @@ impl Device for GLDevice {
fn set_uniform(&self, uniform: &Self::Uniform, data: UniformData) {
unsafe {
match data {
UniformData::Int(value) => {
gl::Uniform1i(uniform.location, value); ck();
}
UniformData::Mat4(data) => {
assert_eq!(mem::size_of::<[F32x4; 4]>(), 4 * 4 * 4);
let data_ptr: *const F32x4 = data.as_ptr();

View File

@ -191,6 +191,7 @@ pub enum ShaderKind {
#[derive(Clone, Copy)]
pub enum UniformData {
Int(i32),
Mat4([F32x4; 4]),
Vec2(F32x4),
Vec4(F32x4),

View File

@ -128,6 +128,7 @@ pub struct RenderOptions {
pub transform: RenderTransform,
pub dilation: Point2DF32,
pub barrel_distortion: Option<BarrelDistortionCoefficients>,
pub subpixel_aa_enabled: bool,
}
impl RenderOptions {
@ -136,6 +137,7 @@ impl RenderOptions {
transform: self.transform.prepare(bounds),
dilation: self.dilation,
barrel_distortion: self.barrel_distortion,
subpixel_aa_enabled: self.subpixel_aa_enabled,
}
}
}
@ -205,6 +207,7 @@ pub struct PreparedRenderOptions {
pub transform: PreparedRenderTransform,
pub dilation: Point2DF32,
pub barrel_distortion: Option<BarrelDistortionCoefficients>,
pub subpixel_aa_enabled: bool,
}
impl PreparedRenderOptions {
@ -222,3 +225,13 @@ pub enum PreparedRenderTransform {
Transform2D(Transform2DF32),
Perspective { perspective: Perspective, clip_polygon: Vec<Point2DF32>, quad: [Point3DF32; 4] }
}
impl PreparedRenderTransform {
#[inline]
pub fn is_2d(&self) -> bool {
match *self {
PreparedRenderTransform::Transform2D(_) => true,
_ => false,
}
}
}

View File

@ -15,7 +15,7 @@ use crate::scene::ObjectShader;
use crate::tiles::{TILE_HEIGHT, TILE_WIDTH};
use pathfinder_geometry::basic::point::{Point2DI32, Point3DF32};
use pathfinder_geometry::basic::rect::RectI32;
use pathfinder_geometry::color::{ColorF, ColorU};
use pathfinder_geometry::color::ColorF;
use pathfinder_gpu::resources::ResourceLoader;
use pathfinder_gpu::{BlendState, BufferTarget, BufferUploadMode, DepthFunc, DepthState, Device};
use pathfinder_gpu::{Primitive, RenderState, StencilFunc, StencilState, TextureFormat};
@ -321,7 +321,7 @@ impl<D> Renderer<D> where D: Device {
self.device.bind_vertex_array(&alpha_tile_vertex_array.vertex_array);
self.device.use_program(&alpha_tile_program.program);
self.device.set_uniform(&alpha_tile_program.framebuffer_size_uniform,
UniformData::Vec2(self.viewport.size().to_f32().0));
UniformData::Vec2(self.draw_viewport().size().to_f32().0));
self.device.set_uniform(&alpha_tile_program.tile_size_uniform,
UniformData::Vec2(I32x4::new(TILE_WIDTH as i32,
TILE_HEIGHT as i32,
@ -349,9 +349,13 @@ impl<D> Renderer<D> where D: Device {
0,
0).to_f32x4()));
}
RenderMode::Monochrome { fill_color, .. } => {
RenderMode::Monochrome { .. } if self.postprocessing_needed() => {
self.device.set_uniform(&self.alpha_monochrome_tile_program.fill_color_uniform,
UniformData::Vec4(fill_color.0));
UniformData::Vec4(F32x4::splat(1.0)));
}
RenderMode::Monochrome { fg_color, .. } => {
self.device.set_uniform(&self.alpha_monochrome_tile_program.fill_color_uniform,
UniformData::Vec4(fg_color.0));
}
}
@ -370,13 +374,15 @@ impl<D> Renderer<D> where D: Device {
}
fn draw_solid_tiles(&mut self, built_scene: &BuiltScene) {
self.bind_draw_framebuffer();
let solid_tile_vertex_array = self.solid_tile_vertex_array();
let solid_tile_program = self.solid_tile_program();
self.device.bind_vertex_array(&solid_tile_vertex_array.vertex_array);
self.device.use_program(&solid_tile_program.program);
self.device.set_uniform(&solid_tile_program.framebuffer_size_uniform,
UniformData::Vec2(self.viewport.size().0.to_f32x4()));
UniformData::Vec2(self.draw_viewport().size().0.to_f32x4()));
self.device.set_uniform(&solid_tile_program.tile_size_uniform,
UniformData::Vec2(I32x4::new(TILE_WIDTH as i32,
TILE_HEIGHT as i32,
@ -396,9 +402,13 @@ impl<D> Renderer<D> where D: Device {
0,
0).to_f32x4()));
}
RenderMode::Monochrome { fill_color, .. } => {
RenderMode::Monochrome { .. } if self.postprocessing_needed() => {
self.device.set_uniform(&self.solid_monochrome_tile_program.fill_color_uniform,
UniformData::Vec4(fill_color.0));
UniformData::Vec4(F32x4::splat(1.0)));
}
RenderMode::Monochrome { fg_color, .. } => {
self.device.set_uniform(&self.solid_monochrome_tile_program.fill_color_uniform,
UniformData::Vec4(fg_color.0));
}
}
@ -414,17 +424,19 @@ impl<D> Renderer<D> where D: Device {
}
fn postprocess(&mut self) {
let (fill_color, defringing_kernel, gamma_correction_bg_color);
let (fg_color, bg_color, defringing_kernel, gamma_correction_enabled);
match self.render_mode {
RenderMode::Multicolor => return,
RenderMode::Monochrome {
fill_color: fc,
defringing_kernel: dk,
gamma_correction_bg_color: gcbc,
fg_color: fg,
bg_color: bg,
defringing_kernel: kernel,
gamma_correction,
} => {
fill_color = fc;
defringing_kernel = dk;
gamma_correction_bg_color = gcbc;
fg_color = fg;
bg_color = bg;
defringing_kernel = kernel;
gamma_correction_enabled = gamma_correction;
}
}
@ -456,22 +468,13 @@ impl<D> Renderer<D> where D: Device {
self.device.bind_texture(&self.gamma_lut_texture, 1);
self.device.set_uniform(&self.postprocess_program.gamma_lut_uniform,
UniformData::TextureUnit(1));
let gamma_correction_bg_color_uniform = &self.postprocess_program
.gamma_correction_bg_color_uniform;
match gamma_correction_bg_color {
None => {
self.device.set_uniform(gamma_correction_bg_color_uniform,
UniformData::Vec4(F32x4::default()));
}
Some(color) => {
self.device.set_uniform(gamma_correction_bg_color_uniform,
UniformData::Vec4(color.to_f32().0));
}
}
self.device.draw_arrays(Primitive::TriangleFan, 4, &RenderState {
blend: BlendState::RGBOneAlphaOne,
..RenderState::default()
});
self.device.set_uniform(&self.postprocess_program.fg_color_uniform,
UniformData::Vec4(fg_color.0));
self.device.set_uniform(&self.postprocess_program.bg_color_uniform,
UniformData::Vec4(bg_color.0));
self.device.set_uniform(&self.postprocess_program.gamma_correction_enabled_uniform,
UniformData::Int(gamma_correction_enabled as i32));
self.device.draw_arrays(Primitive::TriangleFan, 4, &RenderState::default());
}
fn solid_tile_program(&self) -> &SolidTileProgram<D> {
@ -543,12 +546,14 @@ impl<D> Renderer<D> where D: Device {
return;
}
let source_framebuffer_size = self.draw_viewport().size();
match self.postprocess_source_framebuffer {
Some(ref framebuffer) if
self.device.texture_size(self.device.framebuffer_texture(framebuffer)) ==
self.viewport.size() => {}
source_framebuffer_size => {}
_ => {
let texture = self.device.create_texture(TextureFormat::R8, self.viewport.size());
let texture = self.device.create_texture(TextureFormat::R8,
source_framebuffer_size);
self.postprocess_source_framebuffer = Some(self.device.create_framebuffer(texture))
}
};
@ -559,8 +564,8 @@ impl<D> Renderer<D> where D: Device {
fn postprocessing_needed(&self) -> bool {
match self.render_mode {
RenderMode::Monochrome { ref defringing_kernel, gamma_correction_bg_color, .. } => {
defringing_kernel.is_some() || gamma_correction_bg_color.is_some()
RenderMode::Monochrome { ref defringing_kernel, gamma_correction, .. } => {
defringing_kernel.is_some() || gamma_correction
}
_ => false,
}
@ -573,6 +578,16 @@ impl<D> Renderer<D> where D: Device {
Some(StencilState { func: StencilFunc::Equal, reference: 1, mask: 1, write: false })
}
fn draw_viewport(&self) -> RectI32 {
match self.render_mode {
RenderMode::Monochrome { defringing_kernel: Some(..), .. } => {
RectI32::new(Point2DI32::default(),
self.viewport.size().scale_xy(Point2DI32::new(3, 1)))
}
_ => self.viewport
}
}
}
struct FillVertexArray<D> where D: Device {
@ -824,8 +839,6 @@ struct AlphaTileProgram<D> where D: Device {
tile_size_uniform: D::Uniform,
stencil_texture_uniform: D::Uniform,
stencil_texture_size_uniform: D::Uniform,
fill_colors_texture_uniform: D::Uniform,
fill_colors_texture_size_uniform: D::Uniform,
view_box_origin_uniform: D::Uniform,
}
@ -839,9 +852,6 @@ impl<D> AlphaTileProgram<D> where D: Device {
let tile_size_uniform = device.get_uniform(&program, "TileSize");
let stencil_texture_uniform = device.get_uniform(&program, "StencilTexture");
let stencil_texture_size_uniform = device.get_uniform(&program, "StencilTextureSize");
let fill_colors_texture_uniform = device.get_uniform(&program, "FillColorsTexture");
let fill_colors_texture_size_uniform = device.get_uniform(&program,
"FillColorsTextureSize");
let view_box_origin_uniform = device.get_uniform(&program, "ViewBoxOrigin");
AlphaTileProgram {
program,
@ -849,8 +859,6 @@ impl<D> AlphaTileProgram<D> where D: Device {
tile_size_uniform,
stencil_texture_uniform,
stencil_texture_size_uniform,
fill_colors_texture_uniform,
fill_colors_texture_size_uniform,
view_box_origin_uniform,
}
}
@ -897,7 +905,9 @@ struct PostprocessProgram<D> where D: Device {
framebuffer_size_uniform: D::Uniform,
kernel_uniform: D::Uniform,
gamma_lut_uniform: D::Uniform,
gamma_correction_bg_color_uniform: D::Uniform,
gamma_correction_enabled_uniform: D::Uniform,
fg_color_uniform: D::Uniform,
bg_color_uniform: D::Uniform,
}
impl<D> PostprocessProgram<D> where D: Device {
@ -908,8 +918,10 @@ impl<D> PostprocessProgram<D> where D: Device {
let framebuffer_size_uniform = device.get_uniform(&program, "FramebufferSize");
let kernel_uniform = device.get_uniform(&program, "Kernel");
let gamma_lut_uniform = device.get_uniform(&program, "GammaLUT");
let gamma_correction_bg_color_uniform = device.get_uniform(&program,
"GammaCorrectionBGColor");
let gamma_correction_enabled_uniform = device.get_uniform(&program,
"GammaCorrectionEnabled");
let fg_color_uniform = device.get_uniform(&program, "FGColor");
let bg_color_uniform = device.get_uniform(&program, "BGColor");
PostprocessProgram {
program,
source_uniform,
@ -917,7 +929,9 @@ impl<D> PostprocessProgram<D> where D: Device {
framebuffer_size_uniform,
kernel_uniform,
gamma_lut_uniform,
gamma_correction_bg_color_uniform,
gamma_correction_enabled_uniform,
fg_color_uniform,
bg_color_uniform,
}
}
}
@ -990,9 +1004,10 @@ impl<D> StencilVertexArray<D> where D: Device {
pub enum RenderMode {
Multicolor,
Monochrome {
fill_color: ColorF,
fg_color: ColorF,
bg_color: ColorF,
defringing_kernel: Option<DefringingKernel>,
gamma_correction_bg_color: Option<ColorU>,
gamma_correction: bool,
},
}

View File

@ -15,7 +15,9 @@ use crate::gpu_data::BuiltObject;
use crate::tiles::Tiler;
use crate::z_buffer::ZBuffer;
use hashbrown::HashMap;
use pathfinder_geometry::basic::point::Point2DF32;
use pathfinder_geometry::basic::rect::{RectF32, RectI32};
use pathfinder_geometry::basic::transform2d::Transform2DF32;
use pathfinder_geometry::color::ColorU;
use pathfinder_geometry::outline::Outline;
use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator};
@ -74,7 +76,7 @@ impl Scene {
let outline = self.apply_render_options(&object.outline, &built_options);
let mut tiler = Tiler::new(
&outline,
self.view_box,
self.effective_view_box(&built_options),
object_index as u16,
ShaderId(object.paint.0),
z_buffer,
@ -94,7 +96,7 @@ impl Scene {
let outline = self.apply_render_options(&object.outline, &built_options);
let mut tiler = Tiler::new(
&outline,
self.view_box,
self.effective_view_box(&built_options),
object_index as u16,
ShaderId(object.paint.0),
z_buffer,
@ -107,6 +109,8 @@ impl Scene {
fn apply_render_options(&self, original_outline: &Outline, options: &PreparedRenderOptions)
-> Outline {
let effective_view_box = self.effective_view_box(options);
let mut outline;
match options.transform {
PreparedRenderTransform::Perspective { ref perspective, ref clip_polygon, .. } => {
@ -121,17 +125,26 @@ impl Scene {
if let Some(barrel_distortion) = options.barrel_distortion {
outline.barrel_distort(barrel_distortion, perspective.window_size);
}
// TODO(pcwalton): Support subpixel AA in 3D.
}
}
PreparedRenderTransform::Transform2D(ref transform) => {
_ => {
// TODO(pcwalton): Short circuit.
outline = (*original_outline).clone();
outline.transform(transform);
outline.clip_against_rect(self.view_box);
if options.transform.is_2d() || options.subpixel_aa_enabled {
let mut transform = match options.transform {
PreparedRenderTransform::Transform2D(transform) => transform,
PreparedRenderTransform::None => Transform2DF32::default(),
PreparedRenderTransform::Perspective { .. } => unreachable!(),
};
if options.subpixel_aa_enabled {
transform = transform.post_mul(&Transform2DF32::from_scale(
&Point2DF32::new(3.0, 1.0)))
}
PreparedRenderTransform::None => {
outline = (*original_outline).clone();
outline.clip_against_rect(self.view_box);
outline.transform(&transform);
}
outline.clip_against_rect(effective_view_box);
}
}
@ -141,7 +154,7 @@ impl Scene {
// TODO(pcwalton): Fold this into previous passes to avoid unnecessary clones during
// monotonic conversion.
outline.prepare_for_tiling(self.view_box);
outline.prepare_for_tiling(self.effective_view_box(options));
outline
}
@ -155,6 +168,15 @@ impl Scene {
}
Some(self.paints[first_paint_id.0 as usize].color)
}
#[inline]
pub fn effective_view_box(&self, render_options: &PreparedRenderOptions) -> RectF32 {
if render_options.subpixel_aa_enabled {
self.view_box.scale_xy(Point2DF32::new(3.0, 1.0))
} else {
self.view_box
}
}
}
impl Debug for Scene {

View File

@ -17,6 +17,9 @@ precision highp float;
uniform sampler2D uSource;
uniform vec2 uSourceSize;
uniform vec4 uFGColor;
uniform vec4 uBGColor;
uniform int uGammaCorrectionEnabled;
in vec2 vTexCoord;
@ -32,9 +35,9 @@ float sample1Tap(float offset) {
void main() {
// Apply defringing if necessary.
vec4 fgColor;
vec3 alpha;
if (uKernel.w == 0.0) {
fgColor = texture(uSource, vTexCoord);
alpha = texture(uSource, vTexCoord).rrr;
} else {
vec4 alphaLeft, alphaRight;
float alphaCenter;
@ -44,13 +47,13 @@ void main() {
float g = convolve7Tap(vec4(alphaLeft.yzw, alphaCenter), alphaRight.xyz);
float b = convolve7Tap(vec4(alphaLeft.zw, alphaCenter, alphaRight.x), alphaRight.yzw);
fgColor = vec4(r);
alpha = vec3(r, g, b);
}
// Apply gamma correction if necessary.
/*if (uGammaCorrectionBGColor.a > 0.0)
fgColor.rgb = gammaCorrect(fgColor.rgb);*/
if (uGammaCorrectionEnabled != 0)
alpha = gammaCorrect(uBGColor.rgb, alpha);
// Finish.
oFragColor = fgColor;
oFragColor = vec4(mix(uBGColor.rgb, uFGColor.rgb, alpha), 1.0);
}

View File

@ -12,17 +12,13 @@
// expects.
uniform sampler2D uGammaLUT;
// The background color to blend against. Zero if no gamma correction is to be
// performed.
uniform vec4 uGammaCorrectionBGColor;
float gammaCorrectChannel(float fgColor) {
return texture(uGammaLUT, vec2(fgColor, 1.0 - uGammaCorrectionBGColor)).r;
float gammaCorrectChannel(float bgColor, float fgColor) {
return texture(uGammaLUT, vec2(fgColor, 1.0 - bgColor)).r;
}
// `fgColor` is in linear space.
vec3 gammaCorrect(vec3 fgColor) {
return vec3(gammaCorrectChannel(fgColor.r),
gammaCorrectChannel(fgColor.g),
gammaCorrectChannel(fgColor.b));
vec3 gammaCorrect(vec3 bgColor, vec3 fgColor) {
return vec3(gammaCorrectChannel(bgColor.r, fgColor.r),
gammaCorrectChannel(bgColor.g, fgColor.g),
gammaCorrectChannel(bgColor.b, fgColor.b));
}