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