servo: Merge #1092 - Add a headless mode for tests (from kmcallister:headless); r=metajack

Source-Repo: https://github.com/servo/servo
Source-Revision: f4df246f9d70c23dca293510207041c03c388c47
This commit is contained in:
Keegan McAllister 2013-10-22 13:52:14 -07:00
Родитель cdbd5ffc99
Коммит 74b0891125
7 изменённых файлов: 468 добавлений и 394 удалений

Просмотреть файл

@ -72,6 +72,7 @@ make
- `-p INTERVAL` turns on the profiler and dumps info to the console every
`INTERVAL` seconds
- `-s SIZE` sets the tile size for rendering; defaults to 512
- `-z` disables all graphical output; useful for running JS / layout tests
### Keyboard Shortcuts

Просмотреть файл

@ -19,6 +19,7 @@ pub struct Opts {
profiler_period: Option<f64>,
exit_after_load: bool,
output_file: Option<~str>,
headless: bool,
}
pub fn from_cmdline_args(args: &[~str]) -> Opts {
@ -33,6 +34,7 @@ pub fn from_cmdline_args(args: &[~str]) -> Opts {
getopts::optopt("t"), // threads to render with
getopts::optflagopt("p"), // profiler flag and output interval
getopts::optflag("x"), // exit after load flag
getopts::optflag("z"), // headless mode
];
let opt_match = match getopts::getopts(args, opts) {
@ -80,17 +82,14 @@ pub fn from_cmdline_args(args: &[~str]) -> Opts {
from_str(period).unwrap()
};
let exit_after_load = opt_match.opt_present("x");
let output_file = opt_match.opt_str("o");
Opts {
urls: urls,
render_backend: render_backend,
n_render_threads: n_render_threads,
tile_size: tile_size,
profiler_period: profiler_period,
exit_after_load: exit_after_load,
output_file: output_file,
exit_after_load: opt_match.opt_present("x"),
output_file: opt_match.opt_str("o"),
headless: opt_match.opt_present("z"),
}
}

Просмотреть файл

@ -204,6 +204,13 @@ impl<C: RenderListener + Send,T:Send+Freeze> RenderTask<C,T> {
}
fn render(&mut self, tiles: ~[BufferRequest], scale: f32) {
// In headless mode, disable the renderer, because it makes OpenGL
// calls. Once we have CPU rendering we should render in CPU mode and
// just disable texture upload.
if self.opts.headless {
return;
}
let render_layer;
match self.render_layer {
Some(ref r_layer) => {

Просмотреть файл

@ -2,51 +2,30 @@
* 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;
mod run_headless;
/// The implementation of the layers-based compositor.
#[deriving(Clone)]
@ -160,31 +139,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 +160,11 @@ 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();
}
if self.opts.headless {
run_headless::run_compositor(self);
} else {
run::run_compositor(self);
}
self.shutdown_chan.send(())
}
}

Просмотреть файл

@ -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(())
}

Просмотреть файл

@ -0,0 +1,41 @@
/* 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 geom::size::Size2D;
use std::ptr;
use compositing::*;
/// Starts the compositor, which listens for messages on the specified port.
///
/// This is the null compositor which doesn't draw anything to the screen.
/// It's intended for headless testing.
pub fn run_compositor(compositor: &CompositorTask) {
loop {
match compositor.port.recv() {
Exit => break,
GetSize(chan) => {
chan.send(Size2D(500, 500));
}
GetGLContext(chan) => {
chan.send(ptr::null());
}
SetIds(_, response_chan, _) => {
response_chan.send(());
}
// Explicitly list ignored messages so that when we add a new one,
// we'll notice and think about whether it needs a response, like
// SetIds.
NewLayer(*) | SetLayerPageSize(*) | SetLayerClipRect(*) | DeleteLayer(*) |
Paint(*) | InvalidateRect(*) | ChangeReadyState(*) | ChangeRenderState(*)
=> ()
}
}
compositor.shutdown_chan.send(())
}

Просмотреть файл

@ -85,7 +85,7 @@ fn run_test(file: ~str) {
let path = os::make_absolute(&Path::new(file));
// FIXME (#1094): not the right way to transform a path
let infile = ~"file://" + path.display().to_str();
let res = run::process_output("./servo", [infile]);
let res = run::process_output("./servo", [~"-z", infile]);
let out = str::from_utf8(res.output);
io::print(out);
let lines: ~[&str] = out.split_iter('\n').collect();