Vendor the current version of WebRender

This is a step toward upgrading WebRender, which will be upgraded and
patched in the `third_party` directory. This change vendors the current
private branch of WebRender that we use and adds a `patches` directory
which tracks the changes on top of the upstream WebRender commit
described by third_party/webrender/patches/head.
This commit is contained in:
Martin Robinson 2023-07-03 17:43:57 +02:00
parent c19eb800de
commit 49277f5c3f
No known key found for this signature in database
GPG key ID: D56AA4FA55EFE6F8
1215 changed files with 185677 additions and 34 deletions

View file

@ -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"

View file

@ -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");
}

View file

@ -0,0 +1,802 @@
/* 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);
}
}

View file

@ -0,0 +1,261 @@
/* 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);
}
}

View file

@ -0,0 +1,13 @@
[package]
name = "compositor"
version = "0.1.0"
authors = ["Glenn Watson <gw@intuitionlibrary.com>"]
edition = "2018"
license = "MPL-2.0"
[dependencies]
webrender = { path = "../../webrender" }
gleam = "0.12.0"
[target.'cfg(windows)'.dependencies]
compositor-windows = { path = "../compositor-windows" }

View file

@ -0,0 +1,484 @@
/* 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::api::*;
use webrender::api::units::*;
#[cfg(target_os = "windows")]
use compositor_windows 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,
id: webrender::NativeSurfaceId,
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,
id: webrender::NativeSurfaceId,
) {
compositor::destroy_surface(self.window, id.0);
}
fn create_tile(
&mut self,
id: webrender::NativeTileId,
) {
compositor::create_tile(
self.window,
id.surface_id.0,
id.x,
id.y,
);
}
fn destroy_tile(
&mut self,
id: webrender::NativeTileId,
) {
compositor::destroy_tile(
self.window,
id.surface_id.0,
id.x,
id.y,
);
}
fn bind(
&mut self,
id: webrender::NativeTileId,
dirty_rect: DeviceIntRect,
) -> webrender::NativeSurfaceInfo {
let (fbo_id, x, y) = compositor::bind_surface(
self.window,
id.surface_id.0,
id.x,
id.y,
dirty_rect.origin.x,
dirty_rect.origin.y,
dirty_rect.size.width,
dirty_rect.size.height,
);
webrender::NativeSurfaceInfo {
origin: DeviceIntPoint::new(x, y),
fbo_id,
}
}
fn unbind(&mut self) {
compositor::unbind_surface(self.window);
}
fn begin_frame(&mut self) {
compositor::begin_transaction(self.window);
}
fn add_surface(
&mut self,
id: webrender::NativeSurfaceId,
position: DeviceIntPoint,
clip_rect: DeviceIntRect,
) {
compositor::add_surface(
self.window,
id.0,
position.x,
position.y,
clip_rect.origin.x,
clip_rect.origin.y,
clip_rect.size.width,
clip_rect.size.height,
);
}
fn end_frame(&mut self) {
compositor::end_transaction(self.window);
}
}
// Simplisitic implementation of the WR notifier interface to know when a frame
// has been prepared and can be rendered.
struct Notifier {
tx: mpsc::Sender<()>,
}
impl Notifier {
fn new(tx: mpsc::Sender<()>) -> Self {
Notifier {
tx,
}
}
}
impl RenderNotifier for Notifier {
fn clone(&self) -> Box<dyn RenderNotifier> {
Box::new(Notifier {
tx: self.tx.clone()
})
}
fn wake_up(&self) {
}
fn new_frame_ready(&self,
_: DocumentId,
_scrolled: bool,
_composite_needed: bool,
_render_time: Option<u64>) {
self.tx.send(()).ok();
}
}
fn push_rotated_rect(
builder: &mut DisplayListBuilder,
rect: LayoutRect,
color: ColorF,
spatial_id: SpatialId,
root_pipeline_id: PipelineId,
angle: f32,
time: f32,
) {
let color = color.scale_rgb(time);
let rotation = LayoutTransform::create_rotation(
0.0,
0.0,
1.0,
Angle::radians(2.0 * std::f32::consts::PI * angle),
);
let transform_origin = LayoutVector3D::new(
rect.origin.x + rect.size.width * 0.5,
rect.origin.y + rect.size.height * 0.5,
0.0,
);
let transform = rotation
.pre_translate(-transform_origin)
.post_translate(transform_origin);
let spatial_id = builder.push_reference_frame(
LayoutPoint::zero(),
spatial_id,
TransformStyle::Flat,
PropertyBinding::Value(transform),
ReferenceFrameKind::Transform,
);
builder.push_rect(
&CommonItemProperties::new(
rect,
SpaceAndClipInfo {
spatial_id,
clip_id: ClipId::root(root_pipeline_id),
},
),
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_id: ClipId::root(root_pipeline_id),
};
let scroll_space_info = builder.define_scroll_frame(
&fixed_space_info,
Some(scroll_id),
LayoutRect::new(LayoutPoint::zero(), layout_size),
LayoutRect::new(LayoutPoint::zero(), layout_size),
ScrollSensitivity::Script,
LayoutVector2D::zero(),
);
builder.push_rect(
&CommonItemProperties::new(
LayoutRect::new(LayoutPoint::zero(), layout_size).inflate(-10.0, -10.0),
fixed_space_info,
),
LayoutRect::new(LayoutPoint::zero(), layout_size).inflate(-10.0, -10.0),
ColorF::new(0.8, 0.8, 0.8, 1.0),
);
push_rotated_rect(
builder,
LayoutRect::new(
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_space_info.spatial_id,
root_pipeline_id,
time,
time,
);
push_rotated_rect(
builder,
LayoutRect::new(
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,
);
push_rotated_rect(
builder,
LayoutRect::new(
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_space_info.spatial_id,
root_pipeline_id,
0.1,
time,
);
}
#[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 {
max_update_rects: 1,
compositor: Box::new(DirectCompositeInterface::new(window)),
}
} else {
webrender::CompositorConfig::Draw {
max_partial_present_rects: 0,
}
};
let opts = webrender::RendererOptions {
clear_color: Some(ColorF::new(1.0, 1.0, 1.0, 1.0)),
debug_flags,
enable_picture_caching: true,
compositor_config,
..webrender::RendererOptions::default()
};
let (tx, rx) = mpsc::channel();
let notifier = Box::new(Notifier::new(tx));
let gl = unsafe {
gl::GlesFns::load_with(
|symbol| {
let symbol = CString::new(symbol).unwrap();
let ptr = compositor::get_proc_address(symbol.as_ptr());
ptr
}
)
};
let (mut renderer, sender) = webrender::Renderer::new(
gl.clone(),
notifier,
opts,
None,
device_size,
).unwrap();
let api = sender.create_api();
let document_id = api.add_document(device_size, 0);
let device_pixel_ratio = 1.0;
let mut current_epoch = Epoch(0);
let root_pipeline_id = PipelineId(0, 0);
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, layout_size);
build_display_list(
&mut root_builder,
scroll_id,
root_pipeline_id,
layout_size,
1.0,
inv_mode,
);
txn.set_display_list(
current_epoch,
None,
layout_size,
root_builder.finalize(),
true,
);
}
txn.generate_frame();
api.send_transaction(document_id, txn);
// Tick the compositor (in this sample, we don't block on UI events)
while compositor::tick(window) {
// If there is a new frame ready to draw
if let Ok(..) = rx.try_recv() {
// Update and render. This will invoke the native compositor interface implemented above
// as required.
renderer.update();
renderer.render(device_size).unwrap();
let _ = renderer.flush_pipeline_info();
// Construct a simple display list that can be drawn and composited by DC.
let mut txn = Transaction::new();
match inv_mode {
Invalidations::Small | Invalidations::Large => {
let mut root_builder = DisplayListBuilder::new(root_pipeline_id, layout_size);
build_display_list(
&mut root_builder,
scroll_id,
root_pipeline_id,
layout_size,
time,
inv_mode,
);
txn.set_display_list(
current_epoch,
None,
layout_size,
root_builder.finalize(),
true,
);
}
Invalidations::Scrolling => {
let d = 0.5 - 0.5 * (2.0 * f32::consts::PI * 5.0 * time).cos();
txn.scroll_node_with_id(
LayoutPoint::new(0.0, (d * 100.0).round()),
scroll_id,
ScrollClamping::NoClamping,
);
}
}
txn.generate_frame();
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);
}