auto merge of #557 : eschweic/servo/master, r=pcwalton

The compositor now keeps track of what has been rendered and what needs rendering based on the window rect, and asks the renderer for tiles only when needed.
This commit is contained in:
bors-servo 2013-07-10 17:33:36 -07:00
commit 4cf1ac9e69
4 changed files with 722 additions and 316 deletions

View file

@ -11,7 +11,6 @@ use servo_msg::compositor_msg::{RenderListener, IdleRenderState, RenderingRender
use servo_msg::compositor_msg::{LayerBufferSet}; use servo_msg::compositor_msg::{LayerBufferSet};
use font_context::FontContext; use font_context::FontContext;
use geom::matrix2d::Matrix2D; use geom::matrix2d::Matrix2D;
use geom::point::Point2D;
use geom::size::Size2D; use geom::size::Size2D;
use geom::rect::Rect; use geom::rect::Rect;
use opts::Opts; use opts::Opts;
@ -19,7 +18,6 @@ use render_context::RenderContext;
use std::cell::Cell; use std::cell::Cell;
use std::comm::{Chan, Port, SharedChan}; use std::comm::{Chan, Port, SharedChan};
use std::uint;
use servo_util::time::{ProfilerChan, profile}; use servo_util::time::{ProfilerChan, profile};
use servo_util::time; use servo_util::time;
@ -33,12 +31,28 @@ pub struct RenderLayer {
pub enum Msg { pub enum Msg {
RenderMsg(RenderLayer), RenderMsg(RenderLayer),
ReRenderMsg(f32), ReRenderMsg(~[BufferRequest], f32),
PaintPermissionGranted, PaintPermissionGranted,
PaintPermissionRevoked, PaintPermissionRevoked,
ExitMsg(Chan<()>), ExitMsg(Chan<()>),
} }
/// A request from the compositor to the renderer for tiles that need to be (re)displayed.
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 { pub struct RenderChan {
chan: SharedChan<Msg>, chan: SharedChan<Msg>,
@ -119,18 +133,19 @@ impl<C: RenderListener + Send> RenderTask<C> {
loop { loop {
match self.port.recv() { match self.port.recv() {
RenderMsg(render_layer) => { RenderMsg(render_layer) => {
if self.paint_permission {
self.compositor.new_layer(render_layer.size, self.opts.tile_size);
}
self.render_layer = Some(render_layer); self.render_layer = Some(render_layer);
self.render(1.0);
} }
ReRenderMsg(scale) => { ReRenderMsg(tiles, scale) => {
self.render(scale); self.render(tiles, scale);
} }
PaintPermissionGranted => { PaintPermissionGranted => {
self.paint_permission = true; self.paint_permission = true;
match self.last_paint_msg { match self.render_layer {
Some((ref layer_buffer_set, layer_size)) => { Some(ref render_layer) => {
self.compositor.paint(self.id, layer_buffer_set.clone(), layer_size); self.compositor.new_layer(render_layer.size, self.opts.tile_size);
self.compositor.set_render_state(IdleRenderState);
} }
None => {} None => {}
} }
@ -146,83 +161,69 @@ impl<C: RenderListener + Send> RenderTask<C> {
} }
} }
fn render(&mut self, scale: f32) { fn render(&mut self, tiles: ~[BufferRequest], scale: f32) {
debug!("render_task: rendering");
let render_layer; let render_layer;
match (self.render_layer) { match self.render_layer {
None => return,
Some(ref r_layer) => { Some(ref r_layer) => {
render_layer = r_layer; render_layer = r_layer;
} }
_ => return, // nothing to do
} }
self.compositor.set_render_state(RenderingRenderState); self.compositor.set_render_state(RenderingRenderState);
do time::profile(time::RenderingCategory, self.profiler_chan.clone()) { do time::profile(time::RenderingCategory, self.profiler_chan.clone()) {
let tile_size = self.opts.tile_size;
// FIXME: Try not to create a new array here. // FIXME: Try not to create a new array here.
let mut new_buffers = ~[]; let mut new_buffers = ~[];
// Divide up the layer into tiles. // Divide up the layer into tiles.
do time::profile(time::RenderingPrepBuffCategory, self.profiler_chan.clone()) { do time::profile(time::RenderingPrepBuffCategory, self.profiler_chan.clone()) {
let mut y = 0; for tiles.iter().advance |tile| {
while y < (render_layer.size.height as f32 * scale).ceil() as uint { let width = tile.screen_rect.size.width;
let mut x = 0; let height = tile.screen_rect.size.height;
while x < (render_layer.size.width as f32 * scale).ceil() as uint {
// Figure out the dimension of this tile. let buffer = LayerBuffer {
let right = uint::min(x + tile_size, (render_layer.size.width as f32 * scale).ceil() as uint); draw_target: DrawTarget::new_with_fbo(self.opts.render_backend,
let bottom = uint::min(y + tile_size, (render_layer.size.height as f32 * scale).ceil() as uint); self.share_gl_context,
let width = right - x; Size2D(width as i32, height as i32),
let height = bottom - y; B8G8R8A8),
rect: tile.page_rect,
let tile_rect = Rect(Point2D(x as f32 / scale, y as f32 / scale), Size2D(width as f32, height as f32)); screen_pos: tile.screen_rect,
let screen_rect = Rect(Point2D(x, y), Size2D(width, height)); resolution: scale,
stride: (width * 4) as uint
let buffer = LayerBuffer { };
draw_target: DrawTarget::new_with_fbo(self.opts.render_backend,
self.share_gl_context,
Size2D(width as i32, {
height as i32), // Build the render context.
B8G8R8A8), let ctx = RenderContext {
rect: tile_rect, canvas: &buffer,
screen_pos: screen_rect, font_ctx: self.font_ctx,
stride: (width * 4) as uint opts: &self.opts
}; };
{ // Apply the translation to render the tile we want.
// Build the render context. let matrix: Matrix2D<AzFloat> = Matrix2D::identity();
let ctx = RenderContext { let matrix = matrix.scale(scale as AzFloat, scale as AzFloat);
canvas: &buffer, let matrix = matrix.translate(-(buffer.rect.origin.x) as AzFloat,
font_ctx: self.font_ctx, -(buffer.rect.origin.y) as AzFloat);
opts: &self.opts
}; ctx.canvas.draw_target.set_transform(&matrix);
// Apply the translation to render the tile we want. // Clear the buffer.
let matrix: Matrix2D<AzFloat> = Matrix2D::identity(); ctx.clear();
let matrix = matrix.scale(scale as AzFloat, scale as AzFloat);
let matrix = matrix.translate(-(buffer.rect.origin.x) as AzFloat, // Draw the display list.
-(buffer.rect.origin.y) as AzFloat); do profile(time::RenderingDrawingCategory, self.profiler_chan.clone()) {
render_layer.display_list.draw_into_context(&ctx);
ctx.canvas.draw_target.set_transform(&matrix); ctx.canvas.draw_target.flush();
// Clear the buffer.
ctx.clear();
// Draw the display list.
do profile(time::RenderingDrawingCategory, self.profiler_chan.clone()) {
render_layer.display_list.draw_into_context(&ctx);
ctx.canvas.draw_target.flush();
}
} }
new_buffers.push(buffer);
x += tile_size;
} }
y += tile_size; new_buffers.push(buffer);
} }
} }
let layer_buffer_set = LayerBufferSet { let layer_buffer_set = LayerBufferSet {

View file

@ -10,7 +10,7 @@ use windowing::{ApplicationMethods, WindowMethods, WindowMouseEvent, WindowClick
use windowing::{WindowMouseDownEvent, WindowMouseUpEvent}; use windowing::{WindowMouseDownEvent, WindowMouseUpEvent};
use servo_msg::compositor_msg::{RenderListener, LayerBufferSet, RenderState}; use servo_msg::compositor_msg::{RenderListener, LayerBuffer, LayerBufferSet, RenderState};
use servo_msg::compositor_msg::{ReadyState, ScriptListener}; use servo_msg::compositor_msg::{ReadyState, ScriptListener};
use servo_msg::constellation_msg::{CompositorAck, ConstellationChan}; use servo_msg::constellation_msg::{CompositorAck, ConstellationChan};
use servo_msg::constellation_msg; use servo_msg::constellation_msg;
@ -28,6 +28,7 @@ use extra::timer;
use geom::matrix::identity; use geom::matrix::identity;
use geom::point::Point2D; use geom::point::Point2D;
use geom::size::Size2D; use geom::size::Size2D;
use geom::rect::Rect;
use layers::layers::{ARGB32Format, ContainerLayer, ContainerLayerKind, Format}; use layers::layers::{ARGB32Format, ContainerLayer, ContainerLayerKind, Format};
use layers::layers::{ImageData, WithDataFn}; use layers::layers::{ImageData, WithDataFn};
use layers::layers::{TextureLayerKind, TextureLayer, TextureManager}; use layers::layers::{TextureLayerKind, TextureLayer, TextureManager};
@ -40,6 +41,10 @@ use servo_util::time::ProfilerChan;
use extra::arc; use extra::arc;
pub use windowing; pub use windowing;
use extra::time::precise_time_s;
use compositing::quadtree::Quadtree;
mod quadtree;
/// The implementation of the layers-based compositor. /// The implementation of the layers-based compositor.
#[deriving(Clone)] #[deriving(Clone)]
pub struct CompositorChan { pub struct CompositorChan {
@ -70,6 +75,16 @@ impl RenderListener for CompositorChan {
self.chan.send(Paint(id, layer_buffer_set, new_size)) self.chan.send(Paint(id, layer_buffer_set, new_size))
} }
fn new_layer(&self, page_size: Size2D<uint>, tile_size: uint) {
self.chan.send(NewLayer(page_size, tile_size))
}
fn resize_layer(&self, page_size: Size2D<uint>) {
self.chan.send(ResizeLayer(page_size))
}
fn delete_layer(&self) {
self.chan.send(DeleteLayer)
}
fn set_render_state(&self, render_state: RenderState) { fn set_render_state(&self, render_state: RenderState) {
self.chan.send(ChangeRenderState(render_state)) self.chan.send(ChangeRenderState(render_state))
} }
@ -102,6 +117,15 @@ pub enum Msg {
GetSize(Chan<Size2D<int>>), GetSize(Chan<Size2D<int>>),
/// Requests the compositors GL context. /// Requests the compositors GL context.
GetGLContext(Chan<AzGLContext>), GetGLContext(Chan<AzGLContext>),
// TODO: Attach layer ids and epochs to these messages
/// Alerts the compositor that there is a new layer to be rendered.
NewLayer(Size2D<uint>, uint),
/// Alerts the compositor that the current layer has changed size.
ResizeLayer(Size2D<uint>),
/// Alerts the compositor that the current layer has been deleted.
DeleteLayer,
/// Requests that the compositor paint the given layer buffer set for the given page size. /// Requests that the compositor paint the given layer buffer set for the given page size.
Paint(uint, arc::ARC<LayerBufferSet>, Size2D<uint>), Paint(uint, arc::ARC<LayerBufferSet>, Size2D<uint>),
/// Alerts the compositor to the current status of page loading. /// Alerts the compositor to the current status of page loading.
@ -199,9 +223,106 @@ impl CompositorTask {
let local_zoom = @mut 1f32; let local_zoom = @mut 1f32;
// Channel to the current renderer. // Channel to the current renderer.
// FIXME: This probably shouldn't be stored like this. // FIXME: This probably shouldn't be stored like this.
let render_chan: @mut Option<RenderChan> = @mut None; let render_chan: @mut Option<RenderChan> = @mut None;
let pipeline_id: @mut Option<uint> = @mut None; let pipeline_id: @mut Option<uint> = @mut None;
// Quadtree for this layer
// FIXME: This should be one-per-layer
let quadtree: @mut Option<Quadtree<~LayerBuffer>> = @mut None;
// Keeps track of if we have performed a zoom event and how recently.
let zoom_action = @mut false;
let zoom_time = @mut 0f;
// Extract tiles from the given quadtree and build and display the render tree.
let build_layer_tree: @fn(&Quadtree<~LayerBuffer>) = |quad: &Quadtree<~LayerBuffer>| {
// Iterate over the children of the container layer.
let mut current_layer_child = root_layer.first_child;
let all_tiles = quad.get_all_tiles();
for all_tiles.iter().advance |buffer| {
let width = buffer.screen_pos.size.width as uint;
let height = buffer.screen_pos.size.height as uint;
debug!("osmain: compositing buffer rect %?", &buffer.rect);
// Find or create a texture layer.
let texture_layer;
current_layer_child = match current_layer_child {
None => {
debug!("osmain: adding new texture layer");
texture_layer = @mut TextureLayer::new(@buffer.draw_target.clone() as @TextureManager,
buffer.screen_pos.size);
root_layer.add_child(TextureLayerKind(texture_layer));
None
}
Some(TextureLayerKind(existing_texture_layer)) => {
texture_layer = existing_texture_layer;
texture_layer.manager = @buffer.draw_target.clone() as @TextureManager;
// Move on to the next sibling.
do current_layer_child.get().with_common |common| {
common.next_sibling
}
}
Some(_) => fail!(~"found unexpected layer kind"),
};
let origin = buffer.rect.origin;
let origin = Point2D(origin.x as f32, origin.y as f32);
// Set the layer's transform.
let transform = identity().translate(origin.x * *world_zoom, origin.y * *world_zoom, 0.0);
let transform = transform.scale(width as f32 * *world_zoom / buffer.resolution, height as f32 * *world_zoom / buffer.resolution, 1.0);
texture_layer.common.set_transform(transform);
}
// Delete leftover layers
while current_layer_child.is_some() {
let trash = current_layer_child.get();
do current_layer_child.get().with_common |common| {
current_layer_child = common.next_sibling;
}
root_layer.remove_child(trash);
}
// Reset zoom
*local_zoom = 1f32;
root_layer.common.set_transform(identity().translate(-world_offset.x,
-world_offset.y,
0.0));
*recomposite = true;
};
let ask_for_tiles: @fn() = || {
match *quadtree {
Some(ref mut quad) => {
let valid = |tile: &~LayerBuffer| -> bool {
tile.resolution == *world_zoom
};
let (tile_request, redisplay) = quad.get_tile_rects(Rect(Point2D(world_offset.x as int,
world_offset.y as int),
*window_size), valid, *world_zoom);
if !tile_request.is_empty() {
match *render_chan {
Some(ref chan) => {
chan.send(ReRenderMsg(tile_request, *world_zoom));
}
_ => {
println("Warning: Compositor: Cannot send tile request, no render chan initialized");
}
}
} else if redisplay {
build_layer_tree(quad);
}
}
_ => {
fail!("Compositor: Tried to ask for tiles without an initialized quadtree");
}
}
};
let update_layout_callbacks: @fn(LayoutChan) = |layout_chan: LayoutChan| { let update_layout_callbacks: @fn(LayoutChan) = |layout_chan: LayoutChan| {
let layout_chan_clone = layout_chan.clone(); let layout_chan_clone = layout_chan.clone();
do window.set_navigation_callback |direction| { do window.set_navigation_callback |direction| {
@ -247,18 +368,9 @@ impl CompositorTask {
} }
WindowMouseDownEvent(button, layer_mouse_point) => { WindowMouseDownEvent(button, layer_mouse_point) => {
event = MouseDownEvent(button, world_mouse_point(layer_mouse_point)); event = MouseDownEvent(button, world_mouse_point(layer_mouse_point));
} }
WindowMouseUpEvent(button, layer_mouse_point) => { WindowMouseUpEvent(button, layer_mouse_point) => {
// rerender layer at new zoom level
// FIXME: this should happen when the user stops zooming, definitely not here
match *render_chan {
Some(ref r_chan) => {
r_chan.send(ReRenderMsg(*world_zoom));
}
None => {} // Nothing to do
}
event = MouseUpEvent(button, world_mouse_point(layer_mouse_point)); event = MouseUpEvent(button, world_mouse_point(layer_mouse_point));
} }
} }
@ -266,6 +378,7 @@ impl CompositorTask {
} }
}; };
let check_for_messages: @fn(&Port<Msg>) = |port: &Port<Msg>| { let check_for_messages: @fn(&Port<Msg>) = |port: &Port<Msg>| {
// Handle messages // Handle messages
while port.peek() { while port.peek() {
@ -291,78 +404,49 @@ impl CompositorTask {
} }
GetGLContext(chan) => chan.send(current_gl_context()), GetGLContext(chan) => chan.send(current_gl_context()),
NewLayer(new_size, tile_size) => {
*page_size = Size2D(new_size.width as f32, new_size.height as f32);
*quadtree = Some(Quadtree::new(0, 0, new_size.width, new_size.height, tile_size));
ask_for_tiles();
}
ResizeLayer(new_size) => {
*page_size = Size2D(new_size.width as f32, new_size.height as f32);
// TODO: update quadtree, ask for tiles
}
DeleteLayer => {
// TODO: create secondary layer tree, keep displaying until new tiles come in
}
Paint(id, new_layer_buffer_set, new_size) => { Paint(id, new_layer_buffer_set, new_size) => {
match *pipeline_id { match *pipeline_id {
Some(pipeline_id) => if id != pipeline_id { loop; }, Some(pipeline_id) => if id != pipeline_id { loop; },
None => { loop; }, None => { loop; },
} }
debug!("osmain: received new frame"); debug!("osmain: received new frame");
*page_size = Size2D(new_size.width as f32, new_size.height as f32);
let quad;
match *quadtree {
Some(ref mut q) => quad = q,
None => fail!("Compositor: given paint command with no quadtree initialized"),
}
let new_layer_buffer_set = new_layer_buffer_set.get(); let new_layer_buffer_set = new_layer_buffer_set.get();
// Iterate over the children of the container layer.
let mut current_layer_child = root_layer.first_child;
for new_layer_buffer_set.buffers.iter().advance |buffer| { for new_layer_buffer_set.buffers.iter().advance |buffer| {
let width = buffer.rect.size.width as uint; // FIXME: Don't copy the buffers here
let height = buffer.rect.size.height as uint; quad.add_tile(buffer.screen_pos.origin.x, buffer.screen_pos.origin.y,
*world_zoom, ~buffer.clone());
debug!("osmain: compositing buffer rect %?", &buffer.rect);
// Find or create a texture layer.
let texture_layer;
current_layer_child = match current_layer_child {
None => {
debug!("osmain: adding new texture layer");
texture_layer = @mut TextureLayer::new(@buffer.draw_target.clone() as @TextureManager,
buffer.screen_pos.size);
root_layer.add_child(TextureLayerKind(texture_layer));
None
}
Some(TextureLayerKind(existing_texture_layer)) => {
texture_layer = existing_texture_layer;
texture_layer.manager = @buffer.draw_target.clone() as @TextureManager;
// Move on to the next sibling.
do current_layer_child.get().with_common |common| {
common.next_sibling
}
}
Some(_) => fail!(~"found unexpected layer kind"),
};
let origin = buffer.screen_pos.origin;
let origin = Point2D(origin.x as f32, origin.y as f32);
// Set the layer's transform.
let transform = identity().translate(origin.x, origin.y, 0.0);
let transform = transform.scale(width as f32, height as f32, 1.0);
texture_layer.common.set_transform(transform);
} }
// Delete leftover layers *page_size = Size2D(new_size.width as f32, new_size.height as f32);
while current_layer_child.is_some() {
let trash = current_layer_child.get(); build_layer_tree(quad);
do current_layer_child.get().with_common |common| {
current_layer_child = common.next_sibling;
}
root_layer.remove_child(trash);
}
// Reset zoom
*local_zoom = 1f32;
root_layer.common.set_transform(identity().translate(-world_offset.x,
-world_offset.y,
0.0));
// TODO: Recycle the old buffers; send them back to the renderer to reuse if // TODO: Recycle the old buffers; send them back to the renderer to reuse if
// it wishes. // it wishes.
*recomposite = true;
} }
} }
} }
@ -409,6 +493,11 @@ impl CompositorTask {
root_layer.common.set_transform(scroll_transform); root_layer.common.set_transform(scroll_transform);
// FIXME: ask_for_tiles() should be called here, but currently this sends a flood of requests
// to the renderer, which slows the application dramatically. Instead, ask_for_tiles() is only
// called on a click event.
ask_for_tiles();
*recomposite = true; *recomposite = true;
} }
@ -416,6 +505,8 @@ impl CompositorTask {
// When the user pinch-zooms, scale the layer // When the user pinch-zooms, scale the layer
do window.set_zoom_callback |magnification| { do window.set_zoom_callback |magnification| {
*zoom_action = true;
*zoom_time = precise_time_s();
let old_world_zoom = *world_zoom; let old_world_zoom = *world_zoom;
// Determine zoom amount // Determine zoom amount
@ -465,6 +556,13 @@ impl CompositorTask {
} }
timer::sleep(&uv_global_loop::get(), 100); timer::sleep(&uv_global_loop::get(), 100);
// If a pinch-zoom happened recently, ask for tiles at the new resolution
if *zoom_action && precise_time_s() - *zoom_time > 0.3 {
*zoom_action = false;
ask_for_tiles();
}
} }
self.shutdown_chan.send(()) self.shutdown_chan.send(())

View file

@ -8,11 +8,29 @@
use geom::point::Point2D; use geom::point::Point2D;
use geom::size::Size2D; use geom::size::Size2D;
use geom::rect::Rect; use geom::rect::Rect;
use std::uint::{div_ceil, next_power_of_two};
use std::vec::build_sized;
use gfx::render_task::BufferRequest;
priv enum Quadtype { /// Parent to all quadtree nodes. Stores variables needed at all levels. All method calls
Empty, /// at this level are in pixel coordinates.
Base, pub struct Quadtree<T> {
Branch, root: QuadtreeNode<T>,
max_tile_size: 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.
tile: Option<T>,
/// The position of the node in page coordinates.
origin: Point2D<f32>,
/// The width and height of the node in page coordinates.
size: f32,
/// The node's children.
quadrants: [Option<~QuadtreeNode<T>>, ..4],
/// If this node is marked for rendering
render_flag: bool,
} }
priv enum Quadrant { priv enum Quadrant {
@ -22,206 +40,488 @@ priv enum Quadrant {
BR = 3, BR = 3,
} }
impl<T> Quadtree<T> {
/// Public method to create a new Quadtree
pub fn new(x: uint, y: uint, width: uint, height: uint, tile_size: uint) -> Quadtree<T> {
// Spaces must be squares and powers of 2, so expand the space until it is
let longer = width.max(&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: QuadtreeNode {
tile: None,
origin: Point2D(x as f32, y as f32),
size: size as f32,
quadrants: [None, None, None, None],
render_flag: false,
},
max_tile_size: tile_size,
}
}
/// Return the maximum allowed tile size
pub fn get_tile_size(&self) -> uint {
self.max_tile_size
}
/// Get a tile at a given pixel position and scale.
pub fn get_tile<'r>(&'r self, x: uint, y: uint, scale: f32) -> &'r Option<T> {
self.root.get_tile(x as f32 / scale, y as f32 / scale)
}
/// Add a tile associtated with a given pixel position and scale.
pub fn add_tile(&mut self, x: uint, y: uint, scale: f32, tile: T) {
self.root.add_tile(x as f32 / scale, y as f32 / scale, tile, self.max_tile_size as f32 / scale);
}
/// Get the tile rect in screen and page coordinates for a given pixel position
pub fn get_tile_rect(&mut self, x: uint, y: uint, scale: f32) -> BufferRequest {
self.root.get_tile_rect(x as f32 / scale, y as f32 / scale, scale, self.max_tile_size as f32 / scale)
}
/// Get all the tiles in the tree
pub fn get_all_tiles<'r>(&'r self) -> ~[&'r T] {
self.root.get_all_tiles()
}
/// Ask a tile to be deleted from the quadtree. This tries to delete a tile that is far from the
/// given point in pixel coordinates.
pub fn remove_tile(&mut self, x: uint, y: uint, scale: f32) {
self.root.remove_tile(x as f32 / scale, y as f32 / scale);
}
/// Given a window rect in page coordinates and a function to check if an existing tile is "valid"
/// (i.e. is the correct resolution), this function returns a list of BufferRequests for tiles that
/// need to be rendered. It also returns a boolean 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.
pub fn get_tile_rects(&mut self, window: Rect<int>, valid: &fn(&T) -> bool, scale: f32) ->
(~[BufferRequest], bool) {
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)),
valid, scale, self.max_tile_size as f32 / scale)
}
/// Generate html to visualize the tree. For debugging purposes only.
pub fn get_html(&self) -> ~str {
static HEADER: &'static str = "<!DOCTYPE html><html>";
fmt!("%s<body>%s</body></html>", HEADER, self.root.get_html())
}
pub struct Quadtree {
quadtype: Quadtype,
rect: Rect<uint>,
quadrants: [Option<~Quadtree>, ..4],
} }
impl<T> QuadtreeNode<T> {
impl Quadtree { /// Private method to create new children
pub fn new(x: uint, y: uint, width: uint, height: uint) -> Quadtree { fn new_child(x: f32, y: f32, size: f32) -> QuadtreeNode<T> {
Quadtree { QuadtreeNode {
quadtype: Empty, tile: None,
rect: Rect { origin: Point2D(x, y),
origin: Point2D(x, y), size: size,
size: Size2D(width, height),
},
quadrants: [None, None, None, None], quadrants: [None, None, None, None],
render_flag: false,
} }
} }
/// Determine which child contains a given point /// Determine which child contains a given point in page coords.
priv fn get_quadrant(&self, x: uint, y: uint) -> Quadrant { fn get_quadrant(&self, x: f32, y: f32) -> Quadrant {
let self_width = self.rect.size.width; if x < self.origin.x + self.size / 2.0 {
let self_height = self.rect.size.height; if y < self.origin.y + self.size / 2.0 {
let self_x = self.rect.origin.x; TL
let self_y = self.rect.origin.y; } else {
match (self_width, self_height) { BL
(1, _) => {
if y < self_y + self_height / 2 {
TL
} else {
BR
}
}
(_, 1) => {
if x < self_x + self_width / 2 {
TL
} else {
BR
}
}
_ => {
if x < self_x + self_width / 2 {
if y < self_y + self_height / 2 {
TL
} else {
BL
}
} else if y < self_y + self_height / 2 {
TR
} else {
BR
}
} }
} else if y < self.origin.y + self.size / 2.0 {
TR
} else {
BR
} }
} }
/// Change a point from Empty to Base
pub fn add_region(&mut self, x: uint, y: uint) {
let self_x = self.rect.origin.x;
let self_y = self.rect.origin.y;
let self_width = self.rect.size.width;
let self_height = self.rect.size.height;
debug!("Quadtree: adding: (%?, %?) w:%?, h:%?", self_x, self_y, self_width, self_height); /// Get the lowest-level (highest resolution) tile associated with a given position in page coords.
fn get_tile<'r> (&'r self, x: f32, y: f32) -> &'r Option<T> {
if x >= self_x + self_width || x < self_x if x >= self.origin.x + self.size || x < self.origin.x
|| y >= self_y + self_height || y < self_y { || y >= self.origin.y + self.size || y < self.origin.y {
return; // Out of bounds fail!("Quadtree: Tried to get a tile outside of range");
}
match self.quadtype {
Base => return,
Empty => {
if self_width == 1 && self_height == 1 {
self.quadtype = Base;
return;
}
self.quadtype = Branch;
// Initialize children
self.quadrants[TL as int] = Some(~Quadtree::new(self_x,
self_y,
(self_width / 2).max(&1),
(self_height / 2).max(&1)));
if self_width > 1 && self_height > 1 {
self.quadrants[TR as int] = Some(~Quadtree::new(self_x + self_width / 2,
self_y,
self_width - self_width / 2,
self_height / 2));
self.quadrants[BL as int] = Some(~Quadtree::new(self_x,
self_y + self_height / 2,
self_width / 2,
self_height - self_height / 2));
}
self.quadrants[BR as int] = Some(~Quadtree::new(self_x + self_width / 2,
self_y + self_height / 2,
self_width - self_width / 2,
self_height - self_height / 2));
}
Branch => {} // Fall through
} }
// If we've made it this far, we know we are a branch and therefore have children
let index = self.get_quadrant(x, y) as int; let index = self.get_quadrant(x, y) as int;
match self.quadrants[index] { match self.quadrants[index] {
None => fail!("Quadtree: child query failure"), None => &'r self.tile,
Some(ref mut region) => { Some(ref child) => child.get_tile(x, y),
// Recurse if necessary }
match region.quadtype { }
Empty | Branch => {
region.add_region(x, y); /// Get all tiles in the tree, parents first.
} fn get_all_tiles<'r>(&'r self) -> ~[&'r T] {
Base => {} // nothing to do let mut ret = ~[];
}
match self.tile {
Some(ref tile) => ret = ~[tile],
None => {}
}
for self.quadrants.iter().advance |quad| {
match *quad {
Some(ref child) => ret = ret + 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.
fn add_tile(&mut self, x: f32, y: f32, tile: T, tile_size: f32) {
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");
}
// FIXME: ideally we could make the assignments in the match, if self.size <= tile_size { // We are the child
// but borrowed pointers prevent that. So here's a flag instead. self.tile = Some(tile);
let mut base_flag = 0; // FIXME: This should be inline, but currently won't compile
let quads = [TL, TR, BL, BR];
// If all children are Bases, convert self to Base for quads.iter().advance |quad| {
match (&self.quadrants, self_width, self_height) { self.quadrants[*quad as int] = None;
(&[Some(ref tl_q), _, _, Some(ref br_q)], 1, _) |
(&[Some(ref tl_q), _, _, Some(ref br_q)], _, 1) => {
match(tl_q.quadtype, br_q.quadtype) {
(Base, Base) => {
base_flag = 1;
}
_ => {} // nothing to do
}
} }
(&[Some(ref tl_q), Some(ref tr_q), Some(ref bl_q), Some(ref br_q)], _, _) => { self.render_flag = false;
match (tl_q.quadtype, tr_q.quadtype, bl_q.quadtype, br_q.quadtype) { } else { // Send tile to children
(Base, Base, Base, Base) => { let quad = self.get_quadrant(x, y);
base_flag = 2; match self.quadrants[quad as int] {
Some(ref mut child) => child.add_tile(x, y, tile, tile_size),
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 = ~QuadtreeNode::new_child(new_x, new_y, new_size);
c.add_tile(x, y, tile, tile_size);
self.quadrants[quad as int] = Some(c);
// If my tile is completely occluded, get rid of it.
// FIXME: figure out a better way to determine if a tile is completely occluded
// e.g. this alg doesn't work if a tile is covered by its grandchildren
match self.quadrants {
[Some(ref tl_child), Some(ref tr_child), Some(ref bl_child), Some(ref br_child)] => {
match (&tl_child.tile, &tr_child.tile, &bl_child.tile, &br_child.tile) {
(&Some(_), &Some(_), &Some(_), &Some(_)) => self.tile = None,
_ => {}
}
} }
_ => {} // nothing to do _ => {}
} }
}
_ => {} // nothing to do
}
match base_flag {
0 => {}
1 => {
self.quadtype = Base;
self.quadrants[TL as int] = None;
self.quadrants[BR as int] = None;
}
2 => {
self.quadtype = Base;
self.quadrants[TL as int] = None;
self.quadrants[TR as int] = None;
self.quadrants[BL as int] = None;
self.quadrants[BR as int] = None;
}
_ => fail!("Quadtree: Unknown flag type"),
}
}
/// Check if a point is a Base or Empty.
pub fn check_region(&self, x: uint, y: uint) -> bool {
let self_x = self.rect.origin.x;
let self_y = self.rect.origin.y;
let self_width = self.rect.size.width;
let self_height = self.rect.size.height;
if x >= self_x + self_width || x < self_x
|| y >= self_y + self_height || y < self_y {
return false; // out of bounds
}
match self.quadtype {
Empty => false,
Base => true,
Branch => {
let index = self.get_quadrant(x,y) as int;
match self.quadrants[index] {
None => fail!("Quadtree: child query failed"),
Some(ref region) => region.check_region(x, y)
} }
} }
} }
} }
/// 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, 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 self_x = (self.origin.x * scale).ceil() as uint;
let self_y = (self.origin.y * scale).ceil() as uint;
let self_size = (self.size * scale).ceil() as uint;
return BufferRequest(Rect(Point2D(self_x, self_y), Size2D(self_size, self_size)),
Rect(Point2D(self.origin.x, self.origin.y), Size2D(self.size, self.size)));
}
let quad = self.get_quadrant(x,y);
match self.quadrants[quad as int] {
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 = ~QuadtreeNode::new_child(new_x, new_y, new_size);
c.render_flag = true;
let result = c.get_tile_rect(x, y, scale, tile_size);
self.quadrants[quad as int] = Some(c);
result
}
Some(ref mut child) => child.get_tile_rect(x, y, scale, tile_size),
}
}
/// Removes a tile that is far from the given input point in page coords. Returns true if the child
/// has no tiles and needs to be deleted.
fn remove_tile(&mut self, x: f32, y: f32) -> bool {
match (&self.tile, &self.quadrants) {
(&Some(_), &[None, None, None, None]) => {
self.tile = None;
return true;
}
(&Some(_), _) => {
self.tile = None;
return false;
}
_ => {}
}
// 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 my_child = match quad {
TL => {
match (&self.quadrants[BR as int], &self.quadrants[BL as int], &self.quadrants[TR as int]) {
(&Some(_), _, _) => BR,
(&None, &Some(_), _) => BL,
(&None, &None, &Some(_)) => TR,
_ => TL,
}
}
TR => {
match (&self.quadrants[BL as int], &self.quadrants[BR as int], &self.quadrants[TL as int]) {
(&Some(_), _, _) => BL,
(&None, &Some(_), _) => BR,
(&None, &None, &Some(_)) => TL,
_ => TR,
}
}
BL => {
match (&self.quadrants[TR as int], &self.quadrants[TL as int], &self.quadrants[BR as int]) {
(&Some(_), _, _) => TR,
(&None, &Some(_), _) => TL,
(&None, &None, &Some(_)) => BR,
_ => BL,
}
}
BR => {
match (&self.quadrants[TL as int], &self.quadrants[TR as int], &self.quadrants[BL as int]) {
(&Some(_), _, _) => TL,
(&None, &Some(_), _) => TR,
(&None, &None, &Some(_)) => BL,
_ => BR,
}
}
};
match self.quadrants[my_child as int] {
Some(ref mut child) if !child.remove_tile(x, y) => {
return false;
}
Some(_) => {} // fall through
None => fail!("Quadtree: child query failure"),
}
// child.remove_tile() returned true
self.quadrants[my_child as int] = None;
match self.quadrants {
[None, None, None, None] => true,
_ => false,
}
}
/// Given a window rect in page coordinates and a tile validation function, returns a BufferRequest array
/// and a redisplay boolean. See QuadTree function description for more details.
fn get_tile_rects(&mut self, window: Rect<f32>, valid: &fn(&T) -> bool, scale: f32, tile_size: f32) ->
(~[BufferRequest], bool) {
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 w_x < s_x || w_x + w_width > s_x + s_size
|| w_y < s_y || w_y + w_height > s_y + s_size {
println(fmt!("window: %?, %?, %?, %?; self: %?, %?, %?", w_x, w_y, w_width, w_height, s_x, s_y, s_size));
fail!("Quadtree: tried to query an invalid tile rect");
}
if s_size <= tile_size { // We are the child
match self.tile {
Some(ref tile) if valid(tile) => {
let redisplay = match self.quadrants {
[None, None, None, None] => false,
_ => true,
};
if redisplay {
// FIXME: This should be inline, but currently won't compile
let quads = [TL, TR, BL, BR];
for quads.iter().advance |quad| {
self.quadrants[*quad as int] = None;
}
}
return (~[], redisplay);
}
None if self.render_flag => {
return(~[], false);
}
_ => {
return (~[self.get_tile_rect(s_x, s_y, scale, tile_size)], false);
}
}
}
// 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 builder = |push: &fn(Quadrant)| {
match (w_tl_quad, w_br_quad) {
(tl, br) if tl as int == br as int => {
push(tl);
}
(TL, br) => {
push(TL);
push(br);
match br {
BR => {
push(TR);
push(BL);
}
_ => {}
}
}
(tl, br) => {
push(tl);
push(br);
}
}
};
let quads_to_check = build_sized(4, builder);
let mut ret = ~[];
let mut redisplay = false;
for quads_to_check.iter().advance |quad| {
match self.quadrants[*quad as int] {
Some(ref mut child) => {
// 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 (c_ret, c_redisplay) = child.get_tile_rects(new_window, |x| valid(x), scale, tile_size);
ret = ret + c_ret;
redisplay = redisplay || c_redisplay;
}
None => {
// Figure out locations of future children
let (x_start, y_start, x_end, y_end) = match *quad {
TL => (w_x,
w_y,
(w_x + w_width).min(&(s_x + s_size / 2.0)),
(w_y + w_height).min(&(s_y + s_size / 2.0))),
TR => (w_x.max(&(s_x + s_size / 2.0)),
w_y,
(w_x + w_width + tile_size).min(&(s_x + s_size)),
(w_y + w_height).min(&(s_y + s_size / 2.0))),
BL => (w_x,
w_y.max(&(s_y + s_size / 2.0)),
(w_x + w_width).min(&(s_x + s_size / 2.0)),
(w_y + w_height + tile_size).min(&(s_y + s_size))),
BR => (w_x.max(&(s_x + s_size / 2.0)),
w_y.max(&(s_y + s_size / 2.0)),
(w_x + w_width + tile_size).min(&(s_x + s_size)),
(w_y + w_height + tile_size).min(&(s_y + s_size))),
};
let size = (((x_end - x_start) / tile_size).ceil() *
((y_end - y_start) / tile_size).ceil()) as uint;
let builder = |push: &fn(BufferRequest)| {
let mut y = y_start;
while y < y_end {
let mut x = x_start;
while x < x_end {
push(self.get_tile_rect(x, y, scale, tile_size));
x = x + tile_size;
}
y = y + tile_size;
}
};
ret = ret + build_sized(size, builder);
}
}
}
(ret, redisplay)
}
/// Generate html to visualize the tree.
/// This is really inefficient, but it's for testing only.
fn get_html(&self) -> ~str {
let mut ret = ~"";
match self.tile {
Some(ref tile) => {
ret = fmt!("%s%?", ret, tile);
}
None => {
ret = fmt!("%sNO TILE", ret);
}
}
match self.quadrants {
[None, None, None, None] => {}
_ => {
ret = fmt!("%s<table border=1><tr>", ret);
// FIXME: This should be inline, but currently won't compile
let quads = [TL, TR, BL, BR];
for quads.iter().advance |quad| {
match self.quadrants[*quad as int] {
Some(ref child) => {
ret = fmt!("%s<td>%s</td>", ret, child.get_html());
}
None => {
ret = fmt!("%s<td>EMPTY CHILD</td>", ret);
}
}
match *quad {
TR => ret = fmt!("%s</tr><tr>", ret),
_ => {}
}
}
ret = fmt!("%s</table>\n", ret);
}
}
return ret;
}
} }
#[test] #[test]
fn test_add_region() { fn test_add_tile() {
let mut t = Quadtree::new(50, 50, 3, 4); let mut t = Quadtree::new(50, 30, 20, 20, 10);
assert!(!t.check_region(50, 50)); assert!(t.get_tile(50, 30, 1.0).is_none());
t.add_region(50, 50); t.add_tile(50, 30, 1.0, 1);
assert!(t.check_region(50, 50)); assert!(t.get_tile(50, 30, 1.0).get() == 1);
assert!(!t.check_region(51, 50)); assert!(t.get_tile(59, 39, 1.0).get() == 1);
assert!(!t.check_region(50, 51)); assert!(t.get_tile(60, 40, 1.0).is_none());
t.add_region(53, 50); assert!(t.get_tile(110, 70, 2.0).get() == 1);
assert!(!t.check_region(53, 50)); t.add_tile(100, 60, 2.0, 2);
assert!(t.get_tile(109, 69, 2.0).get() == 2);
assert!(t.get_tile(110, 70, 2.0).get() == 1);
} }

View file

@ -19,8 +19,12 @@ pub struct LayerBuffer {
// The rect in pixels that will be drawn to the screen. // The rect in pixels that will be drawn to the screen.
screen_pos: Rect<uint>, screen_pos: Rect<uint>,
// The scale at which this tile is rendered
resolution: f32,
// NB: stride is in pixels, like OpenGL GL_UNPACK_ROW_LENGTH. // NB: stride is in pixels, like OpenGL GL_UNPACK_ROW_LENGTH.
stride: uint stride: uint,
} }
/// A set of layer buffers. This is an atomic unit used to switch between the front and back /// A set of layer buffers. This is an atomic unit used to switch between the front and back
@ -49,6 +53,9 @@ pub enum ReadyState {
/// submit them to be drawn to the display. /// submit them to be drawn to the display.
pub trait RenderListener { pub trait RenderListener {
fn get_gl_context(&self) -> AzGLContext; fn get_gl_context(&self) -> AzGLContext;
fn new_layer(&self, Size2D<uint>, uint);
fn resize_layer(&self, Size2D<uint>);
fn delete_layer(&self);
fn paint(&self, id: uint, layer_buffer_set: arc::ARC<LayerBufferSet>, new_size: Size2D<uint>); fn paint(&self, id: uint, layer_buffer_set: arc::ARC<LayerBufferSet>, new_size: Size2D<uint>);
fn set_render_state(&self, render_state: RenderState); fn set_render_state(&self, render_state: RenderState);
} }