From a6bf439abc8f4e24ec5418bdfc838e94a23a32db Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Fri, 20 Nov 2015 15:50:39 -0800 Subject: [PATCH] Batch similar composite operations together. Known bugs and limitations: * Only 32 composite operations can be batched, because that is the maximum size of the matrix palette. * Nested composite operations aren't correctly handled at the moment. They use the wrong device pixel ratio and read from and write to the same texture, which is undefined behavior (although usually works). * When a large number (>700 in my tests) of composite operations occur, there can be stray triangles drawn. I haven't been able to pin down why that occurs yet. --- res/blit.fs.glsl | 2 +- res/box_shadow.fs.glsl | 2 +- res/clear.fs.glsl | 4 + res/clear.vs.glsl | 5 + src/batch.rs | 65 ++++++- src/device.rs | 27 +-- src/frame.rs | 307 +++++++++++++++++++++++---------- src/internal_types.rs | 83 +++++++-- src/node_compiler.rs | 12 +- src/render_backend.rs | 9 +- src/renderer.rs | 379 ++++++++++++++++++++++++++++++----------- src/resource_cache.rs | 29 +++- src/texture_cache.rs | 112 ++++++------ 13 files changed, 748 insertions(+), 288 deletions(-) create mode 100644 res/clear.fs.glsl create mode 100644 res/clear.vs.glsl diff --git a/res/blit.fs.glsl b/res/blit.fs.glsl index 9e9c35ee9c..021b835065 100644 --- a/res/blit.fs.glsl +++ b/res/blit.fs.glsl @@ -1,5 +1,5 @@ void main(void) { - vec4 diffuse = Texture(sDiffuse, vColorTexCoord.xy); + vec4 diffuse = Texture(sDiffuse, vColorTexCoord); SetFragColor(diffuse * vColor); } diff --git a/res/box_shadow.fs.glsl b/res/box_shadow.fs.glsl index 8d28fd7289..9d016e0945 100644 --- a/res/box_shadow.fs.glsl +++ b/res/box_shadow.fs.glsl @@ -19,7 +19,7 @@ void main(void) float length; float value; vec2 position = vPosition - vBorderPosition.zw; - if (vBorderRadii.y == 0.0) { + if (vBorderRadii.z == 0.0) { length = range; value = position.x; } else { diff --git a/res/clear.fs.glsl b/res/clear.fs.glsl new file mode 100644 index 0000000000..34d631c0a1 --- /dev/null +++ b/res/clear.fs.glsl @@ -0,0 +1,4 @@ +void main(void) +{ + SetFragColor(vColor); +} diff --git a/res/clear.vs.glsl b/res/clear.vs.glsl new file mode 100644 index 0000000000..2220f968cc --- /dev/null +++ b/res/clear.vs.glsl @@ -0,0 +1,5 @@ +void main(void) +{ + vColor = aColor / 255.0; + gl_Position = uTransform * vec4(aPosition, 1.0); +} diff --git a/src/batch.rs b/src/batch.rs index d51b572f8c..c1226aad38 100644 --- a/src/batch.rs +++ b/src/batch.rs @@ -1,5 +1,10 @@ use device::{ProgramId, TextureId}; -use internal_types::{AxisDirection, PackedVertex, PackedVertexForTextureCacheUpdate, Primitive}; +use fnv::FnvHasher; +use internal_types::{AxisDirection, CompositeBatchInfo, CompositeInfo, CompositionOp}; +use internal_types::{PackedVertex, PackedVertexForTextureCacheUpdate, Primitive}; +use std::collections::HashMap; +use std::collections::hash_map::Entry; +use std::collections::hash_state::DefaultState; use std::sync::atomic::Ordering::SeqCst; use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT}; @@ -133,6 +138,13 @@ impl<'a> BatchBuilder<'a> { primitive: Primitive, vertices: &mut [PackedVertex], tile_params: Option) { + let index_count = match primitive { + Primitive::Rectangles | Primitive::Glyphs => { + (vertices.len() / 4 * 6) as u16 + } + Primitive::Triangles => vertices.len() as u16, + Primitive::TriangleFan => ((vertices.len() - 2) * 3) as u16, + }; let need_new_batch = match self.batches.last_mut() { Some(batch) => { @@ -153,28 +165,26 @@ impl<'a> BatchBuilder<'a> { self.vertex_buffer.indices.len() as u16)); } - let mut index_count = 0; - match primitive { Primitive::Rectangles | Primitive::Glyphs => { for i in (0..vertices.len()).step_by(4) { let index_base = (index_offset + i) as u16; + debug_assert!(index_base as usize == index_offset + i); self.vertex_buffer.indices.push(index_base + 0); self.vertex_buffer.indices.push(index_base + 1); self.vertex_buffer.indices.push(index_base + 2); self.vertex_buffer.indices.push(index_base + 2); self.vertex_buffer.indices.push(index_base + 3); self.vertex_buffer.indices.push(index_base + 1); - index_count += 6; } } Primitive::Triangles => { for i in (0..vertices.len()).step_by(3) { let index_base = (index_offset + i) as u16; + debug_assert!(index_base as usize == index_offset + i); self.vertex_buffer.indices.push(index_base + 0); self.vertex_buffer.indices.push(index_base + 1); self.vertex_buffer.indices.push(index_base + 2); - index_count += 3; } } Primitive::TriangleFan => { @@ -182,7 +192,6 @@ impl<'a> BatchBuilder<'a> { self.vertex_buffer.indices.push(index_offset as u16); // center vertex self.vertex_buffer.indices.push((index_offset + i + 0) as u16); self.vertex_buffer.indices.push((index_offset + i + 1) as u16); - index_count += 3; } } } @@ -271,3 +280,47 @@ impl RasterBatch { self.vertices.push_all(vertices); } } + +/// A batch builder for composition operations. +#[derive(Debug)] +pub struct CompositeBatchBuilder { + batches: HashMap>, +} + +impl CompositeBatchBuilder { + pub fn new() -> CompositeBatchBuilder { + CompositeBatchBuilder { + batches: HashMap::with_hash_state(Default::default()), + } + } + + pub fn add(&mut self, operation: &CompositionOp, job: &CompositeInfo) { + let key = CompositeBatchKey { + operation: (*operation).clone(), + texture_id: job.render_target_texture.texture_id, + }; + match self.batches.entry(key) { + Entry::Vacant(entry) => { + entry.insert(CompositeBatchInfo { + operation: (*operation).clone(), + texture_id: job.render_target_texture.texture_id, + jobs: vec![(*job).clone()], + }); + } + Entry::Occupied(entry) => entry.into_mut().jobs.push((*job).clone()), + } + } + + /// FIXME(pcwalton): Very inefficient. + pub fn batches(&self) -> Vec { + self.batches.iter().map(|(_, batch)| (*batch).clone()).collect() + } +} + +/// The key we use for sorting composition operations into batches. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct CompositeBatchKey { + pub operation: CompositionOp, + pub texture_id: TextureId, +} + diff --git a/src/device.rs b/src/device.rs index 1fa51f6d18..57fd220576 100644 --- a/src/device.rs +++ b/src/device.rs @@ -723,6 +723,7 @@ impl Device { } } + #[allow(dead_code)] pub fn deinit_texture(&mut self, texture_id: TextureId) { debug_assert!(self.inside_frame); @@ -906,13 +907,13 @@ impl Device { data); } - pub fn update_texture(&mut self, - texture_id: TextureId, - x0: u32, - y0: u32, - width: u32, - height: u32, - data: &[u8]) { + fn update_texture(&mut self, + texture_id: TextureId, + x0: u32, + y0: u32, + width: u32, + height: u32, + data: &[u8]) { debug_assert!(self.inside_frame); let (gl_format, bpp) = match self.textures.get(&texture_id).unwrap().format { @@ -945,8 +946,8 @@ impl Device { fn read_framebuffer_rect_for_2d_texture(&mut self, texture_id: TextureId, - x: u32, y: u32, - width: u32, height: u32) { + x: i32, y: i32, + width: i32, height: i32) { self.bind_color_texture(texture_id); gl::copy_tex_sub_image_2d(gl::TEXTURE_2D, 0, @@ -958,10 +959,10 @@ impl Device { pub fn read_framebuffer_rect(&mut self, texture_id: TextureId, - x: u32, - y: u32, - width: u32, - height: u32) { + x: i32, + y: i32, + width: i32, + height: i32) { self.read_framebuffer_rect_for_2d_texture(texture_id, x, y, width, height) } diff --git a/src/frame.rs b/src/frame.rs index 7a7e17c122..efacc65855 100644 --- a/src/frame.rs +++ b/src/frame.rs @@ -1,13 +1,14 @@ use app_units::Au; -use batch::MAX_MATRICES_PER_BATCH; +use batch::{CompositeBatchBuilder, MAX_MATRICES_PER_BATCH}; use device::{TextureId}; use euclid::{Rect, Point2D, Size2D, Matrix4}; use fnv::FnvHasher; use internal_types::{AxisDirection, LowLevelFilterOp, CompositionOp, DrawListItemIndex}; -use internal_types::{BatchUpdateList, DrawListId}; +use internal_types::{BatchUpdateList, CompositeBatchInfo, DrawListId}; use internal_types::{RendererFrame, DrawListContext, BatchInfo, DrawCall}; -use internal_types::{BatchUpdate, BatchUpdateOp, DrawLayer}; -use internal_types::{DrawCommand, ClearInfo, CompositeInfo, ANGLE_FLOAT_TO_FIXED}; +use internal_types::{ANGLE_FLOAT_TO_FIXED, BatchUpdate, BatchUpdateOp, DrawLayer}; +use internal_types::{DrawCommand, ClearInfo, CompositeInfo, FrameRenderTarget}; +use internal_types::{RenderTargetTexture}; use layer::Layer; use node_compiler::NodeCompiler; use resource_cache::ResourceCache; @@ -23,6 +24,9 @@ use util::MatrixHelpers; use webrender_traits::{PipelineId, Epoch, ScrollPolicy, ScrollLayerId, StackingContext}; use webrender_traits::{FilterOp, ImageFormat, MixBlendMode, StackingLevel}; +#[derive(Clone, Copy, Debug, Ord, PartialOrd, PartialEq, Eq, Hash)] +pub struct RenderTargetGroupIndex(pub u32); + #[derive(Clone, Copy, Debug, Ord, PartialOrd, PartialEq, Eq, Hash)] pub struct RenderTargetIndex(pub u32); @@ -35,29 +39,65 @@ pub struct DrawListBatchInfo { #[derive(Debug)] pub enum FrameRenderItem { Clear(ClearInfo), - Composite(CompositeInfo), + CompositeBatch(CompositeBatchInfo), DrawListBatch(DrawListBatchInfo), } #[derive(Debug)] -pub struct FrameRenderTarget { - pub size: Size2D, - pub texture_id: Option, +pub struct FrameRenderTargetGroup { + pub composite_batch_builder: CompositeBatchBuilder, pub items: Vec, + render_targets: Vec, + render_target_stack: Vec, + texture_id: Option, current_batch: Option, } -impl FrameRenderTarget { - pub fn new(size: Size2D, - texture_id: Option) -> FrameRenderTarget { - FrameRenderTarget { - size: size, - items: Vec::new(), - texture_id: texture_id, +impl FrameRenderTargetGroup { + pub fn new() -> FrameRenderTargetGroup { + FrameRenderTargetGroup { + render_targets: vec![], + render_target_stack: vec![], + composite_batch_builder: CompositeBatchBuilder::new(), + items: vec![], + texture_id: None, current_batch: None, } } + fn can_add_render_target(&self, render_target: &FrameRenderTarget) -> bool { + if self.render_targets.is_empty() { + debug_assert!(self.texture_id.is_none()); + return true + } + + match (render_target.texture, self.texture_id) { + (None, None) => true, + (Some(ref new_texture), Some(this_texture_id)) => { + new_texture.texture_id == this_texture_id + } + (None, Some(_)) | (Some(_), None) => false, + } + } + + fn push_render_target(&mut self, render_target: FrameRenderTarget) { + debug_assert!(self.can_add_render_target(&render_target)); + self.texture_id = render_target.texture.map(|texture| texture.texture_id); + self.render_target_stack.push(RenderTargetIndex(self.render_targets.len() as u32)); + self.render_targets.push(render_target); + } + + /// Returns true if more render targets remain after popping the last one or false if this + /// render target group is now empty. + fn pop_render_target(&mut self) -> bool { + self.render_target_stack.pop(); + !self.render_target_stack.is_empty() + } + + fn current_render_target(&self) -> FrameRenderTarget { + self.render_targets[self.render_target_stack.last().unwrap().0 as usize].clone() + } + fn finalize(&mut self) { self.flush(); } @@ -66,6 +106,9 @@ impl FrameRenderTarget { if let Some(batch) = self.current_batch.take() { self.items.push(FrameRenderItem::DrawListBatch(batch)); } + for batch in self.composite_batch_builder.batches() { + self.items.push(FrameRenderItem::CompositeBatch(batch)); + } } fn push_clear(&mut self, clear_info: ClearInfo) { @@ -73,14 +116,11 @@ impl FrameRenderTarget { self.items.push(FrameRenderItem::Clear(clear_info)); } - fn push_composite(&mut self, composite_info: CompositeInfo) { - self.flush(); - self.items.push(FrameRenderItem::Composite(composite_info)); + fn push_composite(&mut self, operation: &CompositionOp, job: &CompositeInfo) { + self.composite_batch_builder.add(operation, job) } - fn push_draw_list(&mut self, - draw_list_id: DrawListId, - scroll_layer_id: ScrollLayerId) { + fn push_draw_list(&mut self, draw_list_id: DrawListId, scroll_layer_id: ScrollLayerId) { let need_new_batch = match self.current_batch { Some(ref batch) => { batch.scroll_layer_id != scroll_layer_id || @@ -107,8 +147,9 @@ impl FrameRenderTarget { pub struct Frame { pub layers: HashMap>, pub pipeline_epoch_map: HashMap>, - pub render_targets: Vec, - pub render_target_stack: Vec, + pub render_target_groups: Vec, + pub render_target_group_stack: Vec, + pub last_render_target_group: Option, pub pending_updates: BatchUpdateList, } @@ -255,21 +296,24 @@ impl Frame { Frame { layers: HashMap::with_hash_state(Default::default()), pipeline_epoch_map: HashMap::with_hash_state(Default::default()), - render_targets: Vec::new(), - render_target_stack: Vec::new(), + render_target_groups: Vec::new(), + render_target_group_stack: Vec::new(), + last_render_target_group: None, pending_updates: BatchUpdateList::new(), } } - pub fn reset(&mut self, - resource_cache: &mut ResourceCache) -> HashMap> { + pub fn reset(&mut self, resource_cache: &mut ResourceCache) + -> HashMap> { self.pipeline_epoch_map.clear(); // Free any render targets from last frame. // TODO: This should really re-use existing targets here... - for render_target in self.render_targets.drain(..) { - if let Some(texture_id) = render_target.texture_id { - resource_cache.free_render_target(texture_id); + for render_target_group in self.render_target_groups.drain(..) { + for render_target in render_target_group.render_targets.into_iter() { + if let Some(texture) = render_target.texture { + resource_cache.free_render_target(texture); + } } } @@ -302,6 +346,7 @@ impl Frame { pub fn create(&mut self, scene: &Scene, viewport_size: Size2D, + device_pixel_ratio: f32, resource_cache: &mut ResourceCache, pipeline_sizes: &mut HashMap>) { if let Some(root_pipeline_id) = scene.root_pipeline_id { @@ -316,7 +361,7 @@ impl Frame { .scroll_layer_id .expect("root layer must be a scroll layer!"); - debug_assert!(self.render_target_stack.len() == 0); + debug_assert!(self.render_target_group_stack.len() == 0); self.push_render_target(viewport_size, None); self.flatten(SceneItemKind::Pipeline(root_pipeline), &Point2D::zero(), @@ -328,9 +373,12 @@ impl Frame { scene, &old_layers, &root_stacking_context.stacking_context.overflow, - pipeline_sizes); + pipeline_sizes, + device_pixel_ratio); + self.pop_render_target(); - debug_assert!(self.render_target_stack.len() == 0); + self.finalize_last_render_target_group(); + debug_assert!(self.render_target_group_stack.len() == 0); // TODO(gw): This should be moved elsewhere! if let Some(root_scroll_layer) = self.layers.get_mut(&root_scroll_layer_id) { @@ -355,7 +403,8 @@ impl Frame { scene: &Scene, old_layers: &HashMap>, scene_rect: &Rect, - pipeline_sizes: &mut HashMap>) { + pipeline_sizes: &mut HashMap>, + device_pixel_ratio: f32) { let _pf = util::ProfileScope::new(" flatten"); let stacking_context = match item_kind { @@ -464,23 +513,29 @@ impl Frame { } } + let transform_in_scene_space = final_transform; for composition_operation in composition_operations.iter() { - let size = Size2D::new(stacking_context.overflow.size.width as u32, - stacking_context.overflow.size.height as u32); + let size = Size2D::new(stacking_context.overflow.size.width as i32, + stacking_context.overflow.size.height as i32); // TODO(gw): Get composition ops working with transforms - let origin = Point2D::new(child_offset.x as u32, child_offset.y as u32); + let origin = Point2D::new(child_offset.x as i32, child_offset.y as i32); + + let x = origin.x as f32; + let y = origin.y as f32; + let origin = Point2D::new(x as i32, y as i32); - let texture_id = resource_cache.allocate_render_target(size.width, - size.height, - ImageFormat::RGBA8); + let texture = resource_cache.allocate_render_target( + (size.width as f32 * device_pixel_ratio) as u32, + (size.height as f32 * device_pixel_ratio) as u32, + ImageFormat::RGBA8); - self.push_composite(CompositeInfo { - operation: *composition_operation, - color_texture_id: texture_id, + self.push_composite(composition_operation, &CompositeInfo { rect: Rect::new(origin, size), + render_target_texture: texture, }); - self.push_render_target(size, Some(texture_id)); + self.push_render_target(Size2D::new(size.width as u32, size.height as u32), + Some(texture)); final_transform = Matrix4::identity(); } @@ -491,6 +546,8 @@ impl Frame { SpecificSceneItem::DrawList(draw_list_id) => { self.push_draw_list(draw_list_id, this_scroll_layer); + let current_render_target = self.current_render_target_group() + .current_render_target(); let layer = match self.layers.entry(this_scroll_layer) { Occupied(entry) => { entry.into_mut() @@ -512,6 +569,7 @@ impl Frame { origin: child_offset, overflow: overflow, final_transform: final_transform, + render_target: current_render_target, }); for (item_index, item) in draw_list.items.iter().enumerate() { @@ -520,7 +578,7 @@ impl Frame { // may already be set for draw lists from other iframe(s) that weren't updated // as part of this new stacking context. let item_index = DrawListItemIndex(item_index as u32); - let rect = final_transform.transform_rect(&item.rect); + let rect = transform_in_scene_space.transform_rect(&item.rect); layer.insert(&rect, draw_list_id, item_index); } } @@ -541,7 +599,8 @@ impl Frame { scene, old_layers, scene_rect, - pipeline_sizes); + pipeline_sizes, + device_pixel_ratio); } SpecificSceneItem::Iframe(iframe_info) => { let pipeline = scene.pipeline_map @@ -573,7 +632,8 @@ impl Frame { scene, old_layers, scene_rect, - pipeline_sizes); + pipeline_sizes, + device_pixel_ratio); } } } @@ -586,49 +646,74 @@ impl Frame { } } - pub fn push_render_target(&mut self, - size: Size2D, - texture_id: Option) { - let rt_index = RenderTargetIndex(self.render_targets.len() as u32); - self.render_target_stack.push(rt_index); + pub fn push_render_target(&mut self, size: Size2D, texture: Option) { + let render_target = FrameRenderTarget::new(size, texture); + if let Some(last_render_target_group_index) = self.last_render_target_group { + if self.render_target_groups[last_render_target_group_index.0 as usize] + .can_add_render_target(&render_target) { + self.render_target_group_stack.push(last_render_target_group_index); + self.last_render_target_group = None; + self.current_render_target_group().push_render_target(render_target); + return + } + + self.render_target_groups[last_render_target_group_index.0 as usize].finalize(); + self.last_render_target_group = None; + } - let render_target = FrameRenderTarget::new(size, texture_id); - self.render_targets.push(render_target); + let rt_index = RenderTargetGroupIndex(self.render_target_groups.len() as u32); + self.render_target_group_stack.push(rt_index); + + let mut render_target_group = FrameRenderTargetGroup::new(); + render_target_group.push_render_target(render_target); + self.render_target_groups.push(render_target_group); } #[inline] fn push_clear(&mut self, clear_info: ClearInfo) { - self.current_render_target().push_clear(clear_info); + self.current_render_target_group().push_clear(clear_info); } #[inline] - fn push_composite(&mut self, composite_info: CompositeInfo) { - self.current_render_target().push_composite(composite_info); + fn push_composite(&mut self, operation: &CompositionOp, job: &CompositeInfo) { + self.current_render_target_group().push_composite(operation, job) } #[inline] fn push_draw_list(&mut self, draw_list_id: DrawListId, scroll_layer_id: ScrollLayerId) { - self.current_render_target().push_draw_list(draw_list_id, - scroll_layer_id); + self.current_render_target_group().push_draw_list(draw_list_id, scroll_layer_id); } - fn current_render_target(&mut self) -> &mut FrameRenderTarget { - let RenderTargetIndex(index) = *self.render_target_stack.last().unwrap(); - &mut self.render_targets[index as usize] + fn current_render_target_group(&mut self) -> &mut FrameRenderTargetGroup { + let RenderTargetGroupIndex(index) = *self.render_target_group_stack.last().unwrap(); + &mut self.render_target_groups[index as usize] } pub fn pop_render_target(&mut self) { - self.current_render_target().finalize(); - self.render_target_stack.pop().unwrap(); + if self.current_render_target_group().pop_render_target() { + return + } + + self.finalize_last_render_target_group(); + self.last_render_target_group = self.render_target_group_stack.pop(); + } + + pub fn finalize_last_render_target_group(&mut self) { + if let Some(last_render_target_group_index) = self.last_render_target_group { + self.render_target_groups[last_render_target_group_index.0 as usize] + .finalize(); + self.last_render_target_group = None; + } } pub fn build(&mut self, - viewport: &Rect, - resource_cache: &mut ResourceCache, - thread_pool: &mut scoped_threadpool::Pool, - device_pixel_ratio: f32) -> RendererFrame { + viewport: &Rect, + resource_cache: &mut ResourceCache, + thread_pool: &mut scoped_threadpool::Pool, + device_pixel_ratio: f32) + -> RendererFrame { let origin = Point2D::new(viewport.origin.x as f32, viewport.origin.y as f32); let size = Size2D::new(viewport.size.width as f32, viewport.size.height as f32); let viewport_rect = Rect::new(origin, size); @@ -657,7 +742,7 @@ impl Frame { self.update_batch_cache(); // Collect the visible batches into a frame - self.collect_and_sort_visible_batches(resource_cache) + self.collect_and_sort_visible_batches(resource_cache, device_pixel_ratio) } pub fn update_resource_lists(&mut self, @@ -707,7 +792,7 @@ impl Frame { let _pf = util::ProfileScope::new(" compile_visible_nodes"); let layers = &mut self.layers; - let render_targets = &self.render_targets; + let render_target_groups = &self.render_target_groups; thread_pool.scoped(|scope| { for (_, layer) in layers { @@ -715,9 +800,7 @@ impl Frame { for node in nodes { if node.is_visible && node.compiled_node.is_none() { scope.execute(move || { - node.compile(resource_cache, - render_targets, - device_pixel_ratio); + node.compile(resource_cache, render_target_groups, device_pixel_ratio); }); } } @@ -748,35 +831,60 @@ impl Frame { } pub fn collect_and_sort_visible_batches(&mut self, - resource_cache: &ResourceCache) -> RendererFrame { + resource_cache: &ResourceCache, + device_pixel_ratio: f32) + -> RendererFrame { let mut frame = RendererFrame::new(self.pipeline_epoch_map.clone()); - for render_target in &self.render_targets { - let mut commands = Vec::new(); + for render_target_group in &self.render_target_groups { + let mut layer_rect = None; + for render_target in &render_target_group.render_targets { + let rect = match render_target.texture { + Some(ref texture) => texture.uv_rect, + None => Rect::new(Point2D::new(0, 0), render_target.size), + }; + match layer_rect { + None => layer_rect = Some(rect), + Some(old_layer_rect) => layer_rect = Some(old_layer_rect.union(&rect)), + } + } + let layer_rect = layer_rect.unwrap(); - for item in &render_target.items { + let mut commands = vec![]; + for item in &render_target_group.items { match item { &FrameRenderItem::Clear(ref info) => { commands.push(DrawCommand::Clear(info.clone())); } - &FrameRenderItem::Composite(ref info) => { - commands.push(DrawCommand::Composite(info.clone())); + &FrameRenderItem::CompositeBatch(ref info) => { + commands.push(DrawCommand::CompositeBatch(info.clone())); } &FrameRenderItem::DrawListBatch(ref batch_info) => { let layer = &self.layers[&batch_info.scroll_layer_id]; let first_draw_list_id = *batch_info.draw_lists.first().unwrap(); - debug_assert!(batch_info.draw_lists.len() < MAX_MATRICES_PER_BATCH); - let mut matrix_palette = vec![Matrix4::identity(); batch_info.draw_lists.len()]; + debug_assert!(batch_info.draw_lists.len() <= MAX_MATRICES_PER_BATCH); + let mut matrix_palette = + vec![Matrix4::identity(); batch_info.draw_lists.len()]; // Update batch matrices for (index, draw_list_id) in batch_info.draw_lists.iter().enumerate() { let draw_list = resource_cache.get_draw_list(*draw_list_id); - let transform = draw_list.context.as_ref().unwrap().final_transform; - let transform = transform.translate(layer.scroll_offset.x, - layer.scroll_offset.y, - 0.0); - matrix_palette[index] = transform; + let context = draw_list.context.as_ref().unwrap(); + let mut transform = context.final_transform; + transform = transform.translate(layer.scroll_offset.x, + layer.scroll_offset.y, + 0.0); + if let Some(ref render_target_texture) = context.render_target + .texture { + transform = transform.translate( + (render_target_texture.uv_rect.origin.x - layer_rect.origin.x) + as f32 / device_pixel_ratio, + (render_target_texture.uv_rect.origin.y - layer_rect.origin.y) + as f32 / device_pixel_ratio, + 0.0); + } + matrix_palette[index] = transform } let mut batch_info = BatchInfo::new(matrix_palette); @@ -814,9 +922,30 @@ impl Frame { } } - let layer = DrawLayer::new(render_target.texture_id, - render_target.size, - commands); + let mut render_targets = vec![]; + let mut bounding_rect = Rect::new(Point2D::new(0, 0), Size2D::new(0, 0)); + let mut texture_id = None; + for render_target in &render_target_group.render_targets { + render_targets.push((*render_target).clone()); + + let origin; + match render_target.texture { + Some(ref texture) => { + debug_assert!(texture_id.is_none() || + texture_id == Some(texture.texture_id)); + texture_id = Some(texture.texture_id); + origin = texture.uv_rect.origin; + } + None => { + debug_assert!(texture_id.is_none()); + origin = Point2D::new(0, 0); + } + } + + bounding_rect = bounding_rect.union(&Rect::new(origin, render_target.size)); + } + + let layer = DrawLayer::new(&bounding_rect.size, texture_id, render_targets, commands); frame.layers.push(layer); } diff --git a/src/internal_types.rs b/src/internal_types.rs index c1a0065dea..c2338bb7aa 100644 --- a/src/internal_types.rs +++ b/src/internal_types.rs @@ -126,7 +126,7 @@ impl WorkVertex { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy)] #[repr(C)] pub struct PackedVertex { pub x: f32, @@ -273,7 +273,6 @@ pub struct TextureImage { pub enum TextureUpdateOp { Create(u32, u32, ImageFormat, TextureFilter, RenderTargetMode, Option>), Update(u32, u32, u32, u32, TextureUpdateDetails), - DeinitRenderTarget(TextureId), } pub struct TextureUpdate { @@ -336,10 +335,16 @@ pub struct ClearInfo { } #[derive(Clone, Debug)] -pub struct CompositeInfo { +pub struct CompositeBatchInfo { pub operation: CompositionOp, - pub rect: Rect, - pub color_texture_id: TextureId, + pub texture_id: TextureId, + pub jobs: Vec, +} + +#[derive(Clone, Debug)] +pub struct CompositeInfo { + pub rect: Rect, + pub render_target_texture: RenderTargetTexture, } #[derive(Clone, Debug)] @@ -370,7 +375,7 @@ impl BatchInfo { #[derive(Clone, Debug)] pub enum DrawCommand { Batch(BatchInfo), - Composite(CompositeInfo), + CompositeBatch(CompositeBatchInfo), Clear(ClearInfo), } @@ -379,18 +384,22 @@ pub struct RenderTargetIndex(pub u32); #[derive(Debug)] pub struct DrawLayer { - pub texture_id: Option, pub size: Size2D, + pub texture_id: Option, + pub render_targets: Vec, pub commands: Vec, } impl DrawLayer { - pub fn new(texture_id: Option, - size: Size2D, - commands: Vec) -> DrawLayer { + pub fn new(size: &Size2D, + texture_id: Option, + render_targets: Vec, + commands: Vec) + -> DrawLayer { DrawLayer { + size: *size, texture_id: texture_id, - size: size, + render_targets: render_targets, commands: commands, } } @@ -520,6 +529,22 @@ pub struct DrawListContext { pub origin: Point2D, pub overflow: Rect, pub final_transform: Matrix4, + pub render_target: FrameRenderTarget, +} + +#[derive(Debug, Clone)] +pub struct FrameRenderTarget { + pub size: Size2D, + pub texture: Option, +} + +impl FrameRenderTarget { + pub fn new(size: Size2D, texture: Option) -> FrameRenderTarget { + FrameRenderTarget { + size: size, + texture: texture, + } + } } #[derive(Debug)] @@ -681,6 +706,25 @@ impl RectUv { } } } + + pub fn to_rect(&self) -> Rect { + fn min(x: f32, y: f32) -> f32 { + if x < y { x } else { y } + } + fn max(x: f32, y: f32) -> f32 { + if x > y { x } else { y } + } + + let min_x = min(min(min(self.top_left.x, self.top_right.x), self.bottom_right.x), + self.bottom_left.x); + let min_y = min(min(min(self.top_left.y, self.top_right.y), self.bottom_right.y), + self.bottom_left.y); + let max_x = max(max(max(self.top_left.x, self.top_right.x), self.bottom_right.x), + self.bottom_left.x); + let max_y = max(max(max(self.top_left.y, self.top_right.y), self.bottom_right.y), + self.bottom_left.y); + Rect::new(Point2D::new(min_x, min_y), Size2D::new(max_x - min_x, max_y - min_y)) + } } #[derive(Clone, Debug)] @@ -921,7 +965,7 @@ impl<'a> CombinedClipRegion<'a> { } } -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum LowLevelFilterOp { Blur(Au, AxisDirection), Brightness(Au), @@ -935,9 +979,22 @@ pub enum LowLevelFilterOp { Sepia(Au), } -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum CompositionOp { MixBlend(MixBlendMode), Filter(LowLevelFilterOp), } +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum BlurDirection { + Horizontal, + Vertical, +} + +#[derive(Clone, Copy, Debug)] +pub struct RenderTargetTexture { + pub texture_id: TextureId, + pub uv_rect: Rect, + pub texture_size: Size2D, +} + diff --git a/src/node_compiler.rs b/src/node_compiler.rs index 7f12f386de..d001b5037f 100644 --- a/src/node_compiler.rs +++ b/src/node_compiler.rs @@ -1,7 +1,7 @@ use aabbtree::AABBTreeNode; use batch::{BatchBuilder, MatrixIndex, VertexBuffer}; use clipper::{ClipBuffers}; -use frame::{FrameRenderItem, FrameRenderTarget}; +use frame::{FrameRenderItem, FrameRenderTargetGroup}; use internal_types::{DrawListItemIndex, CompiledNode, CombinedClipRegion, BatchList}; use resource_cache::ResourceCache; use webrender_traits::SpecificDisplayItem; @@ -9,25 +9,25 @@ use webrender_traits::SpecificDisplayItem; pub trait NodeCompiler { fn compile(&mut self, resource_cache: &ResourceCache, - render_targets: &Vec, + render_target_groups: &Vec, device_pixel_ratio: f32); } impl NodeCompiler for AABBTreeNode { fn compile(&mut self, resource_cache: &ResourceCache, - render_targets: &Vec, + render_target_groups: &Vec, device_pixel_ratio: f32) { let mut compiled_node = CompiledNode::new(); let mut vertex_buffer = VertexBuffer::new(); let mut clip_buffers = ClipBuffers::new(); - for render_target in render_targets { - for item in &render_target.items { + for render_target_group in render_target_groups { + for item in &render_target_group.items { match item { &FrameRenderItem::Clear(..) | - &FrameRenderItem::Composite(..) => {} + &FrameRenderItem::CompositeBatch(..) => {} &FrameRenderItem::DrawListBatch(ref batch_info) => { // TODO: Move this to outer loop when combining with >1 draw list! let mut builder = BatchBuilder::new(&mut vertex_buffer); diff --git a/src/render_backend.rs b/src/render_backend.rs index f2f1a76c81..07e216680e 100644 --- a/src/render_backend.rs +++ b/src/render_backend.rs @@ -1,6 +1,6 @@ use euclid::{Rect, Size2D}; use frame::Frame; -use internal_types::{FontTemplate, ResultMsg, DrawLayer, RendererFrame}; +use internal_types::{FontTemplate, FrameRenderTarget, ResultMsg, DrawLayer, RendererFrame}; use ipc_channel::ipc::IpcReceiver; use profiler::BackendProfileCounters; use resource_cache::ResourceCache; @@ -266,6 +266,7 @@ impl RenderBackend { self.frame.create(&self.scene, Size2D::new(self.viewport.size.width as u32, self.viewport.size.height as u32), + self.device_pixel_ratio, &mut self.resource_cache, &mut new_pipeline_sizes); @@ -328,10 +329,12 @@ impl RenderBackend { // cleared to the default UA background color. Perhaps // there is a better way to handle this... if frame.layers.len() == 0 { + let size = Size2D::new(self.viewport.size.width as u32, + self.viewport.size.height as u32); frame.layers.push(DrawLayer { + render_targets: vec![FrameRenderTarget::new(size, None)], texture_id: None, - size: Size2D::new(self.viewport.size.width as u32, - self.viewport.size.height as u32), + size: size, commands: Vec::new(), }); } diff --git a/src/renderer.rs b/src/renderer.rs index a562ffd511..09dc4a76e7 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -89,8 +89,11 @@ pub struct Renderer { blur_program_id: ProgramId, u_direction: UniformLocation, + clear_program_id: ProgramId, + notifier: Arc>>>, viewport_size: Size2D, + framebuffer_size: Size2D, enable_profiler: bool, debug: DebugRenderer, @@ -103,6 +106,7 @@ pub struct Renderer { impl Renderer { pub fn new(width: u32, height: u32, + framebuffer_size: &Size2D, device_pixel_ratio: f32, resource_path: PathBuf, enable_aa: bool, @@ -123,6 +127,7 @@ impl Renderer { let box_shadow_program_id = device.create_program("box_shadow.vs.glsl", "box_shadow.fs.glsl"); let blur_program_id = device.create_program("blur.vs.glsl", "blur.fs.glsl"); + let clear_program_id = device.create_program("clear.vs.glsl", "clear.fs.glsl"); let u_quad_transform_array = device.get_uniform_location(quad_program_id, "uMatrixPalette"); let u_tile_params = device.get_uniform_location(quad_program_id, "uTileParams"); @@ -210,6 +215,7 @@ impl Renderer { blit_program_id: blit_program_id, box_shadow_program_id: box_shadow_program_id, blur_program_id: blur_program_id, + clear_program_id: clear_program_id, u_blend_params: u_blend_params, u_filter_params: u_filter_params, u_direction: u_direction, @@ -218,6 +224,7 @@ impl Renderer { u_tile_params: u_tile_params, notifier: notifier, viewport_size: Size2D::new(width, height), + framebuffer_size: *framebuffer_size, debug: debug_renderer, backend_profile_counters: BackendProfileCounters::new(), profile_counters: RendererProfileCounters::new(), @@ -346,9 +353,6 @@ impl Renderer { } } } - TextureUpdateOp::DeinitRenderTarget(id) => { - self.device.deinit_texture(id); - } TextureUpdateOp::Update(x, y, width, height, details) => { match details { TextureUpdateDetails::Raw => { @@ -518,9 +522,6 @@ impl Renderer { inner_ry, index, inverted) => { - println!("outer_rx={:?} outer_ry={:?} inner_rx={:?} inner_ry={:?}", - outer_rx, outer_ry, - inner_rx, inner_ry); let x = x as f32; let y = y as f32; let inner_rx = inner_rx.to_f32_px(); @@ -646,10 +647,15 @@ impl Renderer { let zero_point = Point2D::new(0.0, 0.0); let zero_size = Size2D::new(0.0, 0.0); - let arc_radius = match box_shadow_part { - BoxShadowPart::Edge => Point2D::new(rect.size.width, 0.0), + // `arc_radius_inner` here is just a flag to specify to the shader whether we're an edge + // (zero) or a corner (nonzero). + let (arc_radius_outer, arc_radius_inner) = match box_shadow_part { + BoxShadowPart::Edge => { + (Point2D::new(rect.size.width, 0.0), Point2D::new(0.0, 0.0)) + } BoxShadowPart::Corner(border_radius) => { - Point2D::new(border_radius.to_f32_px(), border_radius.to_f32_px()) + (Point2D::new(border_radius.to_f32_px(), border_radius.to_f32_px()), + Point2D::new(1.0, 1.0)) } }; @@ -657,8 +663,8 @@ impl Renderer { PackedVertexForTextureCacheUpdate::new(&rect.origin, &color, &zero_point, - &arc_radius, - &zero_point, + &arc_radius_outer, + &arc_radius_inner, &zero_point, &rect.origin, &rect.size, @@ -667,8 +673,8 @@ impl Renderer { PackedVertexForTextureCacheUpdate::new(&rect.top_right(), &color, &zero_point, - &arc_radius, - &zero_point, + &arc_radius_outer, + &arc_radius_inner, &zero_point, &rect.origin, &rect.size, @@ -677,8 +683,8 @@ impl Renderer { PackedVertexForTextureCacheUpdate::new(&rect.bottom_left(), &color, &zero_point, - &arc_radius, - &zero_point, + &arc_radius_outer, + &arc_radius_inner, &zero_point, &rect.origin, &rect.size, @@ -687,8 +693,8 @@ impl Renderer { PackedVertexForTextureCacheUpdate::new(&rect.bottom_right(), &color, &zero_point, - &arc_radius, - &zero_point, + &arc_radius_outer, + &arc_radius_inner, &zero_point, &rect.origin, &rect.size, @@ -770,6 +776,7 @@ impl Renderer { blur_direction: Option) { gl::disable(gl::BLEND); gl::disable(gl::DEPTH_TEST); + gl::disable(gl::SCISSOR_TEST); let (texture_width, texture_height) = self.device.get_texture_dimensions(update_id); @@ -830,31 +837,80 @@ impl Renderer { }; debug_assert!(frame.layers.len() > 0); - let framebuffer_size = frame.layers[0].size; + let framebuffer_size = self.framebuffer_size; for layer in frame.layers.iter().rev() { render_context.layer_size = layer.size; - render_context.projection = Matrix4::ortho(0.0, - layer.size.width as f32, - layer.size.height as f32, - 0.0, - ORTHO_NEAR_PLANE, - ORTHO_FAR_PLANE); - - let layer_texture_id = layer.texture_id; + + let layer_texture_id = layer.render_targets[0].texture.map(|texture| { + texture.texture_id + }); self.device.bind_render_target(layer_texture_id); - gl::viewport(0, - 0, - (layer.size.width as f32 * self.device_pixel_ratio) as gl::GLint, - (layer.size.height as f32 * self.device_pixel_ratio) as gl::GLint); + + let mut uv; + let mut uv_rects = vec![]; + let render_target_size; + let scale_factor; + let viewport_y; + match layer.render_targets[0].texture { + None => { + let v = layer.size.height as f32; + uv = Rect::new(Point2D::new(0, v as u32), layer.size); + uv_rects.push(uv); + scale_factor = framebuffer_size.width as f32 / layer.size.width as f32; + render_target_size = Size2D::new( + (layer.size.width as f32 * scale_factor) as u32, + (layer.size.height as f32 * scale_factor) as u32); + viewport_y = render_target_size.height as f32 - uv.origin.y as f32 * + scale_factor; + } + Some(ref texture) => { + render_target_size = texture.texture_size; + scale_factor = 1.0; + uv = texture.uv_rect; + uv_rects.push(uv); + + for render_target in &layer.render_targets[1..] { + if let Some(ref texture) = render_target.texture { + uv = uv.union(&texture.uv_rect); + uv_rects.push(texture.uv_rect); + } + } + + viewport_y = render_target_size.height as f32 - + uv.max_y() as f32 * scale_factor; + } + }; + let viewport = Rect::new( + Point2D::new((uv.origin.x as f32 * scale_factor) as gl::GLint, + viewport_y as gl::GLint), + Size2D::new((uv.size.width as f32 * scale_factor) as gl::GLint, + (uv.size.height as f32 * scale_factor) as gl::GLint)); + gl::viewport(viewport.origin.x, + viewport.origin.y, + viewport.size.width, + viewport.size.height); + + // Clear frame buffer + clear_framebuffer(&mut self.device, + &mut render_context, + &mut self.profile_counters, + &viewport, + &uv, + self.device_pixel_ratio, + &uv_rects[..], + self.clear_program_id, + layer.render_targets[0].texture.is_some()); gl::enable(gl::DEPTH_TEST); gl::depth_func(gl::LEQUAL); - // Clear frame buffer - gl::clear_color(1.0, 1.0, 1.0, 1.0); - gl::clear(gl::COLOR_BUFFER_BIT | - gl::DEPTH_BUFFER_BIT | - gl::STENCIL_BUFFER_BIT); + render_context.projection = + Matrix4::ortho(0.0, + viewport.size.width as f32 / self.device_pixel_ratio, + viewport.size.height as f32 / self.device_pixel_ratio, + 0.0, + ORTHO_NEAR_PLANE, + ORTHO_FAR_PLANE); for cmd in &layer.commands { match cmd { @@ -919,14 +975,16 @@ impl Renderer { mask_size.0 as f32, mask_size.1 as f32); - self.profile_counters.vertices.add(draw_call.index_count as usize); + let index_count = draw_call.index_count as i32; + assert!(draw_call.first_vertex <= 65535); + self.profile_counters.draw_calls.inc(); self.device.draw_triangles_u16(draw_call.first_vertex as i32, - draw_call.index_count as i32); + index_count); } } - &DrawCommand::Composite(ref info) => { + &DrawCommand::CompositeBatch(ref info) => { let needs_fb = match info.operation { CompositionOp::MixBlend(MixBlendMode::Normal) => unreachable!(), @@ -957,29 +1015,32 @@ impl Renderer { CompositionOp::MixBlend(MixBlendMode::Lighten) => false, }; - let x0 = info.rect.origin.x; - let y0 = info.rect.origin.y; - let x1 = x0 + info.rect.size.width; - let y1 = y0 + info.rect.size.height; - let alpha; if needs_fb { gl::disable(gl::BLEND); - // TODO: No need to re-init this FB working copy texture every time... - self.device.init_texture(render_context.temporary_fb_texture, - info.rect.size.width, - info.rect.size.height, - ImageFormat::RGBA8, - TextureFilter::Nearest, - RenderTargetMode::None, - None); - self.device.read_framebuffer_rect( - render_context.temporary_fb_texture, - x0, - framebuffer_size.height - info.rect.size.height - y0, - info.rect.size.width, - info.rect.size.height); + // TODO(glennw): No need to re-init this FB working copy texture + // every time... + for job in &info.jobs { + let x0 = job.rect.origin.x; + let y0 = job.rect.origin.y; + + self.device.init_texture(render_context.temporary_fb_texture, + job.rect.size.width as u32, + job.rect.size.height as u32, + ImageFormat::RGBA8, + TextureFilter::Nearest, + RenderTargetMode::None, + None); + self.device.read_framebuffer_rect( + render_context.temporary_fb_texture, + x0, + render_context.layer_size.height as i32 - + job.rect.size.height - + y0, + job.rect.size.width, + job.rect.size.height); + } match info.operation { CompositionOp::MixBlend(blend_mode) => { @@ -1086,55 +1147,173 @@ impl Renderer { _ => unreachable!(), } - self.device.bind_program(self.blit_program_id, &render_context.projection); + self.device.bind_program(self.blit_program_id, + &render_context.projection); } - let color = ColorF::new(1.0, 1.0, 1.0, alpha); - let indices: [u16; 6] = [ 0, 1, 2, 2, 3, 1 ]; - let vertices: [PackedVertex; 4] = [ - PackedVertex::from_components_unscaled_muv( - x0 as f32, y0 as f32, - &color, - 0.0, 1.0, - info.rect.size.width as u16, - info.rect.size.height as u16), - PackedVertex::from_components_unscaled_muv( - x1 as f32, y0 as f32, - &color, - 1.0, 1.0, - info.rect.size.width as u16, - info.rect.size.height as u16), - PackedVertex::from_components_unscaled_muv( - x0 as f32, y1 as f32, - &color, - 0.0, 0.0, - info.rect.size.width as u16, - info.rect.size.height as u16), - PackedVertex::from_components_unscaled_muv( - x1 as f32, y1 as f32, - &color, - 1.0, 0.0, - info.rect.size.width as u16, - info.rect.size.height as u16), - ]; - // TODO: Don't re-create this VAO all the time. - // Create it once and set positions via uniforms. - let vao_id = self.device.create_vao(VertexFormat::Batch); - self.device.bind_color_texture(info.color_texture_id); - self.device.bind_vao(vao_id); - self.device.update_vao_indices(vao_id, &indices, VertexUsageHint::Dynamic); - self.device.update_vao_vertices(vao_id, &vertices, VertexUsageHint::Dynamic); - - self.profile_counters.vertices.add(indices.len()); - self.profile_counters.draw_calls.inc(); - - self.device.draw_triangles_u16(0, indices.len() as gl::GLint); - - self.device.delete_vao(vao_id); + let (mut indices, mut vertices) = (vec![], vec![]); + for job in &info.jobs { + let x0 = job.rect.origin.x; + let y0 = job.rect.origin.y; + let x1 = job.rect.max_x(); + let y1 = job.rect.max_y(); + + let vertex_count = vertices.len() as u16; + indices.push(vertex_count + 0); + indices.push(vertex_count + 1); + indices.push(vertex_count + 2); + indices.push(vertex_count + 2); + indices.push(vertex_count + 3); + indices.push(vertex_count + 1); + + let color = ColorF::new(1.0, 1.0, 1.0, alpha); + + let pixel_uv = Rect::new( + Point2D::new(job.render_target_texture.uv_rect.origin.x, + job.render_target_texture.uv_rect.origin.y), + Size2D::new( + (job.rect.size.width as f32 * self.device_pixel_ratio) as + u32, + (job.rect.size.height as f32 * self.device_pixel_ratio) as + u32)); + let texture_width = + job.render_target_texture.texture_size.width as f32; + let texture_height = + job.render_target_texture.texture_size.height as f32; + let texture_uv = Rect::new( + Point2D::new( + pixel_uv.origin.x as f32 / texture_width, + 1.0 - (pixel_uv.origin.y + pixel_uv.size.height) as f32 / + texture_height), + Size2D::new(pixel_uv.size.width as f32 / texture_width, + pixel_uv.size.height as f32 / texture_height)); + + vertices.push_all(&[ + PackedVertex::from_components_unscaled_muv( + x0 as f32, y0 as f32, + &color, + texture_uv.origin.x, texture_uv.max_y(), + job.rect.size.width as u16, job.rect.size.height as u16), + PackedVertex::from_components_unscaled_muv( + x1 as f32, y0 as f32, + &color, + texture_uv.max_x(), texture_uv.max_y(), + job.rect.size.width as u16, job.rect.size.height as u16), + PackedVertex::from_components_unscaled_muv( + x0 as f32, y1 as f32, + &color, + texture_uv.origin.x, texture_uv.origin.y, + job.rect.size.width as u16, job.rect.size.height as u16), + PackedVertex::from_components_unscaled_muv( + x1 as f32, y1 as f32, + &color, + texture_uv.max_x(), texture_uv.origin.y, + job.rect.size.width as u16, job.rect.size.height as u16), + ]); + } + + draw_simple_triangles(&mut self.device, + &mut render_context, + &mut self.profile_counters, + &indices[..], + &vertices[..], + info.texture_id); } } } } } } + +} + +fn clear_framebuffer(device: &mut Device, + render_context: &mut RenderContext, + profile_counters: &mut RendererProfileCounters, + viewport: &Rect, + combined_uv: &Rect, + device_pixel_ratio: f32, + uv_rects: &[Rect], + program_id: ProgramId, + clear_to_transparent: bool) { + let clear_color = ColorF { + r: 1.0, + g: 1.0, + b: 1.0, + a: if clear_to_transparent { 0.0 } else { 1.0 }, + }; + + // Fast path if we only have one rect: just use glClear(). + // + // TODO(pcwalton): We could take this path too if the rects all union together precisely + // to the viewport. But I kinda doubt it's worth the trouble. + if uv_rects.len() < 2 { + gl::scissor(viewport.origin.x, viewport.origin.y, + viewport.size.width, viewport.size.height); + gl::enable(gl::SCISSOR_TEST); + gl::clear_color(clear_color.r, clear_color.g, clear_color.b, clear_color.a); + gl::clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT | gl::STENCIL_BUFFER_BIT); + gl::disable(gl::SCISSOR_TEST); + return; + } + + // Slow path if we have multiple rects. + // + // We use [0,1) coordinates here because it's simpler to centralize the scaling to the viewport + // in one place. + let (mut indices, mut vertices) = (vec![], vec![]); + for rect in uv_rects { + let x0 = (rect.origin.x as f32 - combined_uv.origin.x as f32 * device_pixel_ratio) / + viewport.size.width as f32; + let y0 = (rect.origin.y as f32 - combined_uv.origin.y as f32 * device_pixel_ratio) / + viewport.size.height as f32; + let x1 = (rect.max_x() as f32 - combined_uv.origin.x as f32 * device_pixel_ratio) / + viewport.size.width as f32; + let y1 = (rect.max_y() as f32 - combined_uv.origin.y as f32 * device_pixel_ratio) / + viewport.size.height as f32; + indices.extend([0, 1, 2, 1, 3, 2].iter().map(|index| (index + vertices.len()) as u16)); + vertices.push_all(&[ + PackedVertex::from_components(x0, y0, &clear_color, 0.0, 0.0, 0.0, 0.0), + PackedVertex::from_components(x1, y0, &clear_color, 0.0, 0.0, 0.0, 0.0), + PackedVertex::from_components(x0, y1, &clear_color, 0.0, 0.0, 0.0, 0.0), + PackedVertex::from_components(x1, y1, &clear_color, 0.0, 0.0, 0.0, 0.0), + ]); + } + + let projection = Matrix4::ortho(0.0, 1.0, + 1.0, 0.0, + ORTHO_NEAR_PLANE, + ORTHO_FAR_PLANE); + device.bind_program(program_id, &projection); + gl::disable(gl::BLEND); + draw_simple_triangles(device, + render_context, + profile_counters, + &indices[..], + &vertices[..], + TextureId(0)); + + gl::clear(gl::DEPTH_BUFFER_BIT | gl::STENCIL_BUFFER_BIT); } + +fn draw_simple_triangles(device: &mut Device, + render_context: &mut RenderContext, + profile_counters: &mut RendererProfileCounters, + indices: &[u16], + vertices: &[PackedVertex], + texture: TextureId) { + // TODO(glennw): Don't re-create this VAO all the time. Create it once and set positions + // via uniforms. + let vao_id = device.create_vao(VertexFormat::Batch); + device.bind_color_texture(texture); + device.bind_vao(vao_id); + device.update_vao_indices(vao_id, &indices[..], VertexUsageHint::Dynamic); + device.update_vao_vertices(vao_id, &vertices[..], VertexUsageHint::Dynamic); + + profile_counters.vertices.add(indices.len()); + profile_counters.draw_calls.inc(); + + device.draw_triangles_u16(0, indices.len() as gl::GLint); + device.delete_vao(vao_id); +} + diff --git a/src/resource_cache.rs b/src/resource_cache.rs index dfecb0f189..ffc443d7d7 100644 --- a/src/resource_cache.rs +++ b/src/resource_cache.rs @@ -1,9 +1,9 @@ use app_units::Au; -use device::{TextureId, TextureFilter}; +use device::{TextureFilter, TextureId}; use euclid::Size2D; use fnv::FnvHasher; use freelist::FreeList; -use internal_types::{FontTemplate, GlyphKey, RasterItem}; +use internal_types::{FontTemplate, GlyphKey, RasterItem, RenderTargetTexture}; use internal_types::{TextureUpdateList, DrawListId, DrawList}; use platform::font::{FontContext, RasterizedGlyph}; use renderer::BLUR_INFLATION_FACTOR; @@ -17,7 +17,7 @@ use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT}; use std::sync::atomic::Ordering::SeqCst; use std::thread; use std::time::Duration; -use texture_cache::{TextureCache, TextureCacheItem, TextureCacheItemId}; +use texture_cache::{TextureCache, TextureCacheItem, TextureCacheItemId, TextureCacheItemKind}; use texture_cache::{BorderType, TextureInsertOp}; use webrender_traits::{Epoch, FontKey, ImageKey, ImageFormat, DisplayItem, ImageRendering}; use webrender_traits::{WebGLContextId}; @@ -294,13 +294,26 @@ impl ResourceCache { self.draw_lists.free(draw_list_id); } - pub fn allocate_render_target(&mut self, width: u32, height: u32, format: ImageFormat) - -> TextureId { - self.texture_cache.allocate_render_target(width, height, format) + pub fn allocate_render_target(&mut self, + width: u32, + height: u32, + format: ImageFormat) + -> RenderTargetTexture { + let image_id = self.texture_cache.new_item_id(); + let texture = self.texture_cache.allocate(image_id, + 0, 0, + width, height, + format, + TextureCacheItemKind::RenderTarget, + BorderType::NoBorder, + TextureFilter::Linear); + texture.to_render_target_texture() } - pub fn free_render_target(&mut self, texture_id: TextureId) { - self.texture_cache.free_render_target(texture_id) + pub fn free_render_target(&mut self, texture: RenderTargetTexture) { + self.texture_cache.free(texture.texture_id, + &texture.uv_rect, + TextureCacheItemKind::RenderTarget) } pub fn pending_updates(&mut self) -> TextureUpdateList { diff --git a/src/texture_cache.rs b/src/texture_cache.rs index a609735c5a..2d6b22a2d6 100644 --- a/src/texture_cache.rs +++ b/src/texture_cache.rs @@ -3,7 +3,7 @@ use device::{MAX_TEXTURE_SIZE, TextureId, TextureFilter}; use euclid::{Point2D, Rect, Size2D}; use fnv::FnvHasher; use freelist::{FreeList, FreeListItem, FreeListItemId}; -use internal_types::{TextureUpdate, TextureUpdateOp, TextureUpdateDetails}; +use internal_types::{TextureUpdate, TextureUpdateOp, TextureUpdateDetails, RenderTargetTexture}; use internal_types::{RasterItem, RenderTargetMode, TextureImage, TextureUpdateList}; use internal_types::{BasicRotationAngle, RectUv}; use std::cmp::Ordering; @@ -249,6 +249,7 @@ impl TexturePage { free_list[work_index].union(&free_list[candidate_index]); free_list[candidate_index].size.width = 0 } + new_free_list.push(free_list[work_index]) } new_free_list.push(free_list[work_index]) } @@ -296,7 +297,6 @@ impl TexturePage { self.dirty = false; } - #[allow(dead_code)] fn free(&mut self, rect: &Rect) { debug_assert!(self.allocations > 0); self.allocations -= 1; @@ -487,6 +487,7 @@ struct TextureCacheArena { pages_rgba8: Vec, alternate_pages_a8: Vec, alternate_pages_rgba8: Vec, + render_target_pages: Vec, } impl TextureCacheArena { @@ -497,6 +498,7 @@ impl TextureCacheArena { pages_rgba8: Vec::new(), alternate_pages_a8: Vec::new(), alternate_pages_rgba8: Vec::new(), + render_target_pages: Vec::new(), } } } @@ -507,6 +509,9 @@ pub struct TextureCache { alternate_free_texture_levels: HashMap, DefaultState>, + render_target_free_texture_levels: HashMap, + DefaultState>, items: FreeList, arena: TextureCacheArena, pending_updates: TextureUpdateList, @@ -518,17 +523,35 @@ pub enum AllocationKind { Standalone, } +#[derive(Debug)] pub struct AllocationResult { kind: AllocationKind, item: TextureCacheItem, } +impl AllocationResult { + pub fn to_render_target_texture(self) -> RenderTargetTexture { + let uv_rect = self.item.uv_rect.to_rect(); + let texture_width = self.item.texture_size.width as f32; + let texture_height = self.item.texture_size.height as f32; + RenderTargetTexture { + texture_id: self.item.texture_id, + uv_rect: Rect::new(Point2D::new((uv_rect.origin.x * texture_width) as u32, + (uv_rect.origin.y * texture_height) as u32), + Size2D::new((uv_rect.size.width * texture_width) as u32, + (uv_rect.size.height * texture_height) as u32)), + texture_size: self.item.texture_size, + } + } +} + impl TextureCache { pub fn new(free_texture_ids: Vec) -> TextureCache { TextureCache { free_texture_ids: free_texture_ids, free_texture_levels: HashMap::with_hash_state(Default::default()), alternate_free_texture_levels: HashMap::with_hash_state(Default::default()), + render_target_free_texture_levels: HashMap::with_hash_state(Default::default()), items: FreeList::new(), pending_updates: TextureUpdateList::new(), arena: TextureCacheArena::new(), @@ -556,37 +579,25 @@ impl TextureCache { }, allocated_rect: Rect::zero(), requested_rect: Rect::zero(), - texture_id: TextureId::invalid(), texture_size: Size2D::zero(), + texture_id: TextureId::invalid(), }; self.items.insert(new_item) } - pub fn allocate_render_target(&mut self, width: u32, height: u32, format: ImageFormat) - -> TextureId { - let texture_id = self.free_texture_ids - .pop() - .expect("TODO: Handle running out of texture IDs!"); - let update_op = TextureUpdate { - id: texture_id, - op: TextureUpdateOp::Create(width, - height, - format, - TextureFilter::Nearest, - RenderTargetMode::RenderTarget, - None), - }; - self.pending_updates.push(update_op); - texture_id - } - - pub fn free_render_target(&mut self, texture_id: TextureId) { - self.free_texture_ids.push(texture_id); - let update_op = TextureUpdate { - id: texture_id, - op: TextureUpdateOp::DeinitRenderTarget(texture_id), - }; - self.pending_updates.push(update_op); + pub fn free(&mut self, texture_id: TextureId, uv: &Rect, kind: TextureCacheItemKind) { + match kind { + TextureCacheItemKind::RenderTarget => { + for page in &mut self.arena.render_target_pages { + if page.texture_id == texture_id { + page.free(uv); + return + } + } + unreachable!("couldn't find texture ID in render target pages!") + } + _ => panic!("can't free non-render target texture items yet!"), + } } pub fn allocate(&mut self, @@ -613,11 +624,15 @@ impl TextureCache { (ImageFormat::RGBA8, TextureCacheItemKind::Alternate) => { (&mut self.arena.alternate_pages_rgba8, RenderTargetMode::RenderTarget) } + (ImageFormat::RGBA8, TextureCacheItemKind::RenderTarget) => { + (&mut self.arena.render_target_pages, RenderTargetMode::RenderTarget) + } (ImageFormat::RGB8, TextureCacheItemKind::Standard) => { (&mut self.arena.pages_rgb8, RenderTargetMode::None) } (ImageFormat::Invalid, TextureCacheItemKind::Standard) | - (_, TextureCacheItemKind::Alternate) => unreachable!(), + (_, TextureCacheItemKind::Alternate) | + (_, TextureCacheItemKind::RenderTarget) => unreachable!(), }; let border_size = match border_type { @@ -627,8 +642,9 @@ impl TextureCache { let requested_size = Size2D::new(requested_width, requested_height); let allocation_size = Size2D::new(requested_width + border_size * 2, requested_height + border_size * 2); - let location = page_list.last_mut().and_then(|last_page| last_page.allocate(&allocation_size, - filter)); + let location = page_list.last_mut().and_then(|last_page| { + last_page.allocate(&allocation_size, filter) + }); let location = match location { Some(location) => location, None => { @@ -640,6 +656,9 @@ impl TextureCache { TextureCacheItemKind::Alternate => { self.alternate_free_texture_levels.entry(format) } + TextureCacheItemKind::RenderTarget => { + self.render_target_free_texture_levels.entry(format) + } }; let mut free_texture_levels = match free_texture_levels_entry { Entry::Vacant(entry) => entry.insert(Vec::new()), @@ -667,19 +686,18 @@ impl TextureCache { let texture_id = self.free_texture_ids .pop() .expect("TODO: Handle running out of texture ids!"); - let cache_item = TextureCacheItem::new(texture_id, - user_x0, user_y0, - Rect::new(Point2D::zero(), - requested_size), - Rect::new(Point2D::zero(), - requested_size), - &requested_size, - RectUv { - top_left: Point2D::new(0.0, 0.0), - top_right: Point2D::new(1.0, 0.0), - bottom_left: Point2D::new(0.0, 1.0), - bottom_right: Point2D::new(1.0, 1.0), - }); + let cache_item = TextureCacheItem::new( + texture_id, + user_x0, user_y0, + Rect::new(Point2D::zero(), requested_size), + Rect::new(Point2D::zero(), requested_size), + &requested_size, + RectUv { + top_left: Point2D::new(0.0, 0.0), + top_right: Point2D::new(1.0, 0.0), + bottom_left: Point2D::new(0.0, 1.0), + bottom_right: Point2D::new(1.0, 1.0), + }); *self.items.get_mut(image_id) = cache_item; return AllocationResult { @@ -716,16 +734,13 @@ impl TextureCache { }); *self.items.get_mut(image_id) = cache_item; - // TODO(pcwalton): Select a texture index if we're using texture arrays. AllocationResult { item: self.items.get(image_id).clone(), kind: AllocationKind::TexturePage, } } - pub fn insert_raster_op(&mut self, - image_id: TextureCacheItemId, - item: &RasterItem) { + pub fn insert_raster_op(&mut self, image_id: TextureCacheItemId, item: &RasterItem) { let update_op = match item { &RasterItem::BorderRadius(ref op) => { let rect = Rect::new(Point2D::new(0.0, 0.0), @@ -1053,5 +1068,6 @@ fn texture_size() -> u32 { pub enum TextureCacheItemKind { Standard, Alternate, + RenderTarget, }