Bug 1589512 - Part 2 - Add example DirectComposite webrender integration. r=kvark
authorGlenn Watson <git@intuitionlibrary.com>
Wed, 23 Oct 2019 20:35:55 +0000
changeset 498770 e6cd2b2207bd4a3e0abe296852ac3f4cfc21a244
parent 498769 010fd924d606a8f8e1365b2a27b2cd0c0e6899fc
child 498771 42a2ce72610baccd3bfed14e182c1e8be3bed032
push id36725
push useraiakab@mozilla.com
push dateThu, 24 Oct 2019 03:54:06 +0000
treeherdermozilla-central@91756881b7ab [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskvark
bugs1589512
milestone72.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 1589512 - Part 2 - Add example DirectComposite webrender integration. r=kvark Differential Revision: https://phabricator.services.mozilla.com/D49869
gfx/wr/Cargo.lock
gfx/wr/Cargo.toml
gfx/wr/ci-scripts/linux-debug-tests.sh
gfx/wr/ci-scripts/macos-debug-tests.sh
gfx/wr/example-compositor/compositor-windows/Cargo.toml
gfx/wr/example-compositor/compositor-windows/build.rs
gfx/wr/example-compositor/compositor-windows/src/lib.cpp
gfx/wr/example-compositor/compositor-windows/src/lib.rs
gfx/wr/example-compositor/compositor/Cargo.toml
gfx/wr/example-compositor/compositor/src/main.rs
--- a/gfx/wr/Cargo.lock
+++ b/gfx/wr/Cargo.lock
@@ -233,16 +233,32 @@ dependencies = [
  "core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-graphics 0.17.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)",
  "objc 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
+name = "compositor"
+version = "0.1.0"
+dependencies = [
+ "compositor-windows 0.1.0",
+ "gleam 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "webrender 0.60.0",
+]
+
+[[package]]
+name = "compositor-windows"
+version = "0.1.0"
+dependencies = [
+ "cc 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
 name = "core-foundation"
 version = "0.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
--- a/gfx/wr/Cargo.toml
+++ b/gfx/wr/Cargo.toml
@@ -1,15 +1,16 @@
 [workspace]
 members = [
     "direct-composition",
     "examples",
     "webrender",
     "webrender_api",
     "wrench",
+    "example-compositor/compositor",
 ]
 
 [profile.release]
 debug = true
 panic = "abort"
 
 [profile.dev]
 panic = "abort"
--- a/gfx/wr/ci-scripts/linux-debug-tests.sh
+++ b/gfx/wr/ci-scripts/linux-debug-tests.sh
@@ -30,9 +30,10 @@ pushd wrench
 cargo build ${CARGOFLAGS} --features env_logger
 OPTIMIZED=0 python script/headless.py reftest
 popd
 
 pushd examples
 cargo build ${CARGOFLAGS}
 popd
 
-cargo test ${CARGOFLAGS} --all
+cargo test ${CARGOFLAGS} \
+    --all --exclude compositor-windows --exclude compositor
--- a/gfx/wr/ci-scripts/macos-debug-tests.sh
+++ b/gfx/wr/ci-scripts/macos-debug-tests.sh
@@ -36,9 +36,10 @@ popd
 pushd wrench
 cargo check ${CARGOFLAGS} --features env_logger
 popd
 
 pushd examples
 cargo check ${CARGOFLAGS}
 popd
 
-cargo test ${CARGOFLAGS} ${CARGOTESTFLAGS} --all
+cargo test ${CARGOFLAGS} ${CARGOTESTFLAGS} \
+    --all --exclude compositor-windows --exclude compositor
new file mode 100644
--- /dev/null
+++ b/gfx/wr/example-compositor/compositor-windows/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "compositor-windows"
+version = "0.1.0"
+authors = ["Glenn Watson <gw@intuitionlibrary.com>"]
+edition = "2018"
+license = "MPL-2.0"
+
+[build-dependencies]
+cc = "1.0"
new file mode 100644
--- /dev/null
+++ b/gfx/wr/example-compositor/compositor-windows/build.rs
@@ -0,0 +1,24 @@
+/* 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/. */
+
+fn main() {
+    // HACK - This build script relies on Gecko having been built, so that the ANGLE libraries
+    //        have already been compiled. It also assumes they are being built with an in-tree
+    //        x86_64 object directory.
+
+    cc::Build::new()
+        .file("src/lib.cpp")
+        .include("../../../angle/checkout/include")
+        .compile("windows");
+
+    // Set up linker paths for ANGLE that is built by Gecko
+    println!("cargo:rustc-link-search=../../obj-x86_64-pc-mingw32/gfx/angle/targets/libEGL");
+    println!("cargo:rustc-link-search=../../obj-x86_64-pc-mingw32/gfx/angle/targets/libGLESv2");
+
+    // Link to libEGL and libGLESv2 (ANGLE) and D3D11 + DirectComposition
+    println!("cargo:rustc-link-lib=libEGL");
+    println!("cargo:rustc-link-lib=libGLESv2");
+    println!("cargo:rustc-link-lib=dcomp");
+    println!("cargo:rustc-link-lib=d3d11");
+}
new file mode 100644
--- /dev/null
+++ b/gfx/wr/example-compositor/compositor-windows/src/lib.cpp
@@ -0,0 +1,470 @@
+/* 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/. */
+
+#define UNICODE
+
+#include <windows.h>
+#include <math.h>
+#include <dcomp.h>
+#include <d3d11.h>
+#include <assert.h>
+#include <map>
+
+#define EGL_EGL_PROTOTYPES 1
+#define EGL_EGLEXT_PROTOTYPES 1
+#include "EGL/egl.h"
+#include "EGL/eglext.h"
+#include "EGL/eglext_angle.h"
+#include "GL/gl.h"
+
+// The OS compositor representation of a picture cache tile.
+struct Tile {
+    // Represents the underlying DirectComposition surface texture that gets drawn into.
+    IDCompositionSurface *pSurface;
+    // Represents the node in the visual tree that defines the properties of this tile (clip, position etc).
+    IDCompositionVisual *pVisual;
+};
+
+struct Window {
+    // Win32 window details
+    HWND hWnd;
+    HINSTANCE hInstance;
+    int width;
+    int height;
+    bool enable_compositor;
+    RECT client_rect;
+
+    // Main interfaces to D3D11 and DirectComposition
+    ID3D11Device *pD3D11Device;
+    IDCompositionDevice *pDCompDevice;
+    IDCompositionTarget *pDCompTarget;
+    IDXGIDevice *pDXGIDevice;
+
+    // ANGLE interfaces that wrap the D3D device
+    EGLDeviceEXT EGLDevice;
+    EGLDisplay EGLDisplay;
+    EGLContext EGLContext;
+    EGLConfig config;
+    // Framebuffer surface for debug mode when we are not using DC
+    EGLSurface fb_surface;
+
+    // The currently bound surface, valid during bind() and unbind()
+    EGLSurface current_surface;
+    IDCompositionSurface *pCurrentSurface;
+
+    // The root of the DC visual tree. Nothing is drawn on this, but
+    // all child tiles are parented to here.
+    IDCompositionVisual *pRoot;
+    // Maps the WR surface IDs to the DC representation of each tile.
+    std::map<uint64_t, Tile> tiles;
+};
+
+static const wchar_t *CLASS_NAME = L"WR DirectComposite";
+
+static LRESULT CALLBACK WndProc(
+    HWND hwnd,
+    UINT message,
+    WPARAM wParam,
+    LPARAM lParam
+) {
+    switch (message) {
+        case WM_DESTROY:
+            PostQuitMessage(0);
+            return 1;
+    }
+
+    return DefWindowProc(hwnd, message, wParam, lParam);
+}
+
+extern "C" {
+    Window *com_dc_create_window(int width, int height, bool enable_compositor) {
+        // Create a simple Win32 window
+        Window *window = new Window;
+        window->hInstance = GetModuleHandle(NULL);
+        window->width = width;
+        window->height = height;
+        window->enable_compositor = enable_compositor;
+
+        WNDCLASSEX wcex = { sizeof(WNDCLASSEX) };
+        wcex.style = CS_HREDRAW | CS_VREDRAW;
+        wcex.lpfnWndProc = WndProc;
+        wcex.cbClsExtra = 0;
+        wcex.cbWndExtra = 0;
+        wcex.hInstance = window->hInstance;
+        wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);;
+        wcex.lpszMenuName = nullptr;
+        wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
+        wcex.lpszClassName = CLASS_NAME;
+        RegisterClassEx(&wcex);
+
+        int dpiX = 0;
+        int dpiY = 0;
+        HDC hdc = GetDC(NULL);
+        if (hdc) {
+            dpiX = GetDeviceCaps(hdc, LOGPIXELSX);
+            dpiY = GetDeviceCaps(hdc, LOGPIXELSY);
+            ReleaseDC(NULL, hdc);
+        }
+
+        window->hWnd = CreateWindow(
+            CLASS_NAME,
+            L"DirectComposition Demo Application",
+            WS_OVERLAPPEDWINDOW,
+            CW_USEDEFAULT,
+            CW_USEDEFAULT,
+            static_cast<UINT>(ceil(float(width) * dpiX / 96.f)),
+            static_cast<UINT>(ceil(float(height) * dpiY / 96.f)),
+            NULL,
+            NULL,
+            window->hInstance,
+            NULL
+        );
+
+        ShowWindow(window->hWnd, SW_SHOWNORMAL);
+        UpdateWindow(window->hWnd);
+        GetClientRect(window->hWnd, &window->client_rect);
+
+        // Create a D3D11 device
+        D3D_FEATURE_LEVEL featureLevelSupported;
+        HRESULT hr = D3D11CreateDevice(
+            nullptr,
+            D3D_DRIVER_TYPE_HARDWARE,
+            NULL,
+            D3D11_CREATE_DEVICE_BGRA_SUPPORT,
+            NULL,
+            0,
+            D3D11_SDK_VERSION,
+            &window->pD3D11Device,
+            &featureLevelSupported,
+            nullptr
+        );
+        assert(SUCCEEDED(hr));
+
+        hr = window->pD3D11Device->QueryInterface(&window->pDXGIDevice);
+        assert(SUCCEEDED(hr));
+
+        // Create a DirectComposition device
+        hr = DCompositionCreateDevice(
+            window->pDXGIDevice,
+            __uuidof(IDCompositionDevice),
+            (void **) &window->pDCompDevice
+        );
+        assert(SUCCEEDED(hr));
+
+        // Create a DirectComposition target for a Win32 window handle
+        hr = window->pDCompDevice->CreateTargetForHwnd(
+            window->hWnd,
+            TRUE,
+            &window->pDCompTarget
+        );
+        assert(SUCCEEDED(hr));
+
+        // Create an ANGLE EGL device that wraps D3D11
+        window->EGLDevice = eglCreateDeviceANGLE(
+            EGL_D3D11_DEVICE_ANGLE,
+            window->pD3D11Device,
+            nullptr
+        );
+
+        EGLint display_attribs[] = {
+            EGL_NONE
+        };
+
+        window->EGLDisplay = eglGetPlatformDisplayEXT(
+            EGL_PLATFORM_DEVICE_EXT,
+            window->EGLDevice,
+            display_attribs
+        );
+
+        eglInitialize(
+            window->EGLDisplay,
+            nullptr,
+            nullptr
+        );
+
+        EGLint num_configs = 0;
+        EGLint cfg_attribs[] = {
+            EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+            EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+            EGL_RED_SIZE, 8,
+            EGL_GREEN_SIZE, 8,
+            EGL_BLUE_SIZE, 8,
+            EGL_ALPHA_SIZE, 8,
+            EGL_DEPTH_SIZE, 24,
+            EGL_NONE
+        };
+        EGLConfig configs[32];
+
+        eglChooseConfig(
+            window->EGLDisplay,
+            cfg_attribs,
+            configs,
+            sizeof(configs) / sizeof(EGLConfig),
+            &num_configs
+        );
+        assert(num_configs > 0);
+        window->config = configs[0];
+
+        if (window->enable_compositor) {
+            window->fb_surface = EGL_NO_SURFACE;
+        } else {
+            window->fb_surface = eglCreateWindowSurface(
+                window->EGLDisplay,
+                window->config,
+                window->hWnd,
+                NULL
+            );
+            assert(window->fb_surface != EGL_NO_SURFACE);
+        }
+
+        EGLint ctx_attribs[] = {
+            EGL_CONTEXT_CLIENT_VERSION, 3,
+            EGL_NONE
+        };
+
+        // Create an EGL context that can be used for drawing
+        window->EGLContext = eglCreateContext(
+            window->EGLDisplay,
+            window->config,
+            EGL_NO_CONTEXT,
+            ctx_attribs
+        );
+
+        // Create the root of the DirectComposition visual tree
+        hr = window->pDCompDevice->CreateVisual(&window->pRoot);
+        assert(SUCCEEDED(hr));
+        hr = window->pDCompTarget->SetRoot(window->pRoot);
+        assert(SUCCEEDED(hr));
+
+        EGLBoolean ok = eglMakeCurrent(
+            window->EGLDisplay,
+            window->fb_surface,
+            window->fb_surface,
+            window->EGLContext
+        );
+        assert(ok);
+
+        return window;
+    }
+
+    void com_dc_destroy_window(Window *window) {
+        for (auto it=window->tiles.begin() ; it != window->tiles.end() ; ++it) {
+            it->second.pSurface->Release();
+            it->second.pVisual->Release();
+        }
+
+        if (window->fb_surface != EGL_NO_SURFACE) {
+            eglDestroySurface(window->EGLDisplay, window->fb_surface);
+        }
+        eglDestroyContext(window->EGLDisplay, window->EGLContext);
+        eglTerminate(window->EGLDisplay);
+        eglReleaseDeviceANGLE(window->EGLDevice);
+
+        window->pRoot->Release();
+        window->pD3D11Device->Release();
+        window->pDXGIDevice->Release();
+        window->pDCompDevice->Release();
+        window->pDCompTarget->Release();
+
+        CloseWindow(window->hWnd);
+        UnregisterClass(CLASS_NAME, window->hInstance);
+
+        delete window;
+    }
+
+    bool com_dc_tick(Window *window) {
+        // Check and dispatch the windows event loop
+        MSG msg;
+        while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
+            if (msg.message == WM_QUIT) {
+                return false;
+            }
+
+            TranslateMessage(&msg);
+            DispatchMessage(&msg);
+        }
+
+        return true;
+    }
+
+    void com_dc_swap_buffers(Window *window) {
+        // If not using DC mode, then do a normal EGL swap buffers.
+        if (window->fb_surface != EGL_NO_SURFACE) {
+            eglSwapBuffers(window->EGLDisplay, window->fb_surface);
+        }
+    }
+
+    // Create a new DC surface
+    void com_dc_create_surface(
+        Window *window,
+        uint64_t id,
+        int width,
+        int height
+    ) {
+        assert(window->tiles.count(id) == 0);
+
+        Tile tile;
+
+        // Create the video memory surface.
+        // TODO(gw): We should set alpha mode appropriately so that DC
+        //           can do opaque composites when possible!
+        HRESULT hr = window->pDCompDevice->CreateSurface(
+            width,
+            height,
+            DXGI_FORMAT_B8G8R8A8_UNORM,
+            DXGI_ALPHA_MODE_PREMULTIPLIED,
+            &tile.pSurface
+        );
+        assert(SUCCEEDED(hr));
+
+        // Create the visual node in the DC tree that stores properties
+        hr = window->pDCompDevice->CreateVisual(&tile.pVisual);
+        assert(SUCCEEDED(hr));
+
+        // Bind the surface memory to this visual
+        hr = tile.pVisual->SetContent(tile.pSurface);
+        assert(SUCCEEDED(hr));
+
+        window->tiles[id] = tile;
+    }
+
+    void com_dc_destroy_surface(
+        Window *window,
+        uint64_t id
+    ) {
+        assert(window->tiles.count(id) == 1);
+
+        // Release the video memory and visual in the tree
+        Tile &tile = window->tiles[id];
+        tile.pVisual->Release();
+        tile.pSurface->Release();
+
+        window->tiles.erase(id);
+    }
+
+    // Bind a DC surface to allow issuing GL commands to it
+    void com_dc_bind_surface(
+        Window *window,
+        uint64_t id,
+        int *x_offset,
+        int *y_offset
+    ) {
+        assert(window->tiles.count(id) == 1);
+        Tile &tile = window->tiles[id];
+
+        // Store the current surface for unbinding later
+        window->pCurrentSurface = tile.pSurface;
+
+        // Inform DC that we want to draw on this surface. DC uses texture
+        // atlases when the tiles are small. It returns an offset where the
+        // client code must draw into this surface when this happens.
+        POINT offset;
+        D3D11_TEXTURE2D_DESC desc;
+        ID3D11Texture2D *pTexture;
+        HRESULT hr = tile.pSurface->BeginDraw(
+            NULL,
+            __uuidof(ID3D11Texture2D),
+            (void **) &pTexture,
+            &offset
+        );
+        assert(SUCCEEDED(hr));
+        pTexture->GetDesc(&desc);
+
+        // Construct an EGL off-screen surface that is bound to the DC surface
+        EGLint buffer_attribs[] = {
+            EGL_WIDTH, desc.Width,
+            EGL_HEIGHT, desc.Height,
+            EGL_FLEXIBLE_SURFACE_COMPATIBILITY_SUPPORTED_ANGLE, EGL_TRUE,
+            EGL_NONE
+        };
+
+        window->current_surface = eglCreatePbufferFromClientBuffer(
+            window->EGLDisplay,
+            EGL_D3D_TEXTURE_ANGLE,
+            pTexture,
+            window->config,
+            buffer_attribs
+        );
+        assert(window->current_surface != EGL_NO_SURFACE);
+
+        // Make EGL current on the DC surface
+        EGLBoolean ok = eglMakeCurrent(
+            window->EGLDisplay,
+            window->current_surface,
+            window->current_surface,
+            window->EGLContext
+        );
+        assert(ok);
+
+        *x_offset = offset.x;
+        *y_offset = offset.y;
+    }
+
+    // Unbind a currently bound DC surface
+    void com_dc_unbind_surface(Window *window) {
+        HRESULT hr = window->pCurrentSurface->EndDraw();
+        assert(SUCCEEDED(hr));
+
+        eglDestroySurface(window->EGLDisplay, window->current_surface);
+    }
+
+    // At the start of a transaction, remove all visuals from the tree.
+    // TODO(gw): This is super simple, maybe it has performance implications
+    //           and we should mutate the visual tree instead of rebuilding
+    //           it each composition?
+    void com_dc_begin_transaction(Window *window) {
+        HRESULT hr = window->pRoot->RemoveAllVisuals();
+        assert(SUCCEEDED(hr));
+    }
+
+    // Add a DC surface to the visual tree. Called per-frame to build the composition.
+    void com_dc_add_surface(
+        Window *window,
+        uint64_t id,
+        int x,
+        int y,
+        int clip_x,
+        int clip_y,
+        int clip_w,
+        int clip_h
+    ) {
+        Tile &tile = window->tiles[id];
+
+        // Add this visual as the last element in the visual tree (z-order is implicit,
+        // based on the order tiles are added).
+        HRESULT hr = window->pRoot->AddVisual(
+            tile.pVisual,
+            FALSE,
+            NULL
+        );
+        assert(SUCCEEDED(hr));
+
+        // Place the visual - this changes frame to frame based on scroll position
+        // of the slice.
+        int offset_x = x + window->client_rect.left;
+        int offset_y = y + window->client_rect.top;
+        tile.pVisual->SetOffsetX(offset_x);
+        tile.pVisual->SetOffsetY(offset_y);
+
+        // Set the clip rect - converting from world space to the pre-offset space
+        // that DC requires for rectangle clips.
+        D2D_RECT_F clip_rect;
+        clip_rect.left = clip_x - offset_x;
+        clip_rect.top = clip_y - offset_y;
+        clip_rect.right = clip_rect.left + clip_w;
+        clip_rect.bottom = clip_rect.top + clip_h;
+        tile.pVisual->SetClip(clip_rect);
+    }
+
+    // Finish the composition transaction, telling DC to composite
+    void com_dc_end_transaction(Window *window) {
+        HRESULT hr = window->pDCompDevice->Commit();
+        assert(SUCCEEDED(hr));
+    }
+
+    // Get a pointer to an EGL symbol
+    void *com_dc_get_proc_address(const char *name) {
+        return eglGetProcAddress(name);
+    }
+}
new file mode 100644
--- /dev/null
+++ b/gfx/wr/example-compositor/compositor-windows/src/lib.rs
@@ -0,0 +1,184 @@
+/* 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 std::os::raw::{c_void, c_char};
+
+/*
+
+  This is a very simple (and unsafe!) rust wrapper for the DirectComposite / D3D11 / ANGLE
+  implementation in lib.cpp.
+
+  It just proxies the calls from the Compositor impl to the C99 code. This is very
+  hacky and not suitable for production!
+
+ */
+
+// Opaque wrapper for the Window type in lib.cpp
+#[repr(C)]
+pub struct Window {
+    _unused: [u8; 0]
+}
+
+// C99 functions that do the compositor work
+extern {
+    fn com_dc_create_window(width: i32, height: i32, enable_compositor: bool) -> *mut Window;
+    fn com_dc_destroy_window(window: *mut Window);
+    fn com_dc_tick(window: *mut Window) -> bool;
+    fn com_dc_get_proc_address(name: *const c_char) -> *const c_void;
+    fn com_dc_swap_buffers(window: *mut Window);
+
+    fn com_dc_create_surface(
+        window: *mut Window,
+        id: u64,
+        width: i32,
+        height: i32,
+    );
+
+    fn com_dc_destroy_surface(
+        window: *mut Window,
+        id: u64,
+    );
+
+    fn com_dc_bind_surface(
+        window: *mut Window,
+        id: u64,
+        x_offset: &mut i32,
+        y_offset: &mut i32,
+    );
+    fn com_dc_unbind_surface(window: *mut Window);
+
+    fn com_dc_begin_transaction(window: *mut Window);
+
+    fn com_dc_add_surface(
+        window: *mut Window,
+        id: u64,
+        x: i32,
+        y: i32,
+        clip_x: i32,
+        clip_y: i32,
+        clip_w: i32,
+        clip_h: i32,
+    );
+
+    fn com_dc_end_transaction(window: *mut Window);
+}
+
+pub fn create_window(width: i32, height: i32, enable_compositor: bool) -> *mut Window {
+    unsafe {
+        com_dc_create_window(width, height, enable_compositor)
+    }
+}
+
+pub fn destroy_window(window: *mut Window) {
+    unsafe {
+        com_dc_destroy_window(window);
+    }
+}
+
+pub fn tick(window: *mut Window) -> bool {
+    unsafe {
+        com_dc_tick(window)
+    }
+}
+
+pub fn get_proc_address(name: *const c_char) -> *const c_void {
+    unsafe {
+        com_dc_get_proc_address(name)
+    }
+}
+
+pub fn create_surface(
+    window: *mut Window,
+    id: u64,
+    width: i32,
+    height: i32,
+) {
+    unsafe {
+        com_dc_create_surface(
+            window,
+            id,
+            width,
+            height
+        )
+    }
+}
+
+pub fn destroy_surface(
+    window: *mut Window,
+    id: u64,
+) {
+    unsafe {
+        com_dc_destroy_surface(
+            window,
+            id,
+        )
+    }
+}
+
+pub fn bind_surface(
+    window: *mut Window,
+    id: u64,
+) -> (i32, i32) {
+    unsafe {
+        let mut x_offset = 0;
+        let mut y_offset = 0;
+
+        com_dc_bind_surface(
+            window,
+            id,
+            &mut x_offset,
+            &mut y_offset,
+        );
+
+        (x_offset, y_offset)
+    }
+}
+
+pub fn add_surface(
+    window: *mut Window,
+    id: u64,
+    x: i32,
+    y: i32,
+    clip_x: i32,
+    clip_y: i32,
+    clip_w: i32,
+    clip_h: i32,
+) {
+    unsafe {
+        com_dc_add_surface(
+            window,
+            id,
+            x,
+            y,
+            clip_x,
+            clip_y,
+            clip_w,
+            clip_h,
+        )
+    }
+}
+
+pub fn begin_transaction(window: *mut Window) {
+    unsafe {
+        com_dc_begin_transaction(window)
+    }
+}
+
+pub fn unbind_surface(window: *mut Window) {
+    unsafe {
+        com_dc_unbind_surface(window)
+    }
+}
+
+pub fn end_transaction(window: *mut Window) {
+    unsafe {
+        com_dc_end_transaction(window)
+    }
+}
+
+pub fn swap_buffers(window: *mut Window) {
+    unsafe {
+        com_dc_swap_buffers(window);
+    }
+}
new file mode 100644
--- /dev/null
+++ b/gfx/wr/example-compositor/compositor/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "compositor"
+version = "0.1.0"
+authors = ["Glenn Watson <gw@intuitionlibrary.com>"]
+edition = "2018"
+license = "MPL-2.0"
+
+[dependencies]
+webrender = { path = "../../webrender" }
+gleam = "0.6.2"
+
+[target.'cfg(windows)'.dependencies]
+compositor-windows = { path = "../compositor-windows" }
new file mode 100644
--- /dev/null
+++ b/gfx/wr/example-compositor/compositor/src/main.rs
@@ -0,0 +1,231 @@
+/* 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/. */
+
+/*
+
+    An example of how to implement the Compositor trait that
+    allows picture caching surfaces to be composited by the operating
+    system.
+
+    The current example supports DirectComposite on Windows only.
+
+ */
+
+use gleam::gl;
+use std::ffi::CString;
+use std::sync::mpsc;
+use webrender::api::*;
+use webrender::api::units::*;
+#[cfg(target_os = "windows")]
+use compositor_windows as compositor;
+
+// A very hacky integration with DirectComposite. It proxies calls from the compositor
+// interface to a simple C99 library which does the DirectComposition / D3D11 / ANGLE
+// interfacing. This is a very unsafe impl due to the way the window pointer is passed
+// around!
+struct DirectCompositeInterface {
+    window: *mut compositor::Window,
+}
+
+impl DirectCompositeInterface {
+    fn new(window: *mut compositor::Window) -> Self {
+        DirectCompositeInterface {
+            window,
+        }
+    }
+}
+
+impl webrender::Compositor for DirectCompositeInterface {
+    fn create_surface(
+        &mut self,
+        id: webrender::NativeSurfaceId,
+        size: DeviceIntSize,
+    ) {
+        compositor::create_surface(self.window, id.0, size.width, size.height);
+    }
+
+    fn destroy_surface(
+        &mut self,
+        id: webrender::NativeSurfaceId,
+    ) {
+        compositor::destroy_surface(self.window, id.0);
+    }
+
+    fn bind(
+        &mut self,
+        id: webrender::NativeSurfaceId,
+    ) -> DeviceIntPoint {
+        let (x, y) = compositor::bind_surface(
+            self.window,
+            id.0,
+        );
+
+        DeviceIntPoint::new(x, y)
+    }
+
+    fn unbind(&mut self) {
+        compositor::unbind_surface(self.window);
+    }
+
+    fn begin_frame(&mut self) {
+        compositor::begin_transaction(self.window);
+    }
+
+    fn add_surface(
+        &mut self,
+        id: webrender::NativeSurfaceId,
+        position: DeviceIntPoint,
+        clip_rect: DeviceIntRect,
+    ) {
+        compositor::add_surface(
+            self.window,
+            id.0,
+            position.x,
+            position.y,
+            clip_rect.origin.x,
+            clip_rect.origin.y,
+            clip_rect.size.width,
+            clip_rect.size.height,
+        );
+    }
+
+    fn end_frame(&mut self) {
+        compositor::end_transaction(self.window);
+    }
+}
+
+// Simplisitic implementation of the WR notifier interface to know when a frame
+// has been prepared and can be rendered.
+struct Notifier {
+    tx: mpsc::Sender<()>,
+}
+
+impl Notifier {
+    fn new(tx: mpsc::Sender<()>) -> Self {
+        Notifier {
+            tx,
+        }
+    }
+}
+
+impl RenderNotifier for Notifier {
+    fn clone(&self) -> Box<dyn RenderNotifier> {
+        Box::new(Notifier {
+            tx: self.tx.clone()
+        })
+    }
+
+    fn wake_up(&self) {
+    }
+
+    fn new_frame_ready(&self,
+                       _: DocumentId,
+                       _scrolled: bool,
+                       _composite_needed: bool,
+                       _render_time: Option<u64>) {
+        self.tx.send(()).ok();
+    }
+}
+
+
+fn main() {
+    // If true, use DirectComposition. If false, this will fall back to normal
+    // WR compositing of picture caching tiles, which can be used to check
+    // correctness of implementation.
+    let enable_compositor = true;
+
+    // Load GL, construct WR and the native compositor interface.
+    let device_size = DeviceIntSize::new(1024, 1024);
+    let window = compositor::create_window(
+        device_size.width,
+        device_size.height,
+        enable_compositor,
+    );
+    let debug_flags = DebugFlags::empty();
+    let native_compositor: Option<Box<dyn webrender::Compositor>> = if enable_compositor {
+        Some(Box::new(DirectCompositeInterface::new(window)))
+    } else {
+        None
+    };
+    let opts = webrender::RendererOptions {
+        clear_color: Some(ColorF::new(1.0, 1.0, 1.0, 1.0)),
+        debug_flags,
+        enable_picture_caching: true,
+        native_compositor,
+        ..webrender::RendererOptions::default()
+    };
+    let (tx, rx) = mpsc::channel();
+    let notifier = Box::new(Notifier::new(tx));
+    let gl = unsafe {
+        gl::GlesFns::load_with(
+            |symbol| {
+                let symbol = CString::new(symbol).unwrap();
+                let ptr = compositor::get_proc_address(symbol.as_ptr());
+                ptr
+            }
+        )
+    };
+    let (mut renderer, sender) = webrender::Renderer::new(
+        gl.clone(),
+        notifier,
+        opts,
+        None,
+        device_size,
+    ).unwrap();
+    let api = sender.create_api();
+    let document_id = api.add_document(device_size, 0);
+    let device_pixel_ratio = 1.0;
+    let mut current_epoch = Epoch(0);
+    let root_pipeline_id = PipelineId(0, 0);
+
+    // Kick off first transaction which will mean we get a notify below to build the DL and render.
+    let mut txn = Transaction::new();
+    txn.set_root_pipeline(root_pipeline_id);
+    txn.generate_frame();
+    api.send_transaction(document_id, txn);
+
+    // Tick the compositor (in this sample, we don't block on UI events)
+    while compositor::tick(window) {
+        // If there is a new frame ready to draw
+        if let Ok(..) = rx.try_recv() {
+            // Update and render. This will invoke the native compositor interface implemented above
+            // as required.
+            renderer.update();
+            renderer.render(device_size).unwrap();
+            let _ = renderer.flush_pipeline_info();
+
+            // Construct a simple display list that can be drawn and composited by DC.
+            let layout_size = device_size.to_f32() / euclid::Scale::new(device_pixel_ratio);
+            let mut txn = Transaction::new();
+            let mut root_builder = DisplayListBuilder::new(root_pipeline_id, layout_size);
+            let bg_rect = LayoutRect::new(LayoutPoint::new(100.0, 100.0), LayoutSize::new(800.0, 600.0));
+            root_builder.push_rect(
+                &CommonItemProperties::new(
+                    bg_rect,
+                    SpaceAndClipInfo {
+                        spatial_id: SpatialId::root_scroll_node(root_pipeline_id),
+                        clip_id: ClipId::root(root_pipeline_id),
+                    },
+                ),
+                ColorF::new(0.3, 0.3, 0.3, 1.0),
+            );
+            txn.set_display_list(
+                current_epoch,
+                None,
+                layout_size,
+                root_builder.finalize(),
+                true,
+            );
+            txn.generate_frame();
+            api.send_transaction(document_id, txn);
+            current_epoch.0 += 1;
+
+            // This does nothing when native compositor is enabled
+            compositor::swap_buffers(window);
+        }
+    }
+
+    renderer.deinit();
+    compositor::destroy_window(window);
+}