Merge remote-tracking branch 'origin/master' into pathfinder-unity-fun

This commit is contained in:
Atul Varma 2019-05-27 13:41:19 -04:00
commit ea3cdbb2a9
54 changed files with 2396 additions and 977 deletions

1
.gitignore vendored
View File

@ -5,6 +5,7 @@ target
/site/package-lock.json
/site/dist
node_modules
/examples/c_canvas_minimal/build
# Editors
*.swp

673
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,6 @@
[workspace]
members = [
"c",
"canvas",
"demo/android/rust",
"demo/common",
@ -8,9 +9,11 @@ members = [
"examples/canvas_minimal",
"examples/canvas_moire",
"examples/canvas_text",
"examples/lottie_basic",
"geometry",
"gl",
"gpu",
"lottie",
"renderer",
"simd",
"svg",

29
c/Cargo.toml Normal file
View File

@ -0,0 +1,29 @@
[package]
name = "pathfinder_c"
version = "0.1.0"
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
edition = "2018"
[lib]
crate-type = ["staticlib"]
[dependencies]
gl = "0.6"
[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"
[dependencies.pathfinder_simd]
path = "../simd"

View File

@ -0,0 +1,158 @@
// pathfinder/c/include/pathfinder/pathfinder.h
//
// 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.
#ifndef PF_PATHFINDER_H
#define PF_PATHFINDER_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
// Macros
// `gl`
#define PF_GL_VERSION_GL3 0
#define PF_GL_VERSION_GLES3 1
// `gpu`
#define PF_CLEAR_FLAGS_HAS_COLOR 0x1
#define PF_CLEAR_FLAGS_HAS_DEPTH 0x2
#define PF_CLEAR_FLAGS_HAS_STENCIL 0x4
#define PF_CLEAR_FLAGS_HAS_RECT 0x8
// Types
// `canvas`
struct PFCanvas;
typedef struct PFCanvas *PFCanvasRef;
struct PFPath;
typedef struct PFPath *PFPathRef;
// `geometry`
struct PFColorF {
float r, g, b, a;
};
typedef struct PFColorF PFColorF;
struct PFPoint2DF {
float x, y;
};
typedef struct PFPoint2DF PFPoint2DF;
struct PFPoint2DI {
int32_t x, y;
};
typedef struct PFPoint2DI PFPoint2DI;
struct PFRectF {
PFPoint2DF origin, lower_right;
};
typedef struct PFRectF PFRectF;
struct PFRectI {
PFPoint2DI origin, lower_right;
};
typedef struct PFRectI PFRectI;
// `gl`
struct PFGLDevice;
typedef struct PFGLDevice *PFGLDeviceRef;
struct PFGLDestFramebuffer;
typedef struct PFGLDestFramebuffer *PFGLDestFramebufferRef;
typedef const void *(*PFGLFunctionLoader)(const char *data, void *userdata);
struct PFGLRenderer;
typedef struct PFGLRenderer *PFGLRendererRef;
typedef uint32_t PFGLVersion;
// `gpu`
typedef uint8_t PFClearFlags;
struct PFClearParams {
PFColorF color;
float depth;
uint8_t stencil;
PFRectI rect;
PFClearFlags flags;
};
typedef struct PFClearParams PFClearParams;
struct PFResourceLoader;
typedef struct PFResourceLoader *PFResourceLoaderRef;
// `renderer`
struct PFRenderOptions {
uint32_t placeholder;
};
typedef struct PFRenderOptions PFRenderOptions;
struct PFScene;
typedef struct PFScene *PFSceneRef;
struct PFSceneProxy;
typedef struct PFSceneProxy *PFSceneProxyRef;
// Functions
// `canvas`
PFCanvasRef PFCanvasCreate(const PFPoint2DF *size);
void PFCanvasDestroy(PFCanvasRef canvas);
PFSceneRef PFCanvasCreateScene(PFCanvasRef canvas);
void PFCanvasFillRect(PFCanvasRef canvas, const PFRectF *rect);
void PFCanvasStrokeRect(PFCanvasRef canvas, const PFRectF *rect);
void PFCanvasSetLineWidth(PFCanvasRef canvas, float new_line_width);
void PFCanvasFillPath(PFCanvasRef canvas, PFPathRef path);
void PFCanvasStrokePath(PFCanvasRef canvas, PFPathRef path);
PFPathRef PFPathCreate();
void PFPathDestroy(PFPathRef path);
PFPathRef PFPathClone(PFPathRef path);
void PFPathMoveTo(PFPathRef path, const PFPoint2DF *to);
void PFPathLineTo(PFPathRef path, const PFPoint2DF *to);
void PFPathQuadraticCurveTo(PFPathRef path, const PFPoint2DF *ctrl, const PFPoint2DF *to);
void PFPathBezierCurveTo(PFPathRef path,
const PFPoint2DF *ctrl0,
const PFPoint2DF *ctrl1,
const PFPoint2DF *to);
void PFPathClosePath(PFPathRef path);
// `gl`
PFGLDestFramebufferRef PFGLDestFramebufferCreateFullWindow(const PFPoint2DI *window_size);
void PFGLDestFramebufferDestroy(PFGLDestFramebufferRef dest_framebuffer);
PFGLDeviceRef PFGLDeviceCreate(PFGLVersion version, uint32_t default_framebuffer);
void PFGLDeviceDestroy(PFGLDeviceRef device);
void PFGLDeviceClear(PFGLDeviceRef device, const PFClearParams *params);
void PFGLLoadWith(PFGLFunctionLoader loader, void *userdata);
PFGLRendererRef PFGLRendererCreate(PFGLDeviceRef device,
PFResourceLoaderRef resources,
PFGLDestFramebufferRef dest_framebuffer);
void PFGLRendererDestroy(PFGLRendererRef renderer);
/// Returns a borrowed reference to the device.
PFGLDeviceRef PFGLRendererGetDevice(PFGLRendererRef renderer);
void PFSceneProxyBuildAndRenderGL(PFSceneProxyRef scene_proxy,
PFGLRendererRef renderer,
const PFRenderOptions *options);
// `gpu`
PFResourceLoaderRef PFFilesystemResourceLoaderLocate();
void PFResourceLoaderDestroy(PFResourceLoaderRef loader);
// `renderer`
PFSceneProxyRef PFSceneProxyCreateFromSceneAndRayonExecutor(PFSceneRef scene);
void PFSceneProxyDestroy(PFSceneProxyRef scene_proxy);
#ifdef __cplusplus
}
#endif
#endif

360
c/src/lib.rs Normal file
View File

@ -0,0 +1,360 @@
// pathfinder/c/src/lib.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.
//! C bindings to Pathfinder.
use gl;
use pathfinder_canvas::{CanvasRenderingContext2D, Path2D};
use pathfinder_geometry::basic::point::{Point2DF32, Point2DI32};
use pathfinder_geometry::basic::rect::{RectF32, RectI32};
use pathfinder_geometry::color::ColorF;
use pathfinder_gl::{GLDevice, GLVersion};
use pathfinder_gpu::resources::{FilesystemResourceLoader, ResourceLoader};
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 pathfinder_renderer::scene::Scene;
use pathfinder_simd::default::F32x4;
use std::ffi::CString;
use std::os::raw::{c_char, c_void};
// Constants
pub const PF_CLEAR_FLAGS_HAS_COLOR: u8 = 0x1;
pub const PF_CLEAR_FLAGS_HAS_DEPTH: u8 = 0x2;
pub const PF_CLEAR_FLAGS_HAS_STENCIL: u8 = 0x4;
pub const PF_CLEAR_FLAGS_HAS_RECT: u8 = 0x8;
// Types
// `canvas`
pub type PFCanvasRef = *mut CanvasRenderingContext2D;
pub type PFPathRef = *mut Path2D;
// `geometry`
#[repr(C)]
pub struct PFPoint2DF {
pub x: f32,
pub y: f32,
}
#[repr(C)]
pub struct PFPoint2DI {
pub x: i32,
pub y: i32,
}
#[repr(C)]
pub struct PFRectF {
pub origin: PFPoint2DF,
pub lower_right: PFPoint2DF,
}
#[repr(C)]
pub struct PFRectI {
pub origin: PFPoint2DI,
pub lower_right: PFPoint2DI,
}
#[repr(C)]
pub struct PFColorF {
pub r: f32,
pub g: f32,
pub b: f32,
pub a: f32,
}
// `gl`
pub type PFGLDeviceRef = *mut GLDevice;
pub type PFGLVersion = GLVersion;
pub type PFGLFunctionLoader = extern "C" fn(name: *const c_char, userdata: *mut c_void)
-> *const c_void;
// `gpu`
pub type PFGLDestFramebufferRef = *mut DestFramebuffer<GLDevice>;
pub type PFGLRendererRef = *mut Renderer<GLDevice>;
// FIXME(pcwalton): Double-boxing is unfortunate. Remove this when `std::raw::TraitObject` is
// stable?
pub type PFResourceLoaderRef = *mut Box<dyn ResourceLoader>;
#[repr(C)]
pub struct PFClearParams {
pub color: PFColorF,
pub depth: f32,
pub stencil: u8,
pub rect: PFRectI,
pub flags: PFClearFlags,
}
pub type PFClearFlags = u8;
// `renderer`
pub type PFSceneRef = *mut Scene;
pub type PFSceneProxyRef = *mut SceneProxy;
// TODO(pcwalton)
#[repr(C)]
pub struct PFRenderOptions {
pub placeholder: u32,
}
// `canvas`
#[no_mangle]
pub unsafe extern "C" fn PFCanvasCreate(size: *const PFPoint2DF) -> PFCanvasRef {
Box::into_raw(Box::new(CanvasRenderingContext2D::new((*size).to_rust())))
}
#[no_mangle]
pub unsafe extern "C" fn PFCanvasDestroy(canvas: PFCanvasRef) {
drop(Box::from_raw(canvas))
}
/// Consumes the canvas.
#[no_mangle]
pub unsafe extern "C" fn PFCanvasCreateScene(canvas: PFCanvasRef) -> PFSceneRef {
Box::into_raw(Box::new(Box::from_raw(canvas).into_scene()))
}
#[no_mangle]
pub unsafe extern "C" fn PFCanvasFillRect(canvas: PFCanvasRef, rect: *const PFRectF) {
(*canvas).fill_rect((*rect).to_rust())
}
#[no_mangle]
pub unsafe extern "C" fn PFCanvasStrokeRect(canvas: PFCanvasRef, rect: *const PFRectF) {
(*canvas).stroke_rect((*rect).to_rust())
}
#[no_mangle]
pub unsafe extern "C" fn PFCanvasSetLineWidth(canvas: PFCanvasRef, new_line_width: f32) {
(*canvas).set_line_width(new_line_width)
}
/// Consumes the path.
#[no_mangle]
pub unsafe extern "C" fn PFCanvasFillPath(canvas: PFCanvasRef, path: PFPathRef) {
(*canvas).fill_path(*Box::from_raw(path))
}
/// Consumes the path.
#[no_mangle]
pub unsafe extern "C" fn PFCanvasStrokePath(canvas: PFCanvasRef, path: PFPathRef) {
(*canvas).stroke_path(*Box::from_raw(path))
}
#[no_mangle]
pub unsafe extern "C" fn PFPathCreate() -> PFPathRef {
Box::into_raw(Box::new(Path2D::new()))
}
#[no_mangle]
pub unsafe extern "C" fn PFPathDestroy(path: PFPathRef) {
drop(Box::from_raw(path))
}
#[no_mangle]
pub unsafe extern "C" fn PFPathClone(path: PFPathRef) -> PFPathRef {
Box::into_raw(Box::new((*path).clone()))
}
#[no_mangle]
pub unsafe extern "C" fn PFPathMoveTo(path: PFPathRef, to: *const PFPoint2DF) {
(*path).move_to((*to).to_rust())
}
#[no_mangle]
pub unsafe extern "C" fn PFPathLineTo(path: PFPathRef, to: *const PFPoint2DF) {
(*path).line_to((*to).to_rust())
}
#[no_mangle]
pub unsafe extern "C" fn PFPathQuadraticCurveTo(path: PFPathRef,
ctrl: *const PFPoint2DF,
to: *const PFPoint2DF) {
(*path).quadratic_curve_to((*ctrl).to_rust(), (*to).to_rust())
}
#[no_mangle]
pub unsafe extern "C" fn PFPathBezierCurveTo(path: PFPathRef,
ctrl0: *const PFPoint2DF,
ctrl1: *const PFPoint2DF,
to: *const PFPoint2DF) {
(*path).bezier_curve_to((*ctrl0).to_rust(), (*ctrl1).to_rust(), (*to).to_rust())
}
#[no_mangle]
pub unsafe extern "C" fn PFPathClosePath(path: PFPathRef) {
(*path).close_path()
}
// `gl`
#[no_mangle]
pub unsafe extern "C" fn PFFilesystemResourceLoaderLocate() -> PFResourceLoaderRef {
let loader = Box::new(FilesystemResourceLoader::locate());
Box::into_raw(Box::new(loader as Box<dyn ResourceLoader>))
}
#[no_mangle]
pub unsafe extern "C" fn PFGLLoadWith(loader: PFGLFunctionLoader, userdata: *mut c_void) {
gl::load_with(|name| {
let name = CString::new(name).unwrap();
loader(name.as_ptr(), userdata)
});
}
#[no_mangle]
pub unsafe extern "C" fn PFGLDeviceCreate(version: PFGLVersion, default_framebuffer: u32)
-> PFGLDeviceRef {
Box::into_raw(Box::new(GLDevice::new(version, default_framebuffer)))
}
#[no_mangle]
pub unsafe extern "C" fn PFGLDeviceDestroy(device: PFGLDeviceRef) {
drop(Box::from_raw(device))
}
#[no_mangle]
pub unsafe extern "C" fn PFGLDeviceClear(device: PFGLDeviceRef, params: *const PFClearParams) {
(*device).clear(&(*params).to_rust())
}
#[no_mangle]
pub unsafe extern "C" fn PFResourceLoaderDestroy(loader: PFResourceLoaderRef) {
drop(Box::from_raw(loader))
}
// `gpu`
#[no_mangle]
pub unsafe extern "C" fn PFGLDestFramebufferCreateFullWindow(window_size: *const PFPoint2DI)
-> PFGLDestFramebufferRef {
Box::into_raw(Box::new(DestFramebuffer::full_window((*window_size).to_rust())))
}
#[no_mangle]
pub unsafe extern "C" fn PFGLDestFramebufferDestroy(dest_framebuffer: PFGLDestFramebufferRef) {
drop(Box::from_raw(dest_framebuffer))
}
/// Takes ownership of `device` and `dest_framebuffer`, but not `resources`.
#[no_mangle]
pub unsafe extern "C" fn PFGLRendererCreate(device: PFGLDeviceRef,
resources: PFResourceLoaderRef,
dest_framebuffer: PFGLDestFramebufferRef)
-> PFGLRendererRef {
Box::into_raw(Box::new(Renderer::new(*Box::from_raw(device),
&**resources,
*Box::from_raw(dest_framebuffer))))
}
#[no_mangle]
pub unsafe extern "C" fn PFGLRendererDestroy(renderer: PFGLRendererRef) {
drop(Box::from_raw(renderer))
}
#[no_mangle]
pub unsafe extern "C" fn PFGLRendererGetDevice(renderer: PFGLRendererRef) -> PFGLDeviceRef {
&mut (*renderer).device
}
#[no_mangle]
pub unsafe extern "C" fn PFSceneProxyBuildAndRenderGL(scene_proxy: PFSceneProxyRef,
renderer: PFGLRendererRef,
options: *const PFRenderOptions) {
(*scene_proxy).build_and_render(&mut *renderer, (*options).to_rust())
}
// `renderer`
#[no_mangle]
pub unsafe extern "C" fn PFSceneDestroy(scene: PFSceneRef) {
drop(Box::from_raw(scene))
}
#[no_mangle]
pub unsafe extern "C" fn PFSceneProxyCreateFromSceneAndRayonExecutor(scene: PFSceneRef)
-> PFSceneProxyRef {
Box::into_raw(Box::new(SceneProxy::from_scene(*Box::from_raw(scene), RayonExecutor)))
}
#[no_mangle]
pub unsafe extern "C" fn PFSceneProxyDestroy(scene_proxy: PFSceneProxyRef) {
drop(Box::from_raw(scene_proxy))
}
// Helpers for `geometry`
impl PFColorF {
#[inline]
pub fn to_rust(&self) -> ColorF {
ColorF(F32x4::new(self.r, self.g, self.b, self.a))
}
}
impl PFRectF {
#[inline]
pub fn to_rust(&self) -> RectF32 {
RectF32::from_points(self.origin.to_rust(), self.lower_right.to_rust())
}
}
impl PFRectI {
#[inline]
pub fn to_rust(&self) -> RectI32 {
RectI32::from_points(self.origin.to_rust(), self.lower_right.to_rust())
}
}
impl PFPoint2DF {
#[inline]
pub fn to_rust(&self) -> Point2DF32 {
Point2DF32::new(self.x, self.y)
}
}
impl PFPoint2DI {
#[inline]
pub fn to_rust(&self) -> Point2DI32 {
Point2DI32::new(self.x, self.y)
}
}
// Helpers for `gpu`
impl PFClearParams {
pub fn to_rust(&self) -> ClearParams {
ClearParams {
color: if (self.flags & PF_CLEAR_FLAGS_HAS_COLOR) != 0 {
Some(self.color.to_rust())
} else {
None
},
rect: if (self.flags & PF_CLEAR_FLAGS_HAS_RECT) != 0 {
Some(self.rect.to_rust())
} else {
None
},
depth: if (self.flags & PF_CLEAR_FLAGS_HAS_DEPTH) != 0 {
Some(self.depth)
} else {
None
},
stencil: if (self.flags & PF_CLEAR_FLAGS_HAS_STENCIL) != 0 {
Some(self.stencil)
} else {
None
},
}
}
}
// Helpers for `renderer`
impl PFRenderOptions {
pub fn to_rust(&self) -> RenderOptions {
RenderOptions::default()
}
}

View File

@ -4,6 +4,9 @@ version = "0.1.0"
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
edition = "2018"
[lib]
crate-type = ["rlib", "staticlib"]
[dependencies]
font-kit = "0.1"
@ -18,4 +21,4 @@ path = "../text"
[dependencies.skribo]
git = "https://github.com/linebender/skribo.git"
rev = "f759c2d2c188b668bce998179f03b6319835353e"
rev = "a89e9ca99e0d6736ea1b7754517f4df14fd96a2b"

View File

@ -19,8 +19,9 @@ use pathfinder_geometry::basic::rect::RectF32;
use pathfinder_geometry::basic::transform2d::Transform2DF32;
use pathfinder_geometry::color::ColorU;
use pathfinder_geometry::outline::{Contour, Outline};
use pathfinder_geometry::stroke::OutlineStrokeToFill;
use pathfinder_renderer::scene::{Paint, PathObject, Scene};
use pathfinder_geometry::stroke::{LineCap, LineJoin, OutlineStrokeToFill, StrokeStyle};
use pathfinder_renderer::paint::Paint;
use pathfinder_renderer::scene::{PathObject, Scene};
use pathfinder_text::{SceneExt, TextRenderMode};
use skribo::{FontCollection, FontFamily, TextStyle};
use std::default::Default;
@ -94,10 +95,12 @@ impl CanvasRenderingContext2D {
pub fn fill_text(&mut self, string: &str, position: Point2DF32) {
// TODO(pcwalton): Report errors.
let paint_id = self.scene.push_paint(&self.current_state.fill_paint);
let transform = Transform2DF32::from_translation(position).post_mul(&self.current_state
.transform);
drop(self.scene.push_text(string,
&TextStyle { size: self.current_state.font_size },
&self.current_state.font_collection,
&Transform2DF32::from_translation(&position),
&transform,
TextRenderMode::Fill,
HintingOptions::None,
paint_id));
@ -106,11 +109,13 @@ impl CanvasRenderingContext2D {
pub fn stroke_text(&mut self, string: &str, position: Point2DF32) {
// TODO(pcwalton): Report errors.
let paint_id = self.scene.push_paint(&self.current_state.stroke_paint);
let transform = Transform2DF32::from_translation(position).post_mul(&self.current_state
.transform);
drop(self.scene.push_text(string,
&TextStyle { size: self.current_state.font_size },
&self.current_state.font_collection,
&Transform2DF32::from_translation(&position),
TextRenderMode::Stroke(self.current_state.line_width),
&transform,
TextRenderMode::Stroke(self.current_state.stroke_style),
HintingOptions::None,
paint_id));
}
@ -119,7 +124,17 @@ impl CanvasRenderingContext2D {
#[inline]
pub fn set_line_width(&mut self, new_line_width: f32) {
self.current_state.line_width = new_line_width
self.current_state.stroke_style.line_width = new_line_width
}
#[inline]
pub fn set_line_cap(&mut self, new_line_cap: LineCap) {
self.current_state.stroke_style.line_cap = new_line_cap
}
#[inline]
pub fn set_line_join(&mut self, new_line_join: LineJoin) {
self.current_state.stroke_style.line_join = new_line_join
}
#[inline]
@ -139,23 +154,64 @@ impl CanvasRenderingContext2D {
self.current_state.font_size = new_font_size;
}
// Paths
// Drawing paths
#[inline]
pub fn fill_path(&mut self, path: Path2D) {
let paint_id = self.scene.push_paint(&self.current_state.fill_paint);
self.scene.push_path(PathObject::new(path.into_outline(), paint_id, String::new()))
let mut outline = path.into_outline();
outline.transform(&self.current_state.transform);
let paint = self.current_state.resolve_paint(self.current_state.fill_paint);
let paint_id = self.scene.push_paint(&paint);
self.scene.push_path(PathObject::new(outline, paint_id, String::new()))
}
#[inline]
pub fn stroke_path(&mut self, path: Path2D) {
let paint_id = self.scene.push_paint(&self.current_state.stroke_paint);
let stroke_width = f32::max(self.current_state.line_width, HAIRLINE_STROKE_WIDTH);
let mut stroke_to_fill = OutlineStrokeToFill::new(path.into_outline(), stroke_width);
let paint = self.current_state.resolve_paint(self.current_state.stroke_paint);
let paint_id = self.scene.push_paint(&paint);
let mut stroke_style = self.current_state.stroke_style;
stroke_style.line_width = f32::max(stroke_style.line_width, HAIRLINE_STROKE_WIDTH);
let mut stroke_to_fill = OutlineStrokeToFill::new(path.into_outline(), stroke_style);
stroke_to_fill.offset();
stroke_to_fill.outline.transform(&self.current_state.transform);
self.scene.push_path(PathObject::new(stroke_to_fill.outline, paint_id, String::new()))
}
// Transformations
#[inline]
pub fn current_transform(&self) -> Transform2DF32 {
self.current_state.transform
}
#[inline]
pub fn set_current_transform(&mut self, new_transform: &Transform2DF32) {
self.current_state.transform = *new_transform;
}
#[inline]
pub fn reset_transform(&mut self) {
self.current_state.transform = Transform2DF32::default();
}
// Compositing
#[inline]
pub fn global_alpha(&self) -> f32 {
self.current_state.global_alpha
}
#[inline]
pub fn set_global_alpha(&mut self, new_global_alpha: f32) {
self.current_state.global_alpha = new_global_alpha;
}
// The canvas state
#[inline]
pub fn save(&mut self) {
self.saved_states.push(self.current_state.clone());
@ -171,23 +227,32 @@ impl CanvasRenderingContext2D {
#[derive(Clone)]
pub struct State {
transform: Transform2DF32,
font_collection: Arc<FontCollection>,
font_size: f32,
fill_paint: Paint,
stroke_paint: Paint,
line_width: f32,
stroke_style: StrokeStyle,
global_alpha: f32,
}
impl State {
fn default(default_font_collection: Arc<FontCollection>) -> State {
State {
transform: Transform2DF32::default(),
font_collection: default_font_collection,
font_size: DEFAULT_FONT_SIZE,
fill_paint: Paint { color: ColorU::black() },
stroke_paint: Paint { color: ColorU::black() },
line_width: 1.0,
stroke_style: StrokeStyle::default(),
global_alpha: 1.0,
}
}
fn resolve_paint(&self, mut paint: Paint) -> Paint {
paint.color.a = (paint.color.a as f32 * self.global_alpha).round() as u8;
paint
}
}
#[derive(Clone)]
@ -196,7 +261,7 @@ pub struct Path2D {
current_contour: Contour,
}
// TODO(pcwalton): `arc`, `ellipse`
// TODO(pcwalton): `ellipse`
impl Path2D {
#[inline]
pub fn new() -> Path2D {
@ -230,6 +295,11 @@ impl Path2D {
self.current_contour.push_cubic(ctrl0, ctrl1, to);
}
#[inline]
pub fn arc(&mut self, center: Point2DF32, radius: f32, start_angle: f32, end_angle: f32) {
self.current_contour.push_arc(center, radius, start_angle, end_angle);
}
pub fn rect(&mut self, rect: RectF32) {
self.flush_current_contour();
self.current_contour.push_endpoint(rect.origin());

View File

@ -56,7 +56,7 @@ impl Camera {
let scale = i32::min(viewport_size.x(), viewport_size.y()) as f32
* scale_factor_for_view_box(view_box);
let origin = viewport_size.to_f32().scale(0.5) - view_box.size().scale(scale * 0.5);
Camera::TwoD(Transform2DF32::from_scale(&Point2DF32::splat(scale)).post_translate(origin))
Camera::TwoD(Transform2DF32::from_scale(Point2DF32::splat(scale)).post_translate(origin))
}
fn new_3d(mode: Mode, view_box: RectF32, viewport_size: Point2DI32) -> Camera {

View File

@ -11,7 +11,7 @@
//! GPU rendering code specifically for the demo.
use pathfinder_gpu::resources::ResourceLoader;
use pathfinder_gpu::{BufferTarget, Device, VertexAttrType};
use pathfinder_gpu::{BufferTarget, Device, VertexAttrClass, VertexAttrDescriptor, VertexAttrType};
pub struct GroundProgram<D>
where
@ -67,7 +67,14 @@ where
device.bind_vertex_array(&vertex_array);
device.use_program(&ground_program.program);
device.bind_buffer(quad_vertex_positions_buffer, BufferTarget::Vertex);
device.configure_float_vertex_attr(&position_attr, 2, VertexAttrType::U8, false, 0, 0, 0);
device.configure_vertex_attr(&position_attr, &VertexAttrDescriptor {
size: 2,
class: VertexAttrClass::Float,
attr_type: VertexAttrType::U8,
stride: 0,
offset: 0,
divisor: 0,
});
GroundVertexArray { vertex_array }
}

View File

@ -143,7 +143,7 @@ impl<W> DemoApp<W> where W: Window {
viewport.size());
let camera = Camera::new(options.mode, scene_metadata.view_box, viewport.size());
let scene_proxy = SceneProxy::new(built_svg.scene, executor);
let scene_proxy = SceneProxy::from_scene(built_svg.scene, executor);
let ground_program = GroundProgram::new(&renderer.device, resources);
let ground_vertex_array = GroundVertexArray::new(&renderer.device,

View File

@ -0,0 +1,47 @@
OBJ_DIR?=build
TARGET_DIR?=build
SRC_DIR?=.
RUST_TARGET_DIR?=../../target
RUST_SRC_DIR?=../../c
RUSTFLAGS?=-C target-cpu=native
CFLAGS?=-Wall -g -I../../c/include
LIBS?=-lpathfinder_c
MKDIR?=mkdir -p
RM?=rm
CARGO?=cargo
UNAME=$(shell uname -s)
ifeq ($(UNAME),Darwin)
# FIXME(pcwalton): Don't link against HarfBuzz!!
LIBS+=-framework OpenGL -framework CoreFoundation -framework CoreGraphics -framework CoreText
LIBS+=-lharfbuzz
else
LIBS+=-lGL
endif
ifeq ($(DEBUG),)
CFLAGS+=-O2
LDFLAGS?=-L$(RUST_TARGET_DIR)/release
CARGOFLAGS?=--release
else
CFLAGS+=-O0
LDFLAGS?=-L$(RUST_TARGET_DIR)/debug
CARGOFLAGS?=
endif
all: $(TARGET_DIR)/c_canvas_minimal
.PHONY: clean rustlib
$(TARGET_DIR)/c_canvas_minimal: $(OBJ_DIR)/c_canvas_minimal.o rustlib
$(MKDIR) $(TARGET_DIR) && $(CC) $(LDFLAGS) $(LIBS) `sdl2-config --libs` -o $@ $<
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c
$(MKDIR) $(OBJ_DIR) && $(CC) -c $(CFLAGS) `sdl2-config --cflags` -o $@ $<
rustlib:
cd $(RUST_SRC_DIR) && RUSTFLAGS="$(RUSTFLAGS)" $(CARGO) build $(CARGOFLAGS)
clean:
$(RM) -rf $(TARGET_DIR) && $(RM) -rf $(OBJ_DIR)

View File

@ -0,0 +1,117 @@
// pathfinder/examples/c_canvas_minimal/c_canvas_minimal.c
//
// 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.
#include <SDL2/SDL.h>
#include <SDL2/SDL_opengl.h>
#include <pathfinder/pathfinder.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
static const void *LoadGLFunction(const char *name, void *userdata);
static void SDLFailed(const char *msg);
int main(int argc, const char **argv) {
// Set up SDL2.
if (SDL_Init(SDL_INIT_EVENTS | SDL_INIT_VIDEO) != 0)
SDLFailed("Failed to initialize SDL");
// Make sure we have at least a GL 3.0 context. Pathfinder requires this.
if (SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3) != 0)
SDLFailed("Failed to set GL major version");
if (SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2) != 0)
SDLFailed("Failed to set GL minor version");
if (SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE) != 0)
SDLFailed("Failed to set GL profile");
if (SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1) != 0)
SDLFailed("Failed to make GL context double-buffered");
// Open a window.
SDL_Window *window = SDL_CreateWindow("Minimal canvas example (C API)",
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
640,
480,
SDL_WINDOW_OPENGL);
if (window == NULL)
SDLFailed("Failed to create SDL window");
// Create the GL context, and make it current.
SDL_GLContext gl_context = SDL_GL_CreateContext(window);
if (gl_context == NULL)
SDLFailed("Failed to create GL context");
// Put the window on screen.
SDL_ShowWindow(window);
// Create a Pathfinder renderer.
PFGLLoadWith(LoadGLFunction, NULL);
PFGLDestFramebufferRef dest_framebuffer =
PFGLDestFramebufferCreateFullWindow(&(PFPoint2DI){640, 480});
PFGLRendererRef renderer = PFGLRendererCreate(PFGLDeviceCreate(PF_GL_VERSION_GL3, 0),
PFFilesystemResourceLoaderLocate(),
dest_framebuffer);
// Clear to white.
PFGLDeviceClear(PFGLRendererGetDevice(renderer), &(PFClearParams){
(PFColorF){1.0, 1.0, 1.0, 1.0}, 0.0, 0, {0}, PF_CLEAR_FLAGS_HAS_COLOR
});
// Make a canvas. We're going to draw a house.
PFCanvasRef canvas = PFCanvasCreate(&(PFPoint2DF){640.0f, 480.0f});
// Set line width.
PFCanvasSetLineWidth(canvas, 10.0f);
// Draw walls.
PFCanvasStrokeRect(canvas, &(PFRectF){{75.0f, 140.0f}, {225.0f, 250.0f}});
// Draw door.
PFCanvasFillRect(canvas, &(PFRectF){{130.0f, 190.0f}, {170.0f, 250.0f}});
// Draw roof.
PFPathRef path = PFPathCreate();
PFPathMoveTo(path, &(PFPoint2DF){50.0, 140.0});
PFPathLineTo(path, &(PFPoint2DF){150.0, 60.0});
PFPathLineTo(path, &(PFPoint2DF){250.0, 140.0});
PFPathClosePath(path);
PFCanvasStrokePath(canvas, path);
// Render the canvas to screen.
PFSceneRef scene = PFCanvasCreateScene(canvas);
PFSceneProxyRef scene_proxy = PFSceneProxyCreateFromSceneAndRayonExecutor(scene);
PFSceneProxyBuildAndRenderGL(scene_proxy, renderer, &(PFRenderOptions){0});
SDL_GL_SwapWindow(window);
// Wait for a keypress.
while (true) {
SDL_Event event;
if (SDL_WaitEvent(&event) == 0)
SDLFailed("Failed to get SDL event");
if (event.type == SDL_QUIT ||
(event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE)) {
break;
}
}
// Finish up.
SDL_Quit();
return 0;
}
static const void *LoadGLFunction(const char *name, void *userdata) {
return SDL_GL_GetProcAddress(name);
}
static void SDLFailed(const char *msg) {
fprintf(stderr, "%s: %s\n", msg, SDL_GetError());
exit(EXIT_FAILURE);
}

View File

@ -74,7 +74,7 @@ fn main() {
canvas.stroke_path(path);
// Render the canvas to screen.
let scene = SceneProxy::new(canvas.into_scene(), RayonExecutor);
let scene = SceneProxy::from_scene(canvas.into_scene(), RayonExecutor);
scene.build_and_render(&mut renderer, RenderOptions::default());
window.gl_swap_window();

View File

@ -21,6 +21,7 @@ use pathfinder_renderer::options::RenderOptions;
use sdl2::event::Event;
use sdl2::keyboard::Keycode;
use sdl2::video::GLProfile;
use std::f32::consts::PI;
use std::f32;
const VELOCITY: f32 = 0.02;
@ -84,7 +85,7 @@ fn main() {
struct MoireRenderer {
renderer: Renderer<GLDevice>,
scene: Option<SceneProxy>,
scene: SceneProxy,
frame: i32,
window_size: Point2DI32,
drawable_size: Point2DI32,
@ -97,7 +98,7 @@ impl MoireRenderer {
-> MoireRenderer {
MoireRenderer {
renderer,
scene: None,
scene: SceneProxy::new(RayonExecutor),
frame: 0,
window_size,
drawable_size,
@ -130,47 +131,28 @@ impl MoireRenderer {
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()));
canvas.set_global_alpha(0.75);
// 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());
// Build and render scene.
self.scene.replace_scene(canvas.into_scene());
self.scene.build_and_render(&mut self.renderer, RenderOptions::default());
self.frame += 1;
}
fn draw_circles(&self, canvas: &mut CanvasRenderingContext2D, center: Point2DF32) {
let center = center.scale(self.device_pixel_ratio);
for index in 0..CIRCLE_COUNT {
let radius = (index + 1) as f32 * CIRCLE_SPACING * self.device_pixel_ratio;
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);
path.arc(center, radius, 0.0, PI * 2.0);
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]);

View File

@ -8,9 +8,8 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use pathfinder_canvas::{CanvasRenderingContext2D, Path2D};
use pathfinder_canvas::CanvasRenderingContext2D;
use pathfinder_geometry::basic::point::{Point2DF32, Point2DI32};
use pathfinder_geometry::basic::rect::RectF32;
use pathfinder_geometry::color::ColorF;
use pathfinder_gl::{GLDevice, GLVersion};
use pathfinder_gpu::resources::FilesystemResourceLoader;
@ -62,7 +61,7 @@ fn main() {
canvas.stroke_text("Goodbye Pathfinder!", Point2DF32::new(32.0, 96.0));
// Render the canvas to screen.
let scene = SceneProxy::new(canvas.into_scene(), RayonExecutor);
let scene = SceneProxy::from_scene(canvas.into_scene(), RayonExecutor);
scene.build_and_render(&mut renderer, RenderOptions::default());
window.gl_swap_window();

View File

@ -0,0 +1,10 @@
[package]
name = "lottie_basic"
version = "0.1.0"
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
edition = "2018"
[dependencies]
[dependencies.pathfinder_lottie]
path = "../../lottie"

View File

@ -0,0 +1,23 @@
// pathfinder/examples/lottie_basic/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.
//! Experimental example for reading Lottie animations. This is very incomplete.
use pathfinder_lottie::Lottie;
use std::env;
use std::fs::File;
use std::io::BufReader;
fn main() {
let path = env::args().skip(1).next().unwrap();
let file = BufReader::new(File::open(path).unwrap());
let lottie = Lottie::from_reader(file).unwrap();
println!("{:#?}", lottie);
}

View File

@ -11,6 +11,7 @@
//! Line segment types, optimized with SIMD.
use crate::basic::point::Point2DF32;
use crate::basic::transform2d::Matrix2x2F32;
use crate::util;
use pathfinder_simd::default::F32x4;
use std::ops::{Add, Sub};
@ -20,7 +21,7 @@ pub struct LineSegmentF32(pub F32x4);
impl LineSegmentF32 {
#[inline]
pub fn new(from: &Point2DF32, to: &Point2DF32) -> LineSegmentF32 {
pub fn new(from: Point2DF32, to: Point2DF32) -> LineSegmentF32 {
LineSegmentF32(from.0.concat_xy_xy(to.0))
}
@ -227,15 +228,12 @@ impl LineSegmentF32 {
// http://www.cs.swan.ac.uk/~cssimon/line_intersection.html
pub fn intersection_t(&self, other: &LineSegmentF32) -> Option<f32> {
let d0d1 = self.vector().0.concat_xy_xy(other.vector().0);
let offset = other.from() - self.from();
let factors = d0d1.concat_wz_yx(offset.0);
let terms = d0d1 * factors;
let denom = terms[0] - terms[1];
if f32::abs(denom) < EPSILON {
let p0p1 = self.vector();
let matrix = Matrix2x2F32(other.vector().0.concat_xy_xy((-p0p1).0));
if f32::abs(matrix.det()) < EPSILON {
return None;
}
return Some((terms[3] - terms[2]) / denom);
return Some(matrix.inverse().transform_point(self.from() - other.from()).y());
const EPSILON: f32 = 0.0001;
}

View File

@ -10,6 +10,7 @@
//! 2D affine transforms.
use crate::basic::line_segment::LineSegmentF32;
use crate::basic::point::Point2DF32;
use crate::basic::rect::RectF32;
use crate::basic::transform3d::Transform3DF32;
@ -24,13 +25,13 @@ pub struct Matrix2x2F32(pub F32x4);
impl Default for Matrix2x2F32 {
#[inline]
fn default() -> Matrix2x2F32 {
Self::from_scale(&Point2DF32::splat(1.0))
Self::from_scale(Point2DF32::splat(1.0))
}
}
impl Matrix2x2F32 {
#[inline]
pub fn from_scale(scale: &Point2DF32) -> Matrix2x2F32 {
pub fn from_scale(scale: Point2DF32) -> Matrix2x2F32 {
Matrix2x2F32(F32x4::new(scale.x(), 0.0, 0.0, scale.y()))
}
@ -66,7 +67,7 @@ impl Matrix2x2F32 {
}
#[inline]
pub fn transform_point(&self, point: &Point2DF32) -> Point2DF32 {
pub fn transform_point(&self, point: Point2DF32) -> Point2DF32 {
let halves = self.0 * point.0.xxyy();
Point2DF32(halves + halves.zwzw())
}
@ -118,13 +119,13 @@ pub struct Transform2DF32 {
impl Default for Transform2DF32 {
#[inline]
fn default() -> Transform2DF32 {
Self::from_scale(&Point2DF32::splat(1.0))
Self::from_scale(Point2DF32::splat(1.0))
}
}
impl Transform2DF32 {
#[inline]
pub fn from_scale(scale: &Point2DF32) -> Transform2DF32 {
pub fn from_scale(scale: Point2DF32) -> Transform2DF32 {
Transform2DF32 {
matrix: Matrix2x2F32::from_scale(scale),
vector: Point2DF32::default(),
@ -140,11 +141,8 @@ impl Transform2DF32 {
}
#[inline]
pub fn from_translation(vector: &Point2DF32) -> Transform2DF32 {
Transform2DF32 {
matrix: Matrix2x2F32::default(),
vector: *vector,
}
pub fn from_translation(vector: Point2DF32) -> Transform2DF32 {
Transform2DF32 { matrix: Matrix2x2F32::default(), vector }
}
#[inline]
@ -154,10 +152,8 @@ impl Transform2DF32 {
translation: Point2DF32,
) -> Transform2DF32 {
let rotation = Transform2DF32::from_rotation(theta);
let translation = Transform2DF32::from_translation(&translation);
Transform2DF32::from_scale(&scale)
.post_mul(&rotation)
.post_mul(&translation)
let translation = Transform2DF32::from_translation(translation);
Transform2DF32::from_scale(scale).post_mul(&rotation).post_mul(&translation)
}
#[inline]
@ -169,16 +165,22 @@ impl Transform2DF32 {
}
#[inline]
pub fn transform_point(&self, point: &Point2DF32) -> Point2DF32 {
pub fn transform_point(&self, point: Point2DF32) -> Point2DF32 {
self.matrix.transform_point(point) + self.vector
}
#[inline]
pub fn transform_line_segment(&self, line_segment: &LineSegmentF32) -> LineSegmentF32 {
LineSegmentF32::new(self.transform_point(line_segment.from()),
self.transform_point(line_segment.to()))
}
#[inline]
pub fn transform_rect(&self, rect: &RectF32) -> RectF32 {
let upper_left = self.transform_point(&rect.origin());
let upper_right = self.transform_point(&rect.upper_right());
let lower_left = self.transform_point(&rect.lower_left());
let lower_right = self.transform_point(&rect.lower_right());
let upper_left = self.transform_point(rect.origin());
let upper_right = self.transform_point(rect.upper_right());
let lower_left = self.transform_point(rect.lower_left());
let lower_right = self.transform_point(rect.lower_right());
let min_point = upper_left.min(upper_right).min(lower_left).min(lower_right);
let max_point = upper_left.max(upper_right).max(lower_left).max(lower_right);
RectF32::from_points(min_point, max_point)
@ -187,7 +189,7 @@ impl Transform2DF32 {
#[inline]
pub fn post_mul(&self, other: &Transform2DF32) -> Transform2DF32 {
let matrix = self.matrix.post_mul(&other.matrix);
let vector = other.transform_point(&self.vector);
let vector = other.transform_point(self.vector);
Transform2DF32 { matrix, vector }
}
@ -243,7 +245,7 @@ impl Transform2DF32 {
#[inline]
pub fn post_translate(&self, vector: Point2DF32) -> Transform2DF32 {
self.post_mul(&Transform2DF32::from_translation(&vector))
self.post_mul(&Transform2DF32::from_translation(vector))
}
#[inline]
@ -253,7 +255,7 @@ impl Transform2DF32 {
#[inline]
pub fn post_scale(&self, scale: Point2DF32) -> Transform2DF32 {
self.post_mul(&Transform2DF32::from_scale(&scale))
self.post_mul(&Transform2DF32::from_scale(scale))
}
/// Returns the translation part of this matrix.
@ -303,18 +305,18 @@ where
if !segment.is_none() {
segment
.baseline
.set_from(&self.transform.transform_point(&segment.baseline.from()));
.set_from(&self.transform.transform_point(segment.baseline.from()));
segment
.baseline
.set_to(&self.transform.transform_point(&segment.baseline.to()));
.set_to(&self.transform.transform_point(segment.baseline.to()));
if !segment.is_line() {
segment
.ctrl
.set_from(&self.transform.transform_point(&segment.ctrl.from()));
.set_from(&self.transform.transform_point(segment.ctrl.from()));
if !segment.is_quadratic() {
segment
.ctrl
.set_to(&self.transform.transform_point(&segment.ctrl.to()));
.set_to(&self.transform.transform_point(segment.ctrl.to()));
}
}
}

View File

@ -325,7 +325,7 @@ impl ContourPolygonClipper {
Some(prev) => *prev,
};
for &next in &clip_polygon {
self.clip_against(Edge(LineSegmentF32::new(&prev, &next)));
self.clip_against(Edge(LineSegmentF32::new(prev, next)));
prev = next;
}

View File

@ -19,9 +19,12 @@ use crate::clip::{self, ContourPolygonClipper, ContourRectClipper};
use crate::dilation::ContourDilator;
use crate::orientation::Orientation;
use crate::segment::{Segment, SegmentFlags, SegmentKind};
use std::f32::consts::{FRAC_PI_2, PI};
use std::fmt::{self, Debug, Formatter};
use std::mem;
const TWO_PI: f32 = PI * 2.0;
#[derive(Clone)]
pub struct Outline {
pub(crate) contours: Vec<Contour>,
@ -126,6 +129,10 @@ impl Outline {
}
pub fn transform(&mut self, transform: &Transform2DF32) {
if transform.is_identity() {
return;
}
let mut new_bounds = None;
for contour in &mut self.contours {
contour.transform(transform);
@ -262,6 +269,11 @@ impl Contour {
self.points[index as usize]
}
#[inline]
pub(crate) fn position_of_last(&self, index: u32) -> Point2DF32 {
self.points[self.points.len() - index as usize]
}
#[inline]
pub(crate) fn last_position(&self) -> Option<Point2DF32> {
self.points.last().cloned()
@ -292,7 +304,10 @@ impl Contour {
// TODO(pcwalton): SIMD.
#[inline]
pub(crate) fn push_point(&mut self, point: Point2DF32, flags: PointFlags, update_bounds: bool) {
pub(crate) fn push_point(&mut self,
point: Point2DF32,
flags: PointFlags,
update_bounds: bool) {
debug_assert!(!point.x().is_nan() && !point.y().is_nan());
if update_bounds {
@ -354,6 +369,45 @@ impl Contour {
self.push_point(segment.baseline.to(), PointFlags::empty(), update_bounds);
}
pub fn push_arc(&mut self,
center: Point2DF32,
radius: f32,
mut start_angle: f32,
mut end_angle: f32) {
start_angle %= TWO_PI;
end_angle %= TWO_PI;
if end_angle <= start_angle {
end_angle += TWO_PI;
}
let scale = Transform2DF32::from_scale(Point2DF32::splat(radius));
let translation = Transform2DF32::from_translation(center);
let (mut angle, mut first_segment) = (start_angle, true);
while angle < end_angle {
let sweep_angle = f32::min(FRAC_PI_2, end_angle - angle);
let mut segment = Segment::arc(sweep_angle);
let rotation = Transform2DF32::from_rotation(sweep_angle * 0.5 + angle);
segment = segment.transform(&scale.post_mul(&rotation).post_mul(&translation));
debug!("angle={} start_angle={} end_angle={} sweep_angle={} segment={:?}",
angle,
start_angle,
end_angle,
sweep_angle,
segment);
if first_segment {
self.push_full_segment(&segment, true);
first_segment = false;
} else {
self.push_segment(segment, true);
}
angle += sweep_angle;
}
}
#[inline]
pub fn segment_after(&self, point_index: u32) -> Segment {
debug_assert!(self.point_is_endpoint(point_index));
@ -388,8 +442,8 @@ impl Contour {
pub fn hull_segment_after(&self, prev_point_index: u32) -> LineSegmentF32 {
let next_point_index = self.next_point_index_of(prev_point_index);
LineSegmentF32::new(
&self.points[prev_point_index as usize],
&self.points[next_point_index as usize],
self.points[prev_point_index as usize],
self.points[next_point_index as usize],
)
}
@ -454,8 +508,12 @@ impl Contour {
}
pub fn transform(&mut self, transform: &Transform2DF32) {
if transform.is_identity() {
return;
}
for (point_index, point) in self.points.iter_mut().enumerate() {
*point = transform.transform_point(point);
*point = transform.transform_point(*point);
union_rect(&mut self.bounds, *point, point_index == 0);
}
}
@ -522,8 +580,8 @@ impl Contour {
point_index
};
let baseline = LineSegmentF32::new(
&contour.points[last_endpoint_index as usize],
&contour.points[position_index as usize],
contour.points[last_endpoint_index as usize],
contour.points[position_index as usize],
);
let point_count = point_index - last_endpoint_index + 1;
if point_count == 3 {
@ -531,13 +589,13 @@ impl Contour {
let ctrl_position = &contour.points[ctrl_point_index];
handle_cubic(
self,
Segment::quadratic(&baseline, &ctrl_position).to_cubic(),
Segment::quadratic(&baseline, *ctrl_position).to_cubic(),
);
} else if point_count == 4 {
let first_ctrl_point_index = last_endpoint_index as usize + 1;
let ctrl_position_0 = &contour.points[first_ctrl_point_index + 0];
let ctrl_position_1 = &contour.points[first_ctrl_point_index + 1];
let ctrl = LineSegmentF32::new(&ctrl_position_0, &ctrl_position_1);
let ctrl = LineSegmentF32::new(*ctrl_position_0, *ctrl_position_1);
handle_cubic(self, Segment::cubic(&baseline, &ctrl));
}
@ -723,24 +781,21 @@ impl<'a> Iterator for ContourIter<'a> {
if self.index == contour.len() {
let point1 = contour.position_of(0);
self.index += 1;
return Some(Segment::line(&LineSegmentF32::new(&point0, &point1)));
return Some(Segment::line(&LineSegmentF32::new(point0, point1)));
}
let point1_index = self.index;
self.index += 1;
let point1 = contour.position_of(point1_index);
if contour.point_is_endpoint(point1_index) {
return Some(Segment::line(&LineSegmentF32::new(&point0, &point1)));
return Some(Segment::line(&LineSegmentF32::new(point0, point1)));
}
let point2_index = self.index;
let point2 = contour.position_of(point2_index);
self.index += 1;
if contour.point_is_endpoint(point2_index) {
return Some(Segment::quadratic(
&LineSegmentF32::new(&point0, &point2),
&point1,
));
return Some(Segment::quadratic(&LineSegmentF32::new(point0, point2), point1));
}
let point3_index = self.index;
@ -748,8 +803,8 @@ impl<'a> Iterator for ContourIter<'a> {
self.index += 1;
debug_assert!(contour.point_is_endpoint(point3_index));
return Some(Segment::cubic(
&LineSegmentF32::new(&point0, &point3),
&LineSegmentF32::new(&point1, &point2),
&LineSegmentF32::new(point0, point3),
&LineSegmentF32::new(point1, point2),
));
}
}

View File

@ -12,8 +12,10 @@
use crate::basic::line_segment::LineSegmentF32;
use crate::basic::point::Point2DF32;
use crate::basic::transform2d::Transform2DF32;
use crate::util::{self, EPSILON};
use pathfinder_simd::default::F32x4;
use std::f32::consts::SQRT_2;
const MAX_NEWTON_ITERATIONS: u32 = 32;
@ -47,10 +49,10 @@ impl Segment {
}
#[inline]
pub fn quadratic(baseline: &LineSegmentF32, ctrl: &Point2DF32) -> Segment {
pub fn quadratic(baseline: &LineSegmentF32, ctrl: Point2DF32) -> Segment {
Segment {
baseline: *baseline,
ctrl: LineSegmentF32::new(ctrl, &Point2DF32::default()),
ctrl: LineSegmentF32::new(ctrl, Point2DF32::default()),
kind: SegmentKind::Quadratic,
flags: SegmentFlags::empty(),
}
@ -66,6 +68,24 @@ impl Segment {
}
}
/// Approximates an unit-length arc with a cubic Bézier curve.
///
/// The maximum supported `sweep_angle` is π/2 (i.e. 90°).
pub fn arc(sweep_angle: f32) -> Segment {
// Aleksas Riškus, "Approximation of a Cubic Bézier Curve by Circular Arcs and Vice Versa"
// 2006.
//
// https://pdfs.semanticscholar.org/1639/0db1a470bd13fe428e0896671a9a5745070a.pdf
let phi = 0.5 * sweep_angle;
let p0 = Point2DF32::new(f32::cos(phi), f32::sin(phi));
let p3 = p0.scale_xy(Point2DF32::new(1.0, -1.0));
let p1 = p0 - p3.yx().scale(K);
let p2 = p3 + p0.yx().scale(K);
return Segment::cubic(&LineSegmentF32::new(p3, p0), &LineSegmentF32::new(p2, p1));
const K: f32 = 4.0 / 3.0 * (SQRT_2 - 1.0);
}
#[inline]
pub fn as_line_segment(&self) -> LineSegmentF32 {
debug_assert!(self.is_line());
@ -109,7 +129,7 @@ impl Segment {
let mut new_segment = *self;
let p1_2 = self.ctrl.from() + self.ctrl.from();
new_segment.ctrl =
LineSegmentF32::new(&(self.baseline.from() + p1_2), &(p1_2 + self.baseline.to()))
LineSegmentF32::new(self.baseline.from() + p1_2, p1_2 + self.baseline.to())
.scale(1.0 / 3.0);
new_segment.kind = SegmentKind::Cubic;
new_segment
@ -176,6 +196,16 @@ impl Segment {
self.to_cubic().as_cubic_segment().sample(t)
}
}
#[inline]
pub fn transform(self, transform: &Transform2DF32) -> Segment {
Segment {
baseline: transform.transform_line_segment(&self.baseline),
ctrl: transform.transform_line_segment(&self.ctrl),
kind: self.kind,
flags: self.flags,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
@ -215,16 +245,16 @@ impl<'s> CubicSegment<'s> {
let (baseline0, ctrl0, baseline1, ctrl1);
if t <= 0.0 {
let from = &self.0.baseline.from();
baseline0 = LineSegmentF32::new(from, from);
ctrl0 = LineSegmentF32::new(from, from);
baseline0 = LineSegmentF32::new(*from, *from);
ctrl0 = LineSegmentF32::new(*from, *from);
baseline1 = self.0.baseline;
ctrl1 = self.0.ctrl;
} else if t >= 1.0 {
let to = &self.0.baseline.to();
baseline0 = self.0.baseline;
ctrl0 = self.0.ctrl;
baseline1 = LineSegmentF32::new(to, to);
ctrl1 = LineSegmentF32::new(to, to);
baseline1 = LineSegmentF32::new(*to, *to);
ctrl1 = LineSegmentF32::new(*to, *to);
} else {
let tttt = F32x4::splat(t);

View File

@ -11,96 +11,183 @@
//! Utilities for converting path strokes to fills.
use crate::basic::line_segment::LineSegmentF32;
use crate::basic::point::Point2DF32;
use crate::basic::rect::RectF32;
use crate::outline::{Contour, Outline};
use crate::segment::Segment;
use std::f32::consts::FRAC_PI_2;
use std::f32;
use std::mem;
const TOLERANCE: f32 = 0.01;
pub struct OutlineStrokeToFill {
pub outline: Outline,
pub stroke_width: f32,
pub style: StrokeStyle,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct StrokeStyle {
pub line_width: f32,
pub line_cap: LineCap,
pub line_join: LineJoin,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum LineCap {
Butt,
Square,
Round,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum LineJoin {
Miter,
Bevel,
}
impl OutlineStrokeToFill {
#[inline]
pub fn new(outline: Outline, stroke_width: f32) -> OutlineStrokeToFill {
OutlineStrokeToFill {
outline,
stroke_width,
}
pub fn new(outline: Outline, style: StrokeStyle) -> OutlineStrokeToFill {
OutlineStrokeToFill { outline, style }
}
#[inline]
pub fn offset(&mut self) {
let mut new_bounds = None;
for contour in &mut self.outline.contours {
let input = mem::replace(contour, Contour::new());
let mut contour_stroke_to_fill =
ContourStrokeToFill::new(input, Contour::new(), self.stroke_width * 0.5);
contour_stroke_to_fill.offset_forward();
contour_stroke_to_fill.offset_backward();
*contour = contour_stroke_to_fill.output;
contour.update_bounds(&mut new_bounds);
let mut new_contours = vec![];
for input in mem::replace(&mut self.outline.contours, vec![]) {
let closed = input.closed;
let mut stroker = ContourStrokeToFill::new(input,
Contour::new(),
self.style.line_width * 0.5,
self.style.line_join);
stroker.offset_forward();
if closed {
self.push_stroked_contour(&mut new_contours, stroker.output, true);
stroker = ContourStrokeToFill::new(stroker.input,
Contour::new(),
self.style.line_width * 0.5,
self.style.line_join);
} else {
self.add_cap(&mut stroker.output);
}
stroker.offset_backward();
if !closed {
self.add_cap(&mut stroker.output);
}
self.push_stroked_contour(&mut new_contours, stroker.output, closed);
}
let mut new_bounds = None;
new_contours.iter().for_each(|contour| contour.update_bounds(&mut new_bounds));
self.outline.contours = new_contours;
self.outline.bounds = new_bounds.unwrap_or_else(|| RectF32::default());
}
fn push_stroked_contour(&mut self,
new_contours: &mut Vec<Contour>,
mut contour: Contour,
closed: bool) {
// Add join if necessary.
if closed && contour.needs_join(self.style.line_join) {
let (p1, p0) = (contour.position_of(1), contour.position_of(0));
contour.add_join(self.style.line_join, &LineSegmentF32::new(p0, p1));
}
contour.closed = true;
new_contours.push(contour);
}
fn add_cap(&mut self, contour: &mut Contour) {
if self.style.line_cap == LineCap::Butt || contour.len() < 2 {
return
}
let width = self.style.line_width;
let (p0, p1) = (contour.position_of_last(2), contour.position_of_last(1));
let gradient = (p1 - p0).normalize();
match self.style.line_cap {
LineCap::Butt => unreachable!(),
LineCap::Square => {
let offset = gradient.scale(width * 0.5);
let p2 = p1 + offset;
let p3 = p2 + gradient.yx().scale_xy(Point2DF32::new(-width, width));
let p4 = p3 - offset;
contour.push_endpoint(p2);
contour.push_endpoint(p3);
contour.push_endpoint(p4);
}
LineCap::Round => {
// FIXME(pcwalton): Should we really be using angles here at all?
let offset = gradient.yx().scale_xy(Point2DF32::new(-width * 0.5, width * 0.5));
let angle = f32::atan2(gradient.y(), gradient.x());
contour.push_arc(p1 + offset, width * 0.5, angle - FRAC_PI_2, angle + FRAC_PI_2);
}
}
}
}
struct ContourStrokeToFill {
input: Contour,
output: Contour,
radius: f32,
join: LineJoin,
}
impl ContourStrokeToFill {
#[inline]
fn new(input: Contour, output: Contour, radius: f32) -> ContourStrokeToFill {
ContourStrokeToFill {
input,
output,
radius,
}
fn new(input: Contour, output: Contour, radius: f32, join: LineJoin) -> ContourStrokeToFill {
ContourStrokeToFill { input, output, radius, join }
}
fn offset_forward(&mut self) {
for segment in self.input.iter() {
segment.offset(self.radius, &mut self.output);
for (segment_index, segment) in self.input.iter().enumerate() {
// FIXME(pcwalton): We negate the radius here so that round end caps can be drawn
// clockwise. Of course, we should just implement anticlockwise arcs to begin with...
let join = if segment_index == 0 { LineJoin::Bevel } else { self.join };
segment.offset(-self.radius, join, &mut self.output);
}
}
fn offset_backward(&mut self) {
// FIXME(pcwalton)
let mut segments: Vec<_> = self
.input
.iter()
.map(|segment| segment.reversed())
.collect();
segments.reverse();
for segment in &segments {
segment.offset(self.radius, &mut self.output);
for (segment_index, segment) in segments.iter().enumerate() {
// FIXME(pcwalton): We negate the radius here so that round end caps can be drawn
// clockwise. Of course, we should just implement anticlockwise arcs to begin with...
let join = if segment_index == 0 { LineJoin::Bevel } else { self.join };
segment.offset(-self.radius, join, &mut self.output);
}
}
}
trait Offset {
fn offset(&self, distance: f32, contour: &mut Contour);
fn offset(&self, distance: f32, join: LineJoin, contour: &mut Contour);
fn add_to_contour(&self, join: LineJoin, contour: &mut Contour);
fn offset_once(&self, distance: f32) -> Self;
fn error_is_within_tolerance(&self, other: &Segment, distance: f32) -> bool;
}
impl Offset for Segment {
fn offset(&self, distance: f32, contour: &mut Contour) {
fn offset(&self, distance: f32, join: LineJoin, contour: &mut Contour) {
if self.baseline.square_length() < TOLERANCE * TOLERANCE {
contour.push_full_segment(self, true);
self.add_to_contour(join, contour);
return;
}
let candidate = self.offset_once(distance);
if self.error_is_within_tolerance(&candidate, distance) {
contour.push_full_segment(&candidate, true);
candidate.add_to_contour(join, contour);
return;
}
@ -108,8 +195,27 @@ impl Offset for Segment {
debug!("... PRE-SPLIT: {:?}", self);
let (before, after) = self.split(0.5);
debug!("... AFTER-SPLIT: {:?} {:?}", before, after);
before.offset(distance, contour);
after.offset(distance, contour);
before.offset(distance, join, contour);
after.offset(distance, join, contour);
}
fn add_to_contour(&self, join: LineJoin, contour: &mut Contour) {
// Add join if necessary.
if contour.needs_join(join) {
let p3 = self.baseline.from();
let p4 = if self.is_line() {
self.baseline.to()
} else {
// NB: If you change the representation of quadratic curves, you will need to
// change this.
self.ctrl.from()
};
contour.add_join(join, &LineSegmentF32::new(p4, p3));
}
// Push segment.
contour.push_full_segment(self, true);
}
fn offset_once(&self, distance: f32) -> Segment {
@ -118,51 +224,51 @@ impl Offset for Segment {
}
if self.is_quadratic() {
let mut segment_0 = LineSegmentF32::new(&self.baseline.from(), &self.ctrl.from());
let mut segment_1 = LineSegmentF32::new(&self.ctrl.from(), &self.baseline.to());
let mut segment_0 = LineSegmentF32::new(self.baseline.from(), self.ctrl.from());
let mut segment_1 = LineSegmentF32::new(self.ctrl.from(), self.baseline.to());
segment_0 = segment_0.offset(distance);
segment_1 = segment_1.offset(distance);
let ctrl = match segment_0.intersection_t(&segment_1) {
Some(t) => segment_0.sample(t),
None => segment_0.to().lerp(segment_1.from(), 0.5),
};
let baseline = LineSegmentF32::new(&segment_0.from(), &segment_1.to());
return Segment::quadratic(&baseline, &ctrl);
let baseline = LineSegmentF32::new(segment_0.from(), segment_1.to());
return Segment::quadratic(&baseline, ctrl);
}
debug_assert!(self.is_cubic());
if self.baseline.from() == self.ctrl.from() {
let mut segment_0 = LineSegmentF32::new(&self.baseline.from(), &self.ctrl.to());
let mut segment_1 = LineSegmentF32::new(&self.ctrl.to(), &self.baseline.to());
let mut segment_0 = LineSegmentF32::new(self.baseline.from(), self.ctrl.to());
let mut segment_1 = LineSegmentF32::new(self.ctrl.to(), self.baseline.to());
segment_0 = segment_0.offset(distance);
segment_1 = segment_1.offset(distance);
let ctrl = match segment_0.intersection_t(&segment_1) {
Some(t) => segment_0.sample(t),
None => segment_0.to().lerp(segment_1.from(), 0.5),
};
let baseline = LineSegmentF32::new(&segment_0.from(), &segment_1.to());
let ctrl = LineSegmentF32::new(&segment_0.from(), &ctrl);
let baseline = LineSegmentF32::new(segment_0.from(), segment_1.to());
let ctrl = LineSegmentF32::new(segment_0.from(), ctrl);
return Segment::cubic(&baseline, &ctrl);
}
if self.ctrl.to() == self.baseline.to() {
let mut segment_0 = LineSegmentF32::new(&self.baseline.from(), &self.ctrl.from());
let mut segment_1 = LineSegmentF32::new(&self.ctrl.from(), &self.baseline.to());
let mut segment_0 = LineSegmentF32::new(self.baseline.from(), self.ctrl.from());
let mut segment_1 = LineSegmentF32::new(self.ctrl.from(), self.baseline.to());
segment_0 = segment_0.offset(distance);
segment_1 = segment_1.offset(distance);
let ctrl = match segment_0.intersection_t(&segment_1) {
Some(t) => segment_0.sample(t),
None => segment_0.to().lerp(segment_1.from(), 0.5),
};
let baseline = LineSegmentF32::new(&segment_0.from(), &segment_1.to());
let ctrl = LineSegmentF32::new(&ctrl, &segment_1.to());
let baseline = LineSegmentF32::new(segment_0.from(), segment_1.to());
let ctrl = LineSegmentF32::new(ctrl, segment_1.to());
return Segment::cubic(&baseline, &ctrl);
}
let mut segment_0 = LineSegmentF32::new(&self.baseline.from(), &self.ctrl.from());
let mut segment_1 = LineSegmentF32::new(&self.ctrl.from(), &self.ctrl.to());
let mut segment_2 = LineSegmentF32::new(&self.ctrl.to(), &self.baseline.to());
let mut segment_0 = LineSegmentF32::new(self.baseline.from(), self.ctrl.from());
let mut segment_1 = LineSegmentF32::new(self.ctrl.from(), self.ctrl.to());
let mut segment_2 = LineSegmentF32::new(self.ctrl.to(), self.baseline.to());
segment_0 = segment_0.offset(distance);
segment_1 = segment_1.offset(distance);
segment_2 = segment_2.offset(distance);
@ -176,8 +282,8 @@ impl Offset for Segment {
segment_1.to().lerp(segment_2.from(), 0.5),
),
};
let baseline = LineSegmentF32::new(&segment_0.from(), &segment_2.to());
let ctrl = LineSegmentF32::new(&ctrl_0, &ctrl_1);
let baseline = LineSegmentF32::new(segment_0.from(), segment_2.to());
let ctrl = LineSegmentF32::new(ctrl_0, ctrl_1);
Segment::cubic(&baseline, &ctrl)
}
@ -209,3 +315,40 @@ impl Offset for Segment {
const SAMPLE_COUNT: u32 = 16;
}
}
impl Contour {
fn needs_join(&self, join: LineJoin) -> bool {
// TODO(pcwalton): Miter limit.
join == LineJoin::Miter && self.len() >= 2
}
fn add_join(&mut self, _: LineJoin, next_tangent: &LineSegmentF32) {
// TODO(pcwalton): Round joins.
let (p0, p1) = (self.position_of_last(2), self.position_of_last(1));
let prev_tangent = LineSegmentF32::new(p0, p1);
if let Some(prev_tangent_t) = prev_tangent.intersection_t(&next_tangent) {
self.push_endpoint(prev_tangent.sample(prev_tangent_t));
}
}
}
impl Default for StrokeStyle {
#[inline]
fn default() -> StrokeStyle {
StrokeStyle {
line_width: 1.0,
line_cap: LineCap::default(),
line_join: LineJoin::default(),
}
}
}
impl Default for LineCap {
#[inline]
fn default() -> LineCap { LineCap::Butt }
}
impl Default for LineJoin {
#[inline]
fn default() -> LineJoin { LineJoin::Miter }
}

View File

@ -4,9 +4,11 @@ version = "0.1.0"
edition = "2018"
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
[lib]
crate-type = ["rlib", "staticlib"]
[dependencies]
gl = "0.6"
rustache = "0.1"
[dependencies.image]
version = "0.21"

View File

@ -16,13 +16,13 @@ extern crate log;
use gl::types::{GLboolean, GLchar, GLenum, GLfloat, GLint, GLsizei, GLsizeiptr, GLuint, GLvoid};
use pathfinder_geometry::basic::point::Point2DI32;
use pathfinder_geometry::basic::rect::RectI32;
use pathfinder_gpu::resources::ResourceLoader;
use pathfinder_gpu::{BlendState, BufferData, BufferTarget, BufferUploadMode, ClearParams};
use pathfinder_gpu::{DepthFunc, Device, Primitive, RenderState, ShaderKind, StencilFunc};
use pathfinder_gpu::{TextureFormat, UniformData, VertexAttrType};
use pathfinder_gpu::{TextureFormat, UniformData, VertexAttrClass};
use pathfinder_gpu::{VertexAttrDescriptor, VertexAttrType};
use pathfinder_simd::default::F32x4;
use rustache::{HashBuilder, Render};
use std::ffi::CString;
use std::io::Cursor;
use std::mem;
use std::ptr;
use std::str;
@ -226,18 +226,18 @@ impl Device for GLDevice {
}
fn create_shader_from_source(&self,
resources: &dyn ResourceLoader,
name: &str,
source: &[u8],
kind: ShaderKind,
mut template_input: HashBuilder)
includes: &[&str])
-> GLShader {
// FIXME(pcwalton): Do this once and cache it.
let glsl_version_spec = self.version.to_glsl_version_spec();
template_input = template_input.insert("version", glsl_version_spec);
let mut output = Cursor::new(vec![]);
template_input.render(str::from_utf8(source).unwrap(), &mut output).unwrap();
let source = output.into_inner();
let mut output = vec![];
self.preprocess(&mut output, resources, source, includes, glsl_version_spec);
let source = output;
let gl_shader_kind = match kind {
ShaderKind::Vertex => gl::VERTEX_SHADER,
@ -271,6 +271,7 @@ impl Device for GLDevice {
}
fn create_program_from_shaders(&self,
resources: &dyn ResourceLoader,
name: &str,
vertex_shader: GLShader,
fragment_shader: GLShader)
@ -331,40 +332,33 @@ impl Device for GLDevice {
}
}
fn configure_float_vertex_attr(&self,
attr: &GLVertexAttr,
size: usize,
attr_type: VertexAttrType,
normalized: bool,
stride: usize,
offset: usize,
divisor: u32) {
fn configure_vertex_attr(&self, attr: &GLVertexAttr, descriptor: &VertexAttrDescriptor) {
unsafe {
let attr_type = descriptor.attr_type.to_gl_type();
match descriptor.class {
VertexAttrClass::Float | VertexAttrClass::FloatNorm => {
let normalized = if descriptor.class == VertexAttrClass::FloatNorm {
gl::TRUE
} else {
gl::FALSE
};
gl::VertexAttribPointer(attr.attr,
size as GLint,
attr_type.to_gl_type(),
if normalized { gl::TRUE } else { gl::FALSE },
stride as GLint,
offset as *const GLvoid); ck();
gl::VertexAttribDivisor(attr.attr, divisor); ck();
gl::EnableVertexAttribArray(attr.attr); ck();
descriptor.size as GLint,
attr_type,
normalized,
descriptor.stride as GLint,
descriptor.offset as *const GLvoid); ck();
}
VertexAttrClass::Int => {
gl::VertexAttribIPointer(attr.attr,
descriptor.size as GLint,
attr_type,
descriptor.stride as GLint,
descriptor.offset as *const GLvoid); ck();
}
}
fn configure_int_vertex_attr(&self,
attr: &GLVertexAttr,
size: usize,
attr_type: VertexAttrType,
stride: usize,
offset: usize,
divisor: u32) {
unsafe {
gl::VertexAttribIPointer(attr.attr,
size as GLint,
attr_type.to_gl_type(),
stride as GLint,
offset as *const GLvoid); ck();
gl::VertexAttribDivisor(attr.attr, divisor); ck();
gl::VertexAttribDivisor(attr.attr, descriptor.divisor); ck();
gl::EnableVertexAttribArray(attr.attr); ck();
}
}
@ -654,6 +648,41 @@ impl Device for GLDevice {
}
}
impl GLDevice {
fn preprocess(&self,
output: &mut Vec<u8>,
resources: &dyn ResourceLoader,
source: &[u8],
includes: &[&str],
version: &str) {
let mut index = 0;
while index < source.len() {
if source[index..].starts_with(b"{{") {
let end_index = source[index..].iter()
.position(|character| *character == b'}')
.expect("Expected `}`!") + index;
assert_eq!(source[end_index + 1], b'}');
let ident = String::from_utf8_lossy(&source[(index + 2)..end_index]);
if ident == "version" {
output.extend_from_slice(version.as_bytes());
} else if ident.starts_with("include_") {
let include_name = &ident["include_".len()..];
if includes.iter().any(|include| *include == include_name) {
let include_source = self.load_shader_include(resources, include_name);
self.preprocess(output, resources, &include_source, includes, version);
}
} else {
panic!("unknown template variable: `{}`", ident);
}
index = end_index + 2;
} else {
output.push(source[index]);
index += 1;
}
}
}
}
pub struct GLVertexArray {
pub gl_vertex_array: GLuint,
}
@ -869,11 +898,13 @@ impl VertexAttrTypeExt for VertexAttrType {
}
/// The version/dialect of OpenGL we should render with.
#[derive(Clone, Copy)]
#[repr(u32)]
pub enum GLVersion {
/// OpenGL 3.0+, core profile.
GL3,
GL3 = 0,
/// OpenGL ES 3.0+.
GLES3,
GLES3 = 1,
}
impl GLVersion {
@ -899,3 +930,5 @@ fn ck() {
#[cfg(not(debug))]
fn ck() {}
// Shader preprocessing

View File

@ -5,7 +5,6 @@ authors = ["Patrick Walton <pcwalton@mimiga.net>"]
edition = "2018"
[dependencies]
rustache = "0.1"
[dependencies.image]
version = "0.21"

View File

@ -17,11 +17,19 @@ use pathfinder_geometry::basic::rect::RectI32;
use pathfinder_geometry::basic::transform3d::Transform3DF32;
use pathfinder_geometry::color::ColorF;
use pathfinder_simd::default::F32x4;
use rustache::HashBuilder;
use std::time::Duration;
pub mod resources;
static INCLUDES: [&str; 6] = [
"tile_alpha_vertex",
"tile_monochrome",
"tile_multicolor",
"tile_solid_vertex",
"post_convolve",
"post_gamma_correct",
];
pub trait Device {
type Buffer;
type Framebuffer;
@ -37,14 +45,16 @@ pub trait Device {
fn create_texture_from_data(&self, size: Point2DI32, data: &[u8]) -> Self::Texture;
fn create_shader_from_source(
&self,
resources: &dyn ResourceLoader,
name: &str,
source: &[u8],
kind: ShaderKind,
template_input: HashBuilder,
includes: &[&str],
) -> Self::Shader;
fn create_vertex_array(&self) -> Self::VertexArray;
fn create_program_from_shaders(
&self,
resources: &dyn ResourceLoader,
name: &str,
vertex_shader: Self::Shader,
fragment_shader: Self::Shader,
@ -52,25 +62,7 @@ pub trait Device {
fn get_vertex_attr(&self, program: &Self::Program, name: &str) -> Self::VertexAttr;
fn get_uniform(&self, program: &Self::Program, name: &str) -> Self::Uniform;
fn use_program(&self, program: &Self::Program);
fn configure_float_vertex_attr(
&self,
attr: &Self::VertexAttr,
size: usize,
attr_type: VertexAttrType,
normalized: bool,
stride: usize,
offset: usize,
divisor: u32,
);
fn configure_int_vertex_attr(
&self,
attr: &Self::VertexAttr,
size: usize,
attr_type: VertexAttrType,
stride: usize,
offset: usize,
divisor: u32,
);
fn configure_vertex_attr(&self, attr: &Self::VertexAttr, descriptor: &VertexAttrDescriptor);
fn set_uniform(&self, uniform: &Self::Uniform, data: UniformData);
fn create_framebuffer(&self, texture: Self::Texture) -> Self::Framebuffer;
fn create_buffer(&self) -> Self::Buffer;
@ -127,39 +119,8 @@ pub trait Device {
ShaderKind::Vertex => 'v',
ShaderKind::Fragment => 'f',
};
let source = resources
.slurp(&format!("shaders/{}.{}s.glsl", name, suffix))
.unwrap();
let mut load_include_tile_alpha_vertex =
|_| load_shader_include(resources, "tile_alpha_vertex");
let mut load_include_tile_monochrome =
|_| load_shader_include(resources, "tile_monochrome");
let mut load_include_tile_multicolor =
|_| load_shader_include(resources, "tile_multicolor");
let mut load_include_tile_solid_vertex =
|_| load_shader_include(resources, "tile_solid_vertex");
let mut load_include_post_convolve = |_| load_shader_include(resources, "post_convolve");
let mut load_include_post_gamma_correct =
|_| load_shader_include(resources, "post_gamma_correct");
let template_input = HashBuilder::new()
.insert_lambda(
"include_tile_alpha_vertex",
&mut load_include_tile_alpha_vertex,
)
.insert_lambda("include_tile_monochrome", &mut load_include_tile_monochrome)
.insert_lambda("include_tile_multicolor", &mut load_include_tile_multicolor)
.insert_lambda(
"include_tile_solid_vertex",
&mut load_include_tile_solid_vertex,
)
.insert_lambda("include_post_convolve", &mut load_include_post_convolve)
.insert_lambda(
"include_post_gamma_correct",
&mut load_include_post_gamma_correct,
);
self.create_shader_from_source(name, &source, kind, template_input)
let source = resources.slurp(&format!("shaders/{}.{}s.glsl", name, suffix)).unwrap();
self.create_shader_from_source(resources, name, &source, kind, &INCLUDES)
}
fn create_program_from_shader_names(
@ -172,12 +133,16 @@ pub trait Device {
let vertex_shader = self.create_shader(resources, vertex_shader_name, ShaderKind::Vertex);
let fragment_shader =
self.create_shader(resources, fragment_shader_name, ShaderKind::Fragment);
self.create_program_from_shaders(program_name, vertex_shader, fragment_shader)
self.create_program_from_shaders(resources, program_name, vertex_shader, fragment_shader)
}
fn create_program(&self, resources: &dyn ResourceLoader, name: &str) -> Self::Program {
self.create_program_from_shader_names(resources, name, name, name)
}
fn load_shader_include(&self, resources: &dyn ResourceLoader, include_name: &str) -> Vec<u8> {
resources.slurp(&format!("shaders/{}.inc.glsl", include_name)).unwrap()
}
}
#[derive(Clone, Copy, Debug)]
@ -340,9 +305,19 @@ impl UniformData {
}
}
fn load_shader_include(resources: &dyn ResourceLoader, include_name: &str) -> String {
let resource = resources
.slurp(&format!("shaders/{}.inc.glsl", include_name))
.unwrap();
String::from_utf8_lossy(&resource).to_string()
#[derive(Clone, Copy, Debug)]
pub struct VertexAttrDescriptor {
pub size: usize,
pub class: VertexAttrClass,
pub attr_type: VertexAttrType,
pub stride: usize,
pub offset: usize,
pub divisor: u32,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum VertexAttrClass {
Float,
FloatNorm,
Int,
}

12
lottie/Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[package]
name = "pathfinder_lottie"
version = "0.1.0"
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
edition = "2018"
[dependencies]
serde_json = "1.0"
[dependencies.serde]
version = "1.0"
features = ["derive"]

313
lottie/src/lib.rs Normal file
View File

@ -0,0 +1,313 @@
// pathfinder/lottie/src/lib.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.
//! Experimental support for Lottie. This is very incomplete.
use serde::{Deserialize, Serialize};
use serde_json::Error as JSONError;
use std::io::Read;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Lottie {
#[serde(rename = "v")]
pub version: String,
#[serde(rename = "fr")]
pub frame_rate: i64,
#[serde(rename = "ip")]
pub in_point: i64,
#[serde(rename = "op")]
pub out_point: i64,
#[serde(rename = "w")]
pub width: f64,
#[serde(rename = "h")]
pub height: f64,
#[serde(rename = "ddd")]
pub three_d: i64,
pub assets: Vec<Asset>,
pub layers: Vec<Layer>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Asset {}
// FIXME(pcwalton): Using an untagged enum is a botch here. There actually is a tag: it's just an
// integer, which `serde_json` doesn't support natively.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Layer {
Shape {
#[serde(rename = "ddd")]
three_d: i64,
#[serde(rename = "ind")]
index: i64,
#[serde(rename = "nm")]
name: String,
#[serde(rename = "ks")]
transform: Transform,
#[serde(rename = "ao")]
auto_orient: i64,
#[serde(rename = "ip")]
in_point: i64,
#[serde(rename = "op")]
out_point: i64,
#[serde(rename = "st")]
start_time: i64,
#[serde(rename = "bm")]
blend_mode: i64,
#[serde(rename = "sr")]
stretch: i64,
#[serde(rename = "ln")]
#[serde(default)]
layer_id: Option<String>,
shapes: Vec<Shape>,
},
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Transform {
#[serde(rename = "p")]
pub position: MultidimensionalPropertyValue,
#[serde(rename = "a")]
pub anchor_point: MultidimensionalPropertyValue,
#[serde(rename = "s")]
pub scale: MultidimensionalPropertyValue,
#[serde(rename = "r")]
pub rotation: PropertyValue,
#[serde(rename = "o")]
#[serde(default)]
pub opacity: Option<PropertyValue>,
#[serde(rename = "px")]
#[serde(default)]
pub position_x: Option<PropertyValue>,
#[serde(rename = "py")]
#[serde(default)]
pub position_y: Option<PropertyValue>,
#[serde(rename = "pz")]
#[serde(default)]
pub position_z: Option<PropertyValue>,
#[serde(rename = "sk")]
#[serde(default)]
pub skew: Option<PropertyValue>,
#[serde(rename = "sa")]
#[serde(default)]
pub skew_axis: Option<PropertyValue>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum PropertyValue {
Value {
#[serde(rename = "k")]
value: f32,
#[serde(rename = "x")]
#[serde(default)]
expression: Option<String>,
#[serde(rename = "ix")]
#[serde(default)]
index: Option<i64>,
},
KeyframedValue {
#[serde(rename = "k")]
keyframes: Vec<KeyframeValue>,
#[serde(rename = "x")]
#[serde(default)]
expression: Option<String>,
#[serde(rename = "ix")]
#[serde(default)]
index: Option<i64>,
},
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct KeyframeValue {
#[serde(rename = "s")]
#[serde(default)]
pub start: Option<Vec<f32>>,
#[serde(rename = "t")]
pub time: i64,
#[serde(rename = "i")]
#[serde(default)]
pub interpolation: Option<OffsetInterpolation>,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct Interpolation {
pub x: f32,
pub y: f32,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct OffsetInterpolation {
pub x: Vec<f32>,
pub y: Vec<f32>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct OffsetKeyframe {
#[serde(rename = "s")]
#[serde(default)]
pub start: Option<Vec<f32>>,
#[serde(rename = "t")]
pub time: i64,
#[serde(rename = "i")]
#[serde(default)]
pub in_value: Option<OffsetInterpolation>,
#[serde(rename = "o")]
#[serde(default)]
pub out_value: Option<OffsetInterpolation>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum MultidimensionalPropertyValue {
Value {
#[serde(rename = "k")]
value: Vec<f32>,
#[serde(rename = "x")]
#[serde(default)]
expression: Option<String>,
#[serde(rename = "ix")]
#[serde(default)]
index: Option<i64>,
},
KeyframedValue {
#[serde(rename = "k")]
keyframes: Vec<OffsetKeyframe>,
#[serde(rename = "x")]
#[serde(default)]
expression: Option<String>,
#[serde(rename = "ix")]
#[serde(default)]
index: Option<i64>,
#[serde(rename = "ti")]
#[serde(default)]
in_tangent: Option<i64>,
#[serde(rename = "to")]
#[serde(default)]
out_tangent: Option<i64>,
},
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "ty")]
pub enum Shape {
#[serde(rename = "gr")]
Group {
#[serde(rename = "it")]
items: Vec<Shape>,
#[serde(rename = "nm")]
name: String,
},
#[serde(rename = "sh")]
Shape {
#[serde(rename = "ks")]
vertices: ShapeVertices,
#[serde(rename = "d")]
#[serde(default)]
direction: Option<i64>,
},
#[serde(rename = "fl")]
Fill {
#[serde(rename = "nm")]
#[serde(default)]
name: Option<String>,
#[serde(rename = "o")]
#[serde(default)]
opacity: Option<PropertyValue>,
#[serde(rename = "c")]
color: MultidimensionalPropertyValue,
},
#[serde(rename = "tr")]
Transform {
#[serde(rename = "r")]
rotation: PropertyValue,
#[serde(rename = "sk")]
skew: PropertyValue,
#[serde(rename = "sa")]
skew_axis: PropertyValue,
#[serde(rename = "p")]
position: MultidimensionalPropertyValue,
#[serde(rename = "a")]
anchor_point: MultidimensionalPropertyValue,
#[serde(rename = "s")]
scale: MultidimensionalPropertyValue,
},
#[serde(other)]
Unimplemented,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ShapeVertices {
Shape {
#[serde(rename = "k")]
value: ShapeProperty,
#[serde(rename = "x")]
#[serde(default)]
expression: Option<String>,
#[serde(rename = "ix")]
#[serde(default)]
index: Option<i64>,
#[serde(rename = "a")]
animated: i64,
},
ShapeKeyframed {
#[serde(rename = "k")]
value: Vec<ShapeKeyframeProperty>,
#[serde(rename = "x")]
#[serde(default)]
expression: Option<String>,
#[serde(rename = "ix")]
#[serde(default)]
index: Option<i64>,
#[serde(rename = "a")]
animated: i64,
#[serde(rename = "ti")]
#[serde(default)]
in_tangent: Vec<i64>,
#[serde(rename = "to")]
#[serde(default)]
out_tangent: Vec<i64>,
},
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ShapeProperty {
#[serde(rename = "c")]
pub closed: bool,
#[serde(rename = "i")]
pub in_points: Vec<[f32; 2]>,
#[serde(rename = "o")]
pub out_points: Vec<[f32; 2]>,
#[serde(rename = "v")]
pub vertices: Vec<[f32; 2]>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ShapeKeyframeProperty {
#[serde(rename = "s")]
#[serde(default)]
pub start: Vec<Option<ShapeProperty>>,
#[serde(rename = "t")]
pub time: i64,
#[serde(rename = "i")]
#[serde(default)]
pub in_value: Option<OffsetInterpolation>,
#[serde(rename = "o")]
#[serde(default)]
pub out_value: Option<OffsetInterpolation>,
}
impl Lottie {
#[inline]
pub fn from_reader<R>(reader: R) -> Result<Lottie, JSONError> where R: Read {
serde_json::from_reader(reader)
}
}

View File

@ -30,4 +30,4 @@ path = "../simd"
path = "../ui"
[dev-dependencies]
quickcheck = "0.7"
quickcheck = "0.8"

View File

@ -59,7 +59,7 @@ impl<'a> SceneBuilder<'a> {
let path_count = self.scene.paths.len();
self.listener.send(RenderCommand::Start { bounding_quad, path_count });
self.listener.send(RenderCommand::AddShaders(self.scene.build_shaders()));
self.listener.send(RenderCommand::AddPaintData(self.scene.build_paint_data()));
let effective_view_box = self.scene.effective_view_box(self.built_options);
let alpha_tiles = executor.flatten_into_vector(path_count, |path_index| {
@ -81,8 +81,16 @@ impl<'a> SceneBuilder<'a> {
) -> Vec<AlphaTileBatchPrimitive> {
let path_object = &scene.paths[path_index];
let outline = scene.apply_render_options(path_object.outline(), built_options);
let paint_id = path_object.paint();
let object_is_opaque = scene.paints[paint_id.0 as usize].is_opaque();
let mut tiler = Tiler::new(self,
&outline,
view_box,
path_index as u16,
paint_id,
object_is_opaque);
let mut tiler = Tiler::new(self, &outline, view_box, path_index as u16);
tiler.generate_tiles();
self.listener.send(RenderCommand::AddFills(tiler.built_object.fills));
@ -108,7 +116,7 @@ impl<'a> SceneBuilder<'a> {
fn pack_alpha_tiles(&mut self, alpha_tiles: Vec<AlphaTileBatchPrimitive>) {
let path_count = self.scene.paths.len() as u32;
let solid_tiles = self.z_buffer.build_solid_tiles(0..path_count);
let solid_tiles = self.z_buffer.build_solid_tiles(&self.scene.paths, 0..path_count);
if !solid_tiles.is_empty() {
self.listener.send(RenderCommand::SolidTile(solid_tiles));
}
@ -234,9 +242,9 @@ impl BuiltObject {
let right = Point2DF32::new(right, tile_origin_y);
let segment = if winding < 0 {
LineSegmentF32::new(&left, &right)
LineSegmentF32::new(left, right)
} else {
LineSegmentF32::new(&right, &left)
LineSegmentF32::new(right, left)
};
debug!(
@ -298,14 +306,14 @@ impl BuiltObject {
let point = Point2DF32::new(x, segment.solve_y_for_x(x));
if !winding {
fill_to = point;
segment = LineSegmentF32::new(&point, &segment.to());
segment = LineSegmentF32::new(point, segment.to());
} else {
fill_from = point;
segment = LineSegmentF32::new(&segment.from(), &point);
segment = LineSegmentF32::new(segment.from(), point);
}
}
let fill_segment = LineSegmentF32::new(&fill_from, &fill_to);
let fill_segment = LineSegmentF32::new(fill_from, fill_to);
let fill_tile_coords = Point2DI32::new(subsegment_tile_x, tile_y);
self.add_fill(builder, &fill_segment, fill_tile_coords);
}

View File

@ -36,7 +36,12 @@ pub struct SceneProxy {
}
impl SceneProxy {
pub fn new<E>(scene: Scene, executor: E) -> SceneProxy where E: Executor + Send + 'static {
pub fn new<E>(executor: E) -> SceneProxy where E: Executor + Send + 'static {
SceneProxy::from_scene(Scene::new(), executor)
}
pub fn from_scene<E>(scene: Scene, executor: E) -> SceneProxy
where E: Executor + Send + 'static {
let (main_to_worker_sender, main_to_worker_receiver) = mpsc::channel();
thread::spawn(move || scene_thread(scene, executor, main_to_worker_receiver));
SceneProxy { sender: main_to_worker_sender }

View File

@ -9,10 +9,9 @@
// except according to those terms.
use crate::gpu::debug::DebugUIPresenter;
use crate::gpu_data::{AlphaTileBatchPrimitive, FillBatchPrimitive};
use crate::gpu_data::{AlphaTileBatchPrimitive, FillBatchPrimitive, PaintData};
use crate::gpu_data::{RenderCommand, SolidTileBatchPrimitive};
use crate::post::DefringingKernel;
use crate::scene::ObjectShader;
use crate::tiles::{TILE_HEIGHT, TILE_WIDTH};
use pathfinder_geometry::basic::point::{Point2DI32, Point3DF32};
use pathfinder_geometry::basic::rect::RectI32;
@ -21,7 +20,8 @@ use pathfinder_geometry::color::ColorF;
use pathfinder_gpu::resources::ResourceLoader;
use pathfinder_gpu::{BlendState, BufferData, BufferTarget, BufferUploadMode, ClearParams};
use pathfinder_gpu::{DepthFunc, DepthState, Device, Primitive, RenderState, StencilFunc};
use pathfinder_gpu::{StencilState, TextureFormat, UniformData, VertexAttrType};
use pathfinder_gpu::{StencilState, TextureFormat, UniformData, VertexAttrClass};
use pathfinder_gpu::{VertexAttrDescriptor, VertexAttrType};
use pathfinder_simd::default::{F32x4, I32x4};
use std::cmp;
use std::collections::VecDeque;
@ -38,11 +38,8 @@ const MASK_FRAMEBUFFER_HEIGHT: i32 = TILE_HEIGHT as i32 * 256;
// TODO(pcwalton): Replace with `mem::size_of` calls?
const FILL_INSTANCE_SIZE: usize = 8;
const SOLID_TILE_INSTANCE_SIZE: usize = 6;
const MASK_TILE_INSTANCE_SIZE: usize = 8;
const FILL_COLORS_TEXTURE_WIDTH: i32 = 256;
const FILL_COLORS_TEXTURE_HEIGHT: i32 = 256;
const SOLID_TILE_INSTANCE_SIZE: usize = 10;
const MASK_TILE_INSTANCE_SIZE: usize = 12;
const MAX_FILLS_PER_BATCH: usize = 0x4000;
@ -68,7 +65,7 @@ where
quad_vertex_positions_buffer: D::Buffer,
fill_vertex_array: FillVertexArray<D>,
mask_framebuffer: D::Framebuffer,
fill_colors_texture: D::Texture,
paint_texture: Option<D::Texture>,
// Postprocessing shader
postprocess_source_framebuffer: Option<D::Framebuffer>,
@ -171,10 +168,6 @@ where
device.create_texture(TextureFormat::R16F, mask_framebuffer_size);
let mask_framebuffer = device.create_framebuffer(mask_framebuffer_texture);
let fill_colors_size =
Point2DI32::new(FILL_COLORS_TEXTURE_WIDTH, FILL_COLORS_TEXTURE_HEIGHT);
let fill_colors_texture = device.create_texture(TextureFormat::RGBA8, fill_colors_size);
let window_size = dest_framebuffer.window_size(&device);
let debug_ui_presenter = DebugUIPresenter::new(&device, resources, window_size);
@ -195,7 +188,7 @@ where
quad_vertex_positions_buffer,
fill_vertex_array,
mask_framebuffer,
fill_colors_texture,
paint_texture: None,
postprocess_source_framebuffer: None,
postprocess_program,
@ -242,7 +235,7 @@ where
}
self.stats.path_count = path_count;
}
RenderCommand::AddShaders(ref shaders) => self.upload_shaders(shaders),
RenderCommand::AddPaintData(ref paint_data) => self.upload_paint_data(paint_data),
RenderCommand::AddFills(ref fills) => self.add_fills(fills),
RenderCommand::FlushFills => {
self.begin_composite_timer_query();
@ -345,17 +338,19 @@ where
&self.quad_vertex_positions_buffer
}
fn upload_shaders(&mut self, shaders: &[ObjectShader]) {
let size = Point2DI32::new(FILL_COLORS_TEXTURE_WIDTH, FILL_COLORS_TEXTURE_HEIGHT);
let mut fill_colors = vec![0; size.x() as usize * size.y() as usize * 4];
for (shader_index, shader) in shaders.iter().enumerate() {
fill_colors[shader_index * 4 + 0] = shader.fill_color.r;
fill_colors[shader_index * 4 + 1] = shader.fill_color.g;
fill_colors[shader_index * 4 + 2] = shader.fill_color.b;
fill_colors[shader_index * 4 + 3] = shader.fill_color.a;
fn upload_paint_data(&mut self, paint_data: &PaintData) {
match self.paint_texture {
Some(ref paint_texture) if
self.device.texture_size(paint_texture) == paint_data.size => {}
_ => {
let texture = self.device.create_texture(TextureFormat::RGBA8, paint_data.size);
self.paint_texture = Some(texture)
}
self.device
.upload_to_texture(&self.fill_colors_texture, size, &fill_colors);
}
self.device.upload_to_texture(self.paint_texture.as_ref().unwrap(),
paint_data.size,
&paint_data.texels);
}
fn upload_solid_tiles(&mut self, solid_tiles: &[SolidTileBatchPrimitive]) {
@ -493,32 +488,26 @@ where
match self.render_mode {
RenderMode::Multicolor => {
self.device.bind_texture(&self.fill_colors_texture, 1);
let paint_texture = self.paint_texture.as_ref().unwrap();
self.device.bind_texture(paint_texture, 1);
self.device.set_uniform(
&self
.alpha_multicolor_tile_program
.fill_colors_texture_uniform,
&self.alpha_multicolor_tile_program.paint_texture_uniform,
UniformData::TextureUnit(1),
);
self.device.set_uniform(
&self
.alpha_multicolor_tile_program
.fill_colors_texture_size_uniform,
UniformData::Vec2(
I32x4::new(FILL_COLORS_TEXTURE_WIDTH, FILL_COLORS_TEXTURE_HEIGHT, 0, 0)
.to_f32x4(),
),
&self.alpha_multicolor_tile_program.paint_texture_size_uniform,
UniformData::Vec2(self.device.texture_size(paint_texture).0.to_f32x4())
);
}
RenderMode::Monochrome { .. } if self.postprocessing_needed() => {
self.device.set_uniform(
&self.alpha_monochrome_tile_program.fill_color_uniform,
&self.alpha_monochrome_tile_program.color_uniform,
UniformData::Vec4(F32x4::splat(1.0)),
);
}
RenderMode::Monochrome { fg_color, .. } => {
self.device.set_uniform(
&self.alpha_monochrome_tile_program.fill_color_uniform,
&self.alpha_monochrome_tile_program.color_uniform,
UniformData::Vec4(fg_color.0),
);
}
@ -558,32 +547,30 @@ where
match self.render_mode {
RenderMode::Multicolor => {
self.device.bind_texture(&self.fill_colors_texture, 0);
let paint_texture = self.paint_texture.as_ref().unwrap();
self.device.bind_texture(paint_texture, 0);
self.device.set_uniform(
&self
.solid_multicolor_tile_program
.fill_colors_texture_uniform,
.paint_texture_uniform,
UniformData::TextureUnit(0),
);
self.device.set_uniform(
&self
.solid_multicolor_tile_program
.fill_colors_texture_size_uniform,
UniformData::Vec2(
I32x4::new(FILL_COLORS_TEXTURE_WIDTH, FILL_COLORS_TEXTURE_HEIGHT, 0, 0)
.to_f32x4(),
),
.paint_texture_size_uniform,
UniformData::Vec2(self.device.texture_size(paint_texture).0.to_f32x4())
);
}
RenderMode::Monochrome { .. } if self.postprocessing_needed() => {
self.device.set_uniform(
&self.solid_monochrome_tile_program.fill_color_uniform,
&self.solid_monochrome_tile_program.color_uniform,
UniformData::Vec4(F32x4::splat(1.0)),
);
}
RenderMode::Monochrome { fg_color, .. } => {
self.device.set_uniform(
&self.solid_monochrome_tile_program.fill_color_uniform,
&self.solid_monochrome_tile_program.color_uniform,
UniformData::Vec4(fg_color.0),
);
}
@ -932,55 +919,57 @@ where
device.bind_vertex_array(&vertex_array);
device.use_program(&fill_program.program);
device.bind_buffer(quad_vertex_positions_buffer, BufferTarget::Vertex);
device.configure_float_vertex_attr(&tess_coord_attr, 2, VertexAttrType::U8, false, 0, 0, 0);
device.configure_vertex_attr(&tess_coord_attr, &VertexAttrDescriptor {
size: 2,
class: VertexAttrClass::Float,
attr_type: VertexAttrType::U8,
stride: 0,
offset: 0,
divisor: 0,
});
device.bind_buffer(&vertex_buffer, BufferTarget::Vertex);
device.configure_int_vertex_attr(
&from_px_attr,
1,
VertexAttrType::U8,
FILL_INSTANCE_SIZE,
0,
1,
);
device.configure_int_vertex_attr(
&to_px_attr,
1,
VertexAttrType::U8,
FILL_INSTANCE_SIZE,
1,
1,
);
device.configure_float_vertex_attr(
&from_subpx_attr,
2,
VertexAttrType::U8,
true,
FILL_INSTANCE_SIZE,
2,
1,
);
device.configure_float_vertex_attr(
&to_subpx_attr,
2,
VertexAttrType::U8,
true,
FILL_INSTANCE_SIZE,
4,
1,
);
device.configure_int_vertex_attr(
&tile_index_attr,
1,
VertexAttrType::U16,
FILL_INSTANCE_SIZE,
6,
1,
);
device.configure_vertex_attr(&from_px_attr, &VertexAttrDescriptor {
size: 1,
class: VertexAttrClass::Int,
attr_type: VertexAttrType::U8,
stride: FILL_INSTANCE_SIZE,
offset: 0,
divisor: 1,
});
device.configure_vertex_attr(&to_px_attr, &VertexAttrDescriptor {
size: 1,
class: VertexAttrClass::Int,
attr_type: VertexAttrType::U8,
stride: FILL_INSTANCE_SIZE,
offset: 1,
divisor: 1,
});
device.configure_vertex_attr(&from_subpx_attr, &VertexAttrDescriptor {
size: 2,
class: VertexAttrClass::FloatNorm,
attr_type: VertexAttrType::U8,
stride: FILL_INSTANCE_SIZE,
offset: 2,
divisor: 1,
});
device.configure_vertex_attr(&to_subpx_attr, &VertexAttrDescriptor {
size: 2,
class: VertexAttrClass::FloatNorm,
attr_type: VertexAttrType::U8,
stride: FILL_INSTANCE_SIZE,
offset: 4,
divisor: 1,
});
device.configure_vertex_attr(&tile_index_attr, &VertexAttrDescriptor {
size: 1,
class: VertexAttrClass::Int,
attr_type: VertexAttrType::U16,
stride: FILL_INSTANCE_SIZE,
offset: 6,
divisor: 1,
});
FillVertexArray {
vertex_array,
vertex_buffer,
}
FillVertexArray { vertex_array, vertex_buffer }
}
}
@ -1006,53 +995,58 @@ where
let tess_coord_attr = device.get_vertex_attr(&alpha_tile_program.program, "TessCoord");
let tile_origin_attr = device.get_vertex_attr(&alpha_tile_program.program, "TileOrigin");
let backdrop_attr = device.get_vertex_attr(&alpha_tile_program.program, "Backdrop");
let object_attr = device.get_vertex_attr(&alpha_tile_program.program, "Object");
let tile_index_attr = device.get_vertex_attr(&alpha_tile_program.program, "TileIndex");
let color_tex_coord_attr = device.get_vertex_attr(&alpha_tile_program.program,
"ColorTexCoord");
// NB: The object must be of type `I16`, not `U16`, to work around a macOS Radeon
// driver bug.
device.bind_vertex_array(&vertex_array);
device.use_program(&alpha_tile_program.program);
device.bind_buffer(quad_vertex_positions_buffer, BufferTarget::Vertex);
device.configure_float_vertex_attr(&tess_coord_attr, 2, VertexAttrType::U8, false, 0, 0, 0);
device.configure_vertex_attr(&tess_coord_attr, &VertexAttrDescriptor {
size: 2,
class: VertexAttrClass::Float,
attr_type: VertexAttrType::U8,
stride: 0,
offset: 0,
divisor: 0,
});
device.bind_buffer(&vertex_buffer, BufferTarget::Vertex);
device.configure_int_vertex_attr(
&tile_origin_attr,
3,
VertexAttrType::U8,
MASK_TILE_INSTANCE_SIZE,
0,
1,
);
device.configure_int_vertex_attr(
&backdrop_attr,
1,
VertexAttrType::I8,
MASK_TILE_INSTANCE_SIZE,
3,
1,
);
device.configure_int_vertex_attr(
&object_attr,
2,
VertexAttrType::I16,
MASK_TILE_INSTANCE_SIZE,
4,
1,
);
device.configure_int_vertex_attr(
&tile_index_attr,
2,
VertexAttrType::I16,
MASK_TILE_INSTANCE_SIZE,
6,
1,
);
device.configure_vertex_attr(&tile_origin_attr, &VertexAttrDescriptor {
size: 3,
class: VertexAttrClass::Int,
attr_type: VertexAttrType::U8,
stride: MASK_TILE_INSTANCE_SIZE,
offset: 0,
divisor: 1,
});
device.configure_vertex_attr(&backdrop_attr, &VertexAttrDescriptor {
size: 1,
class: VertexAttrClass::Int,
attr_type: VertexAttrType::I8,
stride: MASK_TILE_INSTANCE_SIZE,
offset: 3,
divisor: 1,
});
device.configure_vertex_attr(&tile_index_attr, &VertexAttrDescriptor {
size: 1,
class: VertexAttrClass::Int,
attr_type: VertexAttrType::I16,
stride: MASK_TILE_INSTANCE_SIZE,
offset: 6,
divisor: 1,
});
device.configure_vertex_attr(&color_tex_coord_attr, &VertexAttrDescriptor {
size: 2,
class: VertexAttrClass::FloatNorm,
attr_type: VertexAttrType::U16,
stride: MASK_TILE_INSTANCE_SIZE,
offset: 8,
divisor: 1,
});
AlphaTileVertexArray {
vertex_array,
vertex_buffer,
}
AlphaTileVertexArray { vertex_array, vertex_buffer }
}
}
@ -1077,37 +1071,41 @@ where
let tess_coord_attr = device.get_vertex_attr(&solid_tile_program.program, "TessCoord");
let tile_origin_attr = device.get_vertex_attr(&solid_tile_program.program, "TileOrigin");
let object_attr = device.get_vertex_attr(&solid_tile_program.program, "Object");
let color_tex_coord_attr = device.get_vertex_attr(&solid_tile_program.program,
"ColorTexCoord");
// NB: The object must be of type short, not unsigned short, to work around a macOS
// Radeon driver bug.
device.bind_vertex_array(&vertex_array);
device.use_program(&solid_tile_program.program);
device.bind_buffer(quad_vertex_positions_buffer, BufferTarget::Vertex);
device.configure_float_vertex_attr(&tess_coord_attr, 2, VertexAttrType::U8, false, 0, 0, 0);
device.configure_vertex_attr(&tess_coord_attr, &VertexAttrDescriptor {
size: 2,
class: VertexAttrClass::Float,
attr_type: VertexAttrType::U8,
stride: 0,
offset: 0,
divisor: 0,
});
device.bind_buffer(&vertex_buffer, BufferTarget::Vertex);
device.configure_float_vertex_attr(
&tile_origin_attr,
2,
VertexAttrType::I16,
false,
SOLID_TILE_INSTANCE_SIZE,
0,
1,
);
device.configure_int_vertex_attr(
&object_attr,
1,
VertexAttrType::I16,
SOLID_TILE_INSTANCE_SIZE,
4,
1,
);
device.configure_vertex_attr(&tile_origin_attr, &VertexAttrDescriptor {
size: 2,
class: VertexAttrClass::Float,
attr_type: VertexAttrType::I16,
stride: SOLID_TILE_INSTANCE_SIZE,
offset: 0,
divisor: 1,
});
device.configure_vertex_attr(&color_tex_coord_attr, &VertexAttrDescriptor {
size: 2,
class: VertexAttrClass::FloatNorm,
attr_type: VertexAttrType::U16,
stride: SOLID_TILE_INSTANCE_SIZE,
offset: 4,
divisor: 1,
});
SolidTileVertexArray {
vertex_array,
vertex_buffer,
}
SolidTileVertexArray { vertex_array, vertex_buffer }
}
}
@ -1177,8 +1175,8 @@ where
D: Device,
{
solid_tile_program: SolidTileProgram<D>,
fill_colors_texture_uniform: D::Uniform,
fill_colors_texture_size_uniform: D::Uniform,
paint_texture_uniform: D::Uniform,
paint_texture_size_uniform: D::Uniform,
}
impl<D> SolidTileMulticolorProgram<D>
@ -1187,14 +1185,14 @@ where
{
fn new(device: &D, resources: &dyn ResourceLoader) -> SolidTileMulticolorProgram<D> {
let solid_tile_program = SolidTileProgram::new(device, "tile_solid_multicolor", resources);
let fill_colors_texture_uniform =
device.get_uniform(&solid_tile_program.program, "FillColorsTexture");
let fill_colors_texture_size_uniform =
device.get_uniform(&solid_tile_program.program, "FillColorsTextureSize");
let paint_texture_uniform =
device.get_uniform(&solid_tile_program.program, "PaintTexture");
let paint_texture_size_uniform =
device.get_uniform(&solid_tile_program.program, "PaintTextureSize");
SolidTileMulticolorProgram {
solid_tile_program,
fill_colors_texture_uniform,
fill_colors_texture_size_uniform,
paint_texture_uniform,
paint_texture_size_uniform,
}
}
}
@ -1204,7 +1202,7 @@ where
D: Device,
{
solid_tile_program: SolidTileProgram<D>,
fill_color_uniform: D::Uniform,
color_uniform: D::Uniform,
}
impl<D> SolidTileMonochromeProgram<D>
@ -1213,10 +1211,10 @@ where
{
fn new(device: &D, resources: &dyn ResourceLoader) -> SolidTileMonochromeProgram<D> {
let solid_tile_program = SolidTileProgram::new(device, "tile_solid_monochrome", resources);
let fill_color_uniform = device.get_uniform(&solid_tile_program.program, "FillColor");
let color_uniform = device.get_uniform(&solid_tile_program.program, "Color");
SolidTileMonochromeProgram {
solid_tile_program,
fill_color_uniform,
color_uniform,
}
}
}
@ -1265,8 +1263,8 @@ where
D: Device,
{
alpha_tile_program: AlphaTileProgram<D>,
fill_colors_texture_uniform: D::Uniform,
fill_colors_texture_size_uniform: D::Uniform,
paint_texture_uniform: D::Uniform,
paint_texture_size_uniform: D::Uniform,
}
impl<D> AlphaTileMulticolorProgram<D>
@ -1275,14 +1273,14 @@ where
{
fn new(device: &D, resources: &dyn ResourceLoader) -> AlphaTileMulticolorProgram<D> {
let alpha_tile_program = AlphaTileProgram::new(device, "tile_alpha_multicolor", resources);
let fill_colors_texture_uniform =
device.get_uniform(&alpha_tile_program.program, "FillColorsTexture");
let fill_colors_texture_size_uniform =
device.get_uniform(&alpha_tile_program.program, "FillColorsTextureSize");
let paint_texture_uniform =
device.get_uniform(&alpha_tile_program.program, "PaintTexture");
let paint_texture_size_uniform =
device.get_uniform(&alpha_tile_program.program, "PaintTextureSize");
AlphaTileMulticolorProgram {
alpha_tile_program,
fill_colors_texture_uniform,
fill_colors_texture_size_uniform,
paint_texture_uniform,
paint_texture_size_uniform,
}
}
}
@ -1292,7 +1290,7 @@ where
D: Device,
{
alpha_tile_program: AlphaTileProgram<D>,
fill_color_uniform: D::Uniform,
color_uniform: D::Uniform,
}
impl<D> AlphaTileMonochromeProgram<D>
@ -1301,10 +1299,10 @@ where
{
fn new(device: &D, resources: &dyn ResourceLoader) -> AlphaTileMonochromeProgram<D> {
let alpha_tile_program = AlphaTileProgram::new(device, "tile_alpha_monochrome", resources);
let fill_color_uniform = device.get_uniform(&alpha_tile_program.program, "FillColor");
let color_uniform = device.get_uniform(&alpha_tile_program.program, "Color");
AlphaTileMonochromeProgram {
alpha_tile_program,
fill_color_uniform,
color_uniform,
}
}
}
@ -1375,7 +1373,14 @@ where
device.bind_vertex_array(&vertex_array);
device.use_program(&postprocess_program.program);
device.bind_buffer(quad_vertex_positions_buffer, BufferTarget::Vertex);
device.configure_float_vertex_attr(&position_attr, 2, VertexAttrType::U8, false, 0, 0, 0);
device.configure_vertex_attr(&position_attr, &VertexAttrDescriptor {
size: 2,
class: VertexAttrClass::Float,
attr_type: VertexAttrType::U8,
stride: 0,
offset: 0,
divisor: 0,
});
PostprocessVertexArray { vertex_array }
}
@ -1418,20 +1423,16 @@ where
device.bind_vertex_array(&vertex_array);
device.use_program(&stencil_program.program);
device.bind_buffer(&vertex_buffer, BufferTarget::Vertex);
device.configure_float_vertex_attr(
&position_attr,
3,
VertexAttrType::F32,
false,
4 * 4,
0,
0,
);
device.configure_vertex_attr(&position_attr, &VertexAttrDescriptor {
size: 3,
class: VertexAttrClass::Float,
attr_type: VertexAttrType::F32,
stride: 4 * 4,
offset: 0,
divisor: 0,
});
StencilVertexArray {
vertex_array,
vertex_buffer,
}
StencilVertexArray { vertex_array, vertex_buffer }
}
}
@ -1487,7 +1488,14 @@ where
device.bind_vertex_array(&vertex_array);
device.use_program(&reprojection_program.program);
device.bind_buffer(quad_vertex_positions_buffer, BufferTarget::Vertex);
device.configure_float_vertex_attr(&position_attr, 2, VertexAttrType::U8, false, 0, 0, 0);
device.configure_vertex_attr(&position_attr, &VertexAttrDescriptor {
size: 2,
class: VertexAttrClass::Float,
attr_type: VertexAttrType::U8,
stride: 0,
offset: 0,
divisor: 0,
});
ReprojectionVertexArray { vertex_array }
}

View File

@ -11,9 +11,9 @@
//! Packed data ready to be sent to the GPU.
use crate::options::BoundingQuad;
use crate::scene::ObjectShader;
use crate::tile_map::DenseTileMap;
use pathfinder_geometry::basic::line_segment::{LineSegmentU4, LineSegmentU8};
use pathfinder_geometry::basic::point::Point2DI32;
use pathfinder_geometry::basic::rect::RectF32;
use std::fmt::{Debug, Formatter, Result as DebugResult};
use std::time::Duration;
@ -28,7 +28,7 @@ pub(crate) struct BuiltObject {
pub enum RenderCommand {
Start { path_count: usize, bounding_quad: BoundingQuad },
AddShaders(Vec<ObjectShader>),
AddPaintData(PaintData),
AddFills(Vec<FillBatchPrimitive>),
FlushFills,
AlphaTile(Vec<AlphaTileBatchPrimitive>),
@ -36,6 +36,12 @@ pub enum RenderCommand {
Finish { build_time: Duration },
}
#[derive(Clone, Debug)]
pub struct PaintData {
pub size: Point2DI32,
pub texels: Vec<u8>,
}
#[derive(Clone, Copy, Debug)]
pub struct FillObjectPrimitive {
pub px: LineSegmentU4,
@ -66,6 +72,8 @@ pub struct FillBatchPrimitive {
pub struct SolidTileBatchPrimitive {
pub tile_x: i16,
pub tile_y: i16,
pub origin_u: u16,
pub origin_v: u16,
pub object_index: u16,
}
@ -78,14 +86,16 @@ pub struct AlphaTileBatchPrimitive {
pub backdrop: i8,
pub object_index: u16,
pub tile_index: u16,
pub origin_u: u16,
pub origin_v: u16,
}
impl Debug for RenderCommand {
fn fmt(&self, formatter: &mut Formatter) -> DebugResult {
match *self {
RenderCommand::Start { .. } => write!(formatter, "Start"),
RenderCommand::AddShaders(ref shaders) => {
write!(formatter, "AddShaders(x{})", shaders.len())
RenderCommand::AddPaintData(ref paint_data) => {
write!(formatter, "AddPaintData({}x{})", paint_data.size.x(), paint_data.size.y())
}
RenderCommand::AddFills(ref fills) => write!(formatter, "AddFills(x{})", fills.len()),
RenderCommand::FlushFills => write!(formatter, "FlushFills"),

View File

@ -17,6 +17,7 @@ pub mod concurrent;
pub mod gpu;
pub mod gpu_data;
pub mod options;
pub mod paint;
pub mod post;
pub mod scene;

51
renderer/src/paint.rs Normal file
View File

@ -0,0 +1,51 @@
// pathfinder/renderer/src/paint.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 crate::gpu_data::PaintData;
use crate::scene::Scene;
use pathfinder_geometry::basic::point::Point2DI32;
use pathfinder_geometry::color::ColorU;
const PAINT_TEXTURE_WIDTH: i32 = 256;
const PAINT_TEXTURE_HEIGHT: i32 = 256;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Paint {
pub color: ColorU,
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct PaintId(pub u16);
impl Paint {
pub(crate) fn is_opaque(&self) -> bool {
self.color.a == 255
}
}
impl Scene {
pub fn build_paint_data(&self) -> PaintData {
let size = Point2DI32::new(PAINT_TEXTURE_WIDTH, PAINT_TEXTURE_HEIGHT);
let mut texels = vec![0; size.x() as usize * size.y() as usize * 4];
for (paint_index, paint) in self.paints.iter().enumerate() {
texels[paint_index * 4 + 0] = paint.color.r;
texels[paint_index * 4 + 1] = paint.color.g;
texels[paint_index * 4 + 2] = paint.color.b;
texels[paint_index * 4 + 3] = paint.color.a;
}
PaintData { size, texels }
}
}
pub(crate) fn paint_id_to_tex_coords(paint_id: PaintId) -> Point2DI32 {
let tex_coords = Point2DI32::new(paint_id.0 as i32 % PAINT_TEXTURE_WIDTH,
paint_id.0 as i32 / PAINT_TEXTURE_WIDTH);
tex_coords.scale(256) + Point2DI32::new(128, 128)
}

View File

@ -14,6 +14,7 @@ use crate::builder::SceneBuilder;
use crate::concurrent::executor::Executor;
use crate::options::{PreparedRenderOptions, PreparedRenderTransform};
use crate::options::{RenderCommandListener, RenderOptions};
use crate::paint::{Paint, PaintId};
use hashbrown::HashMap;
use pathfinder_geometry::basic::point::Point2DF32;
use pathfinder_geometry::basic::rect::RectF32;
@ -25,7 +26,7 @@ use std::io::{self, Write};
#[derive(Clone)]
pub struct Scene {
pub(crate) paths: Vec<PathObject>,
paints: Vec<Paint>,
pub(crate) paints: Vec<Paint>,
paint_cache: HashMap<Paint, PaintId>,
bounds: RectF32,
view_box: RectF32,
@ -85,15 +86,6 @@ impl Scene {
self.view_box = new_view_box;
}
pub fn build_shaders(&self) -> Vec<ObjectShader> {
self.paths
.iter()
.map(|path_object| {
ObjectShader { fill_color: self.paints[path_object.paint.0 as usize].color }
})
.collect()
}
pub(crate) fn apply_render_options(
&self,
original_outline: &Outline,
@ -129,7 +121,7 @@ impl Scene {
};
if options.subpixel_aa_enabled {
transform = transform
.post_mul(&Transform2DF32::from_scale(&Point2DF32::new(3.0, 1.0)))
.post_mul(&Transform2DF32::from_scale(Point2DF32::new(3.0, 1.0)))
}
outline.transform(&transform);
}
@ -225,17 +217,9 @@ impl PathObject {
pub fn outline(&self) -> &Outline {
&self.outline
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Paint {
pub color: ColorU,
#[inline]
pub(crate) fn paint(&self) -> PaintId {
self.paint
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct PaintId(pub u16);
#[derive(Clone, Copy, Debug, Default)]
pub struct ObjectShader {
pub fill_color: ColorU,
}

View File

@ -10,6 +10,7 @@
use crate::builder::SceneBuilder;
use crate::gpu_data::{AlphaTileBatchPrimitive, BuiltObject, TileObjectPrimitive};
use crate::paint::{self, PaintId};
use crate::sorted_vector::SortedVector;
use pathfinder_geometry::basic::line_segment::LineSegmentF32;
use pathfinder_geometry::basic::point::{Point2DF32, Point2DI32};
@ -29,7 +30,9 @@ pub(crate) struct Tiler<'a> {
builder: &'a SceneBuilder<'a>,
outline: &'a Outline,
pub built_object: BuiltObject,
paint_id: PaintId,
object_index: u16,
object_is_opaque: bool,
point_queue: SortedVector<QueuedEndpoint>,
active_edges: SortedVector<ActiveEdge>,
@ -43,6 +46,8 @@ impl<'a> Tiler<'a> {
outline: &'a Outline,
view_box: RectF32,
object_index: u16,
paint_id: PaintId,
object_is_opaque: bool,
) -> Tiler<'a> {
let bounds = outline
.bounds()
@ -55,6 +60,8 @@ impl<'a> Tiler<'a> {
outline,
built_object,
object_index,
paint_id,
object_is_opaque,
point_queue: SortedVector::new(),
active_edges: SortedVector::new(),
@ -107,18 +114,28 @@ impl<'a> Tiler<'a> {
let tile_coords = self
.built_object
.local_tile_index_to_coords(tile_index as u32);
if tile.is_solid() {
if tile.backdrop != 0 {
self.builder.z_buffer.update(tile_coords, self.object_index);
}
// Blank tiles are always skipped.
if tile.backdrop == 0 {
continue;
}
// If this is a solid tile, poke it into the Z-buffer and stop here.
if self.object_is_opaque {
self.builder.z_buffer.update(tile_coords, self.object_index);
continue;
}
}
let origin_uv = paint::paint_id_to_tex_coords(self.paint_id);
let alpha_tile = AlphaTileBatchPrimitive::new(
tile_coords,
tile.backdrop,
self.object_index,
tile.alpha_tile_index as u16,
origin_uv,
);
self.built_object.alpha_tiles.push(alpha_tile);
@ -434,8 +451,7 @@ impl ActiveEdge {
// If necessary, draw initial line.
if self.crossing.y() < segment.baseline.min_y() {
let first_line_segment =
LineSegmentF32::new(&self.crossing, &segment.baseline.upper_point())
.orient(winding);
LineSegmentF32::new(self.crossing, segment.baseline.upper_point()).orient(winding);
if self
.process_line_segment(&first_line_segment, builder, built_object, tile_y)
.is_some()
@ -522,7 +538,8 @@ impl AlphaTileBatchPrimitive {
fn new(tile_coords: Point2DI32,
backdrop: i8,
object_index: u16,
tile_index: u16)
tile_index: u16,
origin_uv: Point2DI32)
-> AlphaTileBatchPrimitive {
AlphaTileBatchPrimitive {
tile_x_lo: (tile_coords.x() & 0xff) as u8,
@ -531,6 +548,8 @@ impl AlphaTileBatchPrimitive {
backdrop,
object_index,
tile_index,
origin_u: origin_uv.x() as u16,
origin_v: origin_uv.y() as u16,
}
}

View File

@ -11,6 +11,8 @@
//! Software occlusion culling.
use crate::gpu_data::SolidTileBatchPrimitive;
use crate::paint;
use crate::scene::PathObject;
use crate::tile_map::DenseTileMap;
use crate::tiles;
use pathfinder_geometry::basic::point::Point2DI32;
@ -54,7 +56,8 @@ impl ZBuffer {
}
}
pub fn build_solid_tiles(&self, object_range: Range<u32>) -> Vec<SolidTileBatchPrimitive> {
pub fn build_solid_tiles(&self, paths: &[PathObject], object_range: Range<u32>)
-> Vec<SolidTileBatchPrimitive> {
let mut solid_tiles = vec![];
for tile_index in 0..self.buffer.data.len() {
let depth = self.buffer.data[tile_index].load(AtomicOrdering::Relaxed);
@ -67,13 +70,27 @@ impl ZBuffer {
if object_index < object_range.start || object_index >= object_range.end {
continue;
}
solid_tiles.push(SolidTileBatchPrimitive {
tile_x: (tile_coords.x() + self.buffer.rect.min_x()) as i16,
tile_y: (tile_coords.y() + self.buffer.rect.min_y()) as i16,
object_index: object_index as u16,
});
let origin_uv = paint::paint_id_to_tex_coords(paths[object_index as usize].paint());
solid_tiles.push(SolidTileBatchPrimitive::new(tile_coords + self.buffer.rect.origin(),
object_index as u16,
origin_uv));
}
solid_tiles
}
}
impl SolidTileBatchPrimitive {
fn new(tile_coords: Point2DI32, object_index: u16, origin_uv: Point2DI32)
-> SolidTileBatchPrimitive {
SolidTileBatchPrimitive {
tile_x: tile_coords.x() as i16,
tile_y: tile_coords.y() as i16,
object_index: object_index,
origin_u: origin_uv.x() as u16,
origin_v: origin_uv.y() as u16,
}
}
}

View File

@ -25,8 +25,8 @@ in vec2 vTexCoord;
out vec4 oFragColor;
{{{include_post_gamma_correct}}}
{{{include_post_convolve}}}
{{include_post_gamma_correct}}
{{include_post_convolve}}
// Convolve horizontally in this pass.
float sample1Tap(float offset) {

View File

@ -12,8 +12,8 @@
precision highp float;
{{{include_tile_alpha_vertex}}}
{{{include_tile_monochrome}}}
{{include_tile_alpha_vertex}}
{{include_tile_monochrome}}
void main() {
computeVaryings();

View File

@ -12,8 +12,8 @@
precision highp float;
{{{include_tile_alpha_vertex}}}
{{{include_tile_multicolor}}}
{{include_tile_alpha_vertex}}
{{include_tile_multicolor}}
void main() {
computeVaryings();

View File

@ -16,14 +16,13 @@ uniform vec2 uViewBoxOrigin;
in vec2 aTessCoord;
in uvec3 aTileOrigin;
in int aBackdrop;
in uint aObject;
in uint aTileIndex;
out vec2 vTexCoord;
out float vBackdrop;
out vec4 vColor;
vec4 getFillColor(uint object);
vec4 getColor();
vec2 computeTileOffset(uint tileIndex, float stencilTextureWidth) {
uint tilesPerRow = uint(stencilTextureWidth / uTileSize.x);
@ -35,11 +34,12 @@ void computeVaryings() {
vec2 origin = vec2(aTileOrigin.xy) + vec2(aTileOrigin.z & 15u, aTileOrigin.z >> 4u) * 256.0;
vec2 pixelPosition = (origin + aTessCoord) * uTileSize + uViewBoxOrigin;
vec2 position = (pixelPosition / uFramebufferSize * 2.0 - 1.0) * vec2(1.0, -1.0);
vec2 texCoord = computeTileOffset(aTileIndex, uStencilTextureSize.x) + aTessCoord * uTileSize;
vec2 maskTexCoordOrigin = computeTileOffset(aTileIndex, uStencilTextureSize.x);
vec2 maskTexCoord = maskTexCoordOrigin + aTessCoord * uTileSize;
vTexCoord = texCoord / uStencilTextureSize;
vTexCoord = maskTexCoord / uStencilTextureSize;
vBackdrop = float(aBackdrop);
vColor = getFillColor(aObject);
vColor = getColor();
gl_Position = vec4(position, 0.0, 1.0);
}

View File

@ -8,8 +8,8 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
uniform vec4 uFillColor;
uniform vec4 uColor;
vec4 getFillColor(uint object) {
return uFillColor;
vec4 getColor() {
return uColor;
}

View File

@ -8,15 +8,11 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
uniform sampler2D uFillColorsTexture;
uniform vec2 uFillColorsTextureSize;
uniform sampler2D uPaintTexture;
uniform vec2 uPaintTextureSize;
vec2 computeFillColorTexCoord(uint object, vec2 textureSize) {
uint width = uint(textureSize.x);
return (vec2(float(object % width), float(object / width)) + vec2(0.5)) / textureSize;
}
in vec2 aColorTexCoord;
vec4 getFillColor(uint object) {
vec2 colorTexCoord = computeFillColorTexCoord(object, uFillColorsTextureSize);
return texture(uFillColorsTexture, colorTexCoord);
vec4 getColor() {
return texture(uPaintTexture, aColorTexCoord);
}

View File

@ -12,8 +12,8 @@
precision highp float;
{{{include_tile_solid_vertex}}}
{{{include_tile_monochrome}}}
{{include_tile_solid_vertex}}
{{include_tile_monochrome}}
void main() {
computeVaryings();

View File

@ -12,8 +12,8 @@
precision highp float;
{{{include_tile_solid_vertex}}}
{{{include_tile_multicolor}}}
{{include_tile_solid_vertex}}
{{include_tile_multicolor}}
void main() {
computeVaryings();

View File

@ -14,16 +14,16 @@ uniform vec2 uViewBoxOrigin;
in vec2 aTessCoord;
in vec2 aTileOrigin;
in uint aObject;
out vec4 vColor;
vec4 getFillColor(uint object);
vec4 getColor();
void computeVaryings() {
vec2 pixelPosition = (aTileOrigin + aTessCoord) * uTileSize + uViewBoxOrigin;
vec2 position = (pixelPosition / uFramebufferSize * 2.0 - 1.0) * vec2(1.0, -1.0);
vColor = getFillColor(aObject);
vColor = getColor();
//vColor = vec4(1.0, 0.0, 0.0, 1.0);
gl_Position = vec4(position, 0.0, 1.0);
}

View File

@ -173,11 +173,6 @@ impl F32x4 {
pub fn concat_wz_yx(self, other: F32x4) -> F32x4 {
F32x4([self[3], self[2], other[1], other[0]])
}
#[inline]
pub fn cross(&self, other: F32x4) -> F32x4 {
unimplemented!()
}
}
impl Index<usize> for F32x4 {

View File

@ -20,13 +20,14 @@ use pathfinder_geometry::basic::transform2d::{Transform2DF32, Transform2DF32Path
use pathfinder_geometry::color::ColorU;
use pathfinder_geometry::outline::Outline;
use pathfinder_geometry::segment::{Segment, SegmentFlags};
use pathfinder_geometry::stroke::OutlineStrokeToFill;
use pathfinder_renderer::scene::{Paint, PathObject, Scene};
use pathfinder_geometry::stroke::{LineCap, LineJoin, OutlineStrokeToFill, StrokeStyle};
use pathfinder_renderer::paint::Paint;
use pathfinder_renderer::scene::{PathObject, Scene};
use std::fmt::{Display, Formatter, Result as FormatResult};
use std::mem;
use usvg::{Color as SvgColor, Node, NodeExt, NodeKind, Paint as UsvgPaint};
use usvg::{PathSegment as UsvgPathSegment, Rect as UsvgRect, Transform as UsvgTransform};
use usvg::{Tree, Visibility};
use usvg::{Color as SvgColor, LineCap as UsvgLineCap, LineJoin as UsvgLineJoin, Node, NodeExt};
use usvg::{NodeKind, Opacity, Paint as UsvgPaint, PathSegment as UsvgPathSegment};
use usvg::{Rect as UsvgRect, Transform as UsvgTransform, Tree, Visibility};
const HAIRLINE_STROKE_WIDTH: f32 = 0.0333;
@ -114,9 +115,11 @@ impl BuiltSVG {
}
NodeKind::Path(ref path) if path.visibility == Visibility::Visible => {
if let Some(ref fill) = path.fill {
let style = self
.scene
.push_paint(&Paint::from_svg_paint(&fill.paint, &mut self.result_flags));
let style = self.scene.push_paint(&Paint::from_svg_paint(
&fill.paint,
fill.opacity,
&mut self.result_flags,
));
let path = UsvgPathToSegments::new(path.segments.iter().cloned());
let path = Transform2DF32PathIter::new(path, &transform);
@ -129,14 +132,20 @@ impl BuiltSVG {
if let Some(ref stroke) = path.stroke {
let style = self.scene.push_paint(&Paint::from_svg_paint(
&stroke.paint,
stroke.opacity,
&mut self.result_flags,
));
let stroke_width = f32::max(stroke.width.value() as f32, HAIRLINE_STROKE_WIDTH);
let stroke_style = StrokeStyle {
line_width: f32::max(stroke.width.value() as f32, HAIRLINE_STROKE_WIDTH),
line_cap: LineCap::from_usvg_line_cap(stroke.linecap),
line_join: LineJoin::from_usvg_line_join(stroke.linejoin),
};
let path = UsvgPathToSegments::new(path.segments.iter().cloned());
let outline = Outline::from_segments(path);
let mut stroke_to_fill = OutlineStrokeToFill::new(outline, stroke_width);
let mut stroke_to_fill = OutlineStrokeToFill::new(outline, stroke_style);
stroke_to_fill.offset();
let mut outline = stroke_to_fill.outline;
outline.transform(&transform);
@ -235,15 +244,17 @@ impl Display for BuildResultFlags {
}
trait PaintExt {
fn from_svg_paint(svg_paint: &UsvgPaint, result_flags: &mut BuildResultFlags) -> Self;
fn from_svg_paint(svg_paint: &UsvgPaint, opacity: Opacity, result_flags: &mut BuildResultFlags)
-> Self;
}
impl PaintExt for Paint {
#[inline]
fn from_svg_paint(svg_paint: &UsvgPaint, result_flags: &mut BuildResultFlags) -> Paint {
fn from_svg_paint(svg_paint: &UsvgPaint, opacity: Opacity, result_flags: &mut BuildResultFlags)
-> Paint {
Paint {
color: match *svg_paint {
UsvgPaint::Color(color) => ColorU::from_svg_color(color),
UsvgPaint::Color(color) => ColorU::from_svg_color(color, opacity),
UsvgPaint::Link(_) => {
// TODO(pcwalton)
result_flags.insert(BuildResultFlags::UNSUPPORTED_LINK_PAINT);
@ -313,8 +324,7 @@ where
}
UsvgPathSegment::LineTo { x, y } => {
let to = Point2DF32::new(x as f32, y as f32);
let mut segment =
Segment::line(&LineSegmentF32::new(&self.last_subpath_point, &to));
let mut segment = Segment::line(&LineSegmentF32::new(self.last_subpath_point, to));
if self.just_moved {
segment.flags.insert(SegmentFlags::FIRST_IN_SUBPATH);
}
@ -334,8 +344,8 @@ where
let ctrl1 = Point2DF32::new(x2 as f32, y2 as f32);
let to = Point2DF32::new(x as f32, y as f32);
let mut segment = Segment::cubic(
&LineSegmentF32::new(&self.last_subpath_point, &to),
&LineSegmentF32::new(&ctrl0, &ctrl1),
&LineSegmentF32::new(self.last_subpath_point, to),
&LineSegmentF32::new(ctrl0, ctrl1),
);
if self.just_moved {
segment.flags.insert(SegmentFlags::FIRST_IN_SUBPATH);
@ -346,8 +356,8 @@ where
}
UsvgPathSegment::ClosePath => {
let mut segment = Segment::line(&LineSegmentF32::new(
&self.last_subpath_point,
&self.first_subpath_point,
self.last_subpath_point,
self.first_subpath_point,
));
segment.flags.insert(SegmentFlags::CLOSES_SUBPATH);
self.just_moved = false;
@ -359,17 +369,53 @@ where
}
trait ColorUExt {
fn from_svg_color(svg_color: SvgColor) -> Self;
fn from_svg_color(svg_color: SvgColor, opacity: Opacity) -> Self;
}
impl ColorUExt for ColorU {
#[inline]
fn from_svg_color(svg_color: SvgColor) -> ColorU {
fn from_svg_color(svg_color: SvgColor, opacity: Opacity) -> ColorU {
ColorU {
r: svg_color.red,
g: svg_color.green,
b: svg_color.blue,
a: 255,
a: (opacity.value() * 255.0).round() as u8,
}
}
}
trait LineCapExt {
fn from_usvg_line_cap(usvg_line_cap: UsvgLineCap) -> Self;
}
impl LineCapExt for LineCap {
#[inline]
fn from_usvg_line_cap(usvg_line_cap: UsvgLineCap) -> LineCap {
match usvg_line_cap {
UsvgLineCap::Butt => LineCap::Butt,
UsvgLineCap::Round => {
// TODO(pcwalton)
LineCap::Square
}
UsvgLineCap::Square => LineCap::Square,
}
}
}
trait LineJoinExt {
fn from_usvg_line_join(usvg_line_join: UsvgLineJoin) -> Self;
}
impl LineJoinExt for LineJoin {
#[inline]
fn from_usvg_line_join(usvg_line_join: UsvgLineJoin) -> LineJoin {
match usvg_line_join {
UsvgLineJoin::Miter => LineJoin::Miter,
UsvgLineJoin::Round => {
// TODO(pcwalton)
LineJoin::Miter
}
UsvgLineJoin::Bevel => LineJoin::Bevel,
}
}
}

View File

@ -17,4 +17,4 @@ path = "../renderer"
[dependencies.skribo]
git = "https://github.com/linebender/skribo.git"
rev = "f759c2d2c188b668bce998179f03b6319835353e"
rev = "a89e9ca99e0d6736ea1b7754517f4df14fd96a2b"

View File

@ -16,8 +16,9 @@ use lyon_path::builder::{FlatPathBuilder, PathBuilder};
use pathfinder_geometry::basic::point::Point2DF32;
use pathfinder_geometry::basic::transform2d::Transform2DF32;
use pathfinder_geometry::outline::{Contour, Outline};
use pathfinder_geometry::stroke::OutlineStrokeToFill;
use pathfinder_renderer::scene::{PaintId, PathObject, Scene};
use pathfinder_geometry::stroke::{OutlineStrokeToFill, StrokeStyle};
use pathfinder_renderer::paint::PaintId;
use pathfinder_renderer::scene::{PathObject, Scene};
use skribo::{FontCollection, Layout, TextStyle};
use std::mem;
@ -68,8 +69,8 @@ impl SceneExt for Scene {
font.outline(glyph_id, hinting_options, &mut outline_builder)?;
let mut outline = outline_builder.build();
if let TextRenderMode::Stroke(stroke_width) = render_mode {
let mut stroke_to_fill = OutlineStrokeToFill::new(outline, stroke_width);
if let TextRenderMode::Stroke(stroke_style) = render_mode {
let mut stroke_to_fill = OutlineStrokeToFill::new(outline, stroke_style);
stroke_to_fill.offset();
outline = stroke_to_fill.outline;
}
@ -93,7 +94,7 @@ impl SceneExt for Scene {
let scale = style.size / (font.metrics().units_per_em as f32);
let scale = Point2DF32::new(scale, -scale);
let transform =
Transform2DF32::from_scale(&scale).post_mul(transform).post_translate(offset);
Transform2DF32::from_scale(scale).post_mul(transform).post_translate(offset);
self.push_glyph(font,
glyph.glyph_id,
&transform,
@ -122,7 +123,7 @@ impl SceneExt for Scene {
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum TextRenderMode {
Fill,
Stroke(f32),
Stroke(StrokeStyle),
}
struct OutlinePathBuilder {
@ -147,7 +148,7 @@ impl OutlinePathBuilder {
}
fn convert_point(&self, point: Point2D<f32>) -> Point2DF32 {
self.transform.transform_point(&Point2DF32::new(point.x, point.y))
self.transform.transform_point(Point2DF32::new(point.x, point.y))
}
}

View File

@ -22,7 +22,8 @@ use pathfinder_geometry::basic::rect::RectI32;
use pathfinder_geometry::color::ColorU;
use pathfinder_gpu::resources::ResourceLoader;
use pathfinder_gpu::{BlendState, BufferData, BufferTarget, BufferUploadMode, Device, Primitive};
use pathfinder_gpu::{RenderState, UniformData, VertexAttrType};
use pathfinder_gpu::{RenderState, UniformData, VertexAttrClass};
use pathfinder_gpu::{VertexAttrDescriptor, VertexAttrType};
use pathfinder_simd::default::F32x4;
use serde_json;
use std::mem;
@ -591,20 +592,22 @@ impl<D> DebugTextureVertexArray<D> where D: Device {
device.use_program(&debug_texture_program.program);
device.bind_buffer(&vertex_buffer, BufferTarget::Vertex);
device.bind_buffer(&index_buffer, BufferTarget::Index);
device.configure_float_vertex_attr(&position_attr,
2,
VertexAttrType::U16,
false,
DEBUG_TEXTURE_VERTEX_SIZE,
0,
0);
device.configure_float_vertex_attr(&tex_coord_attr,
2,
VertexAttrType::U16,
false,
DEBUG_TEXTURE_VERTEX_SIZE,
4,
0);
device.configure_vertex_attr(&position_attr, &VertexAttrDescriptor {
size: 2,
class: VertexAttrClass::Float,
attr_type: VertexAttrType::U16,
stride: DEBUG_TEXTURE_VERTEX_SIZE,
offset: 0,
divisor: 0,
});
device.configure_vertex_attr(&tex_coord_attr, &VertexAttrDescriptor {
size: 2,
class: VertexAttrClass::Float,
attr_type: VertexAttrType::U16,
stride: DEBUG_TEXTURE_VERTEX_SIZE,
offset: 4,
divisor: 0,
});
DebugTextureVertexArray { vertex_array, vertex_buffer, index_buffer }
}
@ -626,13 +629,14 @@ impl<D> DebugSolidVertexArray<D> where D: Device {
device.use_program(&debug_solid_program.program);
device.bind_buffer(&vertex_buffer, BufferTarget::Vertex);
device.bind_buffer(&index_buffer, BufferTarget::Index);
device.configure_float_vertex_attr(&position_attr,
2,
VertexAttrType::U16,
false,
DEBUG_SOLID_VERTEX_SIZE,
0,
0);
device.configure_vertex_attr(&position_attr, &VertexAttrDescriptor {
size: 2,
class: VertexAttrClass::Float,
attr_type: VertexAttrType::U16,
stride: DEBUG_SOLID_VERTEX_SIZE,
offset: 0,
divisor: 0,
});
DebugSolidVertexArray { vertex_array, vertex_buffer, index_buffer }
}