зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
cdbd5ffc99
Коммит
74b0891125
|
@ -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();
|
||||
|
|
Загрузка…
Ссылка в новой задаче