Skip to content

Commit

Permalink
Merge pull request #5 from nxsaken/bench
Browse files Browse the repository at this point in the history
Benchmark, performance fixes, release 0.1.2
  • Loading branch information
nxsaken committed Nov 2, 2023
2 parents b40eafd + af553fa commit 691d67e
Show file tree
Hide file tree
Showing 7 changed files with 355 additions and 11 deletions.
161 changes: 161 additions & 0 deletions BENCHMARKS.md

Large diffs are not rendered by default.

13 changes: 11 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "clipline"
version = "0.1.1"
version = "0.1.2"
authors = ["Nurzhan Sakén <nurzhan.sakenov@gmail.com>"]
edition = "2021"
description = "Efficient scan conversion (rasterization) of line segments with clipping to a rectangular window."
Expand All @@ -17,4 +17,13 @@ include = [
[features]
default = ["func", "iter"]
iter = []
func = []
func = []

[dev-dependencies]
criterion = { version = "0.5.1"}
bresenham = "0.1.1"
line_drawing = "1.0.0"

[[bench]]
name = "bresenham_comparison"
harness = false
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ clipping algorithms like [Cohen-Sutherland](https://en.wikipedia.org/wiki/Cohen%

![`clipline` in action](img/clip_anim.gif)

## Benchmarks

[Benchmarks are available here](BENCHMARKS.md). I used [criterion.rs](https://github.com/bheisler/criterion.rs) to
compare
`clipline` to two
popular line drawing crates – `bresenham` and `line_drawing`, on a variety of
clipping window sizes, line orientations and counts.

## Installation

To use `clipline`, add it to your `Cargo.toml` file:
Expand Down Expand Up @@ -52,7 +60,7 @@ for (x, y) in Clipline::new(line, clip_rect).unwrap() {
draw_pixel(x, y);
}

// C. Iterate over each `Clipline` case directly (faster, recommended)
// C. Iterate over each `Clipline` case directly
match Clipline::new(line, clip_rect).unwrap() {
Vlipline(pixels) => pixels.for_each(|(x, y)| draw_pixel(x, y)),
Hlipline(pixels) => pixels.for_each(|(x, y)| draw_pixel(x, y)),
Expand Down
153 changes: 153 additions & 0 deletions benches/bresenham_comparison.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
use bresenham::Bresenham as BresenhamA;
use clipline::{clipline, Clipline};
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use line_drawing::Bresenham as BresenhamB;

type Point = (isize, isize);
type Line = (Point, Point);
type Rect = (Point, Point);

fn draw_pixel_checked(p: Point, clip: Rect) {
let (x, y) = p;
let ((wx1, wy1), (wx2, wy2)) = clip;
if black_box(x >= wx1 && x < wx2 && y >= wy1 && y < wy2) {
black_box((x, y));
}
}

fn draw_pixel_unchecked(p: Point) {
black_box(p);
}

fn line_a(line: Line, clip: Rect) {
let (p1, p2) = line;
for p in BresenhamA::new(black_box(p1), black_box(p2)) {
draw_pixel_checked(p, clip)
}
}

fn line_b(line: Line, clip: Rect) {
let (p1, p2) = line;
for p in BresenhamB::new(black_box(p1), black_box(p2)) {
draw_pixel_checked(p, clip)
}
}

fn clipline_a(line: Line, clip: Rect) {
clipline(black_box(line), black_box(clip), |x, y| {
draw_pixel_unchecked((x, y))
});
}

fn clipline_b(line: Line, clip: Rect) {
if let Some(clipline) = Clipline::new(black_box(line), black_box(clip)) {
for p in clipline {
draw_pixel_unchecked(p)
}
}
}

fn clipline_c(line: Line, clip: Rect) {
if let Some(clipline) = Clipline::new(black_box(line), black_box(clip)) {
match clipline {
Clipline::Vlipline(ln) => ln.for_each(draw_pixel_unchecked),
Clipline::Hlipline(ln) => ln.for_each(draw_pixel_unchecked),
Clipline::Gentleham(ln) => ln.for_each(draw_pixel_unchecked),
Clipline::Steepnham(ln) => ln.for_each(draw_pixel_unchecked),
}
}
}

fn bench_lines(c: &mut Criterion) {
let cases = [1, 8, 32].iter().flat_map(|mult| {
let (w, h) = (160 * mult, 160 * mult);
[
(
format!("{w}x{h}_inside_vert"),
(w, h),
((w / 2, 0), (w / 2, h - 1)),
),
(
format!("{w}x{h}_inside_hor"),
(w, h),
((0, h / 2), (w - 1, h / 2)),
),
(
format!("{w}x{h}_inside_gentle"),
(w, h),
((0, h - 1), (w / 2, 0)),
),
(
format!("{w}x{h}_inside_steep"),
(w, h),
((0, h - 1), (w - 1, h / 2)),
),
(format!("{w}x{h}_outside"), (w, h), ((0, h), (w - 1, 2 * h))),
]
});
for (case_name, clip_size, line) in cases {
let mut group = c.benchmark_group(case_name);
for num_lines in [1, 1024, 8192, 32768] {
group.throughput(Throughput::Elements(num_lines));
group.bench_with_input(
BenchmarkId::new("bresenham", num_lines),
&num_lines,
|b, &num_lines| {
b.iter(|| {
for _ in 0..num_lines {
line_a(line, ((0, 0), clip_size));
}
});
},
);
group.bench_with_input(
BenchmarkId::new("line_drawing", num_lines),
&num_lines,
|b, &num_lines| {
b.iter(|| {
for _ in 0..num_lines {
line_b(line, ((0, 0), clip_size));
}
});
},
);
group.bench_with_input(
BenchmarkId::new("clipline(fn)", num_lines),
&num_lines,
|b, &num_lines| {
b.iter(|| {
for _ in 0..num_lines {
clipline_a(line, ((0, 0), clip_size));
}
});
},
);
group.bench_with_input(
BenchmarkId::new("Clipline(iter)", num_lines),
&num_lines,
|b, &num_lines| {
b.iter(|| {
for _ in 0..num_lines {
clipline_b(line, ((0, 0), clip_size));
}
});
},
);
group.bench_with_input(
BenchmarkId::new("Clipline(match-iter)", num_lines),
&num_lines,
|b, &num_lines| {
b.iter(|| {
for _ in 0..num_lines {
clipline_c(line, ((0, 0), clip_size));
}
});
},
);
}
group.finish()
}
}

criterion_group!(benches, bench_lines);
criterion_main!(benches);
2 changes: 1 addition & 1 deletion src/func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ where
mod tests {
use super::*;

fn draw_pixel(_x: isize, _y: isize) {}
const fn draw_pixel(_x: isize, _y: isize) {}

#[test]
fn test_line_outside_clip() {
Expand Down
16 changes: 11 additions & 5 deletions src/iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,8 @@ impl Hlipline {

impl Bresenham {
#[allow(clippy::too_many_arguments)]
fn new(
#[inline(always)]
const fn new(
tx: isize,
ty: isize,
dx2: isize,
Expand All @@ -343,19 +344,21 @@ impl Bresenham {
impl Iterator for Clipline {
type Item = Point;

#[inline]
fn next(&mut self) -> Option<Self::Item> {
match self {
Clipline::Vlipline(iter) => iter.next(),
Clipline::Hlipline(iter) => iter.next(),
Clipline::Gentleham(iter) => iter.next(),
Clipline::Steepnham(iter) => iter.next(),
Self::Vlipline(iter) => iter.next(),
Self::Hlipline(iter) => iter.next(),
Self::Gentleham(iter) => iter.next(),
Self::Steepnham(iter) => iter.next(),
}
}
}

impl Iterator for Vlipline {
type Item = Point;

#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.y1 * self.sy > self.y2 * self.sy {
return None;
Expand All @@ -369,6 +372,7 @@ impl Iterator for Vlipline {
impl Iterator for Hlipline {
type Item = Point;

#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.x1 * self.sx > self.x2 * self.sx {
return None;
Expand All @@ -382,6 +386,7 @@ impl Iterator for Hlipline {
impl Iterator for Gentleham {
type Item = Point;

#[inline]
fn next(&mut self) -> Option<Self::Item> {
let Self(b) = self;
if b.xd == b.term {
Expand All @@ -396,6 +401,7 @@ impl Iterator for Gentleham {
impl Iterator for Steepnham {
type Item = Point;

#[inline]
fn next(&mut self) -> Option<Self::Item> {
let Self(b) = self;
if b.yd == b.term {
Expand Down
11 changes: 9 additions & 2 deletions src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use core::cmp::{max, min};
pub type Point = (isize, isize);

/// Standardizes the line segment (such that `x1 < x2 && y1 < y2`).
#[inline(always)]
pub fn standardize(
xy1: isize,
xy2: isize,
Expand All @@ -17,6 +18,7 @@ pub fn standardize(
}

#[allow(clippy::too_many_arguments)]
#[inline(always)]
pub fn vertical_line(
x: isize,
y1: isize,
Expand All @@ -35,13 +37,14 @@ pub fn vertical_line(
return None;
}
let (cy1, cy2) = (max(y1, wy1), min(y2, wy2));
for y in cy1..=cy2 {
for y in cy1..(cy2 + 1) {
pixel_op(x, y);
}
Some((cy1, cy2))
}

#[allow(clippy::too_many_arguments)]
#[inline(always)]
pub fn horizontal_line(
y: isize,
x1: isize,
Expand All @@ -62,13 +65,14 @@ pub fn horizontal_line(
let (cx1, cx2) = (max(x1, wx1), min(x2, wx2));
// in practice it's better to fill the whole row in one operation,
// but to keep the API simple we do it pixel-wise
for x in cx1..=cx2 {
for x in cx1..(cx2 + 1) {
pixel_op(x, y);
}
Some((cx1, cx2))
}

#[allow(clippy::too_many_arguments)]
#[inline(always)]
pub fn clip_rect_entry(
xy1: isize,
yx1: isize,
Expand Down Expand Up @@ -124,6 +128,7 @@ pub fn clip_rect_entry(
}

#[allow(clippy::too_many_arguments)]
#[inline(always)]
pub fn clip_rect_exit(
xy1: isize,
xy2: isize,
Expand All @@ -148,6 +153,7 @@ pub fn clip_rect_exit(
}

#[allow(clippy::too_many_arguments)]
#[inline(always)]
pub fn destandardize(
mut term: isize,
mut xyd: isize,
Expand All @@ -162,6 +168,7 @@ pub fn destandardize(
(xyd, yxd, term)
}

#[inline(always)]
pub fn bresenham_step(
mut err: isize,
mut xyd: isize,
Expand Down

0 comments on commit 691d67e

Please sign in to comment.