Implement a blur filter for canvas shadows
This commit is contained in:
parent
d1c7da8bd2
commit
3a014d78eb
|
@ -1,6 +1,6 @@
|
||||||
// pathfinder/canvas/src/lib.rs
|
// pathfinder/canvas/src/lib.rs
|
||||||
//
|
//
|
||||||
// Copyright © 2019 The Pathfinder Project Developers.
|
// Copyright © 2020 The Pathfinder Project Developers.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||||
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
use pathfinder_color::ColorU;
|
use pathfinder_color::ColorU;
|
||||||
use pathfinder_content::dash::OutlineDash;
|
use pathfinder_content::dash::OutlineDash;
|
||||||
use pathfinder_content::effects::{BlendMode, CompositeOp, Effects, Filter};
|
use pathfinder_content::effects::{BlendMode, BlurDirection, CompositeOp, Effects, Filter};
|
||||||
use pathfinder_content::fill::FillRule;
|
use pathfinder_content::fill::FillRule;
|
||||||
use pathfinder_content::gradient::Gradient;
|
use pathfinder_content::gradient::Gradient;
|
||||||
use pathfinder_content::outline::{ArcDirection, Contour, Outline};
|
use pathfinder_content::outline::{ArcDirection, Contour, Outline};
|
||||||
|
@ -161,11 +161,34 @@ impl CanvasRenderingContext2D {
|
||||||
|
|
||||||
// Shadows
|
// Shadows
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn shadow_blur(&self) -> f32 {
|
||||||
|
self.current_state.shadow_blur
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_shadow_blur(&mut self, new_shadow_blur: f32) {
|
||||||
|
self.current_state.shadow_blur = new_shadow_blur;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn shadow_color(&self) -> ColorU {
|
||||||
|
match self.current_state.shadow_paint {
|
||||||
|
Paint::Color(color) => color,
|
||||||
|
_ => panic!("Unexpected shadow paint!"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_shadow_color(&mut self, new_shadow_color: ColorU) {
|
pub fn set_shadow_color(&mut self, new_shadow_color: ColorU) {
|
||||||
self.current_state.shadow_paint = Paint::Color(new_shadow_color);
|
self.current_state.shadow_paint = Paint::Color(new_shadow_color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn shadow_offset(&self) -> Vector2F {
|
||||||
|
self.current_state.shadow_offset
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_shadow_offset(&mut self, new_shadow_offset: Vector2F) {
|
pub fn set_shadow_offset(&mut self, new_shadow_offset: Vector2F) {
|
||||||
self.current_state.shadow_offset = new_shadow_offset;
|
self.current_state.shadow_offset = new_shadow_offset;
|
||||||
|
@ -235,7 +258,8 @@ impl CanvasRenderingContext2D {
|
||||||
let opacity = (self.current_state.global_alpha * 255.0) as u8;
|
let opacity = (self.current_state.global_alpha * 255.0) as u8;
|
||||||
|
|
||||||
if !self.current_state.shadow_paint.is_fully_transparent() {
|
if !self.current_state.shadow_paint.is_fully_transparent() {
|
||||||
let render_target_id = self.push_render_target_if_needed(composite_op);
|
let composite_render_target_id = self.push_render_target_if_needed(composite_op);
|
||||||
|
let shadow_blur_render_target_ids = self.push_shadow_blur_render_targets_if_needed();
|
||||||
|
|
||||||
let paint = self.current_state.resolve_paint(&self.current_state.shadow_paint);
|
let paint = self.current_state.resolve_paint(&self.current_state.shadow_paint);
|
||||||
let paint_id = self.scene.push_paint(&paint);
|
let paint_id = self.scene.push_paint(&paint);
|
||||||
|
@ -249,7 +273,8 @@ impl CanvasRenderingContext2D {
|
||||||
path.set_opacity(opacity);
|
path.set_opacity(opacity);
|
||||||
self.scene.push_path(path);
|
self.scene.push_path(path);
|
||||||
|
|
||||||
self.composite_render_target_if_needed(composite_op, render_target_id);
|
self.composite_shadow_blur_render_targets_if_needed(shadow_blur_render_target_ids);
|
||||||
|
self.composite_render_target_if_needed(composite_op, composite_render_target_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
let render_target_id = self.push_render_target_if_needed(composite_op);
|
let render_target_id = self.push_render_target_if_needed(composite_op);
|
||||||
|
@ -274,6 +299,19 @@ impl CanvasRenderingContext2D {
|
||||||
Some(self.scene.push_render_target(RenderTarget::new(render_target_size, String::new())))
|
Some(self.scene.push_render_target(RenderTarget::new(render_target_size, String::new())))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn push_shadow_blur_render_targets_if_needed(&mut self) -> Option<[RenderTargetId; 2]> {
|
||||||
|
if self.current_state.shadow_blur == 0.0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let render_target_size = self.scene.view_box().size().ceil().to_i32();
|
||||||
|
let render_target_id_a =
|
||||||
|
self.scene.push_render_target(RenderTarget::new(render_target_size, String::new()));
|
||||||
|
let render_target_id_b =
|
||||||
|
self.scene.push_render_target(RenderTarget::new(render_target_size, String::new()));
|
||||||
|
Some([render_target_id_a, render_target_id_b])
|
||||||
|
}
|
||||||
|
|
||||||
fn composite_render_target_if_needed(&mut self,
|
fn composite_render_target_if_needed(&mut self,
|
||||||
composite_op: Option<CompositeOp>,
|
composite_op: Option<CompositeOp>,
|
||||||
render_target_id: Option<RenderTargetId>) {
|
render_target_id: Option<RenderTargetId>) {
|
||||||
|
@ -287,6 +325,27 @@ impl CanvasRenderingContext2D {
|
||||||
Effects::new(Filter::Composite(composite_op)));
|
Effects::new(Filter::Composite(composite_op)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn composite_shadow_blur_render_targets_if_needed(
|
||||||
|
&mut self,
|
||||||
|
render_target_ids: Option<[RenderTargetId; 2]>) {
|
||||||
|
let render_target_ids = match render_target_ids {
|
||||||
|
None => return,
|
||||||
|
Some(render_target_ids) => render_target_ids,
|
||||||
|
};
|
||||||
|
|
||||||
|
let sigma = self.current_state.shadow_blur * 0.5;
|
||||||
|
self.scene.pop_render_target();
|
||||||
|
self.scene.draw_render_target(render_target_ids[1], Effects::new(Filter::Blur {
|
||||||
|
direction: BlurDirection::X,
|
||||||
|
sigma,
|
||||||
|
}));
|
||||||
|
self.scene.pop_render_target();
|
||||||
|
self.scene.draw_render_target(render_target_ids[0], Effects::new(Filter::Blur {
|
||||||
|
direction: BlurDirection::Y,
|
||||||
|
sigma,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
// Transformations
|
// Transformations
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -377,6 +436,7 @@ struct State {
|
||||||
fill_paint: Paint,
|
fill_paint: Paint,
|
||||||
stroke_paint: Paint,
|
stroke_paint: Paint,
|
||||||
shadow_paint: Paint,
|
shadow_paint: Paint,
|
||||||
|
shadow_blur: f32,
|
||||||
shadow_offset: Vector2F,
|
shadow_offset: Vector2F,
|
||||||
text_align: TextAlign,
|
text_align: TextAlign,
|
||||||
image_smoothing_enabled: bool,
|
image_smoothing_enabled: bool,
|
||||||
|
@ -401,6 +461,7 @@ impl State {
|
||||||
fill_paint: Paint::black(),
|
fill_paint: Paint::black(),
|
||||||
stroke_paint: Paint::black(),
|
stroke_paint: Paint::black(),
|
||||||
shadow_paint: Paint::transparent_black(),
|
shadow_paint: Paint::transparent_black(),
|
||||||
|
shadow_blur: 0.0,
|
||||||
shadow_offset: Vector2F::default(),
|
shadow_offset: Vector2F::default(),
|
||||||
text_align: TextAlign::Left,
|
text_align: TextAlign::Left,
|
||||||
image_smoothing_enabled: true,
|
image_smoothing_enabled: true,
|
||||||
|
|
|
@ -57,6 +57,15 @@ pub enum Filter {
|
||||||
/// If this is enabled, stem darkening is advised.
|
/// If this is enabled, stem darkening is advised.
|
||||||
gamma_correction: bool,
|
gamma_correction: bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// A blur operation in one direction, either horizontal or vertical.
|
||||||
|
///
|
||||||
|
/// To produce a full Gaussian blur, perform two successive blur operations, one in each
|
||||||
|
/// direction.
|
||||||
|
Blur {
|
||||||
|
direction: BlurDirection,
|
||||||
|
sigma: f32,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
@ -123,6 +132,12 @@ pub enum BlendMode {
|
||||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||||
pub struct DefringingKernel(pub [f32; 4]);
|
pub struct DefringingKernel(pub [f32; 4]);
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||||
|
pub enum BlurDirection {
|
||||||
|
X,
|
||||||
|
Y,
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for CompositeOp {
|
impl Default for CompositeOp {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn default() -> CompositeOp {
|
fn default() -> CompositeOp {
|
||||||
|
|
|
@ -156,6 +156,9 @@ impl GLDevice {
|
||||||
UniformData::Vec2(data) => {
|
UniformData::Vec2(data) => {
|
||||||
gl::Uniform2f(uniform.location, data.x(), data.y()); ck();
|
gl::Uniform2f(uniform.location, data.x(), data.y()); ck();
|
||||||
}
|
}
|
||||||
|
UniformData::Vec3(data) => {
|
||||||
|
gl::Uniform3f(uniform.location, data[0], data[1], data[2]); ck();
|
||||||
|
}
|
||||||
UniformData::Vec4(data) => {
|
UniformData::Vec4(data) => {
|
||||||
gl::Uniform4f(uniform.location, data.x(), data.y(), data.z(), data.w()); ck();
|
gl::Uniform4f(uniform.location, data.x(), data.y(), data.z(), data.w()); ck();
|
||||||
}
|
}
|
||||||
|
|
|
@ -173,6 +173,7 @@ pub enum UniformData {
|
||||||
Mat2(F32x4),
|
Mat2(F32x4),
|
||||||
Mat4([F32x4; 4]),
|
Mat4([F32x4; 4]),
|
||||||
Vec2(F32x2),
|
Vec2(F32x2),
|
||||||
|
Vec3([f32; 3]),
|
||||||
Vec4(F32x4),
|
Vec4(F32x4),
|
||||||
TextureUnit(u32),
|
TextureUnit(u32),
|
||||||
}
|
}
|
||||||
|
|
|
@ -934,6 +934,11 @@ impl MetalDevice {
|
||||||
uniform_buffer_data.write_f32::<NativeEndian>(vector.x()).unwrap();
|
uniform_buffer_data.write_f32::<NativeEndian>(vector.x()).unwrap();
|
||||||
uniform_buffer_data.write_f32::<NativeEndian>(vector.y()).unwrap();
|
uniform_buffer_data.write_f32::<NativeEndian>(vector.y()).unwrap();
|
||||||
}
|
}
|
||||||
|
UniformData::Vec3(array) => {
|
||||||
|
uniform_buffer_data.write_f32::<NativeEndian>(array[0]).unwrap();
|
||||||
|
uniform_buffer_data.write_f32::<NativeEndian>(array[1]).unwrap();
|
||||||
|
uniform_buffer_data.write_f32::<NativeEndian>(array[2]).unwrap();
|
||||||
|
}
|
||||||
UniformData::Vec4(vector) => {
|
UniformData::Vec4(vector) => {
|
||||||
uniform_buffer_data.write_f32::<NativeEndian>(vector.x()).unwrap();
|
uniform_buffer_data.write_f32::<NativeEndian>(vector.x()).unwrap();
|
||||||
uniform_buffer_data.write_f32::<NativeEndian>(vector.y()).unwrap();
|
uniform_buffer_data.write_f32::<NativeEndian>(vector.y()).unwrap();
|
||||||
|
|
|
@ -14,19 +14,21 @@ use crate::gpu::shaders::{AlphaTileBlendModeProgram, AlphaTileDodgeBurnProgram};
|
||||||
use crate::gpu::shaders::{AlphaTileHSLProgram, AlphaTileOverlayProgram};
|
use crate::gpu::shaders::{AlphaTileHSLProgram, AlphaTileOverlayProgram};
|
||||||
use crate::gpu::shaders::{AlphaTileProgram, AlphaTileVertexArray, CopyTileProgram};
|
use crate::gpu::shaders::{AlphaTileProgram, AlphaTileVertexArray, CopyTileProgram};
|
||||||
use crate::gpu::shaders::{CopyTileVertexArray, FillProgram, FillVertexArray, FilterBasicProgram};
|
use crate::gpu::shaders::{CopyTileVertexArray, FillProgram, FillVertexArray, FilterBasicProgram};
|
||||||
use crate::gpu::shaders::{FilterBasicVertexArray, FilterTextProgram, FilterTextVertexArray};
|
use crate::gpu::shaders::{FilterBasicVertexArray,FilterBlurProgram, FilterBlurVertexArray};
|
||||||
use crate::gpu::shaders::{MAX_FILLS_PER_BATCH, MaskTileProgram, MaskTileVertexArray};
|
use crate::gpu::shaders::{FilterTextProgram, FilterTextVertexArray, MAX_FILLS_PER_BATCH};
|
||||||
use crate::gpu::shaders::{ReprojectionProgram, ReprojectionVertexArray, SolidTileProgram};
|
use crate::gpu::shaders::{MaskTileProgram, MaskTileVertexArray, ReprojectionProgram};
|
||||||
use crate::gpu::shaders::{SolidTileVertexArray, StencilProgram, StencilVertexArray};
|
use crate::gpu::shaders::{ReprojectionVertexArray, SolidTileProgram, SolidTileVertexArray};
|
||||||
|
use crate::gpu::shaders::{StencilProgram, StencilVertexArray};
|
||||||
use crate::gpu_data::{AlphaTile, FillBatchPrimitive, MaskTile, PaintData, PaintPageContents};
|
use crate::gpu_data::{AlphaTile, FillBatchPrimitive, MaskTile, PaintData, PaintPageContents};
|
||||||
use crate::gpu_data::{PaintPageId, RenderCommand, SolidTileVertex};
|
use crate::gpu_data::{PaintPageId, RenderCommand, SolidTileVertex};
|
||||||
use crate::options::BoundingQuad;
|
use crate::options::BoundingQuad;
|
||||||
use crate::tiles::{TILE_HEIGHT, TILE_WIDTH};
|
use crate::tiles::{TILE_HEIGHT, TILE_WIDTH};
|
||||||
use pathfinder_color::{self as color, ColorF};
|
use pathfinder_color::{self as color, ColorF};
|
||||||
use pathfinder_content::effects::{BlendMode, CompositeOp, DefringingKernel, Effects, Filter};
|
use pathfinder_content::effects::{BlendMode, BlurDirection, CompositeOp, DefringingKernel};
|
||||||
|
use pathfinder_content::effects::{Effects, Filter};
|
||||||
use pathfinder_content::fill::FillRule;
|
use pathfinder_content::fill::FillRule;
|
||||||
use pathfinder_content::pattern::RenderTargetId;
|
use pathfinder_content::pattern::RenderTargetId;
|
||||||
use pathfinder_geometry::vector::{Vector2I, Vector4F};
|
use pathfinder_geometry::vector::{Vector2F, Vector2I, Vector4F};
|
||||||
use pathfinder_geometry::rect::RectI;
|
use pathfinder_geometry::rect::RectI;
|
||||||
use pathfinder_geometry::transform3d::Transform4F;
|
use pathfinder_geometry::transform3d::Transform4F;
|
||||||
use pathfinder_gpu::resources::ResourceLoader;
|
use pathfinder_gpu::resources::ResourceLoader;
|
||||||
|
@ -37,6 +39,7 @@ use pathfinder_gpu::{TextureFormat, TextureSamplingFlags, UniformData};
|
||||||
use pathfinder_simd::default::{F32x2, F32x4};
|
use pathfinder_simd::default::{F32x2, F32x4};
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
use std::f32;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::ops::{Add, Div};
|
use std::ops::{Add, Div};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
@ -48,6 +51,9 @@ static QUAD_VERTEX_INDICES: [u32; 6] = [0, 1, 3, 1, 2, 3];
|
||||||
pub(crate) const MASK_TILES_ACROSS: u32 = 256;
|
pub(crate) const MASK_TILES_ACROSS: u32 = 256;
|
||||||
pub(crate) const MASK_TILES_DOWN: u32 = 256;
|
pub(crate) const MASK_TILES_DOWN: u32 = 256;
|
||||||
|
|
||||||
|
// 1.0 / sqrt(2*pi)
|
||||||
|
const SQRT_2_PI_INV: f32 = 0.3989422804014327;
|
||||||
|
|
||||||
const TEXTURE_CACHE_SIZE: usize = 8;
|
const TEXTURE_CACHE_SIZE: usize = 8;
|
||||||
|
|
||||||
// FIXME(pcwalton): Shrink this again!
|
// FIXME(pcwalton): Shrink this again!
|
||||||
|
@ -118,6 +124,8 @@ where
|
||||||
// Filter shaders
|
// Filter shaders
|
||||||
filter_basic_program: FilterBasicProgram<D>,
|
filter_basic_program: FilterBasicProgram<D>,
|
||||||
filter_basic_vertex_array: FilterBasicVertexArray<D>,
|
filter_basic_vertex_array: FilterBasicVertexArray<D>,
|
||||||
|
filter_blur_program: FilterBlurProgram<D>,
|
||||||
|
filter_blur_vertex_array: FilterBlurVertexArray<D>,
|
||||||
filter_text_program: FilterTextProgram<D>,
|
filter_text_program: FilterTextProgram<D>,
|
||||||
filter_text_vertex_array: FilterTextVertexArray<D>,
|
filter_text_vertex_array: FilterTextVertexArray<D>,
|
||||||
gamma_lut_texture: D::Texture,
|
gamma_lut_texture: D::Texture,
|
||||||
|
@ -177,6 +185,7 @@ where
|
||||||
"tile_alpha_exclusion");
|
"tile_alpha_exclusion");
|
||||||
let alpha_tile_hsl_program = AlphaTileHSLProgram::new(&device, resources);
|
let alpha_tile_hsl_program = AlphaTileHSLProgram::new(&device, resources);
|
||||||
let filter_basic_program = FilterBasicProgram::new(&device, resources);
|
let filter_basic_program = FilterBasicProgram::new(&device, resources);
|
||||||
|
let filter_blur_program = FilterBlurProgram::new(&device, resources);
|
||||||
let filter_text_program = FilterTextProgram::new(&device, resources);
|
let filter_text_program = FilterTextProgram::new(&device, resources);
|
||||||
let stencil_program = StencilProgram::new(&device, resources);
|
let stencil_program = StencilProgram::new(&device, resources);
|
||||||
let reprojection_program = ReprojectionProgram::new(&device, resources);
|
let reprojection_program = ReprojectionProgram::new(&device, resources);
|
||||||
|
@ -276,6 +285,12 @@ where
|
||||||
&quad_vertex_positions_buffer,
|
&quad_vertex_positions_buffer,
|
||||||
&quad_vertex_indices_buffer,
|
&quad_vertex_indices_buffer,
|
||||||
);
|
);
|
||||||
|
let filter_blur_vertex_array = FilterBlurVertexArray::new(
|
||||||
|
&device,
|
||||||
|
&filter_blur_program,
|
||||||
|
&quad_vertex_positions_buffer,
|
||||||
|
&quad_vertex_indices_buffer,
|
||||||
|
);
|
||||||
let filter_text_vertex_array = FilterTextVertexArray::new(
|
let filter_text_vertex_array = FilterTextVertexArray::new(
|
||||||
&device,
|
&device,
|
||||||
&filter_text_program,
|
&filter_text_program,
|
||||||
|
@ -361,6 +376,8 @@ where
|
||||||
|
|
||||||
filter_basic_program,
|
filter_basic_program,
|
||||||
filter_basic_vertex_array,
|
filter_basic_vertex_array,
|
||||||
|
filter_blur_program,
|
||||||
|
filter_blur_vertex_array,
|
||||||
filter_text_program,
|
filter_text_program,
|
||||||
filter_text_vertex_array,
|
filter_text_vertex_array,
|
||||||
gamma_lut_texture,
|
gamma_lut_texture,
|
||||||
|
@ -1147,6 +1164,9 @@ where
|
||||||
defringing_kernel,
|
defringing_kernel,
|
||||||
gamma_correction)
|
gamma_correction)
|
||||||
}
|
}
|
||||||
|
Filter::Blur { direction, sigma } => {
|
||||||
|
self.draw_blur_render_target(render_target_id, direction, sigma)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.preserve_draw_framebuffer();
|
self.preserve_draw_framebuffer();
|
||||||
|
@ -1235,6 +1255,55 @@ where
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn draw_blur_render_target(&self,
|
||||||
|
render_target_id: RenderTargetId,
|
||||||
|
direction: BlurDirection,
|
||||||
|
sigma: f32) {
|
||||||
|
let clear_color = self.clear_color_for_draw_operation();
|
||||||
|
let source_framebuffer = &self.render_targets[render_target_id.0 as usize].framebuffer;
|
||||||
|
let source_texture = self.device.framebuffer_texture(source_framebuffer);
|
||||||
|
let source_texture_size = self.device.texture_size(source_texture);
|
||||||
|
let main_viewport = self.main_viewport();
|
||||||
|
|
||||||
|
let sigma_inv = 1.0 / sigma;
|
||||||
|
let gauss_coeff_x = SQRT_2_PI_INV * sigma_inv;
|
||||||
|
let gauss_coeff_y = f32::exp(-0.5 * sigma_inv * sigma_inv);
|
||||||
|
let gauss_coeff_z = gauss_coeff_y * gauss_coeff_y;
|
||||||
|
|
||||||
|
let src_offset = match direction {
|
||||||
|
BlurDirection::X => Vector2F::new(1.0, 0.0),
|
||||||
|
BlurDirection::Y => Vector2F::new(0.0, 1.0),
|
||||||
|
};
|
||||||
|
let src_offset_scale = src_offset / source_texture_size.to_f32();
|
||||||
|
|
||||||
|
let uniforms = vec![
|
||||||
|
(&self.filter_blur_program.framebuffer_size_uniform,
|
||||||
|
UniformData::Vec2(main_viewport.size().to_f32().0)),
|
||||||
|
(&self.filter_blur_program.src_uniform, UniformData::TextureUnit(0)),
|
||||||
|
(&self.filter_blur_program.src_offset_scale_uniform,
|
||||||
|
UniformData::Vec2(src_offset_scale.0)),
|
||||||
|
(&self.filter_blur_program.initial_gauss_coeff_uniform,
|
||||||
|
UniformData::Vec3([gauss_coeff_x, gauss_coeff_y, gauss_coeff_z])),
|
||||||
|
(&self.filter_blur_program.support_uniform,
|
||||||
|
UniformData::Int(f32::ceil(1.5 * sigma) as i32 * 2)),
|
||||||
|
];
|
||||||
|
|
||||||
|
self.device.draw_elements(6, &RenderState {
|
||||||
|
target: &self.draw_render_target(),
|
||||||
|
program: &self.filter_blur_program.program,
|
||||||
|
vertex_array: &self.filter_blur_vertex_array.vertex_array,
|
||||||
|
primitive: Primitive::Triangles,
|
||||||
|
textures: &[&source_texture],
|
||||||
|
uniforms: &uniforms,
|
||||||
|
viewport: main_viewport,
|
||||||
|
options: RenderOptions {
|
||||||
|
clear_ops: ClearOps { color: clear_color, ..ClearOps::default() },
|
||||||
|
blend: CompositeOp::SrcOver.to_blend_state(),
|
||||||
|
..RenderOptions::default()
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn blit_intermediate_dest_framebuffer_if_necessary(&mut self) {
|
fn blit_intermediate_dest_framebuffer_if_necessary(&mut self) {
|
||||||
if !self.flags.contains(RendererFlags::INTERMEDIATE_DEST_FRAMEBUFFER_NEEDED) {
|
if !self.flags.contains(RendererFlags::INTERMEDIATE_DEST_FRAMEBUFFER_NEEDED) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -572,6 +572,68 @@ impl<D> FilterBasicVertexArray<D> where D: Device {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct FilterBlurProgram<D> where D: Device {
|
||||||
|
pub program: D::Program,
|
||||||
|
pub framebuffer_size_uniform: D::Uniform,
|
||||||
|
pub src_uniform: D::Uniform,
|
||||||
|
pub src_offset_scale_uniform: D::Uniform,
|
||||||
|
pub initial_gauss_coeff_uniform: D::Uniform,
|
||||||
|
pub support_uniform: D::Uniform,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D> FilterBlurProgram<D> where D: Device {
|
||||||
|
pub fn new(device: &D, resources: &dyn ResourceLoader) -> FilterBlurProgram<D> {
|
||||||
|
let program = device.create_program_from_shader_names(resources,
|
||||||
|
"filter_blur",
|
||||||
|
"filter",
|
||||||
|
"filter_blur");
|
||||||
|
let framebuffer_size_uniform = device.get_uniform(&program, "FramebufferSize");
|
||||||
|
let src_uniform = device.get_uniform(&program, "Src");
|
||||||
|
let src_offset_scale_uniform = device.get_uniform(&program, "SrcOffsetScale");
|
||||||
|
let initial_gauss_coeff_uniform = device.get_uniform(&program, "InitialGaussCoeff");
|
||||||
|
let support_uniform = device.get_uniform(&program, "Support");
|
||||||
|
FilterBlurProgram {
|
||||||
|
program,
|
||||||
|
framebuffer_size_uniform,
|
||||||
|
src_uniform,
|
||||||
|
src_offset_scale_uniform,
|
||||||
|
initial_gauss_coeff_uniform,
|
||||||
|
support_uniform,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FilterBlurVertexArray<D> where D: Device {
|
||||||
|
pub vertex_array: D::VertexArray,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D> FilterBlurVertexArray<D> where D: Device {
|
||||||
|
pub fn new(
|
||||||
|
device: &D,
|
||||||
|
fill_blur_program: &FilterBlurProgram<D>,
|
||||||
|
quad_vertex_positions_buffer: &D::Buffer,
|
||||||
|
quad_vertex_indices_buffer: &D::Buffer,
|
||||||
|
) -> FilterBlurVertexArray<D> {
|
||||||
|
let vertex_array = device.create_vertex_array();
|
||||||
|
let position_attr = device.get_vertex_attr(&fill_blur_program.program, "Position")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
device.bind_buffer(&vertex_array, quad_vertex_positions_buffer, BufferTarget::Vertex);
|
||||||
|
device.configure_vertex_attr(&vertex_array, &position_attr, &VertexAttrDescriptor {
|
||||||
|
size: 2,
|
||||||
|
class: VertexAttrClass::Int,
|
||||||
|
attr_type: VertexAttrType::I16,
|
||||||
|
stride: 4,
|
||||||
|
offset: 0,
|
||||||
|
divisor: 0,
|
||||||
|
buffer_index: 0,
|
||||||
|
});
|
||||||
|
device.bind_buffer(&vertex_array, quad_vertex_indices_buffer, BufferTarget::Index);
|
||||||
|
|
||||||
|
FilterBlurVertexArray { vertex_array }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct FilterTextProgram<D> where D: Device {
|
pub struct FilterTextProgram<D> where D: Device {
|
||||||
pub program: D::Program,
|
pub program: D::Program,
|
||||||
pub source_uniform: D::Uniform,
|
pub source_uniform: D::Uniform,
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
#version {{version}}
|
||||||
|
// Automatically generated from files in pathfinder/shaders/. Do not edit!
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#extension GL_GOOGLE_include_directive : enable
|
||||||
|
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
uniform sampler2D uSrc;
|
||||||
|
uniform vec2 uSrcOffsetScale;
|
||||||
|
uniform vec3 uInitialGaussCoeff;
|
||||||
|
uniform int uSupport;
|
||||||
|
|
||||||
|
in vec2 vTexCoord;
|
||||||
|
|
||||||
|
out vec4 oFragColor;
|
||||||
|
|
||||||
|
void main(){
|
||||||
|
|
||||||
|
vec3 gaussCoeff = uInitialGaussCoeff;
|
||||||
|
float gaussSum = gaussCoeff . x;
|
||||||
|
vec4 color = texture(uSrc, vTexCoord)* gaussCoeff . x;
|
||||||
|
gaussCoeff . xy *= gaussCoeff . yz;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
for(int i = 1;i <= uSupport;i += 2){
|
||||||
|
float gaussPartialSum = gaussCoeff . x;
|
||||||
|
gaussCoeff . xy *= gaussCoeff . yz;
|
||||||
|
gaussPartialSum += gaussCoeff . x;
|
||||||
|
|
||||||
|
vec2 srcOffset = uSrcOffsetScale *(float(i)+ gaussCoeff . x / gaussPartialSum);
|
||||||
|
color +=(texture(uSrc, vTexCoord - srcOffset)+ texture(uSrc, vTexCoord + srcOffset))*
|
||||||
|
gaussPartialSum;
|
||||||
|
|
||||||
|
gaussSum += 2.0 * gaussPartialSum;
|
||||||
|
gaussCoeff . xy *= gaussCoeff . yz;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
color /= gaussSum;
|
||||||
|
color . rgb *= color . a;
|
||||||
|
oFragColor = color;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
// Automatically generated from files in pathfinder/shaders/. Do not edit!
|
||||||
|
#include <metal_stdlib>
|
||||||
|
#include <simd/simd.h>
|
||||||
|
|
||||||
|
using namespace metal;
|
||||||
|
|
||||||
|
struct spvDescriptorSetBuffer0
|
||||||
|
{
|
||||||
|
constant float3* uInitialGaussCoeff [[id(0)]];
|
||||||
|
texture2d<float> uSrc [[id(1)]];
|
||||||
|
sampler uSrcSmplr [[id(2)]];
|
||||||
|
constant int* uSupport [[id(3)]];
|
||||||
|
constant float2* uSrcOffsetScale [[id(4)]];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct main0_out
|
||||||
|
{
|
||||||
|
float4 oFragColor [[color(0)]];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct main0_in
|
||||||
|
{
|
||||||
|
float2 vTexCoord [[user(locn0)]];
|
||||||
|
};
|
||||||
|
|
||||||
|
fragment main0_out main0(main0_in in [[stage_in]], constant spvDescriptorSetBuffer0& spvDescriptorSet0 [[buffer(0)]])
|
||||||
|
{
|
||||||
|
main0_out out = {};
|
||||||
|
float3 gaussCoeff = (*spvDescriptorSet0.uInitialGaussCoeff);
|
||||||
|
float gaussSum = gaussCoeff.x;
|
||||||
|
float4 color = spvDescriptorSet0.uSrc.sample(spvDescriptorSet0.uSrcSmplr, in.vTexCoord) * gaussCoeff.x;
|
||||||
|
float2 _39 = gaussCoeff.xy * gaussCoeff.yz;
|
||||||
|
gaussCoeff = float3(_39.x, _39.y, gaussCoeff.z);
|
||||||
|
for (int i = 1; i <= (*spvDescriptorSet0.uSupport); i += 2)
|
||||||
|
{
|
||||||
|
float gaussPartialSum = gaussCoeff.x;
|
||||||
|
float2 _64 = gaussCoeff.xy * gaussCoeff.yz;
|
||||||
|
gaussCoeff = float3(_64.x, _64.y, gaussCoeff.z);
|
||||||
|
gaussPartialSum += gaussCoeff.x;
|
||||||
|
float2 srcOffset = (*spvDescriptorSet0.uSrcOffsetScale) * (float(i) + (gaussCoeff.x / gaussPartialSum));
|
||||||
|
color += ((spvDescriptorSet0.uSrc.sample(spvDescriptorSet0.uSrcSmplr, (in.vTexCoord - srcOffset)) + spvDescriptorSet0.uSrc.sample(spvDescriptorSet0.uSrcSmplr, (in.vTexCoord + srcOffset))) * gaussPartialSum);
|
||||||
|
gaussSum += (2.0 * gaussPartialSum);
|
||||||
|
float2 _108 = gaussCoeff.xy * gaussCoeff.yz;
|
||||||
|
gaussCoeff = float3(_108.x, _108.y, gaussCoeff.z);
|
||||||
|
}
|
||||||
|
color /= float4(gaussSum);
|
||||||
|
float3 _123 = color.xyz * color.w;
|
||||||
|
color = float4(_123.x, _123.y, _123.z, color.w);
|
||||||
|
out.oFragColor = color;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
|
@ -11,12 +11,13 @@ SHADERS=\
|
||||||
demo_ground.vs.glsl \
|
demo_ground.vs.glsl \
|
||||||
fill.fs.glsl \
|
fill.fs.glsl \
|
||||||
fill.vs.glsl \
|
fill.vs.glsl \
|
||||||
|
filter.vs.glsl \
|
||||||
|
filter_basic.fs.glsl \
|
||||||
|
filter_blur.fs.glsl \
|
||||||
|
filter_text.fs.glsl \
|
||||||
mask.vs.glsl \
|
mask.vs.glsl \
|
||||||
mask_evenodd.fs.glsl \
|
mask_evenodd.fs.glsl \
|
||||||
mask_winding.fs.glsl \
|
mask_winding.fs.glsl \
|
||||||
filter.vs.glsl \
|
|
||||||
filter_basic.fs.glsl \
|
|
||||||
filter_text.fs.glsl \
|
|
||||||
reproject.fs.glsl \
|
reproject.fs.glsl \
|
||||||
reproject.vs.glsl \
|
reproject.vs.glsl \
|
||||||
stencil.fs.glsl \
|
stencil.fs.glsl \
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
#version 330
|
||||||
|
|
||||||
|
// pathfinder/shaders/filter_blur.fs.glsl
|
||||||
|
//
|
||||||
|
// Copyright © 2020 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.
|
||||||
|
|
||||||
|
// The technique here is "Incremental Computation of the Gaussian", GPU Gems 3, chapter 40:
|
||||||
|
// https://developer.nvidia.com/gpugems/gpugems3/part-vi-gpu-computing/chapter-40-incremental-computation-gaussian
|
||||||
|
//
|
||||||
|
// It's the same technique WebRender uses.
|
||||||
|
|
||||||
|
#extension GL_GOOGLE_include_directive : enable
|
||||||
|
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
#define SQRT_PI_2_INV 2.5066282746310002
|
||||||
|
|
||||||
|
uniform sampler2D uSrc;
|
||||||
|
uniform vec2 uSrcOffsetScale;
|
||||||
|
uniform vec3 uInitialGaussCoeff;
|
||||||
|
uniform int uSupport;
|
||||||
|
|
||||||
|
in vec2 vTexCoord;
|
||||||
|
|
||||||
|
out vec4 oFragColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// Set up our incremental calculation.
|
||||||
|
vec3 gaussCoeff = uInitialGaussCoeff;
|
||||||
|
float gaussSum = gaussCoeff.x;
|
||||||
|
vec4 color = texture(uSrc, vTexCoord) * gaussCoeff.x;
|
||||||
|
gaussCoeff.xy *= gaussCoeff.yz;
|
||||||
|
|
||||||
|
// This is a common trick that lets us use the texture filtering hardware to evaluate two
|
||||||
|
// texels at a time. The basic principle is that, if c0 and c1 are colors of adjacent texels
|
||||||
|
// and k0 and k1 are arbitrary factors, the formula `k0 * c0 + k1 * c1` is equivalent to
|
||||||
|
// `(k0 + k1) * lerp(c0, c1, k1 / (k0 + k1))`. Linear interpolation, as performed by the
|
||||||
|
// texturing hardware when sampling adjacent pixels in one direction, evaluates
|
||||||
|
// `lerp(c0, c1, t)` where t is the offset from the texel with color `c0`. To evaluate the
|
||||||
|
// formula `k0 * c0 + k1 * c1`, therefore, we can use the texture hardware to perform linear
|
||||||
|
// interpolation with `t = k1 / (k0 + k1)`.
|
||||||
|
for (int i = 1; i <= uSupport; i += 2) {
|
||||||
|
float gaussPartialSum = gaussCoeff.x;
|
||||||
|
gaussCoeff.xy *= gaussCoeff.yz;
|
||||||
|
gaussPartialSum += gaussCoeff.x;
|
||||||
|
|
||||||
|
vec2 srcOffset = uSrcOffsetScale * (float(i) + gaussCoeff.x / gaussPartialSum);
|
||||||
|
color += (texture(uSrc, vTexCoord - srcOffset) + texture(uSrc, vTexCoord + srcOffset)) *
|
||||||
|
gaussPartialSum;
|
||||||
|
|
||||||
|
gaussSum += 2.0 * gaussPartialSum;
|
||||||
|
gaussCoeff.xy *= gaussCoeff.yz;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finish.
|
||||||
|
color /= gaussSum;
|
||||||
|
color.rgb *= color.a;
|
||||||
|
oFragColor = color;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue