diff --git a/Cargo.lock b/Cargo.lock index d5a5ced7..da630ac5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -170,6 +170,20 @@ dependencies = [ "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]] name = "canvas_text" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index c7f5b16c..506e2089 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "demo/magicleap", "demo/native", "examples/canvas_minimal", + "examples/canvas_moire", "examples/canvas_text", "geometry", "gl", diff --git a/examples/canvas_minimal/src/main.rs b/examples/canvas_minimal/src/main.rs index 8238cf20..449fdd54 100644 --- a/examples/canvas_minimal/src/main.rs +++ b/examples/canvas_minimal/src/main.rs @@ -1,4 +1,4 @@ -// pathfinder/canvas_minimal/src/main.rs +// pathfinder/examples/canvas_minimal/src/main.rs // // Copyright © 2019 The Pathfinder Project Developers. // diff --git a/examples/canvas_moire/Cargo.toml b/examples/canvas_moire/Cargo.toml new file mode 100644 index 00000000..0bab187d --- /dev/null +++ b/examples/canvas_moire/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "canvas_moire" +version = "0.1.0" +authors = ["Patrick Walton "] +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" diff --git a/examples/canvas_moire/src/main.rs b/examples/canvas_moire/src/main.rs new file mode 100644 index 00000000..61b02e51 --- /dev/null +++ b/examples/canvas_moire/src/main.rs @@ -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 or the MIT license +// , 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, + scene: Option, + frame: i32, + window_size: Point2DI32, + drawable_size: Point2DI32, + device_pixel_ratio: f32, + colors: ColorGradient, +} + +impl MoireRenderer { + fn new(renderer: Renderer, 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)) + } +} diff --git a/examples/canvas_text/src/main.rs b/examples/canvas_text/src/main.rs index a4f3118a..db0a455e 100644 --- a/examples/canvas_text/src/main.rs +++ b/examples/canvas_text/src/main.rs @@ -1,4 +1,4 @@ -// pathfinder/canvas_text/src/main.rs +// pathfinder/examples/canvas_text/src/main.rs // // Copyright © 2019 The Pathfinder Project Developers. // diff --git a/geometry/src/color.rs b/geometry/src/color.rs index 5b27ff8a..e0699a19 100644 --- a/geometry/src/color.rs +++ b/geometry/src/color.rs @@ -11,6 +11,7 @@ use pathfinder_simd::default::F32x4; use std::fmt::{self, Debug, Formatter}; +// TODO(pcwalton): Maybe this should be a u32? #[derive(Clone, Copy, PartialEq, Eq, Hash, Default)] pub struct ColorU { pub r: u8, @@ -20,6 +21,16 @@ pub struct 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] pub fn black() -> ColorU { ColorU { @@ -68,6 +79,17 @@ impl ColorF { 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] pub fn r(&self) -> f32 { self.0[0] diff --git a/renderer/src/concurrent/scene_proxy.rs b/renderer/src/concurrent/scene_proxy.rs index 2b5e62ab..52534cae 100644 --- a/renderer/src/concurrent/scene_proxy.rs +++ b/renderer/src/concurrent/scene_proxy.rs @@ -78,9 +78,11 @@ impl SceneProxy { #[inline] pub fn build_and_render(&self, renderer: &mut Renderer, options: RenderOptions) where D: Device { + renderer.begin_scene(); for command in self.build_with_stream(options) { renderer.render_command(&command) } + renderer.end_scene(); } pub fn as_svg(&self) -> Vec { diff --git a/simd/src/arm/mod.rs b/simd/src/arm/mod.rs index 89ffe691..97c990a8 100644 --- a/simd/src/arm/mod.rs +++ b/simd/src/arm/mod.rs @@ -71,6 +71,11 @@ impl F32x4 { unsafe { F32x4(ceil_v4f32(self.0)) } } + #[inline] + pub fn round(self) -> F32x4 { + unsafe { F32x4(round_v4f32(self.0)) } + } + // Packed comparisons #[inline] @@ -407,6 +412,8 @@ extern "C" { fn floor_v4f32(a: float32x4_t) -> float32x4_t; #[link_name = "llvm.ceil.v4f32"] 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"] fn vrecpe_v4f32(a: float32x4_t) -> float32x4_t; diff --git a/simd/src/scalar/mod.rs b/simd/src/scalar/mod.rs index cd18bcb3..553b1780 100644 --- a/simd/src/scalar/mod.rs +++ b/simd/src/scalar/mod.rs @@ -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 #[inline] diff --git a/simd/src/x86/mod.rs b/simd/src/x86/mod.rs index 5aadde51..2896b487 100644 --- a/simd/src/x86/mod.rs +++ b/simd/src/x86/mod.rs @@ -8,7 +8,7 @@ // option. This file may not be copied, modified, or distributed // 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::fmt::{self, Debug, Formatter}; use std::mem; @@ -78,6 +78,11 @@ impl F32x4 { 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 #[inline]