mirror of
https://github.com/servo/servo.git
synced 2025-06-17 12:54:28 +00:00
script caches last loaded url -- currently no caching policy naive caching of render layers for near-instant forward/back handling evicted pipelines is currently broken
448 lines
19 KiB
Rust
448 lines
19 KiB
Rust
/* 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 script::dom::event::{Event, ClickEvent, MouseDownEvent, MouseUpEvent, ResizeEvent};
|
|
use script::script_task::{LoadMsg, NavigateMsg, SendEventMsg};
|
|
use script::layout_interface::{LayoutChan, RouteScriptMsg};
|
|
use windowing::{ApplicationMethods, WindowMethods, WindowMouseEvent, WindowClickEvent};
|
|
use windowing::{WindowMouseDownEvent, WindowMouseUpEvent};
|
|
|
|
use servo_msg::compositor::{RenderListener, LayerBufferSet, RenderState};
|
|
use servo_msg::compositor::{ReadyState, ScriptListener};
|
|
use servo_msg::constellation;
|
|
use gfx::render_task::{RenderChan, ReRenderMsg};
|
|
|
|
use azure::azure_hl::{DataSourceSurface, DrawTarget, SourceSurfaceMethods, current_gl_context};
|
|
use azure::azure::AzGLContext;
|
|
use std::cell::Cell;
|
|
use std::comm;
|
|
use std::comm::{Chan, SharedChan, Port};
|
|
use std::num::Orderable;
|
|
use std::task;
|
|
use std::util;
|
|
use geom::matrix::identity;
|
|
use geom::point::Point2D;
|
|
use geom::size::Size2D;
|
|
use layers::layers::{ARGB32Format, ContainerLayer, ContainerLayerKind, Format};
|
|
use layers::layers::{ImageData, WithDataFn};
|
|
use layers::layers::{TextureLayerKind, TextureLayer, TextureManager};
|
|
use layers::rendergl;
|
|
use layers::scene::Scene;
|
|
use servo_util::{time, url};
|
|
use servo_util::time::profile;
|
|
use servo_util::time::ProfilerChan;
|
|
|
|
pub use windowing;
|
|
|
|
/// The implementation of the layers-based compositor.
|
|
#[deriving(Clone)]
|
|
pub struct CompositorChan {
|
|
/// A channel on which messages can be sent to the compositor.
|
|
chan: SharedChan<Msg>,
|
|
}
|
|
|
|
/// Implementation of the abstract `ScriptListener` interface.
|
|
impl ScriptListener for CompositorChan {
|
|
fn set_ready_state(&self, ready_state: ReadyState) {
|
|
let msg = ChangeReadyState(ready_state);
|
|
self.chan.send(msg);
|
|
}
|
|
}
|
|
|
|
/// Implementation of the abstract `RenderListener` interface.
|
|
impl RenderListener for CompositorChan {
|
|
fn get_gl_context(&self) -> AzGLContext {
|
|
let (port, chan) = comm::stream();
|
|
self.chan.send(GetGLContext(chan));
|
|
port.recv()
|
|
}
|
|
fn paint(&self, layer_buffer_set: LayerBufferSet, new_size: Size2D<uint>) {
|
|
self.chan.send(Paint(layer_buffer_set, new_size))
|
|
}
|
|
fn set_render_state(&self, render_state: RenderState) {
|
|
self.chan.send(ChangeRenderState(render_state))
|
|
}
|
|
}
|
|
|
|
impl CompositorChan {
|
|
pub fn new(chan: Chan<Msg>) -> CompositorChan {
|
|
CompositorChan {
|
|
chan: SharedChan::new(chan),
|
|
}
|
|
}
|
|
pub fn send(&self, msg: Msg) {
|
|
self.chan.send(msg);
|
|
}
|
|
}
|
|
|
|
/// Messages to the compositor.
|
|
pub enum Msg {
|
|
/// Requests that the compositor shut down.
|
|
Exit,
|
|
/// Requests the compositors GL context.
|
|
GetGLContext(Chan<AzGLContext>),
|
|
/// Requests that the compositor paint the given layer buffer set for the given page size.
|
|
Paint(LayerBufferSet, Size2D<uint>),
|
|
/// Alerts the compositor to the current status of page loading.
|
|
ChangeReadyState(ReadyState),
|
|
/// Alerts the compositor to the current status of rendering.
|
|
ChangeRenderState(RenderState),
|
|
/// Sets the channel to the current layout task
|
|
SetLayoutChan(LayoutChan),
|
|
/// Sets the channel to the current renderer
|
|
SetRenderChan(RenderChan),
|
|
}
|
|
|
|
/// 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 {
|
|
port: Port<Msg>,
|
|
profiler_chan: ProfilerChan,
|
|
shutdown_chan: SharedChan<()>,
|
|
}
|
|
|
|
impl CompositorTask {
|
|
pub fn new(port: Port<Msg>,
|
|
profiler_chan: ProfilerChan,
|
|
shutdown_chan: Chan<()>)
|
|
-> CompositorTask {
|
|
CompositorTask {
|
|
port: port,
|
|
profiler_chan: profiler_chan,
|
|
shutdown_chan: SharedChan::new(shutdown_chan),
|
|
}
|
|
}
|
|
|
|
/// Starts the compositor, which listens for messages on the specified port.
|
|
pub fn create(port: Port<Msg>,
|
|
profiler_chan: ProfilerChan,
|
|
shutdown_chan: Chan<()>) {
|
|
let port = Cell::new(port);
|
|
let shutdown_chan = Cell::new(shutdown_chan);
|
|
do on_osmain {
|
|
let compositor_task = CompositorTask::new(port.take(),
|
|
profiler_chan.clone(),
|
|
shutdown_chan.take());
|
|
debug!("preparing to enter main loop");
|
|
compositor_task.run_main_loop();
|
|
};
|
|
}
|
|
|
|
fn run_main_loop(&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 scene = @mut Scene(ContainerLayerKind(root_layer), Size2D(800.0f32, 600.0), identity());
|
|
let done = @mut false;
|
|
|
|
// FIXME: This should not be a separate offset applied after the fact but rather should be
|
|
// applied to the layers themselves on a per-layer basis. However, this won't work until scroll
|
|
// positions are sent to content.
|
|
let world_offset = @mut Point2D(0f32, 0f32);
|
|
let page_size = @mut Size2D(0f32, 0f32);
|
|
let window_size = @mut Size2D(800, 600);
|
|
|
|
// Keeps track of the current zoom factor
|
|
let world_zoom = @mut 1f32;
|
|
// Keeps track of local zoom factor. Reset to 1 after a rerender event.
|
|
let local_zoom = @mut 1f32;
|
|
// Channel to the current renderer.
|
|
// FIXME: This probably shouldn't be stored like this.
|
|
let render_chan: @mut Option<RenderChan> = @mut None;
|
|
|
|
let update_layout_callbacks: @fn(LayoutChan) = |layout_chan: LayoutChan| {
|
|
let layout_chan_clone = layout_chan.clone();
|
|
do window.set_navigation_callback |direction| {
|
|
let direction = match direction {
|
|
windowing::Forward => constellation::Forward,
|
|
windowing::Back => constellation::Back,
|
|
};
|
|
layout_chan_clone.send(RouteScriptMsg(NavigateMsg(direction)));
|
|
}
|
|
|
|
let layout_chan_clone = layout_chan.clone();
|
|
// Hook the windowing system's resize callback up to the resize rate limiter.
|
|
do window.set_resize_callback |width, height| {
|
|
let new_size = Size2D(width as int, height as int);
|
|
if *window_size != new_size {
|
|
debug!("osmain: window resized to %ux%u", width, height);
|
|
*window_size = new_size;
|
|
layout_chan_clone.send(RouteScriptMsg(SendEventMsg(ResizeEvent(width, height))));
|
|
} else {
|
|
debug!("osmain: dropping window resize since size is still %ux%u", width, height);
|
|
}
|
|
}
|
|
|
|
let layout_chan_clone = layout_chan.clone();
|
|
|
|
// When the user enters a new URL, load it.
|
|
do window.set_load_url_callback |url_string| {
|
|
debug!("osmain: loading URL `%s`", url_string);
|
|
layout_chan_clone.send(RouteScriptMsg(LoadMsg(url::make_url(url_string.to_str(), None))));
|
|
}
|
|
|
|
let layout_chan_clone = layout_chan.clone();
|
|
|
|
// When the user triggers a mouse event, perform appropriate hit testing
|
|
do window.set_mouse_callback |window_mouse_event: WindowMouseEvent| {
|
|
let event: Event;
|
|
let world_mouse_point = |layer_mouse_point: Point2D<f32>| {
|
|
layer_mouse_point + *world_offset
|
|
};
|
|
match window_mouse_event {
|
|
WindowClickEvent(button, layer_mouse_point) => {
|
|
event = ClickEvent(button, world_mouse_point(layer_mouse_point));
|
|
}
|
|
WindowMouseDownEvent(button, layer_mouse_point) => {
|
|
event = MouseDownEvent(button, world_mouse_point(layer_mouse_point));
|
|
}
|
|
WindowMouseUpEvent(button, layer_mouse_point) => {
|
|
|
|
// rerender layer at new zoom level
|
|
// FIXME: this should happen when the user stops zooming, definitely not here
|
|
match *render_chan {
|
|
Some(ref r_chan) => {
|
|
r_chan.send(ReRenderMsg(*world_zoom));
|
|
}
|
|
None => {} // Nothing to do
|
|
}
|
|
|
|
event = MouseUpEvent(button, world_mouse_point(layer_mouse_point));
|
|
}
|
|
}
|
|
layout_chan_clone.send(RouteScriptMsg(SendEventMsg(event)));
|
|
}
|
|
};
|
|
|
|
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),
|
|
|
|
SetLayoutChan(layout_chan) => {
|
|
update_layout_callbacks(layout_chan);
|
|
}
|
|
|
|
SetRenderChan(new_render_chan) => {
|
|
*render_chan = Some(new_render_chan);
|
|
}
|
|
|
|
GetGLContext(chan) => chan.send(current_gl_context()),
|
|
|
|
Paint(new_layer_buffer_set, new_size) => {
|
|
debug!("osmain: received new frame");
|
|
|
|
*page_size = Size2D(new_size.width as f32, new_size.height as f32);
|
|
|
|
let mut new_layer_buffer_set = new_layer_buffer_set;
|
|
|
|
// Iterate over the children of the container layer.
|
|
let mut current_layer_child = root_layer.first_child;
|
|
|
|
// Replace the image layer data with the buffer data. Also compute the page
|
|
// size here.
|
|
let buffers = util::replace(&mut new_layer_buffer_set.buffers, ~[]);
|
|
|
|
for buffers.each |buffer| {
|
|
let width = buffer.rect.size.width as uint;
|
|
let height = buffer.rect.size.height as uint;
|
|
|
|
debug!("osmain: compositing buffer rect %?", &buffer.rect);
|
|
|
|
// Find or create a texture layer.
|
|
let texture_layer;
|
|
current_layer_child = match current_layer_child {
|
|
None => {
|
|
debug!("osmain: adding new texture layer");
|
|
texture_layer = @mut TextureLayer::new(@buffer.draw_target.clone() as @TextureManager,
|
|
buffer.screen_pos.size);
|
|
root_layer.add_child(TextureLayerKind(texture_layer));
|
|
None
|
|
}
|
|
Some(TextureLayerKind(existing_texture_layer)) => {
|
|
texture_layer = existing_texture_layer;
|
|
texture_layer.manager = @buffer.draw_target.clone() as @TextureManager;
|
|
|
|
// Move on to the next sibling.
|
|
do current_layer_child.get().with_common |common| {
|
|
common.next_sibling
|
|
}
|
|
}
|
|
Some(_) => fail!(~"found unexpected layer kind"),
|
|
};
|
|
|
|
let origin = buffer.screen_pos.origin;
|
|
let origin = Point2D(origin.x as f32, origin.y as f32);
|
|
|
|
// Set the layer's transform.
|
|
let transform = identity().translate(origin.x, origin.y, 0.0);
|
|
let transform = transform.scale(width as f32, height as f32, 1.0);
|
|
texture_layer.common.set_transform(transform);
|
|
}
|
|
|
|
// Delete leftover layers
|
|
while current_layer_child.is_some() {
|
|
let trash = current_layer_child.get();
|
|
do current_layer_child.get().with_common |common| {
|
|
current_layer_child = common.next_sibling;
|
|
}
|
|
root_layer.remove_child(trash);
|
|
}
|
|
|
|
// Reset zoom
|
|
*local_zoom = 1f32;
|
|
root_layer.common.set_transform(identity().translate(-world_offset.x,
|
|
-world_offset.y,
|
|
0.0));
|
|
|
|
// TODO: Recycle the old buffers; send them back to the renderer to reuse if
|
|
// it wishes.
|
|
|
|
window.set_needs_display();
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
let profiler_chan = self.profiler_chan.clone();
|
|
do window.set_composite_callback {
|
|
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);
|
|
}
|
|
|
|
window.present();
|
|
}
|
|
|
|
// When the user scrolls, move the layer around.
|
|
do window.set_scroll_callback |delta| {
|
|
// FIXME (Rust #2528): Can't use `-=`.
|
|
let world_offset_copy = *world_offset;
|
|
*world_offset = world_offset_copy - delta;
|
|
|
|
// Clamp the world offset to the screen size.
|
|
let max_x = (page_size.width * *world_zoom - window_size.width as f32).max(&0.0);
|
|
world_offset.x = world_offset.x.clamp(&0.0, &max_x).round();
|
|
let max_y = (page_size.height * *world_zoom - window_size.height as f32).max(&0.0);
|
|
world_offset.y = world_offset.y.clamp(&0.0, &max_y).round();
|
|
|
|
debug!("compositor: scrolled to %?", *world_offset);
|
|
|
|
|
|
let mut scroll_transform = identity();
|
|
|
|
scroll_transform = scroll_transform.translate(window_size.width as f32 / 2f32 * *local_zoom - world_offset.x,
|
|
window_size.height as f32 / 2f32 * *local_zoom - world_offset.y,
|
|
0.0);
|
|
scroll_transform = scroll_transform.scale(*local_zoom, *local_zoom, 1f32);
|
|
scroll_transform = scroll_transform.translate(window_size.width as f32 / -2f32,
|
|
window_size.height as f32 / -2f32,
|
|
0.0);
|
|
|
|
root_layer.common.set_transform(scroll_transform);
|
|
|
|
window.set_needs_display()
|
|
}
|
|
|
|
|
|
|
|
// When the user pinch-zooms, scale the layer
|
|
do window.set_zoom_callback |magnification| {
|
|
let old_world_zoom = *world_zoom;
|
|
|
|
// Determine zoom amount
|
|
*world_zoom = (*world_zoom * magnification).max(&1.0);
|
|
*local_zoom = *local_zoom * *world_zoom/old_world_zoom;
|
|
|
|
// Update world offset
|
|
let corner_to_center_x = world_offset.x + window_size.width as f32 / 2f32;
|
|
let new_corner_to_center_x = corner_to_center_x * *world_zoom / old_world_zoom;
|
|
world_offset.x = world_offset.x + new_corner_to_center_x - corner_to_center_x;
|
|
|
|
let corner_to_center_y = world_offset.y + window_size.height as f32 / 2f32;
|
|
let new_corner_to_center_y = corner_to_center_y * *world_zoom / old_world_zoom;
|
|
world_offset.y = world_offset.y + new_corner_to_center_y - corner_to_center_y;
|
|
|
|
// Clamp to page bounds when zooming out
|
|
let max_x = (page_size.width * *world_zoom - window_size.width as f32).max(&0.0);
|
|
world_offset.x = world_offset.x.clamp(&0.0, &max_x).round();
|
|
let max_y = (page_size.height * *world_zoom - window_size.height as f32).max(&0.0);
|
|
world_offset.y = world_offset.y.clamp(&0.0, &max_y).round();
|
|
|
|
// Apply transformations
|
|
let mut zoom_transform = identity();
|
|
zoom_transform = zoom_transform.translate(window_size.width as f32 / 2f32 * *local_zoom - world_offset.x,
|
|
window_size.height as f32 / 2f32 * *local_zoom - world_offset.y,
|
|
0.0);
|
|
zoom_transform = zoom_transform.scale(*local_zoom, *local_zoom, 1f32);
|
|
zoom_transform = zoom_transform.translate(window_size.width as f32 / -2f32,
|
|
window_size.height as f32 / -2f32,
|
|
0.0);
|
|
root_layer.common.set_transform(zoom_transform);
|
|
|
|
|
|
window.set_needs_display()
|
|
}
|
|
|
|
// Enter the main event loop.
|
|
while !*done {
|
|
// Check for new messages coming from the rendering task.
|
|
check_for_messages(&self.port);
|
|
|
|
// Check for messages coming from the windowing system.
|
|
window.check_loop();
|
|
}
|
|
|
|
self.shutdown_chan.send(())
|
|
}
|
|
}
|
|
|
|
/// A function for spawning into the platform's main thread.
|
|
fn on_osmain(f: ~fn()) {
|
|
// FIXME: rust#6399
|
|
let mut main_task = task::task();
|
|
main_task.sched_mode(task::PlatformThread);
|
|
do main_task.spawn {
|
|
f();
|
|
}
|
|
}
|
|
|