diff options
author | Keegan McAllister <kmcallister@mozilla.com> | 2013-10-18 15:46:47 -0700 |
---|---|---|
committer | Keegan McAllister <kmcallister@mozilla.com> | 2013-10-22 13:35:09 -0700 |
commit | ecc3db7b1f9ea5088b39684c687985667b83bd3d (patch) | |
tree | 60602d2190a89f4a3135d0e04848f5e3241b3eb7 /src | |
parent | 797143a1d3e09b4ddd35a1ce5948be424ec3f9ef (diff) | |
download | servo-ecc3db7b1f9ea5088b39684c687985667b83bd3d.tar.gz servo-ecc3db7b1f9ea5088b39684c687985667b83bd3d.zip |
Split out the part of the compositor which makes OpenGL calls
Diffstat (limited to 'src')
-rw-r--r-- | src/components/main/compositing/mod.rs | 391 | ||||
-rw-r--r-- | src/components/main/compositing/run.rs | 405 |
2 files changed, 408 insertions, 388 deletions
diff --git a/src/components/main/compositing/mod.rs b/src/components/main/compositing/mod.rs index 7e236dddc70..1575e60f975 100644 --- a/src/components/main/compositing/mod.rs +++ b/src/components/main/compositing/mod.rs @@ -2,50 +2,27 @@ * 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 platform::{Application, Window}; - pub use windowing; -use windowing::{ApplicationMethods, WindowEvent, WindowMethods}; -use windowing::{IdleWindowEvent, ResizeWindowEvent, LoadUrlWindowEvent, MouseWindowEventClass}; -use windowing::{ScrollWindowEvent, ZoomWindowEvent, NavigationWindowEvent, FinishedWindowEvent}; -use windowing::{QuitWindowEvent, MouseWindowClickEvent, MouseWindowMouseDownEvent, MouseWindowMouseUpEvent}; use servo_msg::compositor_msg::{RenderListener, LayerBufferSet, RenderState}; use servo_msg::compositor_msg::{ReadyState, ScriptListener, Epoch}; -use servo_msg::constellation_msg::{ConstellationChan, NavigateMsg, PipelineId, ResizedWindowMsg, LoadUrlMsg}; -use servo_msg::constellation_msg; +use servo_msg::constellation_msg::{ConstellationChan, PipelineId}; use gfx::opts::Opts; -use azure::azure_hl::{DataSourceSurface, DrawTarget, SourceSurfaceMethods, current_gl_context}; use azure::azure::AzGLContext; use std::comm; use std::comm::{Chan, SharedChan, Port}; use std::num::Orderable; -use std::vec; -use std::path::Path; -use std::rt::io::timer::Timer; -use geom::matrix::identity; use geom::point::Point2D; use geom::size::Size2D; use geom::rect::Rect; -use layers::layers::{ARGB32Format, ContainerLayer, ContainerLayerKind, Format}; -use layers::layers::{ImageData, WithDataFn}; -use layers::rendergl; -use layers::scene::Scene; -use opengles::gl2; -use png; -use servo_util::{time, url}; -use servo_util::time::profile; use servo_util::time::ProfilerChan; -use extra::future::Future; -use extra::time::precise_time_s; - use constellation::SendableFrameTree; -use compositing::compositor_layer::CompositorLayer; mod quadtree; mod compositor_layer; +mod run; /// The implementation of the layers-based compositor. @@ -160,31 +137,6 @@ pub enum Msg { SetIds(SendableFrameTree, Chan<()>, ConstellationChan), } -/// Azure surface wrapping to work with the layers infrastructure. -struct AzureDrawTargetImageData { - draw_target: DrawTarget, - data_source_surface: DataSourceSurface, - size: Size2D<uint>, -} - -impl ImageData for AzureDrawTargetImageData { - fn size(&self) -> Size2D<uint> { - self.size - } - fn stride(&self) -> uint { - self.data_source_surface.stride() as uint - } - fn format(&self) -> Format { - // FIXME: This is not always correct. We should query the Azure draw target for the format. - ARGB32Format - } - fn with_data(&self, f: WithDataFn) { - do self.data_source_surface.with_data |data| { - f(data); - } - } -} - pub struct CompositorTask { opts: Opts, port: Port<Msg>, @@ -206,344 +158,7 @@ impl CompositorTask { } } - /// Starts the compositor, which listens for messages on the specified port. pub fn run(&self) { - let app: Application = ApplicationMethods::new(); - let window: @mut Window = WindowMethods::new(&app); - - // Create an initial layer tree. - // - // 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. - let context = rendergl::init_render_context(); - let root_layer = @mut ContainerLayer(); - let window_size = window.size(); - let mut scene = Scene(ContainerLayerKind(root_layer), window_size, identity()); - let mut window_size = Size2D(window_size.width as uint, window_size.height as uint); - let mut done = false; - let mut recomposite = false; - - // Keeps track of the current zoom factor - let mut world_zoom = 1f32; - let mut zoom_action = false; - let mut zoom_time = 0f64; - - // The root CompositorLayer - let mut compositor_layer: Option<CompositorLayer> = None; - let mut constellation_chan: Option<ConstellationChan> = None; - - // Get BufferRequests from each layer. - let ask_for_tiles = || { - let window_size_page = Size2D(window_size.width as f32 / world_zoom, - window_size.height as f32 / world_zoom); - for layer in compositor_layer.mut_iter() { - if !layer.hidden { - recomposite = layer.get_buffer_request(Rect(Point2D(0f32, 0f32), window_size_page), - world_zoom) || recomposite; - } else { - debug!("Compositor: root layer is hidden!"); - } - } - }; - - let check_for_messages: &fn(&Port<Msg>) = |port: &Port<Msg>| { - // Handle messages - while port.peek() { - match port.recv() { - Exit => done = true, - - ChangeReadyState(ready_state) => window.set_ready_state(ready_state), - ChangeRenderState(render_state) => window.set_render_state(render_state), - - SetIds(frame_tree, response_chan, new_constellation_chan) => { - response_chan.send(()); - - // This assumes there is at most one child, which should be the case. - match root_layer.first_child { - Some(old_layer) => root_layer.remove_child(old_layer), - None => {} - } - - let layer = CompositorLayer::from_frame_tree(frame_tree, - self.opts.tile_size, - Some(10000000u)); - root_layer.add_child_start(ContainerLayerKind(layer.root_layer)); - compositor_layer = Some(layer); - - constellation_chan = Some(new_constellation_chan); - } - - GetSize(chan) => { - let size = window.size(); - chan.send(Size2D(size.width as int, size.height as int)); - } - - GetGLContext(chan) => chan.send(current_gl_context()), - - NewLayer(_id, new_size) => { - // FIXME: This should create an additional layer instead of replacing the current one. - // Once ResizeLayer messages are set up, we can switch to the new functionality. - - let p = match compositor_layer { - Some(ref compositor_layer) => compositor_layer.pipeline.clone(), - None => fail!("Compositor: Received new layer without initialized pipeline"), - }; - let page_size = Size2D(new_size.width as f32, new_size.height as f32); - let new_layer = CompositorLayer::new(p, Some(page_size), - self.opts.tile_size, Some(10000000u)); - - let current_child = root_layer.first_child; - // This assumes there is at most one child, which should be the case. - match current_child { - Some(old_layer) => root_layer.remove_child(old_layer), - None => {} - } - root_layer.add_child_start(ContainerLayerKind(new_layer.root_layer)); - compositor_layer = Some(new_layer); - - ask_for_tiles(); - } - - SetLayerPageSize(id, new_size, epoch) => { - match compositor_layer { - Some(ref mut layer) => { - let page_window = Size2D(window_size.width as f32 / world_zoom, - window_size.height as f32 / world_zoom); - assert!(layer.resize(id, new_size, page_window, epoch)); - ask_for_tiles(); - } - None => {} - } - } - - SetLayerClipRect(id, new_rect) => { - match compositor_layer { - Some(ref mut layer) => { - assert!(layer.set_clipping_rect(id, new_rect)); - ask_for_tiles(); - } - None => {} - } - } - - DeleteLayer(id) => { - match compositor_layer { - Some(ref mut layer) => { - assert!(layer.delete(id)); - ask_for_tiles(); - } - None => {} - } - } - - Paint(id, new_layer_buffer_set, epoch) => { - debug!("osmain: received new frame"); - - match compositor_layer { - Some(ref mut layer) => { - assert!(layer.add_buffers(id, new_layer_buffer_set, epoch)); - recomposite = true; - } - None => { - fail!("Compositor: given paint command with no CompositorLayer initialized"); - } - } - // TODO: Recycle the old buffers; send them back to the renderer to reuse if - // it wishes. - } - - InvalidateRect(id, rect) => { - match compositor_layer { - Some(ref mut layer) => { - layer.invalidate_rect(id, Rect(Point2D(rect.origin.x as f32, - rect.origin.y as f32), - Size2D(rect.size.width as f32, - rect.size.height as f32))); - ask_for_tiles(); - } - None => {} // Nothing to do - } - } - } - } - }; - - let check_for_window_messages: &fn(WindowEvent) = |event| { - match event { - IdleWindowEvent => {} - - ResizeWindowEvent(width, height) => { - let new_size = Size2D(width, height); - if window_size != new_size { - debug!("osmain: window resized to %ux%u", width, height); - window_size = new_size; - match constellation_chan { - Some(ref chan) => chan.send(ResizedWindowMsg(new_size)), - None => error!("Compositor: Recieved resize event without initialized layout chan"), - } - } else { - debug!("osmain: dropping window resize since size is still %ux%u", width, height); - } - } - - LoadUrlWindowEvent(url_string) => { - debug!("osmain: loading URL `%s`", url_string); - let root_pipeline_id = match compositor_layer { - Some(ref layer) => layer.pipeline.id.clone(), - None => fail!("Compositor: Received LoadUrlWindowEvent without initialized compositor layers"), - }; - match constellation_chan { - Some(ref chan) => chan.send(LoadUrlMsg(root_pipeline_id, - url::make_url(url_string.to_str(), None), - Future::from_value(window_size))), - None => error!("Compositor: Recieved loadurl event without initialized layout chan"), - } - } - - MouseWindowEventClass(mouse_window_event) => { - let point = match mouse_window_event { - MouseWindowClickEvent(_, p) => Point2D(p.x / world_zoom, p.y / world_zoom), - MouseWindowMouseDownEvent(_, p) => Point2D(p.x / world_zoom, p.y / world_zoom), - MouseWindowMouseUpEvent(_, p) => Point2D(p.x / world_zoom, p.y / world_zoom), - }; - for layer in compositor_layer.iter() { - layer.send_mouse_event(mouse_window_event, point); - } - } - - ScrollWindowEvent(delta, cursor) => { - // TODO: modify delta to snap scroll to pixels. - let page_delta = Point2D(delta.x as f32 / world_zoom, delta.y as f32 / world_zoom); - let page_cursor: Point2D<f32> = Point2D(cursor.x as f32 / world_zoom, - cursor.y as f32 / world_zoom); - let page_window = Size2D(window_size.width as f32 / world_zoom, - window_size.height as f32 / world_zoom); - for layer in compositor_layer.mut_iter() { - recomposite = layer.scroll(page_delta, page_cursor, page_window) || recomposite; - } - ask_for_tiles(); - } - - ZoomWindowEvent(magnification) => { - zoom_action = true; - zoom_time = precise_time_s(); - let old_world_zoom = world_zoom; - - // Determine zoom amount - world_zoom = (world_zoom * magnification).max(&1.0); - root_layer.common.set_transform(identity().scale(world_zoom, world_zoom, 1f32)); - - // Scroll as needed - let page_delta = Point2D(window_size.width as f32 * (1.0 / world_zoom - 1.0 / old_world_zoom) * 0.5, - window_size.height as f32 * (1.0 / world_zoom - 1.0 / old_world_zoom) * 0.5); - // TODO: modify delta to snap scroll to pixels. - let page_cursor = Point2D(-1f32, -1f32); // Make sure this hits the base layer - let page_window = Size2D(window_size.width as f32 / world_zoom, - window_size.height as f32 / world_zoom); - for layer in compositor_layer.mut_iter() { - layer.scroll(page_delta, page_cursor, page_window); - } - - recomposite = true; - } - - NavigationWindowEvent(direction) => { - let direction = match direction { - windowing::Forward => constellation_msg::Forward, - windowing::Back => constellation_msg::Back, - }; - match constellation_chan { - Some(ref chan) => chan.send(NavigateMsg(direction)), - None => error!("Compositor: Recieved navigation event without initialized layout chan"), - } - } - - FinishedWindowEvent => { - if self.opts.exit_after_load { - done = true; - } - } - - QuitWindowEvent => { - done = true; - } - } - }; - - - let profiler_chan = self.profiler_chan.clone(); - let write_png = self.opts.output_file.is_some(); - let exit = self.opts.exit_after_load; - let composite = || { - do profile(time::CompositingCategory, profiler_chan.clone()) { - debug!("compositor: compositing"); - // Adjust the layer dimensions as necessary to correspond to the size of the window. - scene.size = window.size(); - - // Render the scene. - rendergl::render_scene(context, &scene); - } - - // Render to PNG. We must read from the back buffer (ie, before - // window.present()) as OpenGL ES 2 does not have glReadBuffer(). - if write_png { - let (width, height) = (window_size.width as uint, window_size.height as uint); - let path = from_str::<Path>(*self.opts.output_file.get_ref()).unwrap(); - let mut pixels = gl2::read_pixels(0, 0, - width as gl2::GLsizei, - height as gl2::GLsizei, - gl2::RGB, gl2::UNSIGNED_BYTE); - // flip image vertically (texture is upside down) - let orig_pixels = pixels.clone(); - let stride = width * 3; - for y in range(0, height) { - let dst_start = y * stride; - let src_start = (height - y - 1) * stride; - vec::bytes::copy_memory(pixels.mut_slice(dst_start, dst_start + stride), - orig_pixels.slice(src_start, src_start + stride), - stride); - } - let img = png::Image { - width: width as u32, - height: height as u32, - color_type: png::RGB8, - pixels: pixels, - }; - let res = png::store_png(&img, &path); - assert!(res.is_ok()); - - done = true; - } - - window.present(); - - if exit { done = true; } - }; - - // Enter the main event loop. - let mut tm = Timer::new().unwrap(); - while !done { - // Check for new messages coming from the rendering task. - check_for_messages(&self.port); - - // Check for messages coming from the windowing system. - check_for_window_messages(window.recv()); - - if recomposite { - recomposite = false; - composite(); - } - - tm.sleep(10); - - // 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(()) + run::run_compositor(self); } } diff --git a/src/components/main/compositing/run.rs b/src/components/main/compositing/run.rs new file mode 100644 index 00000000000..7fda95b3d83 --- /dev/null +++ b/src/components/main/compositing/run.rs @@ -0,0 +1,405 @@ +/* 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 platform::{Application, Window}; + +use windowing::{ApplicationMethods, WindowEvent, WindowMethods}; +use windowing::{IdleWindowEvent, ResizeWindowEvent, LoadUrlWindowEvent, MouseWindowEventClass}; +use windowing::{ScrollWindowEvent, ZoomWindowEvent, NavigationWindowEvent, FinishedWindowEvent}; +use windowing::{QuitWindowEvent, MouseWindowClickEvent, MouseWindowMouseDownEvent, MouseWindowMouseUpEvent}; + +use servo_msg::constellation_msg::{ConstellationChan, NavigateMsg, ResizedWindowMsg, LoadUrlMsg}; +use servo_msg::constellation_msg; + +use azure::azure_hl::{DataSourceSurface, DrawTarget, SourceSurfaceMethods, current_gl_context}; +use std::comm::Port; +use std::num::Orderable; +use std::vec; +use std::path::Path; +use std::rt::io::timer::Timer; +use geom::matrix::identity; +use geom::point::Point2D; +use geom::size::Size2D; +use geom::rect::Rect; +use layers::layers::{ARGB32Format, ContainerLayer, ContainerLayerKind, Format}; +use layers::layers::{ImageData, WithDataFn}; +use layers::rendergl; +use layers::scene::Scene; +use opengles::gl2; +use png; +use servo_util::{time, url}; +use servo_util::time::profile; + +use extra::future::Future; +use extra::time::precise_time_s; + +use compositing::compositor_layer::CompositorLayer; + +use compositing::*; + +/// Azure surface wrapping to work with the layers infrastructure. +struct AzureDrawTargetImageData { + draw_target: DrawTarget, + data_source_surface: DataSourceSurface, + size: Size2D<uint>, +} + +impl ImageData for AzureDrawTargetImageData { + fn size(&self) -> Size2D<uint> { + self.size + } + fn stride(&self) -> uint { + self.data_source_surface.stride() as uint + } + fn format(&self) -> Format { + // FIXME: This is not always correct. We should query the Azure draw target for the format. + ARGB32Format + } + fn with_data(&self, f: WithDataFn) { + do self.data_source_surface.with_data |data| { + f(data); + } + } +} + +/// Starts the compositor, which listens for messages on the specified port. +pub fn run_compositor(compositor: &CompositorTask) { + let app: Application = ApplicationMethods::new(); + let window: @mut Window = WindowMethods::new(&app); + + // Create an initial layer tree. + // + // 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. + let context = rendergl::init_render_context(); + let root_layer = @mut ContainerLayer(); + let window_size = window.size(); + let mut scene = Scene(ContainerLayerKind(root_layer), window_size, identity()); + let mut window_size = Size2D(window_size.width as uint, window_size.height as uint); + let mut done = false; + let mut recomposite = false; + + // Keeps track of the current zoom factor + let mut world_zoom = 1f32; + let mut zoom_action = false; + let mut zoom_time = 0f64; + + // The root CompositorLayer + let mut compositor_layer: Option<CompositorLayer> = None; + let mut constellation_chan: Option<ConstellationChan> = None; + + // Get BufferRequests from each layer. + let ask_for_tiles = || { + let window_size_page = Size2D(window_size.width as f32 / world_zoom, + window_size.height as f32 / world_zoom); + for layer in compositor_layer.mut_iter() { + if !layer.hidden { + recomposite = layer.get_buffer_request(Rect(Point2D(0f32, 0f32), window_size_page), + world_zoom) || recomposite; + } else { + debug!("Compositor: root layer is hidden!"); + } + } + }; + + let check_for_messages: &fn(&Port<Msg>) = |port: &Port<Msg>| { + // Handle messages + while port.peek() { + match port.recv() { + Exit => done = true, + + ChangeReadyState(ready_state) => window.set_ready_state(ready_state), + ChangeRenderState(render_state) => window.set_render_state(render_state), + + SetIds(frame_tree, response_chan, new_constellation_chan) => { + response_chan.send(()); + + // This assumes there is at most one child, which should be the case. + match root_layer.first_child { + Some(old_layer) => root_layer.remove_child(old_layer), + None => {} + } + + let layer = CompositorLayer::from_frame_tree(frame_tree, + compositor.opts.tile_size, + Some(10000000u)); + root_layer.add_child_start(ContainerLayerKind(layer.root_layer)); + compositor_layer = Some(layer); + + constellation_chan = Some(new_constellation_chan); + } + + GetSize(chan) => { + let size = window.size(); + chan.send(Size2D(size.width as int, size.height as int)); + } + + GetGLContext(chan) => chan.send(current_gl_context()), + + NewLayer(_id, new_size) => { + // FIXME: This should create an additional layer instead of replacing the current one. + // Once ResizeLayer messages are set up, we can switch to the new functionality. + + let p = match compositor_layer { + Some(ref compositor_layer) => compositor_layer.pipeline.clone(), + None => fail!("Compositor: Received new layer without initialized pipeline"), + }; + let page_size = Size2D(new_size.width as f32, new_size.height as f32); + let new_layer = CompositorLayer::new(p, Some(page_size), + compositor.opts.tile_size, Some(10000000u)); + + let current_child = root_layer.first_child; + // This assumes there is at most one child, which should be the case. + match current_child { + Some(old_layer) => root_layer.remove_child(old_layer), + None => {} + } + root_layer.add_child_start(ContainerLayerKind(new_layer.root_layer)); + compositor_layer = Some(new_layer); + + ask_for_tiles(); + } + + SetLayerPageSize(id, new_size, epoch) => { + match compositor_layer { + Some(ref mut layer) => { + let page_window = Size2D(window_size.width as f32 / world_zoom, + window_size.height as f32 / world_zoom); + assert!(layer.resize(id, new_size, page_window, epoch)); + ask_for_tiles(); + } + None => {} + } + } + + SetLayerClipRect(id, new_rect) => { + match compositor_layer { + Some(ref mut layer) => { + assert!(layer.set_clipping_rect(id, new_rect)); + ask_for_tiles(); + } + None => {} + } + } + + DeleteLayer(id) => { + match compositor_layer { + Some(ref mut layer) => { + assert!(layer.delete(id)); + ask_for_tiles(); + } + None => {} + } + } + + Paint(id, new_layer_buffer_set, epoch) => { + debug!("osmain: received new frame"); + + match compositor_layer { + Some(ref mut layer) => { + assert!(layer.add_buffers(id, new_layer_buffer_set, epoch)); + recomposite = true; + } + None => { + fail!("Compositor: given paint command with no CompositorLayer initialized"); + } + } + // TODO: Recycle the old buffers; send them back to the renderer to reuse if + // it wishes. + } + + InvalidateRect(id, rect) => { + match compositor_layer { + Some(ref mut layer) => { + layer.invalidate_rect(id, Rect(Point2D(rect.origin.x as f32, + rect.origin.y as f32), + Size2D(rect.size.width as f32, + rect.size.height as f32))); + ask_for_tiles(); + } + None => {} // Nothing to do + } + } + } + } + }; + + let check_for_window_messages: &fn(WindowEvent) = |event| { + match event { + IdleWindowEvent => {} + + ResizeWindowEvent(width, height) => { + let new_size = Size2D(width, height); + if window_size != new_size { + debug!("osmain: window resized to %ux%u", width, height); + window_size = new_size; + match constellation_chan { + Some(ref chan) => chan.send(ResizedWindowMsg(new_size)), + None => error!("Compositor: Recieved resize event without initialized layout chan"), + } + } else { + debug!("osmain: dropping window resize since size is still %ux%u", width, height); + } + } + + LoadUrlWindowEvent(url_string) => { + debug!("osmain: loading URL `%s`", url_string); + let root_pipeline_id = match compositor_layer { + Some(ref layer) => layer.pipeline.id.clone(), + None => fail!("Compositor: Received LoadUrlWindowEvent without initialized compositor layers"), + }; + match constellation_chan { + Some(ref chan) => chan.send(LoadUrlMsg(root_pipeline_id, + url::make_url(url_string.to_str(), None), + Future::from_value(window_size))), + None => error!("Compositor: Recieved loadurl event without initialized layout chan"), + } + } + + MouseWindowEventClass(mouse_window_event) => { + let point = match mouse_window_event { + MouseWindowClickEvent(_, p) => Point2D(p.x / world_zoom, p.y / world_zoom), + MouseWindowMouseDownEvent(_, p) => Point2D(p.x / world_zoom, p.y / world_zoom), + MouseWindowMouseUpEvent(_, p) => Point2D(p.x / world_zoom, p.y / world_zoom), + }; + for layer in compositor_layer.iter() { + layer.send_mouse_event(mouse_window_event, point); + } + } + + ScrollWindowEvent(delta, cursor) => { + // TODO: modify delta to snap scroll to pixels. + let page_delta = Point2D(delta.x as f32 / world_zoom, delta.y as f32 / world_zoom); + let page_cursor: Point2D<f32> = Point2D(cursor.x as f32 / world_zoom, + cursor.y as f32 / world_zoom); + let page_window = Size2D(window_size.width as f32 / world_zoom, + window_size.height as f32 / world_zoom); + for layer in compositor_layer.mut_iter() { + recomposite = layer.scroll(page_delta, page_cursor, page_window) || recomposite; + } + ask_for_tiles(); + } + + ZoomWindowEvent(magnification) => { + zoom_action = true; + zoom_time = precise_time_s(); + let old_world_zoom = world_zoom; + + // Determine zoom amount + world_zoom = (world_zoom * magnification).max(&1.0); + root_layer.common.set_transform(identity().scale(world_zoom, world_zoom, 1f32)); + + // Scroll as needed + let page_delta = Point2D(window_size.width as f32 * (1.0 / world_zoom - 1.0 / old_world_zoom) * 0.5, + window_size.height as f32 * (1.0 / world_zoom - 1.0 / old_world_zoom) * 0.5); + // TODO: modify delta to snap scroll to pixels. + let page_cursor = Point2D(-1f32, -1f32); // Make sure this hits the base layer + let page_window = Size2D(window_size.width as f32 / world_zoom, + window_size.height as f32 / world_zoom); + for layer in compositor_layer.mut_iter() { + layer.scroll(page_delta, page_cursor, page_window); + } + + recomposite = true; + } + + NavigationWindowEvent(direction) => { + let direction = match direction { + windowing::Forward => constellation_msg::Forward, + windowing::Back => constellation_msg::Back, + }; + match constellation_chan { + Some(ref chan) => chan.send(NavigateMsg(direction)), + None => error!("Compositor: Recieved navigation event without initialized layout chan"), + } + } + + FinishedWindowEvent => { + if compositor.opts.exit_after_load { + done = true; + } + } + + QuitWindowEvent => { + done = true; + } + } + }; + + + let profiler_chan = compositor.profiler_chan.clone(); + let write_png = compositor.opts.output_file.is_some(); + let exit = compositor.opts.exit_after_load; + let composite = || { + do profile(time::CompositingCategory, profiler_chan.clone()) { + debug!("compositor: compositing"); + // Adjust the layer dimensions as necessary to correspond to the size of the window. + scene.size = window.size(); + + // Render the scene. + rendergl::render_scene(context, &scene); + } + + // Render to PNG. We must read from the back buffer (ie, before + // window.present()) as OpenGL ES 2 does not have glReadBuffer(). + if write_png { + let (width, height) = (window_size.width as uint, window_size.height as uint); + let path = from_str::<Path>(*compositor.opts.output_file.get_ref()).unwrap(); + let mut pixels = gl2::read_pixels(0, 0, + width as gl2::GLsizei, + height as gl2::GLsizei, + gl2::RGB, gl2::UNSIGNED_BYTE); + // flip image vertically (texture is upside down) + let orig_pixels = pixels.clone(); + let stride = width * 3; + for y in range(0, height) { + let dst_start = y * stride; + let src_start = (height - y - 1) * stride; + vec::bytes::copy_memory(pixels.mut_slice(dst_start, dst_start + stride), + orig_pixels.slice(src_start, src_start + stride), + stride); + } + let img = png::Image { + width: width as u32, + height: height as u32, + color_type: png::RGB8, + pixels: pixels, + }; + let res = png::store_png(&img, &path); + assert!(res.is_ok()); + + done = true; + } + + window.present(); + + if exit { done = true; } + }; + + // Enter the main event loop. + let mut tm = Timer::new().unwrap(); + while !done { + // Check for new messages coming from the rendering task. + check_for_messages(&compositor.port); + + // Check for messages coming from the windowing system. + check_for_window_messages(window.recv()); + + if recomposite { + recomposite = false; + composite(); + } + + tm.sleep(10); + + // 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(); + } + + } + + compositor.shutdown_chan.send(()) +} |