diff --git a/demo3/shaders/post.fs.glsl b/demo3/shaders/post.fs.glsl index 060534bb..4eaf5c57 100644 --- a/demo3/shaders/post.fs.glsl +++ b/demo3/shaders/post.fs.glsl @@ -18,15 +18,17 @@ precision highp float; uniform sampler2D uSource; uniform sampler2D uGammaLUT; uniform vec2 uFramebufferSize; +// Zero if no subpixel AA is to be performed. uniform vec4 uKernel; -uniform vec4 uBGColor; +// Zero if no gamma correction is to be performed. +uniform vec4 uGammaCorrectionBGColor; in vec2 vTexCoord; out vec4 oFragColor; float gammaCorrectChannel(float fgColor) { - return texture(uGammaLUT, vec2(fgColor, 1.0 - uBGColor)).r; + return texture(uGammaLUT, vec2(fgColor, 1.0 - uGammaCorrectionBGColor)).r; } // `fgColor` is in linear space. @@ -76,7 +78,7 @@ void main() { } // Apply gamma correction if necessary. - if (uBGColor.a > 0.0) + if (uGammaCorrectionBGColor.a > 0.0) fgColor = gammaCorrect(fgColor); // Finish. diff --git a/demo3/shaders/post.vs.glsl b/demo3/shaders/post.vs.glsl index 74172dd8..259c7960 100644 --- a/demo3/shaders/post.vs.glsl +++ b/demo3/shaders/post.vs.glsl @@ -12,13 +12,11 @@ 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); + gl_Position = vec4(aPosition * 2.0 - 1.0, 0.0, 1.0); } diff --git a/demo3/src/main.rs b/demo3/src/main.rs index 52af292a..0a6f1b63 100644 --- a/demo3/src/main.rs +++ b/demo3/src/main.rs @@ -21,6 +21,8 @@ use pathfinder_gl::device::Texture; use pathfinder_gl::renderer::Renderer; use pathfinder_renderer::builder::{RenderOptions, RenderTransform, SceneBuilder}; use pathfinder_renderer::gpu_data::BuiltScene; +use pathfinder_renderer::paint::ColorU; +use pathfinder_renderer::post::DEFRINGING_KERNEL_CORE_GRAPHICS; use pathfinder_renderer::scene::Scene; use pathfinder_renderer::z_buffer::ZBuffer; use pathfinder_svg::SceneExt; @@ -46,7 +48,7 @@ const MAIN_FRAMEBUFFER_HEIGHT: u32 = 800; const MOUSELOOK_ROTATION_SPEED: f32 = 0.007; const CAMERA_VELOCITY: f32 = 25.0; -const BACKGROUND_COLOR: f32 = 0.22; +const BACKGROUND_COLOR: ColorU = ColorU { r: 32, g: 32, b: 32, a: 255 }; const EFFECTS_WINDOW_WIDTH: i32 = 550; const EFFECTS_WINDOW_HEIGHT: i32 = BUTTON_HEIGHT * 3 + PADDING * 4; @@ -129,7 +131,9 @@ fn main() { let count = if frame_counter == 0 { 2 } else { 1 }; for _ in 0..count { - scene_thread_proxy.sender.send(MainToSceneMsg::Build(perspective)).unwrap(); + scene_thread_proxy.sender.send(MainToSceneMsg::Build(BuildOptions { + perspective + })).unwrap(); } // FIXME(pcwalton): This can cause us to miss UI events if things get backed up... @@ -204,8 +208,24 @@ fn main() { tile_time } = scene_thread_proxy.receiver.recv().unwrap(); unsafe { - gl::ClearColor(BACKGROUND_COLOR, BACKGROUND_COLOR, BACKGROUND_COLOR, 1.0); + gl::ClearColor(BACKGROUND_COLOR.r as f32 / 255.0, + BACKGROUND_COLOR.g as f32 / 255.0, + BACKGROUND_COLOR.b as f32 / 255.0, + BACKGROUND_COLOR.a as f32 / 255.0); gl::Clear(gl::COLOR_BUFFER_BIT); + + if demo_ui.gamma_correction_effect_enabled { + renderer.enable_gamma_correction(BACKGROUND_COLOR); + } else { + renderer.disable_gamma_correction(); + } + + if demo_ui.subpixel_aa_effect_enabled { + renderer.enable_subpixel_aa(&DEFRINGING_KERNEL_CORE_GRAPHICS); + } else { + renderer.disable_subpixel_aa(); + } + renderer.render_scene(&built_scene); let rendering_time = renderer.shift_timer_query(); @@ -268,9 +288,9 @@ impl SceneThread { RectF32::new(Point2DF32::default(), Point2DF32::new(size.width as f32, size.height as f32)); } - MainToSceneMsg::Build(perspective) => { + MainToSceneMsg::Build(build_options) => { let start_time = Instant::now(); - let built_scene = build_scene(&self.scene, perspective, &self.options); + let built_scene = build_scene(&self.scene, build_options, self.options.jobs); let tile_time = Instant::now() - start_time; self.sender.send(SceneToMainMsg::Render { built_scene, tile_time }).unwrap(); } @@ -281,7 +301,11 @@ impl SceneThread { enum MainToSceneMsg { SetDrawableSize(Size2D), - Build(Option), + Build(BuildOptions), +} + +struct BuildOptions { + perspective: Option, } enum SceneToMainMsg { @@ -344,11 +368,11 @@ fn load_scene(options: &Options) -> Scene { scene } -fn build_scene(scene: &Scene, perspective: Option, options: &Options) -> BuiltScene { +fn build_scene(scene: &Scene, build_options: BuildOptions, jobs: Option) -> BuiltScene { let z_buffer = ZBuffer::new(scene.view_box); let render_options = RenderOptions { - transform: match perspective { + transform: match build_options.perspective { None => RenderTransform::Transform2D(Transform2DF32::default()), Some(perspective) => RenderTransform::Perspective(perspective), }, @@ -356,7 +380,7 @@ fn build_scene(scene: &Scene, perspective: Option, options: &Option }; let built_objects = panic::catch_unwind(|| { - match options.jobs { + match jobs { Some(1) => scene.build_objects_sequentially(render_options, &z_buffer), _ => scene.build_objects(render_options, &z_buffer), } diff --git a/gl/src/device.rs b/gl/src/device.rs index bafed0b5..bc6eda9c 100644 --- a/gl/src/device.rs +++ b/gl/src/device.rs @@ -70,8 +70,7 @@ pub struct Framebuffer { } impl Framebuffer { - pub fn new(size: &Size2D) -> Framebuffer { - let texture = Texture::new_r16f(size); + pub fn new(texture: Texture) -> Framebuffer { let mut gl_framebuffer = 0; unsafe { gl::GenFramebuffers(1, &mut gl_framebuffer); @@ -271,7 +270,7 @@ pub struct Texture { } impl Texture { - fn new_r16f(size: &Size2D) -> Texture { + pub fn new_r16f(size: &Size2D) -> Texture { let mut texture = Texture { gl_texture: 0, size: *size }; unsafe { gl::GenTextures(1, &mut texture.gl_texture); diff --git a/gl/src/renderer.rs b/gl/src/renderer.rs index 2b5a610d..e8cca8f5 100644 --- a/gl/src/renderer.rs +++ b/gl/src/renderer.rs @@ -14,9 +14,11 @@ use crate::device::{TimerQuery, Uniform, VertexAttr}; use euclid::Size2D; use gl::types::{GLfloat, GLint, GLuint}; use pathfinder_renderer::gpu_data::{Batch, BuiltScene, SolidTileScenePrimitive}; -use pathfinder_renderer::paint::ObjectShader; +use pathfinder_renderer::paint::{ColorU, ObjectShader}; +use pathfinder_renderer::post::DefringingKernel; use pathfinder_renderer::tiles::{TILE_HEIGHT, TILE_WIDTH}; use std::collections::VecDeque; +use std::ptr; use std::time::Duration; static QUAD_VERTEX_POSITIONS: [u8; 8] = [0, 0, 1, 0, 1, 1, 0, 1]; @@ -47,6 +49,7 @@ pub struct Renderer { fill_colors_texture: Texture, // Postprocessing shader + postprocess_source_framebuffer: Option, postprocess_program: PostprocessProgram, postprocess_vertex_array: PostprocessVertexArray, gamma_lut_texture: Texture, @@ -58,6 +61,7 @@ pub struct Renderer { // Extra info main_framebuffer_size: Size2D, + postprocess_options: PostprocessOptions, } impl Renderer { @@ -85,8 +89,9 @@ impl Renderer { let postprocess_vertex_array = PostprocessVertexArray::new(&postprocess_program, &quad_vertex_positions_buffer); - let mask_framebuffer = Framebuffer::new(&Size2D::new(MASK_FRAMEBUFFER_WIDTH, - MASK_FRAMEBUFFER_HEIGHT)); + let mask_framebuffer_texture = Texture::new_r16f(&Size2D::new(MASK_FRAMEBUFFER_WIDTH, + MASK_FRAMEBUFFER_HEIGHT)); + let mask_framebuffer = Framebuffer::new(mask_framebuffer_texture); let fill_colors_texture = Texture::new_rgba(&Size2D::new(FILL_COLORS_TEXTURE_WIDTH, FILL_COLORS_TEXTURE_HEIGHT)); @@ -105,6 +110,7 @@ impl Renderer { mask_framebuffer, fill_colors_texture, + postprocess_source_framebuffer: None, postprocess_program, postprocess_vertex_array, gamma_lut_texture, @@ -115,10 +121,13 @@ impl Renderer { debug_ui, main_framebuffer_size: *main_framebuffer_size, + postprocess_options: PostprocessOptions::default(), } } pub fn render_scene(&mut self, built_scene: &BuiltScene) { + self.init_postprocessing_framebuffer(); + let timer_query = self.free_timer_queries.pop().unwrap_or_else(|| TimerQuery::new()); timer_query.begin(); @@ -133,6 +142,10 @@ impl Renderer { self.draw_batch_mask_tiles(batch); } + if self.postprocessing_needed() { + self.postprocess(); + } + timer_query.end(); self.pending_timer_queries.push_back(timer_query); } @@ -148,11 +161,32 @@ impl Renderer { Some(result) } + #[inline] pub fn set_main_framebuffer_size(&mut self, new_framebuffer_size: &Size2D) { self.main_framebuffer_size = *new_framebuffer_size; self.debug_ui.set_framebuffer_size(new_framebuffer_size); } + #[inline] + pub fn disable_subpixel_aa(&mut self) { + self.postprocess_options.defringing_kernel = None; + } + + #[inline] + pub fn enable_subpixel_aa(&mut self, defringing_kernel: &DefringingKernel) { + self.postprocess_options.defringing_kernel = Some(*defringing_kernel); + } + + #[inline] + pub fn disable_gamma_correction(&mut self) { + self.postprocess_options.gamma_correction_bg_color = None; + } + + #[inline] + pub fn enable_gamma_correction(&mut self, bg_color: ColorU) { + self.postprocess_options.gamma_correction_bg_color = Some(bg_color); + } + fn upload_shaders(&mut self, shaders: &[ObjectShader]) { let size = Size2D::new(FILL_COLORS_TEXTURE_WIDTH, FILL_COLORS_TEXTURE_HEIGHT); let mut fill_colors = vec![0; size.width as usize * size.height as usize * 4]; @@ -208,11 +242,8 @@ impl Renderer { fn draw_batch_mask_tiles(&mut self, batch: &Batch) { unsafe { - gl::BindFramebuffer(gl::FRAMEBUFFER, 0); - gl::Viewport(0, - 0, - self.main_framebuffer_size.width as GLint, - self.main_framebuffer_size.height as GLint); + self.bind_draw_framebuffer(); + self.set_main_viewport(); gl::BindVertexArray(self.mask_tile_vertex_array.gl_vertex_array); gl::UseProgram(self.mask_tile_program.program.gl_program); @@ -244,11 +275,8 @@ impl Renderer { fn draw_solid_tiles(&mut self, solid_tiles: &[SolidTileScenePrimitive]) { unsafe { - gl::BindFramebuffer(gl::FRAMEBUFFER, 0); - gl::Viewport(0, - 0, - self.main_framebuffer_size.width as GLint, - self.main_framebuffer_size.height as GLint); + self.bind_draw_framebuffer(); + self.set_main_viewport(); gl::BindVertexArray(self.solid_tile_vertex_array.gl_vertex_array); gl::UseProgram(self.solid_tile_program.program.gl_program); @@ -269,6 +297,100 @@ impl Renderer { gl::DrawArraysInstanced(gl::TRIANGLE_FAN, 0, 4, solid_tiles.len() as GLint); } } + + fn postprocess(&mut self) { + unsafe { + gl::BindFramebuffer(gl::FRAMEBUFFER, 0); + self.set_main_viewport(); + + gl::BindVertexArray(self.postprocess_vertex_array.gl_vertex_array); + gl::UseProgram(self.postprocess_program.program.gl_program); + gl::Uniform2f(self.postprocess_program.framebuffer_size_uniform.location, + self.main_framebuffer_size.width as GLfloat, + self.main_framebuffer_size.height as GLfloat); + match self.postprocess_options.defringing_kernel { + Some(ref kernel) => { + debug_assert!(kernel.0.len() == 4); + let data: *const f32 = kernel.0.as_ptr(); + gl::Uniform4fv(self.postprocess_program.kernel_uniform.location, 1, data); + } + None => { + gl::Uniform4f(self.postprocess_program.kernel_uniform.location, + 0.0, + 0.0, + 0.0, + 0.0); + } + } + self.postprocess_source_framebuffer.as_ref().unwrap().texture.bind(0); + gl::Uniform1i(self.postprocess_program.source_uniform.location, 0); + self.gamma_lut_texture.bind(1); + gl::Uniform1i(self.postprocess_program.gamma_lut_uniform.location, 1); + let gamma_correction_bg_color_uniform_location = + self.postprocess_program.gamma_correction_bg_color_uniform.location; + match self.postprocess_options.gamma_correction_bg_color { + None => { + gl::Uniform4f(gamma_correction_bg_color_uniform_location, 0.0, 0.0, 0.0, 0.0); + } + Some(color) => { + gl::Uniform4f(gamma_correction_bg_color_uniform_location, + color.r as f32 / 255.0, + color.g as f32 / 255.0, + color.b as f32 / 255.0, + color.a as f32 / 255.0); + } + } + gl::Disable(gl::BLEND); + gl::DrawArrays(gl::TRIANGLE_FAN, 0, 4); + } + } + + fn bind_draw_framebuffer(&self) { + unsafe { + if self.postprocessing_needed() { + let fbo = self.postprocess_source_framebuffer.as_ref().unwrap().gl_framebuffer; + gl::BindFramebuffer(gl::FRAMEBUFFER, fbo); + } else { + gl::BindFramebuffer(gl::FRAMEBUFFER, 0); + } + } + } + + fn set_main_viewport(&self) { + unsafe { + gl::Viewport(0, + 0, + self.main_framebuffer_size.width as GLint, + self.main_framebuffer_size.height as GLint); + } + } + + fn init_postprocessing_framebuffer(&mut self) { + if !self.postprocessing_needed() { + self.postprocess_source_framebuffer = None; + return; + } + + if let Some(ref existing_framebuffer) = self.postprocess_source_framebuffer { + if existing_framebuffer.texture.size == self.main_framebuffer_size { + return; + } + } + + self.postprocess_source_framebuffer = + Some(Framebuffer::new(Texture::new_rgba(&self.main_framebuffer_size))); + } + + fn postprocessing_needed(&self) -> bool { + self.postprocess_options.defringing_kernel.is_some() || + self.postprocess_options.gamma_correction_bg_color.is_some() + } +} + +#[derive(Clone, Copy, Default)] +struct PostprocessOptions { + defringing_kernel: Option, + gamma_correction_bg_color: Option, } struct FillVertexArray { @@ -481,7 +603,7 @@ struct PostprocessProgram { framebuffer_size_uniform: Uniform, kernel_uniform: Uniform, gamma_lut_uniform: Uniform, - bg_color_uniform: Uniform, + gamma_correction_bg_color_uniform: Uniform, } impl PostprocessProgram { @@ -491,14 +613,14 @@ impl PostprocessProgram { let framebuffer_size_uniform = Uniform::new(&program, "FramebufferSize"); let kernel_uniform = Uniform::new(&program, "Kernel"); let gamma_lut_uniform = Uniform::new(&program, "GammaLUT"); - let bg_color_uniform = Uniform::new(&program, "BGColor"); + let gamma_correction_bg_color_uniform = Uniform::new(&program, "GammaCorrectionBGColor"); PostprocessProgram { program, source_uniform, framebuffer_size_uniform, kernel_uniform, gamma_lut_uniform, - bg_color_uniform, + gamma_correction_bg_color_uniform, } } } diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index 009f349b..e09c8474 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -13,6 +13,7 @@ pub mod builder; pub mod gpu_data; pub mod paint; +pub mod post; pub mod scene; pub mod serialization; pub mod tiles; diff --git a/renderer/src/post.rs b/renderer/src/post.rs new file mode 100644 index 00000000..ec1af3e4 --- /dev/null +++ b/renderer/src/post.rs @@ -0,0 +1,27 @@ +// pathfinder/renderer/src/post.rs +// +// 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. + +//! Functionality related to postprocessing effects. +//! +//! Since these effects run on GPU as fragment shaders, this contains no +//! implementations, just shared declarations. + +#[derive(Clone, Copy, PartialEq, Debug)] +pub struct DefringingKernel(pub [f32; 4]); + +/// This intentionally does not precisely match what Core Graphics does (a +/// Lanczos function), because we don't want any ringing artefacts. +pub static DEFRINGING_KERNEL_CORE_GRAPHICS: DefringingKernel = DefringingKernel([ + 0.033165660, 0.102074051, 0.221434336, 0.286651906 +]); +pub static DEFRINGING_KERNEL_FREETYPE: DefringingKernel = DefringingKernel([ + 0.0, 0.031372549, 0.301960784, 0.337254902 +]); +