mirror of
https://github.com/bevyengine/bevy
synced 2024-11-25 06:00:20 +00:00
remove pathfinder code
its not ready yet so it shouldnt be on master :)
This commit is contained in:
parent
44717c7b10
commit
73cc20768c
302 changed files with 3 additions and 90838 deletions
56
.vscode/launch.json
vendored
56
.vscode/launch.json
vendored
|
@ -819,25 +819,6 @@
|
|||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug unit tests in library 'bevy_pathfinder'",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"test",
|
||||
"--no-run",
|
||||
"--lib",
|
||||
"--package=bevy_pathfinder"
|
||||
],
|
||||
"filter": {
|
||||
"name": "bevy_pathfinder",
|
||||
"kind": "lib"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
|
@ -989,43 +970,6 @@
|
|||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug example 'pathfinder'",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"build",
|
||||
"--example=pathfinder",
|
||||
"--package=bevy"
|
||||
],
|
||||
"filter": {
|
||||
"name": "pathfinder",
|
||||
"kind": "example"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug unit tests in example 'pathfinder'",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"test",
|
||||
"--no-run",
|
||||
"--example=pathfinder",
|
||||
"--package=bevy"
|
||||
],
|
||||
"filter": {
|
||||
"name": "pathfinder",
|
||||
"kind": "example"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
|
|
12
Cargo.toml
12
Cargo.toml
|
@ -6,14 +6,13 @@ edition = "2018"
|
|||
|
||||
[features]
|
||||
default = ["headless", "wgpu", "winit"]
|
||||
headless = ["asset", "core", "derive", "diagnostic", "gltf", "input", "pathfinder", "pbr", "render", "serialization", "transform", "ui", "window"]
|
||||
headless = ["asset", "core", "derive", "diagnostic", "gltf", "input", "pbr", "render", "serialization", "transform", "ui", "window"]
|
||||
asset = ["bevy_asset"]
|
||||
core = ["bevy_core"]
|
||||
derive = ["bevy_derive"]
|
||||
diagnostic = ["bevy_diagnostic"]
|
||||
gltf = ["bevy_gltf"]
|
||||
input = ["bevy_input"]
|
||||
pathfinder = ["bevy_pathfinder"]
|
||||
pbr = ["bevy_pbr"]
|
||||
render = ["bevy_render"]
|
||||
serialization = ["bevy_serialization"]
|
||||
|
@ -29,10 +28,6 @@ members = [
|
|||
"crates/*",
|
||||
"examples/app/dynamic_plugin_loading/example_plugin"
|
||||
]
|
||||
exclude = [
|
||||
"crates/pathfinder"
|
||||
]
|
||||
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
|
@ -43,7 +38,6 @@ bevy_derive = { path = "crates/bevy_derive", optional = true }
|
|||
bevy_diagnostic = { path = "crates/bevy_diagnostic", optional = true }
|
||||
bevy_gltf = { path = "crates/bevy_gltf", optional = true }
|
||||
bevy_input = { path = "crates/bevy_input", optional = true }
|
||||
bevy_pathfinder = { path = "crates/bevy_pathfinder", optional = true }
|
||||
bevy_pbr = { path = "crates/bevy_pbr", optional = true }
|
||||
bevy_render = { path = "crates/bevy_render", optional = true }
|
||||
bevy_serialization = { path = "crates/bevy_serialization", optional = true }
|
||||
|
@ -71,10 +65,6 @@ opt-level = 3
|
|||
name = "hello_world"
|
||||
path = "examples/hello_world.rs"
|
||||
|
||||
[[example]]
|
||||
name = "pathfinder"
|
||||
path = "examples/2d/pathfinder.rs"
|
||||
|
||||
[[example]]
|
||||
name = "sprite"
|
||||
path = "examples/2d/sprite.rs"
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
[package]
|
||||
name = "bevy_pathfinder"
|
||||
version = "0.1.0"
|
||||
authors = ["Carter Anderson <mcanders1@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bevy_app = { path = "../bevy_app" }
|
||||
bevy_asset = { path = "../bevy_asset" }
|
||||
bevy_render = { path = "../bevy_render" }
|
||||
legion = { path = "../bevy_legion" }
|
||||
|
||||
pathfinder_geometry = { path = "../pathfinder/geometry", features = ["shader_alignment_32_bits"] }
|
||||
pathfinder_gpu = { path = "../pathfinder/gpu", features = ["shader_alignment_32_bits"] }
|
||||
pathfinder_renderer = { path = "../pathfinder/renderer" }
|
||||
pathfinder_simd = { path = "../pathfinder/simd" }
|
||||
pathfinder_resources = { path = "../pathfinder/resources" }
|
||||
pathfinder_color = { path = "../pathfinder/color" }
|
||||
pathfinder_canvas = { path = "../pathfinder/canvas" }
|
||||
|
||||
zerocopy = "0.3.0"
|
||||
byteorder = "1.3"
|
File diff suppressed because it is too large
Load diff
|
@ -1,39 +0,0 @@
|
|||
mod device;
|
||||
mod pathfinder_node;
|
||||
use bevy_app::{AppBuilder, AppPlugin};
|
||||
pub use device::*;
|
||||
|
||||
use bevy_render::{
|
||||
base_render_graph,
|
||||
render_graph::{
|
||||
nodes::{WindowSwapChainNode, WindowTextureNode},
|
||||
RenderGraph,
|
||||
},
|
||||
};
|
||||
use pathfinder_node::PathfinderNode;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PathfinderPlugin;
|
||||
|
||||
impl AppPlugin for PathfinderPlugin {
|
||||
fn build(&self, app: &mut AppBuilder) {
|
||||
let mut render_graph = app.resources().get_mut::<RenderGraph>().unwrap();
|
||||
render_graph.add_node_named("pathfinder", PathfinderNode::default());
|
||||
render_graph
|
||||
.add_slot_edge(
|
||||
base_render_graph::node::PRIMARY_SWAP_CHAIN,
|
||||
WindowSwapChainNode::OUT_TEXTURE,
|
||||
"pathfinder",
|
||||
PathfinderNode::IN_COLOR_TEXTURE,
|
||||
)
|
||||
.unwrap();
|
||||
render_graph
|
||||
.add_slot_edge(
|
||||
base_render_graph::node::MAIN_DEPTH_TEXTURE,
|
||||
WindowTextureNode::OUT_TEXTURE,
|
||||
"pathfinder",
|
||||
PathfinderNode::IN_DEPTH_STENCIL_TEXTURE,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
use crate::BevyPathfinderDevice;
|
||||
use bevy_asset::AssetStorage;
|
||||
use bevy_render::{
|
||||
render_graph::{Node, ResourceSlotInfo, ResourceSlots},
|
||||
render_resource::ResourceInfo,
|
||||
renderer::RenderContext,
|
||||
shader::{FieldBindType, Shader},
|
||||
};
|
||||
use legion::prelude::{Resources, World};
|
||||
use pathfinder_canvas::{vec2f, Canvas, CanvasFontContext, ColorF, Path2D, RectF, Vector2I};
|
||||
use pathfinder_renderer::{
|
||||
concurrent::{rayon::RayonExecutor, scene_proxy::SceneProxy},
|
||||
gpu::{
|
||||
options::{DestFramebuffer, RendererOptions},
|
||||
renderer::Renderer,
|
||||
},
|
||||
options::BuildOptions,
|
||||
};
|
||||
use pathfinder_resources::embedded::EmbeddedResourceLoader;
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PathfinderNode;
|
||||
|
||||
impl PathfinderNode {
|
||||
pub const IN_COLOR_TEXTURE: &'static str = "color";
|
||||
pub const IN_DEPTH_STENCIL_TEXTURE: &'static str = "depth_stencil";
|
||||
}
|
||||
|
||||
impl Node for PathfinderNode {
|
||||
fn input(&self) -> &[ResourceSlotInfo] {
|
||||
static INPUT: &[ResourceSlotInfo] = &[
|
||||
ResourceSlotInfo {
|
||||
name: Cow::Borrowed(PathfinderNode::IN_COLOR_TEXTURE),
|
||||
resource_type: FieldBindType::Texture,
|
||||
},
|
||||
ResourceSlotInfo {
|
||||
name: Cow::Borrowed(PathfinderNode::IN_DEPTH_STENCIL_TEXTURE),
|
||||
resource_type: FieldBindType::Texture,
|
||||
},
|
||||
];
|
||||
INPUT
|
||||
}
|
||||
fn update(
|
||||
&mut self,
|
||||
_world: &World,
|
||||
resources: &Resources,
|
||||
render_context: &mut dyn RenderContext,
|
||||
input: &ResourceSlots,
|
||||
_output: &mut ResourceSlots,
|
||||
) {
|
||||
let mut shaders = resources.get_mut::<AssetStorage<Shader>>().unwrap();
|
||||
let color_texture = input.get(PathfinderNode::IN_COLOR_TEXTURE).unwrap();
|
||||
let depth_stencil_texture = input.get(PathfinderNode::IN_DEPTH_STENCIL_TEXTURE).unwrap();
|
||||
let device = BevyPathfinderDevice::new(
|
||||
render_context,
|
||||
&mut shaders,
|
||||
color_texture,
|
||||
depth_stencil_texture,
|
||||
);
|
||||
let window_size = Vector2I::new(1280 as i32, 720 as i32);
|
||||
let mut renderer = Renderer::new(
|
||||
device,
|
||||
&EmbeddedResourceLoader::new(),
|
||||
DestFramebuffer::full_window(window_size),
|
||||
RendererOptions {
|
||||
background_color: Some(ColorF::white()),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
// Make a canvas. We're going to draw a house.
|
||||
let mut canvas = Canvas::new(window_size.to_f32())
|
||||
.get_context_2d(CanvasFontContext::from_system_source());
|
||||
|
||||
// Set line width.
|
||||
canvas.set_line_width(10.0);
|
||||
|
||||
// Draw walls.
|
||||
canvas.stroke_rect(RectF::new(vec2f(75.0, 140.0), vec2f(150.0, 110.0)));
|
||||
|
||||
// Draw door.
|
||||
canvas.fill_rect(RectF::new(vec2f(130.0, 190.0), vec2f(40.0, 60.0)));
|
||||
|
||||
// Draw roof.
|
||||
let mut path = Path2D::new();
|
||||
path.move_to(vec2f(50.0, 140.0));
|
||||
path.line_to(vec2f(150.0, 60.0));
|
||||
path.line_to(vec2f(250.0, 140.0));
|
||||
path.close_path();
|
||||
canvas.stroke_path(path);
|
||||
|
||||
// Render the canvas to screen.
|
||||
let scene = SceneProxy::from_scene(canvas.into_canvas().into_scene(), RayonExecutor);
|
||||
scene.build_and_render(&mut renderer, BuildOptions::default());
|
||||
// TODO: submit command buffers?
|
||||
}
|
||||
}
|
|
@ -314,7 +314,6 @@ mod tests {
|
|||
use super::RenderGraph;
|
||||
use crate::{
|
||||
render_graph::{Edge, Node, NodeId, RenderGraphError, ResourceSlotInfo, ResourceSlots},
|
||||
render_resource::ResourceInfo,
|
||||
renderer::RenderContext, shader::FieldBindType,
|
||||
};
|
||||
use legion::prelude::{Resources, World};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use super::RenderGraphError;
|
||||
use crate::{shader::FieldBindType, render_resource::{RenderResource, ResourceInfo}};
|
||||
use crate::{render_resource::RenderResource, shader::FieldBindType};
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
|
@ -3,7 +3,7 @@ use crate::{
|
|||
pass::{PassDescriptor, TextureAttachment},
|
||||
pipeline::{PipelineCompiler, PipelineDescriptor},
|
||||
render_graph::{Node, ResourceSlotInfo, ResourceSlots},
|
||||
render_resource::{RenderResourceAssignments, ResourceInfo},
|
||||
render_resource::RenderResourceAssignments,
|
||||
renderer::RenderContext,
|
||||
shader::{FieldBindType, Shader},
|
||||
};
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use crate::{
|
||||
render_graph::{Node, ResourceSlotInfo, ResourceSlots},
|
||||
render_resource::ResourceInfo,
|
||||
renderer::RenderContext, shader::FieldBindType,
|
||||
};
|
||||
use bevy_app::{EventReader, Events};
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use crate::{
|
||||
render_graph::{Node, ResourceSlotInfo, ResourceSlots},
|
||||
render_resource::ResourceInfo,
|
||||
renderer::RenderContext,
|
||||
texture::TextureDescriptor, shader::FieldBindType,
|
||||
};
|
||||
|
|
|
@ -264,7 +264,6 @@ mod tests {
|
|||
use super::{DependentNodeStager, OrderedJob, RenderGraphStager, Stage};
|
||||
use crate::{
|
||||
render_graph::{Node, NodeId, RenderGraph, ResourceSlotInfo, ResourceSlots},
|
||||
render_resource::ResourceInfo,
|
||||
renderer::RenderContext, shader::FieldBindType,
|
||||
};
|
||||
use legion::prelude::{Resources, World};
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"c",
|
||||
"canvas",
|
||||
"color",
|
||||
"content",
|
||||
"demo/android/rust",
|
||||
"demo/common",
|
||||
"demo/magicleap",
|
||||
"demo/native",
|
||||
"examples/canvas_glutin_minimal",
|
||||
"examples/canvas_metal_minimal",
|
||||
"examples/canvas_minimal",
|
||||
"examples/canvas_moire",
|
||||
"examples/canvas_nanovg",
|
||||
"examples/canvas_text",
|
||||
"examples/lottie_basic",
|
||||
"examples/swf_basic",
|
||||
"geometry",
|
||||
"gl",
|
||||
"gpu",
|
||||
"lottie",
|
||||
"export",
|
||||
"metal",
|
||||
"renderer",
|
||||
"resources",
|
||||
"simd",
|
||||
"svg",
|
||||
"swf",
|
||||
"text",
|
||||
"ui",
|
||||
"utils/area-lut",
|
||||
"utils/gamma-lut",
|
||||
"utils/svg-to-skia",
|
||||
"utils/convert",
|
||||
"webgl",
|
||||
]
|
||||
|
||||
default-members = [
|
||||
"c",
|
||||
"canvas",
|
||||
"content",
|
||||
"demo/common",
|
||||
"demo/native",
|
||||
"examples/canvas_glutin_minimal",
|
||||
"examples/canvas_minimal",
|
||||
"examples/canvas_moire",
|
||||
"examples/canvas_text",
|
||||
"examples/lottie_basic",
|
||||
"examples/swf_basic",
|
||||
"geometry",
|
||||
"gl",
|
||||
"gpu",
|
||||
"lottie",
|
||||
"export",
|
||||
"renderer",
|
||||
"simd",
|
||||
"svg",
|
||||
"swf",
|
||||
"text",
|
||||
"ui",
|
||||
"utils/area-lut",
|
||||
"utils/gamma-lut",
|
||||
"utils/svg-to-skia",
|
||||
"utils/convert",
|
||||
]
|
||||
|
||||
[patch.crates-io]
|
||||
pathfinder_geometry = { path = "geometry" }
|
||||
pathfinder_simd = { path = "simd" }
|
|
@ -1,201 +0,0 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -1,25 +0,0 @@
|
|||
Copyright (c) 2010 The Rust Project Developers
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
|
@ -1,109 +0,0 @@
|
|||
# Pathfinder 3
|
||||
|
||||
![Logo](https://github.com/servo/pathfinder/raw/master/resources/textures/pathfinder-logo.png)
|
||||
|
||||
Pathfinder 3 is a fast, practical, GPU-based rasterizer for fonts and vector graphics using OpenGL
|
||||
3.0+, OpenGL ES 3.0+, WebGL 2, and Metal.
|
||||
|
||||
Please note that Pathfinder is under heavy development and is incomplete in various areas.
|
||||
|
||||
## Quick start
|
||||
|
||||
Pathfinder contains a library that implements a subset of the
|
||||
[HTML canvas API](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API). You can quickly add
|
||||
vector rendering to any Rust app with it. The library is available on `crates.io`. See
|
||||
`examples/canvas_minimal` for a small example of usage.
|
||||
|
||||
### Demos
|
||||
|
||||
Demo app sources are available in [demo/](https://github.com/servo/pathfinder/tree/master/demo). A prebuilt package for Magic Leap can be found in [releases](https://github.com/servo/pathfinder/releases).
|
||||
|
||||
## Features
|
||||
|
||||
The project features:
|
||||
|
||||
* High quality antialiasing. Pathfinder can compute exact fractional trapezoidal area coverage on a
|
||||
per-pixel basis for the highest-quality antialiasing possible (effectively 256xAA).
|
||||
|
||||
* Fast CPU setup, making full use of parallelism. Pathfinder 3 uses the Rayon library to quickly
|
||||
perform a CPU tiling prepass to prepare vector scenes for the GPU. This prepass can be pipelined
|
||||
with the GPU to hide its latency.
|
||||
|
||||
* Fast GPU rendering, even at small pixel sizes. Even on lower-end GPUs, Pathfinder typically
|
||||
matches or exceeds the performance of the best CPU rasterizers. The difference is particularly
|
||||
pronounced at large sizes, where Pathfinder regularly achieves multi-factor speedups.
|
||||
|
||||
* GPU compute-based rendering, where available. Pathfinder can optionally use compute shaders to
|
||||
achieve better performance than what the built-in GPU rasterization hardware can provide. Compute
|
||||
shader capability is not required, and all features are available without it.
|
||||
|
||||
* Advanced font rendering. Pathfinder can render fonts with slight hinting and can perform subpixel
|
||||
antialiasing on LCD screens. It can do stem darkening/font dilation like macOS and FreeType in
|
||||
order to make text easier to read at small sizes. The library also has support for gamma
|
||||
correction.
|
||||
|
||||
* Support for SVG. Pathfinder 3 is designed to efficiently handle workloads that consist of many
|
||||
overlapping vector paths, such as those commonly found in SVG and PDF files. It can perform
|
||||
occlusion culling, which often results in dramatic performance wins over typical software
|
||||
renderers that use the painter's algorithm. A simple loader that leverages the `resvg` library
|
||||
to render a subset of SVG is included, so it's easy to get started.
|
||||
|
||||
* 3D capability. Pathfinder can render fonts and vector paths in 3D environments without any loss
|
||||
in quality. This is intended to be useful for vector-graphics-based user interfaces in VR, for
|
||||
example.
|
||||
|
||||
* Lightweight. Unlike large vector graphics packages that mix and match many different algorithms,
|
||||
Pathfinder 3 uses a single, simple technique. It consists of a set of modular crates, so
|
||||
applications can pick and choose only the components that are necessary to minimize dependencies.
|
||||
|
||||
* Portability to most GPUs manufactured in the last decade, including integrated and mobile GPUs.
|
||||
Geometry, tessellation, and compute shader functionality is not required.
|
||||
|
||||
## Building
|
||||
|
||||
Pathfinder 3 is a set of modular packages, allowing you to choose which parts of the library you
|
||||
need. An SVG rendering demo, written in Rust, is included, so you can try Pathfinder out right
|
||||
away. It also provides an example of how to use the library. (Note that, like the rest of
|
||||
Pathfinder, the demo is under heavy development and has known bugs.)
|
||||
|
||||
Running the demo is as simple as:
|
||||
|
||||
$ cd demo/native
|
||||
$ cargo run --release
|
||||
|
||||
Running examples (e.g. `canvas_nanovg`) can be done with:
|
||||
|
||||
$ cd examples/canvas_nanovg
|
||||
$ cargo run --release
|
||||
|
||||
Pathfinder libraries are available on `crates.io` with the `pathfinder_` prefix (e.g.
|
||||
`pathfinder_canvas`), but you may wish to use the `master` branch for the latest features and bug
|
||||
fixes.
|
||||
|
||||
## Community
|
||||
|
||||
There's a Matrix chat room available at
|
||||
[`#pathfinder:mozilla.org`](https://matrix.to/#/!XiDASQfNTTMrJbXHTw:mozilla.org?via=mozilla.org).
|
||||
If you're on the Mozilla Matrix server, you can search for Pathfinder to find it. For more
|
||||
information on connecting to the Matrix network, see
|
||||
[this `wiki.mozilla.org` page](https://wiki.mozilla.org/Matrix).
|
||||
|
||||
The entire Pathfinder community, including the chat room and GitHub project, is expected to abide
|
||||
by the same Code of Conduct that the Rust project itself follows.
|
||||
|
||||
## Build status
|
||||
|
||||
[![Build Status](https://travis-ci.org/servo/pathfinder.svg?branch=master)](https://travis-ci.org/servo/pathfinder)
|
||||
|
||||
## Authors
|
||||
|
||||
The primary author is Patrick Walton (@pcwalton), with contributions from the Servo development
|
||||
community.
|
||||
|
||||
The logo was designed by Jay Vining.
|
||||
|
||||
## License
|
||||
|
||||
Pathfinder is licensed under the same terms as Rust itself. See `LICENSE-APACHE` and `LICENSE-MIT`.
|
||||
|
||||
Material Design icons are copyright Google Inc. and licensed under the Apache 2.0 license.
|
|
@ -1,44 +0,0 @@
|
|||
[package]
|
||||
name = "pathfinder_canvas"
|
||||
version = "0.5.0"
|
||||
edition = "2018"
|
||||
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
|
||||
description = "A GPU-accelerated vector graphics renderer that works like HTML canvas"
|
||||
license = "MIT/Apache-2.0"
|
||||
repository = "https://github.com/servo/pathfinder"
|
||||
homepage = "https://github.com/servo/pathfinder"
|
||||
keywords = ["pathfinder", "canvas", "vector", "graphics", "gpu"]
|
||||
|
||||
[lib]
|
||||
crate-type = ["rlib", "staticlib"]
|
||||
|
||||
[dependencies]
|
||||
font-kit = { version = "0.6", optional = true }
|
||||
|
||||
[dependencies.pathfinder_color]
|
||||
path = "../color"
|
||||
version = "0.5"
|
||||
|
||||
[dependencies.pathfinder_content]
|
||||
path = "../content"
|
||||
version = "0.5"
|
||||
|
||||
[dependencies.pathfinder_geometry]
|
||||
path = "../geometry"
|
||||
version = "0.5"
|
||||
|
||||
[dependencies.pathfinder_renderer]
|
||||
path = "../renderer"
|
||||
version = "0.5"
|
||||
|
||||
[dependencies.pathfinder_text]
|
||||
path = "../text"
|
||||
version = "0.5"
|
||||
optional = true
|
||||
|
||||
[dependencies.skribo]
|
||||
version = "0.1"
|
||||
optional = true
|
||||
|
||||
[features]
|
||||
pf-text = ["pathfinder_text", "skribo", "font-kit"]
|
|
@ -1,959 +0,0 @@
|
|||
// pathfinder/canvas/src/lib.rs
|
||||
//
|
||||
// Copyright © 2020 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.
|
||||
|
||||
//! A simple API for Pathfinder that mirrors a subset of HTML canvas.
|
||||
|
||||
pub use pathfinder_color::{ColorF, ColorU, rgbaf, rgbau, rgbf, rgbu};
|
||||
pub use pathfinder_color::{color_slice_to_u8_slice, u8_slice_to_color_slice, u8_vec_to_color_vec};
|
||||
pub use pathfinder_content::fill::FillRule;
|
||||
pub use pathfinder_content::stroke::LineCap;
|
||||
pub use pathfinder_content::outline::ArcDirection;
|
||||
pub use pathfinder_geometry::rect::{RectF, RectI};
|
||||
pub use pathfinder_geometry::transform2d::Transform2F;
|
||||
pub use pathfinder_geometry::vector::{IntoVector2F, Vector2F, Vector2I, vec2f, vec2i};
|
||||
|
||||
use pathfinder_content::dash::OutlineDash;
|
||||
use pathfinder_content::effects::{BlendMode, BlurDirection, PatternFilter};
|
||||
use pathfinder_content::gradient::Gradient;
|
||||
use pathfinder_content::outline::{Contour, Outline};
|
||||
use pathfinder_content::pattern::Pattern;
|
||||
use pathfinder_content::render_target::RenderTargetId;
|
||||
use pathfinder_content::stroke::{LineJoin as StrokeLineJoin};
|
||||
use pathfinder_content::stroke::{OutlineStrokeToFill, StrokeStyle};
|
||||
use pathfinder_geometry::line_segment::LineSegment2F;
|
||||
use pathfinder_renderer::paint::{Paint, PaintCompositeOp};
|
||||
use pathfinder_renderer::scene::{ClipPath, ClipPathId, DrawPath, RenderTarget, Scene};
|
||||
use std::borrow::Cow;
|
||||
use std::default::Default;
|
||||
use std::f32::consts::PI;
|
||||
use std::f32;
|
||||
use std::fmt::{Debug, Error as FmtError, Formatter};
|
||||
use std::mem;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use text::CanvasFontContext;
|
||||
|
||||
#[cfg(feature = "pf-text")]
|
||||
use skribo::FontCollection;
|
||||
#[cfg(not(feature = "pf-text"))]
|
||||
use crate::text::FontCollection;
|
||||
|
||||
#[cfg(feature = "pf-text")]
|
||||
pub use text::TextMetrics;
|
||||
|
||||
const HAIRLINE_STROKE_WIDTH: f32 = 0.0333;
|
||||
const DEFAULT_FONT_SIZE: f32 = 10.0;
|
||||
|
||||
#[cfg(feature = "pf-text")]
|
||||
mod text;
|
||||
|
||||
// For users who don't want text capability, include a tiny convenience stub.
|
||||
#[cfg(not(feature = "pf-text"))]
|
||||
mod text {
|
||||
#[derive(Clone)]
|
||||
pub struct CanvasFontContext;
|
||||
|
||||
impl CanvasFontContext {
|
||||
pub fn from_system_source() -> Self {
|
||||
CanvasFontContext
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FontCollection;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub struct Canvas {
|
||||
scene: Scene,
|
||||
}
|
||||
|
||||
impl Canvas {
|
||||
#[inline]
|
||||
pub fn new(size: Vector2F) -> Canvas {
|
||||
let mut scene = Scene::new();
|
||||
scene.set_view_box(RectF::new(Vector2F::zero(), size));
|
||||
Canvas::from_scene(scene)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_scene(scene: Scene) -> Canvas {
|
||||
Canvas { scene }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn into_scene(self) -> Scene {
|
||||
self.scene
|
||||
}
|
||||
|
||||
pub fn get_context_2d(self, canvas_font_context: CanvasFontContext)
|
||||
-> CanvasRenderingContext2D {
|
||||
#[cfg(feature = "pf-text")]
|
||||
let default_font_collection =
|
||||
canvas_font_context.0.borrow().default_font_collection.clone();
|
||||
#[cfg(not(feature = "pf-text"))]
|
||||
let default_font_collection = Arc::new(FontCollection);
|
||||
CanvasRenderingContext2D {
|
||||
canvas: self,
|
||||
current_state: State::default(default_font_collection),
|
||||
saved_states: vec![],
|
||||
canvas_font_context,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn size(&self) -> Vector2I {
|
||||
self.scene.view_box().size().ceil().to_i32()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CanvasRenderingContext2D {
|
||||
canvas: Canvas,
|
||||
current_state: State,
|
||||
saved_states: Vec<State>,
|
||||
#[allow(dead_code)]
|
||||
canvas_font_context: CanvasFontContext,
|
||||
}
|
||||
|
||||
impl CanvasRenderingContext2D {
|
||||
// Canvas accessors
|
||||
|
||||
#[inline]
|
||||
pub fn canvas(&self) -> &Canvas {
|
||||
&self.canvas
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn into_canvas(self) -> Canvas {
|
||||
self.canvas
|
||||
}
|
||||
|
||||
// Drawing rectangles
|
||||
|
||||
#[inline]
|
||||
pub fn fill_rect(&mut self, rect: RectF) {
|
||||
let mut path = Path2D::new();
|
||||
path.rect(rect);
|
||||
self.fill_path(path, FillRule::Winding);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn stroke_rect(&mut self, rect: RectF) {
|
||||
let mut path = Path2D::new();
|
||||
path.rect(rect);
|
||||
self.stroke_path(path);
|
||||
}
|
||||
|
||||
pub fn clear_rect(&mut self, rect: RectF) {
|
||||
let mut path = Path2D::new();
|
||||
path.rect(rect);
|
||||
|
||||
let paint = Paint::transparent_black();
|
||||
let paint = self.current_state.resolve_paint(&paint);
|
||||
let paint_id = self.canvas.scene.push_paint(&paint);
|
||||
|
||||
let mut outline = path.into_outline();
|
||||
outline.transform(&self.current_state.transform);
|
||||
|
||||
let mut path = DrawPath::new(outline, paint_id);
|
||||
path.set_blend_mode(BlendMode::Clear);
|
||||
self.canvas.scene.push_path(path);
|
||||
}
|
||||
|
||||
// Line styles
|
||||
|
||||
#[inline]
|
||||
pub fn set_line_width(&mut self, new_line_width: f32) {
|
||||
self.current_state.line_width = new_line_width
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_line_cap(&mut self, new_line_cap: LineCap) {
|
||||
self.current_state.line_cap = new_line_cap
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_line_join(&mut self, new_line_join: LineJoin) {
|
||||
self.current_state.line_join = new_line_join
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_miter_limit(&mut self, new_miter_limit: f32) {
|
||||
self.current_state.miter_limit = new_miter_limit
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_line_dash(&mut self, mut new_line_dash: Vec<f32>) {
|
||||
// Duplicate and concatenate if an odd number of dashes are present.
|
||||
if new_line_dash.len() % 2 == 1 {
|
||||
let mut real_line_dash = new_line_dash.clone();
|
||||
real_line_dash.extend(new_line_dash.into_iter());
|
||||
new_line_dash = real_line_dash;
|
||||
}
|
||||
|
||||
self.current_state.line_dash = new_line_dash
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_line_dash_offset(&mut self, new_line_dash_offset: f32) {
|
||||
self.current_state.line_dash_offset = new_line_dash_offset
|
||||
}
|
||||
|
||||
// Fill and stroke styles
|
||||
|
||||
#[inline]
|
||||
pub fn set_fill_style<FS>(&mut self, new_fill_style: FS) where FS: Into<FillStyle> {
|
||||
self.current_state.fill_paint = new_fill_style.into().into_paint();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_stroke_style<FS>(&mut self, new_stroke_style: FS) where FS: Into<FillStyle> {
|
||||
self.current_state.stroke_paint = new_stroke_style.into().into_paint();
|
||||
}
|
||||
|
||||
// Shadows
|
||||
|
||||
#[inline]
|
||||
pub fn shadow_blur(&self) -> f32 {
|
||||
self.current_state.shadow_blur
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_shadow_blur(&mut self, new_shadow_blur: f32) {
|
||||
self.current_state.shadow_blur = new_shadow_blur;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn shadow_color(&self) -> ColorU {
|
||||
self.current_state.shadow_color
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_shadow_color(&mut self, new_shadow_color: ColorU) {
|
||||
self.current_state.shadow_color = new_shadow_color;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn shadow_offset(&self) -> Vector2F {
|
||||
self.current_state.shadow_offset
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_shadow_offset(&mut self, new_shadow_offset: Vector2F) {
|
||||
self.current_state.shadow_offset = new_shadow_offset;
|
||||
}
|
||||
|
||||
// Drawing paths
|
||||
|
||||
#[inline]
|
||||
pub fn fill_path(&mut self, path: Path2D, fill_rule: FillRule) {
|
||||
self.push_path(path.into_outline(), PathOp::Fill, fill_rule);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn stroke_path(&mut self, path: Path2D) {
|
||||
let mut stroke_style = self.current_state.resolve_stroke_style();
|
||||
|
||||
// The smaller scale is relevant here, as we multiply by it and want to ensure it is always
|
||||
// bigger than `HAIRLINE_STROKE_WIDTH`.
|
||||
let transform_scales = self.current_state.transform.extract_scale();
|
||||
let transform_scale = f32::min(transform_scales.x(), transform_scales.y());
|
||||
|
||||
// Avoid the division in the normal case of sufficient thickness.
|
||||
if stroke_style.line_width * transform_scale < HAIRLINE_STROKE_WIDTH {
|
||||
stroke_style.line_width = HAIRLINE_STROKE_WIDTH / transform_scale;
|
||||
}
|
||||
|
||||
let mut outline = path.into_outline();
|
||||
if !self.current_state.line_dash.is_empty() {
|
||||
let mut dash = OutlineDash::new(&outline,
|
||||
&self.current_state.line_dash,
|
||||
self.current_state.line_dash_offset);
|
||||
dash.dash();
|
||||
outline = dash.into_outline();
|
||||
}
|
||||
|
||||
let mut stroke_to_fill = OutlineStrokeToFill::new(&outline, stroke_style);
|
||||
stroke_to_fill.offset();
|
||||
outline = stroke_to_fill.into_outline();
|
||||
|
||||
self.push_path(outline, PathOp::Stroke, FillRule::Winding);
|
||||
}
|
||||
|
||||
pub fn clip_path(&mut self, path: Path2D, fill_rule: FillRule) {
|
||||
let mut outline = path.into_outline();
|
||||
outline.transform(&self.current_state.transform);
|
||||
|
||||
let mut clip_path = ClipPath::new(outline);
|
||||
clip_path.set_fill_rule(fill_rule);
|
||||
let clip_path_id = self.canvas.scene.push_clip_path(clip_path);
|
||||
|
||||
self.current_state.clip_path = Some(clip_path_id);
|
||||
}
|
||||
|
||||
fn push_path(&mut self, mut outline: Outline, path_op: PathOp, fill_rule: FillRule) {
|
||||
let paint = self.current_state.resolve_paint(match path_op {
|
||||
PathOp::Fill => &self.current_state.fill_paint,
|
||||
PathOp::Stroke => &self.current_state.stroke_paint,
|
||||
});
|
||||
let paint_id = self.canvas.scene.push_paint(&paint);
|
||||
|
||||
let transform = self.current_state.transform;
|
||||
let clip_path = self.current_state.clip_path;
|
||||
let blend_mode = self.current_state.global_composite_operation.to_blend_mode();
|
||||
|
||||
outline.transform(&transform);
|
||||
|
||||
if !self.current_state.shadow_color.is_fully_transparent() {
|
||||
let mut outline = outline.clone();
|
||||
outline.transform(&Transform2F::from_translation(self.current_state.shadow_offset));
|
||||
|
||||
let shadow_blur_info =
|
||||
push_shadow_blur_render_targets_if_needed(&mut self.canvas.scene,
|
||||
&self.current_state,
|
||||
outline.bounds());
|
||||
|
||||
if let Some(ref shadow_blur_info) = shadow_blur_info {
|
||||
outline.transform(&Transform2F::from_translation(-shadow_blur_info.bounds
|
||||
.origin()
|
||||
.to_f32()));
|
||||
}
|
||||
|
||||
// Per spec the shadow must respect the alpha of the shadowed path, but otherwise have
|
||||
// the color of the shadow paint.
|
||||
let mut shadow_paint = (*paint).clone();
|
||||
let shadow_base_alpha = shadow_paint.base_color().a;
|
||||
let mut shadow_color = self.current_state.shadow_color.to_f32();
|
||||
shadow_color.set_a(shadow_color.a() * shadow_base_alpha as f32 / 255.0);
|
||||
shadow_paint.set_base_color(shadow_color.to_u8());
|
||||
if let &mut Some(ref mut shadow_paint_overlay) = shadow_paint.overlay_mut() {
|
||||
shadow_paint_overlay.set_composite_op(PaintCompositeOp::DestIn);
|
||||
}
|
||||
let shadow_paint_id = self.canvas.scene.push_paint(&shadow_paint);
|
||||
|
||||
let mut path = DrawPath::new(outline, shadow_paint_id);
|
||||
if shadow_blur_info.is_none() {
|
||||
path.set_clip_path(clip_path);
|
||||
}
|
||||
path.set_fill_rule(fill_rule);
|
||||
path.set_blend_mode(blend_mode);
|
||||
self.canvas.scene.push_path(path);
|
||||
|
||||
composite_shadow_blur_render_targets_if_needed(&mut self.canvas.scene,
|
||||
shadow_blur_info,
|
||||
clip_path);
|
||||
}
|
||||
|
||||
let mut path = DrawPath::new(outline, paint_id);
|
||||
path.set_clip_path(clip_path);
|
||||
path.set_fill_rule(fill_rule);
|
||||
path.set_blend_mode(blend_mode);
|
||||
self.canvas.scene.push_path(path);
|
||||
|
||||
fn push_shadow_blur_render_targets_if_needed(scene: &mut Scene,
|
||||
current_state: &State,
|
||||
outline_bounds: RectF)
|
||||
-> Option<ShadowBlurRenderTargetInfo> {
|
||||
if current_state.shadow_blur == 0.0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let sigma = current_state.shadow_blur * 0.5;
|
||||
let bounds = outline_bounds.dilate(sigma * 3.0).round_out().to_i32();
|
||||
|
||||
let render_target_y = RenderTarget::new(bounds.size(), String::new());
|
||||
let render_target_id_y = scene.push_render_target(render_target_y);
|
||||
let render_target_x = RenderTarget::new(bounds.size(), String::new());
|
||||
let render_target_id_x = scene.push_render_target(render_target_x);
|
||||
|
||||
Some(ShadowBlurRenderTargetInfo {
|
||||
id_x: render_target_id_x,
|
||||
id_y: render_target_id_y,
|
||||
bounds,
|
||||
sigma,
|
||||
})
|
||||
}
|
||||
|
||||
fn composite_shadow_blur_render_targets_if_needed(scene: &mut Scene,
|
||||
info: Option<ShadowBlurRenderTargetInfo>,
|
||||
clip_path: Option<ClipPathId>) {
|
||||
let info = match info {
|
||||
None => return,
|
||||
Some(info) => info,
|
||||
};
|
||||
|
||||
let mut paint_x = Pattern::from_render_target(info.id_x, info.bounds.size());
|
||||
let mut paint_y = Pattern::from_render_target(info.id_y, info.bounds.size());
|
||||
paint_y.apply_transform(Transform2F::from_translation(info.bounds.origin().to_f32()));
|
||||
|
||||
let sigma = info.sigma;
|
||||
paint_x.set_filter(Some(PatternFilter::Blur { direction: BlurDirection::X, sigma }));
|
||||
paint_y.set_filter(Some(PatternFilter::Blur { direction: BlurDirection::Y, sigma }));
|
||||
|
||||
let paint_id_x = scene.push_paint(&Paint::from_pattern(paint_x));
|
||||
let paint_id_y = scene.push_paint(&Paint::from_pattern(paint_y));
|
||||
|
||||
// TODO(pcwalton): Apply clip as necessary.
|
||||
let outline_x = Outline::from_rect(RectF::new(vec2f(0.0, 0.0),
|
||||
info.bounds.size().to_f32()));
|
||||
let path_x = DrawPath::new(outline_x, paint_id_x);
|
||||
let outline_y = Outline::from_rect(info.bounds.to_f32());
|
||||
let mut path_y = DrawPath::new(outline_y, paint_id_y);
|
||||
path_y.set_clip_path(clip_path);
|
||||
|
||||
scene.pop_render_target();
|
||||
scene.push_path(path_x);
|
||||
scene.pop_render_target();
|
||||
scene.push_path(path_y);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Transformations
|
||||
|
||||
#[inline]
|
||||
pub fn rotate(&mut self, angle: f32) {
|
||||
self.current_state.transform *= Transform2F::from_rotation(angle)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn scale<S>(&mut self, scale: S) where S: IntoVector2F {
|
||||
self.current_state.transform *= Transform2F::from_scale(scale)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn translate(&mut self, offset: Vector2F) {
|
||||
self.current_state.transform *= Transform2F::from_translation(offset)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn transform(&self) -> Transform2F {
|
||||
self.current_state.transform
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_transform(&mut self, new_transform: &Transform2F) {
|
||||
self.current_state.transform = *new_transform;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn reset_transform(&mut self) {
|
||||
self.current_state.transform = Transform2F::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;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn global_composite_operation(&self) -> CompositeOperation {
|
||||
self.current_state.global_composite_operation
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_global_composite_operation(&mut self, new_composite_operation: CompositeOperation) {
|
||||
self.current_state.global_composite_operation = new_composite_operation;
|
||||
}
|
||||
|
||||
// Drawing images
|
||||
|
||||
#[inline]
|
||||
pub fn draw_image<I, L>(&mut self, image: I, dest_location: L)
|
||||
where I: CanvasImageSource, L: CanvasImageDestLocation {
|
||||
let pattern = image.to_pattern(self, Transform2F::default());
|
||||
let src_rect = RectF::new(vec2f(0.0, 0.0), pattern.size().to_f32());
|
||||
self.draw_subimage(pattern, src_rect, dest_location)
|
||||
}
|
||||
|
||||
pub fn draw_subimage<I, L>(&mut self, image: I, src_location: RectF, dest_location: L)
|
||||
where I: CanvasImageSource, L: CanvasImageDestLocation {
|
||||
let dest_size = dest_location.size().unwrap_or(src_location.size());
|
||||
let scale = dest_size / src_location.size();
|
||||
let offset = dest_location.origin() - src_location.origin();
|
||||
let transform = Transform2F::from_scale(scale).translate(offset);
|
||||
|
||||
let pattern = image.to_pattern(self, transform);
|
||||
let old_fill_paint = self.current_state.fill_paint.clone();
|
||||
self.set_fill_style(pattern);
|
||||
self.fill_rect(RectF::new(dest_location.origin(), dest_size));
|
||||
self.current_state.fill_paint = old_fill_paint;
|
||||
}
|
||||
|
||||
// Image smoothing
|
||||
|
||||
#[inline]
|
||||
pub fn image_smoothing_enabled(&self) -> bool {
|
||||
self.current_state.image_smoothing_enabled
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_image_smoothing_enabled(&mut self, enabled: bool) {
|
||||
self.current_state.image_smoothing_enabled = enabled
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn image_smoothing_quality(&self) -> ImageSmoothingQuality {
|
||||
self.current_state.image_smoothing_quality
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_image_smoothing_quality(&mut self, new_quality: ImageSmoothingQuality) {
|
||||
self.current_state.image_smoothing_quality = new_quality
|
||||
}
|
||||
|
||||
// The canvas state
|
||||
|
||||
#[inline]
|
||||
pub fn save(&mut self) {
|
||||
self.saved_states.push(self.current_state.clone());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn restore(&mut self) {
|
||||
if let Some(state) = self.saved_states.pop() {
|
||||
self.current_state = state;
|
||||
}
|
||||
}
|
||||
|
||||
// Extensions
|
||||
|
||||
pub fn create_pattern_from_canvas(&mut self, canvas: Canvas, transform: Transform2F)
|
||||
-> Pattern {
|
||||
let subscene_size = canvas.size();
|
||||
let subscene = canvas.into_scene();
|
||||
let render_target = RenderTarget::new(subscene_size, String::new());
|
||||
let render_target_id = self.canvas.scene.push_render_target(render_target);
|
||||
self.canvas.scene.append_scene(subscene);
|
||||
self.canvas.scene.pop_render_target();
|
||||
|
||||
let mut pattern = Pattern::from_render_target(render_target_id, subscene_size);
|
||||
pattern.apply_transform(transform);
|
||||
pattern
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct State {
|
||||
transform: Transform2F,
|
||||
font_collection: Arc<FontCollection>,
|
||||
font_size: f32,
|
||||
line_width: f32,
|
||||
line_cap: LineCap,
|
||||
line_join: LineJoin,
|
||||
miter_limit: f32,
|
||||
line_dash: Vec<f32>,
|
||||
line_dash_offset: f32,
|
||||
fill_paint: Paint,
|
||||
stroke_paint: Paint,
|
||||
shadow_color: ColorU,
|
||||
shadow_blur: f32,
|
||||
shadow_offset: Vector2F,
|
||||
text_align: TextAlign,
|
||||
text_baseline: TextBaseline,
|
||||
image_smoothing_enabled: bool,
|
||||
image_smoothing_quality: ImageSmoothingQuality,
|
||||
global_alpha: f32,
|
||||
global_composite_operation: CompositeOperation,
|
||||
clip_path: Option<ClipPathId>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn default(default_font_collection: Arc<FontCollection>) -> State {
|
||||
State {
|
||||
transform: Transform2F::default(),
|
||||
font_collection: default_font_collection,
|
||||
font_size: DEFAULT_FONT_SIZE,
|
||||
line_width: 1.0,
|
||||
line_cap: LineCap::Butt,
|
||||
line_join: LineJoin::Miter,
|
||||
miter_limit: 10.0,
|
||||
line_dash: vec![],
|
||||
line_dash_offset: 0.0,
|
||||
fill_paint: Paint::black(),
|
||||
stroke_paint: Paint::black(),
|
||||
shadow_color: ColorU::transparent_black(),
|
||||
shadow_blur: 0.0,
|
||||
shadow_offset: Vector2F::zero(),
|
||||
text_align: TextAlign::Left,
|
||||
text_baseline: TextBaseline::Alphabetic,
|
||||
image_smoothing_enabled: true,
|
||||
image_smoothing_quality: ImageSmoothingQuality::Low,
|
||||
global_alpha: 1.0,
|
||||
global_composite_operation: CompositeOperation::SourceOver,
|
||||
clip_path: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_paint<'a>(&self, paint: &'a Paint) -> Cow<'a, Paint> {
|
||||
let mut must_copy = !self.transform.is_identity() || self.global_alpha < 1.0;
|
||||
if !must_copy {
|
||||
if let Some(ref pattern) = paint.pattern() {
|
||||
must_copy = self.image_smoothing_enabled != pattern.smoothing_enabled()
|
||||
}
|
||||
}
|
||||
|
||||
if !must_copy {
|
||||
return Cow::Borrowed(paint);
|
||||
}
|
||||
|
||||
let mut paint = (*paint).clone();
|
||||
paint.apply_transform(&self.transform);
|
||||
|
||||
let mut base_color = paint.base_color().to_f32();
|
||||
base_color.set_a(base_color.a() * self.global_alpha);
|
||||
paint.set_base_color(base_color.to_u8());
|
||||
|
||||
if let Some(ref mut pattern) = paint.pattern_mut() {
|
||||
pattern.set_smoothing_enabled(self.image_smoothing_enabled);
|
||||
}
|
||||
Cow::Owned(paint)
|
||||
}
|
||||
|
||||
fn resolve_stroke_style(&self) -> StrokeStyle {
|
||||
StrokeStyle {
|
||||
line_width: self.line_width,
|
||||
line_cap: self.line_cap,
|
||||
line_join: match self.line_join {
|
||||
LineJoin::Miter => StrokeLineJoin::Miter(self.miter_limit),
|
||||
LineJoin::Bevel => StrokeLineJoin::Bevel,
|
||||
LineJoin::Round => StrokeLineJoin::Round,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Path2D {
|
||||
outline: Outline,
|
||||
current_contour: Contour,
|
||||
}
|
||||
|
||||
impl Path2D {
|
||||
#[inline]
|
||||
pub fn new() -> Path2D {
|
||||
Path2D { outline: Outline::new(), current_contour: Contour::new() }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn close_path(&mut self) {
|
||||
self.current_contour.close();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn move_to(&mut self, to: Vector2F) {
|
||||
// TODO(pcwalton): Cull degenerate contours.
|
||||
self.flush_current_contour();
|
||||
self.current_contour.push_endpoint(to);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn line_to(&mut self, to: Vector2F) {
|
||||
self.current_contour.push_endpoint(to);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn quadratic_curve_to(&mut self, ctrl: Vector2F, to: Vector2F) {
|
||||
self.current_contour.push_quadratic(ctrl, to);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn bezier_curve_to(&mut self, ctrl0: Vector2F, ctrl1: Vector2F, to: Vector2F) {
|
||||
self.current_contour.push_cubic(ctrl0, ctrl1, to);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn arc(&mut self,
|
||||
center: Vector2F,
|
||||
radius: f32,
|
||||
start_angle: f32,
|
||||
end_angle: f32,
|
||||
direction: ArcDirection) {
|
||||
let transform = Transform2F::from_scale(radius).translate(center);
|
||||
self.current_contour.push_arc(&transform, start_angle, end_angle, direction);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn arc_to(&mut self, ctrl: Vector2F, to: Vector2F, radius: f32) {
|
||||
// FIXME(pcwalton): What should we do if there's no initial point?
|
||||
let from = self.current_contour.last_position().unwrap_or_default();
|
||||
let (v0, v1) = (from - ctrl, to - ctrl);
|
||||
let (vu0, vu1) = (v0.normalize(), v1.normalize());
|
||||
let hypot = radius / f32::sqrt(0.5 * (1.0 - vu0.dot(vu1)));
|
||||
let bisector = vu0 + vu1;
|
||||
let center = ctrl + bisector * (hypot / bisector.length());
|
||||
|
||||
let transform = Transform2F::from_scale(radius).translate(center);
|
||||
let chord = LineSegment2F::new(vu0.yx() * vec2f(-1.0, 1.0), vu1.yx() * vec2f( 1.0, -1.0));
|
||||
|
||||
// FIXME(pcwalton): Is clockwise direction correct?
|
||||
self.current_contour.push_arc_from_unit_chord(&transform, chord, ArcDirection::CW);
|
||||
}
|
||||
|
||||
pub fn rect(&mut self, rect: RectF) {
|
||||
self.flush_current_contour();
|
||||
self.current_contour.push_endpoint(rect.origin());
|
||||
self.current_contour.push_endpoint(rect.upper_right());
|
||||
self.current_contour.push_endpoint(rect.lower_right());
|
||||
self.current_contour.push_endpoint(rect.lower_left());
|
||||
self.current_contour.close();
|
||||
}
|
||||
|
||||
pub fn ellipse<A>(&mut self,
|
||||
center: Vector2F,
|
||||
axes: A,
|
||||
rotation: f32,
|
||||
start_angle: f32,
|
||||
end_angle: f32)
|
||||
where A: IntoVector2F {
|
||||
self.flush_current_contour();
|
||||
|
||||
let transform = Transform2F::from_scale(axes).rotate(rotation).translate(center);
|
||||
self.current_contour.push_arc(&transform, start_angle, end_angle, ArcDirection::CW);
|
||||
|
||||
if end_angle - start_angle >= 2.0 * PI {
|
||||
self.current_contour.close();
|
||||
}
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#dom-path2d-addpath
|
||||
pub fn add_path(&mut self, mut path: Path2D, transform: &Transform2F) {
|
||||
self.flush_current_contour();
|
||||
path.flush_current_contour();
|
||||
path.outline.transform(transform);
|
||||
let last_contour = path.outline.pop_contour();
|
||||
for contour in path.outline.into_contours() {
|
||||
self.outline.push_contour(contour);
|
||||
}
|
||||
self.current_contour = last_contour.unwrap_or_else(Contour::new);
|
||||
}
|
||||
|
||||
pub fn into_outline(mut self) -> Outline {
|
||||
self.flush_current_contour();
|
||||
self.outline
|
||||
}
|
||||
|
||||
fn flush_current_contour(&mut self) {
|
||||
if !self.current_contour.is_empty() {
|
||||
self.outline.push_contour(mem::replace(&mut self.current_contour, Contour::new()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum FillStyle {
|
||||
Color(ColorU),
|
||||
Gradient(Gradient),
|
||||
Pattern(Pattern),
|
||||
}
|
||||
|
||||
impl FillStyle {
|
||||
fn into_paint(self) -> Paint {
|
||||
match self {
|
||||
FillStyle::Color(color) => Paint::from_color(color),
|
||||
FillStyle::Gradient(gradient) => Paint::from_gradient(gradient),
|
||||
FillStyle::Pattern(pattern) => Paint::from_pattern(pattern),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum TextAlign {
|
||||
Left,
|
||||
Right,
|
||||
Center,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum TextBaseline {
|
||||
Alphabetic,
|
||||
Top,
|
||||
Hanging,
|
||||
Middle,
|
||||
Ideographic,
|
||||
Bottom,
|
||||
}
|
||||
|
||||
// We duplicate `pathfinder_content::stroke::LineJoin` here because the HTML canvas API treats the
|
||||
// miter limit as part of the canvas state, while the native Pathfinder API treats the miter limit
|
||||
// as part of the line join. Pathfinder's choice is more logical, because the miter limit is
|
||||
// specific to miter joins. In this API, however, for compatibility we go with the HTML canvas
|
||||
// semantics.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum LineJoin {
|
||||
Miter,
|
||||
Bevel,
|
||||
Round,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum CompositeOperation {
|
||||
SourceOver,
|
||||
SourceIn,
|
||||
SourceOut,
|
||||
SourceAtop,
|
||||
DestinationOver,
|
||||
DestinationIn,
|
||||
DestinationOut,
|
||||
DestinationAtop,
|
||||
Lighter,
|
||||
Copy,
|
||||
Xor,
|
||||
Multiply,
|
||||
Screen,
|
||||
Overlay,
|
||||
Darken,
|
||||
Lighten,
|
||||
ColorDodge,
|
||||
ColorBurn,
|
||||
HardLight,
|
||||
SoftLight,
|
||||
Difference,
|
||||
Exclusion,
|
||||
Hue,
|
||||
Saturation,
|
||||
Color,
|
||||
Luminosity,
|
||||
}
|
||||
|
||||
impl CompositeOperation {
|
||||
fn to_blend_mode(self) -> BlendMode {
|
||||
match self {
|
||||
CompositeOperation::Copy => BlendMode::Copy,
|
||||
CompositeOperation::SourceAtop => BlendMode::SrcAtop,
|
||||
CompositeOperation::DestinationOver => BlendMode::DestOver,
|
||||
CompositeOperation::DestinationOut => BlendMode::DestOut,
|
||||
CompositeOperation::Xor => BlendMode::Xor,
|
||||
CompositeOperation::Lighter => BlendMode::Lighter,
|
||||
CompositeOperation::Multiply => BlendMode::Multiply,
|
||||
CompositeOperation::Screen => BlendMode::Screen,
|
||||
CompositeOperation::Overlay => BlendMode::Overlay,
|
||||
CompositeOperation::Darken => BlendMode::Darken,
|
||||
CompositeOperation::Lighten => BlendMode::Lighten,
|
||||
CompositeOperation::ColorDodge => BlendMode::ColorDodge,
|
||||
CompositeOperation::ColorBurn => BlendMode::ColorBurn,
|
||||
CompositeOperation::HardLight => BlendMode::HardLight,
|
||||
CompositeOperation::SoftLight => BlendMode::SoftLight,
|
||||
CompositeOperation::Difference => BlendMode::Difference,
|
||||
CompositeOperation::Exclusion => BlendMode::Exclusion,
|
||||
CompositeOperation::Hue => BlendMode::Hue,
|
||||
CompositeOperation::Saturation => BlendMode::Saturation,
|
||||
CompositeOperation::Color => BlendMode::Color,
|
||||
CompositeOperation::Luminosity => BlendMode::Luminosity,
|
||||
CompositeOperation::SourceOver => BlendMode::SrcOver,
|
||||
CompositeOperation::SourceIn => BlendMode::SrcIn,
|
||||
CompositeOperation::SourceOut => BlendMode::SrcOut,
|
||||
CompositeOperation::DestinationIn => BlendMode::DestIn,
|
||||
CompositeOperation::DestinationAtop => BlendMode::DestAtop,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum ImageSmoothingQuality {
|
||||
Low,
|
||||
Medium,
|
||||
High,
|
||||
}
|
||||
|
||||
pub trait CanvasImageSource {
|
||||
fn to_pattern(self, dest_context: &mut CanvasRenderingContext2D, transform: Transform2F)
|
||||
-> Pattern;
|
||||
}
|
||||
|
||||
pub trait CanvasImageDestLocation {
|
||||
fn origin(&self) -> Vector2F;
|
||||
fn size(&self) -> Option<Vector2F>;
|
||||
}
|
||||
|
||||
impl CanvasImageSource for Pattern {
|
||||
#[inline]
|
||||
fn to_pattern(mut self, _: &mut CanvasRenderingContext2D, transform: Transform2F) -> Pattern {
|
||||
self.apply_transform(transform);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl CanvasImageSource for Canvas {
|
||||
#[inline]
|
||||
fn to_pattern(self, dest_context: &mut CanvasRenderingContext2D, transform: Transform2F)
|
||||
-> Pattern {
|
||||
dest_context.create_pattern_from_canvas(self, transform)
|
||||
}
|
||||
}
|
||||
|
||||
impl CanvasImageDestLocation for RectF {
|
||||
#[inline]
|
||||
fn origin(&self) -> Vector2F {
|
||||
RectF::origin(*self)
|
||||
}
|
||||
#[inline]
|
||||
fn size(&self) -> Option<Vector2F> {
|
||||
Some(RectF::size(*self))
|
||||
}
|
||||
}
|
||||
|
||||
impl CanvasImageDestLocation for Vector2F {
|
||||
#[inline]
|
||||
fn origin(&self) -> Vector2F {
|
||||
*self
|
||||
}
|
||||
#[inline]
|
||||
fn size(&self) -> Option<Vector2F> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Path2D {
|
||||
fn fmt(&self, formatter: &mut Formatter) -> Result<(), FmtError> {
|
||||
self.clone().into_outline().fmt(formatter)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ColorU> for FillStyle {
|
||||
#[inline]
|
||||
fn from(color: ColorU) -> FillStyle {
|
||||
FillStyle::Color(color)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Gradient> for FillStyle {
|
||||
#[inline]
|
||||
fn from(gradient: Gradient) -> FillStyle {
|
||||
FillStyle::Gradient(gradient)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Pattern> for FillStyle {
|
||||
#[inline]
|
||||
fn from(pattern: Pattern) -> FillStyle {
|
||||
FillStyle::Pattern(pattern)
|
||||
}
|
||||
}
|
||||
|
||||
struct ShadowBlurRenderTargetInfo {
|
||||
id_x: RenderTargetId,
|
||||
id_y: RenderTargetId,
|
||||
bounds: RectI,
|
||||
sigma: f32,
|
||||
}
|
||||
|
||||
enum PathOp {
|
||||
Fill,
|
||||
Stroke,
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
// pathfinder/canvas/src/tests.rs
|
||||
//
|
||||
// For this file only, any copyright is dedicated to the Public Domain.
|
||||
// https://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
use pathfinder_geometry::vector::{Vector2F, vec2f};
|
||||
use super::Path2D;
|
||||
|
||||
#[test]
|
||||
pub fn test_path2d_formatting() {
|
||||
let mut path = Path2D::new();
|
||||
path.move_to(vec2f(0.0, 1.0));
|
||||
path.line_to(vec2f(2.0, 3.0));
|
||||
assert_eq!(format!("{:?}", path), "M 0 1 L 2 3");
|
||||
path.line_to(vec2f(4.0, 5.0));
|
||||
assert_eq!(format!("{:?}", path), "M 0 1 L 2 3 L 4 5");
|
||||
path.close_path();
|
||||
assert_eq!(format!("{:?}", path), "M 0 1 L 2 3 L 4 5 z");
|
||||
}
|
|
@ -1,507 +0,0 @@
|
|||
// pathfinder/canvas/src/text.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::{CanvasRenderingContext2D, State, TextAlign, TextBaseline};
|
||||
use font_kit::canvas::RasterizationOptions;
|
||||
use font_kit::family_name::FamilyName;
|
||||
use font_kit::handle::Handle;
|
||||
use font_kit::hinting::HintingOptions;
|
||||
use font_kit::loaders::default::Font;
|
||||
use font_kit::properties::Properties;
|
||||
use font_kit::source::{Source, SystemSource};
|
||||
use font_kit::sources::mem::MemSource;
|
||||
use pathfinder_geometry::transform2d::Transform2F;
|
||||
use pathfinder_geometry::util;
|
||||
use pathfinder_geometry::vector::{Vector2F, vec2f};
|
||||
use pathfinder_renderer::paint::PaintId;
|
||||
use pathfinder_text::{FontContext, FontRenderOptions, TextRenderMode};
|
||||
use skribo::{FontCollection, FontFamily, FontRef, Layout, TextStyle};
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
impl CanvasRenderingContext2D {
|
||||
pub fn fill_text(&mut self, string: &str, position: Vector2F) {
|
||||
let paint = self.current_state.resolve_paint(&self.current_state.fill_paint);
|
||||
let paint_id = self.canvas.scene.push_paint(&paint);
|
||||
self.fill_or_stroke_text(string, position, paint_id, TextRenderMode::Fill);
|
||||
}
|
||||
|
||||
pub fn stroke_text(&mut self, string: &str, position: Vector2F) {
|
||||
let paint = self.current_state.resolve_paint(&self.current_state.stroke_paint);
|
||||
let paint_id = self.canvas.scene.push_paint(&paint);
|
||||
let render_mode = TextRenderMode::Stroke(self.current_state.resolve_stroke_style());
|
||||
self.fill_or_stroke_text(string, position, paint_id, render_mode);
|
||||
}
|
||||
|
||||
pub fn measure_text(&self, string: &str) -> TextMetrics {
|
||||
let mut metrics = self.layout_text(string).metrics();
|
||||
metrics.make_origin_relative(&self.current_state);
|
||||
metrics
|
||||
}
|
||||
|
||||
pub fn fill_layout(&mut self, layout: &Layout, transform: Transform2F) {
|
||||
let paint_id = self.canvas.scene.push_paint(&self.current_state.fill_paint);
|
||||
|
||||
let clip_path = self.current_state.clip_path;
|
||||
let blend_mode = self.current_state.global_composite_operation.to_blend_mode();
|
||||
|
||||
// TODO(pcwalton): Report errors.
|
||||
drop(self.canvas_font_context
|
||||
.0
|
||||
.borrow_mut()
|
||||
.font_context
|
||||
.push_layout(&mut self.canvas.scene,
|
||||
&layout,
|
||||
&TextStyle { size: self.current_state.font_size },
|
||||
&FontRenderOptions {
|
||||
transform: transform * self.current_state.transform,
|
||||
render_mode: TextRenderMode::Fill,
|
||||
hinting_options: HintingOptions::None,
|
||||
clip_path,
|
||||
blend_mode,
|
||||
paint_id,
|
||||
}));
|
||||
}
|
||||
|
||||
fn fill_or_stroke_text(&mut self,
|
||||
string: &str,
|
||||
mut position: Vector2F,
|
||||
paint_id: PaintId,
|
||||
render_mode: TextRenderMode) {
|
||||
let layout = self.layout_text(string);
|
||||
|
||||
let clip_path = self.current_state.clip_path;
|
||||
let blend_mode = self.current_state.global_composite_operation.to_blend_mode();
|
||||
|
||||
position += layout.metrics().text_origin(&self.current_state);
|
||||
let transform = self.current_state.transform * Transform2F::from_translation(position);
|
||||
|
||||
// TODO(pcwalton): Report errors.
|
||||
drop(self.canvas_font_context
|
||||
.0
|
||||
.borrow_mut()
|
||||
.font_context
|
||||
.push_layout(&mut self.canvas.scene,
|
||||
&layout,
|
||||
&TextStyle { size: self.current_state.font_size },
|
||||
&FontRenderOptions {
|
||||
transform,
|
||||
render_mode,
|
||||
hinting_options: HintingOptions::None,
|
||||
clip_path,
|
||||
blend_mode,
|
||||
paint_id,
|
||||
}));
|
||||
}
|
||||
|
||||
fn layout_text(&self, string: &str) -> Layout {
|
||||
skribo::layout(&TextStyle { size: self.current_state.font_size },
|
||||
&self.current_state.font_collection,
|
||||
string)
|
||||
}
|
||||
|
||||
// Text styles
|
||||
|
||||
#[inline]
|
||||
pub fn font(&self) -> Arc<FontCollection> {
|
||||
self.current_state.font_collection.clone()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_font<FC>(&mut self, font_collection: FC) where FC: IntoFontCollection {
|
||||
let font_collection = font_collection.into_font_collection(&self.canvas_font_context);
|
||||
self.current_state.font_collection = font_collection;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn font_size(&self) -> f32 {
|
||||
self.current_state.font_size
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_font_size(&mut self, new_font_size: f32) {
|
||||
self.current_state.font_size = new_font_size;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn text_align(&self) -> TextAlign {
|
||||
self.current_state.text_align
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_text_align(&mut self, new_text_align: TextAlign) {
|
||||
self.current_state.text_align = new_text_align;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn text_baseline(&self) -> TextBaseline {
|
||||
self.current_state.text_baseline
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_text_baseline(&mut self, new_text_baseline: TextBaseline) {
|
||||
self.current_state.text_baseline = new_text_baseline;
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the dimensions of a piece of text in the canvas.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct TextMetrics {
|
||||
/// The calculated width of a segment of inline text in pixels.
|
||||
pub width: f32,
|
||||
/// The distance from the alignment point given by the `text_align` state to the left side of
|
||||
/// the bounding rectangle of the given text, in pixels. The distance is measured parallel to
|
||||
/// the baseline.
|
||||
pub actual_bounding_box_left: f32,
|
||||
/// The distance from the alignment point given by the `text_align` state to the right side of
|
||||
/// the bounding rectangle of the given text, in pixels. The distance is measured parallel to
|
||||
/// the baseline.
|
||||
pub actual_bounding_box_right: f32,
|
||||
/// The distance from the horizontal line indicated by the `text_baseline` state to the top of
|
||||
/// the highest bounding rectangle of all the fonts used to render the text, in pixels.
|
||||
pub font_bounding_box_ascent: f32,
|
||||
/// The distance from the horizontal line indicated by the `text_baseline` state to the bottom
|
||||
/// of the highest bounding rectangle of all the fonts used to render the text, in pixels.
|
||||
pub font_bounding_box_descent: f32,
|
||||
/// The distance from the horizontal line indicated by the `text_baseline` state to the top of
|
||||
/// the bounding rectangle used to render the text, in pixels.
|
||||
pub actual_bounding_box_ascent: f32,
|
||||
/// The distance from the horizontal line indicated by the `text_baseline` state to the bottom
|
||||
/// of the bounding rectangle used to render the text, in pixels.
|
||||
pub actual_bounding_box_descent: f32,
|
||||
/// The distance from the horizontal line indicated by the `text_baseline` state to the top of
|
||||
/// the em square in the line box, in pixels.
|
||||
pub em_height_ascent: f32,
|
||||
/// The distance from the horizontal line indicated by the `text_baseline` state to the bottom
|
||||
/// of the em square in the line box, in pixels.
|
||||
pub em_height_descent: f32,
|
||||
/// The distance from the horizontal line indicated by the `text_baseline` state to the hanging
|
||||
/// baseline of the line box, in pixels.
|
||||
pub hanging_baseline: f32,
|
||||
/// The distance from the horizontal line indicated by the `text_baseline` state to the
|
||||
/// alphabetic baseline of the line box, in pixels.
|
||||
pub alphabetic_baseline: f32,
|
||||
/// The distance from the horizontal line indicated by the `text_baseline` state to the
|
||||
/// ideographic baseline of the line box, in pixels.
|
||||
pub ideographic_baseline: f32,
|
||||
}
|
||||
|
||||
#[cfg(feature = "pf-text")]
|
||||
#[derive(Clone)]
|
||||
pub struct CanvasFontContext(pub(crate) Rc<RefCell<CanvasFontContextData>>);
|
||||
|
||||
pub(super) struct CanvasFontContextData {
|
||||
pub(super) font_context: FontContext<Font>,
|
||||
#[allow(dead_code)]
|
||||
pub(super) font_source: Arc<dyn Source>,
|
||||
#[allow(dead_code)]
|
||||
pub(super) default_font_collection: Arc<FontCollection>,
|
||||
}
|
||||
|
||||
impl CanvasFontContext {
|
||||
pub fn new(font_source: Arc<dyn Source>) -> CanvasFontContext {
|
||||
let mut default_font_collection = FontCollection::new();
|
||||
if let Ok(default_font) = font_source.select_best_match(&[FamilyName::SansSerif],
|
||||
&Properties::new()) {
|
||||
if let Ok(default_font) = default_font.load() {
|
||||
default_font_collection.add_family(FontFamily::new_from_font(default_font));
|
||||
}
|
||||
}
|
||||
|
||||
CanvasFontContext(Rc::new(RefCell::new(CanvasFontContextData {
|
||||
font_source,
|
||||
default_font_collection: Arc::new(default_font_collection),
|
||||
font_context: FontContext::new(),
|
||||
})))
|
||||
}
|
||||
|
||||
/// A convenience method to create a font context with the system source.
|
||||
/// This allows usage of fonts installed on the system.
|
||||
pub fn from_system_source() -> CanvasFontContext {
|
||||
CanvasFontContext::new(Arc::new(SystemSource::new()))
|
||||
}
|
||||
|
||||
/// A convenience method to create a font context with a set of in-memory fonts.
|
||||
pub fn from_fonts<I>(fonts: I) -> CanvasFontContext where I: Iterator<Item = Handle> {
|
||||
CanvasFontContext::new(Arc::new(MemSource::from_fonts(fonts).unwrap()))
|
||||
}
|
||||
|
||||
fn get_font_by_postscript_name(&self, postscript_name: &str) -> Font {
|
||||
let this = self.0.borrow();
|
||||
if let Some(cached_font) = this.font_context.get_cached_font(postscript_name) {
|
||||
return (*cached_font).clone();
|
||||
}
|
||||
this.font_source
|
||||
.select_by_postscript_name(postscript_name)
|
||||
.expect("Couldn't find a font with that PostScript name!")
|
||||
.load()
|
||||
.expect("Failed to load the font!")
|
||||
}
|
||||
}
|
||||
|
||||
// Text layout utilities
|
||||
|
||||
impl TextMetrics {
|
||||
fn text_origin(&self, state: &State) -> Vector2F {
|
||||
let x = match state.text_align {
|
||||
TextAlign::Left => 0.0,
|
||||
TextAlign::Right => -self.width,
|
||||
TextAlign::Center => -0.5 * self.width,
|
||||
};
|
||||
|
||||
let y = match state.text_baseline {
|
||||
TextBaseline::Alphabetic => 0.0,
|
||||
TextBaseline::Top => self.em_height_ascent,
|
||||
TextBaseline::Middle => util::lerp(self.em_height_ascent, self.em_height_descent, 0.5),
|
||||
TextBaseline::Bottom => self.em_height_descent,
|
||||
TextBaseline::Ideographic => self.ideographic_baseline,
|
||||
TextBaseline::Hanging => self.hanging_baseline,
|
||||
};
|
||||
|
||||
vec2f(x, y)
|
||||
}
|
||||
|
||||
fn make_origin_relative(&mut self, state: &State) {
|
||||
let text_origin = self.text_origin(state);
|
||||
self.actual_bounding_box_left += text_origin.x();
|
||||
self.actual_bounding_box_right += text_origin.x();
|
||||
self.font_bounding_box_ascent -= text_origin.y();
|
||||
self.font_bounding_box_descent -= text_origin.y();
|
||||
self.actual_bounding_box_ascent -= text_origin.y();
|
||||
self.actual_bounding_box_descent -= text_origin.y();
|
||||
self.em_height_ascent -= text_origin.y();
|
||||
self.em_height_descent -= text_origin.y();
|
||||
self.hanging_baseline -= text_origin.y();
|
||||
self.alphabetic_baseline -= text_origin.y();
|
||||
self.ideographic_baseline -= text_origin.y();
|
||||
}
|
||||
}
|
||||
|
||||
pub trait LayoutExt {
|
||||
fn metrics(&self) -> TextMetrics;
|
||||
fn width(&self) -> f32;
|
||||
fn actual_bounding_box_left(&self) -> f32;
|
||||
fn actual_bounding_box_right(&self) -> f32;
|
||||
fn hanging_baseline(&self) -> f32;
|
||||
fn ideographic_baseline(&self) -> f32;
|
||||
}
|
||||
|
||||
impl LayoutExt for Layout {
|
||||
// NB: This does not return origin-relative values. To get those, call `make_origin_relative()`
|
||||
// afterward.
|
||||
fn metrics(&self) -> TextMetrics {
|
||||
let (mut em_height_ascent, mut em_height_descent) = (0.0, 0.0);
|
||||
let (mut font_bounding_box_ascent, mut font_bounding_box_descent) = (0.0, 0.0);
|
||||
let (mut actual_bounding_box_ascent, mut actual_bounding_box_descent) = (0.0, 0.0);
|
||||
|
||||
let mut last_font: Option<Arc<Font>> = None;
|
||||
for glyph in &self.glyphs {
|
||||
match last_font {
|
||||
Some(ref last_font) if Arc::ptr_eq(&last_font, &glyph.font.font) => {}
|
||||
_ => {
|
||||
let font = glyph.font.font.clone();
|
||||
|
||||
let font_metrics = font.metrics();
|
||||
let scale_factor = self.size / font_metrics.units_per_em as f32;
|
||||
em_height_ascent = (font_metrics.ascent * scale_factor).max(em_height_ascent);
|
||||
em_height_descent =
|
||||
(font_metrics.descent * scale_factor).min(em_height_descent);
|
||||
font_bounding_box_ascent = (font_metrics.bounding_box.max_y() *
|
||||
scale_factor).max(font_bounding_box_ascent);
|
||||
font_bounding_box_descent = (font_metrics.bounding_box.min_y() *
|
||||
scale_factor).min(font_bounding_box_descent);
|
||||
|
||||
last_font = Some(font);
|
||||
}
|
||||
}
|
||||
|
||||
let font = last_font.as_ref().unwrap();
|
||||
let glyph_rect = font.raster_bounds(glyph.glyph_id,
|
||||
self.size,
|
||||
Transform2F::default(),
|
||||
HintingOptions::None,
|
||||
RasterizationOptions::GrayscaleAa).unwrap();
|
||||
actual_bounding_box_ascent =
|
||||
(glyph_rect.max_y() as f32).max(actual_bounding_box_ascent);
|
||||
actual_bounding_box_descent =
|
||||
(glyph_rect.min_y() as f32).min(actual_bounding_box_descent);
|
||||
}
|
||||
|
||||
TextMetrics {
|
||||
width: self.width(),
|
||||
actual_bounding_box_left: self.actual_bounding_box_left(),
|
||||
actual_bounding_box_right: self.actual_bounding_box_right(),
|
||||
font_bounding_box_ascent,
|
||||
font_bounding_box_descent,
|
||||
actual_bounding_box_ascent,
|
||||
actual_bounding_box_descent,
|
||||
em_height_ascent,
|
||||
em_height_descent,
|
||||
alphabetic_baseline: 0.0,
|
||||
hanging_baseline: self.hanging_baseline(),
|
||||
ideographic_baseline: self.ideographic_baseline(),
|
||||
}
|
||||
}
|
||||
|
||||
fn width(&self) -> f32 {
|
||||
let last_glyph = match self.glyphs.last() {
|
||||
None => return 0.0,
|
||||
Some(last_glyph) => last_glyph,
|
||||
};
|
||||
|
||||
let glyph_id = last_glyph.glyph_id;
|
||||
let font_metrics = last_glyph.font.font.metrics();
|
||||
let scale_factor = self.size / font_metrics.units_per_em as f32;
|
||||
let glyph_rect = last_glyph.font.font.typographic_bounds(glyph_id).unwrap();
|
||||
last_glyph.offset.x() + glyph_rect.max_x() * scale_factor
|
||||
}
|
||||
|
||||
fn actual_bounding_box_left(&self) -> f32 {
|
||||
let first_glyph = match self.glyphs.get(0) {
|
||||
None => return 0.0,
|
||||
Some(first_glyph) => first_glyph,
|
||||
};
|
||||
|
||||
let glyph_id = first_glyph.glyph_id;
|
||||
let font_metrics = first_glyph.font.font.metrics();
|
||||
let scale_factor = self.size / font_metrics.units_per_em as f32;
|
||||
let glyph_rect = first_glyph.font
|
||||
.font
|
||||
.raster_bounds(glyph_id,
|
||||
font_metrics.units_per_em as f32,
|
||||
Transform2F::default(),
|
||||
HintingOptions::None,
|
||||
RasterizationOptions::GrayscaleAa).unwrap();
|
||||
first_glyph.offset.x() + glyph_rect.min_x() as f32 * scale_factor
|
||||
}
|
||||
|
||||
fn actual_bounding_box_right(&self) -> f32 {
|
||||
let last_glyph = match self.glyphs.last() {
|
||||
None => return 0.0,
|
||||
Some(last_glyph) => last_glyph,
|
||||
};
|
||||
|
||||
let glyph_id = last_glyph.glyph_id;
|
||||
let font_metrics = last_glyph.font.font.metrics();
|
||||
let scale_factor = self.size / font_metrics.units_per_em as f32;
|
||||
let glyph_rect = last_glyph.font
|
||||
.font
|
||||
.raster_bounds(glyph_id,
|
||||
font_metrics.units_per_em as f32,
|
||||
Transform2F::default(),
|
||||
HintingOptions::None,
|
||||
RasterizationOptions::GrayscaleAa).unwrap();
|
||||
last_glyph.offset.x() + glyph_rect.max_x() as f32 * scale_factor
|
||||
}
|
||||
|
||||
fn hanging_baseline(&self) -> f32 {
|
||||
// TODO(pcwalton)
|
||||
0.0
|
||||
}
|
||||
|
||||
fn ideographic_baseline(&self) -> f32 {
|
||||
// TODO(pcwalton)
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Various things that can be conveniently converted into font collections for use with
|
||||
/// `CanvasRenderingContext2D::set_font()`.
|
||||
pub trait IntoFontCollection {
|
||||
fn into_font_collection(self, font_context: &CanvasFontContext) -> Arc<FontCollection>;
|
||||
}
|
||||
|
||||
impl IntoFontCollection for Arc<FontCollection> {
|
||||
#[inline]
|
||||
fn into_font_collection(self, _: &CanvasFontContext) -> Arc<FontCollection> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoFontCollection for FontFamily {
|
||||
#[inline]
|
||||
fn into_font_collection(self, _: &CanvasFontContext) -> Arc<FontCollection> {
|
||||
let mut font_collection = FontCollection::new();
|
||||
font_collection.add_family(self);
|
||||
Arc::new(font_collection)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoFontCollection for Vec<FontFamily> {
|
||||
#[inline]
|
||||
fn into_font_collection(self, _: &CanvasFontContext) -> Arc<FontCollection> {
|
||||
let mut font_collection = FontCollection::new();
|
||||
for family in self {
|
||||
font_collection.add_family(family);
|
||||
}
|
||||
Arc::new(font_collection)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
impl IntoFontCollection for Handle {
|
||||
#[inline]
|
||||
fn into_font_collection(self, context: &CanvasFontContext) -> Arc<FontCollection> {
|
||||
self.load().expect("Failed to load the font!").into_font_collection(context)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoFontCollection for &'a [Handle] {
|
||||
#[inline]
|
||||
fn into_font_collection(self, context: &CanvasFontContext) -> Arc<FontCollection> {
|
||||
let mut font_collection = FontCollection::new();
|
||||
for handle in self {
|
||||
let postscript_name = handle.postscript_name();
|
||||
|
||||
let font = handle.load().expect("Failed to load the font!");
|
||||
font_collection.add_family(FontFamily::new_from_font(font));
|
||||
}
|
||||
Arc::new(font_collection)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
impl IntoFontCollection for Font {
|
||||
#[inline]
|
||||
fn into_font_collection(self, context: &CanvasFontContext) -> Arc<FontCollection> {
|
||||
FontFamily::new_from_font(self).into_font_collection(context)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoFontCollection for &'a [Font] {
|
||||
#[inline]
|
||||
fn into_font_collection(self, context: &CanvasFontContext) -> Arc<FontCollection> {
|
||||
let mut family = FontFamily::new();
|
||||
for font in self {
|
||||
family.add_font(FontRef::new((*font).clone()))
|
||||
}
|
||||
family.into_font_collection(context)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoFontCollection for &'a str {
|
||||
#[inline]
|
||||
fn into_font_collection(self, context: &CanvasFontContext) -> Arc<FontCollection> {
|
||||
context.get_font_by_postscript_name(self).into_font_collection(context)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> IntoFontCollection for &'a [&'b str] {
|
||||
#[inline]
|
||||
fn into_font_collection(self, context: &CanvasFontContext) -> Arc<FontCollection> {
|
||||
let mut font_collection = FontCollection::new();
|
||||
for postscript_name in self {
|
||||
let font = context.get_font_by_postscript_name(postscript_name);
|
||||
font_collection.add_family(FontFamily::new_from_font(font));
|
||||
}
|
||||
Arc::new(font_collection)
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
[package]
|
||||
name = "pathfinder_color"
|
||||
version = "0.5.0"
|
||||
edition = "2018"
|
||||
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
|
||||
description = "A minimal SIMD-accelerated color handling library"
|
||||
license = "MIT/Apache-2.0"
|
||||
repository = "https://github.com/servo/pathfinder"
|
||||
homepage = "https://github.com/servo/pathfinder"
|
||||
|
||||
[dependencies]
|
||||
|
||||
[dependencies.pathfinder_simd]
|
||||
path = "../simd"
|
||||
version = "0.5"
|
|
@ -1,272 +0,0 @@
|
|||
// pathfinder/color/src/lib.rs
|
||||
//
|
||||
// Copyright © 2020 The Pathfinder Project Developers.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use pathfinder_simd::default::F32x4;
|
||||
use std::f32::consts::PI;
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::slice;
|
||||
|
||||
// TODO(pcwalton): Maybe this should be a u32? Need to be aware of endianness issues if we do that.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||
#[repr(C)]
|
||||
pub struct ColorU {
|
||||
pub r: u8,
|
||||
pub g: u8,
|
||||
pub b: u8,
|
||||
pub a: u8,
|
||||
}
|
||||
|
||||
impl ColorU {
|
||||
#[inline]
|
||||
pub fn new(r: u8, g: u8, b: u8, a: u8) -> ColorU {
|
||||
ColorU { r, g, b, a }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn transparent_black() -> ColorU {
|
||||
ColorU::from_u32(0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_u32(rgba: u32) -> ColorU {
|
||||
ColorU {
|
||||
r: (rgba >> 24) as u8,
|
||||
g: ((rgba >> 16) & 0xff) as u8,
|
||||
b: ((rgba >> 8) & 0xff) as u8,
|
||||
a: (rgba & 0xff) as u8,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn black() -> ColorU {
|
||||
ColorU {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
a: 255,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn white() -> ColorU {
|
||||
ColorU {
|
||||
r: 255,
|
||||
g: 255,
|
||||
b: 255,
|
||||
a: 255,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_f32(&self) -> ColorF {
|
||||
let color = F32x4::new(self.r as f32, self.g as f32, self.b as f32, self.a as f32);
|
||||
ColorF(color * F32x4::splat(1.0 / 255.0))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_opaque(&self) -> bool {
|
||||
self.a == !0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_fully_transparent(&self) -> bool {
|
||||
self.a == 0
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for ColorU {
|
||||
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
|
||||
if self.a == 255 {
|
||||
write!(formatter, "#{:02x}{:02x}{:02x}", self.r, self.g, self.b)
|
||||
} else {
|
||||
write!(
|
||||
formatter,
|
||||
"rgba({}, {}, {}, {})",
|
||||
self.r,
|
||||
self.g,
|
||||
self.b,
|
||||
self.a as f32 / 255.0
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Default)]
|
||||
pub struct ColorF(pub F32x4);
|
||||
|
||||
impl ColorF {
|
||||
// Constructors
|
||||
|
||||
#[inline]
|
||||
pub fn new(r: f32, g: f32, b: f32, a: f32) -> ColorF {
|
||||
ColorF(F32x4::new(r, g, b, a))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_hsla(mut h: f32, s: f32, l: f32, a: f32) -> ColorF {
|
||||
// https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB
|
||||
|
||||
// Make sure hue is always positive.
|
||||
h %= 2.0 * PI;
|
||||
if h < 0.0 {
|
||||
h += 2.0 * PI;
|
||||
}
|
||||
|
||||
h *= 3.0 / PI;
|
||||
|
||||
// Calculate chroma.
|
||||
let c = (1.0 - f32::abs(2.0 * l - 1.0)) * s;
|
||||
let xc = F32x4::new(c * (1.0 - f32::abs(h % 2.0 - 1.0)), c, 0.0, a);
|
||||
let rgba = match f32::ceil(h) as i32 {
|
||||
1 => xc.yxzw(),
|
||||
2 => xc.xyzw(),
|
||||
3 => xc.zyxw(),
|
||||
4 => xc.zxyw(),
|
||||
5 => xc.xzyw(),
|
||||
0 | 6 => xc.yzxw(),
|
||||
_ => xc.zzzw(),
|
||||
};
|
||||
let m = l - 0.5 * c;
|
||||
ColorF(rgba + F32x4::new(m, m, m, 0.0))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_hsl(h: f32, s: f32, l: f32) -> ColorF {
|
||||
ColorF::from_hsla(h, s, l, 1.0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn transparent_black() -> ColorF {
|
||||
ColorF::default()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn black() -> ColorF {
|
||||
ColorF(F32x4::new(0.0, 0.0, 0.0, 1.0))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn white() -> ColorF {
|
||||
ColorF(F32x4::splat(1.0))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_u8(&self) -> ColorU {
|
||||
let color = (self.0 * F32x4::splat(255.0)).to_i32x4();
|
||||
ColorU { r: color[0] as u8, g: color[1] as u8, b: color[2] as u8, a: color[3] as u8 }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn lerp(&self, other: ColorF, t: f32) -> ColorF {
|
||||
ColorF(self.0 + (other.0 - self.0) * F32x4::splat(t))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn r(&self) -> f32 {
|
||||
self.0[0]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn g(&self) -> f32 {
|
||||
self.0[1]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn b(&self) -> f32 {
|
||||
self.0[2]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn a(&self) -> f32 {
|
||||
self.0[3]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_r(&mut self, r: f32) {
|
||||
self.0[0] = r;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_g(&mut self, g: f32) {
|
||||
self.0[1] = g;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_b(&mut self, b: f32) {
|
||||
self.0[2] = b;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_a(&mut self, a: f32) {
|
||||
self.0[3] = a;
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for ColorF {
|
||||
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
|
||||
write!(
|
||||
formatter,
|
||||
"rgba({}, {}, {}, {})",
|
||||
self.r() * 255.0,
|
||||
self.g() * 255.0,
|
||||
self.b() * 255.0,
|
||||
self.a()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn color_slice_to_u8_slice(slice: &[ColorU]) -> &[u8] {
|
||||
unsafe {
|
||||
slice::from_raw_parts(slice.as_ptr() as *const u8, slice.len() * 4)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn u8_slice_to_color_slice(slice: &[u8]) -> &[ColorU] {
|
||||
unsafe {
|
||||
assert_eq!(slice.len() % 4, 0);
|
||||
slice::from_raw_parts(slice.as_ptr() as *const ColorU, slice.len() / 4)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(pcwalton): Do this without a copy?
|
||||
#[inline]
|
||||
pub fn u8_vec_to_color_vec(buffer: Vec<u8>) -> Vec<ColorU> {
|
||||
u8_slice_to_color_slice(&buffer).to_vec()
|
||||
}
|
||||
|
||||
/// A convenience method to construct a `ColorU` from an RGB triple.
|
||||
///
|
||||
/// Alpha is set to 255.
|
||||
#[inline]
|
||||
pub fn rgbu(r: u8, g: u8, b: u8) -> ColorU {
|
||||
ColorU::new(r, g, b, 255)
|
||||
}
|
||||
|
||||
/// A convenience method to construct a `ColorU` from an RGBA triple.
|
||||
#[inline]
|
||||
pub fn rgbau(r: u8, g: u8, b: u8, a: u8) -> ColorU {
|
||||
ColorU::new(r, g, b, a)
|
||||
}
|
||||
|
||||
/// A convenience method to construct a `ColorF` from an RGB triple.
|
||||
///
|
||||
/// Alpha is set to 1.0.
|
||||
#[inline]
|
||||
pub fn rgbf(r: f32, g: f32, b: f32) -> ColorF {
|
||||
ColorF::new(r, g, b, 1.0)
|
||||
}
|
||||
|
||||
/// A convenience method to construct a `ColorF` from an RGBA triple.
|
||||
#[inline]
|
||||
pub fn rgbaf(r: f32, g: f32, b: f32, a: f32) -> ColorF {
|
||||
ColorF::new(r, g, b, a)
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
[package]
|
||||
name = "pathfinder_content"
|
||||
version = "0.5.0"
|
||||
edition = "2018"
|
||||
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
|
||||
description = "Vector path utilities for the Pathfinder rendering library"
|
||||
license = "MIT/Apache-2.0"
|
||||
repository = "https://github.com/servo/pathfinder"
|
||||
homepage = "https://github.com/servo/pathfinder"
|
||||
|
||||
[dependencies]
|
||||
arrayvec = "0.5"
|
||||
bitflags = "1.0"
|
||||
log = "0.4"
|
||||
smallvec = "1.2"
|
||||
|
||||
[dependencies.image]
|
||||
version = "0.23"
|
||||
default-features = false
|
||||
features = []
|
||||
optional = true
|
||||
|
||||
[features]
|
||||
default = ["pf-image"]
|
||||
pf-image = ["image"]
|
||||
|
||||
[dependencies.pathfinder_color]
|
||||
path = "../color"
|
||||
version = "0.5"
|
||||
|
||||
[dependencies.pathfinder_geometry]
|
||||
path = "../geometry"
|
||||
version = "0.5"
|
||||
|
||||
[dependencies.pathfinder_simd]
|
||||
path = "../simd"
|
||||
version = "0.5"
|
||||
|
||||
[dev-dependencies]
|
||||
quickcheck = "0.9"
|
|
@ -1,555 +0,0 @@
|
|||
// pathfinder/content/src/clip.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::outline::{Contour, ContourIterFlags, PointFlags, PushSegmentFlags};
|
||||
use crate::segment::{CubicSegment, Segment};
|
||||
use arrayvec::ArrayVec;
|
||||
use pathfinder_geometry::line_segment::LineSegment2F;
|
||||
use pathfinder_geometry::rect::RectF;
|
||||
use pathfinder_geometry::util::lerp;
|
||||
use pathfinder_geometry::vector::{Vector2F, Vector4F};
|
||||
use smallvec::SmallVec;
|
||||
use std::fmt::Debug;
|
||||
use std::mem;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct Edge(LineSegment2F);
|
||||
|
||||
impl TEdge for Edge {
|
||||
#[inline]
|
||||
fn point_is_inside(&self, point: Vector2F) -> bool {
|
||||
let area = (self.0.to() - self.0.from()).det(point - self.0.from());
|
||||
debug!("point_is_inside({:?}, {:?}), area={}", self, point, area);
|
||||
area >= 0.0
|
||||
}
|
||||
|
||||
fn intersect_line_segment(&self, segment: LineSegment2F) -> ArrayVec<[f32; 3]> {
|
||||
let mut results = ArrayVec::new();
|
||||
if let Some(t) = segment.intersection_t(self.0) {
|
||||
if t >= 0.0 && t <= 1.0 {
|
||||
results.push(t);
|
||||
}
|
||||
}
|
||||
results
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
enum AxisAlignedEdge {
|
||||
Left(f32),
|
||||
Top(f32),
|
||||
Right(f32),
|
||||
Bottom(f32),
|
||||
}
|
||||
|
||||
impl TEdge for AxisAlignedEdge {
|
||||
#[inline]
|
||||
fn point_is_inside(&self, point: Vector2F) -> bool {
|
||||
match *self {
|
||||
AxisAlignedEdge::Left(x) => point.x() >= x,
|
||||
AxisAlignedEdge::Top(y) => point.y() >= y,
|
||||
AxisAlignedEdge::Right(x) => point.x() <= x,
|
||||
AxisAlignedEdge::Bottom(y) => point.y() <= y,
|
||||
}
|
||||
}
|
||||
|
||||
fn intersect_line_segment(&self, segment: LineSegment2F) -> ArrayVec<[f32; 3]> {
|
||||
let mut results = ArrayVec::new();
|
||||
let t = match *self {
|
||||
AxisAlignedEdge::Left(x) | AxisAlignedEdge::Right(x) => segment.solve_t_for_x(x),
|
||||
AxisAlignedEdge::Top(y) | AxisAlignedEdge::Bottom(y) => segment.solve_t_for_y(y),
|
||||
};
|
||||
if t >= 0.0 && t <= 1.0 {
|
||||
results.push(t);
|
||||
}
|
||||
results
|
||||
}
|
||||
}
|
||||
|
||||
trait TEdge: Debug {
|
||||
fn point_is_inside(&self, point: Vector2F) -> bool;
|
||||
fn intersect_line_segment(&self, segment: LineSegment2F) -> ArrayVec<[f32; 3]>;
|
||||
|
||||
fn trivially_test_segment(&self, segment: &Segment) -> EdgeRelativeLocation {
|
||||
let from_inside = self.point_is_inside(segment.baseline.from());
|
||||
debug!(
|
||||
"point {:?} inside {:?}: {:?}",
|
||||
segment.baseline.from(),
|
||||
self,
|
||||
from_inside
|
||||
);
|
||||
if from_inside != self.point_is_inside(segment.baseline.to()) {
|
||||
return EdgeRelativeLocation::Intersecting;
|
||||
}
|
||||
if !segment.is_line() {
|
||||
if from_inside != self.point_is_inside(segment.ctrl.from()) {
|
||||
return EdgeRelativeLocation::Intersecting;
|
||||
}
|
||||
if !segment.is_quadratic() {
|
||||
if from_inside != self.point_is_inside(segment.ctrl.to()) {
|
||||
return EdgeRelativeLocation::Intersecting;
|
||||
}
|
||||
}
|
||||
}
|
||||
if from_inside {
|
||||
EdgeRelativeLocation::Inside
|
||||
} else {
|
||||
EdgeRelativeLocation::Outside
|
||||
}
|
||||
}
|
||||
|
||||
fn intersect_segment(&self, segment: &Segment) -> ArrayVec<[f32; 3]> {
|
||||
if segment.is_line() {
|
||||
return self.intersect_line_segment(segment.baseline);
|
||||
}
|
||||
|
||||
let mut segment = *segment;
|
||||
if segment.is_quadratic() {
|
||||
segment = segment.to_cubic();
|
||||
}
|
||||
|
||||
let mut results = ArrayVec::new();
|
||||
let mut prev_t = 0.0;
|
||||
while !results.is_full() {
|
||||
if prev_t >= 1.0 {
|
||||
break;
|
||||
}
|
||||
let next_t = match self.intersect_cubic_segment(&segment, prev_t, 1.0) {
|
||||
None => break,
|
||||
Some(next_t) => next_t,
|
||||
};
|
||||
results.push(next_t);
|
||||
prev_t = next_t + EPSILON;
|
||||
}
|
||||
return results;
|
||||
|
||||
const EPSILON: f32 = 0.0001;
|
||||
}
|
||||
|
||||
fn intersect_cubic_segment(
|
||||
&self,
|
||||
segment: &Segment,
|
||||
mut t_min: f32,
|
||||
mut t_max: f32,
|
||||
) -> Option<f32> {
|
||||
debug!(
|
||||
"... intersect_cubic_segment({:?}, {:?}, t=({}, {}))",
|
||||
self, segment, t_min, t_max
|
||||
);
|
||||
|
||||
let mut segment = segment.as_cubic_segment().split_after(t_min);
|
||||
segment = segment
|
||||
.as_cubic_segment()
|
||||
.split_before(t_max / (1.0 - t_min));
|
||||
|
||||
if !self.intersects_cubic_segment_hull(segment.as_cubic_segment()) {
|
||||
return None;
|
||||
}
|
||||
|
||||
loop {
|
||||
let t_mid = lerp(t_min, t_max, 0.5);
|
||||
if t_max - t_min < 0.00001 {
|
||||
return Some(t_mid);
|
||||
}
|
||||
|
||||
let (prev_segment, next_segment) = segment.as_cubic_segment().split(0.5);
|
||||
if self.intersects_cubic_segment_hull(prev_segment.as_cubic_segment()) {
|
||||
t_max = t_mid;
|
||||
segment = prev_segment;
|
||||
} else if self.intersects_cubic_segment_hull(next_segment.as_cubic_segment()) {
|
||||
t_min = t_mid;
|
||||
segment = next_segment;
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn intersects_cubic_segment_hull(&self, cubic_segment: CubicSegment) -> bool {
|
||||
let inside = self.point_is_inside(cubic_segment.0.baseline.from());
|
||||
inside != self.point_is_inside(cubic_segment.0.ctrl.from())
|
||||
|| inside != self.point_is_inside(cubic_segment.0.ctrl.to())
|
||||
|| inside != self.point_is_inside(cubic_segment.0.baseline.to())
|
||||
}
|
||||
}
|
||||
|
||||
trait ContourClipper
|
||||
where
|
||||
Self::Edge: TEdge + Debug,
|
||||
{
|
||||
type Edge;
|
||||
|
||||
fn contour_mut(&mut self) -> &mut Contour;
|
||||
|
||||
fn clip_against(&mut self, edge: Self::Edge) {
|
||||
// Fast path to avoid allocation in the no-clip case.
|
||||
match self.check_for_fast_clip(&edge) {
|
||||
FastClipResult::SlowPath => {}
|
||||
FastClipResult::AllInside => return,
|
||||
FastClipResult::AllOutside => {
|
||||
*self.contour_mut() = Contour::new();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let input = self.contour_mut().take();
|
||||
for segment in input.iter(ContourIterFlags::empty()) {
|
||||
self.clip_segment_against(segment, &edge);
|
||||
}
|
||||
if input.is_closed() {
|
||||
self.contour_mut().close();
|
||||
}
|
||||
}
|
||||
|
||||
fn clip_segment_against(&mut self, mut segment: Segment, edge: &Self::Edge) {
|
||||
// Easy cases.
|
||||
match edge.trivially_test_segment(&segment) {
|
||||
EdgeRelativeLocation::Outside => return,
|
||||
EdgeRelativeLocation::Inside => {
|
||||
debug!("trivial test inside, pushing segment");
|
||||
self.push_segment(&segment);
|
||||
return;
|
||||
}
|
||||
EdgeRelativeLocation::Intersecting => {}
|
||||
}
|
||||
|
||||
// We have a potential intersection.
|
||||
debug!("potential intersection: {:?} edge: {:?}", segment, edge);
|
||||
let mut starts_inside = edge.point_is_inside(segment.baseline.from());
|
||||
let intersection_ts = edge.intersect_segment(&segment);
|
||||
let mut last_t = 0.0;
|
||||
debug!("... intersections: {:?}", intersection_ts);
|
||||
for t in intersection_ts {
|
||||
let (before_split, after_split) = segment.split((t - last_t) / (1.0 - last_t));
|
||||
|
||||
// Push the split segment if appropriate.
|
||||
debug!(
|
||||
"... ... edge={:?} before_split={:?} t={:?} starts_inside={:?}",
|
||||
edge, before_split, t, starts_inside
|
||||
);
|
||||
if starts_inside {
|
||||
debug!("... split segment case, pushing segment");
|
||||
self.push_segment(&before_split);
|
||||
}
|
||||
|
||||
// We've now transitioned from inside to outside or vice versa.
|
||||
starts_inside = !starts_inside;
|
||||
last_t = t;
|
||||
segment = after_split;
|
||||
}
|
||||
|
||||
// No more intersections. Push the last segment if applicable.
|
||||
if starts_inside {
|
||||
debug!("... last segment case, pushing segment");
|
||||
self.push_segment(&segment);
|
||||
}
|
||||
}
|
||||
|
||||
fn push_segment(&mut self, segment: &Segment) {
|
||||
let contour = self.contour_mut();
|
||||
if let Some(last_position) = contour.last_position() {
|
||||
if last_position != segment.baseline.from() {
|
||||
// Add a line to join up segments.
|
||||
contour.push_point(segment.baseline.from(), PointFlags::empty(), true);
|
||||
}
|
||||
}
|
||||
|
||||
contour.push_segment(segment, PushSegmentFlags::UPDATE_BOUNDS);
|
||||
}
|
||||
|
||||
fn check_for_fast_clip(&mut self, edge: &Self::Edge) -> FastClipResult {
|
||||
let mut result = None;
|
||||
for segment in self.contour_mut().iter(ContourIterFlags::empty()) {
|
||||
let location = edge.trivially_test_segment(&segment);
|
||||
match (result, location) {
|
||||
(None, EdgeRelativeLocation::Outside) => {
|
||||
result = Some(FastClipResult::AllOutside);
|
||||
}
|
||||
(None, EdgeRelativeLocation::Inside) => {
|
||||
result = Some(FastClipResult::AllInside);
|
||||
}
|
||||
(Some(FastClipResult::AllInside), EdgeRelativeLocation::Inside)
|
||||
| (Some(FastClipResult::AllOutside), EdgeRelativeLocation::Outside) => {}
|
||||
(_, _) => return FastClipResult::SlowPath,
|
||||
}
|
||||
}
|
||||
result.unwrap_or(FastClipResult::AllOutside)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum FastClipResult {
|
||||
SlowPath,
|
||||
AllInside,
|
||||
AllOutside,
|
||||
}
|
||||
|
||||
// General convex polygon clipping in 2D
|
||||
|
||||
pub(crate) struct ContourPolygonClipper {
|
||||
clip_polygon: SmallVec<[Vector2F; 4]>,
|
||||
contour: Contour,
|
||||
}
|
||||
|
||||
impl ContourClipper for ContourPolygonClipper {
|
||||
type Edge = Edge;
|
||||
|
||||
#[inline]
|
||||
fn contour_mut(&mut self) -> &mut Contour {
|
||||
&mut self.contour
|
||||
}
|
||||
}
|
||||
|
||||
impl ContourPolygonClipper {
|
||||
#[inline]
|
||||
pub(crate) fn new(clip_polygon: &[Vector2F], contour: Contour) -> ContourPolygonClipper {
|
||||
ContourPolygonClipper {
|
||||
clip_polygon: SmallVec::from_slice(clip_polygon),
|
||||
contour,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn clip(mut self) -> Contour {
|
||||
// TODO(pcwalton): Maybe have a coarse circumscribed rect and use that for clipping?
|
||||
|
||||
let clip_polygon = mem::replace(&mut self.clip_polygon, SmallVec::default());
|
||||
let mut prev = match clip_polygon.last() {
|
||||
None => return Contour::new(),
|
||||
Some(prev) => *prev,
|
||||
};
|
||||
for &next in &clip_polygon {
|
||||
self.clip_against(Edge(LineSegment2F::new(prev, next)));
|
||||
prev = next;
|
||||
}
|
||||
|
||||
self.contour
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum EdgeRelativeLocation {
|
||||
Intersecting,
|
||||
Inside,
|
||||
Outside,
|
||||
}
|
||||
|
||||
// Fast axis-aligned box 2D clipping
|
||||
|
||||
pub(crate) struct ContourRectClipper {
|
||||
clip_rect: RectF,
|
||||
contour: Contour,
|
||||
}
|
||||
|
||||
impl ContourClipper for ContourRectClipper {
|
||||
type Edge = AxisAlignedEdge;
|
||||
|
||||
#[inline]
|
||||
fn contour_mut(&mut self) -> &mut Contour {
|
||||
&mut self.contour
|
||||
}
|
||||
}
|
||||
|
||||
impl ContourRectClipper {
|
||||
#[inline]
|
||||
pub(crate) fn new(clip_rect: RectF, contour: Contour) -> ContourRectClipper {
|
||||
ContourRectClipper { clip_rect, contour }
|
||||
}
|
||||
|
||||
pub(crate) fn clip(mut self) -> Contour {
|
||||
if self.clip_rect.contains_rect(self.contour.bounds()) {
|
||||
return self.contour;
|
||||
}
|
||||
|
||||
self.clip_against(AxisAlignedEdge::Left(self.clip_rect.min_x()));
|
||||
self.clip_against(AxisAlignedEdge::Top(self.clip_rect.min_y()));
|
||||
self.clip_against(AxisAlignedEdge::Right(self.clip_rect.max_x()));
|
||||
self.clip_against(AxisAlignedEdge::Bottom(self.clip_rect.max_y()));
|
||||
|
||||
self.contour
|
||||
}
|
||||
}
|
||||
|
||||
// 3D quad clipping
|
||||
|
||||
pub struct PolygonClipper3D {
|
||||
subject: Vec<Vector4F>,
|
||||
}
|
||||
|
||||
impl PolygonClipper3D {
|
||||
#[inline]
|
||||
pub fn new(subject: Vec<Vector4F>) -> PolygonClipper3D {
|
||||
PolygonClipper3D { subject }
|
||||
}
|
||||
|
||||
pub fn clip(mut self) -> Vec<Vector4F> {
|
||||
// TODO(pcwalton): Fast path for completely contained polygon?
|
||||
|
||||
debug!("before clipping against bottom: {:?}", self.subject);
|
||||
self.clip_against(Edge3D::Bottom);
|
||||
debug!("before clipping against top: {:?}", self.subject);
|
||||
self.clip_against(Edge3D::Top);
|
||||
debug!("before clipping against left: {:?}", self.subject);
|
||||
self.clip_against(Edge3D::Left);
|
||||
debug!("before clipping against right: {:?}", self.subject);
|
||||
self.clip_against(Edge3D::Right);
|
||||
debug!("before clipping against far: {:?}", self.subject);
|
||||
self.clip_against(Edge3D::Far);
|
||||
debug!("before clipping against near: {:?}", self.subject);
|
||||
self.clip_against(Edge3D::Near);
|
||||
debug!("after clipping: {:?}", self.subject);
|
||||
|
||||
self.subject
|
||||
}
|
||||
|
||||
fn clip_against(&mut self, edge: Edge3D) {
|
||||
let input = mem::replace(&mut self.subject, vec![]);
|
||||
let mut prev = match input.last() {
|
||||
None => return,
|
||||
Some(point) => *point,
|
||||
};
|
||||
for next in input {
|
||||
if edge.point_is_inside(next) {
|
||||
if !edge.point_is_inside(prev) {
|
||||
self.subject.push(edge.line_intersection(prev, next));
|
||||
}
|
||||
self.subject.push(next);
|
||||
} else if edge.point_is_inside(prev) {
|
||||
self.subject.push(edge.line_intersection(prev, next));
|
||||
}
|
||||
prev = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
enum Edge3D {
|
||||
Left,
|
||||
Right,
|
||||
Bottom,
|
||||
Top,
|
||||
Near,
|
||||
Far,
|
||||
}
|
||||
|
||||
impl Edge3D {
|
||||
#[inline]
|
||||
fn point_is_inside(self, point: Vector4F) -> bool {
|
||||
let w = point.w();
|
||||
match self {
|
||||
Edge3D::Left => point.x() >= -w,
|
||||
Edge3D::Right => point.x() <= w,
|
||||
Edge3D::Bottom => point.y() >= -w,
|
||||
Edge3D::Top => point.y() <= w,
|
||||
Edge3D::Near => point.z() >= -w,
|
||||
Edge3D::Far => point.z() <= w,
|
||||
}
|
||||
}
|
||||
|
||||
// Blinn & Newell, "Clipping using homogeneous coordinates", SIGGRAPH 1978.
|
||||
fn line_intersection(self, prev: Vector4F, next: Vector4F) -> Vector4F {
|
||||
let (x0, x1) = match self {
|
||||
Edge3D::Left | Edge3D::Right => (prev.x(), next.x()),
|
||||
Edge3D::Bottom | Edge3D::Top => (prev.y(), next.y()),
|
||||
Edge3D::Near | Edge3D::Far => (prev.z(), next.z()),
|
||||
};
|
||||
let (w0, w1) = (prev.w(), next.w());
|
||||
let sign = match self {
|
||||
Edge3D::Left | Edge3D::Bottom | Edge3D::Near => -1.0,
|
||||
Edge3D::Right | Edge3D::Top | Edge3D::Far => 1.0,
|
||||
};
|
||||
let alpha = ((x0 - sign * w0) as f64) / ((sign * (w1 - w0) - (x1 - x0)) as f64);
|
||||
prev.lerp(next, alpha as f32)
|
||||
}
|
||||
}
|
||||
|
||||
/// Coarse collision detection
|
||||
|
||||
// Separating axis theorem. Requires that the polygon be convex.
|
||||
pub(crate) fn rect_is_outside_polygon(rect: RectF, polygon_points: &[Vector2F]) -> bool {
|
||||
let mut outcode = Outcode::all();
|
||||
for point in polygon_points {
|
||||
if point.x() > rect.min_x() {
|
||||
outcode.remove(Outcode::LEFT);
|
||||
}
|
||||
if point.x() < rect.max_x() {
|
||||
outcode.remove(Outcode::RIGHT);
|
||||
}
|
||||
if point.y() > rect.min_y() {
|
||||
outcode.remove(Outcode::TOP);
|
||||
}
|
||||
if point.y() < rect.max_y() {
|
||||
outcode.remove(Outcode::BOTTOM);
|
||||
}
|
||||
}
|
||||
if !outcode.is_empty() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// FIXME(pcwalton): Check winding!
|
||||
let rect_points = [
|
||||
rect.origin(),
|
||||
rect.upper_right(),
|
||||
rect.lower_left(),
|
||||
rect.lower_right(),
|
||||
];
|
||||
for (next_point_index, &next) in polygon_points.iter().enumerate() {
|
||||
let prev_point_index = if next_point_index == 0 {
|
||||
polygon_points.len() - 1
|
||||
} else {
|
||||
next_point_index - 1
|
||||
};
|
||||
let prev = polygon_points[prev_point_index];
|
||||
let polygon_edge_vector = next - prev;
|
||||
if rect_points
|
||||
.iter()
|
||||
.all(|&rect_point| polygon_edge_vector.det(rect_point - prev) < 0.0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
// Edge equation method. Requires that the polygon be convex.
|
||||
pub(crate) fn rect_is_inside_polygon(rect: RectF, polygon_points: &[Vector2F]) -> bool {
|
||||
// FIXME(pcwalton): Check winding!
|
||||
let rect_points = [
|
||||
rect.origin(),
|
||||
rect.upper_right(),
|
||||
rect.lower_left(),
|
||||
rect.lower_right(),
|
||||
];
|
||||
for (next_point_index, &next) in polygon_points.iter().enumerate() {
|
||||
let prev_point_index = if next_point_index == 0 {
|
||||
polygon_points.len() - 1
|
||||
} else {
|
||||
next_point_index - 1
|
||||
};
|
||||
let prev = polygon_points[prev_point_index];
|
||||
let polygon_edge_vector = next - prev;
|
||||
for &rect_point in &rect_points {
|
||||
if polygon_edge_vector.det(rect_point - prev) < 0.0 {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
struct Outcode: u8 {
|
||||
const LEFT = 0x01;
|
||||
const RIGHT = 0x02;
|
||||
const TOP = 0x04;
|
||||
const BOTTOM = 0x08;
|
||||
}
|
||||
}
|
|
@ -1,134 +0,0 @@
|
|||
// pathfinder/content/src/dash.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.
|
||||
|
||||
//! Line dashing support.
|
||||
|
||||
use crate::outline::{Contour, ContourIterFlags, Outline, PushSegmentFlags};
|
||||
use std::mem;
|
||||
|
||||
const EPSILON: f32 = 0.0001;
|
||||
|
||||
pub struct OutlineDash<'a> {
|
||||
input: &'a Outline,
|
||||
output: Outline,
|
||||
state: DashState<'a>,
|
||||
}
|
||||
|
||||
impl<'a> OutlineDash<'a> {
|
||||
#[inline]
|
||||
pub fn new(input: &'a Outline, dashes: &'a [f32], offset: f32) -> OutlineDash<'a> {
|
||||
OutlineDash { input, output: Outline::new(), state: DashState::new(dashes, offset) }
|
||||
}
|
||||
|
||||
pub fn dash(&mut self) {
|
||||
for contour in &self.input.contours {
|
||||
ContourDash::new(contour, &mut self.output, &mut self.state).dash()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_outline(mut self) -> Outline {
|
||||
if self.state.is_on() {
|
||||
self.output.push_contour(self.state.output);
|
||||
}
|
||||
self.output
|
||||
}
|
||||
}
|
||||
|
||||
struct ContourDash<'a, 'b, 'c> {
|
||||
input: &'a Contour,
|
||||
output: &'b mut Outline,
|
||||
state: &'c mut DashState<'a>,
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'c> ContourDash<'a, 'b, 'c> {
|
||||
fn new(input: &'a Contour, output: &'b mut Outline, state: &'c mut DashState<'a>)
|
||||
-> ContourDash<'a, 'b, 'c> {
|
||||
ContourDash { input, output, state }
|
||||
}
|
||||
|
||||
fn dash(&mut self) {
|
||||
let mut iterator = self.input.iter(ContourIterFlags::empty());
|
||||
let mut queued_segment = None;
|
||||
loop {
|
||||
if queued_segment.is_none() {
|
||||
match iterator.next() {
|
||||
None => break,
|
||||
Some(segment) => queued_segment = Some(segment),
|
||||
}
|
||||
}
|
||||
|
||||
let mut current_segment = queued_segment.take().unwrap();
|
||||
let mut distance = self.state.distance_left;
|
||||
|
||||
let t = current_segment.time_for_distance(distance);
|
||||
if t < 1.0 {
|
||||
let (prev_segment, next_segment) = current_segment.split(t);
|
||||
current_segment = prev_segment;
|
||||
queued_segment = Some(next_segment);
|
||||
} else {
|
||||
distance = current_segment.arc_length();
|
||||
}
|
||||
|
||||
if self.state.is_on() {
|
||||
self.state.output.push_segment(¤t_segment, PushSegmentFlags::empty());
|
||||
}
|
||||
|
||||
self.state.distance_left -= distance;
|
||||
if self.state.distance_left < EPSILON {
|
||||
if self.state.is_on() {
|
||||
self.output.push_contour(mem::replace(&mut self.state.output, Contour::new()));
|
||||
}
|
||||
|
||||
self.state.current_dash_index += 1;
|
||||
if self.state.current_dash_index == self.state.dashes.len() {
|
||||
self.state.current_dash_index = 0;
|
||||
}
|
||||
|
||||
self.state.distance_left = self.state.dashes[self.state.current_dash_index];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DashState<'a> {
|
||||
output: Contour,
|
||||
dashes: &'a [f32],
|
||||
current_dash_index: usize,
|
||||
distance_left: f32,
|
||||
}
|
||||
|
||||
impl<'a> DashState<'a> {
|
||||
fn new(dashes: &'a [f32], mut offset: f32) -> DashState<'a> {
|
||||
let total: f32 = dashes.iter().cloned().sum();
|
||||
offset %= total;
|
||||
|
||||
let mut current_dash_index = 0;
|
||||
while current_dash_index < dashes.len() {
|
||||
let dash = dashes[current_dash_index];
|
||||
if offset < dash {
|
||||
break;
|
||||
}
|
||||
offset -= dash;
|
||||
current_dash_index += 1;
|
||||
}
|
||||
|
||||
DashState {
|
||||
output: Contour::new(),
|
||||
dashes,
|
||||
current_dash_index,
|
||||
distance_left: offset,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_on(&self) -> bool {
|
||||
self.current_dash_index % 2 == 0
|
||||
}
|
||||
}
|
|
@ -1,125 +0,0 @@
|
|||
// pathfinder/content/src/dilation.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::orientation::Orientation;
|
||||
use crate::outline::Contour;
|
||||
use pathfinder_geometry::vector::{Vector2F, vec2f};
|
||||
|
||||
pub struct ContourDilator<'a> {
|
||||
contour: &'a mut Contour,
|
||||
amount: Vector2F,
|
||||
orientation: Orientation,
|
||||
}
|
||||
|
||||
impl<'a> ContourDilator<'a> {
|
||||
pub fn new(
|
||||
contour: &'a mut Contour,
|
||||
amount: Vector2F,
|
||||
orientation: Orientation,
|
||||
) -> ContourDilator<'a> {
|
||||
ContourDilator {
|
||||
contour,
|
||||
amount,
|
||||
orientation,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dilate(&mut self) {
|
||||
// Determine orientation.
|
||||
let scale = self.amount * (match self.orientation {
|
||||
Orientation::Ccw => vec2f( 1.0, -1.0),
|
||||
Orientation::Cw => vec2f(-1.0, 1.0),
|
||||
});
|
||||
|
||||
// Find the starting and previous positions.
|
||||
let first_position = self.contour.position_of(0);
|
||||
let mut prev_point_index = 0;
|
||||
let mut prev_position;
|
||||
|
||||
loop {
|
||||
prev_point_index = self.contour.prev_point_index_of(prev_point_index);
|
||||
prev_position = self.contour.position_of(prev_point_index);
|
||||
if prev_point_index == 0 || prev_position != first_position {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize our loop.
|
||||
let first_point_index = self.contour.next_point_index_of(prev_point_index);
|
||||
let mut current_point_index = first_point_index;
|
||||
let mut position = first_position;
|
||||
|
||||
let mut prev_vector = (position - prev_position).normalize();
|
||||
|
||||
loop {
|
||||
// Find the next non-degenerate position.
|
||||
let mut next_point_index = current_point_index;
|
||||
let mut next_position;
|
||||
loop {
|
||||
next_point_index = self.contour.next_point_index_of(next_point_index);
|
||||
if next_point_index == first_point_index {
|
||||
next_position = first_position;
|
||||
break;
|
||||
}
|
||||
next_position = self.contour.position_of(next_point_index);
|
||||
if next_point_index == current_point_index || next_position != position {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let next_vector = (next_position - position).normalize();
|
||||
|
||||
debug!(
|
||||
"prev={} cur={} next={}",
|
||||
prev_point_index, current_point_index, next_point_index
|
||||
);
|
||||
|
||||
// Calculate new position by moving the point by the bisector.
|
||||
let bisector = prev_vector.yx() + next_vector.yx();
|
||||
let bisector_length = bisector.length();
|
||||
let scaled_bisector = if bisector_length == 0.0 {
|
||||
Vector2F::zero()
|
||||
} else {
|
||||
bisector * scale * (1.0 / bisector_length)
|
||||
};
|
||||
let new_position = position - scaled_bisector;
|
||||
|
||||
debug!(
|
||||
"dilate(): prev={}({:?}) cur={}({:?}) next={}({:?}) bisector={:?}({:?}, {:?})",
|
||||
prev_point_index,
|
||||
prev_position,
|
||||
current_point_index,
|
||||
position,
|
||||
next_point_index,
|
||||
next_position,
|
||||
bisector,
|
||||
bisector_length,
|
||||
scaled_bisector
|
||||
);
|
||||
|
||||
// Update all points.
|
||||
let mut point_index = current_point_index;
|
||||
while point_index != next_point_index {
|
||||
self.contour.points[point_index as usize] = new_position;
|
||||
debug!("... updating {:?}", point_index);
|
||||
point_index = self.contour.next_point_index_of(point_index);
|
||||
}
|
||||
|
||||
// Check to see if we're done.
|
||||
if next_point_index == first_point_index {
|
||||
break;
|
||||
}
|
||||
|
||||
// Continue.
|
||||
prev_vector = next_vector;
|
||||
position = next_position;
|
||||
current_point_index = next_point_index;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,205 +0,0 @@
|
|||
// pathfinder/content/src/effects.rs
|
||||
//
|
||||
// Copyright © 2020 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.
|
||||
|
||||
//! Special effects that can be applied to layers.
|
||||
|
||||
use pathfinder_color::ColorF;
|
||||
use pathfinder_geometry::line_segment::LineSegment2F;
|
||||
use pathfinder_geometry::vector::Vector2F;
|
||||
use pathfinder_simd::default::F32x2;
|
||||
|
||||
/// This intentionally does not precisely match what Core Graphics does (a
|
||||
/// Lanczos function), because we don't want any ringing artefacts.
|
||||
pub static DEFRINGING_KERNEL_CORE_GRAPHICS: DefringingKernel =
|
||||
DefringingKernel([0.033165660, 0.102074051, 0.221434336, 0.286651906]);
|
||||
pub static DEFRINGING_KERNEL_FREETYPE: DefringingKernel =
|
||||
DefringingKernel([0.0, 0.031372549, 0.301960784, 0.337254902]);
|
||||
|
||||
/// Should match macOS 10.13 High Sierra.
|
||||
pub static STEM_DARKENING_FACTORS: [f32; 2] = [0.0121, 0.0121 * 1.25];
|
||||
|
||||
/// Should match macOS 10.13 High Sierra.
|
||||
pub const MAX_STEM_DARKENING_AMOUNT: [f32; 2] = [0.3, 0.3];
|
||||
|
||||
/// This value is a subjective cutoff. Above this ppem value, no stem darkening is performed.
|
||||
pub const MAX_STEM_DARKENING_PIXELS_PER_EM: f32 = 72.0;
|
||||
|
||||
/// The shader that should be used when compositing this layer onto its destination.
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
pub enum Filter {
|
||||
/// No special filter.
|
||||
None,
|
||||
|
||||
/// Converts a linear gradient to a radial one.
|
||||
RadialGradient {
|
||||
/// The line that the circles lie along.
|
||||
line: LineSegment2F,
|
||||
/// The radii of the circles at the two endpoints.
|
||||
radii: F32x2,
|
||||
/// The origin of the linearized gradient in the texture.
|
||||
uv_origin: Vector2F,
|
||||
},
|
||||
|
||||
PatternFilter(PatternFilter),
|
||||
}
|
||||
|
||||
/// Shaders applicable to patterns.
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
pub enum PatternFilter {
|
||||
/// Performs postprocessing operations useful for monochrome text.
|
||||
Text {
|
||||
/// The foreground color of the text.
|
||||
fg_color: ColorF,
|
||||
/// The background color of the text.
|
||||
bg_color: ColorF,
|
||||
/// The kernel used for defringing, if subpixel AA is enabled.
|
||||
defringing_kernel: Option<DefringingKernel>,
|
||||
/// Whether gamma correction is used when compositing.
|
||||
///
|
||||
/// If this is enabled, stem darkening is advised.
|
||||
gamma_correction: bool,
|
||||
},
|
||||
|
||||
/// A blur operation in one direction, either horizontal or vertical.
|
||||
///
|
||||
/// To produce a full Gaussian blur, perform two successive blur operations, one in each
|
||||
/// direction.
|
||||
Blur {
|
||||
direction: BlurDirection,
|
||||
sigma: f32,
|
||||
},
|
||||
}
|
||||
|
||||
/// Blend modes that can be applied to individual paths.
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
pub enum BlendMode {
|
||||
// Porter-Duff, supported by GPU blender
|
||||
Clear,
|
||||
Copy,
|
||||
SrcIn,
|
||||
SrcOut,
|
||||
SrcOver,
|
||||
SrcAtop,
|
||||
DestIn,
|
||||
DestOut,
|
||||
DestOver,
|
||||
DestAtop,
|
||||
Xor,
|
||||
Lighter,
|
||||
|
||||
// Others, unsupported by GPU blender
|
||||
Darken,
|
||||
Lighten,
|
||||
Multiply,
|
||||
Screen,
|
||||
HardLight,
|
||||
Overlay,
|
||||
ColorDodge,
|
||||
ColorBurn,
|
||||
SoftLight,
|
||||
Difference,
|
||||
Exclusion,
|
||||
Hue,
|
||||
Saturation,
|
||||
Color,
|
||||
Luminosity,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
pub struct DefringingKernel(pub [f32; 4]);
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
pub enum BlurDirection {
|
||||
X,
|
||||
Y,
|
||||
}
|
||||
|
||||
impl Default for BlendMode {
|
||||
#[inline]
|
||||
fn default() -> BlendMode {
|
||||
BlendMode::SrcOver
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Filter {
|
||||
#[inline]
|
||||
fn default() -> Filter {
|
||||
Filter::None
|
||||
}
|
||||
}
|
||||
|
||||
impl BlendMode {
|
||||
/// Whether the backdrop is irrelevant when applying this blend mode (i.e. destination blend
|
||||
/// factor is zero when source alpha is one).
|
||||
#[inline]
|
||||
pub fn occludes_backdrop(self) -> bool {
|
||||
match self {
|
||||
BlendMode::SrcOver | BlendMode::Clear => true,
|
||||
BlendMode::DestOver |
|
||||
BlendMode::DestOut |
|
||||
BlendMode::SrcAtop |
|
||||
BlendMode::Xor |
|
||||
BlendMode::Lighter |
|
||||
BlendMode::Lighten |
|
||||
BlendMode::Darken |
|
||||
BlendMode::Copy |
|
||||
BlendMode::SrcIn |
|
||||
BlendMode::DestIn |
|
||||
BlendMode::SrcOut |
|
||||
BlendMode::DestAtop |
|
||||
BlendMode::Multiply |
|
||||
BlendMode::Screen |
|
||||
BlendMode::HardLight |
|
||||
BlendMode::Overlay |
|
||||
BlendMode::ColorDodge |
|
||||
BlendMode::ColorBurn |
|
||||
BlendMode::SoftLight |
|
||||
BlendMode::Difference |
|
||||
BlendMode::Exclusion |
|
||||
BlendMode::Hue |
|
||||
BlendMode::Saturation |
|
||||
BlendMode::Color |
|
||||
BlendMode::Luminosity => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// True if this blend mode does not preserve destination areas outside the source.
|
||||
pub fn is_destructive(self) -> bool {
|
||||
match self {
|
||||
BlendMode::Clear |
|
||||
BlendMode::Copy |
|
||||
BlendMode::SrcIn |
|
||||
BlendMode::DestIn |
|
||||
BlendMode::SrcOut |
|
||||
BlendMode::DestAtop => true,
|
||||
BlendMode::SrcOver |
|
||||
BlendMode::DestOver |
|
||||
BlendMode::DestOut |
|
||||
BlendMode::SrcAtop |
|
||||
BlendMode::Xor |
|
||||
BlendMode::Lighter |
|
||||
BlendMode::Lighten |
|
||||
BlendMode::Darken |
|
||||
BlendMode::Multiply |
|
||||
BlendMode::Screen |
|
||||
BlendMode::HardLight |
|
||||
BlendMode::Overlay |
|
||||
BlendMode::ColorDodge |
|
||||
BlendMode::ColorBurn |
|
||||
BlendMode::SoftLight |
|
||||
BlendMode::Difference |
|
||||
BlendMode::Exclusion |
|
||||
BlendMode::Hue |
|
||||
BlendMode::Saturation |
|
||||
BlendMode::Color |
|
||||
BlendMode::Luminosity => false,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
// pathfinder/content/src/fill.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.
|
||||
|
||||
//! Fill rules.
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
pub enum FillRule {
|
||||
Winding,
|
||||
EvenOdd,
|
||||
}
|
|
@ -1,205 +0,0 @@
|
|||
// pathfinder/content/src/gradient.rs
|
||||
//
|
||||
// Copyright © 2020 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::sorted_vector::SortedVector;
|
||||
use crate::util;
|
||||
use pathfinder_color::ColorU;
|
||||
use pathfinder_geometry::line_segment::LineSegment2F;
|
||||
use pathfinder_geometry::transform2d::Transform2F;
|
||||
use pathfinder_geometry::vector::Vector2F;
|
||||
use pathfinder_geometry::util as geometry_util;
|
||||
use pathfinder_simd::default::F32x2;
|
||||
use std::cmp::{Ordering, PartialOrd};
|
||||
use std::convert;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::mem;
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub struct Gradient {
|
||||
pub geometry: GradientGeometry,
|
||||
stops: SortedVector<ColorStop>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)]
|
||||
pub struct ColorStop {
|
||||
pub offset: f32,
|
||||
pub color: ColorU,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub enum GradientGeometry {
|
||||
Linear(LineSegment2F),
|
||||
Radial {
|
||||
/// The line that connects the two circles. It may have zero length for simple radial
|
||||
/// gradients.
|
||||
line: LineSegment2F,
|
||||
/// The radii of the two circles. The first value may be zero.
|
||||
radii: F32x2,
|
||||
/// Transform from radial gradient space into screen space.
|
||||
///
|
||||
/// Like `gradientTransform` in SVG. Note that this is the inverse of Cairo's gradient
|
||||
/// transform.
|
||||
transform: Transform2F,
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Gradient {}
|
||||
|
||||
impl Hash for Gradient {
|
||||
fn hash<H>(&self, state: &mut H) where H: Hasher {
|
||||
match self.geometry {
|
||||
GradientGeometry::Linear(line) => {
|
||||
(0).hash(state);
|
||||
util::hash_line_segment(line, state);
|
||||
}
|
||||
GradientGeometry::Radial { line, radii, transform } => {
|
||||
(1).hash(state);
|
||||
util::hash_line_segment(line, state);
|
||||
util::hash_f32(radii.x(), state);
|
||||
util::hash_f32(radii.y(), state);
|
||||
util::hash_f32(transform.m11(), state);
|
||||
util::hash_f32(transform.m12(), state);
|
||||
util::hash_f32(transform.m13(), state);
|
||||
util::hash_f32(transform.m21(), state);
|
||||
util::hash_f32(transform.m22(), state);
|
||||
util::hash_f32(transform.m23(), state);
|
||||
}
|
||||
}
|
||||
self.stops.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for ColorStop {}
|
||||
|
||||
impl Hash for ColorStop {
|
||||
fn hash<H>(&self, state: &mut H) where H: Hasher {
|
||||
unsafe {
|
||||
self.color.hash(state);
|
||||
let offset = mem::transmute::<f32, u32>(self.offset);
|
||||
offset.hash(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Gradient {
|
||||
#[inline]
|
||||
pub fn linear(line: LineSegment2F) -> Gradient {
|
||||
Gradient { geometry: GradientGeometry::Linear(line), stops: SortedVector::new() }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn linear_from_points(from: Vector2F, to: Vector2F) -> Gradient {
|
||||
Gradient::linear(LineSegment2F::new(from, to))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn radial<L>(line: L, radii: F32x2) -> Gradient where L: RadialGradientLine {
|
||||
let transform = Transform2F::default();
|
||||
Gradient {
|
||||
geometry: GradientGeometry::Radial { line: line.to_line(), radii, transform },
|
||||
stops: SortedVector::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn add(&mut self, stop: ColorStop) {
|
||||
self.stops.push(stop);
|
||||
}
|
||||
|
||||
/// A convenience method to add a color stop.
|
||||
#[inline]
|
||||
pub fn add_color_stop(&mut self, color: ColorU, offset: f32) {
|
||||
self.add(ColorStop::new(color, offset))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn stops(&self) -> &[ColorStop] {
|
||||
&self.stops.array
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn stops_mut(&mut self) -> &mut [ColorStop] {
|
||||
&mut self.stops.array
|
||||
}
|
||||
|
||||
pub fn sample(&self, mut t: f32) -> ColorU {
|
||||
if self.stops.is_empty() {
|
||||
return ColorU::transparent_black();
|
||||
}
|
||||
|
||||
t = geometry_util::clamp(t, 0.0, 1.0);
|
||||
let last_index = self.stops.len() - 1;
|
||||
let upper_index = self.stops.binary_search_by(|stop| {
|
||||
stop.offset.partial_cmp(&t).unwrap_or(Ordering::Less)
|
||||
}).unwrap_or_else(convert::identity).min(last_index);
|
||||
let lower_index = if upper_index > 0 { upper_index - 1 } else { upper_index };
|
||||
|
||||
let lower_stop = &self.stops.array[lower_index];
|
||||
let upper_stop = &self.stops.array[upper_index];
|
||||
|
||||
let denom = upper_stop.offset - lower_stop.offset;
|
||||
if denom == 0.0 {
|
||||
return lower_stop.color;
|
||||
}
|
||||
|
||||
lower_stop.color
|
||||
.to_f32()
|
||||
.lerp(upper_stop.color.to_f32(), (t - lower_stop.offset) / denom)
|
||||
.to_u8()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_opaque(&self) -> bool {
|
||||
self.stops.array.iter().all(|stop| stop.color.is_opaque())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_fully_transparent(&self) -> bool {
|
||||
self.stops.array.iter().all(|stop| stop.color.is_fully_transparent())
|
||||
}
|
||||
|
||||
pub fn apply_transform(&mut self, new_transform: Transform2F) {
|
||||
if new_transform.is_identity() {
|
||||
return;
|
||||
}
|
||||
|
||||
match self.geometry {
|
||||
GradientGeometry::Linear(ref mut line) => *line = new_transform * *line,
|
||||
GradientGeometry::Radial { ref mut transform, .. } => {
|
||||
*transform = new_transform * *transform
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ColorStop {
|
||||
#[inline]
|
||||
pub fn new(color: ColorU, offset: f32) -> ColorStop {
|
||||
ColorStop { color, offset }
|
||||
}
|
||||
}
|
||||
|
||||
pub trait RadialGradientLine {
|
||||
fn to_line(self) -> LineSegment2F;
|
||||
}
|
||||
|
||||
impl RadialGradientLine for LineSegment2F {
|
||||
#[inline]
|
||||
fn to_line(self) -> LineSegment2F {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RadialGradientLine for Vector2F {
|
||||
#[inline]
|
||||
fn to_line(self) -> LineSegment2F {
|
||||
LineSegment2F::new(self, self)
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
// pathfinder/content/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.
|
||||
|
||||
//! Pathfinder's representation of a vector scene.
|
||||
//!
|
||||
//! This module also contains various path utilities.
|
||||
|
||||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
pub mod clip;
|
||||
pub mod dash;
|
||||
pub mod effects;
|
||||
pub mod fill;
|
||||
pub mod gradient;
|
||||
pub mod orientation;
|
||||
pub mod outline;
|
||||
pub mod pattern;
|
||||
pub mod render_target;
|
||||
pub mod segment;
|
||||
pub mod sorted_vector;
|
||||
pub mod stroke;
|
||||
pub mod transform;
|
||||
|
||||
mod dilation;
|
||||
mod util;
|
|
@ -1,43 +0,0 @@
|
|||
// pathfinder/geometry/src/orientation.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::outline::Outline;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum Orientation {
|
||||
Ccw = -1,
|
||||
Cw = 1,
|
||||
}
|
||||
|
||||
impl Orientation {
|
||||
/// This follows the FreeType algorithm.
|
||||
pub fn from_outline(outline: &Outline) -> Orientation {
|
||||
let mut area = 0.0;
|
||||
for contour in &outline.contours {
|
||||
let mut prev_position = match contour.last_position() {
|
||||
None => continue,
|
||||
Some(position) => position,
|
||||
};
|
||||
for &next_position in &contour.points {
|
||||
area += prev_position.det(next_position);
|
||||
prev_position = next_position;
|
||||
}
|
||||
}
|
||||
Orientation::from_area(area)
|
||||
}
|
||||
|
||||
fn from_area(area: f32) -> Orientation {
|
||||
if area <= 0.0 {
|
||||
Orientation::Ccw
|
||||
} else {
|
||||
Orientation::Cw
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,927 +0,0 @@
|
|||
// pathfinder/content/src/outline.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.
|
||||
|
||||
//! A compressed in-memory representation of paths.
|
||||
|
||||
use crate::clip::{self, ContourPolygonClipper, ContourRectClipper};
|
||||
use crate::dilation::ContourDilator;
|
||||
use crate::orientation::Orientation;
|
||||
use crate::segment::{Segment, SegmentFlags, SegmentKind};
|
||||
use pathfinder_geometry::line_segment::LineSegment2F;
|
||||
use pathfinder_geometry::rect::RectF;
|
||||
use pathfinder_geometry::transform2d::Transform2F;
|
||||
use pathfinder_geometry::transform3d::Perspective;
|
||||
use pathfinder_geometry::unit_vector::UnitVector;
|
||||
use pathfinder_geometry::vector::{Vector2F, vec2f};
|
||||
use std::f32::consts::PI;
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::mem;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Outline {
|
||||
pub(crate) contours: Vec<Contour>,
|
||||
pub(crate) bounds: RectF,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Contour {
|
||||
pub(crate) points: Vec<Vector2F>,
|
||||
pub(crate) flags: Vec<PointFlags>,
|
||||
pub(crate) bounds: RectF,
|
||||
pub(crate) closed: bool,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct PointFlags: u8 {
|
||||
const CONTROL_POINT_0 = 0x01;
|
||||
const CONTROL_POINT_1 = 0x02;
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct PushSegmentFlags: u8 {
|
||||
const UPDATE_BOUNDS = 0x01;
|
||||
const INCLUDE_FROM_POINT = 0x02;
|
||||
}
|
||||
}
|
||||
|
||||
impl Outline {
|
||||
#[inline]
|
||||
pub fn new() -> Outline {
|
||||
Outline {
|
||||
contours: vec![],
|
||||
bounds: RectF::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_segments<I>(segments: I) -> Outline
|
||||
where
|
||||
I: Iterator<Item = Segment>,
|
||||
{
|
||||
let mut outline = Outline::new();
|
||||
let mut current_contour = Contour::new();
|
||||
|
||||
for segment in segments {
|
||||
if segment.flags.contains(SegmentFlags::FIRST_IN_SUBPATH) {
|
||||
if !current_contour.is_empty() {
|
||||
outline
|
||||
.contours
|
||||
.push(mem::replace(&mut current_contour, Contour::new()));
|
||||
}
|
||||
current_contour.push_point(segment.baseline.from(), PointFlags::empty(), true);
|
||||
}
|
||||
|
||||
if segment.flags.contains(SegmentFlags::CLOSES_SUBPATH) {
|
||||
if !current_contour.is_empty() {
|
||||
current_contour.close();
|
||||
let contour = mem::replace(&mut current_contour, Contour::new());
|
||||
outline.push_contour(contour);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if segment.is_none() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if !segment.is_line() {
|
||||
current_contour.push_point(segment.ctrl.from(), PointFlags::CONTROL_POINT_0, true);
|
||||
if !segment.is_quadratic() {
|
||||
current_contour.push_point(
|
||||
segment.ctrl.to(),
|
||||
PointFlags::CONTROL_POINT_1,
|
||||
true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
current_contour.push_point(segment.baseline.to(), PointFlags::empty(), true);
|
||||
}
|
||||
|
||||
outline.push_contour(current_contour);
|
||||
outline
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_rect(rect: RectF) -> Outline {
|
||||
let mut outline = Outline::new();
|
||||
outline.push_contour(Contour::from_rect(rect));
|
||||
outline
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn bounds(&self) -> RectF {
|
||||
self.bounds
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn contours(&self) -> &[Contour] {
|
||||
&self.contours
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn into_contours(self) -> Vec<Contour> {
|
||||
self.contours
|
||||
}
|
||||
|
||||
/// Removes all contours from this outline.
|
||||
#[inline]
|
||||
pub fn clear(&mut self) {
|
||||
self.contours.clear();
|
||||
self.bounds = RectF::default();
|
||||
}
|
||||
|
||||
pub fn push_contour(&mut self, contour: Contour) {
|
||||
if contour.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.contours.is_empty() {
|
||||
self.bounds = contour.bounds;
|
||||
} else {
|
||||
self.bounds = self.bounds.union_rect(contour.bounds);
|
||||
}
|
||||
|
||||
self.contours.push(contour);
|
||||
}
|
||||
|
||||
pub fn pop_contour(&mut self) -> Option<Contour> {
|
||||
let last_contour = self.contours.pop();
|
||||
|
||||
let mut new_bounds = None;
|
||||
for contour in &mut self.contours {
|
||||
contour.update_bounds(&mut new_bounds);
|
||||
}
|
||||
self.bounds = new_bounds.unwrap_or_else(|| RectF::default());
|
||||
|
||||
last_contour
|
||||
}
|
||||
|
||||
pub fn transform(&mut self, transform: &Transform2F) {
|
||||
if transform.is_identity() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut new_bounds = None;
|
||||
for contour in &mut self.contours {
|
||||
contour.transform(transform);
|
||||
contour.update_bounds(&mut new_bounds);
|
||||
}
|
||||
self.bounds = new_bounds.unwrap_or_else(|| RectF::default());
|
||||
}
|
||||
|
||||
pub fn apply_perspective(&mut self, perspective: &Perspective) {
|
||||
let mut new_bounds = None;
|
||||
for contour in &mut self.contours {
|
||||
contour.apply_perspective(perspective);
|
||||
contour.update_bounds(&mut new_bounds);
|
||||
}
|
||||
self.bounds = new_bounds.unwrap_or_else(|| RectF::default());
|
||||
}
|
||||
|
||||
pub fn dilate(&mut self, amount: Vector2F) {
|
||||
let orientation = Orientation::from_outline(self);
|
||||
self.contours
|
||||
.iter_mut()
|
||||
.for_each(|contour| contour.dilate(amount, orientation));
|
||||
self.bounds = self.bounds.dilate(amount);
|
||||
}
|
||||
|
||||
pub fn prepare_for_tiling(&mut self, view_box: RectF) {
|
||||
self.contours
|
||||
.iter_mut()
|
||||
.for_each(|contour| contour.prepare_for_tiling(view_box));
|
||||
self.bounds = self
|
||||
.bounds
|
||||
.intersection(view_box)
|
||||
.unwrap_or_else(|| RectF::default());
|
||||
}
|
||||
|
||||
pub fn is_outside_polygon(&self, clip_polygon: &[Vector2F]) -> bool {
|
||||
clip::rect_is_outside_polygon(self.bounds, clip_polygon)
|
||||
}
|
||||
|
||||
fn is_inside_polygon(&self, clip_polygon: &[Vector2F]) -> bool {
|
||||
clip::rect_is_inside_polygon(self.bounds, clip_polygon)
|
||||
}
|
||||
|
||||
pub fn clip_against_polygon(&mut self, clip_polygon: &[Vector2F]) {
|
||||
// Quick check.
|
||||
if self.is_inside_polygon(clip_polygon) {
|
||||
return;
|
||||
}
|
||||
|
||||
for contour in mem::replace(&mut self.contours, vec![]) {
|
||||
self.push_contour(ContourPolygonClipper::new(clip_polygon, contour).clip());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clip_against_rect(&mut self, clip_rect: RectF) {
|
||||
if clip_rect.contains_rect(self.bounds) {
|
||||
return;
|
||||
}
|
||||
|
||||
for contour in mem::replace(&mut self.contours, vec![]) {
|
||||
self.push_contour(ContourRectClipper::new(clip_rect, contour).clip());
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn close_all_contours(&mut self) {
|
||||
self.contours.iter_mut().for_each(|contour| contour.close());
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Outline {
|
||||
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
|
||||
for (contour_index, contour) in self.contours.iter().enumerate() {
|
||||
if contour_index > 0 {
|
||||
write!(formatter, " ")?;
|
||||
}
|
||||
contour.fmt(formatter)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Contour {
|
||||
#[inline]
|
||||
pub fn new() -> Contour {
|
||||
Contour {
|
||||
points: vec![],
|
||||
flags: vec![],
|
||||
bounds: RectF::default(),
|
||||
closed: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn with_capacity(length: usize) -> Contour {
|
||||
Contour {
|
||||
points: Vec::with_capacity(length),
|
||||
flags: Vec::with_capacity(length),
|
||||
bounds: RectF::default(),
|
||||
closed: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_rect(rect: RectF) -> Contour {
|
||||
let mut contour = Contour::new();
|
||||
contour.push_point(rect.origin(), PointFlags::empty(), false);
|
||||
contour.push_point(rect.upper_right(), PointFlags::empty(), false);
|
||||
contour.push_point(rect.lower_right(), PointFlags::empty(), false);
|
||||
contour.push_point(rect.lower_left(), PointFlags::empty(), false);
|
||||
contour.close();
|
||||
contour.bounds = rect;
|
||||
contour
|
||||
}
|
||||
|
||||
// Replaces this contour with a new one, with arrays preallocated to match `self`.
|
||||
#[inline]
|
||||
pub(crate) fn take(&mut self) -> Contour {
|
||||
let length = self.len() as usize;
|
||||
mem::replace(
|
||||
self,
|
||||
Contour {
|
||||
points: Vec::with_capacity(length),
|
||||
flags: Vec::with_capacity(length),
|
||||
bounds: RectF::default(),
|
||||
closed: false,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// restore self to the state of Contour::new(), but keep the points buffer allocated
|
||||
#[inline]
|
||||
pub fn clear(&mut self) {
|
||||
self.points.clear();
|
||||
self.flags.clear();
|
||||
self.bounds = RectF::default();
|
||||
self.closed = false;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn iter(&self, flags: ContourIterFlags) -> ContourIter {
|
||||
ContourIter {
|
||||
contour: self,
|
||||
index: 1,
|
||||
flags,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.points.is_empty()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn len(&self) -> u32 {
|
||||
self.points.len() as u32
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn bounds(&self) -> RectF {
|
||||
self.bounds
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_closed(&self) -> bool {
|
||||
self.closed
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn position_of(&self, index: u32) -> Vector2F {
|
||||
self.points[index as usize]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn last_position(&self) -> Option<Vector2F> {
|
||||
self.points.last().cloned()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn position_of_last(&self, index: u32) -> Vector2F {
|
||||
self.points[self.points.len() - index as usize]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn push_endpoint(&mut self, point: Vector2F) {
|
||||
self.push_point(point, PointFlags::empty(), true);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn push_quadratic(&mut self, ctrl: Vector2F, point: Vector2F) {
|
||||
self.push_point(ctrl, PointFlags::CONTROL_POINT_0, true);
|
||||
self.push_point(point, PointFlags::empty(), true);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn push_cubic(&mut self, ctrl0: Vector2F, ctrl1: Vector2F, point: Vector2F) {
|
||||
self.push_point(ctrl0, PointFlags::CONTROL_POINT_0, true);
|
||||
self.push_point(ctrl1, PointFlags::CONTROL_POINT_1, true);
|
||||
self.push_point(point, PointFlags::empty(), true);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn close(&mut self) {
|
||||
self.closed = true;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn push_point(&mut self,
|
||||
point: Vector2F,
|
||||
flags: PointFlags,
|
||||
update_bounds: bool) {
|
||||
debug_assert!(!point.x().is_nan() && !point.y().is_nan());
|
||||
|
||||
if update_bounds {
|
||||
let first = self.is_empty();
|
||||
union_rect(&mut self.bounds, point, first);
|
||||
}
|
||||
|
||||
self.points.push(point);
|
||||
self.flags.push(flags);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn push_segment(&mut self, segment: &Segment, flags: PushSegmentFlags) {
|
||||
if segment.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
let update_bounds = flags.contains(PushSegmentFlags::UPDATE_BOUNDS);
|
||||
self.push_point(segment.baseline.from(), PointFlags::empty(), update_bounds);
|
||||
|
||||
if !segment.is_line() {
|
||||
self.push_point(
|
||||
segment.ctrl.from(),
|
||||
PointFlags::CONTROL_POINT_0,
|
||||
update_bounds,
|
||||
);
|
||||
if !segment.is_quadratic() {
|
||||
self.push_point(
|
||||
segment.ctrl.to(),
|
||||
PointFlags::CONTROL_POINT_1,
|
||||
update_bounds,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
self.push_point(segment.baseline.to(), PointFlags::empty(), update_bounds);
|
||||
}
|
||||
|
||||
pub fn push_arc(&mut self,
|
||||
transform: &Transform2F,
|
||||
start_angle: f32,
|
||||
end_angle: f32,
|
||||
direction: ArcDirection) {
|
||||
if end_angle - start_angle >= PI * 2.0 {
|
||||
self.push_ellipse(transform);
|
||||
} else {
|
||||
let start = vec2f(start_angle.cos(), start_angle.sin());
|
||||
let end = vec2f(end_angle.cos(), end_angle.sin());
|
||||
self.push_arc_from_unit_chord(transform, LineSegment2F::new(start, end), direction);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_arc_from_unit_chord(&mut self,
|
||||
transform: &Transform2F,
|
||||
mut chord: LineSegment2F,
|
||||
direction: ArcDirection) {
|
||||
let mut direction_transform = Transform2F::default();
|
||||
if direction == ArcDirection::CCW {
|
||||
chord *= vec2f(1.0, -1.0);
|
||||
direction_transform = Transform2F::from_scale(vec2f(1.0, -1.0));
|
||||
}
|
||||
|
||||
let (mut vector, end_vector) = (UnitVector(chord.from()), UnitVector(chord.to()));
|
||||
for segment_index in 0..4 {
|
||||
debug!("push_arc_from_unit_chord(): loop segment index {}", segment_index);
|
||||
|
||||
let mut sweep_vector = end_vector.rev_rotate_by(vector);
|
||||
let last = sweep_vector.0.x() >= -EPSILON && sweep_vector.0.y() >= -EPSILON;
|
||||
debug!("... end_vector={:?} vector={:?} sweep_vector={:?} last={:?}",
|
||||
end_vector,
|
||||
vector,
|
||||
sweep_vector,
|
||||
last);
|
||||
|
||||
let mut segment;
|
||||
if !last {
|
||||
sweep_vector = UnitVector(vec2f(0.0, 1.0));
|
||||
segment = Segment::quarter_circle_arc();
|
||||
} else {
|
||||
segment = Segment::arc_from_cos(sweep_vector.0.x());
|
||||
}
|
||||
|
||||
let half_sweep_vector = sweep_vector.halve_angle();
|
||||
let rotation = Transform2F::from_rotation_vector(half_sweep_vector.rotate_by(vector));
|
||||
segment = segment.transform(&(*transform * direction_transform * rotation));
|
||||
|
||||
let mut push_segment_flags = PushSegmentFlags::UPDATE_BOUNDS;
|
||||
if segment_index == 0 {
|
||||
push_segment_flags.insert(PushSegmentFlags::INCLUDE_FROM_POINT);
|
||||
}
|
||||
self.push_segment(&segment, push_segment_flags);
|
||||
|
||||
if last {
|
||||
break;
|
||||
}
|
||||
|
||||
vector = vector.rotate_by(sweep_vector);
|
||||
}
|
||||
|
||||
const EPSILON: f32 = 0.001;
|
||||
}
|
||||
|
||||
pub fn push_ellipse(&mut self, transform: &Transform2F) {
|
||||
let segment = Segment::quarter_circle_arc();
|
||||
let mut rotation;
|
||||
self.push_segment(&segment.transform(transform),
|
||||
PushSegmentFlags::UPDATE_BOUNDS | PushSegmentFlags::INCLUDE_FROM_POINT);
|
||||
rotation = Transform2F::from_rotation_vector(UnitVector(vec2f( 0.0, 1.0)));
|
||||
self.push_segment(&segment.transform(&(*transform * rotation)),
|
||||
PushSegmentFlags::UPDATE_BOUNDS);
|
||||
rotation = Transform2F::from_rotation_vector(UnitVector(vec2f(-1.0, 0.0)));
|
||||
self.push_segment(&segment.transform(&(*transform * rotation)),
|
||||
PushSegmentFlags::UPDATE_BOUNDS);
|
||||
rotation = Transform2F::from_rotation_vector(UnitVector(vec2f( 0.0, -1.0)));
|
||||
self.push_segment(&segment.transform(&(*transform * rotation)),
|
||||
PushSegmentFlags::UPDATE_BOUNDS);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn segment_after(&self, point_index: u32) -> Segment {
|
||||
debug_assert!(self.point_is_endpoint(point_index));
|
||||
|
||||
let mut segment = Segment::none();
|
||||
segment.baseline.set_from(self.position_of(point_index));
|
||||
|
||||
let point1_index = self.add_to_point_index(point_index, 1);
|
||||
if self.point_is_endpoint(point1_index) {
|
||||
segment.baseline.set_to(self.position_of(point1_index));
|
||||
segment.kind = SegmentKind::Line;
|
||||
} else {
|
||||
segment.ctrl.set_from(self.position_of(point1_index));
|
||||
|
||||
let point2_index = self.add_to_point_index(point_index, 2);
|
||||
if self.point_is_endpoint(point2_index) {
|
||||
segment.baseline.set_to(self.position_of(point2_index));
|
||||
segment.kind = SegmentKind::Quadratic;
|
||||
} else {
|
||||
segment.ctrl.set_to(self.position_of(point2_index));
|
||||
segment.kind = SegmentKind::Cubic;
|
||||
|
||||
let point3_index = self.add_to_point_index(point_index, 3);
|
||||
segment.baseline.set_to(self.position_of(point3_index));
|
||||
}
|
||||
}
|
||||
|
||||
segment
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hull_segment_after(&self, prev_point_index: u32) -> LineSegment2F {
|
||||
let next_point_index = self.next_point_index_of(prev_point_index);
|
||||
LineSegment2F::new(
|
||||
self.points[prev_point_index as usize],
|
||||
self.points[next_point_index as usize],
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn point_is_endpoint(&self, point_index: u32) -> bool {
|
||||
!self.flags[point_index as usize]
|
||||
.intersects(PointFlags::CONTROL_POINT_0 | PointFlags::CONTROL_POINT_1)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn add_to_point_index(&self, point_index: u32, addend: u32) -> u32 {
|
||||
let (index, limit) = (point_index + addend, self.len());
|
||||
if index >= limit {
|
||||
index - limit
|
||||
} else {
|
||||
index
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn point_is_logically_above(&self, a: u32, b: u32) -> bool {
|
||||
let (a_y, b_y) = (self.points[a as usize].y(), self.points[b as usize].y());
|
||||
a_y < b_y || (a_y == b_y && a < b)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn prev_endpoint_index_of(&self, mut point_index: u32) -> u32 {
|
||||
loop {
|
||||
point_index = self.prev_point_index_of(point_index);
|
||||
if self.point_is_endpoint(point_index) {
|
||||
return point_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn next_endpoint_index_of(&self, mut point_index: u32) -> u32 {
|
||||
loop {
|
||||
point_index = self.next_point_index_of(point_index);
|
||||
if self.point_is_endpoint(point_index) {
|
||||
return point_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn prev_point_index_of(&self, point_index: u32) -> u32 {
|
||||
if point_index == 0 {
|
||||
self.len() - 1
|
||||
} else {
|
||||
point_index - 1
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn next_point_index_of(&self, point_index: u32) -> u32 {
|
||||
if point_index == self.len() - 1 {
|
||||
0
|
||||
} else {
|
||||
point_index + 1
|
||||
}
|
||||
}
|
||||
|
||||
pub fn transform(&mut self, transform: &Transform2F) {
|
||||
if transform.is_identity() {
|
||||
return;
|
||||
}
|
||||
|
||||
for (point_index, point) in self.points.iter_mut().enumerate() {
|
||||
*point = *transform * *point;
|
||||
union_rect(&mut self.bounds, *point, point_index == 0);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_perspective(&mut self, perspective: &Perspective) {
|
||||
for (point_index, point) in self.points.iter_mut().enumerate() {
|
||||
*point = *perspective * *point;
|
||||
union_rect(&mut self.bounds, *point, point_index == 0);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dilate(&mut self, amount: Vector2F, orientation: Orientation) {
|
||||
ContourDilator::new(self, amount, orientation).dilate();
|
||||
self.bounds = self.bounds.dilate(amount);
|
||||
}
|
||||
|
||||
fn prepare_for_tiling(&mut self, view_box: RectF) {
|
||||
// Snap points to the view box bounds. This mops up floating point error from the clipping
|
||||
// process.
|
||||
let (mut last_endpoint_index, mut contour_is_monotonic) = (None, true);
|
||||
for point_index in 0..(self.points.len() as u32) {
|
||||
if contour_is_monotonic {
|
||||
if self.point_is_endpoint(point_index) {
|
||||
if let Some(last_endpoint_index) = last_endpoint_index {
|
||||
if !self.curve_with_endpoints_is_monotonic(last_endpoint_index,
|
||||
point_index) {
|
||||
contour_is_monotonic = false;
|
||||
}
|
||||
}
|
||||
last_endpoint_index = Some(point_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to monotonic, if necessary.
|
||||
if !contour_is_monotonic {
|
||||
self.make_monotonic();
|
||||
}
|
||||
|
||||
// Update bounds.
|
||||
self.bounds = self
|
||||
.bounds
|
||||
.intersection(view_box)
|
||||
.unwrap_or_else(|| RectF::default());
|
||||
}
|
||||
|
||||
fn make_monotonic(&mut self) {
|
||||
debug!("--- make_monotonic() ---");
|
||||
|
||||
let contour = self.take();
|
||||
self.bounds = contour.bounds;
|
||||
|
||||
let mut last_endpoint_index = None;
|
||||
let input_point_count = contour.points.len() as u32;
|
||||
for point_index in 0..(input_point_count + 1) {
|
||||
if point_index < input_point_count && !contour.point_is_endpoint(point_index) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(last_endpoint_index) = last_endpoint_index {
|
||||
let position_index = if point_index == input_point_count {
|
||||
0
|
||||
} else {
|
||||
point_index
|
||||
};
|
||||
let baseline = LineSegment2F::new(
|
||||
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 {
|
||||
let ctrl_point_index = last_endpoint_index as usize + 1;
|
||||
let ctrl_position = &contour.points[ctrl_point_index];
|
||||
handle_cubic(
|
||||
self,
|
||||
&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 = LineSegment2F::new(*ctrl_position_0, *ctrl_position_1);
|
||||
handle_cubic(self, &Segment::cubic(baseline, ctrl));
|
||||
}
|
||||
|
||||
self.push_point(
|
||||
contour.points[position_index as usize],
|
||||
PointFlags::empty(),
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
last_endpoint_index = Some(point_index);
|
||||
}
|
||||
|
||||
fn handle_cubic(contour: &mut Contour, segment: &Segment) {
|
||||
debug!("handle_cubic({:?})", segment);
|
||||
|
||||
match segment.as_cubic_segment().y_extrema() {
|
||||
(Some(t0), Some(t1)) => {
|
||||
let (segments_01, segment_2) = segment.as_cubic_segment().split(t1);
|
||||
let (segment_0, segment_1) = segments_01.as_cubic_segment().split(t0 / t1);
|
||||
contour.push_segment(&segment_0, PushSegmentFlags::empty());
|
||||
contour.push_segment(&segment_1, PushSegmentFlags::empty());
|
||||
contour.push_segment(&segment_2, PushSegmentFlags::empty());
|
||||
}
|
||||
(Some(t0), None) | (None, Some(t0)) => {
|
||||
let (segment_0, segment_1) = segment.as_cubic_segment().split(t0);
|
||||
contour.push_segment(&segment_0, PushSegmentFlags::empty());
|
||||
contour.push_segment(&segment_1, PushSegmentFlags::empty());
|
||||
}
|
||||
(None, None) => contour.push_segment(segment, PushSegmentFlags::empty()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn curve_with_endpoints_is_monotonic(
|
||||
&self,
|
||||
start_endpoint_index: u32,
|
||||
end_endpoint_index: u32,
|
||||
) -> bool {
|
||||
let start_position = self.points[start_endpoint_index as usize];
|
||||
let end_position = self.points[end_endpoint_index as usize];
|
||||
|
||||
if start_position.x() <= end_position.x() {
|
||||
for point_index in start_endpoint_index..end_endpoint_index {
|
||||
if self.points[point_index as usize].x() > self.points[point_index as usize + 1].x()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for point_index in start_endpoint_index..end_endpoint_index {
|
||||
if self.points[point_index as usize].x() < self.points[point_index as usize + 1].x()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if start_position.y() <= end_position.y() {
|
||||
for point_index in start_endpoint_index..end_endpoint_index {
|
||||
if self.points[point_index as usize].y() > self.points[point_index as usize + 1].y()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for point_index in start_endpoint_index..end_endpoint_index {
|
||||
if self.points[point_index as usize].y() < self.points[point_index as usize + 1].y()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
// Use this function to keep bounds up to date when mutating paths. See `Outline::transform()`
|
||||
// for an example of use.
|
||||
pub(crate) fn update_bounds(&self, bounds: &mut Option<RectF>) {
|
||||
*bounds = Some(match *bounds {
|
||||
None => self.bounds,
|
||||
Some(bounds) => bounds.union_rect(self.bounds),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Contour {
|
||||
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
|
||||
for (segment_index, segment) in self.iter(ContourIterFlags::IGNORE_CLOSE_SEGMENT)
|
||||
.enumerate() {
|
||||
if segment_index == 0 {
|
||||
write!(
|
||||
formatter,
|
||||
"M {} {}",
|
||||
segment.baseline.from_x(),
|
||||
segment.baseline.from_y()
|
||||
)?;
|
||||
}
|
||||
|
||||
match segment.kind {
|
||||
SegmentKind::None => {}
|
||||
SegmentKind::Line => {
|
||||
write!(
|
||||
formatter,
|
||||
" L {} {}",
|
||||
segment.baseline.to_x(),
|
||||
segment.baseline.to_y()
|
||||
)?;
|
||||
}
|
||||
SegmentKind::Quadratic => {
|
||||
write!(
|
||||
formatter,
|
||||
" Q {} {} {} {}",
|
||||
segment.ctrl.from_x(),
|
||||
segment.ctrl.from_y(),
|
||||
segment.baseline.to_x(),
|
||||
segment.baseline.to_y()
|
||||
)?;
|
||||
}
|
||||
SegmentKind::Cubic => {
|
||||
write!(
|
||||
formatter,
|
||||
" C {} {} {} {} {} {}",
|
||||
segment.ctrl.from_x(),
|
||||
segment.ctrl.from_y(),
|
||||
segment.ctrl.to_x(),
|
||||
segment.ctrl.to_y(),
|
||||
segment.baseline.to_x(),
|
||||
segment.baseline.to_y()
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.closed {
|
||||
write!(formatter, " z")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct PointIndex(u32);
|
||||
|
||||
impl PointIndex {
|
||||
#[inline]
|
||||
pub fn new(contour: u32, point: u32) -> PointIndex {
|
||||
debug_assert!(contour <= 0xfff);
|
||||
debug_assert!(point <= 0x000f_ffff);
|
||||
PointIndex((contour << 20) | point)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn contour(self) -> u32 {
|
||||
self.0 >> 20
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn point(self) -> u32 {
|
||||
self.0 & 0x000f_ffff
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ContourIter<'a> {
|
||||
contour: &'a Contour,
|
||||
index: u32,
|
||||
flags: ContourIterFlags,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for ContourIter<'a> {
|
||||
type Item = Segment;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Segment> {
|
||||
let contour = self.contour;
|
||||
|
||||
let include_close_segment = self.contour.closed &&
|
||||
!self.flags.contains(ContourIterFlags::IGNORE_CLOSE_SEGMENT);
|
||||
if (self.index == contour.len() && !include_close_segment) ||
|
||||
self.index == contour.len() + 1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let point0_index = self.index - 1;
|
||||
let point0 = contour.position_of(point0_index);
|
||||
if self.index == contour.len() {
|
||||
let point1 = contour.position_of(0);
|
||||
self.index += 1;
|
||||
return Some(Segment::line(LineSegment2F::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(LineSegment2F::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(LineSegment2F::new(point0, point2), point1));
|
||||
}
|
||||
|
||||
let point3_index = self.index;
|
||||
let point3 = contour.position_of(point3_index);
|
||||
self.index += 1;
|
||||
debug_assert!(contour.point_is_endpoint(point3_index));
|
||||
return Some(Segment::cubic(
|
||||
LineSegment2F::new(point0, point3),
|
||||
LineSegment2F::new(point1, point2),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum ArcDirection {
|
||||
CW,
|
||||
CCW,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct ContourIterFlags: u8 {
|
||||
const IGNORE_CLOSE_SEGMENT = 1;
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn union_rect(bounds: &mut RectF, new_point: Vector2F, first: bool) {
|
||||
if first {
|
||||
*bounds = RectF::from_points(new_point, new_point);
|
||||
} else {
|
||||
*bounds = bounds.union_point(new_point)
|
||||
}
|
||||
}
|
|
@ -1,226 +0,0 @@
|
|||
// pathfinder/content/src/pattern.rs
|
||||
//
|
||||
// Copyright © 2020 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.
|
||||
|
||||
//! Raster image patterns.
|
||||
|
||||
use crate::effects::PatternFilter;
|
||||
use crate::render_target::RenderTargetId;
|
||||
use crate::util;
|
||||
use pathfinder_color::{self as color, ColorU};
|
||||
use pathfinder_geometry::transform2d::Transform2F;
|
||||
use pathfinder_geometry::vector::{Vector2I, vec2i};
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[cfg(feature = "pf-image")]
|
||||
use image::RgbaImage;
|
||||
|
||||
/// A raster image pattern.
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub struct Pattern {
|
||||
source: PatternSource,
|
||||
transform: Transform2F,
|
||||
filter: Option<PatternFilter>,
|
||||
flags: PatternFlags,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub enum PatternSource {
|
||||
Image(Image),
|
||||
RenderTarget {
|
||||
id: RenderTargetId,
|
||||
size: Vector2I,
|
||||
}
|
||||
}
|
||||
|
||||
/// RGBA, non-premultiplied.
|
||||
// FIXME(pcwalton): Hash the pixel contents so that we don't have to compare every pixel!
|
||||
// TODO(pcwalton): Should the pixels be premultiplied?
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct Image {
|
||||
size: Vector2I,
|
||||
pixels: Arc<Vec<ColorU>>,
|
||||
pixels_hash: u64,
|
||||
is_opaque: bool,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct PatternFlags: u8 {
|
||||
const REPEAT_X = 0x01;
|
||||
const REPEAT_Y = 0x02;
|
||||
const NO_SMOOTHING = 0x04;
|
||||
}
|
||||
}
|
||||
|
||||
impl Pattern {
|
||||
#[inline]
|
||||
fn from_source(source: PatternSource) -> Pattern {
|
||||
Pattern {
|
||||
source,
|
||||
transform: Transform2F::default(),
|
||||
filter: None,
|
||||
flags: PatternFlags::empty(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_image(image: Image) -> Pattern {
|
||||
Pattern::from_source(PatternSource::Image(image))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_render_target(id: RenderTargetId, size: Vector2I) -> Pattern {
|
||||
Pattern::from_source(PatternSource::RenderTarget { id, size })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn transform(&self) -> Transform2F {
|
||||
self.transform
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn apply_transform(&mut self, transform: Transform2F) {
|
||||
self.transform = transform * self.transform;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn size(&self) -> Vector2I {
|
||||
match self.source {
|
||||
PatternSource::Image(ref image) => image.size(),
|
||||
PatternSource::RenderTarget { size, .. } => size,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn filter(&self) -> Option<PatternFilter> {
|
||||
self.filter
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_filter(&mut self, filter: Option<PatternFilter>) {
|
||||
self.filter = filter;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn repeat_x(&self) -> bool {
|
||||
self.flags.contains(PatternFlags::REPEAT_X)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_repeat_x(&mut self, repeat_x: bool) {
|
||||
self.flags.set(PatternFlags::REPEAT_X, repeat_x);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn repeat_y(&self) -> bool {
|
||||
self.flags.contains(PatternFlags::REPEAT_Y)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_repeat_y(&mut self, repeat_y: bool) {
|
||||
self.flags.set(PatternFlags::REPEAT_Y, repeat_y);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn smoothing_enabled(&self) -> bool {
|
||||
!self.flags.contains(PatternFlags::NO_SMOOTHING)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_smoothing_enabled(&mut self, enable: bool) {
|
||||
self.flags.set(PatternFlags::NO_SMOOTHING, !enable);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_opaque(&self) -> bool {
|
||||
self.source.is_opaque()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn source(&self) -> &PatternSource {
|
||||
&self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl Image {
|
||||
#[inline]
|
||||
pub fn new(size: Vector2I, pixels: Arc<Vec<ColorU>>) -> Image {
|
||||
assert_eq!(size.x() as usize * size.y() as usize, pixels.len());
|
||||
let is_opaque = pixels.iter().all(|pixel| pixel.is_opaque());
|
||||
|
||||
let mut pixels_hasher = DefaultHasher::new();
|
||||
pixels.hash(&mut pixels_hasher);
|
||||
let pixels_hash = pixels_hasher.finish();
|
||||
|
||||
Image { size, pixels, pixels_hash, is_opaque }
|
||||
}
|
||||
|
||||
#[cfg(feature = "pf-image")]
|
||||
pub fn from_image_buffer(image_buffer: RgbaImage) -> Image {
|
||||
let (width, height) = image_buffer.dimensions();
|
||||
let pixels = color::u8_vec_to_color_vec(image_buffer.into_raw());
|
||||
Image::new(vec2i(width as i32, height as i32), Arc::new(pixels))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn size(&self) -> Vector2I {
|
||||
self.size
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn pixels(&self) -> &Arc<Vec<ColorU>> {
|
||||
&self.pixels
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_opaque(&self) -> bool {
|
||||
self.is_opaque
|
||||
}
|
||||
}
|
||||
|
||||
impl PatternSource {
|
||||
#[inline]
|
||||
pub fn is_opaque(&self) -> bool {
|
||||
match *self {
|
||||
PatternSource::Image(ref image) => image.is_opaque(),
|
||||
PatternSource::RenderTarget { .. } => {
|
||||
// TODO(pcwalton): Maybe do something smarter here?
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Image {
|
||||
#[inline]
|
||||
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
|
||||
write!(formatter, "(image {}×{} px)", self.size.x(), self.size.y())
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for Image {
|
||||
fn hash<H>(&self, hasher: &mut H) where H: Hasher {
|
||||
self.size.hash(hasher);
|
||||
self.pixels_hash.hash(hasher);
|
||||
self.is_opaque.hash(hasher);
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Pattern {}
|
||||
|
||||
impl Hash for Pattern {
|
||||
fn hash<H>(&self, state: &mut H) where H: Hasher {
|
||||
self.source.hash(state);
|
||||
util::hash_transform2f(self.transform, state);
|
||||
self.flags.hash(state);
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
// pathfinder/content/src/render_target.rs
|
||||
//
|
||||
// Copyright © 2020 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.
|
||||
|
||||
//! Render targets.
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct RenderTargetId {
|
||||
pub scene: u32,
|
||||
pub render_target: u32,
|
||||
}
|
|
@ -1,426 +0,0 @@
|
|||
// pathfinder/content/src/segment.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.
|
||||
|
||||
//! Line or curve segments, optimized with SIMD.
|
||||
|
||||
use pathfinder_geometry::line_segment::LineSegment2F;
|
||||
use pathfinder_geometry::transform2d::Transform2F;
|
||||
use pathfinder_geometry::util::{self, EPSILON};
|
||||
use pathfinder_geometry::vector::{Vector2F, vec2f};
|
||||
use pathfinder_simd::default::F32x4;
|
||||
use std::f32::consts::SQRT_2;
|
||||
|
||||
const MAX_NEWTON_ITERATIONS: u32 = 32;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Segment {
|
||||
pub baseline: LineSegment2F,
|
||||
pub ctrl: LineSegment2F,
|
||||
pub kind: SegmentKind,
|
||||
pub flags: SegmentFlags,
|
||||
}
|
||||
|
||||
impl Segment {
|
||||
#[inline]
|
||||
pub fn none() -> Segment {
|
||||
Segment {
|
||||
baseline: LineSegment2F::default(),
|
||||
ctrl: LineSegment2F::default(),
|
||||
kind: SegmentKind::None,
|
||||
flags: SegmentFlags::empty(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn line(line: LineSegment2F) -> Segment {
|
||||
Segment {
|
||||
baseline: line,
|
||||
ctrl: LineSegment2F::default(),
|
||||
kind: SegmentKind::Line,
|
||||
flags: SegmentFlags::empty(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn quadratic(baseline: LineSegment2F, ctrl: Vector2F) -> Segment {
|
||||
Segment {
|
||||
baseline,
|
||||
ctrl: LineSegment2F::new(ctrl, Vector2F::zero()),
|
||||
kind: SegmentKind::Quadratic,
|
||||
flags: SegmentFlags::empty(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn cubic(baseline: LineSegment2F, ctrl: LineSegment2F) -> Segment {
|
||||
Segment {
|
||||
baseline,
|
||||
ctrl,
|
||||
kind: SegmentKind::Cubic,
|
||||
flags: SegmentFlags::empty(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
Segment::arc_from_cos(f32::cos(sweep_angle))
|
||||
}
|
||||
|
||||
/// Approximates an unit-length arc with a cubic Bézier curve, given the cosine of the sweep
|
||||
/// angle.
|
||||
///
|
||||
/// The maximum supported sweep angle is π/2 (i.e. 90°).
|
||||
pub fn arc_from_cos(cos_sweep_angle: f32) -> Segment {
|
||||
// Richard A. DeVeneza, "How to determine the control points of a Bézier curve that
|
||||
// approximates a small arc", 2004.
|
||||
//
|
||||
// https://www.tinaja.com/glib/bezcirc2.pdf
|
||||
if cos_sweep_angle >= 1.0 - EPSILON {
|
||||
return Segment::line(LineSegment2F::new(vec2f(1.0, 0.0), vec2f(1.0, 0.0)));
|
||||
}
|
||||
|
||||
let term = F32x4::new(cos_sweep_angle, -cos_sweep_angle,
|
||||
cos_sweep_angle, -cos_sweep_angle);
|
||||
let signs = F32x4::new(1.0, -1.0, 1.0, 1.0);
|
||||
let p3p0 = ((F32x4::splat(1.0) + term) * F32x4::splat(0.5)).sqrt() * signs;
|
||||
let (p0x, p0y) = (p3p0.z(), p3p0.w());
|
||||
let (p1x, p1y) = (4.0 - p0x, (1.0 - p0x) * (3.0 - p0x) / p0y);
|
||||
let p2p1 = F32x4::new(p1x, -p1y, p1x, p1y) * F32x4::splat(1.0 / 3.0);
|
||||
return Segment::cubic(LineSegment2F(p3p0), LineSegment2F(p2p1));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn quarter_circle_arc() -> Segment {
|
||||
let p0 = Vector2F::splat(SQRT_2 * 0.5);
|
||||
let p1 = vec2f(-SQRT_2 / 6.0 + 4.0 / 3.0, 7.0 * SQRT_2 / 6.0 - 4.0 / 3.0);
|
||||
let flip = vec2f(1.0, -1.0);
|
||||
let (p2, p3) = (p1 * flip, p0 * flip);
|
||||
Segment::cubic(LineSegment2F::new(p3, p0), LineSegment2F::new(p2, p1))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_line_segment(&self) -> LineSegment2F {
|
||||
debug_assert!(self.is_line());
|
||||
self.baseline
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_none(&self) -> bool {
|
||||
self.kind == SegmentKind::None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_line(&self) -> bool {
|
||||
self.kind == SegmentKind::Line
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_quadratic(&self) -> bool {
|
||||
self.kind == SegmentKind::Quadratic
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_cubic(&self) -> bool {
|
||||
self.kind == SegmentKind::Cubic
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_cubic_segment(&self) -> CubicSegment {
|
||||
debug_assert!(self.is_cubic());
|
||||
CubicSegment(self)
|
||||
}
|
||||
|
||||
// FIXME(pcwalton): We should basically never use this function.
|
||||
// FIXME(pcwalton): Handle lines!
|
||||
#[inline]
|
||||
pub fn to_cubic(&self) -> Segment {
|
||||
if self.is_cubic() {
|
||||
return *self;
|
||||
}
|
||||
|
||||
let mut new_segment = *self;
|
||||
let p1_2 = self.ctrl.from() + self.ctrl.from();
|
||||
new_segment.ctrl = LineSegment2F::new(self.baseline.from() + p1_2,
|
||||
p1_2 + self.baseline.to()) * (1.0 / 3.0);
|
||||
new_segment.kind = SegmentKind::Cubic;
|
||||
new_segment
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_monotonic(&self) -> bool {
|
||||
// FIXME(pcwalton): Don't degree elevate!
|
||||
match self.kind {
|
||||
SegmentKind::None | SegmentKind::Line => true,
|
||||
SegmentKind::Quadratic => self.to_cubic().as_cubic_segment().is_monotonic(),
|
||||
SegmentKind::Cubic => self.as_cubic_segment().is_monotonic(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn reversed(&self) -> Segment {
|
||||
Segment {
|
||||
baseline: self.baseline.reversed(),
|
||||
ctrl: if self.is_quadratic() {
|
||||
self.ctrl
|
||||
} else {
|
||||
self.ctrl.reversed()
|
||||
},
|
||||
kind: self.kind,
|
||||
flags: self.flags,
|
||||
}
|
||||
}
|
||||
|
||||
// Reverses if necessary so that the from point is above the to point. Calling this method
|
||||
// again will undo the transformation.
|
||||
#[inline]
|
||||
pub fn orient(&self, y_winding: i32) -> Segment {
|
||||
if y_winding >= 0 {
|
||||
*self
|
||||
} else {
|
||||
self.reversed()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_tiny(&self) -> bool {
|
||||
const EPSILON: f32 = 0.0001;
|
||||
self.baseline.square_length() < EPSILON
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn split(&self, t: f32) -> (Segment, Segment) {
|
||||
// FIXME(pcwalton): Don't degree elevate!
|
||||
if self.is_line() {
|
||||
let (before, after) = self.as_line_segment().split(t);
|
||||
(Segment::line(before), Segment::line(after))
|
||||
} else {
|
||||
self.to_cubic().as_cubic_segment().split(t)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn sample(self, t: f32) -> Vector2F {
|
||||
// FIXME(pcwalton): Don't degree elevate!
|
||||
if self.is_line() {
|
||||
self.as_line_segment().sample(t)
|
||||
} else {
|
||||
self.to_cubic().as_cubic_segment().sample(t)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn transform(self, transform: &Transform2F) -> Segment {
|
||||
Segment {
|
||||
baseline: *transform * self.baseline,
|
||||
ctrl: *transform * self.ctrl,
|
||||
kind: self.kind,
|
||||
flags: self.flags,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn arc_length(&self) -> f32 {
|
||||
// FIXME(pcwalton)
|
||||
self.baseline.vector().length()
|
||||
}
|
||||
|
||||
pub fn time_for_distance(&self, distance: f32) -> f32 {
|
||||
// FIXME(pcwalton)
|
||||
distance / self.arc_length()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[repr(u8)]
|
||||
pub enum SegmentKind {
|
||||
None,
|
||||
Line,
|
||||
Quadratic,
|
||||
Cubic,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct SegmentFlags: u8 {
|
||||
const FIRST_IN_SUBPATH = 0x01;
|
||||
const CLOSES_SUBPATH = 0x02;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct CubicSegment<'s>(pub &'s Segment);
|
||||
|
||||
impl<'s> CubicSegment<'s> {
|
||||
// See Kaspar Fischer, "Piecewise Linear Approximation of Bézier Curves", 2000.
|
||||
#[inline]
|
||||
pub fn is_flat(self, tolerance: f32) -> bool {
|
||||
let mut uv = F32x4::splat(3.0) * self.0.ctrl.0
|
||||
- self.0.baseline.0
|
||||
- self.0.baseline.0
|
||||
- self.0.baseline.reversed().0;
|
||||
uv = uv * uv;
|
||||
uv = uv.max(uv.zwxy());
|
||||
uv[0] + uv[1] <= 16.0 * tolerance * tolerance
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn split(self, t: f32) -> (Segment, Segment) {
|
||||
let (baseline0, ctrl0, baseline1, ctrl1);
|
||||
if t <= 0.0 {
|
||||
let from = &self.0.baseline.from();
|
||||
baseline0 = LineSegment2F::new(*from, *from);
|
||||
ctrl0 = LineSegment2F::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 = LineSegment2F::new(*to, *to);
|
||||
ctrl1 = LineSegment2F::new(*to, *to);
|
||||
} else {
|
||||
let tttt = F32x4::splat(t);
|
||||
|
||||
let (p0p3, p1p2) = (self.0.baseline.0, self.0.ctrl.0);
|
||||
let p0p1 = p0p3.concat_xy_xy(p1p2);
|
||||
|
||||
// p01 = lerp(p0, p1, t), p12 = lerp(p1, p2, t), p23 = lerp(p2, p3, t)
|
||||
let p01p12 = p0p1 + tttt * (p1p2 - p0p1);
|
||||
let pxxp23 = p1p2 + tttt * (p0p3 - p1p2);
|
||||
let p12p23 = p01p12.concat_zw_zw(pxxp23);
|
||||
|
||||
// p012 = lerp(p01, p12, t), p123 = lerp(p12, p23, t)
|
||||
let p012p123 = p01p12 + tttt * (p12p23 - p01p12);
|
||||
let p123 = p012p123.zwzw();
|
||||
|
||||
// p0123 = lerp(p012, p123, t)
|
||||
let p0123 = p012p123 + tttt * (p123 - p012p123);
|
||||
|
||||
baseline0 = LineSegment2F(p0p3.concat_xy_xy(p0123));
|
||||
ctrl0 = LineSegment2F(p01p12.concat_xy_xy(p012p123));
|
||||
baseline1 = LineSegment2F(p0123.concat_xy_zw(p0p3));
|
||||
ctrl1 = LineSegment2F(p012p123.concat_zw_zw(p12p23));
|
||||
}
|
||||
|
||||
(
|
||||
Segment {
|
||||
baseline: baseline0,
|
||||
ctrl: ctrl0,
|
||||
kind: SegmentKind::Cubic,
|
||||
flags: self.0.flags & SegmentFlags::FIRST_IN_SUBPATH,
|
||||
},
|
||||
Segment {
|
||||
baseline: baseline1,
|
||||
ctrl: ctrl1,
|
||||
kind: SegmentKind::Cubic,
|
||||
flags: self.0.flags & SegmentFlags::CLOSES_SUBPATH,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn split_before(self, t: f32) -> Segment {
|
||||
self.split(t).0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn split_after(self, t: f32) -> Segment {
|
||||
self.split(t).1
|
||||
}
|
||||
|
||||
// FIXME(pcwalton): Use Horner's method!
|
||||
#[inline]
|
||||
pub fn sample(self, t: f32) -> Vector2F {
|
||||
self.split(t).0.baseline.to()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_monotonic(self) -> bool {
|
||||
// TODO(pcwalton): Optimize this.
|
||||
let (p0, p3) = (self.0.baseline.from_y(), self.0.baseline.to_y());
|
||||
let (p1, p2) = (self.0.ctrl.from_y(), self.0.ctrl.to_y());
|
||||
(p0 <= p1 && p1 <= p2 && p2 <= p3) || (p0 >= p1 && p1 >= p2 && p2 >= p3)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn y_extrema(self) -> (Option<f32>, Option<f32>) {
|
||||
if self.is_monotonic() {
|
||||
return (None, None);
|
||||
}
|
||||
|
||||
let p0p1p2p3 = F32x4::new(
|
||||
self.0.baseline.from_y(),
|
||||
self.0.ctrl.from_y(),
|
||||
self.0.ctrl.to_y(),
|
||||
self.0.baseline.to_y(),
|
||||
);
|
||||
|
||||
let pxp0p1p2 = p0p1p2p3.wxyz();
|
||||
let pxv0v1v2 = p0p1p2p3 - pxp0p1p2;
|
||||
let (v0, v1, v2) = (pxv0v1v2[1], pxv0v1v2[2], pxv0v1v2[3]);
|
||||
|
||||
let (t0, t1);
|
||||
let (v0_to_v1, v2_to_v1) = (v0 - v1, v2 - v1);
|
||||
let denom = v0_to_v1 + v2_to_v1;
|
||||
|
||||
if util::approx_eq(denom, 0.0) {
|
||||
// Let's not divide by zero (issue #146). Fall back to Newton's method.
|
||||
// FIXME(pcwalton): Can we have two roots here?
|
||||
let mut t = 0.5;
|
||||
for _ in 0..MAX_NEWTON_ITERATIONS {
|
||||
let dydt = 3.0 * ((denom * t - v0_to_v1 - v0_to_v1) * t + v0);
|
||||
if f32::abs(dydt) <= EPSILON {
|
||||
break
|
||||
}
|
||||
let d2ydt2 = 6.0 * (denom * t - v0_to_v1);
|
||||
t -= dydt / d2ydt2;
|
||||
}
|
||||
t0 = t;
|
||||
t1 = 0.0;
|
||||
debug!("... t=(newton) {}", t);
|
||||
} else {
|
||||
// Algebraically compute the values for t.
|
||||
let discrim = f32::sqrt(v1 * v1 - v0 * v2);
|
||||
let denom_recip = 1.0 / denom;
|
||||
|
||||
t0 = (v0_to_v1 + discrim) * denom_recip;
|
||||
t1 = (v0_to_v1 - discrim) * denom_recip;
|
||||
|
||||
debug!("... t=({} +/- {})/{} t0={} t1={}", v0_to_v1, discrim, denom, t0, t1);
|
||||
}
|
||||
|
||||
return match (
|
||||
t0 > EPSILON && t0 < 1.0 - EPSILON,
|
||||
t1 > EPSILON && t1 < 1.0 - EPSILON,
|
||||
) {
|
||||
(false, false) => (None, None),
|
||||
(true, false) => (Some(t0), None),
|
||||
(false, true) => (Some(t1), None),
|
||||
(true, true) => (Some(f32::min(t0, t1)), Some(f32::max(t0, t1))),
|
||||
};
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn min_x(&self) -> f32 {
|
||||
f32::min(self.0.baseline.min_x(), self.0.ctrl.min_x())
|
||||
}
|
||||
#[inline]
|
||||
pub fn min_y(&self) -> f32 {
|
||||
f32::min(self.0.baseline.min_y(), self.0.ctrl.min_y())
|
||||
}
|
||||
#[inline]
|
||||
pub fn max_x(&self) -> f32 {
|
||||
f32::max(self.0.baseline.max_x(), self.0.ctrl.max_x())
|
||||
}
|
||||
#[inline]
|
||||
pub fn max_y(&self) -> f32 {
|
||||
f32::max(self.0.baseline.max_y(), self.0.ctrl.max_y())
|
||||
}
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
// pathfinder/content/src/sorted_vector.rs
|
||||
//
|
||||
// Copyright © 2020 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.
|
||||
|
||||
//! A vector that maintains sorted order with insertion sort.
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::convert;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct SortedVector<T>
|
||||
where
|
||||
T: PartialOrd,
|
||||
{
|
||||
pub array: Vec<T>,
|
||||
}
|
||||
|
||||
impl<T> SortedVector<T>
|
||||
where
|
||||
T: PartialOrd,
|
||||
{
|
||||
#[inline]
|
||||
pub fn new() -> SortedVector<T> {
|
||||
SortedVector { array: vec![] }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn push(&mut self, value: T) {
|
||||
let index = self.binary_search_by(|other| {
|
||||
other.partial_cmp(&value).unwrap_or(Ordering::Less)
|
||||
}).unwrap_or_else(convert::identity);
|
||||
self.array.insert(index, value);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn peek(&self) -> Option<&T> {
|
||||
self.array.last()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn pop(&mut self) -> Option<T> {
|
||||
self.array.pop()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn clear(&mut self) {
|
||||
self.array.clear()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.array.is_empty()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.array.len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn binary_search_by<'a, F>(&'a self, f: F) -> Result<usize, usize>
|
||||
where F: FnMut(&'a T) -> Ordering {
|
||||
self.array.binary_search_by(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::sorted_vector::SortedVector;
|
||||
use quickcheck;
|
||||
|
||||
#[test]
|
||||
fn test_sorted_vec() {
|
||||
quickcheck::quickcheck(prop_sorted_vec as fn(Vec<i32>) -> bool);
|
||||
|
||||
fn prop_sorted_vec(mut values: Vec<i32>) -> bool {
|
||||
let mut sorted_vec = SortedVector::new();
|
||||
for &value in &values {
|
||||
sorted_vec.push(value)
|
||||
}
|
||||
|
||||
values.sort();
|
||||
let mut results = Vec::with_capacity(values.len());
|
||||
while !sorted_vec.is_empty() {
|
||||
results.push(sorted_vec.pop().unwrap());
|
||||
}
|
||||
results.reverse();
|
||||
assert_eq!(&values, &results);
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,428 +0,0 @@
|
|||
// pathfinder/content/src/stroke.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.
|
||||
|
||||
//! Utilities for converting path strokes to fills.
|
||||
|
||||
use crate::outline::{ArcDirection, Contour, ContourIterFlags, Outline, PushSegmentFlags};
|
||||
use crate::segment::Segment;
|
||||
use pathfinder_geometry::line_segment::LineSegment2F;
|
||||
use pathfinder_geometry::rect::RectF;
|
||||
use pathfinder_geometry::transform2d::Transform2F;
|
||||
use pathfinder_geometry::util::EPSILON;
|
||||
use pathfinder_geometry::vector::{Vector2F, vec2f};
|
||||
use std::f32;
|
||||
|
||||
const TOLERANCE: f32 = 0.01;
|
||||
|
||||
pub struct OutlineStrokeToFill<'a> {
|
||||
input: &'a Outline,
|
||||
output: Outline,
|
||||
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(f32),
|
||||
Bevel,
|
||||
Round,
|
||||
}
|
||||
|
||||
impl<'a> OutlineStrokeToFill<'a> {
|
||||
#[inline]
|
||||
pub fn new(input: &Outline, style: StrokeStyle) -> OutlineStrokeToFill {
|
||||
OutlineStrokeToFill { input, output: Outline::new(), style }
|
||||
}
|
||||
|
||||
pub fn offset(&mut self) {
|
||||
let mut new_contours = vec![];
|
||||
for input in &self.input.contours {
|
||||
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, true);
|
||||
stroker = ContourStrokeToFill::new(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, closed);
|
||||
}
|
||||
|
||||
let mut new_bounds = None;
|
||||
new_contours.iter().for_each(|contour| contour.update_bounds(&mut new_bounds));
|
||||
|
||||
self.output.contours = new_contours;
|
||||
self.output.bounds = new_bounds.unwrap_or_else(|| RectF::default());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn into_outline(self) -> Outline {
|
||||
self.output
|
||||
}
|
||||
|
||||
fn push_stroked_contour(&mut self,
|
||||
new_contours: &mut Vec<Contour>,
|
||||
mut stroker: ContourStrokeToFill,
|
||||
closed: bool) {
|
||||
// Add join if necessary.
|
||||
if closed && stroker.output.might_need_join(self.style.line_join) {
|
||||
let (p1, p0) = (stroker.output.position_of(1), stroker.output.position_of(0));
|
||||
let final_segment = LineSegment2F::new(p1, p0);
|
||||
stroker.output.add_join(self.style.line_width * 0.5,
|
||||
self.style.line_join,
|
||||
stroker.input.position_of(0),
|
||||
final_segment);
|
||||
}
|
||||
|
||||
stroker.output.closed = true;
|
||||
new_contours.push(stroker.output);
|
||||
}
|
||||
|
||||
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 p1 = contour.position_of_last(1);
|
||||
|
||||
// Determine the ending gradient.
|
||||
let mut p0;
|
||||
let mut p0_index = contour.len() - 2;
|
||||
loop {
|
||||
p0 = contour.position_of(p0_index);
|
||||
if (p1 - p0).square_length() > EPSILON {
|
||||
break;
|
||||
}
|
||||
if p0_index == 0 {
|
||||
return;
|
||||
}
|
||||
p0_index -= 1;
|
||||
}
|
||||
let gradient = (p1 - p0).normalize();
|
||||
|
||||
match self.style.line_cap {
|
||||
LineCap::Butt => unreachable!(),
|
||||
|
||||
LineCap::Square => {
|
||||
let offset = gradient * (width * 0.5);
|
||||
|
||||
let p2 = p1 + offset;
|
||||
let p3 = p2 + gradient.yx() * vec2f(-width, width);
|
||||
let p4 = p3 - offset;
|
||||
|
||||
contour.push_endpoint(p2);
|
||||
contour.push_endpoint(p3);
|
||||
contour.push_endpoint(p4);
|
||||
}
|
||||
|
||||
LineCap::Round => {
|
||||
let scale = width * 0.5;
|
||||
let offset = gradient.yx() * vec2f(-1.0, 1.0);
|
||||
let translation = p1 + offset * (width * 0.5);
|
||||
let transform = Transform2F::from_scale(scale).translate(translation);
|
||||
let chord = LineSegment2F::new(-offset, offset);
|
||||
contour.push_arc_from_unit_chord(&transform, chord, ArcDirection::CW);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ContourStrokeToFill<'a> {
|
||||
input: &'a Contour,
|
||||
output: Contour,
|
||||
radius: f32,
|
||||
join: LineJoin,
|
||||
}
|
||||
|
||||
impl<'a> ContourStrokeToFill<'a> {
|
||||
#[inline]
|
||||
fn new(input: &Contour, output: Contour, radius: f32, join: LineJoin) -> ContourStrokeToFill {
|
||||
ContourStrokeToFill { input, output, radius, join }
|
||||
}
|
||||
|
||||
fn offset_forward(&mut self) {
|
||||
for (segment_index, segment) in self.input.iter(ContourIterFlags::empty()).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) {
|
||||
let mut segments: Vec<_> = self
|
||||
.input
|
||||
.iter(ContourIterFlags::empty())
|
||||
.map(|segment| segment.reversed())
|
||||
.collect();
|
||||
segments.reverse();
|
||||
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, join: LineJoin, contour: &mut Contour);
|
||||
fn add_to_contour(&self,
|
||||
distance: f32,
|
||||
join: LineJoin,
|
||||
join_point: Vector2F,
|
||||
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, join: LineJoin, contour: &mut Contour) {
|
||||
let join_point = self.baseline.from();
|
||||
if self.baseline.square_length() < TOLERANCE * TOLERANCE {
|
||||
self.add_to_contour(distance, join, join_point, contour);
|
||||
return;
|
||||
}
|
||||
|
||||
let candidate = self.offset_once(distance);
|
||||
if self.error_is_within_tolerance(&candidate, distance) {
|
||||
candidate.add_to_contour(distance, join, join_point, contour);
|
||||
return;
|
||||
}
|
||||
|
||||
debug!("--- SPLITTING ---");
|
||||
debug!("... PRE-SPLIT: {:?}", self);
|
||||
let (before, after) = self.split(0.5);
|
||||
debug!("... AFTER-SPLIT: {:?} {:?}", before, after);
|
||||
before.offset(distance, join, contour);
|
||||
after.offset(distance, join, contour);
|
||||
}
|
||||
|
||||
fn add_to_contour(&self,
|
||||
distance: f32,
|
||||
join: LineJoin,
|
||||
join_point: Vector2F,
|
||||
contour: &mut Contour) {
|
||||
// Add join if necessary.
|
||||
if contour.might_need_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(distance, join, join_point, LineSegment2F::new(p4, p3));
|
||||
}
|
||||
|
||||
// Push segment.
|
||||
let flags = PushSegmentFlags::UPDATE_BOUNDS | PushSegmentFlags::INCLUDE_FROM_POINT;
|
||||
contour.push_segment(self, flags);
|
||||
}
|
||||
|
||||
fn offset_once(&self, distance: f32) -> Segment {
|
||||
if self.is_line() {
|
||||
return Segment::line(self.baseline.offset(distance));
|
||||
}
|
||||
|
||||
if self.is_quadratic() {
|
||||
let mut segment_0 = LineSegment2F::new(self.baseline.from(), self.ctrl.from());
|
||||
let mut segment_1 = LineSegment2F::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 = LineSegment2F::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 = LineSegment2F::new(self.baseline.from(), self.ctrl.to());
|
||||
let mut segment_1 = LineSegment2F::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 = LineSegment2F::new(segment_0.from(), segment_1.to());
|
||||
let ctrl = LineSegment2F::new(segment_0.from(), ctrl);
|
||||
return Segment::cubic(baseline, ctrl);
|
||||
}
|
||||
|
||||
if self.ctrl.to() == self.baseline.to() {
|
||||
let mut segment_0 = LineSegment2F::new(self.baseline.from(), self.ctrl.from());
|
||||
let mut segment_1 = LineSegment2F::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 = LineSegment2F::new(segment_0.from(), segment_1.to());
|
||||
let ctrl = LineSegment2F::new(ctrl, segment_1.to());
|
||||
return Segment::cubic(baseline, ctrl);
|
||||
}
|
||||
|
||||
let mut segment_0 = LineSegment2F::new(self.baseline.from(), self.ctrl.from());
|
||||
let mut segment_1 = LineSegment2F::new(self.ctrl.from(), self.ctrl.to());
|
||||
let mut segment_2 = LineSegment2F::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);
|
||||
let (ctrl_0, ctrl_1) = match (
|
||||
segment_0.intersection_t(segment_1),
|
||||
segment_1.intersection_t(segment_2),
|
||||
) {
|
||||
(Some(t0), Some(t1)) => (segment_0.sample(t0), segment_1.sample(t1)),
|
||||
_ => (
|
||||
segment_0.to().lerp(segment_1.from(), 0.5),
|
||||
segment_1.to().lerp(segment_2.from(), 0.5),
|
||||
),
|
||||
};
|
||||
let baseline = LineSegment2F::new(segment_0.from(), segment_2.to());
|
||||
let ctrl = LineSegment2F::new(ctrl_0, ctrl_1);
|
||||
Segment::cubic(baseline, ctrl)
|
||||
}
|
||||
|
||||
fn error_is_within_tolerance(&self, other: &Segment, distance: f32) -> bool {
|
||||
let (mut min, mut max) = (
|
||||
f32::abs(distance) - TOLERANCE,
|
||||
f32::abs(distance) + TOLERANCE,
|
||||
);
|
||||
min = if min <= 0.0 { 0.0 } else { min * min };
|
||||
max = if max <= 0.0 { 0.0 } else { max * max };
|
||||
|
||||
for t_num in 0..(SAMPLE_COUNT + 1) {
|
||||
let t = t_num as f32 / SAMPLE_COUNT as f32;
|
||||
// FIXME(pcwalton): Use signed distance!
|
||||
let (this_p, other_p) = (self.sample(t), other.sample(t));
|
||||
let vector = this_p - other_p;
|
||||
let square_distance = vector.square_length();
|
||||
debug!(
|
||||
"this_p={:?} other_p={:?} vector={:?} sqdist={:?} min={:?} max={:?}",
|
||||
this_p, other_p, vector, square_distance, min, max
|
||||
);
|
||||
if square_distance < min || square_distance > max {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
const SAMPLE_COUNT: u32 = 16;
|
||||
}
|
||||
}
|
||||
|
||||
impl Contour {
|
||||
fn might_need_join(&self, join: LineJoin) -> bool {
|
||||
if self.len() < 2 {
|
||||
false
|
||||
} else {
|
||||
match join {
|
||||
LineJoin::Miter(_) | LineJoin::Round => true,
|
||||
LineJoin::Bevel => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_join(&mut self,
|
||||
distance: f32,
|
||||
join: LineJoin,
|
||||
join_point: Vector2F,
|
||||
next_tangent: LineSegment2F) {
|
||||
let (p0, p1) = (self.position_of_last(2), self.position_of_last(1));
|
||||
let prev_tangent = LineSegment2F::new(p0, p1);
|
||||
|
||||
if prev_tangent.square_length() < EPSILON || next_tangent.square_length() < EPSILON {
|
||||
return;
|
||||
}
|
||||
|
||||
match join {
|
||||
LineJoin::Bevel => {}
|
||||
LineJoin::Miter(miter_limit) => {
|
||||
if let Some(prev_tangent_t) = prev_tangent.intersection_t(next_tangent) {
|
||||
if prev_tangent_t < -EPSILON {
|
||||
return;
|
||||
}
|
||||
let miter_endpoint = prev_tangent.sample(prev_tangent_t);
|
||||
let threshold = miter_limit * distance;
|
||||
if (miter_endpoint - join_point).square_length() > threshold * threshold {
|
||||
return;
|
||||
}
|
||||
self.push_endpoint(miter_endpoint);
|
||||
}
|
||||
}
|
||||
LineJoin::Round => {
|
||||
let scale = distance.abs();
|
||||
let transform = Transform2F::from_scale(scale).translate(join_point);
|
||||
let chord_from = (prev_tangent.to() - join_point).normalize();
|
||||
let chord_to = (next_tangent.to() - join_point).normalize();
|
||||
let chord = LineSegment2F::new(chord_from, chord_to);
|
||||
self.push_arc_from_unit_chord(&transform, chord, ArcDirection::CW);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(10.0) }
|
||||
}
|
|
@ -1,106 +0,0 @@
|
|||
// pathfinder/content/src/transform.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.
|
||||
|
||||
//! Utilities for transforming paths.
|
||||
|
||||
use crate::segment::Segment;
|
||||
use pathfinder_geometry::transform2d::Transform2F;
|
||||
use pathfinder_geometry::transform3d::Perspective;
|
||||
|
||||
/// Transforms a path with a SIMD 2D transform.
|
||||
pub struct Transform2FPathIter<I>
|
||||
where
|
||||
I: Iterator<Item = Segment>,
|
||||
{
|
||||
iter: I,
|
||||
transform: Transform2F,
|
||||
}
|
||||
|
||||
impl<I> Iterator for Transform2FPathIter<I>
|
||||
where
|
||||
I: Iterator<Item = Segment>,
|
||||
{
|
||||
type Item = Segment;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Segment> {
|
||||
// TODO(pcwalton): Can we go faster by transforming an entire line segment with SIMD?
|
||||
let mut segment = self.iter.next()?;
|
||||
if !segment.is_none() {
|
||||
segment.baseline.set_from(self.transform * segment.baseline.from());
|
||||
segment.baseline.set_to(self.transform * segment.baseline.to());
|
||||
if !segment.is_line() {
|
||||
segment.ctrl.set_from(self.transform * segment.ctrl.from());
|
||||
if !segment.is_quadratic() {
|
||||
segment.ctrl.set_to(self.transform * segment.ctrl.to());
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(segment)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Transform2FPathIter<I>
|
||||
where
|
||||
I: Iterator<Item = Segment>,
|
||||
{
|
||||
#[inline]
|
||||
pub fn new(iter: I, transform: &Transform2F) -> Transform2FPathIter<I> {
|
||||
Transform2FPathIter {
|
||||
iter,
|
||||
transform: *transform,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Transforms a path with a perspective projection.
|
||||
pub struct PerspectivePathIter<I>
|
||||
where
|
||||
I: Iterator<Item = Segment>,
|
||||
{
|
||||
iter: I,
|
||||
perspective: Perspective,
|
||||
}
|
||||
|
||||
impl<I> Iterator for PerspectivePathIter<I>
|
||||
where
|
||||
I: Iterator<Item = Segment>,
|
||||
{
|
||||
type Item = Segment;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Segment> {
|
||||
let mut segment = self.iter.next()?;
|
||||
if !segment.is_none() {
|
||||
segment.baseline.set_from(self.perspective * segment.baseline.from());
|
||||
segment.baseline.set_to(self.perspective * segment.baseline.to());
|
||||
if !segment.is_line() {
|
||||
segment.ctrl.set_from(self.perspective * segment.ctrl.from());
|
||||
if !segment.is_quadratic() {
|
||||
segment.ctrl.set_to(self.perspective * segment.ctrl.to());
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(segment)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> PerspectivePathIter<I>
|
||||
where
|
||||
I: Iterator<Item = Segment>,
|
||||
{
|
||||
#[inline]
|
||||
pub fn new(iter: I, perspective: &Perspective) -> PerspectivePathIter<I> {
|
||||
PerspectivePathIter {
|
||||
iter,
|
||||
perspective: *perspective,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
// pathfinder/content/src/util.rs
|
||||
//
|
||||
// Copyright © 2020 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.
|
||||
|
||||
//! Miscellaneous utilities.
|
||||
|
||||
use pathfinder_geometry::line_segment::LineSegment2F;
|
||||
use pathfinder_geometry::transform2d::Transform2F;
|
||||
use pathfinder_simd::default::{F32x2, F32x4};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::mem;
|
||||
|
||||
pub(crate) fn hash_line_segment<H>(line_segment: LineSegment2F, state: &mut H) where H: Hasher {
|
||||
hash_f32x4(line_segment.0, state);
|
||||
}
|
||||
|
||||
pub(crate) fn hash_transform2f<H>(transform: Transform2F, state: &mut H) where H: Hasher {
|
||||
hash_f32x4(transform.matrix.0, state);
|
||||
hash_f32x2(transform.vector.0, state);
|
||||
}
|
||||
|
||||
pub(crate) fn hash_f32<H>(value: f32, state: &mut H) where H: Hasher {
|
||||
unsafe {
|
||||
let data: u32 = mem::transmute::<f32, u32>(value);
|
||||
data.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn hash_f32x2<H>(vector: F32x2, state: &mut H) where H: Hasher {
|
||||
unsafe {
|
||||
let data: [u32; 2] = mem::transmute::<F32x2, [u32; 2]>(vector);
|
||||
data.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn hash_f32x4<H>(vector: F32x4, state: &mut H) where H: Hasher {
|
||||
unsafe {
|
||||
let data: [u32; 4] = mem::transmute::<F32x4, [u32; 4]>(vector);
|
||||
data.hash(state);
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
[package]
|
||||
name = "pathfinder_export"
|
||||
version = "0.1.0"
|
||||
authors = ["Sebastian Köln <sebk@rynx.org>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
pathfinder_color = { path = "../color" }
|
||||
pathfinder_content = { path = "../content" }
|
||||
pathfinder_geometry = { path = "../geometry" }
|
||||
pathfinder_renderer = { path = "../renderer" }
|
||||
deflate = "*"
|
|
@ -1,194 +0,0 @@
|
|||
// pathfinder/export/src/lib.rs
|
||||
//
|
||||
// Copyright © 2020 The Pathfinder Project Developers.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use pathfinder_content::outline::ContourIterFlags;
|
||||
use pathfinder_content::segment::SegmentKind;
|
||||
use pathfinder_renderer::scene::Scene;
|
||||
use pathfinder_geometry::vector::{Vector2F, vec2f};
|
||||
use std::fmt;
|
||||
use std::io::{self, Write};
|
||||
|
||||
mod pdf;
|
||||
use pdf::Pdf;
|
||||
|
||||
pub enum FileFormat {
|
||||
/// Scalable Vector Graphics
|
||||
SVG,
|
||||
|
||||
/// Portable Document Format
|
||||
PDF,
|
||||
|
||||
/// PostScript
|
||||
PS,
|
||||
}
|
||||
|
||||
pub trait Export {
|
||||
fn export<W: Write>(&self, writer: &mut W, format: FileFormat) -> io::Result<()>;
|
||||
}
|
||||
|
||||
impl Export for Scene {
|
||||
fn export<W: Write>(&self, writer: &mut W, format: FileFormat) -> io::Result<()> {
|
||||
match format {
|
||||
FileFormat::SVG => export_svg(self, writer),
|
||||
FileFormat::PDF => export_pdf(self, writer),
|
||||
FileFormat::PS => export_ps(self, writer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn export_svg<W: Write>(scene: &Scene, writer: &mut W) -> io::Result<()> {
|
||||
let view_box = scene.view_box();
|
||||
writeln!(
|
||||
writer,
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"{} {} {} {}\">",
|
||||
view_box.origin().x(),
|
||||
view_box.origin().y(),
|
||||
view_box.size().x(),
|
||||
view_box.size().y()
|
||||
)?;
|
||||
for (paint, outline, name) in scene.paths() {
|
||||
write!(writer, " <path")?;
|
||||
if !name.is_empty() {
|
||||
write!(writer, " id=\"{}\"", name)?;
|
||||
}
|
||||
writeln!(writer, " fill=\"{:?}\" d=\"{:?}\" />", paint, outline)?;
|
||||
}
|
||||
writeln!(writer, "</svg>")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn export_pdf<W: Write>(scene: &Scene, writer: &mut W) -> io::Result<()> {
|
||||
let mut pdf = Pdf::new();
|
||||
let view_box = scene.view_box();
|
||||
pdf.add_page(view_box.size());
|
||||
|
||||
let height = view_box.size().y();
|
||||
let tr = |v: Vector2F| -> Vector2F {
|
||||
let r = v - view_box.origin();
|
||||
vec2f(r.x(), height - r.y())
|
||||
};
|
||||
|
||||
for (paint, outline, _) in scene.paths() {
|
||||
// TODO(pcwalton): Gradients and patterns.
|
||||
if paint.is_color() {
|
||||
pdf.set_fill_color(paint.base_color());
|
||||
}
|
||||
|
||||
for contour in outline.contours() {
|
||||
for (segment_index, segment) in contour.iter(ContourIterFlags::empty()).enumerate() {
|
||||
if segment_index == 0 {
|
||||
pdf.move_to(tr(segment.baseline.from()));
|
||||
}
|
||||
|
||||
match segment.kind {
|
||||
SegmentKind::None => {}
|
||||
SegmentKind::Line => pdf.line_to(tr(segment.baseline.to())),
|
||||
SegmentKind::Quadratic => {
|
||||
let current = segment.baseline.from();
|
||||
let c = segment.ctrl.from();
|
||||
let p = segment.baseline.to();
|
||||
let c1 = c * (2.0 / 3.0) + current * (1.0 / 3.0);
|
||||
let c2 = c * (2.0 / 3.0) + p * (1.0 / 3.0);
|
||||
pdf.cubic_to(c1, c2, p);
|
||||
}
|
||||
SegmentKind::Cubic => {
|
||||
pdf.cubic_to(tr(segment.ctrl.from()),
|
||||
tr(segment.ctrl.to()),
|
||||
tr(segment.baseline.to()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if contour.is_closed() {
|
||||
pdf.close();
|
||||
}
|
||||
}
|
||||
|
||||
// closes implicitly
|
||||
pdf.fill();
|
||||
}
|
||||
pdf.write_to(writer)
|
||||
}
|
||||
|
||||
fn export_ps<W: Write>(scene: &Scene, writer: &mut W) -> io::Result<()> {
|
||||
struct P(Vector2F);
|
||||
impl fmt::Display for P {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{} {}", self.0.x(), self.0.y())
|
||||
}
|
||||
}
|
||||
|
||||
let view_box = scene.view_box();
|
||||
writeln!(writer, "%!PS-Adobe-3.0 EPSF-3.0")?;
|
||||
writeln!(writer, "%%BoundingBox: {:.0} {:.0}",
|
||||
P(view_box.origin()),
|
||||
P(view_box.size()),
|
||||
)?;
|
||||
writeln!(writer, "%%HiResBoundingBox: {} {}",
|
||||
P(view_box.origin()),
|
||||
P(view_box.size()),
|
||||
)?;
|
||||
writeln!(writer, "0 {} translate", view_box.size().y())?;
|
||||
writeln!(writer, "1 -1 scale")?;
|
||||
|
||||
for (paint, outline, name) in scene.paths() {
|
||||
if !name.is_empty() {
|
||||
writeln!(writer, "newpath % {}", name)?;
|
||||
} else {
|
||||
writeln!(writer, "newpath")?;
|
||||
}
|
||||
|
||||
for contour in outline.contours() {
|
||||
for (segment_index, segment) in contour.iter(ContourIterFlags::empty()).enumerate() {
|
||||
if segment_index == 0 {
|
||||
writeln!(writer, "{} moveto", P(segment.baseline.from()))?;
|
||||
}
|
||||
|
||||
match segment.kind {
|
||||
SegmentKind::None => {}
|
||||
SegmentKind::Line => {
|
||||
writeln!(writer, "{} lineto", P(segment.baseline.to()))?;
|
||||
}
|
||||
SegmentKind::Quadratic => {
|
||||
let current = segment.baseline.from();
|
||||
let c = segment.ctrl.from();
|
||||
let p = segment.baseline.to();
|
||||
let c1 = c * (2.0 / 3.0) + current * (1.0 / 3.0);
|
||||
let c2 = c * (2.0 / 3.0) + p * (1.0 / 3.0);
|
||||
writeln!(writer, "{} {} {} curveto", P(c1), P(c2), P(p))?;
|
||||
}
|
||||
SegmentKind::Cubic => {
|
||||
writeln!(writer, "{} {} {} curveto",
|
||||
P(segment.ctrl.from()),
|
||||
P(segment.ctrl.to()),
|
||||
P(segment.baseline.to())
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if contour.is_closed() {
|
||||
writeln!(writer, "closepath")?;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(pcwalton): Gradients and patterns.
|
||||
if paint.is_color() {
|
||||
let color = paint.base_color();
|
||||
writeln!(writer, "{} {} {} setrgbcolor", color.r, color.g, color.b)?;
|
||||
}
|
||||
|
||||
writeln!(writer, "fill")?;
|
||||
}
|
||||
writeln!(writer, "showpage")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -1,260 +0,0 @@
|
|||
// pathfinder/export/src/pdf.rs
|
||||
//
|
||||
// Copyright © 2020 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.
|
||||
|
||||
//! This is a heavily modified version of the pdfpdf crate by Benjamin Kimock <kimockb@gmail.com>
|
||||
//! (aka. saethlin)
|
||||
|
||||
use deflate::Compression;
|
||||
use pathfinder_color::ColorU;
|
||||
use pathfinder_geometry::vector::Vector2F;
|
||||
use std::io::{self, Write};
|
||||
|
||||
struct Counter<T> {
|
||||
inner: T,
|
||||
count: u64
|
||||
}
|
||||
impl<T> Counter<T> {
|
||||
pub fn new(inner: T) -> Counter<T> {
|
||||
Counter {
|
||||
inner,
|
||||
count: 0
|
||||
}
|
||||
}
|
||||
pub fn pos(&self) -> u64 {
|
||||
self.count
|
||||
}
|
||||
}
|
||||
impl<W: Write> Write for Counter<W> {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
match self.inner.write(buf) {
|
||||
Ok(n) => {
|
||||
self.count += n as u64;
|
||||
Ok(n)
|
||||
},
|
||||
Err(e) => Err(e)
|
||||
}
|
||||
}
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.inner.flush()
|
||||
}
|
||||
|
||||
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
|
||||
self.inner.write_all(buf)?;
|
||||
self.count += buf.len() as u64;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a PDF internal object
|
||||
struct PdfObject {
|
||||
contents: Vec<u8>,
|
||||
is_page: bool,
|
||||
is_xobject: bool,
|
||||
offset: Option<u64>,
|
||||
}
|
||||
|
||||
/// The top-level struct that represents a (partially) in-memory PDF file
|
||||
pub struct Pdf {
|
||||
page_buffer: Vec<u8>,
|
||||
objects: Vec<PdfObject>,
|
||||
page_size: Option<Vector2F>,
|
||||
compression: Option<Compression>,
|
||||
}
|
||||
|
||||
impl Default for Pdf {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Pdf {
|
||||
/// Create a new blank PDF document
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
page_buffer: Vec::new(),
|
||||
objects: vec![
|
||||
PdfObject {
|
||||
contents: Vec::new(),
|
||||
is_page: false,
|
||||
is_xobject: false,
|
||||
offset: None,
|
||||
},
|
||||
PdfObject {
|
||||
contents: Vec::new(),
|
||||
is_page: false,
|
||||
is_xobject: false,
|
||||
offset: None,
|
||||
},
|
||||
],
|
||||
page_size: None,
|
||||
compression: Some(Compression::Fast)
|
||||
}
|
||||
}
|
||||
|
||||
fn add_object(&mut self, data: Vec<u8>, is_page: bool, is_xobject: bool) -> usize {
|
||||
self.objects.push(PdfObject {
|
||||
contents: data,
|
||||
is_page,
|
||||
is_xobject,
|
||||
offset: None,
|
||||
});
|
||||
self.objects.len()
|
||||
}
|
||||
|
||||
/// Set the color for all subsequent drawing operations
|
||||
#[inline]
|
||||
pub fn set_fill_color(&mut self, color: ColorU) {
|
||||
let norm = |color| f32::from(color) / 255.0;
|
||||
writeln!(self.page_buffer, "{} {} {} rg",
|
||||
norm(color.r),
|
||||
norm(color.g),
|
||||
norm(color.b)
|
||||
).unwrap();
|
||||
}
|
||||
|
||||
/// Move to a new page in the PDF document
|
||||
#[inline]
|
||||
pub fn add_page(&mut self, size: Vector2F) {
|
||||
// Compress and write out the previous page if it exists
|
||||
if !self.page_buffer.is_empty() {
|
||||
self.end_page();
|
||||
self.page_buffer.clear();
|
||||
}
|
||||
|
||||
self.page_buffer
|
||||
.extend("/DeviceRGB cs /DeviceRGB CS\n1 j 1 J\n".bytes());
|
||||
self.page_size = Some(size);
|
||||
}
|
||||
|
||||
pub fn move_to(&mut self, p: Vector2F) {
|
||||
writeln!(self.page_buffer, "{} {} m", p.x(), p.y()).unwrap();
|
||||
}
|
||||
|
||||
pub fn line_to(&mut self, p: Vector2F) {
|
||||
writeln!(self.page_buffer, "{} {} l", p.x(), p.y()).unwrap();
|
||||
}
|
||||
|
||||
pub fn cubic_to(&mut self, c1: Vector2F, c2: Vector2F, p: Vector2F) {
|
||||
writeln!(self.page_buffer, "{} {} {} {} {} {} c", c1.x(), c1.y(), c2.x(), c2.y(), p.x(), p.y()).unwrap();
|
||||
}
|
||||
pub fn fill(&mut self) {
|
||||
writeln!(self.page_buffer, "f").unwrap();
|
||||
}
|
||||
|
||||
pub fn close(&mut self) {
|
||||
writeln!(self.page_buffer, "h").unwrap();
|
||||
}
|
||||
/// Dump a page out to disk
|
||||
fn end_page(&mut self) {
|
||||
let size = match self.page_size.take() {
|
||||
Some(size) => size,
|
||||
None => return // no page started
|
||||
};
|
||||
let page_stream = if let Some(level) = self.compression {
|
||||
let compressed = deflate::deflate_bytes_zlib_conf(&self.page_buffer, level);
|
||||
let mut page = format!(
|
||||
"<< /Length {} /Filter [/FlateDecode] >>\nstream\n",
|
||||
compressed.len()
|
||||
)
|
||||
.into_bytes();
|
||||
page.extend_from_slice(&compressed);
|
||||
page.extend(b"endstream\n");
|
||||
page
|
||||
} else {
|
||||
let mut page = Vec::new();
|
||||
page.extend(format!("<< /Length {} >>\nstream\n", self.page_buffer.len()).bytes());
|
||||
page.extend(&self.page_buffer);
|
||||
page.extend(b"endstream\n");
|
||||
page
|
||||
};
|
||||
|
||||
// Create the stream object for this page
|
||||
let stream_object_id = self.add_object(page_stream, false, false);
|
||||
|
||||
// Create the page object, which describes settings for the whole page
|
||||
let mut page_object = b"<< /Type /Page\n \
|
||||
/Parent 2 0 R\n \
|
||||
/Resources <<\n"
|
||||
.to_vec();
|
||||
|
||||
for (idx, _obj) in self.objects.iter().enumerate().filter(|&(_, o)| o.is_xobject) {
|
||||
write!(page_object, "/XObject {} 0 R ", idx+1).unwrap();
|
||||
}
|
||||
|
||||
write!(page_object,
|
||||
" >>\n \
|
||||
/MediaBox [0 0 {} {}]\n \
|
||||
/Contents {} 0 R\n\
|
||||
>>\n",
|
||||
size.x(), size.y(), stream_object_id
|
||||
).unwrap();
|
||||
self.add_object(page_object, true, false);
|
||||
}
|
||||
|
||||
/// Write the in-memory PDF representation to disk
|
||||
pub fn write_to<W>(&mut self, writer: W) -> io::Result<()> where W: Write {
|
||||
let mut out = Counter::new(writer);
|
||||
out.write_all(b"%PDF-1.7\n%\xB5\xED\xAE\xFB\n")?;
|
||||
|
||||
if !self.page_buffer.is_empty() {
|
||||
self.end_page();
|
||||
}
|
||||
|
||||
// Write out each object
|
||||
for (idx, obj) in self.objects.iter_mut().enumerate().skip(2) {
|
||||
obj.offset = Some(out.pos());
|
||||
write!(out, "{} 0 obj\n", idx+1)?;
|
||||
out.write_all(&obj.contents)?;
|
||||
out.write_all(b"endobj\n")?;
|
||||
}
|
||||
|
||||
// Write out the page tree object
|
||||
self.objects[1].offset = Some(out.pos());
|
||||
out.write_all(b"2 0 obj\n")?;
|
||||
out.write_all(b"<< /Type /Pages\n")?;
|
||||
write!(out,
|
||||
"/Count {}\n",
|
||||
self.objects.iter().filter(|o| o.is_page).count()
|
||||
)?;
|
||||
out.write_all(b"/Kids [")?;
|
||||
for (idx, _obj) in self.objects.iter().enumerate().filter(|&(_, obj)| obj.is_page) {
|
||||
write!(out, "{} 0 R ", idx + 1)?;
|
||||
}
|
||||
out.write_all(b"] >>\nendobj\n")?;
|
||||
|
||||
// Write out the catalog dictionary object
|
||||
self.objects[0].offset = Some(out.pos());
|
||||
out.write_all(b"1 0 obj\n<< /Type /Catalog\n/Pages 2 0 R >>\nendobj\n")?;
|
||||
|
||||
// Write the cross-reference table
|
||||
let startxref = out.pos() + 1; // NOTE: apparently there's some 1-based indexing??
|
||||
out.write_all(b"xref\n")?;
|
||||
write!(out, "0 {}\n", self.objects.len() + 1)?;
|
||||
out.write_all(b"0000000000 65535 f \n")?;
|
||||
|
||||
for obj in &self.objects {
|
||||
write!(out, "{:010} 00000 f \n", obj.offset.unwrap())?;
|
||||
}
|
||||
|
||||
// Write the document trailer
|
||||
out.write_all(b"trailer\n")?;
|
||||
write!(out, "<< /Size {}\n", self.objects.len())?;
|
||||
out.write_all(b"/Root 1 0 R >>\n")?;
|
||||
|
||||
// Write the offset to the xref table
|
||||
write!(out, "startxref\n{}\n", startxref)?;
|
||||
|
||||
// Write the PDF EOF
|
||||
out.write_all(b"%%EOF")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
[package]
|
||||
name = "pathfinder_geometry"
|
||||
version = "0.5.1"
|
||||
edition = "2018"
|
||||
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
|
||||
description = "Basic SIMD-accelerated geometry/linear algebra"
|
||||
license = "MIT/Apache-2.0"
|
||||
repository = "https://github.com/servo/pathfinder"
|
||||
homepage = "https://github.com/servo/pathfinder"
|
||||
|
||||
[features]
|
||||
shader_alignment_32_bits = []
|
||||
|
||||
[dependencies]
|
||||
|
||||
[dependencies.log]
|
||||
version = "0.4"
|
||||
|
||||
[dependencies.pathfinder_simd]
|
||||
path = "../simd"
|
||||
version = "0.5"
|
|
@ -1,19 +0,0 @@
|
|||
#[cfg(feature = "shader_alignment_32_bits")]
|
||||
pub type AlignedU8 = u32;
|
||||
#[cfg(not(feature = "shader_alignment_32_bits"))]
|
||||
pub type AlignedU8 = u8;
|
||||
|
||||
#[cfg(feature = "shader_alignment_32_bits")]
|
||||
pub type AlignedU16 = u32;
|
||||
#[cfg(not(feature = "shader_alignment_32_bits"))]
|
||||
pub type AlignedU16 = u16;
|
||||
|
||||
#[cfg(feature = "shader_alignment_32_bits")]
|
||||
pub type AlignedI8 = i32;
|
||||
#[cfg(not(feature = "shader_alignment_32_bits"))]
|
||||
pub type AlignedI8 = i8;
|
||||
|
||||
#[cfg(feature = "shader_alignment_32_bits")]
|
||||
pub type AlignedI16 = i32;
|
||||
#[cfg(not(feature = "shader_alignment_32_bits"))]
|
||||
pub type AlignedI16 = i16;
|
|
@ -1,19 +0,0 @@
|
|||
// pathfinder/geometry/src/angle.rs
|
||||
//
|
||||
// Copyright © 2020 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.
|
||||
|
||||
//! Angle utilities.
|
||||
|
||||
use std::f32::consts::PI;
|
||||
|
||||
#[inline]
|
||||
pub fn angle_from_degrees(degrees: f32) -> f32 {
|
||||
const SCALE: f32 = 2.0 * PI / 360.0;
|
||||
degrees * SCALE
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
// pathfinder/geometry/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.
|
||||
|
||||
//! Basic geometry and linear algebra primitives, optimized with SIMD.
|
||||
|
||||
pub mod angle;
|
||||
pub mod line_segment;
|
||||
pub mod rect;
|
||||
pub mod transform2d;
|
||||
pub mod transform3d;
|
||||
pub mod unit_vector;
|
||||
pub mod util;
|
||||
pub mod vector;
|
||||
pub mod alignment;
|
|
@ -1,309 +0,0 @@
|
|||
// pathfinder/geometry/src/basic/line_segment.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.
|
||||
|
||||
//! Line segment types, optimized with SIMD.
|
||||
|
||||
use crate::transform2d::Matrix2x2F;
|
||||
use crate::alignment::AlignedU8;
|
||||
use crate::util;
|
||||
use crate::vector::{vec2f, Vector2F};
|
||||
use pathfinder_simd::default::F32x4;
|
||||
use std::ops::{Add, Mul, MulAssign, Sub};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Default)]
|
||||
pub struct LineSegment2F(pub F32x4);
|
||||
|
||||
impl LineSegment2F {
|
||||
#[inline]
|
||||
pub fn new(from: Vector2F, to: Vector2F) -> LineSegment2F {
|
||||
LineSegment2F(from.0.concat_xy_xy(to.0))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from(self) -> Vector2F {
|
||||
Vector2F(self.0.xy())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to(self) -> Vector2F {
|
||||
Vector2F(self.0.zw())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_from(&mut self, point: Vector2F) {
|
||||
self.0 = point.0.to_f32x4().concat_xy_zw(self.0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_to(&mut self, point: Vector2F) {
|
||||
self.0 = self.0.concat_xy_xy(point.0.to_f32x4())
|
||||
}
|
||||
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
#[inline]
|
||||
pub fn from_x(self) -> f32 {
|
||||
self.0[0]
|
||||
}
|
||||
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
#[inline]
|
||||
pub fn from_y(self) -> f32 {
|
||||
self.0[1]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_x(self) -> f32 {
|
||||
self.0[2]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_y(self) -> f32 {
|
||||
self.0[3]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_from_x(&mut self, x: f32) {
|
||||
self.0[0] = x
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_from_y(&mut self, y: f32) {
|
||||
self.0[1] = y
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_to_x(&mut self, x: f32) {
|
||||
self.0[2] = x
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_to_y(&mut self, y: f32) {
|
||||
self.0[3] = y
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn split(self, t: f32) -> (LineSegment2F, LineSegment2F) {
|
||||
debug_assert!(t >= 0.0 && t <= 1.0);
|
||||
let (from_from, to_to) = (self.0.xyxy(), self.0.zwzw());
|
||||
let d_d = to_to - from_from;
|
||||
let mid_mid = from_from + d_d * F32x4::splat(t);
|
||||
(
|
||||
LineSegment2F(from_from.concat_xy_xy(mid_mid)),
|
||||
LineSegment2F(mid_mid.concat_xy_xy(to_to)),
|
||||
)
|
||||
}
|
||||
|
||||
// Returns the left segment first, followed by the right segment.
|
||||
#[inline]
|
||||
pub fn split_at_x(self, x: f32) -> (LineSegment2F, LineSegment2F) {
|
||||
let (min_part, max_part) = self.split(self.solve_t_for_x(x));
|
||||
if min_part.from_x() < max_part.from_x() {
|
||||
(min_part, max_part)
|
||||
} else {
|
||||
(max_part, min_part)
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the upper segment first, followed by the lower segment.
|
||||
#[inline]
|
||||
pub fn split_at_y(self, y: f32) -> (LineSegment2F, LineSegment2F) {
|
||||
let (min_part, max_part) = self.split(self.solve_t_for_y(y));
|
||||
|
||||
// Make sure we compare `from_y` and `to_y` to properly handle the case in which one of the
|
||||
// two segments is zero-length.
|
||||
if min_part.from_y() < max_part.to_y() {
|
||||
(min_part, max_part)
|
||||
} else {
|
||||
(max_part, min_part)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn solve_t_for_x(self, x: f32) -> f32 {
|
||||
(x - self.from_x()) / (self.to_x() - self.from_x())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn solve_t_for_y(self, y: f32) -> f32 {
|
||||
(y - self.from_y()) / (self.to_y() - self.from_y())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn solve_x_for_y(self, y: f32) -> f32 {
|
||||
util::lerp(self.from_x(), self.to_x(), self.solve_t_for_y(y))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn solve_y_for_x(self, x: f32) -> f32 {
|
||||
util::lerp(self.from_y(), self.to_y(), self.solve_t_for_x(x))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn reversed(self) -> LineSegment2F {
|
||||
LineSegment2F(self.0.zwxy())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn upper_point(self) -> Vector2F {
|
||||
if self.from_y() < self.to_y() {
|
||||
self.from()
|
||||
} else {
|
||||
self.to()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn min_x(self) -> f32 {
|
||||
f32::min(self.from_x(), self.to_x())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn max_x(self) -> f32 {
|
||||
f32::max(self.from_x(), self.to_x())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn min_y(self) -> f32 {
|
||||
f32::min(self.from_y(), self.to_y())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn max_y(self) -> f32 {
|
||||
f32::max(self.from_y(), self.to_y())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn y_winding(self) -> i32 {
|
||||
if self.from_y() < self.to_y() {
|
||||
1
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
}
|
||||
|
||||
// Reverses if necessary so that the from point is above the to point. Calling this method
|
||||
// again will undo the transformation.
|
||||
#[inline]
|
||||
pub fn orient(self, y_winding: i32) -> LineSegment2F {
|
||||
if y_winding >= 0 {
|
||||
self
|
||||
} else {
|
||||
self.reversed()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(pcwalton): Optimize with SIMD.
|
||||
#[inline]
|
||||
pub fn square_length(self) -> f32 {
|
||||
let (dx, dy) = (self.to_x() - self.from_x(), self.to_y() - self.from_y());
|
||||
dx * dx + dy * dy
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn length(self) -> f32 {
|
||||
self.square_length().sqrt()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn vector(self) -> Vector2F {
|
||||
self.to() - self.from()
|
||||
}
|
||||
|
||||
// http://www.cs.swan.ac.uk/~cssimon/line_intersection.html
|
||||
pub fn intersection_t(self, other: LineSegment2F) -> Option<f32> {
|
||||
let p0p1 = self.vector();
|
||||
let matrix = Matrix2x2F(other.vector().0.concat_xy_xy((-p0p1).0));
|
||||
if f32::abs(matrix.det()) < EPSILON {
|
||||
return None;
|
||||
}
|
||||
return Some((matrix.inverse() * (self.from() - other.from())).y());
|
||||
|
||||
const EPSILON: f32 = 0.0001;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn sample(self, t: f32) -> Vector2F {
|
||||
self.from() + self.vector() * t
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn midpoint(self) -> Vector2F {
|
||||
self.sample(0.5)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn offset(self, distance: f32) -> LineSegment2F {
|
||||
if self.is_zero_length() {
|
||||
self
|
||||
} else {
|
||||
self + self.vector().yx().normalize() * vec2f(-distance, distance)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_zero_length(self) -> bool {
|
||||
self.vector().is_zero()
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Vector2F> for LineSegment2F {
|
||||
type Output = LineSegment2F;
|
||||
#[inline]
|
||||
fn add(self, point: Vector2F) -> LineSegment2F {
|
||||
LineSegment2F(self.0 + point.0.to_f32x4().xyxy())
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<Vector2F> for LineSegment2F {
|
||||
type Output = LineSegment2F;
|
||||
#[inline]
|
||||
fn sub(self, point: Vector2F) -> LineSegment2F {
|
||||
LineSegment2F(self.0 - point.0.to_f32x4().xyxy())
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Vector2F> for LineSegment2F {
|
||||
type Output = LineSegment2F;
|
||||
#[inline]
|
||||
fn mul(self, factors: Vector2F) -> LineSegment2F {
|
||||
LineSegment2F(self.0 * factors.0.to_f32x4().xyxy())
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<f32> for LineSegment2F {
|
||||
type Output = LineSegment2F;
|
||||
#[inline]
|
||||
fn mul(self, factor: f32) -> LineSegment2F {
|
||||
LineSegment2F(self.0 * F32x4::splat(factor))
|
||||
}
|
||||
}
|
||||
|
||||
impl MulAssign<Vector2F> for LineSegment2F {
|
||||
#[inline]
|
||||
fn mul_assign(&mut self, factors: Vector2F) {
|
||||
*self = *self * factors
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
#[repr(C)]
|
||||
pub struct LineSegmentU4 {
|
||||
pub from: AlignedU8,
|
||||
pub to: AlignedU8,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
#[repr(C)]
|
||||
pub struct LineSegmentU8 {
|
||||
pub from_x: u8,
|
||||
pub from_y: u8,
|
||||
pub to_x: u8,
|
||||
pub to_y: u8,
|
||||
}
|
|
@ -1,371 +0,0 @@
|
|||
// pathfinder/geometry/src/basic/rect.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.
|
||||
|
||||
//! 2D axis-aligned rectangles, optimized with SIMD.
|
||||
|
||||
use crate::vector::{IntoVector2F, Vector2F, Vector2I};
|
||||
use pathfinder_simd::default::{F32x4, I32x4};
|
||||
use std::ops::{Add, Mul, Sub};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Default)]
|
||||
pub struct RectF(pub F32x4);
|
||||
|
||||
impl RectF {
|
||||
#[inline]
|
||||
pub fn new(origin: Vector2F, size: Vector2F) -> RectF {
|
||||
RectF(origin.0.concat_xy_xy(origin.0 + size.0))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_points(origin: Vector2F, lower_right: Vector2F) -> RectF {
|
||||
RectF(origin.0.concat_xy_xy(lower_right.0))
|
||||
}
|
||||
|
||||
// Accessors
|
||||
|
||||
#[inline]
|
||||
pub fn origin(self) -> Vector2F {
|
||||
Vector2F(self.0.xy())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn size(self) -> Vector2F {
|
||||
Vector2F(self.0.zw() - self.0.xy())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn origin_x(self) -> f32 {
|
||||
self.0.x()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn origin_y(self) -> f32 {
|
||||
self.0.y()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn width(self) -> f32 {
|
||||
self.0.z() - self.0.x()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn height(self) -> f32 {
|
||||
self.0.w() - self.0.y()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn upper_right(self) -> Vector2F {
|
||||
Vector2F(self.0.zy())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn lower_left(self) -> Vector2F {
|
||||
Vector2F(self.0.xw())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn lower_right(self) -> Vector2F {
|
||||
Vector2F(self.0.zw())
|
||||
}
|
||||
|
||||
// Mutators
|
||||
|
||||
#[inline]
|
||||
pub fn set_origin_x(&mut self, x: f32) {
|
||||
self.0.set_x(x)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_origin_y(&mut self, y: f32) {
|
||||
self.0.set_y(y)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn contains_point(self, point: Vector2F) -> bool {
|
||||
// self.origin <= point && point <= self.lower_right
|
||||
let point = point.0.to_f32x4();
|
||||
self.0.concat_xy_xy(point).packed_le(point.concat_xy_zw(self.0)).all_true()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn contains_rect(self, other: RectF) -> bool {
|
||||
// self.origin <= other.origin && other.lower_right <= self.lower_right
|
||||
self.0.concat_xy_zw(other.0).packed_le(other.0.concat_xy_zw(self.0)).all_true()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_empty(self) -> bool {
|
||||
self.origin() == self.lower_right()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn union_point(self, point: Vector2F) -> RectF {
|
||||
RectF::from_points(self.origin().min(point), self.lower_right().max(point))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn union_rect(self, other: RectF) -> RectF {
|
||||
RectF::from_points(
|
||||
self.origin().min(other.origin()),
|
||||
self.lower_right().max(other.lower_right()),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn intersects(self, other: RectF) -> bool {
|
||||
// self.origin < other.lower_right && other.origin < self.lower_right
|
||||
self.0.concat_xy_xy(other.0).packed_lt(other.0.concat_zw_zw(self.0)).all_true()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn intersection(self, other: RectF) -> Option<RectF> {
|
||||
if !self.intersects(other) {
|
||||
None
|
||||
} else {
|
||||
Some(RectF::from_points(
|
||||
self.origin().max(other.origin()),
|
||||
self.lower_right().min(other.lower_right()),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn min_x(self) -> f32 {
|
||||
self.0[0]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn min_y(self) -> f32 {
|
||||
self.0[1]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn max_x(self) -> f32 {
|
||||
self.0[2]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn max_y(self) -> f32 {
|
||||
self.0[3]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn center(self) -> Vector2F {
|
||||
self.origin() + self.size() * 0.5
|
||||
}
|
||||
|
||||
/// Rounds all points to the nearest integer.
|
||||
#[inline]
|
||||
pub fn round(self) -> RectF {
|
||||
RectF(self.0.to_i32x4().to_f32x4())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn round_out(self) -> RectF {
|
||||
RectF::from_points(self.origin().floor(), self.lower_right().ceil())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn dilate<A>(self, amount: A) -> RectF where A: IntoVector2F {
|
||||
let amount = amount.into_vector_2f();
|
||||
RectF::from_points(self.origin() - amount, self.lower_right() + amount)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn contract<A>(self, amount: A) -> RectF where A: IntoVector2F {
|
||||
let amount = amount.into_vector_2f();
|
||||
RectF::from_points(self.origin() + amount, self.lower_right() - amount)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_i32(&self) -> RectI {
|
||||
RectI(self.0.to_i32x4())
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Vector2F> for RectF {
|
||||
type Output = RectF;
|
||||
#[inline]
|
||||
fn add(self, other: Vector2F) -> RectF {
|
||||
RectF::new(self.origin() + other, self.size())
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<f32> for RectF {
|
||||
type Output = RectF;
|
||||
#[inline]
|
||||
fn add(self, other: f32) -> RectF {
|
||||
RectF::new(self.origin() + other, self.size())
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Vector2F> for RectF {
|
||||
type Output = RectF;
|
||||
#[inline]
|
||||
fn mul(self, factors: Vector2F) -> RectF {
|
||||
RectF(self.0 * factors.0.concat_xy_xy(factors.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<f32> for RectF {
|
||||
type Output = RectF;
|
||||
#[inline]
|
||||
fn mul(self, factor: f32) -> RectF {
|
||||
RectF(self.0 * F32x4::splat(factor))
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<Vector2F> for RectF {
|
||||
type Output = RectF;
|
||||
#[inline]
|
||||
fn sub(self, other: Vector2F) -> RectF {
|
||||
RectF::new(self.origin() - other, self.size())
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<f32> for RectF {
|
||||
type Output = RectF;
|
||||
#[inline]
|
||||
fn sub(self, other: f32) -> RectF {
|
||||
RectF::new(self.origin() - other, self.size())
|
||||
}
|
||||
}
|
||||
|
||||
/// NB: The origin is inclusive, while the lower right point is exclusive.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Default)]
|
||||
pub struct RectI(pub I32x4);
|
||||
|
||||
impl RectI {
|
||||
#[inline]
|
||||
pub fn new(origin: Vector2I, size: Vector2I) -> RectI {
|
||||
RectI(origin.0.concat_xy_xy(origin.0 + size.0))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_points(origin: Vector2I, lower_right: Vector2I) -> RectI {
|
||||
RectI(origin.0.concat_xy_xy(lower_right.0))
|
||||
}
|
||||
|
||||
// Accessors
|
||||
|
||||
#[inline]
|
||||
pub fn origin(&self) -> Vector2I {
|
||||
Vector2I(self.0.xy())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn size(&self) -> Vector2I {
|
||||
Vector2I(self.0.zw() - self.0.xy())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn origin_x(self) -> i32 {
|
||||
self.0.x()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn origin_y(self) -> i32 {
|
||||
self.0.y()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn width(self) -> i32 {
|
||||
self.0.z() - self.0.x()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn height(self) -> i32 {
|
||||
self.0.w() - self.0.y()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn upper_right(&self) -> Vector2I {
|
||||
Vector2I(self.0.zy())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn lower_left(&self) -> Vector2I {
|
||||
Vector2I(self.0.xw())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn lower_right(&self) -> Vector2I {
|
||||
Vector2I(self.0.zw())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn scale(self, factor: i32) -> RectI {
|
||||
RectI(self.0 * I32x4::splat(factor))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn scale_xy(self, factors: Vector2I) -> RectI {
|
||||
RectI(self.0 * factors.0.concat_xy_xy(factors.0))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn min_x(self) -> i32 {
|
||||
self.0[0]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn min_y(self) -> i32 {
|
||||
self.0[1]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn max_x(self) -> i32 {
|
||||
self.0[2]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn max_y(self) -> i32 {
|
||||
self.0[3]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn intersects(self, other: RectI) -> bool {
|
||||
// self.origin < other.lower_right && other.origin < self.lower_right
|
||||
self.0.concat_xy_xy(other.0).packed_lt(other.0.concat_zw_zw(self.0)).all_true()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn intersection(self, other: RectI) -> Option<RectI> {
|
||||
if !self.intersects(other) {
|
||||
None
|
||||
} else {
|
||||
Some(RectI::from_points(
|
||||
self.origin().max(other.origin()),
|
||||
self.lower_right().min(other.lower_right()),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn contains_point(&self, point: Vector2I) -> bool {
|
||||
// self.origin <= point && point <= self.lower_right - 1
|
||||
let lower_right = self.lower_right() - 1;
|
||||
self.origin()
|
||||
.0
|
||||
.concat_xy_xy(point.0)
|
||||
.packed_le(point.0.concat_xy_xy(lower_right.0))
|
||||
.all_true()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn contract(self, amount: Vector2I) -> RectI {
|
||||
RectI::from_points(self.origin() + amount, self.lower_right() - amount)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_f32(&self) -> RectF {
|
||||
RectF(self.0.to_f32x4())
|
||||
}
|
||||
}
|
|
@ -1,345 +0,0 @@
|
|||
// pathfinder/geometry/src/basic/transform2d.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.
|
||||
|
||||
//! 2D affine transforms.
|
||||
|
||||
use crate::line_segment::LineSegment2F;
|
||||
use crate::rect::RectF;
|
||||
use crate::transform3d::Transform4F;
|
||||
use crate::unit_vector::UnitVector;
|
||||
use crate::vector::{IntoVector2F, Vector2F, vec2f};
|
||||
use pathfinder_simd::default::F32x4;
|
||||
use std::ops::{Mul, MulAssign, Sub};
|
||||
|
||||
/// A 2x2 matrix, optimized with SIMD, in column-major order.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Matrix2x2F(pub F32x4);
|
||||
|
||||
impl Default for Matrix2x2F {
|
||||
#[inline]
|
||||
fn default() -> Matrix2x2F {
|
||||
Self::from_scale(1.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Matrix2x2F {
|
||||
#[inline]
|
||||
pub fn from_scale<S>(scale: S) -> Matrix2x2F where S: IntoVector2F {
|
||||
let scale = scale.into_vector_2f();
|
||||
Matrix2x2F(F32x4::new(scale.x(), 0.0, 0.0, scale.y()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_rotation(theta: f32) -> Matrix2x2F {
|
||||
Matrix2x2F::from_rotation_vector(UnitVector::from_angle(theta))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_rotation_vector(vector: UnitVector) -> Matrix2x2F {
|
||||
Matrix2x2F((vector.0).0.to_f32x4().xyyx() * F32x4::new(1.0, 1.0, -1.0, 1.0))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn row_major(m00: f32, m01: f32, m10: f32, m11: f32) -> Matrix2x2F {
|
||||
Matrix2x2F(F32x4::new(m00, m10, m01, m11))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn entrywise_mul(&self, other: &Matrix2x2F) -> Matrix2x2F {
|
||||
Matrix2x2F(self.0 * other.0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn adjugate(&self) -> Matrix2x2F {
|
||||
Matrix2x2F(self.0.wyzx() * F32x4::new(1.0, -1.0, -1.0, 1.0))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn det(&self) -> f32 {
|
||||
self.0[0] * self.0[3] - self.0[2] * self.0[1]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn inverse(&self) -> Matrix2x2F {
|
||||
Matrix2x2F(F32x4::splat(1.0 / self.det()) * self.adjugate().0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn scale(&self, factor: f32) -> Matrix2x2F {
|
||||
Matrix2x2F(self.0 * F32x4::splat(factor))
|
||||
}
|
||||
|
||||
/// Extracts the scale from this matrix.
|
||||
#[inline]
|
||||
pub fn extract_scale(&self) -> Vector2F {
|
||||
let squared = self.0 * self.0;
|
||||
Vector2F((squared.xy() + squared.zw()).sqrt())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn m11(&self) -> f32 {
|
||||
self.0[0]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn m21(&self) -> f32 {
|
||||
self.0[1]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn m12(&self) -> f32 {
|
||||
self.0[2]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn m22(&self) -> f32 {
|
||||
self.0[3]
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<Matrix2x2F> for Matrix2x2F {
|
||||
type Output = Matrix2x2F;
|
||||
#[inline]
|
||||
fn sub(self, other: Matrix2x2F) -> Matrix2x2F {
|
||||
Matrix2x2F(self.0 - other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Matrix2x2F> for Matrix2x2F {
|
||||
type Output = Matrix2x2F;
|
||||
#[inline]
|
||||
fn mul(self, other: Matrix2x2F) -> Matrix2x2F {
|
||||
Matrix2x2F(self.0.xyxy() * other.0.xxzz() + self.0.zwzw() * other.0.yyww())
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Vector2F> for Matrix2x2F {
|
||||
type Output = Vector2F;
|
||||
#[inline]
|
||||
fn mul(self, vector: Vector2F) -> Vector2F {
|
||||
let halves = self.0 * vector.0.to_f32x4().xxyy();
|
||||
Vector2F(halves.xy() + halves.zw())
|
||||
}
|
||||
}
|
||||
|
||||
/// An affine transform, optimized with SIMD.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Transform2F {
|
||||
pub matrix: Matrix2x2F,
|
||||
pub vector: Vector2F,
|
||||
}
|
||||
|
||||
impl Default for Transform2F {
|
||||
#[inline]
|
||||
fn default() -> Transform2F {
|
||||
Self::from_scale(vec2f(1.0, 1.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl Transform2F {
|
||||
#[inline]
|
||||
pub fn from_scale<S>(scale: S) -> Transform2F where S: IntoVector2F {
|
||||
let scale = scale.into_vector_2f();
|
||||
Transform2F {
|
||||
matrix: Matrix2x2F::from_scale(scale),
|
||||
vector: Vector2F::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_rotation(theta: f32) -> Transform2F {
|
||||
Transform2F {
|
||||
matrix: Matrix2x2F::from_rotation(theta),
|
||||
vector: Vector2F::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_rotation_vector(vector: UnitVector) -> Transform2F {
|
||||
Transform2F {
|
||||
matrix: Matrix2x2F::from_rotation_vector(vector),
|
||||
vector: Vector2F::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_translation(vector: Vector2F) -> Transform2F {
|
||||
Transform2F { matrix: Matrix2x2F::default(), vector }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_scale_rotation_translation<S>(scale: S, theta: f32, translation: Vector2F)
|
||||
-> Transform2F where S: IntoVector2F {
|
||||
let scale = scale.into_vector_2f();
|
||||
let rotation = Transform2F::from_rotation(theta);
|
||||
let translation = Transform2F::from_translation(translation);
|
||||
Transform2F::from_scale(scale) * rotation * translation
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn row_major(m11: f32, m12: f32, m13: f32, m21: f32, m22: f32, m23: f32) -> Transform2F {
|
||||
Transform2F {
|
||||
matrix: Matrix2x2F::row_major(m11, m12, m21, m22),
|
||||
vector: Vector2F::new(m13, m23),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(pcwalton): Optimize better with SIMD.
|
||||
#[inline]
|
||||
pub fn to_3d(&self) -> Transform4F {
|
||||
Transform4F::row_major(
|
||||
self.matrix.0[0],
|
||||
self.matrix.0[1],
|
||||
0.0,
|
||||
self.vector.x(),
|
||||
self.matrix.0[2],
|
||||
self.matrix.0[3],
|
||||
0.0,
|
||||
self.vector.y(),
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_identity(&self) -> bool {
|
||||
*self == Transform2F::default()
|
||||
}
|
||||
|
||||
/// Extracts the scale from this matrix.
|
||||
#[inline]
|
||||
pub fn extract_scale(&self) -> Vector2F {
|
||||
self.matrix.extract_scale()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn m11(&self) -> f32 {
|
||||
self.matrix.m11()
|
||||
}
|
||||
#[inline]
|
||||
pub fn m21(&self) -> f32 {
|
||||
self.matrix.m21()
|
||||
}
|
||||
#[inline]
|
||||
pub fn m12(&self) -> f32 {
|
||||
self.matrix.m12()
|
||||
}
|
||||
#[inline]
|
||||
pub fn m22(&self) -> f32 {
|
||||
self.matrix.m22()
|
||||
}
|
||||
#[inline]
|
||||
pub fn m13(&self) -> f32 {
|
||||
self.vector.x()
|
||||
}
|
||||
#[inline]
|
||||
pub fn m23(&self) -> f32 {
|
||||
self.vector.y()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn translate(&self, vector: Vector2F) -> Transform2F {
|
||||
Transform2F::from_translation(vector) * *self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn rotate(&self, theta: f32) -> Transform2F {
|
||||
Transform2F::from_rotation(theta) * *self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn scale<S>(&self, scale: S) -> Transform2F where S: IntoVector2F {
|
||||
let scale = scale.into_vector_2f();
|
||||
Transform2F::from_scale(scale) * *self
|
||||
}
|
||||
|
||||
/// Returns the translation part of this matrix.
|
||||
///
|
||||
/// This decomposition assumes that scale, rotation, and translation are applied in that order.
|
||||
#[inline]
|
||||
pub fn translation(&self) -> Vector2F {
|
||||
self.vector
|
||||
}
|
||||
|
||||
/// Returns the rotation angle of this matrix.
|
||||
///
|
||||
/// This decomposition assumes that scale, rotation, and translation are applied in that order.
|
||||
#[inline]
|
||||
pub fn rotation(&self) -> f32 {
|
||||
f32::atan2(self.m21(), self.m11())
|
||||
}
|
||||
|
||||
/// Returns the scale factor of this matrix.
|
||||
///
|
||||
/// This decomposition assumes that scale, rotation, and translation are applied in that order.
|
||||
#[inline]
|
||||
pub fn scale_factor(&self) -> f32 {
|
||||
Vector2F(self.matrix.0.zw()).length()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn inverse(&self) -> Transform2F {
|
||||
let matrix_inv = self.matrix.inverse();
|
||||
let vector_inv = -(matrix_inv * self.vector);
|
||||
Transform2F { matrix: matrix_inv, vector: vector_inv }
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Transform2F> for Transform2F {
|
||||
type Output = Transform2F;
|
||||
#[inline]
|
||||
fn mul(self, other: Transform2F) -> Transform2F {
|
||||
Transform2F {
|
||||
matrix: self.matrix * other.matrix,
|
||||
vector: self * other.vector,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Vector2F> for Transform2F {
|
||||
type Output = Vector2F;
|
||||
#[inline]
|
||||
fn mul(self, vector: Vector2F) -> Vector2F {
|
||||
self.matrix * vector + self.vector
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<LineSegment2F> for Transform2F {
|
||||
type Output = LineSegment2F;
|
||||
#[inline]
|
||||
fn mul(self, line_segment: LineSegment2F) -> LineSegment2F {
|
||||
LineSegment2F::new(self * line_segment.from(), self * line_segment.to())
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<RectF> for Transform2F {
|
||||
type Output = RectF;
|
||||
#[inline]
|
||||
fn mul(self, rect: RectF) -> RectF {
|
||||
let (upper_left, upper_right) = (self * rect.origin(), self * rect.upper_right());
|
||||
let (lower_left, lower_right) = (self * rect.lower_left(), self * 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);
|
||||
RectF::from_points(min_point, max_point)
|
||||
}
|
||||
}
|
||||
|
||||
impl MulAssign for Transform2F {
|
||||
#[inline]
|
||||
fn mul_assign(&mut self, other: Transform2F) {
|
||||
*self = *self * other
|
||||
}
|
||||
}
|
|
@ -1,504 +0,0 @@
|
|||
// pathfinder/geometry/src/basic/transform3d.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.
|
||||
|
||||
//! 3D transforms that can be applied to paths.
|
||||
|
||||
use crate::rect::RectF;
|
||||
use crate::transform2d::Matrix2x2F;
|
||||
use crate::vector::{Vector2F, Vector2I, Vector3F, Vector4F};
|
||||
use pathfinder_simd::default::F32x4;
|
||||
use std::ops::{Add, Mul, MulAssign, Neg};
|
||||
|
||||
/// An transform, optimized with SIMD.
|
||||
///
|
||||
/// In column-major order.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub struct Transform4F {
|
||||
pub c0: F32x4,
|
||||
pub c1: F32x4,
|
||||
pub c2: F32x4,
|
||||
pub c3: F32x4,
|
||||
}
|
||||
|
||||
impl Default for Transform4F {
|
||||
#[inline]
|
||||
fn default() -> Transform4F {
|
||||
Transform4F {
|
||||
c0: F32x4::new(1.0, 0.0, 0.0, 0.0),
|
||||
c1: F32x4::new(0.0, 1.0, 0.0, 0.0),
|
||||
c2: F32x4::new(0.0, 0.0, 1.0, 0.0),
|
||||
c3: F32x4::new(0.0, 0.0, 0.0, 1.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Transform4F {
|
||||
#[inline]
|
||||
pub fn row_major(
|
||||
m00: f32,
|
||||
m01: f32,
|
||||
m02: f32,
|
||||
m03: f32,
|
||||
m10: f32,
|
||||
m11: f32,
|
||||
m12: f32,
|
||||
m13: f32,
|
||||
m20: f32,
|
||||
m21: f32,
|
||||
m22: f32,
|
||||
m23: f32,
|
||||
m30: f32,
|
||||
m31: f32,
|
||||
m32: f32,
|
||||
m33: f32,
|
||||
) -> Transform4F {
|
||||
Transform4F {
|
||||
c0: F32x4::new(m00, m10, m20, m30),
|
||||
c1: F32x4::new(m01, m11, m21, m31),
|
||||
c2: F32x4::new(m02, m12, m22, m32),
|
||||
c3: F32x4::new(m03, m13, m23, m33),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_scale(scale: Vector4F) -> Transform4F {
|
||||
Transform4F {
|
||||
c0: F32x4::new(scale.x(), 0.0, 0.0, 0.0),
|
||||
c1: F32x4::new(0.0, scale.y(), 0.0, 0.0),
|
||||
c2: F32x4::new(0.0, 0.0, scale.z(), 0.0),
|
||||
c3: F32x4::new(0.0, 0.0, 0.0, 1.0),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_uniform_scale(factor: f32) -> Transform4F {
|
||||
Transform4F::from_scale(Vector4F::splat(factor))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_translation(mut translation: Vector4F) -> Transform4F {
|
||||
translation.set_w(1.0);
|
||||
Transform4F { c3: translation.0, ..Transform4F::default() }
|
||||
}
|
||||
|
||||
// TODO(pcwalton): Optimize.
|
||||
pub fn from_rotation(yaw: f32, pitch: f32, roll: f32) -> Transform4F {
|
||||
let (cos_b, sin_b) = (yaw.cos(), yaw.sin());
|
||||
let (cos_c, sin_c) = (pitch.cos(), pitch.sin());
|
||||
let (cos_a, sin_a) = (roll.cos(), roll.sin());
|
||||
let m00 = cos_a * cos_b;
|
||||
let m01 = cos_a * sin_b * sin_c - sin_a * cos_c;
|
||||
let m02 = cos_a * sin_b * cos_c + sin_a * sin_c;
|
||||
let m10 = sin_a * cos_b;
|
||||
let m11 = sin_a * sin_b * sin_c + cos_a * cos_c;
|
||||
let m12 = sin_a * sin_b * cos_c - cos_a * sin_c;
|
||||
let m20 = -sin_b;
|
||||
let m21 = cos_b * sin_c;
|
||||
let m22 = cos_b * cos_c;
|
||||
Transform4F::row_major(
|
||||
m00, m01, m02, 0.0, m10, m11, m12, 0.0, m20, m21, m22, 0.0, 0.0, 0.0, 0.0, 1.0,
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates a rotation matrix from the given quaternion.
|
||||
///
|
||||
/// The quaternion is expected to be packed into a SIMD type (x, y, z, w) corresponding to
|
||||
/// x + yi + zj + wk.
|
||||
pub fn from_rotation_quaternion(q: F32x4) -> Transform4F {
|
||||
// TODO(pcwalton): Optimize better with more shuffles.
|
||||
let (mut sq, mut w, mut xy_xz_yz) = (q * q, q.wwww() * q, q.xxyy() * q.yzzy());
|
||||
sq += sq;
|
||||
w += w;
|
||||
xy_xz_yz += xy_xz_yz;
|
||||
let diag = F32x4::splat(1.0) - (sq.yxxy() + sq.zzyy());
|
||||
let (wx2, wy2, wz2) = (w.x(), w.y(), w.z());
|
||||
let (xy2, xz2, yz2) = (xy_xz_yz.x(), xy_xz_yz.y(), xy_xz_yz.z());
|
||||
Transform4F::row_major(
|
||||
diag.x(),
|
||||
xy2 - wz2,
|
||||
xz2 + wy2,
|
||||
0.0,
|
||||
xy2 + wz2,
|
||||
diag.y(),
|
||||
yz2 - wx2,
|
||||
0.0,
|
||||
xz2 - wy2,
|
||||
yz2 + wx2,
|
||||
diag.z(),
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0,
|
||||
)
|
||||
}
|
||||
|
||||
/// Just like `glOrtho()`.
|
||||
#[inline]
|
||||
pub fn from_ortho(
|
||||
left: f32,
|
||||
right: f32,
|
||||
bottom: f32,
|
||||
top: f32,
|
||||
near_val: f32,
|
||||
far_val: f32,
|
||||
) -> Transform4F {
|
||||
let x_inv = 1.0 / (right - left);
|
||||
let y_inv = 1.0 / (top - bottom);
|
||||
let z_inv = 1.0 / (far_val - near_val);
|
||||
let tx = -(right + left) * x_inv;
|
||||
let ty = -(top + bottom) * y_inv;
|
||||
let tz = -(far_val + near_val) * z_inv;
|
||||
Transform4F::row_major(
|
||||
2.0 * x_inv,
|
||||
0.0,
|
||||
0.0,
|
||||
tx,
|
||||
0.0,
|
||||
2.0 * y_inv,
|
||||
0.0,
|
||||
ty,
|
||||
0.0,
|
||||
0.0,
|
||||
-2.0 * z_inv,
|
||||
tz,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0,
|
||||
)
|
||||
}
|
||||
|
||||
/// Linearly interpolate between transforms
|
||||
pub fn lerp(&self, weight: f32, other: &Transform4F) -> Transform4F {
|
||||
let c0 = self.c0 * F32x4::splat(weight) + other.c0 * F32x4::splat(1.0 - weight);
|
||||
let c1 = self.c1 * F32x4::splat(weight) + other.c1 * F32x4::splat(1.0 - weight);
|
||||
let c2 = self.c2 * F32x4::splat(weight) + other.c2 * F32x4::splat(1.0 - weight);
|
||||
let c3 = self.c3 * F32x4::splat(weight) + other.c3 * F32x4::splat(1.0 - weight);
|
||||
Transform4F { c0, c1, c2, c3 }
|
||||
}
|
||||
|
||||
/// Just like `gluPerspective()`.
|
||||
#[inline]
|
||||
pub fn from_perspective(fov_y: f32, aspect: f32, z_near: f32, z_far: f32) -> Transform4F {
|
||||
let f = 1.0 / (fov_y * 0.5).tan();
|
||||
let z_denom = 1.0 / (z_near - z_far);
|
||||
let m00 = f / aspect;
|
||||
let m11 = f;
|
||||
let m22 = (z_far + z_near) * z_denom;
|
||||
let m23 = 2.0 * z_far * z_near * z_denom;
|
||||
let m32 = -1.0;
|
||||
Transform4F::row_major(
|
||||
m00, 0.0, 0.0, 0.0, 0.0, m11, 0.0, 0.0, 0.0, 0.0, m22, m23, 0.0, 0.0, m32, 0.0,
|
||||
)
|
||||
}
|
||||
|
||||
/// Just like `gluLookAt()`.
|
||||
#[inline]
|
||||
pub fn looking_at(eye: Vector3F, center: Vector3F, mut up: Vector3F) -> Transform4F {
|
||||
let f = (center - eye).normalize();
|
||||
up = up.normalize();
|
||||
let s = f.cross(up);
|
||||
let u = s.normalize().cross(f);
|
||||
let minus_f = -f;
|
||||
|
||||
// TODO(pcwalton): Use SIMD. This needs a matrix transpose:
|
||||
// https://fgiesen.wordpress.com/2013/07/09/simd-transposes-1/
|
||||
let transform = Transform4F::row_major(s.x(), s.y(), s.z(), 0.0,
|
||||
u.x(), u.y(), u.z(), 0.0,
|
||||
minus_f.x(), minus_f.y(), minus_f.z(), 0.0,
|
||||
0.0, 0.0, 0.0, 1.0) *
|
||||
Transform4F::from_translation((-eye).to_4d());
|
||||
transform
|
||||
}
|
||||
|
||||
// +- -+
|
||||
// | A B |
|
||||
// | C D |
|
||||
// +- -+
|
||||
#[inline]
|
||||
pub fn from_submatrices(
|
||||
a: Matrix2x2F,
|
||||
b: Matrix2x2F,
|
||||
c: Matrix2x2F,
|
||||
d: Matrix2x2F,
|
||||
) -> Transform4F {
|
||||
Transform4F {
|
||||
c0: a.0.concat_xy_xy(c.0),
|
||||
c1: a.0.concat_zw_zw(c.0),
|
||||
c2: b.0.concat_xy_xy(d.0),
|
||||
c3: b.0.concat_zw_zw(d.0),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn rotate(&self, yaw: f32, pitch: f32, roll: f32) -> Transform4F {
|
||||
Transform4F::from_rotation(yaw, pitch, roll) * *self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn scale(&self, scale: Vector4F) -> Transform4F {
|
||||
Transform4F::from_scale(scale) * *self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn uniform_scale(&self, scale: f32) -> Transform4F {
|
||||
Transform4F::from_uniform_scale(scale) * *self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn translate(&self, translation: Vector4F) -> Transform4F {
|
||||
Transform4F::from_translation(translation) * *self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn upper_left(&self) -> Matrix2x2F {
|
||||
Matrix2x2F(self.c0.concat_xy_xy(self.c1))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn upper_right(&self) -> Matrix2x2F {
|
||||
Matrix2x2F(self.c2.concat_xy_xy(self.c3))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn lower_left(&self) -> Matrix2x2F {
|
||||
Matrix2x2F(self.c0.concat_zw_zw(self.c1))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn lower_right(&self) -> Matrix2x2F {
|
||||
Matrix2x2F(self.c2.concat_zw_zw(self.c3))
|
||||
}
|
||||
|
||||
// https://en.wikipedia.org/wiki/Invertible_matrix#Blockwise_inversion
|
||||
//
|
||||
// If A is the upper left submatrix of this matrix, this method assumes that A and the Schur
|
||||
// complement of A are invertible.
|
||||
pub fn inverse(&self) -> Transform4F {
|
||||
// Extract submatrices.
|
||||
let (a, b) = (self.upper_left(), self.upper_right());
|
||||
let (c, d) = (self.lower_left(), self.lower_right());
|
||||
|
||||
// Compute temporary matrices.
|
||||
let a_inv = a.inverse();
|
||||
let x = c * a_inv;
|
||||
let y = (d - x * b).inverse();
|
||||
let z = a_inv * b;
|
||||
|
||||
// Compute new submatrices.
|
||||
let (a_new, b_new) = (a_inv + z * y * x, -z * y);
|
||||
let (c_new, d_new) = (-y * x, y);
|
||||
|
||||
// Construct inverse.
|
||||
Transform4F::from_submatrices(a_new, b_new, c_new, d_new)
|
||||
}
|
||||
|
||||
pub fn approx_eq(&self, other: &Transform4F, epsilon: f32) -> bool {
|
||||
self.c0.approx_eq(other.c0, epsilon)
|
||||
&& self.c1.approx_eq(other.c1, epsilon)
|
||||
&& self.c2.approx_eq(other.c2, epsilon)
|
||||
&& self.c3.approx_eq(other.c3, epsilon)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_ptr(&self) -> *const f32 {
|
||||
(&self.c0) as *const F32x4 as *const f32
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_columns(&self) -> [F32x4; 4] {
|
||||
[self.c0, self.c1, self.c2, self.c3]
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Transform4F> for Transform4F {
|
||||
type Output = Transform4F;
|
||||
|
||||
// https://stackoverflow.com/a/18508113
|
||||
#[inline]
|
||||
fn mul(self, other: Transform4F) -> Transform4F {
|
||||
return Transform4F {
|
||||
c0: mul_col(&self, other.c0),
|
||||
c1: mul_col(&self, other.c1),
|
||||
c2: mul_col(&self, other.c2),
|
||||
c3: mul_col(&self, other.c3),
|
||||
};
|
||||
|
||||
#[inline]
|
||||
fn mul_col(a: &Transform4F, b_col: F32x4) -> F32x4 {
|
||||
a.c0 * b_col.xxxx() + a.c1 * b_col.yyyy() + a.c2 * b_col.zzzz() + a.c3 * b_col.wwww()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Vector4F> for Transform4F {
|
||||
type Output = Vector4F;
|
||||
|
||||
#[inline]
|
||||
fn mul(self, vector: Vector4F) -> Vector4F {
|
||||
let term0 = self.c0 * F32x4::splat(vector.x());
|
||||
let term1 = self.c1 * F32x4::splat(vector.y());
|
||||
let term2 = self.c2 * F32x4::splat(vector.z());
|
||||
let term3 = self.c3 * F32x4::splat(vector.w());
|
||||
Vector4F(term0 + term1 + term2 + term3)
|
||||
}
|
||||
}
|
||||
|
||||
impl MulAssign<Transform4F> for Transform4F {
|
||||
fn mul_assign(&mut self, other: Transform4F) {
|
||||
*self = *self * other
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Matrix2x2F> for Matrix2x2F {
|
||||
type Output = Matrix2x2F;
|
||||
#[inline]
|
||||
fn add(self, other: Matrix2x2F) -> Matrix2x2F {
|
||||
Matrix2x2F(self.0 + other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for Matrix2x2F {
|
||||
type Output = Matrix2x2F;
|
||||
#[inline]
|
||||
fn neg(self) -> Matrix2x2F {
|
||||
Matrix2x2F(-self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Perspective {
|
||||
pub transform: Transform4F,
|
||||
pub window_size: Vector2I,
|
||||
}
|
||||
|
||||
impl Perspective {
|
||||
#[inline]
|
||||
pub fn new(transform: &Transform4F, window_size: Vector2I) -> Perspective {
|
||||
Perspective {
|
||||
transform: *transform,
|
||||
window_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Transform4F> for Perspective {
|
||||
type Output = Perspective;
|
||||
#[inline]
|
||||
fn mul(self, other: Transform4F) -> Perspective {
|
||||
Perspective {
|
||||
transform: self.transform * other,
|
||||
window_size: self.window_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Vector2F> for Perspective {
|
||||
type Output = Vector2F;
|
||||
#[inline]
|
||||
fn mul(self, vector: Vector2F) -> Vector2F {
|
||||
let point = (self.transform * vector.to_4d()).to_2d() * Vector2F::new(1.0, -1.0);
|
||||
(point + 1.0) * self.window_size.to_f32() * 0.5
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<RectF> for Perspective {
|
||||
type Output = RectF;
|
||||
#[inline]
|
||||
fn mul(self, rect: RectF) -> RectF {
|
||||
let (upper_left, upper_right) = (self * rect.origin(), self * rect.upper_right());
|
||||
let (lower_left, lower_right) = (self * rect.lower_left(), self * 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);
|
||||
RectF::from_points(min_point, max_point)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::vector::Vector4F;
|
||||
use crate::transform3d::Transform4F;
|
||||
|
||||
#[test]
|
||||
fn test_post_mul() {
|
||||
let a = Transform4F::row_major(
|
||||
3.0, 1.0, 4.0, 5.0, 9.0, 2.0, 6.0, 5.0, 3.0, 5.0, 8.0, 9.0, 7.0, 9.0, 3.0, 2.0,
|
||||
);
|
||||
let b = Transform4F::row_major(
|
||||
3.0, 8.0, 4.0, 6.0, 2.0, 6.0, 4.0, 3.0, 3.0, 8.0, 3.0, 2.0, 7.0, 9.0, 5.0, 0.0,
|
||||
);
|
||||
let c = Transform4F::row_major(
|
||||
58.0, 107.0, 53.0, 29.0, 84.0, 177.0, 87.0, 72.0, 106.0, 199.0, 101.0, 49.0, 62.0,
|
||||
152.0, 83.0, 75.0,
|
||||
);
|
||||
assert_eq!(a * b, c);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pre_mul() {
|
||||
let a = Transform4F::row_major(
|
||||
3.0, 1.0, 4.0, 5.0, 9.0, 2.0, 6.0, 5.0, 3.0, 5.0, 8.0, 9.0, 7.0, 9.0, 3.0, 2.0,
|
||||
);
|
||||
let b = Transform4F::row_major(
|
||||
3.0, 8.0, 4.0, 6.0, 2.0, 6.0, 4.0, 3.0, 3.0, 8.0, 3.0, 2.0, 7.0, 9.0, 5.0, 0.0,
|
||||
);
|
||||
let c = Transform4F::row_major(
|
||||
135.0, 93.0, 110.0, 103.0, 93.0, 61.0, 85.0, 82.0, 104.0, 52.0, 90.0, 86.0, 117.0,
|
||||
50.0, 122.0, 125.0,
|
||||
);
|
||||
assert_eq!(b * a, c);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transform_point() {
|
||||
let a = Transform4F::row_major(
|
||||
3.0, 1.0, 4.0, 5.0, 9.0, 2.0, 6.0, 5.0, 3.0, 5.0, 8.0, 9.0, 7.0, 9.0, 3.0, 2.0,
|
||||
);
|
||||
let p = Vector4F::new(3.0, 8.0, 4.0, 6.0);
|
||||
let q = Vector4F::new(63.0, 97.0, 135.0, 117.0);
|
||||
assert_eq!(a * p, q);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inverse() {
|
||||
// Random matrix.
|
||||
let m = Transform4F::row_major(
|
||||
0.86277982, 0.15986552, 0.90739898, 0.60066808, 0.17386167, 0.016353, 0.8535783,
|
||||
0.12969608, 0.0946466, 0.43248631, 0.63480505, 0.08154603, 0.50305436, 0.48359687,
|
||||
0.51057162, 0.24812012,
|
||||
);
|
||||
let p0 = Vector4F::new(0.95536648, 0.80633691, 0.16357357, 0.5477598);
|
||||
let p1 = m * p0;
|
||||
let m_inv = m.inverse();
|
||||
let m_inv_exp = Transform4F::row_major(
|
||||
-2.47290136,
|
||||
3.48865688,
|
||||
-6.12298336,
|
||||
6.17536696,
|
||||
0.00124033357,
|
||||
-1.72561993,
|
||||
2.16876606,
|
||||
0.186227748,
|
||||
-0.375021729,
|
||||
1.53883017,
|
||||
-0.0558194403,
|
||||
0.121857058,
|
||||
5.78300323,
|
||||
-6.87635769,
|
||||
8.30196620,
|
||||
-9.10374060,
|
||||
);
|
||||
assert!(m_inv.approx_eq(&m_inv_exp, 0.0001));
|
||||
let p2 = m_inv * p1;
|
||||
assert!(p0.approx_eq(p2, 0.0001));
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
// pathfinder/geometry/src/unit_vector.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.
|
||||
|
||||
//! A utility module that allows unit vectors to be treated like angles.
|
||||
|
||||
use crate::vector::Vector2F;
|
||||
use pathfinder_simd::default::F32x2;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct UnitVector(pub Vector2F);
|
||||
|
||||
impl UnitVector {
|
||||
#[inline]
|
||||
pub fn from_angle(theta: f32) -> UnitVector {
|
||||
UnitVector(Vector2F::new(theta.cos(), theta.sin()))
|
||||
}
|
||||
|
||||
/// Angle addition formula.
|
||||
#[inline]
|
||||
pub fn rotate_by(&self, other: UnitVector) -> UnitVector {
|
||||
let products = (self.0).0.to_f32x4().xyyx() * (other.0).0.to_f32x4().xyxy();
|
||||
UnitVector(Vector2F::new(products[0] - products[1], products[2] + products[3]))
|
||||
}
|
||||
|
||||
/// Angle subtraction formula.
|
||||
#[inline]
|
||||
pub fn rev_rotate_by(&self, other: UnitVector) -> UnitVector {
|
||||
let products = (self.0).0.to_f32x4().xyyx() * (other.0).0.to_f32x4().xyxy();
|
||||
UnitVector(Vector2F::new(products[0] + products[1], products[2] - products[3]))
|
||||
}
|
||||
|
||||
/// Half angle formula.
|
||||
#[inline]
|
||||
pub fn halve_angle(&self) -> UnitVector {
|
||||
let x = self.0.x();
|
||||
let term = F32x2::new(x, -x);
|
||||
UnitVector(Vector2F((F32x2::splat(0.5) * (F32x2::splat(1.0) + term)).max(F32x2::default())
|
||||
.sqrt()))
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
// pathfinder/geometry/src/util.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.
|
||||
|
||||
//! Various utilities.
|
||||
|
||||
use std::f32;
|
||||
|
||||
pub const EPSILON: f32 = 0.001;
|
||||
|
||||
/// Approximate equality.
|
||||
#[inline]
|
||||
pub fn approx_eq(a: f32, b: f32) -> bool {
|
||||
f32::abs(a - b) <= EPSILON
|
||||
}
|
||||
|
||||
/// Linear interpolation.
|
||||
#[inline]
|
||||
pub fn lerp(a: f32, b: f32, t: f32) -> f32 {
|
||||
a + (b - a) * t
|
||||
}
|
||||
|
||||
/// Clamping.
|
||||
#[inline]
|
||||
pub fn clamp(x: f32, min_val: f32, max_val: f32) -> f32 {
|
||||
f32::min(max_val, f32::max(min_val, x))
|
||||
}
|
||||
|
||||
/// Divides `a` by `b`, rounding up.
|
||||
#[inline]
|
||||
pub fn alignup_i32(a: i32, b: i32) -> i32 {
|
||||
(a + b - 1) / b
|
||||
}
|
|
@ -1,687 +0,0 @@
|
|||
// pathfinder/geometry/src/basic/point.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.
|
||||
|
||||
//! A SIMD-optimized point type.
|
||||
|
||||
use pathfinder_simd::default::{F32x2, F32x4, I32x2};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Sub, SubAssign};
|
||||
|
||||
/// 2D points with 32-bit floating point coordinates.
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct Vector2F(pub F32x2);
|
||||
|
||||
impl Vector2F {
|
||||
#[inline]
|
||||
pub fn new(x: f32, y: f32) -> Vector2F {
|
||||
Vector2F(F32x2::new(x, y))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn splat(value: f32) -> Vector2F {
|
||||
Vector2F(F32x2::splat(value))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn zero() -> Vector2F {
|
||||
Vector2F::default()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_3d(self) -> Vector3F {
|
||||
Vector3F(self.0.to_f32x4().concat_xy_zw(F32x4::new(0.0, 0.0, 0.0, 0.0)))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_4d(self) -> Vector4F {
|
||||
Vector4F(self.0.to_f32x4().concat_xy_zw(F32x4::new(0.0, 0.0, 0.0, 1.0)))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn x(self) -> f32 {
|
||||
self.0[0]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn y(self) -> f32 {
|
||||
self.0[1]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_x(&mut self, x: f32) {
|
||||
self.0[0] = x;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_y(&mut self, y: f32) {
|
||||
self.0[1] = y;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn min(self, other: Vector2F) -> Vector2F {
|
||||
Vector2F(self.0.min(other.0))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn max(self, other: Vector2F) -> Vector2F {
|
||||
Vector2F(self.0.max(other.0))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn clamp(self, min_val: Vector2F, max_val: Vector2F) -> Vector2F {
|
||||
self.max(min_val).min(max_val)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn det(self, other: Vector2F) -> f32 {
|
||||
self.x() * other.y() - self.y() * other.x()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn dot(self, other: Vector2F) -> f32 {
|
||||
let xy = self.0 * other.0;
|
||||
xy.x() + xy.y()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn floor(self) -> Vector2F {
|
||||
Vector2F(self.0.floor())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn ceil(self) -> Vector2F {
|
||||
Vector2F(self.0.ceil())
|
||||
}
|
||||
|
||||
/// Rounds both coordinates to the nearest integer.
|
||||
#[inline]
|
||||
pub fn round(self) -> Vector2F {
|
||||
Vector2F(self.0.to_i32x2().to_f32x2())
|
||||
}
|
||||
|
||||
/// Treats this point as a vector and calculates its squared length.
|
||||
#[inline]
|
||||
pub fn square_length(self) -> f32 {
|
||||
let squared = self.0 * self.0;
|
||||
squared[0] + squared[1]
|
||||
}
|
||||
|
||||
/// Treats this point as a vector and calculates its length.
|
||||
#[inline]
|
||||
pub fn length(self) -> f32 {
|
||||
f32::sqrt(self.square_length())
|
||||
}
|
||||
|
||||
/// Treats this point as a vector and normalizes it.
|
||||
#[inline]
|
||||
pub fn normalize(self) -> Vector2F {
|
||||
self * (1.0 / self.length())
|
||||
}
|
||||
|
||||
/// Swaps y and x.
|
||||
#[inline]
|
||||
pub fn yx(self) -> Vector2F {
|
||||
Vector2F(self.0.yx())
|
||||
}
|
||||
|
||||
/// Returns the coefficient when the given vector `a` is projected onto this one.
|
||||
///
|
||||
/// That is, if this vector is `v` and this function returns `c`, then `proj_v a = cv`. In
|
||||
/// other words, this function computes `(a⋅v) / (v⋅v)`.
|
||||
#[inline]
|
||||
pub fn projection_coefficient(self, a: Vector2F) -> f32 {
|
||||
a.dot(self) / self.square_length()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_zero(self) -> bool {
|
||||
self == Vector2F::zero()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn lerp(self, other: Vector2F, t: f32) -> Vector2F {
|
||||
self + (other - self) * t
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_i32(self) -> Vector2I {
|
||||
Vector2I(self.0.to_i32x2())
|
||||
}
|
||||
}
|
||||
|
||||
/// A convenience alias for `Vector2F::new()`.
|
||||
#[inline]
|
||||
pub fn vec2f(x: f32, y: f32) -> Vector2F {
|
||||
Vector2F::new(x, y)
|
||||
}
|
||||
|
||||
impl PartialEq for Vector2F {
|
||||
#[inline]
|
||||
fn eq(&self, other: &Vector2F) -> bool {
|
||||
self.0.packed_eq(other.0).all_true()
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Vector2F> for Vector2F {
|
||||
type Output = Vector2F;
|
||||
#[inline]
|
||||
fn add(self, other: Vector2F) -> Vector2F {
|
||||
Vector2F(self.0 + other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<f32> for Vector2F {
|
||||
type Output = Vector2F;
|
||||
#[inline]
|
||||
fn add(self, other: f32) -> Vector2F {
|
||||
self + Vector2F::splat(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign<Vector2F> for Vector2F {
|
||||
#[inline]
|
||||
fn add_assign(&mut self, other: Vector2F) {
|
||||
*self = *self + other
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<Vector2F> for Vector2F {
|
||||
type Output = Vector2F;
|
||||
#[inline]
|
||||
fn sub(self, other: Vector2F) -> Vector2F {
|
||||
Vector2F(self.0 - other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<f32> for Vector2F {
|
||||
type Output = Vector2F;
|
||||
#[inline]
|
||||
fn sub(self, other: f32) -> Vector2F {
|
||||
self - Vector2F::splat(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign<Vector2F> for Vector2F {
|
||||
#[inline]
|
||||
fn sub_assign(&mut self, other: Vector2F) {
|
||||
*self = *self - other
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Vector2F> for Vector2F {
|
||||
type Output = Vector2F;
|
||||
#[inline]
|
||||
fn mul(self, other: Vector2F) -> Vector2F {
|
||||
Vector2F(self.0 * other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<f32> for Vector2F {
|
||||
type Output = Vector2F;
|
||||
#[inline]
|
||||
fn mul(self, other: f32) -> Vector2F {
|
||||
self * Vector2F::splat(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl MulAssign<Vector2F> for Vector2F {
|
||||
#[inline]
|
||||
fn mul_assign(&mut self, other: Vector2F) {
|
||||
*self = *self * other
|
||||
}
|
||||
}
|
||||
|
||||
impl MulAssign<f32> for Vector2F {
|
||||
#[inline]
|
||||
fn mul_assign(&mut self, other: f32) {
|
||||
*self = *self * other
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<Vector2F> for Vector2F {
|
||||
type Output = Vector2F;
|
||||
#[inline]
|
||||
fn div(self, other: Vector2F) -> Vector2F {
|
||||
Vector2F(self.0 / other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<f32> for Vector2F {
|
||||
type Output = Vector2F;
|
||||
#[inline]
|
||||
fn div(self, other: f32) -> Vector2F {
|
||||
self / Vector2F::splat(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for Vector2F {
|
||||
type Output = Vector2F;
|
||||
#[inline]
|
||||
fn neg(self) -> Vector2F {
|
||||
Vector2F(-self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Either a scalar or a `Vector2F`.
|
||||
///
|
||||
/// Scalars will be automatically splatted (i.e. `x` becomes `vec2f(x, x)`).
|
||||
///
|
||||
/// Be judicious with the use of this trait. Only use it if it will aid readability without the
|
||||
/// potential to introduce bugs.
|
||||
pub trait IntoVector2F {
|
||||
fn into_vector_2f(self) -> Vector2F;
|
||||
}
|
||||
|
||||
impl IntoVector2F for Vector2F {
|
||||
#[inline]
|
||||
fn into_vector_2f(self) -> Vector2F {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoVector2F for f32 {
|
||||
#[inline]
|
||||
fn into_vector_2f(self) -> Vector2F {
|
||||
Vector2F::splat(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// 2D points with 32-bit signed integer coordinates.
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct Vector2I(pub I32x2);
|
||||
|
||||
impl Vector2I {
|
||||
#[inline]
|
||||
pub fn new(x: i32, y: i32) -> Vector2I {
|
||||
Vector2I(I32x2::new(x, y))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn splat(value: i32) -> Vector2I {
|
||||
Vector2I(I32x2::splat(value))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn zero() -> Vector2I {
|
||||
Vector2I::default()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn x(self) -> i32 {
|
||||
self.0[0]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn y(self) -> i32 {
|
||||
self.0[1]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_x(&mut self, x: i32) {
|
||||
self.0[0] = x;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_y(&mut self, y: i32) {
|
||||
self.0[1] = y;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn min(self, other: Vector2I) -> Vector2I {
|
||||
Vector2I(self.0.min(other.0))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn max(self, other: Vector2I) -> Vector2I {
|
||||
Vector2I(self.0.max(other.0))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_f32(self) -> Vector2F {
|
||||
Vector2F(self.0.to_f32x2())
|
||||
}
|
||||
}
|
||||
|
||||
/// A convenience alias for `Vector2I::new()`.
|
||||
#[inline]
|
||||
pub fn vec2i(x: i32, y: i32) -> Vector2I {
|
||||
Vector2I::new(x, y)
|
||||
}
|
||||
|
||||
impl Add<Vector2I> for Vector2I {
|
||||
type Output = Vector2I;
|
||||
#[inline]
|
||||
fn add(self, other: Vector2I) -> Vector2I {
|
||||
Vector2I(self.0 + other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<i32> for Vector2I {
|
||||
type Output = Vector2I;
|
||||
#[inline]
|
||||
fn add(self, other: i32) -> Vector2I {
|
||||
self + Vector2I::splat(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign<Vector2I> for Vector2I {
|
||||
#[inline]
|
||||
fn add_assign(&mut self, other: Vector2I) {
|
||||
self.0 += other.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for Vector2I {
|
||||
type Output = Vector2I;
|
||||
#[inline]
|
||||
fn neg(self) -> Vector2I {
|
||||
Vector2I(-self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<Vector2I> for Vector2I {
|
||||
type Output = Vector2I;
|
||||
#[inline]
|
||||
fn sub(self, other: Vector2I) -> Vector2I {
|
||||
Vector2I(self.0 - other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<i32> for Vector2I {
|
||||
type Output = Vector2I;
|
||||
#[inline]
|
||||
fn sub(self, other: i32) -> Vector2I {
|
||||
self - Vector2I::splat(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Vector2I> for Vector2I {
|
||||
type Output = Vector2I;
|
||||
#[inline]
|
||||
fn mul(self, other: Vector2I) -> Vector2I {
|
||||
Vector2I(self.0 * other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<i32> for Vector2I {
|
||||
type Output = Vector2I;
|
||||
#[inline]
|
||||
fn mul(self, other: i32) -> Vector2I {
|
||||
self * Vector2I::splat(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Vector2I {
|
||||
#[inline]
|
||||
fn eq(&self, other: &Vector2I) -> bool {
|
||||
self.0.packed_eq(other.0).all_true()
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Vector2I {}
|
||||
|
||||
impl Hash for Vector2I {
|
||||
#[inline]
|
||||
fn hash<H>(&self, state: &mut H) where H: Hasher {
|
||||
self.x().hash(state);
|
||||
self.y().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
/// 3D points.
|
||||
///
|
||||
/// The w value in the SIMD vector is always 0.0.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct Vector3F(pub F32x4);
|
||||
|
||||
impl Vector3F {
|
||||
#[inline]
|
||||
pub fn new(x: f32, y: f32, z: f32) -> Vector3F {
|
||||
Vector3F(F32x4::new(x, y, z, 0.0))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn splat(x: f32) -> Vector3F {
|
||||
let mut vector = F32x4::splat(x);
|
||||
vector.set_w(0.0);
|
||||
Vector3F(vector)
|
||||
}
|
||||
|
||||
/// Truncates this vector to 2D.
|
||||
#[inline]
|
||||
pub fn to_2d(self) -> Vector2F {
|
||||
Vector2F(self.0.xy())
|
||||
}
|
||||
|
||||
/// Converts this vector to an equivalent 3D homogeneous one with a w component of 1.0.
|
||||
#[inline]
|
||||
pub fn to_4d(self) -> Vector4F {
|
||||
let mut vector = self.0;
|
||||
vector.set_w(1.0);
|
||||
Vector4F(vector)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn cross(self, other: Vector3F) -> Vector3F {
|
||||
Vector3F(self.0.yzxw() * other.0.zxyw() - self.0.zxyw() * other.0.yzxw())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn square_length(self) -> f32 {
|
||||
let squared = self.0 * self.0;
|
||||
squared[0] + squared[1] + squared[2]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn length(self) -> f32 {
|
||||
f32::sqrt(self.square_length())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn normalize(self) -> Vector3F {
|
||||
Vector3F(self.0 * F32x4::splat(1.0 / self.length()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn x(self) -> f32 {
|
||||
self.0[0]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn y(self) -> f32 {
|
||||
self.0[1]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn z(self) -> f32 {
|
||||
self.0[2]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn scale(self, factor: f32) -> Vector3F {
|
||||
Vector3F(self.0 * F32x4::splat(factor))
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Vector3F> for Vector3F {
|
||||
type Output = Vector3F;
|
||||
#[inline]
|
||||
fn add(self, other: Vector3F) -> Vector3F {
|
||||
Vector3F(self.0 + other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign for Vector3F {
|
||||
#[inline]
|
||||
fn add_assign(&mut self, other: Vector3F) {
|
||||
self.0 += other.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for Vector3F {
|
||||
type Output = Vector3F;
|
||||
#[inline]
|
||||
fn neg(self) -> Vector3F {
|
||||
Vector3F(self.0 * F32x4::new(-1.0, -1.0, -1.0, 0.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<Vector3F> for Vector3F {
|
||||
type Output = Vector3F;
|
||||
#[inline]
|
||||
fn sub(self, other: Vector3F) -> Vector3F {
|
||||
Vector3F(self.0 - other.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// 3D homogeneous points.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Vector4F(pub F32x4);
|
||||
|
||||
impl Vector4F {
|
||||
#[inline]
|
||||
pub fn new(x: f32, y: f32, z: f32, w: f32) -> Vector4F {
|
||||
Vector4F(F32x4::new(x, y, z, w))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn splat(value: f32) -> Vector4F {
|
||||
Vector4F(F32x4::splat(value))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_2d(self) -> Vector2F {
|
||||
self.to_3d().to_2d()
|
||||
}
|
||||
|
||||
/// Performs perspective division to convert this vector to 3D.
|
||||
#[inline]
|
||||
pub fn to_3d(self) -> Vector3F {
|
||||
let mut vector = self.0 * F32x4::splat(1.0 / self.w());
|
||||
vector.set_w(0.0);
|
||||
Vector3F(vector)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn x(self) -> f32 {
|
||||
self.0[0]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn y(self) -> f32 {
|
||||
self.0[1]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn z(self) -> f32 {
|
||||
self.0[2]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn w(self) -> f32 {
|
||||
self.0[3]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn scale(self, x: f32) -> Vector4F {
|
||||
let mut factors = F32x4::splat(x);
|
||||
factors[3] = 1.0;
|
||||
Vector4F(self.0 * factors)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_x(&mut self, x: f32) {
|
||||
self.0[0] = x
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_y(&mut self, y: f32) {
|
||||
self.0[1] = y
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_z(&mut self, z: f32) {
|
||||
self.0[2] = z
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_w(&mut self, w: f32) {
|
||||
self.0[3] = w
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn approx_eq(self, other: Vector4F, epsilon: f32) -> bool {
|
||||
self.0.approx_eq(other.0, epsilon)
|
||||
}
|
||||
|
||||
/// Checks to see whether this *homogeneous* coordinate equals zero.
|
||||
///
|
||||
/// Note that since this treats the coordinate as a homogeneous coordinate, the `w` is ignored.
|
||||
// TODO(pcwalton): Optimize with SIMD.
|
||||
#[inline]
|
||||
pub fn is_zero(self) -> bool {
|
||||
self.x() == 0.0 && self.y() == 0.0 && self.z() == 0.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn lerp(self, other: Vector4F, t: f32) -> Vector4F {
|
||||
Vector4F(self.0 + (other.0 - self.0) * F32x4::splat(t))
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Vector4F> for Vector4F {
|
||||
type Output = Vector4F;
|
||||
#[inline]
|
||||
fn add(self, other: Vector4F) -> Vector4F {
|
||||
Vector4F(self.0 + other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign for Vector4F {
|
||||
#[inline]
|
||||
fn add_assign(&mut self, other: Vector4F) {
|
||||
self.0 += other.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Vector4F> for Vector4F {
|
||||
type Output = Vector4F;
|
||||
#[inline]
|
||||
fn mul(self, other: Vector4F) -> Vector4F {
|
||||
Vector4F(self.0 * other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for Vector4F {
|
||||
type Output = Vector4F;
|
||||
/// NB: This does not negate w, because that is rarely what you what for homogeneous
|
||||
/// coordinates.
|
||||
#[inline]
|
||||
fn neg(self) -> Vector4F {
|
||||
Vector4F(self.0 * F32x4::new(-1.0, -1.0, -1.0, 1.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<Vector4F> for Vector4F {
|
||||
type Output = Vector4F;
|
||||
#[inline]
|
||||
fn sub(self, other: Vector4F) -> Vector4F {
|
||||
Vector4F(self.0 - other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Vector4F {
|
||||
#[inline]
|
||||
fn default() -> Vector4F {
|
||||
let mut point = F32x4::default();
|
||||
point.set_w(1.0);
|
||||
Vector4F(point)
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
[package]
|
||||
name = "pathfinder_gpu"
|
||||
version = "0.5.0"
|
||||
edition = "2018"
|
||||
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
|
||||
description = "A simple cross-platform GPU abstraction library"
|
||||
license = "MIT/Apache-2.0"
|
||||
repository = "https://github.com/servo/pathfinder"
|
||||
homepage = "https://github.com/servo/pathfinder"
|
||||
|
||||
[features]
|
||||
shader_alignment_32_bits = ["pathfinder_geometry/shader_alignment_32_bits"]
|
||||
|
||||
[dependencies]
|
||||
bitflags = "1.0"
|
||||
half = "1.5"
|
||||
|
||||
[dependencies.image]
|
||||
version = "0.23"
|
||||
default-features = false
|
||||
features = ["png"]
|
||||
|
||||
[dependencies.pathfinder_color]
|
||||
path = "../color"
|
||||
version = "0.5"
|
||||
|
||||
[dependencies.pathfinder_geometry]
|
||||
path = "../geometry"
|
||||
version = "0.5"
|
||||
|
||||
[dependencies.pathfinder_resources]
|
||||
path = "../resources"
|
||||
version = "0.5"
|
||||
|
||||
[dependencies.pathfinder_simd]
|
||||
path = "../simd"
|
||||
version = "0.5"
|
|
@ -1,605 +0,0 @@
|
|||
// pathfinder/gpu/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.
|
||||
|
||||
//! Minimal abstractions over GPU device capabilities.
|
||||
|
||||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
|
||||
use half::f16;
|
||||
use image::ImageFormat;
|
||||
use pathfinder_color::ColorF;
|
||||
use pathfinder_geometry::rect::RectI;
|
||||
use pathfinder_geometry::transform3d::Transform4F;
|
||||
use pathfinder_geometry::vector::{Vector2I, vec2i};
|
||||
use pathfinder_resources::ResourceLoader;
|
||||
use pathfinder_simd::default::{F32x2, F32x4, I32x2};
|
||||
use std::os::raw::c_void;
|
||||
use std::time::Duration;
|
||||
|
||||
pub trait Device: Sized {
|
||||
type Buffer;
|
||||
type Fence;
|
||||
type Framebuffer;
|
||||
type Program;
|
||||
type Shader;
|
||||
type StorageBuffer;
|
||||
type Texture;
|
||||
type TextureDataReceiver;
|
||||
type TimerQuery;
|
||||
type Uniform;
|
||||
type VertexArray;
|
||||
type VertexAttr;
|
||||
|
||||
fn feature_level(&self) -> FeatureLevel;
|
||||
fn create_texture(&self, format: TextureFormat, size: Vector2I) -> Self::Texture;
|
||||
fn create_texture_from_data(&self, format: TextureFormat, size: Vector2I, data: TextureDataRef)
|
||||
-> Self::Texture;
|
||||
fn create_shader(&self, resources: &dyn ResourceLoader, name: &str, kind: ShaderKind)
|
||||
-> Self::Shader;
|
||||
fn create_shader_from_source(&self, name: &str, source: &[u8], kind: ShaderKind)
|
||||
-> Self::Shader;
|
||||
fn create_vertex_array(&self) -> Self::VertexArray;
|
||||
fn create_program_from_shaders(&self,
|
||||
resources: &dyn ResourceLoader,
|
||||
name: &str,
|
||||
shaders: ProgramKind<Self::Shader>)
|
||||
-> Self::Program;
|
||||
fn set_compute_program_local_size(&self,
|
||||
program: &mut Self::Program,
|
||||
local_size: ComputeDimensions);
|
||||
fn get_vertex_attr(&self, program: &Self::Program, name: &str) -> Option<Self::VertexAttr>;
|
||||
fn get_uniform(&self, program: &Self::Program, name: &str) -> Self::Uniform;
|
||||
fn get_storage_buffer(&self, program: &Self::Program, name: &str, binding: u32)
|
||||
-> Self::StorageBuffer;
|
||||
fn bind_buffer(&self,
|
||||
vertex_array: &Self::VertexArray,
|
||||
buffer: &Self::Buffer,
|
||||
target: BufferTarget);
|
||||
fn configure_vertex_attr(&self,
|
||||
vertex_array: &Self::VertexArray,
|
||||
attr: &Self::VertexAttr,
|
||||
descriptor: &VertexAttrDescriptor);
|
||||
fn create_framebuffer(&self, texture: Self::Texture) -> Self::Framebuffer;
|
||||
fn create_buffer(&self, mode: BufferUploadMode) -> Self::Buffer;
|
||||
fn allocate_buffer<T>(&self,
|
||||
buffer: &Self::Buffer,
|
||||
data: BufferData<T>,
|
||||
target: BufferTarget);
|
||||
fn upload_to_buffer<T>(&self,
|
||||
buffer: &Self::Buffer,
|
||||
position: usize,
|
||||
data: &[T],
|
||||
target: BufferTarget);
|
||||
fn framebuffer_texture<'f>(&self, framebuffer: &'f Self::Framebuffer) -> &'f Self::Texture;
|
||||
fn destroy_framebuffer(&self, framebuffer: Self::Framebuffer) -> Self::Texture;
|
||||
fn texture_format(&self, texture: &Self::Texture) -> TextureFormat;
|
||||
fn texture_size(&self, texture: &Self::Texture) -> Vector2I;
|
||||
fn set_texture_sampling_mode(&self, texture: &Self::Texture, flags: TextureSamplingFlags);
|
||||
fn upload_to_texture(&self, texture: &Self::Texture, rect: RectI, data: TextureDataRef);
|
||||
fn read_pixels(&self, target: &RenderTarget<Self>, viewport: RectI)
|
||||
-> Self::TextureDataReceiver;
|
||||
fn begin_commands(&self);
|
||||
fn end_commands(&self);
|
||||
fn draw_arrays(&self, index_count: u32, render_state: &RenderState<Self>);
|
||||
fn draw_elements(&self, index_count: u32, render_state: &RenderState<Self>);
|
||||
fn draw_elements_instanced(&self,
|
||||
index_count: u32,
|
||||
instance_count: u32,
|
||||
render_state: &RenderState<Self>);
|
||||
fn dispatch_compute(&self, dimensions: ComputeDimensions, state: &ComputeState<Self>);
|
||||
fn add_fence(&self) -> Self::Fence;
|
||||
fn wait_for_fence(&self, fence: &Self::Fence);
|
||||
fn create_timer_query(&self) -> Self::TimerQuery;
|
||||
fn begin_timer_query(&self, query: &Self::TimerQuery);
|
||||
fn end_timer_query(&self, query: &Self::TimerQuery);
|
||||
fn try_recv_timer_query(&self, query: &Self::TimerQuery) -> Option<Duration>;
|
||||
fn recv_timer_query(&self, query: &Self::TimerQuery) -> Duration;
|
||||
fn try_recv_texture_data(&self, receiver: &Self::TextureDataReceiver) -> Option<TextureData>;
|
||||
fn recv_texture_data(&self, receiver: &Self::TextureDataReceiver) -> TextureData;
|
||||
|
||||
fn create_texture_from_png(&self,
|
||||
resources: &dyn ResourceLoader,
|
||||
name: &str,
|
||||
format: TextureFormat)
|
||||
-> Self::Texture {
|
||||
let data = resources.slurp(&format!("textures/{}.png", name)).unwrap();
|
||||
let image = image::load_from_memory_with_format(&data, ImageFormat::Png).unwrap();
|
||||
match format {
|
||||
TextureFormat::R8 => {
|
||||
let image = image.to_luma();
|
||||
let size = vec2i(image.width() as i32, image.height() as i32);
|
||||
self.create_texture_from_data(format, size, TextureDataRef::U8(&image))
|
||||
}
|
||||
TextureFormat::RGBA8 => {
|
||||
let image = image.to_rgba();
|
||||
let size = vec2i(image.width() as i32, image.height() as i32);
|
||||
self.create_texture_from_data(format, size, TextureDataRef::U8(&image))
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_program_from_shader_names(
|
||||
&self,
|
||||
resources: &dyn ResourceLoader,
|
||||
program_name: &str,
|
||||
shader_names: ProgramKind<&str>,
|
||||
) -> Self::Program {
|
||||
let shaders = match shader_names {
|
||||
ProgramKind::Raster { vertex, fragment } => {
|
||||
ProgramKind::Raster {
|
||||
vertex: self.create_shader(resources, vertex, ShaderKind::Vertex),
|
||||
fragment: self.create_shader(resources, fragment, ShaderKind::Fragment),
|
||||
}
|
||||
}
|
||||
ProgramKind::Compute(compute) => {
|
||||
ProgramKind::Compute(self.create_shader(resources, compute, ShaderKind::Compute))
|
||||
}
|
||||
};
|
||||
self.create_program_from_shaders(resources, program_name, shaders)
|
||||
}
|
||||
|
||||
fn create_raster_program(&self, resources: &dyn ResourceLoader, name: &str) -> Self::Program {
|
||||
let shaders = ProgramKind::Raster { vertex: name, fragment: name };
|
||||
self.create_program_from_shader_names(resources, name, shaders)
|
||||
}
|
||||
|
||||
fn create_compute_program(&self, resources: &dyn ResourceLoader, name: &str) -> Self::Program {
|
||||
let shaders = ProgramKind::Compute(name);
|
||||
self.create_program_from_shader_names(resources, name, shaders)
|
||||
}
|
||||
}
|
||||
|
||||
/// These are rough analogues to D3D versions; don't expect them to represent exactly the feature
|
||||
/// set of the versions.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum FeatureLevel {
|
||||
D3D10,
|
||||
D3D11,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum TextureFormat {
|
||||
R8,
|
||||
R16F,
|
||||
RGBA8,
|
||||
RGBA16F,
|
||||
RGBA32F,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum VertexAttrType {
|
||||
F32,
|
||||
I32,
|
||||
I16,
|
||||
I8,
|
||||
U32,
|
||||
U16,
|
||||
U8,
|
||||
}
|
||||
|
||||
impl VertexAttrType {
|
||||
pub fn get_size(&self) -> usize {
|
||||
match *self {
|
||||
VertexAttrType::F32 => 4,
|
||||
VertexAttrType::I32 => 4,
|
||||
VertexAttrType::I16 => 2,
|
||||
VertexAttrType::I8 => 1,
|
||||
VertexAttrType::U32 => 4,
|
||||
VertexAttrType::U16 => 2,
|
||||
VertexAttrType::U8 => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "shader_alignment_32_bits")]
|
||||
pub const ALIGNED_U8_ATTR: VertexAttrType = VertexAttrType::U32;
|
||||
#[cfg(not(feature = "shader_alignment_32_bits"))]
|
||||
pub const ALIGNED_U8_ATTR: VertexAttrType = VertexAttrType::U8;
|
||||
|
||||
#[cfg(feature = "shader_alignment_32_bits")]
|
||||
pub const ALIGNED_U16_ATTR: VertexAttrType = VertexAttrType::U32;
|
||||
#[cfg(not(feature = "shader_alignment_32_bits"))]
|
||||
pub const ALIGNED_U16_ATTR: VertexAttrType = VertexAttrType::U16;
|
||||
|
||||
#[cfg(feature = "shader_alignment_32_bits")]
|
||||
pub const ALIGNED_I8_ATTR: VertexAttrType = VertexAttrType::I32;
|
||||
#[cfg(not(feature = "shader_alignment_32_bits"))]
|
||||
pub const ALIGNED_I8_ATTR: VertexAttrType = VertexAttrType::I8;
|
||||
|
||||
#[cfg(feature = "shader_alignment_32_bits")]
|
||||
pub const ALIGNED_I16_ATTR: VertexAttrType = VertexAttrType::I32;
|
||||
#[cfg(not(feature = "shader_alignment_32_bits"))]
|
||||
pub const ALIGNED_I16_ATTR: VertexAttrType = VertexAttrType::I16;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum BufferData<'a, T> {
|
||||
Uninitialized(usize),
|
||||
Memory(&'a [T]),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum BufferTarget {
|
||||
Vertex,
|
||||
Index,
|
||||
Storage,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum BufferUploadMode {
|
||||
Static,
|
||||
Dynamic,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum ShaderKind {
|
||||
Vertex,
|
||||
Fragment,
|
||||
Compute,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum ProgramKind<T> {
|
||||
Raster {
|
||||
vertex: T,
|
||||
fragment: T,
|
||||
},
|
||||
Compute(T),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct ComputeDimensions {
|
||||
pub x: u32,
|
||||
pub y: u32,
|
||||
pub z: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum UniformData {
|
||||
Float(f32),
|
||||
IVec2(I32x2),
|
||||
IVec3([i32; 3]),
|
||||
Int(i32),
|
||||
Mat2(F32x4),
|
||||
Mat4([F32x4; 4]),
|
||||
Vec2(F32x2),
|
||||
Vec3([f32; 3]),
|
||||
Vec4(F32x4),
|
||||
TextureUnit(u32),
|
||||
ImageUnit(u32),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Primitive {
|
||||
Triangles,
|
||||
Lines,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RenderState<'a, D> where D: Device {
|
||||
pub target: &'a RenderTarget<'a, D>,
|
||||
pub program: &'a D::Program,
|
||||
pub vertex_array: &'a D::VertexArray,
|
||||
pub primitive: Primitive,
|
||||
pub uniforms: &'a [(&'a D::Uniform, UniformData)],
|
||||
pub textures: &'a [&'a D::Texture],
|
||||
pub images: &'a [ImageBinding<'a, D>],
|
||||
pub viewport: RectI,
|
||||
pub options: RenderOptions,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ComputeState<'a, D> where D: Device {
|
||||
pub program: &'a D::Program,
|
||||
pub uniforms: &'a [(&'a D::Uniform, UniformData)],
|
||||
pub textures: &'a [&'a D::Texture],
|
||||
pub images: &'a [ImageBinding<'a, D>],
|
||||
pub storage_buffers: &'a [(&'a D::StorageBuffer, &'a D::Buffer)],
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ImageBinding<'a, D> where D: Device {
|
||||
pub texture: &'a D::Texture,
|
||||
pub access: ImageAccess,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RenderOptions {
|
||||
pub blend: Option<BlendState>,
|
||||
pub depth: Option<DepthState>,
|
||||
pub stencil: Option<StencilState>,
|
||||
pub clear_ops: ClearOps,
|
||||
pub color_mask: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct ClearOps {
|
||||
pub color: Option<ColorF>,
|
||||
pub depth: Option<f32>,
|
||||
pub stencil: Option<u8>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum RenderTarget<'a, D> where D: Device {
|
||||
Default,
|
||||
Framebuffer(&'a D::Framebuffer),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct BlendState {
|
||||
pub dest_rgb_factor: BlendFactor,
|
||||
pub dest_alpha_factor: BlendFactor,
|
||||
pub src_rgb_factor: BlendFactor,
|
||||
pub src_alpha_factor: BlendFactor,
|
||||
pub op: BlendOp,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum BlendFactor {
|
||||
Zero,
|
||||
One,
|
||||
SrcAlpha,
|
||||
OneMinusSrcAlpha,
|
||||
DestAlpha,
|
||||
OneMinusDestAlpha,
|
||||
DestColor,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum BlendOp {
|
||||
Add,
|
||||
Subtract,
|
||||
ReverseSubtract,
|
||||
Min,
|
||||
Max,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default, Debug)]
|
||||
pub struct DepthState {
|
||||
pub func: DepthFunc,
|
||||
pub write: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum DepthFunc {
|
||||
Less,
|
||||
Always,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct StencilState {
|
||||
pub func: StencilFunc,
|
||||
pub reference: u32,
|
||||
pub mask: u32,
|
||||
pub write: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum StencilFunc {
|
||||
Always,
|
||||
Equal,
|
||||
}
|
||||
|
||||
impl Default for RenderOptions {
|
||||
#[inline]
|
||||
fn default() -> RenderOptions {
|
||||
RenderOptions {
|
||||
blend: None,
|
||||
depth: None,
|
||||
stencil: None,
|
||||
clear_ops: ClearOps::default(),
|
||||
color_mask: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BlendOp {
|
||||
#[inline]
|
||||
fn default() -> BlendOp {
|
||||
BlendOp::Add
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for StencilState {
|
||||
#[inline]
|
||||
fn default() -> StencilState {
|
||||
StencilState {
|
||||
func: StencilFunc::default(),
|
||||
reference: 0,
|
||||
mask: !0,
|
||||
write: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DepthFunc {
|
||||
#[inline]
|
||||
fn default() -> DepthFunc {
|
||||
DepthFunc::Less
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for StencilFunc {
|
||||
#[inline]
|
||||
fn default() -> StencilFunc {
|
||||
StencilFunc::Always
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum TextureData {
|
||||
U8(Vec<u8>),
|
||||
U16(Vec<u16>),
|
||||
F16(Vec<f16>),
|
||||
F32(Vec<f32>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum TextureDataRef<'a> {
|
||||
U8(&'a [u8]),
|
||||
F16(&'a [f16]),
|
||||
F32(&'a [f32]),
|
||||
}
|
||||
|
||||
impl UniformData {
|
||||
#[inline]
|
||||
pub fn from_transform_3d(transform: &Transform4F) -> UniformData {
|
||||
UniformData::Mat4([transform.c0, transform.c1, transform.c2, transform.c3])
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct VertexAttrDescriptor {
|
||||
pub size: usize,
|
||||
pub class: VertexAttrClass,
|
||||
pub attr_type: VertexAttrType,
|
||||
pub stride: usize,
|
||||
pub offset: usize,
|
||||
pub divisor: u32,
|
||||
pub buffer_index: u32,
|
||||
}
|
||||
|
||||
impl VertexAttrDescriptor {
|
||||
pub const fn datatype_only(class: VertexAttrClass, attr_type: VertexAttrType, size: usize) -> Self {
|
||||
VertexAttrDescriptor {
|
||||
size,
|
||||
class,
|
||||
attr_type,
|
||||
divisor: 0,
|
||||
buffer_index: 0,
|
||||
stride: 0,
|
||||
offset: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VertexBufferDescriptor {
|
||||
pub index: u32,
|
||||
pub divisor: u32,
|
||||
pub vertex_attrs: Vec<VertexAttrDescriptor>,
|
||||
}
|
||||
|
||||
impl VertexBufferDescriptor {
|
||||
pub fn update_attrs(&mut self) {
|
||||
let mut offset = 0;
|
||||
for attr in self.vertex_attrs.iter_mut() {
|
||||
attr.buffer_index = self.index;
|
||||
attr.divisor = self.divisor;
|
||||
attr.offset = offset;
|
||||
offset += attr.size * attr.attr_type.get_size();
|
||||
}
|
||||
|
||||
for attr in self.vertex_attrs.iter_mut() {
|
||||
attr.stride = offset;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn configure_vertex_attrs<D: Device>(&self, device: &D, vertex_array: &D::VertexArray, attrs: &[D::VertexAttr]) {
|
||||
for (attr, descriptor) in attrs.iter().zip(self.vertex_attrs.iter()) {
|
||||
device.configure_vertex_attr(vertex_array, attr, &descriptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum VertexAttrClass {
|
||||
Float,
|
||||
FloatNorm,
|
||||
Int,
|
||||
}
|
||||
|
||||
impl TextureFormat {
|
||||
#[inline]
|
||||
pub fn channels(self) -> usize {
|
||||
match self {
|
||||
TextureFormat::R8 | TextureFormat::R16F => 1,
|
||||
TextureFormat::RGBA8 | TextureFormat::RGBA16F | TextureFormat::RGBA32F => 4,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn bytes_per_pixel(self) -> usize {
|
||||
match self {
|
||||
TextureFormat::R8 => 1,
|
||||
TextureFormat::R16F => 2,
|
||||
TextureFormat::RGBA8 => 4,
|
||||
TextureFormat::RGBA16F => 8,
|
||||
TextureFormat::RGBA32F => 16,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ClearOps {
|
||||
#[inline]
|
||||
pub fn has_ops(&self) -> bool {
|
||||
self.color.is_some() || self.depth.is_some() || self.stencil.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BlendState {
|
||||
#[inline]
|
||||
fn default() -> BlendState {
|
||||
BlendState {
|
||||
src_rgb_factor: BlendFactor::One,
|
||||
dest_rgb_factor: BlendFactor::OneMinusSrcAlpha,
|
||||
src_alpha_factor: BlendFactor::One,
|
||||
dest_alpha_factor: BlendFactor::One,
|
||||
op: BlendOp::Add,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct TextureSamplingFlags: u8 {
|
||||
const REPEAT_U = 0x01;
|
||||
const REPEAT_V = 0x02;
|
||||
const NEAREST_MIN = 0x04;
|
||||
const NEAREST_MAG = 0x08;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum ImageAccess {
|
||||
Read,
|
||||
Write,
|
||||
ReadWrite,
|
||||
}
|
||||
|
||||
impl<'a> TextureDataRef<'a> {
|
||||
#[doc(hidden)]
|
||||
pub fn check_and_extract_data_ptr(self, minimum_size: Vector2I, format: TextureFormat)
|
||||
-> *const c_void {
|
||||
let channels = match (format, self) {
|
||||
(TextureFormat::R8, TextureDataRef::U8(_)) => 1,
|
||||
(TextureFormat::RGBA8, TextureDataRef::U8(_)) => 4,
|
||||
(TextureFormat::RGBA16F, TextureDataRef::F16(_)) => 4,
|
||||
(TextureFormat::RGBA32F, TextureDataRef::F32(_)) => 4,
|
||||
_ => panic!("Unimplemented texture format!"),
|
||||
};
|
||||
|
||||
let area = minimum_size.x() as usize * minimum_size.y() as usize;
|
||||
|
||||
match self {
|
||||
TextureDataRef::U8(data) => {
|
||||
assert!(data.len() >= area * channels);
|
||||
data.as_ptr() as *const c_void
|
||||
}
|
||||
TextureDataRef::F16(data) => {
|
||||
assert!(data.len() >= area * channels);
|
||||
data.as_ptr() as *const c_void
|
||||
}
|
||||
TextureDataRef::F32(data) => {
|
||||
assert!(data.len() >= area * channels);
|
||||
data.as_ptr() as *const c_void
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
[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"]
|
|
@ -1,313 +0,0 @@
|
|||
// 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)
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
[package]
|
||||
name = "pathfinder_renderer"
|
||||
version = "0.5.0"
|
||||
edition = "2018"
|
||||
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
|
||||
description = "A GPU-accelerated vector graphics and font renderer"
|
||||
license = "MIT/Apache-2.0"
|
||||
repository = "https://github.com/servo/pathfinder"
|
||||
homepage = "https://github.com/servo/pathfinder"
|
||||
|
||||
[dependencies]
|
||||
bitflags = "1.0"
|
||||
byteorder = "1.2"
|
||||
crossbeam-channel = "0.4"
|
||||
once_cell = "1.3.1"
|
||||
fxhash = "0.2"
|
||||
half = "1.5"
|
||||
hashbrown = "0.7"
|
||||
rayon = "1.0"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
smallvec = "1.2"
|
||||
vec_map = "0.8"
|
||||
instant = { version = "0.1.2", features = ["wasm-bindgen"] }
|
||||
|
||||
[dependencies.log]
|
||||
version = "0.4"
|
||||
|
||||
[dependencies.pathfinder_color]
|
||||
path = "../color"
|
||||
version = "0.5"
|
||||
|
||||
[dependencies.pathfinder_content]
|
||||
path = "../content"
|
||||
version = "0.5"
|
||||
|
||||
[dependencies.pathfinder_geometry]
|
||||
path = "../geometry"
|
||||
version = "0.5"
|
||||
|
||||
[dependencies.pathfinder_gpu]
|
||||
path = "../gpu"
|
||||
version = "0.5"
|
||||
|
||||
[dependencies.pathfinder_resources]
|
||||
path = "../resources"
|
||||
version = "0.5"
|
||||
|
||||
[dependencies.pathfinder_simd]
|
||||
path = "../simd"
|
||||
version = "0.5"
|
||||
|
||||
[dependencies.pathfinder_ui]
|
||||
path = "../ui"
|
||||
version = "0.5"
|
||||
|
||||
[dev-dependencies]
|
||||
quickcheck = "0.9"
|
|
@ -1,327 +0,0 @@
|
|||
// pathfinder/renderer/src/allocator.rs
|
||||
//
|
||||
// Copyright © 2020 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.
|
||||
|
||||
//! A simple quadtree-based texture allocator.
|
||||
|
||||
use crate::gpu_data::{TextureLocation, TexturePageId};
|
||||
use pathfinder_geometry::rect::RectI;
|
||||
use pathfinder_geometry::vector::{Vector2F, Vector2I, vec2f, vec2i};
|
||||
|
||||
const ATLAS_TEXTURE_LENGTH: u32 = 1024;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TextureAllocator {
|
||||
pages: Vec<TexturePage>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TexturePage {
|
||||
allocator: TexturePageAllocator,
|
||||
is_new: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum TexturePageAllocator {
|
||||
// An atlas allocated with our quadtree allocator.
|
||||
Atlas(TextureAtlasAllocator),
|
||||
// A single image.
|
||||
Image { size: Vector2I },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TextureAtlasAllocator {
|
||||
root: TreeNode,
|
||||
size: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum TreeNode {
|
||||
EmptyLeaf,
|
||||
FullLeaf,
|
||||
// Top left, top right, bottom left, and bottom right, in that order.
|
||||
Parent([Box<TreeNode>; 4]),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub enum AllocationMode {
|
||||
Atlas,
|
||||
OwnPage,
|
||||
}
|
||||
|
||||
impl TextureAllocator {
|
||||
#[inline]
|
||||
pub fn new() -> TextureAllocator {
|
||||
TextureAllocator { pages: vec![] }
|
||||
}
|
||||
|
||||
pub fn allocate(&mut self, requested_size: Vector2I, mode: AllocationMode) -> TextureLocation {
|
||||
// If requested, or if the image is too big, use a separate page.
|
||||
if mode == AllocationMode::OwnPage ||
|
||||
requested_size.x() > ATLAS_TEXTURE_LENGTH as i32 ||
|
||||
requested_size.y() > ATLAS_TEXTURE_LENGTH as i32 {
|
||||
return self.allocate_image(requested_size);
|
||||
}
|
||||
|
||||
// Try to add to each atlas.
|
||||
for (page_index, page) in self.pages.iter_mut().enumerate() {
|
||||
match page.allocator {
|
||||
TexturePageAllocator::Image { .. } => {}
|
||||
TexturePageAllocator::Atlas(ref mut allocator) => {
|
||||
if let Some(rect) = allocator.allocate(requested_size) {
|
||||
return TextureLocation { page: TexturePageId(page_index as u32), rect };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add a new atlas.
|
||||
let page = TexturePageId(self.pages.len() as u32);
|
||||
let mut allocator = TextureAtlasAllocator::new();
|
||||
let rect = allocator.allocate(requested_size).expect("Allocation failed!");
|
||||
self.pages.push(TexturePage {
|
||||
is_new: true,
|
||||
allocator: TexturePageAllocator::Atlas(allocator),
|
||||
});
|
||||
TextureLocation { page, rect }
|
||||
}
|
||||
|
||||
pub fn allocate_image(&mut self, requested_size: Vector2I) -> TextureLocation {
|
||||
let page = TexturePageId(self.pages.len() as u32);
|
||||
let rect = RectI::new(Vector2I::default(), requested_size);
|
||||
self.pages.push(TexturePage {
|
||||
is_new: true,
|
||||
allocator: TexturePageAllocator::Image { size: rect.size() },
|
||||
});
|
||||
TextureLocation { page, rect }
|
||||
}
|
||||
|
||||
pub fn page_size(&self, page_id: TexturePageId) -> Vector2I {
|
||||
match self.pages[page_id.0 as usize].allocator {
|
||||
TexturePageAllocator::Atlas(ref atlas) => Vector2I::splat(atlas.size as i32),
|
||||
TexturePageAllocator::Image { size, .. } => size,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn page_scale(&self, page_id: TexturePageId) -> Vector2F {
|
||||
vec2f(1.0, 1.0) / self.page_size(page_id).to_f32()
|
||||
}
|
||||
|
||||
pub fn page_is_new(&self, page_id: TexturePageId) -> bool {
|
||||
self.pages[page_id.0 as usize].is_new
|
||||
}
|
||||
|
||||
pub fn mark_page_as_allocated(&mut self, page_id: TexturePageId) {
|
||||
self.pages[page_id.0 as usize].is_new = false;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn page_count(&self) -> u32 {
|
||||
self.pages.len() as u32
|
||||
}
|
||||
}
|
||||
|
||||
impl TextureAtlasAllocator {
|
||||
#[inline]
|
||||
fn new() -> TextureAtlasAllocator {
|
||||
TextureAtlasAllocator::with_length(ATLAS_TEXTURE_LENGTH)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_length(length: u32) -> TextureAtlasAllocator {
|
||||
TextureAtlasAllocator { root: TreeNode::EmptyLeaf, size: length }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn allocate(&mut self, requested_size: Vector2I) -> Option<RectI> {
|
||||
let requested_length =
|
||||
(requested_size.x().max(requested_size.y()) as u32).next_power_of_two();
|
||||
self.root.allocate(Vector2I::default(), self.size, requested_length)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[allow(dead_code)]
|
||||
fn free(&mut self, rect: RectI) {
|
||||
let requested_length = rect.width() as u32;
|
||||
self.root.free(Vector2I::default(), self.size, rect.origin(), requested_length)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[allow(dead_code)]
|
||||
fn is_empty(&self) -> bool {
|
||||
match self.root {
|
||||
TreeNode::EmptyLeaf => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TreeNode {
|
||||
// Invariant: `requested_size` must be a power of two.
|
||||
fn allocate(&mut self, this_origin: Vector2I, this_size: u32, requested_size: u32)
|
||||
-> Option<RectI> {
|
||||
if let TreeNode::FullLeaf = *self {
|
||||
// No room here.
|
||||
return None;
|
||||
}
|
||||
if this_size < requested_size {
|
||||
// Doesn't fit.
|
||||
return None;
|
||||
}
|
||||
|
||||
// Allocate here or split, as necessary.
|
||||
if let TreeNode::EmptyLeaf = *self {
|
||||
// Do we have a perfect fit?
|
||||
if this_size == requested_size {
|
||||
*self = TreeNode::FullLeaf;
|
||||
return Some(RectI::new(this_origin, Vector2I::splat(this_size as i32)));
|
||||
}
|
||||
|
||||
// Split.
|
||||
*self = TreeNode::Parent([
|
||||
Box::new(TreeNode::EmptyLeaf),
|
||||
Box::new(TreeNode::EmptyLeaf),
|
||||
Box::new(TreeNode::EmptyLeaf),
|
||||
Box::new(TreeNode::EmptyLeaf),
|
||||
]);
|
||||
}
|
||||
|
||||
// Recurse into children.
|
||||
match *self {
|
||||
TreeNode::Parent(ref mut kids) => {
|
||||
let kid_size = this_size / 2;
|
||||
if let Some(origin) = kids[0].allocate(this_origin, kid_size, requested_size) {
|
||||
return Some(origin);
|
||||
}
|
||||
if let Some(origin) = kids[1].allocate(this_origin + vec2i(kid_size as i32, 0),
|
||||
kid_size,
|
||||
requested_size) {
|
||||
return Some(origin);
|
||||
}
|
||||
if let Some(origin) = kids[2].allocate(this_origin + vec2i(0, kid_size as i32),
|
||||
kid_size,
|
||||
requested_size) {
|
||||
return Some(origin);
|
||||
}
|
||||
if let Some(origin) = kids[3].allocate(this_origin + kid_size as i32,
|
||||
kid_size,
|
||||
requested_size) {
|
||||
return Some(origin);
|
||||
}
|
||||
|
||||
self.merge_if_necessary();
|
||||
return None;
|
||||
}
|
||||
TreeNode::EmptyLeaf | TreeNode::FullLeaf => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn free(&mut self,
|
||||
this_origin: Vector2I,
|
||||
this_size: u32,
|
||||
requested_origin: Vector2I,
|
||||
requested_size: u32) {
|
||||
if this_size <= requested_size {
|
||||
if this_size == requested_size && this_origin == requested_origin {
|
||||
*self = TreeNode::EmptyLeaf;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let child_size = this_size / 2;
|
||||
let this_center = this_origin + child_size as i32;
|
||||
|
||||
let child_index;
|
||||
let mut child_origin = this_origin;
|
||||
|
||||
if requested_origin.y() < this_center.y() {
|
||||
if requested_origin.x() < this_center.x() {
|
||||
child_index = 0;
|
||||
} else {
|
||||
child_index = 1;
|
||||
child_origin += vec2i(child_size as i32, 0);
|
||||
}
|
||||
} else {
|
||||
if requested_origin.x() < this_center.x() {
|
||||
child_index = 2;
|
||||
child_origin += vec2i(0, child_size as i32);
|
||||
} else {
|
||||
child_index = 3;
|
||||
child_origin = this_center;
|
||||
}
|
||||
}
|
||||
|
||||
match *self {
|
||||
TreeNode::Parent(ref mut kids) => {
|
||||
kids[child_index].free(child_origin, child_size, requested_origin, requested_size);
|
||||
self.merge_if_necessary();
|
||||
}
|
||||
TreeNode::EmptyLeaf | TreeNode::FullLeaf => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn merge_if_necessary(&mut self) {
|
||||
match *self {
|
||||
TreeNode::Parent(ref mut kids) => {
|
||||
if kids.iter().all(|kid| {
|
||||
match **kid {
|
||||
TreeNode::EmptyLeaf => true,
|
||||
_ => false,
|
||||
}
|
||||
}) {
|
||||
*self = TreeNode::EmptyLeaf;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use pathfinder_geometry::vector::vec2i;
|
||||
use quickcheck;
|
||||
use std::u32;
|
||||
|
||||
use super::TextureAtlasAllocator;
|
||||
|
||||
#[test]
|
||||
fn test_allocation_and_freeing() {
|
||||
quickcheck::quickcheck(prop_allocation_and_freeing_work as
|
||||
fn(u32, Vec<(u32, u32)>) -> bool);
|
||||
|
||||
fn prop_allocation_and_freeing_work(mut length: u32, mut sizes: Vec<(u32, u32)>) -> bool {
|
||||
length = u32::next_power_of_two(length).max(1);
|
||||
|
||||
for &mut (ref mut width, ref mut height) in &mut sizes {
|
||||
*width = (*width).min(length).max(1);
|
||||
*height = (*height).min(length).max(1);
|
||||
}
|
||||
|
||||
let mut allocator = TextureAtlasAllocator::with_length(length);
|
||||
let mut locations = vec![];
|
||||
for &(width, height) in &sizes {
|
||||
let size = vec2i(width as i32, height as i32);
|
||||
if let Some(location) = allocator.allocate(size) {
|
||||
locations.push(location);
|
||||
}
|
||||
}
|
||||
|
||||
for location in locations {
|
||||
allocator.free(location);
|
||||
}
|
||||
|
||||
assert!(allocator.is_empty());
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,884 +0,0 @@
|
|||
// pathfinder/renderer/src/builder.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.
|
||||
|
||||
//! Packs data onto the GPU.
|
||||
|
||||
use crate::concurrent::executor::Executor;
|
||||
use crate::gpu::renderer::{BlendModeExt, MASK_TILES_ACROSS, MASK_TILES_DOWN};
|
||||
use crate::gpu_data::{AlphaTileId, Clip, ClipBatch, ClipBatchKey, ClipBatchKind, Fill};
|
||||
use crate::gpu_data::{FillBatchEntry, RenderCommand, TILE_CTRL_MASK_0_SHIFT};
|
||||
use crate::gpu_data::{TILE_CTRL_MASK_EVEN_ODD, TILE_CTRL_MASK_WINDING, Tile, TileBatch};
|
||||
use crate::gpu_data::{TileBatchTexture, TileObjectPrimitive};
|
||||
use crate::options::{PreparedBuildOptions, PreparedRenderTransform, RenderCommandListener};
|
||||
use crate::paint::{PaintInfo, PaintMetadata};
|
||||
use crate::scene::{DisplayItem, Scene};
|
||||
use crate::tile_map::DenseTileMap;
|
||||
use crate::tiles::{self, DrawTilingPathInfo, PackedTile, TILE_HEIGHT, TILE_WIDTH};
|
||||
use crate::tiles::{Tiler, TilingPathInfo};
|
||||
use crate::z_buffer::{DepthMetadata, ZBuffer};
|
||||
use pathfinder_content::effects::{BlendMode, Filter};
|
||||
use pathfinder_content::fill::FillRule;
|
||||
use pathfinder_content::render_target::RenderTargetId;
|
||||
use pathfinder_geometry::alignment::{AlignedI16, AlignedU8, AlignedU16, AlignedI8};
|
||||
use pathfinder_geometry::line_segment::{LineSegment2F, LineSegmentU4, LineSegmentU8};
|
||||
use pathfinder_geometry::rect::{RectF, RectI};
|
||||
use pathfinder_geometry::transform2d::Transform2F;
|
||||
use pathfinder_geometry::vector::{Vector2F, Vector2I, vec2f, vec2i};
|
||||
use pathfinder_gpu::TextureSamplingFlags;
|
||||
use pathfinder_simd::default::{F32x4, I32x4};
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use instant::Instant;
|
||||
use std::u32;
|
||||
|
||||
pub(crate) const ALPHA_TILE_LEVEL_COUNT: usize = 2;
|
||||
pub(crate) const ALPHA_TILES_PER_LEVEL: usize = 1 << (32 - ALPHA_TILE_LEVEL_COUNT + 1);
|
||||
|
||||
pub(crate) struct SceneBuilder<'a, 'b> {
|
||||
scene: &'a mut Scene,
|
||||
built_options: &'b PreparedBuildOptions,
|
||||
next_alpha_tile_indices: [AtomicUsize; ALPHA_TILE_LEVEL_COUNT],
|
||||
pub(crate) listener: Box<dyn RenderCommandListener>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ObjectBuilder {
|
||||
pub built_path: BuiltPath,
|
||||
pub fills: Vec<FillBatchEntry>,
|
||||
pub bounds: RectF,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct BuiltDrawPath {
|
||||
path: BuiltPath,
|
||||
blend_mode: BlendMode,
|
||||
filter: Filter,
|
||||
color_texture: Option<TileBatchTexture>,
|
||||
sampling_flags_1: TextureSamplingFlags,
|
||||
mask_0_fill_rule: FillRule,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct BuiltPath {
|
||||
pub solid_tiles: SolidTiles,
|
||||
pub empty_tiles: Vec<BuiltTile>,
|
||||
pub single_mask_tiles: Vec<BuiltTile>,
|
||||
pub clip_tiles: Vec<BuiltClip>,
|
||||
pub tiles: DenseTileMap<TileObjectPrimitive>,
|
||||
pub fill_rule: FillRule,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BuiltTile {
|
||||
pub page: u16,
|
||||
pub tile: Tile,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct BuiltClip {
|
||||
pub clip: Clip,
|
||||
pub key: ClipBatchKey,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) enum SolidTiles {
|
||||
Occluders(Vec<Occluder>),
|
||||
Regular(Vec<BuiltTile>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) struct Occluder {
|
||||
pub(crate) coords: Vector2I,
|
||||
}
|
||||
|
||||
impl<'a, 'b> SceneBuilder<'a, 'b> {
|
||||
pub(crate) fn new(
|
||||
scene: &'a mut Scene,
|
||||
built_options: &'b PreparedBuildOptions,
|
||||
listener: Box<dyn RenderCommandListener>,
|
||||
) -> SceneBuilder<'a, 'b> {
|
||||
SceneBuilder {
|
||||
scene,
|
||||
built_options,
|
||||
next_alpha_tile_indices: [AtomicUsize::new(0), AtomicUsize::new(0)],
|
||||
listener,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build<E>(&mut self, executor: &E) where E: Executor {
|
||||
let start_time = Instant::now();
|
||||
|
||||
// Send the start rendering command.
|
||||
let bounding_quad = self.built_options.bounding_quad();
|
||||
|
||||
let clip_path_count = self.scene.clip_paths.len();
|
||||
let draw_path_count = self.scene.paths.len();
|
||||
let total_path_count = clip_path_count + draw_path_count;
|
||||
|
||||
let needs_readable_framebuffer = self.needs_readable_framebuffer();
|
||||
|
||||
self.listener.send(RenderCommand::Start {
|
||||
bounding_quad,
|
||||
path_count: total_path_count,
|
||||
needs_readable_framebuffer,
|
||||
});
|
||||
|
||||
let render_transform = match self.built_options.transform {
|
||||
PreparedRenderTransform::Transform2D(transform) => transform.inverse(),
|
||||
_ => Transform2F::default()
|
||||
};
|
||||
|
||||
// Build paint data.
|
||||
let PaintInfo {
|
||||
render_commands,
|
||||
paint_metadata,
|
||||
render_target_metadata: _,
|
||||
} = self.scene.build_paint_info(render_transform);
|
||||
for render_command in render_commands {
|
||||
self.listener.send(render_command);
|
||||
}
|
||||
|
||||
let effective_view_box = self.scene.effective_view_box(self.built_options);
|
||||
|
||||
let built_clip_paths = executor.build_vector(clip_path_count, |path_index| {
|
||||
self.build_clip_path(PathBuildParams {
|
||||
path_index,
|
||||
view_box: effective_view_box,
|
||||
built_options: &self.built_options,
|
||||
scene: &self.scene,
|
||||
})
|
||||
});
|
||||
|
||||
let built_draw_paths = executor.build_vector(draw_path_count, |path_index| {
|
||||
self.build_draw_path(DrawPathBuildParams {
|
||||
path_build_params: PathBuildParams {
|
||||
path_index,
|
||||
view_box: effective_view_box,
|
||||
built_options: &self.built_options,
|
||||
scene: &self.scene,
|
||||
},
|
||||
paint_metadata: &paint_metadata,
|
||||
built_clip_paths: &built_clip_paths,
|
||||
})
|
||||
});
|
||||
|
||||
self.finish_building(&paint_metadata, built_draw_paths);
|
||||
|
||||
let cpu_build_time = Instant::now() - start_time;
|
||||
self.listener.send(RenderCommand::Finish { cpu_build_time });
|
||||
}
|
||||
|
||||
fn build_clip_path(&self, params: PathBuildParams) -> BuiltPath {
|
||||
let PathBuildParams { path_index, view_box, built_options, scene } = params;
|
||||
let path_object = &scene.clip_paths[path_index];
|
||||
let outline = scene.apply_render_options(path_object.outline(), built_options);
|
||||
|
||||
let mut tiler = Tiler::new(self,
|
||||
&outline,
|
||||
path_object.fill_rule(),
|
||||
view_box,
|
||||
TilingPathInfo::Clip);
|
||||
|
||||
tiler.generate_tiles();
|
||||
self.send_fills(tiler.object_builder.fills);
|
||||
tiler.object_builder.built_path
|
||||
}
|
||||
|
||||
fn build_draw_path(&self, params: DrawPathBuildParams) -> BuiltDrawPath {
|
||||
let DrawPathBuildParams {
|
||||
path_build_params: PathBuildParams { path_index, view_box, built_options, scene },
|
||||
paint_metadata,
|
||||
built_clip_paths,
|
||||
} = params;
|
||||
|
||||
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 paint_metadata = &paint_metadata[paint_id.0 as usize];
|
||||
let built_clip_path = path_object.clip_path().map(|clip_path_id| {
|
||||
&built_clip_paths[clip_path_id.0 as usize]
|
||||
});
|
||||
|
||||
let mut tiler = Tiler::new(self,
|
||||
&outline,
|
||||
path_object.fill_rule(),
|
||||
view_box,
|
||||
TilingPathInfo::Draw(DrawTilingPathInfo {
|
||||
paint_id,
|
||||
paint_metadata,
|
||||
blend_mode: path_object.blend_mode(),
|
||||
built_clip_path,
|
||||
fill_rule: path_object.fill_rule(),
|
||||
}));
|
||||
|
||||
tiler.generate_tiles();
|
||||
self.send_fills(tiler.object_builder.fills);
|
||||
BuiltDrawPath {
|
||||
path: tiler.object_builder.built_path,
|
||||
blend_mode: path_object.blend_mode(),
|
||||
filter: paint_metadata.filter(),
|
||||
color_texture: paint_metadata.tile_batch_texture(),
|
||||
sampling_flags_1: TextureSamplingFlags::empty(),
|
||||
mask_0_fill_rule: path_object.fill_rule(),
|
||||
}
|
||||
}
|
||||
|
||||
fn send_fills(&self, fills: Vec<FillBatchEntry>) {
|
||||
if !fills.is_empty() {
|
||||
self.listener.send(RenderCommand::AddFills(fills));
|
||||
}
|
||||
}
|
||||
|
||||
fn build_clips(&self, built_draw_paths: &[BuiltDrawPath]) {
|
||||
let mut built_clip_tiles = vec![];
|
||||
for built_draw_path in built_draw_paths {
|
||||
for built_clip_tile in &built_draw_path.path.clip_tiles {
|
||||
built_clip_tiles.push(*built_clip_tile);
|
||||
}
|
||||
}
|
||||
|
||||
built_clip_tiles.sort_by_key(|built_clip_tile| built_clip_tile.key);
|
||||
|
||||
let mut batches: Vec<ClipBatch> = vec![];
|
||||
for built_clip_tile in built_clip_tiles {
|
||||
if batches.is_empty() || batches.last_mut().unwrap().key != built_clip_tile.key {
|
||||
batches.push(ClipBatch { key: built_clip_tile.key, clips: vec![] });
|
||||
}
|
||||
batches.last_mut().unwrap().clips.push(built_clip_tile.clip);
|
||||
}
|
||||
|
||||
if !batches.is_empty() {
|
||||
self.listener.send(RenderCommand::ClipTiles(batches));
|
||||
}
|
||||
}
|
||||
|
||||
fn cull_tiles(&self, paint_metadata: &[PaintMetadata], built_draw_paths: Vec<BuiltDrawPath>)
|
||||
-> CulledTiles {
|
||||
let mut culled_tiles = CulledTiles { display_list: vec![] };
|
||||
|
||||
let mut remaining_layer_z_buffers = self.build_solid_tiles(&built_draw_paths);
|
||||
remaining_layer_z_buffers.reverse();
|
||||
|
||||
// Process first Z-buffer.
|
||||
let first_z_buffer = remaining_layer_z_buffers.pop().unwrap();
|
||||
let first_solid_tiles = first_z_buffer.build_solid_tiles(paint_metadata);
|
||||
for batch in first_solid_tiles.batches {
|
||||
culled_tiles.display_list.push(CulledDisplayItem::DrawTiles(batch));
|
||||
}
|
||||
|
||||
let mut layer_z_buffers_stack = vec![first_z_buffer];
|
||||
let mut current_depth = 1;
|
||||
|
||||
for display_item in &self.scene.display_list {
|
||||
match *display_item {
|
||||
DisplayItem::PushRenderTarget(render_target_id) => {
|
||||
culled_tiles.display_list
|
||||
.push(CulledDisplayItem::PushRenderTarget(render_target_id));
|
||||
|
||||
let z_buffer = remaining_layer_z_buffers.pop().unwrap();
|
||||
let solid_tiles = z_buffer.build_solid_tiles(paint_metadata);
|
||||
for batch in solid_tiles.batches {
|
||||
culled_tiles.display_list.push(CulledDisplayItem::DrawTiles(batch));
|
||||
}
|
||||
layer_z_buffers_stack.push(z_buffer);
|
||||
}
|
||||
|
||||
DisplayItem::PopRenderTarget => {
|
||||
culled_tiles.display_list.push(CulledDisplayItem::PopRenderTarget);
|
||||
layer_z_buffers_stack.pop();
|
||||
}
|
||||
|
||||
DisplayItem::DrawPaths {
|
||||
start_index: start_draw_path_index,
|
||||
end_index: end_draw_path_index,
|
||||
} => {
|
||||
for draw_path_index in start_draw_path_index..end_draw_path_index {
|
||||
let built_draw_path = &built_draw_paths[draw_path_index as usize];
|
||||
let layer_z_buffer = layer_z_buffers_stack.last().unwrap();
|
||||
let color_texture = built_draw_path.color_texture;
|
||||
|
||||
debug_assert!(built_draw_path.path.empty_tiles.is_empty() ||
|
||||
built_draw_path.blend_mode.is_destructive());
|
||||
self.add_alpha_tiles(&mut culled_tiles,
|
||||
layer_z_buffer,
|
||||
&built_draw_path.path.empty_tiles,
|
||||
current_depth,
|
||||
None,
|
||||
built_draw_path.blend_mode,
|
||||
built_draw_path.filter);
|
||||
|
||||
self.add_alpha_tiles(&mut culled_tiles,
|
||||
layer_z_buffer,
|
||||
&built_draw_path.path.single_mask_tiles,
|
||||
current_depth,
|
||||
color_texture,
|
||||
built_draw_path.blend_mode,
|
||||
built_draw_path.filter);
|
||||
|
||||
match built_draw_path.path.solid_tiles {
|
||||
SolidTiles::Regular(ref tiles) => {
|
||||
self.add_alpha_tiles(&mut culled_tiles,
|
||||
layer_z_buffer,
|
||||
tiles,
|
||||
current_depth,
|
||||
color_texture,
|
||||
built_draw_path.blend_mode,
|
||||
built_draw_path.filter);
|
||||
}
|
||||
SolidTiles::Occluders(_) => {}
|
||||
}
|
||||
|
||||
current_depth += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
culled_tiles
|
||||
}
|
||||
|
||||
fn build_solid_tiles(&self, built_draw_paths: &[BuiltDrawPath]) -> Vec<ZBuffer> {
|
||||
let effective_view_box = self.scene.effective_view_box(self.built_options);
|
||||
let mut z_buffers = vec![ZBuffer::new(effective_view_box)];
|
||||
let mut z_buffer_index_stack = vec![0];
|
||||
let mut current_depth = 1;
|
||||
|
||||
// Create Z-buffers.
|
||||
for display_item in &self.scene.display_list {
|
||||
match *display_item {
|
||||
DisplayItem::PushRenderTarget { .. } => {
|
||||
z_buffer_index_stack.push(z_buffers.len());
|
||||
z_buffers.push(ZBuffer::new(effective_view_box));
|
||||
}
|
||||
DisplayItem::PopRenderTarget => {
|
||||
z_buffer_index_stack.pop();
|
||||
}
|
||||
DisplayItem::DrawPaths { start_index, end_index } => {
|
||||
let (start_index, end_index) = (start_index as usize, end_index as usize);
|
||||
let z_buffer = &mut z_buffers[*z_buffer_index_stack.last().unwrap()];
|
||||
for (path_subindex, built_draw_path) in
|
||||
built_draw_paths[start_index..end_index].iter().enumerate() {
|
||||
let path_index = (path_subindex + start_index) as u32;
|
||||
let path = &self.scene.paths[path_index as usize];
|
||||
let metadata = DepthMetadata { paint_id: path.paint() };
|
||||
match built_draw_path.path.solid_tiles {
|
||||
SolidTiles::Regular(_) => {
|
||||
z_buffer.update(&[], current_depth, metadata);
|
||||
}
|
||||
SolidTiles::Occluders(ref occluders) => {
|
||||
z_buffer.update(occluders, current_depth, metadata);
|
||||
}
|
||||
}
|
||||
current_depth += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
debug_assert_eq!(z_buffer_index_stack.len(), 1);
|
||||
|
||||
z_buffers
|
||||
}
|
||||
|
||||
fn add_alpha_tiles(&self,
|
||||
culled_tiles: &mut CulledTiles,
|
||||
layer_z_buffer: &ZBuffer,
|
||||
built_alpha_tiles: &[BuiltTile],
|
||||
current_depth: u32,
|
||||
color_texture: Option<TileBatchTexture>,
|
||||
blend_mode: BlendMode,
|
||||
filter: Filter) {
|
||||
let mut batch_indices: Vec<BatchIndex> = vec![];
|
||||
for built_alpha_tile in built_alpha_tiles {
|
||||
// Early cull if possible.
|
||||
let alpha_tile_coords = built_alpha_tile.tile.tile_position();
|
||||
if !layer_z_buffer.test(alpha_tile_coords, current_depth) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find an appropriate batch if we can.
|
||||
let mut dest_batch_index = batch_indices.iter().filter(|&batch_index| {
|
||||
batch_index.tile_page == built_alpha_tile.page
|
||||
}).next().cloned();
|
||||
|
||||
// If no batch was found, try to reuse the last batch in the display list.
|
||||
//
|
||||
// TODO(pcwalton): We could try harder to find a batch by taking tile positions into
|
||||
// account...
|
||||
if dest_batch_index.is_none() {
|
||||
match culled_tiles.display_list.last() {
|
||||
Some(&CulledDisplayItem::DrawTiles(TileBatch {
|
||||
tiles: _,
|
||||
color_texture: ref batch_color_texture,
|
||||
blend_mode: batch_blend_mode,
|
||||
filter: batch_filter,
|
||||
tile_page: batch_tile_page
|
||||
})) if *batch_color_texture == color_texture &&
|
||||
batch_blend_mode == blend_mode &&
|
||||
batch_filter == filter &&
|
||||
!batch_blend_mode.needs_readable_framebuffer() &&
|
||||
batch_tile_page == built_alpha_tile.page => {
|
||||
dest_batch_index = Some(BatchIndex {
|
||||
display_item_index: culled_tiles.display_list.len() - 1,
|
||||
tile_page: batch_tile_page,
|
||||
});
|
||||
batch_indices.push(dest_batch_index.unwrap());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// If it's still the case that no suitable batch was found, then make a new one.
|
||||
if dest_batch_index.is_none() {
|
||||
dest_batch_index = Some(BatchIndex {
|
||||
display_item_index: culled_tiles.display_list.len(),
|
||||
tile_page: built_alpha_tile.page,
|
||||
});
|
||||
batch_indices.push(dest_batch_index.unwrap());
|
||||
culled_tiles.display_list.push(CulledDisplayItem::DrawTiles(TileBatch {
|
||||
tiles: vec![],
|
||||
color_texture,
|
||||
blend_mode,
|
||||
filter,
|
||||
tile_page: built_alpha_tile.page,
|
||||
}));
|
||||
}
|
||||
|
||||
// Add to the appropriate batch.
|
||||
match culled_tiles.display_list[dest_batch_index.unwrap().display_item_index] {
|
||||
CulledDisplayItem::DrawTiles(ref mut tiles) => {
|
||||
tiles.tiles.push(built_alpha_tile.tile);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct BatchIndex {
|
||||
display_item_index: usize,
|
||||
tile_page: u16,
|
||||
}
|
||||
}
|
||||
|
||||
fn pack_tiles(&mut self, culled_tiles: CulledTiles) {
|
||||
self.listener.send(RenderCommand::BeginTileDrawing);
|
||||
for display_item in culled_tiles.display_list {
|
||||
match display_item {
|
||||
CulledDisplayItem::DrawTiles(batch) => {
|
||||
self.listener.send(RenderCommand::DrawTiles(batch))
|
||||
}
|
||||
CulledDisplayItem::PushRenderTarget(render_target_id) => {
|
||||
self.listener.send(RenderCommand::PushRenderTarget(render_target_id))
|
||||
}
|
||||
CulledDisplayItem::PopRenderTarget => {
|
||||
self.listener.send(RenderCommand::PopRenderTarget)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn finish_building(&mut self,
|
||||
paint_metadata: &[PaintMetadata],
|
||||
built_draw_paths: Vec<BuiltDrawPath>) {
|
||||
self.listener.send(RenderCommand::FlushFills);
|
||||
self.build_clips(&built_draw_paths);
|
||||
let culled_tiles = self.cull_tiles(paint_metadata, built_draw_paths);
|
||||
self.pack_tiles(culled_tiles);
|
||||
}
|
||||
|
||||
fn needs_readable_framebuffer(&self) -> bool {
|
||||
let mut framebuffer_nesting = 0;
|
||||
for display_item in &self.scene.display_list {
|
||||
match *display_item {
|
||||
DisplayItem::PushRenderTarget(_) => framebuffer_nesting += 1,
|
||||
DisplayItem::PopRenderTarget => framebuffer_nesting -= 1,
|
||||
DisplayItem::DrawPaths { start_index, end_index } => {
|
||||
if framebuffer_nesting > 0 {
|
||||
continue;
|
||||
}
|
||||
for path_index in start_index..end_index {
|
||||
let blend_mode = self.scene.paths[path_index as usize].blend_mode();
|
||||
if blend_mode.needs_readable_framebuffer() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
struct PathBuildParams<'a> {
|
||||
path_index: usize,
|
||||
view_box: RectF,
|
||||
built_options: &'a PreparedBuildOptions,
|
||||
scene: &'a Scene,
|
||||
}
|
||||
|
||||
struct DrawPathBuildParams<'a> {
|
||||
path_build_params: PathBuildParams<'a>,
|
||||
paint_metadata: &'a [PaintMetadata],
|
||||
built_clip_paths: &'a [BuiltPath],
|
||||
}
|
||||
|
||||
impl BuiltPath {
|
||||
fn new(path_bounds: RectF,
|
||||
view_box_bounds: RectF,
|
||||
fill_rule: FillRule,
|
||||
tiling_path_info: &TilingPathInfo)
|
||||
-> BuiltPath {
|
||||
let occludes = match *tiling_path_info {
|
||||
TilingPathInfo::Draw(ref draw_tiling_path_info) => {
|
||||
draw_tiling_path_info.paint_metadata.is_opaque &&
|
||||
draw_tiling_path_info.blend_mode.occludes_backdrop()
|
||||
}
|
||||
TilingPathInfo::Clip => true,
|
||||
};
|
||||
|
||||
let tile_map_bounds = if tiling_path_info.has_destructive_blend_mode() {
|
||||
view_box_bounds
|
||||
} else {
|
||||
path_bounds
|
||||
};
|
||||
|
||||
BuiltPath {
|
||||
empty_tiles: vec![],
|
||||
single_mask_tiles: vec![],
|
||||
clip_tiles: vec![],
|
||||
solid_tiles: if occludes {
|
||||
SolidTiles::Occluders(vec![])
|
||||
} else {
|
||||
SolidTiles::Regular(vec![])
|
||||
},
|
||||
tiles: DenseTileMap::new(tiles::round_rect_out_to_tile_bounds(tile_map_bounds)),
|
||||
fill_rule,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Occluder {
|
||||
#[inline]
|
||||
pub(crate) fn new(coords: Vector2I) -> Occluder {
|
||||
Occluder { coords }
|
||||
}
|
||||
}
|
||||
|
||||
struct CulledTiles {
|
||||
display_list: Vec<CulledDisplayItem>,
|
||||
}
|
||||
|
||||
enum CulledDisplayItem {
|
||||
DrawTiles(TileBatch),
|
||||
PushRenderTarget(RenderTargetId),
|
||||
PopRenderTarget,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct TileStats {
|
||||
pub solid_tile_count: u32,
|
||||
pub alpha_tile_count: u32,
|
||||
}
|
||||
|
||||
// Utilities for built objects
|
||||
|
||||
impl ObjectBuilder {
|
||||
pub(crate) fn new(path_bounds: RectF,
|
||||
view_box_bounds: RectF,
|
||||
fill_rule: FillRule,
|
||||
tiling_path_info: &TilingPathInfo)
|
||||
-> ObjectBuilder {
|
||||
ObjectBuilder {
|
||||
built_path: BuiltPath::new(path_bounds, view_box_bounds, fill_rule, tiling_path_info),
|
||||
bounds: path_bounds,
|
||||
fills: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn tile_rect(&self) -> RectI {
|
||||
self.built_path.tiles.rect
|
||||
}
|
||||
|
||||
fn add_fill(
|
||||
&mut self,
|
||||
scene_builder: &SceneBuilder,
|
||||
segment: LineSegment2F,
|
||||
tile_coords: Vector2I,
|
||||
) {
|
||||
debug!("add_fill({:?} ({:?}))", segment, tile_coords);
|
||||
|
||||
// Ensure this fill is in bounds. If not, cull it.
|
||||
if self.tile_coords_to_local_index(tile_coords).is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
debug_assert_eq!(TILE_WIDTH, TILE_HEIGHT);
|
||||
|
||||
// Compute the upper left corner of the tile.
|
||||
let tile_size = F32x4::splat(TILE_WIDTH as f32);
|
||||
let tile_upper_left = tile_coords.to_f32().0.to_f32x4().xyxy() * tile_size;
|
||||
|
||||
// Convert to 4.8 fixed point.
|
||||
let segment = (segment.0 - tile_upper_left) * F32x4::splat(256.0);
|
||||
let (min, max) = (F32x4::default(), F32x4::splat((TILE_WIDTH * 256 - 1) as f32));
|
||||
let segment = segment.clamp(min, max).to_i32x4();
|
||||
let (from_x, from_y, to_x, to_y) = (segment[0], segment[1], segment[2], segment[3]);
|
||||
|
||||
// Cull degenerate fills.
|
||||
if from_x == to_x {
|
||||
debug!("... culling!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Allocate a global tile if necessary.
|
||||
let alpha_tile_id = self.get_or_allocate_alpha_tile_index(scene_builder, tile_coords);
|
||||
|
||||
// Pack whole pixels.
|
||||
let px = (segment & I32x4::splat(0xf00)).to_u32x4();
|
||||
let px = (px >> 8).to_i32x4() | (px >> 4).to_i32x4().yxwz();
|
||||
|
||||
// Pack instance data.
|
||||
debug!("... OK, pushing");
|
||||
self.fills.push(FillBatchEntry {
|
||||
page: alpha_tile_id.page(),
|
||||
fill: Fill {
|
||||
px: LineSegmentU4 { from: px[0] as AlignedU8, to: px[2] as AlignedU8 },
|
||||
subpx: LineSegmentU8 {
|
||||
from_x: from_x as u8,
|
||||
from_y: from_y as u8,
|
||||
to_x: to_x as u8,
|
||||
to_y: to_y as u8,
|
||||
},
|
||||
alpha_tile_index: alpha_tile_id.tile() as AlignedU16,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
fn get_or_allocate_alpha_tile_index(
|
||||
&mut self,
|
||||
scene_builder: &SceneBuilder,
|
||||
tile_coords: Vector2I,
|
||||
) -> AlphaTileId {
|
||||
let local_tile_index = self.built_path.tiles.coords_to_index_unchecked(tile_coords);
|
||||
let alpha_tile_id = self.built_path.tiles.data[local_tile_index].alpha_tile_id;
|
||||
if alpha_tile_id.is_valid() {
|
||||
return alpha_tile_id;
|
||||
}
|
||||
|
||||
let alpha_tile_id = AlphaTileId::new(&scene_builder.next_alpha_tile_indices, 0);
|
||||
self.built_path.tiles.data[local_tile_index].alpha_tile_id = alpha_tile_id;
|
||||
alpha_tile_id
|
||||
}
|
||||
|
||||
pub(crate) fn add_active_fill(
|
||||
&mut self,
|
||||
scene_builder: &SceneBuilder,
|
||||
left: f32,
|
||||
right: f32,
|
||||
mut winding: i32,
|
||||
tile_coords: Vector2I,
|
||||
) {
|
||||
let tile_origin_y = (tile_coords.y() * TILE_HEIGHT as i32) as f32;
|
||||
let left = vec2f(left, tile_origin_y);
|
||||
let right = vec2f(right, tile_origin_y);
|
||||
|
||||
let segment = if winding < 0 {
|
||||
LineSegment2F::new(left, right)
|
||||
} else {
|
||||
LineSegment2F::new(right, left)
|
||||
};
|
||||
|
||||
debug!(
|
||||
"... emitting active fill {} -> {} winding {} @ tile {:?}",
|
||||
left.x(),
|
||||
right.x(),
|
||||
winding,
|
||||
tile_coords
|
||||
);
|
||||
|
||||
while winding != 0 {
|
||||
self.add_fill(scene_builder, segment, tile_coords);
|
||||
if winding < 0 {
|
||||
winding += 1
|
||||
} else {
|
||||
winding -= 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn generate_fill_primitives_for_line(
|
||||
&mut self,
|
||||
scene_builder: &SceneBuilder,
|
||||
mut segment: LineSegment2F,
|
||||
tile_y: i32,
|
||||
) {
|
||||
debug!(
|
||||
"... generate_fill_primitives_for_line(): segment={:?} tile_y={} ({}-{})",
|
||||
segment,
|
||||
tile_y,
|
||||
tile_y as f32 * TILE_HEIGHT as f32,
|
||||
(tile_y + 1) as f32 * TILE_HEIGHT as f32
|
||||
);
|
||||
|
||||
let winding = segment.from_x() > segment.to_x();
|
||||
let (segment_left, segment_right) = if !winding {
|
||||
(segment.from_x(), segment.to_x())
|
||||
} else {
|
||||
(segment.to_x(), segment.from_x())
|
||||
};
|
||||
|
||||
let mut subsegment_x = (segment_left as i32 & !(TILE_WIDTH as i32 - 1)) as f32;
|
||||
while subsegment_x < segment_right {
|
||||
let (mut fill_from, mut fill_to) = (segment.from(), segment.to());
|
||||
let subsegment_x_next = subsegment_x + TILE_WIDTH as f32;
|
||||
if subsegment_x_next < segment_right {
|
||||
let x = subsegment_x_next;
|
||||
let point = Vector2F::new(x, segment.solve_y_for_x(x));
|
||||
if !winding {
|
||||
fill_to = point;
|
||||
segment = LineSegment2F::new(point, segment.to());
|
||||
} else {
|
||||
fill_from = point;
|
||||
segment = LineSegment2F::new(segment.from(), point);
|
||||
}
|
||||
}
|
||||
|
||||
let fill_segment = LineSegment2F::new(fill_from, fill_to);
|
||||
let fill_tile_coords = vec2i(subsegment_x as i32 / TILE_WIDTH as i32, tile_y);
|
||||
self.add_fill(scene_builder, fill_segment, fill_tile_coords);
|
||||
|
||||
subsegment_x = subsegment_x_next;
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn tile_coords_to_local_index(&self, coords: Vector2I) -> Option<u32> {
|
||||
self.built_path.tiles.coords_to_index(coords).map(|index| index as u32)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn local_tile_index_to_coords(&self, tile_index: u32) -> Vector2I {
|
||||
self.built_path.tiles.index_to_coords(tile_index as usize)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PackedTile<'a> {
|
||||
pub(crate) fn add_to(&self,
|
||||
tiles: &mut Vec<BuiltTile>,
|
||||
clips: &mut Vec<BuiltClip>,
|
||||
draw_tiling_path_info: &DrawTilingPathInfo,
|
||||
scene_builder: &SceneBuilder) {
|
||||
let draw_tile_page = self.draw_tile.alpha_tile_id.page() as u16;
|
||||
let draw_tile_index = self.draw_tile.alpha_tile_id.tile() as u16;
|
||||
let draw_tile_backdrop = self.draw_tile.backdrop as i8;
|
||||
|
||||
match self.clip_tile {
|
||||
None => {
|
||||
tiles.push(BuiltTile {
|
||||
page: draw_tile_page,
|
||||
tile: Tile::new_alpha(self.tile_coords,
|
||||
draw_tile_index,
|
||||
draw_tile_backdrop,
|
||||
draw_tiling_path_info),
|
||||
});
|
||||
}
|
||||
Some(clip_tile) => {
|
||||
let clip_tile_page = clip_tile.alpha_tile_id.page() as u16;
|
||||
let clip_tile_index = clip_tile.alpha_tile_id.tile() as u16;
|
||||
let clip_tile_backdrop = clip_tile.backdrop;
|
||||
|
||||
let dest_tile_id = AlphaTileId::new(&scene_builder.next_alpha_tile_indices, 1);
|
||||
let dest_tile_page = dest_tile_id.page() as u16;
|
||||
let dest_tile_index = dest_tile_id.tile() as u16;
|
||||
|
||||
clips.push(BuiltClip {
|
||||
clip: Clip::new(dest_tile_index, draw_tile_index, draw_tile_backdrop),
|
||||
key: ClipBatchKey {
|
||||
src_page: draw_tile_page,
|
||||
dest_page: dest_tile_page,
|
||||
kind: ClipBatchKind::Draw,
|
||||
},
|
||||
});
|
||||
clips.push(BuiltClip {
|
||||
clip: Clip::new(dest_tile_index, clip_tile_index, clip_tile_backdrop),
|
||||
key: ClipBatchKey {
|
||||
src_page: clip_tile_page,
|
||||
dest_page: dest_tile_page,
|
||||
kind: ClipBatchKind::Clip,
|
||||
},
|
||||
});
|
||||
tiles.push(BuiltTile {
|
||||
page: dest_tile_page,
|
||||
tile: Tile::new_alpha(self.tile_coords,
|
||||
dest_tile_index,
|
||||
0,
|
||||
draw_tiling_path_info),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Tile {
|
||||
#[inline]
|
||||
fn new_alpha(tile_origin: Vector2I,
|
||||
draw_tile_index: u16,
|
||||
draw_tile_backdrop: i8,
|
||||
draw_tiling_path_info: &DrawTilingPathInfo)
|
||||
-> Tile {
|
||||
let mask_0_uv = calculate_mask_uv(draw_tile_index);
|
||||
|
||||
let mut ctrl = 0;
|
||||
match draw_tiling_path_info.fill_rule {
|
||||
FillRule::EvenOdd => ctrl |= TILE_CTRL_MASK_EVEN_ODD << TILE_CTRL_MASK_0_SHIFT,
|
||||
FillRule::Winding => ctrl |= TILE_CTRL_MASK_WINDING << TILE_CTRL_MASK_0_SHIFT,
|
||||
}
|
||||
|
||||
Tile {
|
||||
tile_x: tile_origin.x() as AlignedI16,
|
||||
tile_y: tile_origin.y() as AlignedI16,
|
||||
mask_0_u: mask_0_uv.x() as AlignedU8,
|
||||
mask_0_v: mask_0_uv.y() as AlignedU8,
|
||||
mask_0_backdrop: draw_tile_backdrop as AlignedI8,
|
||||
ctrl: ctrl as AlignedU16,
|
||||
pad: 0,
|
||||
color: draw_tiling_path_info.paint_id.0 as AlignedU16,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn tile_position(&self) -> Vector2I {
|
||||
vec2i(self.tile_x as i32, self.tile_y as i32)
|
||||
}
|
||||
}
|
||||
|
||||
impl Clip {
|
||||
#[inline]
|
||||
fn new(dest_tile_index: u16, src_tile_index: u16, src_backdrop: i8) -> Clip {
|
||||
let dest_uv = calculate_mask_uv(dest_tile_index);
|
||||
let src_uv = calculate_mask_uv(src_tile_index);
|
||||
Clip {
|
||||
dest_u: dest_uv.x() as AlignedU8,
|
||||
dest_v: dest_uv.y() as AlignedU8,
|
||||
src_u: src_uv.x() as AlignedU8,
|
||||
src_v: src_uv.y() as AlignedU8,
|
||||
backdrop: src_backdrop as AlignedI8,
|
||||
pad_0: 0,
|
||||
pad_1: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_mask_uv(tile_index: u16) -> Vector2I {
|
||||
debug_assert_eq!(MASK_TILES_ACROSS, MASK_TILES_DOWN);
|
||||
let mask_u = tile_index as i32 % MASK_TILES_ACROSS as i32;
|
||||
let mask_v = tile_index as i32 / MASK_TILES_ACROSS as i32;
|
||||
vec2i(mask_u, mask_v)
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
// pathfinder/renderer/src/concurrent/executor.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.
|
||||
|
||||
//! An abstraction over threading and parallelism systems such as Rayon.
|
||||
|
||||
/// An abstraction over threading and parallelism systems such as Rayon.
|
||||
pub trait Executor {
|
||||
/// Like the Rayon snippet:
|
||||
///
|
||||
/// ```norun
|
||||
/// (0..length).into_par_iter().map(builder).collect()
|
||||
/// ```
|
||||
fn build_vector<T, F>(&self, length: usize, builder: F) -> Vec<T>
|
||||
where T: Send, F: Fn(usize) -> T + Send + Sync;
|
||||
}
|
||||
|
||||
pub struct SequentialExecutor;
|
||||
|
||||
impl Executor for SequentialExecutor {
|
||||
fn build_vector<T, F>(&self, length: usize, builder: F) -> Vec<T>
|
||||
where T: Send, F: Fn(usize) -> T + Send + Sync {
|
||||
(0..length).into_iter().map(builder).collect()
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
// pathfinder/renderer/src/concurrent/mod.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.
|
||||
|
||||
//! Threading and concurrency support.
|
||||
|
||||
pub mod executor;
|
||||
pub mod rayon;
|
||||
pub mod scene_proxy;
|
|
@ -1,23 +0,0 @@
|
|||
// pathfinder/renderer/src/concurrent/rayon.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.
|
||||
|
||||
//! An implementation of the executor using the Rayon library.
|
||||
|
||||
use crate::concurrent::executor::Executor;
|
||||
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||
|
||||
pub struct RayonExecutor;
|
||||
|
||||
impl Executor for RayonExecutor {
|
||||
fn build_vector<T, F>(&self, length: usize, builder: F) -> Vec<T>
|
||||
where T: Send, F: Fn(usize) -> T + Send + Sync {
|
||||
(0..length).into_par_iter().map(builder).collect()
|
||||
}
|
||||
}
|
|
@ -1,151 +0,0 @@
|
|||
// pathfinder/renderer/src/concurrent/scene_proxy.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.
|
||||
|
||||
//! A version of `Scene` that proxies all method calls out to a separate
|
||||
//! thread.
|
||||
//!
|
||||
//! This is useful for:
|
||||
//!
|
||||
//! * Avoiding GPU driver stalls on synchronous APIs such as OpenGL.
|
||||
//!
|
||||
//! * Avoiding UI latency by building scenes off the main thread.
|
||||
//!
|
||||
//! You don't need to use this API to use Pathfinder; it's only a convenience.
|
||||
|
||||
use crate::concurrent::executor::Executor;
|
||||
use crate::gpu::renderer::Renderer;
|
||||
use crate::gpu_data::RenderCommand;
|
||||
use crate::options::{BuildOptions, RenderCommandListener};
|
||||
use crate::scene::Scene;
|
||||
use crossbeam_channel::{self, Receiver, Sender};
|
||||
use pathfinder_geometry::rect::RectF;
|
||||
use pathfinder_gpu::Device;
|
||||
use std::thread;
|
||||
|
||||
const MAX_MESSAGES_IN_FLIGHT: usize = 1024;
|
||||
|
||||
pub struct SceneProxy {
|
||||
sender: Sender<MainToWorkerMsg>,
|
||||
}
|
||||
|
||||
impl SceneProxy {
|
||||
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) =
|
||||
crossbeam_channel::bounded(MAX_MESSAGES_IN_FLIGHT);
|
||||
thread::spawn(move || scene_thread(scene, executor, main_to_worker_receiver));
|
||||
SceneProxy { sender: main_to_worker_sender }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn replace_scene(&self, new_scene: Scene) {
|
||||
self.sender.send(MainToWorkerMsg::ReplaceScene(new_scene)).unwrap();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_view_box(&self, new_view_box: RectF) {
|
||||
self.sender.send(MainToWorkerMsg::SetViewBox(new_view_box)).unwrap();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn build_with_listener(&self,
|
||||
options: BuildOptions,
|
||||
listener: Box<dyn RenderCommandListener>) {
|
||||
self.sender.send(MainToWorkerMsg::Build(options, listener)).unwrap();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn build_with_stream(&self, options: BuildOptions) -> RenderCommandStream {
|
||||
let (sender, receiver) = crossbeam_channel::bounded(MAX_MESSAGES_IN_FLIGHT);
|
||||
let listener = Box::new(move |command| drop(sender.send(command)));
|
||||
self.build_with_listener(options, listener);
|
||||
RenderCommandStream::new(receiver)
|
||||
}
|
||||
|
||||
/// A convenience method to build a scene and send the resulting commands
|
||||
/// to the given renderer.
|
||||
///
|
||||
/// Exactly equivalent to:
|
||||
///
|
||||
/// ```norun
|
||||
/// for command in scene_proxy.build_with_stream(options) {
|
||||
/// renderer.render_command(&command)
|
||||
/// }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn build_and_render<D>(&self, renderer: &mut Renderer<D>, build_options: BuildOptions)
|
||||
where D: Device {
|
||||
renderer.begin_scene();
|
||||
for command in self.build_with_stream(build_options) {
|
||||
renderer.render_command(&command);
|
||||
}
|
||||
renderer.end_scene();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn copy_scene(&self) -> Scene {
|
||||
let (sender, receiver) = crossbeam_channel::bounded(MAX_MESSAGES_IN_FLIGHT);
|
||||
self.sender.send(MainToWorkerMsg::CopyScene(sender)).unwrap();
|
||||
receiver.recv().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn scene_thread<E>(mut scene: Scene,
|
||||
executor: E,
|
||||
main_to_worker_receiver: Receiver<MainToWorkerMsg>)
|
||||
where E: Executor {
|
||||
while let Ok(msg) = main_to_worker_receiver.recv() {
|
||||
match msg {
|
||||
MainToWorkerMsg::ReplaceScene(new_scene) => scene = new_scene,
|
||||
MainToWorkerMsg::CopyScene(sender) => sender.send(scene.clone()).unwrap(),
|
||||
MainToWorkerMsg::SetViewBox(new_view_box) => scene.set_view_box(new_view_box),
|
||||
MainToWorkerMsg::Build(options, listener) => scene.build(options, listener, &executor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum MainToWorkerMsg {
|
||||
ReplaceScene(Scene),
|
||||
CopyScene(Sender<Scene>),
|
||||
SetViewBox(RectF),
|
||||
Build(BuildOptions, Box<dyn RenderCommandListener>),
|
||||
}
|
||||
|
||||
pub struct RenderCommandStream {
|
||||
receiver: Receiver<RenderCommand>,
|
||||
done: bool,
|
||||
}
|
||||
|
||||
impl RenderCommandStream {
|
||||
fn new(receiver: Receiver<RenderCommand>) -> RenderCommandStream {
|
||||
RenderCommandStream { receiver, done: false }
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for RenderCommandStream {
|
||||
type Item = RenderCommand;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<RenderCommand> {
|
||||
if self.done {
|
||||
None
|
||||
} else {
|
||||
let command = self.receiver.recv().unwrap();
|
||||
if let RenderCommand::Finish { .. } = command {
|
||||
self.done = true;
|
||||
}
|
||||
Some(command)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,217 +0,0 @@
|
|||
// pathfinder/renderer/src/gpu/debug.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.
|
||||
|
||||
//! A debug overlay.
|
||||
//!
|
||||
//! We don't render the demo UI text using Pathfinder itself so that we can use the debug UI to
|
||||
//! debug Pathfinder if it's totally busted.
|
||||
//!
|
||||
//! The debug font atlas was generated using: https://evanw.github.io/font-texture-generator/
|
||||
|
||||
use crate::gpu::renderer::{RenderStats, RenderTime};
|
||||
use pathfinder_geometry::vector::{Vector2I, vec2i};
|
||||
use pathfinder_geometry::rect::RectI;
|
||||
use pathfinder_gpu::Device;
|
||||
use pathfinder_resources::ResourceLoader;
|
||||
use pathfinder_ui::{FONT_ASCENT, LINE_HEIGHT, PADDING, UIPresenter, WINDOW_COLOR};
|
||||
use std::collections::VecDeque;
|
||||
use std::ops::{Add, Div};
|
||||
use std::time::Duration;
|
||||
|
||||
const SAMPLE_BUFFER_SIZE: usize = 60;
|
||||
|
||||
const STATS_WINDOW_WIDTH: i32 = 325;
|
||||
const STATS_WINDOW_HEIGHT: i32 = LINE_HEIGHT * 4 + PADDING + 2;
|
||||
|
||||
const PERFORMANCE_WINDOW_WIDTH: i32 = 400;
|
||||
const PERFORMANCE_WINDOW_HEIGHT: i32 = LINE_HEIGHT * 4 + PADDING + 2;
|
||||
|
||||
pub struct DebugUIPresenter<D>
|
||||
where
|
||||
D: Device,
|
||||
{
|
||||
pub ui_presenter: UIPresenter<D>,
|
||||
cpu_samples: SampleBuffer<RenderStats>,
|
||||
gpu_samples: SampleBuffer<RenderTime>,
|
||||
}
|
||||
|
||||
impl<D> DebugUIPresenter<D>
|
||||
where
|
||||
D: Device,
|
||||
{
|
||||
pub fn new(
|
||||
device: &D,
|
||||
resources: &dyn ResourceLoader,
|
||||
framebuffer_size: Vector2I,
|
||||
) -> DebugUIPresenter<D> {
|
||||
let ui_presenter = UIPresenter::new(device, resources, framebuffer_size);
|
||||
DebugUIPresenter {
|
||||
ui_presenter,
|
||||
cpu_samples: SampleBuffer::new(),
|
||||
gpu_samples: SampleBuffer::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_sample(&mut self, stats: RenderStats, rendering_time: RenderTime) {
|
||||
self.cpu_samples.push(stats);
|
||||
self.gpu_samples.push(rendering_time);
|
||||
}
|
||||
|
||||
pub fn draw(&self, device: &D) {
|
||||
self.draw_stats_window(device);
|
||||
self.draw_performance_window(device);
|
||||
}
|
||||
|
||||
fn draw_stats_window(&self, device: &D) {
|
||||
let framebuffer_size = self.ui_presenter.framebuffer_size();
|
||||
let bottom = framebuffer_size.y() - PADDING;
|
||||
let window_rect = RectI::new(
|
||||
vec2i(framebuffer_size.x() - PADDING - STATS_WINDOW_WIDTH,
|
||||
bottom - PERFORMANCE_WINDOW_HEIGHT - PADDING - STATS_WINDOW_HEIGHT),
|
||||
vec2i(STATS_WINDOW_WIDTH, STATS_WINDOW_HEIGHT),
|
||||
);
|
||||
|
||||
self.ui_presenter.draw_solid_rounded_rect(device, window_rect, WINDOW_COLOR);
|
||||
|
||||
let mean_cpu_sample = self.cpu_samples.mean();
|
||||
let origin = window_rect.origin() + vec2i(PADDING, PADDING + FONT_ASCENT);
|
||||
self.ui_presenter.draw_text(
|
||||
device,
|
||||
&format!("Paths: {}", mean_cpu_sample.path_count),
|
||||
origin,
|
||||
false,
|
||||
);
|
||||
self.ui_presenter.draw_text(
|
||||
device,
|
||||
&format!("Solid Tiles: {}", mean_cpu_sample.solid_tile_count),
|
||||
origin + vec2i(0, LINE_HEIGHT * 1),
|
||||
false,
|
||||
);
|
||||
self.ui_presenter.draw_text(
|
||||
device,
|
||||
&format!("Alpha Tiles: {}", mean_cpu_sample.alpha_tile_count),
|
||||
origin + vec2i(0, LINE_HEIGHT * 2),
|
||||
false,
|
||||
);
|
||||
self.ui_presenter.draw_text(
|
||||
device,
|
||||
&format!("Fills: {}", mean_cpu_sample.fill_count),
|
||||
origin + vec2i(0, LINE_HEIGHT * 3),
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
fn draw_performance_window(&self, device: &D) {
|
||||
let framebuffer_size = self.ui_presenter.framebuffer_size();
|
||||
let bottom = framebuffer_size.y() - PADDING;
|
||||
let window_rect = RectI::new(
|
||||
vec2i(framebuffer_size.x() - PADDING - PERFORMANCE_WINDOW_WIDTH,
|
||||
bottom - PERFORMANCE_WINDOW_HEIGHT),
|
||||
vec2i(PERFORMANCE_WINDOW_WIDTH, PERFORMANCE_WINDOW_HEIGHT),
|
||||
);
|
||||
|
||||
self.ui_presenter.draw_solid_rounded_rect(device, window_rect, WINDOW_COLOR);
|
||||
|
||||
let mean_cpu_sample = self.cpu_samples.mean();
|
||||
let origin = window_rect.origin() + vec2i(PADDING, PADDING + FONT_ASCENT);
|
||||
self.ui_presenter.draw_text(
|
||||
device,
|
||||
&format!("CPU: {:.3} ms", duration_to_ms(mean_cpu_sample.cpu_build_time)),
|
||||
origin,
|
||||
false,
|
||||
);
|
||||
|
||||
let mean_gpu_sample = self.gpu_samples.mean();
|
||||
self.ui_presenter.draw_text(
|
||||
device,
|
||||
&format!("GPU: {:.3} ms", duration_to_ms(mean_gpu_sample.gpu_time)),
|
||||
origin + vec2i(0, LINE_HEIGHT * 1),
|
||||
false,
|
||||
);
|
||||
|
||||
let wallclock_time = f64::max(duration_to_ms(mean_gpu_sample.gpu_time),
|
||||
duration_to_ms(mean_cpu_sample.cpu_build_time));
|
||||
self.ui_presenter.draw_text(
|
||||
device,
|
||||
&format!("Wallclock: {:.3} ms", wallclock_time),
|
||||
origin + vec2i(0, LINE_HEIGHT * 3),
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct SampleBuffer<S>
|
||||
where
|
||||
S: Add<S, Output = S> + Div<usize, Output = S> + Clone + Default,
|
||||
{
|
||||
samples: VecDeque<S>,
|
||||
}
|
||||
|
||||
impl<S> SampleBuffer<S>
|
||||
where
|
||||
S: Add<S, Output = S> + Div<usize, Output = S> + Clone + Default,
|
||||
{
|
||||
fn new() -> SampleBuffer<S> {
|
||||
SampleBuffer {
|
||||
samples: VecDeque::with_capacity(SAMPLE_BUFFER_SIZE),
|
||||
}
|
||||
}
|
||||
|
||||
fn push(&mut self, time: S) {
|
||||
self.samples.push_back(time);
|
||||
while self.samples.len() > SAMPLE_BUFFER_SIZE {
|
||||
self.samples.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
fn mean(&self) -> S {
|
||||
let mut mean = Default::default();
|
||||
if self.samples.is_empty() {
|
||||
return mean;
|
||||
}
|
||||
|
||||
for time in &self.samples {
|
||||
mean = mean + (*time).clone();
|
||||
}
|
||||
|
||||
mean / self.samples.len()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
struct CPUSample {
|
||||
elapsed: Duration,
|
||||
stats: RenderStats,
|
||||
}
|
||||
|
||||
impl Add<CPUSample> for CPUSample {
|
||||
type Output = CPUSample;
|
||||
fn add(self, other: CPUSample) -> CPUSample {
|
||||
CPUSample {
|
||||
elapsed: self.elapsed + other.elapsed,
|
||||
stats: self.stats + other.stats,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<usize> for CPUSample {
|
||||
type Output = CPUSample;
|
||||
fn div(self, divisor: usize) -> CPUSample {
|
||||
CPUSample {
|
||||
elapsed: self.elapsed / (divisor as u32),
|
||||
stats: self.stats / divisor,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn duration_to_ms(time: Duration) -> f64 {
|
||||
time.as_secs() as f64 * 1000.0 + time.subsec_nanos() as f64 / 1000000.0
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
// pathfinder/renderer/src/gpu/mod.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.
|
||||
|
||||
//! The GPU renderer for Pathfinder 3.
|
||||
|
||||
pub mod debug;
|
||||
pub mod options;
|
||||
pub mod renderer;
|
||||
|
||||
pub(crate) mod shaders;
|
|
@ -1,58 +0,0 @@
|
|||
// pathfinder/renderer/src/gpu/options.rs
|
||||
//
|
||||
// Copyright © 2019 The Pathfinder Project Developers.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use pathfinder_color::ColorF;
|
||||
use pathfinder_geometry::rect::RectI;
|
||||
use pathfinder_geometry::vector::Vector2I;
|
||||
use pathfinder_gpu::Device;
|
||||
|
||||
/// Options that influence rendering.
|
||||
#[derive(Default)]
|
||||
pub struct RendererOptions {
|
||||
pub background_color: Option<ColorF>,
|
||||
pub no_compute: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum DestFramebuffer<D> where D: Device {
|
||||
Default {
|
||||
viewport: RectI,
|
||||
window_size: Vector2I,
|
||||
},
|
||||
Other(D::Framebuffer),
|
||||
}
|
||||
|
||||
impl<D> Default for DestFramebuffer<D> where D: Device {
|
||||
#[inline]
|
||||
fn default() -> DestFramebuffer<D> {
|
||||
DestFramebuffer::Default { viewport: RectI::default(), window_size: Vector2I::default() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> DestFramebuffer<D>
|
||||
where
|
||||
D: Device,
|
||||
{
|
||||
#[inline]
|
||||
pub fn full_window(window_size: Vector2I) -> DestFramebuffer<D> {
|
||||
let viewport = RectI::new(Vector2I::default(), window_size);
|
||||
DestFramebuffer::Default { viewport, window_size }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn window_size(&self, device: &D) -> Vector2I {
|
||||
match *self {
|
||||
DestFramebuffer::Default { window_size, .. } => window_size,
|
||||
DestFramebuffer::Other(ref framebuffer) => {
|
||||
device.texture_size(device.framebuffer_texture(framebuffer))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,648 +0,0 @@
|
|||
// pathfinder/renderer/src/gpu/shaders.rs
|
||||
//
|
||||
// Copyright © 2020 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::options::RendererOptions;
|
||||
use crate::gpu::renderer::{MASK_TILES_ACROSS, MASK_TILES_DOWN};
|
||||
use crate::tiles::{TILE_HEIGHT, TILE_WIDTH};
|
||||
use pathfinder_gpu::{BufferTarget, BufferUploadMode, ComputeDimensions, Device, FeatureLevel, VertexAttrClass};
|
||||
use pathfinder_gpu::{VertexAttrDescriptor, VertexAttrType, VertexBufferDescriptor};
|
||||
use pathfinder_gpu::{ALIGNED_I16_ATTR, ALIGNED_I8_ATTR, ALIGNED_U8_ATTR, ALIGNED_U16_ATTR};
|
||||
use pathfinder_resources::ResourceLoader;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
pub const MAX_FILLS_PER_BATCH: usize = 0x10000;
|
||||
pub const MAX_TILES_PER_BATCH: usize = MASK_TILES_ACROSS as usize * MASK_TILES_DOWN as usize;
|
||||
|
||||
pub struct BlitVertexArray<D> where D: Device {
|
||||
pub vertex_array: D::VertexArray,
|
||||
}
|
||||
|
||||
impl<D> BlitVertexArray<D> where D: Device {
|
||||
pub fn new(device: &D,
|
||||
blit_program: &BlitProgram<D>,
|
||||
quad_vertex_positions_buffer: &D::Buffer,
|
||||
quad_vertex_indices_buffer: &D::Buffer)
|
||||
-> BlitVertexArray<D> {
|
||||
let vertex_array = device.create_vertex_array();
|
||||
let position_attrs= &[
|
||||
device.get_vertex_attr(&blit_program.program, "Position").unwrap(),
|
||||
];
|
||||
|
||||
static POSITION_BUFFER: Lazy<VertexBufferDescriptor> = Lazy::new(|| {
|
||||
let mut descriptor = VertexBufferDescriptor{
|
||||
index: 0,
|
||||
divisor: 0,
|
||||
vertex_attrs: vec![
|
||||
VertexAttrDescriptor::datatype_only(VertexAttrClass::Int, ALIGNED_I16_ATTR, 2),
|
||||
]
|
||||
};
|
||||
descriptor.update_attrs();
|
||||
descriptor
|
||||
});
|
||||
|
||||
device.bind_buffer(&vertex_array, quad_vertex_positions_buffer, BufferTarget::Vertex);
|
||||
POSITION_BUFFER.configure_vertex_attrs(device, &vertex_array, position_attrs);
|
||||
device.bind_buffer(&vertex_array, quad_vertex_indices_buffer, BufferTarget::Index);
|
||||
|
||||
BlitVertexArray { vertex_array }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ClearVertexArray<D> where D: Device {
|
||||
pub vertex_array: D::VertexArray,
|
||||
}
|
||||
|
||||
impl<D> ClearVertexArray<D> where D: Device {
|
||||
pub fn new(device: &D,
|
||||
clear_program: &ClearProgram<D>,
|
||||
quad_vertex_positions_buffer: &D::Buffer,
|
||||
quad_vertex_indices_buffer: &D::Buffer)
|
||||
-> ClearVertexArray<D> {
|
||||
let vertex_array = device.create_vertex_array();
|
||||
let position_attrs= &[
|
||||
device.get_vertex_attr(&clear_program.program, "Position").unwrap(),
|
||||
];
|
||||
|
||||
static POSITION_BUFFER: Lazy<VertexBufferDescriptor> = Lazy::new(|| {
|
||||
let mut descriptor = VertexBufferDescriptor{
|
||||
index: 0,
|
||||
divisor: 0,
|
||||
vertex_attrs: vec![
|
||||
VertexAttrDescriptor::datatype_only(VertexAttrClass::Int, ALIGNED_I16_ATTR, 2),
|
||||
]
|
||||
};
|
||||
descriptor.update_attrs();
|
||||
descriptor
|
||||
});
|
||||
|
||||
device.bind_buffer(&vertex_array, quad_vertex_positions_buffer, BufferTarget::Vertex);
|
||||
POSITION_BUFFER.configure_vertex_attrs(device, &vertex_array, position_attrs);
|
||||
device.bind_buffer(&vertex_array, quad_vertex_indices_buffer, BufferTarget::Index);
|
||||
|
||||
ClearVertexArray { vertex_array }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FillVertexArray<D> where D: Device {
|
||||
pub vertex_array: D::VertexArray,
|
||||
}
|
||||
|
||||
impl<D> FillVertexArray<D>
|
||||
where
|
||||
D: Device,
|
||||
{
|
||||
pub fn new(
|
||||
device: &D,
|
||||
fill_program: &FillRasterProgram<D>,
|
||||
vertex_buffer: &D::Buffer,
|
||||
quad_vertex_positions_buffer: &D::Buffer,
|
||||
quad_vertex_indices_buffer: &D::Buffer,
|
||||
) -> FillVertexArray<D> {
|
||||
let vertex_array = device.create_vertex_array();
|
||||
|
||||
let tess_coord_attrs= &[
|
||||
device.get_vertex_attr(&fill_program.program, "TessCoord").unwrap()
|
||||
];
|
||||
|
||||
static TESS_COORD_BUFFER: Lazy<VertexBufferDescriptor> = Lazy::new(|| {
|
||||
let mut descriptor = VertexBufferDescriptor{
|
||||
index: 0,
|
||||
divisor: 0,
|
||||
vertex_attrs: vec![
|
||||
VertexAttrDescriptor::datatype_only(VertexAttrClass::Int, ALIGNED_U16_ATTR, 2),
|
||||
]
|
||||
};
|
||||
descriptor.update_attrs();
|
||||
descriptor
|
||||
});
|
||||
|
||||
let fill_attrs= &[
|
||||
device.get_vertex_attr(&fill_program.program, "FromSubpx").unwrap(),
|
||||
device.get_vertex_attr(&fill_program.program, "ToSubpx").unwrap(),
|
||||
device.get_vertex_attr(&fill_program.program, "FromPx").unwrap(),
|
||||
device.get_vertex_attr(&fill_program.program, "ToPx").unwrap(),
|
||||
device.get_vertex_attr(&fill_program.program, "TileIndex").unwrap(),
|
||||
];
|
||||
|
||||
static FILL_BUFFER: Lazy<VertexBufferDescriptor> = Lazy::new(|| {
|
||||
let mut descriptor = VertexBufferDescriptor{
|
||||
index: 1,
|
||||
divisor: 1,
|
||||
vertex_attrs: vec![
|
||||
VertexAttrDescriptor::datatype_only(VertexAttrClass::FloatNorm, VertexAttrType::U8, 2),
|
||||
VertexAttrDescriptor::datatype_only(VertexAttrClass::FloatNorm, VertexAttrType::U8, 2),
|
||||
VertexAttrDescriptor::datatype_only(VertexAttrClass::Int, ALIGNED_U8_ATTR, 1),
|
||||
VertexAttrDescriptor::datatype_only(VertexAttrClass::Int, ALIGNED_U8_ATTR, 1),
|
||||
VertexAttrDescriptor::datatype_only(VertexAttrClass::Int, ALIGNED_U16_ATTR, 1),
|
||||
]
|
||||
};
|
||||
descriptor.update_attrs();
|
||||
descriptor
|
||||
});
|
||||
|
||||
device.bind_buffer(&vertex_array, quad_vertex_positions_buffer, BufferTarget::Vertex);
|
||||
TESS_COORD_BUFFER.configure_vertex_attrs(device, &vertex_array, tess_coord_attrs);
|
||||
device.bind_buffer(&vertex_array, &vertex_buffer, BufferTarget::Vertex);
|
||||
FILL_BUFFER.configure_vertex_attrs(device, &vertex_array, fill_attrs);
|
||||
device.bind_buffer(&vertex_array, quad_vertex_indices_buffer, BufferTarget::Index);
|
||||
|
||||
FillVertexArray { vertex_array }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TileVertexArray<D> where D: Device {
|
||||
pub vertex_array: D::VertexArray,
|
||||
}
|
||||
|
||||
impl<D> TileVertexArray<D> where D: Device {
|
||||
pub fn new(device: &D,
|
||||
tile_program: &TileProgram<D>,
|
||||
tile_vertex_buffer: &D::Buffer,
|
||||
quad_vertex_positions_buffer: &D::Buffer,
|
||||
quad_vertex_indices_buffer: &D::Buffer)
|
||||
-> TileVertexArray<D> {
|
||||
let vertex_array = device.create_vertex_array();
|
||||
|
||||
let tile_offset_attrs = &[
|
||||
device.get_vertex_attr(&tile_program.program, "TileOffset").unwrap(),
|
||||
];
|
||||
|
||||
static TILE_OFFSET_BUFFER: Lazy<VertexBufferDescriptor> = Lazy::new(|| {
|
||||
let mut descriptor = VertexBufferDescriptor{
|
||||
index: 0,
|
||||
divisor: 0,
|
||||
vertex_attrs: vec![
|
||||
VertexAttrDescriptor::datatype_only(VertexAttrClass::Int, ALIGNED_U16_ATTR, 2),
|
||||
]
|
||||
};
|
||||
descriptor.update_attrs();
|
||||
descriptor
|
||||
});
|
||||
|
||||
let tile_buffer_attrs = &[
|
||||
device.get_vertex_attr(&tile_program.program, "TileOrigin").unwrap(),
|
||||
device.get_vertex_attr(&tile_program.program, "MaskTexCoord0").unwrap(),
|
||||
device.get_vertex_attr(&tile_program.program, "MaskBackdrop").unwrap(),
|
||||
device.get_vertex_attr(&tile_program.program, "Color").unwrap(),
|
||||
device.get_vertex_attr(&tile_program.program, "TileCtrl").unwrap(),
|
||||
];
|
||||
|
||||
static TILE_BUFFER: Lazy<VertexBufferDescriptor> = Lazy::new(|| {
|
||||
let mut descriptor = VertexBufferDescriptor{
|
||||
index: 1,
|
||||
divisor: 1,
|
||||
vertex_attrs: vec![
|
||||
VertexAttrDescriptor::datatype_only(VertexAttrClass::Int, ALIGNED_I16_ATTR, 2),
|
||||
VertexAttrDescriptor::datatype_only(VertexAttrClass::Int, ALIGNED_U8_ATTR, 2),
|
||||
VertexAttrDescriptor::datatype_only(VertexAttrClass::Int, ALIGNED_I8_ATTR, 2),
|
||||
VertexAttrDescriptor::datatype_only(VertexAttrClass::Int, ALIGNED_I16_ATTR, 1),
|
||||
VertexAttrDescriptor::datatype_only(VertexAttrClass::Int, ALIGNED_I16_ATTR, 1),
|
||||
]
|
||||
};
|
||||
descriptor.update_attrs();
|
||||
descriptor
|
||||
|
||||
});
|
||||
|
||||
device.bind_buffer(&vertex_array, quad_vertex_positions_buffer, BufferTarget::Vertex);
|
||||
TILE_OFFSET_BUFFER.configure_vertex_attrs(device, &vertex_array, tile_offset_attrs);
|
||||
device.bind_buffer(&vertex_array, tile_vertex_buffer, BufferTarget::Vertex);
|
||||
TILE_BUFFER.configure_vertex_attrs(device, &vertex_array, tile_buffer_attrs);
|
||||
device.bind_buffer(&vertex_array, quad_vertex_indices_buffer, BufferTarget::Index);
|
||||
|
||||
TileVertexArray { vertex_array }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CopyTileVertexArray<D> where D: Device {
|
||||
pub vertex_array: D::VertexArray,
|
||||
}
|
||||
|
||||
impl<D> CopyTileVertexArray<D> where D: Device {
|
||||
pub fn new(
|
||||
device: &D,
|
||||
copy_tile_program: &CopyTileProgram<D>,
|
||||
copy_tile_vertex_buffer: &D::Buffer,
|
||||
quads_vertex_indices_buffer: &D::Buffer,
|
||||
) -> CopyTileVertexArray<D> {
|
||||
let vertex_array = device.create_vertex_array();
|
||||
|
||||
let copy_tile_attrs = &[
|
||||
device.get_vertex_attr(©_tile_program.program, "TilePosition").unwrap(),
|
||||
];
|
||||
|
||||
static COPY_TILE_BUFFER: Lazy<VertexBufferDescriptor> = Lazy::new(|| {
|
||||
let mut descriptor = VertexBufferDescriptor {
|
||||
index: 0,
|
||||
divisor: 0,
|
||||
vertex_attrs: vec![
|
||||
VertexAttrDescriptor::datatype_only(VertexAttrClass::Int, ALIGNED_I16_ATTR, 2),
|
||||
],
|
||||
};
|
||||
descriptor.update_attrs();
|
||||
descriptor
|
||||
});
|
||||
|
||||
device.bind_buffer(&vertex_array, copy_tile_vertex_buffer, BufferTarget::Vertex);
|
||||
COPY_TILE_BUFFER.configure_vertex_attrs(device, &vertex_array, copy_tile_attrs);
|
||||
device.bind_buffer(&vertex_array, quads_vertex_indices_buffer, BufferTarget::Index);
|
||||
|
||||
CopyTileVertexArray { vertex_array }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ClipTileVertexArray<D> where D: Device {
|
||||
pub vertex_array: D::VertexArray,
|
||||
pub vertex_buffer: D::Buffer,
|
||||
}
|
||||
|
||||
impl<D> ClipTileVertexArray<D> where D: Device {
|
||||
pub fn new(device: &D,
|
||||
clip_tile_program: &ClipTileProgram<D>,
|
||||
quad_vertex_positions_buffer: &D::Buffer,
|
||||
quad_vertex_indices_buffer: &D::Buffer)
|
||||
-> ClipTileVertexArray<D> {
|
||||
let vertex_array = device.create_vertex_array();
|
||||
let vertex_buffer = device.create_buffer(BufferUploadMode::Dynamic);
|
||||
let tile_offset_attrs = &[
|
||||
device.get_vertex_attr(&clip_tile_program.program, "TileOffset").unwrap(),
|
||||
];
|
||||
|
||||
static TILE_OFFSET_BUFFER: Lazy<VertexBufferDescriptor> = Lazy::new(|| {
|
||||
let mut descriptor = VertexBufferDescriptor{
|
||||
index: 0,
|
||||
divisor: 0,
|
||||
vertex_attrs: vec![
|
||||
VertexAttrDescriptor::datatype_only(VertexAttrClass::Int, ALIGNED_I16_ATTR, 2),
|
||||
],
|
||||
};
|
||||
descriptor.update_attrs();
|
||||
descriptor
|
||||
});
|
||||
|
||||
let clip_tile_attrs = &[
|
||||
device.get_vertex_attr(&clip_tile_program.program, "DestTileOrigin").unwrap(),
|
||||
device.get_vertex_attr(&clip_tile_program.program, "SrcTileOrigin").unwrap(),
|
||||
device.get_vertex_attr(&clip_tile_program.program, "SrcBackdrop").unwrap(),
|
||||
];
|
||||
|
||||
static CLIP_TILE_BUFFER: Lazy<VertexBufferDescriptor> = Lazy::new(|| {
|
||||
let mut descriptor = VertexBufferDescriptor{
|
||||
index: 1,
|
||||
divisor: 1,
|
||||
vertex_attrs: vec![
|
||||
VertexAttrDescriptor::datatype_only(VertexAttrClass::Int, ALIGNED_U8_ATTR, 2),
|
||||
VertexAttrDescriptor::datatype_only(VertexAttrClass::Int, ALIGNED_U8_ATTR, 2),
|
||||
VertexAttrDescriptor::datatype_only(VertexAttrClass::Int, ALIGNED_I8_ATTR, 1),
|
||||
],
|
||||
};
|
||||
descriptor.update_attrs();
|
||||
descriptor
|
||||
});
|
||||
|
||||
device.bind_buffer(&vertex_array, quad_vertex_positions_buffer, BufferTarget::Vertex);
|
||||
TILE_OFFSET_BUFFER.configure_vertex_attrs(device, &vertex_array, tile_offset_attrs);
|
||||
device.bind_buffer(&vertex_array, &vertex_buffer, BufferTarget::Vertex);
|
||||
CLIP_TILE_BUFFER.configure_vertex_attrs(device, &vertex_array, clip_tile_attrs);
|
||||
device.bind_buffer(&vertex_array, quad_vertex_indices_buffer, BufferTarget::Index);
|
||||
|
||||
ClipTileVertexArray { vertex_array, vertex_buffer }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub struct BlitProgram<D> where D: Device {
|
||||
pub program: D::Program,
|
||||
pub src_uniform: D::Uniform,
|
||||
}
|
||||
|
||||
impl<D> BlitProgram<D> where D: Device {
|
||||
pub fn new(device: &D, resources: &dyn ResourceLoader) -> BlitProgram<D> {
|
||||
let program = device.create_raster_program(resources, "blit");
|
||||
let src_uniform = device.get_uniform(&program, "Src");
|
||||
BlitProgram { program, src_uniform }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ClearProgram<D> where D: Device {
|
||||
pub program: D::Program,
|
||||
pub rect_uniform: D::Uniform,
|
||||
pub framebuffer_size_uniform: D::Uniform,
|
||||
pub color_uniform: D::Uniform,
|
||||
}
|
||||
|
||||
impl<D> ClearProgram<D> where D: Device {
|
||||
pub fn new(device: &D, resources: &dyn ResourceLoader) -> ClearProgram<D> {
|
||||
let program = device.create_raster_program(resources, "clear");
|
||||
let rect_uniform = device.get_uniform(&program, "Rect");
|
||||
let framebuffer_size_uniform = device.get_uniform(&program, "FramebufferSize");
|
||||
let color_uniform = device.get_uniform(&program, "Color");
|
||||
ClearProgram { program, rect_uniform, framebuffer_size_uniform, color_uniform }
|
||||
}
|
||||
}
|
||||
|
||||
pub enum FillProgram<D> where D: Device {
|
||||
Raster(FillRasterProgram<D>),
|
||||
Compute(FillComputeProgram<D>),
|
||||
}
|
||||
|
||||
impl<D> FillProgram<D> where D: Device {
|
||||
pub fn new(device: &D, resources: &dyn ResourceLoader, options: &RendererOptions)
|
||||
-> FillProgram<D> {
|
||||
match (options.no_compute, device.feature_level()) {
|
||||
(false, FeatureLevel::D3D11) => {
|
||||
FillProgram::Compute(FillComputeProgram::new(device, resources))
|
||||
}
|
||||
(_, FeatureLevel::D3D10) | (true, _) => {
|
||||
FillProgram::Raster(FillRasterProgram::new(device, resources))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FillRasterProgram<D> where D: Device {
|
||||
pub program: D::Program,
|
||||
pub framebuffer_size_uniform: D::Uniform,
|
||||
pub tile_size_uniform: D::Uniform,
|
||||
pub area_lut_uniform: D::Uniform,
|
||||
}
|
||||
|
||||
impl<D> FillRasterProgram<D> where D: Device {
|
||||
pub fn new(device: &D, resources: &dyn ResourceLoader) -> FillRasterProgram<D> {
|
||||
let program = device.create_raster_program(resources, "fill");
|
||||
let framebuffer_size_uniform = device.get_uniform(&program, "FramebufferSize");
|
||||
let tile_size_uniform = device.get_uniform(&program, "TileSize");
|
||||
let area_lut_uniform = device.get_uniform(&program, "AreaLUT");
|
||||
FillRasterProgram {
|
||||
program,
|
||||
framebuffer_size_uniform,
|
||||
tile_size_uniform,
|
||||
area_lut_uniform,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FillComputeProgram<D> where D: Device {
|
||||
pub program: D::Program,
|
||||
pub dest_uniform: D::Uniform,
|
||||
pub area_lut_uniform: D::Uniform,
|
||||
pub first_tile_index_uniform: D::Uniform,
|
||||
pub fills_storage_buffer: D::StorageBuffer,
|
||||
pub next_fills_storage_buffer: D::StorageBuffer,
|
||||
pub fill_tile_map_storage_buffer: D::StorageBuffer,
|
||||
}
|
||||
|
||||
impl<D> FillComputeProgram<D> where D: Device {
|
||||
pub fn new(device: &D, resources: &dyn ResourceLoader) -> FillComputeProgram<D> {
|
||||
let mut program = device.create_compute_program(resources, "fill");
|
||||
let local_size = ComputeDimensions { x: TILE_WIDTH, y: TILE_HEIGHT / 4, z: 1 };
|
||||
device.set_compute_program_local_size(&mut program, local_size);
|
||||
|
||||
let dest_uniform = device.get_uniform(&program, "Dest");
|
||||
let area_lut_uniform = device.get_uniform(&program, "AreaLUT");
|
||||
let first_tile_index_uniform = device.get_uniform(&program, "FirstTileIndex");
|
||||
let fills_storage_buffer = device.get_storage_buffer(&program, "Fills", 0);
|
||||
let next_fills_storage_buffer = device.get_storage_buffer(&program, "NextFills", 1);
|
||||
let fill_tile_map_storage_buffer = device.get_storage_buffer(&program, "FillTileMap", 2);
|
||||
|
||||
FillComputeProgram {
|
||||
program,
|
||||
dest_uniform,
|
||||
area_lut_uniform,
|
||||
first_tile_index_uniform,
|
||||
fills_storage_buffer,
|
||||
next_fills_storage_buffer,
|
||||
fill_tile_map_storage_buffer,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TileProgram<D> where D: Device {
|
||||
pub program: D::Program,
|
||||
pub transform_uniform: D::Uniform,
|
||||
pub tile_size_uniform: D::Uniform,
|
||||
pub texture_metadata_uniform: D::Uniform,
|
||||
pub texture_metadata_size_uniform: D::Uniform,
|
||||
pub dest_texture_uniform: D::Uniform,
|
||||
pub color_texture_0_uniform: D::Uniform,
|
||||
pub color_texture_size_0_uniform: D::Uniform,
|
||||
pub color_texture_1_uniform: D::Uniform,
|
||||
pub mask_texture_0_uniform: D::Uniform,
|
||||
pub mask_texture_size_0_uniform: D::Uniform,
|
||||
pub gamma_lut_uniform: D::Uniform,
|
||||
pub filter_params_0_uniform: D::Uniform,
|
||||
pub filter_params_1_uniform: D::Uniform,
|
||||
pub filter_params_2_uniform: D::Uniform,
|
||||
pub framebuffer_size_uniform: D::Uniform,
|
||||
pub ctrl_uniform: D::Uniform,
|
||||
}
|
||||
|
||||
impl<D> TileProgram<D> where D: Device {
|
||||
pub fn new(device: &D, resources: &dyn ResourceLoader) -> TileProgram<D> {
|
||||
let program = device.create_raster_program(resources, "tile");
|
||||
let transform_uniform = device.get_uniform(&program, "Transform");
|
||||
let tile_size_uniform = device.get_uniform(&program, "TileSize");
|
||||
let texture_metadata_uniform = device.get_uniform(&program, "TextureMetadata");
|
||||
let texture_metadata_size_uniform = device.get_uniform(&program, "TextureMetadataSize");
|
||||
let dest_texture_uniform = device.get_uniform(&program, "DestTexture");
|
||||
let color_texture_0_uniform = device.get_uniform(&program, "ColorTexture0");
|
||||
let color_texture_size_0_uniform = device.get_uniform(&program, "ColorTextureSize0");
|
||||
let color_texture_1_uniform = device.get_uniform(&program, "ColorTexture1");
|
||||
let mask_texture_0_uniform = device.get_uniform(&program, "MaskTexture0");
|
||||
let mask_texture_size_0_uniform = device.get_uniform(&program, "MaskTextureSize0");
|
||||
let gamma_lut_uniform = device.get_uniform(&program, "GammaLUT");
|
||||
let filter_params_0_uniform = device.get_uniform(&program, "FilterParams0");
|
||||
let filter_params_1_uniform = device.get_uniform(&program, "FilterParams1");
|
||||
let filter_params_2_uniform = device.get_uniform(&program, "FilterParams2");
|
||||
let framebuffer_size_uniform = device.get_uniform(&program, "FramebufferSize");
|
||||
let ctrl_uniform = device.get_uniform(&program, "Ctrl");
|
||||
TileProgram {
|
||||
program,
|
||||
transform_uniform,
|
||||
tile_size_uniform,
|
||||
texture_metadata_uniform,
|
||||
texture_metadata_size_uniform,
|
||||
dest_texture_uniform,
|
||||
color_texture_0_uniform,
|
||||
color_texture_size_0_uniform,
|
||||
color_texture_1_uniform,
|
||||
mask_texture_0_uniform,
|
||||
mask_texture_size_0_uniform,
|
||||
gamma_lut_uniform,
|
||||
filter_params_0_uniform,
|
||||
filter_params_1_uniform,
|
||||
filter_params_2_uniform,
|
||||
framebuffer_size_uniform,
|
||||
ctrl_uniform,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CopyTileProgram<D> where D: Device {
|
||||
pub program: D::Program,
|
||||
pub transform_uniform: D::Uniform,
|
||||
pub tile_size_uniform: D::Uniform,
|
||||
pub framebuffer_size_uniform: D::Uniform,
|
||||
pub src_uniform: D::Uniform,
|
||||
}
|
||||
|
||||
impl<D> CopyTileProgram<D> where D: Device {
|
||||
pub fn new(device: &D, resources: &dyn ResourceLoader) -> CopyTileProgram<D> {
|
||||
let program = device.create_raster_program(resources, "tile_copy");
|
||||
let transform_uniform = device.get_uniform(&program, "Transform");
|
||||
let tile_size_uniform = device.get_uniform(&program, "TileSize");
|
||||
let framebuffer_size_uniform = device.get_uniform(&program, "FramebufferSize");
|
||||
let src_uniform = device.get_uniform(&program, "Src");
|
||||
CopyTileProgram {
|
||||
program,
|
||||
transform_uniform,
|
||||
tile_size_uniform,
|
||||
framebuffer_size_uniform,
|
||||
src_uniform,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ClipTileProgram<D> where D: Device {
|
||||
pub program: D::Program,
|
||||
pub src_uniform: D::Uniform,
|
||||
}
|
||||
|
||||
impl<D> ClipTileProgram<D> where D: Device {
|
||||
pub fn new(device: &D, resources: &dyn ResourceLoader) -> ClipTileProgram<D> {
|
||||
let program = device.create_raster_program(resources, "tile_clip");
|
||||
let src_uniform = device.get_uniform(&program, "Src");
|
||||
ClipTileProgram { program, src_uniform }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StencilProgram<D>
|
||||
where
|
||||
D: Device,
|
||||
{
|
||||
pub program: D::Program,
|
||||
}
|
||||
|
||||
impl<D> StencilProgram<D>
|
||||
where
|
||||
D: Device,
|
||||
{
|
||||
pub fn new(device: &D, resources: &dyn ResourceLoader) -> StencilProgram<D> {
|
||||
let program = device.create_raster_program(resources, "stencil");
|
||||
StencilProgram { program }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StencilVertexArray<D>
|
||||
where
|
||||
D: Device,
|
||||
{
|
||||
pub vertex_array: D::VertexArray,
|
||||
pub vertex_buffer: D::Buffer,
|
||||
pub index_buffer: D::Buffer,
|
||||
}
|
||||
|
||||
impl<D> StencilVertexArray<D>
|
||||
where
|
||||
D: Device,
|
||||
{
|
||||
pub fn new(device: &D, stencil_program: &StencilProgram<D>) -> StencilVertexArray<D> {
|
||||
let vertex_array = device.create_vertex_array();
|
||||
let vertex_buffer = device.create_buffer(BufferUploadMode::Static);
|
||||
let index_buffer = device.create_buffer(BufferUploadMode::Static);
|
||||
|
||||
let position_attr = device.get_vertex_attr(&stencil_program.program, "Position").unwrap();
|
||||
|
||||
device.bind_buffer(&vertex_array, &vertex_buffer, BufferTarget::Vertex);
|
||||
device.configure_vertex_attr(&vertex_array, &position_attr, &VertexAttrDescriptor {
|
||||
size: 3,
|
||||
class: VertexAttrClass::Float,
|
||||
attr_type: VertexAttrType::F32,
|
||||
stride: 4 * 4,
|
||||
offset: 0,
|
||||
divisor: 0,
|
||||
buffer_index: 0,
|
||||
});
|
||||
device.bind_buffer(&vertex_array, &index_buffer, BufferTarget::Index);
|
||||
|
||||
StencilVertexArray { vertex_array, vertex_buffer, index_buffer }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ReprojectionProgram<D>
|
||||
where
|
||||
D: Device,
|
||||
{
|
||||
pub program: D::Program,
|
||||
pub old_transform_uniform: D::Uniform,
|
||||
pub new_transform_uniform: D::Uniform,
|
||||
pub texture_uniform: D::Uniform,
|
||||
}
|
||||
|
||||
impl<D> ReprojectionProgram<D>
|
||||
where
|
||||
D: Device,
|
||||
{
|
||||
pub fn new(device: &D, resources: &dyn ResourceLoader) -> ReprojectionProgram<D> {
|
||||
let program = device.create_raster_program(resources, "reproject");
|
||||
let old_transform_uniform = device.get_uniform(&program, "OldTransform");
|
||||
let new_transform_uniform = device.get_uniform(&program, "NewTransform");
|
||||
let texture_uniform = device.get_uniform(&program, "Texture");
|
||||
|
||||
ReprojectionProgram {
|
||||
program,
|
||||
old_transform_uniform,
|
||||
new_transform_uniform,
|
||||
texture_uniform,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ReprojectionVertexArray<D>
|
||||
where
|
||||
D: Device,
|
||||
{
|
||||
pub vertex_array: D::VertexArray,
|
||||
}
|
||||
|
||||
impl<D> ReprojectionVertexArray<D>
|
||||
where
|
||||
D: Device,
|
||||
{
|
||||
pub fn new(
|
||||
device: &D,
|
||||
reprojection_program: &ReprojectionProgram<D>,
|
||||
quad_vertex_positions_buffer: &D::Buffer,
|
||||
quad_vertex_indices_buffer: &D::Buffer,
|
||||
) -> ReprojectionVertexArray<D> {
|
||||
let vertex_array = device.create_vertex_array();
|
||||
let position_attrs = &[
|
||||
device.get_vertex_attr(&reprojection_program.program, "Position")
|
||||
.unwrap(),
|
||||
];
|
||||
static POSITION_BUFFER: Lazy<VertexBufferDescriptor> = Lazy::new(|| {
|
||||
let mut descriptor = VertexBufferDescriptor{
|
||||
index: 0,
|
||||
divisor: 0,
|
||||
vertex_attrs: vec![
|
||||
VertexAttrDescriptor::datatype_only(VertexAttrClass::Int, ALIGNED_I16_ATTR, 2),
|
||||
]
|
||||
};
|
||||
descriptor.update_attrs();
|
||||
descriptor
|
||||
});
|
||||
|
||||
device.bind_buffer(&vertex_array, quad_vertex_positions_buffer, BufferTarget::Vertex);
|
||||
POSITION_BUFFER.configure_vertex_attrs(device, &vertex_array, position_attrs);
|
||||
device.bind_buffer(&vertex_array, quad_vertex_indices_buffer, BufferTarget::Index);
|
||||
|
||||
ReprojectionVertexArray { vertex_array }
|
||||
}
|
||||
}
|
|
@ -1,277 +0,0 @@
|
|||
// pathfinder/renderer/src/gpu_data.rs
|
||||
//
|
||||
// Copyright © 2020 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.
|
||||
|
||||
//! Packed data ready to be sent to the GPU.
|
||||
|
||||
use crate::builder::{ALPHA_TILES_PER_LEVEL, ALPHA_TILE_LEVEL_COUNT};
|
||||
use crate::options::BoundingQuad;
|
||||
use crate::paint::PaintCompositeOp;
|
||||
use pathfinder_color::ColorU;
|
||||
use pathfinder_content::effects::{BlendMode, Filter};
|
||||
use pathfinder_content::render_target::RenderTargetId;
|
||||
use pathfinder_geometry::alignment::{AlignedI8, AlignedU8, AlignedI16, AlignedU16};
|
||||
use pathfinder_geometry::line_segment::{LineSegmentU4, LineSegmentU8};
|
||||
use pathfinder_geometry::rect::RectI;
|
||||
use pathfinder_geometry::transform2d::Transform2F;
|
||||
use pathfinder_geometry::vector::Vector2I;
|
||||
use pathfinder_gpu::TextureSamplingFlags;
|
||||
use std::fmt::{Debug, Formatter, Result as DebugResult};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::time::Duration;
|
||||
use std::u32;
|
||||
|
||||
pub const TILE_CTRL_MASK_MASK: i32 = 0x3;
|
||||
pub const TILE_CTRL_MASK_WINDING: i32 = 0x1;
|
||||
pub const TILE_CTRL_MASK_EVEN_ODD: i32 = 0x2;
|
||||
|
||||
pub const TILE_CTRL_MASK_0_SHIFT: i32 = 0;
|
||||
|
||||
pub enum RenderCommand {
|
||||
// Starts rendering a frame.
|
||||
Start {
|
||||
/// The number of paths that will be rendered.
|
||||
path_count: usize,
|
||||
|
||||
/// A bounding quad for the scene.
|
||||
bounding_quad: BoundingQuad,
|
||||
|
||||
/// Whether the framebuffer we're rendering to must be readable.
|
||||
///
|
||||
/// This is needed if a path that renders directly to the output framebuffer (i.e. not to a
|
||||
/// render target) uses one of the more exotic blend modes.
|
||||
needs_readable_framebuffer: bool,
|
||||
},
|
||||
|
||||
// Allocates a texture page.
|
||||
AllocateTexturePage { page_id: TexturePageId, descriptor: TexturePageDescriptor },
|
||||
|
||||
// Uploads data to a texture page.
|
||||
UploadTexelData { texels: Arc<Vec<ColorU>>, location: TextureLocation },
|
||||
|
||||
// Associates a render target with a texture page.
|
||||
//
|
||||
// TODO(pcwalton): Add a rect to this so we can render to subrects of a page.
|
||||
DeclareRenderTarget { id: RenderTargetId, location: TextureLocation },
|
||||
|
||||
// Upload texture metadata.
|
||||
UploadTextureMetadata(Vec<TextureMetadataEntry>),
|
||||
|
||||
// Adds fills to the queue.
|
||||
AddFills(Vec<FillBatchEntry>),
|
||||
|
||||
// Flushes the queue of fills.
|
||||
FlushFills,
|
||||
|
||||
// Renders clips to the mask tile.
|
||||
ClipTiles(Vec<ClipBatch>),
|
||||
|
||||
// Pushes a render target onto the stack. Draw commands go to the render target on top of the
|
||||
// stack.
|
||||
PushRenderTarget(RenderTargetId),
|
||||
|
||||
// Pops a render target from the stack.
|
||||
PopRenderTarget,
|
||||
|
||||
// Marks that tile compositing is about to begin.
|
||||
BeginTileDrawing,
|
||||
|
||||
// Draws a batch of tiles to the render target on top of the stack.
|
||||
DrawTiles(TileBatch),
|
||||
|
||||
// Presents a rendered frame.
|
||||
Finish { cpu_build_time: Duration },
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Debug, Default)]
|
||||
pub struct TexturePageId(pub u32);
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct TexturePageDescriptor {
|
||||
pub size: Vector2I,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Debug, Default)]
|
||||
pub struct TextureLocation {
|
||||
pub page: TexturePageId,
|
||||
pub rect: RectI,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TileBatch {
|
||||
pub tiles: Vec<Tile>,
|
||||
pub color_texture: Option<TileBatchTexture>,
|
||||
pub filter: Filter,
|
||||
pub blend_mode: BlendMode,
|
||||
pub tile_page: u16,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct TileBatchTexture {
|
||||
pub page: TexturePageId,
|
||||
pub sampling_flags: TextureSamplingFlags,
|
||||
pub composite_op: PaintCompositeOp,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct FillObjectPrimitive {
|
||||
pub px: LineSegmentU4,
|
||||
pub subpx: LineSegmentU8,
|
||||
pub tile_x: i16,
|
||||
pub tile_y: i16,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct TileObjectPrimitive {
|
||||
pub alpha_tile_id: AlphaTileId,
|
||||
pub backdrop: i8,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct TextureMetadataEntry {
|
||||
pub color_0_transform: Transform2F,
|
||||
pub base_color: ColorU,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct FillBatchEntry {
|
||||
pub fill: Fill,
|
||||
pub page: u16,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
#[repr(C)]
|
||||
pub struct Fill {
|
||||
pub subpx: LineSegmentU8,
|
||||
pub px: LineSegmentU4,
|
||||
pub alpha_tile_index: AlignedU16,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ClipBatch {
|
||||
pub clips: Vec<Clip>,
|
||||
pub key: ClipBatchKey,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct ClipBatchKey {
|
||||
pub dest_page: u16,
|
||||
pub src_page: u16,
|
||||
pub kind: ClipBatchKind,
|
||||
}
|
||||
|
||||
// Order is significant here.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum ClipBatchKind {
|
||||
Draw,
|
||||
Clip,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
#[repr(C)]
|
||||
pub struct Clip {
|
||||
pub dest_u: AlignedU8,
|
||||
pub dest_v: AlignedU8,
|
||||
pub src_u: AlignedU8,
|
||||
pub src_v: AlignedU8,
|
||||
pub backdrop: AlignedI8,
|
||||
pub pad_0: AlignedU8,
|
||||
pub pad_1: AlignedU16,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
#[repr(C)]
|
||||
pub struct Tile {
|
||||
pub tile_x: AlignedI16,
|
||||
pub tile_y: AlignedI16,
|
||||
pub mask_0_u: AlignedU8,
|
||||
pub mask_0_v: AlignedU8,
|
||||
pub mask_0_backdrop: AlignedI8,
|
||||
pub pad: AlignedU8,
|
||||
pub color: AlignedU16,
|
||||
pub ctrl: AlignedU16,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
pub struct AlphaTileId(pub u32);
|
||||
|
||||
impl AlphaTileId {
|
||||
#[inline]
|
||||
pub fn new(next_alpha_tile_index: &[AtomicUsize; ALPHA_TILE_LEVEL_COUNT], level: usize)
|
||||
-> AlphaTileId {
|
||||
let alpha_tile_index = next_alpha_tile_index[level].fetch_add(1, Ordering::Relaxed);
|
||||
debug_assert!(alpha_tile_index < ALPHA_TILES_PER_LEVEL);
|
||||
AlphaTileId((level * ALPHA_TILES_PER_LEVEL + alpha_tile_index) as u32)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn invalid() -> AlphaTileId {
|
||||
AlphaTileId(!0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn page(self) -> u16 {
|
||||
(self.0 >> 16) as u16
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn tile(self) -> u16 {
|
||||
(self.0 & 0xffff) as u16
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_valid(self) -> bool {
|
||||
self.0 < !0
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for RenderCommand {
|
||||
fn fmt(&self, formatter: &mut Formatter) -> DebugResult {
|
||||
match *self {
|
||||
RenderCommand::Start { .. } => write!(formatter, "Start"),
|
||||
RenderCommand::AllocateTexturePage { page_id, descriptor: _ } => {
|
||||
write!(formatter, "AllocateTexturePage({})", page_id.0)
|
||||
}
|
||||
RenderCommand::UploadTexelData { ref texels, location } => {
|
||||
write!(formatter, "UploadTexelData(x{:?}, {:?})", texels.len(), location)
|
||||
}
|
||||
RenderCommand::DeclareRenderTarget { id, location } => {
|
||||
write!(formatter, "DeclareRenderTarget({:?}, {:?})", id, location)
|
||||
}
|
||||
RenderCommand::UploadTextureMetadata(ref metadata) => {
|
||||
write!(formatter, "UploadTextureMetadata(x{})", metadata.len())
|
||||
}
|
||||
RenderCommand::AddFills(ref fills) => {
|
||||
write!(formatter, "AddFills(x{})", fills.len())
|
||||
}
|
||||
RenderCommand::FlushFills => write!(formatter, "FlushFills"),
|
||||
RenderCommand::ClipTiles(ref batches) => {
|
||||
write!(formatter, "ClipTiles(x{})", batches.len())
|
||||
}
|
||||
RenderCommand::PushRenderTarget(render_target_id) => {
|
||||
write!(formatter, "PushRenderTarget({:?})", render_target_id)
|
||||
}
|
||||
RenderCommand::PopRenderTarget => write!(formatter, "PopRenderTarget"),
|
||||
RenderCommand::BeginTileDrawing => write!(formatter, "BeginTileDrawing"),
|
||||
RenderCommand::DrawTiles(ref batch) => {
|
||||
write!(formatter,
|
||||
"DrawTiles(x{}, C0 {:?}, {:?})",
|
||||
batch.tiles.len(),
|
||||
batch.color_texture,
|
||||
batch.blend_mode)
|
||||
}
|
||||
RenderCommand::Finish { cpu_build_time } => {
|
||||
write!(formatter, "Finish({} ms)", cpu_build_time.as_secs_f64() * 1000.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
// pathfinder/renderer/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.
|
||||
|
||||
//! The CPU portion of Pathfinder's renderer.
|
||||
|
||||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
pub mod concurrent;
|
||||
pub mod gpu;
|
||||
pub mod gpu_data;
|
||||
pub mod options;
|
||||
pub mod paint;
|
||||
pub mod scene;
|
||||
|
||||
mod allocator;
|
||||
mod builder;
|
||||
mod tile_map;
|
||||
mod tiles;
|
||||
mod z_buffer;
|
|
@ -1,153 +0,0 @@
|
|||
// pathfinder/renderer/src/options.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.
|
||||
|
||||
//! Options that control how rendering is to be performed.
|
||||
|
||||
use crate::gpu_data::RenderCommand;
|
||||
use pathfinder_geometry::rect::RectF;
|
||||
use pathfinder_geometry::transform2d::Transform2F;
|
||||
use pathfinder_geometry::transform3d::Perspective;
|
||||
use pathfinder_geometry::vector::{Vector2F, Vector4F};
|
||||
use pathfinder_content::clip::PolygonClipper3D;
|
||||
|
||||
pub trait RenderCommandListener: Send + Sync {
|
||||
fn send(&self, command: RenderCommand);
|
||||
}
|
||||
|
||||
impl<F> RenderCommandListener for F
|
||||
where
|
||||
F: Fn(RenderCommand) + Send + Sync,
|
||||
{
|
||||
#[inline]
|
||||
fn send(&self, command: RenderCommand) {
|
||||
(*self)(command)
|
||||
}
|
||||
}
|
||||
|
||||
/// Options that influence scene building.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct BuildOptions {
|
||||
pub transform: RenderTransform,
|
||||
pub dilation: Vector2F,
|
||||
pub subpixel_aa_enabled: bool,
|
||||
}
|
||||
|
||||
impl BuildOptions {
|
||||
pub(crate) fn prepare(self, bounds: RectF) -> PreparedBuildOptions {
|
||||
PreparedBuildOptions {
|
||||
transform: self.transform.prepare(bounds),
|
||||
dilation: self.dilation,
|
||||
subpixel_aa_enabled: self.subpixel_aa_enabled,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum RenderTransform {
|
||||
Transform2D(Transform2F),
|
||||
Perspective(Perspective),
|
||||
}
|
||||
|
||||
impl Default for RenderTransform {
|
||||
#[inline]
|
||||
fn default() -> RenderTransform {
|
||||
RenderTransform::Transform2D(Transform2F::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderTransform {
|
||||
fn prepare(&self, bounds: RectF) -> PreparedRenderTransform {
|
||||
let perspective = match self {
|
||||
RenderTransform::Transform2D(ref transform) => {
|
||||
if transform.is_identity() {
|
||||
return PreparedRenderTransform::None;
|
||||
}
|
||||
return PreparedRenderTransform::Transform2D(*transform);
|
||||
}
|
||||
RenderTransform::Perspective(ref perspective) => *perspective,
|
||||
};
|
||||
|
||||
let mut points = vec![
|
||||
bounds.origin().to_4d(),
|
||||
bounds.upper_right().to_4d(),
|
||||
bounds.lower_right().to_4d(),
|
||||
bounds.lower_left().to_4d(),
|
||||
];
|
||||
debug!("-----");
|
||||
debug!("bounds={:?} ORIGINAL quad={:?}", bounds, points);
|
||||
for point in &mut points {
|
||||
*point = perspective.transform * *point;
|
||||
}
|
||||
debug!("... PERSPECTIVE quad={:?}", points);
|
||||
|
||||
// Compute depth.
|
||||
let quad = [
|
||||
points[0].to_3d().to_4d(),
|
||||
points[1].to_3d().to_4d(),
|
||||
points[2].to_3d().to_4d(),
|
||||
points[3].to_3d().to_4d(),
|
||||
];
|
||||
debug!("... PERSPECTIVE-DIVIDED points = {:?}", quad);
|
||||
|
||||
points = PolygonClipper3D::new(points).clip();
|
||||
debug!("... CLIPPED quad={:?}", points);
|
||||
for point in &mut points {
|
||||
*point = point.to_3d().to_4d()
|
||||
}
|
||||
|
||||
let inverse_transform = perspective.transform.inverse();
|
||||
let clip_polygon = points.into_iter()
|
||||
.map(|point| (inverse_transform * point).to_2d())
|
||||
.collect();
|
||||
return PreparedRenderTransform::Perspective {
|
||||
perspective,
|
||||
clip_polygon,
|
||||
quad,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct PreparedBuildOptions {
|
||||
pub(crate) transform: PreparedRenderTransform,
|
||||
pub(crate) dilation: Vector2F,
|
||||
pub(crate) subpixel_aa_enabled: bool,
|
||||
}
|
||||
|
||||
impl PreparedBuildOptions {
|
||||
#[inline]
|
||||
pub(crate) fn bounding_quad(&self) -> BoundingQuad {
|
||||
match self.transform {
|
||||
PreparedRenderTransform::Perspective { quad, .. } => quad,
|
||||
_ => [Vector4F::default(); 4],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type BoundingQuad = [Vector4F; 4];
|
||||
|
||||
pub(crate) enum PreparedRenderTransform {
|
||||
None,
|
||||
Transform2D(Transform2F),
|
||||
Perspective {
|
||||
perspective: Perspective,
|
||||
clip_polygon: Vec<Vector2F>,
|
||||
quad: [Vector4F; 4],
|
||||
},
|
||||
}
|
||||
|
||||
impl PreparedRenderTransform {
|
||||
#[inline]
|
||||
pub(crate) fn is_2d(&self) -> bool {
|
||||
match *self {
|
||||
PreparedRenderTransform::Transform2D(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,702 +0,0 @@
|
|||
// pathfinder/renderer/src/paint.rs
|
||||
//
|
||||
// Copyright © 2020 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::allocator::{AllocationMode, TextureAllocator};
|
||||
use crate::gpu_data::{RenderCommand, TextureLocation, TextureMetadataEntry, TexturePageDescriptor};
|
||||
use crate::gpu_data::{TexturePageId, TileBatchTexture};
|
||||
use crate::scene::{RenderTarget, SceneId};
|
||||
use hashbrown::HashMap;
|
||||
use pathfinder_color::ColorU;
|
||||
use pathfinder_content::effects::{Filter, PatternFilter};
|
||||
use pathfinder_content::gradient::{Gradient, GradientGeometry};
|
||||
use pathfinder_content::pattern::{Pattern, PatternSource};
|
||||
use pathfinder_content::render_target::RenderTargetId;
|
||||
use pathfinder_geometry::line_segment::LineSegment2F;
|
||||
use pathfinder_geometry::rect::{RectF, RectI};
|
||||
use pathfinder_geometry::transform2d::Transform2F;
|
||||
use pathfinder_geometry::vector::{Vector2F, Vector2I, vec2f, vec2i};
|
||||
use pathfinder_gpu::TextureSamplingFlags;
|
||||
use pathfinder_simd::default::{F32x2, F32x4};
|
||||
use std::f32;
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::sync::Arc;
|
||||
|
||||
// The size of a gradient tile.
|
||||
//
|
||||
// TODO(pcwalton): Choose this size dynamically!
|
||||
const GRADIENT_TILE_LENGTH: u32 = 256;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Palette {
|
||||
pub paints: Vec<Paint>,
|
||||
render_targets: Vec<RenderTargetData>,
|
||||
cache: HashMap<Paint, PaintId>,
|
||||
allocator: TextureAllocator,
|
||||
scene_id: SceneId,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct RenderTargetData {
|
||||
render_target: RenderTarget,
|
||||
metadata: RenderTargetMetadata,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct Paint {
|
||||
base_color: ColorU,
|
||||
overlay: Option<PaintOverlay>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct PaintOverlay {
|
||||
composite_op: PaintCompositeOp,
|
||||
contents: PaintContents,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
pub enum PaintContents {
|
||||
Gradient(Gradient),
|
||||
Pattern(Pattern),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct PaintId(pub u16);
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct GradientId(pub u32);
|
||||
|
||||
/// How a paint is to be composited over a base color, or vice versa.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||
pub enum PaintCompositeOp {
|
||||
SrcIn,
|
||||
DestIn,
|
||||
}
|
||||
|
||||
impl Debug for PaintContents {
|
||||
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
PaintContents::Gradient(ref gradient) => gradient.fmt(formatter),
|
||||
PaintContents::Pattern(ref pattern) => pattern.fmt(formatter),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Palette {
|
||||
#[inline]
|
||||
pub fn new(scene_id: SceneId) -> Palette {
|
||||
Palette {
|
||||
paints: vec![],
|
||||
render_targets: vec![],
|
||||
cache: HashMap::new(),
|
||||
allocator: TextureAllocator::new(),
|
||||
scene_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Paint {
|
||||
#[inline]
|
||||
pub fn from_color(color: ColorU) -> Paint {
|
||||
Paint { base_color: color, overlay: None }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_gradient(gradient: Gradient) -> Paint {
|
||||
Paint {
|
||||
base_color: ColorU::white(),
|
||||
overlay: Some(PaintOverlay {
|
||||
composite_op: PaintCompositeOp::SrcIn,
|
||||
contents: PaintContents::Gradient(gradient),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_pattern(pattern: Pattern) -> Paint {
|
||||
Paint {
|
||||
base_color: ColorU::white(),
|
||||
overlay: Some(PaintOverlay {
|
||||
composite_op: PaintCompositeOp::SrcIn,
|
||||
contents: PaintContents::Pattern(pattern),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn black() -> Paint {
|
||||
Paint::from_color(ColorU::black())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn transparent_black() -> Paint {
|
||||
Paint::from_color(ColorU::transparent_black())
|
||||
}
|
||||
|
||||
pub fn is_opaque(&self) -> bool {
|
||||
if !self.base_color.is_opaque() {
|
||||
return false;
|
||||
}
|
||||
|
||||
match self.overlay {
|
||||
None => true,
|
||||
Some(ref overlay) => {
|
||||
match overlay.contents {
|
||||
PaintContents::Gradient(ref gradient) => gradient.is_opaque(),
|
||||
PaintContents::Pattern(ref pattern) => pattern.is_opaque(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_fully_transparent(&self) -> bool {
|
||||
if !self.base_color.is_fully_transparent() {
|
||||
return false;
|
||||
}
|
||||
|
||||
match self.overlay {
|
||||
None => true,
|
||||
Some(ref overlay) => {
|
||||
match overlay.contents {
|
||||
PaintContents::Gradient(ref gradient) => gradient.is_fully_transparent(),
|
||||
PaintContents::Pattern(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_color(&self) -> bool {
|
||||
self.overlay.is_none()
|
||||
}
|
||||
|
||||
pub fn apply_transform(&mut self, transform: &Transform2F) {
|
||||
if transform.is_identity() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(ref mut overlay) = self.overlay {
|
||||
match overlay.contents {
|
||||
PaintContents::Gradient(ref mut gradient) => gradient.apply_transform(*transform),
|
||||
PaintContents::Pattern(ref mut pattern) => pattern.apply_transform(*transform),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn base_color(&self) -> ColorU {
|
||||
self.base_color
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_base_color(&mut self, new_base_color: ColorU) {
|
||||
self.base_color = new_base_color;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn overlay(&self) -> &Option<PaintOverlay> {
|
||||
&self.overlay
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn overlay_mut(&mut self) -> &mut Option<PaintOverlay> {
|
||||
&mut self.overlay
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn pattern(&self) -> Option<&Pattern> {
|
||||
match self.overlay {
|
||||
None => None,
|
||||
Some(ref overlay) => {
|
||||
match overlay.contents {
|
||||
PaintContents::Pattern(ref pattern) => Some(pattern),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn pattern_mut(&mut self) -> Option<&mut Pattern> {
|
||||
match self.overlay {
|
||||
None => None,
|
||||
Some(ref mut overlay) => {
|
||||
match overlay.contents {
|
||||
PaintContents::Pattern(ref mut pattern) => Some(pattern),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn gradient(&self) -> Option<&Gradient> {
|
||||
match self.overlay {
|
||||
None => None,
|
||||
Some(ref overlay) => {
|
||||
match overlay.contents {
|
||||
PaintContents::Gradient(ref gradient) => Some(gradient),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PaintOverlay {
|
||||
#[inline]
|
||||
pub fn contents(&self) -> &PaintContents {
|
||||
&self.contents
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn composite_op(&self) -> PaintCompositeOp {
|
||||
self.composite_op
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_composite_op(&mut self, new_composite_op: PaintCompositeOp) {
|
||||
self.composite_op = new_composite_op
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PaintInfo {
|
||||
/// The render commands needed to prepare the textures.
|
||||
pub render_commands: Vec<RenderCommand>,
|
||||
/// The metadata for each paint.
|
||||
///
|
||||
/// The indices of this vector are paint IDs.
|
||||
pub paint_metadata: Vec<PaintMetadata>,
|
||||
/// The metadata for each render target.
|
||||
///
|
||||
/// The indices of this vector are render target IDs.
|
||||
pub render_target_metadata: Vec<RenderTargetMetadata>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PaintMetadata {
|
||||
/// Metadata associated with the color texture, if applicable.
|
||||
pub color_texture_metadata: Option<PaintColorTextureMetadata>,
|
||||
/// The base color that the color texture gets mixed into.
|
||||
pub base_color: ColorU,
|
||||
/// True if this paint is fully opaque.
|
||||
pub is_opaque: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PaintColorTextureMetadata {
|
||||
/// The location of the paint.
|
||||
pub location: TextureLocation,
|
||||
/// The scale for the page this paint is on.
|
||||
pub page_scale: Vector2F,
|
||||
/// The transform to apply to screen coordinates to translate them into UVs.
|
||||
pub transform: Transform2F,
|
||||
/// The sampling mode for the texture.
|
||||
pub sampling_flags: TextureSamplingFlags,
|
||||
/// The filter to be applied to this paint.
|
||||
pub filter: PaintFilter,
|
||||
/// How the color texture is to be composited over the base color.
|
||||
pub composite_op: PaintCompositeOp,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct RadialGradientMetadata {
|
||||
/// The line segment that connects the two circles.
|
||||
pub line: LineSegment2F,
|
||||
/// The radii of the two circles.
|
||||
pub radii: F32x2,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct RenderTargetMetadata {
|
||||
/// The location of the render target.
|
||||
pub location: TextureLocation,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PaintFilter {
|
||||
None,
|
||||
RadialGradient {
|
||||
/// The line segment that connects the two circles.
|
||||
line: LineSegment2F,
|
||||
/// The radii of the two circles.
|
||||
radii: F32x2,
|
||||
},
|
||||
PatternFilter(PatternFilter),
|
||||
}
|
||||
|
||||
impl Palette {
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
pub fn push_paint(&mut self, paint: &Paint) -> PaintId {
|
||||
if let Some(paint_id) = self.cache.get(paint) {
|
||||
return *paint_id;
|
||||
}
|
||||
|
||||
let paint_id = PaintId(self.paints.len() as u16);
|
||||
self.cache.insert((*paint).clone(), paint_id);
|
||||
self.paints.push((*paint).clone());
|
||||
paint_id
|
||||
}
|
||||
|
||||
pub fn push_render_target(&mut self, render_target: RenderTarget) -> RenderTargetId {
|
||||
let id = self.render_targets.len() as u32;
|
||||
|
||||
let metadata = RenderTargetMetadata {
|
||||
location: self.allocator.allocate_image(render_target.size()),
|
||||
};
|
||||
|
||||
self.render_targets.push(RenderTargetData { render_target, metadata });
|
||||
RenderTargetId { scene: self.scene_id.0, render_target: id }
|
||||
}
|
||||
|
||||
pub fn build_paint_info(&mut self, render_transform: Transform2F) -> PaintInfo {
|
||||
let mut paint_metadata = vec![];
|
||||
|
||||
// Assign paint locations.
|
||||
let mut gradient_tile_builder = GradientTileBuilder::new();
|
||||
let mut image_texel_info = vec![];
|
||||
for paint in &self.paints {
|
||||
let allocator = &mut self.allocator;
|
||||
let render_targets = &self.render_targets;
|
||||
let color_texture_metadata = paint.overlay.as_ref().map(|overlay| {
|
||||
match overlay.contents {
|
||||
PaintContents::Gradient(ref gradient) => {
|
||||
// FIXME(pcwalton): The gradient size might not be big enough. Detect this.
|
||||
let location = gradient_tile_builder.allocate(allocator, gradient);
|
||||
PaintColorTextureMetadata {
|
||||
location,
|
||||
page_scale: allocator.page_scale(location.page),
|
||||
sampling_flags: TextureSamplingFlags::empty(),
|
||||
filter: match gradient.geometry {
|
||||
GradientGeometry::Linear(_) => PaintFilter::None,
|
||||
GradientGeometry::Radial { line, radii, .. } => {
|
||||
PaintFilter::RadialGradient { line, radii }
|
||||
}
|
||||
},
|
||||
transform: Transform2F::default(),
|
||||
composite_op: overlay.composite_op(),
|
||||
}
|
||||
}
|
||||
PaintContents::Pattern(ref pattern) => {
|
||||
let location;
|
||||
match *pattern.source() {
|
||||
PatternSource::RenderTarget { id: render_target_id, .. } => {
|
||||
let index = render_target_id.render_target as usize;
|
||||
location = render_targets[index].metadata.location;
|
||||
}
|
||||
PatternSource::Image(ref image) => {
|
||||
// TODO(pcwalton): We should be able to use tile cleverness to
|
||||
// repeat inside the atlas in some cases.
|
||||
let allocation_mode = AllocationMode::OwnPage;
|
||||
location = allocator.allocate(image.size(), allocation_mode);
|
||||
image_texel_info.push(ImageTexelInfo {
|
||||
location,
|
||||
texels: (*image.pixels()).clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let mut sampling_flags = TextureSamplingFlags::empty();
|
||||
if pattern.repeat_x() {
|
||||
sampling_flags.insert(TextureSamplingFlags::REPEAT_U);
|
||||
}
|
||||
if pattern.repeat_y() {
|
||||
sampling_flags.insert(TextureSamplingFlags::REPEAT_V);
|
||||
}
|
||||
if !pattern.smoothing_enabled() {
|
||||
sampling_flags.insert(TextureSamplingFlags::NEAREST_MIN |
|
||||
TextureSamplingFlags::NEAREST_MAG);
|
||||
}
|
||||
|
||||
let filter = match pattern.filter() {
|
||||
None => PaintFilter::None,
|
||||
Some(pattern_filter) => PaintFilter::PatternFilter(pattern_filter),
|
||||
};
|
||||
|
||||
PaintColorTextureMetadata {
|
||||
location,
|
||||
page_scale: allocator.page_scale(location.page),
|
||||
sampling_flags,
|
||||
filter,
|
||||
transform: Transform2F::default(),
|
||||
composite_op: overlay.composite_op(),
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
paint_metadata.push(PaintMetadata {
|
||||
color_texture_metadata,
|
||||
is_opaque: paint.is_opaque(),
|
||||
base_color: paint.base_color(),
|
||||
});
|
||||
}
|
||||
|
||||
// Calculate texture transforms.
|
||||
for (paint, metadata) in self.paints.iter().zip(paint_metadata.iter_mut()) {
|
||||
let mut color_texture_metadata = match metadata.color_texture_metadata {
|
||||
None => continue,
|
||||
Some(ref mut color_texture_metadata) => color_texture_metadata,
|
||||
};
|
||||
|
||||
let texture_scale = self.allocator.page_scale(color_texture_metadata.location.page);
|
||||
let texture_rect = color_texture_metadata.location.rect;
|
||||
color_texture_metadata.transform = match paint.overlay
|
||||
.as_ref()
|
||||
.expect("Why do we have color texture \
|
||||
metadata but no overlay?")
|
||||
.contents {
|
||||
PaintContents::Gradient(Gradient {
|
||||
geometry: GradientGeometry::Linear(gradient_line),
|
||||
..
|
||||
}) => {
|
||||
// Project gradient line onto (0.0-1.0, v0).
|
||||
let v0 = texture_rect.to_f32().center().y() * texture_scale.y();
|
||||
let dp = gradient_line.vector();
|
||||
let m0 = dp.0.concat_xy_xy(dp.0) / F32x4::splat(gradient_line.square_length());
|
||||
let m13 = m0.zw() * -gradient_line.from().0;
|
||||
Transform2F::row_major(m0.x(), m0.y(), m13.x() + m13.y(), 0.0, 0.0, v0)
|
||||
}
|
||||
PaintContents::Gradient(Gradient {
|
||||
geometry: GradientGeometry::Radial { ref transform, .. },
|
||||
..
|
||||
}) => transform.inverse(),
|
||||
PaintContents::Pattern(ref pattern) => {
|
||||
match pattern.source() {
|
||||
PatternSource::Image(_) => {
|
||||
let texture_origin_uv =
|
||||
rect_to_uv(texture_rect, texture_scale).origin();
|
||||
Transform2F::from_scale(texture_scale).translate(texture_origin_uv) *
|
||||
pattern.transform().inverse()
|
||||
}
|
||||
PatternSource::RenderTarget { .. } => {
|
||||
// FIXME(pcwalton): Only do this in GL, not Metal!
|
||||
let texture_origin_uv =
|
||||
rect_to_uv(texture_rect, texture_scale).lower_left();
|
||||
Transform2F::from_translation(texture_origin_uv) *
|
||||
Transform2F::from_scale(texture_scale * vec2f(1.0, -1.0)) *
|
||||
pattern.transform().inverse()
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
color_texture_metadata.transform *= render_transform;
|
||||
}
|
||||
|
||||
// Create texture metadata.
|
||||
let texture_metadata = paint_metadata.iter().map(|paint_metadata| {
|
||||
TextureMetadataEntry {
|
||||
color_0_transform: match paint_metadata.color_texture_metadata {
|
||||
None => Transform2F::default(),
|
||||
Some(ref color_texture_metadata) => color_texture_metadata.transform,
|
||||
},
|
||||
base_color: paint_metadata.base_color,
|
||||
}
|
||||
}).collect();
|
||||
let mut render_commands = vec![RenderCommand::UploadTextureMetadata(texture_metadata)];
|
||||
|
||||
// Allocate textures.
|
||||
let mut texture_page_descriptors = vec![];
|
||||
for page_index in 0..self.allocator.page_count() {
|
||||
let page_id = TexturePageId(page_index);
|
||||
let page_size = self.allocator.page_size(page_id);
|
||||
let descriptor = TexturePageDescriptor { size: page_size };
|
||||
texture_page_descriptors.push(descriptor);
|
||||
|
||||
if self.allocator.page_is_new(page_id) {
|
||||
render_commands.push(RenderCommand::AllocateTexturePage { page_id, descriptor });
|
||||
self.allocator.mark_page_as_allocated(page_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Gather up render target metadata.
|
||||
let render_target_metadata: Vec<_> = self.render_targets.iter().map(|render_target_data| {
|
||||
render_target_data.metadata
|
||||
}).collect();
|
||||
|
||||
// Create render commands.
|
||||
for (index, metadata) in render_target_metadata.iter().enumerate() {
|
||||
let id = RenderTargetId { scene: self.scene_id.0, render_target: index as u32 };
|
||||
render_commands.push(RenderCommand::DeclareRenderTarget {
|
||||
id,
|
||||
location: metadata.location,
|
||||
});
|
||||
}
|
||||
gradient_tile_builder.create_render_commands(&mut render_commands);
|
||||
for image_texel_info in image_texel_info {
|
||||
render_commands.push(RenderCommand::UploadTexelData {
|
||||
texels: image_texel_info.texels,
|
||||
location: image_texel_info.location,
|
||||
});
|
||||
}
|
||||
|
||||
PaintInfo { render_commands, paint_metadata, render_target_metadata }
|
||||
}
|
||||
|
||||
pub(crate) fn append_palette(&mut self, palette: Palette) -> MergedPaletteInfo {
|
||||
// Merge render targets.
|
||||
let mut render_target_mapping = HashMap::new();
|
||||
for (old_render_target_index, render_target) in palette.render_targets
|
||||
.into_iter()
|
||||
.enumerate() {
|
||||
let old_render_target_id = RenderTargetId {
|
||||
scene: palette.scene_id.0,
|
||||
render_target: old_render_target_index as u32,
|
||||
};
|
||||
let new_render_target_id = self.push_render_target(render_target.render_target);
|
||||
render_target_mapping.insert(old_render_target_id, new_render_target_id);
|
||||
}
|
||||
|
||||
// Merge paints.
|
||||
let mut paint_mapping = HashMap::new();
|
||||
for (old_paint_index, old_paint) in palette.paints.iter().enumerate() {
|
||||
let old_paint_id = PaintId(old_paint_index as u16);
|
||||
let new_paint_id = match *old_paint.overlay() {
|
||||
None => self.push_paint(old_paint),
|
||||
Some(ref overlay) => {
|
||||
match *overlay.contents() {
|
||||
PaintContents::Pattern(ref pattern) => {
|
||||
match pattern.source() {
|
||||
PatternSource::RenderTarget { id: old_render_target_id, size } => {
|
||||
let mut new_pattern =
|
||||
Pattern::from_render_target(*old_render_target_id, *size);
|
||||
new_pattern.set_filter(pattern.filter());
|
||||
new_pattern.apply_transform(pattern.transform());
|
||||
new_pattern.set_repeat_x(pattern.repeat_x());
|
||||
new_pattern.set_repeat_y(pattern.repeat_y());
|
||||
new_pattern.set_smoothing_enabled(pattern.smoothing_enabled());
|
||||
self.push_paint(&Paint::from_pattern(new_pattern))
|
||||
}
|
||||
_ => self.push_paint(old_paint),
|
||||
}
|
||||
}
|
||||
_ => self.push_paint(old_paint),
|
||||
}
|
||||
}
|
||||
};
|
||||
paint_mapping.insert(old_paint_id, new_paint_id);
|
||||
}
|
||||
|
||||
MergedPaletteInfo { render_target_mapping, paint_mapping }
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct MergedPaletteInfo {
|
||||
pub(crate) render_target_mapping: HashMap<RenderTargetId, RenderTargetId>,
|
||||
pub(crate) paint_mapping: HashMap<PaintId, PaintId>,
|
||||
}
|
||||
|
||||
impl PaintMetadata {
|
||||
pub(crate) fn filter(&self) -> Filter {
|
||||
match self.color_texture_metadata {
|
||||
None => Filter::None,
|
||||
Some(ref color_metadata) => {
|
||||
match color_metadata.filter {
|
||||
PaintFilter::None => Filter::None,
|
||||
PaintFilter::RadialGradient { line, radii } => {
|
||||
let uv_rect = rect_to_uv(color_metadata.location.rect,
|
||||
color_metadata.page_scale).contract(
|
||||
vec2f(0.0, color_metadata.page_scale.y() * 0.5));
|
||||
Filter::RadialGradient { line, radii, uv_origin: uv_rect.origin() }
|
||||
}
|
||||
PaintFilter::PatternFilter(pattern_filter) => {
|
||||
Filter::PatternFilter(pattern_filter)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn tile_batch_texture(&self) -> Option<TileBatchTexture> {
|
||||
self.color_texture_metadata.as_ref().map(PaintColorTextureMetadata::as_tile_batch_texture)
|
||||
}
|
||||
}
|
||||
|
||||
fn rect_to_uv(rect: RectI, texture_scale: Vector2F) -> RectF {
|
||||
rect.to_f32() * texture_scale
|
||||
}
|
||||
|
||||
// Gradient allocation
|
||||
|
||||
struct GradientTileBuilder {
|
||||
tiles: Vec<GradientTile>,
|
||||
}
|
||||
|
||||
struct GradientTile {
|
||||
texels: Vec<ColorU>,
|
||||
page: TexturePageId,
|
||||
next_index: u32,
|
||||
}
|
||||
|
||||
impl GradientTileBuilder {
|
||||
fn new() -> GradientTileBuilder {
|
||||
GradientTileBuilder { tiles: vec![] }
|
||||
}
|
||||
|
||||
fn allocate(&mut self, allocator: &mut TextureAllocator, gradient: &Gradient)
|
||||
-> TextureLocation {
|
||||
if self.tiles.is_empty() ||
|
||||
self.tiles.last().unwrap().next_index == GRADIENT_TILE_LENGTH {
|
||||
let size = Vector2I::splat(GRADIENT_TILE_LENGTH as i32);
|
||||
let area = size.x() as usize * size.y() as usize;
|
||||
self.tiles.push(GradientTile {
|
||||
texels: vec![ColorU::black(); area],
|
||||
page: allocator.allocate(size, AllocationMode::OwnPage).page,
|
||||
next_index: 0,
|
||||
})
|
||||
}
|
||||
|
||||
let mut data = self.tiles.last_mut().unwrap();
|
||||
let location = TextureLocation {
|
||||
page: data.page,
|
||||
rect: RectI::new(vec2i(0, data.next_index as i32),
|
||||
vec2i(GRADIENT_TILE_LENGTH as i32, 1)),
|
||||
};
|
||||
data.next_index += 1;
|
||||
|
||||
// FIXME(pcwalton): Paint transparent if gradient line has zero size, per spec.
|
||||
// TODO(pcwalton): Optimize this:
|
||||
// 1. Calculate ∇t up front and use differencing in the inner loop.
|
||||
// 2. Go four pixels at a time with SIMD.
|
||||
let first_address = location.rect.origin_y() as usize * GRADIENT_TILE_LENGTH as usize;
|
||||
for x in 0..(GRADIENT_TILE_LENGTH as i32) {
|
||||
let t = (x as f32 + 0.5) / GRADIENT_TILE_LENGTH as f32;
|
||||
data.texels[first_address + x as usize] = gradient.sample(t);
|
||||
}
|
||||
|
||||
location
|
||||
}
|
||||
|
||||
fn create_render_commands(self, render_commands: &mut Vec<RenderCommand>) {
|
||||
for tile in self.tiles {
|
||||
render_commands.push(RenderCommand::UploadTexelData {
|
||||
texels: Arc::new(tile.texels),
|
||||
location: TextureLocation {
|
||||
rect: RectI::new(vec2i(0, 0), Vector2I::splat(GRADIENT_TILE_LENGTH as i32)),
|
||||
page: tile.page,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ImageTexelInfo {
|
||||
location: TextureLocation,
|
||||
texels: Arc<Vec<ColorU>>,
|
||||
}
|
||||
|
||||
impl PaintColorTextureMetadata {
|
||||
pub(crate) fn as_tile_batch_texture(&self) -> TileBatchTexture {
|
||||
TileBatchTexture {
|
||||
page: self.location.page,
|
||||
sampling_flags: self.sampling_flags,
|
||||
composite_op: self.composite_op,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,419 +0,0 @@
|
|||
// pathfinder/renderer/src/scene.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.
|
||||
|
||||
//! A set of paths to be rendered.
|
||||
|
||||
use crate::builder::SceneBuilder;
|
||||
use crate::concurrent::executor::Executor;
|
||||
use crate::options::{BuildOptions, PreparedBuildOptions};
|
||||
use crate::options::{PreparedRenderTransform, RenderCommandListener};
|
||||
use crate::paint::{MergedPaletteInfo, Paint, PaintId, PaintInfo, Palette};
|
||||
use pathfinder_content::effects::BlendMode;
|
||||
use pathfinder_content::fill::FillRule;
|
||||
use pathfinder_content::outline::Outline;
|
||||
use pathfinder_content::render_target::RenderTargetId;
|
||||
use pathfinder_geometry::rect::RectF;
|
||||
use pathfinder_geometry::transform2d::Transform2F;
|
||||
use pathfinder_geometry::vector::{Vector2I, vec2f};
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
static NEXT_SCENE_ID: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Scene {
|
||||
pub(crate) display_list: Vec<DisplayItem>,
|
||||
pub(crate) paths: Vec<DrawPath>,
|
||||
pub(crate) clip_paths: Vec<ClipPath>,
|
||||
palette: Palette,
|
||||
bounds: RectF,
|
||||
view_box: RectF,
|
||||
id: SceneId,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct SceneId(pub u32);
|
||||
|
||||
impl Scene {
|
||||
#[inline]
|
||||
pub fn new() -> Scene {
|
||||
let scene_id = SceneId(NEXT_SCENE_ID.fetch_add(1, Ordering::Relaxed) as u32);
|
||||
Scene {
|
||||
display_list: vec![],
|
||||
paths: vec![],
|
||||
clip_paths: vec![],
|
||||
palette: Palette::new(scene_id),
|
||||
bounds: RectF::default(),
|
||||
view_box: RectF::default(),
|
||||
id: scene_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_path(&mut self, path: DrawPath) {
|
||||
let path_index = self.paths.len() as u32;
|
||||
self.paths.push(path);
|
||||
self.push_path_with_index(path_index);
|
||||
}
|
||||
|
||||
fn push_path_with_index(&mut self, path_index: u32) {
|
||||
self.bounds = self.bounds.union_rect(self.paths[path_index as usize].outline.bounds());
|
||||
|
||||
if let Some(DisplayItem::DrawPaths {
|
||||
start_index: _,
|
||||
ref mut end_index
|
||||
}) = self.display_list.last_mut() {
|
||||
*end_index = path_index + 1;
|
||||
} else {
|
||||
self.display_list.push(DisplayItem::DrawPaths {
|
||||
start_index: path_index,
|
||||
end_index: path_index + 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_clip_path(&mut self, clip_path: ClipPath) -> ClipPathId {
|
||||
self.bounds = self.bounds.union_rect(clip_path.outline.bounds());
|
||||
let clip_path_id = ClipPathId(self.clip_paths.len() as u32);
|
||||
self.clip_paths.push(clip_path);
|
||||
clip_path_id
|
||||
}
|
||||
|
||||
pub fn push_render_target(&mut self, render_target: RenderTarget) -> RenderTargetId {
|
||||
let render_target_id = self.palette.push_render_target(render_target);
|
||||
self.display_list.push(DisplayItem::PushRenderTarget(render_target_id));
|
||||
render_target_id
|
||||
}
|
||||
|
||||
pub fn pop_render_target(&mut self) {
|
||||
self.display_list.push(DisplayItem::PopRenderTarget);
|
||||
}
|
||||
|
||||
pub fn append_scene(&mut self, scene: Scene) {
|
||||
let MergedPaletteInfo {
|
||||
render_target_mapping,
|
||||
paint_mapping,
|
||||
} = self.palette.append_palette(scene.palette);
|
||||
|
||||
// Merge clip paths.
|
||||
let mut clip_path_mapping = Vec::with_capacity(scene.clip_paths.len());
|
||||
for clip_path in scene.clip_paths {
|
||||
clip_path_mapping.push(self.clip_paths.len());
|
||||
self.clip_paths.push(clip_path);
|
||||
}
|
||||
|
||||
// Merge draw paths.
|
||||
let mut draw_path_mapping = Vec::with_capacity(scene.paths.len());
|
||||
for draw_path in scene.paths {
|
||||
draw_path_mapping.push(self.paths.len() as u32);
|
||||
self.paths.push(DrawPath {
|
||||
outline: draw_path.outline,
|
||||
paint: paint_mapping[&draw_path.paint],
|
||||
clip_path: draw_path.clip_path.map(|clip_path_id| {
|
||||
ClipPathId(clip_path_mapping[clip_path_id.0 as usize] as u32)
|
||||
}),
|
||||
fill_rule: draw_path.fill_rule,
|
||||
blend_mode: draw_path.blend_mode,
|
||||
name: draw_path.name,
|
||||
});
|
||||
}
|
||||
|
||||
// Merge display items.
|
||||
for display_item in scene.display_list {
|
||||
match display_item {
|
||||
DisplayItem::PushRenderTarget(old_render_target_id) => {
|
||||
let new_render_target_id = render_target_mapping[&old_render_target_id];
|
||||
self.display_list.push(DisplayItem::PushRenderTarget(new_render_target_id));
|
||||
}
|
||||
DisplayItem::PopRenderTarget => {
|
||||
self.display_list.push(DisplayItem::PopRenderTarget);
|
||||
}
|
||||
DisplayItem::DrawPaths {
|
||||
start_index: old_start_path_index,
|
||||
end_index: old_end_path_index,
|
||||
} => {
|
||||
for old_path_index in old_start_path_index..old_end_path_index {
|
||||
self.push_path_with_index(draw_path_mapping[old_path_index as usize])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn build_paint_info(&mut self, render_transform: Transform2F) -> PaintInfo {
|
||||
self.palette.build_paint_info(render_transform)
|
||||
}
|
||||
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
pub fn push_paint(&mut self, paint: &Paint) -> PaintId {
|
||||
self.palette.push_paint(paint)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn path_count(&self) -> usize {
|
||||
self.paths.len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn bounds(&self) -> RectF {
|
||||
self.bounds
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_bounds(&mut self, new_bounds: RectF) {
|
||||
self.bounds = new_bounds;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn view_box(&self) -> RectF {
|
||||
self.view_box
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_view_box(&mut self, new_view_box: RectF) {
|
||||
self.view_box = new_view_box;
|
||||
}
|
||||
|
||||
pub(crate) fn apply_render_options(
|
||||
&self,
|
||||
original_outline: &Outline,
|
||||
options: &PreparedBuildOptions,
|
||||
) -> Outline {
|
||||
let effective_view_box = self.effective_view_box(options);
|
||||
|
||||
let mut outline;
|
||||
match options.transform {
|
||||
PreparedRenderTransform::Perspective {
|
||||
ref perspective,
|
||||
ref clip_polygon,
|
||||
..
|
||||
} => {
|
||||
if original_outline.is_outside_polygon(clip_polygon) {
|
||||
outline = Outline::new();
|
||||
} else {
|
||||
outline = (*original_outline).clone();
|
||||
outline.close_all_contours();
|
||||
outline.clip_against_polygon(clip_polygon);
|
||||
outline.apply_perspective(perspective);
|
||||
|
||||
// TODO(pcwalton): Support subpixel AA in 3D.
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// TODO(pcwalton): Short circuit.
|
||||
outline = (*original_outline).clone();
|
||||
outline.close_all_contours();
|
||||
if options.transform.is_2d() || options.subpixel_aa_enabled {
|
||||
let mut transform = match options.transform {
|
||||
PreparedRenderTransform::Transform2D(transform) => transform,
|
||||
PreparedRenderTransform::None => Transform2F::default(),
|
||||
PreparedRenderTransform::Perspective { .. } => unreachable!(),
|
||||
};
|
||||
if options.subpixel_aa_enabled {
|
||||
transform *= Transform2F::from_scale(vec2f(3.0, 1.0))
|
||||
}
|
||||
outline.transform(&transform);
|
||||
}
|
||||
outline.clip_against_rect(effective_view_box);
|
||||
}
|
||||
}
|
||||
|
||||
if !options.dilation.is_zero() {
|
||||
outline.dilate(options.dilation);
|
||||
}
|
||||
|
||||
// TODO(pcwalton): Fold this into previous passes to avoid unnecessary clones during
|
||||
// monotonic conversion.
|
||||
outline.prepare_for_tiling(self.effective_view_box(options));
|
||||
outline
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn effective_view_box(&self, render_options: &PreparedBuildOptions) -> RectF {
|
||||
if render_options.subpixel_aa_enabled {
|
||||
self.view_box * vec2f(3.0, 1.0)
|
||||
} else {
|
||||
self.view_box
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn build<E>(&mut self,
|
||||
options: BuildOptions,
|
||||
listener: Box<dyn RenderCommandListener>,
|
||||
executor: &E)
|
||||
where E: Executor {
|
||||
let prepared_options = options.prepare(self.bounds);
|
||||
SceneBuilder::new(self, &prepared_options, listener).build(executor)
|
||||
}
|
||||
|
||||
pub fn paths<'a>(&'a self) -> PathIter {
|
||||
PathIter {
|
||||
scene: self,
|
||||
pos: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PathIter<'a> {
|
||||
scene: &'a Scene,
|
||||
pos: usize
|
||||
}
|
||||
|
||||
impl<'a> Iterator for PathIter<'a> {
|
||||
type Item = (&'a Paint, &'a Outline, &'a str);
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let item = self.scene.paths.get(self.pos).map(|path_object| {
|
||||
(
|
||||
self.scene.palette.paints.get(path_object.paint.0 as usize).unwrap(),
|
||||
&path_object.outline,
|
||||
&*path_object.name
|
||||
)
|
||||
});
|
||||
self.pos += 1;
|
||||
item
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DrawPath {
|
||||
outline: Outline,
|
||||
paint: PaintId,
|
||||
clip_path: Option<ClipPathId>,
|
||||
fill_rule: FillRule,
|
||||
blend_mode: BlendMode,
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ClipPath {
|
||||
outline: Outline,
|
||||
fill_rule: FillRule,
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct ClipPathId(pub u32);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RenderTarget {
|
||||
size: Vector2I,
|
||||
name: String,
|
||||
}
|
||||
|
||||
/// Drawing commands.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum DisplayItem {
|
||||
/// Draws paths to the render target on top of the stack.
|
||||
DrawPaths { start_index: u32, end_index: u32 },
|
||||
|
||||
/// Pushes a render target onto the top of the stack.
|
||||
PushRenderTarget(RenderTargetId),
|
||||
|
||||
/// Pops a render target from the stack.
|
||||
PopRenderTarget,
|
||||
}
|
||||
|
||||
impl DrawPath {
|
||||
#[inline]
|
||||
pub fn new(outline: Outline, paint: PaintId) -> DrawPath {
|
||||
DrawPath {
|
||||
outline,
|
||||
paint,
|
||||
clip_path: None,
|
||||
fill_rule: FillRule::Winding,
|
||||
blend_mode: BlendMode::SrcOver,
|
||||
name: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn outline(&self) -> &Outline {
|
||||
&self.outline
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn clip_path(&self) -> Option<ClipPathId> {
|
||||
self.clip_path
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_clip_path(&mut self, new_clip_path: Option<ClipPathId>) {
|
||||
self.clip_path = new_clip_path
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn paint(&self) -> PaintId {
|
||||
self.paint
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn fill_rule(&self) -> FillRule {
|
||||
self.fill_rule
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_fill_rule(&mut self, new_fill_rule: FillRule) {
|
||||
self.fill_rule = new_fill_rule
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn blend_mode(&self) -> BlendMode {
|
||||
self.blend_mode
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_blend_mode(&mut self, new_blend_mode: BlendMode) {
|
||||
self.blend_mode = new_blend_mode
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_name(&mut self, new_name: String) {
|
||||
self.name = new_name
|
||||
}
|
||||
}
|
||||
|
||||
impl ClipPath {
|
||||
#[inline]
|
||||
pub fn new(outline: Outline) -> ClipPath {
|
||||
ClipPath { outline, fill_rule: FillRule::Winding, name: String::new() }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn outline(&self) -> &Outline {
|
||||
&self.outline
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn fill_rule(&self) -> FillRule {
|
||||
self.fill_rule
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_fill_rule(&mut self, new_fill_rule: FillRule) {
|
||||
self.fill_rule = new_fill_rule
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_name(&mut self, new_name: String) {
|
||||
self.name = new_name
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderTarget {
|
||||
#[inline]
|
||||
pub fn new(size: Vector2I, name: String) -> RenderTarget {
|
||||
RenderTarget { size, name }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn size(&self) -> Vector2I {
|
||||
self.size
|
||||
}
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
// pathfinder/renderer/src/tile_map.rs
|
||||
//
|
||||
// Copyright © 2019 The Pathfinder Project Developers.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use pathfinder_geometry::rect::RectI;
|
||||
use pathfinder_geometry::vector::{Vector2I, vec2i};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DenseTileMap<T> {
|
||||
pub data: Vec<T>,
|
||||
pub rect: RectI,
|
||||
}
|
||||
|
||||
impl<T> DenseTileMap<T> {
|
||||
#[inline]
|
||||
pub fn new(rect: RectI) -> DenseTileMap<T>
|
||||
where
|
||||
T: Copy + Clone + Default,
|
||||
{
|
||||
let length = rect.size().x() as usize * rect.size().y() as usize;
|
||||
DenseTileMap {
|
||||
data: vec![T::default(); length],
|
||||
rect,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_builder<F>(build: F, rect: RectI) -> DenseTileMap<T>
|
||||
where
|
||||
F: FnMut(usize) -> T,
|
||||
{
|
||||
let length = rect.size().x() as usize * rect.size().y() as usize;
|
||||
DenseTileMap {
|
||||
data: (0..length).map(build).collect(),
|
||||
rect,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get(&self, coords: Vector2I) -> Option<&T> {
|
||||
self.coords_to_index(coords).and_then(|index| self.data.get(index))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn coords_to_index(&self, coords: Vector2I) -> Option<usize> {
|
||||
if self.rect.contains_point(coords) {
|
||||
Some(self.coords_to_index_unchecked(coords))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn coords_to_index_unchecked(&self, coords: Vector2I) -> usize {
|
||||
(coords.y() - self.rect.min_y()) as usize * self.rect.size().x() as usize
|
||||
+ (coords.x() - self.rect.min_x()) as usize
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn index_to_coords(&self, index: usize) -> Vector2I {
|
||||
let (width, index) = (self.rect.size().x(), index as i32);
|
||||
self.rect.origin() + vec2i(index % width, index / width)
|
||||
}
|
||||
}
|
|
@ -1,701 +0,0 @@
|
|||
// pathfinder/renderer/src/tiles.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::builder::{BuiltPath, ObjectBuilder, Occluder, SceneBuilder, SolidTiles};
|
||||
use crate::gpu_data::{AlphaTileId, TileObjectPrimitive};
|
||||
use crate::paint::{PaintId, PaintMetadata};
|
||||
use pathfinder_content::effects::BlendMode;
|
||||
use pathfinder_content::fill::FillRule;
|
||||
use pathfinder_content::outline::{Contour, Outline, PointIndex};
|
||||
use pathfinder_content::segment::Segment;
|
||||
use pathfinder_content::sorted_vector::SortedVector;
|
||||
use pathfinder_geometry::line_segment::LineSegment2F;
|
||||
use pathfinder_geometry::rect::{RectF, RectI};
|
||||
use pathfinder_geometry::vector::{Vector2F, Vector2I, vec2f, vec2i};
|
||||
use std::cmp::Ordering;
|
||||
use std::mem;
|
||||
|
||||
// TODO(pcwalton): Make this configurable.
|
||||
const FLATTENING_TOLERANCE: f32 = 0.1;
|
||||
|
||||
pub const TILE_WIDTH: u32 = 16;
|
||||
pub const TILE_HEIGHT: u32 = 16;
|
||||
|
||||
pub(crate) struct Tiler<'a, 'b> {
|
||||
scene_builder: &'a SceneBuilder<'b, 'a>,
|
||||
pub(crate) object_builder: ObjectBuilder,
|
||||
outline: &'a Outline,
|
||||
path_info: TilingPathInfo<'a>,
|
||||
|
||||
point_queue: SortedVector<QueuedEndpoint>,
|
||||
active_edges: SortedVector<ActiveEdge>,
|
||||
old_active_edges: Vec<ActiveEdge>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) enum TilingPathInfo<'a> {
|
||||
Clip,
|
||||
Draw(DrawTilingPathInfo<'a>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct DrawTilingPathInfo<'a> {
|
||||
pub(crate) paint_id: PaintId,
|
||||
pub(crate) paint_metadata: &'a PaintMetadata,
|
||||
pub(crate) blend_mode: BlendMode,
|
||||
pub(crate) built_clip_path: Option<&'a BuiltPath>,
|
||||
pub(crate) fill_rule: FillRule,
|
||||
}
|
||||
|
||||
impl<'a, 'b> Tiler<'a, 'b> {
|
||||
#[allow(clippy::or_fun_call)]
|
||||
pub(crate) fn new(
|
||||
scene_builder: &'a SceneBuilder<'b, 'a>,
|
||||
outline: &'a Outline,
|
||||
fill_rule: FillRule,
|
||||
view_box: RectF,
|
||||
path_info: TilingPathInfo<'a>,
|
||||
) -> Tiler<'a, 'b> {
|
||||
let bounds = outline
|
||||
.bounds()
|
||||
.intersection(view_box)
|
||||
.unwrap_or(RectF::default());
|
||||
let object_builder = ObjectBuilder::new(bounds, view_box, fill_rule, &path_info);
|
||||
|
||||
Tiler {
|
||||
scene_builder,
|
||||
object_builder,
|
||||
outline,
|
||||
path_info,
|
||||
|
||||
point_queue: SortedVector::new(),
|
||||
active_edges: SortedVector::new(),
|
||||
old_active_edges: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn generate_tiles(&mut self) {
|
||||
// Initialize the point queue.
|
||||
self.init_point_queue();
|
||||
|
||||
// Reset active edges.
|
||||
self.active_edges.clear();
|
||||
self.old_active_edges.clear();
|
||||
|
||||
// Generate strips.
|
||||
let tile_rect = self.object_builder.tile_rect();
|
||||
for strip_origin_y in tile_rect.min_y()..tile_rect.max_y() {
|
||||
self.generate_strip(strip_origin_y);
|
||||
}
|
||||
|
||||
// Pack and cull.
|
||||
self.pack_and_cull();
|
||||
|
||||
// Done!
|
||||
debug!("{:#?}", self.object_builder.built_path);
|
||||
}
|
||||
|
||||
fn generate_strip(&mut self, strip_origin_y: i32) {
|
||||
// Process old active edges.
|
||||
self.process_old_active_edges(strip_origin_y);
|
||||
|
||||
// Add new active edges.
|
||||
let strip_max_y = ((i32::from(strip_origin_y) + 1) * TILE_HEIGHT as i32) as f32;
|
||||
while let Some(queued_endpoint) = self.point_queue.peek() {
|
||||
// We're done when we see an endpoint that belongs to the next tile strip.
|
||||
//
|
||||
// Note that this test must be `>`, not `>=`, in order to make sure we don't miss
|
||||
// active edges that lie precisely on the tile strip boundary.
|
||||
if queued_endpoint.y > strip_max_y {
|
||||
break;
|
||||
}
|
||||
|
||||
self.add_new_active_edge(strip_origin_y);
|
||||
}
|
||||
}
|
||||
|
||||
fn pack_and_cull(&mut self) {
|
||||
let draw_tiling_path_info = match self.path_info {
|
||||
TilingPathInfo::Clip => return,
|
||||
TilingPathInfo::Draw(draw_tiling_path_info) => draw_tiling_path_info,
|
||||
};
|
||||
|
||||
let blend_mode_is_destructive = draw_tiling_path_info.blend_mode.is_destructive();
|
||||
|
||||
for (draw_tile_index, draw_tile) in self.object_builder
|
||||
.built_path
|
||||
.tiles
|
||||
.data
|
||||
.iter()
|
||||
.enumerate() {
|
||||
let packed_tile = PackedTile::new(draw_tile_index as u32,
|
||||
draw_tile,
|
||||
&draw_tiling_path_info,
|
||||
&self.object_builder);
|
||||
|
||||
match packed_tile.tile_type {
|
||||
TileType::Solid => {
|
||||
match self.object_builder.built_path.solid_tiles {
|
||||
SolidTiles::Occluders(ref mut occluders) => {
|
||||
occluders.push(Occluder::new(packed_tile.tile_coords));
|
||||
}
|
||||
SolidTiles::Regular(ref mut solid_tiles) => {
|
||||
packed_tile.add_to(solid_tiles,
|
||||
&mut self.object_builder.built_path.clip_tiles,
|
||||
&draw_tiling_path_info,
|
||||
&self.scene_builder);
|
||||
}
|
||||
}
|
||||
}
|
||||
TileType::SingleMask => {
|
||||
debug_assert_ne!(packed_tile.draw_tile.alpha_tile_id.page(), !0);
|
||||
packed_tile.add_to(&mut self.object_builder.built_path.single_mask_tiles,
|
||||
&mut self.object_builder.built_path.clip_tiles,
|
||||
&draw_tiling_path_info,
|
||||
&self.scene_builder);
|
||||
}
|
||||
TileType::Empty if blend_mode_is_destructive => {
|
||||
packed_tile.add_to(&mut self.object_builder.built_path.empty_tiles,
|
||||
&mut self.object_builder.built_path.clip_tiles,
|
||||
&draw_tiling_path_info,
|
||||
&self.scene_builder);
|
||||
}
|
||||
TileType::Empty => {
|
||||
// Just cull.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_old_active_edges(&mut self, tile_y: i32) {
|
||||
let mut current_tile_x = self.object_builder.tile_rect().min_x();
|
||||
let mut current_subtile_x = 0.0;
|
||||
let mut current_winding = 0;
|
||||
|
||||
debug_assert!(self.old_active_edges.is_empty());
|
||||
mem::swap(&mut self.old_active_edges, &mut self.active_edges.array);
|
||||
|
||||
// FIXME(pcwalton): Yuck.
|
||||
let mut last_segment_x = -9999.0;
|
||||
|
||||
let tile_top = (i32::from(tile_y) * TILE_HEIGHT as i32) as f32;
|
||||
|
||||
debug!("---------- tile y {}({}) ----------", tile_y, tile_top);
|
||||
debug!("old active edges: {:#?}", self.old_active_edges);
|
||||
|
||||
for mut active_edge in self.old_active_edges.drain(..) {
|
||||
// Determine x-intercept and winding.
|
||||
let segment_x = active_edge.crossing.x();
|
||||
let edge_winding =
|
||||
if active_edge.segment.baseline.from_y() < active_edge.segment.baseline.to_y() {
|
||||
1
|
||||
} else {
|
||||
-1
|
||||
};
|
||||
|
||||
debug!(
|
||||
"tile Y {}({}): segment_x={} edge_winding={} current_tile_x={} \
|
||||
current_subtile_x={} current_winding={}",
|
||||
tile_y,
|
||||
tile_top,
|
||||
segment_x,
|
||||
edge_winding,
|
||||
current_tile_x,
|
||||
current_subtile_x,
|
||||
current_winding
|
||||
);
|
||||
debug!(
|
||||
"... segment={:#?} crossing={:?}",
|
||||
active_edge.segment, active_edge.crossing
|
||||
);
|
||||
|
||||
// FIXME(pcwalton): Remove this debug code!
|
||||
debug_assert!(segment_x >= last_segment_x);
|
||||
last_segment_x = segment_x;
|
||||
|
||||
// Do initial subtile fill, if necessary.
|
||||
let segment_tile_x = f32::floor(segment_x) as i32 / TILE_WIDTH as i32;
|
||||
if current_tile_x < segment_tile_x && current_subtile_x > 0.0 {
|
||||
let current_x =
|
||||
(i32::from(current_tile_x) * TILE_WIDTH as i32) as f32 + current_subtile_x;
|
||||
let tile_right_x = ((i32::from(current_tile_x) + 1) * TILE_WIDTH as i32) as f32;
|
||||
let current_tile_coords = vec2i(current_tile_x, tile_y);
|
||||
self.object_builder.add_active_fill(
|
||||
self.scene_builder,
|
||||
current_x,
|
||||
tile_right_x,
|
||||
current_winding,
|
||||
current_tile_coords,
|
||||
);
|
||||
current_tile_x += 1;
|
||||
current_subtile_x = 0.0;
|
||||
}
|
||||
|
||||
// Move over to the correct tile, filling in as we go.
|
||||
while current_tile_x < segment_tile_x {
|
||||
debug!(
|
||||
"... emitting backdrop {} @ tile {}",
|
||||
current_winding, current_tile_x
|
||||
);
|
||||
let current_tile_coords = vec2i(current_tile_x, tile_y);
|
||||
if let Some(tile_index) = self.object_builder
|
||||
.tile_coords_to_local_index(current_tile_coords) {
|
||||
// FIXME(pcwalton): Handle winding overflow.
|
||||
self.object_builder.built_path.tiles.data[tile_index as usize].backdrop =
|
||||
current_winding as i8;
|
||||
}
|
||||
|
||||
current_tile_x += 1;
|
||||
current_subtile_x = 0.0;
|
||||
}
|
||||
|
||||
// Do final subtile fill, if necessary.
|
||||
debug_assert_eq!(current_tile_x, segment_tile_x);
|
||||
let segment_subtile_x =
|
||||
segment_x - (i32::from(current_tile_x) * TILE_WIDTH as i32) as f32;
|
||||
if segment_subtile_x > current_subtile_x {
|
||||
let current_x =
|
||||
(i32::from(current_tile_x) * TILE_WIDTH as i32) as f32 + current_subtile_x;
|
||||
let current_tile_coords = vec2i(current_tile_x, tile_y);
|
||||
self.object_builder.add_active_fill(
|
||||
self.scene_builder,
|
||||
current_x,
|
||||
segment_x,
|
||||
current_winding,
|
||||
current_tile_coords,
|
||||
);
|
||||
current_subtile_x = segment_subtile_x;
|
||||
}
|
||||
|
||||
// Update winding.
|
||||
current_winding += edge_winding;
|
||||
|
||||
// Process the edge.
|
||||
debug!("about to process existing active edge {:#?}", active_edge);
|
||||
debug_assert!(f32::abs(active_edge.crossing.y() - tile_top) < 0.1);
|
||||
active_edge.process(self.scene_builder, &mut self.object_builder, tile_y);
|
||||
if !active_edge.segment.is_none() {
|
||||
self.active_edges.push(active_edge);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_new_active_edge(&mut self, tile_y: i32) {
|
||||
let outline = &self.outline;
|
||||
let point_index = self.point_queue.pop().unwrap().point_index;
|
||||
|
||||
let contour = &outline.contours()[point_index.contour() as usize];
|
||||
|
||||
// TODO(pcwalton): Could use a bitset of processed edges…
|
||||
let prev_endpoint_index = contour.prev_endpoint_index_of(point_index.point());
|
||||
let next_endpoint_index = contour.next_endpoint_index_of(point_index.point());
|
||||
|
||||
debug!(
|
||||
"adding new active edge, tile_y={} point_index={} prev={} next={} pos={:?} \
|
||||
prevpos={:?} nextpos={:?}",
|
||||
tile_y,
|
||||
point_index.point(),
|
||||
prev_endpoint_index,
|
||||
next_endpoint_index,
|
||||
contour.position_of(point_index.point()),
|
||||
contour.position_of(prev_endpoint_index),
|
||||
contour.position_of(next_endpoint_index)
|
||||
);
|
||||
|
||||
if contour.point_is_logically_above(point_index.point(), prev_endpoint_index) {
|
||||
debug!("... adding prev endpoint");
|
||||
|
||||
process_active_segment(
|
||||
contour,
|
||||
prev_endpoint_index,
|
||||
&mut self.active_edges,
|
||||
self.scene_builder,
|
||||
&mut self.object_builder,
|
||||
tile_y,
|
||||
);
|
||||
|
||||
self.point_queue.push(QueuedEndpoint {
|
||||
point_index: PointIndex::new(point_index.contour(), prev_endpoint_index),
|
||||
y: contour.position_of(prev_endpoint_index).y(),
|
||||
});
|
||||
|
||||
debug!("... done adding prev endpoint");
|
||||
}
|
||||
|
||||
if contour.point_is_logically_above(point_index.point(), next_endpoint_index) {
|
||||
debug!(
|
||||
"... adding next endpoint {} -> {}",
|
||||
point_index.point(),
|
||||
next_endpoint_index
|
||||
);
|
||||
|
||||
process_active_segment(
|
||||
contour,
|
||||
point_index.point(),
|
||||
&mut self.active_edges,
|
||||
self.scene_builder,
|
||||
&mut self.object_builder,
|
||||
tile_y,
|
||||
);
|
||||
|
||||
self.point_queue.push(QueuedEndpoint {
|
||||
point_index: PointIndex::new(point_index.contour(), next_endpoint_index),
|
||||
y: contour.position_of(next_endpoint_index).y(),
|
||||
});
|
||||
|
||||
debug!("... done adding next endpoint");
|
||||
}
|
||||
}
|
||||
|
||||
fn init_point_queue(&mut self) {
|
||||
// Find MIN points.
|
||||
self.point_queue.clear();
|
||||
for (contour_index, contour) in self.outline.contours().iter().enumerate() {
|
||||
let contour_index = contour_index as u32;
|
||||
let mut cur_endpoint_index = 0;
|
||||
let mut prev_endpoint_index = contour.prev_endpoint_index_of(cur_endpoint_index);
|
||||
let mut next_endpoint_index = contour.next_endpoint_index_of(cur_endpoint_index);
|
||||
loop {
|
||||
if contour.point_is_logically_above(cur_endpoint_index, prev_endpoint_index)
|
||||
&& contour.point_is_logically_above(cur_endpoint_index, next_endpoint_index)
|
||||
{
|
||||
self.point_queue.push(QueuedEndpoint {
|
||||
point_index: PointIndex::new(contour_index, cur_endpoint_index),
|
||||
y: contour.position_of(cur_endpoint_index).y(),
|
||||
});
|
||||
}
|
||||
|
||||
if cur_endpoint_index >= next_endpoint_index {
|
||||
break;
|
||||
}
|
||||
|
||||
prev_endpoint_index = cur_endpoint_index;
|
||||
cur_endpoint_index = next_endpoint_index;
|
||||
next_endpoint_index = contour.next_endpoint_index_of(cur_endpoint_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TilingPathInfo<'a> {
|
||||
pub(crate) fn has_destructive_blend_mode(&self) -> bool {
|
||||
match *self {
|
||||
TilingPathInfo::Draw(ref draw_tiling_path_info) => {
|
||||
draw_tiling_path_info.blend_mode.is_destructive()
|
||||
}
|
||||
TilingPathInfo::Clip => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct PackedTile<'a> {
|
||||
pub(crate) tile_type: TileType,
|
||||
pub(crate) tile_coords: Vector2I,
|
||||
pub(crate) draw_tile: &'a TileObjectPrimitive,
|
||||
pub(crate) clip_tile: Option<&'a TileObjectPrimitive>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub(crate) enum TileType {
|
||||
Solid,
|
||||
Empty,
|
||||
SingleMask,
|
||||
}
|
||||
|
||||
impl<'a> PackedTile<'a> {
|
||||
fn new(draw_tile_index: u32,
|
||||
draw_tile: &'a TileObjectPrimitive,
|
||||
draw_tiling_path_info: &DrawTilingPathInfo<'a>,
|
||||
object_builder: &ObjectBuilder)
|
||||
-> PackedTile<'a> {
|
||||
let tile_coords = object_builder.local_tile_index_to_coords(draw_tile_index as u32);
|
||||
|
||||
// First, if the draw tile is empty, cull it regardless of clip.
|
||||
if draw_tile.is_solid() {
|
||||
match (object_builder.built_path.fill_rule, draw_tile.backdrop) {
|
||||
(FillRule::Winding, 0) => {
|
||||
return PackedTile {
|
||||
tile_type: TileType::Empty,
|
||||
tile_coords,
|
||||
draw_tile,
|
||||
clip_tile: None,
|
||||
};
|
||||
}
|
||||
(FillRule::Winding, _) => {}
|
||||
(FillRule::EvenOdd, backdrop) if backdrop % 2 == 0 => {
|
||||
return PackedTile {
|
||||
tile_type: TileType::Empty,
|
||||
tile_coords,
|
||||
draw_tile,
|
||||
clip_tile: None,
|
||||
};
|
||||
}
|
||||
(FillRule::EvenOdd, _) => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Figure out what clip tile we need, if any.
|
||||
let clip_tile = match draw_tiling_path_info.built_clip_path {
|
||||
None => None,
|
||||
Some(built_clip_path) => {
|
||||
match built_clip_path.tiles.get(tile_coords) {
|
||||
None => {
|
||||
// This tile is outside of the bounds of the clip path entirely. We can
|
||||
// cull it.
|
||||
return PackedTile {
|
||||
tile_type: TileType::Empty,
|
||||
tile_coords,
|
||||
draw_tile,
|
||||
clip_tile: None,
|
||||
};
|
||||
}
|
||||
Some(clip_tile) if clip_tile.is_solid() => {
|
||||
if clip_tile.backdrop != 0 {
|
||||
// The clip tile is fully opaque, so this tile isn't clipped at
|
||||
// all.
|
||||
None
|
||||
} else {
|
||||
// This tile is completely clipped out. Cull it.
|
||||
return PackedTile {
|
||||
tile_type: TileType::Empty,
|
||||
tile_coords,
|
||||
draw_tile,
|
||||
clip_tile: None,
|
||||
};
|
||||
}
|
||||
}
|
||||
Some(clip_tile) => Some(clip_tile),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Choose a tile type.
|
||||
match clip_tile {
|
||||
None if draw_tile.is_solid() => {
|
||||
// This is a solid tile that completely occludes the background.
|
||||
PackedTile { tile_type: TileType::Solid, tile_coords, draw_tile, clip_tile }
|
||||
}
|
||||
None => {
|
||||
// We have a draw tile and no clip tile.
|
||||
PackedTile {
|
||||
tile_type: TileType::SingleMask,
|
||||
tile_coords,
|
||||
draw_tile,
|
||||
clip_tile: None,
|
||||
}
|
||||
}
|
||||
Some(clip_tile) if draw_tile.is_solid() => {
|
||||
// We have a solid draw tile and a clip tile. This is effectively the same as
|
||||
// having a draw tile and no clip tile.
|
||||
//
|
||||
// FIXME(pcwalton): This doesn't preserve the fill rule of the clip path!
|
||||
PackedTile {
|
||||
tile_type: TileType::SingleMask,
|
||||
tile_coords,
|
||||
draw_tile: clip_tile,
|
||||
clip_tile: None,
|
||||
}
|
||||
}
|
||||
Some(clip_tile) => {
|
||||
// We have both a draw and clip mask. Composite them together.
|
||||
PackedTile {
|
||||
tile_type: TileType::SingleMask,
|
||||
tile_coords,
|
||||
draw_tile,
|
||||
clip_tile: Some(clip_tile),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn round_rect_out_to_tile_bounds(rect: RectF) -> RectI {
|
||||
(rect * vec2f(1.0 / TILE_WIDTH as f32, 1.0 / TILE_HEIGHT as f32)).round_out().to_i32()
|
||||
}
|
||||
|
||||
fn process_active_segment(
|
||||
contour: &Contour,
|
||||
from_endpoint_index: u32,
|
||||
active_edges: &mut SortedVector<ActiveEdge>,
|
||||
builder: &SceneBuilder,
|
||||
object_builder: &mut ObjectBuilder,
|
||||
tile_y: i32,
|
||||
) {
|
||||
let mut active_edge = ActiveEdge::from_segment(&contour.segment_after(from_endpoint_index));
|
||||
debug!("... process_active_segment({:#?})", active_edge);
|
||||
active_edge.process(builder, object_builder, tile_y);
|
||||
if !active_edge.segment.is_none() {
|
||||
debug!("... ... pushing resulting active edge: {:#?}", active_edge);
|
||||
active_edges.push(active_edge);
|
||||
}
|
||||
}
|
||||
|
||||
// Queued endpoints
|
||||
|
||||
#[derive(PartialEq)]
|
||||
struct QueuedEndpoint {
|
||||
point_index: PointIndex,
|
||||
y: f32,
|
||||
}
|
||||
|
||||
impl Eq for QueuedEndpoint {}
|
||||
|
||||
impl PartialOrd<QueuedEndpoint> for QueuedEndpoint {
|
||||
fn partial_cmp(&self, other: &QueuedEndpoint) -> Option<Ordering> {
|
||||
// NB: Reversed!
|
||||
(other.y, other.point_index).partial_cmp(&(self.y, self.point_index))
|
||||
}
|
||||
}
|
||||
|
||||
// Active edges
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
struct ActiveEdge {
|
||||
segment: Segment,
|
||||
// TODO(pcwalton): Shrink `crossing` down to just one f32?
|
||||
crossing: Vector2F,
|
||||
}
|
||||
|
||||
impl ActiveEdge {
|
||||
fn from_segment(segment: &Segment) -> ActiveEdge {
|
||||
let crossing = if segment.baseline.from_y() < segment.baseline.to_y() {
|
||||
segment.baseline.from()
|
||||
} else {
|
||||
segment.baseline.to()
|
||||
};
|
||||
ActiveEdge::from_segment_and_crossing(segment, crossing)
|
||||
}
|
||||
|
||||
fn from_segment_and_crossing(segment: &Segment, crossing: Vector2F) -> ActiveEdge {
|
||||
ActiveEdge { segment: *segment, crossing }
|
||||
}
|
||||
|
||||
fn process(&mut self,
|
||||
builder: &SceneBuilder,
|
||||
object_builder: &mut ObjectBuilder,
|
||||
tile_y: i32) {
|
||||
let tile_bottom = ((i32::from(tile_y) + 1) * TILE_HEIGHT as i32) as f32;
|
||||
debug!(
|
||||
"process_active_edge({:#?}, tile_y={}({}))",
|
||||
self, tile_y, tile_bottom
|
||||
);
|
||||
|
||||
let mut segment = self.segment;
|
||||
let winding = segment.baseline.y_winding();
|
||||
|
||||
if segment.is_line() {
|
||||
let line_segment = segment.as_line_segment();
|
||||
self.segment =
|
||||
match self.process_line_segment(line_segment, builder, object_builder, tile_y) {
|
||||
Some(lower_part) => Segment::line(lower_part),
|
||||
None => Segment::none(),
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(pcwalton): Don't degree elevate!
|
||||
if !segment.is_cubic() {
|
||||
segment = segment.to_cubic();
|
||||
}
|
||||
|
||||
// If necessary, draw initial line.
|
||||
if self.crossing.y() < segment.baseline.min_y() {
|
||||
let first_line_segment =
|
||||
LineSegment2F::new(self.crossing, segment.baseline.upper_point()).orient(winding);
|
||||
if self.process_line_segment(first_line_segment, builder, object_builder, tile_y)
|
||||
.is_some() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let mut oriented_segment = segment.orient(winding);
|
||||
loop {
|
||||
let mut split_t = 1.0;
|
||||
let mut before_segment = oriented_segment;
|
||||
let mut after_segment = None;
|
||||
|
||||
while !before_segment
|
||||
.as_cubic_segment()
|
||||
.is_flat(FLATTENING_TOLERANCE)
|
||||
{
|
||||
let next_t = 0.5 * split_t;
|
||||
let (before, after) = oriented_segment.as_cubic_segment().split(next_t);
|
||||
before_segment = before;
|
||||
after_segment = Some(after);
|
||||
split_t = next_t;
|
||||
}
|
||||
|
||||
debug!(
|
||||
"... tile_y={} winding={} segment={:?} t={} before_segment={:?}
|
||||
after_segment={:?}",
|
||||
tile_y, winding, segment, split_t, before_segment, after_segment
|
||||
);
|
||||
|
||||
let line = before_segment.baseline.orient(winding);
|
||||
match self.process_line_segment(line, builder, object_builder, tile_y) {
|
||||
Some(lower_part) if split_t == 1.0 => {
|
||||
self.segment = Segment::line(lower_part);
|
||||
return;
|
||||
}
|
||||
None if split_t == 1.0 => {
|
||||
self.segment = Segment::none();
|
||||
return;
|
||||
}
|
||||
Some(_) => {
|
||||
self.segment = after_segment.unwrap().orient(winding);
|
||||
return;
|
||||
}
|
||||
None => oriented_segment = after_segment.unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_line_segment(
|
||||
&mut self,
|
||||
line_segment: LineSegment2F,
|
||||
builder: &SceneBuilder,
|
||||
object_builder: &mut ObjectBuilder,
|
||||
tile_y: i32,
|
||||
) -> Option<LineSegment2F> {
|
||||
let tile_bottom = ((i32::from(tile_y) + 1) * TILE_HEIGHT as i32) as f32;
|
||||
debug!(
|
||||
"process_line_segment({:?}, tile_y={}) tile_bottom={}",
|
||||
line_segment, tile_y, tile_bottom
|
||||
);
|
||||
|
||||
if line_segment.max_y() <= tile_bottom {
|
||||
object_builder.generate_fill_primitives_for_line(builder, line_segment, tile_y);
|
||||
return None;
|
||||
}
|
||||
|
||||
let (upper_part, lower_part) = line_segment.split_at_y(tile_bottom);
|
||||
object_builder.generate_fill_primitives_for_line(builder, upper_part, tile_y);
|
||||
self.crossing = lower_part.upper_point();
|
||||
Some(lower_part)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd<ActiveEdge> for ActiveEdge {
|
||||
fn partial_cmp(&self, other: &ActiveEdge) -> Option<Ordering> {
|
||||
self.crossing.x().partial_cmp(&other.crossing.x())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TileObjectPrimitive {
|
||||
#[inline]
|
||||
fn default() -> TileObjectPrimitive {
|
||||
TileObjectPrimitive { backdrop: 0, alpha_tile_id: AlphaTileId::invalid() }
|
||||
}
|
||||
}
|
||||
|
||||
impl TileObjectPrimitive {
|
||||
#[inline]
|
||||
pub fn is_solid(&self) -> bool { !self.alpha_tile_id.is_valid() }
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
// pathfinder/renderer/src/z_buffer.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.
|
||||
|
||||
//! Software occlusion culling.
|
||||
|
||||
use crate::builder::Occluder;
|
||||
use crate::gpu_data::{Tile, TileBatch};
|
||||
use crate::paint::{PaintId, PaintMetadata};
|
||||
use crate::tile_map::DenseTileMap;
|
||||
use crate::tiles;
|
||||
use pathfinder_content::effects::BlendMode;
|
||||
use pathfinder_geometry::alignment::{AlignedU16, AlignedI16};
|
||||
use pathfinder_geometry::rect::RectF;
|
||||
use pathfinder_geometry::vector::Vector2I;
|
||||
use vec_map::VecMap;
|
||||
|
||||
pub(crate) struct ZBuffer {
|
||||
buffer: DenseTileMap<u32>,
|
||||
depth_metadata: VecMap<DepthMetadata>,
|
||||
}
|
||||
|
||||
pub(crate) struct SolidTiles {
|
||||
pub(crate) batches: Vec<TileBatch>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct DepthMetadata {
|
||||
pub(crate) paint_id: PaintId,
|
||||
}
|
||||
impl ZBuffer {
|
||||
pub(crate) fn new(view_box: RectF) -> ZBuffer {
|
||||
let tile_rect = tiles::round_rect_out_to_tile_bounds(view_box);
|
||||
ZBuffer {
|
||||
buffer: DenseTileMap::from_builder(|_| 0, tile_rect),
|
||||
depth_metadata: VecMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn test(&self, coords: Vector2I, depth: u32) -> bool {
|
||||
let tile_index = self.buffer.coords_to_index_unchecked(coords);
|
||||
self.buffer.data[tile_index as usize] < depth
|
||||
}
|
||||
|
||||
pub(crate) fn update(&mut self,
|
||||
solid_tiles: &[Occluder],
|
||||
depth: u32,
|
||||
metadata: DepthMetadata) {
|
||||
self.depth_metadata.insert(depth as usize, metadata);
|
||||
for solid_tile in solid_tiles {
|
||||
let tile_index = self.buffer.coords_to_index_unchecked(solid_tile.coords);
|
||||
let z_dest = &mut self.buffer.data[tile_index as usize];
|
||||
*z_dest = u32::max(*z_dest, depth);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn build_solid_tiles(&self, paint_metadata: &[PaintMetadata]) -> SolidTiles {
|
||||
let mut solid_tiles = SolidTiles { batches: vec![] };
|
||||
|
||||
for tile_index in 0..self.buffer.data.len() {
|
||||
let depth = self.buffer.data[tile_index];
|
||||
if depth == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let tile_coords = self.buffer.index_to_coords(tile_index);
|
||||
|
||||
let depth_metadata = self.depth_metadata[depth as usize];
|
||||
let paint_id = depth_metadata.paint_id;
|
||||
let paint_metadata = &paint_metadata[paint_id.0 as usize];
|
||||
|
||||
let tile_position = tile_coords + self.buffer.rect.origin();
|
||||
|
||||
// Create a batch if necessary.
|
||||
let paint_tile_batch_texture = paint_metadata.tile_batch_texture();
|
||||
let paint_filter = paint_metadata.filter();
|
||||
match solid_tiles.batches.last() {
|
||||
Some(TileBatch { color_texture: tile_batch_texture, filter: tile_filter, .. }) if
|
||||
*tile_batch_texture == paint_tile_batch_texture &&
|
||||
*tile_filter == paint_filter => {}
|
||||
_ => {
|
||||
// Batch break.
|
||||
//
|
||||
// TODO(pcwalton): We could be more aggressive with batching here, since we
|
||||
// know there are no overlaps.
|
||||
solid_tiles.batches.push(TileBatch {
|
||||
color_texture: paint_tile_batch_texture,
|
||||
tiles: vec![],
|
||||
filter: paint_filter,
|
||||
blend_mode: BlendMode::default(),
|
||||
tile_page: !0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let batch = solid_tiles.batches.last_mut().unwrap();
|
||||
batch.tiles.push(Tile::new_solid_from_paint_id(tile_position, paint_id));
|
||||
}
|
||||
|
||||
solid_tiles
|
||||
}
|
||||
}
|
||||
|
||||
impl Tile {
|
||||
pub(crate) fn new_solid_from_paint_id(tile_origin: Vector2I, paint_id: PaintId) -> Tile {
|
||||
Tile {
|
||||
tile_x: tile_origin.x() as AlignedI16,
|
||||
tile_y: tile_origin.y() as AlignedI16,
|
||||
mask_0_backdrop: 0,
|
||||
mask_0_u: 0,
|
||||
mask_0_v: 0,
|
||||
ctrl: 0,
|
||||
pad: 0,
|
||||
color: paint_id.0 as AlignedU16,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
[package]
|
||||
name = "pathfinder_resources"
|
||||
version = "0.5.0"
|
||||
edition = "2018"
|
||||
authors = ["Patrick Walton <pcwalton@mimiga.net>"]
|
||||
description = "Shaders, textures, etc. for the Pathfinder vector graphics library"
|
||||
license = "MIT/Apache-2.0"
|
||||
repository = "https://github.com/servo/pathfinder"
|
||||
homepage = "https://github.com/servo/pathfinder"
|
||||
|
||||
[dependencies]
|
|
@ -1,106 +0,0 @@
|
|||
# This file must contain the paths of all resources that are used by the Pathfinder library.
|
||||
#
|
||||
# When you add a new resource, make sure to add it to this file.
|
||||
|
||||
debug-fonts/regular.json
|
||||
shaders/gl3/blit.fs.glsl
|
||||
shaders/gl3/blit.vs.glsl
|
||||
shaders/gl3/clear.fs.glsl
|
||||
shaders/gl3/clear.vs.glsl
|
||||
shaders/gl3/debug_solid.fs.glsl
|
||||
shaders/gl3/debug_solid.vs.glsl
|
||||
shaders/gl3/debug_texture.fs.glsl
|
||||
shaders/gl3/debug_texture.vs.glsl
|
||||
shaders/gl3/demo_ground.fs.glsl
|
||||
shaders/gl3/demo_ground.vs.glsl
|
||||
shaders/gl3/fill.fs.glsl
|
||||
shaders/gl3/fill.vs.glsl
|
||||
shaders/gl3/reproject.fs.glsl
|
||||
shaders/gl3/reproject.vs.glsl
|
||||
shaders/gl3/stencil.fs.glsl
|
||||
shaders/gl3/stencil.vs.glsl
|
||||
shaders/gl3/tile.fs.glsl
|
||||
shaders/gl3/tile.vs.glsl
|
||||
shaders/gl3/tile_clip.fs.glsl
|
||||
shaders/gl3/tile_clip.vs.glsl
|
||||
shaders/gl3/tile_copy.fs.glsl
|
||||
shaders/gl3/tile_copy.vs.glsl
|
||||
shaders/gl4/blit.fs.glsl
|
||||
shaders/gl4/blit.vs.glsl
|
||||
shaders/gl4/clear.fs.glsl
|
||||
shaders/gl4/clear.vs.glsl
|
||||
shaders/gl4/debug_solid.fs.glsl
|
||||
shaders/gl4/debug_solid.vs.glsl
|
||||
shaders/gl4/debug_texture.fs.glsl
|
||||
shaders/gl4/debug_texture.vs.glsl
|
||||
shaders/gl4/demo_ground.fs.glsl
|
||||
shaders/gl4/demo_ground.vs.glsl
|
||||
shaders/gl4/fill.fs.glsl
|
||||
shaders/gl4/fill.vs.glsl
|
||||
shaders/gl4/reproject.fs.glsl
|
||||
shaders/gl4/reproject.vs.glsl
|
||||
shaders/gl4/stencil.fs.glsl
|
||||
shaders/gl4/stencil.vs.glsl
|
||||
shaders/gl4/tile.fs.glsl
|
||||
shaders/gl4/tile.vs.glsl
|
||||
shaders/gl4/tile_clip.fs.glsl
|
||||
shaders/gl4/tile_clip.vs.glsl
|
||||
shaders/gl4/tile_copy.fs.glsl
|
||||
shaders/gl4/tile_copy.vs.glsl
|
||||
shaders/metal/blit.fs.metal
|
||||
shaders/metal/blit.vs.metal
|
||||
shaders/metal/clear.fs.metal
|
||||
shaders/metal/clear.vs.metal
|
||||
shaders/metal/debug_solid.fs.metal
|
||||
shaders/metal/debug_solid.vs.metal
|
||||
shaders/metal/debug_texture.fs.metal
|
||||
shaders/metal/debug_texture.vs.metal
|
||||
shaders/metal/demo_ground.fs.metal
|
||||
shaders/metal/demo_ground.vs.metal
|
||||
shaders/metal/fill.fs.metal
|
||||
shaders/metal/fill.vs.metal
|
||||
shaders/metal/reproject.fs.metal
|
||||
shaders/metal/reproject.vs.metal
|
||||
shaders/metal/stencil.fs.metal
|
||||
shaders/metal/stencil.vs.metal
|
||||
shaders/metal/tile.fs.metal
|
||||
shaders/metal/tile.vs.metal
|
||||
shaders/metal/tile_clip.fs.metal
|
||||
shaders/metal/tile_clip.vs.metal
|
||||
shaders/metal/tile_copy.fs.metal
|
||||
shaders/metal/tile_copy.vs.metal
|
||||
shaders/vulkan/blit.fs.spv
|
||||
shaders/vulkan/blit.vs.spv
|
||||
shaders/vulkan/clear.fs.spv
|
||||
shaders/vulkan/clear.vs.spv
|
||||
shaders/vulkan/debug_solid.fs.spv
|
||||
shaders/vulkan/debug_solid.vs.spv
|
||||
shaders/vulkan/debug_texture.fs.spv
|
||||
shaders/vulkan/debug_texture.vs.spv
|
||||
shaders/vulkan/demo_ground.fs.spv
|
||||
shaders/vulkan/demo_ground.vs.spv
|
||||
shaders/vulkan/fill.fs.spv
|
||||
shaders/vulkan/fill.vs.spv
|
||||
shaders/vulkan/reproject.fs.spv
|
||||
shaders/vulkan/reproject.vs.spv
|
||||
shaders/vulkan/stencil.fs.spv
|
||||
shaders/vulkan/stencil.vs.spv
|
||||
shaders/vulkan/tile.fs.spv
|
||||
shaders/vulkan/tile.vs.spv
|
||||
shaders/vulkan/tile_clip.fs.spv
|
||||
shaders/vulkan/tile_clip.vs.spv
|
||||
shaders/vulkan/tile_copy.fs.spv
|
||||
shaders/vulkan/tile_copy.vs.spv
|
||||
textures/area-lut.png
|
||||
textures/debug-corner-fill.png
|
||||
textures/debug-corner-outline.png
|
||||
textures/debug-font.png
|
||||
textures/demo-background.png
|
||||
textures/demo-effects.png
|
||||
textures/demo-open.png
|
||||
textures/demo-rotate.png
|
||||
textures/demo-screenshot.png
|
||||
textures/demo-zoom-actual-size.png
|
||||
textures/demo-zoom-in.png
|
||||
textures/demo-zoom-out.png
|
||||
textures/gamma-lut.png
|
|
@ -1,51 +0,0 @@
|
|||
// pathfinder/resources/build.rs
|
||||
//
|
||||
// Copyright © 2020 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 std::env;
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::path::Path;
|
||||
|
||||
fn main() {
|
||||
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||
let dest_path = Path::new(&out_dir).join("manifest.rs");
|
||||
let mut dest = File::create(dest_path).unwrap();
|
||||
let cwd = env::current_dir().unwrap();
|
||||
|
||||
writeln!(&mut dest, "// Generated by `pathfinder/resources/build.rs`. Do not edit!\n").unwrap();
|
||||
writeln!(&mut dest,
|
||||
"pub static RESOURCES: &'static [(&'static str, &'static [u8])] = &[").unwrap();
|
||||
|
||||
let src = BufReader::new(File::open("MANIFEST").unwrap());
|
||||
for line in src.lines() {
|
||||
let line = line.unwrap();
|
||||
let line = line.trim_start().trim_end();
|
||||
if line.is_empty() || line.starts_with("#") {
|
||||
continue;
|
||||
}
|
||||
|
||||
let escaped_path = line.escape_default().to_string();
|
||||
let mut full_path = cwd.clone();
|
||||
full_path.push(line);
|
||||
let escaped_full_path = full_path.to_str().unwrap().escape_default().to_string();
|
||||
|
||||
writeln!(&mut dest,
|
||||
" (\"{}\", include_bytes!(\"{}\")),",
|
||||
escaped_path,
|
||||
escaped_full_path).unwrap();
|
||||
|
||||
println!("cargo:rerun-if-changed={}", line);
|
||||
}
|
||||
|
||||
writeln!(&mut dest, "];").unwrap();
|
||||
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
println!("cargo:rerun-if-changed=MANIFEST");
|
||||
}
|
|
@ -1,105 +0,0 @@
|
|||
{
|
||||
"name": "D-DIN",
|
||||
"size": 28,
|
||||
"bold": false,
|
||||
"italic": false,
|
||||
"width": 512,
|
||||
"height": 128,
|
||||
"characters": {
|
||||
"0":{"x":338,"y":0,"width":18,"height":30,"originX":0,"originY":28,"advance":18},
|
||||
"1":{"x":30,"y":64,"width":11,"height":29,"originX":0,"originY":28,"advance":12},
|
||||
"2":{"x":490,"y":35,"width":17,"height":29,"originX":0,"originY":28,"advance":17},
|
||||
"3":{"x":356,"y":0,"width":18,"height":30,"originX":0,"originY":28,"advance":18},
|
||||
"4":{"x":398,"y":35,"width":19,"height":29,"originX":0,"originY":28,"advance":19},
|
||||
"5":{"x":374,"y":0,"width":18,"height":30,"originX":0,"originY":28,"advance":18},
|
||||
"6":{"x":392,"y":0,"width":18,"height":30,"originX":0,"originY":28,"advance":18},
|
||||
"7":{"x":436,"y":35,"width":18,"height":29,"originX":1,"originY":28,"advance":16},
|
||||
"8":{"x":319,"y":0,"width":19,"height":30,"originX":0,"originY":28,"advance":19},
|
||||
"9":{"x":454,"y":35,"width":18,"height":29,"originX":0,"originY":28,"advance":18},
|
||||
" ":{"x":147,"y":93,"width":3,"height":3,"originX":1,"originY":1,"advance":8},
|
||||
"!":{"x":62,"y":64,"width":7,"height":29,"originX":-1,"originY":28,"advance":9},
|
||||
"\"":{"x":52,"y":93,"width":12,"height":12,"originX":0,"originY":28,"advance":11},
|
||||
"#":{"x":233,"y":35,"width":21,"height":29,"originX":1,"originY":28,"advance":19},
|
||||
"$":{"x":138,"y":0,"width":18,"height":34,"originX":1,"originY":30,"advance":17},
|
||||
"%":{"x":156,"y":0,"width":31,"height":30,"originX":0,"originY":28,"advance":31},
|
||||
"&":{"x":187,"y":0,"width":22,"height":30,"originX":0,"originY":28,"advance":22},
|
||||
"'":{"x":64,"y":93,"width":6,"height":12,"originX":0,"originY":28,"advance":6},
|
||||
"(":{"x":59,"y":0,"width":10,"height":35,"originX":0,"originY":30,"advance":11},
|
||||
")":{"x":48,"y":0,"width":11,"height":35,"originX":0,"originY":30,"advance":11},
|
||||
"*":{"x":19,"y":93,"width":14,"height":14,"originX":0,"originY":28,"advance":14},
|
||||
"+":{"x":432,"y":64,"width":19,"height":19,"originX":0,"originY":20,"advance":19},
|
||||
",":{"x":70,"y":93,"width":6,"height":11,"originX":0,"originY":5,"advance":6},
|
||||
"-":{"x":105,"y":93,"width":14,"height":6,"originX":0,"originY":16,"advance":15},
|
||||
".":{"x":119,"y":93,"width":7,"height":6,"originX":0,"originY":5,"advance":7},
|
||||
"/":{"x":446,"y":0,"width":14,"height":30,"originX":0,"originY":28,"advance":15},
|
||||
":":{"x":470,"y":64,"width":7,"height":19,"originX":0,"originY":18,"advance":7},
|
||||
";":{"x":171,"y":64,"width":7,"height":24,"originX":0,"originY":18,"advance":7},
|
||||
"<":{"x":477,"y":64,"width":19,"height":17,"originX":0,"originY":19,"advance":19},
|
||||
"=":{"x":33,"y":93,"width":19,"height":13,"originX":0,"originY":17,"advance":19},
|
||||
">":{"x":0,"y":93,"width":19,"height":17,"originX":0,"originY":19,"advance":19},
|
||||
"?":{"x":0,"y":64,"width":17,"height":29,"originX":1,"originY":28,"advance":16},
|
||||
"@":{"x":106,"y":0,"width":32,"height":34,"originX":0,"originY":28,"advance":32},
|
||||
"A":{"x":26,"y":35,"width":25,"height":29,"originX":2,"originY":28,"advance":22},
|
||||
"B":{"x":254,"y":35,"width":21,"height":29,"originX":-1,"originY":28,"advance":23},
|
||||
"C":{"x":209,"y":0,"width":22,"height":30,"originX":0,"originY":28,"advance":23},
|
||||
"D":{"x":145,"y":35,"width":22,"height":29,"originX":-1,"originY":28,"advance":23},
|
||||
"E":{"x":338,"y":35,"width":20,"height":29,"originX":-1,"originY":28,"advance":20},
|
||||
"F":{"x":358,"y":35,"width":20,"height":29,"originX":-1,"originY":28,"advance":20},
|
||||
"G":{"x":231,"y":0,"width":22,"height":30,"originX":0,"originY":28,"advance":23},
|
||||
"H":{"x":275,"y":35,"width":21,"height":29,"originX":-1,"originY":28,"advance":23},
|
||||
"I":{"x":69,"y":64,"width":7,"height":29,"originX":-1,"originY":28,"advance":9},
|
||||
"J":{"x":41,"y":64,"width":11,"height":29,"originX":1,"originY":28,"advance":12},
|
||||
"K":{"x":76,"y":35,"width":23,"height":29,"originX":-1,"originY":28,"advance":22},
|
||||
"L":{"x":378,"y":35,"width":20,"height":29,"originX":-1,"originY":28,"advance":20},
|
||||
"M":{"x":0,"y":35,"width":26,"height":29,"originX":-1,"originY":28,"advance":28},
|
||||
"N":{"x":99,"y":35,"width":23,"height":29,"originX":-1,"originY":28,"advance":25},
|
||||
"O":{"x":253,"y":0,"width":22,"height":30,"originX":0,"originY":28,"advance":23},
|
||||
"P":{"x":296,"y":35,"width":21,"height":29,"originX":-1,"originY":28,"advance":22},
|
||||
"Q":{"x":0,"y":0,"width":22,"height":35,"originX":0,"originY":28,"advance":23},
|
||||
"R":{"x":167,"y":35,"width":22,"height":29,"originX":-1,"originY":28,"advance":22},
|
||||
"S":{"x":275,"y":0,"width":22,"height":30,"originX":1,"originY":28,"advance":20},
|
||||
"T":{"x":189,"y":35,"width":22,"height":29,"originX":2,"originY":28,"advance":18},
|
||||
"U":{"x":297,"y":0,"width":22,"height":30,"originX":-1,"originY":28,"advance":24},
|
||||
"V":{"x":122,"y":35,"width":23,"height":29,"originX":2,"originY":28,"advance":19},
|
||||
"W":{"x":474,"y":0,"width":33,"height":29,"originX":2,"originY":28,"advance":30},
|
||||
"X":{"x":51,"y":35,"width":25,"height":29,"originX":2,"originY":28,"advance":21},
|
||||
"Y":{"x":211,"y":35,"width":22,"height":29,"originX":2,"originY":28,"advance":19},
|
||||
"Z":{"x":317,"y":35,"width":21,"height":29,"originX":1,"originY":28,"advance":19},
|
||||
"[":{"x":69,"y":0,"width":10,"height":35,"originX":-1,"originY":30,"advance":11},
|
||||
"\\":{"x":460,"y":0,"width":14,"height":30,"originX":0,"originY":28,"advance":15},
|
||||
"]":{"x":79,"y":0,"width":10,"height":35,"originX":0,"originY":30,"advance":11},
|
||||
"^":{"x":451,"y":64,"width":19,"height":19,"originX":0,"originY":28,"advance":19},
|
||||
"_":{"x":126,"y":93,"width":21,"height":5,"originX":2,"originY":0,"advance":17},
|
||||
"`":{"x":95,"y":93,"width":10,"height":8,"originX":-4,"originY":28,"advance":19},
|
||||
"a":{"x":235,"y":64,"width":18,"height":23,"originX":0,"originY":21,"advance":19},
|
||||
"b":{"x":410,"y":0,"width":18,"height":30,"originX":-1,"originY":28,"advance":19},
|
||||
"c":{"x":178,"y":64,"width":19,"height":23,"originX":0,"originY":21,"advance":19},
|
||||
"d":{"x":428,"y":0,"width":18,"height":30,"originX":0,"originY":28,"advance":19},
|
||||
"e":{"x":197,"y":64,"width":19,"height":23,"originX":0,"originY":21,"advance":19},
|
||||
"f":{"x":17,"y":64,"width":13,"height":29,"originX":0,"originY":28,"advance":12},
|
||||
"g":{"x":96,"y":64,"width":18,"height":28,"originX":0,"originY":21,"advance":19},
|
||||
"h":{"x":472,"y":35,"width":18,"height":29,"originX":-1,"originY":28,"advance":19},
|
||||
"i":{"x":150,"y":64,"width":7,"height":28,"originX":-1,"originY":27,"advance":9},
|
||||
"j":{"x":89,"y":0,"width":10,"height":35,"originX":3,"originY":28,"advance":8},
|
||||
"k":{"x":417,"y":35,"width":19,"height":29,"originX":-1,"originY":28,"advance":18},
|
||||
"l":{"x":52,"y":64,"width":10,"height":29,"originX":-1,"originY":28,"advance":10},
|
||||
"m":{"x":289,"y":64,"width":29,"height":22,"originX":-1,"originY":21,"advance":31},
|
||||
"n":{"x":366,"y":64,"width":18,"height":22,"originX":-1,"originY":21,"advance":19},
|
||||
"o":{"x":216,"y":64,"width":19,"height":23,"originX":0,"originY":21,"advance":19},
|
||||
"p":{"x":114,"y":64,"width":18,"height":28,"originX":-1,"originY":21,"advance":19},
|
||||
"q":{"x":132,"y":64,"width":18,"height":28,"originX":0,"originY":21,"advance":19},
|
||||
"r":{"x":419,"y":64,"width":13,"height":22,"originX":-1,"originY":21,"advance":13},
|
||||
"s":{"x":253,"y":64,"width":18,"height":23,"originX":0,"originY":21,"advance":17},
|
||||
"t":{"x":157,"y":64,"width":14,"height":27,"originX":1,"originY":26,"advance":13},
|
||||
"u":{"x":271,"y":64,"width":18,"height":23,"originX":-1,"originY":21,"advance":19},
|
||||
"v":{"x":347,"y":64,"width":19,"height":22,"originX":1,"originY":21,"advance":16},
|
||||
"w":{"x":318,"y":64,"width":29,"height":22,"originX":1,"originY":21,"advance":27},
|
||||
"x":{"x":384,"y":64,"width":18,"height":22,"originX":1,"originY":21,"advance":16},
|
||||
"y":{"x":76,"y":64,"width":20,"height":28,"originX":2,"originY":21,"advance":16},
|
||||
"z":{"x":402,"y":64,"width":17,"height":22,"originX":0,"originY":21,"advance":17},
|
||||
"{":{"x":22,"y":0,"width":13,"height":35,"originX":0,"originY":30,"advance":13},
|
||||
"|":{"x":99,"y":0,"width":7,"height":35,"originX":-2,"originY":30,"advance":11},
|
||||
"}":{"x":35,"y":0,"width":13,"height":35,"originX":0,"originY":30,"advance":13},
|
||||
"~":{"x":76,"y":93,"width":19,"height":9,"originX":0,"originY":15,"advance":19}
|
||||
}
|
||||
}
|
|
@ -1,202 +0,0 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -1,49 +0,0 @@
|
|||
Copyright 2011 Red Hat, Inc.,
|
||||
with Reserved Font Name OVERPASS.
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,7 +0,0 @@
|
|||
This directory contains postprocessed versions of the shaders in the top-level
|
||||
`shaders/` directory, for convenience. Don't modify the shaders here; instead
|
||||
modify the corresponding shaders in `shaders/` and rerun `make` in that
|
||||
directory.
|
||||
|
||||
You will need `glslangValidator` and `spirv-cross` installed to execute the
|
||||
Makefile. On macOS, you can get these with `brew install glslang spirv-cross`.
|
|
@ -1,15 +0,0 @@
|
|||
#version {{version}}
|
||||
// Automatically generated from files in pathfinder/shaders/. Do not edit!
|
||||
|
||||
|
||||
uniform sampler2D SPIRV_Cross_CombineduSrcuSampler;
|
||||
|
||||
in vec2 vTexCoord;
|
||||
layout(location = 0) out vec4 oFragColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 color = texture(SPIRV_Cross_CombineduSrcuSampler, vTexCoord);
|
||||
oFragColor = vec4(color.xyz * color.w, color.w);
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
#version {{version}}
|
||||
// Automatically generated from files in pathfinder/shaders/. Do not edit!
|
||||
|
||||
|
||||
layout(location = 0) in ivec2 aPosition;
|
||||
out vec2 vTexCoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 texCoord = vec2(aPosition);
|
||||
vTexCoord = texCoord;
|
||||
gl_Position = vec4(mix(vec2(-1.0), vec2(1.0), vec2(aPosition)), 0.0, 1.0);
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
#version {{version}}
|
||||
// Automatically generated from files in pathfinder/shaders/. Do not edit!
|
||||
|
||||
|
||||
uniform vec4 uColor[1];
|
||||
layout(location = 0) out vec4 oFragColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
oFragColor = vec4(uColor[0].xyz, 1.0) * uColor[0].w;
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
#version {{version}}
|
||||
// Automatically generated from files in pathfinder/shaders/. Do not edit!
|
||||
|
||||
|
||||
uniform vec4 uRect[1];
|
||||
uniform vec4 uFramebufferSize[1];
|
||||
layout(location = 0) in ivec2 aPosition;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 position = ((mix(uRect[0].xy, uRect[0].zw, vec2(aPosition)) / uFramebufferSize[0].xy) * 2.0) - vec2(1.0);
|
||||
gl_Position = vec4(position.x, -position.y, 0.0, 1.0);
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
#version {{version}}
|
||||
// Automatically generated from files in pathfinder/shaders/. Do not edit!
|
||||
|
||||
|
||||
uniform vec4 uColor[1];
|
||||
layout(location = 0) out vec4 oFragColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
oFragColor = vec4(uColor[0].xyz, 1.0) * uColor[0].w;
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
#version {{version}}
|
||||
// Automatically generated from files in pathfinder/shaders/. Do not edit!
|
||||
|
||||
|
||||
uniform vec4 uFramebufferSize[1];
|
||||
layout(location = 0) in ivec2 aPosition;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 position = ((vec2(aPosition) / uFramebufferSize[0].xy) * 2.0) - vec2(1.0);
|
||||
gl_Position = vec4(position.x, -position.y, 0.0, 1.0);
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
#version {{version}}
|
||||
// Automatically generated from files in pathfinder/shaders/. Do not edit!
|
||||
|
||||
|
||||
uniform vec4 uColor[1];
|
||||
uniform sampler2D SPIRV_Cross_CombineduTextureuSampler;
|
||||
|
||||
in vec2 vTexCoord;
|
||||
layout(location = 0) out vec4 oFragColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
float alpha = texture(SPIRV_Cross_CombineduTextureuSampler, vTexCoord).x * uColor[0].w;
|
||||
oFragColor = vec4(uColor[0].xyz, 1.0) * alpha;
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
#version {{version}}
|
||||
// Automatically generated from files in pathfinder/shaders/. Do not edit!
|
||||
|
||||
|
||||
uniform vec4 uTextureSize[1];
|
||||
uniform vec4 uFramebufferSize[1];
|
||||
out vec2 vTexCoord;
|
||||
layout(location = 1) in ivec2 aTexCoord;
|
||||
layout(location = 0) in ivec2 aPosition;
|
||||
|
||||
void main()
|
||||
{
|
||||
vTexCoord = vec2(aTexCoord) / uTextureSize[0].xy;
|
||||
vec2 position = ((vec2(aPosition) / uFramebufferSize[0].xy) * 2.0) - vec2(1.0);
|
||||
gl_Position = vec4(position.x, -position.y, 0.0, 1.0);
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
#version {{version}}
|
||||
// Automatically generated from files in pathfinder/shaders/. Do not edit!
|
||||
|
||||
|
||||
uniform vec4 uGridlineColor[1];
|
||||
uniform vec4 uGroundColor[1];
|
||||
in vec2 vTexCoord;
|
||||
layout(location = 0) out vec4 oFragColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 texCoordPx = fract(vTexCoord) / fwidth(vTexCoord);
|
||||
vec4 _28;
|
||||
if (any(lessThanEqual(texCoordPx, vec2(1.0))))
|
||||
{
|
||||
_28 = uGridlineColor[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
_28 = uGroundColor[0];
|
||||
}
|
||||
oFragColor = _28;
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
#version {{version}}
|
||||
// Automatically generated from files in pathfinder/shaders/. Do not edit!
|
||||
|
||||
|
||||
uniform ivec4 uGridlineCount[1];
|
||||
uniform vec4 uTransform[4];
|
||||
out vec2 vTexCoord;
|
||||
layout(location = 0) in ivec2 aPosition;
|
||||
|
||||
void main()
|
||||
{
|
||||
vTexCoord = vec2(aPosition * ivec2(uGridlineCount[0].x));
|
||||
gl_Position = mat4(uTransform[0], uTransform[1], uTransform[2], uTransform[3]) * vec4(ivec4(aPosition.x, 0, aPosition.y, 1));
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
#version {{version}}
|
||||
// Automatically generated from files in pathfinder/shaders/. Do not edit!
|
||||
|
||||
|
||||
uniform sampler2D SPIRV_Cross_CombineduAreaLUTuSampler;
|
||||
|
||||
layout(location = 0) out vec4 oFragColor;
|
||||
in vec2 vFrom;
|
||||
in vec2 vTo;
|
||||
|
||||
vec4 computeCoverage(vec2 from, vec2 to, sampler2D SPIRV_Cross_CombinedareaLUTareaLUTSampler)
|
||||
{
|
||||
bvec2 _34 = bvec2(from.x < to.x);
|
||||
vec2 left = vec2(_34.x ? from.x : to.x, _34.y ? from.y : to.y);
|
||||
bvec2 _44 = bvec2(from.x < to.x);
|
||||
vec2 right = vec2(_44.x ? to.x : from.x, _44.y ? to.y : from.y);
|
||||
vec2 window = clamp(vec2(from.x, to.x), vec2(-0.5), vec2(0.5));
|
||||
float offset = mix(window.x, window.y, 0.5) - left.x;
|
||||
float t = offset / (right.x - left.x);
|
||||
float y = mix(left.y, right.y, t);
|
||||
float d = (right.y - left.y) / (right.x - left.x);
|
||||
float dX = window.x - window.y;
|
||||
return texture(SPIRV_Cross_CombinedareaLUTareaLUTSampler, vec2(y + 8.0, abs(d * dX)) / vec2(16.0)) * dX;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 param = vFrom;
|
||||
vec2 param_1 = vTo;
|
||||
oFragColor = computeCoverage(param, param_1, SPIRV_Cross_CombineduAreaLUTuSampler);
|
||||
}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
#version {{version}}
|
||||
// Automatically generated from files in pathfinder/shaders/. Do not edit!
|
||||
|
||||
|
||||
uniform vec4 uTileSize[1];
|
||||
uniform vec4 uFramebufferSize[1];
|
||||
layout(location = 5) in uint aTileIndex;
|
||||
layout(location = 3) in uint aFromPx;
|
||||
layout(location = 1) in vec2 aFromSubpx;
|
||||
layout(location = 4) in uint aToPx;
|
||||
layout(location = 2) in vec2 aToSubpx;
|
||||
layout(location = 0) in uvec2 aTessCoord;
|
||||
out vec2 vFrom;
|
||||
out vec2 vTo;
|
||||
|
||||
vec2 computeTileOffset(uint tileIndex, float stencilTextureWidth)
|
||||
{
|
||||
uint tilesPerRow = uint(stencilTextureWidth / uTileSize[0].x);
|
||||
uvec2 tileOffset = uvec2(tileIndex % tilesPerRow, tileIndex / tilesPerRow);
|
||||
return (vec2(tileOffset) * uTileSize[0].xy) * vec2(1.0, 0.25);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
uint param = aTileIndex;
|
||||
float param_1 = uFramebufferSize[0].x;
|
||||
vec2 tileOrigin = computeTileOffset(param, param_1);
|
||||
vec2 from = vec2(float(aFromPx & 15u), float(aFromPx >> 4u)) + aFromSubpx;
|
||||
vec2 to = vec2(float(aToPx & 15u), float(aToPx >> 4u)) + aToSubpx;
|
||||
vec2 position;
|
||||
if (aTessCoord.x == 0u)
|
||||
{
|
||||
position.x = floor(min(from.x, to.x));
|
||||
}
|
||||
else
|
||||
{
|
||||
position.x = ceil(max(from.x, to.x));
|
||||
}
|
||||
if (aTessCoord.y == 0u)
|
||||
{
|
||||
position.y = floor(min(from.y, to.y));
|
||||
}
|
||||
else
|
||||
{
|
||||
position.y = uTileSize[0].y;
|
||||
}
|
||||
position.y = floor(position.y * 0.25);
|
||||
vec2 offset = vec2(0.0, 1.5) - (position * vec2(1.0, 4.0));
|
||||
vFrom = from + offset;
|
||||
vTo = to + offset;
|
||||
vec2 globalPosition = (((tileOrigin + position) / uFramebufferSize[0].xy) * 2.0) - vec2(1.0);
|
||||
gl_Position = vec4(globalPosition, 0.0, 1.0);
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue