Move radial gradients from the CPU to the GPU

This commit is contained in:
Patrick Walton 2020-03-26 20:01:17 -07:00
parent 0c24619dd0
commit 55df287fec
14 changed files with 558 additions and 318 deletions

1
Cargo.lock generated
View File

@ -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)",
]

View File

@ -98,7 +98,7 @@ impl Debug for ColorU {
}
}
#[derive(Clone, Copy, Default)]
#[derive(Clone, Copy, PartialEq, Default)]
pub struct ColorF(pub F32x4);
impl ColorF {

View File

@ -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.

View File

@ -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<F32x2>,
stops: SortedVector<ColorStop>,
}
#[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<H>(&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<F32x2> {
self.radii
}
#[inline]
pub fn set_radii(&mut self, radii: Option<F32x2>) {
self.radii = radii
}
#[inline]

View File

@ -34,3 +34,6 @@ path = "../../renderer"
[dependencies.pathfinder_resources]
path = "../../resources"
[dependencies.pathfinder_simd]
path = "../../simd"

View File

@ -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));

View File

@ -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<TileBatchTexture>,
color_texture_1: Option<TileBatchTexture>,
blend_mode: BlendMode,
effects: Effects,
mask_0_fill_rule: Option<FillRule>,
mask_1_fill_rule: Option<FillRule>) {
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,
};

View File

@ -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)>,

View File

@ -324,7 +324,7 @@ pub struct TileProgram<D> 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<D> TileProgram<D> 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<D> TileProgram<D> where D: Device {
filter_params_0_uniform,
filter_params_1_uniform,
filter_params_2_uniform,
dest_texture_size_uniform,
framebuffer_size_uniform,
ctrl_uniform,
}
}

View File

@ -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(),
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);
*end_radius *= util::lerp(transform.matrix.m11(),
transform.matrix.m22(),
0.5);
}
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<RadialGradientMetadata>,
}
#[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,13 +399,7 @@ 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(_) => {
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.
@ -426,129 +411,6 @@ impl Palette {
}
}
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_image(&self, image: &Image, tex_rect: RectI, texels: &mut Texels) {
let image_size = image.size();
for y in 0..image_size.y() {
@ -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 {

View File

@ -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)]

View File

@ -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;

View File

@ -17,18 +17,18 @@ struct spvDescriptorSetBuffer0
texture2d<float> 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<float> uColorTexture1 [[id(12)]];
sampler uColorTexture1Smplr [[id(13)]];
texture2d<float> 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<float> uColorTexture1 [[id(13)]];
sampler uColorTexture1Smplr [[id(14)]];
texture2d<float> 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<float> ma
return fast::min(maskAlpha, coverage);
}
float4 filterRadialGradient(thread const float2& colorTexCoord, thread const texture2d<float> 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<float> 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<flo
float3 gaussCoeff = filterParams1.xyz;
float gaussSum = gaussCoeff.x;
float4 color = colorTexture.sample(colorTextureSmplr, colorTexCoord) * gaussCoeff.x;
float2 _435 = gaussCoeff.xy * gaussCoeff.yz;
gaussCoeff = float3(_435.x, _435.y, gaussCoeff.z);
float2 _561 = gaussCoeff.xy * gaussCoeff.yz;
gaussCoeff = float3(_561.x, _561.y, gaussCoeff.z);
for (int i = 1; i <= support; i += 2)
{
float gaussPartialSum = gaussCoeff.x;
float2 _455 = gaussCoeff.xy * gaussCoeff.yz;
gaussCoeff = float3(_455.x, _455.y, gaussCoeff.z);
float2 _581 = gaussCoeff.xy * gaussCoeff.yz;
gaussCoeff = float3(_581.x, _581.y, gaussCoeff.z);
gaussPartialSum += gaussCoeff.x;
float2 srcOffset = srcOffsetScale * (float(i) + (gaussCoeff.x / gaussPartialSum));
color += ((colorTexture.sample(colorTextureSmplr, (colorTexCoord - srcOffset)) + colorTexture.sample(colorTextureSmplr, (colorTexCoord + srcOffset))) * gaussPartialSum);
gaussSum += (2.0 * gaussPartialSum);
float2 _495 = gaussCoeff.xy * gaussCoeff.yz;
gaussCoeff = float3(_495.x, _495.y, gaussCoeff.z);
float2 _621 = gaussCoeff.xy * gaussCoeff.yz;
gaussCoeff = float3(_621.x, _621.y, gaussCoeff.z);
}
return color / float4(gaussSum);
}
@ -100,16 +136,16 @@ float filterTextSample1Tap(thread const float& offset, thread const texture2d<fl
void filterTextSample9Tap(thread float4& outAlphaLeft, thread float& outAlphaCenter, thread float4& outAlphaRight, thread const texture2d<float> 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<flo
return sampleColor(colorTexture, colorTextureSmplr, param);
}
float4 filterColor(thread const float2& colorTexCoord, thread const texture2d<float> colorTexture, thread const sampler colorTextureSmplr, thread const texture2d<float> 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<float> colorTexture, thread const sampler colorTextureSmplr, thread const texture2d<float> 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<float> 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<float> uMaskTexture0, thread const sampler uMaskTexture0Smplr, thread float3& vMaskTexCoord0, thread texture2d<float> uMaskTexture1, thread const sampler uMaskTexture1Smplr, thread float3& vMaskTexCoord1, thread float2& vColorTexCoord0, thread texture2d<float> uColorTexture0, thread const sampler uColorTexture0Smplr, thread texture2d<float> uGammaLUT, thread const sampler uGammaLUTSmplr, thread float2 uColorTexture0Size, thread float4 uFilterParams0, thread float4 uFilterParams1, thread float4 uFilterParams2, thread texture2d<float> uColorTexture1, thread const sampler uColorTexture1Smplr, thread float2& vColorTexCoord1, thread texture2d<float> uDestTexture, thread const sampler uDestTextureSmplr, thread float2 uDestTextureSize, thread float4& gl_FragCoord, thread float4& oFragColor)
void calculateColor(thread const int& ctrl, thread texture2d<float> uMaskTexture0, thread const sampler uMaskTexture0Smplr, thread float3& vMaskTexCoord0, thread texture2d<float> uMaskTexture1, thread const sampler uMaskTexture1Smplr, thread float3& vMaskTexCoord1, thread float2& vColorTexCoord0, thread texture2d<float> uColorTexture0, thread const sampler uColorTexture0Smplr, thread texture2d<float> 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<float> uColorTexture1, thread const sampler uColorTexture1Smplr, thread float2& vColorTexCoord1, thread texture2d<float> 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<float> 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;
}

View File

@ -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;