From cdbea4a2573c366bfd16ed58c8f4c8879f38112f Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Thu, 17 Dec 2015 11:16:43 +1000 Subject: [PATCH] Various fixes and refactorings to lay groundwork for future work, such as GPU clipping, scroll layers with transforms etc. * Fix mix-blend-mode. * Use AABB tree per render target / scroll layer. * Change composite batching to create render target per tree layer. * Refactor stacking context flattening to clean up code. * Add debugging support to freelist. * Handle render target dependencies for composite batching. * Fix framebuffer readback during composite batching. * Fix various warnings. * Change render target allocation strategy (prep work for later). (This passes tests but causes some perf regressions that will be fixed in follow up patches). --- res/blend.fs.glsl | 5 +- res/blend.vs.glsl | 2 +- src/aabbtree.rs | 14 +- src/batch.rs | 85 ++- src/device.rs | 6 +- src/frame.rs | 1320 +++++++++++++++++++----------------- src/freelist.rs | 17 + src/internal_types.rs | 108 ++- src/layer.rs | 3 +- src/lib.rs | 2 - src/node_compiler.rs | 213 +++--- src/platform/linux/font.rs | 4 +- src/render_backend.rs | 41 +- src/renderer.rs | 859 +++++++++++------------ src/resource_cache.rs | 22 +- src/texture_cache.rs | 113 +-- 16 files changed, 1401 insertions(+), 1413 deletions(-) diff --git a/res/blend.fs.glsl b/res/blend.fs.glsl index 5f06b66af1..f2b201a0d2 100644 --- a/res/blend.fs.glsl +++ b/res/blend.fs.glsl @@ -141,9 +141,8 @@ vec3 Luminosity(vec3 Cb, vec3 Cs) { void main(void) { - vec2 texCoord = vColorTexCoord.xy; - vec3 Cs = Texture(sDiffuse, texCoord).xyz; - vec3 Cb = Texture(sMask, texCoord).xyz; + vec3 Cs = Texture(sDiffuse, vColorTexCoord).xyz; + vec3 Cb = Texture(sMask, vMaskTexCoord).xyz; // TODO: Relies on the ordering of MixBlendMode enum! // TODO: May be best to have separate shaders (esp. on Tegra) diff --git a/res/blend.vs.glsl b/res/blend.vs.glsl index a8e214d2e0..405c7184bb 100644 --- a/res/blend.vs.glsl +++ b/res/blend.vs.glsl @@ -1,6 +1,6 @@ void main(void) { vColorTexCoord = aColorTexCoord; - vMaskTexCoord = aMaskTexCoord; + vMaskTexCoord = aMaskTexCoord / 65535.0; gl_Position = uTransform * vec4(aPosition, 1.0); } diff --git a/src/aabbtree.rs b/src/aabbtree.rs index fe89685f4b..11a398d69d 100644 --- a/src/aabbtree.rs +++ b/src/aabbtree.rs @@ -84,20 +84,18 @@ pub struct AABBTree { } impl AABBTree { - pub fn new(split_size: f32) -> AABBTree { - AABBTree { + pub fn new(split_size: f32, scene_rect: &Rect) -> AABBTree { + let mut tree = AABBTree { nodes: Vec::new(), split_size: split_size, work_node_indices: Vec::new(), - } - } - - pub fn init(&mut self, scene_rect: &Rect) { - self.nodes.clear(); + }; let root_node = AABBTreeNode::new(scene_rect, NodeIndex(0)); - self.nodes.push(root_node); + tree.nodes.push(root_node); + + tree } #[allow(dead_code)] diff --git a/src/batch.rs b/src/batch.rs index c08a904ce9..70654d2153 100644 --- a/src/batch.rs +++ b/src/batch.rs @@ -1,10 +1,6 @@ use device::{ProgramId, TextureId}; -use fnv::FnvHasher; -use internal_types::{AxisDirection, CompositeBatchInfo, CompositeInfo, CompositionOp}; +use internal_types::{AxisDirection}; 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}; use std::u16; @@ -205,7 +201,7 @@ impl<'a> BatchBuilder<'a> { vertex.tile_params_index = tile_params_index; } - self.vertex_buffer.vertices.push_all(vertices); + self.vertex_buffer.vertices.extend_from_slice(vertices); // TODO(gw): Handle exceeding u16 index buffer! debug_assert!(self.vertex_buffer.vertices.len() < 65535); @@ -249,6 +245,7 @@ impl RasterBatch { blur_direction == self.blur_direction && dest_texture_id == self.dest_texture_id && color_texture_id == self.color_texture_id; +/* println!("batch ok? {:?} program_id={:?}/{:?} blur_direction={:?}/{:?} \ dest_texture_id {:?}/{:?} color_texture_id={:?}/{:?}", batch_ok, @@ -256,6 +253,7 @@ impl RasterBatch { blur_direction, self.blur_direction, dest_texture_id, self.dest_texture_id, color_texture_id, self.color_texture_id); +*/ batch_ok } @@ -277,50 +275,69 @@ impl RasterBatch { self.indices.push(index_base + 1); } - self.vertices.push_all(vertices); + self.vertices.extend_from_slice(vertices); + } +} + +/* +#[derive(Debug)] +pub struct CompositeBatch { + op: CompositionOp, + // TODO(gw): Convert these to a vertex buffer in backend thread... + rects: Vec>, +} + +impl CompositeBatch { + fn new(op: CompositionOp) -> CompositeBatch { + CompositeBatch { + op: op, + rects: Vec::new(), + } + } + + fn can_add_to_batch(&self, op: CompositionOp) -> bool { + self.op == op + } + + fn add_composite_item(&mut self, rect: Rect) { + self.rects.push(rect); } } /// A batch builder for composition operations. #[derive(Debug)] pub struct CompositeBatchBuilder { - batches: HashMap>, + batches: Vec, } impl CompositeBatchBuilder { pub fn new() -> CompositeBatchBuilder { CompositeBatchBuilder { - batches: HashMap::with_hash_state(Default::default()), + batches: Vec::new(), } } - 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()], - }); + pub fn finalize(self) -> Vec { + self.batches + } + + pub fn add_composite_item(&mut self, + op: CompositionOp, + rect: Rect) { + let need_new_batch = match self.batches.last_mut() { + Some(batch) => { + !batch.can_add_to_batch(op) + } + None => { + true } - Entry::Occupied(entry) => entry.into_mut().jobs.push((*job).clone()), + }; + + if need_new_batch { + self.batches.push(CompositeBatch::new(op)); } - } - /// FIXME(pcwalton): Very inefficient. - pub fn batches(&self) -> Vec { - self.batches.iter().map(|(_, batch)| (*batch).clone()).collect() + self.batches.last_mut().unwrap().add_composite_item(rect); } } - -/// 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, -} - +*/ \ No newline at end of file diff --git a/src/device.rs b/src/device.rs index 4e17a2da59..7dd6ee4271 100644 --- a/src/device.rs +++ b/src/device.rs @@ -468,7 +468,7 @@ impl Device { let id = gl::create_shader(shader_type); let mut source = Vec::new(); - source.push_all(s.as_bytes()); + source.extend_from_slice(s.as_bytes()); gl::shader_source(id, &[&source[..]]); gl::compile_shader(id); if gl::get_shader_iv(id, gl::COMPILE_STATUS) == (0 as gl::GLint) { @@ -483,7 +483,7 @@ impl Device { self.inside_frame = true; // Retrive the currently set FBO. - let mut default_fbo = gl::get_integer_v(gl::FRAMEBUFFER_BINDING); + let default_fbo = gl::get_integer_v(gl::FRAMEBUFFER_BINDING); self.default_fbo = default_fbo as gl::GLuint; // Texture state @@ -1136,6 +1136,8 @@ impl Device { } pub fn end_frame(&mut self) { + self.bind_render_target(None); + debug_assert!(self.inside_frame); self.inside_frame = false; diff --git a/src/frame.rs b/src/frame.rs index 59ca2a86dc..d9a73c71b9 100644 --- a/src/frame.rs +++ b/src/frame.rs @@ -1,16 +1,17 @@ use app_units::Au; -use batch::{CompositeBatchBuilder, MAX_MATRICES_PER_BATCH}; -use device::{TextureId}; +use batch::MAX_MATRICES_PER_BATCH; +use device::{TextureId, TextureFilter}; use euclid::{Rect, Point2D, Size2D, Matrix4}; use fnv::FnvHasher; use internal_types::{AxisDirection, LowLevelFilterOp, CompositionOp, DrawListItemIndex}; -use internal_types::{BatchUpdateList, CompositeBatchInfo, DrawListId}; +use internal_types::{BatchUpdateList, RenderTargetIndex, DrawListId}; +use internal_types::{CompositeBatchInfo, CompositeBatchJob}; use internal_types::{RendererFrame, StackingContextInfo, BatchInfo, DrawCall, StackingContextIndex}; use internal_types::{ANGLE_FLOAT_TO_FIXED, BatchUpdate, BatchUpdateOp, DrawLayer}; -use internal_types::{DrawCommand, ClearInfo, CompositeInfo, FrameRenderTarget}; -use internal_types::{RenderTargetTexture}; +use internal_types::{DrawCommand, ClearInfo, DrawTargetInfo}; use layer::Layer; use node_compiler::NodeCompiler; +use renderer::CompositionOpHelpers; use resource_cache::ResourceCache; use resource_list::BuildRequiredResources; use scene::{SceneStackingContext, ScenePipeline, Scene, SceneItem, SpecificSceneItem}; @@ -19,19 +20,24 @@ use std::collections::HashMap; use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::collections::hash_state::DefaultState; use std::mem; +use texture_cache::TexturePage; use util; 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); +struct FlattenContext<'a> { + resource_cache: &'a mut ResourceCache, + scene: &'a Scene, + old_layer_offsets: HashMap, DefaultState>, + scene_rect: Rect, + pipeline_sizes: &'a mut HashMap>, + //device_pixel_ratio: f32, + pipeline_epoch_map: &'a mut HashMap>, +} #[derive(Debug)] -pub struct DrawListBatchInfo { +struct DrawListBatchInfo { pub scroll_layer_id: ScrollLayerId, pub draw_lists: Vec, } @@ -43,72 +49,292 @@ pub enum FrameRenderItem { DrawListBatch(DrawListBatchInfo), } -#[derive(Debug)] -pub struct FrameRenderTargetGroup { - pub composite_batch_builder: CompositeBatchBuilder, - pub items: Vec, - render_targets: Vec, - render_target_stack: Vec, - texture_id: Option, - current_batch: Option, +enum CurrentBatch { + Draw(DrawListBatchInfo), + Composite(CompositeBatchInfo), +} + +pub struct RenderTarget { + // Draw context state + stacking_context_info: Vec, + + // Display items in culling trees + // TODO(gw) make private + pub layers: HashMap>, + + // Child render targets + children: Vec, + + // Batch building + current_batch: Option, + + // Outputs + items: Vec, + + // Texture id for any child render targets to use + child_texture_id: Option, + + size: Size2D, } -impl FrameRenderTargetGroup { - pub fn new() -> FrameRenderTargetGroup { - FrameRenderTargetGroup { - render_targets: vec![], - render_target_stack: vec![], - composite_batch_builder: CompositeBatchBuilder::new(), - items: vec![], - texture_id: None, +impl RenderTarget { + fn new(size: Size2D) -> RenderTarget { + RenderTarget { + layers: HashMap::with_hash_state(Default::default()), + children: Vec::new(), + stacking_context_info: Vec::new(), current_batch: None, + items: Vec::new(), + child_texture_id: None, + size: size, } } - 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 + fn cull(&mut self, viewport_rect: &Rect) { + for (_, layer) in &mut self.layers { + layer.cull(&viewport_rect); } - 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, + for child in &mut self.children { + child.cull(viewport_rect); } + + //println!("todo - vis cull on child render targets"); + //println!("todo - vis cull on layers via rect!"); } - 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); + fn update_resource_lists(&mut self, + resource_cache: &ResourceCache, + thread_pool: &mut scoped_threadpool::Pool) { + for (_, layer) in &mut self.layers { + let nodes = &mut layer.aabb_tree.nodes; + + thread_pool.scoped(|scope| { + for node in nodes { + if node.is_visible && node.compiled_node.is_none() { + scope.execute(move || { + node.build_resource_list(resource_cache); + }); + } + } + }); + } + + for child in &mut self.children { + child.update_resource_lists(resource_cache, thread_pool); + } } - /// 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 update_texture_cache_and_build_raster_jobs(&mut self, + resource_cache: &mut ResourceCache) { + let _pf = util::ProfileScope::new(" update_texture_cache_and_build_raster_jobs"); + + for (_, layer) in &self.layers { + for node in &layer.aabb_tree.nodes { + if node.is_visible { + let resource_list = node.resource_list.as_ref().unwrap(); + resource_cache.add_resource_list(resource_list); + } + } + } + + for child in &mut self.children { + child.update_texture_cache_and_build_raster_jobs(resource_cache); + } } - fn current_render_target(&self) -> FrameRenderTarget { - self.render_targets[self.render_target_stack.last().unwrap().0 as usize].clone() + fn compile_visible_nodes(&mut self, + thread_pool: &mut scoped_threadpool::Pool, + resource_cache: &ResourceCache, + device_pixel_ratio: f32) { + let layers = &mut self.layers; + let items = &self.items; + let stacking_context_info = &self.stacking_context_info; + + thread_pool.scoped(|scope| { + for (_, layer) in layers { + let nodes = &mut layer.aabb_tree.nodes; + for node in nodes { + if node.is_visible && node.compiled_node.is_none() { + scope.execute(move || { + node.compile(resource_cache, + items, + device_pixel_ratio, + stacking_context_info); + }); + } + } + } + }); + + for child in &mut self.children { + child.compile_visible_nodes(thread_pool, + resource_cache, + device_pixel_ratio); + } } - fn finalize(&mut self) { - self.flush(); + fn update_batch_cache(&mut self, pending_updates: &mut BatchUpdateList) { + // Allocate and update VAOs + for (_, layer) in &mut self.layers { + for node in &mut layer.aabb_tree.nodes { + if node.is_visible { + let compiled_node = node.compiled_node.as_mut().unwrap(); + if let Some(vertex_buffer) = compiled_node.vertex_buffer.take() { + debug_assert!(compiled_node.vertex_buffer_id.is_none()); + + pending_updates.push(BatchUpdate { + id: vertex_buffer.id, + op: BatchUpdateOp::Create(vertex_buffer.vertices, + vertex_buffer.indices), + }); + + compiled_node.vertex_buffer_id = Some(vertex_buffer.id); + } + } + } + } + + for child in &mut self.children { + child.update_batch_cache(pending_updates); + } } - fn flush(&mut self) { - if let Some(batch) = self.current_batch.take() { - self.items.push(FrameRenderItem::DrawListBatch(batch)); + fn collect_and_sort_visible_batches(&mut self, + resource_cache: &mut ResourceCache, + device_pixel_ratio: f32) -> DrawLayer { + let mut commands = vec![]; + for item in &self.items { + match item { + &FrameRenderItem::Clear(ref info) => { + commands.push(DrawCommand::Clear(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()]; + + // 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 StackingContextIndex(stacking_context_id) = draw_list.stacking_context_index.unwrap(); + let context = &self.stacking_context_info[stacking_context_id]; + let mut transform = context.world_transform; + transform = transform.translate(layer.scroll_offset.x, + layer.scroll_offset.y, + 0.0); + matrix_palette[index] = transform + } + + let mut batch_info = BatchInfo::new(matrix_palette); + + // Collect relevant draws from each node in the tree. + for node in &layer.aabb_tree.nodes { + if node.is_visible { + debug_assert!(node.compiled_node.is_some()); + let compiled_node = node.compiled_node.as_ref().unwrap(); + + let batch_list = compiled_node.batch_list.iter().find(|batch_list| { + batch_list.first_draw_list_id == first_draw_list_id + }); + + if let Some(batch_list) = batch_list { + let vertex_buffer_id = compiled_node.vertex_buffer_id.unwrap(); + + for batch in &batch_list.batches { + batch_info.draw_calls.push(DrawCall { + tile_params: batch.tile_params.clone(), // TODO(gw): Move this instead? + vertex_buffer_id: vertex_buffer_id, + color_texture_id: batch.color_texture_id, + mask_texture_id: batch.mask_texture_id, + first_vertex: batch.first_vertex, + index_count: batch.index_count, + }); + } + } + } + } + + // Finally, add the batch + draw calls + commands.push(DrawCommand::Batch(batch_info)); + } + } } - for batch in self.composite_batch_builder.batches() { - self.items.push(FrameRenderItem::CompositeBatch(batch)); + + let mut child_layers = Vec::new(); + + let draw_target_info = if self.children.is_empty() { + None + } else { + let texture_size = 2048; + let device_pixel_size = texture_size * device_pixel_ratio as u32; + + // TODO(gw): This doesn't handle not having enough space to store + // draw all child render targets. However, this will soon + // be changing to do the RT allocation in a smarter way + // that greatly reduces the # of active RT allocations. + // When that happens, ensure it handles this case! + if let Some(child_texture_id) = self.child_texture_id.take() { + resource_cache.free_render_target(child_texture_id); + } + + self.child_texture_id = Some(resource_cache.allocate_render_target(device_pixel_size, + device_pixel_size, + ImageFormat::RGBA8)); + + // TODO(gw): Move this texture page allocator based on the suggested changes above. + let mut page = TexturePage::new(self.child_texture_id.unwrap(), texture_size); + + for child in &mut self.children { + let mut child_layer = child.collect_and_sort_visible_batches(resource_cache, + device_pixel_ratio); + + child_layer.layer_origin = page.allocate(&child_layer.layer_size, + TextureFilter::Linear).unwrap(); + child_layers.push(child_layer); + } + + Some(DrawTargetInfo { + size: Size2D::new(texture_size, texture_size), + texture_id: self.child_texture_id.unwrap(), + }) + }; + + DrawLayer::new(draw_target_info, + child_layers, + commands, + self.size) + } + + fn reset(&mut self, + pending_updates: &mut BatchUpdateList, + resource_cache: &mut ResourceCache, + old_layer_offsets: &mut HashMap, DefaultState>) { + + for (layer_id, mut old_layer) in &mut self.layers.drain() { + old_layer.reset(pending_updates); + old_layer_offsets.insert(layer_id, old_layer.scroll_offset); + } + + if let Some(child_texture_id) = self.child_texture_id.take() { + resource_cache.free_render_target(child_texture_id); + } + + for mut child in &mut self.children.drain(..) { + child.reset(pending_updates, + resource_cache, + old_layer_offsets); } + + self.stacking_context_info.clear(); + self.items.clear(); + debug_assert!(self.current_batch.is_none()); } fn push_clear(&mut self, clear_info: ClearInfo) { @@ -116,15 +342,63 @@ impl FrameRenderTargetGroup { self.items.push(FrameRenderItem::Clear(clear_info)); } - fn push_composite(&mut self, operation: &CompositionOp, job: &CompositeInfo) { - self.composite_batch_builder.add(operation, job) + fn push_composite(&mut self, + op: CompositionOp, + target: Rect, + render_target_index: RenderTargetIndex) { + let need_new_batch = match self.current_batch { + Some(ref batch) => { + match batch { + &CurrentBatch::Draw(..) => { + true + } + &CurrentBatch::Composite(ref batch) => { + batch.operation != op || op.needs_framebuffer() + } + } + } + None => { + true + } + }; + + if need_new_batch { + self.flush(); + + self.current_batch = Some(CurrentBatch::Composite(CompositeBatchInfo { + operation: op, + jobs: Vec::new(), + })); + } + + // TODO(gw): This seems a little messy - restructure how current batch works! + match self.current_batch.as_mut().unwrap() { + &mut CurrentBatch::Draw(..) => { + unreachable!(); + } + &mut CurrentBatch::Composite(ref mut batch) => { + batch.jobs.push(CompositeBatchJob { + rect: target, + render_target_index: render_target_index + }); + } + } } - 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 || - batch.draw_lists.len() == MAX_MATRICES_PER_BATCH + match batch { + &CurrentBatch::Draw(ref batch) => { + batch.scroll_layer_id != scroll_layer_id || + batch.draw_lists.len() == MAX_MATRICES_PER_BATCH + } + &CurrentBatch::Composite(..) => { + true + } + } } None => { true @@ -134,24 +408,41 @@ impl FrameRenderTargetGroup { if need_new_batch { self.flush(); - self.current_batch = Some(DrawListBatchInfo { + self.current_batch = Some(CurrentBatch::Draw(DrawListBatchInfo { scroll_layer_id: scroll_layer_id, draw_lists: Vec::new(), - }); + })); } - self.current_batch.as_mut().unwrap().draw_lists.push(draw_list_id); + // TODO(gw): This seems a little messy - restructure how current batch works! + match self.current_batch.as_mut().unwrap() { + &mut CurrentBatch::Draw(ref mut batch) => { + batch.draw_lists.push(draw_list_id); + } + &mut CurrentBatch::Composite(..) => { + unreachable!(); + } + } + } + + fn flush(&mut self) { + if let Some(batch) = self.current_batch.take() { + match batch { + CurrentBatch::Draw(batch) => { + self.items.push(FrameRenderItem::DrawListBatch(batch)); + } + CurrentBatch::Composite(batch) => { + self.items.push(FrameRenderItem::CompositeBatch(batch)); + } + } + } } } pub struct Frame { - pub layers: HashMap>, pub pipeline_epoch_map: HashMap>, - pub render_target_groups: Vec, - pub render_target_group_stack: Vec, - pub last_render_target_group: Option, - pub stacking_context_info: Vec, pub pending_updates: BatchUpdateList, + pub root: RenderTarget, } enum SceneItemKind<'a> { @@ -263,10 +554,227 @@ impl<'a> SceneItemKind<'a> { result.extend(outlines); result } + + fn add_items_to_target(&self, + scene_items: &Vec, + target: &mut RenderTarget, + sc_info: StackingContextInfo, + context: &mut FlattenContext) { + let stacking_context_index = StackingContextIndex(target.stacking_context_info.len()); + target.stacking_context_info.push(sc_info.clone()); // TODO(gw): Avoid clone? + + for item in scene_items { + match item.specific { + SpecificSceneItem::DrawList(draw_list_id) => { + target.push_draw_list(draw_list_id, sc_info.scroll_layer); + + let layer = match target.layers.entry(sc_info.scroll_layer) { + Occupied(entry) => { + entry.into_mut() + } + Vacant(entry) => { + let scroll_offset = match context.old_layer_offsets + .get(&sc_info.scroll_layer) { + Some(old_offset) => *old_offset, + None => Point2D::zero(), + }; + + entry.insert(Layer::new(&context.scene_rect, &scroll_offset)) + } + }; + + let draw_list = context.resource_cache.get_draw_list_mut(draw_list_id); + + // Store draw context + draw_list.stacking_context_index = Some(stacking_context_index); + + for (item_index, item) in draw_list.items.iter().enumerate() { + // Node index may already be Some(..). This can occur when a page has iframes + // and a new root stacking context is received. In this case, the node index + // 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 = sc_info.world_transform.transform_rect(&item.rect); + layer.insert(&rect, draw_list_id, item_index); + } + } + SpecificSceneItem::StackingContext(id) => { + let stacking_context = context.scene + .stacking_context_map + .get(&id) + .unwrap(); + + let child = SceneItemKind::StackingContext(stacking_context); + child.flatten(&sc_info, + context, + target); + } + SpecificSceneItem::Iframe(ref iframe_info) => { + let pipeline = context.scene + .pipeline_map + .get(&iframe_info.id); + + context.pipeline_sizes.insert(iframe_info.id, + iframe_info.bounds.size); + + if let Some(pipeline) = pipeline { + let iframe = SceneItemKind::Pipeline(pipeline); + + // TODO(gw): Doesn't handle transforms on iframes yet! + let world_origin = sc_info.world_origin + iframe_info.bounds.origin; + let iframe_transform = Matrix4::identity().translate(world_origin.x, + world_origin.y, + 0.0); + + let overflow = sc_info.local_overflow + .translate(&-sc_info.local_overflow.origin) + .intersection(&iframe_info.bounds); + + if let Some(overflow) = overflow { + let overflow = overflow.translate(&-iframe_info.bounds.origin); + + let iframe_info = StackingContextInfo { + scroll_layer: sc_info.scroll_layer, + world_origin: world_origin, + world_transform: iframe_transform, + local_overflow: overflow, + world_perspective: Matrix4::identity(), + }; + + iframe.flatten(&iframe_info, + context, + target); + } + } + } + } + } + + target.flush(); + } + + pub fn flatten(&self, + parent: &StackingContextInfo, + context: &mut FlattenContext, + target: &mut RenderTarget) { + let _pf = util::ProfileScope::new(" flatten"); + + let stacking_context = match *self { + SceneItemKind::StackingContext(stacking_context) => { + &stacking_context.stacking_context + } + SceneItemKind::Pipeline(pipeline) => { + context.pipeline_epoch_map.insert(pipeline.pipeline_id, pipeline.epoch); + + &context.scene.stacking_context_map + .get(&pipeline.root_stacking_context_id) + .unwrap() + .stacking_context + } + }; + + let this_scroll_layer = match stacking_context.scroll_policy { + ScrollPolicy::Scrollable => { + let scroll_layer = stacking_context.scroll_layer_id.unwrap_or(parent.scroll_layer); + scroll_layer + } + ScrollPolicy::Fixed => { + debug_assert!(stacking_context.scroll_layer_id.is_none()); + ScrollLayerId::fixed_layer() + } + }; + + let overflow = parent.local_overflow + .translate(&-stacking_context.bounds.origin) + .translate(&-stacking_context.overflow.origin) + .intersection(&stacking_context.overflow); + + if let Some(overflow) = overflow { + let scene_items = self.collect_scene_items(&context.scene); + if !scene_items.is_empty() { + + // When establishing a new 3D context, clear Z. This is only needed if there + // are child stacking contexts, otherwise it is a redundant clear. + if stacking_context.establishes_3d_context && + stacking_context.has_stacking_contexts { + target.push_clear(ClearInfo { + clear_color: false, + clear_z: true, + clear_stencil: true, + }); + } + + // TODO: Account for scroll offset with transforms! + let composition_operations = stacking_context.composition_operations(); + if composition_operations.is_empty() { + // Build world space transform + let origin = stacking_context.bounds.origin; + let local_transform = Matrix4::identity().translate(origin.x, origin.y, 0.0) + .mul(&stacking_context.transform); + + let transform = parent.world_perspective.mul(&parent.world_transform) + .mul(&local_transform); + + // Build world space perspective transform + let perspective_transform = Matrix4::identity().translate(origin.x, origin.y, 0.0) + .mul(&stacking_context.perspective) + .translate(-origin.x, -origin.y, 0.0); + + let info = StackingContextInfo { + world_origin: parent.world_origin + origin, + scroll_layer: this_scroll_layer, + local_overflow: overflow, + world_transform: transform, + world_perspective: perspective_transform, + }; + + self.add_items_to_target(&scene_items, + target, + info, + context); + } else { + let render_target_size = Size2D::new(overflow.size.width as u32, + overflow.size.height as u32); + let mut new_target = RenderTarget::new(render_target_size); + + let info = StackingContextInfo { + world_origin: Point2D::zero(), + scroll_layer: this_scroll_layer, + local_overflow: overflow, + world_transform: stacking_context.transform, + world_perspective: stacking_context.perspective, + }; + + // TODO(gw): Handle transforms + composition ops... + let origin = stacking_context.bounds.origin; + let target_origin = Point2D::new(parent.world_origin.x as i32 + origin.x as i32, + parent.world_origin.y as i32 + origin.y as i32); + let target_size = Size2D::new(overflow.size.width as i32, + overflow.size.height as i32); + let target_rect = Rect::new(target_origin, target_size); + let render_target_index = RenderTargetIndex(target.children.len() as u32); + + for composition_operation in composition_operations { + target.push_composite(composition_operation, + target_rect, + render_target_index); + } + + self.add_items_to_target(&scene_items, + &mut new_target, + info, + context); + + target.children.push(new_target); + } + } + } + } } trait StackingContextHelpers { fn needs_composition_operation_for_mix_blend_mode(&self) -> bool; + fn composition_operations(&self) -> Vec; } impl StackingContextHelpers for StackingContext { @@ -290,36 +798,82 @@ impl StackingContextHelpers for StackingContext { MixBlendMode::Luminosity => true, } } + + fn composition_operations(&self) -> Vec { + let mut composition_operations = vec![]; + if self.needs_composition_operation_for_mix_blend_mode() { + composition_operations.push(CompositionOp::MixBlend(self.mix_blend_mode)); + } + for filter in self.filters.iter() { + match *filter { + FilterOp::Blur(radius) => { + composition_operations.push(CompositionOp::Filter(LowLevelFilterOp::Blur( + radius, + AxisDirection::Horizontal))); + composition_operations.push(CompositionOp::Filter(LowLevelFilterOp::Blur( + radius, + AxisDirection::Vertical))); + } + FilterOp::Brightness(amount) => { + composition_operations.push(CompositionOp::Filter( + LowLevelFilterOp::Brightness(Au::from_f32_px(amount)))); + } + FilterOp::Contrast(amount) => { + composition_operations.push(CompositionOp::Filter( + LowLevelFilterOp::Contrast(Au::from_f32_px(amount)))); + } + FilterOp::Grayscale(amount) => { + composition_operations.push(CompositionOp::Filter( + LowLevelFilterOp::Grayscale(Au::from_f32_px(amount)))); + } + FilterOp::HueRotate(angle) => { + composition_operations.push(CompositionOp::Filter( + LowLevelFilterOp::HueRotate(f32::round( + angle * ANGLE_FLOAT_TO_FIXED) as i32))); + } + FilterOp::Invert(amount) => { + composition_operations.push(CompositionOp::Filter( + LowLevelFilterOp::Invert(Au::from_f32_px(amount)))); + } + FilterOp::Opacity(amount) => { + composition_operations.push(CompositionOp::Filter( + LowLevelFilterOp::Opacity(Au::from_f32_px(amount)))); + } + FilterOp::Saturate(amount) => { + composition_operations.push(CompositionOp::Filter( + LowLevelFilterOp::Saturate(Au::from_f32_px(amount)))); + } + FilterOp::Sepia(amount) => { + composition_operations.push(CompositionOp::Filter( + LowLevelFilterOp::Sepia(Au::from_f32_px(amount)))); + } + } + } + + composition_operations + } } impl Frame { - pub fn new() -> Frame { + pub fn new(size: Size2D) -> Frame { Frame { - layers: HashMap::with_hash_state(Default::default()), pipeline_epoch_map: HashMap::with_hash_state(Default::default()), - render_target_groups: Vec::new(), - render_target_group_stack: Vec::new(), - last_render_target_group: None, - stacking_context_info: Vec::new(), pending_updates: BatchUpdateList::new(), + root: RenderTarget::new(size), } } pub fn reset(&mut self, resource_cache: &mut ResourceCache) - -> HashMap> { + -> HashMap, DefaultState> { self.pipeline_epoch_map.clear(); // Free any render targets from last frame. // TODO: This should really re-use existing targets here... - 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); - } - } - } - - mem::replace(&mut self.layers, HashMap::with_hash_state(Default::default())) + let mut old_layer_offsets = HashMap::with_hash_state(Default::default()); + self.root.reset(&mut self.pending_updates, + resource_cache, + &mut old_layer_offsets); + old_layer_offsets } pub fn pending_updates(&mut self) -> BatchUpdateList { @@ -328,7 +882,7 @@ impl Frame { pub fn scroll(&mut self, delta: &Point2D, viewport_size: &Size2D) { // TODO: Select other layers for scrolling! - let layer = self.layers.get_mut(&ScrollLayerId(0)); + let layer = self.root.layers.get_mut(&ScrollLayerId(0)); if let Some(layer) = layer { layer.scroll_offset = layer.scroll_offset + *delta; @@ -347,13 +901,13 @@ impl Frame { pub fn create(&mut self, scene: &Scene, - viewport_size: Size2D, - device_pixel_ratio: f32, + //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 { if let Some(root_pipeline) = scene.pipeline_map.get(&root_pipeline_id) { - let mut old_layers = self.reset(resource_cache); + let old_layer_offsets = self.reset(resource_cache); let root_stacking_context = scene.stacking_context_map .get(&root_pipeline.root_stacking_context_id) @@ -363,360 +917,34 @@ impl Frame { .scroll_layer_id .expect("root layer must be a scroll layer!"); - debug_assert!(self.render_target_group_stack.len() == 0); - self.push_render_target(viewport_size, None); - self.flatten(SceneItemKind::Pipeline(root_pipeline), - &Point2D::zero(), - &Matrix4::identity(), - &Matrix4::identity(), - root_scroll_layer_id, - resource_cache, - &root_stacking_context.stacking_context.overflow, - scene, - &old_layers, - &root_stacking_context.stacking_context.overflow, - pipeline_sizes, - device_pixel_ratio); - - self.pop_render_target(); - 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) { - root_scroll_layer.scroll_boundaries = root_stacking_context.stacking_context.overflow.size; - } - - for (_, old_layer) in &mut old_layers { - old_layer.reset(&mut self.pending_updates) - } - } - } - } - - pub fn flatten(&mut self, - item_kind: SceneItemKind, - parent_offset: &Point2D, - parent_transform: &Matrix4, - parent_perspective: &Matrix4, - parent_scroll_layer: ScrollLayerId, - resource_cache: &mut ResourceCache, - clip_rect: &Rect, - scene: &Scene, - old_layers: &HashMap>, - scene_rect: &Rect, - pipeline_sizes: &mut HashMap>, - device_pixel_ratio: f32) { - let _pf = util::ProfileScope::new(" flatten"); - - let stacking_context = match item_kind { - SceneItemKind::StackingContext(stacking_context) => { - &stacking_context.stacking_context - } - SceneItemKind::Pipeline(pipeline) => { - self.pipeline_epoch_map.insert(pipeline.pipeline_id, pipeline.epoch); - - &scene.stacking_context_map - .get(&pipeline.root_stacking_context_id) - .unwrap() - .stacking_context - } - }; - - let (this_scroll_layer, parent_scroll_layer) = match stacking_context.scroll_policy { - ScrollPolicy::Scrollable => { - let scroll_layer = stacking_context.scroll_layer_id.unwrap_or(parent_scroll_layer); - (scroll_layer, scroll_layer) - } - ScrollPolicy::Fixed => { - debug_assert!(stacking_context.scroll_layer_id.is_none()); - (ScrollLayerId::fixed_layer(), parent_scroll_layer) - } - }; - - // TODO: Account for scroll offset with transforms! - - // Build world space transform - let origin = &stacking_context.bounds.origin; - let child_offset = *parent_offset + *origin; - let local_transform = Matrix4::identity().translate(origin.x, origin.y, 0.0) - .mul(&stacking_context.transform); - - let mut final_transform = parent_perspective.mul(&parent_transform) - .mul(&local_transform); - - // Build world space perspective transform - let perspective_transform = Matrix4::identity().translate(origin.x, origin.y, 0.0) - .mul(&stacking_context.perspective) - .translate(-origin.x, -origin.y, 0.0); - - let overflow = clip_rect.translate(&stacking_context.overflow.origin) - .intersection(&stacking_context.overflow); - - if let Some(overflow) = overflow { - // When establishing a new 3D context, clear Z. This is only needed if there - // are child stacking contexts, otherwise it is a redundant clear. - if stacking_context.establishes_3d_context && - stacking_context.has_stacking_contexts { - self.push_clear(ClearInfo { - clear_color: false, - clear_z: true, - clear_stencil: true, - }); - } - - let mut composition_operations = vec![]; - if stacking_context.needs_composition_operation_for_mix_blend_mode() { - composition_operations.push(CompositionOp::MixBlend(stacking_context.mix_blend_mode)); - } - for filter in stacking_context.filters.iter() { - match *filter { - FilterOp::Blur(radius) => { - composition_operations.push(CompositionOp::Filter(LowLevelFilterOp::Blur( - radius, - AxisDirection::Horizontal))); - composition_operations.push(CompositionOp::Filter(LowLevelFilterOp::Blur( - radius, - AxisDirection::Vertical))); - } - FilterOp::Brightness(amount) => { - composition_operations.push(CompositionOp::Filter( - LowLevelFilterOp::Brightness(Au::from_f32_px(amount)))); - } - FilterOp::Contrast(amount) => { - composition_operations.push(CompositionOp::Filter( - LowLevelFilterOp::Contrast(Au::from_f32_px(amount)))); - } - FilterOp::Grayscale(amount) => { - composition_operations.push(CompositionOp::Filter( - LowLevelFilterOp::Grayscale(Au::from_f32_px(amount)))); - } - FilterOp::HueRotate(angle) => { - composition_operations.push(CompositionOp::Filter( - LowLevelFilterOp::HueRotate(f32::round( - angle * ANGLE_FLOAT_TO_FIXED) as i32))); - } - FilterOp::Invert(amount) => { - composition_operations.push(CompositionOp::Filter( - LowLevelFilterOp::Invert(Au::from_f32_px(amount)))); - } - FilterOp::Opacity(amount) => { - composition_operations.push(CompositionOp::Filter( - LowLevelFilterOp::Opacity(Au::from_f32_px(amount)))); - } - FilterOp::Saturate(amount) => { - composition_operations.push(CompositionOp::Filter( - LowLevelFilterOp::Saturate(Au::from_f32_px(amount)))); - } - FilterOp::Sepia(amount) => { - composition_operations.push(CompositionOp::Filter( - LowLevelFilterOp::Sepia(Au::from_f32_px(amount)))); - } - } - } - - let transform_in_scene_space = final_transform; - for composition_operation in composition_operations.iter() { - 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 i32 + - (stacking_context.overflow.origin.x as f32 / device_pixel_ratio) as i32, - child_offset.y as i32 + - (stacking_context.overflow.origin.y as f32 / device_pixel_ratio) 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 = 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(composition_operation, &CompositeInfo { - rect: Rect::new(origin, size), - render_target_texture: texture, - }); - - self.push_render_target(Size2D::new(size.width as u32, size.height as u32), - Some(texture)); - final_transform = Matrix4::identity(); - } - - let scene_items = item_kind.collect_scene_items(scene); - - let current_render_target = self.current_render_target_group() - .current_render_target(); - - let stacking_context_info = StackingContextInfo { - origin: child_offset, - overflow: overflow, - final_transform: final_transform, - render_target: current_render_target, - }; - - let stacking_context_index = StackingContextIndex(self.stacking_context_info.len()); - self.stacking_context_info.push(stacking_context_info); - - for item in scene_items { - match item.specific { - SpecificSceneItem::DrawList(draw_list_id) => { - self.push_draw_list(draw_list_id, this_scroll_layer); - - let layer = match self.layers.entry(this_scroll_layer) { - Occupied(entry) => { - entry.into_mut() - } - Vacant(entry) => { - let scroll_offset = match old_layers.get(&this_scroll_layer) { - Some(ref old_layer) => old_layer.scroll_offset, - None => Point2D::zero(), - }; - - entry.insert(Layer::new(scene_rect, &scroll_offset)) - } - }; + let parent_info = StackingContextInfo { + world_origin: Point2D::zero(), + scroll_layer: root_scroll_layer_id, + world_perspective: Matrix4::identity(), + world_transform: Matrix4::identity(), + local_overflow: root_stacking_context.stacking_context.overflow, + }; - let draw_list = resource_cache.get_draw_list_mut(draw_list_id); + let mut context = FlattenContext { + resource_cache: resource_cache, + scene: scene, + scene_rect: root_stacking_context.stacking_context.overflow, + old_layer_offsets: old_layer_offsets, + pipeline_epoch_map: &mut self.pipeline_epoch_map, + //device_pixel_ratio: device_pixel_ratio, + pipeline_sizes: pipeline_sizes, + }; - // Store draw context - draw_list.stacking_context_index = Some(stacking_context_index); + let root_pipeline = SceneItemKind::Pipeline(root_pipeline); + root_pipeline.flatten(&parent_info, + &mut context, + &mut self.root); - for (item_index, item) in draw_list.items.iter().enumerate() { - // Node index may already be Some(..). This can occur when a page has iframes - // and a new root stacking context is received. In this case, the node index - // 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 = transform_in_scene_space.transform_rect(&item.rect); - layer.insert(&rect, draw_list_id, item_index); - } - } - SpecificSceneItem::StackingContext(id) => { - let clip_rect = overflow.translate(&-stacking_context.overflow.origin); - - let stacking_context = scene.stacking_context_map - .get(&id) - .unwrap(); - - self.flatten(SceneItemKind::StackingContext(stacking_context), - &child_offset, - &final_transform, - &perspective_transform, - parent_scroll_layer, - resource_cache, - &clip_rect, - scene, - old_layers, - scene_rect, - pipeline_sizes, - device_pixel_ratio); - } - SpecificSceneItem::Iframe(iframe_info) => { - let pipeline = scene.pipeline_map - .get(&iframe_info.id); - - pipeline_sizes.insert(iframe_info.id, - iframe_info.clip.size); - - if let Some(pipeline) = pipeline { - // TODO: Doesn't handle transforms on iframes yet! - let child_offset = child_offset + iframe_info.offset; - - let iframe_transform = Matrix4::identity().translate(child_offset.x, - child_offset.y, - 0.0); - - let clip_rect = overflow.translate(&-stacking_context.overflow.origin) - .intersection(&iframe_info.clip); - - if let Some(clip_rect) = clip_rect { - let clip_rect = clip_rect.translate(&-iframe_info.offset); - self.flatten(SceneItemKind::Pipeline(pipeline), - &child_offset, - &iframe_transform, - &perspective_transform, - parent_scroll_layer, - resource_cache, - &clip_rect, - scene, - old_layers, - scene_rect, - pipeline_sizes, - device_pixel_ratio); - } - } - } + // TODO(gw): This should be moved elsewhere! + if let Some(root_scroll_layer) = self.root.layers.get_mut(&root_scroll_layer_id) { + root_scroll_layer.scroll_boundaries = root_stacking_context.stacking_context.overflow.size; } } - - for _ in composition_operations.iter() { - self.pop_render_target(); - } - } - } - - 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 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_group().push_clear(clear_info); - } - - #[inline] - 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_group().push_draw_list(draw_list_id, scroll_layer_id); - } - - 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) { - 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; } } @@ -730,10 +958,8 @@ impl Frame { let size = Size2D::new(viewport.size.width as f32, viewport.size.height as f32); let viewport_rect = Rect::new(origin, size); - // Traverse layer trees to calculate visible nodes - for (_, layer) in &mut self.layers { - layer.cull(&viewport_rect); - } + // Traverse render targets and layer trees to calculate visible nodes + self.root.cull(&viewport_rect); // Build resource list for newly visible nodes self.update_resource_lists(resource_cache, thread_pool); @@ -754,40 +980,22 @@ impl Frame { self.update_batch_cache(); // Collect the visible batches into a frame - self.collect_and_sort_visible_batches(resource_cache, device_pixel_ratio) + let frame = self.collect_and_sort_visible_batches(resource_cache, device_pixel_ratio); + + frame } pub fn update_resource_lists(&mut self, - resource_cache: &ResourceCache, - thread_pool: &mut scoped_threadpool::Pool) { + resource_cache: &ResourceCache, + thread_pool: &mut scoped_threadpool::Pool) { let _pf = util::ProfileScope::new(" update_resource_lists"); - - for (_, layer) in &mut self.layers { - let nodes = &mut layer.aabb_tree.nodes; - - thread_pool.scoped(|scope| { - for node in nodes { - if node.is_visible && node.compiled_node.is_none() { - scope.execute(move || { - node.build_resource_list(resource_cache); - }); - } - } - }); - } + self.root.update_resource_lists(resource_cache, thread_pool); } - pub fn update_texture_cache_and_build_raster_jobs(&mut self, resource_cache: &mut ResourceCache) { + pub fn update_texture_cache_and_build_raster_jobs(&mut self, + resource_cache: &mut ResourceCache) { let _pf = util::ProfileScope::new(" update_texture_cache_and_build_raster_jobs"); - - for (_, layer) in &self.layers { - for node in &layer.aabb_tree.nodes { - if node.is_visible { - let resource_list = node.resource_list.as_ref().unwrap(); - resource_cache.add_resource_list(resource_list); - } - } - } + self.root.update_texture_cache_and_build_raster_jobs(resource_cache); } pub fn raster_glyphs(&mut self, @@ -798,176 +1006,26 @@ impl Frame { } pub fn compile_visible_nodes(&mut self, - thread_pool: &mut scoped_threadpool::Pool, - resource_cache: &ResourceCache, - device_pixel_ratio: f32) { + thread_pool: &mut scoped_threadpool::Pool, + resource_cache: &ResourceCache, + device_pixel_ratio: f32) { let _pf = util::ProfileScope::new(" compile_visible_nodes"); - - let layers = &mut self.layers; - let render_target_groups = &self.render_target_groups; - let stacking_context_info = &self.stacking_context_info; - - thread_pool.scoped(|scope| { - for (_, layer) in layers { - let nodes = &mut layer.aabb_tree.nodes; - for node in nodes { - if node.is_visible && node.compiled_node.is_none() { - scope.execute(move || { - node.compile(resource_cache, - render_target_groups, - device_pixel_ratio, - stacking_context_info); - }); - } - } - } - }); + self.root.compile_visible_nodes(thread_pool, + resource_cache, + device_pixel_ratio); } pub fn update_batch_cache(&mut self) { - // Allocate and update VAOs - for (_, layer) in &mut self.layers { - for node in &mut layer.aabb_tree.nodes { - if node.is_visible { - let compiled_node = node.compiled_node.as_mut().unwrap(); - if let Some(vertex_buffer) = compiled_node.vertex_buffer.take() { - debug_assert!(compiled_node.vertex_buffer_id.is_none()); - - self.pending_updates.push(BatchUpdate { - id: vertex_buffer.id, - op: BatchUpdateOp::Create(vertex_buffer.vertices, - vertex_buffer.indices), - }); - - compiled_node.vertex_buffer_id = Some(vertex_buffer.id); - } - } - } - } + let _pf = util::ProfileScope::new(" update_batch_cache"); + self.root.update_batch_cache(&mut self.pending_updates); } pub fn collect_and_sort_visible_batches(&mut self, - resource_cache: &ResourceCache, + resource_cache: &mut ResourceCache, device_pixel_ratio: f32) -> RendererFrame { - let mut frame = RendererFrame::new(self.pipeline_epoch_map.clone()); - - 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(); - - let mut commands = vec![]; - for item in &render_target_group.items { - match item { - &FrameRenderItem::Clear(ref info) => { - commands.push(DrawCommand::Clear(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()]; - - // 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 StackingContextIndex(stacking_context_id) = draw_list.stacking_context_index.unwrap(); - let context = &self.stacking_context_info[stacking_context_id]; - 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 - context.overflow.origin.x) / - device_pixel_ratio, - ((render_target_texture.uv_rect.origin.y - - layer_rect.origin.y) as f32 - context.overflow.origin.y) / - device_pixel_ratio, - 0.0); - } - matrix_palette[index] = transform - } - - let mut batch_info = BatchInfo::new(matrix_palette); - - // Collect relevant draws from each node in the tree. - for node in &layer.aabb_tree.nodes { - if node.is_visible { - debug_assert!(node.compiled_node.is_some()); - let compiled_node = node.compiled_node.as_ref().unwrap(); - - let batch_list = compiled_node.batch_list.iter().find(|batch_list| { - batch_list.first_draw_list_id == first_draw_list_id - }); - - if let Some(batch_list) = batch_list { - let vertex_buffer_id = compiled_node.vertex_buffer_id.unwrap(); - - for batch in &batch_list.batches { - batch_info.draw_calls.push(DrawCall { - tile_params: batch.tile_params.clone(), // TODO(gw): Move this instead? - vertex_buffer_id: vertex_buffer_id, - color_texture_id: batch.color_texture_id, - mask_texture_id: batch.mask_texture_id, - first_vertex: batch.first_vertex, - index_count: batch.index_count, - }); - } - } - } - } - - // Finally, add the batch + draw calls - commands.push(DrawCommand::Batch(batch_info)); - } - } - } - - 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); - } - - frame + let root_layer = self.root.collect_and_sort_visible_batches(resource_cache, + device_pixel_ratio); + RendererFrame::new(self.pipeline_epoch_map.clone(), root_layer) } } diff --git a/src/freelist.rs b/src/freelist.rs index 2e6da647d0..390756b8d0 100644 --- a/src/freelist.rs +++ b/src/freelist.rs @@ -52,12 +52,29 @@ impl FreeList { } } + #[allow(dead_code)] + fn assert_not_in_free_list(&self, id: FreeListItemId) { + let FreeListItemId(id) = id; + let mut next_free_id = self.first_free_index; + + while let Some(free_id) = next_free_id { + let FreeListItemId(index) = free_id; + assert!(index != id); + let free_item = &self.items[index as usize]; + next_free_id = free_item.next_free_id(); + } + } + pub fn get(&self, id: FreeListItemId) -> &T { + //self.assert_not_in_free_list(id); + let FreeListItemId(index) = id; &self.items[index as usize] } pub fn get_mut(&mut self, id: FreeListItemId) -> &mut T { + //self.assert_not_in_free_list(id); + let FreeListItemId(index) = id; &mut self.items[index as usize] } diff --git a/src/internal_types.rs b/src/internal_types.rs index d30b88f796..1efbd4cc7d 100644 --- a/src/internal_types.rs +++ b/src/internal_types.rs @@ -11,7 +11,7 @@ use std::sync::Arc; use texture_cache::TextureCacheItem; use util::{self, RectVaryings, VaryingElement}; use webrender_traits::{FontKey, Epoch, ColorF, PipelineId}; -use webrender_traits::{ImageFormat}; +use webrender_traits::{ImageFormat, ScrollLayerId}; use webrender_traits::{ComplexClipRegion, MixBlendMode, NativeFontHandle, DisplayItem}; const UV_FLOAT_TO_FIXED: f32 = 65535.0; @@ -323,19 +323,6 @@ pub struct ClearInfo { pub clear_stencil: bool, } -#[derive(Clone, Debug)] -pub struct CompositeBatchInfo { - pub operation: CompositionOp, - pub texture_id: TextureId, - pub jobs: Vec, -} - -#[derive(Clone, Debug)] -pub struct CompositeInfo { - pub rect: Rect, - pub render_target_texture: RenderTargetTexture, -} - #[derive(Clone, Debug)] pub struct DrawCall { pub tile_params: Vec, @@ -361,6 +348,18 @@ impl BatchInfo { } } +#[derive(Debug, Clone)] +pub struct CompositeBatchJob { + pub rect: Rect, + pub render_target_index: RenderTargetIndex, +} + +#[derive(Debug, Clone)] +pub struct CompositeBatchInfo { + pub operation: CompositionOp, + pub jobs: Vec, +} + #[derive(Clone, Debug)] pub enum DrawCommand { Batch(BatchInfo), @@ -372,38 +371,50 @@ pub enum DrawCommand { pub struct RenderTargetIndex(pub u32); #[derive(Debug)] -pub struct DrawLayer { +pub struct DrawTargetInfo { + pub texture_id: TextureId, pub size: Size2D, - pub texture_id: Option, - pub render_targets: Vec, +} + +#[derive(Debug)] +pub struct DrawLayer { + // This layer pub commands: Vec, + pub layer_origin: Point2D, + pub layer_size: Size2D, + + // Children + pub child_target: Option, + pub child_layers: Vec, } impl DrawLayer { - pub fn new(size: &Size2D, - texture_id: Option, - render_targets: Vec, - commands: Vec) + pub fn new(child_target: Option, + child_layers: Vec, + commands: Vec, + size: Size2D) -> DrawLayer { DrawLayer { - size: *size, - texture_id: texture_id, - render_targets: render_targets, + child_target: child_target, commands: commands, + child_layers: child_layers, + layer_origin: Point2D::zero(), + layer_size: size, } } } pub struct RendererFrame { pub pipeline_epoch_map: HashMap>, - pub layers: Vec, + pub root_layer: DrawLayer, } impl RendererFrame { - pub fn new(pipeline_epoch_map: HashMap>) -> RendererFrame { + pub fn new(pipeline_epoch_map: HashMap>, + root_layer: DrawLayer) -> RendererFrame { RendererFrame { pipeline_epoch_map: pipeline_epoch_map, - layers: Vec::new(), + root_layer: root_layer, } } } @@ -516,27 +527,16 @@ pub enum AxisDirection { #[derive(Debug, Clone, Copy)] pub struct StackingContextIndex(pub usize); -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct StackingContextInfo { - pub origin: Point2D, - pub overflow: Rect, - pub final_transform: Matrix4, - pub render_target: FrameRenderTarget, -} + pub world_origin: Point2D, -#[derive(Debug, Clone)] -pub struct FrameRenderTarget { - pub size: Size2D, - pub texture: Option, -} + pub local_overflow: Rect, -impl FrameRenderTarget { - pub fn new(size: Size2D, texture: Option) -> FrameRenderTarget { - FrameRenderTarget { - size: size, - texture: texture, - } - } + pub world_transform: Matrix4, + pub world_perspective: Matrix4, + + pub scroll_layer: ScrollLayerId, } #[derive(Debug)] @@ -578,6 +578,7 @@ pub enum Primitive { Glyphs, // font glyphs (some platforms may specialize shader) } +#[derive(Debug)] pub struct BatchList { pub batches: Vec, pub first_draw_list_id: DrawListId, @@ -697,6 +698,7 @@ impl RectUv { } } +/* pub fn to_rect(&self) -> Rect { fn min(x: f32, y: f32) -> f32 { if x < y { x } else { y } @@ -714,7 +716,7 @@ impl RectUv { 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)] @@ -1028,17 +1030,3 @@ 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/layer.rs b/src/layer.rs index 4440c9cf44..3be31d476a 100644 --- a/src/layer.rs +++ b/src/layer.rs @@ -12,8 +12,7 @@ pub struct Layer { impl Layer { pub fn new(scene_rect: &Rect, scroll_offset: &Point2D) -> Layer { - let mut aabb_tree = AABBTree::new(1024.0); - aabb_tree.init(scene_rect); + let aabb_tree = AABBTree::new(1024.0, scene_rect); Layer { aabb_tree: aabb_tree, diff --git a/src/lib.rs b/src/lib.rs index a062e3e9ec..833b966769 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,4 @@ -#![feature(drain)] #![feature(hashmap_hasher)] -#![feature(vec_push_all)] #![feature(step_by, convert, zero_one)] #[macro_use] diff --git a/src/node_compiler.rs b/src/node_compiler.rs index 66dd6eb318..1719dc11b0 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, FrameRenderTargetGroup}; +use frame::FrameRenderItem; use internal_types::{DrawListItemIndex, CompiledNode, StackingContextInfo}; use internal_types::{CombinedClipRegion, BatchList, StackingContextIndex}; use resource_cache::ResourceCache; @@ -10,7 +10,7 @@ use webrender_traits::SpecificDisplayItem; pub trait NodeCompiler { fn compile(&mut self, resource_cache: &ResourceCache, - render_target_groups: &Vec, + render_items: &Vec, device_pixel_ratio: f32, stacking_context_info: &Vec); } @@ -18,7 +18,7 @@ pub trait NodeCompiler { impl NodeCompiler for AABBTreeNode { fn compile(&mut self, resource_cache: &ResourceCache, - render_target_groups: &Vec, + render_items: &Vec, device_pixel_ratio: f32, stacking_context_info: &Vec) { let mut compiled_node = CompiledNode::new(); @@ -26,127 +26,126 @@ impl NodeCompiler for AABBTreeNode { let mut clip_buffers = ClipBuffers::new(); - for render_target_group in render_target_groups { - for item in &render_target_group.items { - match item { - &FrameRenderItem::Clear(..) | - &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); + for item in render_items { + match item { + &FrameRenderItem::Clear(..) => {} + &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); - for (index, draw_list_id) in batch_info.draw_lists.iter().enumerate() { - let draw_list_id = *draw_list_id; - let matrix_index = MatrixIndex(index as u8); + for (index, draw_list_id) in batch_info.draw_lists.iter().enumerate() { + let draw_list_id = *draw_list_id; + let matrix_index = MatrixIndex(index as u8); - let draw_list_index_buffer = self.draw_lists.iter().find(|draw_list| { - draw_list.draw_list_id == draw_list_id - }); + let draw_list_index_buffer = self.draw_lists.iter().find(|draw_list| { + draw_list.draw_list_id == draw_list_id + }); - if let Some(draw_list_index_buffer) = draw_list_index_buffer { - let draw_list = resource_cache.get_draw_list(draw_list_id); + if let Some(draw_list_index_buffer) = draw_list_index_buffer { + let draw_list = resource_cache.get_draw_list(draw_list_id); - for index in &draw_list_index_buffer.indices { - let DrawListItemIndex(index) = *index; - let display_item = &draw_list.items[index as usize]; + let StackingContextIndex(stacking_context_id) = draw_list.stacking_context_index.unwrap(); + let context = &stacking_context_info[stacking_context_id]; - let StackingContextIndex(stacking_context_id) = draw_list.stacking_context_index.unwrap(); - let context = &stacking_context_info[stacking_context_id]; - let clip_rect = display_item.clip.main.intersection(&context.overflow); - let clip_rect = clip_rect.and_then(|clip_rect| { - let split_rect_local_space = self.split_rect.translate(&-context.origin); - clip_rect.intersection(&split_rect_local_space) - }); + for index in &draw_list_index_buffer.indices { + let DrawListItemIndex(index) = *index; + let display_item = &draw_list.items[index as usize]; - if let Some(ref clip_rect) = clip_rect { - let mut clip = CombinedClipRegion::from_clip_in_rect_and_stack( - clip_rect, - &display_item.clip.complex[..]); + let clip_rect = display_item.clip.main.intersection(&context.local_overflow); + let clip_rect = clip_rect.and_then(|clip_rect| { + let split_rect_local_space = self.split_rect.translate(&-context.world_origin); + clip_rect.intersection(&split_rect_local_space) + }); - match display_item.item { - SpecificDisplayItem::WebGL(ref info) => { - builder.add_webgl_rectangle(matrix_index, - &display_item.rect, - &clip, - resource_cache, - &mut clip_buffers, - &info.context_id); - } - SpecificDisplayItem::Image(ref info) => { - builder.add_image(matrix_index, - &display_item.rect, - &clip, - &info.stretch_size, - info.image_key, - info.image_rendering, - resource_cache, - &mut clip_buffers); - } - SpecificDisplayItem::Text(ref info) => { - builder.add_text(matrix_index, - &display_item.rect, - &clip, - info.font_key, - info.size, - info.blur_radius, - &info.color, - &info.glyphs, - resource_cache, - &mut clip_buffers, - device_pixel_ratio); - } - SpecificDisplayItem::Rectangle(ref info) => { - builder.add_color_rectangle(matrix_index, - &display_item.rect, - &clip, - resource_cache, - &mut clip_buffers, - &info.color); - } - SpecificDisplayItem::Gradient(ref info) => { - clip.clip_in_rect(&display_item.rect); - builder.add_gradient(matrix_index, - &clip, - &info.start_point, - &info.end_point, - &info.stops, - resource_cache, - &mut clip_buffers); - } - SpecificDisplayItem::BoxShadow(ref info) => { - builder.add_box_shadow(matrix_index, - &info.box_bounds, - &clip, - &info.offset, - &info.color, - info.blur_radius, - info.spread_radius, - info.border_radius, - info.clip_mode, - resource_cache, - &mut clip_buffers); - } - SpecificDisplayItem::Border(ref info) => { - builder.add_border(matrix_index, - &display_item.rect, + if let Some(ref clip_rect) = clip_rect { + let mut clip = CombinedClipRegion::from_clip_in_rect_and_stack( + clip_rect, + &display_item.clip.complex[..]); + + match display_item.item { + SpecificDisplayItem::WebGL(ref info) => { + builder.add_webgl_rectangle(matrix_index, + &display_item.rect, + &clip, + resource_cache, + &mut clip_buffers, + &info.context_id); + } + SpecificDisplayItem::Image(ref info) => { + builder.add_image(matrix_index, + &display_item.rect, + &clip, + &info.stretch_size, + info.image_key, + info.image_rendering, + resource_cache, + &mut clip_buffers); + } + SpecificDisplayItem::Text(ref info) => { + builder.add_text(matrix_index, + &display_item.rect, + &clip, + info.font_key, + info.size, + info.blur_radius, + &info.color, + &info.glyphs, + resource_cache, + &mut clip_buffers, + device_pixel_ratio); + } + SpecificDisplayItem::Rectangle(ref info) => { + builder.add_color_rectangle(matrix_index, + &display_item.rect, + &clip, + resource_cache, + &mut clip_buffers, + &info.color); + } + SpecificDisplayItem::Gradient(ref info) => { + clip.clip_in_rect(&display_item.rect); + builder.add_gradient(matrix_index, &clip, - info, + &info.start_point, + &info.end_point, + &info.stops, resource_cache, &mut clip_buffers); - } + } + SpecificDisplayItem::BoxShadow(ref info) => { + builder.add_box_shadow(matrix_index, + &info.box_bounds, + &clip, + &info.offset, + &info.color, + info.blur_radius, + info.spread_radius, + info.border_radius, + info.clip_mode, + resource_cache, + &mut clip_buffers); + } + SpecificDisplayItem::Border(ref info) => { + builder.add_border(matrix_index, + &display_item.rect, + &clip, + info, + resource_cache, + &mut clip_buffers); } } } } } + } - let batches = builder.finalize(); + let batches = builder.finalize(); - compiled_node.batch_list.push(BatchList { - batches: batches, - first_draw_list_id: *batch_info.draw_lists.first().unwrap(), - }); - } + compiled_node.batch_list.push(BatchList { + batches: batches, + first_draw_list_id: *batch_info.draw_lists.first().unwrap(), + }); } } } diff --git a/src/platform/linux/font.rs b/src/platform/linux/font.rs index 5a5eeab451..3982943540 100644 --- a/src/platform/linux/font.rs +++ b/src/platform/linux/font.rs @@ -120,7 +120,7 @@ impl FontContext { // Convert to RGBA. for &byte in buffer.iter() { - final_buffer.push_all(&[ 0xff, 0xff, 0xff, byte ]); + final_buffer.extend_from_slice(&[ 0xff, 0xff, 0xff, byte ]); } } else { // This is not exactly efficient... but it's only used by the @@ -136,7 +136,7 @@ impl FontContext { } else { 0 }; - final_buffer.push_all(&[ 0xff, 0xff, 0xff, byte_value ]); + final_buffer.extend_from_slice(&[ 0xff, 0xff, 0xff, byte_value ]); } } } diff --git a/src/render_backend.rs b/src/render_backend.rs index 0ca611f52f..27513f89fb 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, FrameRenderTarget, ResultMsg, DrawLayer, RendererFrame}; +use internal_types::{FontTemplate, ResultMsg, RendererFrame}; use ipc_channel::ipc::IpcReceiver; use profiler::BackendProfileCounters; use resource_cache::ResourceCache; @@ -10,8 +10,8 @@ use std::collections::HashMap; use std::sync::{Arc, Mutex}; use std::sync::mpsc::Sender; use texture_cache::{TextureCache, TextureCacheItemId}; -use webrender_traits::{ApiMsg, IdNamespace, RenderNotifier, ScrollLayerId}; -use webrender_traits::WebGLContextId; +use webrender_traits::{ApiMsg, IdNamespace, RenderNotifier}; +use webrender_traits::{WebGLContextId, ScrollLayerId}; use batch::new_id; use device::TextureId; use offscreen_gl_context::{NativeGLContext, GLContext, ColorAttachmentType, NativeGLContextMethods, NativeGLContextHandle}; @@ -55,6 +55,8 @@ impl RenderBackend { dummy_mask_image_id, device_pixel_ratio, enable_aa); + let viewport_size = Size2D::new(viewport.size.width as u32, + viewport.size.height as u32); let backend = RenderBackend { thread_pool: thread_pool, @@ -64,7 +66,7 @@ impl RenderBackend { device_pixel_ratio: device_pixel_ratio, resource_cache: resource_cache, scene: Scene::new(), - frame: Frame::new(), + frame: Frame::new(viewport_size), next_namespace_id: IdNamespace(1), notifier: notifier, webrender_context_handle: webrender_context_handle, @@ -175,7 +177,7 @@ impl RenderBackend { ApiMsg::TranslatePointToLayerSpace(point, tx) => { // TODO(pcwalton): Select other layers for mouse events. let point = point / self.device_pixel_ratio; - match self.frame.layers.get_mut(&ScrollLayerId(0)) { + match self.frame.root.layers.get_mut(&ScrollLayerId(0)) { None => tx.send(point).unwrap(), Some(layer) => tx.send(point - layer.scroll_offset).unwrap(), } @@ -232,9 +234,9 @@ 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, + //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); @@ -285,25 +287,10 @@ impl RenderBackend { } fn render(&mut self) -> RendererFrame { - let mut frame = self.frame.build(&self.viewport, - &mut self.resource_cache, - &mut self.thread_pool, - self.device_pixel_ratio); - - // Bit of a hack - if there was nothing visible, at least - // add one layer to the frame so that the screen gets - // 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: size, - commands: Vec::new(), - }); - } + let frame = self.frame.build(&self.viewport, + &mut self.resource_cache, + &mut self.thread_pool, + self.device_pixel_ratio); let pending_update = self.resource_cache.pending_updates(); if pending_update.updates.len() > 0 { diff --git a/src/renderer.rs b/src/renderer.rs index ab29467944..ab55035088 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -1,4 +1,3 @@ -use app_units::Au; use batch::{RasterBatch, VertexBufferId}; use debug_render::DebugRenderer; use device::{Device, ProgramId, TextureId, UniformLocation, VertexFormat}; @@ -9,8 +8,8 @@ use gleam::gl; use internal_types::{RendererFrame, ResultMsg, TextureUpdateOp, BatchUpdateOp, BatchUpdateList}; use internal_types::{TextureUpdateDetails, TextureUpdateList, PackedVertex, RenderTargetMode}; use internal_types::{ORTHO_NEAR_PLANE, ORTHO_FAR_PLANE, BasicRotationAngle}; -use internal_types::{PackedVertexForTextureCacheUpdate, CompositionOp}; -use internal_types::{AxisDirection, LowLevelFilterOp, DrawCommand, ANGLE_FLOAT_TO_FIXED}; +use internal_types::{PackedVertexForTextureCacheUpdate, CompositionOp, RenderTargetIndex}; +use internal_types::{AxisDirection, LowLevelFilterOp, DrawCommand, DrawLayer, ANGLE_FLOAT_TO_FIXED}; use ipc_channel::ipc; use profiler::{Profiler, BackendProfileCounters}; use profiler::{RendererProfileTimers, RendererProfileCounters}; @@ -51,12 +50,49 @@ struct VertexBuffer { vao_id: VAOId, } +pub trait CompositionOpHelpers { + fn needs_framebuffer(&self) -> bool; +} + +impl CompositionOpHelpers for CompositionOp { + fn needs_framebuffer(&self) -> bool { + match *self { + CompositionOp::MixBlend(MixBlendMode::Normal) => unreachable!(), + + CompositionOp::MixBlend(MixBlendMode::Screen) | + CompositionOp::MixBlend(MixBlendMode::Overlay) | + CompositionOp::MixBlend(MixBlendMode::ColorDodge) | + CompositionOp::MixBlend(MixBlendMode::ColorBurn) | + CompositionOp::MixBlend(MixBlendMode::HardLight) | + CompositionOp::MixBlend(MixBlendMode::SoftLight) | + CompositionOp::MixBlend(MixBlendMode::Difference) | + CompositionOp::MixBlend(MixBlendMode::Exclusion) | + CompositionOp::MixBlend(MixBlendMode::Hue) | + CompositionOp::MixBlend(MixBlendMode::Saturation) | + CompositionOp::MixBlend(MixBlendMode::Color) | + CompositionOp::MixBlend(MixBlendMode::Luminosity) | + CompositionOp::Filter(LowLevelFilterOp::Blur(..)) | + CompositionOp::Filter(LowLevelFilterOp::Contrast(_)) | + CompositionOp::Filter(LowLevelFilterOp::Grayscale(_)) | + CompositionOp::Filter(LowLevelFilterOp::HueRotate(_)) | + CompositionOp::Filter(LowLevelFilterOp::Invert(_)) | + CompositionOp::Filter(LowLevelFilterOp::Saturate(_)) | + CompositionOp::Filter(LowLevelFilterOp::Sepia(_)) => true, + + CompositionOp::Filter(LowLevelFilterOp::Brightness(_)) | + CompositionOp::Filter(LowLevelFilterOp::Opacity(_)) | + CompositionOp::MixBlend(MixBlendMode::Multiply) | + CompositionOp::MixBlend(MixBlendMode::Darken) | + CompositionOp::MixBlend(MixBlendMode::Lighten) => false, + } + } +} + struct RenderContext { blend_program_id: ProgramId, filter_program_id: ProgramId, temporary_fb_texture: TextureId, - projection: Matrix4, - layer_size: Size2D, + device_pixel_ratio: f32, } pub struct Renderer { @@ -89,11 +125,9 @@ pub struct Renderer { blur_program_id: ProgramId, u_direction: UniformLocation, - clear_program_id: ProgramId, - notifier: Arc>>>, viewport_size: Size2D, - framebuffer_size: Size2D, + //framebuffer_size: Size2D, enable_profiler: bool, debug: DebugRenderer, @@ -106,7 +140,7 @@ pub struct Renderer { impl Renderer { pub fn new(width: u32, height: u32, - framebuffer_size: &Size2D, + //framebuffer_size: &Size2D, device_pixel_ratio: f32, resource_path: PathBuf, enable_aa: bool, @@ -127,7 +161,6 @@ 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"); @@ -139,7 +172,7 @@ impl Renderer { let u_direction = device.get_uniform_location(blur_program_id, "uDirection"); - let texture_ids = device.create_texture_ids(1024); + let texture_ids = device.create_texture_ids(16); let mut texture_cache = TextureCache::new(texture_ids); let white_pixels: Vec = vec![ 0xff, 0xff, 0xff, 0xff, @@ -215,7 +248,6 @@ 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, @@ -224,7 +256,7 @@ impl Renderer { u_tile_params: u_tile_params, notifier: notifier, viewport_size: Size2D::new(width, height), - framebuffer_size: *framebuffer_size, + //framebuffer_size: *framebuffer_size, debug: debug_renderer, backend_profile_counters: BackendProfileCounters::new(), profile_counters: RendererProfileCounters::new(), @@ -827,499 +859,392 @@ impl Renderer { self.device.delete_vao(vao_id); } - fn draw_frame(&mut self) { - if let Some(ref frame) = self.current_frame { - // TODO: cache render targets! + fn draw_layer(&mut self, + layer_target: Option, + layer: &DrawLayer, + render_context: &RenderContext) { + // Draw child layers first, to ensure that dependent render targets + // have been built before they are read as a texture. + for child in &layer.child_layers { + self.draw_layer(Some(layer.child_target.as_ref().unwrap().texture_id), + child, + render_context); + } - // Draw render targets in reverse order to ensure dependencies - // of earlier render targets are already available. - // TODO: Are there cases where this fails and needs something - // like a topological sort with dependencies? - let mut render_context = RenderContext { - blend_program_id: self.blend_program_id, - filter_program_id: self.filter_program_id, - temporary_fb_texture: self.device.create_texture_ids(1)[0], - projection: Matrix4::identity(), - layer_size: Size2D::zero(), - }; + self.device.bind_render_target(layer_target); + + // TODO(gw): This may not be needed in all cases... + gl::scissor(self.device_pixel_ratio as i32 * layer.layer_origin.x as gl::GLint, + self.device_pixel_ratio as i32 * layer.layer_origin.y as gl::GLint, + self.device_pixel_ratio as i32 * layer.layer_size.width as gl::GLint, + self.device_pixel_ratio as i32 * layer.layer_size.height as gl::GLint); + gl::viewport(self.device_pixel_ratio as i32 * layer.layer_origin.x as gl::GLint, + self.device_pixel_ratio as i32 * layer.layer_origin.y as gl::GLint, + self.device_pixel_ratio as i32 * layer.layer_size.width as gl::GLint, + self.device_pixel_ratio as i32 * layer.layer_size.height as gl::GLint); + let clear_color = if layer_target.is_some() { + ColorF::new(0.0, 0.0, 0.0, 0.0) + } else { + ColorF::new(1.0, 1.0, 1.0, 1.0) + }; + 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); - debug_assert!(frame.layers.len() > 0); - let framebuffer_size = self.framebuffer_size; - - for layer in frame.layers.iter().rev() { - render_context.layer_size = layer.size; - - let layer_texture_id = layer.render_targets[0].texture.map(|texture| { - texture.texture_id - }); - self.device.bind_render_target(layer_texture_id); - - 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); - } - } + let projection = Matrix4::ortho(0.0, + layer.layer_size.width as f32, + layer.layer_size.height as f32, + 0.0, + ORTHO_NEAR_PLANE, + ORTHO_FAR_PLANE); - viewport_y = render_target_size.height as f32 - - uv.max_y() as f32 * scale_factor; + for cmd in &layer.commands { + match cmd { + &DrawCommand::Clear(ref info) => { + let mut clear_bits = 0; + if info.clear_color { + clear_bits |= gl::COLOR_BUFFER_BIT; } - }; - 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); - - 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 { - &DrawCommand::Clear(ref info) => { - let mut clear_bits = 0; - if info.clear_color { - clear_bits |= gl::COLOR_BUFFER_BIT; - } - if info.clear_z { - clear_bits |= gl::DEPTH_BUFFER_BIT; - } - if info.clear_stencil { - clear_bits |= gl::STENCIL_BUFFER_BIT; - } - gl::clear(clear_bits); - } - &DrawCommand::Batch(ref info) => { - // TODO: probably worth sorting front to back to minimize overdraw (if profiling shows fragment / rop bound) - - gl::enable(gl::BLEND); - - if layer.render_targets[0].texture.is_some() { - gl::blend_func_separate(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA, - gl::ONE, gl::ONE); - } else { - gl::blend_func(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA); - } + if info.clear_z { + clear_bits |= gl::DEPTH_BUFFER_BIT; + } + if info.clear_stencil { + clear_bits |= gl::STENCIL_BUFFER_BIT; + } + gl::clear(clear_bits); + } + &DrawCommand::Batch(ref info) => { + // TODO: probably worth sorting front to back to minimize overdraw (if profiling shows fragment / rop bound) - gl::blend_equation(gl::FUNC_ADD); + gl::enable(gl::BLEND); - self.device.bind_program(self.quad_program_id, - &render_context.projection); + if layer_target.is_some() { + gl::blend_func_separate(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA, + gl::ONE, gl::ONE); + } else { + gl::blend_func(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA); + } - self.device.set_uniform_mat4_array(self.u_quad_transform_array, - &info.matrix_palette); + gl::blend_equation(gl::FUNC_ADD); - for draw_call in &info.draw_calls { - let vao_id = self.vertex_buffers[&draw_call.vertex_buffer_id].vao_id; - self.device.bind_vao(vao_id); + self.device.bind_program(self.quad_program_id, + &projection); - 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_mat4_array(self.u_quad_transform_array, + &info.matrix_palette); - self.device.set_uniform_vec4_array(self.u_tile_params, - &floats); - } + for draw_call in &info.draw_calls { + let vao_id = self.vertex_buffers[&draw_call.vertex_buffer_id].vao_id; + self.device.bind_vao(vao_id); - 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); - - 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, - index_count); + 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); } - &DrawCommand::CompositeBatch(ref info) => { - let needs_fb = match info.operation { - CompositionOp::MixBlend(MixBlendMode::Normal) => unreachable!(), - - CompositionOp::MixBlend(MixBlendMode::Screen) | - CompositionOp::MixBlend(MixBlendMode::Overlay) | - CompositionOp::MixBlend(MixBlendMode::ColorDodge) | - CompositionOp::MixBlend(MixBlendMode::ColorBurn) | - CompositionOp::MixBlend(MixBlendMode::HardLight) | - CompositionOp::MixBlend(MixBlendMode::SoftLight) | - CompositionOp::MixBlend(MixBlendMode::Difference) | - CompositionOp::MixBlend(MixBlendMode::Exclusion) | - CompositionOp::MixBlend(MixBlendMode::Hue) | - CompositionOp::MixBlend(MixBlendMode::Saturation) | - CompositionOp::MixBlend(MixBlendMode::Color) | - CompositionOp::MixBlend(MixBlendMode::Luminosity) | - CompositionOp::Filter(LowLevelFilterOp::Blur(..)) | - CompositionOp::Filter(LowLevelFilterOp::Contrast(_)) | - CompositionOp::Filter(LowLevelFilterOp::Grayscale(_)) | - CompositionOp::Filter(LowLevelFilterOp::HueRotate(_)) | - CompositionOp::Filter(LowLevelFilterOp::Invert(_)) | - CompositionOp::Filter(LowLevelFilterOp::Saturate(_)) | - CompositionOp::Filter(LowLevelFilterOp::Sepia(_)) => true, - - CompositionOp::Filter(LowLevelFilterOp::Brightness(_)) | - CompositionOp::Filter(LowLevelFilterOp::Opacity(_)) | - CompositionOp::MixBlend(MixBlendMode::Multiply) | - CompositionOp::MixBlend(MixBlendMode::Darken) | - CompositionOp::MixBlend(MixBlendMode::Lighten) => false, - }; - - let alpha; - if needs_fb { - gl::disable(gl::BLEND); - - // 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) => { - self.device.bind_program(render_context.blend_program_id, - &render_context.projection); - self.device.set_uniform_4f(self.u_blend_params, - blend_mode as i32 as f32, - 0.0, - 0.0, - 0.0); + 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); + + 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, + index_count); + } + } + &DrawCommand::CompositeBatch(ref info) => { + let needs_fb = info.operation.needs_framebuffer(); + + let alpha; + if needs_fb { + gl::disable(gl::BLEND); + + match info.operation { + CompositionOp::MixBlend(blend_mode) => { + self.device.bind_program(render_context.blend_program_id, + &projection); + self.device.set_uniform_4f(self.u_blend_params, + blend_mode as i32 as f32, + 0.0, + 0.0, + 0.0); + } + CompositionOp::Filter(filter_op) => { + self.device.bind_program(render_context.filter_program_id, + &projection); + + let (opcode, amount, param0, param1) = match filter_op { + LowLevelFilterOp::Blur(radius, + AxisDirection::Horizontal) => { + (0.0, + radius.to_f32_px() * self.device_pixel_ratio, + 1.0, + 0.0) } - CompositionOp::Filter(filter_op) => { - self.device.bind_program(render_context.filter_program_id, - &render_context.projection); - - let (opcode, amount, param0, param1) = match filter_op { - LowLevelFilterOp::Blur(radius, - AxisDirection::Horizontal) => { - (0.0, - radius.to_f32_px() * self.device_pixel_ratio, - 1.0, - 0.0) - } - LowLevelFilterOp::Blur(radius, - AxisDirection::Vertical) => { - (0.0, - radius.to_f32_px() * self.device_pixel_ratio, - 0.0, - 1.0) - } - LowLevelFilterOp::Contrast(amount) => { - (1.0, amount.to_f32_px(), 0.0, 0.0) - } - LowLevelFilterOp::Grayscale(amount) => { - (2.0, amount.to_f32_px(), 0.0, 0.0) - } - LowLevelFilterOp::HueRotate(angle) => { - (3.0, - (angle as f32) / ANGLE_FLOAT_TO_FIXED, - 0.0, - 0.0) - } - LowLevelFilterOp::Invert(amount) => { - (4.0, amount.to_f32_px(), 0.0, 0.0) - } - LowLevelFilterOp::Saturate(amount) => { - (5.0, amount.to_f32_px(), 0.0, 0.0) - } - LowLevelFilterOp::Sepia(amount) => { - (6.0, amount.to_f32_px(), 0.0, 0.0) - } - LowLevelFilterOp::Brightness(_) | - LowLevelFilterOp::Opacity(_) => { - // Expressible using GL blend modes, so not handled - // here. - unreachable!() - } - }; - - self.device.set_uniform_4f(self.u_filter_params, - opcode, - amount, - param0, - param1); + LowLevelFilterOp::Blur(radius, + AxisDirection::Vertical) => { + (0.0, + radius.to_f32_px() * self.device_pixel_ratio, + 0.0, + 1.0) } - } - self.device.bind_mask_texture(render_context.temporary_fb_texture); - alpha = 1.0; - } else { - gl::enable(gl::BLEND); - - match info.operation { - CompositionOp::Filter(LowLevelFilterOp::Brightness( - amount)) => { - gl::blend_func(gl::CONSTANT_COLOR, gl::ZERO); - gl::blend_equation(gl::FUNC_ADD); - gl::blend_color(amount.to_f32_px(), - amount.to_f32_px(), - amount.to_f32_px(), - 1.0); - alpha = 1.0; + LowLevelFilterOp::Contrast(amount) => { + (1.0, amount.to_f32_px(), 0.0, 0.0) } - CompositionOp::Filter(LowLevelFilterOp::Opacity(amount)) => { - gl::blend_func(gl::SRC_ALPHA, - gl::ONE_MINUS_SRC_ALPHA); - gl::blend_equation(gl::FUNC_ADD); - alpha = amount.to_f32_px(); + LowLevelFilterOp::Grayscale(amount) => { + (2.0, amount.to_f32_px(), 0.0, 0.0) } - CompositionOp::MixBlend(MixBlendMode::Multiply) => { - gl::blend_func(gl::DST_COLOR, gl::ZERO); - gl::blend_equation(gl::FUNC_ADD); - alpha = 1.0; + LowLevelFilterOp::HueRotate(angle) => { + (3.0, + (angle as f32) / ANGLE_FLOAT_TO_FIXED, + 0.0, + 0.0) } - CompositionOp::MixBlend(MixBlendMode::Darken) => { - gl::blend_func(gl::ONE, gl::ONE); - gl::blend_equation(GL_BLEND_MIN); - alpha = 1.0; + LowLevelFilterOp::Invert(amount) => { + (4.0, amount.to_f32_px(), 0.0, 0.0) } - CompositionOp::MixBlend(MixBlendMode::Lighten) => { - gl::blend_func(gl::ONE, gl::ONE); - gl::blend_equation(GL_BLEND_MAX); - alpha = 1.0; + LowLevelFilterOp::Saturate(amount) => { + (5.0, amount.to_f32_px(), 0.0, 0.0) } - _ => unreachable!(), - } + LowLevelFilterOp::Sepia(amount) => { + (6.0, amount.to_f32_px(), 0.0, 0.0) + } + LowLevelFilterOp::Brightness(_) | + LowLevelFilterOp::Opacity(_) => { + // Expressible using GL blend modes, so not handled + // here. + unreachable!() + } + }; - self.device.bind_program(self.blit_program_id, - &render_context.projection); + self.device.set_uniform_4f(self.u_filter_params, + opcode, + amount, + param0, + param1); } - - 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), - ]); + } + self.device.bind_mask_texture(render_context.temporary_fb_texture); + alpha = 1.0; + } else { + gl::enable(gl::BLEND); + + match info.operation { + CompositionOp::Filter(LowLevelFilterOp::Brightness( + amount)) => { + gl::blend_func(gl::CONSTANT_COLOR, gl::ZERO); + gl::blend_equation(gl::FUNC_ADD); + gl::blend_color(amount.to_f32_px(), + amount.to_f32_px(), + amount.to_f32_px(), + 1.0); + alpha = 1.0; + } + CompositionOp::Filter(LowLevelFilterOp::Opacity(amount)) => { + gl::blend_func(gl::SRC_ALPHA, + gl::ONE_MINUS_SRC_ALPHA); + gl::blend_equation(gl::FUNC_ADD); + alpha = amount.to_f32_px(); + } + CompositionOp::MixBlend(MixBlendMode::Multiply) => { + gl::blend_func(gl::DST_COLOR, gl::ZERO); + gl::blend_equation(gl::FUNC_ADD); + alpha = 1.0; } + CompositionOp::MixBlend(MixBlendMode::Darken) => { + gl::blend_func(gl::ONE, gl::ONE); + gl::blend_equation(GL_BLEND_MIN); + alpha = 1.0; + } + CompositionOp::MixBlend(MixBlendMode::Lighten) => { + gl::blend_func(gl::ONE, gl::ONE); + gl::blend_equation(GL_BLEND_MAX); + alpha = 1.0; + } + _ => unreachable!(), + } + + self.device.bind_program(self.blit_program_id, + &projection); + } + + 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(); + + // TODO(glennw): No need to re-init this FB working copy texture + // every time... + if needs_fb { + let fb_rect_size = Size2D::new(job.rect.size.width as f32 * render_context.device_pixel_ratio, + job.rect.size.height as f32 * render_context.device_pixel_ratio); + + let inverted_y0 = layer.layer_size.height as i32 - + job.rect.size.height - + y0; + let fb_rect_origin = Point2D::new(x0 as f32 * render_context.device_pixel_ratio, + inverted_y0 as f32 * render_context.device_pixel_ratio); + + self.device.init_texture(render_context.temporary_fb_texture, + fb_rect_size.width as u32, + fb_rect_size.height as u32, + ImageFormat::RGBA8, + TextureFilter::Nearest, + RenderTargetMode::None, + None); + self.device.read_framebuffer_rect( + render_context.temporary_fb_texture, + fb_rect_origin.x as i32, + fb_rect_origin.y as i32, + fb_rect_size.width as i32, + fb_rect_size.height as i32); + } - draw_simple_triangles(&mut self.device, - &mut render_context, - &mut self.profile_counters, - &indices[..], - &vertices[..], - info.texture_id); + 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 RenderTargetIndex(render_target_index) = job.render_target_index; + let src_target = &layer.child_layers[render_target_index as usize]; + + let pixel_uv = Rect::new( + Point2D::new(src_target.layer_origin.x as u32, + src_target.layer_origin.y as u32), + Size2D::new(src_target.layer_size.width as u32, + src_target.layer_size.height as u32)); + let texture_width = + layer.child_target.as_ref().unwrap().size.width as f32; + let texture_height = + layer.child_target.as_ref().unwrap().size.height as f32; + let texture_uv = Rect::new( + Point2D::new( + pixel_uv.origin.x as f32 / texture_width, + pixel_uv.origin.y as f32 / texture_height), + Size2D::new(pixel_uv.size.width as f32 / texture_width, + pixel_uv.size.height as f32 / texture_height)); + + + if needs_fb { + vertices.extend_from_slice(&[ + PackedVertex::from_components( + x0 as f32, y0 as f32, + &color, + texture_uv.origin.x, texture_uv.max_y(), + 0.0, 1.0), + PackedVertex::from_components( + x1 as f32, y0 as f32, + &color, + texture_uv.max_x(), texture_uv.max_y(), + 1.0, 1.0), + PackedVertex::from_components( + x0 as f32, y1 as f32, + &color, + texture_uv.origin.x, texture_uv.origin.y, + 0.0, 0.0), + PackedVertex::from_components( + x1 as f32, y1 as f32, + &color, + texture_uv.max_x(), texture_uv.origin.y, + 1.0, 0.0), + ]); + } else { + vertices.extend_from_slice(&[ + 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 self.profile_counters, + &indices[..], + &vertices[..], + layer.child_target.as_ref().unwrap().texture_id); } } } } -} + fn draw_frame(&mut self) { + if let Some(frame) = self.current_frame.take() { + // TODO: cache render targets! -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 = if clear_to_transparent { - ColorF { - r: 0.0, - g: 0.0, - b: 0.0, - a: 0.0, - } - } else { - ColorF { - r: 1.0, - g: 1.0, - b: 1.0, - a: 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; - } + // Draw render targets in reverse order to ensure dependencies + // of earlier render targets are already available. + // TODO: Are there cases where this fails and needs something + // like a topological sort with dependencies? + let render_context = RenderContext { + blend_program_id: self.blend_program_id, + filter_program_id: self.filter_program_id, + temporary_fb_texture: self.device.create_texture_ids(1)[0], + device_pixel_ratio: self.device_pixel_ratio, + }; - // 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), - ]); + gl::enable(gl::DEPTH_TEST); + gl::depth_func(gl::LEQUAL); + gl::enable(gl::SCISSOR_TEST); + + self.draw_layer(None, + &frame.root_layer, + &render_context); + + // Restore frame - avoid borrow checker! + self.current_frame = Some(frame); + } } - 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], diff --git a/src/resource_cache.rs b/src/resource_cache.rs index d67dd32e12..0b28c1b6cc 100644 --- a/src/resource_cache.rs +++ b/src/resource_cache.rs @@ -3,7 +3,7 @@ use device::{TextureFilter, TextureId}; use euclid::Size2D; use fnv::FnvHasher; use freelist::FreeList; -use internal_types::{FontTemplate, GlyphKey, RasterItem, RenderTargetTexture}; +use internal_types::{FontTemplate, GlyphKey, RasterItem}; 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, TextureCacheItemKind}; +use texture_cache::{TextureCache, TextureCacheItem, TextureCacheItemId}; use texture_cache::{BorderType, TextureInsertOp}; use webrender_traits::{Epoch, FontKey, ImageKey, ImageFormat, DisplayItem, ImageRendering}; use webrender_traits::{WebGLContextId}; @@ -300,22 +300,12 @@ impl ResourceCache { 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() + -> TextureId { + self.texture_cache.allocate_render_target(width, height, format) } - pub fn free_render_target(&mut self, texture: RenderTargetTexture) { - self.texture_cache.free(texture.texture_id, - &texture.uv_rect, - TextureCacheItemKind::RenderTarget) + pub fn free_render_target(&mut self, texture_id: TextureId) { + self.texture_cache.free_render_target(texture_id) } pub fn pending_updates(&mut self) -> TextureUpdateList { diff --git a/src/texture_cache.rs b/src/texture_cache.rs index 90db258ebb..7daf9038fe 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, RenderTargetTexture}; +use internal_types::{TextureUpdate, TextureUpdateOp, TextureUpdateDetails}; use internal_types::{RasterItem, RenderTargetMode, TextureImage, TextureUpdateList}; use internal_types::{BasicRotationAngle, RectUv}; use std::cmp::Ordering; @@ -64,7 +64,7 @@ fn copy_pixels(src: &[u8], /// /// This approach was chosen because of its simplicity, good performance, and easy support for /// dynamic texture deallocation. -struct TexturePage { +pub struct TexturePage { texture_id: TextureId, texture_size: u32, free_list: FreeRectList, @@ -73,7 +73,7 @@ struct TexturePage { } impl TexturePage { - fn new(texture_id: TextureId, texture_size: u32) -> TexturePage { + pub fn new(texture_id: TextureId, texture_size: u32) -> TexturePage { let mut page = TexturePage { texture_id: texture_id, texture_size: texture_size, @@ -136,9 +136,9 @@ impl TexturePage { } } - fn allocate(&mut self, - requested_dimensions: &Size2D, - filter: TextureFilter) -> Option> { + pub fn allocate(&mut self, + requested_dimensions: &Size2D, + filter: TextureFilter) -> Option> { // TODO(gw): For now, anything that requests nearest filtering // just fails to allocate in a texture page, and gets a standalone // texture. This isn't ideal, as it causes lots of batch breaks, @@ -297,6 +297,7 @@ impl TexturePage { self.dirty = false; } +/* fn free(&mut self, rect: &Rect) { debug_assert!(self.allocations > 0); self.allocations -= 1; @@ -307,7 +308,7 @@ impl TexturePage { self.free_list.push(rect); self.dirty = true - } + }*/ } // TODO(gw): This is used to store data specific to glyphs. @@ -487,7 +488,7 @@ struct TextureCacheArena { pages_rgba8: Vec, alternate_pages_a8: Vec, alternate_pages_rgba8: Vec, - render_target_pages: Vec, + //render_target_pages: Vec, } impl TextureCacheArena { @@ -498,7 +499,7 @@ impl TextureCacheArena { pages_rgba8: Vec::new(), alternate_pages_a8: Vec::new(), alternate_pages_rgba8: Vec::new(), - render_target_pages: Vec::new(), + //render_target_pages: Vec::new(), } } } @@ -509,9 +510,9 @@ pub struct TextureCache { alternate_free_texture_levels: HashMap, DefaultState>, - render_target_free_texture_levels: HashMap, - DefaultState>, + //render_target_free_texture_levels: HashMap, + // DefaultState>, items: FreeList, arena: TextureCacheArena, pending_updates: TextureUpdateList, @@ -529,29 +530,13 @@ pub struct AllocationResult { 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()), + //render_target_free_texture_levels: HashMap::with_hash_state(Default::default()), items: FreeList::new(), pending_updates: TextureUpdateList::new(), arena: TextureCacheArena::new(), @@ -585,19 +570,46 @@ impl TextureCache { self.items.insert(new_item) } - 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 free(&mut self, + _texture_id: TextureId, + _uv: &Rect, + _kind: TextureCacheItemKind) { + panic!("can't free texture items yet!"); + } + */ + + 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 op = TextureUpdateOp::Create(width, + height, + format, + TextureFilter::Linear, + RenderTargetMode::RenderTarget, + None); + let update_op = TextureUpdate { + id: texture_id, + op: op, + }; + self.pending_updates.push(update_op); + texture_id + } + + pub fn free_render_target(&mut self, texture_id: TextureId) { + let op = TextureUpdateOp::Update(0, + 0, + 0, + 0, + TextureUpdateDetails::Blit(Vec::new())); + let update_op = TextureUpdate { + id: texture_id, + op: op, + }; + self.pending_updates.push(update_op); + self.free_texture_ids.push(texture_id); } pub fn allocate(&mut self, @@ -624,15 +636,14 @@ 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::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) | - (_, TextureCacheItemKind::RenderTarget) => unreachable!(), + (_, TextureCacheItemKind::Alternate) => unreachable!(), }; let border_size = match border_type { @@ -656,9 +667,9 @@ impl TextureCache { TextureCacheItemKind::Alternate => { self.alternate_free_texture_levels.entry(format) } - TextureCacheItemKind::RenderTarget => { - self.render_target_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()), @@ -1085,6 +1096,6 @@ fn texture_size() -> u32 { pub enum TextureCacheItemKind { Standard, Alternate, - RenderTarget, + //RenderTarget, }