diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /gfx/wr/example-compositor | |
parent | Initial commit. (diff) | |
download | firefox-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')
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, ®istry_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, ¤tFboId); + glGetIntegerv(GL_RENDERBUFFER_BINDING, ¤tRboId); + + // 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); +} |