From c688d04412ec4324c8a3b68bded1a89e0737c23e Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Mon, 15 Apr 2019 13:21:24 -0700 Subject: [PATCH] Remove the scene assembly thread, and pipeline only fills instead of alpha tiles. Removing a thread should make it easier to manually assign threads to CPUs, as is necessary on devices with poor schedulers like Magic Leap 1. --- demo/common/src/device.rs | 10 +- demo/common/src/lib.rs | 42 +- geometry/src/basic/line_segment.rs | 4 +- gl/src/lib.rs | 44 +- gpu/src/lib.rs | 17 +- renderer/src/builder.rs | 399 ++++--------------- renderer/src/gpu/renderer.rs | 169 +++++--- renderer/src/gpu_data.rs | 161 +++++--- renderer/src/tiles.rs | 88 ++-- resources/shaders/tile_alpha_vertex.inc.glsl | 9 +- ui/src/lib.rs | 36 +- 11 files changed, 448 insertions(+), 531 deletions(-) diff --git a/demo/common/src/device.rs b/demo/common/src/device.rs index f3101b21..017c43fa 100644 --- a/demo/common/src/device.rs +++ b/demo/common/src/device.rs @@ -12,7 +12,7 @@ use crate::GRIDLINE_COUNT; use pathfinder_gpu::resources::ResourceLoader; -use pathfinder_gpu::{BufferTarget, BufferUploadMode, Device, VertexAttrType}; +use pathfinder_gpu::{BufferData, BufferTarget, BufferUploadMode, Device, VertexAttrType}; pub struct GroundProgram where D: Device { pub program: D::Program, @@ -60,10 +60,10 @@ pub struct GroundLineVertexArray where D: Device { impl GroundLineVertexArray where D: Device { pub fn new(device: &D, ground_program: &GroundProgram) -> GroundLineVertexArray { let grid_vertex_positions_buffer = device.create_buffer(); - device.upload_to_buffer(&grid_vertex_positions_buffer, - &create_grid_vertex_positions(), - BufferTarget::Vertex, - BufferUploadMode::Static); + device.allocate_buffer(&grid_vertex_positions_buffer, + BufferData::Memory(&create_grid_vertex_positions()), + BufferTarget::Vertex, + BufferUploadMode::Static); let vertex_array = device.create_vertex_array(); diff --git a/demo/common/src/lib.rs b/demo/common/src/lib.rs index a0750fa6..a9811abf 100644 --- a/demo/common/src/lib.rs +++ b/demo/common/src/lib.rs @@ -25,8 +25,7 @@ use pathfinder_gl::GLDevice; use pathfinder_gpu::resources::ResourceLoader; use pathfinder_gpu::{DepthFunc, DepthState, Device, Primitive, RenderState, StencilFunc}; use pathfinder_gpu::{StencilState, UniformData}; -use pathfinder_renderer::builder::{RenderOptions, RenderTransform}; -use pathfinder_renderer::builder::{SceneBuilder, SceneBuilderContext}; +use pathfinder_renderer::builder::{RenderOptions, RenderTransform, SceneBuilder}; use pathfinder_renderer::gpu::renderer::{RenderMode, Renderer}; use pathfinder_renderer::gpu_data::{BuiltScene, RenderCommand, Stats}; use pathfinder_renderer::post::{DEFRINGING_KERNEL_CORE_GRAPHICS, STEM_DARKENING_FACTORS}; @@ -42,7 +41,7 @@ use std::iter; use std::panic::{self, AssertUnwindSafe}; use std::path::PathBuf; use std::process; -use std::sync::mpsc::{self, Receiver, Sender}; +use std::sync::mpsc::{self, Receiver, Sender, SyncSender}; use std::thread; use std::time::{Duration, Instant}; use usvg::{Options as UsvgOptions, Tree}; @@ -71,6 +70,8 @@ const APPROX_FONT_SIZE: f32 = 16.0; const MESSAGE_TIMEOUT_SECS: u64 = 5; +const MAX_MESSAGES_IN_FLIGHT: usize = 256; + pub const GRIDLINE_COUNT: u8 = 10; pub mod window; @@ -662,13 +663,9 @@ struct SceneThreadProxy { impl SceneThreadProxy { fn new(scene: Scene, options: Options) -> SceneThreadProxy { let (main_to_scene_sender, main_to_scene_receiver) = mpsc::channel(); - let (scene_to_main_sender, scene_to_main_receiver) = mpsc::channel(); - let scene_builder_context = SceneBuilderContext::new(); - SceneThread::new(scene, - scene_to_main_sender, - main_to_scene_receiver, - scene_builder_context, - options); + let (scene_to_main_sender, scene_to_main_receiver) = + mpsc::sync_channel(MAX_MESSAGES_IN_FLIGHT); + SceneThread::new(scene, scene_to_main_sender, main_to_scene_receiver, options); SceneThreadProxy { sender: main_to_scene_sender, receiver: scene_to_main_receiver } } @@ -683,19 +680,17 @@ impl SceneThreadProxy { struct SceneThread { scene: Scene, - sender: Sender, + sender: SyncSender, receiver: Receiver, - context: SceneBuilderContext, options: Options, } impl SceneThread { fn new(scene: Scene, - sender: Sender, + sender: SyncSender, receiver: Receiver, - context: SceneBuilderContext, options: Options) { - thread::spawn(move || (SceneThread { scene, sender, receiver, context, options }).run()); + thread::spawn(move || (SceneThread { scene, sender, receiver, options }).run()); } fn run(mut self) { @@ -715,8 +710,7 @@ impl SceneThread { }).unwrap(); let start_time = Instant::now(); for render_transform in &build_options.render_transforms { - build_scene(&self.context, - &self.scene, + build_scene(&self.scene, &build_options, (*render_transform).clone(), self.options.jobs, @@ -766,12 +760,11 @@ impl Debug for SceneToMainMsg { } } -fn build_scene(context: &SceneBuilderContext, - scene: &Scene, +fn build_scene(scene: &Scene, build_options: &BuildOptions, render_transform: RenderTransform, jobs: Option, - sink: &mut Sender) { + sink: &mut SyncSender) { let render_options = RenderOptions { transform: render_transform.clone(), dilation: match build_options.stem_darkening_font_size { @@ -792,18 +785,17 @@ fn build_scene(context: &SceneBuilderContext, built_scene.shaders = scene.build_shaders(); sink.send(SceneToMainMsg::BeginRenderScene(built_scene)).unwrap(); - let (context, inner_sink) = (AssertUnwindSafe(context), AssertUnwindSafe(sink.clone())); + let inner_sink = AssertUnwindSafe(sink.clone()); let result = panic::catch_unwind(move || { - let mut scene_builder = SceneBuilder::new(&context, scene, &built_options); let sink = (*inner_sink).clone(); let listener = Box::new(move |command| { sink.send(SceneToMainMsg::Execute(command)).unwrap() }); - // FIXME(pcwalton): Actually take the number of jobs into account. + let mut scene_builder = SceneBuilder::new(scene, &built_options, listener); match jobs { - Some(1) => scene_builder.build_sequentially(listener), - _ => scene_builder.build_in_parallel(listener), + Some(1) => scene_builder.build_sequentially(), + _ => scene_builder.build_in_parallel(), } }); diff --git a/geometry/src/basic/line_segment.rs b/geometry/src/basic/line_segment.rs index 315c3187..5494451f 100644 --- a/geometry/src/basic/line_segment.rs +++ b/geometry/src/basic/line_segment.rs @@ -274,10 +274,10 @@ impl Sub for LineSegmentF32 { } } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Default)] #[repr(transparent)] pub struct LineSegmentU4(pub u16); -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Default)] #[repr(transparent)] pub struct LineSegmentU8(pub u32); diff --git a/gl/src/lib.rs b/gl/src/lib.rs index e92a52d5..01d5c092 100644 --- a/gl/src/lib.rs +++ b/gl/src/lib.rs @@ -10,11 +10,12 @@ //! An OpenGL implementation of the device abstraction. -use gl::types::{GLboolean, GLchar, GLenum, GLfloat, GLint, GLsizei, GLsizeiptr, GLuint, GLvoid}; +use gl::types::{GLboolean, GLchar, GLenum, GLfloat, GLint, GLintptr, GLsizei, GLsizeiptr}; +use gl::types::{GLuint, GLvoid}; use pathfinder_geometry::basic::point::Point2DI32; use pathfinder_geometry::basic::rect::RectI32; -use pathfinder_gpu::{BlendState, BufferTarget, BufferUploadMode, DepthFunc, Device, Primitive}; -use pathfinder_gpu::{RenderState, ShaderKind, StencilFunc, TextureFormat}; +use pathfinder_gpu::{BlendState, BufferData, BufferTarget, BufferUploadMode, DepthFunc, Device}; +use pathfinder_gpu::{Primitive, RenderState, ShaderKind, StencilFunc, TextureFormat}; use pathfinder_gpu::{UniformData, VertexAttrType}; use pathfinder_simd::default::F32x4; use rustache::{HashBuilder, Render}; @@ -413,25 +414,24 @@ impl Device for GLDevice { } } - fn upload_to_buffer(&self, - buffer: &GLBuffer, - data: &[T], - target: BufferTarget, - mode: BufferUploadMode) { + fn allocate_buffer(&self, + buffer: &GLBuffer, + data: BufferData, + target: BufferTarget, + mode: BufferUploadMode) { let target = match target { BufferTarget::Vertex => gl::ARRAY_BUFFER, BufferTarget::Index => gl::ELEMENT_ARRAY_BUFFER, }; - let mode = match mode { - BufferUploadMode::Static => gl::STATIC_DRAW, - BufferUploadMode::Dynamic => gl::DYNAMIC_DRAW, + let (ptr, len) = match data { + BufferData::Uninitialized(len) => (ptr::null(), len), + BufferData::Memory(buffer) => (buffer.as_ptr() as *const GLvoid, buffer.len()), }; + let len = (len * mem::size_of::()) as GLsizeiptr; + let usage = mode.to_gl_usage(); unsafe { gl::BindBuffer(target, buffer.gl_buffer); ck(); - gl::BufferData(target, - (data.len() * mem::size_of::()) as GLsizeiptr, - data.as_ptr() as *const GLvoid, - mode); ck(); + gl::BufferData(target, len, ptr, usage); ck(); } } @@ -774,6 +774,19 @@ impl BufferTargetExt for BufferTarget { } } +trait BufferUploadModeExt { + fn to_gl_usage(self) -> GLuint; +} + +impl BufferUploadModeExt for BufferUploadMode { + fn to_gl_usage(self) -> GLuint { + match self { + BufferUploadMode::Static => gl::STATIC_DRAW, + BufferUploadMode::Dynamic => gl::DYNAMIC_DRAW, + } + } +} + trait DepthFuncExt { fn to_gl_depth_func(self) -> GLenum; } @@ -824,6 +837,7 @@ impl VertexAttrTypeExt for VertexAttrType { match self { VertexAttrType::F32 => gl::FLOAT, VertexAttrType::I16 => gl::SHORT, + VertexAttrType::I8 => gl::BYTE, VertexAttrType::U16 => gl::UNSIGNED_SHORT, VertexAttrType::U8 => gl::UNSIGNED_BYTE, } diff --git a/gpu/src/lib.rs b/gpu/src/lib.rs index ada94cdc..b7886420 100644 --- a/gpu/src/lib.rs +++ b/gpu/src/lib.rs @@ -66,11 +66,11 @@ pub trait Device { fn set_uniform(&self, uniform: &Self::Uniform, data: UniformData); fn create_framebuffer(&self, texture: Self::Texture) -> Self::Framebuffer; fn create_buffer(&self) -> Self::Buffer; - fn upload_to_buffer(&self, - buffer: &Self::Buffer, - data: &[T], - target: BufferTarget, - mode: BufferUploadMode); + fn allocate_buffer(&self, + buffer: &Self::Buffer, + data: BufferData, + target: BufferTarget, + mode: BufferUploadMode); fn framebuffer_texture<'f>(&self, framebuffer: &'f Self::Framebuffer) -> &'f Self::Texture; fn texture_size(&self, texture: &Self::Texture) -> Point2DI32; fn upload_to_texture(&self, texture: &Self::Texture, size: Point2DI32, data: &[u8]); @@ -167,10 +167,17 @@ pub enum TextureFormat { pub enum VertexAttrType { F32, I16, + I8, U16, U8, } +#[derive(Clone, Copy, Debug)] +pub enum BufferData<'a, T> { + Uninitialized(usize), + Memory(&'a [T]), +} + #[derive(Clone, Copy, Debug)] pub enum BufferTarget { Vertex, diff --git a/renderer/src/builder.rs b/renderer/src/builder.rs index 9d7bb7f5..e8510a89 100644 --- a/renderer/src/builder.rs +++ b/renderer/src/builder.rs @@ -10,334 +10,123 @@ //! Packs data onto the GPU. -use crate::gpu_data::{AlphaTileBatchPrimitive, BuiltObject, FillBatchPrimitive}; -use crate::gpu_data::{RenderCommand, SolidTileBatchPrimitive}; +use crate::gpu_data::{AlphaTileBatchPrimitive, RenderCommand}; use crate::scene::Scene; -use crate::sorted_vector::SortedVector; use crate::tiles::Tiler; use crate::z_buffer::ZBuffer; -use pathfinder_geometry::basic::point::{Point2DF32, Point2DI32, Point3DF32}; +use pathfinder_geometry::basic::point::{Point2DF32, Point3DF32}; use pathfinder_geometry::basic::rect::RectF32; use pathfinder_geometry::basic::transform2d::Transform2DF32; use pathfinder_geometry::basic::transform3d::Perspective; use pathfinder_geometry::clip::PolygonClipper3D; use pathfinder_geometry::distortion::BarrelDistortionCoefficients; use rayon::iter::{IntoParallelIterator, ParallelIterator}; -use std::cmp::{Ordering, PartialOrd}; -use std::mem; -use std::ops::Range; -use std::sync::mpsc::{self, Receiver, SyncSender}; -use std::sync::{Arc, Mutex}; -use std::thread; +use std::sync::atomic::AtomicUsize; use std::u16; -const MAX_FILLS_PER_BATCH: usize = 0x0002_0000; -const MAX_ALPHA_TILES_PER_BATCH: usize = 0x1000; -const MAX_CHANNEL_MESSAGES: usize = 16; +// Must be a power of two. +pub const MAX_FILLS_PER_BATCH: u32 = 0x1000; -pub struct SceneBuilderContext { - sender: SyncSender, - receiver: Mutex>, -} - -struct SceneAssemblyThread { - receiver: Receiver, - sender: SyncSender, - info: Option, -} - -struct SceneAssemblyThreadInfo { - listener: Box, - built_object_queue: SortedVector, - next_object_index: u32, - - pub(crate) z_buffer: Arc, - current_pass: Pass, -} - -enum MainToSceneAssemblyMsg { - NewScene { listener: Box, z_buffer: Arc }, - AddObject(IndexedBuiltObject), - SceneFinished, - Exit, -} - -enum SceneAssemblyToMainMsg { - FrameFinished, -} - -impl Drop for SceneBuilderContext { - #[inline] - fn drop(&mut self) { - self.sender.send(MainToSceneAssemblyMsg::Exit).unwrap(); - } -} - -pub trait RenderCommandListener: Send { - fn send(&mut self, command: RenderCommand); +pub trait RenderCommandListener: Send + Sync { + fn send(&self, command: RenderCommand); } pub struct SceneBuilder<'a> { - context: &'a SceneBuilderContext, scene: &'a Scene, built_options: &'a PreparedRenderOptions, -} -struct IndexedBuiltObject { - object: BuiltObject, - index: u32, -} - -impl SceneBuilderContext { - #[inline] - pub fn new() -> SceneBuilderContext { - let (main_to_scene_assembly_sender, - main_to_scene_assembly_receiver) = mpsc::sync_channel(MAX_CHANNEL_MESSAGES); - let (scene_assembly_to_main_sender, - scene_assembly_to_main_receiver) = mpsc::sync_channel(MAX_CHANNEL_MESSAGES); - thread::spawn(move || { - SceneAssemblyThread::new(main_to_scene_assembly_receiver, - scene_assembly_to_main_sender).run() - }); - SceneBuilderContext { - sender: main_to_scene_assembly_sender, - receiver: Mutex::new(scene_assembly_to_main_receiver), - } - } -} - -impl SceneAssemblyThread { - #[inline] - fn new(receiver: Receiver, sender: SyncSender) - -> SceneAssemblyThread { - SceneAssemblyThread { receiver, sender, info: None } - } - - fn run(&mut self) { - while let Ok(msg) = self.receiver.recv() { - match msg { - MainToSceneAssemblyMsg::Exit => break, - MainToSceneAssemblyMsg::NewScene { listener, z_buffer } => { - self.info = Some(SceneAssemblyThreadInfo { - listener, - built_object_queue: SortedVector::new(), - next_object_index: 0, - - z_buffer, - current_pass: Pass::new(), - }) - } - MainToSceneAssemblyMsg::AddObject(indexed_built_object) => { - self.info.as_mut().unwrap().built_object_queue.push(indexed_built_object); - - loop { - let next_object_index = self.info.as_ref().unwrap().next_object_index; - match self.info.as_mut().unwrap().built_object_queue.peek() { - Some(ref indexed_object) if - next_object_index == indexed_object.index => {} - _ => break, - } - let indexed_object = self.info.as_mut().unwrap().built_object_queue.pop(); - self.add_object(indexed_object.unwrap().object); - self.info.as_mut().unwrap().next_object_index += 1; - } - } - MainToSceneAssemblyMsg::SceneFinished => { - self.flush_current_pass(); - self.sender.send(SceneAssemblyToMainMsg::FrameFinished).unwrap(); - } - } - } - } - - fn add_object(&mut self, object: BuiltObject) { - // Flush current pass if necessary. - if self.info.as_ref().unwrap().current_pass.fills.len() + object.fills.len() > - MAX_FILLS_PER_BATCH { - self.flush_current_pass(); - } - - // See whether we have room for the alpha tiles. If we don't, then flush. - let (tile_count, mut alpha_tile_count) = (object.tile_count() as usize, 0); - for tile_index in 0..(object.tile_count() as usize) { - if !object.solid_tiles[tile_index] { - alpha_tile_count += 1; - } - } - if self.info.as_ref().unwrap().current_pass.alpha_tiles.len() + alpha_tile_count > - MAX_ALPHA_TILES_PER_BATCH { - self.flush_current_pass(); - } - - // Copy alpha tiles. - let mut current_pass = &mut self.info.as_mut().unwrap().current_pass; - let mut object_tile_index_to_batch_alpha_tile_index = vec![u16::MAX; tile_count]; - for (tile_index, tile_backdrop) in object.tile_backdrops.data.iter().cloned().enumerate() { - // Skip solid tiles. - if object.solid_tiles[tile_index] { - continue; - } - - let batch_alpha_tile_index = current_pass.alpha_tiles.len() as u16; - object_tile_index_to_batch_alpha_tile_index[tile_index] = batch_alpha_tile_index; - - let tile_coords = object.tile_index_to_coords(tile_index as u32); - current_pass.alpha_tiles.push(AlphaTileBatchPrimitive { - tile_x: tile_coords.x() as i16, - tile_y: tile_coords.y() as i16, - object_index: current_pass.object_range.end as u16, - backdrop: tile_backdrop, - }); - } - - // Remap and copy fills, culling as necessary. - for fill in &object.fills { - let tile_coords = Point2DI32::new(fill.tile_x as i32, fill.tile_y as i32); - let object_tile_index = object.tile_coords_to_index(tile_coords).unwrap(); - let object_tile_index = object_tile_index as usize; - let alpha_tile_index = object_tile_index_to_batch_alpha_tile_index[object_tile_index]; - current_pass.fills.push(FillBatchPrimitive { - px: fill.px, - subpx: fill.subpx, - alpha_tile_index, - }); - } - - current_pass.object_range.end += 1; - } - - fn flush_current_pass(&mut self) { - self.cull_alpha_tiles(); - - let mut info = self.info.as_mut().unwrap(); - info.current_pass.solid_tiles = - info.z_buffer.build_solid_tiles(info.current_pass.object_range.clone()); - - let have_solid_tiles = !info.current_pass.solid_tiles.is_empty(); - let have_alpha_tiles = !info.current_pass.alpha_tiles.is_empty(); - let have_fills = !info.current_pass.fills.is_empty(); - if !have_solid_tiles && !have_alpha_tiles && !have_fills { - return - } - - info.listener.send(RenderCommand::ClearMaskFramebuffer); - if have_solid_tiles { - let tiles = mem::replace(&mut info.current_pass.solid_tiles, vec![]); - info.listener.send(RenderCommand::SolidTile(tiles)); - } - if have_fills { - let fills = mem::replace(&mut info.current_pass.fills, vec![]); - info.listener.send(RenderCommand::Fill(fills)); - } - if have_alpha_tiles { - let tiles = mem::replace(&mut info.current_pass.alpha_tiles, vec![]); - info.listener.send(RenderCommand::AlphaTile(tiles)); - } - - info.current_pass.object_range.start = info.current_pass.object_range.end; - } - - fn cull_alpha_tiles(&mut self) { - let info = self.info.as_mut().unwrap(); - for alpha_tile in &mut info.current_pass.alpha_tiles { - let alpha_tile_coords = Point2DI32::new(alpha_tile.tile_x as i32, - alpha_tile.tile_y as i32); - if info.z_buffer.test(alpha_tile_coords, alpha_tile.object_index as u32) { - continue; - } - - // FIXME(pcwalton): Hack! - alpha_tile.tile_x = -1; - alpha_tile.tile_y = -1; - } - } + pub(crate) next_alpha_tile_index: AtomicUsize, + pub(crate) z_buffer: ZBuffer, + pub(crate) listener: Box, } impl<'a> SceneBuilder<'a> { - pub fn new(context: &'a SceneBuilderContext, - scene: &'a Scene, - built_options: &'a PreparedRenderOptions) + pub fn new(scene: &'a Scene, + built_options: &'a PreparedRenderOptions, + listener: Box) -> SceneBuilder<'a> { - SceneBuilder { context, scene, built_options } - } + let effective_view_box = scene.effective_view_box(built_options); + SceneBuilder { + scene, + built_options, - pub fn build_sequentially(&mut self, listener: Box) { - let effective_view_box = self.scene.effective_view_box(self.built_options); - let z_buffer = Arc::new(ZBuffer::new(effective_view_box)); - self.send_new_scene_message_to_assembly_thread(listener, &z_buffer); - - for object_index in 0..self.scene.objects.len() { - build_object(object_index, - effective_view_box, - &z_buffer, - &self.built_options, - &self.scene, - &self.context.sender); - } - - self.finish_and_wait_for_scene_assembly_thread(); - } - - pub fn build_in_parallel(&mut self, listener: Box) { - let effective_view_box = self.scene.effective_view_box(self.built_options); - let z_buffer = Arc::new(ZBuffer::new(effective_view_box)); - self.send_new_scene_message_to_assembly_thread(listener, &z_buffer); - - (0..self.scene.objects.len()).into_par_iter().for_each(|object_index| { - build_object(object_index, - effective_view_box, - &z_buffer, - &self.built_options, - &self.scene, - &self.context.sender); - }); - - self.finish_and_wait_for_scene_assembly_thread(); - } - - fn send_new_scene_message_to_assembly_thread(&mut self, - listener: Box, - z_buffer: &Arc) { - self.context.sender.send(MainToSceneAssemblyMsg::NewScene { + next_alpha_tile_index: AtomicUsize::new(0), + z_buffer: ZBuffer::new(effective_view_box), listener, - z_buffer: z_buffer.clone(), - }).unwrap(); + } } - fn finish_and_wait_for_scene_assembly_thread(&mut self) { - self.context.sender.send(MainToSceneAssemblyMsg::SceneFinished).unwrap(); - self.context.receiver.lock().unwrap().recv().unwrap(); + pub fn build_sequentially(&mut self) { + let effective_view_box = self.scene.effective_view_box(self.built_options); + let object_count = self.scene.objects.len(); + let alpha_tiles: Vec<_> = (0..object_count).into_iter().flat_map(|object_index| { + self.build_object(object_index, + effective_view_box, + &self.built_options, + &self.scene) + }).collect(); + + self.finish_building(alpha_tiles); } -} -fn build_object(object_index: usize, - effective_view_box: RectF32, - z_buffer: &ZBuffer, - built_options: &PreparedRenderOptions, - scene: &Scene, - sender: &SyncSender) { - let object = &scene.objects[object_index]; - let outline = scene.apply_render_options(object.outline(), built_options); + pub fn build_in_parallel(&mut self) { + let effective_view_box = self.scene.effective_view_box(self.built_options); + let object_count = self.scene.objects.len(); + let alpha_tiles: Vec<_> = (0..object_count).into_par_iter().flat_map(|object_index| { + self.build_object(object_index, + effective_view_box, + &self.built_options, + &self.scene) + }).collect(); - let mut tiler = Tiler::new(&outline, effective_view_box, object_index as u16, z_buffer); - tiler.generate_tiles(); + self.finish_building(alpha_tiles); + } - sender.send(MainToSceneAssemblyMsg::AddObject(IndexedBuiltObject { - index: object_index as u32, - object: tiler.built_object, - })).unwrap(); -} + fn build_object(&self, + object_index: usize, + view_box: RectF32, + built_options: &PreparedRenderOptions, + scene: &Scene) + -> Vec { + let object = &scene.objects[object_index]; + let outline = scene.apply_render_options(object.outline(), built_options); -struct Pass { - solid_tiles: Vec, - alpha_tiles: Vec, - fills: Vec, - object_range: Range, -} + let mut tiler = Tiler::new(self, &outline, view_box, object_index as u16); + tiler.generate_tiles(); -impl Pass { - fn new() -> Pass { - Pass { solid_tiles: vec![], alpha_tiles: vec![], fills: vec![], object_range: 0..0 } + self.listener.send(RenderCommand::AddFills(tiler.built_object.fills)); + tiler.built_object.alpha_tiles + } + + fn cull_alpha_tiles(&self, alpha_tiles: &mut Vec) { + for alpha_tile in alpha_tiles { + let alpha_tile_coords = alpha_tile.tile_coords(); + if self.z_buffer.test(alpha_tile_coords, alpha_tile.object_index as u32) { + continue; + } + + // FIXME(pcwalton): Clean this up. + alpha_tile.tile_x_lo = 0xff; + alpha_tile.tile_y_lo = 0xff; + alpha_tile.tile_hi = 0xff; + } + } + + fn pack_alpha_tiles(&mut self, alpha_tiles: Vec) { + let object_count = self.scene.objects.len() as u32; + let solid_tiles = self.z_buffer.build_solid_tiles(0..object_count); + if !solid_tiles.is_empty() { + self.listener.send(RenderCommand::SolidTile(solid_tiles)); + } + if !alpha_tiles.is_empty() { + self.listener.send(RenderCommand::AlphaTile(alpha_tiles)); + } + } + + fn finish_building(&mut self, mut alpha_tiles: Vec) { + self.listener.send(RenderCommand::FlushFills); + self.cull_alpha_tiles(&mut alpha_tiles); + self.pack_alpha_tiles(alpha_tiles); } } @@ -454,21 +243,7 @@ impl PreparedRenderTransform { } } -impl PartialEq for IndexedBuiltObject { +impl RenderCommandListener for F where F: Fn(RenderCommand) + Send + Sync { #[inline] - fn eq(&self, other: &IndexedBuiltObject) -> bool { - other.index == self.index - } -} - -impl PartialOrd for IndexedBuiltObject { - #[inline] - fn partial_cmp(&self, other: &IndexedBuiltObject) -> Option { - other.index.partial_cmp(&self.index) - } -} - -impl RenderCommandListener for F where F: FnMut(RenderCommand) + Send { - #[inline] - fn send(&mut self, command: RenderCommand) { (*self)(command) } + fn send(&self, command: RenderCommand) { (*self)(command) } } diff --git a/renderer/src/gpu/renderer.rs b/renderer/src/gpu/renderer.rs index 27610ba2..c6bbc468 100644 --- a/renderer/src/gpu/renderer.rs +++ b/renderer/src/gpu/renderer.rs @@ -18,17 +18,20 @@ use pathfinder_geometry::basic::point::{Point2DI32, Point3DF32}; use pathfinder_geometry::basic::rect::RectI32; use pathfinder_geometry::color::ColorF; use pathfinder_gpu::resources::ResourceLoader; -use pathfinder_gpu::{BlendState, BufferTarget, BufferUploadMode, DepthFunc, DepthState, Device}; -use pathfinder_gpu::{Primitive, RenderState, StencilFunc, StencilState, TextureFormat}; -use pathfinder_gpu::{UniformData, VertexAttrType}; +use pathfinder_gpu::{BlendState, BufferData, BufferTarget, BufferUploadMode, DepthFunc}; +use pathfinder_gpu::{DepthState, Device, Primitive, RenderState, StencilFunc, StencilState}; +use pathfinder_gpu::{TextureFormat, UniformData, VertexAttrType}; use pathfinder_simd::default::{F32x4, I32x4}; +use std::cmp; use std::collections::VecDeque; use std::time::Duration; +use std::u32; static QUAD_VERTEX_POSITIONS: [u8; 8] = [0, 0, 1, 0, 1, 1, 0, 1]; -const MASK_FRAMEBUFFER_WIDTH: i32 = TILE_WIDTH as i32 * 64; -const MASK_FRAMEBUFFER_HEIGHT: i32 = TILE_HEIGHT as i32 * 64; +// FIXME(pcwalton): Shrink this again! +const MASK_FRAMEBUFFER_WIDTH: i32 = TILE_WIDTH as i32 * 256; +const MASK_FRAMEBUFFER_HEIGHT: i32 = TILE_HEIGHT as i32 * 256; // TODO(pcwalton): Replace with `mem::size_of` calls? const FILL_INSTANCE_SIZE: usize = 8; @@ -38,6 +41,8 @@ const MASK_TILE_INSTANCE_SIZE: usize = 8; const FILL_COLORS_TEXTURE_WIDTH: i32 = 256; const FILL_COLORS_TEXTURE_HEIGHT: i32 = 256; +const MAX_FILLS_PER_BATCH: usize = 0x4000; + pub struct Renderer where D: Device { // Device pub device: D, @@ -78,6 +83,10 @@ pub struct Renderer where D: Device { viewport: RectI32, render_mode: RenderMode, use_depth: bool, + + // Rendering state + mask_framebuffer_cleared: bool, + buffered_fills: Vec, } impl Renderer where D: Device { @@ -100,10 +109,10 @@ impl Renderer where D: Device { let gamma_lut_texture = device.create_texture_from_png(resources, "gamma-lut"); let quad_vertex_positions_buffer = device.create_buffer(); - device.upload_to_buffer(&quad_vertex_positions_buffer, - &QUAD_VERTEX_POSITIONS, - BufferTarget::Vertex, - BufferUploadMode::Static); + device.allocate_buffer(&quad_vertex_positions_buffer, + BufferData::Memory(&QUAD_VERTEX_POSITIONS), + BufferTarget::Vertex, + BufferUploadMode::Static); let fill_vertex_array = FillVertexArray::new(&device, &fill_program, @@ -175,6 +184,9 @@ impl Renderer where D: Device { viewport, render_mode: RenderMode::default(), use_depth: false, + + mask_framebuffer_cleared: false, + buffered_fills: vec![], } } @@ -192,16 +204,14 @@ impl Renderer where D: Device { if self.use_depth { self.draw_stencil(&built_scene.quad); } + + self.mask_framebuffer_cleared = false; } pub fn render_command(&mut self, command: &RenderCommand) { match *command { - RenderCommand::ClearMaskFramebuffer => self.clear_mask_framebuffer(), - RenderCommand::Fill(ref fills) => { - let count = fills.len() as u32; - self.upload_fills(fills); - self.draw_fills(count); - } + RenderCommand::AddFills(ref fills) => self.add_fills(fills), + RenderCommand::FlushFills => self.draw_buffered_fills(), RenderCommand::SolidTile(ref solid_tiles) => { let count = solid_tiles.len() as u32; self.upload_solid_tiles(solid_tiles); @@ -284,24 +294,17 @@ impl Renderer where D: Device { } fn upload_solid_tiles(&mut self, solid_tiles: &[SolidTileBatchPrimitive]) { - self.device.upload_to_buffer(&self.solid_tile_vertex_array().vertex_buffer, - &solid_tiles, - BufferTarget::Vertex, - BufferUploadMode::Dynamic); - } - - fn upload_fills(&mut self, fills: &[FillBatchPrimitive]) { - self.device.upload_to_buffer(&self.fill_vertex_array.vertex_buffer, - &fills, - BufferTarget::Vertex, - BufferUploadMode::Dynamic); + self.device.allocate_buffer(&self.solid_tile_vertex_array().vertex_buffer, + BufferData::Memory(&solid_tiles), + BufferTarget::Vertex, + BufferUploadMode::Dynamic); } fn upload_alpha_tiles(&mut self, alpha_tiles: &[AlphaTileBatchPrimitive]) { - self.device.upload_to_buffer(&self.alpha_tile_vertex_array().vertex_buffer, - &alpha_tiles, - BufferTarget::Vertex, - BufferUploadMode::Dynamic); + self.device.allocate_buffer(&self.alpha_tile_vertex_array().vertex_buffer, + BufferData::Memory(&alpha_tiles), + BufferTarget::Vertex, + BufferUploadMode::Dynamic); } fn clear_mask_framebuffer(&mut self) { @@ -311,7 +314,36 @@ impl Renderer where D: Device { self.device.clear(Some(F32x4::splat(0.0)), None, None); } - fn draw_fills(&mut self, count: u32) { + fn add_fills(&mut self, mut fills: &[FillBatchPrimitive]) { + if fills.is_empty() { + return; + } + + while !fills.is_empty() { + let count = cmp::min(fills.len(), MAX_FILLS_PER_BATCH - self.buffered_fills.len()); + self.buffered_fills.extend_from_slice(&fills[0..count]); + fills = &fills[count..]; + if self.buffered_fills.len() == MAX_FILLS_PER_BATCH { + self.draw_buffered_fills(); + } + } + } + + fn draw_buffered_fills(&mut self) { + if self.buffered_fills.is_empty() { + return; + } + + self.device.allocate_buffer(&self.fill_vertex_array.vertex_buffer, + BufferData::Memory(&self.buffered_fills), + BufferTarget::Vertex, + BufferUploadMode::Dynamic); + + if !self.mask_framebuffer_cleared { + self.clear_mask_framebuffer(); + self.mask_framebuffer_cleared = true; + } + self.device.bind_framebuffer(&self.mask_framebuffer); self.device.bind_vertex_array(&self.fill_vertex_array.vertex_array); @@ -333,7 +365,13 @@ impl Renderer where D: Device { blend: BlendState::RGBOneAlphaOne, ..RenderState::default() }; - self.device.draw_arrays_instanced(Primitive::TriangleFan, 4, count, &render_state); + debug_assert!(self.buffered_fills.len() <= u32::MAX as usize); + self.device.draw_arrays_instanced(Primitive::TriangleFan, + 4, + self.buffered_fills.len() as u32, + &render_state); + + self.buffered_fills.clear() } fn draw_alpha_tiles(&mut self, count: u32) { @@ -530,10 +568,10 @@ impl Renderer where D: Device { } fn draw_stencil(&self, quad_positions: &[Point3DF32]) { - self.device.upload_to_buffer(&self.stencil_vertex_array.vertex_buffer, - quad_positions, - BufferTarget::Vertex, - BufferUploadMode::Dynamic); + self.device.allocate_buffer(&self.stencil_vertex_array.vertex_buffer, + BufferData::Memory(quad_positions), + BufferTarget::Vertex, + BufferUploadMode::Dynamic); self.bind_draw_framebuffer(); self.device.bind_vertex_array(&self.stencil_vertex_array.vertex_array); @@ -619,7 +657,14 @@ impl FillVertexArray where D: Device { fn new(device: &D, fill_program: &FillProgram, quad_vertex_positions_buffer: &D::Buffer) -> FillVertexArray { let vertex_array = device.create_vertex_array(); + let vertex_buffer = device.create_buffer(); + let vertex_buffer_data: BufferData = + BufferData::Uninitialized(MAX_FILLS_PER_BATCH); + device.allocate_buffer(&vertex_buffer, + vertex_buffer_data, + BufferTarget::Vertex, + BufferUploadMode::Dynamic); let tess_coord_attr = device.get_vertex_attr(&fill_program.program, "TessCoord"); let from_px_attr = device.get_vertex_attr(&fill_program.program, "FromPx"); @@ -692,6 +737,7 @@ impl AlphaTileVertexArray where D: Device { let tile_origin_attr = device.get_vertex_attr(&alpha_tile_program.program, "TileOrigin"); let backdrop_attr = device.get_vertex_attr(&alpha_tile_program.program, "Backdrop"); let object_attr = device.get_vertex_attr(&alpha_tile_program.program, "Object"); + let tile_index_attr = device.get_vertex_attr(&alpha_tile_program.program, "TileIndex"); // NB: The object must be of type `I16`, not `U16`, to work around a macOS Radeon // driver bug. @@ -699,32 +745,37 @@ impl AlphaTileVertexArray where D: Device { device.use_program(&alpha_tile_program.program); device.bind_buffer(quad_vertex_positions_buffer, BufferTarget::Vertex); device.configure_float_vertex_attr(&tess_coord_attr, - 2, - VertexAttrType::U8, - false, - 0, - 0, - 0); + 2, + VertexAttrType::U8, + false, + 0, + 0, + 0); device.bind_buffer(&vertex_buffer, BufferTarget::Vertex); - device.configure_float_vertex_attr(&tile_origin_attr, - 2, - VertexAttrType::I16, - false, - MASK_TILE_INSTANCE_SIZE, - 0, - 1); + device.configure_int_vertex_attr(&tile_origin_attr, + 3, + VertexAttrType::U8, + MASK_TILE_INSTANCE_SIZE, + 0, + 1); device.configure_int_vertex_attr(&backdrop_attr, - 1, - VertexAttrType::I16, - MASK_TILE_INSTANCE_SIZE, - 4, - 1); + 1, + VertexAttrType::I8, + MASK_TILE_INSTANCE_SIZE, + 3, + 1); device.configure_int_vertex_attr(&object_attr, - 2, - VertexAttrType::I16, - MASK_TILE_INSTANCE_SIZE, - 6, - 1); + 2, + VertexAttrType::I16, + MASK_TILE_INSTANCE_SIZE, + 4, + 1); + device.configure_int_vertex_attr(&tile_index_attr, + 2, + VertexAttrType::I16, + MASK_TILE_INSTANCE_SIZE, + 6, + 1); AlphaTileVertexArray { vertex_array, vertex_buffer } } diff --git a/renderer/src/gpu_data.rs b/renderer/src/gpu_data.rs index f6715b5e..94eecdce 100644 --- a/renderer/src/gpu_data.rs +++ b/renderer/src/gpu_data.rs @@ -10,10 +10,10 @@ //! Packed data ready to be sent to the GPU. +use crate::builder::SceneBuilder; use crate::scene::ObjectShader; use crate::tile_map::DenseTileMap; use crate::tiles::{self, TILE_HEIGHT, TILE_WIDTH}; -use fixedbitset::FixedBitSet; use pathfinder_geometry::basic::line_segment::{LineSegmentF32, LineSegmentU4, LineSegmentU8}; use pathfinder_geometry::basic::point::{Point2DF32, Point2DI32, Point3DF32}; use pathfinder_geometry::basic::rect::{RectF32, RectI32}; @@ -21,13 +21,14 @@ use pathfinder_geometry::util; use pathfinder_simd::default::{F32x4, I32x4}; use std::fmt::{Debug, Formatter, Result as DebugResult}; use std::ops::Add; +use std::sync::atomic::Ordering; #[derive(Debug)] -pub struct BuiltObject { +pub(crate) struct BuiltObject { pub bounds: RectF32, - pub tile_backdrops: DenseTileMap, - pub fills: Vec, - pub solid_tiles: FixedBitSet, + pub fills: Vec, + pub alpha_tiles: Vec, + pub tiles: DenseTileMap, } #[derive(Debug)] @@ -39,8 +40,8 @@ pub struct BuiltScene { } pub enum RenderCommand { - ClearMaskFramebuffer, - Fill(Vec), + AddFills(Vec), + FlushFills, AlphaTile(Vec), SolidTile(Vec), } @@ -53,8 +54,16 @@ pub struct FillObjectPrimitive { pub tile_y: i16, } -// FIXME(pcwalton): Move `subpx` before `px` and remove `repr(packed)`. #[derive(Clone, Copy, Debug)] +#[repr(C)] +pub struct TileObjectPrimitive { + /// If `u16::MAX`, then this is a solid tile. + pub alpha_tile_index: u16, + pub backdrop: i8, +} + +// FIXME(pcwalton): Move `subpx` before `px` and remove `repr(packed)`. +#[derive(Clone, Copy, Debug, Default)] #[repr(packed)] pub struct FillBatchPrimitive { pub px: LineSegmentU4, @@ -70,13 +79,15 @@ pub struct SolidTileBatchPrimitive { pub object_index: u16, } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Default)] #[repr(C)] pub struct AlphaTileBatchPrimitive { - pub tile_x: i16, - pub tile_y: i16, - pub backdrop: i16, + pub tile_x_lo: u8, + pub tile_y_lo: u8, + pub tile_hi: u8, + pub backdrop: i8, pub object_index: u16, + pub tile_index: u16, } #[derive(Clone, Copy, Debug, Default)] @@ -90,33 +101,26 @@ pub struct Stats { // Utilities for built objects impl BuiltObject { - pub fn new(bounds: RectF32) -> BuiltObject { - // Compute the tile rect. + pub(crate) fn new(bounds: RectF32) -> BuiltObject { let tile_rect = tiles::round_rect_out_to_tile_bounds(bounds); - - // Allocate tiles. - let tile_backdrops = DenseTileMap::new(tile_rect); - let mut solid_tiles = FixedBitSet::with_capacity(tile_backdrops.data.len()); - solid_tiles.insert_range(..); - - BuiltObject { bounds, tile_backdrops, fills: vec![], solid_tiles } + let tiles = DenseTileMap::new(tile_rect); + BuiltObject { bounds, fills: vec![], alpha_tiles: vec![], tiles } } #[inline] - pub fn tile_rect(&self) -> RectI32 { - self.tile_backdrops.rect + pub(crate) fn tile_rect(&self) -> RectI32 { + self.tiles.rect } - #[inline] - pub fn tile_count(&self) -> u32 { - self.tile_backdrops.data.len() as u32 - } - - fn add_fill(&mut self, segment: &LineSegmentF32, tile_coords: Point2DI32) { + fn add_fill(&mut self, + builder: &SceneBuilder, + segment: &LineSegmentF32, + tile_coords: Point2DI32) { //println!("add_fill({:?} ({}, {}))", segment, tile_x, tile_y); - let tile_index = match self.tile_coords_to_index(tile_coords) { - None => return, - Some(tile_index) => tile_index, + + // 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); @@ -142,22 +146,34 @@ impl BuiltObject { return; } + // Allocate global tile if necessary. + let alpha_tile_index = self.get_or_allocate_alpha_tile_index(builder, tile_coords); + //println!("... ... OK, pushing"); - self.fills.push(FillObjectPrimitive { - px, - subpx, - tile_x: tile_coords.x() as i16, - tile_y: tile_coords.y() as i16, - }); - self.solid_tiles.set(tile_index as usize, false); + self.fills.push(FillBatchPrimitive { px, subpx, alpha_tile_index }); } - pub fn add_active_fill(&mut self, - left: f32, - right: f32, - mut winding: i16, - tile_coords: Point2DI32) { + fn get_or_allocate_alpha_tile_index(&mut self, builder: &SceneBuilder, tile_coords: Point2DI32) + -> u16 { + let local_tile_index = self.tiles.coords_to_index_unchecked(tile_coords); + let alpha_tile_index = self.tiles.data[local_tile_index].alpha_tile_index; + if alpha_tile_index != !0 { + return alpha_tile_index; + } + + let alpha_tile_index = builder.next_alpha_tile_index + .fetch_add(1, Ordering::Relaxed) as u16; + self.tiles.data[local_tile_index].alpha_tile_index = alpha_tile_index; + alpha_tile_index + } + + pub(crate) fn add_active_fill(&mut self, + builder: &SceneBuilder, + left: f32, + right: f32, + mut winding: i32, + tile_coords: Point2DI32) { let tile_origin_y = (tile_coords.y() * TILE_HEIGHT as i32) as f32; let left = Point2DF32::new(left, tile_origin_y); let right = Point2DF32::new(right, tile_origin_y); @@ -176,7 +192,7 @@ impl BuiltObject { tile_y);*/ while winding != 0 { - self.add_fill(&segment, tile_coords); + self.add_fill(builder, &segment, tile_coords); if winding < 0 { winding += 1 } else { @@ -185,7 +201,10 @@ impl BuiltObject { } } - pub fn generate_fill_primitives_for_line(&mut self, mut segment: LineSegmentF32, tile_y: i32) { + pub(crate) fn generate_fill_primitives_for_line(&mut self, + builder: &SceneBuilder, + mut segment: LineSegmentF32, + tile_y: i32) { /*println!("... generate_fill_primitives_for_line(): segment={:?} tile_y={} ({}-{})", segment, tile_y, @@ -223,18 +242,19 @@ impl BuiltObject { } let fill_segment = LineSegmentF32::new(&fill_from, &fill_to); - self.add_fill(&fill_segment, Point2DI32::new(subsegment_tile_x, tile_y)); + let fill_tile_coords = Point2DI32::new(subsegment_tile_x, tile_y); + self.add_fill(builder, &fill_segment, fill_tile_coords); } } #[inline] - pub fn tile_coords_to_index(&self, coords: Point2DI32) -> Option { - self.tile_backdrops.coords_to_index(coords).map(|index| index as u32) + pub(crate) fn tile_coords_to_local_index(&self, coords: Point2DI32) -> Option { + self.tiles.coords_to_index(coords).map(|index| index as u32) } #[inline] - pub fn tile_index_to_coords(&self, tile_index: u32) -> Point2DI32 { - self.tile_backdrops.index_to_coords(tile_index as usize) + pub(crate) fn local_tile_index_to_coords(&self, tile_index: u32) -> Point2DI32 { + self.tiles.index_to_coords(tile_index as usize) } } @@ -254,11 +274,46 @@ impl BuiltScene { } } +impl Default for TileObjectPrimitive { + #[inline] + fn default() -> TileObjectPrimitive { + TileObjectPrimitive { backdrop: 0, alpha_tile_index: !0 } + } +} + +impl TileObjectPrimitive { + #[inline] + pub fn is_solid(&self) -> bool { + self.alpha_tile_index == !0 + } +} + +impl AlphaTileBatchPrimitive { + #[inline] + pub fn new(tile_coords: Point2DI32, backdrop: i8, object_index: u16, tile_index: u16) + -> AlphaTileBatchPrimitive { + AlphaTileBatchPrimitive { + tile_x_lo: (tile_coords.x() & 0xff) as u8, + tile_y_lo: (tile_coords.y() & 0xff) as u8, + tile_hi: (((tile_coords.x() >> 8) & 0x0f) | ((tile_coords.y() >> 4) & 0xf0)) as u8, + backdrop, + object_index, + tile_index, + } + } + + #[inline] + pub fn tile_coords(&self) -> Point2DI32 { + Point2DI32::new((self.tile_x_lo as i32) | (((self.tile_hi & 0xf) as i32) << 8), + (self.tile_y_lo as i32) | (((self.tile_hi & 0xf0) as i32) << 4)) + } +} + impl Debug for RenderCommand { fn fmt(&self, formatter: &mut Formatter) -> DebugResult { match *self { - RenderCommand::ClearMaskFramebuffer => write!(formatter, "ClearMaskFramebuffer"), - RenderCommand::Fill(ref fills) => write!(formatter, "Fill(x{})", fills.len()), + RenderCommand::AddFills(ref fills) => write!(formatter, "AddFills(x{})", fills.len()), + RenderCommand::FlushFills => write!(formatter, "FlushFills"), RenderCommand::AlphaTile(ref tiles) => { write!(formatter, "AlphaTile(x{})", tiles.len()) } diff --git a/renderer/src/tiles.rs b/renderer/src/tiles.rs index 50ff4042..6ab50893 100644 --- a/renderer/src/tiles.rs +++ b/renderer/src/tiles.rs @@ -8,9 +8,9 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use crate::gpu_data::BuiltObject; +use crate::builder::SceneBuilder; +use crate::gpu_data::{AlphaTileBatchPrimitive, BuiltObject}; use crate::sorted_vector::SortedVector; -use crate::z_buffer::ZBuffer; use pathfinder_geometry::basic::line_segment::LineSegmentF32; use pathfinder_geometry::basic::point::{Point2DF32, Point2DI32}; use pathfinder_geometry::basic::rect::{RectF32, RectI32}; @@ -25,29 +25,32 @@ const FLATTENING_TOLERANCE: f32 = 0.1; pub const TILE_WIDTH: u32 = 16; pub const TILE_HEIGHT: u32 = 16; -pub struct Tiler<'o, 'z> { - outline: &'o Outline, +pub(crate) struct Tiler<'a> { + builder: &'a SceneBuilder<'a>, + outline: &'a Outline, pub built_object: BuiltObject, object_index: u16, - z_buffer: &'z ZBuffer, point_queue: SortedVector, active_edges: SortedVector, old_active_edges: Vec, } -impl<'o, 'z> Tiler<'o, 'z> { +impl<'a> Tiler<'a> { #[allow(clippy::or_fun_call)] - pub fn new(outline: &'o Outline, view_box: RectF32, object_index: u16, z_buffer: &'z ZBuffer) - -> Tiler<'o, 'z> { + pub(crate) fn new(builder: &'a SceneBuilder<'a>, + outline: &'a Outline, + view_box: RectF32, + object_index: u16) + -> Tiler<'a> { let bounds = outline.bounds().intersection(view_box).unwrap_or(RectF32::default()); let built_object = BuiltObject::new(bounds); Tiler { + builder, outline, built_object, object_index, - z_buffer, point_queue: SortedVector::new(), active_edges: SortedVector::new(), @@ -55,7 +58,7 @@ impl<'o, 'z> Tiler<'o, 'z> { } } - pub fn generate_tiles(&mut self) { + pub(crate) fn generate_tiles(&mut self) { // Initialize the point queue. self.init_point_queue(); @@ -69,8 +72,8 @@ impl<'o, 'z> Tiler<'o, 'z> { self.generate_strip(strip_origin_y); } - // Cull. - self.cull(); + // Pack and cull. + self.pack_and_cull(); //println!("{:#?}", self.built_object); } @@ -93,12 +96,21 @@ impl<'o, 'z> Tiler<'o, 'z> { } } - fn cull(&self) { - for solid_tile_index in self.built_object.solid_tiles.ones() { - if self.built_object.tile_backdrops.data[solid_tile_index] != 0 { - let tile_coords = self.built_object.tile_index_to_coords(solid_tile_index as u32); - self.z_buffer.update(tile_coords, self.object_index); + fn pack_and_cull(&mut self) { + for (tile_index, tile) in self.built_object.tiles.data.iter().enumerate() { + let tile_coords = self.built_object.local_tile_index_to_coords(tile_index as u32); + if tile.is_solid() { + if tile.backdrop != 0 { + self.builder.z_buffer.update(tile_coords, self.object_index); + } + continue; } + + let alpha_tile = AlphaTileBatchPrimitive::new(tile_coords, + tile.backdrop, + self.object_index, + tile.alpha_tile_index as u16); + self.built_object.alpha_tiles.push(alpha_tile); } } @@ -150,7 +162,8 @@ impl<'o, 'z> Tiler<'o, 'z> { (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 = Point2DI32::new(current_tile_x, tile_y); - self.built_object.add_active_fill(current_x, + self.built_object.add_active_fill(self.builder, + current_x, tile_right_x, current_winding, current_tile_coords); @@ -163,8 +176,10 @@ impl<'o, 'z> Tiler<'o, 'z> { //println!("... emitting backdrop {} @ tile {}", current_winding, current_tile_x); let current_tile_coords = Point2DI32::new(current_tile_x, tile_y); if let Some(tile_index) = self.built_object - .tile_coords_to_index(current_tile_coords) { - self.built_object.tile_backdrops.data[tile_index as usize] = current_winding; + .tile_coords_to_local_index(current_tile_coords) { + // FIXME(pcwalton): Handle winding overflow. + self.built_object.tiles.data[tile_index as usize].backdrop = + current_winding as i8; } current_tile_x += 1; @@ -180,7 +195,8 @@ impl<'o, 'z> Tiler<'o, 'z> { let current_x = (i32::from(current_tile_x) * TILE_WIDTH as i32) as f32 + current_subtile_x; let current_tile_coords = Point2DI32::new(current_tile_x, tile_y); - self.built_object.add_active_fill(current_x, + self.built_object.add_active_fill(self.builder, + current_x, segment_x, current_winding, current_tile_coords); @@ -193,7 +209,7 @@ impl<'o, 'z> Tiler<'o, 'z> { // Process the edge. //println!("about to process existing active edge {:#?}", active_edge); debug_assert!(f32::abs(active_edge.crossing.y() - tile_top) < 0.1); - active_edge.process(&mut self.built_object, tile_y); + active_edge.process(self.builder, &mut self.built_object, tile_y); if !active_edge.segment.is_none() { self.active_edges.push(active_edge); } @@ -229,6 +245,7 @@ impl<'o, 'z> Tiler<'o, 'z> { contour, prev_endpoint_index, &mut self.active_edges, + self.builder, &mut self.built_object, tile_y, ); @@ -250,6 +267,7 @@ impl<'o, 'z> Tiler<'o, 'z> { contour, point_index.point(), &mut self.active_edges, + self.builder, &mut self.built_object, tile_y, ); @@ -302,12 +320,13 @@ fn process_active_segment( contour: &Contour, from_endpoint_index: u32, active_edges: &mut SortedVector, + builder: &SceneBuilder, built_object: &mut BuiltObject, tile_y: i32, ) { let mut active_edge = ActiveEdge::from_segment(&contour.segment_after(from_endpoint_index)); //println!("... process_active_segment({:#?})", active_edge); - active_edge.process(built_object, tile_y); + active_edge.process(builder, built_object, tile_y); if !active_edge.segment.is_none() { //println!("... ... pushing resulting active edge: {:#?}", active_edge); active_edges.push(active_edge); @@ -357,7 +376,7 @@ impl ActiveEdge { } } - fn process(&mut self, built_object: &mut BuiltObject, tile_y: i32) { + fn process(&mut self, builder: &SceneBuilder, built_object: &mut BuiltObject, tile_y: i32) { //let tile_bottom = ((i32::from(tile_y) + 1) * TILE_HEIGHT as i32) as f32; //println!("process_active_edge({:#?}, tile_y={}({}))", self, tile_y, tile_bottom); @@ -366,7 +385,10 @@ impl ActiveEdge { if segment.is_line() { let line_segment = segment.as_line_segment(); - self.segment = match self.process_line_segment(&line_segment, built_object, tile_y) { + self.segment = match self.process_line_segment(&line_segment, + builder, + built_object, + tile_y) { Some(lower_part) => Segment::line(&lower_part), None => Segment::none(), }; @@ -383,10 +405,8 @@ impl ActiveEdge { let first_line_segment = LineSegmentF32::new(&self.crossing, &segment.baseline.upper_point()) .orient(winding); - if self - .process_line_segment(&first_line_segment, built_object, tile_y) - .is_some() - { + if self.process_line_segment(&first_line_segment, builder, built_object, tile_y) + .is_some() { return; } } @@ -406,7 +426,8 @@ impl ActiveEdge { } /* - println!("... tile_y={} winding={} segment={:?} t={} before_segment={:?} after_segment={:?}", + println!("... tile_y={} winding={} segment={:?} t={} before_segment={:?} + after_segment={:?}", tile_y, winding, segment, @@ -416,7 +437,7 @@ impl ActiveEdge { */ let line = before_segment.baseline.orient(winding); - match self.process_line_segment(&line, built_object, tile_y) { + match self.process_line_segment(&line, builder, built_object, tile_y) { Some(ref lower_part) if split_t == 1.0 => { self.segment = Segment::line(&lower_part); return; @@ -437,6 +458,7 @@ impl ActiveEdge { fn process_line_segment( &mut self, line_segment: &LineSegmentF32, + builder: &SceneBuilder, built_object: &mut BuiltObject, tile_y: i32, ) -> Option { @@ -444,12 +466,12 @@ impl ActiveEdge { /*println!("process_line_segment({:?}, tile_y={}) tile_bottom={}", line_segment, tile_y, tile_bottom);*/ if line_segment.max_y() <= tile_bottom { - built_object.generate_fill_primitives_for_line(*line_segment, tile_y); + built_object.generate_fill_primitives_for_line(builder, *line_segment, tile_y); return None; } let (upper_part, lower_part) = line_segment.split_at_y(tile_bottom); - built_object.generate_fill_primitives_for_line(upper_part, tile_y); + built_object.generate_fill_primitives_for_line(builder, upper_part, tile_y); self.crossing = lower_part.upper_point(); Some(lower_part) } diff --git a/resources/shaders/tile_alpha_vertex.inc.glsl b/resources/shaders/tile_alpha_vertex.inc.glsl index a07231fa..c339f926 100644 --- a/resources/shaders/tile_alpha_vertex.inc.glsl +++ b/resources/shaders/tile_alpha_vertex.inc.glsl @@ -14,9 +14,10 @@ uniform vec2 uStencilTextureSize; uniform vec2 uViewBoxOrigin; in vec2 aTessCoord; -in vec2 aTileOrigin; +in uvec3 aTileOrigin; in int aBackdrop; in uint aObject; +in uint aTileIndex; out vec2 vTexCoord; out float vBackdrop; @@ -31,10 +32,10 @@ vec2 computeTileOffset(uint tileIndex, float stencilTextureWidth) { } void computeVaryings() { - uint tileIndex = uint(gl_InstanceID); - vec2 pixelPosition = (aTileOrigin + aTessCoord) * uTileSize + uViewBoxOrigin; + vec2 origin = vec2(aTileOrigin.xy) + vec2(aTileOrigin.z & 15u, aTileOrigin.z >> 4u) * 256.0; + vec2 pixelPosition = (origin + aTessCoord) * uTileSize + uViewBoxOrigin; vec2 position = (pixelPosition / uFramebufferSize * 2.0 - 1.0) * vec2(1.0, -1.0); - vec2 texCoord = computeTileOffset(tileIndex, uStencilTextureSize.x) + aTessCoord * uTileSize; + vec2 texCoord = computeTileOffset(aTileIndex, uStencilTextureSize.x) + aTessCoord * uTileSize; vTexCoord = texCoord / uStencilTextureSize; vBackdrop = float(aBackdrop); diff --git a/ui/src/lib.rs b/ui/src/lib.rs index f1e92aa4..0582da3d 100644 --- a/ui/src/lib.rs +++ b/ui/src/lib.rs @@ -21,8 +21,8 @@ use pathfinder_geometry::basic::point::{Point2DF32, Point2DI32}; use pathfinder_geometry::basic::rect::RectI32; use pathfinder_geometry::color::ColorU; use pathfinder_gpu::resources::ResourceLoader; -use pathfinder_gpu::{BlendState, BufferTarget, BufferUploadMode, Device, Primitive, RenderState}; -use pathfinder_gpu::{UniformData, VertexAttrType}; +use pathfinder_gpu::{BlendState, BufferData, BufferTarget, BufferUploadMode, Device, Primitive}; +use pathfinder_gpu::{RenderState, UniformData, VertexAttrType}; use pathfinder_simd::default::F32x4; use serde_json; use std::mem; @@ -160,14 +160,14 @@ impl UI where D: Device { filled: bool) { device.bind_vertex_array(&self.solid_vertex_array.vertex_array); - device.upload_to_buffer(&self.solid_vertex_array.vertex_buffer, - vertex_data, - BufferTarget::Vertex, - BufferUploadMode::Dynamic); - device.upload_to_buffer(&self.solid_vertex_array.index_buffer, - index_data, - BufferTarget::Index, - BufferUploadMode::Dynamic); + device.allocate_buffer(&self.solid_vertex_array.vertex_buffer, + BufferData::Memory(vertex_data), + BufferTarget::Vertex, + BufferUploadMode::Dynamic); + device.allocate_buffer(&self.solid_vertex_array.index_buffer, + BufferData::Memory(index_data), + BufferTarget::Index, + BufferUploadMode::Dynamic); device.use_program(&self.solid_program.program); device.set_uniform(&self.solid_program.framebuffer_size_uniform, @@ -385,14 +385,14 @@ impl UI where D: Device { index_data: &[u32], texture: &D::Texture, color: ColorU) { - device.upload_to_buffer(&self.texture_vertex_array.vertex_buffer, - vertex_data, - BufferTarget::Vertex, - BufferUploadMode::Dynamic); - device.upload_to_buffer(&self.texture_vertex_array.index_buffer, - index_data, - BufferTarget::Index, - BufferUploadMode::Dynamic); + device.allocate_buffer(&self.texture_vertex_array.vertex_buffer, + BufferData::Memory(vertex_data), + BufferTarget::Vertex, + BufferUploadMode::Dynamic); + device.allocate_buffer(&self.texture_vertex_array.index_buffer, + BufferData::Memory(index_data), + BufferTarget::Index, + BufferUploadMode::Dynamic); device.bind_vertex_array(&self.texture_vertex_array.vertex_array); device.use_program(&self.texture_program.program);