Skip to content

Clean up the Perlin noise benchmark #12270

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 15, 2014
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 49 additions & 65 deletions src/test/bench/noise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,128 +8,112 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

// Perlin noise benchmark from https://gist.github.com/1170424
// Multi-language Perlin noise benchmark.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Multi-language?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check out the link in the next line, there's D, Nimrod, Go, C, C#, lots more. Lots of the structure of this code is the way it is in order to remain easily comparable to these other implementations.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, cool!

And we're winning \o/

// See https://github.com/nsf/pnoise for timings and alternative implementations.

use std::f64;
use std::rand::Rng;
use std::rand;
use std::f32::consts::PI;
use std::rand::{Rng, StdRng};

struct Vec2 {
x: f32,
y: f32,
}

#[inline(always)]
fn lerp(a: f32, b: f32, v: f32) -> f32 { a * (1.0 - v) + b * v }

#[inline(always)]
fn smooth(v: f32) -> f32 { v * v * (3.0 - 2.0 * v) }

fn random_gradient<R:Rng>(r: &mut R) -> Vec2 {
let v = 2.0 * f64::consts::PI * r.gen();
Vec2 {
x: v.cos() as f32,
y: v.sin() as f32,
}
fn random_gradient<R: Rng>(r: &mut R) -> Vec2 {
let v = PI * 2.0 * r.gen();
Vec2 { x: v.cos(), y: v.sin() }
}

fn gradient(orig: Vec2, grad: Vec2, p: Vec2) -> f32 {
let sp = Vec2 {x: p.x - orig.x, y: p.y - orig.y};
grad.x * sp.x + grad.y * sp.y
(p.x - orig.x) * grad.x + (p.y - orig.y) * grad.y
}

struct Noise2DContext {
rgradients: [Vec2, ..256],
permutations: [int, ..256],
permutations: [i32, ..256],
}

impl Noise2DContext {
pub fn new() -> Noise2DContext {
let mut r = rand::rng();
let mut rgradients = [ Vec2 { x: 0.0, y: 0.0 }, ..256 ];
for i in range(0, 256) {
rgradients[i] = random_gradient(&mut r);
}
let mut permutations = [ 0, ..256 ];
for i in range(0, 256) {
permutations[i] = i;
fn new() -> Noise2DContext {
let mut rng = StdRng::new();

let mut rgradients = [Vec2 { x: 0.0, y: 0.0 }, ..256];
for x in rgradients.mut_iter() {
*x = random_gradient(&mut rng);
}
r.shuffle_mut(permutations);

Noise2DContext {
rgradients: rgradients,
permutations: permutations,
let mut permutations = [0i32, ..256];
for (i, x) in permutations.mut_iter().enumerate() {
*x = i as i32;
}
rng.shuffle_mut(permutations);

Noise2DContext { rgradients: rgradients, permutations: permutations }
}

#[inline(always)]
pub fn get_gradient(&self, x: int, y: int) -> Vec2 {
fn get_gradient(&self, x: i32, y: i32) -> Vec2 {
let idx = self.permutations[x & 255] + self.permutations[y & 255];
self.rgradients[idx & 255]
}

#[inline]
pub fn get_gradients(&self,
gradients: &mut [Vec2, ..4],
origins: &mut [Vec2, ..4],
x: f32,
y: f32) {
fn get_gradients(&self, x: f32, y: f32) -> ([Vec2, ..4], [Vec2, ..4]) {
let x0f = x.floor();
let y0f = y.floor();
let x0 = x0f as int;
let y0 = y0f as int;
let x1f = x0f + 1.0;
let y1f = y0f + 1.0;

let x0 = x0f as i32;
let y0 = y0f as i32;
let x1 = x0 + 1;
let y1 = y0 + 1;

gradients[0] = self.get_gradient(x0, y0);
gradients[1] = self.get_gradient(x1, y0);
gradients[2] = self.get_gradient(x0, y1);
gradients[3] = self.get_gradient(x1, y1);

origins[0] = Vec2 {x: x0f + 0.0, y: y0f + 0.0};
origins[1] = Vec2 {x: x0f + 1.0, y: y0f + 0.0};
origins[2] = Vec2 {x: x0f + 0.0, y: y0f + 1.0};
origins[3] = Vec2 {x: x0f + 1.0, y: y0f + 1.0};
([self.get_gradient(x0, y0), self.get_gradient(x1, y0),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on the comparative benchmarks... I wonder if this is considered cheating?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit weird, but I don't consider it cheating. The original author of this code, who also wrote all the other implementations, chose to omit the gradients and origins arrays from the context object here. It really doesn't make sense to carry around these as part of the context anyway, so I actually think our version makes more sense. And the only reason that it works in other languages is because they don't require all fields of a struct to be initialized.

self.get_gradient(x0, y1), self.get_gradient(x1, y1)],
[Vec2 { x: x0f, y: y0f }, Vec2 { x: x1f, y: y0f },
Vec2 { x: x0f, y: y1f }, Vec2 { x: x1f, y: y1f }])
}

#[inline]
pub fn get(&self, x: f32, y: f32) -> f32 {
fn get(&self, x: f32, y: f32) -> f32 {
let p = Vec2 {x: x, y: y};
let mut gradients = [ Vec2 { x: 0.0, y: 0.0 }, ..4 ];
let mut origins = [ Vec2 { x: 0.0, y: 0.0 }, ..4 ];
self.get_gradients(&mut gradients, &mut origins, x, y);
let (gradients, origins) = self.get_gradients(x, y);

let v0 = gradient(origins[0], gradients[0], p);
let v1 = gradient(origins[1], gradients[1], p);
let v2 = gradient(origins[2], gradients[2], p);
let v3 = gradient(origins[3], gradients[3], p);

let fx = smooth(x - origins[0].x);
let vx0 = lerp(v0, v1, fx);
let vx1 = lerp(v2, v3, fx);
let fy = smooth(y - origins[0].y);

lerp(vx0, vx1, fy)
}
}

fn main() {
let symbols = [" ", "░", "▒", "▓", "█", "█"];
let symbols = [' ', '░', '▒', '▓', '█', '█'];
let mut pixels = [0f32, ..256*256];
let n2d = ~Noise2DContext::new();
for _ in range(0, 100u) {
let n2d = Noise2DContext::new();

for _ in range(0, 100) {
for y in range(0, 256) {
for x in range(0, 256) {
let v = n2d.get(
x as f32 * 0.1f32,
y as f32 * 0.1f32
) * 0.5f32 + 0.5f32;
pixels[y*256+x] = v;
};
};
};
let v = n2d.get(x as f32 * 0.1, y as f32 * 0.1);
pixels[y*256+x] = v * 0.5 + 0.5;
}
}
}

for y in range(0, 256) {
for x in range(0, 256) {
print!("{}", symbols[(pixels[y*256+x] / 0.2f32) as int]);
let idx = (pixels[y*256+x] / 0.2) as uint;
print!("{:c}", symbols[idx]);
}
println!("");
print!("\n");
}
}