diff --git a/res/mask.fs.glsl b/res/mask.fs.glsl new file mode 100644 index 0000000000..34d631c0a1 --- /dev/null +++ b/res/mask.fs.glsl @@ -0,0 +1,4 @@ +void main(void) +{ + SetFragColor(vColor); +} diff --git a/res/mask.vs.glsl b/res/mask.vs.glsl new file mode 100644 index 0000000000..b426b8814c --- /dev/null +++ b/res/mask.vs.glsl @@ -0,0 +1,5 @@ +void main(void) +{ + vColor = aColorRectTL / 255.0; + gl_Position = uTransform * vec4(aPosition, 1.0); +} diff --git a/src/batch.rs b/src/batch.rs index 7756f1a72c..d3ba34c090 100644 --- a/src/batch.rs +++ b/src/batch.rs @@ -253,10 +253,6 @@ impl<'a> BatchBuilder<'a> { self.current_matrix_index += 1; } - pub fn clip_in_rect(&self) -> Rect { - self.cached_clip_in_rect.unwrap_or(MAX_RECT) - } - // TODO(gw): This is really inefficient to call this every push/pop... fn update_clip_in_rect(&mut self) { self.cached_clip_in_rect = Some(MAX_RECT); diff --git a/src/frame.rs b/src/frame.rs index 787169aeb4..a151d4ba8c 100644 --- a/src/frame.rs +++ b/src/frame.rs @@ -6,7 +6,7 @@ use fnv::FnvHasher; use geometry::ray_intersects_rect; use internal_types::{AxisDirection, LowLevelFilterOp, CompositionOp, DrawListItemIndex}; use internal_types::{BatchUpdateList, ChildLayerIndex, DrawListId}; -use internal_types::{CompositeBatchInfo, CompositeBatchJob}; +use internal_types::{CompositeBatchInfo, CompositeBatchJob, MaskRegion}; use internal_types::{RendererFrame, StackingContextInfo, BatchInfo, DrawCall, StackingContextIndex}; use internal_types::{ANGLE_FLOAT_TO_FIXED, MAX_RECT, BatchUpdate, BatchUpdateOp, DrawLayer}; use internal_types::{DrawCommand, ClearInfo, RenderTargetId, DrawListGroupId}; @@ -225,26 +225,25 @@ impl RenderTarget { }); if let Some(batch_list) = batch_list { + let mut region = MaskRegion::new(); + let vertex_buffer_id = compiled_node.vertex_buffer_id.unwrap(); - let scroll_clip_rect = Rect::new(-layer.scroll_offset, - layer.viewport_size); + // Mask out anything outside this AABB tree node. + // This is a requirement to ensure paint order is correctly + // maintained since the batches are built in parallel. + region.add_mask(node.split_rect, layer.world_transform); + + // Mask out anything outside this viewport. This is used + // for things like clipping content that is outside a + // transformed iframe. + region.add_mask(Rect::new(layer.world_origin, layer.viewport_size), + layer.local_transform); for batch in &batch_list.batches { - let mut clip_rects = batch.clip_rects.clone(); - - // Intersect all local clips for this layer with the viewport - // size. This clips out content outside iframes, scroll layers etc. - for clip_rect in &mut clip_rects { - *clip_rect = match clip_rect.intersection(&scroll_clip_rect) { - Some(clip_rect) => clip_rect, - None => Rect::new(Point2D::zero(), Size2D::zero()), - }; - } - - batch_info.draw_calls.push(DrawCall { + region.draw_calls.push(DrawCall { tile_params: batch.tile_params.clone(), // TODO(gw): Move this instead? - clip_rects: clip_rects, + clip_rects: batch.clip_rects.clone(), vertex_buffer_id: vertex_buffer_id, color_texture_id: batch.color_texture_id, mask_texture_id: batch.mask_texture_id, @@ -252,6 +251,8 @@ impl RenderTarget { instance_count: batch.instance_count, }); } + + batch_info.regions.push(region); } } } diff --git a/src/internal_types.rs b/src/internal_types.rs index 8088db4c38..fff1f1d8d2 100644 --- a/src/internal_types.rs +++ b/src/internal_types.rs @@ -472,11 +472,48 @@ pub struct DrawCall { pub instance_count: u32, } +#[derive(Clone, Debug)] +pub struct OutputMask { + pub rect: Rect, + pub transform: Matrix4, +} + +impl OutputMask { + pub fn new(rect: Rect, + transform: Matrix4) -> OutputMask { + OutputMask { + rect: rect, + transform: transform, + } + } +} + +#[derive(Clone, Debug)] +pub struct MaskRegion { + pub masks: Vec, + pub draw_calls: Vec, +} + +impl MaskRegion { + pub fn new() -> MaskRegion { + MaskRegion { + masks: Vec::new(), + draw_calls: Vec::new(), + } + } + + pub fn add_mask(&mut self, + rect: Rect, + transform: Matrix4) { + self.masks.push(OutputMask::new(rect, transform)); + } +} + #[derive(Clone, Debug)] pub struct BatchInfo { pub matrix_palette: Vec, pub offset_palette: Vec, - pub draw_calls: Vec, + pub regions: Vec, } impl BatchInfo { @@ -485,7 +522,7 @@ impl BatchInfo { BatchInfo { matrix_palette: matrix_palette, offset_palette: offset_palette, - draw_calls: Vec::new(), + regions: Vec::new(), } } } diff --git a/src/node_compiler.rs b/src/node_compiler.rs index ee01504869..c73e7242e2 100644 --- a/src/node_compiler.rs +++ b/src/node_compiler.rs @@ -55,11 +55,6 @@ impl NodeCompiler for AABBTreeNode { let display_item = &draw_list.items[index as usize]; let clip_rect = display_item.clip.main.intersection(&context.local_clip_rect); - let clip_rect = clip_rect.and_then(|clip_rect| { - let split_rect_local_space = self.split_rect - .translate(&-context.offset_from_layer); - clip_rect.intersection(&split_rect_local_space) - }); if let Some(ref clip_rect) = clip_rect { builder.push_clip_in_rect(clip_rect); diff --git a/src/renderer.rs b/src/renderer.rs index 60522b74cb..603f9ca0ea 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -28,7 +28,7 @@ use time::precise_time_ns; use webrender_traits::{ColorF, Epoch, PipelineId, RenderNotifier}; use webrender_traits::{ImageFormat, MixBlendMode, RenderApiSender}; use offscreen_gl_context::{NativeGLContext, NativeGLContextMethods}; -//use util; +use util::RectHelpers; pub const BLUR_INFLATION_FACTOR: u32 = 3; pub const MAX_RASTER_OP_SIZE: u32 = 2048; @@ -135,6 +135,8 @@ pub struct Renderer { blur_program_id: ProgramId, u_direction: UniformLocation, + mask_program_id: ProgramId, + notifier: Arc>>>, enable_profiler: bool, @@ -175,6 +177,7 @@ impl Renderer { let filter_program_id = device.create_program("filter"); let box_shadow_program_id = device.create_program("box_shadow"); let blur_program_id = device.create_program("blur"); + let mask_program_id = device.create_program("mask"); let max_raster_op_size = MAX_RASTER_OP_SIZE * options.device_pixel_ratio as u32; let texture_ids = device.create_texture_ids(1024); @@ -274,6 +277,7 @@ impl Renderer { blit_program_id: blit_program_id, box_shadow_program_id: box_shadow_program_id, blur_program_id: blur_program_id, + mask_program_id: mask_program_id, u_blend_params: UniformLocation::invalid(), u_filter_params: UniformLocation::invalid(), u_direction: UniformLocation::invalid(), @@ -1134,61 +1138,154 @@ impl Renderer { self.device.set_uniform_mat4_array(self.u_quad_transform_array, &info.matrix_palette); - for draw_call in &info.draw_calls { - let vao_id = self.get_or_create_similar_vao_with_offset( - draw_call.vertex_buffer_id, - VertexFormat::Rectangles, - draw_call.first_instance); - self.device.bind_vao(vao_id); + // Render any masks to the stencil buffer. + for region in &info.regions { + let layer_rect = Rect::new(layer.origin, layer.size); + let mut valid_mask_count = 0; + for mask in ®ion.masks { + + // If the mask is larger than the viewport / scissor rect + // then there is no need to draw it. + if mask.transform == Matrix4::identity() && + mask.rect.contains_rect(&layer_rect) { + continue; + } - if draw_call.tile_params.len() > 0 { - // TODO(gw): Avoid alloc here... - let mut floats = Vec::new(); - for vec in &draw_call.tile_params { - floats.push(vec.u0); - floats.push(vec.v0); - floats.push(vec.u_size); - floats.push(vec.v_size); + // First time we find a valid mask, clear stencil and setup render states + if valid_mask_count == 0 { + gl::clear(gl::STENCIL_BUFFER_BIT); + gl::enable(gl::STENCIL_TEST); + gl::color_mask(false, false, false, false); + gl::depth_mask(false); + gl::stencil_mask(0xff); + gl::stencil_func(gl::ALWAYS, 1, 0xff); + gl::stencil_op(gl::KEEP, gl::INCR, gl::INCR) } - self.device.set_uniform_vec4_array(self.u_tile_params, - &floats); + // TODO(gw): The below is a copy pasta and can be trivially optimized. + let (mut indices, mut vertices) = (vec![], vec![]); + indices.push(0); + indices.push(1); + indices.push(2); + indices.push(2); + indices.push(3); + indices.push(1); + + let color = ColorF::new(0.0, 0.0, 0.0, 0.0); + + let x0 = mask.rect.origin.x; + let y0 = mask.rect.origin.y; + let x1 = x0 + mask.rect.size.width; + let y1 = y0 + mask.rect.size.height; + + vertices.extend_from_slice(&[ + PackedVertex::from_components( + x0, y0, + &color, + 0.0, 0.0, + 0.0, 0.0), + PackedVertex::from_components( + x1, y0, + &color, + 1.0, 0.0, + 1.0, 0.0), + PackedVertex::from_components( + x0, y1, + &color, + 0.0, 1.0, + 0.0, 1.0), + PackedVertex::from_components( + x1, y1, + &color, + 1.0, 1.0, + 1.0, 1.0), + ]); + + let wvp = projection.mul(&mask.transform); + self.device.bind_program(self.mask_program_id, &wvp); + + draw_simple_triangles(&mut self.device, + &mut self.profile_counters, + &indices[..], + &vertices[..], + TextureId(0)); + + valid_mask_count += 1; + } + + // If any masks were found, enable stencil test rejection. + // TODO(gw): This may be faster to switch the logic and + // rely on sfail! + if valid_mask_count > 0 { + gl::stencil_op(gl::KEEP, gl::KEEP, gl::KEEP); + gl::stencil_func(gl::EQUAL, valid_mask_count, 0xff); + gl::color_mask(true, true, true, true); + gl::depth_mask(true); + self.device.bind_program(self.quad_program_id, + &projection); } - if draw_call.clip_rects.len() > 0 { - // TODO(gw): Avoid alloc here... - let mut floats = Vec::new(); - for rect in &draw_call.clip_rects { - floats.push(rect.origin.x); - floats.push(rect.origin.y); - floats.push(rect.origin.x + rect.size.width); - floats.push(rect.origin.y + rect.size.height); + for draw_call in ®ion.draw_calls { + let vao_id = self.get_or_create_similar_vao_with_offset( + draw_call.vertex_buffer_id, + VertexFormat::Rectangles, + draw_call.first_instance); + self.device.bind_vao(vao_id); + + if draw_call.tile_params.len() > 0 { + // TODO(gw): Avoid alloc here... + let mut floats = Vec::new(); + for vec in &draw_call.tile_params { + floats.push(vec.u0); + floats.push(vec.v0); + floats.push(vec.u_size); + floats.push(vec.v_size); + } + + self.device.set_uniform_vec4_array(self.u_tile_params, + &floats); } - self.device.set_uniform_vec4_array(self.u_clip_rects, - &floats); + if draw_call.clip_rects.len() > 0 { + // TODO(gw): Avoid alloc here... + let mut floats = Vec::new(); + for rect in &draw_call.clip_rects { + floats.push(rect.origin.x); + floats.push(rect.origin.y); + floats.push(rect.origin.x + rect.size.width); + floats.push(rect.origin.y + rect.size.height); + } + + self.device.set_uniform_vec4_array(self.u_clip_rects, + &floats); + } + + self.device.bind_mask_texture(draw_call.mask_texture_id); + self.device.bind_color_texture(draw_call.color_texture_id); + + // TODO(gw): Although a minor cost, this is an extra hashtable lookup for every + // draw call, when the batch textures are (almost) always the same. + // This could probably be cached or provided elsewhere. + let color_size = self.device + .get_texture_dimensions(draw_call.color_texture_id); + let mask_size = self.device + .get_texture_dimensions(draw_call.mask_texture_id); + self.device.set_uniform_4f(self.u_atlas_params, + color_size.0 as f32, + color_size.1 as f32, + mask_size.0 as f32, + mask_size.1 as f32); + + self.profile_counters.draw_calls.inc(); + + self.device + .draw_triangles_instanced_u16(0, 6, draw_call.instance_count as i32); } - self.device.bind_mask_texture(draw_call.mask_texture_id); - self.device.bind_color_texture(draw_call.color_texture_id); - - // TODO(gw): Although a minor cost, this is an extra hashtable lookup for every - // draw call, when the batch textures are (almost) always the same. - // This could probably be cached or provided elsewhere. - let color_size = self.device - .get_texture_dimensions(draw_call.color_texture_id); - let mask_size = self.device - .get_texture_dimensions(draw_call.mask_texture_id); - self.device.set_uniform_4f(self.u_atlas_params, - color_size.0 as f32, - color_size.1 as f32, - mask_size.0 as f32, - mask_size.1 as f32); - - self.profile_counters.draw_calls.inc(); - - self.device - .draw_triangles_instanced_u16(0, 6, draw_call.instance_count as i32); + // Disable stencil test if it was used + if valid_mask_count > 0 { + gl::disable(gl::STENCIL_TEST); + } } } &DrawCommand::CompositeBatch(ref info) => { diff --git a/src/util.rs b/src/util.rs index 4625393e16..3b176f0867 100644 --- a/src/util.rs +++ b/src/util.rs @@ -64,6 +64,19 @@ impl MatrixHelpers for Matrix4 { } } +pub trait RectHelpers { + fn contains_rect(&self, other: &Rect) -> bool; +} + +impl RectHelpers for Rect { + fn contains_rect(&self, other: &Rect) -> bool { + self.origin.x <= other.origin.x && + self.origin.y <= other.origin.y && + self.max_x() >= other.max_x() && + self.max_y() >= other.max_y() + } +} + pub fn lerp(a: f32, b: f32, t: f32) -> f32 { (b - a) * t + a }