From 55df287fec815105f3f1fd154a41984f84e24d37 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Thu, 26 Mar 2020 20:01:17 -0700 Subject: [PATCH] Move radial gradients from the CPU to the GPU --- Cargo.lock | 1 + color/src/lib.rs | 2 +- content/src/effects.rs | 17 +- content/src/gradient.rs | 65 ++++--- examples/canvas_nanovg/Cargo.toml | 3 + examples/canvas_nanovg/src/main.rs | 14 +- renderer/src/builder.rs | 19 +- renderer/src/gpu/renderer.rs | 24 ++- renderer/src/gpu/shaders.rs | 6 +- renderer/src/paint.rs | 247 +++++++------------------- renderer/src/scene.rs | 2 +- resources/shaders/gl3/tile.fs.glsl | 140 ++++++++++++++- resources/shaders/metal/tile.fs.metal | 194 ++++++++++++-------- shaders/tile.fs.glsl | 142 ++++++++++++++- 14 files changed, 558 insertions(+), 318 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a3ed56d9..bbd4ba43 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -295,6 +295,7 @@ dependencies = [ "pathfinder_gpu 0.1.0", "pathfinder_renderer 0.1.0", "pathfinder_resources 0.1.0", + "pathfinder_simd 0.4.0", "sdl2 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "sdl2-sys 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/color/src/lib.rs b/color/src/lib.rs index afe1efdc..0dec865c 100644 --- a/color/src/lib.rs +++ b/color/src/lib.rs @@ -98,7 +98,7 @@ impl Debug for ColorU { } } -#[derive(Clone, Copy, Default)] +#[derive(Clone, Copy, PartialEq, Default)] pub struct ColorF(pub F32x4); impl ColorF { diff --git a/content/src/effects.rs b/content/src/effects.rs index b3166cc4..c2a695d1 100644 --- a/content/src/effects.rs +++ b/content/src/effects.rs @@ -11,6 +11,9 @@ //! Special effects that can be applied to layers. use pathfinder_color::ColorF; +use pathfinder_geometry::line_segment::LineSegment2F; +use pathfinder_geometry::vector::Vector2F; +use pathfinder_simd::default::F32x2; /// This intentionally does not precisely match what Core Graphics does (a /// Lanczos function), because we don't want any ringing artefacts. @@ -29,18 +32,28 @@ pub const MAX_STEM_DARKENING_AMOUNT: [f32; 2] = [0.3, 0.3]; pub const MAX_STEM_DARKENING_PIXELS_PER_EM: f32 = 72.0; /// Effects that can be applied to a layer. -#[derive(Clone, Copy, Debug, Default)] +#[derive(Clone, Copy, Debug, PartialEq, Default)] pub struct Effects { /// The shader that should be used when compositing this layer onto its destination. pub filter: Filter, } /// The shader that should be used when compositing this layer onto its destination. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, PartialEq, Debug)] pub enum Filter { /// No special filter. None, + /// Converts a linear gradient to a radial one. + RadialGradient { + /// The line that the circles lie along. + line: LineSegment2F, + /// The radii of the circles at the two endpoints. + radii: F32x2, + /// The origin of the linearized gradient in the texture. + uv_origin: Vector2F, + }, + /// Performs postprocessing operations useful for monochrome text. Text { /// The foreground color of the text. diff --git a/content/src/gradient.rs b/content/src/gradient.rs index d607229a..c2998f0f 100644 --- a/content/src/gradient.rs +++ b/content/src/gradient.rs @@ -13,6 +13,7 @@ use crate::util; use pathfinder_color::ColorU; use pathfinder_geometry::line_segment::LineSegment2F; use pathfinder_geometry::util as geometry_util; +use pathfinder_simd::default::F32x2; use std::cmp::{Ordering, PartialOrd}; use std::convert; use std::hash::{Hash, Hasher}; @@ -20,20 +21,17 @@ use std::mem; #[derive(Clone, PartialEq, Debug)] pub struct Gradient { - pub geometry: GradientGeometry, + /// The line this gradient runs along. + /// + /// If this is a radial gradient, this is the line that connects the two circles. It may have + /// zero-length in the case of simple radial gradients. + pub line: LineSegment2F, + /// For radial gradients, the radii of the start and endpoints respectively. If this is a + /// linear gradient, this is `None`. + pub radii: Option, stops: SortedVector, } -#[derive(Clone, Copy, PartialEq, Debug)] -pub enum GradientGeometry { - Linear(LineSegment2F), - Radial { - line: LineSegment2F, - start_radius: f32, - end_radius: f32, - } -} - #[derive(Clone, Copy, PartialEq, PartialOrd, Debug)] pub struct ColorStop { pub offset: f32, @@ -44,19 +42,15 @@ impl Eq for Gradient {} impl Hash for Gradient { fn hash(&self, state: &mut H) where H: Hasher { - match self.geometry { - GradientGeometry::Linear(line) => { - (0).hash(state); - util::hash_line_segment(line, state); - } - GradientGeometry::Radial { line, start_radius, end_radius } => { + util::hash_line_segment(self.line, state); + match self.radii { + None => (0).hash(state), + Some(radii) => { (1).hash(state); - util::hash_line_segment(line, state); - util::hash_f32(start_radius, state); - util::hash_f32(end_radius, state); + util::hash_f32(radii.x(), state); + util::hash_f32(radii.y(), state); } } - self.stops.hash(state); } } @@ -74,19 +68,14 @@ impl Hash for ColorStop { } impl Gradient { - #[inline] - pub fn new(geometry: GradientGeometry) -> Gradient { - Gradient { geometry, stops: SortedVector::new() } - } - #[inline] pub fn linear(line: LineSegment2F) -> Gradient { - Gradient::new(GradientGeometry::Linear(line)) + Gradient { line, radii: None, stops: SortedVector::new() } } #[inline] - pub fn radial(line: LineSegment2F, start_radius: f32, end_radius: f32) -> Gradient { - Gradient::new(GradientGeometry::Radial { line, start_radius, end_radius }) + pub fn radial(line: LineSegment2F, radii: F32x2) -> Gradient { + Gradient { line, radii: Some(radii), stops: SortedVector::new() } } #[inline] @@ -95,13 +84,23 @@ impl Gradient { } #[inline] - pub fn geometry(&self) -> &GradientGeometry { - &self.geometry + pub fn line(&self) -> LineSegment2F { + self.line } #[inline] - pub fn geometry_mut(&mut self) -> &mut GradientGeometry { - &mut self.geometry + pub fn set_line(&mut self, line: LineSegment2F) { + self.line = line + } + + #[inline] + pub fn radii(&self) -> Option { + self.radii + } + + #[inline] + pub fn set_radii(&mut self, radii: Option) { + self.radii = radii } #[inline] diff --git a/examples/canvas_nanovg/Cargo.toml b/examples/canvas_nanovg/Cargo.toml index f954c5e7..94c8c66b 100644 --- a/examples/canvas_nanovg/Cargo.toml +++ b/examples/canvas_nanovg/Cargo.toml @@ -34,3 +34,6 @@ path = "../../renderer" [dependencies.pathfinder_resources] path = "../../resources" + +[dependencies.pathfinder_simd] +path = "../../simd" diff --git a/examples/canvas_nanovg/src/main.rs b/examples/canvas_nanovg/src/main.rs index 792eac3c..42cd7fa3 100644 --- a/examples/canvas_nanovg/src/main.rs +++ b/examples/canvas_nanovg/src/main.rs @@ -29,6 +29,7 @@ use pathfinder_renderer::gpu::options::{DestFramebuffer, RendererOptions}; use pathfinder_renderer::gpu::renderer::Renderer; use pathfinder_renderer::options::BuildOptions; use pathfinder_resources::fs::FilesystemResourceLoader; +use pathfinder_simd::default::F32x2; use sdl2::event::Event; use sdl2::keyboard::Keycode; use sdl2::video::GLProfile; @@ -134,9 +135,9 @@ fn draw_eyes(canvas: &mut CanvasRenderingContext2D, canvas.fill_path(path, FillRule::Winding); let gloss_position = eyes_left_position - eyes_radii.scale_xy(Vector2F::new(0.25, 0.5)); + let gloss_radii = F32x2::new(0.1, 0.75) * F32x2::splat(eyes_radii.x()); let mut gloss = Gradient::radial(LineSegment2F::new(gloss_position, gloss_position), - eyes_radii.x() * 0.1, - eyes_radii.x() * 0.75); + gloss_radii); gloss.add_color_stop(ColorStop::new(ColorU::new(255, 255, 255, 128), 0.0)); gloss.add_color_stop(ColorStop::new(ColorU::new(255, 255, 255, 0), 1.0)); canvas.set_fill_style(FillStyle::Gradient(gloss)); @@ -146,8 +147,7 @@ fn draw_eyes(canvas: &mut CanvasRenderingContext2D, let gloss_position = eyes_right_position - eyes_radii.scale_xy(Vector2F::new(0.25, 0.5)); let mut gloss = Gradient::radial(LineSegment2F::new(gloss_position, gloss_position), - eyes_radii.x() * 0.1, - eyes_radii.x() * 0.75); + gloss_radii); gloss.add_color_stop(ColorStop::new(ColorU::new(255, 255, 255, 128), 0.0)); gloss.add_color_stop(ColorStop::new(ColorU::new(255, 255, 255, 0), 1.0)); canvas.set_fill_style(FillStyle::Gradient(gloss)); @@ -251,8 +251,7 @@ fn draw_graph(canvas: &mut CanvasRenderingContext2D, rect: RectF, time: f32) { for &sample_point in &sample_points { let gradient_center = sample_point + Vector2F::new(0.0, 2.0); let mut background = Gradient::radial(LineSegment2F::new(gradient_center, gradient_center), - 3.0, - 8.0); + F32x2::new(3.0, 8.0)); background.add_color_stop(ColorStop::new(ColorU::new(0, 0, 0, 32), 0.0)); background.add_color_stop(ColorStop::new(ColorU::transparent_black(), 1.0)); canvas.set_fill_style(FillStyle::Gradient(background)); @@ -372,8 +371,7 @@ fn draw_color_wheel(canvas: &mut CanvasRenderingContext2D, rect: RectF, time: f3 // Fill the selection circle. let mut gradient = Gradient::radial(LineSegment2F::new(selection_circle_center, selection_circle_center), - 7.0, - 9.0); + F32x2::new(7.0, 9.0)); gradient.add_color_stop(ColorStop::new(ColorU::new(0, 0, 0, 64), 0.0)); gradient.add_color_stop(ColorStop::new(ColorU::transparent_black(), 1.0)); canvas.set_fill_style(FillStyle::Gradient(gradient)); diff --git a/renderer/src/builder.rs b/renderer/src/builder.rs index 851c0256..9cded136 100644 --- a/renderer/src/builder.rs +++ b/renderer/src/builder.rs @@ -21,7 +21,7 @@ use crate::tile_map::DenseTileMap; use crate::tiles::{self, DrawTilingPathInfo, PackedTile, TILE_HEIGHT, TILE_WIDTH}; use crate::tiles::{Tiler, TilingPathInfo}; use crate::z_buffer::{DepthMetadata, ZBuffer}; -use pathfinder_content::effects::{BlendMode, Effects, Filter}; +use pathfinder_content::effects::{BlendMode, Effects}; use pathfinder_content::fill::FillRule; use pathfinder_content::render_target::RenderTargetId; use pathfinder_geometry::line_segment::{LineSegment2F, LineSegmentU4, LineSegmentU8}; @@ -55,6 +55,7 @@ pub(crate) struct ObjectBuilder { struct BuiltDrawPath { path: BuiltPath, blend_mode: BlendMode, + effects: Effects, color_texture_page_0: TexturePageId, color_texture_page_1: TexturePageId, sampling_flags_0: TextureSamplingFlags, @@ -191,8 +192,9 @@ impl<'a> SceneBuilder<'a> { let paint_id = path_object.paint(); let paint_metadata = &paint_metadata[paint_id.0 as usize]; - let built_clip_path = - path_object.clip_path().map(|clip_path_id| &built_clip_paths[clip_path_id.0 as usize]); + let built_clip_path = path_object.clip_path().map(|clip_path_id| { + &built_clip_paths[clip_path_id.0 as usize] + }); let mut tiler = Tiler::new(self, &outline, @@ -213,6 +215,7 @@ impl<'a> SceneBuilder<'a> { BuiltDrawPath { path: tiler.object_builder.built_path, blend_mode: path_object.blend_mode(), + effects: paint_metadata.effects(), color_texture_page_0: paint_metadata.location.page, sampling_flags_0: paint_metadata.sampling_flags, color_texture_page_1: opacity_tile_page, @@ -322,6 +325,7 @@ impl<'a> SceneBuilder<'a> { None, None, built_draw_path.blend_mode, + built_draw_path.effects, None, None); @@ -332,6 +336,7 @@ impl<'a> SceneBuilder<'a> { color_texture_0, color_texture_1, built_draw_path.blend_mode, + built_draw_path.effects, Some(built_draw_path.mask_0_fill_rule), None); @@ -343,6 +348,7 @@ impl<'a> SceneBuilder<'a> { color_texture_0, color_texture_1, built_draw_path.blend_mode, + built_draw_path.effects, Some(built_draw_path.mask_0_fill_rule), Some(mask_1_fill_rule)); } @@ -356,6 +362,7 @@ impl<'a> SceneBuilder<'a> { color_texture_0, color_texture_1, built_draw_path.blend_mode, + built_draw_path.effects, None, built_draw_path.mask_1_fill_rule); } @@ -425,6 +432,7 @@ impl<'a> SceneBuilder<'a> { color_texture_0: Option, color_texture_1: Option, blend_mode: BlendMode, + effects: Effects, mask_0_fill_rule: Option, mask_1_fill_rule: Option) { if alpha_tiles.is_empty() { @@ -443,12 +451,13 @@ impl<'a> SceneBuilder<'a> { color_texture_0: ref batch_color_texture_0, color_texture_1: ref batch_color_texture_1, blend_mode: batch_blend_mode, - effects: Effects { filter: Filter::None }, + effects: batch_effects, mask_0_fill_rule: batch_mask_0_fill_rule, mask_1_fill_rule: batch_mask_1_fill_rule, })) if *batch_color_texture_0 == color_texture_0 && *batch_color_texture_1 == color_texture_1 && batch_blend_mode == blend_mode && + batch_effects == effects && batch_mask_0_fill_rule == mask_0_fill_rule && batch_mask_1_fill_rule == mask_1_fill_rule && !batch_blend_mode.needs_readable_framebuffer() => {} @@ -458,7 +467,7 @@ impl<'a> SceneBuilder<'a> { color_texture_0, color_texture_1, blend_mode, - effects: Effects::default(), + effects, mask_0_fill_rule, mask_1_fill_rule, }; diff --git a/renderer/src/gpu/renderer.rs b/renderer/src/gpu/renderer.rs index 5d471179..92e9635b 100644 --- a/renderer/src/gpu/renderer.rs +++ b/renderer/src/gpu/renderer.rs @@ -22,6 +22,7 @@ use pathfinder_color::{self as color, ColorF, ColorU}; use pathfinder_content::effects::{BlendMode, BlurDirection, DefringingKernel, Effects, Filter}; use pathfinder_content::fill::FillRule; use pathfinder_content::render_target::RenderTargetId; +use pathfinder_geometry::line_segment::LineSegment2F; use pathfinder_geometry::rect::RectI; use pathfinder_geometry::transform3d::Transform4F; use pathfinder_geometry::vector::{Vector2F, Vector2I, Vector4F}; @@ -59,6 +60,7 @@ const COMBINER_CTRL_MASK_EVEN_ODD: i32 = 0x2; const COMBINER_CTRL_COLOR_ENABLE_MASK: i32 = 0x1; +const COMBINER_CTRL_FILTER_RADIAL_GRADIENT: i32 = 0x1; const COMBINER_CTRL_FILTER_TEXT: i32 = 0x2; const COMBINER_CTRL_FILTER_BLUR: i32 = 0x3; @@ -611,13 +613,13 @@ where UniformData::Mat4(self.tile_transform().to_columns())), (&self.tile_program.tile_size_uniform, UniformData::Vec2(F32x2::new(TILE_WIDTH as f32, TILE_HEIGHT as f32))), + (&self.tile_program.framebuffer_size_uniform, + UniformData::Vec2(draw_viewport.size().to_f32().0)), ]; if needs_readable_framebuffer { uniforms.push((&self.tile_program.dest_texture_uniform, UniformData::TextureUnit(textures.len() as u32))); - uniforms.push((&self.tile_program.dest_texture_size_uniform, - UniformData::Vec2(draw_viewport.size().to_f32().0))); textures.push(self.device.framebuffer_texture(&self.dest_blend_framebuffer)); } @@ -659,6 +661,10 @@ where match effects.filter { Filter::None => {} + Filter::RadialGradient { line, radii, uv_origin } => { + ctrl |= COMBINER_CTRL_FILTER_RADIAL_GRADIENT << COMBINER_CTRL_COLOR_0_FILTER_SHIFT; + self.set_uniforms_for_radial_gradient_filter(&mut uniforms, line, radii, uv_origin) + } Filter::Text { fg_color, bg_color, defringing_kernel, gamma_correction } => { ctrl |= COMBINER_CTRL_FILTER_TEXT << COMBINER_CTRL_COLOR_0_FILTER_SHIFT; self.set_uniforms_for_text_filter(&mut textures, @@ -844,6 +850,20 @@ where self.render_target_stack.pop().expect("Render target stack underflow!"); } + fn set_uniforms_for_radial_gradient_filter<'a>( + &'a self, + uniforms: &mut Vec<(&'a D::Uniform, UniformData)>, + line: LineSegment2F, + radii: F32x2, + uv_origin: Vector2F) { + uniforms.extend_from_slice(&[ + (&self.tile_program.filter_params_0_uniform, + UniformData::Vec4(line.from().0.concat_xy_xy(line.vector().0))), + (&self.tile_program.filter_params_1_uniform, + UniformData::Vec4(radii.concat_xy_xy(uv_origin.0))), + ]); + } + fn set_uniforms_for_text_filter<'a>(&'a self, textures: &mut Vec<&'a D::Texture>, uniforms: &mut Vec<(&'a D::Uniform, UniformData)>, diff --git a/renderer/src/gpu/shaders.rs b/renderer/src/gpu/shaders.rs index 78528d9a..d073c416 100644 --- a/renderer/src/gpu/shaders.rs +++ b/renderer/src/gpu/shaders.rs @@ -324,7 +324,7 @@ pub struct TileProgram where D: Device { pub filter_params_0_uniform: D::Uniform, pub filter_params_1_uniform: D::Uniform, pub filter_params_2_uniform: D::Uniform, - pub dest_texture_size_uniform: D::Uniform, + pub framebuffer_size_uniform: D::Uniform, pub ctrl_uniform: D::Uniform, } @@ -343,7 +343,7 @@ impl TileProgram where D: Device { let filter_params_0_uniform = device.get_uniform(&program, "FilterParams0"); let filter_params_1_uniform = device.get_uniform(&program, "FilterParams1"); let filter_params_2_uniform = device.get_uniform(&program, "FilterParams2"); - let dest_texture_size_uniform = device.get_uniform(&program, "DestTextureSize"); + let framebuffer_size_uniform = device.get_uniform(&program, "FramebufferSize"); let ctrl_uniform = device.get_uniform(&program, "Ctrl"); TileProgram { program, @@ -359,7 +359,7 @@ impl TileProgram where D: Device { filter_params_0_uniform, filter_params_1_uniform, filter_params_2_uniform, - dest_texture_size_uniform, + framebuffer_size_uniform, ctrl_uniform, } } diff --git a/renderer/src/paint.rs b/renderer/src/paint.rs index 965cada6..2a3dcc08 100644 --- a/renderer/src/paint.rs +++ b/renderer/src/paint.rs @@ -14,9 +14,11 @@ use crate::scene::RenderTarget; use crate::tiles::{TILE_HEIGHT, TILE_WIDTH}; use hashbrown::HashMap; use pathfinder_color::ColorU; -use pathfinder_content::gradient::{Gradient, GradientGeometry}; +use pathfinder_content::effects::{Effects, Filter}; +use pathfinder_content::gradient::Gradient; use pathfinder_content::pattern::{Image, Pattern, PatternFlags, PatternSource}; use pathfinder_content::render_target::RenderTargetId; +use pathfinder_geometry::line_segment::LineSegment2F; use pathfinder_geometry::rect::{RectF, RectI}; use pathfinder_geometry::transform2d::{Matrix2x2F, Transform2F}; use pathfinder_geometry::util; @@ -123,26 +125,11 @@ impl Paint { match *self { Paint::Color(_) => {} Paint::Gradient(ref mut gradient) => { - match *gradient.geometry_mut() { - GradientGeometry::Linear(ref mut line) => { - *line = *transform * *line; - } - GradientGeometry::Radial { - ref mut line, - ref mut start_radius, - ref mut end_radius, - } => { - *line = *transform * *line; - - // FIXME(pcwalton): This is wrong; I think the transform can make the - // radial gradient into an ellipse. - *start_radius *= util::lerp(transform.matrix.m11(), - transform.matrix.m22(), - 0.5); - *end_radius *= util::lerp(transform.matrix.m11(), - transform.matrix.m22(), - 0.5); - } + gradient.set_line(*transform * gradient.line()); + if let Some(radii) = gradient.radii() { + gradient.set_radii(Some(radii * F32x2::splat(util::lerp(transform.matrix.m11(), + transform.matrix.m22(), + 0.5)))); } } Paint::Pattern(ref mut pattern) => pattern.transform = *transform * pattern.transform, @@ -177,6 +164,16 @@ pub struct PaintMetadata { pub sampling_flags: TextureSamplingFlags, /// True if this paint is fully opaque. pub is_opaque: bool, + /// The radial gradient for this paint, if applicable. + pub radial_gradient: Option, +} + +#[derive(Clone, Copy, Debug)] +pub struct RadialGradientMetadata { + /// The line segment that connects the two circles. + pub line: LineSegment2F, + /// The radii of the two circles. + pub radii: F32x2, } #[derive(Debug)] @@ -204,7 +201,7 @@ impl Palette { id } - pub fn build_paint_info(&self, view_box_size: Vector2I) -> PaintInfo { + pub fn build_paint_info(&self) -> PaintInfo { let mut allocator = TextureAllocator::new(); let (mut paint_metadata, mut render_target_metadata) = (vec![], vec![]); @@ -220,26 +217,20 @@ impl Palette { let mut solid_color_tile_builder = SolidColorTileBuilder::new(); let mut gradient_tile_builder = GradientTileBuilder::new(); for paint in &self.paints { - let (texture_location, mut sampling_flags); + let (texture_location, mut sampling_flags, radial_gradient); match paint { Paint::Color(_) => { texture_location = solid_color_tile_builder.allocate(&mut allocator); sampling_flags = TextureSamplingFlags::empty(); + radial_gradient = None; } - Paint::Gradient(Gradient { geometry: GradientGeometry::Linear(_), .. }) => { + Paint::Gradient(ref gradient) => { // FIXME(pcwalton): The gradient size might not be big enough. Detect this. texture_location = gradient_tile_builder.allocate(&mut allocator); sampling_flags = TextureSamplingFlags::empty(); - } - Paint::Gradient(Gradient { geometry: GradientGeometry::Radial { .. }, .. }) => { - // TODO(pcwalton): Optimize this: - // 1. Use repeating/clamp on the sides. - // 2. Choose an optimal size for the gradient that minimizes memory usage while - // retaining quality. - texture_location = - allocator.allocate(Vector2I::splat(GRADIENT_TILE_LENGTH as i32), - AllocationMode::Atlas); - sampling_flags = TextureSamplingFlags::empty(); + radial_gradient = gradient.radii().map(|radii| { + RadialGradientMetadata { line: gradient.line(), radii } + }); } Paint::Pattern(ref pattern) => { match pattern.source { @@ -271,6 +262,8 @@ impl Palette { sampling_flags.insert(TextureSamplingFlags::NEAREST_MIN | TextureSamplingFlags::NEAREST_MAG); } + + radial_gradient = None; } }; @@ -279,6 +272,7 @@ impl Palette { texture_transform: Transform2F::default(), sampling_flags, is_opaque: paint.is_opaque(), + radial_gradient, }); } @@ -290,10 +284,7 @@ impl Palette { let vector = rect_to_inset_uv(metadata.location.rect, texture_scale).origin(); Transform2F { matrix: Matrix2x2F(F32x4::default()), vector } } - Paint::Gradient(Gradient { - geometry: GradientGeometry::Linear(gradient_line), - .. - }) => { + Paint::Gradient(Gradient { line: gradient_line, radii: None, .. }) => { let v0 = metadata.location.rect.to_f32().center().y() * texture_scale.y(); let length_inv = 1.0 / gradient_line.square_length(); let (p0, d) = (gradient_line.from(), gradient_line.vector()); @@ -302,12 +293,15 @@ impl Palette { vector: Vector2F::new(-p0.dot(d) * length_inv, v0), } } - Paint::Gradient(Gradient { geometry: GradientGeometry::Radial { .. }, .. }) => { + Paint::Gradient(Gradient { radii: Some(_), .. }) => { let texture_origin_uv = - rect_to_uv(metadata.location.rect, texture_scale).origin(); - let gradient_tile_scale = texture_scale.scale(GRADIENT_TILE_LENGTH as f32); - Transform2F::from_translation(texture_origin_uv) * - Transform2F::from_scale(gradient_tile_scale / view_box_size.to_f32()) + rect_to_inset_uv(metadata.location.rect, texture_scale).origin(); + let gradient_tile_scale = + texture_scale.scale((GRADIENT_TILE_LENGTH - 1) as f32); + Transform2F { + matrix: Matrix2x2F::from_scale(gradient_tile_scale), + vector: texture_origin_uv, + } } Paint::Pattern(Pattern { source: PatternSource::Image(_), transform, .. }) => { let texture_origin_uv = @@ -359,10 +353,7 @@ impl Palette { texels.put_texel(metadata.location.rect.origin(), *color); } Paint::Gradient(ref gradient) => { - self.render_gradient(gradient, - metadata.location.rect, - &metadata.texture_transform, - texels); + self.render_gradient(gradient, metadata.location.rect, texels); } Paint::Pattern(ref pattern) => { match pattern.source { @@ -408,144 +399,15 @@ impl Palette { } // TODO(pcwalton): This is slow. Do on GPU instead. - fn render_gradient(&self, - gradient: &Gradient, - tex_rect: RectI, - tex_transform: &Transform2F, - texels: &mut Texels) { - match *gradient.geometry() { - GradientGeometry::Linear(_) => { - // FIXME(pcwalton): Paint transparent if gradient line has zero size, per spec. - // TODO(pcwalton): Optimize this: - // 1. Calculate ∇t up front and use differencing in the inner loop. - // 2. Go four pixels at a time with SIMD. - for x in 0..(GRADIENT_TILE_LENGTH as i32) { - let point = tex_rect.origin() + Vector2I::new(x, 0); - let t = (x as f32 + 0.5) / GRADIENT_TILE_LENGTH as f32; - texels.put_texel(point, gradient.sample(t)); - } - } - - GradientGeometry::Radial { line, start_radius: r0, end_radius: r1 } => { - // FIXME(pcwalton): Paint transparent if line has zero size and radii are equal, - // per spec. - let line = *tex_transform * line; - - // This is based on Pixman (MIT license). Copy and pasting the excellent comment - // from there: - - // Implementation of radial gradients following the PDF specification. - // See section 8.7.4.5.4 Type 3 (Radial) Shadings of the PDF Reference - // Manual (PDF 32000-1:2008 at the time of this writing). - // - // In the radial gradient problem we are given two circles (c₁,r₁) and - // (c₂,r₂) that define the gradient itself. - // - // Mathematically the gradient can be defined as the family of circles - // - // ((1-t)·c₁ + t·(c₂), (1-t)·r₁ + t·r₂) - // - // excluding those circles whose radius would be < 0. When a point - // belongs to more than one circle, the one with a bigger t is the only - // one that contributes to its color. When a point does not belong - // to any of the circles, it is transparent black, i.e. RGBA (0, 0, 0, 0). - // Further limitations on the range of values for t are imposed when - // the gradient is not repeated, namely t must belong to [0,1]. - // - // The graphical result is the same as drawing the valid (radius > 0) - // circles with increasing t in [-inf, +inf] (or in [0,1] if the gradient - // is not repeated) using SOURCE operator composition. - // - // It looks like a cone pointing towards the viewer if the ending circle - // is smaller than the starting one, a cone pointing inside the page if - // the starting circle is the smaller one and like a cylinder if they - // have the same radius. - // - // What we actually do is, given the point whose color we are interested - // in, compute the t values for that point, solving for t in: - // - // length((1-t)·c₁ + t·(c₂) - p) = (1-t)·r₁ + t·r₂ - // - // Let's rewrite it in a simpler way, by defining some auxiliary - // variables: - // - // cd = c₂ - c₁ - // pd = p - c₁ - // dr = r₂ - r₁ - // length(t·cd - pd) = r₁ + t·dr - // - // which actually means - // - // hypot(t·cdx - pdx, t·cdy - pdy) = r₁ + t·dr - // - // or - // - // ⎷((t·cdx - pdx)² + (t·cdy - pdy)²) = r₁ + t·dr. - // - // If we impose (as stated earlier) that r₁ + t·dr >= 0, it becomes: - // - // (t·cdx - pdx)² + (t·cdy - pdy)² = (r₁ + t·dr)² - // - // where we can actually expand the squares and solve for t: - // - // t²cdx² - 2t·cdx·pdx + pdx² + t²cdy² - 2t·cdy·pdy + pdy² = - // = r₁² + 2·r₁·t·dr + t²·dr² - // - // (cdx² + cdy² - dr²)t² - 2(cdx·pdx + cdy·pdy + r₁·dr)t + - // (pdx² + pdy² - r₁²) = 0 - // - // A = cdx² + cdy² - dr² - // B = pdx·cdx + pdy·cdy + r₁·dr - // C = pdx² + pdy² - r₁² - // At² - 2Bt + C = 0 - // - // The solutions (unless the equation degenerates because of A = 0) are: - // - // t = (B ± ⎷(B² - A·C)) / A - // - // The solution we are going to prefer is the bigger one, unless the - // radius associated to it is negative (or it falls outside the valid t - // range). - // - // Additional observations (useful for optimizations): - // A does not depend on p - // - // A < 0 <=> one of the two circles completely contains the other one - // <=> for every p, the radiuses associated with the two t solutions - // have opposite sign - - let cd = line.vector(); - let dr = r1 - r0; - let a = cd.square_length() - dr * dr; - let a_inv = 1.0 / a; - - for y in 0..(GRADIENT_TILE_LENGTH as i32) { - for x in 0..(GRADIENT_TILE_LENGTH as i32) { - let point = tex_rect.origin() + Vector2I::new(x, y); - let point_f = point.to_f32(); - let pd = point_f - line.from(); - - let b = pd.dot(cd) + r0 * dr; - let c = pd.square_length() - r0 * r0; - let discrim = b * b - a * c; - - let mut color = ColorU::transparent_black(); - if !util::approx_eq(discrim, 0.0) { - let discrim_sqrt = f32::sqrt(discrim); - let discrim_sqrts = F32x2::new(discrim_sqrt, -discrim_sqrt); - let ts = (discrim_sqrts + F32x2::splat(b)) * F32x2::splat(a_inv); - let t_min = f32::min(ts.x(), ts.y()); - let t_max = f32::max(ts.x(), ts.y()); - let t = if t_max <= 1.0 { t_max } else { t_min }; - if t >= 0.0 { - color = gradient.sample(t); - } - }; - - texels.put_texel(point, color); - } - } - } + fn render_gradient(&self, gradient: &Gradient, tex_rect: RectI, texels: &mut Texels) { + // FIXME(pcwalton): Paint transparent if gradient line has zero size, per spec. + // TODO(pcwalton): Optimize this: + // 1. Calculate ∇t up front and use differencing in the inner loop. + // 2. Go four pixels at a time with SIMD. + for x in 0..(GRADIENT_TILE_LENGTH as i32) { + let point = tex_rect.origin() + Vector2I::new(x, 0); + let t = (x as f32 + 0.5) / GRADIENT_TILE_LENGTH as f32; + texels.put_texel(point, gradient.sample(t)); } } @@ -568,6 +430,21 @@ impl PaintMetadata { let tex_coords = self.texture_transform * position; tex_coords } + + pub(crate) fn effects(&self) -> Effects { + Effects { + filter: match self.radial_gradient { + None => Filter::None, + Some(gradient) => { + Filter::RadialGradient { + line: gradient.line, + radii: gradient.radii, + uv_origin: self.texture_transform.vector, + } + } + }, + } + } } struct Texels { diff --git a/renderer/src/scene.rs b/renderer/src/scene.rs index e8aacd28..d33451cf 100644 --- a/renderer/src/scene.rs +++ b/renderer/src/scene.rs @@ -87,7 +87,7 @@ impl Scene { #[inline] pub fn build_paint_info(&self) -> PaintInfo { - self.palette.build_paint_info(self.view_box.size().to_i32()) + self.palette.build_paint_info() } #[allow(clippy::trivially_copy_pass_by_ref)] diff --git a/resources/shaders/gl3/tile.fs.glsl b/resources/shaders/gl3/tile.fs.glsl index a811e4ee..a27e0eaa 100644 --- a/resources/shaders/gl3/tile.fs.glsl +++ b/resources/shaders/gl3/tile.fs.glsl @@ -73,6 +73,8 @@ precision highp float; + + @@ -85,7 +87,7 @@ uniform sampler2D uGammaLUT; uniform vec4 uFilterParams0; uniform vec4 uFilterParams1; uniform vec4 uFilterParams2; -uniform vec2 uDestTextureSize; +uniform vec2 uFramebufferSize; uniform vec2 uColorTexture0Size; uniform int uCtrl; @@ -202,6 +204,128 @@ vec4 filterText(vec2 colorTexCoord, + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +vec4 filterRadialGradient(vec2 colorTexCoord, + sampler2D colorTexture, + vec2 colorTextureSize, + vec2 fragCoord, + vec2 framebufferSize, + vec4 filterParams0, + vec4 filterParams1){ + vec2 lineFrom = filterParams0 . xy, lineVector = filterParams0 . zw; + vec2 radii = filterParams1 . xy, uvOrigin = filterParams1 . zw; + + + fragCoord . y = framebufferSize . y - fragCoord . y; + + + vec2 dP = fragCoord - lineFrom, dC = lineVector; + float dR = radii . y - radii . x; + + float a = dot(dC, dC)- dR * dR; + float b = dot(dP, dC)+ radii . x * dR; + float c = dot(dP, dP)- radii . x * radii . x; + float discrim = b * b - a * c; + + vec4 color = vec4(0.0); + if(abs(discrim)>= 0.00001){ + vec2 ts = vec2(sqrt(discrim)* vec2(1.0, - 1.0)+ vec2(b))/ vec2(a); + float tMax = max(ts . x, ts . y); + float t = tMax <= 1.0 ? tMax : min(ts . x, ts . y); + if(t >= 0.0) + color = texture(colorTexture, uvOrigin + vec2(t, 0.0)); + } + + return color; +} + + + + + + vec4 filterBlur(vec2 colorTexCoord, sampler2D colorTexture, vec2 colorTextureSize, @@ -250,11 +374,21 @@ vec4 filterColor(vec2 colorTexCoord, sampler2D colorTexture, sampler2D gammaLUT, vec2 colorTextureSize, + vec2 fragCoord, + vec2 framebufferSize, vec4 filterParams0, vec4 filterParams1, vec4 filterParams2, int colorFilter){ switch(colorFilter){ + case 0x1 : + return filterRadialGradient(colorTexCoord, + colorTexture, + colorTextureSize, + fragCoord, + framebufferSize, + filterParams0, + filterParams1); case 0x3 : return filterBlur(colorTexCoord, colorTexture, @@ -433,6 +567,8 @@ void calculateColor(int ctrl){ uColorTexture0, uGammaLUT, uColorTexture0Size, + gl_FragCoord . xy, + uFramebufferSize, uFilterParams0, uFilterParams1, uFilterParams2, @@ -446,7 +582,7 @@ void calculateColor(int ctrl){ int compositeOp =(ctrl >> 8)& 0xf; - color = composite(color, uDestTexture, uDestTextureSize, gl_FragCoord . xy, compositeOp); + color = composite(color, uDestTexture, uFramebufferSize, gl_FragCoord . xy, compositeOp); color . rgb *= color . a; diff --git a/resources/shaders/metal/tile.fs.metal b/resources/shaders/metal/tile.fs.metal index da381fb9..18c5ec5c 100644 --- a/resources/shaders/metal/tile.fs.metal +++ b/resources/shaders/metal/tile.fs.metal @@ -17,18 +17,18 @@ struct spvDescriptorSetBuffer0 texture2d uGammaLUT [[id(6)]]; sampler uGammaLUTSmplr [[id(7)]]; constant float2* uColorTexture0Size [[id(8)]]; - constant float4* uFilterParams0 [[id(9)]]; - constant float4* uFilterParams1 [[id(10)]]; - constant float4* uFilterParams2 [[id(11)]]; - texture2d uColorTexture1 [[id(12)]]; - sampler uColorTexture1Smplr [[id(13)]]; - texture2d uDestTexture [[id(14)]]; - sampler uDestTextureSmplr [[id(15)]]; - constant float2* uDestTextureSize [[id(16)]]; + constant float2* uFramebufferSize [[id(9)]]; + constant float4* uFilterParams0 [[id(10)]]; + constant float4* uFilterParams1 [[id(11)]]; + constant float4* uFilterParams2 [[id(12)]]; + texture2d uColorTexture1 [[id(13)]]; + sampler uColorTexture1Smplr [[id(14)]]; + texture2d uDestTexture [[id(15)]]; + sampler uDestTextureSmplr [[id(16)]]; constant int* uCtrl [[id(17)]]; }; -constant float3 _862 = {}; +constant float3 _1003 = {}; struct main0_out { @@ -68,6 +68,42 @@ float sampleMask(thread const float& maskAlpha, thread const texture2d ma return fast::min(maskAlpha, coverage); } +float4 filterRadialGradient(thread const float2& colorTexCoord, thread const texture2d colorTexture, thread const sampler colorTextureSmplr, thread const float2& colorTextureSize, thread const float2& fragCoord, thread const float2& framebufferSize, thread const float4& filterParams0, thread const float4& filterParams1) +{ + float2 lineFrom = filterParams0.xy; + float2 lineVector = filterParams0.zw; + float2 radii = filterParams1.xy; + float2 uvOrigin = filterParams1.zw; + float2 dP = fragCoord - lineFrom; + float2 dC = lineVector; + float dR = radii.y - radii.x; + float a = dot(dC, dC) - (dR * dR); + float b = dot(dP, dC) + (radii.x * dR); + float c = dot(dP, dP) - (radii.x * radii.x); + float discrim = (b * b) - (a * c); + float4 color = float4(0.0); + if (abs(discrim) >= 9.9999997473787516355514526367188e-06) + { + float2 ts = float2((float2(1.0, -1.0) * sqrt(discrim)) + float2(b)) / float2(a); + float tMax = fast::max(ts.x, ts.y); + float _511; + if (tMax <= 1.0) + { + _511 = tMax; + } + else + { + _511 = fast::min(ts.x, ts.y); + } + float t = _511; + if (t >= 0.0) + { + color = colorTexture.sample(colorTextureSmplr, (uvOrigin + float2(t, 0.0))); + } + } + return color; +} + float4 filterBlur(thread const float2& colorTexCoord, thread const texture2d colorTexture, thread const sampler colorTextureSmplr, thread const float2& colorTextureSize, thread const float4& filterParams0, thread const float4& filterParams1) { float2 srcOffsetScale = filterParams0.xy / colorTextureSize; @@ -75,19 +111,19 @@ float4 filterBlur(thread const float2& colorTexCoord, thread const texture2d colorTexture, thread const sampler colorTextureSmplr, thread const float2& colorTexCoord, thread const float4& kernel0, thread const float& onePixel) { bool wide = kernel0.x > 0.0; - float _183; + float _195; if (wide) { float param = (-4.0) * onePixel; float2 param_1 = colorTexCoord; - _183 = filterTextSample1Tap(param, colorTexture, colorTextureSmplr, param_1); + _195 = filterTextSample1Tap(param, colorTexture, colorTextureSmplr, param_1); } else { - _183 = 0.0; + _195 = 0.0; } float param_2 = (-3.0) * onePixel; float2 param_3 = colorTexCoord; @@ -117,7 +153,7 @@ void filterTextSample9Tap(thread float4& outAlphaLeft, thread float& outAlphaCen float2 param_5 = colorTexCoord; float param_6 = (-1.0) * onePixel; float2 param_7 = colorTexCoord; - outAlphaLeft = float4(_183, filterTextSample1Tap(param_2, colorTexture, colorTextureSmplr, param_3), filterTextSample1Tap(param_4, colorTexture, colorTextureSmplr, param_5), filterTextSample1Tap(param_6, colorTexture, colorTextureSmplr, param_7)); + outAlphaLeft = float4(_195, filterTextSample1Tap(param_2, colorTexture, colorTextureSmplr, param_3), filterTextSample1Tap(param_4, colorTexture, colorTextureSmplr, param_5), filterTextSample1Tap(param_6, colorTexture, colorTextureSmplr, param_7)); float param_8 = 0.0; float2 param_9 = colorTexCoord; outAlphaCenter = filterTextSample1Tap(param_8, colorTexture, colorTextureSmplr, param_9); @@ -127,18 +163,18 @@ void filterTextSample9Tap(thread float4& outAlphaLeft, thread float& outAlphaCen float2 param_13 = colorTexCoord; float param_14 = 3.0 * onePixel; float2 param_15 = colorTexCoord; - float _243; + float _255; if (wide) { float param_16 = 4.0 * onePixel; float2 param_17 = colorTexCoord; - _243 = filterTextSample1Tap(param_16, colorTexture, colorTextureSmplr, param_17); + _255 = filterTextSample1Tap(param_16, colorTexture, colorTextureSmplr, param_17); } else { - _243 = 0.0; + _255 = 0.0; } - outAlphaRight = float4(filterTextSample1Tap(param_10, colorTexture, colorTextureSmplr, param_11), filterTextSample1Tap(param_12, colorTexture, colorTextureSmplr, param_13), filterTextSample1Tap(param_14, colorTexture, colorTextureSmplr, param_15), _243); + outAlphaRight = float4(filterTextSample1Tap(param_10, colorTexture, colorTextureSmplr, param_11), filterTextSample1Tap(param_12, colorTexture, colorTextureSmplr, param_13), filterTextSample1Tap(param_14, colorTexture, colorTextureSmplr, param_15), _255); } float filterTextConvolve7Tap(thread const float4& alpha0, thread const float3& alpha1, thread const float4& kernel0) @@ -219,30 +255,40 @@ float4 filterNone(thread const float2& colorTexCoord, thread const texture2d colorTexture, thread const sampler colorTextureSmplr, thread const texture2d gammaLUT, thread const sampler gammaLUTSmplr, thread const float2& colorTextureSize, thread const float4& filterParams0, thread const float4& filterParams1, thread const float4& filterParams2, thread const int& colorFilter) +float4 filterColor(thread const float2& colorTexCoord, thread const texture2d colorTexture, thread const sampler colorTextureSmplr, thread const texture2d gammaLUT, thread const sampler gammaLUTSmplr, thread const float2& colorTextureSize, thread const float2& fragCoord, thread const float2& framebufferSize, thread const float4& filterParams0, thread const float4& filterParams1, thread const float4& filterParams2, thread const int& colorFilter) { switch (colorFilter) { - case 3: + case 1: { float2 param = colorTexCoord; float2 param_1 = colorTextureSize; - float4 param_2 = filterParams0; - float4 param_3 = filterParams1; - return filterBlur(param, colorTexture, colorTextureSmplr, param_1, param_2, param_3); + float2 param_2 = fragCoord; + float2 param_3 = framebufferSize; + float4 param_4 = filterParams0; + float4 param_5 = filterParams1; + return filterRadialGradient(param, colorTexture, colorTextureSmplr, param_1, param_2, param_3, param_4, param_5); + } + case 3: + { + float2 param_6 = colorTexCoord; + float2 param_7 = colorTextureSize; + float4 param_8 = filterParams0; + float4 param_9 = filterParams1; + return filterBlur(param_6, colorTexture, colorTextureSmplr, param_7, param_8, param_9); } case 2: { - float2 param_4 = colorTexCoord; - float2 param_5 = colorTextureSize; - float4 param_6 = filterParams0; - float4 param_7 = filterParams1; - float4 param_8 = filterParams2; - return filterText(param_4, colorTexture, colorTextureSmplr, gammaLUT, gammaLUTSmplr, param_5, param_6, param_7, param_8); + float2 param_10 = colorTexCoord; + float2 param_11 = colorTextureSize; + float4 param_12 = filterParams0; + float4 param_13 = filterParams1; + float4 param_14 = filterParams2; + return filterText(param_10, colorTexture, colorTextureSmplr, gammaLUT, gammaLUTSmplr, param_11, param_12, param_13, param_14); } } - float2 param_9 = colorTexCoord; - return filterNone(param_9, colorTexture, colorTextureSmplr); + float2 param_15 = colorTexCoord; + return filterNone(param_15, colorTexture, colorTextureSmplr); } float3 compositeScreen(thread const float3& destColor, thread const float3& srcColor) @@ -252,34 +298,34 @@ float3 compositeScreen(thread const float3& destColor, thread const float3& srcC float3 compositeSelect(thread const bool3& cond, thread const float3& ifTrue, thread const float3& ifFalse) { - float _546; + float _687; if (cond.x) { - _546 = ifTrue.x; + _687 = ifTrue.x; } else { - _546 = ifFalse.x; + _687 = ifFalse.x; } - float _557; + float _698; if (cond.y) { - _557 = ifTrue.y; + _698 = ifTrue.y; } else { - _557 = ifFalse.y; + _698 = ifFalse.y; } - float _568; + float _709; if (cond.z) { - _568 = ifTrue.z; + _709 = ifTrue.z; } else { - _568 = ifFalse.z; + _709 = ifFalse.z; } - return float3(_546, _557, _568); + return float3(_687, _698, _709); } float3 compositeHardLight(thread const float3& destColor, thread const float3& srcColor) @@ -320,16 +366,16 @@ float3 compositeSoftLight(thread const float3& destColor, thread const float3& s float compositeDivide(thread const float& num, thread const float& denom) { - float _582; + float _723; if (denom != 0.0) { - _582 = num / denom; + _723 = num / denom; } else { - _582 = 0.0; + _723 = 0.0; } - return _582; + return _723; } float3 compositeRGBToHSL(thread const float3& rgb) @@ -338,25 +384,25 @@ float3 compositeRGBToHSL(thread const float3& rgb) float xMin = fast::min(fast::min(rgb.x, rgb.y), rgb.z); float c = v - xMin; float l = mix(xMin, v, 0.5); - float3 _688; + float3 _829; if (rgb.x == v) { - _688 = float3(0.0, rgb.yz); + _829 = float3(0.0, rgb.yz); } else { - float3 _701; + float3 _842; if (rgb.y == v) { - _701 = float3(2.0, rgb.zx); + _842 = float3(2.0, rgb.zx); } else { - _701 = float3(4.0, rgb.xy); + _842 = float3(4.0, rgb.xy); } - _688 = _701; + _829 = _842; } - float3 terms = _688; + float3 terms = _829; float param = ((terms.x * c) + terms.y) - terms.z; float param_1 = c; float h = 1.0471975803375244140625 * compositeDivide(param, param_1); @@ -488,7 +534,7 @@ float4 composite(thread const float4& srcColor, thread const texture2d de return float4(((srcColor.xyz * (srcColor.w * (1.0 - destColor.w))) + (blendedRGB * (srcColor.w * destColor.w))) + (destColor.xyz * (1.0 - srcColor.w)), 1.0); } -void calculateColor(thread const int& ctrl, thread texture2d uMaskTexture0, thread const sampler uMaskTexture0Smplr, thread float3& vMaskTexCoord0, thread texture2d uMaskTexture1, thread const sampler uMaskTexture1Smplr, thread float3& vMaskTexCoord1, thread float2& vColorTexCoord0, thread texture2d uColorTexture0, thread const sampler uColorTexture0Smplr, thread texture2d uGammaLUT, thread const sampler uGammaLUTSmplr, thread float2 uColorTexture0Size, thread float4 uFilterParams0, thread float4 uFilterParams1, thread float4 uFilterParams2, thread texture2d uColorTexture1, thread const sampler uColorTexture1Smplr, thread float2& vColorTexCoord1, thread texture2d uDestTexture, thread const sampler uDestTextureSmplr, thread float2 uDestTextureSize, thread float4& gl_FragCoord, thread float4& oFragColor) +void calculateColor(thread const int& ctrl, thread texture2d uMaskTexture0, thread const sampler uMaskTexture0Smplr, thread float3& vMaskTexCoord0, thread texture2d uMaskTexture1, thread const sampler uMaskTexture1Smplr, thread float3& vMaskTexCoord1, thread float2& vColorTexCoord0, thread texture2d uColorTexture0, thread const sampler uColorTexture0Smplr, thread texture2d uGammaLUT, thread const sampler uGammaLUTSmplr, thread float2 uColorTexture0Size, thread float4& gl_FragCoord, thread float2 uFramebufferSize, thread float4 uFilterParams0, thread float4 uFilterParams1, thread float4 uFilterParams2, thread texture2d uColorTexture1, thread const sampler uColorTexture1Smplr, thread float2& vColorTexCoord1, thread texture2d uDestTexture, thread const sampler uDestTextureSmplr, thread float4& oFragColor) { int maskCtrl0 = (ctrl >> 0) & 3; int maskCtrl1 = (ctrl >> 2) & 3; @@ -507,26 +553,28 @@ void calculateColor(thread const int& ctrl, thread texture2d uMaskTexture int color0Filter = (ctrl >> 4) & 3; float2 param_6 = vColorTexCoord0; float2 param_7 = uColorTexture0Size; - float4 param_8 = uFilterParams0; - float4 param_9 = uFilterParams1; - float4 param_10 = uFilterParams2; - int param_11 = color0Filter; - color += filterColor(param_6, uColorTexture0, uColorTexture0Smplr, uGammaLUT, uGammaLUTSmplr, param_7, param_8, param_9, param_10, param_11); + float2 param_8 = gl_FragCoord.xy; + float2 param_9 = uFramebufferSize; + float4 param_10 = uFilterParams0; + float4 param_11 = uFilterParams1; + float4 param_12 = uFilterParams2; + int param_13 = color0Filter; + color += filterColor(param_6, uColorTexture0, uColorTexture0Smplr, uGammaLUT, uGammaLUTSmplr, param_7, param_8, param_9, param_10, param_11, param_12, param_13); } if (((ctrl >> 7) & 1) != 0) { - float2 param_12 = vColorTexCoord1; - color *= sampleColor(uColorTexture1, uColorTexture1Smplr, param_12); + float2 param_14 = vColorTexCoord1; + color *= sampleColor(uColorTexture1, uColorTexture1Smplr, param_14); } color.w *= maskAlpha; int compositeOp = (ctrl >> 8) & 15; - float4 param_13 = color; - float2 param_14 = uDestTextureSize; - float2 param_15 = gl_FragCoord.xy; - int param_16 = compositeOp; - color = composite(param_13, uDestTexture, uDestTextureSmplr, param_14, param_15, param_16); - float3 _1159 = color.xyz * color.w; - color = float4(_1159.x, _1159.y, _1159.z, color.w); + float4 param_15 = color; + float2 param_16 = uFramebufferSize; + float2 param_17 = gl_FragCoord.xy; + int param_18 = compositeOp; + color = composite(param_15, uDestTexture, uDestTextureSmplr, param_16, param_17, param_18); + float3 _1304 = color.xyz * color.w; + color = float4(_1304.x, _1304.y, _1304.z, color.w); oFragColor = color; } @@ -534,7 +582,7 @@ fragment main0_out main0(main0_in in [[stage_in]], constant spvDescriptorSetBuff { main0_out out = {}; int param = (*spvDescriptorSet0.uCtrl); - calculateColor(param, spvDescriptorSet0.uMaskTexture0, spvDescriptorSet0.uMaskTexture0Smplr, in.vMaskTexCoord0, spvDescriptorSet0.uMaskTexture1, spvDescriptorSet0.uMaskTexture1Smplr, in.vMaskTexCoord1, in.vColorTexCoord0, spvDescriptorSet0.uColorTexture0, spvDescriptorSet0.uColorTexture0Smplr, spvDescriptorSet0.uGammaLUT, spvDescriptorSet0.uGammaLUTSmplr, (*spvDescriptorSet0.uColorTexture0Size), (*spvDescriptorSet0.uFilterParams0), (*spvDescriptorSet0.uFilterParams1), (*spvDescriptorSet0.uFilterParams2), spvDescriptorSet0.uColorTexture1, spvDescriptorSet0.uColorTexture1Smplr, in.vColorTexCoord1, spvDescriptorSet0.uDestTexture, spvDescriptorSet0.uDestTextureSmplr, (*spvDescriptorSet0.uDestTextureSize), gl_FragCoord, out.oFragColor); + calculateColor(param, spvDescriptorSet0.uMaskTexture0, spvDescriptorSet0.uMaskTexture0Smplr, in.vMaskTexCoord0, spvDescriptorSet0.uMaskTexture1, spvDescriptorSet0.uMaskTexture1Smplr, in.vMaskTexCoord1, in.vColorTexCoord0, spvDescriptorSet0.uColorTexture0, spvDescriptorSet0.uColorTexture0Smplr, spvDescriptorSet0.uGammaLUT, spvDescriptorSet0.uGammaLUTSmplr, (*spvDescriptorSet0.uColorTexture0Size), gl_FragCoord, (*spvDescriptorSet0.uFramebufferSize), (*spvDescriptorSet0.uFilterParams0), (*spvDescriptorSet0.uFilterParams1), (*spvDescriptorSet0.uFilterParams2), spvDescriptorSet0.uColorTexture1, spvDescriptorSet0.uColorTexture1Smplr, in.vColorTexCoord1, spvDescriptorSet0.uDestTexture, spvDescriptorSet0.uDestTextureSmplr, out.oFragColor); return out; } diff --git a/shaders/tile.fs.glsl b/shaders/tile.fs.glsl index 282108d6..cac7f4f2 100644 --- a/shaders/tile.fs.glsl +++ b/shaders/tile.fs.glsl @@ -35,6 +35,8 @@ precision highp float; +#define EPSILON 0.00001 + #define FRAC_6_PI 1.9098593171027443 #define FRAC_PI_3 1.0471975511965976 @@ -83,7 +85,7 @@ uniform sampler2D uGammaLUT; uniform vec4 uFilterParams0; uniform vec4 uFilterParams1; uniform vec4 uFilterParams2; -uniform vec2 uDestTextureSize; +uniform vec2 uFramebufferSize; uniform vec2 uColorTexture0Size; uniform int uCtrl; @@ -193,7 +195,129 @@ vec4 filterText(vec2 colorTexCoord, return vec4(mix(bgColor, fgColor, alpha), 1.0); } -// Filters +// Other filters + +// This is based on Pixman (MIT license). Copy and pasting the excellent comment +// from there: + +// Implementation of radial gradients following the PDF specification. +// See section 8.7.4.5.4 Type 3 (Radial) Shadings of the PDF Reference +// Manual (PDF 32000-1:2008 at the time of this writing). +// +// In the radial gradient problem we are given two circles (c₁,r₁) and +// (c₂,r₂) that define the gradient itself. +// +// Mathematically the gradient can be defined as the family of circles +// +// ((1-t)·c₁ + t·(c₂), (1-t)·r₁ + t·r₂) +// +// excluding those circles whose radius would be < 0. When a point +// belongs to more than one circle, the one with a bigger t is the only +// one that contributes to its color. When a point does not belong +// to any of the circles, it is transparent black, i.e. RGBA (0, 0, 0, 0). +// Further limitations on the range of values for t are imposed when +// the gradient is not repeated, namely t must belong to [0,1]. +// +// The graphical result is the same as drawing the valid (radius > 0) +// circles with increasing t in [-∞, +∞] (or in [0,1] if the gradient +// is not repeated) using SOURCE operator composition. +// +// It looks like a cone pointing towards the viewer if the ending circle +// is smaller than the starting one, a cone pointing inside the page if +// the starting circle is the smaller one and like a cylinder if they +// have the same radius. +// +// What we actually do is, given the point whose color we are interested +// in, compute the t values for that point, solving for t in: +// +// length((1-t)·c₁ + t·(c₂) - p) = (1-t)·r₁ + t·r₂ +// +// Let's rewrite it in a simpler way, by defining some auxiliary +// variables: +// +// cd = c₂ - c₁ +// pd = p - c₁ +// dr = r₂ - r₁ +// length(t·cd - pd) = r₁ + t·dr +// +// which actually means +// +// hypot(t·cdx - pdx, t·cdy - pdy) = r₁ + t·dr +// +// or +// +// ⎷((t·cdx - pdx)² + (t·cdy - pdy)²) = r₁ + t·dr. +// +// If we impose (as stated earlier) that r₁ + t·dr ≥ 0, it becomes: +// +// (t·cdx - pdx)² + (t·cdy - pdy)² = (r₁ + t·dr)² +// +// where we can actually expand the squares and solve for t: +// +// t²cdx² - 2t·cdx·pdx + pdx² + t²cdy² - 2t·cdy·pdy + pdy² = +// = r₁² + 2·r₁·t·dr + t²·dr² +// +// (cdx² + cdy² - dr²)t² - 2(cdx·pdx + cdy·pdy + r₁·dr)t + +// (pdx² + pdy² - r₁²) = 0 +// +// A = cdx² + cdy² - dr² +// B = pdx·cdx + pdy·cdy + r₁·dr +// C = pdx² + pdy² - r₁² +// At² - 2Bt + C = 0 +// +// The solutions (unless the equation degenerates because of A = 0) are: +// +// t = (B ± ⎷(B² - A·C)) / A +// +// The solution we are going to prefer is the bigger one, unless the +// radius associated to it is negative (or it falls outside the valid t +// range). +// +// Additional observations (useful for optimizations): +// A does not depend on p +// +// A < 0 ⟺ one of the two circles completely contains the other one +// ⟺ for every p, the radii associated with the two t solutions have +// opposite sign +// +// | x y z w +// --------------+------------------------------------------- +// filterParams0 | lineFrom.x lineFrom.y lineVector.x lineVector.y +// filterParams1 | radii.x radii.y uvOrigin.x uvOrigin.y +// filterParams2 | - - - - +vec4 filterRadialGradient(vec2 colorTexCoord, + sampler2D colorTexture, + vec2 colorTextureSize, + vec2 fragCoord, + vec2 framebufferSize, + vec4 filterParams0, + vec4 filterParams1) { + vec2 lineFrom = filterParams0.xy, lineVector = filterParams0.zw; + vec2 radii = filterParams1.xy, uvOrigin = filterParams1.zw; + +#ifndef PF_ORIGIN_UPPER_LEFT + fragCoord.y = framebufferSize.y - fragCoord.y; +#endif + + vec2 dP = fragCoord - lineFrom, dC = lineVector; + float dR = radii.y - radii.x; + + float a = dot(dC, dC) - dR * dR; + float b = dot(dP, dC) + radii.x * dR; + float c = dot(dP, dP) - radii.x * radii.x; + float discrim = b * b - a * c; + + vec4 color = vec4(0.0); + if (abs(discrim) >= EPSILON) { + vec2 ts = vec2(sqrt(discrim) * vec2(1.0, -1.0) + vec2(b)) / vec2(a); + float tMax = max(ts.x, ts.y); + float t = tMax <= 1.0 ? tMax : min(ts.x, ts.y); + if (t >= 0.0) + color = texture(colorTexture, uvOrigin + vec2(t, 0.0)); + } + + return color; +} // | x y z w // --------------+---------------------------------------------------- @@ -248,11 +372,21 @@ vec4 filterColor(vec2 colorTexCoord, sampler2D colorTexture, sampler2D gammaLUT, vec2 colorTextureSize, + vec2 fragCoord, + vec2 framebufferSize, vec4 filterParams0, vec4 filterParams1, vec4 filterParams2, int colorFilter) { switch (colorFilter) { + case COMBINER_CTRL_FILTER_RADIAL_GRADIENT: + return filterRadialGradient(colorTexCoord, + colorTexture, + colorTextureSize, + fragCoord, + framebufferSize, + filterParams0, + filterParams1); case COMBINER_CTRL_FILTER_BLUR: return filterBlur(colorTexCoord, colorTexture, @@ -431,6 +565,8 @@ void calculateColor(int ctrl) { uColorTexture0, uGammaLUT, uColorTexture0Size, + gl_FragCoord.xy, + uFramebufferSize, uFilterParams0, uFilterParams1, uFilterParams2, @@ -444,7 +580,7 @@ void calculateColor(int ctrl) { // Apply composite. int compositeOp = (ctrl >> COMBINER_CTRL_COMPOSITE_SHIFT) & COMBINER_CTRL_COMPOSITE_MASK; - color = composite(color, uDestTexture, uDestTextureSize, gl_FragCoord.xy, compositeOp); + color = composite(color, uDestTexture, uFramebufferSize, gl_FragCoord.xy, compositeOp); // Premultiply alpha. color.rgb *= color.a;