summaryrefslogtreecommitdiffstats
path: root/gfx/wr/example-compositor
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /gfx/wr/example-compositor
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gfx/wr/example-compositor')
-rw-r--r--gfx/wr/example-compositor/compositor-wayland/Cargo.toml11
-rw-r--r--gfx/wr/example-compositor/compositor-wayland/build.rs63
-rw-r--r--gfx/wr/example-compositor/compositor-wayland/src/lib.cpp772
-rw-r--r--gfx/wr/example-compositor/compositor-wayland/src/lib.rs269
-rw-r--r--gfx/wr/example-compositor/compositor-windows/Cargo.toml9
-rw-r--r--gfx/wr/example-compositor/compositor-windows/build.rs25
-rw-r--r--gfx/wr/example-compositor/compositor-windows/src/lib.cpp694
-rw-r--r--gfx/wr/example-compositor/compositor-windows/src/lib.rs265
-rw-r--r--gfx/wr/example-compositor/compositor/Cargo.toml16
-rw-r--r--gfx/wr/example-compositor/compositor/src/main.rs549
10 files changed, 2673 insertions, 0 deletions
diff --git a/gfx/wr/example-compositor/compositor-wayland/Cargo.toml b/gfx/wr/example-compositor/compositor-wayland/Cargo.toml
new file mode 100644
index 0000000000..0f5bba73b5
--- /dev/null
+++ b/gfx/wr/example-compositor/compositor-wayland/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "compositor-wayland"
+version = "0.1.0"
+authors = ["Glenn Watson <gw@intuitionlibrary.com>",
+ "Robert Mader <robert.mader@posteo.de>"]
+edition = "2018"
+license = "MPL-2.0"
+
+[build-dependencies]
+cc = "1.0"
+pkg-config = "^0.3.17"
diff --git a/gfx/wr/example-compositor/compositor-wayland/build.rs b/gfx/wr/example-compositor/compositor-wayland/build.rs
new file mode 100644
index 0000000000..ab418b94c7
--- /dev/null
+++ b/gfx/wr/example-compositor/compositor-wayland/build.rs
@@ -0,0 +1,63 @@
+/* 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::process::Command;
+use std::env;
+use std::fs;
+
+extern crate pkg_config;
+
+fn main() {
+ let out_dir = env::var("OUT_DIR").unwrap();
+
+ fs::create_dir_all(&format!("{}/include", out_dir)).unwrap();
+ Command::new("wayland-scanner")
+ .args(&["client-header", "/usr/share/wayland-protocols/stable/viewporter/viewporter.xml"])
+ .arg(&format!("{}/include/viewporter-client-protocol.h", out_dir))
+ .status().unwrap();
+
+ Command::new("wayland-scanner")
+ .args(&["public-code", "/usr/share/wayland-protocols/stable/viewporter/viewporter.xml"])
+ .arg(&format!("{}/viewporter-protocol.c", out_dir))
+ .status().unwrap();
+
+ Command::new("wayland-scanner")
+ .args(&["client-header", "/usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml"])
+ .arg(&format!("{}/include/xdg-shell-client-protocol.h", out_dir))
+ .status().unwrap();
+
+ Command::new("wayland-scanner")
+ .args(&["public-code", "/usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml"])
+ .arg(&format!("{}/xdg-shell-protocol.c", out_dir))
+ .status().unwrap();
+
+ cc::Build::new()
+ .include(&format!("{}/include", out_dir))
+ .file("src/lib.cpp")
+ .file(&format!("{}/viewporter-protocol.c", out_dir))
+ .file(&format!("{}/xdg-shell-protocol.c", out_dir))
+ .compile("wayland");
+
+ println!("cargo:rustc-link-lib=dylib=stdc++");
+
+ pkg_config::Config::new()
+ .atleast_version("1")
+ .probe("egl")
+ .unwrap();
+ pkg_config::Config::new()
+ .atleast_version("1")
+ .probe("gl")
+ .unwrap();
+ pkg_config::Config::new()
+ .atleast_version("1")
+ .probe("wayland-client")
+ .unwrap();
+ pkg_config::Config::new()
+ .atleast_version("1")
+ .probe("wayland-egl")
+ .unwrap();
+
+ println!("cargo:rerun-if-changed=src/lib.rs");
+ println!("cargo:rerun-if-changed=src/lib.cpp");
+}
diff --git a/gfx/wr/example-compositor/compositor-wayland/src/lib.cpp b/gfx/wr/example-compositor/compositor-wayland/src/lib.cpp
new file mode 100644
index 0000000000..5529c987a9
--- /dev/null
+++ b/gfx/wr/example-compositor/compositor-wayland/src/lib.cpp
@@ -0,0 +1,772 @@
+/* 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 <algorithm>
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <map>
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <unordered_map>
+#include <vector>
+
+#include <wayland-client.h>
+#include <wayland-egl.h>
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <GL/gl.h>
+#include <GLES2/gl2.h>
+
+#include "viewporter-client-protocol.h"
+#include "xdg-shell-client-protocol.h"
+
+#define UNUSED(x) (void)(x)
+
+#define MIN(x, y) (((x) < (y)) ? (x) : (y))
+#define MAX(x, y) (((x) > (y)) ? (x) : (y))
+
+#define NUM_QUERIES 2
+
+#define VIRTUAL_OFFSET 512 * 1024
+
+enum SyncMode {
+ None_ = 0,
+ Swap = 1,
+ Commit = 2,
+ Flush = 3,
+ Query = 4,
+};
+
+// The OS compositor representation of a picture cache tile.
+struct Tile {
+ uint64_t surface_id;
+ int x;
+ int y;
+
+ struct wl_surface* surface;
+ struct wl_subsurface* subsurface;
+ struct wp_viewport* viewport;
+ struct wl_egl_window* egl_window;
+ EGLSurface egl_surface;
+ bool is_visible;
+
+ std::vector<EGLint> damage_rects;
+};
+
+struct TileKey {
+ int x;
+ int y;
+
+ TileKey(int ax, int ay) : x(ax), y(ay) {}
+};
+
+bool operator==(const TileKey& k0, const TileKey& k1) {
+ return k0.x == k1.x && k0.y == k1.y;
+}
+
+struct TileKeyHasher {
+ size_t operator()(const TileKey& key) const { return key.x ^ key.y; }
+};
+
+struct Surface {
+ uint64_t id;
+ int tile_width;
+ int tile_height;
+ bool is_opaque;
+ std::unordered_map<TileKey, Tile*, TileKeyHasher> tiles;
+};
+
+struct WLDisplay {
+ struct wl_display* display;
+ struct wl_registry* registry;
+ struct wl_compositor* compositor;
+ struct wl_subcompositor* subcompositor;
+ struct xdg_wm_base* wm_base;
+ struct wl_seat* seat;
+ struct wl_pointer* pointer;
+ struct wl_touch* touch;
+ struct wl_keyboard* keyboard;
+ struct wl_shm* shm;
+ struct wl_cursor_theme* cursor_theme;
+ struct wl_cursor* default_cursor;
+ struct wl_surface* cursor_surface;
+ struct wp_viewporter* viewporter;
+
+ PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC swap_buffers_with_damage;
+};
+
+struct WLGeometry {
+ int width, height;
+};
+
+struct WLWindow {
+ WLGeometry geometry;
+ bool enable_compositor;
+ SyncMode sync_mode;
+ bool closed;
+
+ WLDisplay* display;
+ struct wl_surface* surface;
+ struct xdg_surface* xdg_surface;
+ struct xdg_toplevel* xdg_toplevel;
+ struct wl_callback* callback;
+ struct wp_viewport* viewport;
+ bool wait_for_configure;
+
+ struct wl_egl_window* egl_window;
+ EGLSurface egl_surface;
+
+ EGLDeviceEXT eglDevice;
+ EGLDisplay eglDisplay;
+ EGLContext eglContext;
+ EGLConfig config;
+
+ // Maintain list of layer state between frames to avoid visual tree rebuild.
+ std::vector<uint64_t> currentLayers;
+ std::vector<uint64_t> prevLayers;
+
+ // Maps WR surface IDs to each OS surface
+ std::unordered_map<uint64_t, Surface> surfaces;
+ std::vector<Tile*> destroyedTiles;
+ std::vector<Tile*> hiddenTiles;
+};
+
+extern "C" {
+
+static void init_wl_registry(WLWindow* window);
+static void init_xdg_window(WLWindow* window);
+
+WLWindow* com_wl_create_window(int width, int height, bool enable_compositor,
+ SyncMode sync_mode) {
+ WLDisplay* display = new WLDisplay;
+ WLWindow* window = new WLWindow;
+
+ window->display = display;
+ window->geometry.width = width;
+ window->geometry.height = height;
+ window->enable_compositor = enable_compositor;
+ window->sync_mode = sync_mode;
+ window->closed = false;
+
+ display->display = wl_display_connect(NULL);
+ assert(display->display);
+
+ init_wl_registry(window);
+ if (enable_compositor && !display->viewporter) {
+ fprintf(stderr, "Native compositor mode requires wp_viewporter support\n");
+ window->closed = true;
+ }
+
+ window->eglDisplay =
+ eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, display->display, NULL);
+
+ eglInitialize(window->eglDisplay, nullptr, nullptr);
+ eglBindAPI(EGL_OPENGL_API);
+
+ EGLint num_configs = 0;
+ EGLint cfg_attribs[] = {EGL_SURFACE_TYPE,
+ EGL_WINDOW_BIT,
+ EGL_RENDERABLE_TYPE,
+ EGL_OPENGL_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];
+
+ EGLint ctx_attribs[] = {EGL_CONTEXT_OPENGL_PROFILE_MASK,
+ EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT,
+ EGL_CONTEXT_MAJOR_VERSION,
+ 3,
+ EGL_CONTEXT_MINOR_VERSION,
+ 2,
+ EGL_NONE};
+
+ // Create an EGL context that can be used for drawing
+ window->eglContext = eglCreateContext(window->eglDisplay, window->config,
+ EGL_NO_CONTEXT, ctx_attribs);
+
+ window->surface = wl_compositor_create_surface(display->compositor);
+ init_xdg_window(window);
+
+ struct wl_region* region =
+ wl_compositor_create_region(window->display->compositor);
+ wl_region_add(region, 0, 0, INT32_MAX, INT32_MAX);
+ wl_surface_set_opaque_region(window->surface, region);
+ wl_region_destroy(region);
+
+ if (enable_compositor) {
+ xdg_toplevel_set_title(window->xdg_toplevel,
+ "example-compositor (Wayland)");
+ } else {
+ xdg_toplevel_set_title(window->xdg_toplevel, "example-compositor (Simple)");
+ }
+
+ window->wait_for_configure = true;
+ wl_surface_commit(window->surface);
+
+ EGLBoolean ok = eglMakeCurrent(window->eglDisplay, EGL_NO_SURFACE,
+ EGL_NO_SURFACE, window->eglContext);
+ assert(ok);
+
+ display->swap_buffers_with_damage =
+ (PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC)eglGetProcAddress(
+ "eglSwapBuffersWithDamageKHR");
+
+ return window;
+}
+
+bool com_wl_tick(WLWindow* window) {
+ if (window->wait_for_configure) {
+ int ret = 0;
+ while (window->wait_for_configure && !window->closed && ret != -1) {
+ wl_display_dispatch(window->display->display);
+ }
+ } else {
+ wl_display_dispatch_pending(window->display->display);
+ }
+
+ return !window->closed;
+}
+
+static void unmap_hidden_tiles(WLWindow* window) {
+ for (Tile* tile : window->hiddenTiles) {
+ if (tile->subsurface) {
+ wl_subsurface_destroy(tile->subsurface);
+ tile->subsurface = nullptr;
+ }
+ }
+ window->hiddenTiles.clear();
+}
+
+static void clean_up_tiles(WLWindow* window) {
+ for (Tile* tile : window->destroyedTiles) {
+ eglDestroySurface(window->eglDisplay, tile->egl_surface);
+ wl_egl_window_destroy(tile->egl_window);
+ wp_viewport_destroy(tile->viewport);
+ wl_surface_destroy(tile->surface);
+ delete tile;
+ }
+ window->destroyedTiles.clear();
+}
+
+static void handle_callback(void* data, struct wl_callback* callback,
+ uint32_t time) {
+ WLWindow* window = (WLWindow*)data;
+ UNUSED(time);
+
+ assert(window->callback == callback);
+
+ wl_callback_destroy(callback);
+ window->callback = nullptr;
+}
+
+static const struct wl_callback_listener frame_listener = {handle_callback};
+
+void com_wl_swap_buffers(WLWindow* window) {
+ if (window->enable_compositor) {
+ for (auto surface_it = window->surfaces.begin();
+ surface_it != window->surfaces.end(); ++surface_it) {
+ Surface* surface = &surface_it->second;
+
+ for (auto tile_it = surface->tiles.begin();
+ tile_it != surface->tiles.end(); ++tile_it) {
+ Tile* tile = tile_it->second;
+
+ if (!tile->damage_rects.empty() && tile->is_visible) {
+ eglMakeCurrent(window->eglDisplay, tile->egl_surface,
+ tile->egl_surface, window->eglContext);
+ eglSwapInterval(window->eglDisplay, 0);
+
+ /* if (window->display->swap_buffers_with_damage) {
+ window->display->swap_buffers_with_damage(
+ window->eglDisplay, tile->egl_surface,
+ tile->damage_rects.data(), tile->damage_rects.size() / 4);
+ } else */
+ eglSwapBuffers(window->eglDisplay, tile->egl_surface);
+ tile->damage_rects.clear();
+
+ eglMakeCurrent(window->eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ window->eglContext);
+ } else {
+ wl_surface_commit(tile->surface);
+ }
+ }
+ }
+ wl_surface_commit(window->surface);
+ unmap_hidden_tiles(window);
+ clean_up_tiles(window);
+
+ int ret = 0;
+ switch (window->sync_mode) {
+ case SyncMode::None_:
+ wl_display_roundtrip(window->display->display);
+ break;
+ case SyncMode::Swap:
+ window->callback = wl_surface_frame(window->surface);
+ wl_callback_add_listener(window->callback, &frame_listener, window);
+ wl_surface_commit(window->surface);
+
+ while (window->callback && !window->closed && ret != -1) {
+ ret = wl_display_dispatch(window->display->display);
+ }
+ break;
+ default:
+ assert(false);
+ break;
+ }
+ } else {
+ // If not using native mode, then do a normal EGL swap buffers.
+ switch (window->sync_mode) {
+ case SyncMode::None_:
+ eglSwapInterval(window->eglDisplay, 0);
+ break;
+ case SyncMode::Swap:
+ eglSwapInterval(window->eglDisplay, 1);
+ break;
+ default:
+ assert(false);
+ break;
+ }
+ eglSwapBuffers(window->eglDisplay, window->egl_surface);
+ }
+}
+
+// Create a new native surface
+void com_wl_create_surface(WLWindow* window, uint64_t surface_id,
+ int tile_width, int tile_height, bool is_opaque) {
+ assert(window->surfaces.count(surface_id) == 0);
+
+ Surface surface;
+ surface.id = surface_id;
+ surface.tile_width = tile_width;
+ surface.tile_height = tile_height;
+ surface.is_opaque = is_opaque;
+
+ window->surfaces.emplace(surface_id, surface);
+}
+
+void com_wl_create_tile(WLWindow* window, uint64_t surface_id, int x, int y) {
+ WLDisplay* display = window->display;
+
+ assert(window->surfaces.count(surface_id) == 1);
+ Surface* surface = &window->surfaces.at(surface_id);
+
+ TileKey key(x, y);
+ assert(surface->tiles.count(key) == 0);
+
+ Tile* tile = new Tile;
+ tile->surface_id = surface_id;
+ tile->x = x;
+ tile->y = y;
+ tile->is_visible = false;
+
+ tile->surface = wl_compositor_create_surface(display->compositor);
+ tile->viewport =
+ wp_viewporter_get_viewport(display->viewporter, tile->surface);
+
+ if (surface->is_opaque) {
+ struct wl_region* region =
+ wl_compositor_create_region(window->display->compositor);
+ wl_region_add(region, 0, 0, INT32_MAX, INT32_MAX);
+ wl_surface_set_opaque_region(tile->surface, region);
+ wl_region_destroy(region);
+ }
+
+ tile->egl_window = wl_egl_window_create(tile->surface, surface->tile_width,
+ surface->tile_height);
+ tile->egl_surface = eglCreateWindowSurface(window->eglDisplay, window->config,
+ tile->egl_window, NULL);
+ assert(tile->egl_surface != EGL_NO_SURFACE);
+
+ surface->tiles.emplace(key, tile);
+}
+
+static void show_tile(WLWindow* window, Tile* tile) {
+ if (tile->is_visible) {
+ assert(tile->subsurface);
+ return;
+ }
+
+ tile->subsurface = wl_subcompositor_get_subsurface(
+ window->display->subcompositor, tile->surface, window->surface);
+
+ /* This is not comprehensive yet, see hide_tile() */
+ Surface* surface = &window->surfaces.at(tile->surface_id);
+ for (auto tile_it = surface->tiles.begin(); tile_it != surface->tiles.end();
+ ++tile_it) {
+ Tile* other_tile = tile_it->second;
+
+ if (other_tile->is_visible) {
+ wl_subsurface_place_above(tile->subsurface, other_tile->surface);
+ }
+ }
+
+ tile->is_visible = true;
+}
+
+static void hide_tile(WLWindow* window, Tile* tile) {
+ if (!tile->is_visible) {
+ return;
+ }
+
+ /*
+ * This is a workaround for missing API on the egl-wayland platform. We
+ * likely want to replace it a solution that detaches the buffer from
+ * the surface, which would require us to manage buffers manually.
+ */
+ wl_subsurface_set_position(tile->subsurface, window->geometry.width / 2,
+ window->geometry.height / 2);
+ wp_viewport_set_source(tile->viewport, wl_fixed_from_int(0),
+ wl_fixed_from_int(0), wl_fixed_from_int(1),
+ wl_fixed_from_int(1));
+ wl_subsurface_place_below(tile->subsurface, window->surface);
+ tile->is_visible = false;
+ window->hiddenTiles.push_back(tile);
+}
+
+void com_wl_destroy_tile(WLWindow* window, uint64_t surface_id, int x, int y) {
+ assert(window->surfaces.count(surface_id) == 1);
+
+ Surface* surface = &window->surfaces.at(surface_id);
+ TileKey key(x, y);
+ assert(surface->tiles.count(key) == 1);
+ Tile* tile = surface->tiles[key];
+
+ hide_tile(window, tile);
+ wl_surface_commit(tile->surface);
+
+ window->destroyedTiles.push_back(tile);
+ surface->tiles.erase(key);
+}
+
+void com_wl_destroy_surface(WLWindow* window, uint64_t surface_id) {
+ assert(window->surfaces.count(surface_id) == 1);
+
+ Surface* surface = &window->surfaces.at(surface_id);
+ for (auto tile_it = surface->tiles.begin(); tile_it != surface->tiles.end();
+ tile_it = surface->tiles.begin()) {
+ Tile* tile = tile_it->second;
+
+ com_wl_destroy_tile(window, surface_id, tile->x, tile->y);
+ }
+
+ window->surfaces.erase(surface_id);
+}
+
+void com_wl_destroy_window(WLWindow* window) {
+ for (auto surface_it = window->surfaces.begin();
+ surface_it != window->surfaces.end(); ++surface_it) {
+ Surface& surface = surface_it->second;
+
+ com_wl_destroy_surface(window, surface.id);
+ }
+
+ if (window->egl_surface != EGL_NO_SURFACE) {
+ eglDestroySurface(window->eglDisplay, window->egl_surface);
+ }
+ eglDestroyContext(window->eglDisplay, window->eglContext);
+ eglTerminate(window->eglDisplay);
+
+ delete window;
+}
+
+// Bind a native surface to allow issuing GL commands to it
+GLuint com_wl_bind_surface(WLWindow* window, uint64_t surface_id, int tile_x,
+ int tile_y, int* x_offset, int* y_offset,
+ int dirty_x0, int dirty_y0, int dirty_width,
+ int dirty_height) {
+ *x_offset = 0;
+ *y_offset = 0;
+
+ assert(window->surfaces.count(surface_id) == 1);
+ Surface* surface = &window->surfaces[surface_id];
+
+ TileKey key(tile_x, tile_y);
+ assert(surface->tiles.count(key) == 1);
+ Tile* tile = surface->tiles[key];
+
+ tile->damage_rects.push_back(dirty_x0);
+ tile->damage_rects.push_back(dirty_y0);
+ tile->damage_rects.push_back(dirty_width);
+ tile->damage_rects.push_back(dirty_height);
+
+ EGLBoolean ok = eglMakeCurrent(window->eglDisplay, tile->egl_surface,
+ tile->egl_surface, window->eglContext);
+ assert(ok);
+
+ return 0;
+}
+
+// Unbind a currently bound native surface
+void com_wl_unbind_surface(WLWindow* window) {
+ eglMakeCurrent(window->eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ window->eglContext);
+}
+
+void com_wl_begin_transaction(WLWindow*) {}
+
+// Add a native surface to the visual tree. Called per-frame to build the
+// composition.
+void com_wl_add_surface(WLWindow* window, uint64_t surface_id, int offset_x,
+ int offset_y, int clip_x, int clip_y, int clip_w,
+ int clip_h) {
+ Surface* surface = &window->surfaces[surface_id];
+ window->currentLayers.push_back(surface_id);
+
+ for (auto tile_it = surface->tiles.begin(); tile_it != surface->tiles.end();
+ ++tile_it) {
+ Tile* tile = tile_it->second;
+
+ int pos_x = MAX((tile->x * surface->tile_width) + offset_x, clip_x);
+ int pos_y = MAX((tile->y * surface->tile_height) + offset_y, clip_y);
+
+ float view_x = MAX((clip_x - offset_x) - tile->x * surface->tile_width, 0);
+ float view_y = MAX((clip_y - offset_y) - tile->y * surface->tile_height, 0);
+
+ float view_w = MIN(surface->tile_width - view_x, (clip_x + clip_w) - pos_x);
+ float view_h =
+ MIN(surface->tile_height - view_y, (clip_y + clip_h) - pos_y);
+ view_w = MIN(window->geometry.width - pos_x, view_w);
+ view_h = MIN(window->geometry.height - pos_y, view_h);
+
+ if (view_w > 0 && view_h > 0) {
+ show_tile(window, tile);
+
+ wl_surface_set_buffer_transform(tile->surface,
+ WL_OUTPUT_TRANSFORM_FLIPPED_180);
+ wl_subsurface_set_position(tile->subsurface, pos_x, pos_y);
+ wp_viewport_set_source(tile->viewport, wl_fixed_from_double(view_x),
+ wl_fixed_from_double(view_y),
+ wl_fixed_from_double(view_w),
+ wl_fixed_from_double(view_h));
+ } else {
+ hide_tile(window, tile);
+ }
+ }
+}
+
+void com_wl_end_transaction(WLWindow* window) {
+ bool same = window->prevLayers == window->currentLayers;
+ if (!same) {
+ struct wl_surface* prev_surface = window->surface;
+
+ for (auto it = window->currentLayers.begin();
+ it != window->currentLayers.end(); ++it) {
+ Surface* surface = &window->surfaces[*it];
+
+ struct wl_surface* next_surface = nullptr;
+ for (auto tile_it = surface->tiles.begin();
+ tile_it != surface->tiles.end(); ++tile_it) {
+ Tile* tile = tile_it->second;
+
+ if (tile->is_visible) {
+ wl_subsurface_place_above(tile->subsurface, prev_surface);
+
+ if (!next_surface) {
+ next_surface = tile->surface;
+ }
+ }
+ }
+ prev_surface = next_surface;
+ }
+ }
+
+ window->prevLayers.swap(window->currentLayers);
+ window->currentLayers.clear();
+}
+
+void glInvalidateFramebuffer(GLenum target, GLsizei numAttachments,
+ const GLenum* attachments) {
+ UNUSED(target);
+ UNUSED(numAttachments);
+ UNUSED(attachments);
+}
+
+// Get a pointer to an EGL symbol
+void* com_wl_get_proc_address(const char* name) {
+ /* Disable glInvalidateFramebuffer for now as it triggers errors.
+ * This is likely due to the egl-wayland platform, which we may want to
+ * replace with a custom implementation in order to have more control
+ * over the low-lever bits.
+ */
+ if (strcmp(name, "glInvalidateFramebuffer") == 0) {
+ return (void*)glInvalidateFramebuffer;
+ }
+
+ return (void*)eglGetProcAddress(name);
+}
+
+void com_wl_deinit(WLWindow* window) { UNUSED(window); }
+
+static void handle_xdg_surface_configure(void* data,
+ struct xdg_surface* surface,
+ uint32_t serial) {
+ WLWindow* window = (WLWindow*)data;
+
+ xdg_surface_ack_configure(surface, serial);
+
+ if (window->wait_for_configure) {
+ if (window->enable_compositor) {
+ int width = window->geometry.width;
+ int height = window->geometry.height;
+
+ window->egl_window = wl_egl_window_create(window->surface, 1, 1);
+ window->egl_surface = eglCreateWindowSurface(
+ window->eglDisplay, window->config, window->egl_window, NULL);
+ assert(window->egl_surface != EGL_NO_SURFACE);
+
+ EGLBoolean ok = eglMakeCurrent(window->eglDisplay, window->egl_surface,
+ window->egl_surface, window->eglContext);
+ assert(ok);
+
+ glClearColor(1.0, 1.0, 1.0, 1.0);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ window->viewport = wp_viewporter_get_viewport(window->display->viewporter,
+ window->surface);
+ wp_viewport_set_destination(window->viewport, width, height);
+
+ eglSwapBuffers(window->eglDisplay, window->egl_surface);
+ } else {
+ window->egl_window = wl_egl_window_create(
+ window->surface, window->geometry.width, window->geometry.height);
+ window->egl_surface = eglCreateWindowSurface(
+ window->eglDisplay, window->config, window->egl_window, NULL);
+ assert(window->egl_surface != EGL_NO_SURFACE);
+
+ EGLBoolean ok = eglMakeCurrent(window->eglDisplay, window->egl_surface,
+ window->egl_surface, window->eglContext);
+ assert(ok);
+ }
+ }
+
+ window->wait_for_configure = false;
+}
+
+static const struct xdg_surface_listener xdg_surface_listener = {
+ handle_xdg_surface_configure};
+
+static void handle_xdg_toplevel_configure(void* data,
+ struct xdg_toplevel* toplevel,
+ int32_t width, int32_t height,
+ struct wl_array* states) {
+ WLWindow* window = (WLWindow*)data;
+ UNUSED(toplevel);
+ UNUSED(states);
+
+ if (width > 0 && height > 0) {
+ window->geometry.width = width;
+ window->geometry.height = height;
+
+ if (!window->wait_for_configure) {
+ if (window->enable_compositor) {
+ wp_viewport_set_destination(window->viewport, window->geometry.width,
+ window->geometry.height);
+ } else {
+ wl_egl_window_resize(window->egl_window, window->geometry.width,
+ window->geometry.height, 0, 0);
+ }
+ }
+ }
+}
+
+static void handle_xdg_toplevel_close(void* data,
+ struct xdg_toplevel* toplevel) {
+ UNUSED(toplevel);
+ WLWindow* window = (WLWindow*)data;
+ window->closed = true;
+}
+
+static const struct xdg_toplevel_listener xdg_toplevel_listener = {
+ handle_xdg_toplevel_configure,
+ handle_xdg_toplevel_close,
+};
+
+static void xdg_wm_base_ping(void* data, struct xdg_wm_base* shell,
+ uint32_t serial) {
+ UNUSED(data);
+ xdg_wm_base_pong(shell, serial);
+}
+
+static const struct xdg_wm_base_listener wm_base_listener = {
+ xdg_wm_base_ping,
+};
+
+static void registry_handle_global(void* data, struct wl_registry* registry,
+ uint32_t name, const char* interface,
+ uint32_t version) {
+ WLDisplay* d = (WLDisplay*)data;
+
+ if (strcmp(interface, "wl_compositor") == 0) {
+ d->compositor = (struct wl_compositor*)wl_registry_bind(
+ registry, name, &wl_compositor_interface, MIN(version, 4));
+ } else if (strcmp(interface, "wp_viewporter") == 0) {
+ d->viewporter = (struct wp_viewporter*)wl_registry_bind(
+ registry, name, &wp_viewporter_interface, 1);
+ } else if (strcmp(interface, "xdg_wm_base") == 0) {
+ d->wm_base = (struct xdg_wm_base*)wl_registry_bind(
+ registry, name, &xdg_wm_base_interface, 1);
+ xdg_wm_base_add_listener(d->wm_base, &wm_base_listener, NULL);
+ } else if (strcmp(interface, "wl_subcompositor") == 0) {
+ d->subcompositor = (struct wl_subcompositor*)wl_registry_bind(
+ registry, name, &wl_subcompositor_interface, 1);
+ }
+}
+
+static void registry_handle_global_remove(void* data,
+ struct wl_registry* registry,
+ uint32_t name) {
+ UNUSED(data);
+ UNUSED(registry);
+ UNUSED(name);
+}
+
+static const struct wl_registry_listener registry_listener = {
+ registry_handle_global, registry_handle_global_remove};
+
+static void init_wl_registry(WLWindow* window) {
+ WLDisplay* display = window->display;
+
+ display->registry = wl_display_get_registry(display->display);
+ wl_registry_add_listener(display->registry, &registry_listener, display);
+
+ wl_display_roundtrip(display->display);
+
+ assert(display->compositor);
+ assert(display->wm_base);
+ assert(display->subcompositor);
+}
+
+static void init_xdg_window(WLWindow* window) {
+ window->xdg_surface =
+ xdg_wm_base_get_xdg_surface(window->display->wm_base, window->surface);
+ assert(window->xdg_surface);
+ xdg_surface_add_listener(window->xdg_surface, &xdg_surface_listener, window);
+
+ window->xdg_toplevel = xdg_surface_get_toplevel(window->xdg_surface);
+ xdg_toplevel_add_listener(window->xdg_toplevel, &xdg_toplevel_listener,
+ window);
+ assert(window->xdg_toplevel);
+}
+}
diff --git a/gfx/wr/example-compositor/compositor-wayland/src/lib.rs b/gfx/wr/example-compositor/compositor-wayland/src/lib.rs
new file mode 100644
index 0000000000..daddbb6495
--- /dev/null
+++ b/gfx/wr/example-compositor/compositor-wayland/src/lib.rs
@@ -0,0 +1,269 @@
+/* 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 Wayland / EGL
+ 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_wl_create_window(
+ width: i32,
+ height: i32,
+ enable_compositor: bool,
+ sync_mode: i32,
+ ) -> *mut Window;
+ fn com_wl_destroy_window(window: *mut Window);
+ fn com_wl_tick(window: *mut Window) -> bool;
+ fn com_wl_get_proc_address(name: *const c_char) -> *const c_void;
+ fn com_wl_swap_buffers(window: *mut Window);
+
+ fn com_wl_create_surface(
+ window: *mut Window,
+ id: u64,
+ tile_width: i32,
+ tile_height: i32,
+ is_opaque: bool,
+ );
+
+ fn com_wl_create_tile(
+ window: *mut Window,
+ id: u64,
+ x: i32,
+ y: i32,
+ );
+
+ fn com_wl_destroy_tile(
+ window: *mut Window,
+ id: u64,
+ x: i32,
+ y: i32,
+ );
+
+ fn com_wl_destroy_surface(
+ window: *mut Window,
+ id: u64,
+ );
+
+ fn com_wl_bind_surface(
+ window: *mut Window,
+ surface_id: u64,
+ tile_x: i32,
+ tile_y: i32,
+ x_offset: &mut i32,
+ y_offset: &mut i32,
+ dirty_x0: i32,
+ dirty_y0: i32,
+ dirty_width: i32,
+ dirty_height: i32,
+ ) -> u32;
+ fn com_wl_unbind_surface(window: *mut Window);
+
+ fn com_wl_begin_transaction(window: *mut Window);
+
+ fn com_wl_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_wl_end_transaction(window: *mut Window);
+
+ fn com_wl_deinit(window: *mut Window);
+}
+
+pub fn create_window(
+ width: i32,
+ height: i32,
+ enable_compositor: bool,
+ sync_mode: i32,
+) -> *mut Window {
+ unsafe {
+ com_wl_create_window(width, height, enable_compositor, sync_mode)
+ }
+}
+
+pub fn destroy_window(window: *mut Window) {
+ unsafe {
+ com_wl_destroy_window(window);
+ }
+}
+
+pub fn tick(window: *mut Window) -> bool {
+ unsafe {
+ com_wl_tick(window)
+ }
+}
+
+pub fn get_proc_address(name: *const c_char) -> *const c_void {
+ unsafe {
+ com_wl_get_proc_address(name)
+ }
+}
+
+pub fn create_surface(
+ window: *mut Window,
+ id: u64,
+ tile_width: i32,
+ tile_height: i32,
+ is_opaque: bool,
+) {
+ unsafe {
+ com_wl_create_surface(
+ window,
+ id,
+ tile_width,
+ tile_height,
+ is_opaque,
+ )
+ }
+}
+
+pub fn create_tile(
+ window: *mut Window,
+ id: u64,
+ x: i32,
+ y: i32,
+) {
+ unsafe {
+ com_wl_create_tile(
+ window,
+ id,
+ x,
+ y,
+ )
+ }
+}
+
+pub fn destroy_tile(
+ window: *mut Window,
+ id: u64,
+ x: i32,
+ y: i32,
+) {
+ unsafe {
+ com_wl_destroy_tile(
+ window,
+ id,
+ x,
+ y,
+ )
+ }
+}
+
+pub fn destroy_surface(
+ window: *mut Window,
+ id: u64,
+) {
+ unsafe {
+ com_wl_destroy_surface(
+ window,
+ id,
+ )
+ }
+}
+
+pub fn bind_surface(
+ window: *mut Window,
+ surface_id: u64,
+ tile_x: i32,
+ tile_y: i32,
+ dirty_x0: i32,
+ dirty_y0: i32,
+ dirty_width: i32,
+ dirty_height: i32,
+) -> (u32, i32, i32) {
+ unsafe {
+ let mut x_offset = 0;
+ let mut y_offset = 0;
+
+ let fbo_id = com_wl_bind_surface(
+ window,
+ surface_id,
+ tile_x,
+ tile_y,
+ &mut x_offset,
+ &mut y_offset,
+ dirty_x0,
+ dirty_y0,
+ dirty_width,
+ dirty_height,
+ );
+
+ (fbo_id, 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_wl_add_surface(
+ window,
+ id,
+ x,
+ y,
+ clip_x,
+ clip_y,
+ clip_w,
+ clip_h,
+ )
+ }
+}
+
+pub fn begin_transaction(window: *mut Window) {
+ unsafe {
+ com_wl_begin_transaction(window)
+ }
+}
+
+pub fn unbind_surface(window: *mut Window) {
+ unsafe {
+ com_wl_unbind_surface(window)
+ }
+}
+
+pub fn end_transaction(window: *mut Window) {
+ unsafe {
+ com_wl_end_transaction(window)
+ }
+}
+
+pub fn swap_buffers(window: *mut Window) {
+ unsafe {
+ com_wl_swap_buffers(window);
+ }
+}
+
+pub fn deinit(window: *mut Window) {
+ unsafe {
+ com_wl_deinit(window);
+ }
+}
diff --git a/gfx/wr/example-compositor/compositor-windows/Cargo.toml b/gfx/wr/example-compositor/compositor-windows/Cargo.toml
new file mode 100644
index 0000000000..b191626a69
--- /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"
diff --git a/gfx/wr/example-compositor/compositor-windows/build.rs b/gfx/wr/example-compositor/compositor-windows/build.rs
new file mode 100644
index 0000000000..dc7358aa6a
--- /dev/null
+++ b/gfx/wr/example-compositor/compositor-windows/build.rs
@@ -0,0 +1,25 @@
+/* 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");
+ println!("cargo:rustc-link-lib=dwmapi");
+}
diff --git a/gfx/wr/example-compositor/compositor-windows/src/lib.cpp b/gfx/wr/example-compositor/compositor-windows/src/lib.cpp
new file mode 100644
index 0000000000..d39726ea0a
--- /dev/null
+++ b/gfx/wr/example-compositor/compositor-windows/src/lib.cpp
@@ -0,0 +1,694 @@
+/* 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>
+#include <vector>
+#include <dwmapi.h>
+#include <unordered_map>
+
+#define EGL_EGL_PROTOTYPES 1
+#define EGL_EGLEXT_PROTOTYPES 1
+#define GL_GLEXT_PROTOTYPES 1
+#include "EGL/egl.h"
+#include "EGL/eglext.h"
+#include "EGL/eglext_angle.h"
+#include "GL/gl.h"
+#include "GLES/gl.h"
+#include "GLES/glext.h"
+#include "GLES3/gl3.h"
+
+#define NUM_QUERIES 2
+
+#define USE_VIRTUAL_SURFACES
+#define VIRTUAL_OFFSET 512 * 1024
+
+enum SyncMode {
+ None = 0,
+ Swap = 1,
+ Commit = 2,
+ Flush = 3,
+ Query = 4,
+};
+
+// The OS compositor representation of a picture cache tile.
+struct Tile {
+#ifndef USE_VIRTUAL_SURFACES
+ // 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).
+ IDCompositionVisual2* pVisual;
+#endif
+};
+
+struct TileKey {
+ int x;
+ int y;
+
+ TileKey(int ax, int ay) : x(ax), y(ay) {}
+};
+
+bool operator==(const TileKey& k0, const TileKey& k1) {
+ return k0.x == k1.x && k0.y == k1.y;
+}
+
+struct TileKeyHasher {
+ size_t operator()(const TileKey& key) const { return key.x ^ key.y; }
+};
+
+struct Surface {
+ int tile_width;
+ int tile_height;
+ bool is_opaque;
+ std::unordered_map<TileKey, Tile, TileKeyHasher> tiles;
+ IDCompositionVisual2* pVisual;
+#ifdef USE_VIRTUAL_SURFACES
+ IDCompositionVirtualSurface* pVirtualSurface;
+#endif
+};
+
+struct CachedFrameBuffer {
+ int width;
+ int height;
+ GLuint fboId;
+ GLuint depthRboId;
+};
+
+struct Window {
+ // Win32 window details
+ HWND hWnd;
+ HINSTANCE hInstance;
+ bool enable_compositor;
+ RECT client_rect;
+ SyncMode sync_mode;
+
+ // Main interfaces to D3D11 and DirectComposition
+ ID3D11Device* pD3D11Device;
+ IDCompositionDesktopDevice* pDCompDevice;
+ IDCompositionTarget* pDCompTarget;
+ IDXGIDevice* pDXGIDevice;
+ ID3D11Query* pQueries[NUM_QUERIES];
+ int current_query;
+
+ // 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()
+ IDCompositionSurface* pCurrentSurface;
+ EGLImage mEGLImage;
+ GLuint mColorRBO;
+
+ // The root of the DC visual tree. Nothing is drawn on this, but
+ // all child tiles are parented to here.
+ IDCompositionVisual2* pRoot;
+ IDCompositionVisualDebug* pVisualDebug;
+ std::vector<CachedFrameBuffer> mFrameBuffers;
+
+ // Maintain list of layer state between frames to avoid visual tree rebuild.
+ std::vector<uint64_t> mCurrentLayers;
+ std::vector<uint64_t> mPrevLayers;
+
+ // Maps WR surface IDs to each OS surface
+ std::unordered_map<uint64_t, Surface> surfaces;
+};
+
+static const wchar_t* CLASS_NAME = L"WR DirectComposite";
+
+static GLuint GetOrCreateFbo(Window* window, int aWidth, int aHeight) {
+ GLuint fboId = 0;
+
+ // Check if we have a cached FBO with matching dimensions
+ for (auto it = window->mFrameBuffers.begin();
+ it != window->mFrameBuffers.end(); ++it) {
+ if (it->width == aWidth && it->height == aHeight) {
+ fboId = it->fboId;
+ break;
+ }
+ }
+
+ // If not, create a new FBO with attached depth buffer
+ if (fboId == 0) {
+ // Create the depth buffer
+ GLuint depthRboId;
+ glGenRenderbuffers(1, &depthRboId);
+ glBindRenderbuffer(GL_RENDERBUFFER, depthRboId);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, aWidth,
+ aHeight);
+
+ // Create the framebuffer and attach the depth buffer to it
+ glGenFramebuffers(1, &fboId);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboId);
+ glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
+ GL_RENDERBUFFER, depthRboId);
+
+ // Store this in the cache for future calls.
+ CachedFrameBuffer frame_buffer_info;
+ frame_buffer_info.width = aWidth;
+ frame_buffer_info.height = aHeight;
+ frame_buffer_info.fboId = fboId;
+ frame_buffer_info.depthRboId = depthRboId;
+ window->mFrameBuffers.push_back(frame_buffer_info);
+ }
+
+ return fboId;
+}
+
+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,
+ SyncMode sync_mode) {
+ // Create a simple Win32 window
+ Window* window = new Window;
+ window->hInstance = GetModuleHandle(NULL);
+ window->enable_compositor = enable_compositor;
+ window->mEGLImage = EGL_NO_IMAGE;
+ window->sync_mode = sync_mode;
+
+ 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);
+ }
+
+ RECT window_rect = {0, 0, width, height};
+ AdjustWindowRect(&window_rect, WS_OVERLAPPEDWINDOW, FALSE);
+ UINT window_width = static_cast<UINT>(
+ ceil(float(window_rect.right - window_rect.left) * dpiX / 96.f));
+ UINT window_height = static_cast<UINT>(
+ ceil(float(window_rect.bottom - window_rect.top) * dpiY / 96.f));
+
+ LPCWSTR name;
+ DWORD style;
+ if (enable_compositor) {
+ name = L"example-compositor (DirectComposition)";
+ style = WS_EX_NOREDIRECTIONBITMAP;
+ } else {
+ name = L"example-compositor (Simple)";
+ style = 0;
+ }
+
+ window->hWnd =
+ CreateWindowEx(style, CLASS_NAME, name, WS_OVERLAPPEDWINDOW,
+ CW_USEDEFAULT, CW_USEDEFAULT, window_width, window_height,
+ 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));
+
+ D3D11_QUERY_DESC query_desc;
+ memset(&query_desc, 0, sizeof(query_desc));
+ query_desc.Query = D3D11_QUERY_EVENT;
+ for (int i = 0; i < NUM_QUERIES; ++i) {
+ hr = window->pD3D11Device->CreateQuery(&query_desc, &window->pQueries[i]);
+ assert(SUCCEEDED(hr));
+ }
+ window->current_query = 0;
+
+ hr = window->pD3D11Device->QueryInterface(&window->pDXGIDevice);
+ assert(SUCCEEDED(hr));
+
+ // Create a DirectComposition device
+ hr = DCompositionCreateDevice2(window->pDXGIDevice,
+ __uuidof(IDCompositionDesktopDevice),
+ (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));
+
+ hr = window->pRoot->QueryInterface(__uuidof(IDCompositionVisualDebug),
+ (void**)&window->pVisualDebug);
+ assert(SUCCEEDED(hr));
+
+ // Uncomment this to see redraw regions during composite
+ // window->pVisualDebug->EnableRedrawRegions();
+
+ 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 surface_it = window->surfaces.begin();
+ surface_it != window->surfaces.end(); ++surface_it) {
+ Surface& surface = surface_it->second;
+
+#ifndef USE_VIRTUAL_SURFACES
+ for (auto tile_it = surface.tiles.begin(); tile_it != surface.tiles.end();
+ ++tile_it) {
+ tile_it->second.pSurface->Release();
+ tile_it->second.pVisual->Release();
+ }
+#endif
+
+ surface.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);
+
+ for (int i = 0; i < NUM_QUERIES; ++i) {
+ window->pQueries[i]->Release();
+ }
+ window->pRoot->Release();
+ window->pVisualDebug->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*) {
+ // 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) {
+ switch (window->sync_mode) {
+ case SyncMode::None:
+ eglSwapInterval(window->EGLDisplay, 0);
+ break;
+ case SyncMode::Swap:
+ eglSwapInterval(window->EGLDisplay, 1);
+ break;
+ default:
+ assert(false); // unexpected vsync mode for simple compositor.
+ break;
+ }
+
+ eglSwapBuffers(window->EGLDisplay, window->fb_surface);
+ } else {
+ switch (window->sync_mode) {
+ case SyncMode::None:
+ break;
+ case SyncMode::Commit:
+ window->pDCompDevice->WaitForCommitCompletion();
+ break;
+ case SyncMode::Flush:
+ DwmFlush();
+ break;
+ case SyncMode::Query:
+ // todo!!!!
+ break;
+ default:
+ assert(false); // unexpected vsync mode for native compositor
+ break;
+ }
+ }
+}
+
+// Create a new DC surface
+void com_dc_create_surface(Window* window, uint64_t id, int tile_width,
+ int tile_height, bool is_opaque) {
+ assert(window->surfaces.count(id) == 0);
+
+ Surface surface;
+ surface.tile_width = tile_width;
+ surface.tile_height = tile_height;
+ surface.is_opaque = is_opaque;
+
+ // Create the visual node in the DC tree that stores properties
+ HRESULT hr = window->pDCompDevice->CreateVisual(&surface.pVisual);
+ assert(SUCCEEDED(hr));
+
+#ifdef USE_VIRTUAL_SURFACES
+ DXGI_ALPHA_MODE alpha_mode = surface.is_opaque
+ ? DXGI_ALPHA_MODE_IGNORE
+ : DXGI_ALPHA_MODE_PREMULTIPLIED;
+
+ hr = window->pDCompDevice->CreateVirtualSurface(
+ VIRTUAL_OFFSET * 2, VIRTUAL_OFFSET * 2, DXGI_FORMAT_B8G8R8A8_UNORM,
+ alpha_mode, &surface.pVirtualSurface);
+ assert(SUCCEEDED(hr));
+
+ // Bind the surface memory to this visual
+ hr = surface.pVisual->SetContent(surface.pVirtualSurface);
+ assert(SUCCEEDED(hr));
+#endif
+
+ window->surfaces[id] = surface;
+}
+
+void com_dc_create_tile(Window* window, uint64_t id, int x, int y) {
+ assert(window->surfaces.count(id) == 1);
+ Surface& surface = window->surfaces[id];
+
+ TileKey key(x, y);
+ assert(surface.tiles.count(key) == 0);
+
+ Tile tile;
+
+#ifndef USE_VIRTUAL_SURFACES
+ // Create the video memory surface.
+ DXGI_ALPHA_MODE alpha_mode = surface.is_opaque
+ ? DXGI_ALPHA_MODE_IGNORE
+ : DXGI_ALPHA_MODE_PREMULTIPLIED;
+ HRESULT hr = window->pDCompDevice->CreateSurface(
+ surface.tile_width, surface.tile_height, DXGI_FORMAT_B8G8R8A8_UNORM,
+ alpha_mode, &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));
+
+ // Place the visual in local-space of this surface
+ float offset_x = (float)(x * surface.tile_width);
+ float offset_y = (float)(y * surface.tile_height);
+ tile.pVisual->SetOffsetX(offset_x);
+ tile.pVisual->SetOffsetY(offset_y);
+
+ surface.pVisual->AddVisual(tile.pVisual, FALSE, NULL);
+#endif
+
+ surface.tiles[key] = tile;
+}
+
+void com_dc_destroy_tile(Window* window, uint64_t id, int x, int y) {
+ assert(window->surfaces.count(id) == 1);
+ Surface& surface = window->surfaces[id];
+
+ TileKey key(x, y);
+ assert(surface.tiles.count(key) == 1);
+ Tile& tile = surface.tiles[key];
+
+#ifndef USE_VIRTUAL_SURFACES
+ surface.pVisual->RemoveVisual(tile.pVisual);
+
+ tile.pVisual->Release();
+ tile.pSurface->Release();
+#endif
+
+ surface.tiles.erase(key);
+}
+
+void com_dc_destroy_surface(Window* window, uint64_t id) {
+ assert(window->surfaces.count(id) == 1);
+ Surface& surface = window->surfaces[id];
+
+ window->pRoot->RemoveVisual(surface.pVisual);
+
+#ifdef USE_VIRTUAL_SURFACES
+ surface.pVirtualSurface->Release();
+#else
+ // Release the video memory and visual in the tree
+ for (auto tile_it = surface.tiles.begin(); tile_it != surface.tiles.end();
+ ++tile_it) {
+ tile_it->second.pSurface->Release();
+ tile_it->second.pVisual->Release();
+ }
+#endif
+
+ surface.pVisual->Release();
+ window->surfaces.erase(id);
+}
+
+// Bind a DC surface to allow issuing GL commands to it
+GLuint com_dc_bind_surface(Window* window, uint64_t surface_id, int tile_x,
+ int tile_y, int* x_offset, int* y_offset,
+ int dirty_x0, int dirty_y0, int dirty_width,
+ int dirty_height) {
+ assert(window->surfaces.count(surface_id) == 1);
+ Surface& surface = window->surfaces[surface_id];
+
+ TileKey key(tile_x, tile_y);
+ assert(surface.tiles.count(key) == 1);
+ Tile& tile = surface.tiles[key];
+
+ // 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.
+ RECT update_rect;
+ update_rect.left = dirty_x0;
+ update_rect.top = dirty_y0;
+ update_rect.right = dirty_x0 + dirty_width;
+ update_rect.bottom = dirty_y0 + dirty_height;
+ POINT offset;
+ D3D11_TEXTURE2D_DESC desc;
+ ID3D11Texture2D* pTexture;
+ HRESULT hr;
+
+ // Store the current surface for unbinding later
+#ifdef USE_VIRTUAL_SURFACES
+ LONG tile_offset_x = VIRTUAL_OFFSET + tile_x * surface.tile_width;
+ LONG tile_offset_y = VIRTUAL_OFFSET + tile_y * surface.tile_height;
+
+ update_rect.left += tile_offset_x;
+ update_rect.top += tile_offset_y;
+ update_rect.right += tile_offset_x;
+ update_rect.bottom += tile_offset_y;
+
+ hr = surface.pVirtualSurface->BeginDraw(
+ &update_rect, __uuidof(ID3D11Texture2D), (void**)&pTexture, &offset);
+ window->pCurrentSurface = surface.pVirtualSurface;
+#else
+ hr = tile.pSurface->BeginDraw(&update_rect, __uuidof(ID3D11Texture2D),
+ (void**)&pTexture, &offset);
+ window->pCurrentSurface = tile.pSurface;
+#endif
+
+ // DC includes the origin of the dirty / update rect in the draw offset,
+ // undo that here since WR expects it to be an absolute offset.
+ assert(SUCCEEDED(hr));
+ offset.x -= dirty_x0;
+ offset.y -= dirty_y0;
+ pTexture->GetDesc(&desc);
+ *x_offset = offset.x;
+ *y_offset = offset.y;
+
+ // Construct an EGLImage wrapper around the D3D texture for ANGLE.
+ const EGLAttrib attribs[] = {EGL_NONE};
+ window->mEGLImage = eglCreateImage(
+ window->EGLDisplay, EGL_NO_CONTEXT, EGL_D3D11_TEXTURE_ANGLE,
+ static_cast<EGLClientBuffer>(pTexture), attribs);
+
+ // Get the current FBO and RBO id, so we can restore them later
+ GLint currentFboId, currentRboId;
+ glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &currentFboId);
+ glGetIntegerv(GL_RENDERBUFFER_BINDING, &currentRboId);
+
+ // Create a render buffer object that is backed by the EGL image.
+ glGenRenderbuffers(1, &window->mColorRBO);
+ glBindRenderbuffer(GL_RENDERBUFFER, window->mColorRBO);
+ glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, window->mEGLImage);
+
+ // Get or create an FBO for the specified dimensions
+ GLuint fboId = GetOrCreateFbo(window, desc.Width, desc.Height);
+
+ // Attach the new renderbuffer to the FBO
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboId);
+ glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_RENDERBUFFER, window->mColorRBO);
+
+ // Restore previous FBO and RBO bindings
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, currentFboId);
+ glBindRenderbuffer(GL_RENDERBUFFER, currentRboId);
+
+ return fboId;
+}
+
+// Unbind a currently bound DC surface
+void com_dc_unbind_surface(Window* window) {
+ HRESULT hr = window->pCurrentSurface->EndDraw();
+ assert(SUCCEEDED(hr));
+
+ glDeleteRenderbuffers(1, &window->mColorRBO);
+ window->mColorRBO = 0;
+
+ eglDestroyImage(window->EGLDisplay, window->mEGLImage);
+ window->mEGLImage = EGL_NO_IMAGE;
+}
+
+void com_dc_begin_transaction(Window*) {}
+
+// 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) {
+ Surface surface = window->surfaces[id];
+ window->mCurrentLayers.push_back(id);
+
+ // Place the visual - this changes frame to frame based on scroll position
+ // of the slice.
+ float offset_x = (float)(x + window->client_rect.left);
+ float offset_y = (float)(y + window->client_rect.top);
+#ifdef USE_VIRTUAL_SURFACES
+ offset_x -= VIRTUAL_OFFSET;
+ offset_y -= VIRTUAL_OFFSET;
+#endif
+ surface.pVisual->SetOffsetX(offset_x);
+ surface.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;
+ surface.pVisual->SetClip(clip_rect);
+}
+
+// Finish the composition transaction, telling DC to composite
+void com_dc_end_transaction(Window* window) {
+ bool same = window->mPrevLayers == window->mCurrentLayers;
+
+ if (!same) {
+ HRESULT hr = window->pRoot->RemoveAllVisuals();
+ assert(SUCCEEDED(hr));
+
+ for (auto it = window->mCurrentLayers.begin();
+ it != window->mCurrentLayers.end(); ++it) {
+ Surface& surface = window->surfaces[*it];
+
+ // Add this visual as the last element in the visual tree (z-order is
+ // implicit, based on the order tiles are added).
+ hr = window->pRoot->AddVisual(surface.pVisual, FALSE, NULL);
+ assert(SUCCEEDED(hr));
+ }
+ }
+
+ window->mPrevLayers.swap(window->mCurrentLayers);
+ window->mCurrentLayers.clear();
+
+ 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);
+}
+}
diff --git a/gfx/wr/example-compositor/compositor-windows/src/lib.rs b/gfx/wr/example-compositor/compositor-windows/src/lib.rs
new file mode 100644
index 0000000000..f0e5fa5b9c
--- /dev/null
+++ b/gfx/wr/example-compositor/compositor-windows/src/lib.rs
@@ -0,0 +1,265 @@
+/* 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,
+ sync_mode: i32,
+ ) -> *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,
+ tile_width: i32,
+ tile_height: i32,
+ is_opaque: bool,
+ );
+
+ fn com_dc_create_tile(
+ window: *mut Window,
+ id: u64,
+ x: i32,
+ y: i32,
+ );
+
+ fn com_dc_destroy_tile(
+ window: *mut Window,
+ id: u64,
+ x: i32,
+ y: i32,
+ );
+
+ fn com_dc_destroy_surface(
+ window: *mut Window,
+ id: u64,
+ );
+
+ fn com_dc_bind_surface(
+ window: *mut Window,
+ surface_id: u64,
+ tile_x: i32,
+ tile_y: i32,
+ x_offset: &mut i32,
+ y_offset: &mut i32,
+ dirty_x0: i32,
+ dirty_y0: i32,
+ dirty_width: i32,
+ dirty_height: i32,
+ ) -> u32;
+ 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,
+ sync_mode: i32,
+) -> *mut Window {
+ unsafe {
+ com_dc_create_window(width, height, enable_compositor, sync_mode)
+ }
+}
+
+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,
+ tile_width: i32,
+ tile_height: i32,
+ is_opaque: bool,
+) {
+ unsafe {
+ com_dc_create_surface(
+ window,
+ id,
+ tile_width,
+ tile_height,
+ is_opaque,
+ )
+ }
+}
+
+pub fn create_tile(
+ window: *mut Window,
+ id: u64,
+ x: i32,
+ y: i32,
+) {
+ unsafe {
+ com_dc_create_tile(
+ window,
+ id,
+ x,
+ y,
+ )
+ }
+}
+
+pub fn destroy_tile(
+ window: *mut Window,
+ id: u64,
+ x: i32,
+ y: i32,
+) {
+ unsafe {
+ com_dc_destroy_tile(
+ window,
+ id,
+ x,
+ y,
+ )
+ }
+}
+
+pub fn destroy_surface(
+ window: *mut Window,
+ id: u64,
+) {
+ unsafe {
+ com_dc_destroy_surface(
+ window,
+ id,
+ )
+ }
+}
+
+pub fn bind_surface(
+ window: *mut Window,
+ surface_id: u64,
+ tile_x: i32,
+ tile_y: i32,
+ dirty_x0: i32,
+ dirty_y0: i32,
+ dirty_width: i32,
+ dirty_height: i32,
+) -> (u32, i32, i32) {
+ unsafe {
+ let mut x_offset = 0;
+ let mut y_offset = 0;
+
+ let fbo_id = com_dc_bind_surface(
+ window,
+ surface_id,
+ tile_x,
+ tile_y,
+ &mut x_offset,
+ &mut y_offset,
+ dirty_x0,
+ dirty_y0,
+ dirty_width,
+ dirty_height,
+ );
+
+ (fbo_id, 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);
+ }
+}
+
+pub fn deinit(_window: *mut Window) {
+ todo!()
+}
diff --git a/gfx/wr/example-compositor/compositor/Cargo.toml b/gfx/wr/example-compositor/compositor/Cargo.toml
new file mode 100644
index 0000000000..02c6ebe0ce
--- /dev/null
+++ b/gfx/wr/example-compositor/compositor/Cargo.toml
@@ -0,0 +1,16 @@
+[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.15"
+
+[target.'cfg(windows)'.dependencies]
+compositor-windows = { path = "../compositor-windows" }
+
+[target.'cfg(target_os = "linux")'.dependencies]
+compositor-wayland = { path = "../compositor-wayland" }
diff --git a/gfx/wr/example-compositor/compositor/src/main.rs b/gfx/wr/example-compositor/compositor/src/main.rs
new file mode 100644
index 0000000000..0a0275fe3f
--- /dev/null
+++ b/gfx/wr/example-compositor/compositor/src/main.rs
@@ -0,0 +1,549 @@
+/* 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 euclid::Angle;
+use gleam::gl;
+use std::ffi::CString;
+use std::sync::mpsc;
+use webrender::{CompositorSurfaceTransform, Transaction, api::*};
+use webrender::api::units::*;
+use webrender::Device;
+#[cfg(target_os = "windows")]
+use compositor_windows as compositor;
+#[cfg(target_os = "linux")]
+use compositor_wayland as compositor;
+use std::{env, f32, process};
+
+// 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,
+ _device: &mut Device,
+ id: webrender::NativeSurfaceId,
+ _virtual_offset: DeviceIntPoint,
+ tile_size: DeviceIntSize,
+ is_opaque: bool,
+ ) {
+ compositor::create_surface(
+ self.window,
+ id.0,
+ tile_size.width,
+ tile_size.height,
+ is_opaque,
+ );
+ }
+
+ fn destroy_surface(&mut self, _device: &mut Device, id: webrender::NativeSurfaceId) {
+ compositor::destroy_surface(self.window, id.0);
+ }
+
+ fn create_tile(&mut self, _device: &mut Device, id: webrender::NativeTileId) {
+ compositor::create_tile(self.window, id.surface_id.0, id.x, id.y);
+ }
+
+ fn destroy_tile(&mut self, _device: &mut Device, id: webrender::NativeTileId) {
+ compositor::destroy_tile(self.window, id.surface_id.0, id.x, id.y);
+ }
+
+ fn bind(
+ &mut self,
+ _device: &mut Device,
+ id: webrender::NativeTileId,
+ dirty_rect: DeviceIntRect,
+ _valid_rect: DeviceIntRect,
+ ) -> webrender::NativeSurfaceInfo {
+ let (fbo_id, x, y) = compositor::bind_surface(
+ self.window,
+ id.surface_id.0,
+ id.x,
+ id.y,
+ dirty_rect.min.x,
+ dirty_rect.min.y,
+ dirty_rect.width(),
+ dirty_rect.height(),
+ );
+
+ webrender::NativeSurfaceInfo {
+ origin: DeviceIntPoint::new(x, y),
+ fbo_id,
+ }
+ }
+
+ fn unbind(&mut self, _device: &mut Device) {
+ compositor::unbind_surface(self.window);
+ }
+
+ fn begin_frame(&mut self, _device: &mut Device) {
+ compositor::begin_transaction(self.window);
+ }
+
+ fn add_surface(
+ &mut self,
+ _device: &mut Device,
+ id: webrender::NativeSurfaceId,
+ transform: CompositorSurfaceTransform,
+ clip_rect: DeviceIntRect,
+ _image_rendering: ImageRendering,
+ ) {
+ compositor::add_surface(
+ self.window,
+ id.0,
+ transform.offset.x as i32,
+ transform.offset.y as i32,
+ clip_rect.min.x,
+ clip_rect.min.y,
+ clip_rect.width(),
+ clip_rect.height(),
+ );
+ }
+
+ fn end_frame(&mut self, _device: &mut Device) {
+ compositor::end_transaction(self.window);
+ }
+ fn create_external_surface(
+ &mut self,
+ _device: &mut Device,
+ _id: webrender::NativeSurfaceId,
+ _: bool,
+ ) {
+ todo!()
+ }
+
+ fn attach_external_image(
+ &mut self,
+ _device: &mut Device,
+ _id: webrender::NativeSurfaceId,
+ _external_image: ExternalImageId,
+ ) {
+ todo!()
+ }
+
+ fn enable_native_compositor(&mut self, _device: &mut Device, _enable: bool) {
+ todo!()
+ }
+
+ fn deinit(&mut self, _device: &mut Device) {
+ compositor::deinit(self.window);
+ }
+
+ fn get_capabilities(&self, _device: &mut Device) -> webrender::CompositorCapabilities {
+ webrender::CompositorCapabilities {
+ virtual_surface_size: 1024 * 1024,
+ ..Default::default()
+ }
+ }
+
+ fn invalidate_tile(
+ &mut self,
+ _device: &mut Device,
+ _id: webrender::NativeTileId,
+ _valid_rect: DeviceIntRect,
+ ) {
+ }
+
+ fn start_compositing(
+ &mut self,
+ _device: &mut Device,
+ _color: webrender::webrender_api::ColorF,
+ _dirty_rects: &[DeviceIntRect],
+ _opaque_rects: &[DeviceIntRect],
+ ) {
+ }
+
+ fn create_backdrop_surface(
+ &mut self,
+ _device: &mut Device,
+ _id: webrender::NativeSurfaceId,
+ _color: webrender::webrender_api::ColorF,
+ ) {
+ todo!()
+ }
+
+ fn get_window_visibility(&self, _device: &mut Device) -> webrender::WindowVisibility {
+ todo!()
+ }
+}
+
+// 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, _composite_needed: bool) {}
+
+ fn new_frame_ready(&self, _: DocumentId, _scrolled: bool, _composite_needed: bool, _: FramePublishId) {
+ self.tx.send(()).ok();
+ }
+}
+
+fn push_rotated_rect(
+ builder: &mut DisplayListBuilder,
+ rect: LayoutRect,
+ color: ColorF,
+ spatial_id: SpatialId,
+ _root_pipeline_id: PipelineId,
+ angle: f32,
+ time: f32,
+ item_key: SpatialTreeItemKey,
+) {
+ let color = color.scale_rgb(time);
+ let rotation = LayoutTransform::rotation(
+ 0.0,
+ 0.0,
+ 1.0,
+ Angle::radians(2.0 * std::f32::consts::PI * angle),
+ );
+ let center = rect.center();
+ let transform_origin = LayoutVector3D::new(center.x, center.y, 0.0);
+ let transform = rotation
+ .pre_translate(-transform_origin)
+ .then_translate(transform_origin);
+ let spatial_id = builder.push_reference_frame(
+ LayoutPoint::zero(),
+ spatial_id,
+ TransformStyle::Flat,
+ PropertyBinding::Value(transform),
+ ReferenceFrameKind::Transform {
+ is_2d_scale_translation: false,
+ should_snap: false,
+ paired_with_perspective: false,
+ },
+ item_key,
+ );
+ builder.push_rect(
+ &CommonItemProperties::new(
+ rect,
+ SpaceAndClipInfo {
+ spatial_id,
+ clip_chain_id: ClipChainId::INVALID,
+ },
+ ),
+ rect,
+ color,
+ );
+}
+
+fn build_display_list(
+ builder: &mut DisplayListBuilder,
+ scroll_id: ExternalScrollId,
+ root_pipeline_id: PipelineId,
+ layout_size: LayoutSize,
+ time: f32,
+ invalidations: Invalidations,
+) {
+ let size_factor = match invalidations {
+ Invalidations::Small => 0.1,
+ Invalidations::Large | Invalidations::Scrolling => 1.0,
+ };
+
+ let fixed_space_info = SpaceAndClipInfo {
+ spatial_id: SpatialId::root_scroll_node(root_pipeline_id),
+ clip_chain_id: ClipChainId::INVALID,
+ };
+
+ let scroll_spatial_id = builder.define_scroll_frame(
+ fixed_space_info.spatial_id,
+ scroll_id,
+ LayoutRect::from_size(layout_size),
+ LayoutRect::from_size(layout_size),
+ LayoutVector2D::zero(),
+ APZScrollGeneration::default(),
+ HasScrollLinkedEffect::No,
+ SpatialTreeItemKey::new(1, 0),
+ );
+
+ builder.push_rect(
+ &CommonItemProperties::new(
+ LayoutRect::from_size(layout_size).inflate(-10.0, -10.0),
+ fixed_space_info,
+ ),
+ LayoutRect::from_size(layout_size).inflate(-10.0, -10.0),
+ ColorF::new(0.8, 0.8, 0.8, 1.0),
+ );
+
+ push_rotated_rect(
+ builder,
+ LayoutRect::from_origin_and_size(
+ LayoutPoint::new(100.0, 100.0),
+ LayoutSize::new(size_factor * 400.0, size_factor * 400.0),
+ ),
+ ColorF::new(1.0, 0.0, 0.0, 1.0),
+ scroll_spatial_id,
+ root_pipeline_id,
+ time,
+ time,
+ SpatialTreeItemKey::new(1, 1),
+ );
+
+ push_rotated_rect(
+ builder,
+ LayoutRect::from_origin_and_size(
+ LayoutPoint::new(800.0, 100.0),
+ LayoutSize::new(size_factor * 100.0, size_factor * 600.0),
+ ),
+ ColorF::new(0.0, 1.0, 0.0, 1.0),
+ fixed_space_info.spatial_id,
+ root_pipeline_id,
+ 0.2,
+ time,
+ SpatialTreeItemKey::new(1, 2),
+ );
+
+ push_rotated_rect(
+ builder,
+ LayoutRect::from_origin_and_size(
+ LayoutPoint::new(700.0, 200.0),
+ LayoutSize::new(size_factor * 300.0, size_factor * 300.0),
+ ),
+ ColorF::new(0.0, 0.0, 1.0, 1.0),
+ scroll_spatial_id,
+ root_pipeline_id,
+ 0.1,
+ time,
+ SpatialTreeItemKey::new(1, 3),
+ );
+
+ push_rotated_rect(
+ builder,
+ LayoutRect::from_origin_and_size(
+ LayoutPoint::new(100.0, 600.0),
+ LayoutSize::new(size_factor * 400.0, size_factor * 400.0),
+ ),
+ ColorF::new(1.0, 1.0, 0.0, 1.0),
+ scroll_spatial_id,
+ root_pipeline_id,
+ time,
+ time,
+ SpatialTreeItemKey::new(1, 4),
+ );
+
+ push_rotated_rect(
+ builder,
+ LayoutRect::from_origin_and_size(
+ LayoutPoint::new(700.0, 600.0),
+ LayoutSize::new(size_factor * 400.0, size_factor * 400.0),
+ ),
+ ColorF::new(0.0, 1.0, 1.0, 1.0),
+ scroll_spatial_id,
+ root_pipeline_id,
+ time,
+ time,
+ SpatialTreeItemKey::new(1, 5),
+ );
+}
+
+#[derive(Debug, Copy, Clone)]
+enum Invalidations {
+ Large,
+ Small,
+ Scrolling,
+}
+
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+enum Sync {
+ None = 0,
+ Swap = 1,
+ Commit = 2,
+ Flush = 3,
+ Query = 4,
+}
+
+fn main() {
+ let args: Vec<String> = env::args().collect();
+
+ if args.len() != 6 {
+ println!("USAGE: compositor [native|none] [small|large|scroll] [none|swap|commit|flush|query] width height");
+ process::exit(0);
+ }
+
+ let enable_compositor = match args[1].parse::<String>().unwrap().as_str() {
+ "native" => true,
+ "none" => false,
+ _ => panic!("invalid compositor [native, none]"),
+ };
+
+ let inv_mode = match args[2].parse::<String>().unwrap().as_str() {
+ "small" => Invalidations::Small,
+ "large" => Invalidations::Large,
+ "scroll" => Invalidations::Scrolling,
+ _ => panic!("invalid invalidations [small, large, scroll]"),
+ };
+
+ let sync_mode = match args[3].parse::<String>().unwrap().as_str() {
+ "none" => Sync::None,
+ "swap" => Sync::Swap,
+ "commit" => Sync::Commit,
+ "flush" => Sync::Flush,
+ "query" => Sync::Query,
+ _ => panic!("invalid sync mode [none, swap, commit, flush, query]"),
+ };
+
+ let width = args[4].parse().unwrap();
+ let height = args[5].parse().unwrap();
+ let device_size = DeviceIntSize::new(width, height);
+
+ // Load GL, construct WR and the native compositor interface.
+ let window = compositor::create_window(
+ device_size.width,
+ device_size.height,
+ enable_compositor,
+ sync_mode as i32,
+ );
+ let debug_flags = DebugFlags::empty();
+ let compositor_config = if enable_compositor {
+ webrender::CompositorConfig::Native {
+ compositor: Box::new(DirectCompositeInterface::new(window)),
+ }
+ } else {
+ webrender::CompositorConfig::Draw {
+ max_partial_present_rects: 0,
+ draw_previous_partial_present_regions: false,
+ partial_present: None,
+ }
+ };
+ let opts = webrender::WebRenderOptions {
+ clear_color: ColorF::new(1.0, 1.0, 1.0, 1.0),
+ debug_flags,
+ compositor_config,
+ surface_origin_is_top_left: false,
+ ..webrender::WebRenderOptions::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::create_webrender_instance(gl.clone(), notifier, opts, None).unwrap();
+ let mut api = sender.create_api();
+ let document_id = api.add_document(device_size);
+ let device_pixel_ratio = 1.0;
+ let mut current_epoch = Epoch(0);
+ let root_pipeline_id = PipelineId(0, 0);
+ let layout_size = device_size.to_f32() / euclid::Scale::new(device_pixel_ratio);
+ let mut time = 0.0;
+ let scroll_id = ExternalScrollId(3, root_pipeline_id);
+
+ // 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);
+
+ if let Invalidations::Scrolling = inv_mode {
+ let mut root_builder = DisplayListBuilder::new(root_pipeline_id);
+ root_builder.begin();
+
+ build_display_list(
+ &mut root_builder,
+ scroll_id,
+ root_pipeline_id,
+ layout_size,
+ 1.0,
+ inv_mode,
+ );
+
+ txn.set_display_list(current_epoch, root_builder.end());
+ }
+
+ txn.generate_frame(0, RenderReasons::empty());
+ 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, 0).unwrap();
+ let _ = renderer.flush_pipeline_info();
+
+ // Construct a simple display list that can be drawn and composited by DC.
+ let mut txn = Transaction::new();
+
+ match inv_mode {
+ Invalidations::Small | Invalidations::Large => {
+ let mut root_builder = DisplayListBuilder::new(root_pipeline_id);
+ root_builder.begin();
+
+ build_display_list(
+ &mut root_builder,
+ scroll_id,
+ root_pipeline_id,
+ layout_size,
+ time,
+ inv_mode,
+ );
+
+ txn.set_display_list(current_epoch, root_builder.end());
+ }
+ Invalidations::Scrolling => {
+ let d = 0.5 - 0.5 * (2.0 * f32::consts::PI * 5.0 * time).cos();
+ txn.set_scroll_offsets(
+ scroll_id,
+ vec![SampledScrollOffset {
+ offset: LayoutVector2D::new(0.0, (d * 100.0).round()),
+ generation: APZScrollGeneration::default(),
+ }],
+ );
+ }
+ }
+
+ txn.generate_frame(0, RenderReasons::empty());
+ api.send_transaction(document_id, txn);
+ current_epoch.0 += 1;
+ time += 0.001;
+ if time > 1.0 {
+ time = 0.0;
+ }
+
+ // This does nothing when native compositor is enabled
+ compositor::swap_buffers(window);
+ }
+ }
+
+ renderer.deinit();
+ compositor::destroy_window(window);
+}