mirror of
https://github.com/servo/servo.git
synced 2025-08-03 12:40:06 +01:00
Refactor compositor layer tree design
Instead of having two parallel trees of CompositorLayers and ContainerLayers, transform CompositorLayer to CompositorData and move tiling logic to rust-layers.
This commit is contained in:
parent
ebd7fb060d
commit
0c2538d06d
10 changed files with 867 additions and 1889 deletions
|
@ -49,8 +49,7 @@ pub use constellation::Constellation;
|
||||||
|
|
||||||
mod compositor_task;
|
mod compositor_task;
|
||||||
|
|
||||||
mod quadtree;
|
mod compositor_data;
|
||||||
mod compositor_layer;
|
|
||||||
|
|
||||||
mod compositor;
|
mod compositor;
|
||||||
mod headless;
|
mod headless;
|
||||||
|
|
|
@ -2,13 +2,13 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
use compositor_data::CompositorData;
|
||||||
use compositor_task::{Msg, CompositorTask, Exit, ChangeReadyState, SetUnRenderedColor};
|
use compositor_task::{Msg, CompositorTask, Exit, ChangeReadyState, SetUnRenderedColor};
|
||||||
use compositor_task::{SetIds, GetGraphicsMetadata, CreateRootCompositorLayerIfNecessary};
|
use compositor_task::{SetIds, GetGraphicsMetadata, CreateRootCompositorLayerIfNecessary};
|
||||||
use compositor_task::{CreateDescendantCompositorLayerIfNecessary, SetLayerPageSize};
|
use compositor_task::{CreateDescendantCompositorLayerIfNecessary, SetLayerPageSize};
|
||||||
use compositor_task::{SetLayerClipRect, Paint, ScrollFragmentPoint, LoadComplete};
|
use compositor_task::{SetLayerClipRect, Paint, ScrollFragmentPoint, LoadComplete};
|
||||||
use compositor_task::{ShutdownComplete, ChangeRenderState};
|
use compositor_task::{ShutdownComplete, ChangeRenderState};
|
||||||
use constellation::SendableFrameTree;
|
use constellation::SendableFrameTree;
|
||||||
use compositor_layer::CompositorLayer;
|
|
||||||
use pipeline::CompositionPipeline;
|
use pipeline::CompositionPipeline;
|
||||||
use platform::{Application, Window};
|
use platform::{Application, Window};
|
||||||
use windowing;
|
use windowing;
|
||||||
|
@ -26,14 +26,15 @@ use geom::point::{Point2D, TypedPoint2D};
|
||||||
use geom::rect::Rect;
|
use geom::rect::Rect;
|
||||||
use geom::size::{Size2D, TypedSize2D};
|
use geom::size::{Size2D, TypedSize2D};
|
||||||
use geom::scale_factor::ScaleFactor;
|
use geom::scale_factor::ScaleFactor;
|
||||||
use layers::layers::{ContainerLayer, ContainerLayerKind};
|
use layers::layers::LayerBufferSet;
|
||||||
use layers::platform::surface::NativeCompositingGraphicsContext;
|
use layers::platform::surface::NativeCompositingGraphicsContext;
|
||||||
use layers::rendergl;
|
use layers::rendergl;
|
||||||
use layers::rendergl::RenderContext;
|
use layers::rendergl::RenderContext;
|
||||||
use layers::scene::Scene;
|
use layers::scene::Scene;
|
||||||
|
use layers::layers::ContainerLayer;
|
||||||
use opengles::gl2;
|
use opengles::gl2;
|
||||||
use png;
|
use png;
|
||||||
use servo_msg::compositor_msg::{Blank, Epoch, FinishedLoading, IdleRenderState, LayerBufferSet};
|
use servo_msg::compositor_msg::{Blank, Epoch, FinishedLoading, IdleRenderState};
|
||||||
use servo_msg::compositor_msg::{LayerId, ReadyState, RenderState, ScrollPolicy, Scrollable};
|
use servo_msg::compositor_msg::{LayerId, ReadyState, RenderState, ScrollPolicy, Scrollable};
|
||||||
use servo_msg::constellation_msg::{ConstellationChan, ExitMsg, LoadUrlMsg, NavigateMsg};
|
use servo_msg::constellation_msg::{ConstellationChan, ExitMsg, LoadUrlMsg, NavigateMsg};
|
||||||
use servo_msg::constellation_msg::{PipelineId, ResizedWindowMsg, WindowSizeData};
|
use servo_msg::constellation_msg::{PipelineId, ResizedWindowMsg, WindowSizeData};
|
||||||
|
@ -59,14 +60,11 @@ pub struct IOCompositor {
|
||||||
/// The render context.
|
/// The render context.
|
||||||
context: RenderContext,
|
context: RenderContext,
|
||||||
|
|
||||||
/// The root ContainerLayer.
|
|
||||||
root_layer: Rc<ContainerLayer>,
|
|
||||||
|
|
||||||
/// The root pipeline.
|
/// The root pipeline.
|
||||||
root_pipeline: Option<CompositionPipeline>,
|
root_pipeline: Option<CompositionPipeline>,
|
||||||
|
|
||||||
/// The canvas to paint a page.
|
/// The canvas to paint a page.
|
||||||
scene: Scene,
|
scene: Scene<CompositorData>,
|
||||||
|
|
||||||
/// The application window size.
|
/// The application window size.
|
||||||
window_size: TypedSize2D<DevicePixel, uint>,
|
window_size: TypedSize2D<DevicePixel, uint>,
|
||||||
|
@ -113,9 +111,6 @@ pub struct IOCompositor {
|
||||||
/// The command line option flags.
|
/// The command line option flags.
|
||||||
opts: Opts,
|
opts: Opts,
|
||||||
|
|
||||||
/// The root CompositorLayer
|
|
||||||
compositor_layer: Option<CompositorLayer>,
|
|
||||||
|
|
||||||
/// The channel on which messages can be sent to the constellation.
|
/// The channel on which messages can be sent to the constellation.
|
||||||
constellation_chan: ConstellationChan,
|
constellation_chan: ConstellationChan,
|
||||||
|
|
||||||
|
@ -142,7 +137,6 @@ impl IOCompositor {
|
||||||
//
|
//
|
||||||
// TODO: There should be no initial layer tree until the renderer creates one from the display
|
// TODO: There should be no initial layer tree until the renderer creates one from the display
|
||||||
// list. This is only here because we don't have that logic in the renderer yet.
|
// list. This is only here because we don't have that logic in the renderer yet.
|
||||||
let root_layer = Rc::new(ContainerLayer());
|
|
||||||
let window_size = window.framebuffer_size();
|
let window_size = window.framebuffer_size();
|
||||||
let hidpi_factor = window.hidpi_factor();
|
let hidpi_factor = window.hidpi_factor();
|
||||||
|
|
||||||
|
@ -151,9 +145,8 @@ impl IOCompositor {
|
||||||
port: port,
|
port: port,
|
||||||
opts: opts,
|
opts: opts,
|
||||||
context: rendergl::init_render_context(),
|
context: rendergl::init_render_context(),
|
||||||
root_layer: root_layer.clone(),
|
|
||||||
root_pipeline: None,
|
root_pipeline: None,
|
||||||
scene: Scene(ContainerLayerKind(root_layer), window_size.as_f32().to_untyped(), identity()),
|
scene: Scene(window_size.as_f32().to_untyped(), identity()),
|
||||||
window_size: window_size,
|
window_size: window_size,
|
||||||
hidpi_factor: hidpi_factor,
|
hidpi_factor: hidpi_factor,
|
||||||
graphics_context: CompositorTask::create_graphics_context(),
|
graphics_context: CompositorTask::create_graphics_context(),
|
||||||
|
@ -167,7 +160,6 @@ impl IOCompositor {
|
||||||
zoom_time: 0f64,
|
zoom_time: 0f64,
|
||||||
ready_state: Blank,
|
ready_state: Blank,
|
||||||
load_complete: false,
|
load_complete: false,
|
||||||
compositor_layer: None,
|
|
||||||
constellation_chan: constellation_chan,
|
constellation_chan: constellation_chan,
|
||||||
time_profiler_chan: time_profiler_chan,
|
time_profiler_chan: time_profiler_chan,
|
||||||
memory_profiler_chan: memory_profiler_chan,
|
memory_profiler_chan: memory_profiler_chan,
|
||||||
|
@ -230,9 +222,9 @@ impl IOCompositor {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear out the compositor layers so that painting tasks can destroy the buffers.
|
// Clear out the compositor layers so that painting tasks can destroy the buffers.
|
||||||
match self.compositor_layer {
|
match self.scene.root {
|
||||||
None => {}
|
None => {}
|
||||||
Some(ref mut layer) => layer.forget_all_tiles(),
|
Some(ref layer) => CompositorData::forget_all_tiles(layer.clone()),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drain compositor port, sometimes messages contain channels that are blocking
|
// Drain compositor port, sometimes messages contain channels that are blocking
|
||||||
|
@ -343,8 +335,8 @@ impl IOCompositor {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_unrendered_color(&mut self, pipeline_id: PipelineId, layer_id: LayerId, color: Color) {
|
fn set_unrendered_color(&mut self, pipeline_id: PipelineId, layer_id: LayerId, color: Color) {
|
||||||
match self.compositor_layer {
|
match self.scene.root {
|
||||||
Some(ref mut layer) => layer.set_unrendered_color(pipeline_id, layer_id, color),
|
Some(ref layer) => CompositorData::set_unrendered_color(layer.clone(), pipeline_id, layer_id, color),
|
||||||
None => false,
|
None => false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -367,9 +359,9 @@ impl IOCompositor {
|
||||||
layer_id: LayerId,
|
layer_id: LayerId,
|
||||||
size: Size2D<f32>,
|
size: Size2D<f32>,
|
||||||
unrendered_color: Color) {
|
unrendered_color: Color) {
|
||||||
let (root_pipeline, root_layer_id) = match self.compositor_layer {
|
let (root_pipeline, root_layer_id) = match self.scene.root {
|
||||||
Some(ref compositor_layer) if compositor_layer.pipeline.id == id => {
|
Some(ref root_layer) if root_layer.extra_data.borrow().pipeline.id == id => {
|
||||||
(compositor_layer.pipeline.clone(), compositor_layer.id_of_first_child())
|
(root_layer.extra_data.borrow().pipeline.clone(), CompositorData::id_of_first_child(root_layer.clone()))
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
match self.root_pipeline {
|
match self.root_pipeline {
|
||||||
|
@ -383,31 +375,25 @@ impl IOCompositor {
|
||||||
|
|
||||||
if layer_id != root_layer_id {
|
if layer_id != root_layer_id {
|
||||||
let root_pipeline_id = root_pipeline.id;
|
let root_pipeline_id = root_pipeline.id;
|
||||||
let mut new_layer = CompositorLayer::new_root(root_pipeline,
|
let new_root = Rc::new(ContainerLayer::new(Some(size), self.opts.tile_size,
|
||||||
size,
|
CompositorData::new_root(root_pipeline,
|
||||||
self.opts.tile_size,
|
size, self.opts.cpu_painting)));
|
||||||
self.opts.cpu_painting);
|
new_root.extra_data.borrow_mut().unrendered_color = unrendered_color;
|
||||||
new_layer.unrendered_color = unrendered_color;
|
|
||||||
|
|
||||||
self.root_layer.remove_all_children();
|
let parent_layer_id = new_root.extra_data.borrow().id;
|
||||||
|
assert!(CompositorData::add_child_if_necessary(new_root.clone(),
|
||||||
let new_layer_id = new_layer.id;
|
root_pipeline_id,
|
||||||
assert!(new_layer.add_child_if_necessary(self.root_layer.clone(),
|
parent_layer_id,
|
||||||
root_pipeline_id,
|
layer_id,
|
||||||
new_layer_id,
|
Rect(Point2D(0f32, 0f32), size),
|
||||||
layer_id,
|
size,
|
||||||
Rect(Point2D(0f32, 0f32), size),
|
Scrollable));
|
||||||
size,
|
|
||||||
Scrollable));
|
|
||||||
|
|
||||||
ContainerLayer::add_child_start(self.root_layer.clone(),
|
|
||||||
ContainerLayerKind(new_layer.root_layer.clone()));
|
|
||||||
|
|
||||||
// Release all tiles from the layer before dropping it.
|
// Release all tiles from the layer before dropping it.
|
||||||
for layer in self.compositor_layer.mut_iter() {
|
for layer in self.scene.root.mut_iter() {
|
||||||
layer.clear_all_tiles();
|
CompositorData::clear_all_tiles(layer.clone());
|
||||||
}
|
}
|
||||||
self.compositor_layer = Some(new_layer);
|
self.scene.root = Some(new_root);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.ask_for_tiles();
|
self.ask_for_tiles();
|
||||||
|
@ -418,17 +404,17 @@ impl IOCompositor {
|
||||||
layer_id: LayerId,
|
layer_id: LayerId,
|
||||||
rect: Rect<f32>,
|
rect: Rect<f32>,
|
||||||
scroll_policy: ScrollPolicy) {
|
scroll_policy: ScrollPolicy) {
|
||||||
match self.compositor_layer {
|
match self.scene.root {
|
||||||
Some(ref mut compositor_layer) => {
|
Some(ref root) => {
|
||||||
let compositor_layer_id = compositor_layer.id;
|
let parent_layer_id = root.extra_data.borrow().id;
|
||||||
let page_size = compositor_layer.page_size.unwrap();
|
let page_size = root.extra_data.borrow().page_size.unwrap();
|
||||||
assert!(compositor_layer.add_child_if_necessary(self.root_layer.clone(),
|
assert!(CompositorData::add_child_if_necessary(root.clone(),
|
||||||
pipeline_id,
|
pipeline_id,
|
||||||
compositor_layer_id,
|
parent_layer_id,
|
||||||
layer_id,
|
layer_id,
|
||||||
rect,
|
rect,
|
||||||
page_size,
|
page_size,
|
||||||
scroll_policy))
|
scroll_policy))
|
||||||
}
|
}
|
||||||
None => fail!("Compositor: Received new layer without initialized pipeline"),
|
None => fail!("Compositor: Received new layer without initialized pipeline"),
|
||||||
};
|
};
|
||||||
|
@ -460,11 +446,11 @@ impl IOCompositor {
|
||||||
new_size: Size2D<f32>,
|
new_size: Size2D<f32>,
|
||||||
epoch: Epoch) {
|
epoch: Epoch) {
|
||||||
let page_window = self.page_window();
|
let page_window = self.page_window();
|
||||||
let (ask, move): (bool, bool) = match self.compositor_layer {
|
let (ask, move): (bool, bool) = match self.scene.root {
|
||||||
Some(ref mut layer) => {
|
Some(ref layer) => {
|
||||||
layer.resize(pipeline_id, layer_id, new_size, page_window, epoch);
|
CompositorData::resize(layer.clone(), pipeline_id, layer_id, new_size, page_window, epoch);
|
||||||
let move = self.fragment_point.take().map_or(false, |point| {
|
let move = self.fragment_point.take().map_or(false, |point| {
|
||||||
layer.move(pipeline_id, layer_id, point, page_window)
|
CompositorData::move(layer.clone(), pipeline_id, layer_id, point, page_window)
|
||||||
});
|
});
|
||||||
|
|
||||||
(true, move)
|
(true, move)
|
||||||
|
@ -482,9 +468,9 @@ impl IOCompositor {
|
||||||
pipeline_id: PipelineId,
|
pipeline_id: PipelineId,
|
||||||
layer_id: LayerId,
|
layer_id: LayerId,
|
||||||
new_rect: Rect<f32>) {
|
new_rect: Rect<f32>) {
|
||||||
let ask: bool = match self.compositor_layer {
|
let ask: bool = match self.scene.root {
|
||||||
Some(ref mut layer) => {
|
Some(ref layer) => {
|
||||||
assert!(layer.set_clipping_rect(pipeline_id, layer_id, new_rect));
|
assert!(CompositorData::set_clipping_rect(layer.clone(), pipeline_id, layer_id, new_rect));
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
@ -508,17 +494,18 @@ impl IOCompositor {
|
||||||
let mut new_layer_buffer_set = new_layer_buffer_set;
|
let mut new_layer_buffer_set = new_layer_buffer_set;
|
||||||
new_layer_buffer_set.mark_will_leak();
|
new_layer_buffer_set.mark_will_leak();
|
||||||
|
|
||||||
match self.compositor_layer {
|
match self.scene.root {
|
||||||
Some(ref mut layer) => {
|
Some(ref layer) => {
|
||||||
assert!(layer.add_buffers(&self.graphics_context,
|
assert!(CompositorData::add_buffers(layer.clone(),
|
||||||
pipeline_id,
|
&self.graphics_context,
|
||||||
layer_id,
|
pipeline_id,
|
||||||
new_layer_buffer_set,
|
layer_id,
|
||||||
epoch).is_none());
|
new_layer_buffer_set,
|
||||||
|
epoch).is_none());
|
||||||
self.recomposite = true;
|
self.recomposite = true;
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
fail!("compositor given paint command with no CompositorLayer initialized");
|
fail!("compositor given paint command with no root layer initialized");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -531,9 +518,9 @@ impl IOCompositor {
|
||||||
layer_id: LayerId,
|
layer_id: LayerId,
|
||||||
point: Point2D<f32>) {
|
point: Point2D<f32>) {
|
||||||
let page_window = self.page_window();
|
let page_window = self.page_window();
|
||||||
let (ask, move): (bool, bool) = match self.compositor_layer {
|
let (ask, move): (bool, bool) = match self.scene.root {
|
||||||
Some(ref mut layer) if layer.pipeline.id == pipeline_id && !layer.hidden => {
|
Some(ref layer) if layer.extra_data.borrow().pipeline.id == pipeline_id && !layer.extra_data.borrow().hidden => {
|
||||||
(true, layer.move(pipeline_id, layer_id, point, page_window))
|
(true, CompositorData::move(layer.clone(), pipeline_id, layer_id, point, page_window))
|
||||||
}
|
}
|
||||||
Some(_) | None => {
|
Some(_) | None => {
|
||||||
self.fragment_point = Some(point);
|
self.fragment_point = Some(point);
|
||||||
|
@ -626,8 +613,8 @@ impl IOCompositor {
|
||||||
fn on_load_url_window_event(&mut self, url_string: String) {
|
fn on_load_url_window_event(&mut self, url_string: String) {
|
||||||
debug!("osmain: loading URL `{:s}`", url_string);
|
debug!("osmain: loading URL `{:s}`", url_string);
|
||||||
self.load_complete = false;
|
self.load_complete = false;
|
||||||
let root_pipeline_id = match self.compositor_layer {
|
let root_pipeline_id = match self.scene.root {
|
||||||
Some(ref layer) => layer.pipeline.id.clone(),
|
Some(ref layer) => layer.extra_data.borrow().pipeline.id.clone(),
|
||||||
None => fail!("Compositor: Received LoadUrlWindowEvent without initialized compositor layers"),
|
None => fail!("Compositor: Received LoadUrlWindowEvent without initialized compositor layers"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -643,15 +630,15 @@ impl IOCompositor {
|
||||||
MouseWindowMouseDownEvent(_, p) => p / scale,
|
MouseWindowMouseDownEvent(_, p) => p / scale,
|
||||||
MouseWindowMouseUpEvent(_, p) => p / scale,
|
MouseWindowMouseUpEvent(_, p) => p / scale,
|
||||||
};
|
};
|
||||||
for layer in self.compositor_layer.iter() {
|
for layer in self.scene.root.iter() {
|
||||||
layer.send_mouse_event(mouse_window_event, point);
|
CompositorData::send_mouse_event(layer.clone(), mouse_window_event, point);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_mouse_window_move_event_class(&self, cursor: TypedPoint2D<DevicePixel, f32>) {
|
fn on_mouse_window_move_event_class(&self, cursor: TypedPoint2D<DevicePixel, f32>) {
|
||||||
let scale = self.device_pixels_per_page_px();
|
let scale = self.device_pixels_per_page_px();
|
||||||
for layer in self.compositor_layer.iter() {
|
for layer in self.scene.root.iter() {
|
||||||
layer.send_mouse_move_event(cursor / scale);
|
CompositorData::send_mouse_move_event(layer.clone(), cursor / scale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -664,8 +651,8 @@ impl IOCompositor {
|
||||||
let page_cursor = cursor.as_f32() / scale;
|
let page_cursor = cursor.as_f32() / scale;
|
||||||
let page_window = self.page_window();
|
let page_window = self.page_window();
|
||||||
let mut scroll = false;
|
let mut scroll = false;
|
||||||
for layer in self.compositor_layer.mut_iter() {
|
for layer in self.scene.root.mut_iter() {
|
||||||
scroll = layer.handle_scroll_event(page_delta, page_cursor, page_window) || scroll;
|
scroll = CompositorData::handle_scroll_event(layer.clone(), page_delta, page_cursor, page_window) || scroll;
|
||||||
}
|
}
|
||||||
self.recomposite_if(scroll);
|
self.recomposite_if(scroll);
|
||||||
self.ask_for_tiles();
|
self.ask_for_tiles();
|
||||||
|
@ -687,7 +674,7 @@ impl IOCompositor {
|
||||||
|
|
||||||
fn update_zoom_transform(&mut self) {
|
fn update_zoom_transform(&mut self) {
|
||||||
let scale = self.device_pixels_per_page_px();
|
let scale = self.device_pixels_per_page_px();
|
||||||
self.root_layer.common.borrow_mut().set_transform(identity().scale(scale.get(), scale.get(), 1f32));
|
self.scene.set_transform(identity().scale(scale.get(), scale.get(), 1f32));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_zoom_window_event(&mut self, magnification: f32) {
|
fn on_zoom_window_event(&mut self, magnification: f32) {
|
||||||
|
@ -715,8 +702,8 @@ impl IOCompositor {
|
||||||
let page_cursor = TypedPoint2D(-1f32, -1f32); // Make sure this hits the base layer
|
let page_cursor = TypedPoint2D(-1f32, -1f32); // Make sure this hits the base layer
|
||||||
let page_window = self.page_window();
|
let page_window = self.page_window();
|
||||||
|
|
||||||
for layer in self.compositor_layer.mut_iter() {
|
for layer in self.scene.root.mut_iter() {
|
||||||
layer.handle_scroll_event(page_delta, page_cursor, page_window);
|
CompositorData::handle_scroll_event(layer.clone(), page_delta, page_cursor, page_window);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.recomposite = true;
|
self.recomposite = true;
|
||||||
|
@ -735,13 +722,13 @@ impl IOCompositor {
|
||||||
fn ask_for_tiles(&mut self) {
|
fn ask_for_tiles(&mut self) {
|
||||||
let scale = self.device_pixels_per_page_px();
|
let scale = self.device_pixels_per_page_px();
|
||||||
let page_window = self.page_window();
|
let page_window = self.page_window();
|
||||||
for layer in self.compositor_layer.mut_iter() {
|
for layer in self.scene.root.mut_iter() {
|
||||||
if !layer.hidden {
|
if !layer.extra_data.borrow().hidden {
|
||||||
let rect = Rect(Point2D(0f32, 0f32), page_window.to_untyped());
|
let rect = Rect(Point2D(0f32, 0f32), page_window.to_untyped());
|
||||||
let recomposite = layer.get_buffer_request(&self.graphics_context,
|
let recomposite = CompositorData::get_buffer_request(layer.clone(),
|
||||||
rect,
|
&self.graphics_context,
|
||||||
scale.get()) ||
|
rect,
|
||||||
self.recomposite;
|
scale.get()) || self.recomposite;
|
||||||
self.recomposite = recomposite;
|
self.recomposite = recomposite;
|
||||||
} else {
|
} else {
|
||||||
debug!("Compositor: root layer is hidden!");
|
debug!("Compositor: root layer is hidden!");
|
||||||
|
@ -755,16 +742,16 @@ impl IOCompositor {
|
||||||
// Adjust the layer dimensions as necessary to correspond to the size of the window.
|
// Adjust the layer dimensions as necessary to correspond to the size of the window.
|
||||||
self.scene.size = self.window_size.as_f32().to_untyped();
|
self.scene.size = self.window_size.as_f32().to_untyped();
|
||||||
// Render the scene.
|
// Render the scene.
|
||||||
match self.compositor_layer {
|
match self.scene.root {
|
||||||
Some(ref mut layer) => {
|
Some(ref layer) => {
|
||||||
self.scene.background_color.r = layer.unrendered_color.r;
|
self.scene.background_color.r = layer.extra_data.borrow().unrendered_color.r;
|
||||||
self.scene.background_color.g = layer.unrendered_color.g;
|
self.scene.background_color.g = layer.extra_data.borrow().unrendered_color.g;
|
||||||
self.scene.background_color.b = layer.unrendered_color.b;
|
self.scene.background_color.b = layer.extra_data.borrow().unrendered_color.b;
|
||||||
self.scene.background_color.a = layer.unrendered_color.a;
|
self.scene.background_color.a = layer.extra_data.borrow().unrendered_color.a;
|
||||||
|
rendergl::render_scene(layer.clone(), self.context, &self.scene);
|
||||||
}
|
}
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
rendergl::render_scene(self.context, &self.scene);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Render to PNG. We must read from the back buffer (ie, before
|
// Render to PNG. We must read from the back buffer (ie, before
|
||||||
|
|
776
src/components/compositing/compositor_data.rs
Normal file
776
src/components/compositing/compositor_data.rs
Normal file
|
@ -0,0 +1,776 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
use pipeline::CompositionPipeline;
|
||||||
|
use windowing::{MouseWindowEvent, MouseWindowClickEvent, MouseWindowMouseDownEvent};
|
||||||
|
use windowing::{MouseWindowMouseUpEvent};
|
||||||
|
|
||||||
|
use azure::azure_hl::Color;
|
||||||
|
use geom::length::Length;
|
||||||
|
use geom::matrix::identity;
|
||||||
|
use geom::point::{Point2D, TypedPoint2D};
|
||||||
|
use geom::rect::{Rect, TypedRect};
|
||||||
|
use geom::size::{Size2D, TypedSize2D};
|
||||||
|
use gfx::render_task::{ReRenderMsg, UnusedBufferMsg};
|
||||||
|
use gfx;
|
||||||
|
use layers::layers::{ContainerLayer, Flip, LayerBuffer, LayerBufferSet, NoFlip, TextureLayer};
|
||||||
|
use layers::quadtree::{Tile, Normal, Hidden};
|
||||||
|
use layers::platform::surface::{NativeCompositingGraphicsContext, NativeSurfaceMethods};
|
||||||
|
use layers::texturegl::{Texture, TextureTarget};
|
||||||
|
use script::dom::event::{ClickEvent, MouseDownEvent, MouseMoveEvent, MouseUpEvent};
|
||||||
|
use script::script_task::{ScriptChan, SendEventMsg};
|
||||||
|
use servo_msg::compositor_msg::{Epoch, FixedPosition, LayerId};
|
||||||
|
use servo_msg::compositor_msg::ScrollPolicy;
|
||||||
|
use servo_msg::constellation_msg::PipelineId;
|
||||||
|
use servo_util::geometry::PagePx;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
#[cfg(target_os="macos")]
|
||||||
|
#[cfg(target_os="android")]
|
||||||
|
use layers::layers::VerticalFlip;
|
||||||
|
#[cfg(not(target_os="macos"))]
|
||||||
|
use layers::texturegl::TextureTarget2D;
|
||||||
|
#[cfg(target_os="macos")]
|
||||||
|
use layers::texturegl::TextureTargetRectangle;
|
||||||
|
|
||||||
|
pub struct CompositorData {
|
||||||
|
/// This layer's pipeline. BufferRequests and mouse events will be sent through this.
|
||||||
|
pub pipeline: CompositionPipeline,
|
||||||
|
|
||||||
|
/// The ID of this layer within the pipeline.
|
||||||
|
pub id: LayerId,
|
||||||
|
|
||||||
|
/// The offset of the page due to scrolling. (0,0) is when the window sees the
|
||||||
|
/// top left corner of the page.
|
||||||
|
pub scroll_offset: TypedPoint2D<PagePx, f32>,
|
||||||
|
|
||||||
|
/// The bounds of this layer in terms of its parent (a.k.a. the scissor box).
|
||||||
|
pub bounds: Rect<f32>,
|
||||||
|
|
||||||
|
/// The size of the underlying page in page coordinates. This is an option
|
||||||
|
/// because we may not know the size of the page until layout is finished completely.
|
||||||
|
/// if we have no size yet, the layer is hidden until a size message is recieved.
|
||||||
|
pub page_size: Option<Size2D<f32>>,
|
||||||
|
|
||||||
|
/// When set to true, this layer is ignored by its parents. This is useful for
|
||||||
|
/// soft deletion or when waiting on a page size.
|
||||||
|
pub hidden: bool,
|
||||||
|
|
||||||
|
/// The behavior of this layer when a scroll message is received.
|
||||||
|
pub wants_scroll_events: WantsScrollEventsFlag,
|
||||||
|
|
||||||
|
/// Whether an ancestor layer that receives scroll events moves this layer.
|
||||||
|
pub scroll_policy: ScrollPolicy,
|
||||||
|
|
||||||
|
/// True if CPU rendering is enabled, false if we're using GPU rendering.
|
||||||
|
pub cpu_painting: bool,
|
||||||
|
|
||||||
|
/// The color to use for the unrendered-content void
|
||||||
|
pub unrendered_color: Color,
|
||||||
|
|
||||||
|
pub scissor: Option<Rect<f32>>,
|
||||||
|
|
||||||
|
/// A monotonically increasing counter that keeps track of the current epoch.
|
||||||
|
/// add_buffer() calls that don't match the current epoch will be ignored.
|
||||||
|
pub epoch: Epoch,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deriving(PartialEq, Clone)]
|
||||||
|
pub enum WantsScrollEventsFlag {
|
||||||
|
WantsScrollEvents,
|
||||||
|
DoesntWantScrollEvents,
|
||||||
|
}
|
||||||
|
|
||||||
|
trait Clampable {
|
||||||
|
fn clamp(&self, mn: &Self, mx: &Self) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clampable for f32 {
|
||||||
|
/// Returns the number constrained within the range `mn <= self <= mx`.
|
||||||
|
/// If any of the numbers are `NAN` then `NAN` is returned.
|
||||||
|
#[inline]
|
||||||
|
fn clamp(&self, mn: &f32, mx: &f32) -> f32 {
|
||||||
|
match () {
|
||||||
|
_ if self.is_nan() => *self,
|
||||||
|
_ if !(*self <= *mx) => *mx,
|
||||||
|
_ if !(*self >= *mn) => *mn,
|
||||||
|
_ => *self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CompositorData {
|
||||||
|
pub fn new(pipeline: CompositionPipeline,
|
||||||
|
layer_id: LayerId,
|
||||||
|
bounds: Rect<f32>,
|
||||||
|
page_size: Option<Size2D<f32>>,
|
||||||
|
cpu_painting: bool,
|
||||||
|
wants_scroll_events: WantsScrollEventsFlag,
|
||||||
|
scroll_policy: ScrollPolicy,
|
||||||
|
hidden: bool) -> CompositorData {
|
||||||
|
CompositorData {
|
||||||
|
pipeline: pipeline,
|
||||||
|
id: layer_id,
|
||||||
|
scroll_offset: TypedPoint2D(0f32, 0f32),
|
||||||
|
bounds: bounds,
|
||||||
|
page_size: page_size,
|
||||||
|
hidden: hidden,
|
||||||
|
wants_scroll_events: wants_scroll_events,
|
||||||
|
scroll_policy: scroll_policy,
|
||||||
|
cpu_painting: cpu_painting,
|
||||||
|
unrendered_color: gfx::color::rgba(0.0, 0.0, 0.0, 0.0),
|
||||||
|
scissor: None,
|
||||||
|
epoch: Epoch(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_root(pipeline: CompositionPipeline,
|
||||||
|
page_size: Size2D<f32>,
|
||||||
|
cpu_painting: bool) -> CompositorData {
|
||||||
|
CompositorData::new(pipeline,
|
||||||
|
LayerId::null(),
|
||||||
|
Rect(Point2D(0f32, 0f32), page_size),
|
||||||
|
Some(page_size),
|
||||||
|
cpu_painting,
|
||||||
|
WantsScrollEvents,
|
||||||
|
FixedPosition,
|
||||||
|
false)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn id_of_first_child(layer: Rc<ContainerLayer<CompositorData>>) -> LayerId {
|
||||||
|
layer.children().next().expect("no first child!").extra_data.borrow().id
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a child layer to the layer with the given ID and the given pipeline, if it doesn't
|
||||||
|
/// exist yet. The child layer will have the same pipeline, tile size, memory limit, and CPU
|
||||||
|
/// painting status as its parent.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// * True if the layer was added;
|
||||||
|
/// * True if the layer was not added because it already existed;
|
||||||
|
/// * False if the layer could not be added because no suitable parent layer with the given
|
||||||
|
/// ID and pipeline could be found.
|
||||||
|
pub fn add_child_if_necessary(layer: Rc<ContainerLayer<CompositorData>>,
|
||||||
|
pipeline_id: PipelineId,
|
||||||
|
parent_layer_id: LayerId,
|
||||||
|
child_layer_id: LayerId,
|
||||||
|
rect: Rect<f32>,
|
||||||
|
page_size: Size2D<f32>,
|
||||||
|
scroll_policy: ScrollPolicy) -> bool {
|
||||||
|
if layer.extra_data.borrow().pipeline.id != pipeline_id || layer.extra_data.borrow().id != parent_layer_id {
|
||||||
|
return layer.children().any(|kid| {
|
||||||
|
CompositorData::add_child_if_necessary(kid,
|
||||||
|
pipeline_id,
|
||||||
|
parent_layer_id,
|
||||||
|
child_layer_id,
|
||||||
|
rect,
|
||||||
|
page_size,
|
||||||
|
scroll_policy)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// See if we've already made this child layer.
|
||||||
|
if layer.children().any(|kid| {
|
||||||
|
kid.extra_data.borrow().pipeline.id == pipeline_id &&
|
||||||
|
kid.extra_data.borrow().id == child_layer_id
|
||||||
|
}) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_kid = Rc::new(ContainerLayer::new(Some(page_size),
|
||||||
|
ContainerLayer::tile_size(layer.clone()),
|
||||||
|
CompositorData::new(layer.extra_data.borrow().pipeline.clone(),
|
||||||
|
child_layer_id,
|
||||||
|
rect,
|
||||||
|
Some(page_size),
|
||||||
|
layer.extra_data.borrow().cpu_painting,
|
||||||
|
DoesntWantScrollEvents,
|
||||||
|
scroll_policy,
|
||||||
|
false)));
|
||||||
|
|
||||||
|
new_kid.extra_data.borrow_mut().scissor = Some(rect);
|
||||||
|
new_kid.common.borrow_mut().origin = rect.origin;
|
||||||
|
|
||||||
|
// Place the kid's layer in the container passed in.
|
||||||
|
ContainerLayer::add_child_end(layer.clone(), new_kid.clone());
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Move the layer's descendants that don't want scroll events and scroll by a relative
|
||||||
|
/// specified amount in page coordinates. This also takes in a cursor position to see if the
|
||||||
|
/// mouse is over child layers first. If a layer successfully scrolled, returns true; otherwise
|
||||||
|
/// returns false, so a parent layer can scroll instead.
|
||||||
|
pub fn handle_scroll_event(layer: Rc<ContainerLayer<CompositorData>>,
|
||||||
|
delta: TypedPoint2D<PagePx, f32>,
|
||||||
|
cursor: TypedPoint2D<PagePx, f32>,
|
||||||
|
window_size: TypedSize2D<PagePx, f32>)
|
||||||
|
-> bool {
|
||||||
|
// If this layer is hidden, neither it nor its children will scroll.
|
||||||
|
if layer.extra_data.borrow().hidden {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this layer doesn't want scroll events, neither it nor its children can handle scroll
|
||||||
|
// events.
|
||||||
|
if layer.extra_data.borrow().wants_scroll_events != WantsScrollEvents {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow children to scroll.
|
||||||
|
let cursor = cursor - layer.extra_data.borrow().scroll_offset;
|
||||||
|
for child in layer.children() {
|
||||||
|
match child.extra_data.borrow().scissor {
|
||||||
|
None => {
|
||||||
|
error!("CompositorData: unable to perform cursor hit test for layer");
|
||||||
|
}
|
||||||
|
Some(rect) => {
|
||||||
|
let rect: TypedRect<PagePx, f32> = Rect::from_untyped(&rect);
|
||||||
|
if cursor.x >= rect.origin.x && cursor.x < rect.origin.x + rect.size.width
|
||||||
|
&& cursor.y >= rect.origin.y && cursor.y < rect.origin.y + rect.size.height
|
||||||
|
&& CompositorData::handle_scroll_event(child.clone(),
|
||||||
|
delta,
|
||||||
|
cursor - rect.origin,
|
||||||
|
rect.size) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This scroll event is mine!
|
||||||
|
// Scroll this layer!
|
||||||
|
let old_origin = layer.extra_data.borrow().scroll_offset.clone();
|
||||||
|
layer.extra_data.borrow_mut().scroll_offset = old_origin + delta;
|
||||||
|
|
||||||
|
// bounds checking
|
||||||
|
let page_size = match layer.extra_data.borrow().page_size {
|
||||||
|
Some(size) => size,
|
||||||
|
None => fail!("CompositorData: tried to scroll with no page size set"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let window_size = window_size.to_untyped();
|
||||||
|
let scroll_offset = layer.extra_data.borrow().scroll_offset.to_untyped();
|
||||||
|
|
||||||
|
let min_x = (window_size.width - page_size.width).min(0.0);
|
||||||
|
layer.extra_data.borrow_mut().scroll_offset.x = Length(scroll_offset.x.clamp(&min_x, &0.0));
|
||||||
|
|
||||||
|
let min_y = (window_size.height - page_size.height).min(0.0);
|
||||||
|
layer.extra_data.borrow_mut().scroll_offset.y = Length(scroll_offset.y.clamp(&min_y, &0.0));
|
||||||
|
|
||||||
|
if old_origin - layer.extra_data.borrow().scroll_offset == TypedPoint2D(0f32, 0f32) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
let offset = layer.extra_data.borrow().scroll_offset.clone();
|
||||||
|
CompositorData::scroll(layer.clone(), offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Actually scrolls the descendants of a layer that scroll. This is called by
|
||||||
|
/// `handle_scroll_event` above when it determines that a layer wants to scroll.
|
||||||
|
fn scroll(layer: Rc<ContainerLayer<CompositorData>>, scroll_offset: TypedPoint2D<PagePx, f32>) -> bool {
|
||||||
|
let mut result = false;
|
||||||
|
|
||||||
|
// Only scroll this layer if it's not fixed-positioned.
|
||||||
|
if layer.extra_data.borrow().scroll_policy != FixedPosition {
|
||||||
|
// Scroll this layer!
|
||||||
|
layer.extra_data.borrow_mut().scroll_offset = scroll_offset;
|
||||||
|
|
||||||
|
let scroll_offset = layer.extra_data.borrow().scroll_offset.clone();
|
||||||
|
layer.common.borrow_mut().set_transform(
|
||||||
|
identity().translate(scroll_offset.x.get(), scroll_offset.y.get(), 0.0));
|
||||||
|
|
||||||
|
result = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for child in layer.children() {
|
||||||
|
result = CompositorData::scroll(child.clone(), scroll_offset) || result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Takes in a MouseWindowEvent, determines if it should be passed to children, and
|
||||||
|
// sends the event off to the appropriate pipeline. NB: the cursor position is in
|
||||||
|
// page coordinates.
|
||||||
|
pub fn send_mouse_event(layer: Rc<ContainerLayer<CompositorData>>, event: MouseWindowEvent, cursor: TypedPoint2D<PagePx, f32>) {
|
||||||
|
let cursor = cursor - layer.extra_data.borrow().scroll_offset;
|
||||||
|
for child in layer.children() {
|
||||||
|
if child.extra_data.borrow().hidden {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
match child.extra_data.borrow().scissor {
|
||||||
|
None => {
|
||||||
|
error!("CompositorData: unable to perform cursor hit test for layer");
|
||||||
|
}
|
||||||
|
Some(rect) => {
|
||||||
|
let rect: TypedRect<PagePx, f32> = Rect::from_untyped(&rect);
|
||||||
|
if cursor.x >= rect.origin.x && cursor.x < rect.origin.x + rect.size.width
|
||||||
|
&& cursor.y >= rect.origin.y && cursor.y < rect.origin.y + rect.size.height {
|
||||||
|
CompositorData::send_mouse_event(child.clone(), event, cursor - rect.origin);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This mouse event is mine!
|
||||||
|
let message = match event {
|
||||||
|
MouseWindowClickEvent(button, _) => ClickEvent(button, cursor.to_untyped()),
|
||||||
|
MouseWindowMouseDownEvent(button, _) => MouseDownEvent(button, cursor.to_untyped()),
|
||||||
|
MouseWindowMouseUpEvent(button, _) => MouseUpEvent(button, cursor.to_untyped()),
|
||||||
|
};
|
||||||
|
let ScriptChan(ref chan) = layer.extra_data.borrow().pipeline.script_chan;
|
||||||
|
let _ = chan.send_opt(SendEventMsg(layer.extra_data.borrow().pipeline.id.clone(), message));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_mouse_move_event(layer: Rc<ContainerLayer<CompositorData>>, cursor: TypedPoint2D<PagePx, f32>) {
|
||||||
|
let message = MouseMoveEvent(cursor.to_untyped());
|
||||||
|
let ScriptChan(ref chan) = layer.extra_data.borrow().pipeline.script_chan;
|
||||||
|
let _ = chan.send_opt(SendEventMsg(layer.extra_data.borrow().pipeline.id.clone(), message));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given the current window size, determine which tiles need to be (re-)rendered and sends them
|
||||||
|
// off the the appropriate renderer. Returns true if and only if the scene should be repainted.
|
||||||
|
pub fn get_buffer_request(layer: Rc<ContainerLayer<CompositorData>>,
|
||||||
|
graphics_context: &NativeCompositingGraphicsContext,
|
||||||
|
window_rect: Rect<f32>,
|
||||||
|
scale: f32)
|
||||||
|
-> bool {
|
||||||
|
let (request, unused) = ContainerLayer::get_tile_rects_page(layer.clone(), window_rect, scale);
|
||||||
|
let redisplay = !unused.is_empty();
|
||||||
|
if redisplay {
|
||||||
|
// Send back unused tiles.
|
||||||
|
let _ = layer.extra_data.borrow().pipeline.render_chan.send_opt(UnusedBufferMsg(unused));
|
||||||
|
}
|
||||||
|
if !request.is_empty() {
|
||||||
|
// Ask for tiles.
|
||||||
|
//
|
||||||
|
// FIXME(#2003, pcwalton): We may want to batch these up in the case in which
|
||||||
|
// one page has multiple layers, to avoid the user seeing inconsistent states.
|
||||||
|
let msg = ReRenderMsg(request, scale, layer.extra_data.borrow().id, layer.extra_data.borrow().epoch);
|
||||||
|
let _ = layer.extra_data.borrow().pipeline.render_chan.send_opt(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if redisplay {
|
||||||
|
CompositorData::build_layer_tree(layer.clone(), graphics_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
let transform = |kid: Rc<ContainerLayer<CompositorData>>| -> bool {
|
||||||
|
match kid.extra_data.borrow().scissor {
|
||||||
|
Some(scissor) => {
|
||||||
|
let mut new_rect = window_rect;
|
||||||
|
let offset = kid.extra_data.borrow().scroll_offset.to_untyped();
|
||||||
|
new_rect.origin.x = new_rect.origin.x - offset.x;
|
||||||
|
new_rect.origin.y = new_rect.origin.y - offset.y;
|
||||||
|
match new_rect.intersection(&scissor) {
|
||||||
|
Some(new_rect) => {
|
||||||
|
// Child layers act as if they are rendered at (0,0), so we
|
||||||
|
// subtract the layer's (x,y) coords in its containing page
|
||||||
|
// to make the child_rect appear in coordinates local to it.
|
||||||
|
let child_rect = Rect(new_rect.origin.sub(&scissor.origin),
|
||||||
|
new_rect.size);
|
||||||
|
CompositorData::get_buffer_request(kid.clone(), graphics_context, child_rect, scale)
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
false // Layer is offscreen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => fail!("child layer not clipped!"),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
layer.children().filter(|x| !x.extra_data.borrow().hidden)
|
||||||
|
.map(transform)
|
||||||
|
.fold(false, |a, b| a || b) || redisplay
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the sublayer to an absolute position in page coordinates relative to its parent,
|
||||||
|
// and clip the layer to the specified size in page coordinates.
|
||||||
|
// If the layer is hidden and has a defined page size, unhide it.
|
||||||
|
// This method returns false if the specified layer is not found.
|
||||||
|
pub fn set_clipping_rect(layer: Rc<ContainerLayer<CompositorData>>,
|
||||||
|
pipeline_id: PipelineId,
|
||||||
|
layer_id: LayerId,
|
||||||
|
new_rect: Rect<f32>)
|
||||||
|
-> bool {
|
||||||
|
debug!("compositor_data: starting set_clipping_rect()");
|
||||||
|
match CompositorData::find_child_with_layer_and_pipeline_id(layer.clone(), pipeline_id, layer_id) {
|
||||||
|
Some(child_node) => {
|
||||||
|
debug!("compositor_data: node found for set_clipping_rect()");
|
||||||
|
child_node.common.borrow_mut().origin = new_rect.origin;
|
||||||
|
let old_rect = child_node.extra_data.borrow().scissor.clone();
|
||||||
|
child_node.extra_data.borrow_mut().scissor = Some(new_rect);
|
||||||
|
match old_rect {
|
||||||
|
Some(old_rect) => {
|
||||||
|
ContainerLayer::set_status_page(layer.clone(), old_rect, Normal, false); // Rect is unhidden
|
||||||
|
}
|
||||||
|
None => {} // Nothing to do
|
||||||
|
}
|
||||||
|
ContainerLayer::set_status_page(layer.clone(), new_rect, Hidden, false); // Hide the new rect
|
||||||
|
|
||||||
|
// If possible, unhide child
|
||||||
|
let mut child_data = child_node.extra_data.borrow_mut();
|
||||||
|
if child_data.hidden && child_data.page_size.is_some() {
|
||||||
|
child_data.hidden = false;
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
layer.children()
|
||||||
|
.any(|kid| CompositorData::set_clipping_rect(kid.clone(), pipeline_id, layer_id, new_rect))
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the layer's page size. This signals that the renderer is ready for BufferRequests.
|
||||||
|
// If the layer is hidden and has a defined clipping rect, unhide it.
|
||||||
|
// This method returns false if the specified layer is not found.
|
||||||
|
pub fn resize(layer: Rc<ContainerLayer<CompositorData>>,
|
||||||
|
pipeline_id: PipelineId,
|
||||||
|
layer_id: LayerId,
|
||||||
|
new_size: Size2D<f32>,
|
||||||
|
window_size: TypedSize2D<PagePx, f32>,
|
||||||
|
epoch: Epoch)
|
||||||
|
-> bool {
|
||||||
|
debug!("compositor_data: starting resize()");
|
||||||
|
if layer.extra_data.borrow().pipeline.id != pipeline_id || layer.extra_data.borrow().id != layer_id {
|
||||||
|
return CompositorData::resize_helper(layer.clone(), pipeline_id, layer_id, new_size, epoch)
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("compositor_data: layer found for resize()");
|
||||||
|
layer.extra_data.borrow_mut().epoch = epoch;
|
||||||
|
layer.extra_data.borrow_mut().page_size = Some(new_size);
|
||||||
|
|
||||||
|
let unused_buffers = ContainerLayer::resize(layer.clone(), new_size);
|
||||||
|
if !unused_buffers.is_empty() {
|
||||||
|
let _ = layer.extra_data.borrow().pipeline
|
||||||
|
.render_chan
|
||||||
|
.send_opt(UnusedBufferMsg(unused_buffers));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call scroll for bounds checking if the page shrunk. Use (-1, -1) as the cursor position
|
||||||
|
// to make sure the scroll isn't propagated downwards.
|
||||||
|
CompositorData::handle_scroll_event(layer.clone(), TypedPoint2D(0f32, 0f32), TypedPoint2D(-1f32, -1f32), window_size);
|
||||||
|
layer.extra_data.borrow_mut().hidden = false;
|
||||||
|
CompositorData::set_occlusions(layer.clone());
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn move(layer: Rc<ContainerLayer<CompositorData>>,
|
||||||
|
pipeline_id: PipelineId,
|
||||||
|
layer_id: LayerId,
|
||||||
|
origin: Point2D<f32>,
|
||||||
|
window_size: TypedSize2D<PagePx, f32>)
|
||||||
|
-> bool {
|
||||||
|
// Search children for the right layer to move.
|
||||||
|
if layer.extra_data.borrow().pipeline.id != pipeline_id || layer.extra_data.borrow().id != layer_id {
|
||||||
|
return layer.children().any(|kid| {
|
||||||
|
CompositorData::move(kid.clone(), pipeline_id, layer_id, origin, window_size)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if layer.extra_data.borrow().wants_scroll_events != WantsScrollEvents {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scroll this layer!
|
||||||
|
let old_origin = layer.extra_data.borrow().scroll_offset;
|
||||||
|
layer.extra_data.borrow_mut().scroll_offset = Point2D::from_untyped(&(origin * -1.0));
|
||||||
|
|
||||||
|
// bounds checking
|
||||||
|
let page_size = match layer.extra_data.borrow().page_size {
|
||||||
|
Some(size) => size,
|
||||||
|
None => fail!("CompositorData: tried to scroll with no page size set"),
|
||||||
|
};
|
||||||
|
let window_size = window_size.to_untyped();
|
||||||
|
let scroll_offset = layer.extra_data.borrow().scroll_offset.to_untyped();
|
||||||
|
|
||||||
|
let min_x = (window_size.width - page_size.width).min(0.0);
|
||||||
|
layer.extra_data.borrow_mut().scroll_offset.x = Length(scroll_offset.x.clamp(&min_x, &0.0));
|
||||||
|
let min_y = (window_size.height - page_size.height).min(0.0);
|
||||||
|
layer.extra_data.borrow_mut().scroll_offset.y = Length(scroll_offset.y.clamp(&min_y, &0.0));
|
||||||
|
|
||||||
|
// check to see if we scrolled
|
||||||
|
if old_origin - layer.extra_data.borrow().scroll_offset == TypedPoint2D(0f32, 0f32) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let offset = layer.extra_data.borrow().scroll_offset.clone();
|
||||||
|
CompositorData::scroll(layer.clone(), offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns whether the layer should be vertically flipped.
|
||||||
|
#[cfg(target_os="macos")]
|
||||||
|
fn texture_flip_and_target(cpu_painting: bool, size: Size2D<uint>) -> (Flip, TextureTarget) {
|
||||||
|
let flip = if cpu_painting {
|
||||||
|
NoFlip
|
||||||
|
} else {
|
||||||
|
VerticalFlip
|
||||||
|
};
|
||||||
|
|
||||||
|
(flip, TextureTargetRectangle(size))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os="android")]
|
||||||
|
fn texture_flip_and_target(cpu_painting: bool, size: Size2D<uint>) -> (Flip, TextureTarget) {
|
||||||
|
let flip = if cpu_painting {
|
||||||
|
NoFlip
|
||||||
|
} else {
|
||||||
|
VerticalFlip
|
||||||
|
};
|
||||||
|
|
||||||
|
(flip, TextureTarget2D)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os="linux")]
|
||||||
|
fn texture_flip_and_target(_: bool, _: Size2D<uint>) -> (Flip, TextureTarget) {
|
||||||
|
(NoFlip, TextureTarget2D)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
fn find_child_with_layer_and_pipeline_id(layer: Rc<ContainerLayer<CompositorData>>,
|
||||||
|
pipeline_id: PipelineId,
|
||||||
|
layer_id: LayerId)
|
||||||
|
-> Option<Rc<ContainerLayer<CompositorData>>> {
|
||||||
|
for kid in layer.children() {
|
||||||
|
if pipeline_id == kid.extra_data.borrow().pipeline.id && layer_id == kid.extra_data.borrow().id {
|
||||||
|
return Some(kid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
|
||||||
|
// A helper method to resize sublayers.
|
||||||
|
fn resize_helper(layer: Rc<ContainerLayer<CompositorData>>,
|
||||||
|
pipeline_id: PipelineId,
|
||||||
|
layer_id: LayerId,
|
||||||
|
new_size: Size2D<f32>,
|
||||||
|
epoch: Epoch)
|
||||||
|
-> bool {
|
||||||
|
debug!("compositor_data: starting resize_helper()");
|
||||||
|
|
||||||
|
let found = match CompositorData::find_child_with_layer_and_pipeline_id(layer.clone(), pipeline_id, layer_id) {
|
||||||
|
Some(child) => {
|
||||||
|
debug!("compositor_data: layer found for resize_helper()");
|
||||||
|
child.extra_data.borrow_mut().epoch = epoch;
|
||||||
|
child.extra_data.borrow_mut().page_size = Some(new_size);
|
||||||
|
|
||||||
|
let unused_buffers = ContainerLayer::resize(child.clone(), new_size);
|
||||||
|
if !unused_buffers.is_empty() {
|
||||||
|
let _ = child.extra_data.borrow().pipeline.render_chan.send_opt(UnusedBufferMsg(unused_buffers));
|
||||||
|
}
|
||||||
|
|
||||||
|
let scissor_clone = child.extra_data.borrow().scissor.clone();
|
||||||
|
match scissor_clone {
|
||||||
|
Some(scissor) => {
|
||||||
|
// Call scroll for bounds checking if the page shrunk. Use (-1, -1) as the
|
||||||
|
// cursor position to make sure the scroll isn't propagated downwards.
|
||||||
|
let size: TypedSize2D<PagePx, f32> = Size2D::from_untyped(&scissor.size);
|
||||||
|
CompositorData::handle_scroll_event(child.clone(),
|
||||||
|
TypedPoint2D(0f32, 0f32),
|
||||||
|
TypedPoint2D(-1f32, -1f32),
|
||||||
|
size);
|
||||||
|
child.extra_data.borrow_mut().hidden = false;
|
||||||
|
}
|
||||||
|
None => {} // Nothing to do
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
None => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if found { // Boolean flag to get around double borrow of self
|
||||||
|
CompositorData::set_occlusions(layer.clone());
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we got here, the layer's ID does not match ours, so recurse on descendents (including
|
||||||
|
// hidden children).
|
||||||
|
layer.children().any(|kid| {
|
||||||
|
CompositorData::resize_helper(kid.clone(), pipeline_id, layer_id, new_size, epoch)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect buffers from the quadtree. This method IS NOT recursive, so child layers
|
||||||
|
// are not rebuilt directly from this method.
|
||||||
|
pub fn build_layer_tree(layer: Rc<ContainerLayer<CompositorData>>, graphics_context: &NativeCompositingGraphicsContext) {
|
||||||
|
// Clear all old textures.
|
||||||
|
layer.tiles.borrow_mut().clear();
|
||||||
|
|
||||||
|
// Add new tiles.
|
||||||
|
ContainerLayer::do_for_all_tiles(layer.clone(), |buffer: &Box<LayerBuffer>| {
|
||||||
|
debug!("osmain: compositing buffer rect {}", buffer.rect);
|
||||||
|
|
||||||
|
let size = Size2D(buffer.screen_pos.size.width as int,
|
||||||
|
buffer.screen_pos.size.height as int);
|
||||||
|
|
||||||
|
debug!("osmain: adding new texture layer");
|
||||||
|
|
||||||
|
// Determine, in a platform-specific way, whether we should flip the texture
|
||||||
|
// and which target to use.
|
||||||
|
let (flip, target) =
|
||||||
|
CompositorData::texture_flip_and_target(layer.extra_data.borrow().cpu_painting,
|
||||||
|
buffer.screen_pos.size);
|
||||||
|
|
||||||
|
// Make a new texture and bind the layer buffer's surface to it.
|
||||||
|
let texture = Texture::new(target);
|
||||||
|
debug!("COMPOSITOR binding to native surface {:d}",
|
||||||
|
buffer.native_surface.get_id() as int);
|
||||||
|
buffer.native_surface.bind_to_texture(graphics_context, &texture, size);
|
||||||
|
|
||||||
|
// Set the layer's transform.
|
||||||
|
let rect = buffer.rect;
|
||||||
|
let transform = identity().translate(rect.origin.x, rect.origin.y, 0.0);
|
||||||
|
let transform = transform.scale(rect.size.width, rect.size.height, 1.0);
|
||||||
|
|
||||||
|
// Make a texture layer and add it.
|
||||||
|
let texture_layer = Rc::new(TextureLayer::new(texture, buffer.screen_pos.size,
|
||||||
|
flip, transform));
|
||||||
|
layer.tiles.borrow_mut().push(texture_layer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add LayerBuffers to the specified layer. Returns the layer buffer set back if the layer that
|
||||||
|
// matches the given pipeline ID was not found; otherwise returns None and consumes the layer
|
||||||
|
// buffer set.
|
||||||
|
//
|
||||||
|
// If the epoch of the message does not match the layer's epoch, the message is ignored, the
|
||||||
|
// layer buffer set is consumed, and None is returned.
|
||||||
|
pub fn add_buffers(layer: Rc<ContainerLayer<CompositorData>>,
|
||||||
|
graphics_context: &NativeCompositingGraphicsContext,
|
||||||
|
pipeline_id: PipelineId,
|
||||||
|
layer_id: LayerId,
|
||||||
|
mut new_buffers: Box<LayerBufferSet>,
|
||||||
|
epoch: Epoch)
|
||||||
|
-> Option<Box<LayerBufferSet>> {
|
||||||
|
debug!("compositor_data: starting add_buffers()");
|
||||||
|
if layer.extra_data.borrow().pipeline.id != pipeline_id || layer.extra_data.borrow().id != layer_id {
|
||||||
|
// ID does not match ours, so recurse on descendents (including hidden children).
|
||||||
|
for child_layer in layer.children() {
|
||||||
|
match CompositorData::add_buffers(child_layer.clone(),
|
||||||
|
graphics_context,
|
||||||
|
pipeline_id,
|
||||||
|
layer_id,
|
||||||
|
new_buffers,
|
||||||
|
epoch) {
|
||||||
|
None => return None,
|
||||||
|
Some(buffers) => new_buffers = buffers,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not found. Give the caller the buffers back.
|
||||||
|
return Some(new_buffers)
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("compositor_data: layers found for add_buffers()");
|
||||||
|
|
||||||
|
if layer.extra_data.borrow().epoch != epoch {
|
||||||
|
debug!("add_buffers: compositor epoch mismatch: {:?} != {:?}, id: {:?}",
|
||||||
|
layer.extra_data.borrow().epoch,
|
||||||
|
epoch,
|
||||||
|
layer.extra_data.borrow().pipeline.id);
|
||||||
|
let _ = layer.extra_data.borrow().pipeline.render_chan.send_opt(UnusedBufferMsg(new_buffers.buffers));
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut unused_tiles = vec!();
|
||||||
|
for buffer in new_buffers.buffers.move_iter().rev() {
|
||||||
|
unused_tiles.push_all_move(ContainerLayer::add_tile_pixel(layer.clone(), buffer));
|
||||||
|
}
|
||||||
|
if !unused_tiles.is_empty() { // send back unused buffers
|
||||||
|
let _ = layer.extra_data.borrow().pipeline.render_chan.send_opt(UnusedBufferMsg(unused_tiles));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CompositorData::build_layer_tree(layer.clone(), graphics_context);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively sets occluded portions of quadtrees to Hidden, so that they do not ask for
|
||||||
|
// tile requests. If layers are moved, resized, or deleted, these portions may be updated.
|
||||||
|
fn set_occlusions(layer: Rc<ContainerLayer<CompositorData>>) {
|
||||||
|
for kid in layer.children() {
|
||||||
|
if !kid.extra_data.borrow().hidden {
|
||||||
|
match kid.extra_data.borrow().scissor {
|
||||||
|
None => {} // Nothing to do
|
||||||
|
Some(rect) => {
|
||||||
|
ContainerLayer::set_status_page(layer.clone(), rect, Hidden, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for kid in layer.children() {
|
||||||
|
if !kid.extra_data.borrow().hidden {
|
||||||
|
CompositorData::set_occlusions(kid.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Destroys all quadtree tiles, sending the buffers back to the renderer to be destroyed or
|
||||||
|
/// reused.
|
||||||
|
fn clear(layer: Rc<ContainerLayer<CompositorData>>) {
|
||||||
|
let mut tiles = ContainerLayer::collect_tiles(layer.clone());
|
||||||
|
|
||||||
|
if !tiles.is_empty() {
|
||||||
|
// We have no way of knowing without a race whether the render task is even up and
|
||||||
|
// running, but mark the tiles as not leaking. If the render task died, then the
|
||||||
|
// tiles are going to be cleaned up.
|
||||||
|
for tile in tiles.mut_iter() {
|
||||||
|
tile.mark_wont_leak()
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = layer.extra_data.borrow().pipeline.render_chan.send_opt(UnusedBufferMsg(tiles));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Destroys tiles for this layer and all descendent layers, sending the buffers back to the
|
||||||
|
/// renderer to be destroyed or reused.
|
||||||
|
pub fn clear_all_tiles(layer: Rc<ContainerLayer<CompositorData>>) {
|
||||||
|
CompositorData::clear(layer.clone());
|
||||||
|
for kid in layer.children() {
|
||||||
|
CompositorData::clear_all_tiles(kid.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Destroys all tiles of all layers, including children, *without* sending them back to the
|
||||||
|
/// renderer. You must call this only when the render task is destined to be going down;
|
||||||
|
/// otherwise, you will leak tiles.
|
||||||
|
///
|
||||||
|
/// This is used during shutdown, when we know the render task is going away.
|
||||||
|
pub fn forget_all_tiles(layer: Rc<ContainerLayer<CompositorData>>) {
|
||||||
|
let tiles = ContainerLayer::collect_tiles(layer.clone());
|
||||||
|
for tile in tiles.move_iter() {
|
||||||
|
let mut tile = tile;
|
||||||
|
tile.mark_wont_leak()
|
||||||
|
}
|
||||||
|
|
||||||
|
for kid in layer.children() {
|
||||||
|
CompositorData::forget_all_tiles(kid.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_unrendered_color(layer: Rc<ContainerLayer<CompositorData>>, pipeline_id: PipelineId, layer_id: LayerId, color: Color) -> bool {
|
||||||
|
if layer.extra_data.borrow().pipeline.id != pipeline_id || layer.extra_data.borrow().id != layer_id {
|
||||||
|
for child_layer in layer.children() {
|
||||||
|
if CompositorData::set_unrendered_color(child_layer.clone(), pipeline_id, layer_id, color) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
layer.extra_data.borrow_mut().unrendered_color = color;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,963 +0,0 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
use quadtree::{Quadtree, Normal, Hidden};
|
|
||||||
use pipeline::CompositionPipeline;
|
|
||||||
use windowing::{MouseWindowEvent, MouseWindowClickEvent, MouseWindowMouseDownEvent};
|
|
||||||
use windowing::{MouseWindowMouseUpEvent};
|
|
||||||
|
|
||||||
use azure::azure_hl::Color;
|
|
||||||
use geom::length::Length;
|
|
||||||
use geom::matrix::identity;
|
|
||||||
use geom::point::{Point2D, TypedPoint2D};
|
|
||||||
use geom::rect::{Rect, TypedRect};
|
|
||||||
use geom::size::{Size2D, TypedSize2D};
|
|
||||||
use gfx::render_task::{ReRenderMsg, UnusedBufferMsg};
|
|
||||||
use gfx;
|
|
||||||
use layers::layers::{ContainerLayerKind, ContainerLayer, Flip, NoFlip, TextureLayer};
|
|
||||||
use layers::layers::TextureLayerKind;
|
|
||||||
use layers::platform::surface::{NativeCompositingGraphicsContext, NativeSurfaceMethods};
|
|
||||||
use layers::texturegl::{Texture, TextureTarget};
|
|
||||||
use script::dom::event::{ClickEvent, MouseDownEvent, MouseMoveEvent, MouseUpEvent};
|
|
||||||
use script::script_task::{ScriptChan, SendEventMsg};
|
|
||||||
use servo_msg::compositor_msg::{Epoch, FixedPosition, LayerBuffer, LayerBufferSet, LayerId};
|
|
||||||
use servo_msg::compositor_msg::{ScrollPolicy, Tile};
|
|
||||||
use servo_msg::constellation_msg::PipelineId;
|
|
||||||
use servo_util::geometry::PagePx;
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
#[cfg(target_os="macos")]
|
|
||||||
#[cfg(target_os="android")]
|
|
||||||
use layers::layers::VerticalFlip;
|
|
||||||
#[cfg(not(target_os="macos"))]
|
|
||||||
use layers::texturegl::TextureTarget2D;
|
|
||||||
#[cfg(target_os="macos")]
|
|
||||||
use layers::texturegl::TextureTargetRectangle;
|
|
||||||
|
|
||||||
/// The amount of memory usage allowed per layer.
|
|
||||||
static MAX_TILE_MEMORY_PER_LAYER: uint = 10000000;
|
|
||||||
|
|
||||||
/// The CompositorLayer represents an element on a page that has a unique scroll
|
|
||||||
/// or animation behavior. This can include absolute positioned elements, iframes, etc.
|
|
||||||
/// Each layer can also have child layers.
|
|
||||||
///
|
|
||||||
/// FIXME(#2003, pcwalton): This should be merged with the concept of a layer in `rust-layers` and
|
|
||||||
/// ultimately removed, except as a set of helper methods on `rust-layers` layers.
|
|
||||||
pub struct CompositorLayer {
|
|
||||||
/// This layer's pipeline. BufferRequests and mouse events will be sent through this.
|
|
||||||
pub pipeline: CompositionPipeline,
|
|
||||||
|
|
||||||
/// The ID of this layer within the pipeline.
|
|
||||||
pub id: LayerId,
|
|
||||||
|
|
||||||
/// The bounds of this layer in terms of its parent (a.k.a. the scissor box).
|
|
||||||
pub bounds: Rect<f32>,
|
|
||||||
|
|
||||||
/// The size of the underlying page in page coordinates. This is an option
|
|
||||||
/// because we may not know the size of the page until layout is finished completely.
|
|
||||||
/// if we have no size yet, the layer is hidden until a size message is recieved.
|
|
||||||
pub page_size: Option<Size2D<f32>>,
|
|
||||||
|
|
||||||
/// The offset of the page due to scrolling. (0,0) is when the window sees the
|
|
||||||
/// top left corner of the page.
|
|
||||||
pub scroll_offset: TypedPoint2D<PagePx, f32>,
|
|
||||||
|
|
||||||
/// This layer's children. These could be iframes or any element which
|
|
||||||
/// differs in scroll behavior from its parent. Each is associated with a
|
|
||||||
/// ContainerLayer which determines its position relative to its parent and
|
|
||||||
/// clipping rect. Children are stored in the order in which they are drawn.
|
|
||||||
pub children: Vec<CompositorLayerChild>,
|
|
||||||
|
|
||||||
/// This layer's quadtree. This is where all buffers are stored for this layer.
|
|
||||||
pub quadtree: MaybeQuadtree,
|
|
||||||
|
|
||||||
/// The root layer of this CompositorLayer's layer tree. Buffers are collected
|
|
||||||
/// from the quadtree and inserted here when the layer is painted to the screen.
|
|
||||||
pub root_layer: Rc<ContainerLayer>,
|
|
||||||
|
|
||||||
/// When set to true, this layer is ignored by its parents. This is useful for
|
|
||||||
/// soft deletion or when waiting on a page size.
|
|
||||||
pub hidden: bool,
|
|
||||||
|
|
||||||
/// A monotonically increasing counter that keeps track of the current epoch.
|
|
||||||
/// add_buffer() calls that don't match the current epoch will be ignored.
|
|
||||||
pub epoch: Epoch,
|
|
||||||
|
|
||||||
/// The behavior of this layer when a scroll message is received.
|
|
||||||
pub wants_scroll_events: WantsScrollEventsFlag,
|
|
||||||
|
|
||||||
/// Whether an ancestor layer that receives scroll events moves this layer.
|
|
||||||
pub scroll_policy: ScrollPolicy,
|
|
||||||
|
|
||||||
/// True if CPU rendering is enabled, false if we're using GPU rendering.
|
|
||||||
pub cpu_painting: bool,
|
|
||||||
|
|
||||||
/// The color to use for the unrendered-content void
|
|
||||||
pub unrendered_color: Color,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper struct for keeping CompositorLayer children organized.
|
|
||||||
pub struct CompositorLayerChild {
|
|
||||||
/// The child itself.
|
|
||||||
pub child: Box<CompositorLayer>,
|
|
||||||
/// A ContainerLayer managed by the parent node. This deals with clipping and
|
|
||||||
/// positioning, and is added above the child's layer tree.
|
|
||||||
pub container: Rc<ContainerLayer>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper enum for storing quadtrees. Either contains a quadtree, or contains
|
|
||||||
/// information from which a quadtree can be built.
|
|
||||||
enum MaybeQuadtree {
|
|
||||||
Tree(Quadtree<Box<LayerBuffer>>),
|
|
||||||
NoTree(uint, Option<uint>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MaybeQuadtree {
|
|
||||||
fn tile_size(&self) -> uint {
|
|
||||||
match *self {
|
|
||||||
Tree(ref quadtree) => quadtree.max_tile_size,
|
|
||||||
NoTree(tile_size, _) => tile_size,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[deriving(PartialEq, Clone)]
|
|
||||||
pub enum WantsScrollEventsFlag {
|
|
||||||
WantsScrollEvents,
|
|
||||||
DoesntWantScrollEvents,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_container_layer_from_rect(rect: Rect<f32>) -> Rc<ContainerLayer> {
|
|
||||||
let container = Rc::new(ContainerLayer());
|
|
||||||
*container.scissor.borrow_mut() = Some(rect);
|
|
||||||
container.common.borrow_mut().transform =
|
|
||||||
identity().translate(rect.origin.x, rect.origin.y, 0f32);
|
|
||||||
container
|
|
||||||
}
|
|
||||||
|
|
||||||
trait Clampable {
|
|
||||||
fn clamp(&self, mn: &Self, mx: &Self) -> Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clampable for f32 {
|
|
||||||
/// Returns the number constrained within the range `mn <= self <= mx`.
|
|
||||||
/// If any of the numbers are `NAN` then `NAN` is returned.
|
|
||||||
#[inline]
|
|
||||||
fn clamp(&self, mn: &f32, mx: &f32) -> f32 {
|
|
||||||
match () {
|
|
||||||
_ if self.is_nan() => *self,
|
|
||||||
_ if !(*self <= *mx) => *mx,
|
|
||||||
_ if !(*self >= *mn) => *mn,
|
|
||||||
_ => *self,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl CompositorLayer {
|
|
||||||
/// Creates a new `CompositorLayer`.
|
|
||||||
fn new(pipeline: CompositionPipeline,
|
|
||||||
layer_id: LayerId,
|
|
||||||
bounds: Rect<f32>,
|
|
||||||
page_size: Option<Size2D<f32>>,
|
|
||||||
tile_size: uint,
|
|
||||||
cpu_painting: bool,
|
|
||||||
wants_scroll_events: WantsScrollEventsFlag,
|
|
||||||
scroll_policy: ScrollPolicy)
|
|
||||||
-> CompositorLayer {
|
|
||||||
CompositorLayer {
|
|
||||||
pipeline: pipeline,
|
|
||||||
id: layer_id,
|
|
||||||
bounds: bounds,
|
|
||||||
page_size: page_size,
|
|
||||||
scroll_offset: TypedPoint2D(0f32, 0f32),
|
|
||||||
children: vec!(),
|
|
||||||
quadtree: match page_size {
|
|
||||||
None => NoTree(tile_size, Some(MAX_TILE_MEMORY_PER_LAYER)),
|
|
||||||
Some(page_size) => {
|
|
||||||
Tree(Quadtree::new(Size2D(page_size.width as uint, page_size.height as uint),
|
|
||||||
tile_size,
|
|
||||||
Some(MAX_TILE_MEMORY_PER_LAYER)))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
root_layer: Rc::new(ContainerLayer()),
|
|
||||||
hidden: true,
|
|
||||||
epoch: Epoch(0),
|
|
||||||
wants_scroll_events: wants_scroll_events,
|
|
||||||
scroll_policy: scroll_policy,
|
|
||||||
cpu_painting: cpu_painting,
|
|
||||||
unrendered_color: gfx::color::rgba(0.0, 0.0, 0.0, 0.0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new root `CompositorLayer` bound to a composition pipeline with an optional page
|
|
||||||
/// size. If no page size is given, the layer is initially hidden and initialized without a
|
|
||||||
/// quadtree.
|
|
||||||
pub fn new_root(pipeline: CompositionPipeline,
|
|
||||||
page_size: Size2D<f32>,
|
|
||||||
tile_size: uint,
|
|
||||||
cpu_painting: bool)
|
|
||||||
-> CompositorLayer {
|
|
||||||
CompositorLayer {
|
|
||||||
pipeline: pipeline,
|
|
||||||
id: LayerId::null(),
|
|
||||||
bounds: Rect(Point2D(0f32, 0f32), page_size),
|
|
||||||
page_size: Some(page_size),
|
|
||||||
scroll_offset: TypedPoint2D(0f32, 0f32),
|
|
||||||
children: vec!(),
|
|
||||||
quadtree: NoTree(tile_size, Some(MAX_TILE_MEMORY_PER_LAYER)),
|
|
||||||
root_layer: Rc::new(ContainerLayer()),
|
|
||||||
hidden: false,
|
|
||||||
epoch: Epoch(0),
|
|
||||||
wants_scroll_events: WantsScrollEvents,
|
|
||||||
scroll_policy: FixedPosition,
|
|
||||||
cpu_painting: cpu_painting,
|
|
||||||
unrendered_color: gfx::color::rgba(0.0, 0.0, 0.0, 0.0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds a child layer to the layer with the given ID and the given pipeline, if it doesn't
|
|
||||||
/// exist yet. The child layer will have the same pipeline, tile size, memory limit, and CPU
|
|
||||||
/// painting status as its parent.
|
|
||||||
///
|
|
||||||
/// Returns:
|
|
||||||
/// * True if the layer was added;
|
|
||||||
/// * True if the layer was not added because it already existed;
|
|
||||||
/// * False if the layer could not be added because no suitable parent layer with the given
|
|
||||||
/// ID and pipeline could be found.
|
|
||||||
pub fn add_child_if_necessary(&mut self,
|
|
||||||
container_layer: Rc<ContainerLayer>,
|
|
||||||
pipeline_id: PipelineId,
|
|
||||||
parent_layer_id: LayerId,
|
|
||||||
child_layer_id: LayerId,
|
|
||||||
rect: Rect<f32>,
|
|
||||||
page_size: Size2D<f32>,
|
|
||||||
scroll_policy: ScrollPolicy)
|
|
||||||
-> bool {
|
|
||||||
if self.pipeline.id != pipeline_id || self.id != parent_layer_id {
|
|
||||||
return self.children.mut_iter().any(|kid_holder| {
|
|
||||||
kid_holder.child.add_child_if_necessary(kid_holder.container.clone(),
|
|
||||||
pipeline_id,
|
|
||||||
parent_layer_id,
|
|
||||||
child_layer_id,
|
|
||||||
rect,
|
|
||||||
page_size,
|
|
||||||
scroll_policy)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// See if we've already made this child layer.
|
|
||||||
if self.children.iter().any(|kid_holder| {
|
|
||||||
kid_holder.child.pipeline.id == pipeline_id &&
|
|
||||||
kid_holder.child.id == child_layer_id
|
|
||||||
}) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut kid = box CompositorLayer::new(self.pipeline.clone(),
|
|
||||||
child_layer_id,
|
|
||||||
rect,
|
|
||||||
Some(page_size),
|
|
||||||
self.quadtree.tile_size(),
|
|
||||||
self.cpu_painting,
|
|
||||||
DoesntWantScrollEvents,
|
|
||||||
scroll_policy);
|
|
||||||
|
|
||||||
kid.hidden = false;
|
|
||||||
|
|
||||||
// Place the kid's layer in a container...
|
|
||||||
let kid_container = create_container_layer_from_rect(rect);
|
|
||||||
ContainerLayer::add_child_start(kid_container.clone(),
|
|
||||||
ContainerLayerKind(kid.root_layer.clone()));
|
|
||||||
|
|
||||||
// ...and add *that* container as a child of the container passed in.
|
|
||||||
ContainerLayer::add_child_end(container_layer,
|
|
||||||
ContainerLayerKind(kid_container.clone()));
|
|
||||||
|
|
||||||
self.children.push(CompositorLayerChild {
|
|
||||||
child: kid,
|
|
||||||
container: kid_container,
|
|
||||||
});
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Move the layer's descendants that don't want scroll events and scroll by a relative
|
|
||||||
/// specified amount in page coordinates. This also takes in a cursor position to see if the
|
|
||||||
/// mouse is over child layers first. If a layer successfully scrolled, returns true; otherwise
|
|
||||||
/// returns false, so a parent layer can scroll instead.
|
|
||||||
pub fn handle_scroll_event(&mut self,
|
|
||||||
delta: TypedPoint2D<PagePx, f32>,
|
|
||||||
cursor: TypedPoint2D<PagePx, f32>,
|
|
||||||
window_size: TypedSize2D<PagePx, f32>)
|
|
||||||
-> bool {
|
|
||||||
// If this layer is hidden, neither it nor its children will scroll.
|
|
||||||
if self.hidden {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this layer doesn't want scroll events, neither it nor its children can handle scroll
|
|
||||||
// events.
|
|
||||||
if self.wants_scroll_events != WantsScrollEvents {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allow children to scroll.
|
|
||||||
let cursor = cursor - self.scroll_offset;
|
|
||||||
for child in self.children.mut_iter() {
|
|
||||||
match *child.container.scissor.borrow() {
|
|
||||||
None => {
|
|
||||||
error!("CompositorLayer: unable to perform cursor hit test for layer");
|
|
||||||
}
|
|
||||||
Some(rect) => {
|
|
||||||
let rect: TypedRect<PagePx, f32> = Rect::from_untyped(&rect);
|
|
||||||
if cursor.x >= rect.origin.x && cursor.x < rect.origin.x + rect.size.width
|
|
||||||
&& cursor.y >= rect.origin.y && cursor.y < rect.origin.y + rect.size.height
|
|
||||||
&& child.child.handle_scroll_event(delta,
|
|
||||||
cursor - rect.origin,
|
|
||||||
rect.size) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This scroll event is mine!
|
|
||||||
// Scroll this layer!
|
|
||||||
let old_origin = self.scroll_offset;
|
|
||||||
self.scroll_offset = self.scroll_offset + delta;
|
|
||||||
|
|
||||||
// bounds checking
|
|
||||||
let page_size = match self.page_size {
|
|
||||||
Some(size) => size,
|
|
||||||
None => fail!("CompositorLayer: tried to scroll with no page size set"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let window_size = window_size.to_untyped();
|
|
||||||
let scroll_offset = self.scroll_offset.to_untyped();
|
|
||||||
|
|
||||||
let min_x = (window_size.width - page_size.width).min(0.0);
|
|
||||||
self.scroll_offset.x = Length(scroll_offset.x.clamp(&min_x, &0.0));
|
|
||||||
|
|
||||||
let min_y = (window_size.height - page_size.height).min(0.0);
|
|
||||||
self.scroll_offset.y = Length(scroll_offset.y.clamp(&min_y, &0.0));
|
|
||||||
|
|
||||||
if old_origin - self.scroll_offset == TypedPoint2D(0f32, 0f32) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
let offset = self.scroll_offset;
|
|
||||||
self.scroll(offset)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn dump_layer_tree(&self, layer: Rc<ContainerLayer>, indent: String) {
|
|
||||||
println!("{}scissor {:?}", indent, layer.scissor.borrow());
|
|
||||||
for kid in layer.children() {
|
|
||||||
match kid {
|
|
||||||
ContainerLayerKind(ref container_layer) => {
|
|
||||||
self.dump_layer_tree((*container_layer).clone(), format!("{} ", indent));
|
|
||||||
}
|
|
||||||
TextureLayerKind(_) => {
|
|
||||||
println!("{} (texture layer)", indent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Actually scrolls the descendants of a layer that scroll. This is called by
|
|
||||||
/// `handle_scroll_event` above when it determines that a layer wants to scroll.
|
|
||||||
fn scroll(&mut self, scroll_offset: TypedPoint2D<PagePx, f32>) -> bool {
|
|
||||||
let mut result = false;
|
|
||||||
|
|
||||||
// Only scroll this layer if it's not fixed-positioned.
|
|
||||||
if self.scroll_policy != FixedPosition {
|
|
||||||
// Scroll this layer!
|
|
||||||
self.scroll_offset = scroll_offset;
|
|
||||||
|
|
||||||
self.root_layer.common.borrow_mut().set_transform(
|
|
||||||
identity().translate(self.scroll_offset.x.get(), self.scroll_offset.y.get(), 0.0));
|
|
||||||
|
|
||||||
result = true
|
|
||||||
}
|
|
||||||
|
|
||||||
for kid_holder in self.children.mut_iter() {
|
|
||||||
result = kid_holder.child.scroll(scroll_offset) || result;
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Takes in a MouseWindowEvent, determines if it should be passed to children, and
|
|
||||||
// sends the event off to the appropriate pipeline. NB: the cursor position is in
|
|
||||||
// page coordinates.
|
|
||||||
pub fn send_mouse_event(&self, event: MouseWindowEvent, cursor: TypedPoint2D<PagePx, f32>) {
|
|
||||||
let cursor = cursor - self.scroll_offset;
|
|
||||||
for child in self.children.iter().filter(|&x| !x.child.hidden) {
|
|
||||||
match *child.container.scissor.borrow() {
|
|
||||||
None => {
|
|
||||||
error!("CompositorLayer: unable to perform cursor hit test for layer");
|
|
||||||
}
|
|
||||||
Some(rect) => {
|
|
||||||
let rect: TypedRect<PagePx, f32> = Rect::from_untyped(&rect);
|
|
||||||
if cursor.x >= rect.origin.x && cursor.x < rect.origin.x + rect.size.width
|
|
||||||
&& cursor.y >= rect.origin.y && cursor.y < rect.origin.y + rect.size.height {
|
|
||||||
child.child.send_mouse_event(event, cursor - rect.origin);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This mouse event is mine!
|
|
||||||
let message = match event {
|
|
||||||
MouseWindowClickEvent(button, _) => ClickEvent(button, cursor.to_untyped()),
|
|
||||||
MouseWindowMouseDownEvent(button, _) => MouseDownEvent(button, cursor.to_untyped()),
|
|
||||||
MouseWindowMouseUpEvent(button, _) => MouseUpEvent(button, cursor.to_untyped()),
|
|
||||||
};
|
|
||||||
let ScriptChan(ref chan) = self.pipeline.script_chan;
|
|
||||||
let _ = chan.send_opt(SendEventMsg(self.pipeline.id.clone(), message));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn send_mouse_move_event(&self, cursor: TypedPoint2D<PagePx, f32>) {
|
|
||||||
let message = MouseMoveEvent(cursor.to_untyped());
|
|
||||||
let ScriptChan(ref chan) = self.pipeline.script_chan;
|
|
||||||
let _ = chan.send_opt(SendEventMsg(self.pipeline.id.clone(), message));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Given the current window size, determine which tiles need to be (re-)rendered and sends them
|
|
||||||
// off the the appropriate renderer. Returns true if and only if the scene should be repainted.
|
|
||||||
pub fn get_buffer_request(&mut self,
|
|
||||||
graphics_context: &NativeCompositingGraphicsContext,
|
|
||||||
window_rect: Rect<f32>,
|
|
||||||
scale: f32)
|
|
||||||
-> bool {
|
|
||||||
let mut redisplay = false;
|
|
||||||
match self.quadtree {
|
|
||||||
NoTree(..) => {}
|
|
||||||
Tree(ref mut quadtree) => {
|
|
||||||
let (request, unused) = quadtree.get_tile_rects_page(window_rect, scale);
|
|
||||||
|
|
||||||
// Workaround to make redisplay visible outside block.
|
|
||||||
redisplay = !unused.is_empty();
|
|
||||||
if redisplay {
|
|
||||||
// Send back unused tiles.
|
|
||||||
let _ = self.pipeline.render_chan.send_opt(UnusedBufferMsg(unused));
|
|
||||||
}
|
|
||||||
if !request.is_empty() {
|
|
||||||
// Ask for tiles.
|
|
||||||
//
|
|
||||||
// FIXME(#2003, pcwalton): We may want to batch these up in the case in which
|
|
||||||
// one page has multiple layers, to avoid the user seeing inconsistent states.
|
|
||||||
let msg = ReRenderMsg(request, scale, self.id, self.epoch);
|
|
||||||
let _ = self.pipeline.render_chan.send_opt(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if redisplay {
|
|
||||||
self.build_layer_tree(graphics_context);
|
|
||||||
}
|
|
||||||
|
|
||||||
let transform = |x: &mut CompositorLayerChild| -> bool {
|
|
||||||
match *x.container.scissor.borrow() {
|
|
||||||
Some(scissor) => {
|
|
||||||
let mut new_rect = window_rect;
|
|
||||||
let offset = x.child.scroll_offset.to_untyped();
|
|
||||||
new_rect.origin.x = new_rect.origin.x - offset.x;
|
|
||||||
new_rect.origin.y = new_rect.origin.y - offset.y;
|
|
||||||
match new_rect.intersection(&scissor) {
|
|
||||||
Some(new_rect) => {
|
|
||||||
// Child layers act as if they are rendered at (0,0), so we
|
|
||||||
// subtract the layer's (x,y) coords in its containing page
|
|
||||||
// to make the child_rect appear in coordinates local to it.
|
|
||||||
let child_rect = Rect(new_rect.origin.sub(&scissor.origin),
|
|
||||||
new_rect.size);
|
|
||||||
x.child.get_buffer_request(graphics_context, child_rect, scale)
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
false // Layer is offscreen
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => fail!("child layer not clipped!"),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
self.children.mut_iter().filter(|x| !x.child.hidden)
|
|
||||||
.map(transform)
|
|
||||||
.fold(false, |a, b| a || b) || redisplay
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Move the sublayer to an absolute position in page coordinates relative to its parent,
|
|
||||||
// and clip the layer to the specified size in page coordinates.
|
|
||||||
// If the layer is hidden and has a defined page size, unhide it.
|
|
||||||
// This method returns false if the specified layer is not found.
|
|
||||||
pub fn set_clipping_rect(&mut self,
|
|
||||||
pipeline_id: PipelineId,
|
|
||||||
layer_id: LayerId,
|
|
||||||
new_rect: Rect<f32>)
|
|
||||||
-> bool {
|
|
||||||
debug!("compositor_layer: starting set_clipping_rect()");
|
|
||||||
match self.children.iter().position(|kid_holder| {
|
|
||||||
pipeline_id == kid_holder.child.pipeline.id &&
|
|
||||||
layer_id == kid_holder.child.id
|
|
||||||
}) {
|
|
||||||
Some(i) => {
|
|
||||||
debug!("compositor_layer: node found for set_clipping_rect()");
|
|
||||||
let child_node = self.children.get_mut(i);
|
|
||||||
child_node.container.common.borrow_mut().set_transform(
|
|
||||||
identity().translate(new_rect.origin.x, new_rect.origin.y, 0.0));
|
|
||||||
let old_rect = child_node.container.scissor.borrow().clone();
|
|
||||||
*child_node.container.scissor.borrow_mut() = Some(new_rect);
|
|
||||||
match self.quadtree {
|
|
||||||
NoTree(..) => {} // Nothing to do
|
|
||||||
Tree(ref mut quadtree) => {
|
|
||||||
match old_rect {
|
|
||||||
Some(old_rect) => {
|
|
||||||
quadtree.set_status_page(old_rect, Normal, false); // Rect is unhidden
|
|
||||||
}
|
|
||||||
None => {} // Nothing to do
|
|
||||||
}
|
|
||||||
quadtree.set_status_page(new_rect, Hidden, false); // Hide the new rect
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If possible, unhide child
|
|
||||||
if child_node.child.hidden && child_node.child.page_size.is_some() {
|
|
||||||
child_node.child.hidden = false;
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
// ID does not match any of our immediate children, so recurse on
|
|
||||||
// descendents (including hidden children)
|
|
||||||
self.children
|
|
||||||
.mut_iter()
|
|
||||||
.map(|kid_holder| &mut kid_holder.child)
|
|
||||||
.any(|kid| kid.set_clipping_rect(pipeline_id, layer_id, new_rect))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the layer's page size. This signals that the renderer is ready for BufferRequests.
|
|
||||||
// If the layer is hidden and has a defined clipping rect, unhide it.
|
|
||||||
// This method returns false if the specified layer is not found.
|
|
||||||
pub fn resize(&mut self,
|
|
||||||
pipeline_id: PipelineId,
|
|
||||||
layer_id: LayerId,
|
|
||||||
new_size: Size2D<f32>,
|
|
||||||
window_size: TypedSize2D<PagePx, f32>,
|
|
||||||
epoch: Epoch)
|
|
||||||
-> bool {
|
|
||||||
debug!("compositor_layer: starting resize()");
|
|
||||||
if self.pipeline.id != pipeline_id || self.id != layer_id {
|
|
||||||
return self.resize_helper(pipeline_id, layer_id, new_size, epoch)
|
|
||||||
}
|
|
||||||
|
|
||||||
debug!("compositor_layer: layer found for resize()");
|
|
||||||
self.epoch = epoch;
|
|
||||||
self.page_size = Some(new_size);
|
|
||||||
match self.quadtree {
|
|
||||||
Tree(ref mut quadtree) => {
|
|
||||||
let _ = self.pipeline
|
|
||||||
.render_chan
|
|
||||||
.send_opt(UnusedBufferMsg(quadtree.resize(new_size.width as uint,
|
|
||||||
new_size.height as uint)));
|
|
||||||
}
|
|
||||||
NoTree(tile_size, max_mem) => {
|
|
||||||
self.quadtree = Tree(Quadtree::new(Size2D(new_size.width as uint,
|
|
||||||
new_size.height as uint),
|
|
||||||
tile_size,
|
|
||||||
max_mem))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Call scroll for bounds checking if the page shrunk. Use (-1, -1) as the cursor position
|
|
||||||
// to make sure the scroll isn't propagated downwards.
|
|
||||||
self.handle_scroll_event(TypedPoint2D(0f32, 0f32), TypedPoint2D(-1f32, -1f32), window_size);
|
|
||||||
self.hidden = false;
|
|
||||||
self.set_occlusions();
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn move(&mut self,
|
|
||||||
pipeline_id: PipelineId,
|
|
||||||
layer_id: LayerId,
|
|
||||||
origin: Point2D<f32>,
|
|
||||||
window_size: TypedSize2D<PagePx, f32>)
|
|
||||||
-> bool {
|
|
||||||
// Search children for the right layer to move.
|
|
||||||
if self.pipeline.id != pipeline_id || self.id != layer_id {
|
|
||||||
return self.children.mut_iter().any(|kid_holder| {
|
|
||||||
kid_holder.child.move(pipeline_id, layer_id, origin, window_size)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.wants_scroll_events != WantsScrollEvents {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scroll this layer!
|
|
||||||
let old_origin = self.scroll_offset;
|
|
||||||
self.scroll_offset = Point2D::from_untyped(&(origin * -1.0));
|
|
||||||
|
|
||||||
// bounds checking
|
|
||||||
let page_size = match self.page_size {
|
|
||||||
Some(size) => size,
|
|
||||||
None => fail!("CompositorLayer: tried to scroll with no page size set"),
|
|
||||||
};
|
|
||||||
let window_size = window_size.to_untyped();
|
|
||||||
let scroll_offset = self.scroll_offset.to_untyped();
|
|
||||||
|
|
||||||
let min_x = (window_size.width - page_size.width).min(0.0);
|
|
||||||
self.scroll_offset.x = Length(scroll_offset.x.clamp(&min_x, &0.0));
|
|
||||||
let min_y = (window_size.height - page_size.height).min(0.0);
|
|
||||||
self.scroll_offset.y = Length(scroll_offset.y.clamp(&min_y, &0.0));
|
|
||||||
|
|
||||||
// check to see if we scrolled
|
|
||||||
if old_origin - self.scroll_offset == TypedPoint2D(0f32, 0f32) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let offset = self.scroll_offset;
|
|
||||||
self.scroll(offset)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns whether the layer should be vertically flipped.
|
|
||||||
#[cfg(target_os="macos")]
|
|
||||||
fn texture_flip_and_target(cpu_painting: bool, size: Size2D<uint>) -> (Flip, TextureTarget) {
|
|
||||||
let flip = if cpu_painting {
|
|
||||||
NoFlip
|
|
||||||
} else {
|
|
||||||
VerticalFlip
|
|
||||||
};
|
|
||||||
|
|
||||||
(flip, TextureTargetRectangle(size))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os="android")]
|
|
||||||
fn texture_flip_and_target(cpu_painting: bool, size: Size2D<uint>) -> (Flip, TextureTarget) {
|
|
||||||
let flip = if cpu_painting {
|
|
||||||
NoFlip
|
|
||||||
} else {
|
|
||||||
VerticalFlip
|
|
||||||
};
|
|
||||||
|
|
||||||
(flip, TextureTarget2D)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os="linux")]
|
|
||||||
fn texture_flip_and_target(_: bool, _: Size2D<uint>) -> (Flip, TextureTarget) {
|
|
||||||
(NoFlip, TextureTarget2D)
|
|
||||||
}
|
|
||||||
|
|
||||||
// A helper method to resize sublayers.
|
|
||||||
fn resize_helper(&mut self,
|
|
||||||
pipeline_id: PipelineId,
|
|
||||||
layer_id: LayerId,
|
|
||||||
new_size: Size2D<f32>,
|
|
||||||
epoch: Epoch)
|
|
||||||
-> bool {
|
|
||||||
debug!("compositor_layer: starting resize_helper()");
|
|
||||||
let found = match self.children.iter().position(|kid_holder| {
|
|
||||||
pipeline_id == kid_holder.child.pipeline.id &&
|
|
||||||
layer_id == kid_holder.child.id
|
|
||||||
}) {
|
|
||||||
Some(i) => {
|
|
||||||
debug!("compositor_layer: layer found for resize_helper()");
|
|
||||||
let child_node = self.children.get_mut(i);
|
|
||||||
let child = &mut child_node.child;
|
|
||||||
child.epoch = epoch;
|
|
||||||
child.page_size = Some(new_size);
|
|
||||||
match child.quadtree {
|
|
||||||
Tree(ref mut quadtree) => {
|
|
||||||
let _ = child.pipeline.render_chan.send_opt(UnusedBufferMsg(quadtree.resize(new_size.width as uint,
|
|
||||||
new_size.height as uint)));
|
|
||||||
}
|
|
||||||
NoTree(tile_size, max_mem) => {
|
|
||||||
child.quadtree = Tree(Quadtree::new(Size2D(new_size.width as uint,
|
|
||||||
new_size.height as uint),
|
|
||||||
tile_size,
|
|
||||||
max_mem))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
match *child_node.container.scissor.borrow() {
|
|
||||||
Some(scissor) => {
|
|
||||||
// Call scroll for bounds checking if the page shrunk. Use (-1, -1) as the
|
|
||||||
// cursor position to make sure the scroll isn't propagated downwards.
|
|
||||||
let size: TypedSize2D<PagePx, f32> = Size2D::from_untyped(&scissor.size);
|
|
||||||
child.handle_scroll_event(TypedPoint2D(0f32, 0f32),
|
|
||||||
TypedPoint2D(-1f32, -1f32),
|
|
||||||
size);
|
|
||||||
child.hidden = false;
|
|
||||||
}
|
|
||||||
None => {} // Nothing to do
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
None => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
if found { // Boolean flag to get around double borrow of self
|
|
||||||
self.set_occlusions();
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we got here, the layer's ID does not match ours, so recurse on descendents (including
|
|
||||||
// hidden children).
|
|
||||||
self.children
|
|
||||||
.mut_iter()
|
|
||||||
.map(|kid_holder| &mut kid_holder.child)
|
|
||||||
.any(|kid_holder| kid_holder.resize_helper(pipeline_id, layer_id, new_size, epoch))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect buffers from the quadtree. This method IS NOT recursive, so child CompositorLayers
|
|
||||||
// are not rebuilt directly from this method.
|
|
||||||
pub fn build_layer_tree(&mut self, graphics_context: &NativeCompositingGraphicsContext) {
|
|
||||||
// Iterate over the children of the container layer.
|
|
||||||
let mut current_layer_child = self.root_layer.first_child.borrow().clone();
|
|
||||||
|
|
||||||
// Delete old layer.
|
|
||||||
while current_layer_child.is_some() {
|
|
||||||
let trash = current_layer_child.clone().unwrap();
|
|
||||||
current_layer_child.clone().unwrap().with_common(|common| {
|
|
||||||
current_layer_child = common.next_sibling.clone();
|
|
||||||
});
|
|
||||||
ContainerLayer::remove_child(self.root_layer.clone(), trash);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add new tiles.
|
|
||||||
let quadtree = match self.quadtree {
|
|
||||||
NoTree(..) => fail!("CompositorLayer: cannot build layer tree for {:?},
|
|
||||||
no quadtree initialized", self.pipeline.id),
|
|
||||||
Tree(ref mut quadtree) => quadtree,
|
|
||||||
};
|
|
||||||
|
|
||||||
let all_tiles = quadtree.get_all_tiles();
|
|
||||||
for buffer in all_tiles.iter() {
|
|
||||||
debug!("osmain: compositing buffer rect {}", buffer.rect);
|
|
||||||
|
|
||||||
let size = Size2D(buffer.screen_pos.size.width as int,
|
|
||||||
buffer.screen_pos.size.height as int);
|
|
||||||
|
|
||||||
// Find or create a texture layer.
|
|
||||||
let texture_layer;
|
|
||||||
current_layer_child = match current_layer_child.clone() {
|
|
||||||
None => {
|
|
||||||
debug!("osmain: adding new texture layer");
|
|
||||||
|
|
||||||
// Determine, in a platform-specific way, whether we should flip the texture
|
|
||||||
// and which target to use.
|
|
||||||
let (flip, target) =
|
|
||||||
CompositorLayer::texture_flip_and_target(self.cpu_painting,
|
|
||||||
buffer.screen_pos.size);
|
|
||||||
|
|
||||||
// Make a new texture and bind the layer buffer's surface to it.
|
|
||||||
let texture = Texture::new(target);
|
|
||||||
debug!("COMPOSITOR binding to native surface {:d}",
|
|
||||||
buffer.native_surface.get_id() as int);
|
|
||||||
buffer.native_surface.bind_to_texture(graphics_context, &texture, size);
|
|
||||||
|
|
||||||
// Make a texture layer and add it.
|
|
||||||
texture_layer = Rc::new(TextureLayer::new(texture,
|
|
||||||
buffer.screen_pos.size,
|
|
||||||
flip));
|
|
||||||
ContainerLayer::add_child_end(self.root_layer.clone(),
|
|
||||||
TextureLayerKind(texture_layer.clone()));
|
|
||||||
None
|
|
||||||
}
|
|
||||||
Some(TextureLayerKind(existing_texture_layer)) => {
|
|
||||||
texture_layer = existing_texture_layer.clone();
|
|
||||||
|
|
||||||
let texture = &existing_texture_layer.texture;
|
|
||||||
buffer.native_surface.bind_to_texture(graphics_context, texture, size);
|
|
||||||
|
|
||||||
// Move on to the next sibling.
|
|
||||||
current_layer_child.unwrap().with_common(|common| {
|
|
||||||
common.next_sibling.clone()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Some(_) => fail!("found unexpected layer kind"),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Set the layer's transform.
|
|
||||||
let rect = buffer.rect;
|
|
||||||
let transform = identity().translate(rect.origin.x, rect.origin.y, 0.0);
|
|
||||||
let transform = transform.scale(rect.size.width, rect.size.height, 1.0);
|
|
||||||
texture_layer.common.borrow_mut().set_transform(transform);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add child layers.
|
|
||||||
for child in self.children.mut_iter().filter(|x| !x.child.hidden) {
|
|
||||||
current_layer_child = match current_layer_child {
|
|
||||||
None => {
|
|
||||||
{
|
|
||||||
let mut common = child.container.common.borrow_mut();
|
|
||||||
(*common).parent = None;
|
|
||||||
common.prev_sibling = None;
|
|
||||||
common.next_sibling = None;
|
|
||||||
}
|
|
||||||
ContainerLayer::add_child_end(self.root_layer.clone(),
|
|
||||||
ContainerLayerKind(child.container.clone()));
|
|
||||||
None
|
|
||||||
}
|
|
||||||
Some(_) => {
|
|
||||||
fail!("CompositorLayer: Layer tree failed to delete");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add LayerBuffers to the specified layer. Returns the layer buffer set back if the layer that
|
|
||||||
// matches the given pipeline ID was not found; otherwise returns None and consumes the layer
|
|
||||||
// buffer set.
|
|
||||||
//
|
|
||||||
// If the epoch of the message does not match the layer's epoch, the message is ignored, the
|
|
||||||
// layer buffer set is consumed, and None is returned.
|
|
||||||
pub fn add_buffers(&mut self,
|
|
||||||
graphics_context: &NativeCompositingGraphicsContext,
|
|
||||||
pipeline_id: PipelineId,
|
|
||||||
layer_id: LayerId,
|
|
||||||
mut new_buffers: Box<LayerBufferSet>,
|
|
||||||
epoch: Epoch)
|
|
||||||
-> Option<Box<LayerBufferSet>> {
|
|
||||||
debug!("compositor_layer: starting add_buffers()");
|
|
||||||
if self.pipeline.id != pipeline_id || self.id != layer_id {
|
|
||||||
// ID does not match ours, so recurse on descendents (including hidden children).
|
|
||||||
for child_layer in self.children.mut_iter() {
|
|
||||||
match child_layer.child.add_buffers(graphics_context,
|
|
||||||
pipeline_id,
|
|
||||||
layer_id,
|
|
||||||
new_buffers,
|
|
||||||
epoch) {
|
|
||||||
None => return None,
|
|
||||||
Some(buffers) => new_buffers = buffers,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not found. Give the caller the buffers back.
|
|
||||||
return Some(new_buffers)
|
|
||||||
}
|
|
||||||
|
|
||||||
debug!("compositor_layer: layers found for add_buffers()");
|
|
||||||
|
|
||||||
if self.epoch != epoch {
|
|
||||||
debug!("add_buffers: compositor epoch mismatch: {:?} != {:?}, id: {:?}",
|
|
||||||
self.epoch,
|
|
||||||
epoch,
|
|
||||||
self.pipeline.id);
|
|
||||||
let _ = self.pipeline.render_chan.send_opt(UnusedBufferMsg(new_buffers.buffers));
|
|
||||||
return None
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let quadtree = match self.quadtree {
|
|
||||||
NoTree(..) => {
|
|
||||||
fail!("CompositorLayer: cannot add buffers, no quadtree initialized")
|
|
||||||
}
|
|
||||||
Tree(ref mut quadtree) => quadtree,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut unused_tiles = vec!();
|
|
||||||
for buffer in new_buffers.buffers.move_iter().rev() {
|
|
||||||
unused_tiles.push_all_move(quadtree.add_tile_pixel(buffer.screen_pos.origin.x,
|
|
||||||
buffer.screen_pos.origin.y,
|
|
||||||
buffer.resolution,
|
|
||||||
buffer));
|
|
||||||
}
|
|
||||||
if !unused_tiles.is_empty() { // send back unused buffers
|
|
||||||
let _ = self.pipeline.render_chan.send_opt(UnusedBufferMsg(unused_tiles));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.build_layer_tree(graphics_context);
|
|
||||||
return None
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recursively sets occluded portions of quadtrees to Hidden, so that they do not ask for
|
|
||||||
// tile requests. If layers are moved, resized, or deleted, these portions may be updated.
|
|
||||||
fn set_occlusions(&mut self) {
|
|
||||||
let quadtree = match self.quadtree {
|
|
||||||
NoTree(..) => return, // Cannot calculate occlusions
|
|
||||||
Tree(ref mut quadtree) => quadtree,
|
|
||||||
};
|
|
||||||
for child in self.children.iter().filter(|x| !x.child.hidden) {
|
|
||||||
match *child.container.scissor.borrow() {
|
|
||||||
None => {} // Nothing to do
|
|
||||||
Some(rect) => {
|
|
||||||
quadtree.set_status_page(rect, Hidden, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for child in self.children.mut_iter().filter(|x| !x.child.hidden) {
|
|
||||||
child.child.set_occlusions();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Destroys all quadtree tiles, sending the buffers back to the renderer to be destroyed or
|
|
||||||
/// reused.
|
|
||||||
fn clear(&mut self) {
|
|
||||||
match self.quadtree {
|
|
||||||
NoTree(..) => {}
|
|
||||||
Tree(ref mut quadtree) => {
|
|
||||||
let mut tiles = quadtree.collect_tiles();
|
|
||||||
|
|
||||||
// We have no way of knowing without a race whether the render task is even up and
|
|
||||||
// running, but mark the tiles as not leaking. If the render task died, then the
|
|
||||||
// tiles are going to be cleaned up.
|
|
||||||
for tile in tiles.mut_iter() {
|
|
||||||
tile.mark_wont_leak()
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = self.pipeline.render_chan.send_opt(UnusedBufferMsg(tiles));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Destroys tiles for this layer and all descendent layers, sending the buffers back to the
|
|
||||||
/// renderer to be destroyed or reused.
|
|
||||||
pub fn clear_all_tiles(&mut self) {
|
|
||||||
self.clear();
|
|
||||||
for kid in self.children.mut_iter() {
|
|
||||||
kid.child.clear_all_tiles();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Destroys all tiles of all layers, including children, *without* sending them back to the
|
|
||||||
/// renderer. You must call this only when the render task is destined to be going down;
|
|
||||||
/// otherwise, you will leak tiles.
|
|
||||||
///
|
|
||||||
/// This is used during shutdown, when we know the render task is going away.
|
|
||||||
pub fn forget_all_tiles(&mut self) {
|
|
||||||
match self.quadtree {
|
|
||||||
NoTree(..) => {}
|
|
||||||
Tree(ref mut quadtree) => {
|
|
||||||
let tiles = quadtree.collect_tiles();
|
|
||||||
for tile in tiles.move_iter() {
|
|
||||||
let mut tile = tile;
|
|
||||||
tile.mark_wont_leak()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for kid in self.children.mut_iter() {
|
|
||||||
kid.child.forget_all_tiles();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn id_of_first_child(&self) -> LayerId {
|
|
||||||
self.children.iter().next().expect("no first child!").child.id
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_unrendered_color(&mut self, pipeline_id: PipelineId, layer_id: LayerId, color: Color) -> bool {
|
|
||||||
if self.pipeline.id != pipeline_id || self.id != layer_id {
|
|
||||||
for child_layer in self.children.mut_iter() {
|
|
||||||
if child_layer.child.set_unrendered_color(pipeline_id, layer_id, color) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.unrendered_color = color;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -15,7 +15,8 @@ use geom::point::Point2D;
|
||||||
use geom::rect::Rect;
|
use geom::rect::Rect;
|
||||||
use geom::size::Size2D;
|
use geom::size::Size2D;
|
||||||
use layers::platform::surface::{NativeCompositingGraphicsContext, NativeGraphicsMetadata};
|
use layers::platform::surface::{NativeCompositingGraphicsContext, NativeGraphicsMetadata};
|
||||||
use servo_msg::compositor_msg::{Epoch, LayerBufferSet, LayerId, LayerMetadata, ReadyState};
|
use layers::layers::LayerBufferSet;
|
||||||
|
use servo_msg::compositor_msg::{Epoch, LayerId, LayerMetadata, ReadyState};
|
||||||
use servo_msg::compositor_msg::{RenderListener, RenderState, ScriptListener, ScrollPolicy};
|
use servo_msg::compositor_msg::{RenderListener, RenderState, ScriptListener, ScrollPolicy};
|
||||||
use servo_msg::constellation_msg::{ConstellationChan, PipelineId};
|
use servo_msg::constellation_msg::{ConstellationChan, PipelineId};
|
||||||
use servo_util::memory::MemoryProfilerChan;
|
use servo_util::memory::MemoryProfilerChan;
|
||||||
|
|
|
@ -1,734 +0,0 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
// Implements a Quadtree data structure to keep track of which tiles have
|
|
||||||
// been rasterized and which have not.
|
|
||||||
|
|
||||||
use geom::point::Point2D;
|
|
||||||
use geom::size::Size2D;
|
|
||||||
use geom::rect::Rect;
|
|
||||||
use gfx::render_task::BufferRequest;
|
|
||||||
use std::cmp;
|
|
||||||
use std::mem::replace;
|
|
||||||
use std::num::next_power_of_two;
|
|
||||||
use servo_msg::compositor_msg::Tile;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
use layers::platform::surface::NativePaintingGraphicsContext;
|
|
||||||
|
|
||||||
/// Parent to all quadtree nodes. Stores variables needed at all levels. All method calls
|
|
||||||
/// at this level are in pixel coordinates.
|
|
||||||
pub struct Quadtree<T> {
|
|
||||||
// The root node of the quadtree
|
|
||||||
pub root: Box<QuadtreeNode<T>>,
|
|
||||||
// The size of the layer in pixels. Tiles will be clipped to this size.
|
|
||||||
// Note that the underlying quadtree has a potentailly larger size, since it is rounded
|
|
||||||
// to the next highest power of two.
|
|
||||||
pub clip_size: Size2D<uint>,
|
|
||||||
// The maximum size of the tiles requested in pixels. Tiles requested will be
|
|
||||||
// of a size anywhere between half this value and this value.
|
|
||||||
pub max_tile_size: uint,
|
|
||||||
// The maximum allowed total memory of tiles in the tree. If this limit is reached, tiles
|
|
||||||
// will be removed from the tree. Set this to None to prevent this behavior.
|
|
||||||
pub max_mem: Option<uint>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A node in the tree. All method calls at this level are in page coordinates.
|
|
||||||
struct QuadtreeNode<T> {
|
|
||||||
/// The tile belonging to this node. Note that parent nodes can have tiles.
|
|
||||||
pub tile: Option<T>,
|
|
||||||
/// The position of the node in page coordinates.
|
|
||||||
pub origin: Point2D<f32>,
|
|
||||||
/// The width and height of the node in page coordinates.
|
|
||||||
pub size: f32,
|
|
||||||
/// The node's children.
|
|
||||||
pub quadrants: [Option<Box<QuadtreeNode<T>>>, ..4],
|
|
||||||
/// Combined size of self.tile and tiles of all descendants
|
|
||||||
pub tile_mem: uint,
|
|
||||||
/// The current status of this node. See below for details.
|
|
||||||
pub status: NodeStatus,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The status of a QuadtreeNode. This determines the behavior of the node
|
|
||||||
/// when querying for tile requests.
|
|
||||||
#[deriving(PartialEq)]
|
|
||||||
pub enum NodeStatus {
|
|
||||||
/// If we have no valid tile, request one; otherwise, don't send a request.
|
|
||||||
Normal,
|
|
||||||
/// Render request has been sent; ignore this node until tile is inserted.
|
|
||||||
Rendering,
|
|
||||||
/// Do not send tile requests. Overrides Invalid.
|
|
||||||
Hidden,
|
|
||||||
/// Send tile requests, even if the node has (or child nodes have) a valid tile.
|
|
||||||
Invalid,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Quadrant {
|
|
||||||
TL = 0,
|
|
||||||
TR = 1,
|
|
||||||
BL = 2,
|
|
||||||
BR = 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn div_ceil(x: uint, y: uint) -> uint {
|
|
||||||
let div = x / y;
|
|
||||||
if x % y == 0u { div }
|
|
||||||
else { div + 1u }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Tile> Quadtree<T> {
|
|
||||||
/// Public method to create a new Quadtree
|
|
||||||
/// Takes in the initial width and height of the space, a maximum tile size, and
|
|
||||||
/// a maximum amount of memory. Tiles will be deleted if this memory is exceeded.
|
|
||||||
/// Set max_mem to None to turn off automatic tile removal.
|
|
||||||
pub fn new(clip_size: Size2D<uint>, tile_size: uint, max_mem: Option<uint>) -> Quadtree<T> {
|
|
||||||
// Spaces must be squares and powers of 2, so expand the space until it is
|
|
||||||
let longer = cmp::max(clip_size.width, clip_size.height);
|
|
||||||
let num_tiles = div_ceil(longer, tile_size);
|
|
||||||
let power_of_two = next_power_of_two(num_tiles);
|
|
||||||
let size = power_of_two * tile_size;
|
|
||||||
|
|
||||||
Quadtree {
|
|
||||||
root: box QuadtreeNode {
|
|
||||||
tile: None,
|
|
||||||
origin: Point2D(0f32, 0f32),
|
|
||||||
size: size as f32,
|
|
||||||
quadrants: [None, None, None, None],
|
|
||||||
tile_mem: 0,
|
|
||||||
status: Normal,
|
|
||||||
},
|
|
||||||
clip_size: clip_size,
|
|
||||||
max_tile_size: tile_size,
|
|
||||||
max_mem: max_mem,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add a tile associated with a given pixel position and scale.
|
|
||||||
/// If the tile pushes the total memory over its maximum, tiles will be removed
|
|
||||||
/// until total memory is below the maximum again. These tiles are returned.
|
|
||||||
pub fn add_tile_pixel(&mut self, x: uint, y: uint, scale: f32, tile: T) -> Vec<T> {
|
|
||||||
let (_, tiles) = self.root.add_tile(x as f32 / scale, y as f32 / scale, tile,
|
|
||||||
self.max_tile_size as f32 / scale);
|
|
||||||
let mut tiles = tiles;
|
|
||||||
match self.max_mem {
|
|
||||||
Some(max) => {
|
|
||||||
while self.root.tile_mem > max {
|
|
||||||
let r = self.root.remove_tile(x as f32 / scale, y as f32 / scale);
|
|
||||||
match r {
|
|
||||||
(Some(tile), _, _) => tiles.push(tile),
|
|
||||||
_ => fail!("Quadtree: No valid tiles to remove"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {} // Nothing to do
|
|
||||||
}
|
|
||||||
tiles
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get all the tiles in the tree.
|
|
||||||
pub fn get_all_tiles<'r>(&'r self) -> Vec<&'r T> {
|
|
||||||
self.root.get_all_tiles()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Given a window rect in pixel coordinates, this function returns a list of BufferRequests for tiles that
|
|
||||||
/// need to be rendered. It also returns a vector of tiles if the window needs to be redisplayed, i.e. if
|
|
||||||
/// no tiles need to be rendered, but the display tree needs to be rebuilt. This can occur when the
|
|
||||||
/// user zooms out and cached tiles need to be displayed on top of higher resolution tiles.
|
|
||||||
/// When this happens, higher resolution tiles will be removed from the quadtree.
|
|
||||||
#[cfg(test)]
|
|
||||||
pub fn get_tile_rects_pixel(&mut self, window: Rect<int>, scale: f32) -> (Vec<BufferRequest>, Vec<T>) {
|
|
||||||
let (ret, unused, _) = self.root.get_tile_rects(
|
|
||||||
Rect(Point2D(window.origin.x as f32 / scale, window.origin.y as f32 / scale),
|
|
||||||
Size2D(window.size.width as f32 / scale, window.size.height as f32 / scale)),
|
|
||||||
Size2D(self.clip_size.width as f32, self.clip_size.height as f32),
|
|
||||||
scale, self.max_tile_size as f32 / scale, false);
|
|
||||||
(ret, unused)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Same function as above, using page coordinates for the window.
|
|
||||||
pub fn get_tile_rects_page(&mut self, window: Rect<f32>, scale: f32) -> (Vec<BufferRequest>, Vec<T>) {
|
|
||||||
let (ret, unused, _) = self.root.get_tile_rects(
|
|
||||||
window,
|
|
||||||
Size2D(self.clip_size.width as f32, self.clip_size.height as f32),
|
|
||||||
scale, self.max_tile_size as f32 / scale, false);
|
|
||||||
(ret, unused)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new quadtree at the specified size. This should be called when the window changes size.
|
|
||||||
pub fn resize(&mut self, width: uint, height: uint) -> Vec<T> {
|
|
||||||
// Spaces must be squares and powers of 2, so expand the space until it is
|
|
||||||
let longer = cmp::max(width, height);
|
|
||||||
let num_tiles = div_ceil(longer, self.max_tile_size);
|
|
||||||
let power_of_two = next_power_of_two(num_tiles);
|
|
||||||
let size = power_of_two * self.max_tile_size;
|
|
||||||
let ret = self.root.collect_tiles();
|
|
||||||
self.root = box QuadtreeNode {
|
|
||||||
tile: None,
|
|
||||||
origin: Point2D(0f32, 0f32),
|
|
||||||
size: size as f32,
|
|
||||||
quadrants: [None, None, None, None],
|
|
||||||
status: Normal,
|
|
||||||
tile_mem: 0,
|
|
||||||
};
|
|
||||||
self.clip_size = Size2D(width, height);
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resize the underlying quadtree without removing tiles already in place.
|
|
||||||
/// Might be useful later on, but resize() should be used for now.
|
|
||||||
/// TODO: return tiles after shrinking
|
|
||||||
#[cfg(test)]
|
|
||||||
pub fn bad_resize(&mut self, width: uint, height: uint) {
|
|
||||||
self.clip_size = Size2D(width, height);
|
|
||||||
let longer = cmp::max(width, height);
|
|
||||||
let new_num_tiles = div_ceil(longer, self.max_tile_size);
|
|
||||||
let new_size = next_power_of_two(new_num_tiles);
|
|
||||||
// difference here indicates the number of times the underlying size of the quadtree needs
|
|
||||||
// to be doubled or halved. It will recursively add a new root if it is positive, or
|
|
||||||
// recursivly make a child the new root if it is negative.
|
|
||||||
let difference = (new_size as f32 / self.root.size as f32).log2() as int;
|
|
||||||
if difference > 0 { // doubling
|
|
||||||
let difference = difference as uint;
|
|
||||||
for i in range(0, difference) {
|
|
||||||
let new_root = box QuadtreeNode {
|
|
||||||
tile: None,
|
|
||||||
origin: Point2D(0f32, 0f32),
|
|
||||||
size: new_size as f32 / ((difference - i - 1) as f32).exp2(),
|
|
||||||
quadrants: [None, None, None, None],
|
|
||||||
tile_mem: self.root.tile_mem,
|
|
||||||
status: Normal,
|
|
||||||
};
|
|
||||||
self.root.quadrants[TL as uint] = Some(replace(&mut self.root, new_root));
|
|
||||||
}
|
|
||||||
} else if difference < 0 { // halving
|
|
||||||
let difference = difference.abs() as uint;
|
|
||||||
for _ in range(0, difference) {
|
|
||||||
let remove = replace(&mut self.root.quadrants[TL as uint], None);
|
|
||||||
match remove {
|
|
||||||
Some(child) => self.root = child,
|
|
||||||
None => {
|
|
||||||
self.root = box QuadtreeNode {
|
|
||||||
tile: None,
|
|
||||||
origin: Point2D(0f32, 0f32),
|
|
||||||
size: new_size as f32,
|
|
||||||
quadrants: [None, None, None, None],
|
|
||||||
tile_mem: 0,
|
|
||||||
status: Normal,
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the status of all quadtree nodes within the given rect in page coordinates. If
|
|
||||||
/// include_border is true, then nodes on the edge of the rect will be included; otherwise,
|
|
||||||
/// only nodes completely occluded by the rect will be changed.
|
|
||||||
pub fn set_status_page(&mut self, rect: Rect<f32>, status: NodeStatus, include_border: bool) {
|
|
||||||
self.root.set_status(rect, status, include_border);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove and return all tiles in the tree. Use this before deleting the quadtree to prevent
|
|
||||||
/// a GC pause.
|
|
||||||
pub fn collect_tiles(&mut self) -> Vec<T> {
|
|
||||||
self.root.collect_tiles()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Tile> QuadtreeNode<T> {
|
|
||||||
/// Private method to create new children
|
|
||||||
fn new_child(x: f32, y: f32, size: f32) -> QuadtreeNode<T> {
|
|
||||||
QuadtreeNode {
|
|
||||||
tile: None,
|
|
||||||
origin: Point2D(x, y),
|
|
||||||
size: size,
|
|
||||||
quadrants: [None, None, None, None],
|
|
||||||
tile_mem: 0,
|
|
||||||
status: Normal,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Determine which child contains a given point in page coords.
|
|
||||||
fn get_quadrant(&self, x: f32, y: f32) -> Quadrant {
|
|
||||||
if x < self.origin.x + self.size / 2.0 {
|
|
||||||
if y < self.origin.y + self.size / 2.0 {
|
|
||||||
TL
|
|
||||||
} else {
|
|
||||||
BL
|
|
||||||
}
|
|
||||||
} else if y < self.origin.y + self.size / 2.0 {
|
|
||||||
TR
|
|
||||||
} else {
|
|
||||||
BR
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get all tiles in the tree, parents first.
|
|
||||||
fn get_all_tiles<'r>(&'r self) -> Vec<&'r T> {
|
|
||||||
let mut ret = vec!();
|
|
||||||
|
|
||||||
match self.tile {
|
|
||||||
Some(ref tile) => ret.push(tile),
|
|
||||||
None => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
for quad in self.quadrants.iter() {
|
|
||||||
match *quad {
|
|
||||||
Some(ref child) => ret.push_all_move(child.get_all_tiles()),
|
|
||||||
None => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add a tile associated with a given position in page coords. If the tile size exceeds the maximum,
|
|
||||||
/// the node will be split and the method will recurse until the tile size is within limits.
|
|
||||||
/// Returns an the difference in tile memory between the new quadtree node and the old quadtree node,
|
|
||||||
/// along with any deleted tiles.
|
|
||||||
fn add_tile(&mut self, x: f32, y: f32, tile: T, tile_size: f32) -> (int, Vec<T>) {
|
|
||||||
debug!("Quadtree: Adding: ({}, {}) size:{}px", self.origin.x, self.origin.y, self.size);
|
|
||||||
|
|
||||||
if x >= self.origin.x + self.size || x < self.origin.x
|
|
||||||
|| y >= self.origin.y + self.size || y < self.origin.y {
|
|
||||||
fail!("Quadtree: Tried to add tile to invalid region");
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.size <= tile_size { // We are the child
|
|
||||||
let old_size = self.tile_mem;
|
|
||||||
self.tile_mem = tile.get_mem();
|
|
||||||
let mut unused_tiles = match replace(&mut self.tile, Some(tile)) {
|
|
||||||
Some(old_tile) => vec!(old_tile),
|
|
||||||
None => vec!(),
|
|
||||||
};
|
|
||||||
for child in self.quadrants.mut_iter() {
|
|
||||||
match *child {
|
|
||||||
Some(ref mut node) => {
|
|
||||||
unused_tiles.push_all_move(node.collect_tiles());
|
|
||||||
}
|
|
||||||
None => {} // Nothing to do
|
|
||||||
}
|
|
||||||
*child = None;
|
|
||||||
}
|
|
||||||
self.status = Normal;
|
|
||||||
(self.tile_mem as int - old_size as int, unused_tiles)
|
|
||||||
} else { // Send tile to children
|
|
||||||
let quad = self.get_quadrant(x, y);
|
|
||||||
match self.quadrants[quad as uint] {
|
|
||||||
Some(ref mut child) => {
|
|
||||||
let (delta, unused) = child.add_tile(x, y, tile, tile_size);
|
|
||||||
self.tile_mem = (self.tile_mem as int + delta) as uint;
|
|
||||||
(delta, unused)
|
|
||||||
}
|
|
||||||
None => { // Make new child
|
|
||||||
let new_size = self.size / 2.0;
|
|
||||||
let new_x = match quad {
|
|
||||||
TL | BL => self.origin.x,
|
|
||||||
TR | BR => self.origin.x + new_size,
|
|
||||||
};
|
|
||||||
let new_y = match quad {
|
|
||||||
TL | TR => self.origin.y,
|
|
||||||
BL | BR => self.origin.y + new_size,
|
|
||||||
};
|
|
||||||
let mut c = box QuadtreeNode::new_child(new_x, new_y, new_size);
|
|
||||||
let (delta, unused) = c.add_tile(x, y, tile, tile_size);
|
|
||||||
self.tile_mem = (self.tile_mem as int + delta) as uint;
|
|
||||||
self.quadrants[quad as uint] = Some(c);
|
|
||||||
(delta, unused)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a tile rect in screen and page coords for a given position in page coords
|
|
||||||
fn get_tile_rect(&mut self, x: f32, y: f32, clip_x: f32, clip_y: f32, scale: f32,
|
|
||||||
tile_size: f32) -> BufferRequest {
|
|
||||||
if x >= self.origin.x + self.size || x < self.origin.x
|
|
||||||
|| y >= self.origin.y + self.size || y < self.origin.y {
|
|
||||||
fail!("Quadtree: Tried to query a tile rect outside of range");
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.size <= tile_size {
|
|
||||||
let pix_x = (self.origin.x * scale).ceil() as uint;
|
|
||||||
let pix_y = (self.origin.y * scale).ceil() as uint;
|
|
||||||
let page_width = self.size.min(clip_x - self.origin.x);
|
|
||||||
let page_height = self.size.min(clip_y - self.origin.y);
|
|
||||||
let pix_width = (page_width * scale).ceil() as uint;
|
|
||||||
let pix_height = (page_height * scale).ceil() as uint;
|
|
||||||
self.status = Rendering;
|
|
||||||
return BufferRequest(Rect(Point2D(pix_x, pix_y), Size2D(pix_width, pix_height)),
|
|
||||||
Rect(Point2D(self.origin.x, self.origin.y), Size2D(page_width, page_height)));
|
|
||||||
}
|
|
||||||
|
|
||||||
let quad = self.get_quadrant(x,y);
|
|
||||||
match self.quadrants[quad as uint] {
|
|
||||||
None => {
|
|
||||||
let new_size = self.size / 2.0;
|
|
||||||
let new_x = match quad {
|
|
||||||
TL | BL => self.origin.x,
|
|
||||||
TR | BR => self.origin.x + new_size,
|
|
||||||
};
|
|
||||||
let new_y = match quad {
|
|
||||||
TL | TR => self.origin.y,
|
|
||||||
BL | BR => self.origin.y + new_size,
|
|
||||||
};
|
|
||||||
let mut c = box QuadtreeNode::new_child(new_x, new_y, new_size);
|
|
||||||
let result = c.get_tile_rect(x, y, clip_x, clip_y, scale, tile_size);
|
|
||||||
self.quadrants[quad as uint] = Some(c);
|
|
||||||
result
|
|
||||||
}
|
|
||||||
Some(ref mut child) => child.get_tile_rect(x, y, clip_x, clip_y, scale, tile_size),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Removes a tile that is far from the given input point in page coords. Returns the tile removed,
|
|
||||||
/// a bool that is true if the child has no tiles and needs to be deleted, and an integer showing the
|
|
||||||
/// amount of memory changed by the operation. Unfortunately, the tile has to be an option, because
|
|
||||||
/// there are occasionally leaves without tiles. However, the option will always be Some as long as
|
|
||||||
/// this quadtree node or at least one of its descendants is not empty.
|
|
||||||
fn remove_tile(&mut self, x: f32, y: f32) -> (Option<T>, bool, int) {
|
|
||||||
if self.tile.is_some() {
|
|
||||||
let ret = replace(&mut(self.tile), None);
|
|
||||||
return match (ret, &self.quadrants) {
|
|
||||||
(Some(tile), &[None, None, None, None]) => {
|
|
||||||
let size = -(tile.get_mem() as int);
|
|
||||||
(Some(tile), true, size)
|
|
||||||
}
|
|
||||||
(Some(tile), _) => {
|
|
||||||
let size = -(tile.get_mem() as int);
|
|
||||||
(Some(tile), false, size)
|
|
||||||
}
|
|
||||||
_ => fail!("Quadtree: tile query failure in remove_tile"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is a hacky heuristic to find a tile that is "far away". There are better methods.
|
|
||||||
let quad = self.get_quadrant(x, y);
|
|
||||||
let queue = match quad {
|
|
||||||
TL => [BR, BL, TR, TL],
|
|
||||||
TR => [BL, BR, TL, TR],
|
|
||||||
BL => [TR, TL, BR, BL],
|
|
||||||
BR => [TL, TR, BL, BR],
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut del_quad: Option<Quadrant> = None;
|
|
||||||
let mut ret = (None, false, 0);
|
|
||||||
|
|
||||||
for quad in queue.iter() {
|
|
||||||
match self.quadrants[*quad as uint] {
|
|
||||||
Some(ref mut child) => {
|
|
||||||
let (tile, flag, delta) = child.remove_tile(x, y);
|
|
||||||
match tile {
|
|
||||||
Some(_) => {
|
|
||||||
self.tile_mem = (self.tile_mem as int + delta) as uint;
|
|
||||||
if flag {
|
|
||||||
del_quad = Some(*quad);
|
|
||||||
} else {
|
|
||||||
return (tile, flag, delta);
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = (tile, flag, delta);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
None => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match del_quad {
|
|
||||||
Some(quad) => {
|
|
||||||
self.quadrants[quad as uint] = None;
|
|
||||||
let (tile, _, delta) = ret;
|
|
||||||
match (&self.tile, &self.quadrants) {
|
|
||||||
(&None, &[None, None, None, None]) => (tile, true, delta),
|
|
||||||
_ => (tile, false, delta)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => ret,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Given a window rect in page coordinates, returns a BufferRequest array,
|
|
||||||
/// an unused tile array, and the difference in tile memory between the new and old quadtree nodes.
|
|
||||||
/// The override bool will be true if a parent node was marked as invalid; child nodes will be
|
|
||||||
/// treated as invalid as well.
|
|
||||||
/// NOTE: this method will sometimes modify the tree by deleting tiles.
|
|
||||||
/// See the QuadTree function description for more details.
|
|
||||||
fn get_tile_rects(&mut self,
|
|
||||||
window: Rect<f32>,
|
|
||||||
clip: Size2D<f32>,
|
|
||||||
scale: f32,
|
|
||||||
tile_size: f32,
|
|
||||||
override: bool)
|
|
||||||
-> (Vec<BufferRequest>, Vec<T>, int) {
|
|
||||||
let w_x = window.origin.x;
|
|
||||||
let w_y = window.origin.y;
|
|
||||||
let w_width = window.size.width;
|
|
||||||
let w_height = window.size.height;
|
|
||||||
let s_x = self.origin.x;
|
|
||||||
let s_y = self.origin.y;
|
|
||||||
let s_size = self.size;
|
|
||||||
|
|
||||||
// if window is outside of visible region, nothing to do
|
|
||||||
if w_x + w_width < s_x || w_x > s_x + s_size
|
|
||||||
|| w_y + w_height < s_y || w_y > s_y + s_size
|
|
||||||
|| w_x >= clip.width || w_y >= clip.height {
|
|
||||||
return (vec!(), vec!(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// clip window to visible region
|
|
||||||
let w_width = w_width.min(clip.width - w_x);
|
|
||||||
let w_height = w_height.min(clip.height - w_y);
|
|
||||||
|
|
||||||
if s_size <= tile_size { // We are the child
|
|
||||||
return match self.tile {
|
|
||||||
_ if self.status == Rendering || self.status == Hidden => (vec!(), vec!(), 0),
|
|
||||||
Some(ref tile) if tile.is_valid(scale) && !override
|
|
||||||
&& self.status != Invalid => {
|
|
||||||
let redisplay = match self.quadrants {
|
|
||||||
[None, None, None, None] => false,
|
|
||||||
_ => true,
|
|
||||||
};
|
|
||||||
let mut delta = 0;
|
|
||||||
let mut unused_tiles = vec!();
|
|
||||||
if redisplay {
|
|
||||||
let old_mem = self.tile_mem;
|
|
||||||
for child in self.quadrants.mut_iter() {
|
|
||||||
match *child {
|
|
||||||
Some(ref mut node) => {
|
|
||||||
unused_tiles.push_all_move(node.collect_tiles());
|
|
||||||
}
|
|
||||||
None => {} // Nothing to do
|
|
||||||
}
|
|
||||||
*child = None;
|
|
||||||
}
|
|
||||||
self.tile_mem = tile.get_mem();
|
|
||||||
delta = self.tile_mem as int - old_mem as int;
|
|
||||||
|
|
||||||
}
|
|
||||||
(vec!(), unused_tiles, delta)
|
|
||||||
}
|
|
||||||
_ => (vec!(self.get_tile_rect(s_x, s_y, clip.width, clip.height, scale, tile_size)), vec!(), 0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, we either have children or will have children
|
|
||||||
let w_tl_quad = self.get_quadrant(w_x, w_y);
|
|
||||||
let w_br_quad = self.get_quadrant(w_x + w_width, w_y + w_height);
|
|
||||||
|
|
||||||
// Figure out which quadrants the window is in
|
|
||||||
let mut quads_to_check = Vec::with_capacity(4);
|
|
||||||
match (w_tl_quad, w_br_quad) {
|
|
||||||
(tl, br) if tl as int == br as int => {
|
|
||||||
quads_to_check.push(tl);
|
|
||||||
}
|
|
||||||
(TL, br) => {
|
|
||||||
quads_to_check.push(TL);
|
|
||||||
quads_to_check.push(br);
|
|
||||||
match br {
|
|
||||||
BR => {
|
|
||||||
quads_to_check.push(TR);
|
|
||||||
quads_to_check.push(BL);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(tl, br) => {
|
|
||||||
quads_to_check.push(tl);
|
|
||||||
quads_to_check.push(br);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut request = vec!();
|
|
||||||
let mut unused = vec!();
|
|
||||||
let mut delta = 0;
|
|
||||||
|
|
||||||
for quad in quads_to_check.iter() {
|
|
||||||
// Recurse into child
|
|
||||||
let new_window = match *quad {
|
|
||||||
TL => Rect(window.origin,
|
|
||||||
Size2D(w_width.min(s_x + s_size / 2.0 - w_x),
|
|
||||||
w_height.min(s_y + s_size / 2.0 - w_y))),
|
|
||||||
TR => Rect(Point2D(w_x.max(s_x + s_size / 2.0),
|
|
||||||
w_y),
|
|
||||||
Size2D(w_width.min(w_x + w_width - (s_x + s_size / 2.0)),
|
|
||||||
w_height.min(s_y + s_size / 2.0 - w_y))),
|
|
||||||
BL => Rect(Point2D(w_x,
|
|
||||||
w_y.max(s_y + s_size / 2.0)),
|
|
||||||
Size2D(w_width.min(s_x + s_size / 2.0 - w_x),
|
|
||||||
w_height.min(w_y + w_height - (s_y + s_size / 2.0)))),
|
|
||||||
BR => Rect(Point2D(w_x.max(s_x + s_size / 2.0),
|
|
||||||
w_y.max(s_y + s_size / 2.0)),
|
|
||||||
Size2D(w_width.min(w_x + w_width - (s_x + s_size / 2.0)),
|
|
||||||
w_height.min(w_y + w_height - (s_y + s_size / 2.0)))),
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
let override = override || self.status == Invalid;
|
|
||||||
self.status = Normal;
|
|
||||||
let (c_request, c_unused, c_delta) = match self.quadrants[*quad as uint] {
|
|
||||||
Some(ref mut child) => child.get_tile_rects(new_window, clip, scale, tile_size, override),
|
|
||||||
None => {
|
|
||||||
// Create new child
|
|
||||||
let new_size = self.size / 2.0;
|
|
||||||
let new_x = match *quad {
|
|
||||||
TL | BL => self.origin.x,
|
|
||||||
TR | BR => self.origin.x + new_size,
|
|
||||||
};
|
|
||||||
let new_y = match *quad {
|
|
||||||
TL | TR => self.origin.y,
|
|
||||||
BL | BR => self.origin.y + new_size,
|
|
||||||
};
|
|
||||||
let mut child = box QuadtreeNode::new_child(new_x, new_y, new_size);
|
|
||||||
let ret = child.get_tile_rects(new_window, clip, scale, tile_size, override);
|
|
||||||
self.quadrants[*quad as uint] = Some(child);
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
delta = delta + c_delta;
|
|
||||||
request.push_all(c_request.as_slice());
|
|
||||||
unused.push_all_move(c_unused);
|
|
||||||
}
|
|
||||||
self.tile_mem = (self.tile_mem as int + delta) as uint;
|
|
||||||
(request, unused, delta)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove all tiles from the tree. Use this to collect all tiles before deleting a branch.
|
|
||||||
fn collect_tiles(&mut self) -> Vec<T> {
|
|
||||||
let mut ret = match replace(&mut self.tile, None) {
|
|
||||||
Some(tile) => vec!(tile),
|
|
||||||
None => vec!(),
|
|
||||||
};
|
|
||||||
for child in self.quadrants.mut_iter() {
|
|
||||||
match *child {
|
|
||||||
Some(ref mut node) => {
|
|
||||||
ret.push_all_move(node.collect_tiles());
|
|
||||||
}
|
|
||||||
None => {} // Nothing to do
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the status of nodes contained within the rect. See the quadtree method for
|
|
||||||
/// more info.
|
|
||||||
fn set_status(&mut self, rect: Rect<f32>, status: NodeStatus, borders: bool) {
|
|
||||||
let self_rect = Rect(self.origin, Size2D(self.size, self.size));
|
|
||||||
let intersect = rect.intersection(&self_rect);
|
|
||||||
let intersect = match intersect {
|
|
||||||
None => return, // We do not intersect the rect, nothing to do
|
|
||||||
Some(rect) => rect,
|
|
||||||
};
|
|
||||||
|
|
||||||
if self_rect == intersect { // We are completely contained in the rect
|
|
||||||
if !(self.status == Hidden && status == Invalid) { // Hidden trumps Invalid
|
|
||||||
self.status = status;
|
|
||||||
}
|
|
||||||
return; // No need to recurse
|
|
||||||
}
|
|
||||||
|
|
||||||
match self.quadrants {
|
|
||||||
[None, None, None, None] => { // We are a leaf
|
|
||||||
if borders && !(self.status == Hidden && status == Invalid) {
|
|
||||||
self.status = status;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => { // We are internal
|
|
||||||
for quad in self.quadrants.mut_iter() {
|
|
||||||
match *quad {
|
|
||||||
None => {} // Nothing to do
|
|
||||||
Some(ref mut child) => {
|
|
||||||
child.set_status(intersect, status, borders);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
pub fn test_resize() {
|
|
||||||
struct T {
|
|
||||||
a: int,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Tile for T {
|
|
||||||
fn get_mem(&self) -> uint {
|
|
||||||
1
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_valid(&self, _: f32) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
fn get_size_2d(&self) -> Size2D<uint> {
|
|
||||||
Size2D(0u, 0u)
|
|
||||||
}
|
|
||||||
fn mark_wont_leak(&mut self) {}
|
|
||||||
fn destroy(self, _: &NativePaintingGraphicsContext) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut q = Quadtree::new(Size2D(6u, 6), 1, None);
|
|
||||||
q.add_tile_pixel(0, 0, 1f32, T{a: 0});
|
|
||||||
q.add_tile_pixel(5, 5, 1f32, T{a: 1});
|
|
||||||
q.bad_resize(8, 1);
|
|
||||||
assert!(q.root.size == 8.0);
|
|
||||||
q.bad_resize(18, 1);
|
|
||||||
assert!(q.root.size == 32.0);
|
|
||||||
q.bad_resize(8, 1);
|
|
||||||
assert!(q.root.size == 8.0);
|
|
||||||
q.bad_resize(3, 1);
|
|
||||||
assert!(q.root.size == 4.0);
|
|
||||||
assert!(q.get_all_tiles().len() == 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
pub fn test() {
|
|
||||||
struct T {
|
|
||||||
a: int,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Tile for T {
|
|
||||||
fn get_mem(&self) -> uint {
|
|
||||||
1
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_valid(&self, _: f32) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
fn get_size_2d(&self) -> Size2D<uint> {
|
|
||||||
Size2D(0u, 0u)
|
|
||||||
}
|
|
||||||
fn mark_wont_leak(&mut self) {}
|
|
||||||
fn destroy(self, _: &NativePaintingGraphicsContext) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut q = Quadtree::new(Size2D(8u, 8), 2, Some(4));
|
|
||||||
q.add_tile_pixel(0, 0, 1f32, T{a: 0});
|
|
||||||
q.add_tile_pixel(0, 0, 2f32, T{a: 1});
|
|
||||||
q.add_tile_pixel(0, 0, 2f32, T{a: 2});
|
|
||||||
q.add_tile_pixel(2, 0, 2f32, T{a: 3});
|
|
||||||
assert!(q.root.tile_mem == 3);
|
|
||||||
assert!(q.get_all_tiles().len() == 3);
|
|
||||||
q.add_tile_pixel(0, 2, 2f32, T{a: 4});
|
|
||||||
q.add_tile_pixel(2, 2, 2f32, T{a: 5});
|
|
||||||
assert!(q.root.tile_mem == 4);
|
|
||||||
|
|
||||||
let (request, _) = q.get_tile_rects_pixel(Rect(Point2D(0, 0), Size2D(2, 2)), 2f32);
|
|
||||||
assert!(request.is_empty());
|
|
||||||
let (request, _) = q.get_tile_rects_pixel(Rect(Point2D(0, 0), Size2D(2, 2)), 1.9);
|
|
||||||
assert!(request.is_empty());
|
|
||||||
let (request, _) = q.get_tile_rects_pixel(Rect(Point2D(0, 0), Size2D(2, 2)), 1f32);
|
|
||||||
assert!(request.len() == 4);
|
|
||||||
|
|
||||||
q.add_tile_pixel(0, 0, 0.5, T{a: 6});
|
|
||||||
q.add_tile_pixel(0, 0, 1f32, T{a: 7});
|
|
||||||
let (_, unused) = q.get_tile_rects_pixel(Rect(Point2D(0, 0), Size2D(2, 2)), 0.5);
|
|
||||||
assert!(!unused.is_empty());
|
|
||||||
assert!(q.root.tile_mem == 1);
|
|
||||||
}
|
|
|
@ -5,7 +5,7 @@
|
||||||
use std::collections::hashmap::HashMap;
|
use std::collections::hashmap::HashMap;
|
||||||
use geom::size::Size2D;
|
use geom::size::Size2D;
|
||||||
use layers::platform::surface::NativePaintingGraphicsContext;
|
use layers::platform::surface::NativePaintingGraphicsContext;
|
||||||
use servo_msg::compositor_msg::Tile;
|
use layers::quadtree::Tile;
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::hash::sip::SipState;
|
use std::hash::sip::SipState;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
|
@ -17,11 +17,12 @@ use geom::rect::Rect;
|
||||||
use geom::size::Size2D;
|
use geom::size::Size2D;
|
||||||
use layers::platform::surface::{NativePaintingGraphicsContext, NativeSurface};
|
use layers::platform::surface::{NativePaintingGraphicsContext, NativeSurface};
|
||||||
use layers::platform::surface::{NativeSurfaceMethods};
|
use layers::platform::surface::{NativeSurfaceMethods};
|
||||||
|
use layers::layers::{BufferRequest, LayerBuffer, LayerBufferSet};
|
||||||
use layers;
|
use layers;
|
||||||
use native;
|
use native;
|
||||||
use rustrt::task;
|
use rustrt::task;
|
||||||
use rustrt::task::TaskOpts;
|
use rustrt::task::TaskOpts;
|
||||||
use servo_msg::compositor_msg::{Epoch, IdleRenderState, LayerBuffer, LayerBufferSet, LayerId};
|
use servo_msg::compositor_msg::{Epoch, IdleRenderState, LayerId};
|
||||||
use servo_msg::compositor_msg::{LayerMetadata, RenderListener, RenderingRenderState, ScrollPolicy};
|
use servo_msg::compositor_msg::{LayerMetadata, RenderListener, RenderingRenderState, ScrollPolicy};
|
||||||
use servo_msg::constellation_msg::{ConstellationChan, Failure, FailureMsg, PipelineId};
|
use servo_msg::constellation_msg::{ConstellationChan, Failure, FailureMsg, PipelineId};
|
||||||
use servo_msg::constellation_msg::{RendererReadyMsg};
|
use servo_msg::constellation_msg::{RendererReadyMsg};
|
||||||
|
@ -57,23 +58,6 @@ pub enum Msg {
|
||||||
ExitMsg(Option<Sender<()>>),
|
ExitMsg(Option<Sender<()>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A request from the compositor to the renderer for tiles that need to be (re)displayed.
|
|
||||||
#[deriving(Clone)]
|
|
||||||
pub struct BufferRequest {
|
|
||||||
// The rect in pixels that will be drawn to the screen
|
|
||||||
screen_rect: Rect<uint>,
|
|
||||||
|
|
||||||
// The rect in page coordinates that this tile represents
|
|
||||||
page_rect: Rect<f32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn BufferRequest(screen_rect: Rect<uint>, page_rect: Rect<f32>) -> BufferRequest {
|
|
||||||
BufferRequest {
|
|
||||||
screen_rect: screen_rect,
|
|
||||||
page_rect: page_rect,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[deriving(Clone)]
|
#[deriving(Clone)]
|
||||||
pub struct RenderChan(Sender<Msg>);
|
pub struct RenderChan(Sender<Msg>);
|
||||||
|
|
||||||
|
|
|
@ -5,48 +5,14 @@
|
||||||
use azure::azure_hl::Color;
|
use azure::azure_hl::Color;
|
||||||
use geom::point::Point2D;
|
use geom::point::Point2D;
|
||||||
use geom::rect::Rect;
|
use geom::rect::Rect;
|
||||||
use geom::size::Size2D;
|
use layers::platform::surface::NativeGraphicsMetadata;
|
||||||
use layers::platform::surface::{NativeGraphicsMetadata, NativePaintingGraphicsContext};
|
use layers::layers::LayerBufferSet;
|
||||||
use layers::platform::surface::{NativeSurface, NativeSurfaceMethods};
|
|
||||||
use serialize::{Encoder, Encodable};
|
use serialize::{Encoder, Encodable};
|
||||||
use std::fmt::{Formatter, Show};
|
use std::fmt::{Formatter, Show};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use constellation_msg::PipelineId;
|
use constellation_msg::PipelineId;
|
||||||
|
|
||||||
pub struct LayerBuffer {
|
|
||||||
/// The native surface which can be shared between threads or processes. On Mac this is an
|
|
||||||
/// `IOSurface`; on Linux this is an X Pixmap; on Android this is an `EGLImageKHR`.
|
|
||||||
pub native_surface: NativeSurface,
|
|
||||||
|
|
||||||
/// The rect in the containing RenderLayer that this represents.
|
|
||||||
pub rect: Rect<f32>,
|
|
||||||
|
|
||||||
/// The rect in pixels that will be drawn to the screen.
|
|
||||||
pub screen_pos: Rect<uint>,
|
|
||||||
|
|
||||||
/// The scale at which this tile is rendered
|
|
||||||
pub resolution: f32,
|
|
||||||
|
|
||||||
/// NB: stride is in pixels, like OpenGL GL_UNPACK_ROW_LENGTH.
|
|
||||||
pub stride: uint,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A set of layer buffers. This is an atomic unit used to switch between the front and back
|
|
||||||
/// buffers.
|
|
||||||
pub struct LayerBufferSet {
|
|
||||||
pub buffers: Vec<Box<LayerBuffer>>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LayerBufferSet {
|
|
||||||
/// Notes all buffer surfaces will leak if not destroyed via a call to `destroy`.
|
|
||||||
pub fn mark_will_leak(&mut self) {
|
|
||||||
for buffer in self.buffers.mut_iter() {
|
|
||||||
buffer.native_surface.mark_will_leak()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The status of the renderer.
|
/// The status of the renderer.
|
||||||
#[deriving(PartialEq, Clone)]
|
#[deriving(PartialEq, Clone)]
|
||||||
pub enum RenderState {
|
pub enum RenderState {
|
||||||
|
@ -160,41 +126,3 @@ impl<E, S: Encoder<E>> Encodable<S, E> for Box<ScriptListener> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The interface used by the quadtree and buffer map to get info about layer buffers.
|
|
||||||
pub trait Tile {
|
|
||||||
/// Returns the amount of memory used by the tile
|
|
||||||
fn get_mem(&self) -> uint;
|
|
||||||
/// Returns true if the tile is displayable at the given scale
|
|
||||||
fn is_valid(&self, f32) -> bool;
|
|
||||||
/// Returns the Size2D of the tile
|
|
||||||
fn get_size_2d(&self) -> Size2D<uint>;
|
|
||||||
|
|
||||||
/// Marks the layer buffer as not leaking. See comments on
|
|
||||||
/// `NativeSurfaceMethods::mark_wont_leak` for how this is used.
|
|
||||||
fn mark_wont_leak(&mut self);
|
|
||||||
|
|
||||||
/// Destroys the layer buffer. Painting task only.
|
|
||||||
fn destroy(self, graphics_context: &NativePaintingGraphicsContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Tile for Box<LayerBuffer> {
|
|
||||||
fn get_mem(&self) -> uint {
|
|
||||||
// This works for now, but in the future we may want a better heuristic
|
|
||||||
self.screen_pos.size.width * self.screen_pos.size.height
|
|
||||||
}
|
|
||||||
fn is_valid(&self, scale: f32) -> bool {
|
|
||||||
(self.resolution - scale).abs() < 1.0e-6
|
|
||||||
}
|
|
||||||
fn get_size_2d(&self) -> Size2D<uint> {
|
|
||||||
self.screen_pos.size
|
|
||||||
}
|
|
||||||
fn mark_wont_leak(&mut self) {
|
|
||||||
self.native_surface.mark_wont_leak()
|
|
||||||
}
|
|
||||||
fn destroy(self, graphics_context: &NativePaintingGraphicsContext) {
|
|
||||||
let mut this = self;
|
|
||||||
this.native_surface.destroy(graphics_context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 2b208270fac74af639228bc7d065436a73e67b2b
|
Subproject commit 5ff99f3ac797bd1bed205549d705264fc85d3099
|
Loading…
Add table
Add a link
Reference in a new issue