Add Moiré demo

This commit is contained in:
Patrick Walton 2019-05-11 11:53:26 -07:00
parent a5d373cb91
commit 639a8f39e8
11 changed files with 285 additions and 3 deletions

14
Cargo.lock generated
View File

@ -170,6 +170,20 @@ dependencies = [
"sdl2-sys 0.32.5 (registry+https://github.com/rust-lang/crates.io-index)", "sdl2-sys 0.32.5 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "canvas_moire"
version = "0.1.0"
dependencies = [
"gl 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"pathfinder_canvas 0.1.0",
"pathfinder_geometry 0.3.0",
"pathfinder_gl 0.1.0",
"pathfinder_gpu 0.1.0",
"pathfinder_renderer 0.1.0",
"sdl2 0.32.1 (registry+https://github.com/rust-lang/crates.io-index)",
"sdl2-sys 0.32.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "canvas_text" name = "canvas_text"
version = "0.1.0" version = "0.1.0"

View File

@ -6,6 +6,7 @@ members = [
"demo/magicleap", "demo/magicleap",
"demo/native", "demo/native",
"examples/canvas_minimal", "examples/canvas_minimal",
"examples/canvas_moire",
"examples/canvas_text", "examples/canvas_text",
"geometry", "geometry",
"gl", "gl",

View File

@ -1,4 +1,4 @@
// pathfinder/canvas_minimal/src/main.rs // pathfinder/examples/canvas_minimal/src/main.rs
// //
// Copyright © 2019 The Pathfinder Project Developers. // Copyright © 2019 The Pathfinder Project Developers.
// //

View File

@ -0,0 +1,25 @@
[package]
name = "canvas_moire"
version = "0.1.0"
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
edition = "2018"
[dependencies]
gl = "0.6"
sdl2 = "0.32"
sdl2-sys = "0.32"
[dependencies.pathfinder_canvas]
path = "../../canvas"
[dependencies.pathfinder_geometry]
path = "../../geometry"
[dependencies.pathfinder_gl]
path = "../../gl"
[dependencies.pathfinder_gpu]
path = "../../gpu"
[dependencies.pathfinder_renderer]
path = "../../renderer"

View File

@ -0,0 +1,196 @@
// pathfinder/examples/canvas_moire/src/main.rs
//
// Copyright © 2019 The Pathfinder Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use pathfinder_canvas::{CanvasRenderingContext2D, FillStyle, Path2D};
use pathfinder_geometry::basic::point::{Point2DF32, Point2DI32};
use pathfinder_geometry::color::{ColorF, ColorU};
use pathfinder_gl::{GLDevice, GLVersion};
use pathfinder_gpu::resources::FilesystemResourceLoader;
use pathfinder_gpu::{ClearParams, Device};
use pathfinder_renderer::concurrent::rayon::RayonExecutor;
use pathfinder_renderer::concurrent::scene_proxy::SceneProxy;
use pathfinder_renderer::gpu::renderer::{DestFramebuffer, Renderer};
use pathfinder_renderer::options::RenderOptions;
use sdl2::event::Event;
use sdl2::keyboard::Keycode;
use sdl2::video::GLProfile;
use std::f32;
const VELOCITY: f32 = 0.02;
const OUTER_RADIUS: f32 = 64.0;
const INNER_RADIUS: f32 = 48.0;
// FIXME(pcwalton): Adding more circles causes clipping problems. Fix them!
const CIRCLE_COUNT: u32 = 12;
const CIRCLE_SPACING: f32 = 48.0;
const CIRCLE_THICKNESS: f32 = 16.0;
const COLOR_CYCLE_SPEED: f32 = 0.0025;
fn main() {
// Set up SDL2.
let sdl_context = sdl2::init().unwrap();
let video = sdl_context.video().unwrap();
// Make sure we have at least a GL 3.0 context. Pathfinder requires this.
let gl_attributes = video.gl_attr();
gl_attributes.set_context_profile(GLProfile::Core);
gl_attributes.set_context_version(3, 3);
// Open a window.
let window_size = Point2DI32::new(1067, 800);
let window = video.window("Moire example", window_size.x() as u32, window_size.y() as u32)
.opengl()
.allow_highdpi()
.build()
.unwrap();
let mut event_pump = sdl_context.event_pump().unwrap();
// Get the real window size (for HiDPI).
let (drawable_width, drawable_height) = window.drawable_size();
let drawable_size = Point2DI32::new(drawable_width as i32, drawable_height as i32);
// Create the GL context, and make it current.
let gl_context = window.gl_create_context().unwrap();
gl::load_with(|name| video.gl_get_proc_address(name) as *const _);
window.gl_make_current(&gl_context).unwrap();
// Create our renderers.
let renderer = Renderer::new(GLDevice::new(GLVersion::GL3, 0),
&FilesystemResourceLoader::locate(),
DestFramebuffer::full_window(drawable_size));
let mut moire_renderer = MoireRenderer::new(renderer, window_size, drawable_size);
// Enter main render loop.
loop {
moire_renderer.render();
window.gl_swap_window();
match event_pump.poll_event() {
Some(Event::Quit {..}) |
Some(Event::KeyDown { keycode: Some(Keycode::Escape), .. }) => return,
_ => {}
}
}
}
struct MoireRenderer {
renderer: Renderer<GLDevice>,
scene: Option<SceneProxy>,
frame: i32,
window_size: Point2DI32,
drawable_size: Point2DI32,
device_pixel_ratio: f32,
colors: ColorGradient,
}
impl MoireRenderer {
fn new(renderer: Renderer<GLDevice>, window_size: Point2DI32, drawable_size: Point2DI32)
-> MoireRenderer {
MoireRenderer {
renderer,
scene: None,
frame: 0,
window_size,
drawable_size,
device_pixel_ratio: drawable_size.x() as f32 / window_size.x() as f32,
colors: ColorGradient::new(),
}
}
fn render(&mut self) {
// Calculate animation values.
let time = self.frame as f32;
let (sin_time, cos_time) = (f32::sin(time * VELOCITY), f32::cos(time * VELOCITY));
let color_time = time * COLOR_CYCLE_SPEED;
let background_color = self.colors.sample(color_time);
let foreground_color = self.colors.sample(color_time + 0.5);
// Calculate outer and inner circle centers (circle and Leminscate of Gerono respectively).
let window_center = self.window_size.to_f32().scale(0.5);
let outer_center = window_center + Point2DF32::new(sin_time, cos_time).scale(OUTER_RADIUS);
let inner_center = window_center +
Point2DF32::new(1.0, sin_time).scale(cos_time * INNER_RADIUS);
// Clear to background color.
self.renderer.device.clear(&ClearParams {
color: Some(background_color),
..ClearParams::default()
});
// Make a canvas.
let mut canvas = CanvasRenderingContext2D::new(self.drawable_size.to_f32());
canvas.set_line_width(CIRCLE_THICKNESS * self.device_pixel_ratio);
canvas.set_stroke_style(FillStyle::Color(foreground_color.to_u8()));
// Draw circles.
self.draw_circles(&mut canvas, outer_center);
self.draw_circles(&mut canvas, inner_center);
// Build scene if necessary.
// TODO(pcwalton): Allow the user to build an empty scene proxy so they don't have to do this.
match self.scene {
None => self.scene = Some(SceneProxy::new(canvas.into_scene(), RayonExecutor)),
Some(ref mut scene) => scene.replace_scene(canvas.into_scene()),
}
// Render the scene.
self.scene.as_mut().unwrap().build_and_render(&mut self.renderer,
RenderOptions::default());
self.frame += 1;
}
fn draw_circles(&self, canvas: &mut CanvasRenderingContext2D, center: Point2DF32) {
for index in 0..CIRCLE_COUNT {
let mut path = Path2D::new();
self.add_circle_subpath(&mut path,
center.scale(self.device_pixel_ratio),
index as f32 * CIRCLE_SPACING * self.device_pixel_ratio);
canvas.stroke_path(path);
}
}
fn add_circle_subpath(&self, path: &mut Path2D, center: Point2DF32, radius: f32) {
path.move_to(center + Point2DF32::new(0.0, -radius));
path.quadratic_curve_to(center + Point2DF32::new(radius, -radius),
center + Point2DF32::new(radius, 0.0));
path.quadratic_curve_to(center + Point2DF32::new(radius, radius),
center + Point2DF32::new(0.0, radius));
path.quadratic_curve_to(center + Point2DF32::new(-radius, radius),
center + Point2DF32::new(-radius, 0.0));
path.quadratic_curve_to(center + Point2DF32::new(-radius, -radius),
center + Point2DF32::new(0.0, -radius));
path.close_path();
}
}
struct ColorGradient([ColorF; 5]);
impl ColorGradient {
fn new() -> ColorGradient {
// Extracted from https://stock.adobe.com/69426938/
ColorGradient([
ColorU::from_u32(0x024873ff).to_f32(),
ColorU::from_u32(0x03658cff).to_f32(),
ColorU::from_u32(0x0388a6ff).to_f32(),
ColorU::from_u32(0xf28e6bff).to_f32(),
ColorU::from_u32(0xd95a4eff).to_f32(),
])
}
fn sample(&self, mut t: f32) -> ColorF {
let count = self.0.len();
t *= count as f32;
let (lo, hi) = (t.floor() as usize % count, t.ceil() as usize % count);
self.0[lo].lerp(self.0[hi], f32::fract(t))
}
}

View File

@ -1,4 +1,4 @@
// pathfinder/canvas_text/src/main.rs // pathfinder/examples/canvas_text/src/main.rs
// //
// Copyright © 2019 The Pathfinder Project Developers. // Copyright © 2019 The Pathfinder Project Developers.
// //

View File

@ -11,6 +11,7 @@
use pathfinder_simd::default::F32x4; use pathfinder_simd::default::F32x4;
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
// TODO(pcwalton): Maybe this should be a u32?
#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)] #[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct ColorU { pub struct ColorU {
pub r: u8, pub r: u8,
@ -20,6 +21,16 @@ pub struct ColorU {
} }
impl ColorU { impl ColorU {
#[inline]
pub fn from_u32(rgba: u32) -> ColorU {
ColorU {
r: (rgba >> 24) as u8,
g: ((rgba >> 16) & 0xff) as u8,
b: ((rgba >> 8) & 0xff) as u8,
a: (rgba & 0xff) as u8,
}
}
#[inline] #[inline]
pub fn black() -> ColorU { pub fn black() -> ColorU {
ColorU { ColorU {
@ -68,6 +79,17 @@ impl ColorF {
ColorF(F32x4::splat(1.0)) ColorF(F32x4::splat(1.0))
} }
#[inline]
pub fn to_u8(&self) -> ColorU {
let color = (self.0 * F32x4::splat(255.0)).round().to_i32x4();
ColorU { r: color[0] as u8, g: color[1] as u8, b: color[2] as u8, a: color[3] as u8 }
}
#[inline]
pub fn lerp(&self, other: ColorF, t: f32) -> ColorF {
ColorF(self.0 + (other.0 - self.0) * F32x4::splat(t))
}
#[inline] #[inline]
pub fn r(&self) -> f32 { pub fn r(&self) -> f32 {
self.0[0] self.0[0]

View File

@ -78,9 +78,11 @@ impl SceneProxy {
#[inline] #[inline]
pub fn build_and_render<D>(&self, renderer: &mut Renderer<D>, options: RenderOptions) pub fn build_and_render<D>(&self, renderer: &mut Renderer<D>, options: RenderOptions)
where D: Device { where D: Device {
renderer.begin_scene();
for command in self.build_with_stream(options) { for command in self.build_with_stream(options) {
renderer.render_command(&command) renderer.render_command(&command)
} }
renderer.end_scene();
} }
pub fn as_svg(&self) -> Vec<u8> { pub fn as_svg(&self) -> Vec<u8> {

View File

@ -71,6 +71,11 @@ impl F32x4 {
unsafe { F32x4(ceil_v4f32(self.0)) } unsafe { F32x4(ceil_v4f32(self.0)) }
} }
#[inline]
pub fn round(self) -> F32x4 {
unsafe { F32x4(round_v4f32(self.0)) }
}
// Packed comparisons // Packed comparisons
#[inline] #[inline]
@ -407,6 +412,8 @@ extern "C" {
fn floor_v4f32(a: float32x4_t) -> float32x4_t; fn floor_v4f32(a: float32x4_t) -> float32x4_t;
#[link_name = "llvm.ceil.v4f32"] #[link_name = "llvm.ceil.v4f32"]
fn ceil_v4f32(a: float32x4_t) -> float32x4_t; fn ceil_v4f32(a: float32x4_t) -> float32x4_t;
#[link_name = "llvm.round.v4f32"]
fn round_v4f32(a: float32x4_t) -> float32x4_t;
#[link_name = "llvm.aarch64.neon.frecpe.v4f32"] #[link_name = "llvm.aarch64.neon.frecpe.v4f32"]
fn vrecpe_v4f32(a: float32x4_t) -> float32x4_t; fn vrecpe_v4f32(a: float32x4_t) -> float32x4_t;

View File

@ -89,6 +89,16 @@ impl F32x4 {
]) ])
} }
#[inline]
pub fn round(self) -> F32x4 {
F32x4([
self[0].round(),
self[1].round(),
self[2].round(),
self[3].round(),
])
}
// Packed comparisons // Packed comparisons
#[inline] #[inline]

View File

@ -8,7 +8,7 @@
// option. This file may not be copied, modified, or distributed // option. This file may not be copied, modified, or distributed
// except according to those terms. // except according to those terms.
use std::arch::x86_64::{self, __m128, __m128i}; use std::arch::x86_64::{self, __m128, __m128i, _MM_FROUND_TO_NEAREST_INT};
use std::cmp::PartialEq; use std::cmp::PartialEq;
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use std::mem; use std::mem;
@ -78,6 +78,11 @@ impl F32x4 {
unsafe { F32x4(x86_64::_mm_ceil_ps(self.0)) } unsafe { F32x4(x86_64::_mm_ceil_ps(self.0)) }
} }
#[inline]
pub fn round(self) -> F32x4 {
unsafe { F32x4(x86_64::_mm_round_ps(self.0, _MM_FROUND_TO_NEAREST_INT)) }
}
// Packed comparisons // Packed comparisons
#[inline] #[inline]