diff --git a/c/src/lib.rs b/c/src/lib.rs index cf5ff937..fdf730d8 100644 --- a/c/src/lib.rs +++ b/c/src/lib.rs @@ -15,6 +15,7 @@ use gl; use pathfinder_canvas::{CanvasFontContext, CanvasRenderingContext2D, FillStyle, LineJoin, Path2D}; use pathfinder_canvas::{TextAlign, TextMetrics}; use pathfinder_color::{ColorF, ColorU}; +use pathfinder_content::fill::FillRule; use pathfinder_content::outline::ArcDirection; use pathfinder_content::stroke::LineCap; use pathfinder_geometry::rect::{RectF, RectI}; @@ -372,7 +373,8 @@ pub unsafe extern "C" fn PFCanvasSetStrokeStyle(canvas: PFCanvasRef, /// first. #[no_mangle] pub unsafe extern "C" fn PFCanvasFillPath(canvas: PFCanvasRef, path: PFPathRef) { - (*canvas).fill_path(*Box::from_raw(path)) + // TODO(pcwalton): Expose fill rules to the C API. + (*canvas).fill_path(*Box::from_raw(path), FillRule::Winding) } /// This function automatically destroys the path. If you wish to use the path again, clone it diff --git a/canvas/src/lib.rs b/canvas/src/lib.rs index 6d63fea8..d11fb5da 100644 --- a/canvas/src/lib.rs +++ b/canvas/src/lib.rs @@ -12,6 +12,7 @@ use pathfinder_color::ColorU; use pathfinder_content::dash::OutlineDash; +use pathfinder_content::fill::FillRule; use pathfinder_content::gradient::Gradient; use pathfinder_content::outline::{ArcDirection, Contour, Outline}; use pathfinder_content::pattern::Pattern; @@ -80,7 +81,7 @@ impl CanvasRenderingContext2D { pub fn fill_rect(&mut self, rect: RectF) { let mut path = Path2D::new(); path.rect(rect); - self.fill_path(path); + self.fill_path(path, FillRule::Winding); } #[inline] @@ -156,14 +157,14 @@ impl CanvasRenderingContext2D { // Drawing paths #[inline] - pub fn fill_path(&mut self, path: Path2D) { + pub fn fill_path(&mut self, path: Path2D, fill_rule: FillRule) { let mut outline = path.into_outline(); outline.transform(&self.current_state.transform); let paint = self.current_state.resolve_paint(&self.current_state.fill_paint); let paint_id = self.scene.push_paint(&paint); - self.push_path(outline, paint_id); + self.push_path(outline, paint_id, fill_rule); } #[inline] @@ -196,18 +197,20 @@ impl CanvasRenderingContext2D { outline = stroke_to_fill.into_outline(); outline.transform(&self.current_state.transform); - self.push_path(outline, paint_id); + self.push_path(outline, paint_id, FillRule::Winding); } - pub fn clip_path(&mut self, path: Path2D) { + pub fn clip_path(&mut self, path: Path2D, fill_rule: FillRule) { let mut outline = path.into_outline(); outline.transform(&self.current_state.transform); - let clip_path_id = self.scene.push_clip_path(ClipPath::new(outline, String::new())); + let clip_path_id = self.scene + .push_clip_path(ClipPath::new(outline, fill_rule, String::new())); + self.current_state.clip_path = Some(clip_path_id); } - fn push_path(&mut self, outline: Outline, paint_id: PaintId) { + fn push_path(&mut self, outline: Outline, paint_id: PaintId, fill_rule: FillRule) { let clip_path = self.current_state.clip_path; if !self.current_state.shadow_paint.is_fully_transparent() { @@ -216,10 +219,14 @@ impl CanvasRenderingContext2D { let mut outline = outline.clone(); outline.transform(&Transform2F::from_translation(self.current_state.shadow_offset)); - self.scene.push_path(DrawPath::new(outline, paint_id, clip_path, String::new())) + self.scene.push_path(DrawPath::new(outline, + paint_id, + clip_path, + fill_rule, + String::new())) } - self.scene.push_path(DrawPath::new(outline, paint_id, clip_path, String::new())) + self.scene.push_path(DrawPath::new(outline, paint_id, clip_path, fill_rule, String::new())) } // Transformations diff --git a/content/src/fill.rs b/content/src/fill.rs new file mode 100644 index 00000000..e2941f17 --- /dev/null +++ b/content/src/fill.rs @@ -0,0 +1,17 @@ +// pathfinder/content/src/fill.rs +// +// Copyright © 2019 The Pathfinder Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Fill rules. + +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum FillRule { + Winding, + EvenOdd, +} diff --git a/content/src/lib.rs b/content/src/lib.rs index 25f0b4fa..2a7d68f4 100644 --- a/content/src/lib.rs +++ b/content/src/lib.rs @@ -19,6 +19,7 @@ extern crate log; pub mod clip; pub mod dash; +pub mod fill; pub mod gradient; pub mod orientation; pub mod outline; diff --git a/metal/src/lib.rs b/metal/src/lib.rs index 9c3c08fb..f44411d5 100644 --- a/metal/src/lib.rs +++ b/metal/src/lib.rs @@ -701,7 +701,8 @@ impl MetalDevice { if blit_command_encoder.is_none() { blit_command_encoder = Some(command_buffer.new_blit_command_encoder()); } - let blit_command_encoder = blit_command_encoder.as_ref().unwrap(); + let blit_command_encoder = + blit_command_encoder.as_ref().expect("Where's the blit command encoder?"); blit_command_encoder.synchronize_resource(&texture.texture); texture.dirty.set(false); } @@ -725,9 +726,10 @@ impl MetalDevice { .descriptor)); // Create render pipeline state. - let pipeline_color_attachment = render_pipeline_descriptor.color_attachments() - .object_at(0) - .unwrap(); + let pipeline_color_attachment = + render_pipeline_descriptor.color_attachments() + .object_at(0) + .expect("Where's the color attachment?"); self.prepare_pipeline_color_attachment_for_render(pipeline_color_attachment, render_state); @@ -753,7 +755,9 @@ impl MetalDevice { .enumerate() { let real_index = vertex_buffer_index as u64 + FIRST_VERTEX_BUFFER_INDEX; let buffer = vertex_buffer.buffer.borrow(); - let buffer = buffer.as_ref().map(|buffer| buffer.as_ref()).unwrap(); + let buffer = buffer.as_ref() + .map(|buffer| buffer.as_ref()) + .expect("Where's the vertex buffer?"); encoder.set_vertex_buffer(real_index, Some(buffer), 0); encoder.use_resource(buffer, MTLResourceUsage::Read); } diff --git a/renderer/src/builder.rs b/renderer/src/builder.rs index 83128c70..d22d163c 100644 --- a/renderer/src/builder.rs +++ b/renderer/src/builder.rs @@ -20,6 +20,7 @@ use crate::scene::Scene; use crate::tile_map::DenseTileMap; use crate::tiles::{self, TILE_HEIGHT, TILE_WIDTH, Tiler, TilingPathInfo}; use crate::z_buffer::ZBuffer; +use pathfinder_content::fill::FillRule; use pathfinder_geometry::line_segment::{LineSegment2F, LineSegmentU4, LineSegmentU8}; use pathfinder_geometry::vector::{Vector2F, Vector2I}; use pathfinder_geometry::rect::{RectF, RectI}; @@ -52,6 +53,7 @@ pub(crate) struct BuiltPath { pub mask_tiles: Vec, pub alpha_tiles: Vec, pub tiles: DenseTileMap, + pub fill_rule: FillRule, } impl<'a> SceneBuilder<'a> { @@ -122,6 +124,7 @@ impl<'a> SceneBuilder<'a> { let mut tiler = Tiler::new(self, &outline, + path_object.fill_rule(), view_box, path_index as u16, TilingPathInfo::Clip); @@ -150,6 +153,7 @@ impl<'a> SceneBuilder<'a> { let mut tiler = Tiler::new(self, &outline, + path_object.fill_rule(), view_box, path_index as u16, TilingPathInfo::Draw { @@ -165,14 +169,18 @@ impl<'a> SceneBuilder<'a> { fn cull_tiles(&self, built_clip_paths: Vec, built_draw_paths: Vec) -> CulledTiles { - let mut culled_tiles = CulledTiles { mask_tiles: vec![], alpha_tiles: vec![] }; + let mut culled_tiles = CulledTiles { + mask_winding_tiles: vec![], + mask_evenodd_tiles: vec![], + alpha_tiles: vec![], + }; for built_clip_path in built_clip_paths { - culled_tiles.mask_tiles.extend_from_slice(&built_clip_path.mask_tiles); + culled_tiles.push_mask_tiles(&built_clip_path); } for built_draw_path in built_draw_paths { - culled_tiles.mask_tiles.extend_from_slice(&built_draw_path.mask_tiles); + culled_tiles.push_mask_tiles(&built_draw_path); for alpha_tile in built_draw_path.alpha_tiles { let alpha_tile_coords = alpha_tile.upper_left.tile_position(); @@ -191,9 +199,20 @@ impl<'a> SceneBuilder<'a> { let solid_tiles = self.z_buffer.build_solid_tiles(&self.scene.paths, paint_metadata, 0..path_count); - if !culled_tiles.mask_tiles.is_empty() { - self.listener.send(RenderCommand::RenderMaskTiles(culled_tiles.mask_tiles)); + + if !culled_tiles.mask_winding_tiles.is_empty() { + self.listener.send(RenderCommand::RenderMaskTiles { + tiles: culled_tiles.mask_winding_tiles, + fill_rule: FillRule::Winding, + }); } + if !culled_tiles.mask_evenodd_tiles.is_empty() { + self.listener.send(RenderCommand::RenderMaskTiles { + tiles: culled_tiles.mask_evenodd_tiles, + fill_rule: FillRule::EvenOdd, + }); + } + if !solid_tiles.is_empty() { self.listener.send(RenderCommand::DrawSolidTiles(solid_tiles)); } @@ -218,7 +237,8 @@ impl<'a> SceneBuilder<'a> { } struct CulledTiles { - mask_tiles: Vec, + mask_winding_tiles: Vec, + mask_evenodd_tiles: Vec, alpha_tiles: Vec, } @@ -231,11 +251,11 @@ pub struct TileStats { // Utilities for built objects impl ObjectBuilder { - pub(crate) fn new(bounds: RectF) -> ObjectBuilder { + pub(crate) fn new(bounds: RectF, fill_rule: FillRule) -> ObjectBuilder { let tile_rect = tiles::round_rect_out_to_tile_bounds(bounds); let tiles = DenseTileMap::new(tile_rect); ObjectBuilder { - built_path: BuiltPath { mask_tiles: vec![], alpha_tiles: vec![], tiles }, + built_path: BuiltPath { mask_tiles: vec![], alpha_tiles: vec![], tiles, fill_rule }, bounds, fills: vec![], } @@ -533,3 +553,12 @@ fn calculate_mask_uv(tile_index: u16, tile_offset: Vector2I) -> Vector2I { let mask_uv = Vector2I::new(mask_u, mask_v) + tile_offset; mask_uv.to_f32().scale(mask_scale).to_i32() } + +impl CulledTiles { + fn push_mask_tiles(&mut self, built_path: &BuiltPath) { + match built_path.fill_rule { + FillRule::Winding => self.mask_winding_tiles.extend_from_slice(&built_path.mask_tiles), + FillRule::EvenOdd => self.mask_evenodd_tiles.extend_from_slice(&built_path.mask_tiles), + } + } +} diff --git a/renderer/src/gpu/renderer.rs b/renderer/src/gpu/renderer.rs index 26185924..e8cdfb9f 100644 --- a/renderer/src/gpu/renderer.rs +++ b/renderer/src/gpu/renderer.rs @@ -11,7 +11,7 @@ use crate::gpu::debug::DebugUIPresenter; use crate::gpu::options::{DestFramebuffer, RendererOptions}; use crate::gpu::shaders::{FillProgram, AlphaTileProgram, AlphaTileVertexArray, FillVertexArray}; -use crate::gpu::shaders::{MAX_FILLS_PER_BATCH, MaskWindingTileProgram, MaskWindingTileVertexArray}; +use crate::gpu::shaders::{MAX_FILLS_PER_BATCH, MaskTileProgram, MaskTileVertexArray}; use crate::gpu::shaders::{PostprocessProgram, PostprocessVertexArray, ReprojectionProgram}; use crate::gpu::shaders::{ReprojectionVertexArray, SolidTileProgram, SolidTileVertexArray}; use crate::gpu::shaders::{StencilProgram, StencilVertexArray}; @@ -20,6 +20,7 @@ use crate::gpu_data::{RenderCommand, SolidTileVertex}; use crate::post::DefringingKernel; use crate::tiles::{TILE_HEIGHT, TILE_WIDTH}; use pathfinder_color::{self as color, ColorF, ColorU}; +use pathfinder_content::fill::FillRule; use pathfinder_geometry::vector::{Vector2I, Vector4F}; use pathfinder_geometry::rect::RectI; use pathfinder_geometry::transform3d::Transform4F; @@ -57,10 +58,12 @@ where dest_framebuffer: DestFramebuffer, options: RendererOptions, fill_program: FillProgram, - mask_winding_tile_program: MaskWindingTileProgram, + mask_winding_tile_program: MaskTileProgram, + mask_evenodd_tile_program: MaskTileProgram, solid_tile_program: SolidTileProgram, alpha_tile_program: AlphaTileProgram, - mask_winding_tile_vertex_array: MaskWindingTileVertexArray, + mask_winding_tile_vertex_array: MaskTileVertexArray, + mask_evenodd_tile_vertex_array: MaskTileVertexArray, solid_tile_vertex_array: SolidTileVertexArray, alpha_tile_vertex_array: AlphaTileVertexArray, area_lut_texture: D::Texture, @@ -113,7 +116,12 @@ where options: RendererOptions) -> Renderer { let fill_program = FillProgram::new(&device, resources); - let mask_winding_tile_program = MaskWindingTileProgram::new(&device, resources); + let mask_winding_tile_program = MaskTileProgram::new(FillRule::Winding, + &device, + resources); + let mask_evenodd_tile_program = MaskTileProgram::new(FillRule::EvenOdd, + &device, + resources); let solid_tile_program = SolidTileProgram::new(&device, resources); let alpha_tile_program = AlphaTileProgram::new(&device, resources); let postprocess_program = PostprocessProgram::new(&device, resources); @@ -145,11 +153,16 @@ where &quad_vertex_positions_buffer, &quad_vertex_indices_buffer, ); - let mask_winding_tile_vertex_array = MaskWindingTileVertexArray::new( + let mask_winding_tile_vertex_array = MaskTileVertexArray::new( &device, &mask_winding_tile_program, &quads_vertex_indices_buffer, ); + let mask_evenodd_tile_vertex_array = MaskTileVertexArray::new( + &device, + &mask_evenodd_tile_program, + &quads_vertex_indices_buffer, + ); let alpha_tile_vertex_array = AlphaTileVertexArray::new( &device, &alpha_tile_program, @@ -196,9 +209,11 @@ where options, fill_program, mask_winding_tile_program, + mask_evenodd_tile_program, solid_tile_program, alpha_tile_program, mask_winding_tile_vertex_array, + mask_evenodd_tile_vertex_array, solid_tile_vertex_array, alpha_tile_vertex_array, area_lut_texture, @@ -257,10 +272,10 @@ where self.draw_buffered_fills(); self.begin_composite_timer_query(); } - RenderCommand::RenderMaskTiles(ref mask_tiles) => { + RenderCommand::RenderMaskTiles { tiles: ref mask_tiles, fill_rule } => { let count = mask_tiles.len(); - self.upload_mask_tiles(mask_tiles); - self.draw_mask_tiles(count as u32); + self.upload_mask_tiles(mask_tiles, fill_rule); + self.draw_mask_tiles(count as u32, fill_rule); } RenderCommand::DrawSolidTiles(ref solid_tile_vertices) => { let count = solid_tile_vertices.len() / 4; @@ -397,9 +412,14 @@ where TextureDataRef::U8(texels)); } - fn upload_mask_tiles(&mut self, mask_tiles: &[MaskTile]) { + fn upload_mask_tiles(&mut self, mask_tiles: &[MaskTile], fill_rule: FillRule) { + let vertex_array = match fill_rule { + FillRule::Winding => &self.mask_winding_tile_vertex_array, + FillRule::EvenOdd => &self.mask_evenodd_tile_vertex_array, + }; + self.device.allocate_buffer( - &self.mask_winding_tile_vertex_array.vertex_buffer, + &vertex_array.vertex_buffer, BufferData::Memory(&mask_tiles), BufferTarget::Vertex, BufferUploadMode::Dynamic, @@ -529,7 +549,7 @@ where Transform4F::from_scale(scale).translate(Vector4F::new(-1.0, 1.0, 0.0, 1.0)) } - fn draw_mask_tiles(&mut self, tile_count: u32) { + fn draw_mask_tiles(&mut self, tile_count: u32, fill_rule: FillRule) { let clear_color = if self.framebuffer_flags .contains(FramebufferFlags::MUST_PRESERVE_MASK_FRAMEBUFFER_CONTENTS) { @@ -538,10 +558,19 @@ where Some(ColorF::new(1.0, 1.0, 1.0, 1.0)) }; + let (mask_tile_program, mask_tile_vertex_array) = match fill_rule { + FillRule::Winding => { + (&self.mask_winding_tile_program, &self.mask_winding_tile_vertex_array) + } + FillRule::EvenOdd => { + (&self.mask_evenodd_tile_program, &self.mask_evenodd_tile_vertex_array) + } + }; + self.device.draw_elements(tile_count * 6, &RenderState { target: &RenderTarget::Framebuffer(&self.mask_framebuffer), - program: &self.mask_winding_tile_program.program, - vertex_array: &self.mask_winding_tile_vertex_array.vertex_array, + program: &mask_tile_program.program, + vertex_array: &mask_tile_vertex_array.vertex_array, primitive: Primitive::Triangles, textures: &[self.device.framebuffer_texture(&self.fill_framebuffer)], uniforms: &[ @@ -550,7 +579,6 @@ where ], viewport: self.mask_viewport(), options: RenderOptions { - // TODO(pcwalton): MIN blending for masks. blend: Some(BlendState { func: BlendFunc::RGBOneAlphaOne, op: BlendOp::Min, diff --git a/renderer/src/gpu/shaders.rs b/renderer/src/gpu/shaders.rs index 884e8437..1aa9745d 100644 --- a/renderer/src/gpu/shaders.rs +++ b/renderer/src/gpu/shaders.rs @@ -9,6 +9,7 @@ // except according to those terms. use crate::gpu_data::FillBatchPrimitive; +use pathfinder_content::fill::FillRule; use pathfinder_gpu::{BufferData, BufferTarget, BufferUploadMode, Device, VertexAttrClass}; use pathfinder_gpu::{VertexAttrDescriptor, VertexAttrType}; use pathfinder_gpu::resources::ResourceLoader; @@ -120,23 +121,23 @@ where } } -pub struct MaskWindingTileVertexArray where D: Device { +pub struct MaskTileVertexArray where D: Device { pub vertex_array: D::VertexArray, pub vertex_buffer: D::Buffer, } -impl MaskWindingTileVertexArray where D: Device { +impl MaskTileVertexArray where D: Device { pub fn new(device: &D, - mask_winding_tile_program: &MaskWindingTileProgram, + mask_tile_program: &MaskTileProgram, quads_vertex_indices_buffer: &D::Buffer) - -> MaskWindingTileVertexArray { + -> MaskTileVertexArray { let (vertex_array, vertex_buffer) = (device.create_vertex_array(), device.create_buffer()); - let position_attr = device.get_vertex_attr(&mask_winding_tile_program.program, "Position") + let position_attr = device.get_vertex_attr(&mask_tile_program.program, "Position") .unwrap(); - let fill_tex_coord_attr = device.get_vertex_attr(&mask_winding_tile_program.program, + let fill_tex_coord_attr = device.get_vertex_attr(&mask_tile_program.program, "FillTexCoord").unwrap(); - let backdrop_attr = device.get_vertex_attr(&mask_winding_tile_program.program, "Backdrop") + let backdrop_attr = device.get_vertex_attr(&mask_tile_program.program, "Backdrop") .unwrap(); device.bind_buffer(&vertex_array, &vertex_buffer, BufferTarget::Vertex); @@ -169,7 +170,7 @@ impl MaskWindingTileVertexArray where D: Device { }); device.bind_buffer(&vertex_array, quads_vertex_indices_buffer, BufferTarget::Index); - MaskWindingTileVertexArray { vertex_array, vertex_buffer } + MaskTileVertexArray { vertex_array, vertex_buffer } } } @@ -308,16 +309,26 @@ where } } -pub struct MaskWindingTileProgram where D: Device { +pub struct MaskTileProgram where D: Device { pub program: D::Program, pub fill_texture_uniform: D::Uniform, } -impl MaskWindingTileProgram where D: Device { - pub fn new(device: &D, resources: &dyn ResourceLoader) -> MaskWindingTileProgram { - let program = device.create_program(resources, "mask_winding"); +impl MaskTileProgram where D: Device { + pub fn new(fill_rule: FillRule, device: &D, resources: &dyn ResourceLoader) + -> MaskTileProgram { + let program_name = match fill_rule { + FillRule::Winding => "mask_winding", + FillRule::EvenOdd => "mask_evenodd", + }; + + let program = device.create_program_from_shader_names(resources, + program_name, + "mask", + program_name); + let fill_texture_uniform = device.get_uniform(&program, "FillTexture"); - MaskWindingTileProgram { program, fill_texture_uniform } + MaskTileProgram { program, fill_texture_uniform } } } diff --git a/renderer/src/gpu_data.rs b/renderer/src/gpu_data.rs index 970e9343..01e81a56 100644 --- a/renderer/src/gpu_data.rs +++ b/renderer/src/gpu_data.rs @@ -12,6 +12,7 @@ use crate::options::BoundingQuad; use pathfinder_color::ColorU; +use pathfinder_content::fill::FillRule; use pathfinder_geometry::line_segment::{LineSegmentU4, LineSegmentU8}; use pathfinder_geometry::vector::Vector2I; use std::fmt::{Debug, Formatter, Result as DebugResult}; @@ -22,7 +23,7 @@ pub enum RenderCommand { AddPaintData(PaintData), AddFills(Vec), FlushFills, - RenderMaskTiles(Vec), + RenderMaskTiles { tiles: Vec, fill_rule: FillRule }, DrawAlphaTiles(Vec), DrawSolidTiles(Vec), Finish { build_time: Duration }, @@ -121,8 +122,8 @@ impl Debug for RenderCommand { } RenderCommand::AddFills(ref fills) => write!(formatter, "AddFills(x{})", fills.len()), RenderCommand::FlushFills => write!(formatter, "FlushFills"), - RenderCommand::RenderMaskTiles(ref tiles) => { - write!(formatter, "RenderMaskTiles(x{})", tiles.len()) + RenderCommand::RenderMaskTiles { ref tiles, fill_rule } => { + write!(formatter, "RenderMaskTiles(x{}, {:?})", tiles.len(), fill_rule) } RenderCommand::DrawAlphaTiles(ref tiles) => { write!(formatter, "DrawAlphaTiles(x{})", tiles.len()) diff --git a/renderer/src/scene.rs b/renderer/src/scene.rs index 5fe239d6..ea01dea9 100644 --- a/renderer/src/scene.rs +++ b/renderer/src/scene.rs @@ -16,6 +16,7 @@ use crate::options::{BuildOptions, PreparedBuildOptions}; use crate::options::{PreparedRenderTransform, RenderCommandListener}; use crate::paint::{Paint, PaintId, PaintInfo, Palette}; use pathfinder_color::ColorU; +use pathfinder_content::fill::FillRule; use pathfinder_geometry::vector::Vector2F; use pathfinder_geometry::rect::RectF; use pathfinder_geometry::transform2d::Transform2F; @@ -214,12 +215,14 @@ pub struct DrawPath { outline: Outline, paint: PaintId, clip_path: Option, + fill_rule: FillRule, name: String, } #[derive(Clone, Debug)] pub struct ClipPath { outline: Outline, + fill_rule: FillRule, name: String, } @@ -228,9 +231,13 @@ pub struct ClipPathId(pub u32); impl DrawPath { #[inline] - pub fn new(outline: Outline, paint: PaintId, clip_path: Option, name: String) + pub fn new(outline: Outline, + paint: PaintId, + clip_path: Option, + fill_rule: FillRule, + name: String) -> DrawPath { - DrawPath { outline, paint, clip_path, name } + DrawPath { outline, paint, clip_path, fill_rule, name } } #[inline] @@ -247,16 +254,26 @@ impl DrawPath { pub(crate) fn paint(&self) -> PaintId { self.paint } + + #[inline] + pub(crate) fn fill_rule(&self) -> FillRule { + self.fill_rule + } } impl ClipPath { #[inline] - pub fn new(outline: Outline, name: String) -> ClipPath { - ClipPath { outline, name } + pub fn new(outline: Outline, fill_rule: FillRule, name: String) -> ClipPath { + ClipPath { outline, fill_rule, name } } #[inline] pub fn outline(&self) -> &Outline { &self.outline } + + #[inline] + pub(crate) fn fill_rule(&self) -> FillRule { + self.fill_rule + } } diff --git a/renderer/src/tiles.rs b/renderer/src/tiles.rs index 54e6bfa4..6ef7ca7c 100644 --- a/renderer/src/tiles.rs +++ b/renderer/src/tiles.rs @@ -11,6 +11,7 @@ use crate::builder::{BuiltPath, ObjectBuilder, SceneBuilder}; use crate::gpu_data::TileObjectPrimitive; use crate::paint::PaintMetadata; +use pathfinder_content::fill::FillRule; use pathfinder_content::outline::{Contour, Outline, PointIndex}; use pathfinder_content::segment::Segment; use pathfinder_content::sorted_vector::SortedVector; @@ -49,6 +50,7 @@ impl<'a> Tiler<'a> { pub(crate) fn new( scene_builder: &'a SceneBuilder<'a>, outline: &'a Outline, + fill_rule: FillRule, view_box: RectF, object_index: u16, path_info: TilingPathInfo<'a>, @@ -57,7 +59,7 @@ impl<'a> Tiler<'a> { .bounds() .intersection(view_box) .unwrap_or(RectF::default()); - let object_builder = ObjectBuilder::new(bounds); + let object_builder = ObjectBuilder::new(bounds, fill_rule); Tiler { scene_builder, @@ -162,10 +164,14 @@ impl<'a> Tiler<'a> { }; if clip_tile.is_none() && draw_tile.is_solid() { - // This is a simple case, so we can try some optimizations. First, blank tiles - // are always skipped. - if draw_tile.backdrop == 0 { - continue; + // This is the simple case of a solid tile with no clip, so there are optimization + // opportunities. First, tiles that must be blank per the fill rule are always + // skipped. + match (self.object_builder.built_path.fill_rule, draw_tile.backdrop) { + (FillRule::Winding, 0) => continue, + (FillRule::Winding, _) => {} + (FillRule::EvenOdd, backdrop) if backdrop % 2 == 0 => continue, + (FillRule::EvenOdd, _) => {} } // Next, if this is a solid tile, just poke it into the Z-buffer. We don't need diff --git a/resources/shaders/gl3/mask.vs.glsl b/resources/shaders/gl3/mask.vs.glsl new file mode 100644 index 00000000..b5a7a1e1 --- /dev/null +++ b/resources/shaders/gl3/mask.vs.glsl @@ -0,0 +1,34 @@ +#version {{version}} +// Automatically generated from files in pathfinder/shaders/. Do not edit! + + + + + + + + + + + + +precision highp float; + +in vec2 aPosition; +in vec2 aFillTexCoord; +in int aBackdrop; + +out vec2 vFillTexCoord; +out float vBackdrop; + +void main(){ + vec2 position = mix(vec2(- 1.0), vec2(1.0), aPosition); + + + + + vFillTexCoord = aFillTexCoord; + vBackdrop = float(aBackdrop); + gl_Position = vec4(position, 0.0, 1.0); +} + diff --git a/resources/shaders/gl3/mask_evenodd.fs.glsl b/resources/shaders/gl3/mask_evenodd.fs.glsl new file mode 100644 index 00000000..30f58910 --- /dev/null +++ b/resources/shaders/gl3/mask_evenodd.fs.glsl @@ -0,0 +1,28 @@ +#version {{version}} +// Automatically generated from files in pathfinder/shaders/. Do not edit! + + + + + + + + + + + + +precision highp float; + +uniform sampler2D uFillTexture; + +in vec2 vFillTexCoord; +in float vBackdrop; + +out vec4 oFragColor; + +void main(){ + float alpha = texture(uFillTexture, vFillTexCoord). r + vBackdrop; + oFragColor = vec4(1.0 - abs(1.0 - mod(alpha, 2.0))); +} + diff --git a/resources/shaders/metal/mask.vs.metal b/resources/shaders/metal/mask.vs.metal new file mode 100644 index 00000000..b5698991 --- /dev/null +++ b/resources/shaders/metal/mask.vs.metal @@ -0,0 +1,31 @@ +// Automatically generated from files in pathfinder/shaders/. Do not edit! +#include +#include + +using namespace metal; + +struct main0_out +{ + float2 vFillTexCoord [[user(locn0)]]; + float vBackdrop [[user(locn1)]]; + float4 gl_Position [[position]]; +}; + +struct main0_in +{ + float2 aPosition [[attribute(0)]]; + float2 aFillTexCoord [[attribute(1)]]; + int aBackdrop [[attribute(2)]]; +}; + +vertex main0_out main0(main0_in in [[stage_in]]) +{ + main0_out out = {}; + float2 position = mix(float2(-1.0), float2(1.0), in.aPosition); + position.y = -position.y; + out.vFillTexCoord = in.aFillTexCoord; + out.vBackdrop = float(in.aBackdrop); + out.gl_Position = float4(position, 0.0, 1.0); + return out; +} + diff --git a/resources/shaders/metal/mask_evenodd.fs.metal b/resources/shaders/metal/mask_evenodd.fs.metal new file mode 100644 index 00000000..8ecd4c23 --- /dev/null +++ b/resources/shaders/metal/mask_evenodd.fs.metal @@ -0,0 +1,40 @@ +// Automatically generated from files in pathfinder/shaders/. Do not edit! +#pragma clang diagnostic ignored "-Wmissing-prototypes" + +#include +#include + +using namespace metal; + +struct spvDescriptorSetBuffer0 +{ + texture2d uFillTexture [[id(0)]]; + sampler uFillTextureSmplr [[id(1)]]; +}; + +struct main0_out +{ + float4 oFragColor [[color(0)]]; +}; + +struct main0_in +{ + float2 vFillTexCoord [[user(locn0)]]; + float vBackdrop [[user(locn1)]]; +}; + +// Implementation of the GLSL mod() function, which is slightly different than Metal fmod() +template +Tx mod(Tx x, Ty y) +{ + return x - y * floor(x / y); +} + +fragment main0_out main0(main0_in in [[stage_in]], constant spvDescriptorSetBuffer0& spvDescriptorSet0 [[buffer(0)]]) +{ + main0_out out = {}; + float alpha = spvDescriptorSet0.uFillTexture.sample(spvDescriptorSet0.uFillTextureSmplr, in.vFillTexCoord).x + in.vBackdrop; + out.oFragColor = float4(1.0 - abs(1.0 - mod(alpha, 2.0))); + return out; +} + diff --git a/shaders/Makefile b/shaders/Makefile index 3d6c883f..634331bc 100644 --- a/shaders/Makefile +++ b/shaders/Makefile @@ -11,8 +11,9 @@ SHADERS=\ demo_ground.vs.glsl \ fill.fs.glsl \ fill.vs.glsl \ + mask.vs.glsl \ + mask_evenodd.fs.glsl \ mask_winding.fs.glsl \ - mask_winding.vs.glsl \ post.fs.glsl \ post.vs.glsl \ reproject.fs.glsl \ diff --git a/shaders/mask_winding.vs.glsl b/shaders/mask.vs.glsl similarity index 100% rename from shaders/mask_winding.vs.glsl rename to shaders/mask.vs.glsl diff --git a/shaders/mask_evenodd.fs.glsl b/shaders/mask_evenodd.fs.glsl new file mode 100644 index 00000000..9d704519 --- /dev/null +++ b/shaders/mask_evenodd.fs.glsl @@ -0,0 +1,25 @@ +#version 330 + +// pathfinder/shaders/mask_evenodd.fs.glsl +// +// Copyright © 2020 The Pathfinder Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +precision highp float; + +uniform sampler2D uFillTexture; + +in vec2 vFillTexCoord; +in float vBackdrop; + +out vec4 oFragColor; + +void main() { + float alpha = texture(uFillTexture, vFillTexCoord).r + vBackdrop; + oFragColor = vec4(1.0 - abs(1.0 - mod(alpha, 2.0))); +} diff --git a/svg/src/lib.rs b/svg/src/lib.rs index 3b60d9d8..9419f977 100644 --- a/svg/src/lib.rs +++ b/svg/src/lib.rs @@ -15,6 +15,7 @@ extern crate bitflags; use hashbrown::HashMap; use pathfinder_color::ColorU; +use pathfinder_content::fill::FillRule; use pathfinder_content::outline::Outline; use pathfinder_content::segment::{Segment, SegmentFlags}; use pathfinder_content::stroke::{LineCap, LineJoin, OutlineStrokeToFill, StrokeStyle}; @@ -27,9 +28,10 @@ use pathfinder_renderer::paint::Paint; use pathfinder_renderer::scene::{ClipPath, ClipPathId, DrawPath, Scene}; use std::fmt::{Display, Formatter, Result as FormatResult}; use std::mem; -use usvg::{Color as SvgColor, LineCap as UsvgLineCap, LineJoin as UsvgLineJoin, Node, NodeExt}; -use usvg::{NodeKind, Opacity, Paint as UsvgPaint, PathSegment as UsvgPathSegment}; -use usvg::{Rect as UsvgRect, Transform as UsvgTransform, Tree, Visibility}; +use usvg::{Color as SvgColor, FillRule as UsvgFillRule, LineCap as UsvgLineCap}; +use usvg::{LineJoin as UsvgLineJoin, Node, NodeExt, NodeKind, Opacity, Paint as UsvgPaint}; +use usvg::{PathSegment as UsvgPathSegment, Rect as UsvgRect, Transform as UsvgTransform}; +use usvg::{Tree, Visibility}; const HAIRLINE_STROKE_WIDTH: f32 = 0.0333; @@ -134,7 +136,12 @@ impl BuiltSVG { let outline = Outline::from_segments(path); let name = format!("Fill({})", node.id()); - self.push_draw_path(outline, name, &state, &fill.paint, fill.opacity); + self.push_draw_path(outline, + name, + &state, + &fill.paint, + fill.opacity, + fill.rule); } if let Some(ref stroke) = path.stroke { @@ -154,7 +161,12 @@ impl BuiltSVG { outline.transform(&state.transform); let name = format!("Stroke({})", node.id()); - self.push_draw_path(outline, name, &state, &stroke.paint, stroke.opacity); + self.push_draw_path(outline, + name, + &state, + &stroke.paint, + stroke.opacity, + UsvgFillRule::NonZero); } } NodeKind::Path(..) => {} @@ -167,7 +179,8 @@ impl BuiltSVG { if let Some(clip_outline) = clip_outline { let name = format!("ClipPath({})", node.id()); - let clip_path = ClipPath::new(clip_outline, name); + // FIXME(pcwalton): Is the winding fill rule correct to use? + let clip_path = ClipPath::new(clip_outline, FillRule::Winding, name); let clip_path_id = self.scene.push_clip_path(clip_path); self.clip_paths.insert(node.id().to_owned(), clip_path_id); } @@ -215,11 +228,13 @@ impl BuiltSVG { name: String, state: &State, paint: &UsvgPaint, - opacity: Opacity) { + opacity: Opacity, + fill_rule: UsvgFillRule) { let style = self.scene.push_paint(&Paint::from_svg_paint(paint, opacity, &mut self.result_flags)); - self.scene.push_path(DrawPath::new(outline, style, state.clip_path, name)); + let fill_rule = FillRule::from_usvg_fill_rule(fill_rule); + self.scene.push_path(DrawPath::new(outline, style, state.clip_path, fill_rule, name)); } } @@ -435,6 +450,20 @@ impl LineJoinExt for LineJoin { } } +trait FillRuleExt { + fn from_usvg_fill_rule(usvg_fill_rule: UsvgFillRule) -> Self; +} + +impl FillRuleExt for FillRule { + #[inline] + fn from_usvg_fill_rule(usvg_fill_rule: UsvgFillRule) -> FillRule { + match usvg_fill_rule { + UsvgFillRule::NonZero => FillRule::Winding, + UsvgFillRule::EvenOdd => FillRule::EvenOdd, + } + } +} + #[derive(Clone)] struct State { // Where paths are being appended to.