Implement progressive rendering

This commit is contained in:
eschweic 2013-07-01 15:20:29 -07:00
parent ad7dc32fc8
commit 6bebda4f26
4 changed files with 322 additions and 86 deletions

View file

@ -33,7 +33,7 @@ pub struct RenderLayer {
pub enum Msg { pub enum Msg {
RenderMsg(RenderLayer), RenderMsg(RenderLayer),
ReRenderMsg(f32), ReRenderMsg(~Rect<uint>], f32),
PaintPermissionGranted, PaintPermissionGranted,
PaintPermissionRevoked, PaintPermissionRevoked,
ExitMsg(Chan<()>), ExitMsg(Chan<()>),
@ -119,11 +119,11 @@ impl<C: RenderListener + Send> RenderTask<C> {
loop { loop {
match self.port.recv() { match self.port.recv() {
RenderMsg(render_layer) => { RenderMsg(render_layer) => {
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;
@ -146,15 +146,15 @@ impl<C: RenderListener + Send> RenderTask<C> {
} }
} }
fn render(&mut self, scale: f32) { fn render(&mut self, tiles: ~[Rect<uint>], scale: f32) {
debug!("render_task: rendering"); 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);
@ -166,30 +166,26 @@ impl<C: RenderListener + Send> RenderTask<C> {
// 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.each |tile_rect| {
while y < (render_layer.size.height as f32 * scale).ceil() as uint { let x = tile_rect.origin.x;
let mut x = 0; let y = tile_rect.origin.y;
while x < (render_layer.size.width as f32 * scale).ceil() as uint { let width = tile_rect.size.width;
// Figure out the dimension of this tile. let height = tile_rect.size.height;
let right = uint::min(x + tile_size, (render_layer.size.width as f32 * scale).ceil() as uint);
let bottom = uint::min(y + tile_size, (render_layer.size.height as f32 * scale).ceil() as uint);
let width = right - x;
let height = bottom - y;
let tile_rect = Rect(Point2D(x as f32 / scale, y as f32 / scale), Size2D(width as f32, height as f32)); let rect = Rect(Point2D(x as f32 / scale, y as f32 / scale), Size2D(width as f32, height as f32));
let screen_rect = Rect(Point2D(x, y), Size2D(width, height));
let buffer = LayerBuffer { let buffer = LayerBuffer {
draw_target: DrawTarget::new_with_fbo(self.opts.render_backend, draw_target: DrawTarget::new_with_fbo(self.opts.render_backend,
self.share_gl_context, self.share_gl_context,
Size2D(width as i32, Size2D(width as i32, height as i32),
height as i32),
B8G8R8A8), B8G8R8A8),
rect: tile_rect, rect: rect,
screen_pos: screen_rect, screen_pos: *tile_rect,
resolution: scale,
stride: (width * 4) as uint stride: (width * 4) as uint
}; };
{ {
// Build the render context. // Build the render context.
let ctx = RenderContext { let ctx = RenderContext {
@ -218,11 +214,8 @@ impl<C: RenderListener + Send> RenderTask<C> {
new_buffers.push(buffer); new_buffers.push(buffer);
x += tile_size;
} }
y += tile_size;
}
} }
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; //eschweic
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;
//eschweic
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,17 @@ impl RenderListener for CompositorChan {
self.chan.send(Paint(id, layer_buffer_set, new_size)) self.chan.send(Paint(id, layer_buffer_set, new_size))
} }
//eschweic
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 +118,16 @@ 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>),
//eschweic
// FIXME: 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 +225,61 @@ 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;
let ask_for_tiles: @fn() = || {
match *quadtree {
Some(ref quad) => {
let mut tile_size = quad.get_tile_size(); // temporary solution
let mut tile_request = ~[]; //FIXME: try not to allocate if possible
let mut y = world_offset.y as uint;
while y < world_offset.y as uint + window_size.height + tile_size {
let mut x = world_offset.x as uint;
while x < world_offset.x as uint + window_size.width + tile_size {
match *(quad.get_tile(x, y, *world_zoom)) {
Some(ref current_tile) => {
if current_tile.resolution == *world_zoom {
x += tile_size;
loop; // we already have this tile
}
}
None => {} // fall through
}
let (tile_pos, new_tile_size) = quad.get_tile_rect(x, y, *world_zoom);
tile_size = new_tile_size;
x = tile_pos.x;
y = tile_pos.y;
// TODO: clamp tiles to page bounds
// TODO: add null buffer/checkerboard tile to stop a flood of requests
println(fmt!("requesting tile: (%?, %?): %?", x, y, tile_size));
tile_request.push(Rect(Point2D(x, y), Size2D(tile_size, tile_size)));
x += tile_size;
}
y += tile_size;
}
if !tile_request.is_empty() {
match *render_chan {
Some(ref chan) => {
chan.send(ReRenderMsg(tile_request, *world_zoom));
}
_ => {}
}
}
}
_ => {}
}
};
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,17 +325,31 @@ 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));
//eschweic
match *quadtree {
Some(ref quad) => {
/* let wmp = world_mouse_point(layer_mouse_point);
println(fmt!("mouse: (%?, %?):", wmp.x as uint, wmp.y as uint));
let buffer = quad.get_tile(wmp.x as uint, wmp.y as uint, *world_zoom);
match *buffer {
None => println("None"),
Some(ref buffer) => println(fmt!("Some: (%?, %?), %?, %?", buffer.screen_pos.origin.x, buffer.screen_pos.origin.y, buffer.screen_pos.size.width, buffer.resolution)),
} */
println(quad.get_html());
}
None => {}
}
} }
WindowMouseUpEvent(button, layer_mouse_point) => { WindowMouseUpEvent(button, layer_mouse_point) => {
// rerender layer at new zoom level //FIXME: this should not be here eschweic
// FIXME: this should happen when the user stops zooming, definitely not here ask_for_tiles();
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 +358,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() {
@ -292,6 +385,21 @@ impl CompositorTask {
GetGLContext(chan) => chan.send(current_gl_context()), GetGLContext(chan) => chan.send(current_gl_context()),
//eschweic
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; },
@ -300,6 +408,12 @@ impl CompositorTask {
debug!("osmain: received new frame"); debug!("osmain: received new frame");
let quad;
match *quadtree {
Some(ref mut q) => quad = q,
None => fail!("Compositor: given paint command with no quadtree initialized"),
}
*page_size = Size2D(new_size.width as f32, new_size.height as f32); *page_size = Size2D(new_size.width as f32, new_size.height as f32);
let new_layer_buffer_set = new_layer_buffer_set.get(); let new_layer_buffer_set = new_layer_buffer_set.get();
@ -307,7 +421,15 @@ impl CompositorTask {
// Iterate over the children of the container layer. // Iterate over the children of the container layer.
let mut current_layer_child = root_layer.first_child; let mut current_layer_child = root_layer.first_child;
for new_layer_buffer_set.buffers.iter().advance |buffer| { // Replace the image layer data with the buffer data.
let buffers = util::replace(&mut new_layer_buffer_set.buffers, ~[]);
do vec::consume(buffers) |_, buffer| {
quad.add_tile(buffer.screen_pos.origin.x, buffer.screen_pos.origin.y,
*world_zoom, buffer);
}
for quad.get_all_tiles().each |buffer| {
let width = buffer.rect.size.width as uint; let width = buffer.rect.size.width as uint;
let height = buffer.rect.size.height as uint; let height = buffer.rect.size.height as uint;
@ -339,9 +461,10 @@ impl CompositorTask {
let origin = Point2D(origin.x as f32, origin.y as f32); let origin = Point2D(origin.x as f32, origin.y as f32);
// Set the layer's transform. // Set the layer's transform.
let transform = identity().translate(origin.x, origin.y, 0.0); let transform = identity().translate(origin.x * *world_zoom / buffer.resolution, origin.y * *world_zoom / buffer.resolution, 0.0);
let transform = transform.scale(width as f32, height as f32, 1.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); texture_layer.common.set_transform(transform);
} }
// Delete leftover layers // Delete leftover layers
@ -409,6 +532,8 @@ impl CompositorTask {
root_layer.common.set_transform(scroll_transform); root_layer.common.set_transform(scroll_transform);
// ask_for_tiles();
*recomposite = true; *recomposite = true;
} }
@ -447,6 +572,7 @@ impl CompositorTask {
window_size.height as f32 / -2f32, window_size.height as f32 / -2f32,
0.0); 0.0);
root_layer.common.set_transform(zoom_transform); root_layer.common.set_transform(zoom_transform);
// ask_for_tiles();
*recomposite = true; *recomposite = true;
} }

View file

@ -53,6 +53,10 @@ impl<T> Quadtree<T> {
} }
} }
/// 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. /// 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> { 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) self.root.get_tile(x as f32 / scale, y as f32 / scale)
@ -61,12 +65,24 @@ impl<T> Quadtree<T> {
pub fn add_tile(&mut self, x: uint, y: uint, scale: f32, tile: T) { 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); self.root.add_tile(x as f32 / scale, y as f32 / scale, tile, self.max_tile_size as f32 / scale);
} }
/// Get the tile size/offset for a given pixel position
pub fn get_tile_rect(&self, x: uint, y: uint, scale: f32) -> (Point2D<uint>, uint) {
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()
}
/// Generate html to visualize the tree
pub fn get_html(&self) -> ~str {
let header = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"> <html xmlns=\"http://www.w3.org/1999/xhtml\">";
fmt!("%s<body>%s</body></html>", header, self.root.get_html())
}
} }
impl<T> QuadtreeNode<T> { impl<T> QuadtreeNode<T> {
// Private method to create new children /// Private method to create new children
fn new_child(x: f32, y: f32, size: f32) -> QuadtreeNode<T> { fn new_child(x: f32, y: f32, size: f32) -> QuadtreeNode<T> {
QuadtreeNode { QuadtreeNode {
tile: None, tile: None,
@ -105,6 +121,26 @@ impl<T> QuadtreeNode<T> {
} }
} }
/// Get all tiles in the tree, parents first.
/// FIXME: this could probably be more efficient
fn get_all_tiles<'r>(&'r self) -> ~[&'r T] {
let mut ret = ~[];
match self.tile {
Some (ref tile) => ret = ~[tile],
None => {}
}
for self.quadrants.each |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, /// 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. /// 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) { fn add_tile(&mut self, x: f32, y: f32, tile: T, tile_size: f32) {
@ -117,14 +153,14 @@ impl<T> QuadtreeNode<T> {
if self.size <= tile_size { // We are the child if self.size <= tile_size { // We are the child
self.tile = Some(tile); self.tile = Some(tile);
for vec::each([TL, TR, BL, BR]) |quad| { for [TL, TR, BL, BR].each |quad| {
self.quadrants[*quad as int] = None; self.quadrants[*quad as int] = None;
} }
} else { //send tile to children } else { // Send tile to children
let quad = self.get_quadrant(x, y); let quad = self.get_quadrant(x, y);
match self.quadrants[quad as int] { match self.quadrants[quad as int] {
Some(ref mut child) => child.add_tile(x, y, tile, tile_size), Some(ref mut child) => child.add_tile(x, y, tile, tile_size),
None => { //make new child None => { // Make new child
let new_size = self.size / 2.0; let new_size = self.size / 2.0;
let new_x = match quad { let new_x = match quad {
TL | BL => self.origin.x, TL | BL => self.origin.x,
@ -138,19 +174,93 @@ impl<T> QuadtreeNode<T> {
c.add_tile(x, y, tile, tile_size); c.add_tile(x, y, tile, tile_size);
self.quadrants[quad as int] = Some(c); self.quadrants[quad as int] = Some(c);
// If we have 4 children, we probably shouldn't be hanging onto a tile // If my tile is completely occluded, get rid of it.
// Though this isn't always true if we have grandchildren // 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 { match self.quadrants {
[Some(_), Some(_), Some(_), Some(_)] => { [Some(ref tl_child), Some(ref tr_child), Some(ref bl_child), Some(ref br_child)] => {
self.tile = None; match (&tl_child.tile, &tr_child.tile, &bl_child.tile, &br_child.tile) {
(&Some(_), &Some(_), &Some(_), &Some(_)) => self.tile = None,
_ => {}
}
} }
_ => {} _ => {}
} }
}
}
}
}
/// Get an origin and a width/height for a future tile for a given position in page coords
fn get_tile_rect(&self, x: f32, y: f32, scale: f32, tile_size: f32) -> (Point2D<uint>, uint) {
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 (Point2D(self_x, self_y), self_size);
}
let index = self.get_quadrant(x,y) as int;
match self.quadrants[index] {
None => {
// calculate where the new tile should go
let factor = self.size / tile_size;
let divisor = uint::next_power_of_two(factor.ceil() as uint);
let new_size_page = self.size / (divisor as f32);
let new_size_pixel = (new_size_page * scale).ceil() as uint;
let new_x_page = self.origin.x + new_size_page * ((x - self.origin.x) / new_size_page).floor();
let new_y_page = self.origin.y + new_size_page * ((y - self.origin.y) / new_size_page).floor();
let new_x_pixel = (new_x_page * scale).ceil() as uint;
let new_y_pixel = (new_y_page * scale).ceil() as uint;
(Point2D(new_x_pixel, new_y_pixel), new_size_pixel)
}
Some(ref child) => child.get_tile_rect(x, y, scale, tile_size),
} }
} }
/// 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);
for [TL, TR, BL, BR].each |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]

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, //eschweic
// 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); //eschweic
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);
} }