Optimize the common case of (0, 0, 1) normal

parent a04006e8
......@@ -703,6 +703,7 @@ dependencies = [
"lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
"nalgebra 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"pango 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pango-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
......
......@@ -33,6 +33,7 @@ lazy_static = "1.0.0"
phf = "0.7.21"
float-cmp = "0.4.0"
nalgebra = "0.16"
num-traits = "0.2"
owning_ref = "0.3"
[dependencies.cairo-sys-rs]
......
......@@ -7,7 +7,7 @@ extern crate cairo_sys;
extern crate nalgebra;
extern crate rsvg_internals;
use nalgebra::{Matrix3, Vector3};
use nalgebra::{Matrix3, Vector2};
use rsvg_internals::filters::{
context::IRect,
......@@ -21,6 +21,7 @@ use rsvg_internals::filters::{
top_left_normal,
top_right_normal,
top_row_normal,
Normal,
},
};
use rsvg_internals::surface_utils::{
......@@ -30,13 +31,7 @@ use rsvg_internals::surface_utils::{
};
/// Computes and returns the normal vector for the light filters.
fn normal(
surface: &SharedImageSurface,
bounds: IRect,
x: u32,
y: u32,
surface_scale: f64,
) -> Vector3<f64> {
fn normal(surface: &SharedImageSurface, bounds: IRect, x: u32, y: u32) -> Normal {
assert!(x as i32 >= bounds.x0);
assert!(y as i32 >= bounds.y0);
assert!((x as i32) < bounds.x1);
......@@ -191,13 +186,11 @@ fn normal(
ny += i16::from(pixel.a) * ky[(kernel_y, kernel_x)];
}
let nx = f64::from(nx) / 255. * factor_x * surface_scale;
let ny = f64::from(ny) / 255. * factor_y * surface_scale;
// Negative nx and ny to account for the different coordinate system.
let mut n = Vector3::new(-nx, -ny, 1.0);
n.normalize_mut();
n
Normal {
factor: Vector2::new(factor_x, factor_y),
normal: Vector2::new(-nx, -ny),
}
}
const SURFACE_SIDE: i32 = 512;
......@@ -215,10 +208,10 @@ fn bench_normal(c: &mut Criterion) {
let input_surface = SharedImageSurface::new(input_surface, SurfaceType::SRgb).unwrap();
b.iter(|| {
let mut z = 0.0;
let mut z = 0;
for (x, y, _pixel) in Pixels::new(&input_surface, BOUNDS) {
let n = normal(&input_surface, BOUNDS, x, y, 0.5);
z += n.z;
let n = normal(&input_surface, BOUNDS, x, y);
z += n.normal.x;
}
z
})
......@@ -230,61 +223,61 @@ fn bench_normal(c: &mut Criterion) {
let input_surface = SharedImageSurface::new(input_surface, SurfaceType::SRgb).unwrap();
b.iter(|| {
let mut z = 0.0;
let mut z = 0;
// Top left.
{
let n = top_left_normal(&input_surface, BOUNDS, 0.5);
z += n.z;
let n = top_left_normal(&input_surface, BOUNDS);
z += n.normal.x;
}
// Top right.
{
let n = top_right_normal(&input_surface, BOUNDS, 0.5);
z += n.z;
let n = top_right_normal(&input_surface, BOUNDS);
z += n.normal.x;
}
// Bottom left.
{
let n = bottom_left_normal(&input_surface, BOUNDS, 0.5);
z += n.z;
let n = bottom_left_normal(&input_surface, BOUNDS);
z += n.normal.x;
}
// Bottom right.
{
let n = bottom_right_normal(&input_surface, BOUNDS, 0.5);
z += n.z;
let n = bottom_right_normal(&input_surface, BOUNDS);
z += n.normal.x;
}
// Top row.
for x in BOUNDS.x0 as u32 + 1..BOUNDS.x1 as u32 - 1 {
let n = top_row_normal(&input_surface, BOUNDS, x, 0.5);
z += n.z;
let n = top_row_normal(&input_surface, BOUNDS, x);
z += n.normal.x;
}
// Bottom row.
for x in BOUNDS.x0 as u32 + 1..BOUNDS.x1 as u32 - 1 {
let n = bottom_row_normal(&input_surface, BOUNDS, x, 0.5);
z += n.z;
let n = bottom_row_normal(&input_surface, BOUNDS, x);
z += n.normal.x;
}
// Left column.
for y in BOUNDS.y0 as u32 + 1..BOUNDS.y1 as u32 - 1 {
let n = left_column_normal(&input_surface, BOUNDS, y, 0.5);
z += n.z;
let n = left_column_normal(&input_surface, BOUNDS, y);
z += n.normal.x;
}
// Right column.
for y in BOUNDS.y0 as u32 + 1..BOUNDS.y1 as u32 - 1 {
let n = right_column_normal(&input_surface, BOUNDS, y, 0.5);
z += n.z;
let n = right_column_normal(&input_surface, BOUNDS, y);
z += n.normal.x;
}
// Interior pixels.
for y in BOUNDS.y0 as u32 + 1..BOUNDS.y1 as u32 - 1 {
for x in BOUNDS.x0 as u32 + 1..BOUNDS.x1 as u32 - 1 {
let n = interior_normal(&input_surface, BOUNDS, x, y, 0.5);
z += n.z;
let n = interior_normal(&input_surface, BOUNDS, x, y);
z += n.normal.x;
}
}
......
......@@ -4,6 +4,7 @@ use std::cmp::max;
use cairo::{self, ImageSurface, MatrixTrait};
use cssparser;
use nalgebra::Vector3;
use num_traits::identities::Zero;
use attributes::Attribute;
use drawing_ctx::DrawingCtx;
......@@ -21,6 +22,7 @@ use filters::{
top_left_normal,
top_right_normal,
top_row_normal,
Normal,
},
Filter,
FilterError,
......@@ -251,7 +253,7 @@ impl Filter for Lighting {
{
let mut output_data = output_surface.get_data().unwrap();
let mut compute_output_pixel = |x, y, normal: Vector3<f64>| {
let mut compute_output_pixel = |x, y, normal: Normal| {
let pixel = input_surface.get_pixel(x, y);
let scaled_x = f64::from(x) * ox;
......@@ -264,18 +266,52 @@ impl Filter for Lighting {
Data::Diffuse {
ref diffuse_constant,
} => {
let n_dot_l = normal.dot(&light_vector);
diffuse_constant.get() * n_dot_l
let k = if normal.normal.is_zero() {
// Common case of (0, 0, 1) normal.
light_vector.z
} else {
let mut n = normal.normal.map(|x| f64::from(x) * surface_scale / 255.);
n.component_mul_assign(&normal.factor);
let normal = Vector3::new(n.x, n.y, 1.0);
normal.dot(&light_vector) / normal.norm()
};
diffuse_constant.get() * k
}
Data::Specular {
ref specular_constant,
ref specular_exponent,
} => {
let mut h = light_vector + Vector3::new(0.0, 0.0, 1.0);
let _ = h.try_normalize_mut(0.0);
let n_dot_h = normal.dot(&h);
specular_constant.get() * n_dot_h.powf(specular_exponent.get())
let h = light_vector + Vector3::new(0.0, 0.0, 1.0);
let h_norm = h.norm();
if h_norm == 0.0 {
0.0
} else {
let k = if normal.normal.is_zero() {
// Common case of (0, 0, 1) normal.
let n_dot_h = h.z / h_norm;
if specular_exponent.get() == 1.0 {
n_dot_h
} else {
n_dot_h.powf(specular_exponent.get())
}
} else {
let mut n =
normal.normal.map(|x| f64::from(x) * surface_scale / 255.);
n.component_mul_assign(&normal.factor);
let normal = Vector3::new(n.x, n.y, 1.0);
let n_dot_h = normal.dot(&h) / normal.norm() / h_norm;
if specular_exponent.get() == 1.0 {
n_dot_h
} else {
n_dot_h.powf(specular_exponent.get())
}
};
specular_constant.get() * k
}
}
};
......@@ -299,28 +335,28 @@ impl Filter for Lighting {
compute_output_pixel(
bounds.x0 as u32,
bounds.y0 as u32,
top_left_normal(&input_surface, bounds, surface_scale),
top_left_normal(&input_surface, bounds),
);
// Top right.
compute_output_pixel(
bounds.x1 as u32 - 1,
bounds.y0 as u32,
top_right_normal(&input_surface, bounds, surface_scale),
top_right_normal(&input_surface, bounds),
);
// Bottom left.
compute_output_pixel(
bounds.x0 as u32,
bounds.y1 as u32 - 1,
bottom_left_normal(&input_surface, bounds, surface_scale),
bottom_left_normal(&input_surface, bounds),
);
// Bottom right.
compute_output_pixel(
bounds.x1 as u32 - 1,
bounds.y1 as u32 - 1,
bottom_right_normal(&input_surface, bounds, surface_scale),
bottom_right_normal(&input_surface, bounds),
);
if bounds.x1 - bounds.x0 >= 3 {
......@@ -329,7 +365,7 @@ impl Filter for Lighting {
compute_output_pixel(
x,
bounds.y0 as u32,
top_row_normal(&input_surface, bounds, x, surface_scale),
top_row_normal(&input_surface, bounds, x),
);
}
......@@ -338,7 +374,7 @@ impl Filter for Lighting {
compute_output_pixel(
x,
bounds.y1 as u32 - 1,
bottom_row_normal(&input_surface, bounds, x, surface_scale),
bottom_row_normal(&input_surface, bounds, x),
);
}
}
......@@ -349,7 +385,7 @@ impl Filter for Lighting {
compute_output_pixel(
bounds.x0 as u32,
y,
left_column_normal(&input_surface, bounds, y, surface_scale),
left_column_normal(&input_surface, bounds, y),
);
}
......@@ -358,7 +394,7 @@ impl Filter for Lighting {
compute_output_pixel(
bounds.x1 as u32 - 1,
y,
right_column_normal(&input_surface, bounds, y, surface_scale),
right_column_normal(&input_surface, bounds, y),
);
}
}
......@@ -367,11 +403,7 @@ impl Filter for Lighting {
// Interior pixels.
for y in bounds.y0 as u32 + 1..bounds.y1 as u32 - 1 {
for x in bounds.x0 as u32 + 1..bounds.x1 as u32 - 1 {
compute_output_pixel(
x,
y,
interior_normal(&input_surface, bounds, x, y, surface_scale),
);
compute_output_pixel(x, y, interior_normal(&input_surface, bounds, x, y));
}
}
}
......
//! Light filters and nodes.
use nalgebra::Vector3;
use nalgebra::Vector2;
use filters::context::IRect;
use surface_utils::shared_surface::SharedImageSurface;
......@@ -9,31 +9,29 @@ pub mod lighting;
// Functions here are pub for the purpose of accessing them from benchmarks.
/// 2D normal and factor stored separately.
///
/// The normal needs to be multiplied by `surface_scale * factor / 255` and normalized with 1 as
/// the z component.
#[derive(Debug, Clone, Copy)]
pub struct Normal {
pub factor: Vector2<f64>,
pub normal: Vector2<i16>,
}
/// Inner utility function.
#[inline]
fn return_normal(
factor_x: f64,
nx: i16,
factor_y: f64,
ny: i16,
surface_scale: f64,
) -> Vector3<f64> {
let nx = f64::from(nx) / 255. * factor_x * surface_scale;
let ny = f64::from(ny) / 255. * factor_y * surface_scale;
fn return_normal(factor_x: f64, nx: i16, factor_y: f64, ny: i16) -> Normal {
// Negative nx and ny to account for the different coordinate system.
let mut n = Vector3::new(-nx, -ny, 1.);
n.normalize_mut();
n
Normal {
factor: Vector2::new(factor_x, factor_y),
normal: Vector2::new(-nx, -ny),
}
}
/// Computes and returns the normal vector for the top left pixel for light filters.
#[inline]
pub fn top_left_normal(
surface: &SharedImageSurface,
bounds: IRect,
surface_scale: f64,
) -> Vector3<f64> {
pub fn top_left_normal(surface: &SharedImageSurface, bounds: IRect) -> Normal {
// Surface needs to be at least 2×2.
assert!(bounds.x1 >= bounds.x0 + 2);
assert!(bounds.y1 >= bounds.y0 + 2);
......@@ -52,18 +50,12 @@ pub fn top_left_normal(
-2 * center + 2 * right - bottom + bottom_right,
2. / 3.,
-2 * center - right + 2 * bottom + bottom_right,
surface_scale,
)
}
/// Computes and returns the normal vector for the top row pixels for light filters.
#[inline]
pub fn top_row_normal(
surface: &SharedImageSurface,
bounds: IRect,
x: u32,
surface_scale: f64,
) -> Vector3<f64> {
pub fn top_row_normal(surface: &SharedImageSurface, bounds: IRect, x: u32) -> Normal {
assert!(x as i32 > bounds.x0);
assert!((x as i32) + 1 < bounds.x1);
assert!(bounds.y1 >= bounds.y0 + 2);
......@@ -83,17 +75,12 @@ pub fn top_row_normal(
-2 * left + 2 * right - bottom_left + bottom_right,
1. / 2.,
-left - 2 * center - right + bottom_left + 2 * bottom + bottom_right,
surface_scale,
)
}
/// Computes and returns the normal vector for the top right pixel for light filters.
#[inline]
pub fn top_right_normal(
surface: &SharedImageSurface,
bounds: IRect,
surface_scale: f64,
) -> Vector3<f64> {
pub fn top_right_normal(surface: &SharedImageSurface, bounds: IRect) -> Normal {
// Surface needs to be at least 2×2.
assert!(bounds.x1 >= bounds.x0 + 2);
assert!(bounds.y1 >= bounds.y0 + 2);
......@@ -112,18 +99,12 @@ pub fn top_right_normal(
-2 * left + 2 * center - bottom_left + bottom,
2. / 3.,
-left - 2 * center + bottom_left + 2 * bottom,
surface_scale,
)
}
/// Computes and returns the normal vector for the left column pixels for light filters.
#[inline]
pub fn left_column_normal(
surface: &SharedImageSurface,
bounds: IRect,
y: u32,
surface_scale: f64,
) -> Vector3<f64> {
pub fn left_column_normal(surface: &SharedImageSurface, bounds: IRect, y: u32) -> Normal {
assert!(y as i32 > bounds.y0);
assert!((y as i32) + 1 < bounds.y1);
assert!(bounds.x1 >= bounds.x0 + 2);
......@@ -143,19 +124,12 @@ pub fn left_column_normal(
-top + top_right - 2 * center + 2 * right - bottom + bottom_right,
1. / 3.,
-2 * top - top_right + 2 * bottom + bottom_right,
surface_scale,
)
}
/// Computes and returns the normal vector for the interior pixels for light filters.
#[inline]
pub fn interior_normal(
surface: &SharedImageSurface,
bounds: IRect,
x: u32,
y: u32,
surface_scale: f64,
) -> Vector3<f64> {
pub fn interior_normal(surface: &SharedImageSurface, bounds: IRect, x: u32, y: u32) -> Normal {
assert!(x as i32 > bounds.x0);
assert!((x as i32) + 1 < bounds.x1);
assert!(y as i32 > bounds.y0);
......@@ -177,18 +151,12 @@ pub fn interior_normal(
-top_left + top_right - 2 * left + 2 * right - bottom_left + bottom_right,
1. / 4.,
-top_left - 2 * top - top_right + bottom_left + 2 * bottom + bottom_right,
surface_scale,
)
}
/// Computes and returns the normal vector for the right column pixels for light filters.
#[inline]
pub fn right_column_normal(
surface: &SharedImageSurface,
bounds: IRect,
y: u32,
surface_scale: f64,
) -> Vector3<f64> {
pub fn right_column_normal(surface: &SharedImageSurface, bounds: IRect, y: u32) -> Normal {
assert!(y as i32 > bounds.y0);
assert!((y as i32) + 1 < bounds.y1);
assert!(bounds.x1 >= bounds.x0 + 2);
......@@ -208,17 +176,12 @@ pub fn right_column_normal(
-top_left + top - 2 * left + 2 * center - bottom_left + bottom,
1. / 3.,
-top_left - 2 * top + bottom_left + 2 * bottom,
surface_scale,
)
}
/// Computes and returns the normal vector for the bottom left pixel for light filters.
#[inline]
pub fn bottom_left_normal(
surface: &SharedImageSurface,
bounds: IRect,
surface_scale: f64,
) -> Vector3<f64> {
pub fn bottom_left_normal(surface: &SharedImageSurface, bounds: IRect) -> Normal {
// Surface needs to be at least 2×2.
assert!(bounds.x1 >= bounds.x0 + 2);
assert!(bounds.y1 >= bounds.y0 + 2);
......@@ -237,18 +200,12 @@ pub fn bottom_left_normal(
-top + top_right - 2 * center + 2 * right,
2. / 3.,
-2 * top - top_right + 2 * center + right,
surface_scale,
)
}
/// Computes and returns the normal vector for the bottom row pixels for light filters.
#[inline]
pub fn bottom_row_normal(
surface: &SharedImageSurface,
bounds: IRect,
x: u32,
surface_scale: f64,
) -> Vector3<f64> {
pub fn bottom_row_normal(surface: &SharedImageSurface, bounds: IRect, x: u32) -> Normal {
assert!(x as i32 > bounds.x0);
assert!((x as i32) + 1 < bounds.x1);
assert!(bounds.y1 >= bounds.y0 + 2);
......@@ -268,17 +225,12 @@ pub fn bottom_row_normal(
-top_left + top_right - 2 * left + 2 * right,
1. / 2.,
-top_left - 2 * top - top_right + left + 2 * center + right,
surface_scale,
)
}
/// Computes and returns the normal vector for the bottom right pixel for light filters.
#[inline]
pub fn bottom_right_normal(
surface: &SharedImageSurface,
bounds: IRect,
surface_scale: f64,
) -> Vector3<f64> {
pub fn bottom_right_normal(surface: &SharedImageSurface, bounds: IRect) -> Normal {
// Surface needs to be at least 2×2.
assert!(bounds.x1 >= bounds.x0 + 2);
assert!(bounds.y1 >= bounds.y0 + 2);
......@@ -297,6 +249,5 @@ pub fn bottom_right_normal(
-top_left + top - 2 * left + 2 * center,
2. / 3.,
-top_left - 2 * top + left + 2 * center,
surface_scale,
)
}
......@@ -11,6 +11,7 @@ extern crate glib_sys;
extern crate itertools;
extern crate libc;
extern crate nalgebra;
extern crate num_traits;
extern crate owning_ref;
extern crate pango;
extern crate pango_cairo_sys;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment