aboutsummaryrefslogtreecommitdiffstats
path: root/components/gfx/render_task.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/gfx/render_task.rs')
-rw-r--r--components/gfx/render_task.rs443
1 files changed, 443 insertions, 0 deletions
diff --git a/components/gfx/render_task.rs b/components/gfx/render_task.rs
new file mode 100644
index 00000000000..2d7b8b5e2d7
--- /dev/null
+++ b/components/gfx/render_task.rs
@@ -0,0 +1,443 @@
+/* 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/. */
+
+//! The task that handles all rendering/painting.
+
+use buffer_map::BufferMap;
+use display_list::optimizer::DisplayListOptimizer;
+use display_list::DisplayList;
+use font_context::FontContext;
+use render_context::RenderContext;
+
+use azure::azure_hl::{B8G8R8A8, Color, DrawTarget, StolenGLResources};
+use azure::AzFloat;
+use geom::matrix2d::Matrix2D;
+use geom::rect::Rect;
+use geom::size::Size2D;
+use layers::platform::surface::{NativePaintingGraphicsContext, NativeSurface};
+use layers::platform::surface::{NativeSurfaceMethods};
+use layers::layers::{BufferRequest, LayerBuffer, LayerBufferSet};
+use layers;
+use servo_msg::compositor_msg::{Epoch, IdleRenderState, LayerId};
+use servo_msg::compositor_msg::{LayerMetadata, RenderListener, RenderingRenderState, ScrollPolicy};
+use servo_msg::constellation_msg::{ConstellationChan, Failure, FailureMsg, PipelineId};
+use servo_msg::constellation_msg::{RendererReadyMsg};
+use servo_msg::platform::surface::NativeSurfaceAzureMethods;
+use servo_util::geometry;
+use servo_util::opts::Opts;
+use servo_util::smallvec::{SmallVec, SmallVec1};
+use servo_util::task::spawn_named_with_send_on_failure;
+use servo_util::time::{TimeProfilerChan, profile};
+use servo_util::time;
+use std::comm::{Receiver, Sender, channel};
+use sync::Arc;
+use font_cache_task::FontCacheTask;
+
+/// Information about a layer that layout sends to the painting task.
+pub struct RenderLayer {
+ /// A per-pipeline ID describing this layer that should be stable across reflows.
+ pub id: LayerId,
+ /// The display list describing the contents of this layer.
+ pub display_list: Arc<DisplayList>,
+ /// The position of the layer in pixels.
+ pub position: Rect<uint>,
+ /// The color of the background in this layer. Used for unrendered content.
+ pub background_color: Color,
+ /// The scrolling policy of this layer.
+ pub scroll_policy: ScrollPolicy,
+}
+
+pub struct RenderRequest {
+ pub buffer_requests: Vec<BufferRequest>,
+ pub scale: f32,
+ pub layer_id: LayerId,
+ pub epoch: Epoch,
+}
+
+pub enum Msg {
+ RenderInitMsg(SmallVec1<RenderLayer>),
+ RenderMsg(Vec<RenderRequest>),
+ UnusedBufferMsg(Vec<Box<LayerBuffer>>),
+ PaintPermissionGranted,
+ PaintPermissionRevoked,
+ ExitMsg(Option<Sender<()>>),
+}
+
+#[deriving(Clone)]
+pub struct RenderChan(Sender<Msg>);
+
+impl RenderChan {
+ pub fn new() -> (Receiver<Msg>, RenderChan) {
+ let (chan, port) = channel();
+ (port, RenderChan(chan))
+ }
+
+ pub fn send(&self, msg: Msg) {
+ let &RenderChan(ref chan) = self;
+ assert!(chan.send_opt(msg).is_ok(), "RenderChan.send: render port closed")
+ }
+
+ pub fn send_opt(&self, msg: Msg) -> Result<(), Msg> {
+ let &RenderChan(ref chan) = self;
+ chan.send_opt(msg)
+ }
+}
+
+/// If we're using GPU rendering, this provides the metadata needed to create a GL context that
+/// is compatible with that of the main thread.
+pub enum GraphicsContext {
+ CpuGraphicsContext,
+ GpuGraphicsContext,
+}
+
+pub struct RenderTask<C> {
+ id: PipelineId,
+ port: Receiver<Msg>,
+ compositor: C,
+ constellation_chan: ConstellationChan,
+ font_ctx: Box<FontContext>,
+ opts: Opts,
+
+ /// A channel to the time profiler.
+ time_profiler_chan: TimeProfilerChan,
+
+ /// The graphics context to use.
+ graphics_context: GraphicsContext,
+
+ /// The native graphics context.
+ native_graphics_context: Option<NativePaintingGraphicsContext>,
+
+ /// The layers to be rendered.
+ render_layers: SmallVec1<RenderLayer>,
+
+ /// Permission to send paint messages to the compositor
+ paint_permission: bool,
+
+ /// A counter for epoch messages
+ epoch: Epoch,
+
+ /// A data structure to store unused LayerBuffers
+ buffer_map: BufferMap,
+}
+
+// If we implement this as a function, we get borrowck errors from borrowing
+// the whole RenderTask struct.
+macro_rules! native_graphics_context(
+ ($task:expr) => (
+ $task.native_graphics_context.as_ref().expect("Need a graphics context to do rendering")
+ )
+)
+
+fn initialize_layers<C:RenderListener>(
+ compositor: &mut C,
+ pipeline_id: PipelineId,
+ epoch: Epoch,
+ render_layers: &[RenderLayer]) {
+ let metadata = render_layers.iter().map(|render_layer| {
+ LayerMetadata {
+ id: render_layer.id,
+ position: render_layer.position,
+ background_color: render_layer.background_color,
+ scroll_policy: render_layer.scroll_policy,
+ }
+ }).collect();
+ compositor.initialize_layers_for_pipeline(pipeline_id, metadata, epoch);
+}
+
+impl<C:RenderListener + Send> RenderTask<C> {
+ pub fn create(id: PipelineId,
+ port: Receiver<Msg>,
+ compositor: C,
+ constellation_chan: ConstellationChan,
+ font_cache_task: FontCacheTask,
+ failure_msg: Failure,
+ opts: Opts,
+ time_profiler_chan: TimeProfilerChan,
+ shutdown_chan: Sender<()>) {
+
+ let ConstellationChan(c) = constellation_chan.clone();
+ let fc = font_cache_task.clone();
+
+ spawn_named_with_send_on_failure("RenderTask", proc() {
+ { // Ensures RenderTask and graphics context are destroyed before shutdown msg
+ let native_graphics_context = compositor.get_graphics_metadata().map(
+ |md| NativePaintingGraphicsContext::from_metadata(&md));
+ let cpu_painting = opts.cpu_painting;
+
+ // FIXME: rust/#5967
+ let mut render_task = RenderTask {
+ id: id,
+ port: port,
+ compositor: compositor,
+ constellation_chan: constellation_chan,
+ font_ctx: box FontContext::new(fc.clone()),
+ opts: opts,
+ time_profiler_chan: time_profiler_chan,
+
+ graphics_context: if cpu_painting {
+ CpuGraphicsContext
+ } else {
+ GpuGraphicsContext
+ },
+
+ native_graphics_context: native_graphics_context,
+
+ render_layers: SmallVec1::new(),
+
+ paint_permission: false,
+ epoch: Epoch(0),
+ buffer_map: BufferMap::new(10000000),
+ };
+
+ render_task.start();
+
+ // Destroy all the buffers.
+ match render_task.native_graphics_context.as_ref() {
+ Some(ctx) => render_task.buffer_map.clear(ctx),
+ None => (),
+ }
+ }
+
+ debug!("render_task: shutdown_chan send");
+ shutdown_chan.send(());
+ }, FailureMsg(failure_msg), c, true);
+ }
+
+ fn start(&mut self) {
+ debug!("render_task: beginning rendering loop");
+
+ loop {
+ match self.port.recv() {
+ RenderInitMsg(render_layers) => {
+ self.epoch.next();
+ self.render_layers = render_layers;
+
+ if !self.paint_permission {
+ debug!("render_task: render ready msg");
+ let ConstellationChan(ref mut c) = self.constellation_chan;
+ c.send(RendererReadyMsg(self.id));
+ continue;
+ }
+
+ initialize_layers(&mut self.compositor,
+ self.id,
+ self.epoch,
+ self.render_layers.as_slice());
+ }
+ RenderMsg(requests) => {
+ if !self.paint_permission {
+ debug!("render_task: render ready msg");
+ let ConstellationChan(ref mut c) = self.constellation_chan;
+ c.send(RendererReadyMsg(self.id));
+ self.compositor.render_msg_discarded();
+ continue;
+ }
+
+ self.compositor.set_render_state(RenderingRenderState);
+
+ let mut replies = Vec::new();
+ for RenderRequest { buffer_requests, scale, layer_id, epoch }
+ in requests.move_iter() {
+ if self.epoch == epoch {
+ self.render(&mut replies, buffer_requests, scale, layer_id);
+ } else {
+ debug!("renderer epoch mismatch: {:?} != {:?}", self.epoch, epoch);
+ }
+ }
+
+ self.compositor.set_render_state(IdleRenderState);
+
+ debug!("render_task: returning surfaces");
+ self.compositor.paint(self.id, self.epoch, replies);
+ }
+ UnusedBufferMsg(unused_buffers) => {
+ for buffer in unused_buffers.move_iter().rev() {
+ self.buffer_map.insert(native_graphics_context!(self), buffer);
+ }
+ }
+ PaintPermissionGranted => {
+ self.paint_permission = true;
+
+ // Here we assume that the main layer—the layer responsible for the page size—
+ // is the first layer. This is a pretty fragile assumption. It will be fixed
+ // once we use the layers-based scrolling infrastructure for all scrolling.
+ if self.render_layers.len() > 1 {
+ self.epoch.next();
+ initialize_layers(&mut self.compositor,
+ self.id,
+ self.epoch,
+ self.render_layers.as_slice());
+ }
+ }
+ PaintPermissionRevoked => {
+ self.paint_permission = false;
+ }
+ ExitMsg(response_ch) => {
+ debug!("render_task: exitmsg response send");
+ response_ch.map(|ch| ch.send(()));
+ break;
+ }
+ }
+ }
+ }
+
+ /// Renders one layer and sends the tiles back to the layer.
+ fn render(&mut self,
+ replies: &mut Vec<(LayerId, Box<LayerBufferSet>)>,
+ tiles: Vec<BufferRequest>,
+ scale: f32,
+ layer_id: LayerId) {
+ time::profile(time::RenderingCategory, self.time_profiler_chan.clone(), || {
+ // FIXME: Try not to create a new array here.
+ let mut new_buffers = vec!();
+
+ // Find the appropriate render layer.
+ let render_layer = match self.render_layers.iter().find(|layer| layer.id == layer_id) {
+ Some(render_layer) => render_layer,
+ None => return,
+ };
+
+ // Divide up the layer into tiles.
+ for tile in tiles.iter() {
+ // Optimize the display list for this tile.
+ let page_rect_au = geometry::f32_rect_to_au_rect(tile.page_rect);
+ let optimizer = DisplayListOptimizer::new(render_layer.display_list.clone(),
+ page_rect_au);
+ let display_list = optimizer.optimize();
+
+ let width = tile.screen_rect.size.width;
+ let height = tile.screen_rect.size.height;
+
+ let size = Size2D(width as i32, height as i32);
+ let draw_target = match self.graphics_context {
+ CpuGraphicsContext => {
+ DrawTarget::new(self.opts.render_backend, size, B8G8R8A8)
+ }
+ GpuGraphicsContext => {
+ // FIXME(pcwalton): Cache the components of draw targets
+ // (texture color buffer, renderbuffers) instead of recreating them.
+ let draw_target =
+ DrawTarget::new_with_fbo(self.opts.render_backend,
+ native_graphics_context!(self),
+ size,
+ B8G8R8A8);
+ draw_target.make_current();
+ draw_target
+ }
+ };
+
+ {
+ // Build the render context.
+ let mut ctx = RenderContext {
+ draw_target: &draw_target,
+ font_ctx: &mut self.font_ctx,
+ opts: &self.opts,
+ page_rect: tile.page_rect,
+ screen_rect: tile.screen_rect,
+ };
+
+ // Apply the translation to render the tile we want.
+ let matrix: Matrix2D<AzFloat> = Matrix2D::identity();
+ let matrix = matrix.scale(scale as AzFloat, scale as AzFloat);
+ let matrix = matrix.translate(-(tile.page_rect.origin.x) as AzFloat,
+ -(tile.page_rect.origin.y) as AzFloat);
+ let matrix = matrix.translate(-(render_layer.position.origin.x as AzFloat),
+ -(render_layer.position.origin.y as AzFloat));
+
+ ctx.draw_target.set_transform(&matrix);
+
+ // Clear the buffer.
+ ctx.clear();
+
+ // Draw the display list.
+ profile(time::RenderingDrawingCategory, self.time_profiler_chan.clone(), || {
+ display_list.draw_into_context(&mut ctx, &matrix);
+ ctx.draw_target.flush();
+ });
+ }
+
+ // Extract the texture from the draw target and place it into its slot in the
+ // buffer. If using CPU rendering, upload it first.
+ //
+ // FIXME(pcwalton): We should supply the texture and native surface *to* the
+ // draw target in GPU rendering mode, so that it doesn't have to recreate it.
+ let buffer = match self.graphics_context {
+ CpuGraphicsContext => {
+ let mut buffer = match self.buffer_map.find(tile.screen_rect.size) {
+ Some(buffer) => {
+ let mut buffer = buffer;
+ buffer.rect = tile.page_rect;
+ buffer.screen_pos = tile.screen_rect;
+ buffer.resolution = scale;
+ buffer.native_surface.mark_wont_leak();
+ buffer.painted_with_cpu = true;
+ buffer.content_age = tile.content_age;
+ buffer
+ }
+ None => {
+ // Create an empty native surface. We mark it as not leaking
+ // in case it dies in transit to the compositor task.
+ let mut native_surface: NativeSurface =
+ layers::platform::surface::NativeSurfaceMethods::new(
+ native_graphics_context!(self),
+ Size2D(width as i32, height as i32),
+ width as i32 * 4);
+ native_surface.mark_wont_leak();
+
+ box LayerBuffer {
+ native_surface: native_surface,
+ rect: tile.page_rect,
+ screen_pos: tile.screen_rect,
+ resolution: scale,
+ stride: (width * 4) as uint,
+ painted_with_cpu: true,
+ content_age: tile.content_age,
+ }
+ }
+ };
+
+ draw_target.snapshot().get_data_surface().with_data(|data| {
+ buffer.native_surface.upload(native_graphics_context!(self), data);
+ debug!("RENDERER uploading to native surface {:d}",
+ buffer.native_surface.get_id() as int);
+ });
+
+ buffer
+ }
+ GpuGraphicsContext => {
+ draw_target.make_current();
+ let StolenGLResources {
+ surface: native_surface
+ } = draw_target.steal_gl_resources().unwrap();
+
+ // We mark the native surface as not leaking in case the surfaces
+ // die on their way to the compositor task.
+ let mut native_surface: NativeSurface =
+ NativeSurfaceAzureMethods::from_azure_surface(native_surface);
+ native_surface.mark_wont_leak();
+
+ box LayerBuffer {
+ native_surface: native_surface,
+ rect: tile.page_rect,
+ screen_pos: tile.screen_rect,
+ resolution: scale,
+ stride: (width * 4) as uint,
+ painted_with_cpu: false,
+ content_age: tile.content_age,
+ }
+ }
+ };
+
+ new_buffers.push(buffer);
+ }
+
+ let layer_buffer_set = box LayerBufferSet {
+ buffers: new_buffers,
+ };
+
+ replies.push((render_layer.id, layer_buffer_set));
+ })
+ }
+}
+