Bug 1531217 - Document origin rewrite and framebuffer coordinates r=gw,nical
☠☠ backed out by a066481987fb ☠ ☠
authorDzmitry Malyshau <dmalyshau@mozilla.com>
Fri, 01 Mar 2019 14:25:36 +0000
changeset 461952 93f7dc3084a1350e5c2c21d599ec6634ebe0ec8f
parent 461951 7f3e90c4b9c2b308afaaf0b40c3ea2d2e8fcc947
child 461953 7b3e30ae92a935dd09df08cf7d862f67472cd5c3
push id35634
push userrmaries@mozilla.com
push dateSat, 02 Mar 2019 09:26:10 +0000
treeherdermozilla-central@4166cae81546 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgw, nical
bugs1531217
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1531217 - Document origin rewrite and framebuffer coordinates r=gw,nical The goal of this change was to simplify the semantics of our document placement and split the logical elements inside (display list) from the actual screen rectangle occupied by a document. To achieve that, we introduce the framebuffer space for things Y-flipped on screen. We fix the frame outputs, so that they get produced on the first frame without loopback from the frame building to scene building. Differential Revision: https://phabricator.services.mozilla.com/D21641
gfx/layers/wr/WebRenderBridgeParent.cpp
gfx/webrender_bindings/WebRenderAPI.cpp
gfx/webrender_bindings/WebRenderAPI.h
gfx/webrender_bindings/src/bindings.rs
gfx/wr/examples/alpha_perf.rs
gfx/wr/examples/animation.rs
gfx/wr/examples/basic.rs
gfx/wr/examples/blob.rs
gfx/wr/examples/common/boilerplate.rs
gfx/wr/examples/document.rs
gfx/wr/examples/frame_output.rs
gfx/wr/examples/iframe.rs
gfx/wr/examples/image_resize.rs
gfx/wr/examples/multiwindow.rs
gfx/wr/examples/scrolling.rs
gfx/wr/examples/texture_cache_stress.rs
gfx/wr/examples/yuv.rs
gfx/wr/webrender/src/debug_render.rs
gfx/wr/webrender/src/device/gl.rs
gfx/wr/webrender/src/display_list_flattener.rs
gfx/wr/webrender/src/frame_builder.rs
gfx/wr/webrender/src/render_backend.rs
gfx/wr/webrender/src/renderer.rs
gfx/wr/webrender/src/tiling.rs
gfx/wr/webrender_api/src/api.rs
gfx/wr/webrender_api/src/lib.rs
gfx/wr/webrender_api/src/units.rs
gfx/wr/wrench/src/main.rs
gfx/wr/wrench/src/png.rs
gfx/wr/wrench/src/rawtest.rs
gfx/wr/wrench/src/reftest.rs
gfx/wr/wrench/src/wrench.rs
--- a/gfx/layers/wr/WebRenderBridgeParent.cpp
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -930,17 +930,17 @@ mozilla::ipc::IPCResult WebRenderBridgeP
   wr::Vec<uint8_t> dlData(std::move(dl));
 
   bool observeLayersUpdate = ShouldParentObserveEpoch();
 
   if (validTransaction) {
     if (IsRootWebRenderBridgeParent()) {
       LayoutDeviceIntSize widgetSize = mWidget->GetClientSize();
       LayoutDeviceIntRect docRect(LayoutDeviceIntPoint(), widgetSize);
-      txn.SetWindowParameters(widgetSize, docRect);
+      txn.SetDocumentView(docRect);
     }
     gfx::Color clearColor(0.f, 0.f, 0.f, 0.f);
     txn.SetDisplayList(clearColor, wrEpoch,
                        LayerSize(aSize.width, aSize.height), mPipelineId,
                        aContentSize, dlDesc, dlData);
 
     if (observeLayersUpdate) {
       txn.Notify(wr::Checkpoint::SceneBuilt,
--- a/gfx/webrender_bindings/WebRenderAPI.cpp
+++ b/gfx/webrender_bindings/WebRenderAPI.cpp
@@ -194,28 +194,24 @@ void TransactionBuilder::UpdateDynamicPr
 bool TransactionBuilder::IsEmpty() const {
   return wr_transaction_is_empty(mTxn);
 }
 
 bool TransactionBuilder::IsResourceUpdatesEmpty() const {
   return wr_transaction_resource_updates_is_empty(mTxn);
 }
 
-void TransactionBuilder::SetWindowParameters(
-    const LayoutDeviceIntSize& aWindowSize,
+void TransactionBuilder::SetDocumentView(
     const LayoutDeviceIntRect& aDocumentRect) {
-  wr::DeviceIntSize wrWindowSize;
-  wrWindowSize.width = aWindowSize.width;
-  wrWindowSize.height = aWindowSize.height;
-  wr::DeviceIntRect wrDocRect;
+  wr::FramebufferIntRect wrDocRect;
   wrDocRect.origin.x = aDocumentRect.x;
   wrDocRect.origin.y = aDocumentRect.y;
   wrDocRect.size.width = aDocumentRect.width;
   wrDocRect.size.height = aDocumentRect.height;
-  wr_transaction_set_window_parameters(mTxn, &wrWindowSize, &wrDocRect);
+  wr_transaction_set_document_view(mTxn, &wrDocRect);
 }
 
 void TransactionBuilder::UpdateScrollPosition(
     const wr::WrPipelineId& aPipelineId,
     const layers::ScrollableLayerGuid::ViewID& aScrollId,
     const wr::LayoutPoint& aScrollPosition) {
   wr_transaction_scroll_layer(mTxn, aPipelineId, aScrollId, aScrollPosition);
 }
@@ -288,17 +284,17 @@ already_AddRefed<WebRenderAPI> WebRender
                        mUseTripleBuffering, mSyncHandle);
   renderApi->mRootApi = this;  // Hold root api
   renderApi->mRootDocumentApi = this;
   return renderApi.forget();
 }
 
 already_AddRefed<WebRenderAPI> WebRenderAPI::CreateDocument(
     LayoutDeviceIntSize aSize, int8_t aLayerIndex) {
-  wr::DeviceIntSize wrSize;
+  wr::FramebufferIntSize wrSize;
   wrSize.width = aSize.width;
   wrSize.height = aSize.height;
   wr::DocumentHandle* newDoc;
 
   wr_api_create_document(mDocHandle, &newDoc, wrSize, aLayerIndex);
 
   RefPtr<WebRenderAPI> api(new WebRenderAPI(newDoc, mId, mMaxTextureSize,
                                             mUseANGLE, mUseDComp,
--- a/gfx/webrender_bindings/WebRenderAPI.h
+++ b/gfx/webrender_bindings/WebRenderAPI.h
@@ -98,18 +98,17 @@ class TransactionBuilder {
   void GenerateFrame();
 
   void InvalidateRenderedFrame();
 
   void UpdateDynamicProperties(
       const nsTArray<wr::WrOpacityProperty>& aOpacityArray,
       const nsTArray<wr::WrTransformProperty>& aTransformArray);
 
-  void SetWindowParameters(const LayoutDeviceIntSize& aWindowSize,
-                           const LayoutDeviceIntRect& aDocRect);
+  void SetDocumentView(const LayoutDeviceIntRect& aDocRect);
 
   void UpdateScrollPosition(
       const wr::WrPipelineId& aPipelineId,
       const layers::ScrollableLayerGuid::ViewID& aScrollId,
       const wr::LayoutPoint& aScrollPosition);
 
   bool IsEmpty() const;
 
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -202,17 +202,17 @@ fn make_slice_mut<'a, T>(ptr: *mut T, le
 }
 
 pub struct DocumentHandle {
     api: RenderApi,
     document_id: DocumentId,
 }
 
 impl DocumentHandle {
-    pub fn new(api: RenderApi, size: DeviceIntSize, layer: i8) -> DocumentHandle {
+    pub fn new(api: RenderApi, size: FramebufferIntSize, layer: i8) -> DocumentHandle {
         let doc = api.add_document(size, layer);
         DocumentHandle {
             api: api,
             document_id: doc
         }
     }
 }
 
@@ -671,17 +671,17 @@ pub extern "C" fn wr_renderer_update(ren
 pub extern "C" fn wr_renderer_render(renderer: &mut Renderer,
                                      width: i32,
                                      height: i32,
                                      had_slow_frame: bool,
                                      out_stats: &mut RendererStats) -> bool {
     if had_slow_frame {
       renderer.notify_slow_frame();
     }
-    match renderer.render(DeviceIntSize::new(width, height)) {
+    match renderer.render(FramebufferIntSize::new(width, height)) {
         Ok(results) => {
             *out_stats = results.stats;
             true
         }
         Err(errors) => {
             for e in errors {
                 warn!(" Failed to render: {:?}", e);
                 let msg = CString::new(format!("wr_renderer_render: {:?}", e)).unwrap();
@@ -699,19 +699,17 @@ pub extern "C" fn wr_renderer_render(ren
 pub unsafe extern "C" fn wr_renderer_readback(renderer: &mut Renderer,
                                               width: i32,
                                               height: i32,
                                               dst_buffer: *mut u8,
                                               buffer_size: usize) {
     assert!(is_in_render_thread());
 
     let mut slice = make_slice_mut(dst_buffer, buffer_size);
-    renderer.read_pixels_into(DeviceIntRect::new(
-                                DeviceIntPoint::new(0, 0),
-                                DeviceIntSize::new(width, height)),
+    renderer.read_pixels_into(FramebufferIntSize::new(width, height).into(),
                               ReadPixelsFormat::Standard(ImageFormat::BGRA8),
                               &mut slice);
 }
 
 #[no_mangle]
 pub extern "C" fn wr_renderer_current_epoch(renderer: &mut Renderer,
                                             pipeline_id: WrPipelineId,
                                             out_epoch: &mut WrEpoch)
@@ -1174,30 +1172,30 @@ pub extern "C" fn wr_window_new(window_i
             }
             return false;
         },
     };
 
     unsafe {
         *out_max_texture_size = renderer.get_max_texture_size();
     }
-    let window_size = DeviceIntSize::new(window_width, window_height);
+    let window_size = FramebufferIntSize::new(window_width, window_height);
     let layer = 0;
     *out_handle = Box::into_raw(Box::new(
             DocumentHandle::new(sender.create_api_by_client(next_namespace_id()), window_size, layer)));
     *out_renderer = Box::into_raw(Box::new(renderer));
 
     return true;
 }
 
 #[no_mangle]
 pub extern "C" fn wr_api_create_document(
     root_dh: &mut DocumentHandle,
     out_handle: &mut *mut DocumentHandle,
-    doc_size: DeviceIntSize,
+    doc_size: FramebufferIntSize,
     layer: i8,
 ) {
     assert!(unsafe { is_in_compositor_thread() });
 
     *out_handle = Box::into_raw(Box::new(DocumentHandle::new(
         root_dh.api.clone_sender().create_api_by_client(next_namespace_id()),
         doc_size,
         layer
@@ -1374,23 +1372,21 @@ pub extern "C" fn wr_transaction_set_dis
         color,
         LayoutSize::new(viewport_width, viewport_height),
         (pipeline_id, content_size, dl),
         preserve_frame_state,
     );
 }
 
 #[no_mangle]
-pub extern "C" fn wr_transaction_set_window_parameters(
+pub extern "C" fn wr_transaction_set_document_view(
     txn: &mut Transaction,
-    window_size: &DeviceIntSize,
-    doc_rect: &DeviceIntRect,
+    doc_rect: &FramebufferIntRect,
 ) {
-    txn.set_window_parameters(
-        *window_size,
+    txn.set_document_view(
         *doc_rect,
         1.0,
     );
 }
 
 #[no_mangle]
 pub extern "C" fn wr_transaction_generate_frame(txn: &mut Transaction) {
     txn.generate_frame();
--- a/gfx/wr/examples/alpha_perf.rs
+++ b/gfx/wr/examples/alpha_perf.rs
@@ -20,17 +20,17 @@ struct App {
 }
 
 impl Example for App {
     fn render(
         &mut self,
         _api: &RenderApi,
         builder: &mut DisplayListBuilder,
         _txn: &mut Transaction,
-        _framebuffer_size: DeviceIntSize,
+        _framebuffer_size: FramebufferIntSize,
         pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let bounds = (0, 0).to(1920, 1080);
         let info = LayoutPrimitiveInfo::new(bounds);
         let space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
 
         builder.push_simple_stacking_context(
--- a/gfx/wr/examples/animation.rs
+++ b/gfx/wr/examples/animation.rs
@@ -101,17 +101,17 @@ impl Example for App {
     const WIDTH: u32 = 2048;
     const HEIGHT: u32 = 1536;
 
     fn render(
         &mut self,
         _api: &RenderApi,
         builder: &mut DisplayListBuilder,
         _txn: &mut Transaction,
-        _framebuffer_size: DeviceIntSize,
+        _framebuffer_size: FramebufferIntSize,
         pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let opacity_key = self.opacity_key;
 
         let bounds = (150, 150).to(250, 250);
         let key0 = self.property_key0;
         self.add_rounded_rect(bounds, ColorF::new(1.0, 0.0, 0.0, 0.5), builder, pipeline_id, key0, Some(opacity_key));
--- a/gfx/wr/examples/basic.rs
+++ b/gfx/wr/examples/basic.rs
@@ -180,17 +180,17 @@ impl Example for App {
     // Make this the only example to test all shaders for compile errors.
     const PRECACHE_SHADER_FLAGS: ShaderPrecacheFlags = ShaderPrecacheFlags::FULL_COMPILE;
 
     fn render(
         &mut self,
         api: &RenderApi,
         builder: &mut DisplayListBuilder,
         txn: &mut Transaction,
-        _: DeviceIntSize,
+        _: FramebufferIntSize,
         pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let bounds = LayoutRect::new(LayoutPoint::zero(), builder.content_size());
         let info = LayoutPrimitiveInfo::new(bounds);
         let root_space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
         let spatial_id = root_space_and_clip.spatial_id;
 
--- a/gfx/wr/examples/blob.rs
+++ b/gfx/wr/examples/blob.rs
@@ -194,17 +194,17 @@ impl api::AsyncBlobImageRasterizer for R
 struct App {}
 
 impl Example for App {
     fn render(
         &mut self,
         api: &RenderApi,
         builder: &mut DisplayListBuilder,
         txn: &mut Transaction,
-        _framebuffer_size: api::DeviceIntSize,
+        _framebuffer_size: api::FramebufferIntSize,
         pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let blob_img1 = api.generate_blob_image_key();
         txn.add_blob_image(
             blob_img1,
             api::ImageDescriptor::new(500, 500, api::ImageFormat::BGRA8, true, false),
             serialize_blob(api::ColorU::new(50, 50, 150, 255)),
--- a/gfx/wr/examples/common/boilerplate.rs
+++ b/gfx/wr/examples/common/boilerplate.rs
@@ -74,17 +74,17 @@ pub trait Example {
     const WIDTH: u32 = 1920;
     const HEIGHT: u32 = 1080;
 
     fn render(
         &mut self,
         api: &RenderApi,
         builder: &mut DisplayListBuilder,
         txn: &mut Transaction,
-        framebuffer_size: DeviceIntSize,
+        framebuffer_size: FramebufferIntSize,
         pipeline_id: PipelineId,
         document_id: DocumentId,
     );
     fn on_event(
         &mut self,
         winit::WindowEvent,
         &RenderApi,
         DocumentId,
@@ -159,17 +159,17 @@ pub fn main_wrapper<E: Example>(
         ..options.unwrap_or(webrender::RendererOptions::default())
     };
 
     let framebuffer_size = {
         let size = window
             .get_inner_size()
             .unwrap()
             .to_physical(device_pixel_ratio as f64);
-        DeviceIntSize::new(size.width as i32, size.height as i32)
+        FramebufferIntSize::new(size.width as i32, size.height as i32)
     };
     let notifier = Box::new(Notifier::new(events_loop.create_proxy()));
     let (mut renderer, sender) = webrender::Renderer::new(gl.clone(), notifier, opts, None).unwrap();
     let api = sender.create_api();
     let document_id = api.add_document(framebuffer_size, 0);
 
     let (external, output) = example.get_image_handlers(&*gl);
 
@@ -207,28 +207,29 @@ pub fn main_wrapper<E: Example>(
     api.send_transaction(document_id, txn);
 
     println!("Entering event loop");
     events_loop.run_forever(|global_event| {
         let mut txn = Transaction::new();
         let mut custom_event = true;
 
         let old_flags = debug_flags;
-        match global_event {
-            winit::Event::WindowEvent {
-                event: winit::WindowEvent::CloseRequested,
-                ..
-            } => return winit::ControlFlow::Break,
-            winit::Event::WindowEvent {
-                event: winit::WindowEvent::KeyboardInput {
-                    input: winit::KeyboardInput {
-                        state: winit::ElementState::Pressed,
-                        virtual_keycode: Some(key),
-                        ..
-                    },
+        let win_event = match global_event {
+            winit::Event::WindowEvent { event, .. } => event,
+            _ => return winit::ControlFlow::Continue,
+        };
+        match win_event {
+            winit::WindowEvent::CloseRequested => return winit::ControlFlow::Break,
+            // skip high-frequency events
+            winit::WindowEvent::AxisMotion { .. } |
+            winit::WindowEvent::CursorMoved { .. } => return winit::ControlFlow::Continue,
+            winit::WindowEvent::KeyboardInput {
+                input: winit::KeyboardInput {
+                    state: winit::ElementState::Pressed,
+                    virtual_keycode: Some(key),
                     ..
                 },
                 ..
             } => match key {
                 winit::VirtualKeyCode::Escape => return winit::ControlFlow::Break,
                 winit::VirtualKeyCode::P => debug_flags.toggle(DebugFlags::PROFILER_DBG),
                 winit::VirtualKeyCode::O => debug_flags.toggle(DebugFlags::RENDER_TARGET_DBG),
                 winit::VirtualKeyCode::I => debug_flags.toggle(DebugFlags::TEXTURE_CACHE_DBG),
@@ -236,52 +237,45 @@ pub fn main_wrapper<E: Example>(
                 winit::VirtualKeyCode::T => debug_flags.toggle(DebugFlags::PICTURE_CACHING_DBG),
                 winit::VirtualKeyCode::Q => debug_flags.toggle(
                     DebugFlags::GPU_TIME_QUERIES | DebugFlags::GPU_SAMPLE_QUERIES
                 ),
                 winit::VirtualKeyCode::F => debug_flags.toggle(
                     DebugFlags::NEW_FRAME_INDICATOR | DebugFlags::NEW_SCENE_INDICATOR
                 ),
                 winit::VirtualKeyCode::G => debug_flags.toggle(DebugFlags::GPU_CACHE_DBG),
-                winit::VirtualKeyCode::Key1 => txn.set_window_parameters(
-                    framebuffer_size,
-                    DeviceIntRect::new(DeviceIntPoint::zero(), framebuffer_size),
+                winit::VirtualKeyCode::Key1 => txn.set_document_view(
+                    framebuffer_size.into(),
                     1.0
                 ),
-                winit::VirtualKeyCode::Key2 => txn.set_window_parameters(
-                    framebuffer_size,
-                    DeviceIntRect::new(DeviceIntPoint::zero(), framebuffer_size),
+                winit::VirtualKeyCode::Key2 => txn.set_document_view(
+                    framebuffer_size.into(),
                     2.0
                 ),
                 winit::VirtualKeyCode::M => api.notify_memory_pressure(),
                 winit::VirtualKeyCode::C => {
                     let path: PathBuf = "../captures/example".into();
                     //TODO: switch between SCENE/FRAME capture types
                     // based on "shift" modifier, when `glutin` is updated.
                     let bits = CaptureBits::all();
                     api.save_capture(path, bits);
                 },
                 _ => {
-                    let win_event = match global_event {
-                        winit::Event::WindowEvent { event, .. } => event,
-                        _ => unreachable!()
-                    };
                     custom_event = example.on_event(
                         win_event,
                         &api,
                         document_id,
                     )
                 },
             },
-            winit::Event::WindowEvent { event, .. } => custom_event = example.on_event(
-                event,
+            other => custom_event = example.on_event(
+                other,
                 &api,
                 document_id,
             ),
-            _ => return winit::ControlFlow::Continue,
         };
 
         if debug_flags != old_flags {
             api.send_debug_cmd(DebugCommand::SetFlags(debug_flags));
         }
 
         if custom_event {
             let mut builder = DisplayListBuilder::new(pipeline_id, layout_size);
--- a/gfx/wr/examples/document.rs
+++ b/gfx/wr/examples/document.rs
@@ -28,82 +28,84 @@ struct Document {
 struct App {
     documents: Vec<Document>,
 }
 
 impl App {
     fn init(
         &mut self,
         api: &RenderApi,
-        framebuffer_size: DeviceIntSize,
         device_pixel_ratio: f32,
     ) {
         let init_data = vec![
             (
                 PipelineId(1, 0),
-                -1,
+                1,
                 ColorF::new(0.0, 1.0, 0.0, 1.0),
-                DeviceIntPoint::new(0, 0),
+                FramebufferIntPoint::new(0, 400),
             ),
             (
                 PipelineId(2, 0),
-                -2,
+                2,
                 ColorF::new(1.0, 1.0, 0.0, 1.0),
-                DeviceIntPoint::new(200, 0),
+                FramebufferIntPoint::new(200, 400),
             ),
             (
                 PipelineId(3, 0),
-                -3,
+                3,
                 ColorF::new(1.0, 0.0, 0.0, 1.0),
-                DeviceIntPoint::new(200, 200),
+                FramebufferIntPoint::new(200, 600),
             ),
             (
                 PipelineId(4, 0),
-                -4,
+                4,
                 ColorF::new(1.0, 0.0, 1.0, 1.0),
-                DeviceIntPoint::new(0, 200),
+                FramebufferIntPoint::new(0, 600),
             ),
         ];
 
         for (pipeline_id, layer, color, offset) in init_data {
-            let size = DeviceIntSize::new(250, 250);
-            let bounds = DeviceIntRect::new(offset, size);
+            let size = FramebufferIntSize::new(250, 250);
+            let bounds = FramebufferIntRect::new(offset, size);
 
             let document_id = api.add_document(size, layer);
             let mut txn = Transaction::new();
-            txn.set_window_parameters(framebuffer_size, bounds, device_pixel_ratio);
+            txn.set_document_view(bounds, device_pixel_ratio);
             txn.set_root_pipeline(pipeline_id);
             api.send_transaction(document_id, txn);
 
             self.documents.push(Document {
                 id: document_id,
                 pipeline_id,
-                content_rect: bounds.to_f32() / TypedScale::new(device_pixel_ratio),
+                content_rect: LayoutRect::new(
+                    LayoutPoint::origin(),
+                    bounds.size.to_f32() / TypedScale::new(device_pixel_ratio),
+                ),
                 color,
             });
         }
     }
 }
 
 impl Example for App {
     fn render(
         &mut self,
         api: &RenderApi,
         base_builder: &mut DisplayListBuilder,
         _txn: &mut Transaction,
-        framebuffer_size: DeviceIntSize,
+        framebuffer_size: FramebufferIntSize,
         _pipeline_id: PipelineId,
         _: DocumentId,
     ) {
         if self.documents.is_empty() {
             let device_pixel_ratio = framebuffer_size.width as f32 /
                 base_builder.content_size().width;
             // this is the first run, hack around the boilerplate,
             // which assumes an example only needs one document
-            self.init(api, framebuffer_size, device_pixel_ratio);
+            self.init(api,  device_pixel_ratio);
         }
 
         for doc in &self.documents {
             let space_and_clip = SpaceAndClipInfo::root_scroll(doc.pipeline_id);
             let mut builder = DisplayListBuilder::new(
                 doc.pipeline_id,
                 doc.content_rect.size,
             );
--- a/gfx/wr/examples/frame_output.rs
+++ b/gfx/wr/examples/frame_output.rs
@@ -37,18 +37,18 @@ struct OutputHandler {
     texture_id: gl::GLuint
 }
 
 struct ExternalHandler {
     texture_id: gl::GLuint
 }
 
 impl webrender::OutputImageHandler for OutputHandler {
-    fn lock(&mut self, _id: PipelineId) -> Option<(u32, DeviceIntSize)> {
-        Some((self.texture_id, DeviceIntSize::new(500, 500)))
+    fn lock(&mut self, _id: PipelineId) -> Option<(u32, FramebufferIntSize)> {
+        Some((self.texture_id, FramebufferIntSize::new(500, 500)))
     }
 
     fn unlock(&mut self, _id: PipelineId) {}
 }
 
 impl webrender::ExternalImageHandler for ExternalHandler {
     fn lock(
         &mut self,
@@ -63,43 +63,48 @@ impl webrender::ExternalImageHandler for
     }
     fn unlock(&mut self, _key: ExternalImageId, _channel_index: u8) {}
 }
 
 impl App {
     fn init_output_document(
         &mut self,
         api: &RenderApi,
-        framebuffer_size: DeviceIntSize,
+        framebuffer_size: FramebufferIntSize,
         device_pixel_ratio: f32,
     ) {
         // Generate the external image key that will be used to render the output document to the root document.
         self.external_image_key = Some(api.generate_image_key());
 
         let pipeline_id = PipelineId(1, 0);
         let layer = 1;
         let color = ColorF::new(1., 1., 0., 1.);
-        let bounds = DeviceIntRect::new(DeviceIntPoint::zero(), framebuffer_size);
         let document_id = api.add_document(framebuffer_size, layer);
+        api.enable_frame_output(document_id, pipeline_id, true);
+        api.set_document_view(
+            document_id,
+            FramebufferIntRect::new(
+                FramebufferIntPoint::new(0, 1000),
+                framebuffer_size,
+            ),
+            device_pixel_ratio,
+        );
 
         let document = Document {
             id: document_id,
             pipeline_id,
-            content_rect: bounds.to_f32() / TypedScale::new(device_pixel_ratio),
+            content_rect: LayoutRect::new(
+                LayoutPoint::zero(),
+                framebuffer_size.to_f32() / TypedScale::new(device_pixel_ratio),
+            ),
             color,
         };
 
         let mut txn = Transaction::new();
 
-        txn.enable_frame_output(document.pipeline_id, true);
-
-        api.send_transaction(document.id, txn);
-
-        let mut txn = Transaction::new();
-
         txn.add_image(
             self.external_image_key.unwrap(),
             ImageDescriptor::new(100, 100, ImageFormat::BGRA8, true, false),
             ImageData::External(ExternalImageData {
                 id: ExternalImageId(0),
                 channel_index: 0,
                 image_type: ExternalImageType::TextureHandle(TextureTarget::Default),
             }),
@@ -136,24 +141,24 @@ impl App {
 }
 
 impl Example for App {
     fn render(
         &mut self,
         api: &RenderApi,
         builder: &mut DisplayListBuilder,
         _txn: &mut Transaction,
-        framebuffer_size: DeviceIntSize,
+        framebuffer_size: FramebufferIntSize,
         pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         if self.output_document.is_none() {
             let device_pixel_ratio = framebuffer_size.width as f32 /
                 builder.content_size().width;
-            self.init_output_document(api, DeviceIntSize::new(200, 200), device_pixel_ratio);
+            self.init_output_document(api, FramebufferIntSize::new(200, 200), device_pixel_ratio);
         }
 
         let info = LayoutPrimitiveInfo::new((100, 100).to(200, 200));
         let space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
 
         builder.push_simple_stacking_context(
             &info,
             space_and_clip.spatial_id,
--- a/gfx/wr/examples/iframe.rs
+++ b/gfx/wr/examples/iframe.rs
@@ -20,17 +20,17 @@ use webrender::api::*;
 struct App {}
 
 impl Example for App {
     fn render(
         &mut self,
         api: &RenderApi,
         builder: &mut DisplayListBuilder,
         _txn: &mut Transaction,
-        _framebuffer_size: DeviceIntSize,
+        _framebuffer_size: FramebufferIntSize,
         pipeline_id: PipelineId,
         document_id: DocumentId,
     ) {
         // All the sub_* things are for the nested pipeline
         let sub_size = DeviceIntSize::new(100, 100);
         let sub_bounds = (0, 0).to(sub_size.width as i32, sub_size.height as i32);
 
         let sub_pipeline_id = PipelineId(pipeline_id.0, 42);
--- a/gfx/wr/examples/image_resize.rs
+++ b/gfx/wr/examples/image_resize.rs
@@ -20,17 +20,17 @@ struct App {
 }
 
 impl Example for App {
     fn render(
         &mut self,
         _api: &RenderApi,
         builder: &mut DisplayListBuilder,
         txn: &mut Transaction,
-        _framebuffer_size: DeviceIntSize,
+        _framebuffer_size: FramebufferIntSize,
         pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let (image_descriptor, image_data) = image_helper::make_checkerboard(32, 32);
         txn.add_image(
             self.image_key,
             image_descriptor,
             image_data,
--- a/gfx/wr/examples/multiwindow.rs
+++ b/gfx/wr/examples/multiwindow.rs
@@ -98,17 +98,17 @@ impl Window {
             ..webrender::RendererOptions::default()
         };
 
         let framebuffer_size = {
             let size = window
                 .get_inner_size()
                 .unwrap()
                 .to_physical(device_pixel_ratio as f64);
-            DeviceIntSize::new(size.width as i32, size.height as i32)
+            FramebufferIntSize::new(size.width as i32, size.height as i32)
         };
         let notifier = Box::new(Notifier::new(events_loop.create_proxy()));
         let (renderer, sender) = webrender::Renderer::new(gl.clone(), notifier, opts, None).unwrap();
         let api = sender.create_api();
         let document_id = api.add_document(framebuffer_size, 0);
 
         let epoch = Epoch(0);
         let pipeline_id = PipelineId(0, 0);
@@ -177,17 +177,17 @@ impl Window {
         }
 
         let device_pixel_ratio = self.window.get_hidpi_factor() as f32;
         let framebuffer_size = {
             let size = self.window
                 .get_inner_size()
                 .unwrap()
                 .to_physical(device_pixel_ratio as f64);
-            DeviceIntSize::new(size.width as i32, size.height as i32)
+            FramebufferIntSize::new(size.width as i32, size.height as i32)
         };
         let layout_size = framebuffer_size.to_f32() / euclid::TypedScale::new(device_pixel_ratio);
         let mut txn = Transaction::new();
         let mut builder = DisplayListBuilder::new(self.pipeline_id, layout_size);
         let space_and_clip = SpaceAndClipInfo::root_scroll(self.pipeline_id);
 
         let bounds = LayoutRect::new(LayoutPoint::zero(), builder.content_size());
         let info = LayoutPrimitiveInfo::new(bounds);
--- a/gfx/wr/examples/scrolling.rs
+++ b/gfx/wr/examples/scrolling.rs
@@ -21,17 +21,17 @@ struct App {
 }
 
 impl Example for App {
     fn render(
         &mut self,
         _api: &RenderApi,
         builder: &mut DisplayListBuilder,
         _txn: &mut Transaction,
-        _framebuffer_size: DeviceIntSize,
+        _framebuffer_size: FramebufferIntSize,
         pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let info = LayoutPrimitiveInfo::new(
             LayoutRect::new(LayoutPoint::zero(), builder.content_size())
         );
         let root_space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
         builder.push_simple_stacking_context(
--- a/gfx/wr/examples/texture_cache_stress.rs
+++ b/gfx/wr/examples/texture_cache_stress.rs
@@ -85,17 +85,17 @@ struct App {
 }
 
 impl Example for App {
     fn render(
         &mut self,
         api: &RenderApi,
         builder: &mut DisplayListBuilder,
         txn: &mut Transaction,
-        _framebuffer_size: DeviceIntSize,
+        _framebuffer_size: FramebufferIntSize,
         pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let bounds = (0, 0).to(512, 512);
         let info = LayoutPrimitiveInfo::new(bounds);
         let space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
 
         builder.push_simple_stacking_context(
--- a/gfx/wr/examples/yuv.rs
+++ b/gfx/wr/examples/yuv.rs
@@ -82,17 +82,17 @@ struct App {
 }
 
 impl Example for App {
     fn render(
         &mut self,
         api: &RenderApi,
         builder: &mut DisplayListBuilder,
         txn: &mut Transaction,
-        _framebuffer_size: DeviceIntSize,
+        _framebuffer_size: FramebufferIntSize,
         pipeline_id: PipelineId,
         _document_id: DocumentId,
     ) {
         let bounds = LayoutRect::new(LayoutPoint::zero(), builder.content_size());
         let info = LayoutPrimitiveInfo::new(bounds);
         let space_and_clip = SpaceAndClipInfo::root_scroll(pipeline_id);
 
         builder.push_simple_stacking_context(
--- a/gfx/wr/webrender/src/debug_render.rs
+++ b/gfx/wr/webrender/src/debug_render.rs
@@ -1,14 +1,14 @@
 /* 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 api::{ColorU, ColorF, ImageFormat, TextureTarget};
-use api::{DeviceIntRect, DeviceRect, DevicePoint, DeviceSize, DeviceIntSize};
+use api::units::*;
 use debug_font_data;
 use device::{Device, Program, Texture, TextureSlot, VertexDescriptor, ShaderError, VAO};
 use device::{TextureFilter, VertexAttribute, VertexAttributeKind, VertexUsageHint};
 use euclid::{Point2D, Rect, Size2D, Transform3D};
 use internal_types::{ORTHO_FAR_PLANE, ORTHO_NEAR_PLANE};
 use std::f32;
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
@@ -307,17 +307,17 @@ impl DebugRenderer {
         self.add_line(p1.x, p0.y, color, p1.x, p1.y, color);
         self.add_line(p1.x, p1.y, color, p0.x, p1.y, color);
         self.add_line(p0.x, p1.y, color, p0.x, p0.y, color);
     }
 
     pub fn render(
         &mut self,
         device: &mut Device,
-        viewport_size: Option<DeviceIntSize>,
+        viewport_size: Option<FramebufferIntSize>,
     ) {
         if let Some(viewport_size) = viewport_size {
             device.disable_depth();
             device.set_blend(true);
             device.set_blend_mode_premultiplied_alpha();
 
             let projection = Transform3D::ortho(
                 0.0,
--- a/gfx/wr/webrender/src/device/gl.rs
+++ b/gfx/wr/webrender/src/device/gl.rs
@@ -1,18 +1,16 @@
 /* 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 super::super::shader_source::SHADERS;
-use api::{ColorF, ImageFormat, MemoryReport};
-use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize};
-use api::TextureTarget;
-use api::VoidPtrToSizeFn;
-use api::ImageDescriptor;
+use api::{ColorF, ImageDescriptor, ImageFormat, MemoryReport};
+use api::{TextureTarget, VoidPtrToSizeFn};
+use api::units::*;
 use euclid::Transform3D;
 use gleam::gl;
 use internal_types::{FastHashMap, LayerIndex, RenderTargetInfo};
 use log::Level;
 use sha2::{Digest, Sha256};
 use smallvec::SmallVec;
 use std::borrow::Cow;
 use std::cell::{Cell, RefCell};
@@ -970,72 +968,95 @@ pub struct Device {
     extensions: Vec<String>,
 }
 
 /// Contains the parameters necessary to bind a draw target.
 #[derive(Clone, Copy)]
 pub enum DrawTarget<'a> {
     /// Use the device's default draw target, with the provided dimensions,
     /// which are used to set the viewport.
-    Default(DeviceIntSize),
+    Default {
+        /// Target rectangle to draw.
+        rect: FramebufferIntRect,
+        /// Total size of the target.
+        total_size: FramebufferIntSize,
+    },
     /// Use the provided texture.
     Texture {
         /// The target texture.
         texture: &'a Texture,
         /// The slice within the texture array to draw to.
         layer: LayerIndex,
         /// Whether to draw with the texture's associated depth target.
         with_depth: bool,
     },
 }
 
 impl<'a> DrawTarget<'a> {
+    pub fn new_default(size: FramebufferIntSize) -> Self {
+        DrawTarget::Default {
+            rect: size.into(),
+            total_size: size,
+        }
+    }
+
     /// Returns true if this draw target corresponds to the default framebuffer.
     pub fn is_default(&self) -> bool {
         match *self {
-            DrawTarget::Default(..) => true,
+            DrawTarget::Default {..} => true,
             _ => false,
         }
     }
 
     /// Returns the dimensions of this draw-target.
     pub fn dimensions(&self) -> DeviceIntSize {
         match *self {
-            DrawTarget::Default(d) => d,
+            DrawTarget::Default { total_size, .. } => DeviceIntSize::from_untyped(&total_size.to_untyped()),
             DrawTarget::Texture { texture, .. } => texture.get_dimensions(),
         }
     }
 
+    pub fn to_framebuffer_rect(&self, device_rect: DeviceIntRect) -> FramebufferIntRect {
+        let mut fb_rect = FramebufferIntRect::from_untyped(&device_rect.to_untyped());
+        match *self {
+            DrawTarget::Default { ref rect, .. } => {
+                // perform a Y-flip here
+                fb_rect.origin.y = rect.origin.y + rect.size.height - fb_rect.origin.y - fb_rect.size.height;
+                fb_rect.origin.x += rect.origin.x;
+            }
+            DrawTarget::Texture { .. } => (),
+        }
+        fb_rect
+    }
+
     /// Given a scissor rect, convert it to the right coordinate space
     /// depending on the draw target kind. If no scissor rect was supplied,
     /// returns a scissor rect that encloses the entire render target.
     pub fn build_scissor_rect(
         &self,
         scissor_rect: Option<DeviceIntRect>,
-        framebuffer_target_rect: DeviceIntRect,
-    ) -> DeviceIntRect {
+        content_origin: DeviceIntPoint,
+    ) -> FramebufferIntRect {
         let dimensions = self.dimensions();
 
         match scissor_rect {
-            Some(scissor_rect) => {
-                // Note: `framebuffer_target_rect` needs a Y-flip before going to GL
-                if self.is_default() {
-                    let mut rect = scissor_rect
-                        .intersection(&framebuffer_target_rect.to_i32())
-                        .unwrap_or(DeviceIntRect::zero());
-                    rect.origin.y = dimensions.height as i32 - rect.origin.y - rect.size.height;
-                    rect
-                } else {
-                    scissor_rect
+            Some(scissor_rect) => match *self {
+                DrawTarget::Default { ref rect, .. } => {
+                    self.to_framebuffer_rect(scissor_rect.translate(&-content_origin.to_vector()))
+                        .intersection(rect)
+                        .unwrap_or_else(FramebufferIntRect::zero)
+                }
+                DrawTarget::Texture { .. } => {
+                    FramebufferIntRect::from_untyped(&scissor_rect.to_untyped())
                 }
             }
             None => {
-                DeviceIntRect::new(
-                    DeviceIntPoint::zero(),
-                    dimensions,
+                FramebufferIntRect::new(
+                    FramebufferIntPoint::zero(),
+                    FramebufferIntSize::from_untyped(&dimensions.to_untyped()),
                 )
             }
         }
     }
 }
 
 /// Contains the parameters necessary to bind a texture-backed read target.
 #[derive(Clone, Copy)]
@@ -1049,17 +1070,17 @@ pub enum ReadTarget<'a> {
         /// The slice within the texture array to read from.
         layer: LayerIndex,
     }
 }
 
 impl<'a> From<DrawTarget<'a>> for ReadTarget<'a> {
     fn from(t: DrawTarget<'a>) -> Self {
         match t {
-            DrawTarget::Default(..) => ReadTarget::Default,
+            DrawTarget::Default { .. } => ReadTarget::Default,
             DrawTarget::Texture { texture, layer, .. } =>
                 ReadTarget::Texture { texture, layer },
         }
     }
 }
 
 impl Device {
     pub fn new(
@@ -1420,35 +1441,38 @@ impl Device {
         self.bind_draw_target_impl(fbo);
         self.depth_available = true;
     }
 
     pub fn bind_draw_target(
         &mut self,
         target: DrawTarget,
     ) {
-        let (fbo_id, dimensions, depth_available) = match target {
-            DrawTarget::Default(d) => (self.default_draw_fbo, d, true),
+        let (fbo_id, rect, depth_available) = match target {
+            DrawTarget::Default { rect, .. } => (self.default_draw_fbo, rect, true),
             DrawTarget::Texture { texture, layer, with_depth } => {
-                let dim = texture.get_dimensions();
+                let rect = FramebufferIntRect::new(
+                    FramebufferIntPoint::zero(),
+                    FramebufferIntSize::from_untyped(&texture.get_dimensions().to_untyped()),
+                );
                 if with_depth {
-                    (texture.fbos_with_depth[layer], dim, true)
+                    (texture.fbos_with_depth[layer], rect, true)
                 } else {
-                    (texture.fbos[layer], dim, false)
+                    (texture.fbos[layer], rect, false)
                 }
             }
         };
 
         self.depth_available = depth_available;
         self.bind_draw_target_impl(fbo_id);
         self.gl.viewport(
-            0,
-            0,
-            dimensions.width as _,
-            dimensions.height as _,
+            rect.origin.x,
+            rect.origin.y,
+            rect.size.width,
+            rect.size.height,
         );
     }
 
     /// Creates an unbound FBO object. Additional attachment API calls are
     /// required to make it complete.
     pub fn create_fbo(&mut self) -> FBOId {
         FBOId(self.gl.gen_framebuffers(1)[0])
     }
@@ -1789,17 +1813,20 @@ impl Device {
                 self.gl.copy_image_sub_data(src.id, src.target, 0,
                                             0, 0, 0,
                                             dst.id, dst.target, 0,
                                             0, 0, 0,
                                             src.size.width as _, src.size.height as _, src.layer_count);
             }
         } else {
             // Note that zip() truncates to the shorter of the two iterators.
-            let rect = DeviceIntRect::new(DeviceIntPoint::zero(), src.get_dimensions().to_i32());
+            let rect = FramebufferIntRect::new(
+                FramebufferIntPoint::zero(),
+                FramebufferIntSize::from_untyped(&src.get_dimensions().to_untyped()),
+            );
             for (read_fbo, draw_fbo) in src.fbos.iter().zip(&dst.fbos) {
                 self.bind_read_target_impl(*read_fbo);
                 self.bind_draw_target_impl(*draw_fbo);
                 self.blit_render_target(rect, rect, TextureFilter::Linear);
             }
             self.reset_draw_target();
             self.reset_read_target();
         }
@@ -1946,18 +1973,18 @@ impl Device {
             let (dimensions, target) = entry.remove_entry();
             self.gl.delete_renderbuffers(&[target.rbo_id.0]);
             record_gpu_free(depth_target_size_in_bytes(&dimensions));
         }
     }
 
     pub fn blit_render_target(
         &mut self,
-        src_rect: DeviceIntRect,
-        dest_rect: DeviceIntRect,
+        src_rect: FramebufferIntRect,
+        dest_rect: FramebufferIntRect,
         filter: TextureFilter,
     ) {
         debug_assert!(self.inside_frame);
 
         let filter = match filter {
             TextureFilter::Nearest => gl::NEAREST,
             TextureFilter::Linear | TextureFilter::Trilinear => gl::LINEAR,
         };
@@ -1976,18 +2003,18 @@ impl Device {
         );
     }
 
     /// Performs a blit while flipping vertically. Useful for blitting textures
     /// (which use origin-bottom-left) to the main framebuffer (which uses
     /// origin-top-left).
     pub fn blit_render_target_invert_y(
         &mut self,
-        src_rect: DeviceIntRect,
-        dest_rect: DeviceIntRect,
+        src_rect: FramebufferIntRect,
+        dest_rect: FramebufferIntRect,
     ) {
         debug_assert!(self.inside_frame);
         self.gl.blit_framebuffer(
             src_rect.origin.x,
             src_rect.origin.y,
             src_rect.origin.x + src_rect.size.width,
             src_rect.origin.y + src_rect.size.height,
             dest_rect.origin.x,
@@ -2229,17 +2256,17 @@ impl Device {
             desc.external,
             desc.pixel_type,
         )
     }
 
     /// Read rectangle of pixels into the specified output slice.
     pub fn read_pixels_into(
         &mut self,
-        rect: DeviceIntRect,
+        rect: FramebufferIntRect,
         format: ReadPixelsFormat,
         output: &mut [u8],
     ) {
         let (bytes_per_pixel, desc) = match format {
             ReadPixelsFormat::Standard(imf) => {
                 (imf.bytes_per_pixel(), self.gl_describe_format(imf))
             }
             ReadPixelsFormat::Rgba8 => {
@@ -2604,17 +2631,17 @@ impl Device {
             }
         }
     }
 
     pub fn clear_target(
         &self,
         color: Option<[f32; 4]>,
         depth: Option<f32>,
-        rect: Option<DeviceIntRect>,
+        rect: Option<FramebufferIntRect>,
     ) {
         let mut clear_bits = 0;
 
         if let Some(color) = color {
             self.gl.clear_color(color[0], color[1], color[2], color[3]);
             clear_bits |= gl::COLOR_BUFFER_BIT;
         }
 
@@ -2671,17 +2698,17 @@ impl Device {
     pub fn disable_depth_write(&self) {
         self.gl.depth_mask(false);
     }
 
     pub fn disable_stencil(&self) {
         self.gl.disable(gl::STENCIL_TEST);
     }
 
-    pub fn set_scissor_rect(&self, rect: DeviceIntRect) {
+    pub fn set_scissor_rect(&self, rect: FramebufferIntRect) {
         self.gl.scissor(
             rect.origin.x,
             rect.origin.y,
             rect.size.width,
             rect.size.height,
         );
     }
 
--- a/gfx/wr/webrender/src/display_list_flattener.rs
+++ b/gfx/wr/webrender/src/display_list_flattener.rs
@@ -300,19 +300,18 @@ impl<'a> DisplayListFlattener<'a> {
 
         debug_assert!(flattener.sc_stack.is_empty());
 
         new_scene.root_pipeline_id = Some(root_pipeline_id);
         new_scene.pipeline_epochs = scene.pipeline_epochs.clone();
         new_scene.pipelines = scene.pipelines.clone();
 
         FrameBuilder::with_display_list_flattener(
-            view.inner_rect,
+            DeviceIntSize::from_untyped(&view.framebuffer_rect.size.to_untyped()).into(),
             background_color,
-            view.window_size,
             flattener,
         )
     }
 
     /// Cut the primitives in the root stacking context based on the picture
     /// caching scroll root. This is a temporary solution for the initial
     /// implementation of picture caching. We need to work out the specifics
     /// of how WR should decide (or Gecko should communicate) where the main
--- a/gfx/wr/webrender/src/frame_builder.rs
+++ b/gfx/wr/webrender/src/frame_builder.rs
@@ -1,15 +1,15 @@
 /* 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 api::{ColorF, DeviceIntPoint, DevicePixelScale, LayoutPixel, PicturePixel, RasterPixel};
-use api::{DeviceIntRect, DeviceIntSize, DocumentLayer, FontRenderMode, DebugFlags, PremultipliedColorF};
-use api::{LayoutPoint, LayoutRect, LayoutSize, PipelineId, RasterSpace, WorldPoint, WorldRect, WorldPixel};
+use api::{ColorF, DebugFlags, DocumentLayer, FontRenderMode, PremultipliedColorF};
+use api::{PipelineId, RasterSpace};
+use api::units::*;
 use clip::{ClipDataStore, ClipStore, ClipChainStack};
 use clip_scroll_tree::{ClipScrollTree, ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex};
 use display_list_flattener::{DisplayListFlattener};
 use gpu_cache::{GpuCache, GpuCacheHandle};
 use gpu_types::{PrimitiveHeaders, TransformPalette, UvRectKind, ZBufferIdGenerator};
 use hit_test::{HitTester, HitTestingRun};
 use internal_types::{FastHashMap, PlaneSplitter};
 use picture::{PictureSurface, PictureUpdateState, SurfaceInfo, ROOT_SURFACE_INDEX, SurfaceIndex};
@@ -90,19 +90,18 @@ impl FrameGlobalResources {
             ]);
         }
     }
 }
 
 /// A builder structure for `tiling::Frame`
 #[cfg_attr(feature = "capture", derive(Serialize))]
 pub struct FrameBuilder {
-    screen_rect: DeviceIntRect,
+    output_rect: DeviceIntRect,
     background_color: Option<ColorF>,
-    window_size: DeviceIntSize,
     root_pic_index: PictureIndex,
     /// Cache of surface tiles from the previous frame builder
     /// that can optionally be consumed by this frame builder.
     pending_retained_tiles: RetainedTiles,
     pub prim_store: PrimitiveStore,
     pub clip_store: ClipStore,
     #[cfg_attr(feature = "capture", serde(skip))] //TODO
     pub hit_testing_runs: Vec<HitTestingRun>,
@@ -218,18 +217,17 @@ impl<'a> PrimitiveContext<'a> {
 
 impl FrameBuilder {
     #[cfg(feature = "replay")]
     pub fn empty() -> Self {
         FrameBuilder {
             hit_testing_runs: Vec::new(),
             prim_store: PrimitiveStore::new(&PrimitiveStoreStats::empty()),
             clip_store: ClipStore::new(),
-            screen_rect: DeviceIntRect::zero(),
-            window_size: DeviceIntSize::zero(),
+            output_rect: DeviceIntRect::zero(),
             background_color: None,
             root_pic_index: PictureIndex(0),
             pending_retained_tiles: RetainedTiles::new(),
             globals: FrameGlobalResources::empty(),
             config: FrameBuilderConfig {
                 default_font_render_mode: FontRenderMode::Mono,
                 dual_source_blending_is_enabled: true,
                 dual_source_blending_is_supported: false,
@@ -250,29 +248,27 @@ impl FrameBuilder {
         globals: FrameGlobalResources,
     ) {
         debug_assert!(self.pending_retained_tiles.tiles.is_empty());
         self.pending_retained_tiles = retained_tiles;
         self.globals = globals;
     }
 
     pub fn with_display_list_flattener(
-        screen_rect: DeviceIntRect,
+        output_rect: DeviceIntRect,
         background_color: Option<ColorF>,
-        window_size: DeviceIntSize,
         flattener: DisplayListFlattener,
     ) -> Self {
         FrameBuilder {
             hit_testing_runs: flattener.hit_testing_runs,
             prim_store: flattener.prim_store,
             clip_store: flattener.clip_store,
             root_pic_index: flattener.root_pic_index,
-            screen_rect,
+            output_rect,
             background_color,
-            window_size,
             pending_retained_tiles: RetainedTiles::new(),
             config: flattener.config,
             globals: FrameGlobalResources::empty(),
         }
     }
 
     /// Destroy an existing frame builder. This is called just before
     /// a frame builder is replaced with a newly built scene.
@@ -470,21 +466,18 @@ impl FrameBuilder {
 
         frame_state.pop_dirty_region();
 
         let child_tasks = frame_state
             .surfaces[ROOT_SURFACE_INDEX.0]
             .take_render_tasks();
 
         let root_render_task = RenderTask::new_picture(
-            // The rect here is the whole window. If we were to use the screen_rect,
-            // any offset in that rect would doubly apply for cached pictures.
-            RenderTaskLocation::Fixed(DeviceIntRect::new(DeviceIntPoint::zero(),
-                                                         self.window_size).to_i32()),
-            self.window_size.to_f32(),
+            RenderTaskLocation::Fixed(self.output_rect),
+            self.output_rect.size.to_f32(),
             self.root_pic_index,
             DeviceIntPoint::zero(),
             child_tasks,
             UvRectKind::Rect,
             root_spatial_node_index,
             global_device_pixel_scale,
         );
 
@@ -501,31 +494,28 @@ impl FrameBuilder {
         &mut self,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         stamp: FrameStamp,
         clip_scroll_tree: &mut ClipScrollTree,
         pipelines: &FastHashMap<PipelineId, Arc<ScenePipeline>>,
         global_device_pixel_scale: DevicePixelScale,
         layer: DocumentLayer,
+        framebuffer_origin: FramebufferIntPoint,
         pan: WorldPoint,
         texture_cache_profile: &mut TextureCacheProfileCounters,
         gpu_cache_profile: &mut GpuCacheProfileCounters,
         scene_properties: &SceneProperties,
         data_stores: &mut DataStores,
         scratch: &mut PrimitiveScratchBuffer,
         render_task_counters: &mut RenderTaskTreeCounters,
         debug_flags: DebugFlags,
     ) -> Frame {
         profile_scope!("build");
         profile_marker!("BuildFrame");
-        debug_assert!(
-            DeviceIntRect::new(DeviceIntPoint::zero(), self.window_size)
-                .contains_rect(&self.screen_rect)
-        );
 
         let mut profile_counters = FrameProfileCounters::new();
         profile_counters
             .total_primitives
             .set(self.prim_store.prim_count());
 
         resource_cache.begin_frame(stamp);
         gpu_cache.begin_frame(stamp);
@@ -541,18 +531,18 @@ impl FrameBuilder {
         self.clip_store.clear_old_instances();
 
         let mut render_tasks = RenderTaskTree::new(
             stamp.frame_id(),
             render_task_counters,
         );
         let mut surfaces = Vec::new();
 
-        let screen_size = self.screen_rect.size.to_i32();
-        let screen_world_rect = (self.screen_rect.to_f32() / global_device_pixel_scale).round_out();
+        let output_size = self.output_rect.size.to_i32();
+        let screen_world_rect = (self.output_rect.to_f32() / global_device_pixel_scale).round_out();
 
         let main_render_task_id = self.build_layer_screen_rects_and_cull_layers(
             screen_world_rect,
             clip_scroll_tree,
             pipelines,
             resource_cache,
             gpu_cache,
             &mut render_tasks,
@@ -579,36 +569,36 @@ impl FrameBuilder {
         let mut has_texture_cache_tasks = false;
         let mut prim_headers = PrimitiveHeaders::new();
 
         {
             profile_marker!("Batching");
 
             // Add passes as required for our cached render tasks.
             if !render_tasks.cacheable_render_tasks.is_empty() {
-                passes.push(RenderPass::new_off_screen(screen_size, self.config.gpu_supports_fast_clears));
+                passes.push(RenderPass::new_off_screen(output_size, self.config.gpu_supports_fast_clears));
                 for cacheable_render_task in &render_tasks.cacheable_render_tasks {
                     render_tasks.assign_to_passes(
                         *cacheable_render_task,
                         0,
-                        screen_size,
+                        output_size,
                         &mut passes,
                         self.config.gpu_supports_fast_clears,
                     );
                 }
                 passes.reverse();
             }
 
             if let Some(main_render_task_id) = main_render_task_id {
                 let passes_start = passes.len();
-                passes.push(RenderPass::new_main_framebuffer(screen_size, self.config.gpu_supports_fast_clears));
+                passes.push(RenderPass::new_main_framebuffer(output_size, self.config.gpu_supports_fast_clears));
                 render_tasks.assign_to_passes(
                     main_render_task_id,
                     passes_start,
-                    screen_size,
+                    output_size,
                     &mut passes,
                     self.config.gpu_supports_fast_clears,
                 );
                 passes[passes_start..].reverse();
             }
 
             // Used to generated a unique z-buffer value per primitive.
             let mut z_generator = ZBufferIdGenerator::new();
@@ -654,18 +644,21 @@ impl FrameBuilder {
 
         let gpu_cache_frame_id = gpu_cache.end_frame(gpu_cache_profile).frame_id();
 
         render_tasks.write_task_data();
         *render_task_counters = render_tasks.counters();
         resource_cache.end_frame(texture_cache_profile);
 
         Frame {
-            window_size: self.window_size,
-            inner_rect: self.screen_rect,
+            content_origin: self.output_rect.origin,
+            framebuffer_rect: FramebufferIntRect::new(
+                framebuffer_origin,
+                FramebufferIntSize::from_untyped(&self.output_rect.size.to_untyped()),
+            ),
             background_color: self.background_color,
             layer,
             profile_counters,
             passes,
             transform_palette: transform_palette.transforms,
             render_tasks,
             deferred_resolves,
             gpu_cache_frame_id,
--- a/gfx/wr/webrender/src/render_backend.rs
+++ b/gfx/wr/webrender/src/render_backend.rs
@@ -6,22 +6,21 @@
 //! commands to be issued by the `Renderer`.
 //!
 //! See the comment at the top of the `renderer` module for a description of
 //! how these two pieces interact.
 
 use api::{ApiMsg, BuiltDisplayList, ClearCache, DebugCommand, DebugFlags};
 #[cfg(feature = "debugger")]
 use api::{BuiltDisplayListIter, SpecificDisplayItem};
-use api::{DevicePixelScale, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
 use api::{DocumentId, DocumentLayer, ExternalScrollId, FrameMsg, HitTestFlags, HitTestResult};
-use api::{IdNamespace, LayoutPoint, PipelineId, RenderNotifier, SceneMsg, ScrollClamping};
-use api::{MemoryReport};
+use api::{IdNamespace, MemoryReport, PipelineId, RenderNotifier, SceneMsg, ScrollClamping};
 use api::{ScrollLocation, ScrollNodeState, TransactionMsg, ResourceUpdate, BlobImageKey};
 use api::{NotificationRequest, Checkpoint};
+use api::units::*;
 use api::channel::{MsgReceiver, MsgSender, Payload};
 #[cfg(feature = "capture")]
 use api::CaptureBits;
 #[cfg(feature = "replay")]
 use api::CapturedDocument;
 use clip_scroll_tree::{SpatialNodeIndex, ClipScrollTree};
 #[cfg(feature = "debugger")]
 use debug_server;
@@ -57,22 +56,22 @@ use std::sync::atomic::{AtomicUsize, Ord
 use std::sync::mpsc::{channel, Sender, Receiver};
 use std::time::{UNIX_EPOCH, SystemTime};
 use std::u32;
 #[cfg(feature = "replay")]
 use tiling::Frame;
 use time::precise_time_ns;
 use util::{Recycler, VecHelper, drain_filter};
 
+
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(Clone)]
 pub struct DocumentView {
-    pub window_size: DeviceIntSize,
-    pub inner_rect: DeviceIntRect,
+    pub framebuffer_rect: FramebufferIntRect,
     pub layer: DocumentLayer,
     pub pan: DeviceIntPoint,
     pub device_pixel_ratio: f32,
     pub page_zoom_factor: f32,
     pub pinch_zoom_factor: f32,
 }
 
 impl DocumentView {
@@ -351,26 +350,25 @@ struct Document {
     /// Keep track of the size of render task tree to pre-allocate memory up-front
     /// the next frame.
     render_task_counters: RenderTaskTreeCounters,
 }
 
 impl Document {
     pub fn new(
         id: DocumentId,
-        window_size: DeviceIntSize,
+        size: FramebufferIntSize,
         layer: DocumentLayer,
         default_device_pixel_ratio: f32,
     ) -> Self {
         Document {
             scene: Scene::new(),
             removed_pipelines: Vec::new(),
             view: DocumentView {
-                window_size,
-                inner_rect: DeviceIntRect::new(DeviceIntPoint::zero(), window_size),
+                framebuffer_rect: FramebufferIntRect::new(FramebufferIntPoint::zero(), size),
                 layer,
                 pan: DeviceIntPoint::zero(),
                 page_zoom_factor: 1.0,
                 pinch_zoom_factor: 1.0,
                 device_pixel_ratio: default_device_pixel_ratio,
             },
             clip_scroll_tree: ClipScrollTree::new(),
             stamp: FrameStamp::first(id),
@@ -388,34 +386,27 @@ impl Document {
         }
     }
 
     fn can_render(&self) -> bool {
         self.frame_builder.is_some() && self.scene.has_root_pipeline()
     }
 
     fn has_pixels(&self) -> bool {
-        !self.view.window_size.is_empty_or_negative()
+        !self.view.framebuffer_rect.size.is_empty_or_negative()
     }
 
     fn process_frame_msg(
         &mut self,
         message: FrameMsg,
     ) -> DocumentOps {
         match message {
             FrameMsg::UpdateEpoch(pipeline_id, epoch) => {
                 self.scene.update_epoch(pipeline_id, epoch);
             }
-            FrameMsg::EnableFrameOutput(pipeline_id, enable) => {
-                if enable {
-                    self.output_pipelines.insert(pipeline_id);
-                } else {
-                    self.output_pipelines.remove(&pipeline_id);
-                }
-            }
             FrameMsg::Scroll(delta, cursor) => {
                 profile_scope!("Scroll");
 
                 let node_index = match self.hit_tester {
                     Some(ref hit_tester) => {
                         // Ideally we would call self.scroll_nearest_scrolling_ancestor here, but
                         // we need have to avoid a double-borrow.
                         let test = HitTest::new(None, cursor, HitTestFlags::empty());
@@ -516,16 +507,17 @@ impl Document {
             let frame = frame_builder.build(
                 resource_cache,
                 gpu_cache,
                 self.stamp,
                 &mut self.clip_scroll_tree,
                 &self.scene.pipelines,
                 accumulated_scale_factor,
                 self.view.layer,
+                self.view.framebuffer_rect.origin,
                 pan,
                 &mut resource_profile.texture_cache,
                 &mut resource_profile.gpu_cache,
                 &self.dynamic_properties,
                 &mut self.data_stores,
                 &mut self.scratch,
                 &mut self.render_task_counters,
                 debug_flags,
@@ -752,23 +744,21 @@ impl RenderBackend {
 
         match message {
             SceneMsg::UpdateEpoch(pipeline_id, epoch) => {
                 txn.epoch_updates.push((pipeline_id, epoch));
             }
             SceneMsg::SetPageZoom(factor) => {
                 doc.view.page_zoom_factor = factor.get();
             }
-            SceneMsg::SetWindowParameters {
-                window_size,
-                inner_rect,
+            SceneMsg::SetDocumentView {
+                framebuffer_rect,
                 device_pixel_ratio,
             } => {
-                doc.view.window_size = window_size;
-                doc.view.inner_rect = inner_rect;
+                doc.view.framebuffer_rect = framebuffer_rect;
                 doc.view.device_pixel_ratio = device_pixel_ratio;
             }
             SceneMsg::SetDisplayList {
                 epoch,
                 pipeline_id,
                 background,
                 viewport_size,
                 content_size,
@@ -828,24 +818,29 @@ impl RenderBackend {
                     send_start_time,
                     display_list_received_time,
                     display_list_consumed_time,
                     display_list_len,
                 );
             }
             SceneMsg::SetRootPipeline(pipeline_id) => {
                 profile_scope!("SetRootPipeline");
-
                 txn.set_root_pipeline = Some(pipeline_id);
             }
             SceneMsg::RemovePipeline(pipeline_id) => {
                 profile_scope!("RemovePipeline");
-
                 txn.removed_pipelines.push(pipeline_id);
             }
+            SceneMsg::EnableFrameOutput(pipeline_id, enable) => {
+                if enable {
+                    doc.output_pipelines.insert(pipeline_id);
+                } else {
+                    doc.output_pipelines.remove(&pipeline_id);
+                }
+            }
         }
     }
 
     fn next_namespace_id(&self) -> IdNamespace {
         IdNamespace(NEXT_NAMESPACE_ID.fetch_add(1, Ordering::Relaxed) as u32)
     }
 
     pub fn run(&mut self, mut profile_counters: BackendProfileCounters) {
@@ -1095,17 +1090,16 @@ impl RenderBackend {
                         *frame_counter += 1;
 
                         self.load_capture(&root, profile_counters);
 
                         for (id, doc) in &self.documents {
                             let captured = CapturedDocument {
                                 document_id: *id,
                                 root_pipeline_id: doc.scene.root_pipeline_id,
-                                window_size: doc.view.window_size,
                             };
                             tx.send(captured).unwrap();
 
                             // notify the active recorder
                             if let Some(ref mut r) = self.recorder {
                                 let pipeline_id = doc.scene.root_pipeline_id.unwrap();
                                 let epoch =  doc.scene.pipeline_epochs[&pipeline_id];
                                 let pipeline = &doc.scene.pipelines[&pipeline_id];
--- a/gfx/wr/webrender/src/renderer.rs
+++ b/gfx/wr/webrender/src/renderer.rs
@@ -16,25 +16,37 @@
 //! The consumer is responsible for initializing the render thread before
 //! calling into WebRender, which means that this module also serves as the
 //! initial entry point into WebRender, and is responsible for spawning the
 //! various other threads discussed above. That said, WebRender initialization
 //! returns both the `Renderer` instance as well as a channel for communicating
 //! directly with the `RenderBackend`. Aside from a few high-level operations
 //! like 'render now', most of interesting commands from the consumer go over
 //! that channel and operate on the `RenderBackend`.
-
-use api::{BlobImageHandler, ColorF, ColorU, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
+//!
+//! ## Space conversion guidelines
+//! At this stage, we shuld be operating with `DevicePixel` and `FramebufferPixel` only.
+//! "Framebuffer" space represents the final destination of our rendeing,
+//! and it happens to be Y-flipped on OpenGL. The conversion is done as follows:
+//!   - for rasterized primitives, the orthographics projection transforms
+//! the content rectangle to -1 to 1
+//!   - the viewport transformation is setup to map the whole range to
+//! the framebuffer rectangle provided by the document view, stored in `DrawTarget`
+//!   - all the direct framebuffer operations, like blitting, reading pixels, and setting
+//! up the scissor, are accepting already transformed coordinates, which we can get by
+//! calling `DrawTarget::to_framebuffer_rect`
+
+use api::{BlobImageHandler, ColorF, ColorU};
 use api::{DocumentId, Epoch, ExternalImageId};
 use api::{ExternalImageType, FontRenderMode, FrameMsg, ImageFormat, PipelineId};
 use api::{ImageRendering, Checkpoint, NotificationRequest};
-use api::{MemoryReport, VoidPtrToSizeFn};
+use api::{DebugCommand, MemoryReport, VoidPtrToSizeFn};
 use api::{RenderApiSender, RenderNotifier, TexelRect, TextureTarget};
-use api::{channel};
-use api::DebugCommand;
+use api::channel;
+use api::units::*;
 pub use api::DebugFlags;
 use api::channel::PayloadReceiverHelperMethods;
 use batch::{BatchKind, BatchTextures, BrushBatchKind, ClipBatchList};
 #[cfg(any(feature = "capture", feature = "replay"))]
 use capture::{CaptureConfig, ExternalCaptureImage, PlainExternalImage};
 use debug_colors;
 use debug_render::{DebugItem, DebugRenderer};
 use device::{DepthFunction, Device, GpuFrameId, Program, UploadMethod, Texture, PBO};
@@ -1568,17 +1580,17 @@ pub struct Renderer {
     /// List of profile results from previous frames. Can be retrieved
     /// via get_frame_profiles().
     cpu_profiles: VecDeque<CpuProfile>,
     gpu_profiles: VecDeque<GpuProfile>,
 
     /// Notification requests to be fulfilled after rendering.
     notifications: Vec<NotificationRequest>,
 
-    framebuffer_size: Option<DeviceIntSize>,
+    framebuffer_size: Option<FramebufferIntSize>,
 
     /// A lazily created texture for the zoom debugging widget.
     zoom_debug_texture: Option<Texture>,
 
     /// The current mouse position. This is used for debugging
     /// functionality only, such as the debug zoom widget.
     cursor_position: DeviceIntPoint,
 
@@ -2035,16 +2047,20 @@ impl Renderer {
         // We initially set the flags to default and then now call set_debug_flags
         // to ensure any potential transition when enabling a flag is run.
         renderer.set_debug_flags(debug_flags);
 
         let sender = RenderApiSender::new(api_tx, payload_tx);
         Ok((renderer, sender))
     }
 
+    pub fn framebuffer_size(&self) -> Option<FramebufferIntSize> {
+        self.framebuffer_size
+    }
+
     /// Update the current position of the debug cursor.
     pub fn set_cursor_position(
         &mut self,
         position: DeviceIntPoint,
     ) {
         self.cursor_position = position;
     }
 
@@ -2472,21 +2488,21 @@ impl Renderer {
     /// intersect on the main framebuffer, in which case we don't clear
     /// the whole depth and instead clear each document area separately.
     fn are_documents_intersecting_depth(&self) -> bool {
         let document_rects = self.active_documents
             .iter()
             .filter_map(|&(_, ref render_doc)| {
                 match render_doc.frame.passes.last() {
                     Some(&RenderPass { kind: RenderPassKind::MainFramebuffer(ref target), .. })
-                        if target.needs_depth() => Some(render_doc.frame.inner_rect),
+                        if target.needs_depth() => Some(render_doc.frame.framebuffer_rect),
                     _ => None,
                 }
             })
-            .collect::<Vec<_>>();
+            .collect::<SmallVec<[_; 3]>>();
 
         for (i, rect) in document_rects.iter().enumerate() {
             for other in &document_rects[i+1 ..] {
                 if rect.intersects(other) {
                     return true
                 }
             }
         }
@@ -2498,17 +2514,17 @@ impl Renderer {
         self.slow_frame_indicator.changed();
     }
 
     /// Renders the current frame.
     ///
     /// A Frame is supplied by calling [`generate_frame()`][webrender_api::Transaction::generate_frame].
     pub fn render(
         &mut self,
-        framebuffer_size: DeviceIntSize,
+        framebuffer_size: FramebufferIntSize,
     ) -> Result<RenderResults, Vec<RendererError>> {
         self.framebuffer_size = Some(framebuffer_size);
 
         let result = self.render_impl(Some(framebuffer_size));
 
         drain_filter(
             &mut self.notifications,
             |n| { n.when() == Checkpoint::FrameRendered },
@@ -2524,17 +2540,17 @@ impl Renderer {
     }
 
     // If framebuffer_size is None, don't render
     // to the main frame buffer. This is useful
     // to update texture cache render tasks but
     // avoid doing a full frame render.
     fn render_impl(
         &mut self,
-        framebuffer_size: Option<DeviceIntSize>,
+        framebuffer_size: Option<FramebufferIntSize>,
     ) -> Result<RenderResults, Vec<RendererError>> {
         profile_scope!("render");
         let mut results = RenderResults::default();
         if self.active_documents.is_empty() {
             self.last_time = precise_time_ns();
             return Ok(results);
         }
 
@@ -2636,29 +2652,40 @@ impl Renderer {
                     frame,
                     framebuffer_size,
                     cpu_frame_id,
                     &mut results.stats,
                     fb_clear_color,
                     fb_clear_depth,
                 );
 
+                if let Some(_) = framebuffer_size {
+                    self.draw_frame_debug_items(&frame.debug_items);
+                }
                 if self.debug_flags.contains(DebugFlags::PROFILER_DBG) {
                     frame_profiles.push(frame.profile_counters.clone());
                 }
 
                 let dirty_regions =
                     mem::replace(&mut frame.recorded_dirty_regions, Vec::new());
                 results.recorded_dirty_regions.extend(dirty_regions);
             }
 
             self.unlock_external_images();
             self.active_documents = active_documents;
         });
 
+        if let Some(framebuffer_size) = framebuffer_size {
+            self.draw_render_target_debug(framebuffer_size);
+            self.draw_texture_cache_debug(framebuffer_size);
+            self.draw_gpu_cache_debug(framebuffer_size);
+            self.draw_zoom_debug(framebuffer_size);
+            self.draw_epoch_debug();
+        }
+
         let current_time = precise_time_ns();
         if framebuffer_size.is_some() {
             let ns = current_time - self.last_time;
             self.profile_counters.frame_time.set(ns);
         }
 
         if self.max_recorded_profiles > 0 {
             while self.cpu_profiles.len() >= self.max_recorded_profiles {
@@ -2948,25 +2975,26 @@ impl Renderer {
                                 ExternalImageSource::NativeTexture(eid) => {
                                     panic!("Unexpected external texture {:?} for the texture cache update of {:?}", eid, id);
                                 }
                             };
                             handler.unlock(id, channel_index);
                             size
                         }
                         TextureUpdateSource::DebugClear => {
-                            self.device.bind_draw_target(DrawTarget::Texture {
+                            let draw_target = DrawTarget::Texture {
                                 texture,
                                 layer: layer_index as usize,
                                 with_depth: false,
-                            });
+                            };
+                            self.device.bind_draw_target(draw_target);
                             self.device.clear_target(
                                 Some(TEXTURE_CACHE_DBG_CLEAR_COLOR),
                                 None,
-                                Some(rect.to_i32())
+                                Some(draw_target.to_framebuffer_rect(rect.to_i32()))
                             );
                             0
                         }
                     };
                     self.profile_counters.texture_data_uploaded.add(bytes_uploaded >> 10);
                 }
             }
 
@@ -3035,56 +3063,60 @@ impl Renderer {
                 self.profile_counters.draw_calls.inc();
                 stats.total_draw_calls += 1;
             }
         }
 
         self.profile_counters.vertices.add(6 * data.len());
     }
 
+    //TODO: make this nicer. Currently we can't accept `&mut self` because the `DrawTarget` parameter
+    // needs to borrow self.texture_resolver
     fn handle_blits(
-        &mut self,
+        gpu_profile: &mut GpuProfiler<GpuProfileTag>,
+        device: &mut Device,
+        texture_resolver: &TextureResolver,
         blits: &[BlitJob],
         render_tasks: &RenderTaskTree,
+        draw_target: DrawTarget,
+        content_origin: &DeviceIntPoint,
     ) {
         if blits.is_empty() {
             return;
         }
 
-        let _timer = self.gpu_profile.start_timer(GPU_TAG_BLIT);
+        let _timer = gpu_profile.start_timer(GPU_TAG_BLIT);
 
         // TODO(gw): For now, we don't bother batching these by source texture.
         //           If if ever shows up as an issue, we can easily batch them.
         for blit in blits {
-            let source_rect = match blit.source {
+            let (source, layer, source_rect) = match blit.source {
                 BlitJobSource::Texture(texture_id, layer, source_rect) => {
                     // A blit from a texture into this target.
-                    let texture = self.texture_resolver
-                        .resolve(&texture_id)
-                        .expect("BUG: invalid source texture");
-                    self.device.bind_read_target(ReadTarget::Texture { texture, layer: layer as usize });
-                    source_rect
+                    (texture_id, layer as usize, source_rect)
                 }
                 BlitJobSource::RenderTask(task_id) => {
                     // A blit from the child render task into this target.
                     // TODO(gw): Support R8 format here once we start
                     //           creating mips for alpha masks.
-                    let texture = self.texture_resolver
-                        .resolve(&TextureSource::PrevPassColor)
-                        .expect("BUG: invalid source texture");
                     let source = &render_tasks[task_id];
                     let (source_rect, layer) = source.get_target_rect();
-                    self.device.bind_read_target(ReadTarget::Texture { texture, layer: layer.0 });
-                    source_rect
+                    (TextureSource::PrevPassColor, layer.0, source_rect)
                 }
             };
             debug_assert_eq!(source_rect.size, blit.target_rect.size);
-            self.device.blit_render_target(
-                source_rect,
-                blit.target_rect,
+            let texture = texture_resolver
+                .resolve(&source)
+                .expect("BUG: invalid source texture");
+            let read_target = DrawTarget::Texture { texture, layer, with_depth: false };
+
+            device.bind_read_target(read_target.into());
+            device.blit_render_target(
+                read_target.to_framebuffer_rect(source_rect),
+                draw_target.to_framebuffer_rect(blit.target_rect.translate(&-content_origin.to_vector())),
                 TextureFilter::Linear,
             );
         }
     }
 
     fn handle_scaling(
         &mut self,
         scalings: &[ScalingInstance],
@@ -3119,17 +3151,17 @@ impl Renderer {
             stats,
         );
     }
 
     fn draw_color_target(
         &mut self,
         draw_target: DrawTarget,
         target: &ColorRenderTarget,
-        framebuffer_target_rect: DeviceIntRect,
+        content_origin: DeviceIntPoint,
         clear_color: Option<[f32; 4]>,
         clear_depth: Option<f32>,
         render_tasks: &RenderTaskTree,
         projection: &Transform3D<f32>,
         frame_id: GpuFrameId,
         stats: &mut RendererStats,
     ) {
         self.profile_counters.color_targets.inc();
@@ -3151,57 +3183,58 @@ impl Renderer {
             self.device.bind_draw_target(draw_target);
             self.device.disable_depth();
             self.set_blend(false, framebuffer_kind);
 
             if clear_depth.is_some() {
                 self.device.enable_depth_write();
             }
 
-            let clear_rect = if !draw_target.is_default() {
-                if self.enable_clear_scissor {
+            let clear_rect = match draw_target {
+                DrawTarget::Default { rect, total_size } if rect.origin == FramebufferIntPoint::zero() && rect.size == total_size => {
+                    // whole screen is covered, no need for scissor
+                    None
+                }
+                DrawTarget::Default { rect, .. } => {
+                    Some(rect)
+                }
+                DrawTarget::Texture { .. } if self.enable_clear_scissor => {
                     // TODO(gw): Applying a scissor rect and minimal clear here
                     // is a very large performance win on the Intel and nVidia
                     // GPUs that I have tested with. It's possible it may be a
                     // performance penalty on other GPU types - we should test this
                     // and consider different code paths.
                     //
                     // Note: The above measurements were taken when render
                     // target slices were minimum 2048x2048. Now that we size
                     // them adaptively, this may be less of a win (except perhaps
                     // on a mostly-unused last slice of a large texture array).
-                    Some(target.used_rect())
-                } else {
+                    Some(draw_target.to_framebuffer_rect(target.used_rect()))
+                }
+                DrawTarget::Texture { .. } => {
                     None
                 }
-            } else if framebuffer_target_rect == DeviceIntRect::new(DeviceIntPoint::zero(), draw_target.dimensions()) {
-                // whole screen is covered, no need for scissor
-                None
-            } else {
-                let mut rect = framebuffer_target_rect.to_i32();
-                // Note: `framebuffer_target_rect` needs a Y-flip before going to GL
-                // Note: at this point, the target rectangle is not guaranteed to be within the main framebuffer bounds
-                // but `clear_target_rect` is totally fine with negative origin, as long as width & height are positive
-                rect.origin.y = draw_target.dimensions().height as i32 - rect.origin.y - rect.size.height;
-                Some(rect)
             };
 
             self.device.clear_target(
                 clear_color,
                 clear_depth,
                 clear_rect,
             );
 
             if clear_depth.is_some() {
                 self.device.disable_depth_write();
             }
         }
 
         // Handle any blits from the texture cache to this target.
-        self.handle_blits(&target.blits, render_tasks);
+        Self::handle_blits(
+            &mut self.gpu_profile, &mut self.device, &self.texture_resolver,
+            &target.blits, render_tasks, draw_target, &content_origin,
+        );
 
         // Draw any blurs for this target.
         // Blurs are rendered as a standard 2-pass
         // separable implementation.
         // TODO(gw): In the future, consider having
         //           fast path blur shaders for common
         //           blur radii with fixed weights.
         if !target.vertical_blurs.is_empty() || !target.horizontal_blurs.is_empty() {
@@ -3255,17 +3288,17 @@ impl Renderer {
         for alpha_batch_container in &target.alpha_batch_containers {
             let uses_scissor = alpha_batch_container.task_scissor_rect.is_some() ||
                                !alpha_batch_container.regions.is_empty();
 
             if uses_scissor {
                 self.device.enable_scissor();
                 let scissor_rect = draw_target.build_scissor_rect(
                     alpha_batch_container.task_scissor_rect,
-                    framebuffer_target_rect,
+                    content_origin,
                 );
                 self.device.set_scissor_rect(scissor_rect)
             }
 
             if !alpha_batch_container.opaque_batches.is_empty() {
                 let _gl = self.gpu_profile.start_marker("opaque batches");
                 let opaque_sampler = self.gpu_profile.start_sampler(GPU_SAMPLER_TAG_OPAQUE);
                 self.set_blend(false, framebuffer_kind);
@@ -3291,17 +3324,17 @@ impl Renderer {
                     let _timer = self.gpu_profile.start_timer(batch.key.kind.sampler_tag());
 
                     iterate_regions(
                         &alpha_batch_container.regions,
                         |region| {
                             if let Some(region) = region {
                                 let scissor_rect = draw_target.build_scissor_rect(
                                     Some(region),
-                                    framebuffer_target_rect,
+                                    content_origin,
                                 );
                                 self.device.set_scissor_rect(scissor_rect);
                             }
 
                             self.draw_instanced_batch(
                                 &batch.instances,
                                 VertexArrayKind::Primitive,
                                 &batch.key.textures,
@@ -3369,17 +3402,17 @@ impl Renderer {
                     let _timer = self.gpu_profile.start_timer(batch.key.kind.sampler_tag());
 
                     iterate_regions(
                         &alpha_batch_container.regions,
                         |region| {
                             if let Some(region) = region {
                                 let scissor_rect = draw_target.build_scissor_rect(
                                     Some(region),
-                                    framebuffer_target_rect,
+                                    content_origin,
                                 );
                                 self.device.set_scissor_rect(scissor_rect);
                             }
 
                             self.draw_instanced_batch(
                                 &batch.instances,
                                 VertexArrayKind::Primitive,
                                 &batch.key.textures,
@@ -3427,46 +3460,38 @@ impl Renderer {
 
                 self.device.bind_read_target(draw_target.into());
 
                 for blit in &alpha_batch_container.tile_blits {
                     let texture = self.texture_resolver
                         .resolve(&blit.target.texture_id)
                         .expect("BUG: invalid target texture");
 
-                    self.device.bind_draw_target(DrawTarget::Texture {
+                    let blit_target = DrawTarget::Texture {
                         texture,
                         layer: blit.target.texture_layer as usize,
                         with_depth: false,
-                    });
-
-                    let mut src_rect = DeviceIntRect::new(
-                        blit.src_offset,
+                    };
+                    self.device.bind_draw_target(blit_target);
+
+                    let src_rect = draw_target.to_framebuffer_rect(DeviceIntRect::new(
+                        blit.src_offset - content_origin.to_vector(),
                         blit.size,
-                    );
+                    ));
 
                     let target_rect = blit.target.uv_rect.to_i32();
 
-                    let mut dest_rect = DeviceIntRect::new(
-                        DeviceIntPoint::new(
-                            blit.dest_offset.x + target_rect.origin.x,
-                            blit.dest_offset.y + target_rect.origin.y,
-                        ),
+                    let dest_rect = blit_target.to_framebuffer_rect(DeviceIntRect::new(
+                        blit.dest_offset + (target_rect.origin - content_origin),
                         blit.size,
-                    );
-
-                    // Modify the src/dest rects since we are blitting from the framebuffer
-                    src_rect.origin.y = draw_target.dimensions().height as i32 - src_rect.size.height - src_rect.origin.y;
-                    dest_rect.origin.y += dest_rect.size.height;
-                    dest_rect.size.height = -dest_rect.size.height;
-
-                    self.device.blit_render_target(
+                    ));
+
+                    self.device.blit_render_target_invert_y(
                         src_rect,
                         dest_rect,
-                        TextureFilter::Linear,
                     );
                 }
 
                 self.device.bind_draw_target(draw_target);
             }
         }
 
         // For any registered image outputs on this render target,
@@ -3487,25 +3512,22 @@ impl Renderer {
                     }
                     Entry::Occupied(mut entry) => {
                         let target = entry.get_mut();
                         target.last_access = frame_id;
                         target.fbo_id
                     }
                 };
                 let (src_rect, _) = render_tasks[output.task_id].get_target_rect();
-                let mut dest_rect = DeviceIntRect::new(DeviceIntPoint::zero(), output_size);
-
-                // Invert Y coordinates, to correctly convert between coordinate systems.
-                dest_rect.origin.y += dest_rect.size.height;
-                dest_rect.size.height *= -1;
-
                 self.device.bind_read_target(draw_target.into());
                 self.device.bind_external_draw_target(fbo_id);
-                self.device.blit_render_target(src_rect, dest_rect, TextureFilter::Linear);
+                self.device.blit_render_target_invert_y(
+                    draw_target.to_framebuffer_rect(src_rect.translate(&-content_origin.to_vector())),
+                    output_size.into(),
+                );
                 handler.unlock(output.pipeline_id);
             }
         }
     }
 
     /// Draw all the instances in a clip batcher list to the current target.
     fn draw_clip_batch_list(
         &mut self,
@@ -3595,27 +3617,27 @@ impl Renderer {
             // and consider different code paths.
 
             let zero_color = [0.0, 0.0, 0.0, 0.0];
             for &task_id in &target.zero_clears {
                 let (rect, _) = render_tasks[task_id].get_target_rect();
                 self.device.clear_target(
                     Some(zero_color),
                     None,
-                    Some(rect),
+                    Some(draw_target.to_framebuffer_rect(rect)),
                 );
             }
 
             let one_color = [1.0, 1.0, 1.0, 1.0];
             for &task_id in &target.one_clears {
                 let (rect, _) = render_tasks[task_id].get_target_rect();
                 self.device.clear_target(
                     Some(one_color),
                     None,
-                    Some(rect),
+                    Some(draw_target.to_framebuffer_rect(rect)),
                 );
             }
         }
 
         // Draw any blurs for this target.
         // Blurs are rendered as a standard 2-pass
         // separable implementation.
         // TODO(gw): In the future, consider having
@@ -3716,34 +3738,42 @@ impl Renderer {
 
         // Handle any Pathfinder glyphs.
         let stencil_page = self.stencil_glyphs(&target.glyphs, &projection, &target_size, stats);
 
         {
             let texture = self.texture_resolver
                 .resolve(&texture_source)
                 .expect("BUG: invalid target texture");
-            self.device.bind_draw_target(DrawTarget::Texture {
+            let draw_target = DrawTarget::Texture {
                 texture,
                 layer,
                 with_depth: false,
-            });
+            };
+            self.device.bind_draw_target(draw_target);
+
+            self.device.disable_depth();
+            self.device.disable_depth_write();
+            self.set_blend(false, FramebufferKind::Other);
+
+            for rect in &target.clears {
+                self.device.clear_target(
+                    Some([0.0, 0.0, 0.0, 0.0]),
+                    None,
+                    Some(draw_target.to_framebuffer_rect(*rect)),
+                );
+            }
+
+            // Handle any blits to this texture from child tasks.
+            Self::handle_blits(
+                &mut self.gpu_profile, &mut self.device, &self.texture_resolver,
+                &target.blits, render_tasks, draw_target, &DeviceIntPoint::zero(),
+            );
         }
 
-        self.device.disable_depth();
-        self.device.disable_depth_write();
-        self.set_blend(false, FramebufferKind::Other);
-
-        for rect in &target.clears {
-            self.device.clear_target(Some([0.0, 0.0, 0.0, 0.0]), None, Some(*rect));
-        }
-
-        // Handle any blits to this texture from child tasks.
-        self.handle_blits(&target.blits, render_tasks);
-
         // Draw any borders for this target.
         if !target.border_segments_solid.is_empty() ||
            !target.border_segments_complex.is_empty()
         {
             let _timer = self.gpu_profile.start_timer(GPU_TAG_CACHE_BORDER);
 
             self.set_blend(true, FramebufferKind::Other);
             self.set_blend_mode_premultiplied_alpha(FramebufferKind::Other);
@@ -4055,17 +4085,17 @@ impl Renderer {
 
         debug_assert!(self.texture_resolver.prev_pass_alpha.is_none());
         debug_assert!(self.texture_resolver.prev_pass_color.is_none());
     }
 
     fn draw_tile_frame(
         &mut self,
         frame: &mut Frame,
-        framebuffer_size: Option<DeviceIntSize>,
+        framebuffer_size: Option<FramebufferIntSize>,
         frame_id: GpuFrameId,
         stats: &mut RendererStats,
         fb_clear_color: Option<ColorF>,
         fb_clear_depth: Option<f32>,
     ) {
         let _gm = self.gpu_profile.start_marker("tile frame draw");
 
         if frame.passes.is_empty() {
@@ -4093,29 +4123,34 @@ impl Renderer {
                 &mut self.device,
             );
 
             match pass.kind {
                 RenderPassKind::MainFramebuffer(ref target) => {
                     if let Some(framebuffer_size) = framebuffer_size {
                         stats.color_target_count += 1;
 
+                        let offset = frame.content_origin.to_f32();
+                        let size = frame.framebuffer_rect.size.to_f32();
                         let projection = Transform3D::ortho(
-                            0.0,
-                            framebuffer_size.width as f32,
-                            framebuffer_size.height as f32,
-                            0.0,
+                            offset.x,
+                            offset.x + size.width,
+                            offset.y + size.height,
+                            offset.y,
                             ORTHO_NEAR_PLANE,
                             ORTHO_FAR_PLANE,
                         );
 
                         self.draw_color_target(
-                            DrawTarget::Default(framebuffer_size),
+                            DrawTarget::Default {
+                                rect: frame.framebuffer_rect,
+                                total_size: framebuffer_size,
+                            },
                             target,
-                            frame.inner_rect,
+                            frame.content_origin,
                             fb_clear_color.map(|color| color.to_array()),
                             fb_clear_depth,
                             &frame.render_tasks,
                             &projection,
                             frame_id,
                             stats,
                         );
                     }
@@ -4186,17 +4221,17 @@ impl Renderer {
                             Some(1.0)
                         } else {
                             None
                         };
 
                         self.draw_color_target(
                             draw_target,
                             target,
-                            frame.inner_rect,
+                            frame.content_origin,
                             Some([0.0, 0.0, 0.0, 0.0]),
                             clear_depth,
                             &frame.render_tasks,
                             &projection,
                             frame_id,
                             stats,
                         );
                     }
@@ -4306,17 +4341,17 @@ impl Renderer {
                         (*color).into(),
                         None,
                     );
                 }
             }
         }
     }
 
-    fn draw_render_target_debug(&mut self, framebuffer_size: DeviceIntSize) {
+    fn draw_render_target_debug(&mut self, framebuffer_size: FramebufferIntSize) {
         if !self.debug_flags.contains(DebugFlags::RENDER_TARGET_DBG) {
             return;
         }
 
         let debug_renderer = match self.debug.get_mut(&mut self.device) {
             Some(render) => render,
             None => return,
         };
@@ -4331,17 +4366,17 @@ impl Renderer {
             framebuffer_size,
             0,
             &|_| [0.0, 1.0, 0.0, 1.0], // Use green for all RTs.
         );
     }
 
     fn draw_zoom_debug(
         &mut self,
-        framebuffer_size: DeviceIntSize,
+        framebuffer_size: FramebufferIntSize,
     ) {
         if !self.debug_flags.contains(DebugFlags::ZOOM_DBG) {
             return;
         }
 
         let debug_renderer = match self.debug.get_mut(&mut self.device) {
             Some(render) => render,
             None => return,
@@ -4367,19 +4402,19 @@ impl Renderer {
         let target_rect = DeviceIntRect::new(
             DeviceIntPoint::new(
                 framebuffer_size.width - target_size.width - 64,
                 framebuffer_size.height - target_size.height - 64,
             ),
             target_size,
         );
 
-        let texture_rect = DeviceIntRect::new(
-            DeviceIntPoint::zero(),
-            source_rect.size,
+        let texture_rect = FramebufferIntRect::new(
+            FramebufferIntPoint::zero(),
+            FramebufferIntSize::from_untyped(&source_rect.size.to_untyped()),
         );
 
         debug_renderer.add_rect(
             &target_rect.inflate(1, 1),
             debug_colors::RED.into(),
         );
 
         if self.zoom_debug_texture.is_none() {
@@ -4392,54 +4427,43 @@ impl Renderer {
                 Some(RenderTargetInfo { has_depth: false }),
                 1,
             );
 
             self.zoom_debug_texture = Some(texture);
         }
 
         // Copy frame buffer into the zoom texture
-        self.device.bind_read_target(ReadTarget::Default);
+        let read_target = DrawTarget::new_default(framebuffer_size);
+        self.device.bind_read_target(read_target.into());
         self.device.bind_draw_target(DrawTarget::Texture {
             texture: self.zoom_debug_texture.as_ref().unwrap(),
             layer: 0,
             with_depth: false,
         });
         self.device.blit_render_target(
-            DeviceIntRect::new(
-                DeviceIntPoint::new(
-                    source_rect.origin.x,
-                    framebuffer_size.height - source_rect.size.height - source_rect.origin.y,
-                ),
-                source_rect.size,
-            ),
+            read_target.to_framebuffer_rect(source_rect),
             texture_rect,
             TextureFilter::Nearest,
         );
 
         // Draw the zoom texture back to the framebuffer
         self.device.bind_read_target(ReadTarget::Texture {
             texture: self.zoom_debug_texture.as_ref().unwrap(),
             layer: 0,
         });
-        self.device.bind_draw_target(DrawTarget::Default(framebuffer_size));
+        self.device.bind_draw_target(read_target);
         self.device.blit_render_target(
             texture_rect,
-            DeviceIntRect::new(
-                DeviceIntPoint::new(
-                    target_rect.origin.x,
-                    framebuffer_size.height - target_rect.size.height - target_rect.origin.y,
-                ),
-                target_rect.size,
-            ),
+            read_target.to_framebuffer_rect(target_rect),
             TextureFilter::Nearest,
         );
     }
 
-    fn draw_texture_cache_debug(&mut self, framebuffer_size: DeviceIntSize) {
+    fn draw_texture_cache_debug(&mut self, framebuffer_size: FramebufferIntSize) {
         if !self.debug_flags.contains(DebugFlags::TEXTURE_CACHE_DBG) {
             return;
         }
 
         let debug_renderer = match self.debug.get_mut(&mut self.device) {
             Some(render) => render,
             None => return,
         };
@@ -4464,17 +4488,17 @@ impl Renderer {
             &select_color,
         );
     }
 
     fn do_debug_blit(
         device: &mut Device,
         debug_renderer: &mut DebugRenderer,
         mut textures: Vec<&Texture>,
-        framebuffer_size: DeviceIntSize,
+        framebuffer_size: FramebufferIntSize,
         bottom: i32,
         select_color: &Fn(&Texture) -> [f32; 4],
     ) {
         let mut spacing = 16;
         let mut size = 512;
 
         let fb_width = framebuffer_size.width as i32;
         let fb_height = framebuffer_size.height as i32;
@@ -4494,39 +4518,45 @@ impl Renderer {
         // Note that the vec here is in increasing order, because the elements
         // get drawn right-to-left.
         textures.sort_by_key(|t| t.layer_size_in_bytes());
 
         let mut i = 0;
         for texture in textures.iter() {
             let y = spacing + bottom;
             let dimensions = texture.get_dimensions();
-            let src_rect = DeviceIntRect::new(
-                DeviceIntPoint::zero(),
-                DeviceIntSize::new(dimensions.width as i32, dimensions.height as i32),
+            let src_rect = FramebufferIntRect::new(
+                FramebufferIntPoint::zero(),
+                FramebufferIntSize::new(dimensions.width as i32, dimensions.height as i32),
             );
 
             let layer_count = texture.get_layer_count() as usize;
             for layer in 0 .. layer_count {
                 device.bind_read_target(ReadTarget::Texture { texture, layer});
 
                 let x = fb_width - (spacing + size) * (i as i32 + 1);
 
                 // If we have more targets than fit on one row in screen, just early exit.
                 if x > fb_width {
                     return;
                 }
 
+                //TODO: properly use FramebufferPixel coordinates
+
                 // Draw the info tag.
                 let text_margin = 1;
                 let text_height = 14; // Visually aproximated.
                 let tag_height = text_height + text_margin * 2;
                 let tag_rect = rect(x, y, size, tag_height);
                 let tag_color = select_color(texture);
-                device.clear_target(Some(tag_color), None, Some(tag_rect));
+                device.clear_target(
+                    Some(tag_color),
+                    None,
+                    Some(FramebufferIntRect::from_untyped(&tag_rect.to_untyped())),
+                );
 
                 // Draw the dimensions onto the tag.
                 let dim = texture.get_dimensions();
                 let mut text_rect = tag_rect;
                 text_rect.origin.y =
                     fb_height - text_rect.origin.y - text_rect.size.height; // Top-relative.
                 debug_renderer.add_text(
                     (x + text_margin) as f32,
@@ -4535,17 +4565,20 @@ impl Renderer {
                     ColorU::new(0, 0, 0, 255),
                     Some(text_rect.to_f32())
                 );
 
                 // Blit the contents of the layer. We need to invert Y because
                 // we're blitting from a texture to the main framebuffer, which
                 // use different conventions.
                 let dest_rect = rect(x, y + tag_height, size, size);
-                device.blit_render_target_invert_y(src_rect, dest_rect);
+                device.blit_render_target_invert_y(
+                    src_rect,
+                    FramebufferIntRect::from_untyped(&dest_rect),
+                );
                 i += 1;
             }
         }
     }
 
     fn draw_epoch_debug(&mut self) {
         if !self.debug_flags.contains(DebugFlags::EPOCHS) {
             return;
@@ -4578,17 +4611,17 @@ impl Renderer {
             y0 - margin,
             x0 + text_width + margin,
             y + margin,
             ColorU::new(25, 25, 25, 200),
             ColorU::new(51, 51, 51, 200),
         );
     }
 
-    fn draw_gpu_cache_debug(&mut self, framebuffer_size: DeviceIntSize) {
+    fn draw_gpu_cache_debug(&mut self, framebuffer_size: FramebufferIntSize) {
         if !self.debug_flags.contains(DebugFlags::GPU_CACHE_DBG) {
             return;
         }
 
         let debug_renderer = match self.debug.get_mut(&mut self.device) {
             Some(render) => render,
             None => return,
         };
@@ -4616,34 +4649,34 @@ impl Renderer {
                 y_off + chunk.address.v as f32 + 1.0,
                 color,
                 color,
             );
         }
     }
 
     /// Pass-through to `Device::read_pixels_into`, used by Gecko's WR bindings.
-    pub fn read_pixels_into(&mut self, rect: DeviceIntRect, format: ReadPixelsFormat, output: &mut [u8]) {
+    pub fn read_pixels_into(&mut self, rect: FramebufferIntRect, format: ReadPixelsFormat, output: &mut [u8]) {
         self.device.read_pixels_into(rect, format, output);
     }
 
-    pub fn read_pixels_rgba8(&mut self, rect: DeviceIntRect) -> Vec<u8> {
+    pub fn read_pixels_rgba8(&mut self, rect: FramebufferIntRect) -> Vec<u8> {
         let mut pixels = vec![0; (rect.size.width * rect.size.height * 4) as usize];
         self.device.read_pixels_into(rect, ReadPixelsFormat::Rgba8, &mut pixels);
         pixels
     }
 
-    pub fn read_gpu_cache(&mut self) -> (DeviceIntSize, Vec<u8>) {
+    pub fn read_gpu_cache(&mut self) -> (FramebufferIntSize, Vec<u8>) {
         let texture = self.gpu_cache_texture.texture.as_ref().unwrap();
-        let size = texture.get_dimensions();
+        let size = FramebufferIntSize::from_untyped(&texture.get_dimensions().to_untyped());
         let mut texels = vec![0; (size.width * size.height * 16) as usize];
         self.device.begin_frame();
         self.device.bind_read_target(ReadTarget::Texture { texture, layer: 0 });
         self.device.read_pixels_into(
-            DeviceIntRect::new(DeviceIntPoint::zero(), size),
+            size.into(),
             ReadPixelsFormat::Standard(ImageFormat::RGBAF32),
             &mut texels,
         );
         self.device.reset_read_target();
         self.device.end_frame();
         (size, texels)
     }
 
@@ -4825,17 +4858,17 @@ pub trait ExternalImageHandler {
 }
 
 /// Allows callers to receive a texture with the contents of a specific
 /// pipeline copied to it. Lock should return the native texture handle
 /// and the size of the texture. Unlock will only be called if the lock()
 /// call succeeds, when WR has issued the GL commands to copy the output
 /// to the texture handle.
 pub trait OutputImageHandler {
-    fn lock(&mut self, pipeline_id: PipelineId) -> Option<(u32, DeviceIntSize)>;
+    fn lock(&mut self, pipeline_id: PipelineId) -> Option<(u32, FramebufferIntSize)>;
     fn unlock(&mut self, pipeline_id: PipelineId);
 }
 
 pub trait ThreadListener {
     fn thread_started(&self, thread_name: &str);
     fn thread_stopped(&self, thread_name: &str);
 }
 
@@ -5026,16 +5059,17 @@ struct PlainTexture {
     filter: TextureFilter,
 }
 
 
 #[cfg(any(feature = "capture", feature = "replay"))]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 struct PlainRenderer {
+    framebuffer_size: Option<FramebufferIntSize>,
     gpu_cache: PlainTexture,
     gpu_cache_frame_id: FrameId,
     textures: FastHashMap<CacheTextureId, PlainTexture>,
     external_images: Vec<ExternalCaptureImage>
 }
 
 #[cfg(feature = "replay")]
 enum CapturedExternalImageData {
@@ -5060,17 +5094,17 @@ impl ExternalImageHandler for DummyExter
             }
         }
     }
     fn unlock(&mut self, _key: ExternalImageId, _channel_index: u8) {}
 }
 
 #[cfg(feature = "replay")]
 impl OutputImageHandler for () {
-    fn lock(&mut self, _: PipelineId) -> Option<(u32, DeviceIntSize)> {
+    fn lock(&mut self, _: PipelineId) -> Option<(u32, FramebufferIntSize)> {
         None
     }
     fn unlock(&mut self, _: PipelineId) {
         unreachable!()
     }
 }
 
 #[derive(Default)]
@@ -5086,56 +5120,55 @@ impl Renderer {
     ) -> PlainTexture {
         use std::fs;
         use std::io::Write;
 
         let short_path = format!("textures/{}.raw", name);
 
         let bytes_per_pixel = texture.get_format().bytes_per_pixel();
         let read_format = ReadPixelsFormat::Standard(texture.get_format());
-        let rect = DeviceIntRect::new(
-            DeviceIntPoint::zero(),
-            texture.get_dimensions(),
-        );
+        let rect_size = texture.get_dimensions();
 
         let mut file = fs::File::create(root.join(&short_path))
             .expect(&format!("Unable to create {}", short_path));
-        let bytes_per_layer = (rect.size.width * rect.size.height * bytes_per_pixel) as usize;
+        let bytes_per_layer = (rect_size.width * rect_size.height * bytes_per_pixel) as usize;
         let mut data = vec![0; bytes_per_layer];
 
         //TODO: instead of reading from an FBO with `read_pixels*`, we could
         // read from textures directly with `get_tex_image*`.
 
         for layer_id in 0 .. texture.get_layer_count() {
+            let rect = FramebufferIntSize::from_untyped(&rect_size.to_untyped()).into();
+
             device.attach_read_texture(texture, layer_id);
             #[cfg(feature = "png")]
             {
                 let mut png_data;
                 let (data_ref, format) = match texture.get_format() {
                     ImageFormat::RGBAF32 => {
-                        png_data = vec![0; (rect.size.width * rect.size.height * 4) as usize];
+                        png_data = vec![0; (rect_size.width * rect_size.height * 4) as usize];
                         device.read_pixels_into(rect, ReadPixelsFormat::Rgba8, &mut png_data);
                         (&png_data, ReadPixelsFormat::Rgba8)
                     }
                     fm => (&data, ReadPixelsFormat::Standard(fm)),
                 };
                 CaptureConfig::save_png(
                     root.join(format!("textures/{}-{}.png", name, layer_id)),
-                    rect.size, format,
+                    rect_size, format,
                     data_ref,
                 );
             }
             device.read_pixels_into(rect, read_format, &mut data);
             file.write_all(&data)
                 .unwrap();
         }
 
         PlainTexture {
             data: short_path,
-            size: (rect.size, texture.get_layer_count()),
+            size: (rect_size, texture.get_layer_count()),
             format: texture.get_format(),
             filter: texture.get_filter(),
         }
     }
 
     #[cfg(feature = "replay")]
     fn load_texture(
         target: TextureTarget,
@@ -5261,16 +5294,17 @@ impl Renderer {
             let path_textures = config.root.join("textures");
             if !path_textures.is_dir() {
                 fs::create_dir(&path_textures).unwrap();
             }
 
             info!("saving GPU cache");
             self.update_gpu_cache(); // flush pending updates
             let mut plain_self = PlainRenderer {
+                framebuffer_size: self.framebuffer_size,
                 gpu_cache: Self::save_texture(
                     &self.gpu_cache_texture.texture.as_ref().unwrap(),
                     "gpu", &config.root, &mut self.device,
                 ),
                 gpu_cache_frame_id: self.gpu_cache_frame_id,
                 textures: FastHashMap::default(),
                 external_images: deferred_images,
             };
@@ -5323,16 +5357,17 @@ impl Renderer {
             };
             let ext = plain_ext.external;
             let value = (CapturedExternalImageData::Buffer(data), plain_ext.uv);
             image_handler.data.insert((ext.id, ext.channel_index), value);
         }
 
         if let Some(renderer) = CaptureConfig::deserialize::<PlainRenderer, _>(&root, "renderer") {
             info!("loading cached textures");
+            self.framebuffer_size = renderer.framebuffer_size;
             self.device.begin_frame();
 
             for (_id, texture) in self.texture_resolver.texture_cache_map.drain() {
                 self.device.delete_texture(texture);
             }
             for (id, texture) in renderer.textures {
                 info!("\t{}", texture.data);
                 let t = Self::load_texture(
--- a/gfx/wr/webrender/src/tiling.rs
+++ b/gfx/wr/webrender/src/tiling.rs
@@ -1,15 +1,15 @@
 /* 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 api::{ColorF, BorderStyle, DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixelScale};
-use api::{DocumentLayer, FilterOp, FilterData, ImageFormat, DevicePoint};
-use api::{MixBlendMode, PipelineId, DeviceRect, LayoutSize, WorldRect};
+use api::{ColorF, BorderStyle, MixBlendMode, PipelineId};
+use api::{DocumentLayer, FilterData, FilterOp, ImageFormat};
+use api::units::*;
 use batch::{AlphaBatchBuilder, AlphaBatchContainer, ClipBatcher, resolve_image};
 use clip::ClipStore;
 use clip_scroll_tree::{ClipScrollTree};
 use debug_render::DebugItem;
 use device::{Texture};
 #[cfg(feature = "pathfinder")]
 use euclid::{TypedPoint2D, TypedVector2D};
 use frame_builder::FrameGlobalResources;
@@ -1127,19 +1127,20 @@ impl CompositeOps {
     }
 }
 
 /// A rendering-oriented representation of the frame built by the render backend
 /// and presented to the renderer.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct Frame {
-    //TODO: share the fields with DocumentView struct
-    pub window_size: DeviceIntSize,
-    pub inner_rect: DeviceIntRect,
+    /// The origin on content produced by the render tasks.
+    pub content_origin: DeviceIntPoint,
+    /// The rectangle to show the frame in, on screen.
+    pub framebuffer_rect: FramebufferIntRect,
     pub background_color: Option<ColorF>,
     pub layer: DocumentLayer,
     pub passes: Vec<RenderPass>,
     #[cfg_attr(any(feature = "capture", feature = "replay"), serde(default = "FrameProfileCounters::new", skip))]
     pub profile_counters: FrameProfileCounters,
 
     pub transform_palette: Vec<TransformData>,
     pub render_tasks: RenderTaskTree,
--- a/gfx/wr/webrender_api/src/api.rs
+++ b/gfx/wr/webrender_api/src/api.rs
@@ -194,31 +194,36 @@ impl Transaction {
     ///
     /// Not that notification requests are skipped during serialization, so is is
     /// best to use them for synchronization purposes and not for things that could
     /// affect the WebRender's state.
     pub fn notify(&mut self, event: NotificationRequest) {
         self.notifications.push(event);
     }
 
-    pub fn set_window_parameters(
+    /// Setup the output region in the framebuffer for a given document.
+    pub fn set_document_view(
         &mut self,
-        window_size: DeviceIntSize,
-        inner_rect: DeviceIntRect,
+        framebuffer_rect: FramebufferIntRect,
         device_pixel_ratio: f32,
     ) {
         self.scene_ops.push(
-            SceneMsg::SetWindowParameters {
-                window_size,
-                inner_rect,
+            SceneMsg::SetDocumentView {
+                framebuffer_rect,
                 device_pixel_ratio,
             },
         );
     }
 
+    /// Enable copying of the output of this pipeline id to
+    /// an external texture for callers to consume.
+    pub fn enable_frame_output(&mut self, pipeline_id: PipelineId, enable: bool) {
+        self.scene_ops.push(SceneMsg::EnableFrameOutput(pipeline_id, enable));
+    }
+
     /// Scrolls the scrolling layer under the `cursor`
     ///
     /// WebRender looks for the layer closest to the user
     /// which has `ScrollPolicy::Scrollable` set.
     pub fn scroll(&mut self, scroll_location: ScrollLocation, cursor: WorldPoint) {
         self.frame_ops.push(FrameMsg::Scroll(scroll_location, cursor));
     }
 
@@ -273,22 +278,16 @@ impl Transaction {
     /// Add to the list of animated property bindings that should be used to
     /// resolve bindings in the current display list. This is a convenience method
     /// so the caller doesn't have to figure out all the dynamic properties before
     /// setting them on the transaction but can do them incrementally.
     pub fn append_dynamic_properties(&mut self, properties: DynamicProperties) {
         self.frame_ops.push(FrameMsg::AppendDynamicProperties(properties));
     }
 
-    /// Enable copying of the output of this pipeline id to
-    /// an external texture for callers to consume.
-    pub fn enable_frame_output(&mut self, pipeline_id: PipelineId, enable: bool) {
-        self.frame_ops.push(FrameMsg::EnableFrameOutput(pipeline_id, enable));
-    }
-
     /// Consumes this object and just returns the frame ops.
     pub fn get_frame_ops(self) -> Vec<FrameMsg> {
         self.frame_ops
     }
 
     fn finalize(self) -> (TransactionMsg, Vec<Payload>) {
         (
             TransactionMsg {
@@ -580,70 +579,69 @@ pub struct AddFontInstance {
 
 // Frame messages affect building the scene.
 #[derive(Clone, Deserialize, Serialize)]
 pub enum SceneMsg {
     UpdateEpoch(PipelineId, Epoch),
     SetPageZoom(ZoomFactor),
     SetRootPipeline(PipelineId),
     RemovePipeline(PipelineId),
+    EnableFrameOutput(PipelineId, bool),
     SetDisplayList {
         list_descriptor: BuiltDisplayListDescriptor,
         epoch: Epoch,
         pipeline_id: PipelineId,
         background: Option<ColorF>,
         viewport_size: LayoutSize,
         content_size: LayoutSize,
         preserve_frame_state: bool,
     },
-    SetWindowParameters {
-        window_size: DeviceIntSize,
-        inner_rect: DeviceIntRect,
+    SetDocumentView {
+        framebuffer_rect: FramebufferIntRect,
         device_pixel_ratio: f32,
     },
 }
 
 // Frame messages affect frame generation (applied after building the scene).
 #[derive(Clone, Deserialize, Serialize)]
 pub enum FrameMsg {
     UpdateEpoch(PipelineId, Epoch),
     HitTest(Option<PipelineId>, WorldPoint, HitTestFlags, MsgSender<HitTestResult>),
     SetPan(DeviceIntPoint),
-    EnableFrameOutput(PipelineId, bool),
     Scroll(ScrollLocation, WorldPoint),
     ScrollNodeWithId(LayoutPoint, di::ExternalScrollId, ScrollClamping),
     GetScrollNodeState(MsgSender<Vec<ScrollNodeState>>),
     UpdateDynamicProperties(DynamicProperties),
     AppendDynamicProperties(DynamicProperties),
     SetPinchZoom(ZoomFactor),
 }
 
 impl fmt::Debug for SceneMsg {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         f.write_str(match *self {
             SceneMsg::UpdateEpoch(..) => "SceneMsg::UpdateEpoch",
             SceneMsg::SetDisplayList { .. } => "SceneMsg::SetDisplayList",
             SceneMsg::SetPageZoom(..) => "SceneMsg::SetPageZoom",
             SceneMsg::RemovePipeline(..) => "SceneMsg::RemovePipeline",
-            SceneMsg::SetWindowParameters { .. } => "SceneMsg::SetWindowParameters",
+            SceneMsg::EnableFrameOutput(..) => "SceneMsg::EnableFrameOutput",
+            SceneMsg::SetDocumentView { .. } => "SceneMsg::SetDocumentView",
             SceneMsg::SetRootPipeline(..) => "SceneMsg::SetRootPipeline",
         })
     }
 }
 
 impl fmt::Debug for FrameMsg {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         f.write_str(match *self {
             FrameMsg::UpdateEpoch(..) => "FrameMsg::UpdateEpoch",
             FrameMsg::HitTest(..) => "FrameMsg::HitTest",
             FrameMsg::SetPan(..) => "FrameMsg::SetPan",
             FrameMsg::Scroll(..) => "FrameMsg::Scroll",
             FrameMsg::ScrollNodeWithId(..) => "FrameMsg::ScrollNodeWithId",
             FrameMsg::GetScrollNodeState(..) => "FrameMsg::GetScrollNodeState",
-            FrameMsg::EnableFrameOutput(..) => "FrameMsg::EnableFrameOutput",
             FrameMsg::UpdateDynamicProperties(..) => "FrameMsg::UpdateDynamicProperties",
             FrameMsg::AppendDynamicProperties(..) => "FrameMsg::AppendDynamicProperties",
             FrameMsg::SetPinchZoom(..) => "FrameMsg::SetPinchZoom",
         })
     }
 }
 
 bitflags!{
@@ -670,17 +668,16 @@ bitflags!{
 }
 
 /// Information about a loaded capture of each document
 /// that is returned by `RenderBackend`.
 #[derive(Clone, Debug, Deserialize, Serialize)]
 pub struct CapturedDocument {
     pub document_id: DocumentId,
     pub root_pipeline_id: Option<PipelineId>,
-    pub window_size: DeviceIntSize,
 }
 
 #[derive(Clone, Deserialize, Serialize)]
 pub enum DebugCommand {
     /// Sets the provided debug flags.
     SetFlags(DebugFlags),
     /// Configure if dual-source blending is used, if available.
     EnableDualSourceBlending(bool),
@@ -722,17 +719,17 @@ pub enum ApiMsg {
     ),
     /// Gets the glyph indices from a string
     GetGlyphIndices(font::FontKey, String, MsgSender<Vec<Option<u32>>>),
     /// Adds a new document namespace.
     CloneApi(MsgSender<IdNamespace>),
     /// Adds a new document namespace.
     CloneApiByClient(IdNamespace),
     /// Adds a new document with given initial size.
-    AddDocument(DocumentId, DeviceIntSize, DocumentLayer),
+    AddDocument(DocumentId, FramebufferIntSize, DocumentLayer),
     /// A message targeted at a particular document.
     UpdateDocument(DocumentId, TransactionMsg),
     /// Deletes an existing document.
     DeleteDocument(DocumentId),
     /// An opaque handle that must be passed to the render notifier. It is used by Gecko
     /// to forward gecko-specific messages to the render thread preserving the ordering
     /// within the other messages.
     ExternalEvent(ExternalEvent),
@@ -1041,17 +1038,17 @@ impl RenderApi {
     pub fn get_namespace_id(&self) -> IdNamespace {
         self.namespace_id
     }
 
     pub fn clone_sender(&self) -> RenderApiSender {
         RenderApiSender::new(self.api_sender.clone(), self.payload_sender.clone())
     }
 
-    pub fn add_document(&self, initial_size: DeviceIntSize, layer: DocumentLayer) -> DocumentId {
+    pub fn add_document(&self, initial_size: FramebufferIntSize, layer: DocumentLayer) -> DocumentId {
         let new_id = self.next_unique_id();
         let document_id = DocumentId(self.namespace_id, new_id);
 
         let msg = ApiMsg::AddDocument(document_id, initial_size, layer);
         self.api_sender.send(msg).unwrap();
 
         document_id
     }
@@ -1219,29 +1216,45 @@ impl RenderApi {
 
         self.send_frame_msg(
             document_id,
             FrameMsg::HitTest(pipeline_id, point, flags, tx)
         );
         rx.recv().unwrap()
     }
 
-    pub fn set_window_parameters(
+    /// Setup the output region in the framebuffer for a given document.
+    pub fn set_document_view(
         &self,
         document_id: DocumentId,
-        window_size: DeviceIntSize,
-        inner_rect: DeviceIntRect,
+        framebuffer_rect: FramebufferIntRect,
         device_pixel_ratio: f32,
     ) {
         self.send_scene_msg(
             document_id,
-            SceneMsg::SetWindowParameters { window_size, inner_rect, device_pixel_ratio, },
+            SceneMsg::SetDocumentView { framebuffer_rect, device_pixel_ratio },
         );
     }
 
+    /// Setup the output region in the framebuffer for a given document.
+    /// Enable copying of the output of this pipeline id to
+    /// an external texture for callers to consume.
+    pub fn enable_frame_output(
+        &self,
+        document_id: DocumentId,
+        pipeline_id: PipelineId,
+        enable: bool,
+    ) {
+        self.send_scene_msg(
+            document_id,
+            SceneMsg::EnableFrameOutput(pipeline_id, enable),
+        );
+    }
+
+
     pub fn get_scroll_node_state(&self, document_id: DocumentId) -> Vec<ScrollNodeState> {
         let (tx, rx) = channel::msg_channel().unwrap();
         self.send_frame_msg(document_id, FrameMsg::GetScrollNodeState(tx));
         rx.recv().unwrap()
     }
 
     pub fn wake_scene_builder(&self) {
         self.send_message(ApiMsg::WakeSceneBuilder);
--- a/gfx/wr/webrender_api/src/lib.rs
+++ b/gfx/wr/webrender_api/src/lib.rs
@@ -43,18 +43,19 @@ use wr_malloc_size_of as malloc_size_of;
 mod api;
 pub mod channel;
 mod color;
 mod display_item;
 mod display_list;
 mod font;
 mod gradient_builder;
 mod image;
-mod units;
+pub mod units;
 
 pub use api::*;
 pub use color::*;
 pub use display_item::*;
 pub use display_list::*;
 pub use font::*;
 pub use gradient_builder::*;
 pub use image::*;
+//TODO: stop re-exporting this
 pub use units::*;
--- a/gfx/wr/webrender_api/src/units.rs
+++ b/gfx/wr/webrender_api/src/units.rs
@@ -31,16 +31,25 @@ pub type DeviceIntLength = Length<i32, D
 pub type DeviceIntSideOffsets = TypedSideOffsets2D<i32, DevicePixel>;
 
 pub type DeviceRect = TypedRect<f32, DevicePixel>;
 pub type DevicePoint = TypedPoint2D<f32, DevicePixel>;
 pub type DeviceVector2D = TypedVector2D<f32, DevicePixel>;
 pub type DeviceSize = TypedSize2D<f32, DevicePixel>;
 pub type DeviceHomogeneousVector = HomogeneousVector<f32, DevicePixel>;
 
+/// Geometry in the coordinate system of the framebuffer in physical pixels.
+/// It's Y-flipped comparing to DevicePixel.
+#[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
+pub struct FramebufferPixel;
+
+pub type FramebufferIntPoint = TypedPoint2D<i32, FramebufferPixel>;
+pub type FramebufferIntSize = TypedSize2D<i32, FramebufferPixel>;
+pub type FramebufferIntRect = TypedRect<i32, FramebufferPixel>;
+
 /// Geometry in the coordinate system of a Picture (intermediate
 /// surface) in physical pixels.
 #[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
 pub struct PicturePixel;
 
 pub type PictureIntRect = TypedRect<i32, PicturePixel>;
 pub type PictureIntPoint = TypedPoint2D<i32, PicturePixel>;
 pub type PictureIntSize = TypedSize2D<i32, PicturePixel>;
--- a/gfx/wr/wrench/src/main.rs
+++ b/gfx/wr/wrench/src/main.rs
@@ -174,40 +174,40 @@ impl WindowWrapper {
     fn swap_buffers(&self) {
         match *self {
             WindowWrapper::Window(ref window, _) => window.swap_buffers().unwrap(),
             WindowWrapper::Angle(_, ref context, _) => context.swap_buffers().unwrap(),
             WindowWrapper::Headless(_, _) => {}
         }
     }
 
-    fn get_inner_size(&self) -> DeviceIntSize {
-        fn inner_size(window: &winit::Window) -> DeviceIntSize {
+    fn get_inner_size(&self) -> FramebufferIntSize {
+        fn inner_size(window: &winit::Window) -> FramebufferIntSize {
             let size = window
                 .get_inner_size()
                 .unwrap()
                 .to_physical(window.get_hidpi_factor());
-            DeviceIntSize::new(size.width as i32, size.height as i32)
+            FramebufferIntSize::new(size.width as i32, size.height as i32)
         }
         match *self {
             WindowWrapper::Window(ref window, _) => inner_size(window.window()),
             WindowWrapper::Angle(ref window, ..) => inner_size(window),
-            WindowWrapper::Headless(ref context, _) => DeviceIntSize::new(context.width, context.height),
+            WindowWrapper::Headless(ref context, _) => FramebufferIntSize::new(context.width, context.height),
         }
     }
 
     fn hidpi_factor(&self) -> f32 {
         match *self {
             WindowWrapper::Window(ref window, _) => window.get_hidpi_factor() as f32,
             WindowWrapper::Angle(ref window, ..) => window.get_hidpi_factor() as f32,
             WindowWrapper::Headless(_, _) => 1.0,
         }
     }
 
-    fn resize(&mut self, size: DeviceIntSize) {
+    fn resize(&mut self, size: FramebufferIntSize) {
         match *self {
             WindowWrapper::Window(ref mut window, _) => {
                 window.set_inner_size(LogicalSize::new(size.width as f64, size.height as f64))
             },
             WindowWrapper::Angle(ref mut window, ..) => {
                 window.set_inner_size(LogicalSize::new(size.width as f64, size.height as f64))
             },
             WindowWrapper::Headless(_, _) => unimplemented!(), // requites Glutin update
@@ -235,17 +235,17 @@ impl WindowWrapper {
             WindowWrapper::Window(_, ref gl) |
             WindowWrapper::Angle(_, _, ref gl) |
             WindowWrapper::Headless(_, ref gl) => gl.clone(),
         }
     }
 }
 
 fn make_window(
-    size: DeviceIntSize,
+    size: FramebufferIntSize,
     dp_ratio: Option<f32>,
     vsync: bool,
     events_loop: &Option<winit::EventsLoop>,
     angle: bool,
 ) -> WindowWrapper {
     let wrapper = match *events_loop {
         Some(ref events_loop) => {
             let context_builder = glutin::ContextBuilder::new()
@@ -406,30 +406,30 @@ fn main() {
         "yaml" => wrench::SaveType::Yaml,
         "json" => wrench::SaveType::Json,
         "ron" => wrench::SaveType::Ron,
         "binary" => wrench::SaveType::Binary,
         _ => panic!("Save type must be json, ron, yaml, or binary")
     });
     let size = args.value_of("size")
         .map(|s| if s == "720p" {
-            DeviceIntSize::new(1280, 720)
+            FramebufferIntSize::new(1280, 720)
         } else if s == "1080p" {
-            DeviceIntSize::new(1920, 1080)
+            FramebufferIntSize::new(1920, 1080)
         } else if s == "4k" {
-            DeviceIntSize::new(3840, 2160)
+            FramebufferIntSize::new(3840, 2160)
         } else {
             let x = s.find('x').expect(
                 "Size must be specified exactly as 720p, 1080p, 4k, or width x height",
             );
             let w = s[0 .. x].parse::<i32>().expect("Invalid size width");
             let h = s[x + 1 ..].parse::<i32>().expect("Invalid size height");
-            DeviceIntSize::new(w, h)
+            FramebufferIntSize::new(w, h)
         })
-        .unwrap_or(DeviceIntSize::new(1920, 1080));
+        .unwrap_or(FramebufferIntSize::new(1920, 1080));
     let zoom_factor = args.value_of("zoom").map(|z| z.parse::<f32>().unwrap());
     let chase_primitive = match args.value_of("chase") {
         Some(s) => {
             let mut items = s
                 .split(',')
                 .map(|s| s.parse::<f32>().unwrap())
                 .collect::<Vec<_>>();
             let rect = LayoutRect::new(
@@ -523,29 +523,31 @@ fn main() {
     };
 
     wrench.renderer.deinit();
 }
 
 fn render<'a>(
     wrench: &mut Wrench,
     window: &mut WindowWrapper,
-    size: DeviceIntSize,
+    size: FramebufferIntSize,
     events_loop: &mut Option<winit::EventsLoop>,
     subargs: &clap::ArgMatches<'a>,
 ) {
     let input_path = subargs.value_of("INPUT").map(PathBuf::from).unwrap();
     let mut show_stats = false;
 
     // If the input is a directory, we are looking at a capture.
     let mut thing = if input_path.is_dir() {
         let mut documents = wrench.api.load_capture(input_path);
         println!("loaded {:?}", documents.iter().map(|cd| cd.document_id).collect::<Vec<_>>());
         let captured = documents.swap_remove(0);
-        window.resize(captured.window_size);
+        if let Some(fb_size) = wrench.renderer.framebuffer_size() {
+            window.resize(fb_size);
+        }
         wrench.document_id = captured.document_id;
         Box::new(captured) as Box<WrenchThing>
     } else {
         let extension = input_path
             .extension()
             .expect("Tried to render with an unknown file type.")
             .to_str()
             .expect("Tried to render with an unknown file type.");
@@ -763,18 +765,17 @@ fn render<'a>(
         }
 
         winit::ControlFlow::Continue
     };
 
     match *events_loop {
         None => {
             while body(wrench, vec![winit::Event::Awakened]) == winit::ControlFlow::Continue {}
-            let rect = DeviceIntRect::new(DeviceIntPoint::zero(), size);
-            let pixels = wrench.renderer.read_pixels_rgba8(rect);
+            let pixels = wrench.renderer.read_pixels_rgba8(size.into());
             save_flipped("screenshot.png", pixels, size);
         }
         Some(ref mut events_loop) => {
             // We want to ensure that we:
             //
             // (a) Block the thread when no events are present (for CPU/battery purposes)
             // (b) Don't lag the input events by having the event queue back up.
             loop {
--- a/gfx/wr/wrench/src/png.rs
+++ b/gfx/wr/wrench/src/png.rs
@@ -20,17 +20,17 @@ pub enum ReadSurface {
 pub struct SaveSettings {
     pub flip_vertical: bool,
     pub try_crop: bool,
 }
 
 pub fn save<P: Clone + AsRef<Path>>(
     path: P,
     orig_pixels: Vec<u8>,
-    size: DeviceIntSize,
+    size: FramebufferIntSize,
     settings: SaveSettings
 ) {
     let mut width = size.width as u32;
     let mut height = size.height as u32;
     let mut buffer = image::RgbaImage::from_raw(
         width,
         height,
         orig_pixels,
@@ -61,17 +61,17 @@ pub fn save<P: Clone + AsRef<Path>>(
     encoder
         .encode(&buffer, width, height, ColorType::RGBA(8))
         .expect("Unable to encode PNG!");
 }
 
 pub fn save_flipped<P: Clone + AsRef<Path>>(
     path: P,
     orig_pixels: Vec<u8>,
-    size: DeviceIntSize,
+    size: FramebufferIntSize,
 ) {
     save(path, orig_pixels, size, SaveSettings {
         flip_vertical: true,
         try_crop: true,
     })
 }
 
 pub fn png(
@@ -82,23 +82,22 @@ pub fn png(
     rx: Receiver<NotifierEvent>,
 ) {
     reader.do_frame(wrench);
 
     // wait for the frame
     rx.recv().unwrap();
     wrench.render();
 
-    let (device_size, data, settings) = match surface {
+    let (fb_size, data, settings) = match surface {
         ReadSurface::Screen => {
             let dim = window.get_inner_size();
-            let rect = DeviceIntRect::new(DeviceIntPoint::zero(), dim);
             let data = wrench.renderer
-                .read_pixels_rgba8(rect);
-            (rect.size, data, SaveSettings {
+                .read_pixels_rgba8(dim.into());
+            (dim, data, SaveSettings {
                 flip_vertical: true,
                 try_crop: true,
             })
         }
         ReadSurface::GpuCache => {
             let (size, data) = wrench.renderer
                 .read_gpu_cache();
             (size, data, SaveSettings {
@@ -106,10 +105,10 @@ pub fn png(
                 try_crop: false,
             })
         }
     };
 
     let mut out_path = reader.yaml_path().clone();
     out_path.set_extension("png");
 
-    save(out_path, data, device_size, settings);
+    save(out_path, data, fb_size, settings);
 }
--- a/gfx/wr/wrench/src/rawtest.rs
+++ b/gfx/wr/wrench/src/rawtest.rs
@@ -1,38 +1,27 @@
 /* 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 {WindowWrapper, NotifierEvent};
 use blob;
-use euclid::{TypedRect, TypedSize2D, TypedPoint2D, point2, size2};
+use euclid::{point2, size2, rect};
 use std::sync::Arc;
 use std::sync::atomic::{AtomicIsize, Ordering};
 use std::sync::mpsc::Receiver;
 use webrender::api::*;
 use wrench::Wrench;
 
 pub struct RawtestHarness<'a> {
     wrench: &'a mut Wrench,
     rx: &'a Receiver<NotifierEvent>,
     window: &'a mut WindowWrapper,
 }
 
-fn point<T: Copy, U>(x: T, y: T) -> TypedPoint2D<T, U> {
-    TypedPoint2D::new(x, y)
-}
-
-fn size<T: Copy, U>(x: T, y: T) -> TypedSize2D<T, U> {
-    TypedSize2D::new(x, y)
-}
-
-fn rect<T: Copy, U>(x: T, y: T, width: T, height: T) -> TypedRect<T, U> {
-    TypedRect::new(point(x, y), size(width, height))
-}
 
 impl<'a> RawtestHarness<'a> {
     pub fn new(wrench: &'a mut Wrench,
                window: &'a mut WindowWrapper,
                rx: &'a Receiver<NotifierEvent>) -> Self {
         RawtestHarness {
             wrench,
             rx,
@@ -51,17 +40,17 @@ impl<'a> RawtestHarness<'a> {
         self.test_insufficient_blob_visible_area();
         self.test_offscreen_blob();
         self.test_save_restore();
         self.test_blur_cache();
         self.test_capture();
         self.test_zero_height_window();
     }
 
-    fn render_and_get_pixels(&mut self, window_rect: DeviceIntRect) -> Vec<u8> {
+    fn render_and_get_pixels(&mut self, window_rect: FramebufferIntRect) -> Vec<u8> {
         self.rx.recv().unwrap();
         self.wrench.render();
         self.wrench.renderer.read_pixels_rgba8(window_rect)
     }
 
     fn submit_dl(
         &mut self,
         epoch: &mut Epoch,
@@ -108,18 +97,18 @@ impl<'a> RawtestHarness<'a> {
         );
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         let info = LayoutPrimitiveInfo::new(rect(0.0, 0.0, 64.0, 64.0));
 
         builder.push_image(
             &info,
             &SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id),
-            size(64.0, 64.0),
-            size(64.0, 64.0),
+            size2(64.0, 64.0),
+            size2(64.0, 64.0),
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
             img,
             ColorF::WHITE,
         );
 
         let mut epoch = Epoch(0);
 
@@ -137,18 +126,18 @@ impl<'a> RawtestHarness<'a> {
         );
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         let info = LayoutPrimitiveInfo::new(rect(0.0, 0.0, 1024.0, 1024.0));
 
         builder.push_image(
             &info,
             &SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id),
-            size(1024.0, 1024.0),
-            size(1024.0, 1024.0),
+            size2(1024.0, 1024.0),
+            size2(1024.0, 1024.0),
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
             img,
             ColorF::WHITE,
         );
 
         self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
         self.rx.recv().unwrap();
@@ -163,18 +152,18 @@ impl<'a> RawtestHarness<'a> {
         );
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         let info = LayoutPrimitiveInfo::new(rect(0.0, 0.0, 1024.0, 1024.0));
 
         builder.push_image(
             &info,
             &SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id),
-            size(1024.0, 1024.0),
-            size(1024.0, 1024.0),
+            size2(1024.0, 1024.0),
+            size2(1024.0, 1024.0),
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
             img,
             ColorF::WHITE,
         );
 
         self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
         self.rx.recv().unwrap();
@@ -202,18 +191,18 @@ impl<'a> RawtestHarness<'a> {
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
 
         let info = LayoutPrimitiveInfo::new(rect(448.899994, 74.0, 151.000031, 56.));
 
         // setup some malicious image size parameters
         builder.push_image(
             &info,
             &SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id),
-            size(151., 56.0),
-            size(151.0, 56.0),
+            size2(151., 56.0),
+            size2(151.0, 56.0),
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
             blob_img.as_image(),
             ColorF::WHITE,
         );
 
         let mut epoch = Epoch(0);
 
@@ -231,20 +220,20 @@ impl<'a> RawtestHarness<'a> {
 
     fn test_very_large_blob(&mut self) {
         println!("\tvery large blob...");
 
         assert_eq!(self.wrench.device_pixel_ratio, 1.);
 
         let window_size = self.window.get_inner_size();
 
-        let test_size = DeviceIntSize::new(800, 800);
+        let test_size = FramebufferIntSize::new(800, 800);
 
-        let window_rect = DeviceIntRect::new(
-            DeviceIntPoint::new(0, window_size.height - test_size.height),
+        let window_rect = FramebufferIntRect::new(
+            FramebufferIntPoint::new(0, window_size.height - test_size.height),
             test_size,
         );
 
         // This exposes a crash in tile decomposition
         let layout_size = LayoutSize::new(800., 800.);
         let mut txn = Transaction::new();
 
         let blob_img = self.wrench.api.generate_blob_image_key();
@@ -261,17 +250,17 @@ impl<'a> RawtestHarness<'a> {
         self.wrench.callbacks.lock().unwrap().request = Box::new(move |_| {
             called_inner.fetch_add(1, Ordering::SeqCst);
         });
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
 
         let info = LayoutPrimitiveInfo::new(rect(0., -9600.0, 1510.000031, 111256.));
 
-        let image_size = size(1510., 111256.);
+        let image_size = size2(1510., 111256.);
 
         let root_space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
         let clip_id = builder.define_clip(
             &root_space_and_clip,
             rect(40., 41., 200., 201.),
             vec![],
             None,
         );
@@ -350,23 +339,23 @@ impl<'a> RawtestHarness<'a> {
         // image. The only difference is that one of the display lists specifies a visible
         // area for its blob image which is too small, causing frame building to run into
         // missing tiles, and forcing it to exercise the code path where missing tiles are
         // rendered synchronously on demand.
 
         assert_eq!(self.wrench.device_pixel_ratio, 1.);
 
         let window_size = self.window.get_inner_size();
-        let test_size = DeviceIntSize::new(800, 800);
-        let window_rect = DeviceIntRect::new(
-            DeviceIntPoint::new(0, window_size.height - test_size.height),
+        let test_size = FramebufferIntSize::new(800, 800);
+        let window_rect = FramebufferIntRect::new(
+            point2(0, window_size.height - test_size.height),
             test_size,
         );
         let layout_size = LayoutSize::new(800.0, 800.0);
-        let image_size = size(800.0, 800.0);
+        let image_size = size2(800.0, 800.0);
         let info = LayoutPrimitiveInfo::new(rect(0.0, 0.0, 800.0, 800.0));
         let space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         let mut txn = Transaction::new();
 
         let blob_img1 = self.wrench.api.generate_blob_image_key();
         txn.add_blob_image(
@@ -443,20 +432,19 @@ impl<'a> RawtestHarness<'a> {
 
     fn test_offscreen_blob(&mut self) {
         println!("\toffscreen blob update.");
 
         assert_eq!(self.wrench.device_pixel_ratio, 1.);
 
         let window_size = self.window.get_inner_size();
 
-        let test_size = DeviceIntSize::new(800, 800);
-
-        let window_rect = DeviceIntRect::new(
-            DeviceIntPoint::new(0, window_size.height - test_size.height),
+        let test_size = FramebufferIntSize::new(800, 800);
+        let window_rect = FramebufferIntRect::new(
+            point2(0, window_size.height - test_size.height),
             test_size,
         );
         let space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
 
         // This exposes a crash in tile decomposition
         let mut txn = Transaction::new();
         let layout_size = LayoutSize::new(800., 800.);
 
@@ -467,17 +455,17 @@ impl<'a> RawtestHarness<'a> {
             blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
             None,
         );
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
 
         let info = LayoutPrimitiveInfo::new(rect(0., 0.0, 1510., 1510.));
 
-        let image_size = size(1510., 1510.);
+        let image_size = size2(1510., 1510.);
 
         // setup some malicious image size parameters
         builder.push_image(
             &info,
             &space_and_clip,
             image_size,
             image_size,
             ImageRendering::Auto,
@@ -493,17 +481,17 @@ impl<'a> RawtestHarness<'a> {
         let original_pixels = self.render_and_get_pixels(window_rect);
 
         let mut epoch = Epoch(1);
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
 
         let info = LayoutPrimitiveInfo::new(rect(-10000., 0.0, 1510., 1510.));
 
-        let image_size = size(1510., 1510.);
+        let image_size = size2(1510., 1510.);
 
         // setup some malicious image size parameters
         builder.push_image(
             &info,
             &space_and_clip,
             image_size,
             image_size,
             ImageRendering::Auto,
@@ -524,17 +512,17 @@ impl<'a> RawtestHarness<'a> {
             blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
             &rect(10, 10, 100, 100).into(),
         );
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
 
         let info = LayoutPrimitiveInfo::new(rect(0., 0.0, 1510., 1510.));
 
-        let image_size = size(1510., 1510.);
+        let image_size = size2(1510., 1510.);
 
         // setup some malicious image size parameters
         builder.push_image(
             &info,
             &space_and_clip,
             image_size,
             image_size,
             ImageRendering::Auto,
@@ -558,20 +546,19 @@ impl<'a> RawtestHarness<'a> {
         self.wrench.api.update_resources(txn.resource_updates);
     }
 
     fn test_retained_blob_images_test(&mut self) {
         println!("\tretained blob images test...");
         let blob_img;
         let window_size = self.window.get_inner_size();
 
-        let test_size = DeviceIntSize::new(400, 400);
-
-        let window_rect = DeviceIntRect::new(
-            DeviceIntPoint::new(0, window_size.height - test_size.height),
+        let test_size = FramebufferIntSize::new(400, 400);
+        let window_rect = FramebufferIntRect::new(
+            FramebufferIntPoint::new(0, window_size.height - test_size.height),
             test_size,
         );
         let layout_size = LayoutSize::new(400., 400.);
         let space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
 
         let mut txn = Transaction::new();
         {
             let api = &self.wrench.api;
@@ -594,18 +581,18 @@ impl<'a> RawtestHarness<'a> {
 
         // draw the blob the first time
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         let info = LayoutPrimitiveInfo::new(rect(0.0, 60.0, 200.0, 200.0));
 
         builder.push_image(
             &info,
             &space_and_clip,
-            size(200.0, 200.0),
-            size(0.0, 0.0),
+            size2(200.0, 200.0),
+            size2(0.0, 0.0),
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
             blob_img.as_image(),
             ColorF::WHITE,
         );
 
         let mut epoch = Epoch(0);
 
@@ -618,18 +605,18 @@ impl<'a> RawtestHarness<'a> {
         // draw the blob image a second time at a different location
 
         // make a new display list that refers to the first image
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         let info = LayoutPrimitiveInfo::new(rect(1.0, 60.0, 200.0, 200.0));
         builder.push_image(
             &info,
             &space_and_clip,
-            size(200.0, 200.0),
-            size(0.0, 0.0),
+            size2(200.0, 200.0),
+            size2(0.0, 0.0),
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
             blob_img.as_image(),
             ColorF::WHITE,
         );
 
         txn.resource_updates.clear();
 
@@ -650,20 +637,19 @@ impl<'a> RawtestHarness<'a> {
         *self.wrench.callbacks.lock().unwrap() = blob::BlobCallbacks::new();
     }
 
     fn test_blob_update_epoch_test(&mut self) {
         println!("\tblob update epoch test...");
         let (blob_img, blob_img2);
         let window_size = self.window.get_inner_size();
 
-        let test_size = DeviceIntSize::new(400, 400);
-
-        let window_rect = DeviceIntRect::new(
-            point(0, window_size.height - test_size.height),
+        let test_size = FramebufferIntSize::new(400, 400);
+        let window_rect = FramebufferIntRect::new(
+            point2(0, window_size.height - test_size.height),
             test_size,
         );
         let layout_size = LayoutSize::new(400., 400.);
         let space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
 
         let mut txn = Transaction::new();
         let (blob_img, blob_img2) = {
             let api = &self.wrench.api;
@@ -706,28 +692,28 @@ impl<'a> RawtestHarness<'a> {
         // create two blob images and draw them
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         let info = LayoutPrimitiveInfo::new(rect(0.0, 60.0, 200.0, 200.0));
         let info2 = LayoutPrimitiveInfo::new(rect(200.0, 60.0, 200.0, 200.0));
         let push_images = |builder: &mut DisplayListBuilder| {
             builder.push_image(
                 &info,
                 &space_and_clip,
-                size(200.0, 200.0),
-                size(0.0, 0.0),
+                size2(200.0, 200.0),
+                size2(0.0, 0.0),
                 ImageRendering::Auto,
                 AlphaType::PremultipliedAlpha,
                 blob_img.as_image(),
                 ColorF::WHITE,
             );
             builder.push_image(
                 &info2,
                 &space_and_clip,
-                size(200.0, 200.0),
-                size(0.0, 0.0),
+                size2(200.0, 200.0),
+                size2(0.0, 0.0),
                 ImageRendering::Auto,
                 AlphaType::PremultipliedAlpha,
                 blob_img2.as_image(),
                 ColorF::WHITE,
             );
         };
 
         push_images(&mut builder);
@@ -781,20 +767,19 @@ impl<'a> RawtestHarness<'a> {
         // cleanup
         *self.wrench.callbacks.lock().unwrap() = blob::BlobCallbacks::new();
     }
 
     fn test_blob_update_test(&mut self) {
         println!("\tblob update test...");
         let window_size = self.window.get_inner_size();
 
-        let test_size = DeviceIntSize::new(400, 400);
-
-        let window_rect = DeviceIntRect::new(
-            point(0, window_size.height - test_size.height),
+        let test_size = FramebufferIntSize::new(400, 400);
+        let window_rect = FramebufferIntRect::new(
+            point2(0, window_size.height - test_size.height),
             test_size,
         );
         let layout_size = LayoutSize::new(400., 400.);
         let space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
         let mut txn = Transaction::new();
 
         let blob_img = {
             let img = self.wrench.api.generate_blob_image_key();
@@ -809,18 +794,18 @@ impl<'a> RawtestHarness<'a> {
 
         // draw the blobs the first time
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         let info = LayoutPrimitiveInfo::new(rect(0.0, 60.0, 200.0, 200.0));
 
         builder.push_image(
             &info,
             &space_and_clip,
-            size(200.0, 200.0),
-            size(0.0, 0.0),
+            size2(200.0, 200.0),
+            size2(0.0, 0.0),
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
             blob_img.as_image(),
             ColorF::WHITE,
         );
 
         let mut epoch = Epoch(0);
 
@@ -837,18 +822,18 @@ impl<'a> RawtestHarness<'a> {
         );
 
         // make a new display list that refers to the first image
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         let info = LayoutPrimitiveInfo::new(rect(0.0, 60.0, 200.0, 200.0));
         builder.push_image(
             &info,
             &space_and_clip,
-            size(200.0, 200.0),
-            size(0.0, 0.0),
+            size2(200.0, 200.0),
+            size2(0.0, 0.0),
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
             blob_img.as_image(),
             ColorF::WHITE,
         );
 
         self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
         let pixels_second = self.render_and_get_pixels(window_rect);
@@ -863,18 +848,18 @@ impl<'a> RawtestHarness<'a> {
         );
 
         // make a new display list that refers to the first image
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         let info = LayoutPrimitiveInfo::new(rect(0.0, 60.0, 200.0, 200.0));
         builder.push_image(
             &info,
             &space_and_clip,
-            size(200.0, 200.0),
-            size(0.0, 0.0),
+            size2(200.0, 200.0),
+            size2(0.0, 0.0),
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
             blob_img.as_image(),
             ColorF::WHITE,
         );
 
         self.submit_dl(&mut epoch, layout_size, builder, &txn.resource_updates);
         let pixels_third = self.render_and_get_pixels(window_rect);
@@ -883,20 +868,19 @@ impl<'a> RawtestHarness<'a> {
         assert!(pixels_first != pixels_third);
     }
 
     // Ensures that content doing a save-restore produces the same results as not
     fn test_save_restore(&mut self) {
         println!("\tsave/restore...");
         let window_size = self.window.get_inner_size();
 
-        let test_size = DeviceIntSize::new(400, 400);
-
-        let window_rect = DeviceIntRect::new(
-            DeviceIntPoint::new(0, window_size.height - test_size.height),
+        let test_size = FramebufferIntSize::new(400, 400);
+        let window_rect = FramebufferIntRect::new(
+            point2(0, window_size.height - test_size.height),
             test_size,
         );
         let layout_size = LayoutSize::new(400., 400.);
 
         let mut do_test = |should_try_and_fail| {
             let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
 
             let spatial_id = SpatialId::root_scroll_node(self.wrench.root_pipeline_id);
@@ -979,20 +963,19 @@ impl<'a> RawtestHarness<'a> {
     }
 
     // regression test for #2769
     // "async scene building: cache collisions from reused picture ids"
     fn test_blur_cache(&mut self) {
         println!("\tblur cache...");
         let window_size = self.window.get_inner_size();
 
-        let test_size = DeviceIntSize::new(400, 400);
-
-        let window_rect = DeviceIntRect::new(
-            DeviceIntPoint::new(0, window_size.height - test_size.height),
+        let test_size = FramebufferIntSize::new(400, 400);
+        let window_rect = FramebufferIntRect::new(
+            point2(0, window_size.height - test_size.height),
             test_size,
         );
         let layout_size = LayoutSize::new(400., 400.);
         let space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
 
         let mut do_test = |shadow_is_red| {
             let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
             let shadow_color = if shadow_is_red {
@@ -1032,19 +1015,19 @@ impl<'a> RawtestHarness<'a> {
         assert_ne!(first, second);
     }
 
     fn test_capture(&mut self) {
         println!("\tcapture...");
         let path = "../captures/test";
         let layout_size = LayoutSize::new(400., 400.);
         let dim = self.window.get_inner_size();
-        let window_rect = DeviceIntRect::new(
-            point(0, dim.height - layout_size.height as i32),
-            size(layout_size.width as i32, layout_size.height as i32),
+        let window_rect = FramebufferIntRect::new(
+            point2(0, dim.height - layout_size.height as i32),
+            size2(layout_size.width as i32, layout_size.height as i32),
         );
 
         // 1. render some scene
 
         let mut txn = Transaction::new();
         let image = self.wrench.api.generate_image_key();
         txn.add_image(
             image,
@@ -1053,18 +1036,18 @@ impl<'a> RawtestHarness<'a> {
             None,
         );
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
 
         builder.push_image(
             &LayoutPrimitiveInfo::new(rect(300.0, 70.0, 150.0, 50.0)),
             &SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id),
-            size(150.0, 50.0),
-            size(0.0, 0.0),
+            size2(150.0, 50.0),
+            size2(0.0, 0.0),
             ImageRendering::Auto,
             AlphaType::PremultipliedAlpha,
             image,
             ColorF::WHITE,
         );
 
         let mut txn = Transaction::new();
 
@@ -1115,17 +1098,17 @@ impl<'a> RawtestHarness<'a> {
         let pixels2 = self.render_and_get_pixels(window_rect);
         assert!(pixels0 == pixels2);
     }
 
     fn test_zero_height_window(&mut self) {
         println!("\tzero height test...");
 
         let layout_size = LayoutSize::new(120.0, 0.0);
-        let window_size = DeviceIntSize::new(layout_size.width as i32, layout_size.height as i32);
+        let window_size = FramebufferIntSize::new(layout_size.width as i32, layout_size.height as i32);
         let doc_id = self.wrench.api.add_document(window_size, 1);
 
         let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
         let info = LayoutPrimitiveInfo::new(LayoutRect::new(LayoutPoint::zero(),
                                                             LayoutSize::new(100.0, 100.0)));
         builder.push_rect(
             &info,
             &SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id),
--- a/gfx/wr/wrench/src/reftest.rs
+++ b/gfx/wr/wrench/src/reftest.rs
@@ -116,17 +116,17 @@ impl Display for Reftest {
             self.op,
             self.reference.display()
         )
     }
 }
 
 struct ReftestImage {
     data: Vec<u8>,
-    size: DeviceIntSize,
+    size: FramebufferIntSize,
 }
 enum ReftestImageComparison {
     Equal,
     NotEqual {
         max_difference: usize,
         count_different: usize,
     },
 }
@@ -513,24 +513,24 @@ impl<'a> ReftestHarness<'a> {
 
     fn load_image(&mut self, filename: &Path, format: ImageFormat) -> ReftestImage {
         let file = BufReader::new(File::open(filename).unwrap());
         let img_raw = load_piston_image(file, format).unwrap();
         let img = img_raw.flipv().to_rgba();
         let size = img.dimensions();
         ReftestImage {
             data: img.into_raw(),
-            size: DeviceIntSize::new(size.0 as i32, size.1 as i32),
+            size: FramebufferIntSize::new(size.0 as i32, size.1 as i32),
         }
     }
 
     fn render_yaml(
         &mut self,
         filename: &Path,
-        size: DeviceIntSize,
+        size: FramebufferIntSize,
         font_render_mode: Option<FontRenderMode>,
         allow_mipmaps: bool,
     ) -> YamlRenderOutput {
         let mut reader = YamlFrameReader::new(filename);
         reader.set_font_render_mode(font_render_mode);
         reader.allow_mipmaps(allow_mipmaps);
         reader.do_frame(self.wrench);
 
@@ -543,17 +543,20 @@ impl<'a> ReftestHarness<'a> {
         let window_size = self.window.get_inner_size();
         assert!(
             size.width <= window_size.width &&
             size.height <= window_size.height,
             format!("size={:?} ws={:?}", size, window_size)
         );
 
         // taking the bottom left sub-rectangle
-        let rect = DeviceIntRect::new(DeviceIntPoint::new(0, window_size.height - size.height), size);
+        let rect = FramebufferIntRect::new(
+            FramebufferIntPoint::new(0, window_size.height - size.height),
+            size,
+        );
         let pixels = self.wrench.renderer.read_pixels_rgba8(rect);
         self.window.swap_buffers();
 
         let write_debug_images = false;
         if write_debug_images {
             let debug_path = filename.with_extension("yaml.png");
             save_flipped(debug_path, pixels.clone(), size);
         }
--- a/gfx/wr/wrench/src/wrench.rs
+++ b/gfx/wr/wrench/src/wrench.rs
@@ -138,17 +138,17 @@ impl WrenchThing for CapturedDocument {
                 wrench.refresh();
             }
         }
         0
     }
 }
 
 pub struct Wrench {
-    window_size: DeviceIntSize,
+    window_size: FramebufferIntSize,
     pub device_pixel_ratio: f32,
     page_zoom_factor: ZoomFactor,
 
     pub renderer: webrender::Renderer,
     pub api: RenderApi,
     pub document_id: DocumentId,
     pub root_pipeline_id: PipelineId,
 
@@ -166,17 +166,17 @@ pub struct Wrench {
 
 impl Wrench {
     pub fn new(
         window: &mut WindowWrapper,
         proxy: Option<EventsLoopProxy>,
         shader_override_path: Option<PathBuf>,
         dp_ratio: f32,
         save_type: Option<SaveType>,
-        size: DeviceIntSize,
+        size: FramebufferIntSize,
         do_rebuild: bool,
         no_subpixel_aa: bool,
         verbose: bool,
         no_scissor: bool,
         no_batch: bool,
         precache_shaders: bool,
         disable_dual_source_blending: bool,
         zoom_factor: f32,
@@ -506,17 +506,17 @@ impl Wrench {
 
     #[allow(dead_code)]
     pub fn delete_font_instance(&mut self, key: FontInstanceKey) {
         let mut txn = Transaction::new();
         txn.delete_font_instance(key);
         self.api.update_resources(txn.resource_updates);
     }
 
-    pub fn update(&mut self, dim: DeviceIntSize) {
+    pub fn update(&mut self, dim: FramebufferIntSize) {
         if dim != self.window_size {
             self.window_size = dim;
         }
     }
 
     pub fn begin_frame(&mut self) {
         self.frame_start_sender.push(time::SteadyTime::now());
     }