summaryrefslogtreecommitdiffstats
path: root/gfx/webrender_bindings
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/webrender_bindings')
-rw-r--r--gfx/webrender_bindings/Cargo.toml41
-rw-r--r--gfx/webrender_bindings/DCLayerTree.cpp1042
-rw-r--r--gfx/webrender_bindings/DCLayerTree.h294
-rw-r--r--gfx/webrender_bindings/Moz2DImageRenderer.cpp478
-rw-r--r--gfx/webrender_bindings/README.webrender23
-rw-r--r--gfx/webrender_bindings/RenderAndroidHardwareBufferTextureHost.cpp174
-rw-r--r--gfx/webrender_bindings/RenderAndroidHardwareBufferTextureHost.h49
-rw-r--r--gfx/webrender_bindings/RenderAndroidSurfaceTextureHost.cpp192
-rw-r--r--gfx/webrender_bindings/RenderAndroidSurfaceTextureHost.h63
-rw-r--r--gfx/webrender_bindings/RenderBufferTextureHost.cpp160
-rw-r--r--gfx/webrender_bindings/RenderBufferTextureHost.h63
-rw-r--r--gfx/webrender_bindings/RenderBufferTextureHostSWGL.cpp119
-rw-r--r--gfx/webrender_bindings/RenderBufferTextureHostSWGL.h43
-rw-r--r--gfx/webrender_bindings/RenderCompositor.cpp232
-rw-r--r--gfx/webrender_bindings/RenderCompositor.h211
-rw-r--r--gfx/webrender_bindings/RenderCompositorANGLE.cpp1081
-rw-r--r--gfx/webrender_bindings/RenderCompositorANGLE.h159
-rw-r--r--gfx/webrender_bindings/RenderCompositorD3D11SWGL.cpp599
-rw-r--r--gfx/webrender_bindings/RenderCompositorD3D11SWGL.h196
-rw-r--r--gfx/webrender_bindings/RenderCompositorEGL.cpp314
-rw-r--r--gfx/webrender_bindings/RenderCompositorEGL.h73
-rw-r--r--gfx/webrender_bindings/RenderCompositorNative.cpp653
-rw-r--r--gfx/webrender_bindings/RenderCompositorNative.h226
-rw-r--r--gfx/webrender_bindings/RenderCompositorOGL.cpp131
-rw-r--r--gfx/webrender_bindings/RenderCompositorOGL.h52
-rw-r--r--gfx/webrender_bindings/RenderCompositorRecordedFrame.h49
-rw-r--r--gfx/webrender_bindings/RenderCompositorSWGL.cpp242
-rw-r--r--gfx/webrender_bindings/RenderCompositorSWGL.h79
-rw-r--r--gfx/webrender_bindings/RenderD3D11TextureHost.cpp708
-rw-r--r--gfx/webrender_bindings/RenderD3D11TextureHost.h181
-rw-r--r--gfx/webrender_bindings/RenderDMABUFTextureHost.cpp74
-rw-r--r--gfx/webrender_bindings/RenderDMABUFTextureHost.h47
-rw-r--r--gfx/webrender_bindings/RenderEGLImageTextureHost.cpp102
-rw-r--r--gfx/webrender_bindings/RenderEGLImageTextureHost.h47
-rw-r--r--gfx/webrender_bindings/RenderExternalTextureHost.cpp202
-rw-r--r--gfx/webrender_bindings/RenderExternalTextureHost.h68
-rw-r--r--gfx/webrender_bindings/RenderMacIOSurfaceTextureHost.cpp165
-rw-r--r--gfx/webrender_bindings/RenderMacIOSurfaceTextureHost.h60
-rw-r--r--gfx/webrender_bindings/RenderSharedSurfaceTextureHost.cpp53
-rw-r--r--gfx/webrender_bindings/RenderSharedSurfaceTextureHost.h45
-rw-r--r--gfx/webrender_bindings/RenderSharedSurfaceTextureHostSWGL.cpp53
-rw-r--r--gfx/webrender_bindings/RenderSharedSurfaceTextureHostSWGL.h50
-rw-r--r--gfx/webrender_bindings/RenderTextureHost.cpp58
-rw-r--r--gfx/webrender_bindings/RenderTextureHost.h97
-rw-r--r--gfx/webrender_bindings/RenderTextureHostSWGL.cpp207
-rw-r--r--gfx/webrender_bindings/RenderTextureHostSWGL.h87
-rw-r--r--gfx/webrender_bindings/RenderTextureHostWrapper.cpp137
-rw-r--r--gfx/webrender_bindings/RenderTextureHostWrapper.h62
-rw-r--r--gfx/webrender_bindings/RenderThread.cpp1194
-rw-r--r--gfx/webrender_bindings/RenderThread.h371
-rw-r--r--gfx/webrender_bindings/RendererOGL.cpp434
-rw-r--r--gfx/webrender_bindings/RendererOGL.h168
-rw-r--r--gfx/webrender_bindings/RendererScreenshotGrabber.cpp106
-rw-r--r--gfx/webrender_bindings/RendererScreenshotGrabber.h102
-rw-r--r--gfx/webrender_bindings/WebRenderAPI.cpp1669
-rw-r--r--gfx/webrender_bindings/WebRenderAPI.h784
-rw-r--r--gfx/webrender_bindings/WebRenderTypes.cpp92
-rw-r--r--gfx/webrender_bindings/WebRenderTypes.h833
-rw-r--r--gfx/webrender_bindings/cbindgen.toml47
-rw-r--r--gfx/webrender_bindings/moz.build123
-rw-r--r--gfx/webrender_bindings/rustfmt.toml16
-rw-r--r--gfx/webrender_bindings/src/bindings.rs4011
-rw-r--r--gfx/webrender_bindings/src/lib.rs42
-rw-r--r--gfx/webrender_bindings/src/moz2d_renderer.rs895
-rw-r--r--gfx/webrender_bindings/src/program_cache.rs356
-rw-r--r--gfx/webrender_bindings/src/swgl_bindings.rs1766
-rw-r--r--gfx/webrender_bindings/webrender_ffi.h130
67 files changed, 22680 insertions, 0 deletions
diff --git a/gfx/webrender_bindings/Cargo.toml b/gfx/webrender_bindings/Cargo.toml
new file mode 100644
index 0000000000..5e68c6ff3e
--- /dev/null
+++ b/gfx/webrender_bindings/Cargo.toml
@@ -0,0 +1,41 @@
+[package]
+name = "webrender_bindings"
+version = "0.1.0"
+authors = ["The Mozilla Project Developers"]
+license = "MPL-2.0"
+
+[features]
+webrender_debugger = ["webrender/debugger"]
+
+[dependencies]
+dirs = "2"
+rayon = "1"
+num_cpus = "1.7.0"
+tracy-rs = "0.1"
+euclid = { version = "0.22.0", features = ["serde"] }
+app_units = "0.7"
+gleam = "0.13.1"
+log = "0.4"
+nsstring = { path = "../../xpcom/rust/nsstring" }
+bincode = "1.0"
+uuid = { version = "0.8", features = ["v4"] }
+fxhash = "0.2.1"
+thin-vec = { version = "0.2.1", features = ["gecko-ffi"] }
+swgl = { path = "../wr/swgl" }
+wr_malloc_size_of = { path = "../wr/wr_malloc_size_of" }
+
+[dependencies.webrender]
+path = "../wr/webrender"
+version = "0.61.0"
+default-features = false
+features = ["capture", "serialize_program"]
+
+[target.'cfg(target_os = "windows")'.dependencies]
+dwrote = "0.11"
+winapi = "0.3"
+
+[target.'cfg(target_os = "macos")'.dependencies]
+core-foundation = "0.9"
+core-graphics = "0.22"
+foreign-types = "0.3.0"
+objc = "0.2"
diff --git a/gfx/webrender_bindings/DCLayerTree.cpp b/gfx/webrender_bindings/DCLayerTree.cpp
new file mode 100644
index 0000000000..809117a359
--- /dev/null
+++ b/gfx/webrender_bindings/DCLayerTree.cpp
@@ -0,0 +1,1042 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "DCLayerTree.h"
+
+#include "GLContext.h"
+#include "GLContextEGL.h"
+#include "mozilla/gfx/DeviceManagerDx.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/webrender/RenderD3D11TextureHost.h"
+#include "mozilla/webrender/RenderTextureHost.h"
+#include "mozilla/webrender/RenderThread.h"
+#include "mozilla/Telemetry.h"
+#include "nsPrintfCString.h"
+
+#undef _WIN32_WINNT
+#define _WIN32_WINNT _WIN32_WINNT_WINBLUE
+#undef NTDDI_VERSION
+#define NTDDI_VERSION NTDDI_WINBLUE
+
+#include <d3d11.h>
+#include <d3d11_1.h>
+#include <dcomp.h>
+#include <dxgi1_2.h>
+
+namespace mozilla {
+namespace wr {
+
+/* static */
+UniquePtr<DCLayerTree> DCLayerTree::Create(gl::GLContext* aGL,
+ EGLConfig aEGLConfig,
+ ID3D11Device* aDevice,
+ ID3D11DeviceContext* aCtx,
+ HWND aHwnd, nsACString& aError) {
+ RefPtr<IDCompositionDevice2> dCompDevice =
+ gfx::DeviceManagerDx::Get()->GetDirectCompositionDevice();
+ if (!dCompDevice) {
+ aError.Assign("DCLayerTree(no device)"_ns);
+ return nullptr;
+ }
+
+ auto layerTree =
+ MakeUnique<DCLayerTree>(aGL, aEGLConfig, aDevice, aCtx, dCompDevice);
+ if (!layerTree->Initialize(aHwnd, aError)) {
+ return nullptr;
+ }
+
+ return layerTree;
+}
+
+DCLayerTree::DCLayerTree(gl::GLContext* aGL, EGLConfig aEGLConfig,
+ ID3D11Device* aDevice, ID3D11DeviceContext* aCtx,
+ IDCompositionDevice2* aCompositionDevice)
+ : mGL(aGL),
+ mEGLConfig(aEGLConfig),
+ mDevice(aDevice),
+ mCtx(aCtx),
+ mCompositionDevice(aCompositionDevice),
+ mVideoOverlaySupported(false),
+ mDebugCounter(false),
+ mDebugVisualRedrawRegions(false),
+ mEGLImage(EGL_NO_IMAGE),
+ mColorRBO(0),
+ mPendingCommit(false) {}
+
+DCLayerTree::~DCLayerTree() { ReleaseNativeCompositorResources(); }
+
+void DCLayerTree::ReleaseNativeCompositorResources() {
+ const auto gl = GetGLContext();
+
+ DestroyEGLSurface();
+
+ // Delete any cached FBO objects
+ for (auto it = mFrameBuffers.begin(); it != mFrameBuffers.end(); ++it) {
+ gl->fDeleteRenderbuffers(1, &it->depthRboId);
+ gl->fDeleteFramebuffers(1, &it->fboId);
+ }
+}
+
+bool DCLayerTree::Initialize(HWND aHwnd, nsACString& aError) {
+ HRESULT hr;
+
+ RefPtr<IDCompositionDesktopDevice> desktopDevice;
+ hr = mCompositionDevice->QueryInterface(
+ (IDCompositionDesktopDevice**)getter_AddRefs(desktopDevice));
+ if (FAILED(hr)) {
+ aError.Assign(nsPrintfCString(
+ "DCLayerTree(get IDCompositionDesktopDevice failed %x)", hr));
+ return false;
+ }
+
+ hr = desktopDevice->CreateTargetForHwnd(aHwnd, TRUE,
+ getter_AddRefs(mCompositionTarget));
+ if (FAILED(hr)) {
+ aError.Assign(nsPrintfCString(
+ "DCLayerTree(create DCompositionTarget failed %x)", hr));
+ return false;
+ }
+
+ hr = mCompositionDevice->CreateVisual(getter_AddRefs(mRootVisual));
+ if (FAILED(hr)) {
+ aError.Assign(nsPrintfCString(
+ "DCLayerTree(create root DCompositionVisual failed %x)", hr));
+ return false;
+ }
+
+ hr =
+ mCompositionDevice->CreateVisual(getter_AddRefs(mDefaultSwapChainVisual));
+ if (FAILED(hr)) {
+ aError.Assign(nsPrintfCString(
+ "DCLayerTree(create swap chain DCompositionVisual failed %x)", hr));
+ return false;
+ }
+
+ if (gfx::gfxVars::UseWebRenderDCompVideoOverlayWin()) {
+ if (!InitializeVideoOverlaySupport()) {
+ RenderThread::Get()->HandleWebRenderError(WebRenderError::VIDEO_OVERLAY);
+ }
+ }
+
+ mCompositionTarget->SetRoot(mRootVisual);
+ // Set interporation mode to nearest, to ensure 1:1 sampling.
+ // By default, a visual inherits the interpolation mode of the parent visual.
+ // If no visuals set the interpolation mode, the default for the entire visual
+ // tree is nearest neighbor interpolation.
+ mRootVisual->SetBitmapInterpolationMode(
+ DCOMPOSITION_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR);
+ return true;
+}
+
+bool DCLayerTree::InitializeVideoOverlaySupport() {
+ HRESULT hr;
+
+ hr = mDevice->QueryInterface(
+ (ID3D11VideoDevice**)getter_AddRefs(mVideoDevice));
+ if (FAILED(hr)) {
+ gfxCriticalNote << "Failed to get D3D11VideoDevice: " << gfx::hexa(hr);
+ return false;
+ }
+
+ hr =
+ mCtx->QueryInterface((ID3D11VideoContext**)getter_AddRefs(mVideoContext));
+ if (FAILED(hr)) {
+ gfxCriticalNote << "Failed to get D3D11VideoContext: " << gfx::hexa(hr);
+ return false;
+ }
+
+ // XXX When video is rendered to DXGI_FORMAT_B8G8R8A8_UNORM SwapChain with
+ // VideoProcessor, it seems that we do not need to check
+ // IDXGIOutput3::CheckOverlaySupport().
+ // If we want to yuv at DecodeSwapChain, its support seems necessary.
+
+ mVideoOverlaySupported = true;
+ return true;
+}
+
+DCSurface* DCLayerTree::GetSurface(wr::NativeSurfaceId aId) const {
+ auto surface_it = mDCSurfaces.find(aId);
+ MOZ_RELEASE_ASSERT(surface_it != mDCSurfaces.end());
+ return surface_it->second.get();
+}
+
+void DCLayerTree::SetDefaultSwapChain(IDXGISwapChain1* aSwapChain) {
+ mRootVisual->AddVisual(mDefaultSwapChainVisual, TRUE, nullptr);
+ mDefaultSwapChainVisual->SetContent(aSwapChain);
+ // Default SwapChain's visual does not need linear interporation.
+ mDefaultSwapChainVisual->SetBitmapInterpolationMode(
+ DCOMPOSITION_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR);
+ mPendingCommit = true;
+}
+
+void DCLayerTree::MaybeUpdateDebug() {
+ bool updated = false;
+ updated |= MaybeUpdateDebugCounter();
+ updated |= MaybeUpdateDebugVisualRedrawRegions();
+ if (updated) {
+ mPendingCommit = true;
+ }
+}
+
+void DCLayerTree::MaybeCommit() {
+ if (!mPendingCommit) {
+ return;
+ }
+ mCompositionDevice->Commit();
+}
+
+void DCLayerTree::WaitForCommitCompletion() {
+ mCompositionDevice->WaitForCommitCompletion();
+}
+
+void DCLayerTree::DisableNativeCompositor() {
+ MOZ_ASSERT(mCurrentSurface.isNothing());
+ MOZ_ASSERT(mCurrentLayers.empty());
+
+ ReleaseNativeCompositorResources();
+ mPrevLayers.clear();
+ mRootVisual->RemoveAllVisuals();
+}
+
+bool DCLayerTree::MaybeUpdateDebugCounter() {
+ bool debugCounter = StaticPrefs::gfx_webrender_debug_dcomp_counter();
+ if (mDebugCounter == debugCounter) {
+ return false;
+ }
+
+ RefPtr<IDCompositionDeviceDebug> debugDevice;
+ HRESULT hr = mCompositionDevice->QueryInterface(
+ (IDCompositionDeviceDebug**)getter_AddRefs(debugDevice));
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ if (debugCounter) {
+ debugDevice->EnableDebugCounters();
+ } else {
+ debugDevice->DisableDebugCounters();
+ }
+
+ mDebugCounter = debugCounter;
+ return true;
+}
+
+bool DCLayerTree::MaybeUpdateDebugVisualRedrawRegions() {
+ bool debugVisualRedrawRegions =
+ StaticPrefs::gfx_webrender_debug_dcomp_redraw_regions();
+ if (mDebugVisualRedrawRegions == debugVisualRedrawRegions) {
+ return false;
+ }
+
+ RefPtr<IDCompositionVisualDebug> visualDebug;
+ HRESULT hr = mRootVisual->QueryInterface(
+ (IDCompositionVisualDebug**)getter_AddRefs(visualDebug));
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ if (debugVisualRedrawRegions) {
+ visualDebug->EnableRedrawRegions();
+ } else {
+ visualDebug->DisableRedrawRegions();
+ }
+
+ mDebugVisualRedrawRegions = debugVisualRedrawRegions;
+ return true;
+}
+
+void DCLayerTree::CompositorBeginFrame() { mCurrentFrame++; }
+
+void DCLayerTree::CompositorEndFrame() {
+ auto start = TimeStamp::Now();
+ // Check if the visual tree of surfaces is the same as last frame.
+ bool same = mPrevLayers == mCurrentLayers;
+
+ if (!same) {
+ // If not, we need to rebuild the visual tree. Note that addition or
+ // removal of tiles no longer needs to rebuild the main visual tree
+ // here, since they are added as children of the surface visual.
+ mRootVisual->RemoveAllVisuals();
+ }
+
+ for (auto it = mCurrentLayers.begin(); it != mCurrentLayers.end(); ++it) {
+ auto surface_it = mDCSurfaces.find(*it);
+ MOZ_RELEASE_ASSERT(surface_it != mDCSurfaces.end());
+ const auto surface = surface_it->second.get();
+ // Ensure surface is trimmed to updated tile valid rects
+ surface->UpdateAllocatedRect();
+ if (!same) {
+ // Add surfaces in z-order they were added to the scene.
+ const auto visual = surface->GetVisual();
+ mRootVisual->AddVisual(visual, FALSE, nullptr);
+ }
+ }
+
+ mPrevLayers.swap(mCurrentLayers);
+ mCurrentLayers.clear();
+
+ mCompositionDevice->Commit();
+
+ auto end = TimeStamp::Now();
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::COMPOSITE_SWAP_TIME,
+ (end - start).ToMilliseconds() * 10.);
+
+ // Remove any framebuffers that haven't been
+ // used in the last 60 frames.
+ //
+ // This should use nsTArray::RemoveElementsBy once
+ // CachedFrameBuffer is able to properly destroy
+ // itself in the destructor.
+ const auto gl = GetGLContext();
+ for (uint32_t i = 0, len = mFrameBuffers.Length(); i < len; ++i) {
+ auto& fb = mFrameBuffers[i];
+ if ((mCurrentFrame - fb.lastFrameUsed) > 60) {
+ gl->fDeleteRenderbuffers(1, &fb.depthRboId);
+ gl->fDeleteFramebuffers(1, &fb.fboId);
+ mFrameBuffers.UnorderedRemoveElementAt(i);
+ --i; // Examine the element again, if necessary.
+ --len;
+ }
+ }
+}
+
+void DCLayerTree::Bind(wr::NativeTileId aId, wr::DeviceIntPoint* aOffset,
+ uint32_t* aFboId, wr::DeviceIntRect aDirtyRect,
+ wr::DeviceIntRect aValidRect) {
+ auto surface = GetSurface(aId.surface_id);
+ auto tile = surface->GetTile(aId.x, aId.y);
+ wr::DeviceIntPoint targetOffset{0, 0};
+
+ gfx::IntRect validRect(aValidRect.origin.x, aValidRect.origin.y,
+ aValidRect.size.width, aValidRect.size.height);
+ if (!tile->mValidRect.IsEqualEdges(validRect)) {
+ tile->mValidRect = validRect;
+ surface->DirtyAllocatedRect();
+ }
+ wr::DeviceIntSize tileSize = surface->GetTileSize();
+ RefPtr<IDCompositionSurface> compositionSurface =
+ surface->GetCompositionSurface();
+ wr::DeviceIntPoint virtualOffset = surface->GetVirtualOffset();
+ targetOffset.x = virtualOffset.x + tileSize.width * aId.x;
+ targetOffset.y = virtualOffset.y + tileSize.height * aId.y;
+
+ *aFboId = CreateEGLSurfaceForCompositionSurface(
+ aDirtyRect, aOffset, compositionSurface, targetOffset);
+ mCurrentSurface = Some(compositionSurface);
+}
+
+void DCLayerTree::Unbind() {
+ if (mCurrentSurface.isNothing()) {
+ return;
+ }
+
+ RefPtr<IDCompositionSurface> surface = mCurrentSurface.ref();
+ surface->EndDraw();
+
+ DestroyEGLSurface();
+ mCurrentSurface = Nothing();
+}
+
+void DCLayerTree::CreateSurface(wr::NativeSurfaceId aId,
+ wr::DeviceIntPoint aVirtualOffset,
+ wr::DeviceIntSize aTileSize, bool aIsOpaque) {
+ auto it = mDCSurfaces.find(aId);
+ MOZ_RELEASE_ASSERT(it == mDCSurfaces.end());
+ if (it != mDCSurfaces.end()) {
+ // DCSurface already exists.
+ return;
+ }
+
+ // Tile size needs to be positive.
+ if (aTileSize.width <= 0 || aTileSize.height <= 0) {
+ gfxCriticalNote << "TileSize is not positive aId: " << wr::AsUint64(aId)
+ << " aTileSize(" << aTileSize.width << ","
+ << aTileSize.height << ")";
+ }
+
+ auto surface =
+ MakeUnique<DCSurface>(aTileSize, aVirtualOffset, aIsOpaque, this);
+ if (!surface->Initialize()) {
+ gfxCriticalNote << "Failed to initialize DCSurface: " << wr::AsUint64(aId);
+ return;
+ }
+
+ mDCSurfaces[aId] = std::move(surface);
+}
+
+void DCLayerTree::CreateExternalSurface(wr::NativeSurfaceId aId,
+ bool aIsOpaque) {
+ auto it = mDCSurfaces.find(aId);
+ MOZ_RELEASE_ASSERT(it == mDCSurfaces.end());
+
+ auto surface = MakeUnique<DCSurfaceVideo>(aIsOpaque, this);
+ if (!surface->Initialize()) {
+ gfxCriticalNote << "Failed to initialize DCSurfaceVideo: "
+ << wr::AsUint64(aId);
+ return;
+ }
+
+ mDCSurfaces[aId] = std::move(surface);
+}
+
+void DCLayerTree::DestroySurface(NativeSurfaceId aId) {
+ auto surface_it = mDCSurfaces.find(aId);
+ MOZ_RELEASE_ASSERT(surface_it != mDCSurfaces.end());
+ auto surface = surface_it->second.get();
+
+ mRootVisual->RemoveVisual(surface->GetVisual());
+ mDCSurfaces.erase(surface_it);
+}
+
+void DCLayerTree::CreateTile(wr::NativeSurfaceId aId, int aX, int aY) {
+ auto surface = GetSurface(aId);
+ surface->CreateTile(aX, aY);
+}
+
+void DCLayerTree::DestroyTile(wr::NativeSurfaceId aId, int aX, int aY) {
+ auto surface = GetSurface(aId);
+ surface->DestroyTile(aX, aY);
+}
+
+void DCLayerTree::AttachExternalImage(wr::NativeSurfaceId aId,
+ wr::ExternalImageId aExternalImage) {
+ auto surface_it = mDCSurfaces.find(aId);
+ MOZ_RELEASE_ASSERT(surface_it != mDCSurfaces.end());
+ auto* surfaceVideo = surface_it->second->AsDCSurfaceVideo();
+ MOZ_RELEASE_ASSERT(surfaceVideo);
+
+ surfaceVideo->AttachExternalImage(aExternalImage);
+}
+
+template <typename T>
+static inline D2D1_RECT_F D2DRect(const T& aRect) {
+ return D2D1::RectF(aRect.X(), aRect.Y(), aRect.XMost(), aRect.YMost());
+}
+
+static inline D2D1_MATRIX_3X2_F D2DMatrix(const gfx::Matrix& aTransform) {
+ return D2D1::Matrix3x2F(aTransform._11, aTransform._12, aTransform._21,
+ aTransform._22, aTransform._31, aTransform._32);
+}
+
+void DCLayerTree::AddSurface(wr::NativeSurfaceId aId,
+ const wr::CompositorSurfaceTransform& aTransform,
+ wr::DeviceIntRect aClipRect,
+ wr::ImageRendering aImageRendering) {
+ auto it = mDCSurfaces.find(aId);
+ MOZ_RELEASE_ASSERT(it != mDCSurfaces.end());
+ const auto surface = it->second.get();
+ const auto visual = surface->GetVisual();
+
+ wr::DeviceIntPoint virtualOffset = surface->GetVirtualOffset();
+
+ gfx::Matrix transform(aTransform.m11, aTransform.m12, aTransform.m21,
+ aTransform.m22, aTransform.m41, aTransform.m42);
+ transform.PreTranslate(-virtualOffset.x, -virtualOffset.y);
+
+ // The DirectComposition API applies clipping *before* any transforms/offset,
+ // whereas we want the clip applied after.
+ // Right now, we only support rectilinear transforms, and then we transform
+ // our clip into pre-transform coordinate space for it to be applied there.
+ // DirectComposition does have an option for pre-transform clipping, if you
+ // create an explicit IDCompositionEffectGroup object and set a 3D transform
+ // on that. I suspect that will perform worse though, so we should only do
+ // that for complex transforms (which are never provided right now).
+ MOZ_ASSERT(transform.IsRectilinear());
+ gfx::Rect clip = transform.Inverse().TransformBounds(
+ gfx::Rect(aClipRect.origin.x, aClipRect.origin.y, aClipRect.size.width,
+ aClipRect.size.height));
+ // Set the clip rect - converting from world space to the pre-offset space
+ // that DC requires for rectangle clips.
+ visual->SetClip(D2DRect(clip));
+
+ // TODO: The input matrix is a 4x4, but we only support a 3x2 at
+ // the D3D API level (unless we QI to IDCompositionVisual3, which might
+ // not be available?).
+ // Should we assert here, or restrict at the WR API level.
+ visual->SetTransform(D2DMatrix(transform));
+
+ if (aImageRendering == wr::ImageRendering::Auto) {
+ visual->SetBitmapInterpolationMode(
+ DCOMPOSITION_BITMAP_INTERPOLATION_MODE_LINEAR);
+ } else {
+ visual->SetBitmapInterpolationMode(
+ DCOMPOSITION_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR);
+ }
+
+ mCurrentLayers.push_back(aId);
+}
+
+GLuint DCLayerTree::GetOrCreateFbo(int aWidth, int aHeight) {
+ const auto gl = GetGLContext();
+ GLuint fboId = 0;
+
+ // Check if we have a cached FBO with matching dimensions
+ for (auto it = mFrameBuffers.begin(); it != mFrameBuffers.end(); ++it) {
+ if (it->width == aWidth && it->height == aHeight) {
+ fboId = it->fboId;
+ it->lastFrameUsed = mCurrentFrame;
+ break;
+ }
+ }
+
+ // If not, create a new FBO with attached depth buffer
+ if (fboId == 0) {
+ // Create the depth buffer
+ GLuint depthRboId;
+ gl->fGenRenderbuffers(1, &depthRboId);
+ gl->fBindRenderbuffer(LOCAL_GL_RENDERBUFFER, depthRboId);
+ gl->fRenderbufferStorage(LOCAL_GL_RENDERBUFFER, LOCAL_GL_DEPTH_COMPONENT24,
+ aWidth, aHeight);
+
+ // Create the framebuffer and attach the depth buffer to it
+ gl->fGenFramebuffers(1, &fboId);
+ gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, fboId);
+ gl->fFramebufferRenderbuffer(LOCAL_GL_DRAW_FRAMEBUFFER,
+ LOCAL_GL_DEPTH_ATTACHMENT,
+ LOCAL_GL_RENDERBUFFER, depthRboId);
+
+ // Store this in the cache for future calls.
+ // TODO(gw): Maybe we should periodically scan this list and remove old
+ // entries that
+ // haven't been used for some time?
+ DCLayerTree::CachedFrameBuffer frame_buffer_info;
+ frame_buffer_info.width = aWidth;
+ frame_buffer_info.height = aHeight;
+ frame_buffer_info.fboId = fboId;
+ frame_buffer_info.depthRboId = depthRboId;
+ frame_buffer_info.lastFrameUsed = mCurrentFrame;
+ mFrameBuffers.AppendElement(frame_buffer_info);
+ }
+
+ return fboId;
+}
+
+bool DCLayerTree::EnsureVideoProcessor(const gfx::IntSize& aVideoSize) {
+ HRESULT hr;
+
+ if (!mVideoDevice || !mVideoContext) {
+ return false;
+ }
+
+ if (mVideoProcessor && aVideoSize == mVideoSize) {
+ return true;
+ }
+
+ mVideoProcessor = nullptr;
+ mVideoProcessorEnumerator = nullptr;
+
+ D3D11_VIDEO_PROCESSOR_CONTENT_DESC desc = {};
+ desc.InputFrameFormat = D3D11_VIDEO_FRAME_FORMAT_PROGRESSIVE;
+ desc.InputFrameRate.Numerator = 60;
+ desc.InputFrameRate.Denominator = 1;
+ desc.InputWidth = aVideoSize.width;
+ desc.InputHeight = aVideoSize.height;
+ desc.OutputFrameRate.Numerator = 60;
+ desc.OutputFrameRate.Denominator = 1;
+ desc.OutputWidth = aVideoSize.width;
+ desc.OutputHeight = aVideoSize.height;
+ desc.Usage = D3D11_VIDEO_USAGE_PLAYBACK_NORMAL;
+
+ hr = mVideoDevice->CreateVideoProcessorEnumerator(
+ &desc, getter_AddRefs(mVideoProcessorEnumerator));
+ if (FAILED(hr)) {
+ gfxCriticalNote << "Failed to create VideoProcessorEnumerator: "
+ << gfx::hexa(hr);
+ return false;
+ }
+
+ hr = mVideoDevice->CreateVideoProcessor(mVideoProcessorEnumerator, 0,
+ getter_AddRefs(mVideoProcessor));
+ if (FAILED(hr)) {
+ mVideoProcessor = nullptr;
+ mVideoProcessorEnumerator = nullptr;
+ gfxCriticalNote << "Failed to create VideoProcessor: " << gfx::hexa(hr);
+ return false;
+ }
+
+ // Reduce power cosumption
+ // By default, the driver might perform certain processing tasks automatically
+ mVideoContext->VideoProcessorSetStreamAutoProcessingMode(mVideoProcessor, 0,
+ FALSE);
+
+ mVideoSize = aVideoSize;
+ return true;
+}
+
+DCSurface::DCSurface(wr::DeviceIntSize aTileSize,
+ wr::DeviceIntPoint aVirtualOffset, bool aIsOpaque,
+ DCLayerTree* aDCLayerTree)
+ : mDCLayerTree(aDCLayerTree),
+ mTileSize(aTileSize),
+ mIsOpaque(aIsOpaque),
+ mAllocatedRectDirty(true),
+ mVirtualOffset(aVirtualOffset) {}
+
+DCSurface::~DCSurface() {}
+
+bool DCSurface::Initialize() {
+ HRESULT hr;
+ const auto dCompDevice = mDCLayerTree->GetCompositionDevice();
+ hr = dCompDevice->CreateVisual(getter_AddRefs(mVisual));
+ if (FAILED(hr)) {
+ gfxCriticalNote << "Failed to create DCompositionVisual: " << gfx::hexa(hr);
+ return false;
+ }
+
+ DXGI_ALPHA_MODE alpha_mode =
+ mIsOpaque ? DXGI_ALPHA_MODE_IGNORE : DXGI_ALPHA_MODE_PREMULTIPLIED;
+
+ hr = dCompDevice->CreateVirtualSurface(
+ VIRTUAL_SURFACE_SIZE, VIRTUAL_SURFACE_SIZE, DXGI_FORMAT_B8G8R8A8_UNORM,
+ alpha_mode, getter_AddRefs(mVirtualSurface));
+ MOZ_ASSERT(SUCCEEDED(hr));
+
+ // Bind the surface memory to this visual
+ hr = mVisual->SetContent(mVirtualSurface);
+ MOZ_ASSERT(SUCCEEDED(hr));
+
+ return true;
+}
+
+void DCSurface::CreateTile(int aX, int aY) {
+ TileKey key(aX, aY);
+ MOZ_RELEASE_ASSERT(mDCTiles.find(key) == mDCTiles.end());
+
+ auto tile = MakeUnique<DCTile>(mDCLayerTree);
+ if (!tile->Initialize(aX, aY, mTileSize, mIsOpaque)) {
+ gfxCriticalNote << "Failed to initialize DCTile: " << aX << aY;
+ return;
+ }
+
+ mAllocatedRectDirty = true;
+
+ mDCTiles[key] = std::move(tile);
+}
+
+void DCSurface::DestroyTile(int aX, int aY) {
+ TileKey key(aX, aY);
+ mAllocatedRectDirty = true;
+ mDCTiles.erase(key);
+}
+
+void DCSurface::DirtyAllocatedRect() { mAllocatedRectDirty = true; }
+
+void DCSurface::UpdateAllocatedRect() {
+ if (mAllocatedRectDirty) {
+ // The virtual surface may have holes in it (for example, an empty tile
+ // that has no primitives). Instead of trimming to a single bounding
+ // rect, supply the rect of each valid tile to handle this case.
+ std::vector<RECT> validRects;
+
+ for (auto it = mDCTiles.begin(); it != mDCTiles.end(); ++it) {
+ auto tile = GetTile(it->first.mX, it->first.mY);
+ RECT rect;
+
+ rect.left = (LONG)(mVirtualOffset.x + it->first.mX * mTileSize.width +
+ tile->mValidRect.x);
+ rect.top = (LONG)(mVirtualOffset.y + it->first.mY * mTileSize.height +
+ tile->mValidRect.y);
+ rect.right = rect.left + tile->mValidRect.width;
+ rect.bottom = rect.top + tile->mValidRect.height;
+
+ validRects.push_back(rect);
+ }
+
+ mVirtualSurface->Trim(validRects.data(), validRects.size());
+ mAllocatedRectDirty = false;
+ }
+}
+
+DCTile* DCSurface::GetTile(int aX, int aY) const {
+ TileKey key(aX, aY);
+ auto tile_it = mDCTiles.find(key);
+ MOZ_RELEASE_ASSERT(tile_it != mDCTiles.end());
+ return tile_it->second.get();
+}
+
+DCSurfaceVideo::DCSurfaceVideo(bool aIsOpaque, DCLayerTree* aDCLayerTree)
+ : DCSurface(wr::DeviceIntSize{}, wr::DeviceIntPoint{}, aIsOpaque,
+ aDCLayerTree) {}
+
+void DCSurfaceVideo::AttachExternalImage(wr::ExternalImageId aExternalImage) {
+ RenderTextureHost* texture =
+ RenderThread::Get()->GetRenderTexture(aExternalImage);
+ MOZ_RELEASE_ASSERT(texture);
+
+ if (mPrevTexture == texture) {
+ return;
+ }
+
+ // XXX if software decoded video frame format is nv12, it could be used as
+ // video overlay.
+ if (!texture || !texture->AsRenderDXGITextureHost() ||
+ texture->AsRenderDXGITextureHost()->GetFormat() !=
+ gfx::SurfaceFormat::NV12) {
+ gfxCriticalNote << "Unsupported RenderTexture for overlay: "
+ << gfx::hexa(texture);
+ return;
+ }
+
+ gfx::IntSize size = texture->AsRenderDXGITextureHost()->GetSize(0);
+ if (!mVideoSwapChain || mSwapChainSize != size) {
+ ReleaseDecodeSwapChainResources();
+ CreateVideoSwapChain(texture);
+ }
+
+ if (!mVideoSwapChain) {
+ gfxCriticalNote << "Failed to create VideoSwapChain";
+ RenderThread::Get()->NotifyWebRenderError(
+ wr::WebRenderError::VIDEO_OVERLAY);
+ return;
+ }
+
+ mVisual->SetContent(mVideoSwapChain);
+
+ if (!CallVideoProcessorBlt(texture)) {
+ RenderThread::Get()->NotifyWebRenderError(
+ wr::WebRenderError::VIDEO_OVERLAY);
+ return;
+ }
+
+ mVideoSwapChain->Present(0, 0);
+ mPrevTexture = texture;
+}
+
+bool DCSurfaceVideo::CreateVideoSwapChain(RenderTextureHost* aTexture) {
+ const auto device = mDCLayerTree->GetDevice();
+
+ RefPtr<IDXGIDevice> dxgiDevice;
+ device->QueryInterface((IDXGIDevice**)getter_AddRefs(dxgiDevice));
+
+ RefPtr<IDXGIFactoryMedia> dxgiFactoryMedia;
+ {
+ RefPtr<IDXGIAdapter> adapter;
+ dxgiDevice->GetAdapter(getter_AddRefs(adapter));
+ adapter->GetParent(
+ IID_PPV_ARGS((IDXGIFactoryMedia**)getter_AddRefs(dxgiFactoryMedia)));
+ }
+
+ mSwapChainSurfaceHandle = gfx::DeviceManagerDx::CreateDCompSurfaceHandle();
+ if (!mSwapChainSurfaceHandle) {
+ gfxCriticalNote << "Failed to create DCompSurfaceHandle";
+ return false;
+ }
+
+ gfx::IntSize size = aTexture->AsRenderDXGITextureHost()->GetSize(0);
+ DXGI_ALPHA_MODE alpha_mode =
+ mIsOpaque ? DXGI_ALPHA_MODE_IGNORE : DXGI_ALPHA_MODE_PREMULTIPLIED;
+
+ DXGI_SWAP_CHAIN_DESC1 desc = {};
+ desc.Width = size.width;
+ desc.Height = size.height;
+ desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
+ desc.Stereo = FALSE;
+ desc.SampleDesc.Count = 1;
+ desc.BufferCount = 2;
+ desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
+ desc.Scaling = DXGI_SCALING_STRETCH;
+ desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
+ desc.Flags = 0;
+ desc.AlphaMode = alpha_mode;
+
+ HRESULT hr;
+ hr = dxgiFactoryMedia->CreateSwapChainForCompositionSurfaceHandle(
+ device, mSwapChainSurfaceHandle, &desc, nullptr,
+ getter_AddRefs(mVideoSwapChain));
+
+ if (FAILED(hr)) {
+ gfxCriticalNote << "Failed to create video SwapChain: " << gfx::hexa(hr);
+ return false;
+ }
+
+ mSwapChainSize = size;
+ return true;
+}
+
+static Maybe<DXGI_COLOR_SPACE_TYPE> GetSourceDXGIColorSpace(
+ const gfx::YUVColorSpace aYUVColorSpace,
+ const gfx::ColorRange aColorRange) {
+ if (aYUVColorSpace == gfx::YUVColorSpace::BT601) {
+ if (aColorRange == gfx::ColorRange::FULL) {
+ return Some(DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P601);
+ } else {
+ return Some(DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P601);
+ }
+ } else if (aYUVColorSpace == gfx::YUVColorSpace::BT709) {
+ if (aColorRange == gfx::ColorRange::FULL) {
+ return Some(DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P709);
+ } else {
+ return Some(DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P709);
+ }
+ } else if (aYUVColorSpace == gfx::YUVColorSpace::BT2020) {
+ if (aColorRange == gfx::ColorRange::FULL) {
+ // XXX Add SMPTEST2084 handling. HDR content is not handled yet by
+ // video overlay.
+ return Some(DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P2020);
+ } else {
+ return Some(DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P2020);
+ }
+ }
+
+ return Nothing();
+}
+
+bool DCSurfaceVideo::CallVideoProcessorBlt(RenderTextureHost* aTexture) {
+ HRESULT hr;
+ const auto videoDevice = mDCLayerTree->GetVideoDevice();
+ const auto videoContext = mDCLayerTree->GetVideoContext();
+ const auto texture = aTexture->AsRenderDXGITextureHost();
+
+ Maybe<DXGI_COLOR_SPACE_TYPE> sourceColorSpace = GetSourceDXGIColorSpace(
+ texture->GetYUVColorSpace(), texture->GetColorRange());
+ if (sourceColorSpace.isNothing()) {
+ gfxCriticalNote << "Unsupported color space";
+ return false;
+ }
+
+ RefPtr<ID3D11Texture2D> texture2D = texture->GetD3D11Texture2DWithGL();
+ if (!texture2D) {
+ gfxCriticalNote << "Failed to get D3D11Texture2D";
+ return false;
+ }
+
+ if (!mVideoSwapChain) {
+ return false;
+ }
+
+ if (!mDCLayerTree->EnsureVideoProcessor(mSwapChainSize)) {
+ gfxCriticalNote << "EnsureVideoProcessor Failed";
+ return false;
+ }
+
+ RefPtr<IDXGISwapChain3> swapChain3;
+ mVideoSwapChain->QueryInterface(
+ (IDXGISwapChain3**)getter_AddRefs(swapChain3));
+ if (!swapChain3) {
+ gfxCriticalNote << "Failed to get IDXGISwapChain3";
+ return false;
+ }
+
+ RefPtr<ID3D11VideoContext1> videoContext1;
+ videoContext->QueryInterface(
+ (ID3D11VideoContext1**)getter_AddRefs(videoContext1));
+ if (!videoContext1) {
+ gfxCriticalNote << "Failed to get ID3D11VideoContext1";
+ return false;
+ }
+
+ const auto videoProcessor = mDCLayerTree->GetVideoProcessor();
+ const auto videoProcessorEnumerator =
+ mDCLayerTree->GetVideoProcessorEnumerator();
+
+ DXGI_COLOR_SPACE_TYPE inputColorSpace = sourceColorSpace.ref();
+ videoContext1->VideoProcessorSetStreamColorSpace1(videoProcessor, 0,
+ inputColorSpace);
+ // XXX when content is hdr or yuv swapchain, it need to use other color space.
+ DXGI_COLOR_SPACE_TYPE outputColorSpace =
+ DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
+ hr = swapChain3->SetColorSpace1(outputColorSpace);
+ if (FAILED(hr)) {
+ gfxCriticalNote << "SetColorSpace1 failed: " << gfx::hexa(hr);
+ return false;
+ }
+ videoContext1->VideoProcessorSetOutputColorSpace1(videoProcessor,
+ outputColorSpace);
+
+ D3D11_VIDEO_PROCESSOR_INPUT_VIEW_DESC inputDesc = {};
+ inputDesc.ViewDimension = D3D11_VPIV_DIMENSION_TEXTURE2D;
+ inputDesc.Texture2D.ArraySlice = 0;
+
+ RefPtr<ID3D11VideoProcessorInputView> inputView;
+ hr = videoDevice->CreateVideoProcessorInputView(
+ texture2D, videoProcessorEnumerator, &inputDesc,
+ getter_AddRefs(inputView));
+ if (FAILED(hr)) {
+ gfxCriticalNote << "ID3D11VideoProcessorInputView creation failed: "
+ << gfx::hexa(hr);
+ return false;
+ }
+
+ D3D11_VIDEO_PROCESSOR_STREAM stream = {};
+ stream.Enable = true;
+ stream.OutputIndex = 0;
+ stream.InputFrameOrField = 0;
+ stream.PastFrames = 0;
+ stream.FutureFrames = 0;
+ stream.pInputSurface = inputView.get();
+
+ RECT destRect;
+ destRect.left = 0;
+ destRect.top = 0;
+ destRect.right = mSwapChainSize.width;
+ destRect.bottom = mSwapChainSize.height;
+
+ videoContext->VideoProcessorSetOutputTargetRect(videoProcessor, TRUE,
+ &destRect);
+ videoContext->VideoProcessorSetStreamDestRect(videoProcessor, 0, TRUE,
+ &destRect);
+ RECT sourceRect;
+ sourceRect.left = 0;
+ sourceRect.top = 0;
+ sourceRect.right = mSwapChainSize.width;
+ sourceRect.bottom = mSwapChainSize.height;
+ videoContext->VideoProcessorSetStreamSourceRect(videoProcessor, 0, TRUE,
+ &sourceRect);
+
+ if (!mOutputView) {
+ RefPtr<ID3D11Texture2D> backBuf;
+ mVideoSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D),
+ (void**)getter_AddRefs(backBuf));
+
+ D3D11_VIDEO_PROCESSOR_OUTPUT_VIEW_DESC outputDesc = {};
+ outputDesc.ViewDimension = D3D11_VPOV_DIMENSION_TEXTURE2D;
+ outputDesc.Texture2D.MipSlice = 0;
+
+ hr = videoDevice->CreateVideoProcessorOutputView(
+ backBuf, videoProcessorEnumerator, &outputDesc,
+ getter_AddRefs(mOutputView));
+ if (FAILED(hr)) {
+ gfxCriticalNote << "ID3D11VideoProcessorOutputView creation failed: "
+ << gfx::hexa(hr);
+ return false;
+ }
+ }
+
+ hr = videoContext->VideoProcessorBlt(videoProcessor, mOutputView, 0, 1,
+ &stream);
+ if (FAILED(hr)) {
+ gfxCriticalNote << "VideoProcessorBlt failed: " << gfx::hexa(hr);
+ return false;
+ }
+
+ return true;
+}
+
+void DCSurfaceVideo::ReleaseDecodeSwapChainResources() {
+ mOutputView = nullptr;
+ mVideoSwapChain = nullptr;
+ mDecodeSwapChain = nullptr;
+ mDecodeResource = nullptr;
+ if (mSwapChainSurfaceHandle) {
+ ::CloseHandle(mSwapChainSurfaceHandle);
+ mSwapChainSurfaceHandle = 0;
+ }
+ mSwapChainSize = gfx::IntSize();
+}
+
+DCTile::DCTile(DCLayerTree* aDCLayerTree) : mDCLayerTree(aDCLayerTree) {}
+
+DCTile::~DCTile() {}
+
+bool DCTile::Initialize(int aX, int aY, wr::DeviceIntSize aSize,
+ bool aIsOpaque) {
+ if (aSize.width <= 0 || aSize.height <= 0) {
+ return false;
+ }
+
+ // Initially, the entire tile is considered valid, unless it is set by
+ // the SetTileProperties method.
+ mValidRect.x = 0;
+ mValidRect.y = 0;
+ mValidRect.width = aSize.width;
+ mValidRect.height = aSize.height;
+
+ return true;
+}
+
+GLuint DCLayerTree::CreateEGLSurfaceForCompositionSurface(
+ wr::DeviceIntRect aDirtyRect, wr::DeviceIntPoint* aOffset,
+ RefPtr<IDCompositionSurface> aCompositionSurface,
+ wr::DeviceIntPoint aSurfaceOffset) {
+ MOZ_ASSERT(aCompositionSurface.get());
+
+ HRESULT hr;
+ const auto gl = GetGLContext();
+ RefPtr<ID3D11Texture2D> backBuf;
+ POINT offset;
+
+ RECT update_rect;
+ update_rect.left = aSurfaceOffset.x + aDirtyRect.origin.x;
+ update_rect.top = aSurfaceOffset.y + aDirtyRect.origin.y;
+ update_rect.right = update_rect.left + aDirtyRect.size.width;
+ update_rect.bottom = update_rect.top + aDirtyRect.size.height;
+
+ hr = aCompositionSurface->BeginDraw(&update_rect, __uuidof(ID3D11Texture2D),
+ (void**)getter_AddRefs(backBuf), &offset);
+ if (FAILED(hr)) {
+ gfxCriticalNote << "DCompositionSurface::BeginDraw failed: "
+ << gfx::hexa(hr);
+ RenderThread::Get()->HandleWebRenderError(WebRenderError::NEW_SURFACE);
+ return false;
+ }
+
+ // 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.
+ offset.x -= aDirtyRect.origin.x;
+ offset.y -= aDirtyRect.origin.y;
+
+ D3D11_TEXTURE2D_DESC desc;
+ backBuf->GetDesc(&desc);
+
+ const auto& gle = gl::GLContextEGL::Cast(gl);
+ const auto& egl = gle->mEgl;
+
+ const auto buffer = reinterpret_cast<EGLClientBuffer>(backBuf.get());
+
+ // Construct an EGLImage wrapper around the D3D texture for ANGLE.
+ const EGLint attribs[] = {LOCAL_EGL_NONE};
+ mEGLImage = egl->fCreateImage(EGL_NO_CONTEXT, LOCAL_EGL_D3D11_TEXTURE_ANGLE,
+ buffer, attribs);
+
+ // Get the current FBO and RBO id, so we can restore them later
+ GLint currentFboId, currentRboId;
+ gl->fGetIntegerv(LOCAL_GL_DRAW_FRAMEBUFFER_BINDING, &currentFboId);
+ gl->fGetIntegerv(LOCAL_GL_RENDERBUFFER_BINDING, &currentRboId);
+
+ // Create a render buffer object that is backed by the EGL image.
+ gl->fGenRenderbuffers(1, &mColorRBO);
+ gl->fBindRenderbuffer(LOCAL_GL_RENDERBUFFER, mColorRBO);
+ gl->fEGLImageTargetRenderbufferStorage(LOCAL_GL_RENDERBUFFER, mEGLImage);
+
+ // Get or create an FBO for the specified dimensions
+ GLuint fboId = GetOrCreateFbo(desc.Width, desc.Height);
+
+ // Attach the new renderbuffer to the FBO
+ gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, fboId);
+ gl->fFramebufferRenderbuffer(LOCAL_GL_DRAW_FRAMEBUFFER,
+ LOCAL_GL_COLOR_ATTACHMENT0,
+ LOCAL_GL_RENDERBUFFER, mColorRBO);
+
+ // Restore previous FBO and RBO bindings
+ gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, currentFboId);
+ gl->fBindRenderbuffer(LOCAL_GL_RENDERBUFFER, currentRboId);
+
+ aOffset->x = offset.x;
+ aOffset->y = offset.y;
+
+ return fboId;
+}
+
+void DCLayerTree::DestroyEGLSurface() {
+ const auto gl = GetGLContext();
+
+ if (mColorRBO) {
+ gl->fDeleteRenderbuffers(1, &mColorRBO);
+ mColorRBO = 0;
+ }
+
+ if (mEGLImage) {
+ const auto& gle = gl::GLContextEGL::Cast(gl);
+ const auto& egl = gle->mEgl;
+ egl->fDestroyImage(mEGLImage);
+ mEGLImage = EGL_NO_IMAGE;
+ }
+}
+
+} // namespace wr
+} // namespace mozilla
diff --git a/gfx/webrender_bindings/DCLayerTree.h b/gfx/webrender_bindings/DCLayerTree.h
new file mode 100644
index 0000000000..eba2780ceb
--- /dev/null
+++ b/gfx/webrender_bindings/DCLayerTree.h
@@ -0,0 +1,294 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef MOZILLA_GFX_DCLAYER_TREE_H
+#define MOZILLA_GFX_DCLAYER_TREE_H
+
+#include <unordered_map>
+#include <vector>
+#include <windows.h>
+
+#include "GLTypes.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/webrender/WebRenderTypes.h"
+
+struct ID3D11Device;
+struct ID3D11DeviceContext;
+struct ID3D11VideoDevice;
+struct ID3D11VideoContext;
+struct ID3D11VideoProcessor;
+struct ID3D11VideoProcessorEnumerator;
+struct ID3D11VideoProcessorOutputView;
+struct IDCompositionDevice2;
+struct IDCompositionSurface;
+struct IDCompositionTarget;
+struct IDCompositionVisual2;
+struct IDXGIDecodeSwapChain;
+struct IDXGIResource;
+struct IDXGISwapChain1;
+struct IDCompositionVirtualSurface;
+
+namespace mozilla {
+
+namespace gl {
+class GLContext;
+}
+
+namespace wr {
+
+// The size of the virtual surface. This is large enough such that we
+// will never render a surface larger than this.
+#define VIRTUAL_SURFACE_SIZE (1024 * 1024)
+
+class DCTile;
+class DCSurface;
+class DCSurfaceVideo;
+class RenderTextureHost;
+
+/**
+ * DCLayerTree manages direct composition layers.
+ * It does not manage gecko's layers::Layer.
+ */
+class DCLayerTree {
+ public:
+ static UniquePtr<DCLayerTree> Create(gl::GLContext* aGL, EGLConfig aEGLConfig,
+ ID3D11Device* aDevice,
+ ID3D11DeviceContext* aCtx, HWND aHwnd,
+ nsACString& aError);
+ explicit DCLayerTree(gl::GLContext* aGL, EGLConfig aEGLConfig,
+ ID3D11Device* aDevice, ID3D11DeviceContext* aCtx,
+ IDCompositionDevice2* aCompositionDevice);
+ ~DCLayerTree();
+
+ void SetDefaultSwapChain(IDXGISwapChain1* aSwapChain);
+ void MaybeUpdateDebug();
+ void MaybeCommit();
+ void WaitForCommitCompletion();
+ void DisableNativeCompositor();
+
+ // Interface for wr::Compositor
+ void CompositorBeginFrame();
+ void CompositorEndFrame();
+ void Bind(wr::NativeTileId aId, wr::DeviceIntPoint* aOffset, uint32_t* aFboId,
+ wr::DeviceIntRect aDirtyRect, wr::DeviceIntRect aValidRect);
+ void Unbind();
+ void CreateSurface(wr::NativeSurfaceId aId, wr::DeviceIntPoint aVirtualOffset,
+ wr::DeviceIntSize aTileSize, bool aIsOpaque);
+ void CreateExternalSurface(wr::NativeSurfaceId aId, bool aIsOpaque);
+ void DestroySurface(NativeSurfaceId aId);
+ void CreateTile(wr::NativeSurfaceId aId, int32_t aX, int32_t aY);
+ void DestroyTile(wr::NativeSurfaceId aId, int32_t aX, int32_t aY);
+ void AttachExternalImage(wr::NativeSurfaceId aId,
+ wr::ExternalImageId aExternalImage);
+ void AddSurface(wr::NativeSurfaceId aId,
+ const wr::CompositorSurfaceTransform& aTransform,
+ wr::DeviceIntRect aClipRect,
+ wr::ImageRendering aImageRendering);
+
+ gl::GLContext* GetGLContext() const { return mGL; }
+ EGLConfig GetEGLConfig() const { return mEGLConfig; }
+ ID3D11Device* GetDevice() const { return mDevice; }
+ IDCompositionDevice2* GetCompositionDevice() const {
+ return mCompositionDevice;
+ }
+ ID3D11VideoDevice* GetVideoDevice() const { return mVideoDevice; }
+ ID3D11VideoContext* GetVideoContext() const { return mVideoContext; }
+ ID3D11VideoProcessor* GetVideoProcessor() const { return mVideoProcessor; }
+ ID3D11VideoProcessorEnumerator* GetVideoProcessorEnumerator() const {
+ return mVideoProcessorEnumerator;
+ }
+ bool EnsureVideoProcessor(const gfx::IntSize& aVideoSize);
+
+ DCSurface* GetSurface(wr::NativeSurfaceId aId) const;
+
+ // Get or create an FBO with depth buffer suitable for specified dimensions
+ GLuint GetOrCreateFbo(int aWidth, int aHeight);
+
+ protected:
+ bool Initialize(HWND aHwnd, nsACString& aError);
+ bool InitializeVideoOverlaySupport();
+ bool MaybeUpdateDebugCounter();
+ bool MaybeUpdateDebugVisualRedrawRegions();
+ void DestroyEGLSurface();
+ GLuint CreateEGLSurfaceForCompositionSurface(
+ wr::DeviceIntRect aDirtyRect, wr::DeviceIntPoint* aOffset,
+ RefPtr<IDCompositionSurface> aCompositionSurface,
+ wr::DeviceIntPoint aSurfaceOffset);
+ void ReleaseNativeCompositorResources();
+
+ RefPtr<gl::GLContext> mGL;
+ EGLConfig mEGLConfig;
+
+ RefPtr<ID3D11Device> mDevice;
+ RefPtr<ID3D11DeviceContext> mCtx;
+
+ RefPtr<IDCompositionDevice2> mCompositionDevice;
+ RefPtr<IDCompositionTarget> mCompositionTarget;
+ RefPtr<IDCompositionVisual2> mRootVisual;
+ RefPtr<IDCompositionVisual2> mDefaultSwapChainVisual;
+
+ RefPtr<ID3D11VideoDevice> mVideoDevice;
+ RefPtr<ID3D11VideoContext> mVideoContext;
+ RefPtr<ID3D11VideoProcessor> mVideoProcessor;
+ RefPtr<ID3D11VideoProcessorEnumerator> mVideoProcessorEnumerator;
+ gfx::IntSize mVideoSize;
+
+ bool mVideoOverlaySupported;
+
+ bool mDebugCounter;
+ bool mDebugVisualRedrawRegions;
+
+ Maybe<RefPtr<IDCompositionSurface>> mCurrentSurface;
+
+ // The EGL image that is bound to the D3D texture provided by
+ // DirectComposition.
+ EGLImage mEGLImage;
+
+ // The GL render buffer ID that maps the EGLImage to an RBO for attaching to
+ // an FBO.
+ GLuint mColorRBO;
+
+ struct SurfaceIdHashFn {
+ std::size_t operator()(const wr::NativeSurfaceId& aId) const {
+ return HashGeneric(wr::AsUint64(aId));
+ }
+ };
+
+ std::unordered_map<wr::NativeSurfaceId, UniquePtr<DCSurface>, SurfaceIdHashFn>
+ mDCSurfaces;
+
+ // A list of layer IDs as they are added to the visual tree this frame.
+ std::vector<wr::NativeSurfaceId> mCurrentLayers;
+
+ // The previous frame's list of layer IDs in visual order.
+ std::vector<wr::NativeSurfaceId> mPrevLayers;
+
+ // Information about a cached FBO that is retained between frames.
+ struct CachedFrameBuffer {
+ int width;
+ int height;
+ GLuint fboId;
+ GLuint depthRboId;
+ int lastFrameUsed;
+ };
+
+ // A cache of FBOs, containing a depth buffer allocated to a specific size.
+ // TODO(gw): Might be faster as a hashmap? The length is typically much less
+ // than 10.
+ nsTArray<CachedFrameBuffer> mFrameBuffers;
+ int mCurrentFrame = 0;
+
+ bool mPendingCommit;
+};
+
+/**
+ Represents a single picture cache slice. Each surface contains some
+ number of tiles. An implementation may choose to allocate individual
+ tiles to render in to (as the current impl does), or allocate a large
+ single virtual surface to draw into (e.g. the DirectComposition virtual
+ surface API in future).
+ */
+class DCSurface {
+ public:
+ explicit DCSurface(wr::DeviceIntSize aTileSize,
+ wr::DeviceIntPoint aVirtualOffset, bool aIsOpaque,
+ DCLayerTree* aDCLayerTree);
+ virtual ~DCSurface();
+
+ bool Initialize();
+ void CreateTile(int32_t aX, int32_t aY);
+ void DestroyTile(int32_t aX, int32_t aY);
+
+ IDCompositionVisual2* GetVisual() const { return mVisual; }
+ DCTile* GetTile(int32_t aX, int32_t aY) const;
+
+ struct TileKey {
+ TileKey(int32_t aX, int32_t aY) : mX(aX), mY(aY) {}
+
+ int32_t mX;
+ int32_t mY;
+ };
+
+ wr::DeviceIntSize GetTileSize() const { return mTileSize; }
+ wr::DeviceIntPoint GetVirtualOffset() const { return mVirtualOffset; }
+
+ IDCompositionVirtualSurface* GetCompositionSurface() const {
+ return mVirtualSurface;
+ }
+
+ void UpdateAllocatedRect();
+ void DirtyAllocatedRect();
+
+ virtual DCSurfaceVideo* AsDCSurfaceVideo() { return nullptr; }
+
+ protected:
+ DCLayerTree* mDCLayerTree;
+
+ struct TileKeyHashFn {
+ std::size_t operator()(const TileKey& aId) const {
+ return HashGeneric(aId.mX, aId.mY);
+ }
+ };
+
+ // The visual for this surface. No content is attached to here, but tiles
+ // that belong to this surface are added as children. In this way, we can
+ // set the clip and scroll offset once, on this visual, to affect all
+ // children.
+ RefPtr<IDCompositionVisual2> mVisual;
+
+ wr::DeviceIntSize mTileSize;
+ bool mIsOpaque;
+ bool mAllocatedRectDirty;
+ std::unordered_map<TileKey, UniquePtr<DCTile>, TileKeyHashFn> mDCTiles;
+ wr::DeviceIntPoint mVirtualOffset;
+ RefPtr<IDCompositionVirtualSurface> mVirtualSurface;
+};
+
+class DCSurfaceVideo : public DCSurface {
+ public:
+ DCSurfaceVideo(bool aIsOpaque, DCLayerTree* aDCLayerTree);
+
+ void AttachExternalImage(wr::ExternalImageId aExternalImage);
+
+ DCSurfaceVideo* AsDCSurfaceVideo() override { return this; }
+
+ protected:
+ bool CreateVideoSwapChain(RenderTextureHost* aTexture);
+ bool CallVideoProcessorBlt(RenderTextureHost* aTexture);
+ void ReleaseDecodeSwapChainResources();
+
+ RefPtr<ID3D11VideoProcessorOutputView> mOutputView;
+ RefPtr<IDXGIResource> mDecodeResource;
+ RefPtr<IDXGISwapChain1> mVideoSwapChain;
+ RefPtr<IDXGIDecodeSwapChain> mDecodeSwapChain;
+ HANDLE mSwapChainSurfaceHandle;
+ gfx::IntSize mSwapChainSize;
+ RefPtr<RenderTextureHost> mPrevTexture;
+};
+
+class DCTile {
+ public:
+ explicit DCTile(DCLayerTree* aDCLayerTree);
+ ~DCTile();
+ bool Initialize(int aX, int aY, wr::DeviceIntSize aSize, bool aIsOpaque);
+
+ gfx::IntRect mValidRect;
+
+ DCLayerTree* mDCLayerTree;
+};
+
+static inline bool operator==(const DCSurface::TileKey& a0,
+ const DCSurface::TileKey& a1) {
+ return a0.mX == a1.mX && a0.mY == a1.mY;
+}
+
+} // namespace wr
+} // namespace mozilla
+
+#endif
diff --git a/gfx/webrender_bindings/Moz2DImageRenderer.cpp b/gfx/webrender_bindings/Moz2DImageRenderer.cpp
new file mode 100644
index 0000000000..40f25ecad6
--- /dev/null
+++ b/gfx/webrender_bindings/Moz2DImageRenderer.cpp
@@ -0,0 +1,478 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "mozilla/StaticPrefs_gfx.h"
+#include "gfxUtils.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/Range.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/RectAbsolute.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/gfx/RecordedEvent.h"
+#include "mozilla/layers/WebRenderDrawEventRecorder.h"
+#include "WebRenderTypes.h"
+#include "webrender_ffi.h"
+#include "GeckoProfiler.h"
+
+#include <unordered_map>
+
+#ifdef XP_MACOSX
+# include "mozilla/gfx/UnscaledFontMac.h"
+#elif defined(XP_WIN)
+# include "mozilla/gfx/UnscaledFontDWrite.h"
+#else
+# include "mozilla/gfx/UnscaledFontFreeType.h"
+#endif
+
+namespace std {
+template <>
+struct hash<mozilla::wr::FontKey> {
+ size_t operator()(const mozilla::wr::FontKey& key) const {
+ return hash<size_t>()(mozilla::wr::AsUint64(key));
+ }
+};
+
+template <>
+struct hash<mozilla::wr::FontInstanceKey> {
+ size_t operator()(const mozilla::wr::FontInstanceKey& key) const {
+ return hash<size_t>()(mozilla::wr::AsUint64(key));
+ }
+};
+}; // namespace std
+
+namespace mozilla {
+
+using namespace gfx;
+
+namespace wr {
+
+struct FontTemplate {
+ const uint8_t* mData;
+ size_t mSize;
+ uint32_t mIndex;
+ const VecU8* mVec;
+ RefPtr<UnscaledFont> mUnscaledFont;
+
+ FontTemplate() : mData(nullptr), mSize(0), mIndex(0), mVec(nullptr) {}
+
+ ~FontTemplate() {
+ if (mVec) {
+ wr_dec_ref_arc(mVec);
+ }
+ }
+};
+
+struct FontInstanceData {
+ WrFontKey mFontKey;
+ float mSize;
+ Maybe<FontInstanceOptions> mOptions;
+ Maybe<FontInstancePlatformOptions> mPlatformOptions;
+ UniquePtr<gfx::FontVariation[]> mVariations;
+ size_t mNumVariations;
+ RefPtr<ScaledFont> mScaledFont;
+
+ FontInstanceData() : mSize(0), mNumVariations(0) {}
+};
+
+StaticMutex sFontDataTableLock;
+std::unordered_map<WrFontKey, FontTemplate> sFontDataTable;
+std::unordered_map<WrFontInstanceKey, FontInstanceData> sBlobFontTable;
+
+// Fixed-size ring buffer logging font deletion events to aid debugging.
+static struct FontDeleteLog {
+ static const size_t MAX_ENTRIES = 256;
+
+ uint64_t mEntries[MAX_ENTRIES] = {0};
+ size_t mNextEntry = 0;
+
+ void AddEntry(uint64_t aEntry) {
+ mEntries[mNextEntry] = aEntry;
+ mNextEntry = (mNextEntry + 1) % MAX_ENTRIES;
+ }
+
+ void Add(WrFontKey aKey) { AddEntry(AsUint64(aKey)); }
+
+ // Store namespace clears as font id 0, since this will never be allocated.
+ void Add(WrIdNamespace aNamespace) {
+ AddEntry(AsUint64(WrFontKey{aNamespace, 0}));
+ }
+
+ void AddAll() { AddEntry(~0); }
+
+ // Find a matching entry in the log, searching backwards starting at the
+ // newest entry and finishing with the oldest entry. Returns a brief
+ // description of why the font was deleted, if known.
+ const char* Find(WrFontKey aKey) {
+ uint64_t keyEntry = AsUint64(aKey);
+ uint64_t namespaceEntry = AsUint64(WrFontKey{aKey.mNamespace, 0});
+ size_t offset = mNextEntry;
+ do {
+ offset = (offset + MAX_ENTRIES - 1) % MAX_ENTRIES;
+ if (mEntries[offset] == keyEntry) {
+ return "deleted font";
+ } else if (mEntries[offset] == namespaceEntry) {
+ return "cleared namespace";
+ } else if (mEntries[offset] == (uint64_t)~0) {
+ return "cleared all";
+ }
+ } while (offset != mNextEntry);
+ return "unknown font";
+ }
+} sFontDeleteLog;
+
+void ClearAllBlobImageResources() {
+ StaticMutexAutoLock lock(sFontDataTableLock);
+ sFontDeleteLog.AddAll();
+ sBlobFontTable.clear();
+ sFontDataTable.clear();
+}
+
+extern "C" {
+void ClearBlobImageResources(WrIdNamespace aNamespace) {
+ StaticMutexAutoLock lock(sFontDataTableLock);
+ sFontDeleteLog.Add(aNamespace);
+ for (auto i = sBlobFontTable.begin(); i != sBlobFontTable.end();) {
+ if (i->first.mNamespace == aNamespace) {
+ i = sBlobFontTable.erase(i);
+ } else {
+ i++;
+ }
+ }
+ for (auto i = sFontDataTable.begin(); i != sFontDataTable.end();) {
+ if (i->first.mNamespace == aNamespace) {
+ i = sFontDataTable.erase(i);
+ } else {
+ i++;
+ }
+ }
+}
+
+bool HasFontData(WrFontKey aKey) {
+ StaticMutexAutoLock lock(sFontDataTableLock);
+ return sFontDataTable.find(aKey) != sFontDataTable.end();
+}
+
+void AddFontData(WrFontKey aKey, const uint8_t* aData, size_t aSize,
+ uint32_t aIndex, const ArcVecU8* aVec) {
+ StaticMutexAutoLock lock(sFontDataTableLock);
+ auto i = sFontDataTable.find(aKey);
+ if (i == sFontDataTable.end()) {
+ FontTemplate& font = sFontDataTable[aKey];
+ font.mData = aData;
+ font.mSize = aSize;
+ font.mIndex = aIndex;
+ font.mVec = wr_add_ref_arc(aVec);
+ }
+}
+
+void AddNativeFontHandle(WrFontKey aKey, void* aHandle, uint32_t aIndex) {
+ StaticMutexAutoLock lock(sFontDataTableLock);
+ auto i = sFontDataTable.find(aKey);
+ if (i == sFontDataTable.end()) {
+ FontTemplate& font = sFontDataTable[aKey];
+#ifdef XP_MACOSX
+ font.mUnscaledFont =
+ new UnscaledFontMac(reinterpret_cast<CGFontRef>(aHandle), false);
+#elif defined(XP_WIN)
+ font.mUnscaledFont = new UnscaledFontDWrite(
+ reinterpret_cast<IDWriteFontFace*>(aHandle), nullptr);
+#elif defined(ANDROID)
+ font.mUnscaledFont = new UnscaledFontFreeType(
+ reinterpret_cast<const char*>(aHandle), aIndex);
+#else
+ font.mUnscaledFont = new UnscaledFontFontconfig(
+ reinterpret_cast<const char*>(aHandle), aIndex);
+#endif
+ }
+}
+
+void DeleteFontData(WrFontKey aKey) {
+ StaticMutexAutoLock lock(sFontDataTableLock);
+ sFontDeleteLog.Add(aKey);
+ auto i = sFontDataTable.find(aKey);
+ if (i != sFontDataTable.end()) {
+ sFontDataTable.erase(i);
+ }
+}
+
+void AddBlobFont(WrFontInstanceKey aInstanceKey, WrFontKey aFontKey,
+ float aSize, const FontInstanceOptions* aOptions,
+ const FontInstancePlatformOptions* aPlatformOptions,
+ const FontVariation* aVariations, size_t aNumVariations) {
+ StaticMutexAutoLock lock(sFontDataTableLock);
+ auto i = sBlobFontTable.find(aInstanceKey);
+ if (i == sBlobFontTable.end()) {
+ FontInstanceData& font = sBlobFontTable[aInstanceKey];
+ font.mFontKey = aFontKey;
+ font.mSize = aSize;
+ if (aOptions) {
+ font.mOptions = Some(*aOptions);
+ }
+ if (aPlatformOptions) {
+ font.mPlatformOptions = Some(*aPlatformOptions);
+ }
+ if (aNumVariations) {
+ font.mNumVariations = aNumVariations;
+ font.mVariations.reset(new gfx::FontVariation[aNumVariations]);
+ PodCopy(font.mVariations.get(),
+ reinterpret_cast<const gfx::FontVariation*>(aVariations),
+ aNumVariations);
+ }
+ }
+}
+
+void DeleteBlobFont(WrFontInstanceKey aKey) {
+ StaticMutexAutoLock lock(sFontDataTableLock);
+ auto i = sBlobFontTable.find(aKey);
+ if (i != sBlobFontTable.end()) {
+ sBlobFontTable.erase(i);
+ }
+}
+
+} // extern
+
+static RefPtr<UnscaledFont> GetUnscaledFont(Translator* aTranslator,
+ WrFontKey aKey) {
+ auto i = sFontDataTable.find(aKey);
+ if (i == sFontDataTable.end()) {
+ gfxDevCrash(LogReason::UnscaledFontNotFound)
+ << "Failed to get UnscaledFont entry for FontKey " << aKey.mHandle
+ << " because " << sFontDeleteLog.Find(aKey);
+ return nullptr;
+ }
+ FontTemplate& data = i->second;
+ if (data.mUnscaledFont) {
+ return data.mUnscaledFont;
+ }
+ MOZ_ASSERT(data.mData);
+ FontType type =
+#ifdef XP_MACOSX
+ FontType::MAC;
+#elif defined(XP_WIN)
+ FontType::DWRITE;
+#elif defined(ANDROID)
+ FontType::FREETYPE;
+#else
+ FontType::FONTCONFIG;
+#endif
+ // makes a copy of the data
+ RefPtr<NativeFontResource> fontResource = Factory::CreateNativeFontResource(
+ (uint8_t*)data.mData, data.mSize, type, aTranslator->GetFontContext());
+ RefPtr<UnscaledFont> unscaledFont;
+ if (!fontResource) {
+ gfxDevCrash(LogReason::NativeFontResourceNotFound)
+ << "Failed to create NativeFontResource for FontKey " << aKey.mHandle;
+ } else {
+ // Instance data is only needed for GDI fonts which webrender does not
+ // support.
+ unscaledFont = fontResource->CreateUnscaledFont(data.mIndex, nullptr, 0);
+ if (!unscaledFont) {
+ gfxDevCrash(LogReason::UnscaledFontNotFound)
+ << "Failed to create UnscaledFont for FontKey " << aKey.mHandle;
+ }
+ }
+ data.mUnscaledFont = unscaledFont;
+ return unscaledFont;
+}
+
+static RefPtr<ScaledFont> GetScaledFont(Translator* aTranslator,
+ WrFontInstanceKey aKey) {
+ StaticMutexAutoLock lock(sFontDataTableLock);
+ auto i = sBlobFontTable.find(aKey);
+ if (i == sBlobFontTable.end()) {
+ gfxDevCrash(LogReason::ScaledFontNotFound)
+ << "Failed to get ScaledFont entry for FontInstanceKey "
+ << aKey.mHandle;
+ return nullptr;
+ }
+ FontInstanceData& data = i->second;
+ if (data.mScaledFont) {
+ return data.mScaledFont;
+ }
+ RefPtr<UnscaledFont> unscaled = GetUnscaledFont(aTranslator, data.mFontKey);
+ if (!unscaled) {
+ return nullptr;
+ }
+ RefPtr<ScaledFont> scaled = unscaled->CreateScaledFontFromWRFont(
+ data.mSize, data.mOptions.ptrOr(nullptr),
+ data.mPlatformOptions.ptrOr(nullptr), data.mVariations.get(),
+ data.mNumVariations);
+ if (!scaled) {
+ gfxDevCrash(LogReason::ScaledFontNotFound)
+ << "Failed to create ScaledFont for FontKey " << aKey.mHandle;
+ }
+ data.mScaledFont = scaled;
+ return data.mScaledFont;
+}
+
+template <typename T>
+T ConvertFromBytes(const uint8_t* bytes) {
+ T t;
+ memcpy(&t, bytes, sizeof(T));
+ return t;
+}
+
+struct Reader {
+ const uint8_t* buf;
+ size_t len;
+ size_t pos;
+
+ Reader(const uint8_t* buf, size_t len) : buf(buf), len(len), pos(0) {}
+
+ template <typename T>
+ T Read() {
+ MOZ_RELEASE_ASSERT(pos + sizeof(T) <= len);
+ T ret = ConvertFromBytes<T>(buf + pos);
+ pos += sizeof(T);
+ return ret;
+ }
+
+ size_t ReadSize() { return Read<size_t>(); }
+ int ReadInt() { return Read<int>(); }
+
+ IntRectAbsolute ReadBounds() { return Read<IntRectAbsolute>(); }
+
+ layers::BlobFont ReadBlobFont() { return Read<layers::BlobFont>(); }
+};
+
+static bool Moz2DRenderCallback(const Range<const uint8_t> aBlob,
+ gfx::SurfaceFormat aFormat,
+ const mozilla::wr::DeviceIntRect* aVisibleRect,
+ const mozilla::wr::LayoutIntRect* aRenderRect,
+ const uint16_t aTileSize,
+ const mozilla::wr::TileOffset* aTileOffset,
+ const mozilla::wr::LayoutIntRect* aDirtyRect,
+ Range<uint8_t> aOutput) {
+ IntSize size(aRenderRect->size.width, aRenderRect->size.height);
+ AUTO_PROFILER_TRACING_MARKER("WebRender", "RasterizeSingleBlob", GRAPHICS);
+ MOZ_RELEASE_ASSERT(size.width > 0 && size.height > 0);
+ if (size.width <= 0 || size.height <= 0) {
+ return false;
+ }
+
+ auto stride = size.width * gfx::BytesPerPixel(aFormat);
+
+ if (aOutput.length() < static_cast<size_t>(size.height * stride)) {
+ return false;
+ }
+
+ // In bindings.rs we allocate a buffer filled with opaque white.
+ bool uninitialized = false;
+
+ RefPtr<gfx::DrawTarget> dt = gfx::Factory::CreateDrawTargetForData(
+ gfx::BackendType::SKIA, aOutput.begin().get(), size, stride, aFormat,
+ uninitialized);
+
+ if (!dt) {
+ return false;
+ }
+
+ // We try hard to not have empty blobs but we can end up with
+ // them because of CompositorHitTestInfo and merging.
+ size_t footerSize = sizeof(size_t);
+ MOZ_RELEASE_ASSERT(aBlob.length() >= footerSize);
+ size_t indexOffset = ConvertFromBytes<size_t>(aBlob.end().get() - footerSize);
+
+ // aRenderRect is the part of the blob that we are currently rendering
+ // (for example a tile) in the same coordinate space as aVisibleRect.
+ IntPoint origin = gfx::IntPoint(aRenderRect->origin.x, aRenderRect->origin.y);
+
+ MOZ_RELEASE_ASSERT(indexOffset <= aBlob.length() - footerSize);
+ Reader reader(aBlob.begin().get() + indexOffset,
+ aBlob.length() - footerSize - indexOffset);
+
+ dt = gfx::Factory::CreateOffsetDrawTarget(dt, origin);
+
+ auto bounds = gfx::IntRect(origin, size);
+
+ if (aDirtyRect) {
+ gfx::Rect dirty(aDirtyRect->origin.x, aDirtyRect->origin.y,
+ aDirtyRect->size.width, aDirtyRect->size.height);
+ dt->PushClipRect(dirty);
+ bounds = bounds.Intersect(
+ IntRect(aDirtyRect->origin.x, aDirtyRect->origin.y,
+ aDirtyRect->size.width, aDirtyRect->size.height));
+ }
+
+ bool ret = true;
+ size_t offset = 0;
+ auto absBounds = IntRectAbsolute::FromRect(bounds);
+ while (reader.pos < reader.len) {
+ size_t end = reader.ReadSize();
+ size_t extra_end = reader.ReadSize();
+ MOZ_RELEASE_ASSERT(extra_end >= end);
+ MOZ_RELEASE_ASSERT(extra_end < aBlob.length());
+
+ auto combinedBounds = absBounds.Intersect(reader.ReadBounds());
+ if (combinedBounds.IsEmpty()) {
+ offset = extra_end;
+ continue;
+ }
+
+ layers::WebRenderTranslator translator(dt);
+ Reader fontReader(aBlob.begin().get() + end, extra_end - end);
+ size_t count = fontReader.ReadSize();
+ for (size_t i = 0; i < count; i++) {
+ layers::BlobFont blobFont = fontReader.ReadBlobFont();
+ RefPtr<ScaledFont> scaledFont =
+ GetScaledFont(&translator, blobFont.mFontInstanceKey);
+ translator.AddScaledFont(blobFont.mScaledFontPtr, scaledFont);
+ }
+
+ Range<const uint8_t> blob(aBlob.begin() + offset, aBlob.begin() + end);
+ ret =
+ translator.TranslateRecording((char*)blob.begin().get(), blob.length());
+ if (!ret) {
+ gfxCriticalNote << "Replay failure: " << translator.GetError();
+ MOZ_RELEASE_ASSERT(false);
+ }
+ offset = extra_end;
+ }
+
+ if (StaticPrefs::gfx_webrender_blob_paint_flashing()) {
+ dt->SetTransform(gfx::Matrix());
+ float r = float(rand()) / float(RAND_MAX);
+ float g = float(rand()) / float(RAND_MAX);
+ float b = float(rand()) / float(RAND_MAX);
+ dt->FillRect(gfx::Rect(origin.x, origin.y, size.width, size.height),
+ gfx::ColorPattern(gfx::DeviceColor(r, g, b, 0.5)));
+ }
+
+ if (aDirtyRect) {
+ dt->PopClip();
+ }
+
+#if 0
+ static int i = 0;
+ char filename[40];
+ sprintf(filename, "out%d.png", i++);
+ gfxUtils::WriteAsPNG(dt, filename);
+#endif
+
+ return ret;
+}
+
+} // namespace wr
+} // namespace mozilla
+
+extern "C" {
+
+bool wr_moz2d_render_cb(const mozilla::wr::ByteSlice blob,
+ mozilla::wr::ImageFormat aFormat,
+ const mozilla::wr::LayoutIntRect* aRenderRect,
+ const mozilla::wr::DeviceIntRect* aVisibleRect,
+ const uint16_t aTileSize,
+ const mozilla::wr::TileOffset* aTileOffset,
+ const mozilla::wr::LayoutIntRect* aDirtyRect,
+ mozilla::wr::MutByteSlice output) {
+ return mozilla::wr::Moz2DRenderCallback(
+ mozilla::wr::ByteSliceToRange(blob),
+ mozilla::wr::ImageFormatToSurfaceFormat(aFormat), aVisibleRect,
+ aRenderRect, aTileSize, aTileOffset, aDirtyRect,
+ mozilla::wr::MutByteSliceToRange(output));
+}
+
+} // extern
diff --git a/gfx/webrender_bindings/README.webrender b/gfx/webrender_bindings/README.webrender
new file mode 100644
index 0000000000..a42c33092d
--- /dev/null
+++ b/gfx/webrender_bindings/README.webrender
@@ -0,0 +1,23 @@
+To build and run WebRender in Gecko:
+1. Install Rust if you don't have it already
+ If you are doing gecko builds already, you should already have Rust as it is a build requirement.
+ If not, you can install it using |mach bootstrap| (recommended) or from https://www.rust-lang.org/
+ Note: If installing manually, use the stable 64-bit release - on Windows make sure to use the MSVC ABI installer.
+ Ensure that rustc and cargo are in your $PATH (adding $HOME/.cargo/bin/ should be sufficient)
+2. Build using |mach build|.
+ You don't need anything special in your mozconfig for local builds; webrender will be built by default.
+3. Run with |MOZ_WEBRENDER=1| in your environment. e.g. |MOZ_WEBRENDER=1 ./mach run|.
+ Alternatively, you can set the gfx.webrender.enabled pref to true (browser restart required).
+ Note that on Linux, acceleration is disabled by default and it needs to be enabled for WebRender to work.
+ On Linux you can enable acceleration by putting |MOZ_ACCELERATED=1| in your environment, or setting layers.acceleration.force-enabled to true in about:config.
+4. Verify WebRender is enabled. You can do this by going to about:support and checking the "Compositing" line in the Graphics section. It should say "WebRender".
+ There should also be a WebRender section under "Decision Log" in about:support, which will provide some more detail on what caused it to be enabled/disabled.
+
+When making changes:
+ - Make the changes you want.
+ - Run |mach build| or |mach build binaries| as desired.
+
+For a debug webrender build:
+ Use a debug mozconfig (ac_add_options --enable-debug)
+ You can also use an opt build but make webrender less optimized by putting opt-level=0 in the [profile.release] section of your toolkit/library/rust/Cargo.toml file
+ See also https://groups.google.com/forum/#!topic/mozilla.dev.servo/MbeMcqqO1fs
diff --git a/gfx/webrender_bindings/RenderAndroidHardwareBufferTextureHost.cpp b/gfx/webrender_bindings/RenderAndroidHardwareBufferTextureHost.cpp
new file mode 100644
index 0000000000..19596a6307
--- /dev/null
+++ b/gfx/webrender_bindings/RenderAndroidHardwareBufferTextureHost.cpp
@@ -0,0 +1,174 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "RenderAndroidHardwareBufferTextureHost.h"
+
+#include "mozilla/layers/AndroidHardwareBuffer.h"
+#include "GLContextEGL.h"
+#include "GLLibraryEGL.h"
+
+namespace mozilla {
+namespace wr {
+
+RenderAndroidHardwareBufferTextureHost::RenderAndroidHardwareBufferTextureHost(
+ layers::AndroidHardwareBuffer* aAndroidHardwareBuffer)
+ : mAndroidHardwareBuffer(aAndroidHardwareBuffer),
+ mEGLImage(EGL_NO_IMAGE),
+ mTextureHandle(0) {
+ MOZ_ASSERT(mAndroidHardwareBuffer);
+ MOZ_COUNT_CTOR_INHERITED(RenderAndroidHardwareBufferTextureHost,
+ RenderTextureHost);
+}
+
+RenderAndroidHardwareBufferTextureHost::
+ ~RenderAndroidHardwareBufferTextureHost() {
+ MOZ_COUNT_DTOR_INHERITED(RenderAndroidHardwareBufferTextureHost,
+ RenderTextureHost);
+ DeleteTextureHandle();
+ DestroyEGLImage();
+}
+
+gfx::IntSize RenderAndroidHardwareBufferTextureHost::GetSize() const {
+ if (mAndroidHardwareBuffer) {
+ return mAndroidHardwareBuffer->mSize;
+ }
+ return gfx::IntSize();
+}
+
+bool RenderAndroidHardwareBufferTextureHost::EnsureLockable(
+ wr::ImageRendering aRendering) {
+ if (!mAndroidHardwareBuffer) {
+ return false;
+ }
+
+ auto fenceFd = mAndroidHardwareBuffer->GetAndResetAcquireFence();
+ if (fenceFd.IsValid()) {
+ const auto& gle = gl::GLContextEGL::Cast(mGL);
+ const auto& egl = gle->mEgl;
+
+ auto rawFD = fenceFd.TakePlatformHandle();
+ const EGLint attribs[] = {LOCAL_EGL_SYNC_NATIVE_FENCE_FD_ANDROID,
+ rawFD.get(), LOCAL_EGL_NONE};
+
+ EGLSync sync =
+ egl->fCreateSync(LOCAL_EGL_SYNC_NATIVE_FENCE_ANDROID, attribs);
+ if (sync) {
+ // Release fd here, since it is owned by EGLSync
+ Unused << rawFD.release();
+
+ if (egl->IsExtensionSupported(gl::EGLExtension::KHR_wait_sync)) {
+ egl->fWaitSync(sync, 0);
+ } else {
+ egl->fClientWaitSync(sync, 0, LOCAL_EGL_FOREVER);
+ }
+ egl->fDestroySync(sync);
+ } else {
+ gfxCriticalNote << "Failed to create EGLSync from acquire fence fd";
+ }
+ }
+
+ if (mTextureHandle) {
+ // Update filter if filter was changed.
+ if (IsFilterUpdateNecessary(aRendering)) {
+ ActivateBindAndTexParameteri(mGL, LOCAL_GL_TEXTURE0,
+ LOCAL_GL_TEXTURE_EXTERNAL_OES,
+ mTextureHandle, aRendering);
+ // Cache new rendering filter.
+ mCachedRendering = aRendering;
+ }
+ return true;
+ }
+
+ if (!mEGLImage) {
+ // XXX add crop handling for video
+ // Should only happen the first time.
+ const auto& gle = gl::GLContextEGL::Cast(mGL);
+ const auto& egl = gle->mEgl;
+
+ const EGLint attrs[] = {
+ LOCAL_EGL_IMAGE_PRESERVED,
+ LOCAL_EGL_TRUE,
+ LOCAL_EGL_NONE,
+ LOCAL_EGL_NONE,
+ };
+
+ EGLClientBuffer clientBuffer = egl->mLib->fGetNativeClientBufferANDROID(
+ mAndroidHardwareBuffer->GetNativeBuffer());
+ mEGLImage = egl->fCreateImage(
+ EGL_NO_CONTEXT, LOCAL_EGL_NATIVE_BUFFER_ANDROID, clientBuffer, attrs);
+ }
+ MOZ_ASSERT(mEGLImage);
+
+ mGL->fGenTextures(1, &mTextureHandle);
+ mGL->fBindTexture(LOCAL_GL_TEXTURE_EXTERNAL, mTextureHandle);
+ mGL->fTexParameteri(LOCAL_GL_TEXTURE_EXTERNAL, LOCAL_GL_TEXTURE_WRAP_T,
+ LOCAL_GL_CLAMP_TO_EDGE);
+ mGL->fTexParameteri(LOCAL_GL_TEXTURE_EXTERNAL, LOCAL_GL_TEXTURE_WRAP_S,
+ LOCAL_GL_CLAMP_TO_EDGE);
+ mGL->fEGLImageTargetTexture2D(LOCAL_GL_TEXTURE_EXTERNAL, mEGLImage);
+
+ ActivateBindAndTexParameteri(mGL, LOCAL_GL_TEXTURE0,
+ LOCAL_GL_TEXTURE_EXTERNAL_OES, mTextureHandle,
+ aRendering);
+ // Cache rendering filter.
+ mCachedRendering = aRendering;
+ return true;
+}
+
+wr::WrExternalImage RenderAndroidHardwareBufferTextureHost::Lock(
+ uint8_t aChannelIndex, gl::GLContext* aGL, wr::ImageRendering aRendering) {
+ MOZ_ASSERT(aChannelIndex == 0);
+
+ if (mGL.get() != aGL) {
+ if (mGL) {
+ // This should not happen.
+ MOZ_ASSERT_UNREACHABLE("Unexpected GL context");
+ return InvalidToWrExternalImage();
+ }
+ mGL = aGL;
+ }
+
+ if (!mGL || !mGL->MakeCurrent()) {
+ return InvalidToWrExternalImage();
+ }
+
+ if (!EnsureLockable(aRendering)) {
+ return InvalidToWrExternalImage();
+ }
+
+ return NativeTextureToWrExternalImage(mTextureHandle, 0, 0, GetSize().width,
+ GetSize().height);
+}
+
+void RenderAndroidHardwareBufferTextureHost::Unlock() {}
+
+size_t RenderAndroidHardwareBufferTextureHost::Bytes() {
+ return GetSize().width * GetSize().height *
+ BytesPerPixel(mAndroidHardwareBuffer->mFormat);
+}
+
+void RenderAndroidHardwareBufferTextureHost::DeleteTextureHandle() {
+ if (!mTextureHandle) {
+ return;
+ }
+ MOZ_ASSERT(mGL);
+ mGL->fDeleteTextures(1, &mTextureHandle);
+ mTextureHandle = 0;
+}
+
+void RenderAndroidHardwareBufferTextureHost::DestroyEGLImage() {
+ if (!mEGLImage) {
+ return;
+ }
+ MOZ_ASSERT(mGL);
+ const auto& gle = gl::GLContextEGL::Cast(mGL);
+ const auto& egl = gle->mEgl;
+ egl->fDestroyImage(mEGLImage);
+ mEGLImage = EGL_NO_IMAGE;
+}
+
+} // namespace wr
+} // namespace mozilla
diff --git a/gfx/webrender_bindings/RenderAndroidHardwareBufferTextureHost.h b/gfx/webrender_bindings/RenderAndroidHardwareBufferTextureHost.h
new file mode 100644
index 0000000000..ce551d3453
--- /dev/null
+++ b/gfx/webrender_bindings/RenderAndroidHardwareBufferTextureHost.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef MOZILLA_GFX_RenderAndroidHardwareBufferTextureHost_H
+#define MOZILLA_GFX_RenderAndroidHardwareBufferTextureHost_H
+
+#include "GLTypes.h"
+#include "RenderTextureHost.h"
+
+namespace mozilla {
+
+namespace layers {
+class AndroidHardwareBuffer;
+}
+
+namespace wr {
+
+class RenderAndroidHardwareBufferTextureHost final : public RenderTextureHost {
+ public:
+ explicit RenderAndroidHardwareBufferTextureHost(
+ layers::AndroidHardwareBuffer* aAndroidHardwareBuffer);
+
+ wr::WrExternalImage Lock(uint8_t aChannelIndex, gl::GLContext* aGL,
+ wr::ImageRendering aRendering) override;
+ void Unlock() override;
+
+ size_t Bytes() override;
+
+ private:
+ virtual ~RenderAndroidHardwareBufferTextureHost();
+ bool EnsureLockable(wr::ImageRendering aRendering);
+ void DestroyEGLImage();
+ void DeleteTextureHandle();
+ gfx::IntSize GetSize() const;
+
+ const RefPtr<layers::AndroidHardwareBuffer> mAndroidHardwareBuffer;
+ EGLImage mEGLImage;
+
+ RefPtr<gl::GLContext> mGL;
+ GLuint mTextureHandle;
+};
+
+} // namespace wr
+} // namespace mozilla
+
+#endif // MOZILLA_GFX_RenderAndroidHardwareBufferTextureHost_H
diff --git a/gfx/webrender_bindings/RenderAndroidSurfaceTextureHost.cpp b/gfx/webrender_bindings/RenderAndroidSurfaceTextureHost.cpp
new file mode 100644
index 0000000000..c8ba07415a
--- /dev/null
+++ b/gfx/webrender_bindings/RenderAndroidSurfaceTextureHost.cpp
@@ -0,0 +1,192 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "RenderAndroidSurfaceTextureHost.h"
+
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/webrender/RenderThread.h"
+#include "GLContext.h"
+
+namespace mozilla {
+namespace wr {
+
+RenderAndroidSurfaceTextureHost::RenderAndroidSurfaceTextureHost(
+ const java::GeckoSurfaceTexture::GlobalRef& aSurfTex, gfx::IntSize aSize,
+ gfx::SurfaceFormat aFormat, bool aContinuousUpdate)
+ : mSurfTex(aSurfTex),
+ mSize(aSize),
+ mFormat(aFormat),
+ mContinuousUpdate(aContinuousUpdate),
+ mPrepareStatus(STATUS_NONE),
+ mAttachedToGLContext(false) {
+ MOZ_COUNT_CTOR_INHERITED(RenderAndroidSurfaceTextureHost, RenderTextureHost);
+
+ if (mSurfTex) {
+ mSurfTex->IncrementUse();
+ }
+}
+
+RenderAndroidSurfaceTextureHost::~RenderAndroidSurfaceTextureHost() {
+ MOZ_ASSERT(RenderThread::IsInRenderThread());
+ MOZ_COUNT_DTOR_INHERITED(RenderAndroidSurfaceTextureHost, RenderTextureHost);
+ // The SurfaceTexture gets destroyed when its use count reaches zero.
+ if (mSurfTex) {
+ mSurfTex->DecrementUse();
+ }
+}
+
+wr::WrExternalImage RenderAndroidSurfaceTextureHost::Lock(
+ uint8_t aChannelIndex, gl::GLContext* aGL, wr::ImageRendering aRendering) {
+ MOZ_ASSERT(aChannelIndex == 0);
+ MOZ_ASSERT((mPrepareStatus == STATUS_PREPARED) ||
+ (!mSurfTex->IsSingleBuffer() &&
+ mPrepareStatus == STATUS_UPDATE_TEX_IMAGE_NEEDED));
+
+ if (mGL.get() != aGL) {
+ // This should not happen. On android, SharedGL is used.
+ MOZ_ASSERT_UNREACHABLE("Unexpected GL context");
+ return InvalidToWrExternalImage();
+ }
+
+ if (!mSurfTex || !mGL || !mGL->MakeCurrent()) {
+ return InvalidToWrExternalImage();
+ }
+
+ MOZ_ASSERT(mAttachedToGLContext);
+ if (!mAttachedToGLContext) {
+ return InvalidToWrExternalImage();
+ }
+
+ if (IsFilterUpdateNecessary(aRendering)) {
+ // Cache new rendering filter.
+ mCachedRendering = aRendering;
+ ActivateBindAndTexParameteri(mGL, LOCAL_GL_TEXTURE0,
+ LOCAL_GL_TEXTURE_EXTERNAL_OES,
+ mSurfTex->GetTexName(), aRendering);
+ }
+
+ if (mContinuousUpdate) {
+ MOZ_ASSERT(!mSurfTex->IsSingleBuffer());
+ mSurfTex->UpdateTexImage();
+ } else if (mPrepareStatus == STATUS_UPDATE_TEX_IMAGE_NEEDED) {
+ MOZ_ASSERT(!mSurfTex->IsSingleBuffer());
+ // When SurfaceTexture is not single buffer mode, call UpdateTexImage() once
+ // just before rendering. During playing video, one SurfaceTexture is used
+ // for all RenderAndroidSurfaceTextureHosts of video.
+ mSurfTex->UpdateTexImage();
+ mPrepareStatus = STATUS_PREPARED;
+ }
+
+ return NativeTextureToWrExternalImage(mSurfTex->GetTexName(), 0, 0,
+ mSize.width, mSize.height);
+}
+
+void RenderAndroidSurfaceTextureHost::Unlock() {}
+
+bool RenderAndroidSurfaceTextureHost::EnsureAttachedToGLContext() {
+ // During handling WebRenderError, GeckoSurfaceTexture should not be attached
+ // to GLContext.
+ if (RenderThread::Get()->IsHandlingWebRenderError()) {
+ return false;
+ }
+
+ if (mAttachedToGLContext) {
+ return true;
+ }
+
+ if (!mGL) {
+ mGL = RenderThread::Get()->SharedGL();
+ }
+
+ if (!mSurfTex || !mGL || !mGL->MakeCurrent()) {
+ return false;
+ }
+
+ if (!mSurfTex->IsAttachedToGLContext((int64_t)mGL.get())) {
+ GLuint texName;
+ mGL->fGenTextures(1, &texName);
+ ActivateBindAndTexParameteri(mGL, LOCAL_GL_TEXTURE0,
+ LOCAL_GL_TEXTURE_EXTERNAL_OES, texName,
+ mCachedRendering);
+
+ if (NS_FAILED(mSurfTex->AttachToGLContext((int64_t)mGL.get(), texName))) {
+ MOZ_ASSERT(0);
+ mGL->fDeleteTextures(1, &texName);
+ return false;
+ }
+ }
+
+ mAttachedToGLContext = true;
+ return true;
+}
+
+void RenderAndroidSurfaceTextureHost::PrepareForUse() {
+ // When SurfaceTexture is single buffer mode, UpdateTexImage needs to be
+ // called only once for each publish. If UpdateTexImage is called more
+ // than once, it causes hang on puglish side. And UpdateTexImage needs to
+ // be called on render thread, since the SurfaceTexture is consumed on render
+ // thread.
+ MOZ_ASSERT(RenderThread::IsInRenderThread());
+ MOZ_ASSERT(mPrepareStatus == STATUS_NONE);
+
+ if (mContinuousUpdate || !mSurfTex) {
+ return;
+ }
+
+ mPrepareStatus = STATUS_MIGHT_BE_USED_BY_WR;
+
+ if (mSurfTex->IsSingleBuffer()) {
+ EnsureAttachedToGLContext();
+ // When SurfaceTexture is single buffer mode, it is OK to call
+ // UpdateTexImage() here.
+ mSurfTex->UpdateTexImage();
+ mPrepareStatus = STATUS_PREPARED;
+ }
+}
+
+void RenderAndroidSurfaceTextureHost::NotifyForUse() {
+ MOZ_ASSERT(RenderThread::IsInRenderThread());
+
+ if (mPrepareStatus == STATUS_MIGHT_BE_USED_BY_WR) {
+ // This happens when SurfaceTexture of video is rendered on WebRender.
+ // There is a case that SurfaceTexture is not rendered on WebRender, instead
+ // it is rendered to WebGL and the SurfaceTexture should not be attached to
+ // gl context of WebRender. It is ugly. But it is same as Compositor
+ // rendering.
+ MOZ_ASSERT(!mSurfTex->IsSingleBuffer());
+ if (!EnsureAttachedToGLContext()) {
+ return;
+ }
+ mPrepareStatus = STATUS_UPDATE_TEX_IMAGE_NEEDED;
+ }
+}
+
+void RenderAndroidSurfaceTextureHost::NotifyNotUsed() {
+ MOZ_ASSERT(RenderThread::IsInRenderThread());
+
+ if (!mSurfTex) {
+ MOZ_ASSERT(mPrepareStatus == STATUS_NONE);
+ return;
+ }
+
+ if (mSurfTex->IsSingleBuffer()) {
+ MOZ_ASSERT(mPrepareStatus == STATUS_PREPARED);
+ MOZ_ASSERT(mAttachedToGLContext);
+ // Release SurfaceTexture's buffer to client side.
+ mGL->MakeCurrent();
+ mSurfTex->ReleaseTexImage();
+ } else if (mPrepareStatus == STATUS_UPDATE_TEX_IMAGE_NEEDED) {
+ MOZ_ASSERT(mAttachedToGLContext);
+ // This could happen when video frame was skipped. UpdateTexImage() neeeds
+ // to be called for adjusting SurfaceTexture's buffer status.
+ mSurfTex->UpdateTexImage();
+ }
+
+ mPrepareStatus = STATUS_NONE;
+}
+
+} // namespace wr
+} // namespace mozilla
diff --git a/gfx/webrender_bindings/RenderAndroidSurfaceTextureHost.h b/gfx/webrender_bindings/RenderAndroidSurfaceTextureHost.h
new file mode 100644
index 0000000000..a763afc268
--- /dev/null
+++ b/gfx/webrender_bindings/RenderAndroidSurfaceTextureHost.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef MOZILLA_GFX_RENDERANDROIDSURFACETEXTUREHOST_H
+#define MOZILLA_GFX_RENDERANDROIDSURFACETEXTUREHOST_H
+
+#include "mozilla/java/GeckoSurfaceTextureWrappers.h"
+#include "mozilla/layers/TextureHostOGL.h"
+#include "RenderTextureHost.h"
+
+namespace mozilla {
+
+namespace wr {
+
+class RenderAndroidSurfaceTextureHost final : public RenderTextureHost {
+ public:
+ explicit RenderAndroidSurfaceTextureHost(
+ const java::GeckoSurfaceTexture::GlobalRef& aSurfTex, gfx::IntSize aSize,
+ gfx::SurfaceFormat aFormat, bool aContinuousUpdate);
+
+ wr::WrExternalImage Lock(uint8_t aChannelIndex, gl::GLContext* aGL,
+ wr::ImageRendering aRendering) override;
+ void Unlock() override;
+
+ size_t Bytes() override {
+ return mSize.width * mSize.height * BytesPerPixel(mFormat);
+ }
+
+ void PrepareForUse() override;
+ void NotifyForUse() override;
+ void NotifyNotUsed() override;
+
+ private:
+ virtual ~RenderAndroidSurfaceTextureHost();
+ bool EnsureAttachedToGLContext();
+
+ enum PrepareStatus {
+ STATUS_NONE,
+ STATUS_MIGHT_BE_USED_BY_WR,
+ STATUS_UPDATE_TEX_IMAGE_NEEDED,
+ STATUS_PREPARED
+ };
+
+ const mozilla::java::GeckoSurfaceTexture::GlobalRef mSurfTex;
+ const gfx::IntSize mSize;
+ const gfx::SurfaceFormat mFormat;
+ // mContinuousUpdate was used for rendering video in the past.
+ // It is not used on current gecko.
+ const bool mContinuousUpdate;
+ // XXX const bool mIgnoreTransform;
+ PrepareStatus mPrepareStatus;
+ bool mAttachedToGLContext;
+
+ RefPtr<gl::GLContext> mGL;
+};
+
+} // namespace wr
+} // namespace mozilla
+
+#endif // MOZILLA_GFX_RENDERANDROIDSURFACETEXTUREHOST_H
diff --git a/gfx/webrender_bindings/RenderBufferTextureHost.cpp b/gfx/webrender_bindings/RenderBufferTextureHost.cpp
new file mode 100644
index 0000000000..7f8e454633
--- /dev/null
+++ b/gfx/webrender_bindings/RenderBufferTextureHost.cpp
@@ -0,0 +1,160 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "RenderBufferTextureHost.h"
+
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/layers/ImageDataSerializer.h"
+
+namespace mozilla {
+namespace wr {
+
+RenderBufferTextureHost::RenderBufferTextureHost(
+ uint8_t* aBuffer, const layers::BufferDescriptor& aDescriptor)
+ : mBuffer(aBuffer),
+ mDescriptor(aDescriptor),
+ mMap(),
+ mYMap(),
+ mCbMap(),
+ mCrMap(),
+ mLocked(false) {
+ MOZ_COUNT_CTOR_INHERITED(RenderBufferTextureHost, RenderTextureHost);
+
+ switch (mDescriptor.type()) {
+ case layers::BufferDescriptor::TYCbCrDescriptor: {
+ const layers::YCbCrDescriptor& ycbcr = mDescriptor.get_YCbCrDescriptor();
+ mSize = ycbcr.display().Size();
+ mFormat = gfx::SurfaceFormat::YUV;
+ break;
+ }
+ case layers::BufferDescriptor::TRGBDescriptor: {
+ const layers::RGBDescriptor& rgb = mDescriptor.get_RGBDescriptor();
+ mSize = rgb.size();
+ mFormat = rgb.format();
+ break;
+ }
+ default:
+ gfxCriticalError() << "Bad buffer host descriptor "
+ << (int)mDescriptor.type();
+ MOZ_CRASH("GFX: Bad descriptor");
+ }
+}
+
+RenderBufferTextureHost::~RenderBufferTextureHost() {
+ MOZ_COUNT_DTOR_INHERITED(RenderBufferTextureHost, RenderTextureHost);
+}
+
+wr::WrExternalImage RenderBufferTextureHost::Lock(
+ uint8_t aChannelIndex, gl::GLContext* aGL, wr::ImageRendering aRendering) {
+ if (!mLocked) {
+ if (!GetBuffer()) {
+ // We hit some problems to get the shmem.
+ gfxCriticalNote << "GetBuffer Failed";
+ return InvalidToWrExternalImage();
+ }
+ if (mFormat != gfx::SurfaceFormat::YUV) {
+ mSurface = gfx::Factory::CreateWrappingDataSourceSurface(
+ GetBuffer(),
+ layers::ImageDataSerializer::GetRGBStride(
+ mDescriptor.get_RGBDescriptor()),
+ mSize, mFormat);
+ if (NS_WARN_IF(!mSurface)) {
+ gfxCriticalNote << "DataSourceSurface is null";
+ return InvalidToWrExternalImage();
+ }
+ if (NS_WARN_IF(!mSurface->Map(gfx::DataSourceSurface::MapType::READ_WRITE,
+ &mMap))) {
+ mSurface = nullptr;
+ gfxCriticalNote << "Failed to map Surface";
+ return InvalidToWrExternalImage();
+ }
+ } else {
+ const layers::YCbCrDescriptor& desc = mDescriptor.get_YCbCrDescriptor();
+
+ mYSurface = gfx::Factory::CreateWrappingDataSourceSurface(
+ layers::ImageDataSerializer::GetYChannel(GetBuffer(), desc),
+ desc.yStride(), desc.ySize(), gfx::SurfaceFormat::A8);
+ mCbSurface = gfx::Factory::CreateWrappingDataSourceSurface(
+ layers::ImageDataSerializer::GetCbChannel(GetBuffer(), desc),
+ desc.cbCrStride(), desc.cbCrSize(), gfx::SurfaceFormat::A8);
+ mCrSurface = gfx::Factory::CreateWrappingDataSourceSurface(
+ layers::ImageDataSerializer::GetCrChannel(GetBuffer(), desc),
+ desc.cbCrStride(), desc.cbCrSize(), gfx::SurfaceFormat::A8);
+ if (NS_WARN_IF(!mYSurface || !mCbSurface || !mCrSurface)) {
+ mYSurface = mCbSurface = mCrSurface = nullptr;
+ gfxCriticalNote << "YCbCr Surface is null";
+ return InvalidToWrExternalImage();
+ }
+ if (NS_WARN_IF(
+ !mYSurface->Map(gfx::DataSourceSurface::MapType::READ_WRITE,
+ &mYMap) ||
+ !mCbSurface->Map(gfx::DataSourceSurface::MapType::READ_WRITE,
+ &mCbMap) ||
+ !mCrSurface->Map(gfx::DataSourceSurface::MapType::READ_WRITE,
+ &mCrMap))) {
+ mYSurface = mCbSurface = mCrSurface = nullptr;
+ gfxCriticalNote << "Failed to map YCbCr Surface";
+ return InvalidToWrExternalImage();
+ }
+ }
+ mLocked = true;
+ }
+
+ RenderBufferData data = GetBufferDataForRender(aChannelIndex);
+ return RawDataToWrExternalImage(data.mData, data.mBufferSize);
+}
+
+void RenderBufferTextureHost::Unlock() {
+ if (mLocked) {
+ if (mSurface) {
+ mSurface->Unmap();
+ mSurface = nullptr;
+ } else if (mYSurface) {
+ mYSurface->Unmap();
+ mCbSurface->Unmap();
+ mCrSurface->Unmap();
+ mYSurface = mCbSurface = mCrSurface = nullptr;
+ }
+ mLocked = false;
+ }
+}
+
+RenderBufferTextureHost::RenderBufferData
+RenderBufferTextureHost::GetBufferDataForRender(uint8_t aChannelIndex) {
+ MOZ_ASSERT(mFormat != gfx::SurfaceFormat::YUV || aChannelIndex < 3);
+ MOZ_ASSERT(mFormat == gfx::SurfaceFormat::YUV || aChannelIndex < 1);
+ MOZ_ASSERT(mLocked);
+
+ if (mFormat != gfx::SurfaceFormat::YUV) {
+ MOZ_ASSERT(mSurface);
+
+ return RenderBufferData(mMap.mData,
+ mMap.mStride * mSurface->GetSize().height);
+ } else {
+ MOZ_ASSERT(mYSurface && mCbSurface && mCrSurface);
+
+ switch (aChannelIndex) {
+ case 0:
+ return RenderBufferData(mYMap.mData,
+ mYMap.mStride * mYSurface->GetSize().height);
+ break;
+ case 1:
+ return RenderBufferData(mCbMap.mData,
+ mCbMap.mStride * mCbSurface->GetSize().height);
+ break;
+ case 2:
+ return RenderBufferData(mCrMap.mData,
+ mCrMap.mStride * mCrSurface->GetSize().height);
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("unexpected to be called");
+ return RenderBufferData(nullptr, 0);
+ }
+ }
+}
+
+} // namespace wr
+} // namespace mozilla
diff --git a/gfx/webrender_bindings/RenderBufferTextureHost.h b/gfx/webrender_bindings/RenderBufferTextureHost.h
new file mode 100644
index 0000000000..b104e00025
--- /dev/null
+++ b/gfx/webrender_bindings/RenderBufferTextureHost.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef MOZILLA_GFX_RENDERBUFFERTEXTUREHOST_H
+#define MOZILLA_GFX_RENDERBUFFERTEXTUREHOST_H
+
+#include "RenderTextureHost.h"
+
+namespace mozilla {
+namespace wr {
+
+class RenderBufferTextureHost final : public RenderTextureHost {
+ public:
+ RenderBufferTextureHost(uint8_t* aBuffer,
+ const layers::BufferDescriptor& aDescriptor);
+
+ wr::WrExternalImage Lock(uint8_t aChannelIndex, gl::GLContext* aGL,
+ wr::ImageRendering aRendering) override;
+ void Unlock() override;
+
+ size_t Bytes() override {
+ return mSize.width * mSize.height * BytesPerPixel(mFormat);
+ }
+ class RenderBufferData {
+ public:
+ RenderBufferData(uint8_t* aData, size_t aBufferSize)
+ : mData(aData), mBufferSize(aBufferSize) {}
+ const uint8_t* mData;
+ size_t mBufferSize;
+ };
+
+ RenderBufferData GetBufferDataForRender(uint8_t aChannelIndex);
+
+ private:
+ virtual ~RenderBufferTextureHost();
+
+ uint8_t* GetBuffer() const { return mBuffer; }
+
+ uint8_t* mBuffer;
+ layers::BufferDescriptor mDescriptor;
+ gfx::IntSize mSize;
+ gfx::SurfaceFormat mFormat;
+
+ RefPtr<gfx::DataSourceSurface> mSurface;
+ gfx::DataSourceSurface::MappedSurface mMap;
+
+ RefPtr<gfx::DataSourceSurface> mYSurface;
+ RefPtr<gfx::DataSourceSurface> mCbSurface;
+ RefPtr<gfx::DataSourceSurface> mCrSurface;
+ gfx::DataSourceSurface::MappedSurface mYMap;
+ gfx::DataSourceSurface::MappedSurface mCbMap;
+ gfx::DataSourceSurface::MappedSurface mCrMap;
+
+ bool mLocked;
+};
+
+} // namespace wr
+} // namespace mozilla
+
+#endif // MOZILLA_GFX_RENDERBUFFERTEXTUREHOST_H
diff --git a/gfx/webrender_bindings/RenderBufferTextureHostSWGL.cpp b/gfx/webrender_bindings/RenderBufferTextureHostSWGL.cpp
new file mode 100644
index 0000000000..9c17cbae82
--- /dev/null
+++ b/gfx/webrender_bindings/RenderBufferTextureHostSWGL.cpp
@@ -0,0 +1,119 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "RenderBufferTextureHostSWGL.h"
+
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/layers/ImageDataSerializer.h"
+
+namespace mozilla {
+namespace wr {
+
+RenderBufferTextureHostSWGL::RenderBufferTextureHostSWGL(
+ uint8_t* aBuffer, const layers::BufferDescriptor& aDescriptor)
+ : mBuffer(aBuffer), mDescriptor(aDescriptor) {
+ MOZ_COUNT_CTOR_INHERITED(RenderBufferTextureHostSWGL, RenderTextureHostSWGL);
+
+ switch (mDescriptor.type()) {
+ case layers::BufferDescriptor::TYCbCrDescriptor:
+ case layers::BufferDescriptor::TRGBDescriptor:
+ break;
+ default:
+ gfxCriticalError() << "Bad buffer host descriptor "
+ << (int)mDescriptor.type();
+ MOZ_CRASH("GFX: Bad descriptor");
+ }
+}
+
+RenderBufferTextureHostSWGL::~RenderBufferTextureHostSWGL() {
+ MOZ_COUNT_DTOR_INHERITED(RenderBufferTextureHostSWGL, RenderTextureHostSWGL);
+}
+
+size_t RenderBufferTextureHostSWGL::GetPlaneCount() const {
+ switch (mDescriptor.type()) {
+ case layers::BufferDescriptor::TYCbCrDescriptor:
+ return 3;
+ default:
+ return 1;
+ }
+}
+
+gfx::SurfaceFormat RenderBufferTextureHostSWGL::GetFormat() const {
+ switch (mDescriptor.type()) {
+ case layers::BufferDescriptor::TYCbCrDescriptor:
+ return gfx::SurfaceFormat::YUV;
+ default:
+ return mDescriptor.get_RGBDescriptor().format();
+ }
+}
+
+gfx::ColorDepth RenderBufferTextureHostSWGL::GetColorDepth() const {
+ switch (mDescriptor.type()) {
+ case layers::BufferDescriptor::TYCbCrDescriptor:
+ return mDescriptor.get_YCbCrDescriptor().colorDepth();
+ default:
+ return gfx::ColorDepth::COLOR_8;
+ }
+}
+
+gfx::YUVColorSpace RenderBufferTextureHostSWGL::GetYUVColorSpace() const {
+ switch (mDescriptor.type()) {
+ case layers::BufferDescriptor::TYCbCrDescriptor:
+ return mDescriptor.get_YCbCrDescriptor().yUVColorSpace();
+ default:
+ return gfx::YUVColorSpace::UNKNOWN;
+ }
+}
+
+bool RenderBufferTextureHostSWGL::MapPlane(RenderCompositor* aCompositor,
+ uint8_t aChannelIndex,
+ PlaneInfo& aPlaneInfo) {
+ if (!mBuffer) {
+ // We hit some problems to get the shmem.
+ gfxCriticalNote << "GetBuffer Failed";
+ return false;
+ }
+
+ switch (mDescriptor.type()) {
+ case layers::BufferDescriptor::TYCbCrDescriptor: {
+ const layers::YCbCrDescriptor& desc = mDescriptor.get_YCbCrDescriptor();
+ switch (aChannelIndex) {
+ case 0:
+ aPlaneInfo.mData =
+ layers::ImageDataSerializer::GetYChannel(mBuffer, desc);
+ aPlaneInfo.mStride = desc.yStride();
+ aPlaneInfo.mSize = desc.ySize();
+ break;
+ case 1:
+ aPlaneInfo.mData =
+ layers::ImageDataSerializer::GetCbChannel(mBuffer, desc);
+ aPlaneInfo.mStride = desc.cbCrStride();
+ aPlaneInfo.mSize = desc.cbCrSize();
+ break;
+ case 2:
+ aPlaneInfo.mData =
+ layers::ImageDataSerializer::GetCrChannel(mBuffer, desc);
+ aPlaneInfo.mStride = desc.cbCrStride();
+ aPlaneInfo.mSize = desc.cbCrSize();
+ break;
+ }
+ break;
+ }
+ default: {
+ const layers::RGBDescriptor& desc = mDescriptor.get_RGBDescriptor();
+ aPlaneInfo.mData = mBuffer;
+ aPlaneInfo.mStride = layers::ImageDataSerializer::GetRGBStride(desc);
+ aPlaneInfo.mSize = desc.size();
+ break;
+ }
+ }
+ return true;
+}
+
+void RenderBufferTextureHostSWGL::UnmapPlanes() {}
+
+} // namespace wr
+} // namespace mozilla
diff --git a/gfx/webrender_bindings/RenderBufferTextureHostSWGL.h b/gfx/webrender_bindings/RenderBufferTextureHostSWGL.h
new file mode 100644
index 0000000000..20ec5eb1b9
--- /dev/null
+++ b/gfx/webrender_bindings/RenderBufferTextureHostSWGL.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef MOZILLA_GFX_RENDERBUFFERTEXTUREHOSTSWGL_H
+#define MOZILLA_GFX_RENDERBUFFERTEXTUREHOSTSWGL_H
+
+#include "RenderTextureHostSWGL.h"
+
+namespace mozilla {
+namespace wr {
+
+class RenderBufferTextureHostSWGL final : public RenderTextureHostSWGL {
+ public:
+ RenderBufferTextureHostSWGL(uint8_t* aBuffer,
+ const layers::BufferDescriptor& aDescriptor);
+
+ size_t GetPlaneCount() const override;
+
+ gfx::SurfaceFormat GetFormat() const override;
+
+ gfx::ColorDepth GetColorDepth() const override;
+
+ gfx::YUVColorSpace GetYUVColorSpace() const override;
+
+ bool MapPlane(RenderCompositor* aCompositor, uint8_t aChannelIndex,
+ PlaneInfo& aPlaneInfo) override;
+
+ void UnmapPlanes() override;
+
+ private:
+ virtual ~RenderBufferTextureHostSWGL();
+
+ uint8_t* mBuffer;
+ layers::BufferDescriptor mDescriptor;
+};
+
+} // namespace wr
+} // namespace mozilla
+
+#endif // MOZILLA_GFX_RENDERBUFFERTEXTUREHOSTSWGL_H
diff --git a/gfx/webrender_bindings/RenderCompositor.cpp b/gfx/webrender_bindings/RenderCompositor.cpp
new file mode 100644
index 0000000000..4e77351e4f
--- /dev/null
+++ b/gfx/webrender_bindings/RenderCompositor.cpp
@@ -0,0 +1,232 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "RenderCompositor.h"
+
+#include "gfxConfig.h"
+#include "GLContext.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/layers/SyncObject.h"
+#include "mozilla/webrender/RenderCompositorOGL.h"
+#include "mozilla/webrender/RenderCompositorSWGL.h"
+#include "mozilla/widget/CompositorWidget.h"
+
+#ifdef XP_WIN
+# include "mozilla/webrender/RenderCompositorANGLE.h"
+# include "mozilla/webrender/RenderCompositorD3D11SWGL.h"
+#endif
+
+#if defined(MOZ_WAYLAND) || defined(MOZ_WIDGET_ANDROID)
+# include "mozilla/webrender/RenderCompositorEGL.h"
+#endif
+
+#ifdef XP_MACOSX
+# include "mozilla/webrender/RenderCompositorNative.h"
+#endif
+
+namespace mozilla {
+namespace wr {
+
+void wr_compositor_add_surface(void* aCompositor, wr::NativeSurfaceId aId,
+ const wr::CompositorSurfaceTransform* aTransform,
+ wr::DeviceIntRect aClipRect,
+ wr::ImageRendering aImageRendering) {
+ RenderCompositor* compositor = static_cast<RenderCompositor*>(aCompositor);
+ compositor->AddSurface(aId, *aTransform, aClipRect, aImageRendering);
+}
+
+void wr_compositor_begin_frame(void* aCompositor) {
+ RenderCompositor* compositor = static_cast<RenderCompositor*>(aCompositor);
+ compositor->CompositorBeginFrame();
+}
+
+void wr_compositor_bind(void* aCompositor, wr::NativeTileId aId,
+ wr::DeviceIntPoint* aOffset, uint32_t* aFboId,
+ wr::DeviceIntRect aDirtyRect,
+ wr::DeviceIntRect aValidRect) {
+ RenderCompositor* compositor = static_cast<RenderCompositor*>(aCompositor);
+ compositor->Bind(aId, aOffset, aFboId, aDirtyRect, aValidRect);
+}
+
+void wr_compositor_create_surface(void* aCompositor, wr::NativeSurfaceId aId,
+ wr::DeviceIntPoint aVirtualOffset,
+ wr::DeviceIntSize aTileSize, bool aIsOpaque) {
+ RenderCompositor* compositor = static_cast<RenderCompositor*>(aCompositor);
+ compositor->CreateSurface(aId, aVirtualOffset, aTileSize, aIsOpaque);
+}
+
+void wr_compositor_create_external_surface(void* aCompositor,
+ wr::NativeSurfaceId aId,
+ bool aIsOpaque) {
+ RenderCompositor* compositor = static_cast<RenderCompositor*>(aCompositor);
+ compositor->CreateExternalSurface(aId, aIsOpaque);
+}
+
+void wr_compositor_create_tile(void* aCompositor, wr::NativeSurfaceId aId,
+ int32_t aX, int32_t aY) {
+ RenderCompositor* compositor = static_cast<RenderCompositor*>(aCompositor);
+ compositor->CreateTile(aId, aX, aY);
+}
+
+void wr_compositor_destroy_tile(void* aCompositor, wr::NativeSurfaceId aId,
+ int32_t aX, int32_t aY) {
+ RenderCompositor* compositor = static_cast<RenderCompositor*>(aCompositor);
+ compositor->DestroyTile(aId, aX, aY);
+}
+
+void wr_compositor_destroy_surface(void* aCompositor, NativeSurfaceId aId) {
+ RenderCompositor* compositor = static_cast<RenderCompositor*>(aCompositor);
+ compositor->DestroySurface(aId);
+}
+
+void wr_compositor_attach_external_image(void* aCompositor,
+ wr::NativeSurfaceId aId,
+ wr::ExternalImageId aExternalImage) {
+ RenderCompositor* compositor = static_cast<RenderCompositor*>(aCompositor);
+ compositor->AttachExternalImage(aId, aExternalImage);
+}
+
+void wr_compositor_start_compositing(void* aCompositor,
+ const wr::DeviceIntRect* aDirtyRects,
+ size_t aNumDirtyRects,
+ const wr::DeviceIntRect* aOpaqueRects,
+ size_t aNumOpaqueRects) {
+ RenderCompositor* compositor = static_cast<RenderCompositor*>(aCompositor);
+ compositor->StartCompositing(aDirtyRects, aNumDirtyRects, aOpaqueRects,
+ aNumOpaqueRects);
+}
+
+void wr_compositor_end_frame(void* aCompositor) {
+ RenderCompositor* compositor = static_cast<RenderCompositor*>(aCompositor);
+ compositor->CompositorEndFrame();
+}
+
+void wr_compositor_enable_native_compositor(void* aCompositor, bool aEnable) {
+ RenderCompositor* compositor = static_cast<RenderCompositor*>(aCompositor);
+ compositor->EnableNativeCompositor(aEnable);
+}
+
+CompositorCapabilities wr_compositor_get_capabilities(void* aCompositor) {
+ RenderCompositor* compositor = static_cast<RenderCompositor*>(aCompositor);
+ return compositor->GetCompositorCapabilities();
+}
+
+void wr_compositor_unbind(void* aCompositor) {
+ RenderCompositor* compositor = static_cast<RenderCompositor*>(aCompositor);
+ compositor->Unbind();
+}
+
+void wr_compositor_deinit(void* aCompositor) {
+ RenderCompositor* compositor = static_cast<RenderCompositor*>(aCompositor);
+ compositor->DeInit();
+}
+
+void wr_compositor_map_tile(void* aCompositor, wr::NativeTileId aId,
+ wr::DeviceIntRect aDirtyRect,
+ wr::DeviceIntRect aValidRect, void** aData,
+ int32_t* aStride) {
+ RenderCompositor* compositor = static_cast<RenderCompositor*>(aCompositor);
+ compositor->MapTile(aId, aDirtyRect, aValidRect, aData, aStride);
+}
+
+void wr_compositor_unmap_tile(void* aCompositor) {
+ RenderCompositor* compositor = static_cast<RenderCompositor*>(aCompositor);
+ compositor->UnmapTile();
+}
+
+void wr_partial_present_compositor_set_buffer_damage_region(
+ void* aCompositor, const wr::DeviceIntRect* aRects, size_t aNumRects) {
+ RenderCompositor* compositor = static_cast<RenderCompositor*>(aCompositor);
+ compositor->SetBufferDamageRegion(aRects, aNumRects);
+}
+
+/* static */
+UniquePtr<RenderCompositor> RenderCompositor::Create(
+ RefPtr<widget::CompositorWidget>&& aWidget, nsACString& aError) {
+ if (gfx::gfxVars::UseSoftwareWebRender()) {
+#ifdef XP_MACOSX
+ // Mac uses NativeLayerCA
+ return RenderCompositorNativeSWGL::Create(std::move(aWidget), aError);
+#elif defined(XP_WIN)
+ if (StaticPrefs::gfx_webrender_software_d3d11_AtStartup() &&
+ gfx::gfxConfig::IsEnabled(gfx::Feature::D3D11_COMPOSITING)) {
+ UniquePtr<RenderCompositor> comp =
+ RenderCompositorD3D11SWGL::Create(std::move(aWidget), aError);
+ if (comp) {
+ return comp;
+ }
+ }
+#endif
+ return RenderCompositorSWGL::Create(std::move(aWidget), aError);
+ }
+
+#ifdef XP_WIN
+ if (gfx::gfxVars::UseWebRenderANGLE()) {
+ return RenderCompositorANGLE::Create(std::move(aWidget), aError);
+ }
+#endif
+
+#if defined(MOZ_WAYLAND) || defined(MOZ_WIDGET_ANDROID)
+ UniquePtr<RenderCompositor> eglCompositor =
+ RenderCompositorEGL::Create(aWidget, aError);
+ if (eglCompositor) {
+ return eglCompositor;
+ }
+#endif
+
+#if defined(MOZ_WIDGET_ANDROID)
+ // RenderCompositorOGL is not used on android
+ return nullptr;
+#elif defined(XP_MACOSX)
+ // Mac uses NativeLayerCA
+ return RenderCompositorNativeOGL::Create(std::move(aWidget), aError);
+#else
+ return RenderCompositorOGL::Create(std::move(aWidget), aError);
+#endif
+}
+
+RenderCompositor::RenderCompositor(RefPtr<widget::CompositorWidget>&& aWidget)
+ : mWidget(aWidget) {}
+
+RenderCompositor::~RenderCompositor() = default;
+
+bool RenderCompositor::MakeCurrent() { return gl()->MakeCurrent(); }
+
+GLenum RenderCompositor::IsContextLost(bool aForce) {
+ auto* glc = gl();
+ // GetGraphicsResetStatus may trigger an implicit MakeCurrent if robustness
+ // is not supported, so unless we are forcing, pass on the check.
+ if (!glc || (!aForce && !glc->IsSupported(gl::GLFeature::robustness))) {
+ return LOCAL_GL_NO_ERROR;
+ }
+ auto resetStatus = glc->fGetGraphicsResetStatus();
+ switch (resetStatus) {
+ case LOCAL_GL_NO_ERROR:
+ break;
+ case LOCAL_GL_INNOCENT_CONTEXT_RESET_ARB:
+ NS_WARNING("Device reset due to system / different context");
+ break;
+ case LOCAL_GL_PURGED_CONTEXT_RESET_NV:
+ NS_WARNING("Device reset due to NV video memory purged");
+ break;
+ case LOCAL_GL_GUILTY_CONTEXT_RESET_ARB:
+ gfxCriticalError() << "Device reset due to WR context";
+ break;
+ case LOCAL_GL_UNKNOWN_CONTEXT_RESET_ARB:
+ gfxCriticalNote << "Device reset may be due to WR context";
+ break;
+ default:
+ gfxCriticalError() << "Device reset with WR context unexpected status: "
+ << gfx::hexa(resetStatus);
+ break;
+ }
+ return resetStatus;
+}
+
+} // namespace wr
+} // namespace mozilla
diff --git a/gfx/webrender_bindings/RenderCompositor.h b/gfx/webrender_bindings/RenderCompositor.h
new file mode 100644
index 0000000000..219db77da6
--- /dev/null
+++ b/gfx/webrender_bindings/RenderCompositor.h
@@ -0,0 +1,211 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef MOZILLA_GFX_RENDERCOMPOSITOR_H
+#define MOZILLA_GFX_RENDERCOMPOSITOR_H
+
+#include "mozilla/ipc/FileDescriptor.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/webrender/WebRenderTypes.h"
+#include "Units.h"
+
+#include "GLTypes.h"
+
+namespace mozilla {
+
+namespace gl {
+class GLContext;
+}
+
+namespace layers {
+class CompositionRecorder;
+class SyncObjectHost;
+} // namespace layers
+
+namespace widget {
+class CompositorWidget;
+}
+
+namespace wr {
+
+class RenderCompositorD3D11SWGL;
+
+class RenderCompositor {
+ public:
+ static UniquePtr<RenderCompositor> Create(
+ RefPtr<widget::CompositorWidget>&& aWidget, nsACString& aError);
+
+ RenderCompositor(RefPtr<widget::CompositorWidget>&& aWidget);
+ virtual ~RenderCompositor();
+
+ virtual bool BeginFrame() = 0;
+
+ // Optional handler in case the frame was aborted allowing the compositor
+ // to clean up relevant resources if required.
+ virtual void CancelFrame() {}
+
+ // Called to notify the RenderCompositor that all of the commands for a frame
+ // have been pushed to the queue.
+ // @return a RenderedFrameId for the frame
+ virtual RenderedFrameId EndFrame(
+ const nsTArray<DeviceIntRect>& aDirtyRects) = 0;
+ // Returns false when waiting gpu tasks is failed.
+ // It might happen when rendering context is lost.
+ virtual bool WaitForGPU() { return true; }
+
+ // Check for and return the last completed frame.
+ // @return the last (highest) completed RenderedFrameId
+ virtual RenderedFrameId GetLastCompletedFrameId() {
+ return mLatestRenderFrameId.Prev();
+ }
+
+ // Update FrameId when WR rendering does not happen.
+ virtual RenderedFrameId UpdateFrameId() { return GetNextRenderFrameId(); }
+
+ virtual void Pause() = 0;
+ virtual bool Resume() = 0;
+ // Called when WR rendering is skipped
+ virtual void Update() {}
+
+ virtual gl::GLContext* gl() const { return nullptr; }
+ virtual void* swgl() const { return nullptr; }
+
+ virtual bool MakeCurrent();
+
+ virtual bool UseANGLE() const { return false; }
+
+ virtual bool UseDComp() const { return false; }
+
+ virtual bool UseTripleBuffering() const { return false; }
+
+ virtual layers::WebRenderBackend BackendType() const {
+ return layers::WebRenderBackend::HARDWARE;
+ }
+ virtual layers::WebRenderCompositor CompositorType() const {
+ return layers::WebRenderCompositor::DRAW;
+ }
+
+ virtual RenderCompositorD3D11SWGL* AsRenderCompositorD3D11SWGL() {
+ return nullptr;
+ }
+
+ // True if AttachExternalImage supports being used with an external
+ // image that maps to a RenderBufferTextureHost
+ virtual bool SupportsExternalBufferTextures() const { return false; }
+
+ virtual LayoutDeviceIntSize GetBufferSize() = 0;
+
+ widget::CompositorWidget* GetWidget() const { return mWidget; }
+
+ layers::SyncObjectHost* GetSyncObject() const { return mSyncObject.get(); }
+
+ virtual GLenum IsContextLost(bool aForce);
+
+ virtual bool SupportAsyncScreenshot() { return true; }
+
+ virtual bool ShouldUseNativeCompositor() { return false; }
+ virtual uint32_t GetMaxUpdateRects() { return 0; }
+
+ // Interface for wr::Compositor
+ virtual void CompositorBeginFrame() {}
+ virtual void CompositorEndFrame() {}
+ virtual void Bind(wr::NativeTileId aId, wr::DeviceIntPoint* aOffset,
+ uint32_t* aFboId, wr::DeviceIntRect aDirtyRect,
+ wr::DeviceIntRect aValidRect) {}
+ virtual void Unbind() {}
+ virtual bool MapTile(wr::NativeTileId aId, wr::DeviceIntRect aDirtyRect,
+ wr::DeviceIntRect aValidRect, void** aData,
+ int32_t* aStride) {
+ return false;
+ }
+ virtual void UnmapTile() {}
+ virtual void CreateSurface(wr::NativeSurfaceId aId,
+ wr::DeviceIntPoint aVirtualOffset,
+ wr::DeviceIntSize aTileSize, bool aIsOpaque) {}
+ virtual void CreateExternalSurface(wr::NativeSurfaceId aId, bool aIsOpaque) {}
+ virtual void DestroySurface(NativeSurfaceId aId) {}
+ virtual void CreateTile(wr::NativeSurfaceId, int32_t aX, int32_t aY) {}
+ virtual void DestroyTile(wr::NativeSurfaceId, int32_t aX, int32_t aY) {}
+ virtual void AttachExternalImage(wr::NativeSurfaceId aId,
+ wr::ExternalImageId aExternalImage) {}
+ virtual void AddSurface(wr::NativeSurfaceId aId,
+ const wr::CompositorSurfaceTransform& aTransform,
+ wr::DeviceIntRect aClipRect,
+ wr::ImageRendering aImageRendering) {}
+ // Called in the middle of a frame after all surfaces have been added but
+ // before tiles are updated to signal that early compositing can start
+ virtual void StartCompositing(const wr::DeviceIntRect* aDirtyRects,
+ size_t aNumDirtyRects,
+ const wr::DeviceIntRect* aOpaqueRects,
+ size_t aNumOpaqueRects) {}
+ virtual void EnableNativeCompositor(bool aEnable) {}
+ virtual void DeInit() {}
+ virtual CompositorCapabilities GetCompositorCapabilities() = 0;
+
+ // Interface for partial present
+ virtual bool UsePartialPresent() { return false; }
+ virtual bool RequestFullRender() { return false; }
+ virtual uint32_t GetMaxPartialPresentRects() { return 0; }
+ virtual bool ShouldDrawPreviousPartialPresentRegions() { return false; }
+ // Returns the age of the current backbuffer., This should be used, if
+ // ShouldDrawPreviousPartialPresentRegions() returns true, to determine the
+ // region which must be rendered in addition to the current frame's dirty
+ // rect.
+ virtual size_t GetBufferAge() const { return 0; }
+ // Allows webrender to specify the total region that will be rendered to this
+ // frame, ie the frame's dirty region and some previous frames' dirty regions,
+ // if applicable (calculated using the buffer age). Must be called before
+ // anything has been rendered to the main framebuffer.
+ virtual void SetBufferDamageRegion(const wr::DeviceIntRect* aRects,
+ size_t aNumRects) {}
+
+ // Whether the surface origin is top-left.
+ virtual bool SurfaceOriginIsTopLeft() { return false; }
+
+ // Does readback if wr_renderer_readback() could not get correct WR rendered
+ // result. It could happen when WebRender renders to multiple overlay layers.
+ virtual bool MaybeReadback(const gfx::IntSize& aReadbackSize,
+ const wr::ImageFormat& aReadbackFormat,
+ const Range<uint8_t>& aReadbackBuffer,
+ bool* aNeedsYFlip) {
+ return false;
+ }
+ virtual void MaybeRequestAllowFrameRecording(bool aWillRecord) {}
+ virtual bool MaybeRecordFrame(layers::CompositionRecorder& aRecorder) {
+ return false;
+ }
+ virtual bool MaybeGrabScreenshot(const gfx::IntSize& aWindowSize) {
+ return false;
+ }
+ virtual bool MaybeProcessScreenshotQueue() { return false; }
+
+ // Returns FileDescriptor of release fence.
+ // Release fence is a fence that is used for waiting until usage/composite of
+ // AHardwareBuffer is ended. The fence is delivered to client side via
+ // ImageBridge. It is used only on android.
+ virtual ipc::FileDescriptor GetAndResetReleaseFence() {
+ return ipc::FileDescriptor();
+ }
+
+ virtual bool IsPaused() { return false; }
+
+ protected:
+ // We default this to 2, so that mLatestRenderFrameId.Prev() is always valid.
+ RenderedFrameId mLatestRenderFrameId = RenderedFrameId{2};
+ RenderedFrameId GetNextRenderFrameId() {
+ mLatestRenderFrameId = mLatestRenderFrameId.Next();
+ return mLatestRenderFrameId;
+ }
+
+ RefPtr<widget::CompositorWidget> mWidget;
+ RefPtr<layers::SyncObjectHost> mSyncObject;
+};
+
+} // namespace wr
+} // namespace mozilla
+
+#endif
diff --git a/gfx/webrender_bindings/RenderCompositorANGLE.cpp b/gfx/webrender_bindings/RenderCompositorANGLE.cpp
new file mode 100644
index 0000000000..ccd054c624
--- /dev/null
+++ b/gfx/webrender_bindings/RenderCompositorANGLE.cpp
@@ -0,0 +1,1081 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "RenderCompositorANGLE.h"
+
+#include "GLContext.h"
+#include "GLContextEGL.h"
+#include "GLContextProvider.h"
+#include "mozilla/gfx/DeviceManagerDx.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/gfx/StackArray.h"
+#include "mozilla/layers/HelpersD3D11.h"
+#include "mozilla/layers/SyncObject.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/webrender/DCLayerTree.h"
+#include "mozilla/webrender/RenderThread.h"
+#include "mozilla/widget/CompositorWidget.h"
+#include "mozilla/widget/WinCompositorWidget.h"
+#include "mozilla/WindowsVersion.h"
+#include "mozilla/Telemetry.h"
+#include "nsPrintfCString.h"
+#include "FxROutputHandler.h"
+
+#undef NTDDI_VERSION
+#define NTDDI_VERSION NTDDI_WIN8
+
+#include <d3d11.h>
+#include <dcomp.h>
+#include <dxgi1_2.h>
+
+// Flag for PrintWindow() that is defined in Winuser.h. It is defined since
+// Windows 8.1. This allows PrintWindow to capture window content that is
+// rendered with DirectComposition.
+#undef PW_RENDERFULLCONTENT
+#define PW_RENDERFULLCONTENT 0x00000002
+
+namespace mozilla {
+namespace wr {
+
+/* static */
+UniquePtr<RenderCompositor> RenderCompositorANGLE::Create(
+ RefPtr<widget::CompositorWidget>&& aWidget, nsACString& aError) {
+ const auto& gl = RenderThread::Get()->SharedGL(aError);
+ if (!gl) {
+ if (aError.IsEmpty()) {
+ aError.Assign("RcANGLE(no shared GL)"_ns);
+ } else {
+ aError.Append("(Create)"_ns);
+ }
+ return nullptr;
+ }
+
+ UniquePtr<RenderCompositorANGLE> compositor =
+ MakeUnique<RenderCompositorANGLE>(std::move(aWidget));
+ if (!compositor->Initialize(aError)) {
+ return nullptr;
+ }
+ return compositor;
+}
+
+RenderCompositorANGLE::RenderCompositorANGLE(
+ RefPtr<widget::CompositorWidget>&& aWidget)
+ : RenderCompositor(std::move(aWidget)),
+ mEGLConfig(nullptr),
+ mEGLSurface(nullptr),
+ mUseTripleBuffering(false),
+ mUseAlpha(false),
+ mUseNativeCompositor(true),
+ mUsePartialPresent(false),
+ mFullRender(false),
+ mDisablingNativeCompositor(false) {}
+
+RenderCompositorANGLE::~RenderCompositorANGLE() {
+ DestroyEGLSurface();
+ MOZ_ASSERT(!mEGLSurface);
+}
+
+ID3D11Device* RenderCompositorANGLE::GetDeviceOfEGLDisplay(nsACString& aError) {
+ const auto& gl = RenderThread::Get()->SharedGL(aError);
+ if (!gl) {
+ if (aError.IsEmpty()) {
+ aError.Assign("RcANGLE(no shared GL in get device)"_ns);
+ } else {
+ aError.Append("(GetDevice)"_ns);
+ }
+ return nullptr;
+ }
+ const auto& gle = gl::GLContextEGL::Cast(gl);
+ const auto& egl = gle->mEgl;
+ MOZ_ASSERT(egl);
+ if (!egl || !egl->IsExtensionSupported(gl::EGLExtension::EXT_device_query)) {
+ aError.Assign("RcANGLE(no EXT_device_query support)"_ns);
+ return nullptr;
+ }
+
+ // Fetch the D3D11 device.
+ EGLDeviceEXT eglDevice = nullptr;
+ egl->fQueryDisplayAttribEXT(LOCAL_EGL_DEVICE_EXT, (EGLAttrib*)&eglDevice);
+ MOZ_ASSERT(eglDevice);
+ ID3D11Device* device = nullptr;
+ egl->mLib->fQueryDeviceAttribEXT(eglDevice, LOCAL_EGL_D3D11_DEVICE_ANGLE,
+ (EGLAttrib*)&device);
+ if (!device) {
+ aError.Assign("RcANGLE(get D3D11Device from EGLDisplay failed)"_ns);
+ return nullptr;
+ }
+ return device;
+}
+
+bool RenderCompositorANGLE::ShutdownEGLLibraryIfNecessary(nsACString& aError) {
+ const auto& displayDevice = GetDeviceOfEGLDisplay(aError);
+ RefPtr<ID3D11Device> device =
+ gfx::DeviceManagerDx::Get()->GetCompositorDevice();
+
+ // When DeviceReset is handled by GPUProcessManager/GPUParent,
+ // CompositorDevice is updated to a new device. EGLDisplay also needs to be
+ // updated, since EGLDisplay uses DeviceManagerDx::mCompositorDevice on ANGLE
+ // WebRender use case. EGLDisplay could be updated when Renderer count becomes
+ // 0. It is ensured by GPUProcessManager during handling DeviceReset.
+ // GPUChild::RecvNotifyDeviceReset() destroys all CompositorSessions before
+ // re-creating them.
+
+ if ((!displayDevice || device.get() != displayDevice) &&
+ RenderThread::Get()->RendererCount() == 0) {
+ // Shutdown GLLibraryEGL for updating EGLDisplay.
+ RenderThread::Get()->ClearSharedGL();
+ }
+ return true;
+}
+
+bool RenderCompositorANGLE::Initialize(nsACString& aError) {
+ // TODO(aosmond): This causes us to lose WebRender because it is unable to
+ // distinguish why we failed and retry once the reset is complete. This does
+ // appear to happen in the wild, so we really should try to do something
+ // differently here.
+ if (RenderThread::Get()->IsHandlingDeviceReset()) {
+ aError.Assign("RcANGLE(waiting device reset)"_ns);
+ return false;
+ }
+
+ // Update device if necessary.
+ if (!ShutdownEGLLibraryIfNecessary(aError)) {
+ aError.Append("(Shutdown EGL)"_ns);
+ return false;
+ }
+ const auto gl = RenderThread::Get()->SharedGL(aError);
+ if (!gl) {
+ if (aError.IsEmpty()) {
+ aError.Assign("RcANGLE(no shared GL post maybe shutdown)"_ns);
+ } else {
+ aError.Append("(Initialize)"_ns);
+ }
+ return false;
+ }
+
+ // Force enable alpha channel to make sure ANGLE use correct framebuffer
+ // formart
+ const auto& gle = gl::GLContextEGL::Cast(gl);
+ const auto& egl = gle->mEgl;
+ if (!gl::CreateConfig(*egl, &mEGLConfig, /* bpp */ 32,
+ /* enableDepthBuffer */ true, gl->IsGLES())) {
+ aError.Assign("RcANGLE(create EGLConfig failed)"_ns);
+ return false;
+ }
+ MOZ_ASSERT(mEGLConfig);
+
+ mDevice = GetDeviceOfEGLDisplay(aError);
+
+ if (!mDevice) {
+ return false;
+ }
+
+ mDevice->GetImmediateContext(getter_AddRefs(mCtx));
+ if (!mCtx) {
+ aError.Assign("RcANGLE(get immediate context failed)"_ns);
+ return false;
+ }
+
+ // Create DCLayerTree when DirectComposition is used.
+ if (gfx::gfxVars::UseWebRenderDCompWin()) {
+ HWND compositorHwnd = GetCompositorHwnd();
+ if (compositorHwnd) {
+ mDCLayerTree = DCLayerTree::Create(gl, mEGLConfig, mDevice, mCtx,
+ compositorHwnd, aError);
+ if (!mDCLayerTree) {
+ return false;
+ }
+ } else {
+ aError.Assign("RcANGLE(no compositor window)"_ns);
+ return false;
+ }
+ }
+
+ // Create SwapChain when compositor is not used
+ if (!UseCompositor()) {
+ if (!CreateSwapChain(aError)) {
+ // SwapChain creation failed.
+ return false;
+ }
+ }
+
+ mSyncObject = layers::SyncObjectHost::CreateSyncObjectHost(mDevice);
+ if (!mSyncObject->Init()) {
+ // Some errors occur. Clear the mSyncObject here.
+ // Then, there will be no texture synchronization.
+ aError.Assign("RcANGLE(create SyncObject failed)"_ns);
+ return false;
+ }
+
+ InitializeUsePartialPresent();
+
+ return true;
+}
+
+HWND RenderCompositorANGLE::GetCompositorHwnd() {
+ HWND hwnd = 0;
+
+ if (XRE_IsGPUProcess()) {
+ hwnd = mWidget->AsWindows()->GetCompositorHwnd();
+ } else if (
+ StaticPrefs::
+ gfx_webrender_enabled_no_gpu_process_with_angle_win_AtStartup()) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ // When GPU process does not exist, we do not need to use compositor window.
+ hwnd = mWidget->AsWindows()->GetHwnd();
+ }
+
+ return hwnd;
+}
+
+bool RenderCompositorANGLE::CreateSwapChain(nsACString& aError) {
+ MOZ_ASSERT(!UseCompositor());
+
+ HWND hwnd = mWidget->AsWindows()->GetHwnd();
+
+ RefPtr<IDXGIDevice> dxgiDevice;
+ mDevice->QueryInterface((IDXGIDevice**)getter_AddRefs(dxgiDevice));
+
+ RefPtr<IDXGIFactory> dxgiFactory;
+ {
+ RefPtr<IDXGIAdapter> adapter;
+ dxgiDevice->GetAdapter(getter_AddRefs(adapter));
+
+ adapter->GetParent(
+ IID_PPV_ARGS((IDXGIFactory**)getter_AddRefs(dxgiFactory)));
+ }
+
+ RefPtr<IDXGIFactory2> dxgiFactory2;
+ HRESULT hr = dxgiFactory->QueryInterface(
+ (IDXGIFactory2**)getter_AddRefs(dxgiFactory2));
+ if (FAILED(hr)) {
+ dxgiFactory2 = nullptr;
+ }
+
+ CreateSwapChainForDCompIfPossible(dxgiFactory2);
+ if (gfx::gfxVars::UseWebRenderDCompWin() && !mSwapChain) {
+ MOZ_ASSERT(GetCompositorHwnd());
+ aError.Assign("RcANGLE(create swapchain for dcomp failed)"_ns);
+ return false;
+ }
+
+ if (!mSwapChain && dxgiFactory2) {
+ RefPtr<IDXGISwapChain1> swapChain1;
+ bool useTripleBuffering = false;
+
+ DXGI_SWAP_CHAIN_DESC1 desc{};
+ desc.Width = 0;
+ desc.Height = 0;
+ desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
+ desc.SampleDesc.Count = 1;
+ desc.SampleDesc.Quality = 0;
+ desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
+
+ if (gfx::gfxVars::UseWebRenderFlipSequentialWin()) {
+ useTripleBuffering = gfx::gfxVars::UseWebRenderTripleBufferingWin();
+ if (useTripleBuffering) {
+ desc.BufferCount = 3;
+ } else {
+ desc.BufferCount = 2;
+ }
+ desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
+ } else {
+ desc.BufferCount = 1;
+ desc.SwapEffect = DXGI_SWAP_EFFECT_SEQUENTIAL;
+ }
+ desc.Scaling = DXGI_SCALING_NONE;
+ desc.Flags = 0;
+
+ hr = dxgiFactory2->CreateSwapChainForHwnd(
+ mDevice, hwnd, &desc, nullptr, nullptr, getter_AddRefs(swapChain1));
+ if (SUCCEEDED(hr) && swapChain1) {
+ DXGI_RGBA color = {1.0f, 1.0f, 1.0f, 1.0f};
+ swapChain1->SetBackgroundColor(&color);
+ mSwapChain = swapChain1;
+ mSwapChain1 = swapChain1;
+ mUseTripleBuffering = useTripleBuffering;
+ } else if (gfx::gfxVars::UseWebRenderFlipSequentialWin()) {
+ gfxCriticalNoteOnce << "FLIP_SEQUENTIAL is not supported. Fallback";
+
+ if (mWidget->AsWindows()->GetCompositorHwnd()) {
+ // Destroy compositor window.
+ mWidget->AsWindows()->DestroyCompositorWindow();
+ hwnd = mWidget->AsWindows()->GetHwnd();
+ }
+ }
+ }
+
+ if (!mSwapChain) {
+ DXGI_SWAP_CHAIN_DESC swapDesc{};
+ swapDesc.BufferDesc.Width = 0;
+ swapDesc.BufferDesc.Height = 0;
+ swapDesc.BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
+ swapDesc.BufferDesc.RefreshRate.Numerator = 60;
+ swapDesc.BufferDesc.RefreshRate.Denominator = 1;
+ swapDesc.SampleDesc.Count = 1;
+ swapDesc.SampleDesc.Quality = 0;
+ swapDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
+ swapDesc.BufferCount = 1;
+ swapDesc.OutputWindow = hwnd;
+ swapDesc.Windowed = TRUE;
+ swapDesc.Flags = 0;
+ swapDesc.SwapEffect = DXGI_SWAP_EFFECT_SEQUENTIAL;
+
+ HRESULT hr = dxgiFactory->CreateSwapChain(dxgiDevice, &swapDesc,
+ getter_AddRefs(mSwapChain));
+ if (FAILED(hr)) {
+ aError.Assign(
+ nsPrintfCString("RcANGLE(swap chain create failed %x)", hr));
+ return false;
+ }
+
+ RefPtr<IDXGISwapChain1> swapChain1;
+ hr = mSwapChain->QueryInterface(
+ (IDXGISwapChain1**)getter_AddRefs(swapChain1));
+ if (SUCCEEDED(hr)) {
+ mSwapChain1 = swapChain1;
+ }
+ }
+
+ // We need this because we don't want DXGI to respond to Alt+Enter.
+ dxgiFactory->MakeWindowAssociation(hwnd, DXGI_MWA_NO_WINDOW_CHANGES);
+
+ if (!ResizeBufferIfNeeded()) {
+ aError.Assign("RcANGLE(resize buffer failed)"_ns);
+ return false;
+ }
+
+ return true;
+}
+
+void RenderCompositorANGLE::CreateSwapChainForDCompIfPossible(
+ IDXGIFactory2* aDXGIFactory2) {
+ if (!aDXGIFactory2 || !mDCLayerTree) {
+ return;
+ }
+
+ HWND hwnd = GetCompositorHwnd();
+ if (!hwnd) {
+ // When DirectComposition or DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL is used,
+ // compositor window needs to exist.
+ if (gfx::gfxVars::UseWebRenderDCompWin() ||
+ gfx::gfxVars::UseWebRenderFlipSequentialWin()) {
+ gfxCriticalNote << "Compositor window was not created";
+ }
+ return;
+ }
+
+ // When compositor is enabled, CompositionSurface is used for rendering.
+ // It does not support triple buffering.
+ bool useTripleBuffering =
+ gfx::gfxVars::UseWebRenderTripleBufferingWin() && !UseCompositor();
+ // Non Glass window is common since Windows 10.
+ bool useAlpha = false;
+ RefPtr<IDXGISwapChain1> swapChain1 =
+ CreateSwapChainForDComp(useTripleBuffering, useAlpha);
+ if (swapChain1) {
+ mSwapChain = swapChain1;
+ mSwapChain1 = swapChain1;
+ mUseTripleBuffering = useTripleBuffering;
+ mUseAlpha = useAlpha;
+ mDCLayerTree->SetDefaultSwapChain(swapChain1);
+ } else {
+ // Clear CLayerTree on falire
+ mDCLayerTree = nullptr;
+ }
+}
+
+RefPtr<IDXGISwapChain1> RenderCompositorANGLE::CreateSwapChainForDComp(
+ bool aUseTripleBuffering, bool aUseAlpha) {
+ HRESULT hr;
+ RefPtr<IDXGIDevice> dxgiDevice;
+ mDevice->QueryInterface((IDXGIDevice**)getter_AddRefs(dxgiDevice));
+
+ RefPtr<IDXGIFactory> dxgiFactory;
+ {
+ RefPtr<IDXGIAdapter> adapter;
+ dxgiDevice->GetAdapter(getter_AddRefs(adapter));
+
+ adapter->GetParent(
+ IID_PPV_ARGS((IDXGIFactory**)getter_AddRefs(dxgiFactory)));
+ }
+
+ RefPtr<IDXGIFactory2> dxgiFactory2;
+ hr = dxgiFactory->QueryInterface(
+ (IDXGIFactory2**)getter_AddRefs(dxgiFactory2));
+ if (FAILED(hr)) {
+ return nullptr;
+ }
+
+ RefPtr<IDXGISwapChain1> swapChain1;
+ DXGI_SWAP_CHAIN_DESC1 desc{};
+ // DXGI does not like 0x0 swapchains. Swap chain creation failed when 0x0 was
+ // set.
+ desc.Width = 1;
+ desc.Height = 1;
+ desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
+ desc.SampleDesc.Count = 1;
+ desc.SampleDesc.Quality = 0;
+ desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
+ if (aUseTripleBuffering) {
+ desc.BufferCount = 3;
+ } else {
+ desc.BufferCount = 2;
+ }
+ // DXGI_SCALING_NONE caused swap chain creation failure.
+ desc.Scaling = DXGI_SCALING_STRETCH;
+ desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
+ if (aUseAlpha) {
+ // This could degrade performance. Use it only when it is necessary.
+ desc.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED;
+ } else {
+ desc.AlphaMode = DXGI_ALPHA_MODE_IGNORE;
+ }
+ desc.Flags = 0;
+
+ hr = dxgiFactory2->CreateSwapChainForComposition(mDevice, &desc, nullptr,
+ getter_AddRefs(swapChain1));
+ if (SUCCEEDED(hr) && swapChain1) {
+ DXGI_RGBA color = {1.0f, 1.0f, 1.0f, 1.0f};
+ swapChain1->SetBackgroundColor(&color);
+ return swapChain1;
+ }
+
+ return nullptr;
+}
+
+bool RenderCompositorANGLE::BeginFrame() {
+ mWidget->AsWindows()->UpdateCompositorWndSizeIfNecessary();
+
+ if (!UseCompositor()) {
+ if (mDCLayerTree) {
+ bool useAlpha = mWidget->AsWindows()->HasGlass();
+ // When Alpha usage is changed, SwapChain needs to be recreatd.
+ if (useAlpha != mUseAlpha) {
+ DestroyEGLSurface();
+ mBufferSize.reset();
+
+ RefPtr<IDXGISwapChain1> swapChain1 =
+ CreateSwapChainForDComp(mUseTripleBuffering, useAlpha);
+ if (swapChain1) {
+ mSwapChain = swapChain1;
+ mUseAlpha = useAlpha;
+ mDCLayerTree->SetDefaultSwapChain(swapChain1);
+ // When alpha is used, we want to disable partial present.
+ // See Bug 1595027.
+ if (useAlpha) {
+ mFullRender = true;
+ }
+ } else {
+ gfxCriticalNote << "Failed to re-create SwapChain";
+ RenderThread::Get()->HandleWebRenderError(
+ WebRenderError::NEW_SURFACE);
+ return false;
+ }
+ }
+ }
+
+ if (!ResizeBufferIfNeeded()) {
+ return false;
+ }
+ }
+
+ if (!MakeCurrent()) {
+ gfxCriticalNote << "Failed to make render context current, can't draw.";
+ return false;
+ }
+
+ if (RenderThread::Get()->SyncObjectNeeded() && mSyncObject) {
+ if (!mSyncObject->Synchronize(/* aFallible */ true)) {
+ // It's timeout or other error. Handle the device-reset here.
+ RenderThread::Get()->HandleDeviceReset(
+ "SyncObject", nullptr, LOCAL_GL_UNKNOWN_CONTEXT_RESET_ARB);
+ return false;
+ }
+ }
+ return true;
+}
+
+RenderedFrameId RenderCompositorANGLE::EndFrame(
+ const nsTArray<DeviceIntRect>& aDirtyRects) {
+ RenderedFrameId frameId = GetNextRenderFrameId();
+ InsertGraphicsCommandsFinishedWaitQuery(frameId);
+
+ if (!UseCompositor()) {
+ auto start = TimeStamp::Now();
+ if (mWidget->AsWindows()->HasFxrOutputHandler()) {
+ // There is a Firefox Reality handler for this swapchain. Update this
+ // window's contents to the VR window.
+ FxROutputHandler* fxrHandler =
+ mWidget->AsWindows()->GetFxrOutputHandler();
+ if (fxrHandler->TryInitialize(mSwapChain, mDevice)) {
+ fxrHandler->UpdateOutput(mCtx);
+ }
+ }
+
+ const LayoutDeviceIntSize& bufferSize = mBufferSize.ref();
+
+ // During high contrast mode, alpha is used. In this case,
+ // IDXGISwapChain1::Present1 shows nothing with compositor window.
+ // In this case, we want to disable partial present by full render.
+ // See Bug 1595027
+ MOZ_ASSERT_IF(mUsePartialPresent && mUseAlpha, mFullRender);
+
+ if (mUsePartialPresent && !mUseAlpha && mSwapChain1) {
+ // Clear full render flag.
+ mFullRender = false;
+ // If there is no diry rect, we skip SwapChain present.
+ if (!aDirtyRects.IsEmpty()) {
+ int rectsCount = 0;
+ StackArray<RECT, 1> rects(aDirtyRects.Length());
+
+ for (size_t i = 0; i < aDirtyRects.Length(); ++i) {
+ const DeviceIntRect& rect = aDirtyRects[i];
+ // Clip rect to bufferSize
+ int left = std::max(0, std::min(rect.origin.x, bufferSize.width));
+ int top = std::max(0, std::min(rect.origin.y, bufferSize.height));
+ int right = std::max(
+ 0, std::min(rect.origin.x + rect.size.width, bufferSize.width));
+ int bottom = std::max(
+ 0, std::min(rect.origin.y + rect.size.height, bufferSize.height));
+
+ // When rect is not empty, the rect could be passed to Present1().
+ if (left < right && top < bottom) {
+ rects[rectsCount].left = left;
+ rects[rectsCount].top = top;
+ rects[rectsCount].right = right;
+ rects[rectsCount].bottom = bottom;
+ rectsCount++;
+ }
+ }
+
+ if (rectsCount > 0) {
+ DXGI_PRESENT_PARAMETERS params;
+ PodZero(&params);
+ params.DirtyRectsCount = rectsCount;
+ params.pDirtyRects = rects.data();
+
+ HRESULT hr;
+ hr = mSwapChain1->Present1(0, 0, &params);
+ if (FAILED(hr) && hr != DXGI_STATUS_OCCLUDED) {
+ gfxCriticalNote << "Present1 failed: " << gfx::hexa(hr);
+ mFullRender = true;
+ }
+ }
+ }
+ } else {
+ mSwapChain->Present(0, 0);
+ }
+ auto end = TimeStamp::Now();
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::COMPOSITE_SWAP_TIME,
+ (end - start).ToMilliseconds() * 10.);
+ }
+
+ if (mDisablingNativeCompositor) {
+ // During disabling native compositor, we need to wait all gpu tasks
+ // complete. Otherwise, rendering window could cause white flash.
+ WaitForPreviousGraphicsCommandsFinishedQuery(/* aWaitAll */ true);
+ mDisablingNativeCompositor = false;
+ }
+
+ if (mDCLayerTree) {
+ mDCLayerTree->MaybeUpdateDebug();
+ mDCLayerTree->MaybeCommit();
+ }
+
+ return frameId;
+}
+
+bool RenderCompositorANGLE::WaitForGPU() {
+ // Note: this waits on the query we inserted in the previous frame,
+ // not the one we just inserted now. Example:
+ // Insert query #1
+ // Present #1
+ // (first frame, no wait)
+ // Insert query #2
+ // Present #2
+ // Wait for query #1.
+ // Insert query #3
+ // Present #3
+ // Wait for query #2.
+ //
+ // This ensures we're done reading textures before swapping buffers.
+ return WaitForPreviousGraphicsCommandsFinishedQuery();
+}
+
+bool RenderCompositorANGLE::ResizeBufferIfNeeded() {
+ MOZ_ASSERT(mSwapChain);
+
+ LayoutDeviceIntSize size = mWidget->GetClientSize();
+
+ // DXGI does not like 0x0 swapchains. ResizeBuffers() failed when 0x0 was set
+ // when DComp is used.
+ size.width = std::max(size.width, 1);
+ size.height = std::max(size.height, 1);
+
+ if (mBufferSize.isSome() && mBufferSize.ref() == size) {
+ MOZ_ASSERT(mEGLSurface);
+ return true;
+ }
+
+ // Release EGLSurface of back buffer before calling ResizeBuffers().
+ DestroyEGLSurface();
+
+ mBufferSize = Some(size);
+
+ if (!CreateEGLSurface()) {
+ mBufferSize.reset();
+ return false;
+ }
+
+ if (mUsePartialPresent) {
+ mFullRender = true;
+ }
+ return true;
+}
+
+bool RenderCompositorANGLE::CreateEGLSurface() {
+ MOZ_ASSERT(mBufferSize.isSome());
+ MOZ_ASSERT(mEGLSurface == EGL_NO_SURFACE);
+
+ HRESULT hr;
+ RefPtr<ID3D11Texture2D> backBuf;
+
+ if (mBufferSize.isNothing()) {
+ gfxCriticalNote << "Buffer size is invalid";
+ return false;
+ }
+
+ const LayoutDeviceIntSize& size = mBufferSize.ref();
+
+ // Resize swap chain
+ DXGI_SWAP_CHAIN_DESC desc;
+ hr = mSwapChain->GetDesc(&desc);
+ if (FAILED(hr)) {
+ gfxCriticalNote << "Failed to read swap chain description: "
+ << gfx::hexa(hr) << " Size : " << size;
+ return false;
+ }
+ hr = mSwapChain->ResizeBuffers(desc.BufferCount, size.width, size.height,
+ DXGI_FORMAT_B8G8R8A8_UNORM, 0);
+ if (FAILED(hr)) {
+ gfxCriticalNote << "Failed to resize swap chain buffers: " << gfx::hexa(hr)
+ << " Size : " << size;
+ return false;
+ }
+
+ hr = mSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D),
+ (void**)getter_AddRefs(backBuf));
+ if (hr == DXGI_ERROR_INVALID_CALL) {
+ // This happens on some GPUs/drivers when there's a TDR.
+ if (mDevice->GetDeviceRemovedReason() != S_OK) {
+ gfxCriticalError() << "GetBuffer returned invalid call: " << gfx::hexa(hr)
+ << " Size : " << size;
+ return false;
+ }
+ }
+
+ const EGLint pbuffer_attribs[]{
+ LOCAL_EGL_WIDTH,
+ size.width,
+ LOCAL_EGL_HEIGHT,
+ size.height,
+ LOCAL_EGL_FLEXIBLE_SURFACE_COMPATIBILITY_SUPPORTED_ANGLE,
+ LOCAL_EGL_TRUE,
+ LOCAL_EGL_NONE};
+
+ const auto buffer = reinterpret_cast<EGLClientBuffer>(backBuf.get());
+
+ const auto gl = RenderThread::Get()->SharedGL();
+ const auto& gle = gl::GLContextEGL::Cast(gl);
+ const auto& egl = gle->mEgl;
+ const EGLSurface surface = egl->fCreatePbufferFromClientBuffer(
+ LOCAL_EGL_D3D_TEXTURE_ANGLE, buffer, mEGLConfig, pbuffer_attribs);
+
+ if (!surface) {
+ EGLint err = egl->mLib->fGetError();
+ gfxCriticalError() << "Failed to create Pbuffer of back buffer error: "
+ << gfx::hexa(err) << " Size : " << size;
+ return false;
+ }
+
+ mEGLSurface = surface;
+
+ return true;
+}
+
+void RenderCompositorANGLE::DestroyEGLSurface() {
+ // Release EGLSurface of back buffer before calling ResizeBuffers().
+ if (mEGLSurface) {
+ const auto& gle = gl::GLContextEGL::Cast(gl());
+ const auto& egl = gle->mEgl;
+ gle->SetEGLSurfaceOverride(EGL_NO_SURFACE);
+ egl->fDestroySurface(mEGLSurface);
+ mEGLSurface = nullptr;
+ }
+}
+
+void RenderCompositorANGLE::Pause() {}
+
+bool RenderCompositorANGLE::Resume() { return true; }
+
+void RenderCompositorANGLE::Update() {
+ // Update compositor window's size if it exists.
+ // It needs to be called here, since OS might update compositor
+ // window's size at unexpected timing.
+ mWidget->AsWindows()->UpdateCompositorWndSizeIfNecessary();
+}
+
+bool RenderCompositorANGLE::MakeCurrent() {
+ gl::GLContextEGL::Cast(gl())->SetEGLSurfaceOverride(mEGLSurface);
+ return gl()->MakeCurrent();
+}
+
+LayoutDeviceIntSize RenderCompositorANGLE::GetBufferSize() {
+ if (!UseCompositor()) {
+ MOZ_ASSERT(mBufferSize.isSome());
+ if (mBufferSize.isNothing()) {
+ return LayoutDeviceIntSize();
+ }
+ return mBufferSize.ref();
+ } else {
+ auto size = mWidget->GetClientSize();
+ // This size is used for WR DEBUG_OVERLAY. Its DCTile does not like 0.
+ size.width = std::max(size.width, 1);
+ size.height = std::max(size.height, 1);
+ return size;
+ }
+}
+
+RefPtr<ID3D11Query> RenderCompositorANGLE::GetD3D11Query() {
+ RefPtr<ID3D11Query> query;
+
+ if (mRecycledQuery) {
+ query = mRecycledQuery.forget();
+ return query;
+ }
+
+ CD3D11_QUERY_DESC desc(D3D11_QUERY_EVENT);
+ HRESULT hr = mDevice->CreateQuery(&desc, getter_AddRefs(query));
+ if (FAILED(hr) || !query) {
+ gfxWarning() << "Could not create D3D11_QUERY_EVENT: " << gfx::hexa(hr);
+ return nullptr;
+ }
+ return query;
+}
+
+void RenderCompositorANGLE::InsertGraphicsCommandsFinishedWaitQuery(
+ RenderedFrameId aFrameId) {
+ RefPtr<ID3D11Query> query;
+ query = GetD3D11Query();
+ if (!query) {
+ return;
+ }
+
+ mCtx->End(query);
+ mWaitForPresentQueries.emplace(aFrameId, query);
+}
+
+bool RenderCompositorANGLE::WaitForPreviousGraphicsCommandsFinishedQuery(
+ bool aWaitAll) {
+ size_t waitLatency = mUseTripleBuffering ? 3 : 2;
+ if (aWaitAll) {
+ waitLatency = 1;
+ }
+
+ while (mWaitForPresentQueries.size() >= waitLatency) {
+ auto queryPair = mWaitForPresentQueries.front();
+ BOOL result;
+ bool ret =
+ layers::WaitForFrameGPUQuery(mDevice, mCtx, queryPair.second, &result);
+
+ if (!ret) {
+ mWaitForPresentQueries.pop();
+ return false;
+ }
+
+ // Recycle query for later use.
+ mRecycledQuery = queryPair.second;
+ mLastCompletedFrameId = queryPair.first;
+ mWaitForPresentQueries.pop();
+ }
+ return true;
+}
+
+RenderedFrameId RenderCompositorANGLE::GetLastCompletedFrameId() {
+ while (!mWaitForPresentQueries.empty()) {
+ auto queryPair = mWaitForPresentQueries.front();
+ if (mCtx->GetData(queryPair.second, nullptr, 0, 0) != S_OK) {
+ break;
+ }
+
+ mRecycledQuery = queryPair.second;
+ mLastCompletedFrameId = queryPair.first;
+ mWaitForPresentQueries.pop();
+ }
+
+ return mLastCompletedFrameId;
+}
+
+RenderedFrameId RenderCompositorANGLE::UpdateFrameId() {
+ RenderedFrameId frameId = GetNextRenderFrameId();
+ InsertGraphicsCommandsFinishedWaitQuery(frameId);
+ return frameId;
+}
+
+GLenum RenderCompositorANGLE::IsContextLost(bool aForce) {
+ // glGetGraphicsResetStatus does not always work to detect timeout detection
+ // and recovery (TDR). On Windows, ANGLE itself is just relying upon the same
+ // API, so we should not need to check it separately.
+ auto reason = mDevice->GetDeviceRemovedReason();
+ switch (reason) {
+ case S_OK:
+ return LOCAL_GL_NO_ERROR;
+ case DXGI_ERROR_DEVICE_REMOVED:
+ case DXGI_ERROR_DRIVER_INTERNAL_ERROR:
+ NS_WARNING("Device reset due to system / different device");
+ return LOCAL_GL_INNOCENT_CONTEXT_RESET_ARB;
+ case DXGI_ERROR_DEVICE_HUNG:
+ case DXGI_ERROR_DEVICE_RESET:
+ case DXGI_ERROR_INVALID_CALL:
+ gfxCriticalError() << "Device reset due to WR device: "
+ << gfx::hexa(reason);
+ return LOCAL_GL_GUILTY_CONTEXT_RESET_ARB;
+ default:
+ gfxCriticalError() << "Device reset with WR device unexpected reason: "
+ << gfx::hexa(reason);
+ return LOCAL_GL_UNKNOWN_CONTEXT_RESET_ARB;
+ }
+}
+
+bool RenderCompositorANGLE::UseCompositor() {
+ if (!mUseNativeCompositor) {
+ return false;
+ }
+
+ if (!mDCLayerTree || !gfx::gfxVars::UseWebRenderCompositor()) {
+ return false;
+ }
+ return true;
+}
+
+bool RenderCompositorANGLE::SupportAsyncScreenshot() {
+ return !UseCompositor() && !mDisablingNativeCompositor;
+}
+
+bool RenderCompositorANGLE::ShouldUseNativeCompositor() {
+ return UseCompositor();
+}
+
+uint32_t RenderCompositorANGLE::GetMaxUpdateRects() {
+ if (UseCompositor() &&
+ StaticPrefs::gfx_webrender_compositor_max_update_rects_AtStartup() > 0) {
+ return 1;
+ }
+ return 0;
+}
+
+void RenderCompositorANGLE::CompositorBeginFrame() {
+ mDCLayerTree->CompositorBeginFrame();
+}
+
+void RenderCompositorANGLE::CompositorEndFrame() {
+ mDCLayerTree->CompositorEndFrame();
+}
+
+void RenderCompositorANGLE::Bind(wr::NativeTileId aId,
+ wr::DeviceIntPoint* aOffset, uint32_t* aFboId,
+ wr::DeviceIntRect aDirtyRect,
+ wr::DeviceIntRect aValidRect) {
+ mDCLayerTree->Bind(aId, aOffset, aFboId, aDirtyRect, aValidRect);
+}
+
+void RenderCompositorANGLE::Unbind() { mDCLayerTree->Unbind(); }
+
+void RenderCompositorANGLE::CreateSurface(wr::NativeSurfaceId aId,
+ wr::DeviceIntPoint aVirtualOffset,
+ wr::DeviceIntSize aTileSize,
+ bool aIsOpaque) {
+ mDCLayerTree->CreateSurface(aId, aVirtualOffset, aTileSize, aIsOpaque);
+}
+
+void RenderCompositorANGLE::CreateExternalSurface(wr::NativeSurfaceId aId,
+ bool aIsOpaque) {
+ mDCLayerTree->CreateExternalSurface(aId, aIsOpaque);
+}
+
+void RenderCompositorANGLE::DestroySurface(NativeSurfaceId aId) {
+ mDCLayerTree->DestroySurface(aId);
+}
+
+void RenderCompositorANGLE::CreateTile(wr::NativeSurfaceId aId, int aX,
+ int aY) {
+ mDCLayerTree->CreateTile(aId, aX, aY);
+}
+
+void RenderCompositorANGLE::DestroyTile(wr::NativeSurfaceId aId, int aX,
+ int aY) {
+ mDCLayerTree->DestroyTile(aId, aX, aY);
+}
+
+void RenderCompositorANGLE::AttachExternalImage(
+ wr::NativeSurfaceId aId, wr::ExternalImageId aExternalImage) {
+ mDCLayerTree->AttachExternalImage(aId, aExternalImage);
+}
+
+void RenderCompositorANGLE::AddSurface(
+ wr::NativeSurfaceId aId, const wr::CompositorSurfaceTransform& aTransform,
+ wr::DeviceIntRect aClipRect, wr::ImageRendering aImageRendering) {
+ mDCLayerTree->AddSurface(aId, aTransform, aClipRect, aImageRendering);
+}
+
+CompositorCapabilities RenderCompositorANGLE::GetCompositorCapabilities() {
+ CompositorCapabilities caps;
+
+ caps.virtual_surface_size = VIRTUAL_SURFACE_SIZE;
+
+ return caps;
+}
+
+void RenderCompositorANGLE::EnableNativeCompositor(bool aEnable) {
+ // XXX Re-enable native compositor is not handled yet.
+ MOZ_RELEASE_ASSERT(!mDisablingNativeCompositor);
+ MOZ_RELEASE_ASSERT(!aEnable);
+
+ if (!UseCompositor()) {
+ return;
+ }
+
+ mUseNativeCompositor = false;
+ mDCLayerTree->DisableNativeCompositor();
+
+ bool useAlpha = mWidget->AsWindows()->HasGlass();
+ DestroyEGLSurface();
+ mBufferSize.reset();
+
+ RefPtr<IDXGISwapChain1> swapChain1 =
+ CreateSwapChainForDComp(mUseTripleBuffering, useAlpha);
+ if (swapChain1) {
+ mSwapChain = swapChain1;
+ mUseAlpha = useAlpha;
+ mDCLayerTree->SetDefaultSwapChain(swapChain1);
+ // When alpha is used, we want to disable partial present.
+ // See Bug 1595027.
+ if (useAlpha) {
+ mFullRender = true;
+ }
+ ResizeBufferIfNeeded();
+ } else {
+ gfxCriticalNote << "Failed to re-create SwapChain";
+ RenderThread::Get()->HandleWebRenderError(WebRenderError::NEW_SURFACE);
+ return;
+ }
+ mDisablingNativeCompositor = true;
+}
+
+void RenderCompositorANGLE::InitializeUsePartialPresent() {
+ // Even when mSwapChain1 is null, we could enable WR partial present, since
+ // when mSwapChain1 is null, SwapChain is blit model swap chain with one
+ // buffer.
+ if (UseCompositor() || mWidget->AsWindows()->HasFxrOutputHandler() ||
+ gfx::gfxVars::WebRenderMaxPartialPresentRects() <= 0) {
+ mUsePartialPresent = false;
+ } else {
+ mUsePartialPresent = true;
+ }
+}
+
+bool RenderCompositorANGLE::UsePartialPresent() { return mUsePartialPresent; }
+
+bool RenderCompositorANGLE::RequestFullRender() { return mFullRender; }
+
+uint32_t RenderCompositorANGLE::GetMaxPartialPresentRects() {
+ if (!mUsePartialPresent) {
+ return 0;
+ }
+ return gfx::gfxVars::WebRenderMaxPartialPresentRects();
+}
+
+bool RenderCompositorANGLE::MaybeReadback(
+ const gfx::IntSize& aReadbackSize, const wr::ImageFormat& aReadbackFormat,
+ const Range<uint8_t>& aReadbackBuffer, bool* aNeedsYFlip) {
+ MOZ_ASSERT(aReadbackFormat == wr::ImageFormat::BGRA8);
+
+ if (!UseCompositor()) {
+ return false;
+ }
+
+ auto start = TimeStamp::Now();
+
+ HDC nulldc = ::GetDC(NULL);
+ HDC dc = ::CreateCompatibleDC(nulldc);
+ ::ReleaseDC(nullptr, nulldc);
+ if (!dc) {
+ gfxCriticalError() << "CreateCompatibleDC failed";
+ return false;
+ }
+
+ BITMAPV4HEADER header;
+ memset(&header, 0, sizeof(BITMAPV4HEADER));
+ header.bV4Size = sizeof(BITMAPV4HEADER);
+ header.bV4Width = aReadbackSize.width;
+ header.bV4Height = -LONG(aReadbackSize.height); // top-to-buttom DIB
+ header.bV4Planes = 1;
+ header.bV4BitCount = 32;
+ header.bV4V4Compression = BI_BITFIELDS;
+ header.bV4RedMask = 0x00FF0000;
+ header.bV4GreenMask = 0x0000FF00;
+ header.bV4BlueMask = 0x000000FF;
+ header.bV4AlphaMask = 0xFF000000;
+
+ void* readbackBits = nullptr;
+ HBITMAP bitmap =
+ ::CreateDIBSection(dc, reinterpret_cast<BITMAPINFO*>(&header),
+ DIB_RGB_COLORS, &readbackBits, nullptr, 0);
+ if (!bitmap) {
+ ::DeleteDC(dc);
+ gfxCriticalError() << "CreateDIBSection failed";
+ return false;
+ }
+
+ ::SelectObject(dc, bitmap);
+
+ UINT flags = PW_CLIENTONLY | PW_RENDERFULLCONTENT;
+ HWND hwnd = mWidget->AsWindows()->GetHwnd();
+
+ mDCLayerTree->WaitForCommitCompletion();
+
+ BOOL result = ::PrintWindow(hwnd, dc, flags);
+ if (!result) {
+ ::DeleteObject(bitmap);
+ ::DeleteDC(dc);
+ gfxCriticalError() << "PrintWindow failed";
+ return false;
+ }
+
+ ::GdiFlush();
+
+ memcpy(&aReadbackBuffer[0], readbackBits, aReadbackBuffer.length());
+
+ ::DeleteObject(bitmap);
+ ::DeleteDC(dc);
+
+ uint32_t latencyMs = round((TimeStamp::Now() - start).ToMilliseconds());
+ if (latencyMs > 500) {
+ gfxCriticalNote << "Readback took too long: " << latencyMs << " ms";
+ }
+
+ if (aNeedsYFlip) {
+ *aNeedsYFlip = false;
+ }
+
+ return true;
+}
+
+} // namespace wr
+} // namespace mozilla
diff --git a/gfx/webrender_bindings/RenderCompositorANGLE.h b/gfx/webrender_bindings/RenderCompositorANGLE.h
new file mode 100644
index 0000000000..b0924944bf
--- /dev/null
+++ b/gfx/webrender_bindings/RenderCompositorANGLE.h
@@ -0,0 +1,159 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef MOZILLA_GFX_RENDERCOMPOSITOR_ANGLE_H
+#define MOZILLA_GFX_RENDERCOMPOSITOR_ANGLE_H
+
+#include <queue>
+
+#include "GLTypes.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/webrender/RenderCompositor.h"
+#include "mozilla/webrender/RenderThread.h"
+
+struct ID3D11DeviceContext;
+struct ID3D11Device;
+struct ID3D11Query;
+struct IDXGIFactory2;
+struct IDXGISwapChain;
+struct IDXGISwapChain1;
+
+namespace mozilla {
+namespace gl {
+class GLLibraryEGL;
+} // namespace gl
+
+namespace wr {
+
+class DCLayerTree;
+
+class RenderCompositorANGLE : public RenderCompositor {
+ public:
+ static UniquePtr<RenderCompositor> Create(
+ RefPtr<widget::CompositorWidget>&& aWidget, nsACString& aError);
+
+ explicit RenderCompositorANGLE(RefPtr<widget::CompositorWidget>&& aWidget);
+ virtual ~RenderCompositorANGLE();
+ bool Initialize(nsACString& aError);
+
+ bool BeginFrame() override;
+ RenderedFrameId EndFrame(const nsTArray<DeviceIntRect>& aDirtyRects) final;
+ bool WaitForGPU() override;
+ RenderedFrameId GetLastCompletedFrameId() final;
+ RenderedFrameId UpdateFrameId() final;
+ void Pause() override;
+ bool Resume() override;
+ void Update() override;
+
+ gl::GLContext* gl() const override { return RenderThread::Get()->SharedGL(); }
+
+ bool MakeCurrent() override;
+
+ bool UseANGLE() const override { return true; }
+
+ bool UseDComp() const override { return !!mDCLayerTree; }
+
+ bool UseTripleBuffering() const override { return mUseTripleBuffering; }
+
+ layers::WebRenderCompositor CompositorType() const override {
+ if (UseDComp()) {
+ return layers::WebRenderCompositor::DIRECT_COMPOSITION;
+ }
+ return layers::WebRenderCompositor::DRAW;
+ }
+
+ LayoutDeviceIntSize GetBufferSize() override;
+
+ GLenum IsContextLost(bool aForce) override;
+
+ bool SurfaceOriginIsTopLeft() override { return true; }
+
+ bool SupportAsyncScreenshot() override;
+
+ bool ShouldUseNativeCompositor() override;
+ uint32_t GetMaxUpdateRects() override;
+
+ // Interface for wr::Compositor
+ void CompositorBeginFrame() override;
+ void CompositorEndFrame() override;
+ void Bind(wr::NativeTileId aId, wr::DeviceIntPoint* aOffset, uint32_t* aFboId,
+ wr::DeviceIntRect aDirtyRect,
+ wr::DeviceIntRect aValidRect) override;
+ void Unbind() override;
+ void CreateSurface(wr::NativeSurfaceId aId, wr::DeviceIntPoint aVirtualOffset,
+ wr::DeviceIntSize aTileSize, bool aIsOpaque) override;
+ void CreateExternalSurface(wr::NativeSurfaceId aId, bool aIsOpaque) override;
+ void DestroySurface(NativeSurfaceId aId) override;
+ void CreateTile(wr::NativeSurfaceId aId, int32_t aX, int32_t aY) override;
+ void DestroyTile(wr::NativeSurfaceId aId, int32_t aX, int32_t aY) override;
+ void AttachExternalImage(wr::NativeSurfaceId aId,
+ wr::ExternalImageId aExternalImage) override;
+ void AddSurface(wr::NativeSurfaceId aId,
+ const wr::CompositorSurfaceTransform& aTransform,
+ wr::DeviceIntRect aClipRect,
+ wr::ImageRendering aImageRendering) override;
+ void EnableNativeCompositor(bool aEnable) override;
+ CompositorCapabilities GetCompositorCapabilities() override;
+
+ // Interface for partial present
+ bool UsePartialPresent() override;
+ bool RequestFullRender() override;
+ uint32_t GetMaxPartialPresentRects() override;
+
+ bool MaybeReadback(const gfx::IntSize& aReadbackSize,
+ const wr::ImageFormat& aReadbackFormat,
+ const Range<uint8_t>& aReadbackBuffer,
+ bool* aNeedsYFlip) override;
+
+ protected:
+ bool UseCompositor();
+ void InitializeUsePartialPresent();
+ void InsertGraphicsCommandsFinishedWaitQuery(
+ RenderedFrameId aRenderedFrameId);
+ bool WaitForPreviousGraphicsCommandsFinishedQuery(bool aWaitAll = false);
+ bool ResizeBufferIfNeeded();
+ bool CreateEGLSurface();
+ void DestroyEGLSurface();
+ ID3D11Device* GetDeviceOfEGLDisplay(nsACString& aError);
+ bool CreateSwapChain(nsACString& aError);
+ void CreateSwapChainForDCompIfPossible(IDXGIFactory2* aDXGIFactory2);
+ RefPtr<IDXGISwapChain1> CreateSwapChainForDComp(bool aUseTripleBuffering,
+ bool aUseAlpha);
+ bool ShutdownEGLLibraryIfNecessary(nsACString& aError);
+ RefPtr<ID3D11Query> GetD3D11Query();
+ void ReleaseNativeCompositorResources();
+ HWND GetCompositorHwnd();
+
+ EGLConfig mEGLConfig;
+ EGLSurface mEGLSurface;
+
+ bool mUseTripleBuffering;
+ bool mUseAlpha;
+
+ RefPtr<ID3D11Device> mDevice;
+ RefPtr<ID3D11DeviceContext> mCtx;
+ RefPtr<IDXGISwapChain> mSwapChain;
+ RefPtr<IDXGISwapChain1> mSwapChain1;
+
+ UniquePtr<DCLayerTree> mDCLayerTree;
+
+ std::queue<std::pair<RenderedFrameId, RefPtr<ID3D11Query>>>
+ mWaitForPresentQueries;
+ RefPtr<ID3D11Query> mRecycledQuery;
+ RenderedFrameId mLastCompletedFrameId;
+
+ Maybe<LayoutDeviceIntSize> mBufferSize;
+ bool mUseNativeCompositor;
+ bool mUsePartialPresent;
+ bool mFullRender;
+ // Used to know a timing of disabling native compositor.
+ bool mDisablingNativeCompositor;
+};
+
+} // namespace wr
+} // namespace mozilla
+
+#endif
diff --git a/gfx/webrender_bindings/RenderCompositorD3D11SWGL.cpp b/gfx/webrender_bindings/RenderCompositorD3D11SWGL.cpp
new file mode 100644
index 0000000000..9193bc1b8f
--- /dev/null
+++ b/gfx/webrender_bindings/RenderCompositorD3D11SWGL.cpp
@@ -0,0 +1,599 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "RenderCompositorD3D11SWGL.h"
+
+#include "mozilla/widget/CompositorWidget.h"
+#include "mozilla/layers/Effects.h"
+#include "mozilla/layers/LayerManagerComposite.h"
+#include "mozilla/webrender/RenderD3D11TextureHost.h"
+#include "RenderCompositorRecordedFrame.h"
+
+namespace mozilla {
+using namespace layers;
+
+namespace wr {
+
+RenderCompositorD3D11SWGL::UploadMode
+RenderCompositorD3D11SWGL::GetUploadMode() {
+ int mode = StaticPrefs::gfx_webrender_software_d3d11_upload_mode();
+ switch (mode) {
+ case 1:
+ return Upload_Immediate;
+ case 2:
+ return Upload_Staging;
+ case 3:
+ return Upload_StagingNoBlock;
+ case 4:
+ return Upload_StagingPooled;
+ default:
+ return Upload_Staging;
+ }
+}
+
+UniquePtr<RenderCompositor> RenderCompositorD3D11SWGL::Create(
+ RefPtr<widget::CompositorWidget>&& aWidget, nsACString& aError) {
+ void* ctx = wr_swgl_create_context();
+ if (!ctx) {
+ gfxCriticalNote << "Failed SWGL context creation for WebRender";
+ return nullptr;
+ }
+
+ RefPtr<CompositorD3D11> compositor =
+ MakeAndAddRef<CompositorD3D11>(nullptr, aWidget);
+ nsCString log;
+ if (!compositor->Initialize(&log)) {
+ gfxCriticalNote << "Failed to initialize CompositorD3D11 for SWGL: "
+ << log.get();
+ return nullptr;
+ }
+ compositor->UseForSoftwareWebRender();
+
+ return MakeUnique<RenderCompositorD3D11SWGL>(compositor, std::move(aWidget),
+ ctx);
+}
+
+RenderCompositorD3D11SWGL::RenderCompositorD3D11SWGL(
+ CompositorD3D11* aCompositor, RefPtr<widget::CompositorWidget>&& aWidget,
+ void* aContext)
+ : RenderCompositor(std::move(aWidget)),
+ mCompositor(aCompositor),
+ mContext(aContext) {
+ MOZ_ASSERT(mContext);
+ mSyncObject = mCompositor->GetSyncObject();
+}
+
+RenderCompositorD3D11SWGL::~RenderCompositorD3D11SWGL() {
+ wr_swgl_destroy_context(mContext);
+}
+
+bool RenderCompositorD3D11SWGL::MakeCurrent() {
+ wr_swgl_make_current(mContext);
+ return true;
+}
+
+bool RenderCompositorD3D11SWGL::BeginFrame() {
+ MOZ_ASSERT(!mInFrame);
+ MakeCurrent();
+ IntRect rect = IntRect(IntPoint(0, 0), GetBufferSize().ToUnknownSize());
+ if (!mCompositor->BeginFrameForWindow(nsIntRegion(rect), Nothing(), rect,
+ nsIntRegion())) {
+ return false;
+ }
+ mInFrame = true;
+ mUploadMode = GetUploadMode();
+ return true;
+}
+
+void RenderCompositorD3D11SWGL::CancelFrame() {
+ MOZ_ASSERT(mInFrame);
+ mCompositor->CancelFrame();
+ mInFrame = false;
+}
+
+void RenderCompositorD3D11SWGL::CompositorEndFrame() {
+ nsTArray<FrameSurface> frameSurfaces = std::move(mFrameSurfaces);
+
+ if (!mInFrame) {
+ return;
+ }
+
+ for (auto& frameSurface : frameSurfaces) {
+ auto surfaceCursor = mSurfaces.find(frameSurface.mId);
+ MOZ_RELEASE_ASSERT(surfaceCursor != mSurfaces.end());
+ Surface& surface = surfaceCursor->second;
+
+ for (auto it = surface.mTiles.begin(); it != surface.mTiles.end(); ++it) {
+ gfx::Point tileOffset(it->first.mX * surface.mTileSize.width,
+ it->first.mY * surface.mTileSize.height);
+ gfx::Rect drawRect = it->second.mValidRect + tileOffset;
+
+ RefPtr<TexturedEffect> texturedEffect = CreateTexturedEffect(
+ surface.mIsOpaque ? SurfaceFormat::B8G8R8X8 : SurfaceFormat::B8G8R8A8,
+ it->second.mTexture, frameSurface.mFilter, true);
+
+ texturedEffect->mTextureCoords =
+ gfx::Rect(it->second.mValidRect.x / surface.mTileSize.width,
+ it->second.mValidRect.y / surface.mTileSize.height,
+ it->second.mValidRect.width / surface.mTileSize.width,
+ it->second.mValidRect.height / surface.mTileSize.height);
+
+ EffectChain effect;
+ effect.mPrimaryEffect = texturedEffect;
+ mCompositor->DrawQuad(drawRect, frameSurface.mClipRect, effect, 1.0,
+ frameSurface.mTransform, drawRect);
+ }
+
+ if (surface.mExternalImage) {
+ // We need to hold the texture source separately from the effect,
+ // since the effect doesn't hold a strong reference.
+ RefPtr<DataTextureSourceD3D11> layer;
+ RefPtr<TexturedEffect> texturedEffect;
+ gfx::IntSize size;
+ if (auto* host = surface.mExternalImage->AsRenderDXGITextureHost()) {
+ if (!host->EnsureD3D11Texture2D(mCompositor->GetDevice())) {
+ continue;
+ }
+
+ layer = new DataTextureSourceD3D11(mCompositor->GetDevice(),
+ host->GetFormat(),
+ host->GetD3D11Texture2D());
+ if (host->GetFormat() == SurfaceFormat::NV12 ||
+ host->GetFormat() == SurfaceFormat::P010 ||
+ host->GetFormat() == SurfaceFormat::P016) {
+ texturedEffect = new EffectNV12(
+ layer, host->GetYUVColorSpace(), host->GetColorRange(),
+ host->GetColorDepth(), frameSurface.mFilter);
+ } else {
+ MOZ_ASSERT(host->GetFormat() == SurfaceFormat::B8G8R8X8 ||
+ host->GetFormat() == SurfaceFormat::B8G8R8A8);
+ texturedEffect = CreateTexturedEffect(host->GetFormat(), layer,
+ frameSurface.mFilter, true);
+ }
+ size = host->GetSize(0);
+ host->LockInternal();
+ } else if (auto* host =
+ surface.mExternalImage->AsRenderDXGIYCbCrTextureHost()) {
+ if (!host->EnsureD3D11Texture2D(mCompositor->GetDevice())) {
+ continue;
+ }
+
+ layer = new DataTextureSourceD3D11(mCompositor->GetDevice(),
+ SurfaceFormat::A8,
+ host->GetD3D11Texture2D(0));
+ RefPtr<DataTextureSourceD3D11> u = new DataTextureSourceD3D11(
+ mCompositor->GetDevice(), SurfaceFormat::A8,
+ host->GetD3D11Texture2D(1));
+ layer->SetNextSibling(u);
+ RefPtr<DataTextureSourceD3D11> v = new DataTextureSourceD3D11(
+ mCompositor->GetDevice(), SurfaceFormat::A8,
+ host->GetD3D11Texture2D(2));
+ u->SetNextSibling(v);
+
+ texturedEffect = new EffectYCbCr(
+ layer, host->GetYUVColorSpace(), host->GetColorRange(),
+ host->GetColorDepth(), frameSurface.mFilter);
+ size = host->GetSize(0);
+ host->LockInternal();
+ }
+
+ gfx::Rect drawRect(0, 0, size.width, size.height);
+
+ EffectChain effect;
+ effect.mPrimaryEffect = texturedEffect;
+ mCompositor->DrawQuad(drawRect, frameSurface.mClipRect, effect, 1.0,
+ frameSurface.mTransform, drawRect);
+
+ if (auto* host = surface.mExternalImage->AsRenderDXGITextureHost()) {
+ host->Unlock();
+ } else if (auto* host =
+ surface.mExternalImage->AsRenderDXGIYCbCrTextureHost()) {
+ host->Unlock();
+ }
+ }
+ }
+}
+
+RenderedFrameId RenderCompositorD3D11SWGL::EndFrame(
+ const nsTArray<DeviceIntRect>& aDirtyRects) {
+ MOZ_ASSERT(mInFrame);
+ mInFrame = false;
+ mCompositor->EndFrame();
+ return GetNextRenderFrameId();
+}
+
+void RenderCompositorD3D11SWGL::Pause() {}
+
+bool RenderCompositorD3D11SWGL::Resume() { return true; }
+
+LayoutDeviceIntSize RenderCompositorD3D11SWGL::GetBufferSize() {
+ return mWidget->GetClientSize();
+}
+
+GLenum RenderCompositorD3D11SWGL::IsContextLost(bool aForce) {
+ // CompositorD3D11 uses ID3D11Device for composite. The device status needs to
+ // be checked.
+ auto reason = GetDevice()->GetDeviceRemovedReason();
+ switch (reason) {
+ case S_OK:
+ return LOCAL_GL_NO_ERROR;
+ case DXGI_ERROR_DEVICE_REMOVED:
+ case DXGI_ERROR_DRIVER_INTERNAL_ERROR:
+ NS_WARNING("Device reset due to system / different device");
+ return LOCAL_GL_INNOCENT_CONTEXT_RESET_ARB;
+ case DXGI_ERROR_DEVICE_HUNG:
+ case DXGI_ERROR_DEVICE_RESET:
+ case DXGI_ERROR_INVALID_CALL:
+ gfxCriticalError() << "Device reset due to WR device: "
+ << gfx::hexa(reason);
+ return LOCAL_GL_GUILTY_CONTEXT_RESET_ARB;
+ default:
+ gfxCriticalError() << "Device reset with WR device unexpected reason: "
+ << gfx::hexa(reason);
+ return LOCAL_GL_UNKNOWN_CONTEXT_RESET_ARB;
+ }
+}
+
+CompositorCapabilities RenderCompositorD3D11SWGL::GetCompositorCapabilities() {
+ CompositorCapabilities caps;
+
+ caps.virtual_surface_size = 0;
+ return caps;
+}
+
+void RenderCompositorD3D11SWGL::Bind(wr::NativeTileId aId,
+ wr::DeviceIntPoint* aOffset,
+ uint32_t* aFboId,
+ wr::DeviceIntRect aDirtyRect,
+ wr::DeviceIntRect aValidRect) {
+ MOZ_RELEASE_ASSERT(false);
+}
+
+void RenderCompositorD3D11SWGL::Unbind() { MOZ_RELEASE_ASSERT(false); }
+
+bool RenderCompositorD3D11SWGL::MapTile(wr::NativeTileId aId,
+ wr::DeviceIntRect aDirtyRect,
+ wr::DeviceIntRect aValidRect,
+ void** aData, int32_t* aStride) {
+ auto surfaceCursor = mSurfaces.find(aId.surface_id);
+ MOZ_RELEASE_ASSERT(surfaceCursor != mSurfaces.end());
+ Surface& surface = surfaceCursor->second;
+
+ auto layerCursor = surface.mTiles.find(TileKey(aId.x, aId.y));
+ MOZ_RELEASE_ASSERT(layerCursor != surface.mTiles.end());
+
+ // Check if this tile's upload method matches what we're using for this frame,
+ // and if not then reallocate to fix it. Do this before we copy the struct
+ // into mCurrentTile.
+ if (mUploadMode == Upload_Immediate) {
+ if (layerCursor->second.mStagingTexture) {
+ MOZ_ASSERT(!layerCursor->second.mSurface);
+ layerCursor->second.mStagingTexture = nullptr;
+ layerCursor->second.mSurface = CreateStagingSurface(surface.TileSize());
+ }
+ } else {
+ if (layerCursor->second.mSurface) {
+ MOZ_ASSERT(!layerCursor->second.mStagingTexture);
+ layerCursor->second.mSurface = nullptr;
+ layerCursor->second.mStagingTexture =
+ CreateStagingTexture(surface.TileSize());
+ }
+ }
+
+ mCurrentTile = layerCursor->second;
+ mCurrentTileId = aId;
+ mCurrentTileDirty = IntRect(aDirtyRect.origin.x, aDirtyRect.origin.y,
+ aDirtyRect.size.width, aDirtyRect.size.height);
+
+ if (mUploadMode == Upload_Immediate) {
+ DataSourceSurface::MappedSurface map;
+ if (!mCurrentTile.mSurface->Map(DataSourceSurface::READ_WRITE, &map)) {
+ return false;
+ }
+
+ *aData =
+ map.mData + aValidRect.origin.y * map.mStride + aValidRect.origin.x * 4;
+ *aStride = map.mStride;
+ layerCursor->second.mValidRect =
+ gfx::Rect(aValidRect.origin.x, aValidRect.origin.y,
+ aValidRect.size.width, aValidRect.size.height);
+ return true;
+ }
+
+ RefPtr<ID3D11DeviceContext> context;
+ mCompositor->GetDevice()->GetImmediateContext(getter_AddRefs(context));
+
+ D3D11_MAPPED_SUBRESOURCE mappedSubresource;
+
+ bool shouldBlock = mUploadMode == Upload_Staging;
+
+ HRESULT hr = context->Map(
+ mCurrentTile.mStagingTexture, 0, D3D11_MAP_READ_WRITE,
+ shouldBlock ? 0 : D3D11_MAP_FLAG_DO_NOT_WAIT, &mappedSubresource);
+ if (hr == DXGI_ERROR_WAS_STILL_DRAWING) {
+ // mCurrentTile is a copy of the real tile data, so we can just replace the
+ // staging one with a temporary for this draw. The staging texture for the
+ // real tile remains untouched, so we'll go back to using that for future
+ // frames and discard the new one. In the future we could improve this by
+ // having a pool of shared staging textures for all the tiles.
+
+ // Mark the tile as having a temporary staging texture.
+ mCurrentTile.mIsTemp = true;
+
+ // Try grabbing a texture from the staging pool and see if we can use that.
+ if (mUploadMode == Upload_StagingPooled && surface.mStagingPool.Length()) {
+ mCurrentTile.mStagingTexture = surface.mStagingPool.ElementAt(0);
+ surface.mStagingPool.RemoveElementAt(0);
+ hr = context->Map(mCurrentTile.mStagingTexture, 0, D3D11_MAP_READ_WRITE,
+ D3D11_MAP_FLAG_DO_NOT_WAIT, &mappedSubresource);
+
+ // If that failed, put it back into the pool (but at the end).
+ if (hr == DXGI_ERROR_WAS_STILL_DRAWING) {
+ surface.mStagingPool.AppendElement(mCurrentTile.mStagingTexture);
+ }
+ }
+
+ // No staging textures, or we tried one and it was busy. Allocate a brand
+ // new one instead.
+ if (hr == DXGI_ERROR_WAS_STILL_DRAWING) {
+ mCurrentTile.mStagingTexture = CreateStagingTexture(surface.TileSize());
+ hr = context->Map(mCurrentTile.mStagingTexture, 0, D3D11_MAP_READ_WRITE,
+ 0, &mappedSubresource);
+ }
+ }
+ MOZ_ASSERT(SUCCEEDED(hr));
+
+ // aData is expected to contain a pointer to the first pixel within the valid
+ // rect, so take the mapped resource's data (which covers the full tile size)
+ // and offset it by the top/left of the valid rect.
+ *aData = (uint8_t*)mappedSubresource.pData +
+ aValidRect.origin.y * mappedSubresource.RowPitch +
+ aValidRect.origin.x * 4;
+ *aStride = mappedSubresource.RowPitch;
+
+ // Store the new valid rect, so that we can composite only those pixels
+ layerCursor->second.mValidRect =
+ gfx::Rect(aValidRect.origin.x, aValidRect.origin.y, aValidRect.size.width,
+ aValidRect.size.height);
+ return true;
+}
+
+void RenderCompositorD3D11SWGL::UnmapTile() {
+ if (mCurrentTile.mSurface) {
+ MOZ_ASSERT(mUploadMode == Upload_Immediate);
+ mCurrentTile.mSurface->Unmap();
+ nsIntRegion dirty(mCurrentTileDirty);
+ // This uses UpdateSubresource, which blocks, so is likely implemented as a
+ // memcpy into driver owned memory, followed by an async upload. The staging
+ // method should avoid this extra copy, and is likely to be faster usually.
+ // We could possible do this call on a background thread so that sw-wr can
+ // start drawing the next tile while the memcpy is in progress.
+ mCurrentTile.mTexture->Update(mCurrentTile.mSurface, &dirty);
+ return;
+ }
+
+ RefPtr<ID3D11DeviceContext> context;
+ mCompositor->GetDevice()->GetImmediateContext(getter_AddRefs(context));
+
+ context->Unmap(mCurrentTile.mStagingTexture, 0);
+
+ D3D11_BOX box;
+ box.front = 0;
+ box.back = 1;
+ box.left = mCurrentTileDirty.X();
+ box.top = mCurrentTileDirty.Y();
+ box.right = mCurrentTileDirty.XMost();
+ box.bottom = mCurrentTileDirty.YMost();
+
+ context->CopySubresourceRegion(mCurrentTile.mTexture->GetD3D11Texture(), 0,
+ mCurrentTileDirty.x, mCurrentTileDirty.y, 0,
+ mCurrentTile.mStagingTexture, 0, &box);
+
+ // If we allocated a temp staging texture for this tile, and we're running
+ // in pooled mode, then consider adding it to the pool for later.
+ if (mCurrentTile.mIsTemp && mUploadMode == Upload_StagingPooled) {
+ auto surfaceCursor = mSurfaces.find(mCurrentTileId.surface_id);
+ MOZ_RELEASE_ASSERT(surfaceCursor != mSurfaces.end());
+ Surface& surface = surfaceCursor->second;
+
+ static const uint32_t kMaxPoolSize = 5;
+ if (surface.mStagingPool.Length() < kMaxPoolSize) {
+ surface.mStagingPool.AppendElement(mCurrentTile.mStagingTexture);
+ }
+ }
+ mCurrentTile.mStagingTexture = nullptr;
+}
+
+void RenderCompositorD3D11SWGL::CreateSurface(wr::NativeSurfaceId aId,
+ wr::DeviceIntPoint aVirtualOffset,
+ wr::DeviceIntSize aTileSize,
+ bool aIsOpaque) {
+ MOZ_RELEASE_ASSERT(mSurfaces.find(aId) == mSurfaces.end());
+ mSurfaces.insert({aId, Surface{aTileSize, aIsOpaque}});
+}
+
+void RenderCompositorD3D11SWGL::CreateExternalSurface(wr::NativeSurfaceId aId,
+ bool aIsOpaque) {
+ MOZ_RELEASE_ASSERT(mSurfaces.find(aId) == mSurfaces.end());
+ Surface surface{wr::DeviceIntSize{}, aIsOpaque};
+ surface.mIsExternal = true;
+ mSurfaces.insert({aId, std::move(surface)});
+}
+
+void RenderCompositorD3D11SWGL::DestroySurface(NativeSurfaceId aId) {
+ auto surfaceCursor = mSurfaces.find(aId);
+ MOZ_RELEASE_ASSERT(surfaceCursor != mSurfaces.end());
+ mSurfaces.erase(surfaceCursor);
+}
+
+already_AddRefed<ID3D11Texture2D>
+RenderCompositorD3D11SWGL::CreateStagingTexture(const gfx::IntSize aSize) {
+ CD3D11_TEXTURE2D_DESC desc(DXGI_FORMAT_B8G8R8A8_UNORM, aSize.width,
+ aSize.height, 1, 1);
+
+ desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE | D3D11_CPU_ACCESS_READ;
+ desc.Usage = D3D11_USAGE_STAGING;
+ desc.BindFlags = 0;
+
+ RefPtr<ID3D11Texture2D> cpuTexture;
+ DebugOnly<HRESULT> hr = mCompositor->GetDevice()->CreateTexture2D(
+ &desc, nullptr, getter_AddRefs(cpuTexture));
+ MOZ_ASSERT(SUCCEEDED(hr));
+ return cpuTexture.forget();
+}
+
+already_AddRefed<DataSourceSurface>
+RenderCompositorD3D11SWGL::CreateStagingSurface(const gfx::IntSize aSize) {
+ return Factory::CreateDataSourceSurface(aSize, SurfaceFormat::B8G8R8A8);
+}
+
+void RenderCompositorD3D11SWGL::CreateTile(wr::NativeSurfaceId aId, int32_t aX,
+ int32_t aY) {
+ auto surfaceCursor = mSurfaces.find(aId);
+ MOZ_RELEASE_ASSERT(surfaceCursor != mSurfaces.end());
+ Surface& surface = surfaceCursor->second;
+ MOZ_RELEASE_ASSERT(!surface.mIsExternal);
+
+ if (mUploadMode == Upload_Immediate) {
+ RefPtr<DataTextureSourceD3D11> source =
+ new DataTextureSourceD3D11(gfx::SurfaceFormat::B8G8R8A8, mCompositor,
+ layers::TextureFlags::NO_FLAGS);
+ RefPtr<DataSourceSurface> surf = CreateStagingSurface(surface.TileSize());
+ surface.mTiles.insert({TileKey(aX, aY), Tile{source, nullptr, surf}});
+ return;
+ }
+
+ MOZ_ASSERT(mUploadMode == Upload_Staging ||
+ mUploadMode == Upload_StagingNoBlock ||
+ mUploadMode == Upload_StagingPooled);
+
+ CD3D11_TEXTURE2D_DESC desc(DXGI_FORMAT_B8G8R8A8_UNORM,
+ surface.mTileSize.width, surface.mTileSize.height,
+ 1, 1);
+
+ RefPtr<ID3D11Texture2D> texture;
+ DebugOnly<HRESULT> hr = mCompositor->GetDevice()->CreateTexture2D(
+ &desc, nullptr, getter_AddRefs(texture));
+ MOZ_ASSERT(SUCCEEDED(hr));
+
+ RefPtr<DataTextureSourceD3D11> source = new DataTextureSourceD3D11(
+ mCompositor->GetDevice(), gfx::SurfaceFormat::B8G8R8A8, texture);
+
+ RefPtr<ID3D11Texture2D> cpuTexture = CreateStagingTexture(surface.TileSize());
+ surface.mTiles.insert({TileKey(aX, aY), Tile{source, cpuTexture, nullptr}});
+}
+
+void RenderCompositorD3D11SWGL::DestroyTile(wr::NativeSurfaceId aId, int32_t aX,
+ int32_t aY) {
+ auto surfaceCursor = mSurfaces.find(aId);
+ MOZ_RELEASE_ASSERT(surfaceCursor != mSurfaces.end());
+ Surface& surface = surfaceCursor->second;
+ MOZ_RELEASE_ASSERT(!surface.mIsExternal);
+
+ auto layerCursor = surface.mTiles.find(TileKey(aX, aY));
+ MOZ_RELEASE_ASSERT(layerCursor != surface.mTiles.end());
+ surface.mTiles.erase(layerCursor);
+}
+
+void RenderCompositorD3D11SWGL::AttachExternalImage(
+ wr::NativeSurfaceId aId, wr::ExternalImageId aExternalImage) {
+ RenderTextureHost* image =
+ RenderThread::Get()->GetRenderTexture(aExternalImage);
+ MOZ_RELEASE_ASSERT(image);
+ MOZ_RELEASE_ASSERT(image->AsRenderDXGITextureHost() ||
+ image->AsRenderDXGIYCbCrTextureHost());
+
+ auto surfaceCursor = mSurfaces.find(aId);
+ MOZ_RELEASE_ASSERT(surfaceCursor != mSurfaces.end());
+
+ Surface& surface = surfaceCursor->second;
+ surface.mExternalImage = image;
+ MOZ_RELEASE_ASSERT(surface.mTiles.empty());
+ MOZ_RELEASE_ASSERT(surface.mIsExternal);
+}
+
+gfx::SamplingFilter ToSamplingFilter(wr::ImageRendering aImageRendering) {
+ if (aImageRendering == wr::ImageRendering::Auto) {
+ return gfx::SamplingFilter::LINEAR;
+ }
+ return gfx::SamplingFilter::POINT;
+}
+
+void RenderCompositorD3D11SWGL::AddSurface(
+ wr::NativeSurfaceId aId, const wr::CompositorSurfaceTransform& aTransform,
+ wr::DeviceIntRect aClipRect, wr::ImageRendering aImageRendering) {
+ Matrix4x4 transform(
+ aTransform.m11, aTransform.m12, aTransform.m13, aTransform.m14,
+ aTransform.m21, aTransform.m22, aTransform.m23, aTransform.m24,
+ aTransform.m31, aTransform.m32, aTransform.m33, aTransform.m34,
+ aTransform.m41, aTransform.m42, aTransform.m43, aTransform.m44);
+
+ gfx::IntRect clipRect(aClipRect.origin.x, aClipRect.origin.y,
+ aClipRect.size.width, aClipRect.size.height);
+
+ mFrameSurfaces.AppendElement(FrameSurface{aId, transform, clipRect,
+ ToSamplingFilter(aImageRendering)});
+}
+
+bool RenderCompositorD3D11SWGL::MaybeReadback(
+ const gfx::IntSize& aReadbackSize, const wr::ImageFormat& aReadbackFormat,
+ const Range<uint8_t>& aReadbackBuffer, bool* aNeedsYFlip) {
+ MOZ_ASSERT(aReadbackFormat == wr::ImageFormat::BGRA8);
+
+ auto stride =
+ aReadbackSize.width * gfx::BytesPerPixel(SurfaceFormat::B8G8R8A8);
+ RefPtr<gfx::DrawTarget> dt = gfx::Factory::CreateDrawTargetForData(
+ gfx::BackendType::SKIA, &aReadbackBuffer[0], aReadbackSize, stride,
+ SurfaceFormat::B8G8R8A8, false);
+ if (!dt) {
+ return false;
+ }
+
+ mCompositor->Readback(dt);
+ return true;
+}
+
+void RenderCompositorD3D11SWGL::MaybeRequestAllowFrameRecording(
+ bool aWillRecord) {
+ mCompositor->RequestAllowFrameRecording(aWillRecord);
+}
+
+bool RenderCompositorD3D11SWGL::MaybeRecordFrame(
+ layers::CompositionRecorder& aRecorder) {
+ layers::WindowLMC window(mCompositor);
+ gfx::IntSize size = GetBufferSize().ToUnknownSize();
+ RefPtr<layers::profiler_screenshots::RenderSource> snapshot =
+ window.GetWindowContents(size);
+ if (!snapshot) {
+ return true;
+ }
+
+ RefPtr<layers::profiler_screenshots::AsyncReadbackBuffer> buffer =
+ window.CreateAsyncReadbackBuffer(size);
+ buffer->CopyFrom(snapshot);
+
+ RefPtr<layers::RecordedFrame> frame =
+ new RenderCompositorRecordedFrame(TimeStamp::Now(), std::move(buffer));
+ aRecorder.RecordFrame(frame);
+ return false;
+}
+
+bool RenderCompositorD3D11SWGL::MaybeGrabScreenshot(
+ const gfx::IntSize& aWindowSize) {
+ layers::WindowLMC window(mCompositor);
+ mProfilerScreenshotGrabber.MaybeGrabScreenshot(window, aWindowSize);
+ return true;
+}
+
+bool RenderCompositorD3D11SWGL::MaybeProcessScreenshotQueue() {
+ mProfilerScreenshotGrabber.MaybeProcessQueue();
+ return true;
+}
+
+} // namespace wr
+} // namespace mozilla
diff --git a/gfx/webrender_bindings/RenderCompositorD3D11SWGL.h b/gfx/webrender_bindings/RenderCompositorD3D11SWGL.h
new file mode 100644
index 0000000000..77fcc6461d
--- /dev/null
+++ b/gfx/webrender_bindings/RenderCompositorD3D11SWGL.h
@@ -0,0 +1,196 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef MOZILLA_GFX_RENDERCOMPOSITOR_D3D11_H
+#define MOZILLA_GFX_RENDERCOMPOSITOR_D3D11_H
+
+#include "mozilla/layers/ScreenshotGrabber.h"
+#include "mozilla/layers/TextureD3D11.h"
+#include "mozilla/layers/CompositorD3D11.h"
+#include "mozilla/webrender/RenderCompositor.h"
+
+namespace mozilla {
+
+namespace wr {
+
+class RenderCompositorD3D11SWGL : public RenderCompositor {
+ public:
+ static UniquePtr<RenderCompositor> Create(
+ RefPtr<widget::CompositorWidget>&& aWidget, nsACString& aError);
+
+ RenderCompositorD3D11SWGL(layers::CompositorD3D11* aCompositor,
+ RefPtr<widget::CompositorWidget>&& aWidget,
+ void* aContext);
+ virtual ~RenderCompositorD3D11SWGL();
+
+ void* swgl() const override { return mContext; }
+
+ bool MakeCurrent() override;
+
+ bool BeginFrame() override;
+ void CancelFrame() override;
+ RenderedFrameId EndFrame(const nsTArray<DeviceIntRect>& aDirtyRects) final;
+
+ void Pause() override;
+ bool Resume() override;
+
+ bool SurfaceOriginIsTopLeft() override { return true; }
+
+ LayoutDeviceIntSize GetBufferSize() override;
+
+ GLenum IsContextLost(bool aForce) override;
+
+ // Should we support this?
+ bool SupportsExternalBufferTextures() const override { return false; }
+
+ layers::WebRenderBackend BackendType() const override {
+ return layers::WebRenderBackend::SOFTWARE;
+ }
+ layers::WebRenderCompositor CompositorType() const override {
+ return layers::WebRenderCompositor::D3D11;
+ }
+ RenderCompositorD3D11SWGL* AsRenderCompositorD3D11SWGL() override {
+ return this;
+ }
+
+ // Interface for wr::Compositor
+ CompositorCapabilities GetCompositorCapabilities() override;
+
+ bool ShouldUseNativeCompositor() override { return true; }
+
+ void CompositorBeginFrame() override {}
+ void CompositorEndFrame() override;
+ void Bind(wr::NativeTileId aId, wr::DeviceIntPoint* aOffset, uint32_t* aFboId,
+ wr::DeviceIntRect aDirtyRect,
+ wr::DeviceIntRect aValidRect) override;
+ void Unbind() override;
+ bool MapTile(wr::NativeTileId aId, wr::DeviceIntRect aDirtyRect,
+ wr::DeviceIntRect aValidRect, void** aData,
+ int32_t* aStride) override;
+ void UnmapTile() override;
+ void CreateSurface(wr::NativeSurfaceId aId, wr::DeviceIntPoint aVirtualOffset,
+ wr::DeviceIntSize aTileSize, bool aIsOpaque) override;
+ void CreateExternalSurface(wr::NativeSurfaceId aId, bool aIsOpaque) override;
+ void DestroySurface(NativeSurfaceId aId) override;
+ void CreateTile(wr::NativeSurfaceId, int32_t aX, int32_t aY) override;
+ void DestroyTile(wr::NativeSurfaceId, int32_t aX, int32_t aY) override;
+ void AttachExternalImage(wr::NativeSurfaceId aId,
+ wr::ExternalImageId aExternalImage) override;
+ void AddSurface(wr::NativeSurfaceId aId,
+ const wr::CompositorSurfaceTransform& aTransform,
+ wr::DeviceIntRect aClipRect,
+ wr::ImageRendering aImageRendering) override;
+ void EnableNativeCompositor(bool aEnable) override {}
+ void DeInit() override {}
+
+ bool MaybeReadback(const gfx::IntSize& aReadbackSize,
+ const wr::ImageFormat& aReadbackFormat,
+ const Range<uint8_t>& aReadbackBuffer,
+ bool* aNeedsYFlip) override;
+ void MaybeRequestAllowFrameRecording(bool aWillRecord) override;
+ bool MaybeRecordFrame(layers::CompositionRecorder& aRecorder) override;
+ bool MaybeGrabScreenshot(const gfx::IntSize& aWindowSize) override;
+ bool MaybeProcessScreenshotQueue() override;
+
+ // TODO: Screenshots etc
+
+ struct TileKey {
+ TileKey(int32_t aX, int32_t aY) : mX(aX), mY(aY) {}
+
+ int32_t mX;
+ int32_t mY;
+ };
+
+ ID3D11Device* GetDevice() { return mCompositor->GetDevice(); }
+
+ private:
+ already_AddRefed<ID3D11Texture2D> CreateStagingTexture(
+ const gfx::IntSize aSize);
+ already_AddRefed<DataSourceSurface> CreateStagingSurface(
+ const gfx::IntSize aSize);
+
+ RefPtr<layers::CompositorD3D11> mCompositor;
+ void* mContext = nullptr;
+
+ struct Tile {
+ // Each tile retains a texture, and a DataSourceSurface of the
+ // same size. We draw into the source surface, and then copy the
+ // changed area into the texture.
+ RefPtr<layers::DataTextureSourceD3D11> mTexture;
+ RefPtr<ID3D11Texture2D> mStagingTexture;
+ RefPtr<DataSourceSurface> mSurface;
+ gfx::Rect mValidRect;
+ bool mIsTemp = false;
+
+ struct KeyHashFn {
+ std::size_t operator()(const TileKey& aId) const {
+ return HashGeneric(aId.mX, aId.mY);
+ }
+ };
+ };
+
+ struct Surface {
+ explicit Surface(wr::DeviceIntSize aTileSize, bool aIsOpaque)
+ : mTileSize(aTileSize), mIsOpaque(aIsOpaque) {}
+ gfx::IntSize TileSize() {
+ return gfx::IntSize(mTileSize.width, mTileSize.height);
+ }
+
+ // External images can change size depending on which image
+ // is attached, so mTileSize will be 0,0 when mIsExternal
+ // is true.
+ wr::DeviceIntSize mTileSize;
+ bool mIsOpaque;
+ bool mIsExternal = false;
+ std::unordered_map<TileKey, Tile, Tile::KeyHashFn> mTiles;
+ RefPtr<RenderTextureHost> mExternalImage;
+
+ nsTArray<RefPtr<ID3D11Texture2D>> mStagingPool;
+
+ struct IdHashFn {
+ std::size_t operator()(const wr::NativeSurfaceId& aId) const {
+ return HashGeneric(wr::AsUint64(aId));
+ }
+ };
+ };
+ std::unordered_map<wr::NativeSurfaceId, Surface, Surface::IdHashFn> mSurfaces;
+
+ enum UploadMode {
+ Upload_Immediate,
+ Upload_Staging,
+ Upload_StagingNoBlock,
+ Upload_StagingPooled
+ };
+ UploadMode GetUploadMode();
+ UploadMode mUploadMode = Upload_Staging;
+
+ // Temporary state held between MapTile and UnmapTile
+ Tile mCurrentTile;
+ gfx::IntRect mCurrentTileDirty;
+ wr::NativeTileId mCurrentTileId;
+
+ // The set of surfaces added to be composited for the current frame
+ struct FrameSurface {
+ wr::NativeSurfaceId mId;
+ gfx::Matrix4x4 mTransform;
+ gfx::IntRect mClipRect;
+ gfx::SamplingFilter mFilter;
+ };
+ nsTArray<FrameSurface> mFrameSurfaces;
+ bool mInFrame = false;
+
+ layers::ScreenshotGrabber mProfilerScreenshotGrabber;
+};
+
+static inline bool operator==(const RenderCompositorD3D11SWGL::TileKey& a0,
+ const RenderCompositorD3D11SWGL::TileKey& a1) {
+ return a0.mX == a1.mX && a0.mY == a1.mY;
+}
+
+} // namespace wr
+} // namespace mozilla
+
+#endif
diff --git a/gfx/webrender_bindings/RenderCompositorEGL.cpp b/gfx/webrender_bindings/RenderCompositorEGL.cpp
new file mode 100644
index 0000000000..ddbb968483
--- /dev/null
+++ b/gfx/webrender_bindings/RenderCompositorEGL.cpp
@@ -0,0 +1,314 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "RenderCompositorEGL.h"
+
+#include "GLContext.h"
+#include "GLContextEGL.h"
+#include "GLContextProvider.h"
+#include "GLLibraryEGL.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/layers/BuildConstants.h"
+#include "mozilla/webrender/RenderThread.h"
+#include "mozilla/widget/CompositorWidget.h"
+
+#ifdef MOZ_WAYLAND
+# include "mozilla/widget/GtkCompositorWidget.h"
+# include <gdk/gdk.h>
+# include <gdk/gdkx.h>
+#endif
+
+#ifdef MOZ_WIDGET_ANDROID
+# include "mozilla/java/GeckoSurfaceTextureWrappers.h"
+# include "mozilla/layers/AndroidHardwareBuffer.h"
+# include "mozilla/widget/AndroidCompositorWidget.h"
+# include <android/native_window.h>
+# include <android/native_window_jni.h>
+#endif
+
+namespace mozilla::wr {
+
+/* static */
+UniquePtr<RenderCompositor> RenderCompositorEGL::Create(
+ RefPtr<widget::CompositorWidget> aWidget, nsACString& aError) {
+#ifdef MOZ_WAYLAND
+ if (!gdk_display_get_default() ||
+ GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
+ return nullptr;
+ }
+#endif
+ if (!RenderThread::Get()->SharedGL()) {
+ gfxCriticalNote << "Failed to get shared GL context";
+ return nullptr;
+ }
+ return MakeUnique<RenderCompositorEGL>(aWidget);
+}
+
+EGLSurface RenderCompositorEGL::CreateEGLSurface() {
+ EGLSurface surface = EGL_NO_SURFACE;
+ surface = gl::GLContextEGL::CreateEGLSurfaceForCompositorWidget(
+ mWidget, gl::GLContextEGL::Cast(gl())->mConfig);
+ if (surface == EGL_NO_SURFACE) {
+ gfxCriticalNote << "Failed to create EGLSurface";
+ }
+ return surface;
+}
+
+RenderCompositorEGL::RenderCompositorEGL(
+ RefPtr<widget::CompositorWidget> aWidget)
+ : RenderCompositor(std::move(aWidget)), mEGLSurface(EGL_NO_SURFACE) {}
+
+RenderCompositorEGL::~RenderCompositorEGL() {
+#ifdef MOZ_WIDGET_ANDROID
+ java::GeckoSurfaceTexture::DestroyUnused((int64_t)gl());
+#endif
+ DestroyEGLSurface();
+}
+
+bool RenderCompositorEGL::BeginFrame() {
+#ifdef MOZ_WAYLAND
+ if (mEGLSurface == EGL_NO_SURFACE) {
+ gfxCriticalNote
+ << "We don't have EGLSurface to draw into. Called too early?";
+ return false;
+ }
+ if (mWidget->AsX11()) {
+ mWidget->AsX11()->SetEGLNativeWindowSize(GetBufferSize());
+ }
+#endif
+ if (!MakeCurrent()) {
+ gfxCriticalNote << "Failed to make render context current, can't draw.";
+ return false;
+ }
+
+#ifdef MOZ_WIDGET_ANDROID
+ java::GeckoSurfaceTexture::DestroyUnused((int64_t)gl());
+ gl()->MakeCurrent(); // DestroyUnused can change the current context!
+#endif
+
+ return true;
+}
+
+RenderedFrameId RenderCompositorEGL::EndFrame(
+ const nsTArray<DeviceIntRect>& aDirtyRects) {
+#ifdef MOZ_WIDGET_ANDROID
+ const auto& gle = gl::GLContextEGL::Cast(gl());
+ const auto& egl = gle->mEgl;
+
+ EGLSync sync = nullptr;
+ if (layers::AndroidHardwareBufferApi::Get()) {
+ sync = egl->fCreateSync(LOCAL_EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr);
+ }
+ if (sync) {
+ int fenceFd = egl->fDupNativeFenceFDANDROID(sync);
+ if (fenceFd >= 0) {
+ mReleaseFenceFd = ipc::FileDescriptor(UniqueFileHandle(fenceFd));
+ }
+ egl->fDestroySync(sync);
+ sync = nullptr;
+ }
+#endif
+
+ RenderedFrameId frameId = GetNextRenderFrameId();
+ if (mEGLSurface != EGL_NO_SURFACE && aDirtyRects.Length() > 0) {
+ gfx::IntRegion bufferInvalid;
+ const auto bufferSize = GetBufferSize();
+ for (const DeviceIntRect& rect : aDirtyRects) {
+ const auto left = std::max(0, std::min(bufferSize.width, rect.origin.x));
+ const auto top = std::max(0, std::min(bufferSize.height, rect.origin.y));
+
+ const auto right = std::min(bufferSize.width,
+ std::max(0, rect.origin.x + rect.size.width));
+ const auto bottom = std::min(
+ bufferSize.height, std::max(0, rect.origin.y + rect.size.height));
+
+ const auto width = right - left;
+ const auto height = bottom - top;
+
+ bufferInvalid.OrWith(
+ gfx::IntRect(left, (GetBufferSize().height - bottom), width, height));
+ }
+ gl()->SetDamage(bufferInvalid);
+ }
+ gl()->SwapBuffers();
+ return frameId;
+}
+
+void RenderCompositorEGL::Pause() { DestroyEGLSurface(); }
+
+bool RenderCompositorEGL::Resume() {
+ if (kIsAndroid) {
+ // Destroy EGLSurface if it exists.
+ DestroyEGLSurface();
+
+#ifdef MOZ_WIDGET_ANDROID
+ // Query the new surface size as this may have changed. We cannot use
+ // mWidget->GetClientSize() due to a race condition between
+ // nsWindow::Resize() being called and the frame being rendered after the
+ // surface is resized.
+ EGLNativeWindowType window = mWidget->AsAndroid()->GetEGLNativeWindow();
+ JNIEnv* const env = jni::GetEnvForThread();
+ ANativeWindow* const nativeWindow =
+ ANativeWindow_fromSurface(env, reinterpret_cast<jobject>(window));
+ const int32_t width = ANativeWindow_getWidth(nativeWindow);
+ const int32_t height = ANativeWindow_getHeight(nativeWindow);
+
+ GLint maxTextureSize = 0;
+ gl()->fGetIntegerv(LOCAL_GL_MAX_TEXTURE_SIZE, (GLint*)&maxTextureSize);
+
+ // When window size is too big, hardware buffer allocation could fail.
+ if (maxTextureSize < width || maxTextureSize < height) {
+ gfxCriticalNote << "Too big ANativeWindow size(" << width << ", "
+ << height << ") MaxTextureSize " << maxTextureSize;
+ return false;
+ }
+
+ mEGLSurface = CreateEGLSurface();
+ if (mEGLSurface == EGL_NO_SURFACE) {
+ RenderThread::Get()->HandleWebRenderError(WebRenderError::NEW_SURFACE);
+ return false;
+ }
+ gl::GLContextEGL::Cast(gl())->SetEGLSurfaceOverride(mEGLSurface);
+
+ mEGLSurfaceSize = LayoutDeviceIntSize(width, height);
+ ANativeWindow_release(nativeWindow);
+#endif // MOZ_WIDGET_ANDROID
+ } else if (kIsWayland) {
+ // Destroy EGLSurface if it exists and create a new one. We will set the
+ // swap interval after MakeCurrent() has been called.
+ DestroyEGLSurface();
+ mEGLSurface = CreateEGLSurface();
+ if (mEGLSurface != EGL_NO_SURFACE) {
+ // We have a new EGL surface, which on wayland needs to be configured for
+ // non-blocking buffer swaps. We need MakeCurrent() to set our current EGL
+ // context before we call eglSwapInterval, which is why we do it here
+ // rather than where the surface was created.
+ const auto& gle = gl::GLContextEGL::Cast(gl());
+ const auto& egl = gle->mEgl;
+ MakeCurrent();
+ // Make eglSwapBuffers() non-blocking on wayland.
+ egl->fSwapInterval(0);
+ } else {
+ RenderThread::Get()->HandleWebRenderError(WebRenderError::NEW_SURFACE);
+ return false;
+ }
+ }
+ return true;
+}
+
+bool RenderCompositorEGL::IsPaused() { return mEGLSurface == EGL_NO_SURFACE; }
+
+gl::GLContext* RenderCompositorEGL::gl() const {
+ return RenderThread::Get()->SharedGL();
+}
+
+bool RenderCompositorEGL::MakeCurrent() {
+ gl::GLContextEGL::Cast(gl())->SetEGLSurfaceOverride(mEGLSurface);
+ return gl()->MakeCurrent();
+}
+
+void RenderCompositorEGL::DestroyEGLSurface() {
+ const auto& gle = gl::GLContextEGL::Cast(gl());
+ const auto& egl = gle->mEgl;
+
+ // Release EGLSurface of back buffer before calling ResizeBuffers().
+ if (mEGLSurface) {
+ gle->SetEGLSurfaceOverride(EGL_NO_SURFACE);
+ egl->fDestroySurface(mEGLSurface);
+ mEGLSurface = nullptr;
+ }
+}
+
+ipc::FileDescriptor RenderCompositorEGL::GetAndResetReleaseFence() {
+#ifdef MOZ_WIDGET_ANDROID
+ MOZ_ASSERT(!layers::AndroidHardwareBufferApi::Get() ||
+ mReleaseFenceFd.IsValid());
+ return std::move(mReleaseFenceFd);
+#else
+ return ipc::FileDescriptor();
+#endif
+}
+
+LayoutDeviceIntSize RenderCompositorEGL::GetBufferSize() {
+#ifdef MOZ_WIDGET_ANDROID
+ return mEGLSurfaceSize;
+#else
+ return mWidget->GetClientSize();
+#endif
+}
+
+CompositorCapabilities RenderCompositorEGL::GetCompositorCapabilities() {
+ CompositorCapabilities caps;
+
+ caps.virtual_surface_size = 0;
+
+ return caps;
+}
+
+bool RenderCompositorEGL::UsePartialPresent() {
+ return gfx::gfxVars::WebRenderMaxPartialPresentRects() > 0;
+}
+
+bool RenderCompositorEGL::RequestFullRender() { return false; }
+
+uint32_t RenderCompositorEGL::GetMaxPartialPresentRects() {
+ return gfx::gfxVars::WebRenderMaxPartialPresentRects();
+}
+
+bool RenderCompositorEGL::ShouldDrawPreviousPartialPresentRegions() {
+ return true;
+}
+
+size_t RenderCompositorEGL::GetBufferAge() const {
+ if (!StaticPrefs::
+ gfx_webrender_allow_partial_present_buffer_age_AtStartup()) {
+ return 0;
+ }
+ return gl()->GetBufferAge();
+}
+
+void RenderCompositorEGL::SetBufferDamageRegion(const wr::DeviceIntRect* aRects,
+ size_t aNumRects) {
+ const auto& gle = gl::GLContextEGL::Cast(gl());
+ const auto& egl = gle->mEgl;
+ if (gle->HasKhrPartialUpdate() &&
+ StaticPrefs::gfx_webrender_allow_partial_present_buffer_age_AtStartup()) {
+ std::vector<EGLint> rects;
+ rects.reserve(4 * aNumRects);
+ const auto bufferSize = GetBufferSize();
+ for (size_t i = 0; i < aNumRects; i++) {
+ const auto left =
+ std::max(0, std::min(bufferSize.width, aRects[i].origin.x));
+ const auto top =
+ std::max(0, std::min(bufferSize.height, aRects[i].origin.y));
+
+ const auto right =
+ std::min(bufferSize.width,
+ std::max(0, aRects[i].origin.x + aRects[i].size.width));
+ const auto bottom =
+ std::min(bufferSize.height,
+ std::max(0, aRects[i].origin.y + aRects[i].size.height));
+
+ const auto width = right - left;
+ const auto height = bottom - top;
+
+ rects.push_back(left);
+ rects.push_back(bufferSize.height - bottom);
+ rects.push_back(width);
+ rects.push_back(height);
+ }
+ const auto ret =
+ egl->fSetDamageRegion(mEGLSurface, rects.data(), rects.size() / 4);
+ if (ret == LOCAL_EGL_FALSE) {
+ const auto err = egl->mLib->fGetError();
+ gfxCriticalError() << "Error in eglSetDamageRegion: " << gfx::hexa(err);
+ }
+ }
+}
+
+} // namespace mozilla::wr
diff --git a/gfx/webrender_bindings/RenderCompositorEGL.h b/gfx/webrender_bindings/RenderCompositorEGL.h
new file mode 100644
index 0000000000..4b15ae9d9f
--- /dev/null
+++ b/gfx/webrender_bindings/RenderCompositorEGL.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef MOZILLA_GFX_RENDERCOMPOSITOR_EGL_H
+#define MOZILLA_GFX_RENDERCOMPOSITOR_EGL_H
+
+#include "GLTypes.h"
+#include "mozilla/webrender/RenderCompositor.h"
+
+namespace mozilla {
+
+namespace wr {
+
+class RenderCompositorEGL : public RenderCompositor {
+ public:
+ static UniquePtr<RenderCompositor> Create(
+ RefPtr<widget::CompositorWidget> aWidget, nsACString& aError);
+
+ explicit RenderCompositorEGL(RefPtr<widget::CompositorWidget> aWidget);
+ virtual ~RenderCompositorEGL();
+
+ bool BeginFrame() override;
+ RenderedFrameId EndFrame(const nsTArray<DeviceIntRect>& aDirtyRects) final;
+ void Pause() override;
+ bool Resume() override;
+ bool IsPaused() override;
+
+ gl::GLContext* gl() const override;
+
+ bool MakeCurrent() override;
+
+ bool UseANGLE() const override { return false; }
+
+ LayoutDeviceIntSize GetBufferSize() override;
+
+ CompositorCapabilities GetCompositorCapabilities() override;
+
+ // Interface for partial present
+ bool UsePartialPresent() override;
+ bool RequestFullRender() override;
+ uint32_t GetMaxPartialPresentRects() override;
+ bool ShouldDrawPreviousPartialPresentRegions() override;
+ size_t GetBufferAge() const override;
+ void SetBufferDamageRegion(const wr::DeviceIntRect* aRects,
+ size_t aNumRects) override;
+
+ ipc::FileDescriptor GetAndResetReleaseFence() override;
+
+ protected:
+ EGLSurface CreateEGLSurface();
+
+ void DestroyEGLSurface();
+
+ EGLSurface mEGLSurface;
+#ifdef MOZ_WIDGET_ANDROID
+ // On android we must track our own surface size.
+ LayoutDeviceIntSize mEGLSurfaceSize;
+#endif
+
+ // FileDescriptor of release fence.
+ // Release fence is a fence that is used for waiting until usage/composite of
+ // AHardwareBuffer is ended. The fence is delivered to client side via
+ // ImageBridge. It is used only on android.
+ ipc::FileDescriptor mReleaseFenceFd;
+};
+
+} // namespace wr
+} // namespace mozilla
+
+#endif // MOZILLA_GFX_RENDERCOMPOSITOR_EGL_H
diff --git a/gfx/webrender_bindings/RenderCompositorNative.cpp b/gfx/webrender_bindings/RenderCompositorNative.cpp
new file mode 100644
index 0000000000..6e7a6c8cb0
--- /dev/null
+++ b/gfx/webrender_bindings/RenderCompositorNative.cpp
@@ -0,0 +1,653 @@
+#include "RenderCompositorNative.h"
+#include "RenderCompositorNative.h"
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "RenderCompositorNative.h"
+
+#include "GLContext.h"
+#include "GLContextProvider.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/layers/CompositionRecorder.h"
+#include "mozilla/layers/NativeLayer.h"
+#include "mozilla/layers/SurfacePool.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/webrender/RenderThread.h"
+#include "mozilla/widget/CompositorWidget.h"
+#include "RenderCompositorRecordedFrame.h"
+
+namespace mozilla {
+namespace wr {
+
+RenderCompositorNative::RenderCompositorNative(
+ RefPtr<widget::CompositorWidget>&& aWidget, gl::GLContext* aGL)
+ : RenderCompositor(std::move(aWidget)),
+ mNativeLayerRoot(GetWidget()->GetNativeLayerRoot()) {
+#ifdef XP_MACOSX
+ auto pool = RenderThread::Get()->SharedSurfacePool();
+ if (pool) {
+ mSurfacePoolHandle = pool->GetHandleForGL(aGL);
+ }
+#endif
+ MOZ_RELEASE_ASSERT(mSurfacePoolHandle);
+}
+
+RenderCompositorNative::~RenderCompositorNative() {
+ mProfilerScreenshotGrabber.Destroy();
+ mNativeLayerRoot->SetLayers({});
+ mNativeLayerForEntireWindow = nullptr;
+ mNativeLayerRootSnapshotter = nullptr;
+ mNativeLayerRoot = nullptr;
+}
+
+bool RenderCompositorNative::BeginFrame() {
+ if (!MakeCurrent()) {
+ gfxCriticalNote << "Failed to make render context current, can't draw.";
+ return false;
+ }
+
+ gfx::IntSize bufferSize = GetBufferSize().ToUnknownSize();
+ if (!ShouldUseNativeCompositor()) {
+ if (mNativeLayerForEntireWindow &&
+ mNativeLayerForEntireWindow->GetSize() != bufferSize) {
+ mNativeLayerRoot->RemoveLayer(mNativeLayerForEntireWindow);
+ mNativeLayerForEntireWindow = nullptr;
+ }
+ if (!mNativeLayerForEntireWindow) {
+ mNativeLayerForEntireWindow =
+ mNativeLayerRoot->CreateLayer(bufferSize, false, mSurfacePoolHandle);
+ mNativeLayerForEntireWindow->SetSurfaceIsFlipped(true);
+ mNativeLayerRoot->AppendLayer(mNativeLayerForEntireWindow);
+ }
+ }
+
+ gfx::IntRect bounds({}, bufferSize);
+ if (!InitDefaultFramebuffer(bounds)) {
+ return false;
+ }
+
+ return true;
+}
+
+RenderedFrameId RenderCompositorNative::EndFrame(
+ const nsTArray<DeviceIntRect>& aDirtyRects) {
+ RenderedFrameId frameId = GetNextRenderFrameId();
+
+ DoSwap();
+
+ if (mNativeLayerForEntireWindow) {
+ mNativeLayerForEntireWindow->NotifySurfaceReady();
+ mNativeLayerRoot->CommitToScreen();
+ }
+
+ return frameId;
+}
+
+void RenderCompositorNative::Pause() {}
+
+bool RenderCompositorNative::Resume() { return true; }
+
+inline layers::WebRenderCompositor RenderCompositorNative::CompositorType()
+ const {
+ if (gfx::gfxVars::UseWebRenderCompositor()) {
+ return layers::WebRenderCompositor::CORE_ANIMATION;
+ }
+ return layers::WebRenderCompositor::DRAW;
+}
+
+LayoutDeviceIntSize RenderCompositorNative::GetBufferSize() {
+ return mWidget->GetClientSize();
+}
+
+bool RenderCompositorNative::ShouldUseNativeCompositor() {
+ return gfx::gfxVars::UseWebRenderCompositor();
+}
+
+bool RenderCompositorNative::MaybeReadback(
+ const gfx::IntSize& aReadbackSize, const wr::ImageFormat& aReadbackFormat,
+ const Range<uint8_t>& aReadbackBuffer, bool* aNeedsYFlip) {
+ if (!ShouldUseNativeCompositor()) {
+ return false;
+ }
+
+ MOZ_RELEASE_ASSERT(aReadbackFormat == wr::ImageFormat::BGRA8);
+ if (!mNativeLayerRootSnapshotter) {
+ mNativeLayerRootSnapshotter = mNativeLayerRoot->CreateSnapshotter();
+
+ if (!mNativeLayerRootSnapshotter) {
+ return false;
+ }
+ }
+ bool success = mNativeLayerRootSnapshotter->ReadbackPixels(
+ aReadbackSize, gfx::SurfaceFormat::B8G8R8A8, aReadbackBuffer);
+
+ // ReadbackPixels might have changed the current context. Make sure GL is
+ // current again.
+ MakeCurrent();
+
+ if (aNeedsYFlip) {
+ *aNeedsYFlip = true;
+ }
+
+ return success;
+}
+
+bool RenderCompositorNative::MaybeRecordFrame(
+ layers::CompositionRecorder& aRecorder) {
+ if (!ShouldUseNativeCompositor()) {
+ return false;
+ }
+
+ if (!mNativeLayerRootSnapshotter) {
+ mNativeLayerRootSnapshotter = mNativeLayerRoot->CreateSnapshotter();
+ }
+
+ if (!mNativeLayerRootSnapshotter) {
+ return true;
+ }
+
+ gfx::IntSize size = GetBufferSize().ToUnknownSize();
+ RefPtr<layers::profiler_screenshots::RenderSource> snapshot =
+ mNativeLayerRootSnapshotter->GetWindowContents(size);
+ if (!snapshot) {
+ return true;
+ }
+
+ RefPtr<layers::profiler_screenshots::AsyncReadbackBuffer> buffer =
+ mNativeLayerRootSnapshotter->CreateAsyncReadbackBuffer(size);
+ buffer->CopyFrom(snapshot);
+
+ RefPtr<layers::RecordedFrame> frame =
+ new RenderCompositorRecordedFrame(TimeStamp::Now(), std::move(buffer));
+ aRecorder.RecordFrame(frame);
+
+ // GetWindowContents might have changed the current context. Make sure our
+ // context is current again.
+ MakeCurrent();
+ return true;
+}
+
+bool RenderCompositorNative::MaybeGrabScreenshot(
+ const gfx::IntSize& aWindowSize) {
+ if (!ShouldUseNativeCompositor()) {
+ return false;
+ }
+
+ if (!mNativeLayerRootSnapshotter) {
+ mNativeLayerRootSnapshotter = mNativeLayerRoot->CreateSnapshotter();
+ }
+
+ if (mNativeLayerRootSnapshotter) {
+ mProfilerScreenshotGrabber.MaybeGrabScreenshot(*mNativeLayerRootSnapshotter,
+ aWindowSize);
+
+ // MaybeGrabScreenshot might have changed the current context. Make sure our
+ // context is current again.
+ MakeCurrent();
+ }
+
+ return true;
+}
+
+bool RenderCompositorNative::MaybeProcessScreenshotQueue() {
+ if (!ShouldUseNativeCompositor()) {
+ return false;
+ }
+
+ mProfilerScreenshotGrabber.MaybeProcessQueue();
+
+ // MaybeProcessQueue might have changed the current context. Make sure our
+ // context is current again.
+ MakeCurrent();
+
+ return true;
+}
+
+uint32_t RenderCompositorNative::GetMaxUpdateRects() {
+ if (ShouldUseNativeCompositor() &&
+ StaticPrefs::gfx_webrender_compositor_max_update_rects_AtStartup() > 0) {
+ return 1;
+ }
+ return 0;
+}
+
+void RenderCompositorNative::CompositorBeginFrame() {
+ mAddedLayers.Clear();
+ mAddedTilePixelCount = 0;
+ mAddedClippedPixelCount = 0;
+ mBeginFrameTimeStamp = TimeStamp::NowUnfuzzed();
+ mSurfacePoolHandle->OnBeginFrame();
+}
+
+void RenderCompositorNative::CompositorEndFrame() {
+#ifdef MOZ_GECKO_PROFILER
+ if (profiler_thread_is_being_profiled()) {
+ auto bufferSize = GetBufferSize();
+ uint64_t windowPixelCount = uint64_t(bufferSize.width) * bufferSize.height;
+ int nativeLayerCount = 0;
+ for (const auto& it : mSurfaces) {
+ nativeLayerCount += int(it.second.mNativeLayers.size());
+ }
+ PROFILER_MARKER_TEXT(
+ "WR OS Compositor frame", GRAPHICS,
+ MarkerTiming::IntervalUntilNowFrom(mBeginFrameTimeStamp),
+ nsPrintfCString("%d%% painting, %d%% overdraw, %d used "
+ "layers (%d%% memory) + %d unused layers (%d%% memory)",
+ int(mDrawnPixelCount * 100 / windowPixelCount),
+ int(mAddedClippedPixelCount * 100 / windowPixelCount),
+ int(mAddedLayers.Length()),
+ int(mAddedTilePixelCount * 100 / windowPixelCount),
+ int(nativeLayerCount - mAddedLayers.Length()),
+ int((mTotalTilePixelCount - mAddedTilePixelCount) *
+ 100 / windowPixelCount)));
+ }
+#endif
+ mDrawnPixelCount = 0;
+
+ DoFlush();
+
+ mNativeLayerRoot->SetLayers(mAddedLayers);
+ mNativeLayerRoot->CommitToScreen();
+ mSurfacePoolHandle->OnEndFrame();
+}
+
+void RenderCompositorNative::BindNativeLayer(wr::NativeTileId aId,
+ const gfx::IntRect& aDirtyRect) {
+ MOZ_RELEASE_ASSERT(!mCurrentlyBoundNativeLayer);
+
+ auto surfaceCursor = mSurfaces.find(aId.surface_id);
+ MOZ_RELEASE_ASSERT(surfaceCursor != mSurfaces.end());
+ Surface& surface = surfaceCursor->second;
+
+ auto layerCursor = surface.mNativeLayers.find(TileKey(aId.x, aId.y));
+ MOZ_RELEASE_ASSERT(layerCursor != surface.mNativeLayers.end());
+ RefPtr<layers::NativeLayer> layer = layerCursor->second;
+
+ mCurrentlyBoundNativeLayer = layer;
+
+ mDrawnPixelCount += aDirtyRect.Area();
+}
+
+void RenderCompositorNative::UnbindNativeLayer() {
+ MOZ_RELEASE_ASSERT(mCurrentlyBoundNativeLayer);
+
+ mCurrentlyBoundNativeLayer->NotifySurfaceReady();
+ mCurrentlyBoundNativeLayer = nullptr;
+}
+
+void RenderCompositorNative::CreateSurface(wr::NativeSurfaceId aId,
+ wr::DeviceIntPoint aVirtualOffset,
+ wr::DeviceIntSize aTileSize,
+ bool aIsOpaque) {
+ MOZ_RELEASE_ASSERT(mSurfaces.find(aId) == mSurfaces.end());
+ mSurfaces.insert({aId, Surface{aTileSize, aIsOpaque}});
+}
+
+void RenderCompositorNative::CreateExternalSurface(wr::NativeSurfaceId aId,
+ bool aIsOpaque) {
+ MOZ_RELEASE_ASSERT(mSurfaces.find(aId) == mSurfaces.end());
+
+ RefPtr<layers::NativeLayer> layer =
+ mNativeLayerRoot->CreateLayerForExternalTexture(aIsOpaque);
+
+ Surface surface{DeviceIntSize{}, aIsOpaque};
+ surface.mIsExternal = true;
+ surface.mNativeLayers.insert({TileKey(0, 0), layer});
+
+ mSurfaces.insert({aId, std::move(surface)});
+}
+
+void RenderCompositorNative::AttachExternalImage(
+ wr::NativeSurfaceId aId, wr::ExternalImageId aExternalImage) {
+ RenderTextureHost* image =
+ RenderThread::Get()->GetRenderTexture(aExternalImage);
+ MOZ_RELEASE_ASSERT(image);
+
+ auto surfaceCursor = mSurfaces.find(aId);
+ MOZ_RELEASE_ASSERT(surfaceCursor != mSurfaces.end());
+
+ Surface& surface = surfaceCursor->second;
+ MOZ_RELEASE_ASSERT(surface.mNativeLayers.size() == 1);
+ MOZ_RELEASE_ASSERT(surface.mIsExternal);
+ surface.mNativeLayers.begin()->second->AttachExternalImage(image);
+}
+
+void RenderCompositorNative::DestroySurface(NativeSurfaceId aId) {
+ auto surfaceCursor = mSurfaces.find(aId);
+ MOZ_RELEASE_ASSERT(surfaceCursor != mSurfaces.end());
+
+ Surface& surface = surfaceCursor->second;
+ if (!surface.mIsExternal) {
+ for (const auto& iter : surface.mNativeLayers) {
+ mTotalTilePixelCount -= gfx::IntRect({}, iter.second->GetSize()).Area();
+ }
+ }
+
+ mSurfaces.erase(surfaceCursor);
+}
+
+void RenderCompositorNative::CreateTile(wr::NativeSurfaceId aId, int aX,
+ int aY) {
+ auto surfaceCursor = mSurfaces.find(aId);
+ MOZ_RELEASE_ASSERT(surfaceCursor != mSurfaces.end());
+ Surface& surface = surfaceCursor->second;
+ MOZ_RELEASE_ASSERT(!surface.mIsExternal);
+
+ RefPtr<layers::NativeLayer> layer = mNativeLayerRoot->CreateLayer(
+ surface.TileSize(), surface.mIsOpaque, mSurfacePoolHandle);
+ surface.mNativeLayers.insert({TileKey(aX, aY), layer});
+ mTotalTilePixelCount += gfx::IntRect({}, layer->GetSize()).Area();
+}
+
+void RenderCompositorNative::DestroyTile(wr::NativeSurfaceId aId, int aX,
+ int aY) {
+ auto surfaceCursor = mSurfaces.find(aId);
+ MOZ_RELEASE_ASSERT(surfaceCursor != mSurfaces.end());
+ Surface& surface = surfaceCursor->second;
+ MOZ_RELEASE_ASSERT(!surface.mIsExternal);
+
+ auto layerCursor = surface.mNativeLayers.find(TileKey(aX, aY));
+ MOZ_RELEASE_ASSERT(layerCursor != surface.mNativeLayers.end());
+ RefPtr<layers::NativeLayer> layer = std::move(layerCursor->second);
+ surface.mNativeLayers.erase(layerCursor);
+ mTotalTilePixelCount -= gfx::IntRect({}, layer->GetSize()).Area();
+
+ // If the layer is currently present in mNativeLayerRoot, it will be destroyed
+ // once CompositorEndFrame() replaces mNativeLayerRoot's layers and drops that
+ // reference. So until that happens, the layer still needs to hold on to its
+ // front buffer. However, we can tell it to drop its back buffers now, because
+ // we know that we will never draw to it again.
+ // Dropping the back buffers now puts them back in the surface pool, so those
+ // surfaces can be immediately re-used for drawing in other layers in the
+ // current frame.
+ layer->DiscardBackbuffers();
+}
+
+gfx::SamplingFilter ToSamplingFilter(wr::ImageRendering aImageRendering) {
+ if (aImageRendering == wr::ImageRendering::Auto) {
+ return gfx::SamplingFilter::LINEAR;
+ }
+ return gfx::SamplingFilter::POINT;
+}
+
+void RenderCompositorNative::AddSurface(
+ wr::NativeSurfaceId aId, const wr::CompositorSurfaceTransform& aTransform,
+ wr::DeviceIntRect aClipRect, wr::ImageRendering aImageRendering) {
+ MOZ_RELEASE_ASSERT(!mCurrentlyBoundNativeLayer);
+
+ auto surfaceCursor = mSurfaces.find(aId);
+ MOZ_RELEASE_ASSERT(surfaceCursor != mSurfaces.end());
+ const Surface& surface = surfaceCursor->second;
+
+ Matrix4x4 transform(
+ aTransform.m11, aTransform.m12, aTransform.m13, aTransform.m14,
+ aTransform.m21, aTransform.m22, aTransform.m23, aTransform.m24,
+ aTransform.m31, aTransform.m32, aTransform.m33, aTransform.m34,
+ aTransform.m41, aTransform.m42, aTransform.m43, aTransform.m44);
+
+ for (auto it = surface.mNativeLayers.begin();
+ it != surface.mNativeLayers.end(); ++it) {
+ RefPtr<layers::NativeLayer> layer = it->second;
+ gfx::IntSize layerSize = layer->GetSize();
+ gfx::IntPoint layerPosition(surface.mTileSize.width * it->first.mX,
+ surface.mTileSize.height * it->first.mY);
+ layer->SetPosition(layerPosition);
+ gfx::IntRect clipRect(aClipRect.origin.x, aClipRect.origin.y,
+ aClipRect.size.width, aClipRect.size.height);
+ layer->SetClipRect(Some(clipRect));
+ layer->SetTransform(transform);
+ layer->SetSamplingFilter(ToSamplingFilter(aImageRendering));
+ mAddedLayers.AppendElement(layer);
+
+ if (!surface.mIsExternal) {
+ mAddedTilePixelCount += layerSize.width * layerSize.height;
+ }
+ gfx::Rect r = transform.TransformBounds(
+ gfx::Rect(layer->CurrentSurfaceDisplayRect()));
+ gfx::IntRect visibleRect =
+ clipRect.Intersect(RoundedToInt(r) + layerPosition);
+ mAddedClippedPixelCount += visibleRect.Area();
+ }
+}
+
+CompositorCapabilities RenderCompositorNative::GetCompositorCapabilities() {
+ CompositorCapabilities caps;
+
+ // CoreAnimation doesn't use virtual surfaces
+ caps.virtual_surface_size = 0;
+
+ return caps;
+}
+
+/* static */
+UniquePtr<RenderCompositor> RenderCompositorNativeOGL::Create(
+ RefPtr<widget::CompositorWidget>&& aWidget, nsACString& aError) {
+ RefPtr<gl::GLContext> gl = RenderThread::Get()->SharedGL();
+ if (!gl) {
+ gl = gl::GLContextProvider::CreateForCompositorWidget(
+ aWidget, /* aWebRender */ true, /* aForceAccelerated */ true);
+ RenderThread::MaybeEnableGLDebugMessage(gl);
+ }
+ if (!gl || !gl->MakeCurrent()) {
+ gfxCriticalNote << "Failed GL context creation for WebRender: "
+ << gfx::hexa(gl.get());
+ return nullptr;
+ }
+ return MakeUnique<RenderCompositorNativeOGL>(std::move(aWidget),
+ std::move(gl));
+}
+
+RenderCompositorNativeOGL::RenderCompositorNativeOGL(
+ RefPtr<widget::CompositorWidget>&& aWidget, RefPtr<gl::GLContext>&& aGL)
+ : RenderCompositorNative(std::move(aWidget), aGL), mGL(aGL) {
+ MOZ_ASSERT(mGL);
+}
+
+RenderCompositorNativeOGL::~RenderCompositorNativeOGL() {
+ if (!mGL->MakeCurrent()) {
+ gfxCriticalNote
+ << "Failed to make render context current during destroying.";
+ // Leak resources!
+ mPreviousFrameDoneSync = nullptr;
+ mThisFrameDoneSync = nullptr;
+ return;
+ }
+
+ if (mPreviousFrameDoneSync) {
+ mGL->fDeleteSync(mPreviousFrameDoneSync);
+ }
+ if (mThisFrameDoneSync) {
+ mGL->fDeleteSync(mThisFrameDoneSync);
+ }
+}
+
+bool RenderCompositorNativeOGL::InitDefaultFramebuffer(
+ const gfx::IntRect& aBounds) {
+ if (mNativeLayerForEntireWindow) {
+ Maybe<GLuint> fbo = mNativeLayerForEntireWindow->NextSurfaceAsFramebuffer(
+ aBounds, aBounds, true);
+ if (!fbo) {
+ return false;
+ }
+ mGL->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, *fbo);
+ } else {
+ mGL->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mGL->GetDefaultFramebuffer());
+ }
+ return true;
+}
+
+void RenderCompositorNativeOGL::DoSwap() {
+ InsertFrameDoneSync();
+ if (mNativeLayerForEntireWindow) {
+ mGL->fFlush();
+ }
+}
+
+void RenderCompositorNativeOGL::DoFlush() { mGL->fFlush(); }
+
+void RenderCompositorNativeOGL::InsertFrameDoneSync() {
+#ifdef XP_MACOSX
+ // Only do this on macOS.
+ // On other platforms, SwapBuffers automatically applies back-pressure.
+ if (mThisFrameDoneSync) {
+ mGL->fDeleteSync(mThisFrameDoneSync);
+ }
+ mThisFrameDoneSync = mGL->fFenceSync(LOCAL_GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
+#endif
+}
+
+bool RenderCompositorNativeOGL::WaitForGPU() {
+ if (mPreviousFrameDoneSync) {
+ AUTO_PROFILER_LABEL("Waiting for GPU to finish previous frame", GRAPHICS);
+ mGL->fClientWaitSync(mPreviousFrameDoneSync,
+ LOCAL_GL_SYNC_FLUSH_COMMANDS_BIT,
+ LOCAL_GL_TIMEOUT_IGNORED);
+ mGL->fDeleteSync(mPreviousFrameDoneSync);
+ }
+ mPreviousFrameDoneSync = mThisFrameDoneSync;
+ mThisFrameDoneSync = nullptr;
+
+ return true;
+}
+
+void RenderCompositorNativeOGL::Bind(wr::NativeTileId aId,
+ wr::DeviceIntPoint* aOffset,
+ uint32_t* aFboId,
+ wr::DeviceIntRect aDirtyRect,
+ wr::DeviceIntRect aValidRect) {
+ gfx::IntRect validRect(aValidRect.origin.x, aValidRect.origin.y,
+ aValidRect.size.width, aValidRect.size.height);
+ gfx::IntRect dirtyRect(aDirtyRect.origin.x, aDirtyRect.origin.y,
+ aDirtyRect.size.width, aDirtyRect.size.height);
+
+ BindNativeLayer(aId, dirtyRect);
+
+ Maybe<GLuint> fbo = mCurrentlyBoundNativeLayer->NextSurfaceAsFramebuffer(
+ validRect, dirtyRect, true);
+
+ *aFboId = *fbo;
+ *aOffset = wr::DeviceIntPoint{0, 0};
+}
+
+void RenderCompositorNativeOGL::Unbind() {
+ mGL->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, 0);
+
+ UnbindNativeLayer();
+}
+
+/* static */
+UniquePtr<RenderCompositor> RenderCompositorNativeSWGL::Create(
+ RefPtr<widget::CompositorWidget>&& aWidget, nsACString& aError) {
+ void* ctx = wr_swgl_create_context();
+ if (!ctx) {
+ gfxCriticalNote << "Failed SWGL context creation for WebRender";
+ return nullptr;
+ }
+ return MakeUnique<RenderCompositorNativeSWGL>(std::move(aWidget), ctx);
+}
+
+RenderCompositorNativeSWGL::RenderCompositorNativeSWGL(
+ RefPtr<widget::CompositorWidget>&& aWidget, void* aContext)
+ : RenderCompositorNative(std::move(aWidget)), mContext(aContext) {
+ MOZ_ASSERT(mContext);
+}
+
+RenderCompositorNativeSWGL::~RenderCompositorNativeSWGL() {
+ wr_swgl_destroy_context(mContext);
+}
+
+bool RenderCompositorNativeSWGL::MakeCurrent() {
+ wr_swgl_make_current(mContext);
+ return true;
+}
+
+bool RenderCompositorNativeSWGL::InitDefaultFramebuffer(
+ const gfx::IntRect& aBounds) {
+ if (mNativeLayerForEntireWindow) {
+ if (!MapNativeLayer(mNativeLayerForEntireWindow, aBounds, aBounds)) {
+ return false;
+ }
+ wr_swgl_init_default_framebuffer(mContext, aBounds.x, aBounds.y,
+ aBounds.width, aBounds.height,
+ mLayerStride, mLayerValidRectData);
+ }
+ return true;
+}
+
+void RenderCompositorNativeSWGL::CancelFrame() {
+ if (mNativeLayerForEntireWindow && mLayerTarget) {
+ wr_swgl_init_default_framebuffer(mContext, 0, 0, 0, 0, 0, nullptr);
+ UnmapNativeLayer();
+ }
+}
+
+void RenderCompositorNativeSWGL::DoSwap() {
+ if (mNativeLayerForEntireWindow && mLayerTarget) {
+ wr_swgl_init_default_framebuffer(mContext, 0, 0, 0, 0, 0, nullptr);
+ UnmapNativeLayer();
+ }
+}
+
+bool RenderCompositorNativeSWGL::MapNativeLayer(
+ layers::NativeLayer* aLayer, const gfx::IntRect& aDirtyRect,
+ const gfx::IntRect& aValidRect) {
+ uint8_t* data = nullptr;
+ gfx::IntSize size;
+ int32_t stride = 0;
+ gfx::SurfaceFormat format = gfx::SurfaceFormat::UNKNOWN;
+ RefPtr<gfx::DrawTarget> dt = aLayer->NextSurfaceAsDrawTarget(
+ aValidRect, gfx::IntRegion(aDirtyRect), gfx::BackendType::SKIA);
+ if (!dt || !dt->LockBits(&data, &size, &stride, &format)) {
+ return false;
+ }
+ MOZ_ASSERT(format == gfx::SurfaceFormat::B8G8R8A8 ||
+ format == gfx::SurfaceFormat::B8G8R8X8);
+ mLayerTarget = std::move(dt);
+ mLayerData = data;
+ mLayerValidRectData = data + aValidRect.y * stride + aValidRect.x * 4;
+ mLayerStride = stride;
+ return true;
+}
+
+void RenderCompositorNativeSWGL::UnmapNativeLayer() {
+ MOZ_ASSERT(mLayerTarget && mLayerData);
+ mLayerTarget->ReleaseBits(mLayerData);
+ mLayerTarget = nullptr;
+ mLayerData = nullptr;
+ mLayerValidRectData = nullptr;
+ mLayerStride = 0;
+}
+
+bool RenderCompositorNativeSWGL::MapTile(wr::NativeTileId aId,
+ wr::DeviceIntRect aDirtyRect,
+ wr::DeviceIntRect aValidRect,
+ void** aData, int32_t* aStride) {
+ if (mNativeLayerForEntireWindow) {
+ return false;
+ }
+ gfx::IntRect dirtyRect(aDirtyRect.origin.x, aDirtyRect.origin.y,
+ aDirtyRect.size.width, aDirtyRect.size.height);
+ gfx::IntRect validRect(aValidRect.origin.x, aValidRect.origin.y,
+ aValidRect.size.width, aValidRect.size.height);
+ BindNativeLayer(aId, dirtyRect);
+ if (!MapNativeLayer(mCurrentlyBoundNativeLayer, dirtyRect, validRect)) {
+ UnbindNativeLayer();
+ return false;
+ }
+ *aData = mLayerValidRectData;
+ *aStride = mLayerStride;
+ return true;
+}
+
+void RenderCompositorNativeSWGL::UnmapTile() {
+ if (!mNativeLayerForEntireWindow && mCurrentlyBoundNativeLayer) {
+ UnmapNativeLayer();
+ UnbindNativeLayer();
+ }
+}
+
+} // namespace wr
+} // namespace mozilla
diff --git a/gfx/webrender_bindings/RenderCompositorNative.h b/gfx/webrender_bindings/RenderCompositorNative.h
new file mode 100644
index 0000000000..8a1344bcb7
--- /dev/null
+++ b/gfx/webrender_bindings/RenderCompositorNative.h
@@ -0,0 +1,226 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef MOZILLA_GFX_RENDERCOMPOSITOR_NATIVE_H
+#define MOZILLA_GFX_RENDERCOMPOSITOR_NATIVE_H
+
+#include "GLTypes.h"
+#include "mozilla/layers/ScreenshotGrabber.h"
+#include "mozilla/webrender/RenderCompositor.h"
+#include "mozilla/TimeStamp.h"
+
+namespace mozilla {
+
+namespace layers {
+class NativeLayerRootSnapshotter;
+class NativeLayerRoot;
+class NativeLayer;
+class SurfacePoolHandle;
+} // namespace layers
+
+namespace wr {
+
+// RenderCompositorNative is a skeleton class for implementing compositors
+// backed by NativeLayer surfaces and tiles. This is not meant to be directly
+// instantiated and is instead derived for various use-cases such as OpenGL or
+// SWGL.
+class RenderCompositorNative : public RenderCompositor {
+ public:
+ virtual ~RenderCompositorNative();
+
+ bool BeginFrame() override;
+ RenderedFrameId EndFrame(const nsTArray<DeviceIntRect>& aDirtyRects) final;
+ void Pause() override;
+ bool Resume() override;
+
+ layers::WebRenderCompositor CompositorType() const override;
+
+ LayoutDeviceIntSize GetBufferSize() override;
+
+ bool ShouldUseNativeCompositor() override;
+ uint32_t GetMaxUpdateRects() override;
+
+ // Does the readback for the ShouldUseNativeCompositor() case.
+ bool MaybeReadback(const gfx::IntSize& aReadbackSize,
+ const wr::ImageFormat& aReadbackFormat,
+ const Range<uint8_t>& aReadbackBuffer,
+ bool* aNeedsYFlip) override;
+ bool MaybeRecordFrame(layers::CompositionRecorder& aRecorder) override;
+ bool MaybeGrabScreenshot(const gfx::IntSize& aWindowSize) override;
+ bool MaybeProcessScreenshotQueue() override;
+
+ // Interface for wr::Compositor
+ void CompositorBeginFrame() override;
+ void CompositorEndFrame() override;
+ void CreateSurface(wr::NativeSurfaceId aId, wr::DeviceIntPoint aVirtualOffset,
+ wr::DeviceIntSize aTileSize, bool aIsOpaque) override;
+ void CreateExternalSurface(wr::NativeSurfaceId aId, bool aIsOpaque) override;
+ void DestroySurface(NativeSurfaceId aId) override;
+ void CreateTile(wr::NativeSurfaceId aId, int32_t aX, int32_t aY) override;
+ void DestroyTile(wr::NativeSurfaceId aId, int32_t aX, int32_t aY) override;
+ void AttachExternalImage(wr::NativeSurfaceId aId,
+ wr::ExternalImageId aExternalImage) override;
+ void AddSurface(wr::NativeSurfaceId aId,
+ const wr::CompositorSurfaceTransform& aTransform,
+ wr::DeviceIntRect aClipRect,
+ wr::ImageRendering aImageRendering) override;
+ CompositorCapabilities GetCompositorCapabilities() override;
+
+ struct TileKey {
+ TileKey(int32_t aX, int32_t aY) : mX(aX), mY(aY) {}
+
+ int32_t mX;
+ int32_t mY;
+ };
+
+ protected:
+ explicit RenderCompositorNative(RefPtr<widget::CompositorWidget>&& aWidget,
+ gl::GLContext* aGL = nullptr);
+
+ virtual bool InitDefaultFramebuffer(const gfx::IntRect& aBounds) = 0;
+ virtual void DoSwap() = 0;
+ virtual void DoFlush() {}
+
+ void BindNativeLayer(wr::NativeTileId aId, const gfx::IntRect& aDirtyRect);
+ void UnbindNativeLayer();
+
+ // Can be null.
+ RefPtr<layers::NativeLayerRoot> mNativeLayerRoot;
+ UniquePtr<layers::NativeLayerRootSnapshotter> mNativeLayerRootSnapshotter;
+ layers::ScreenshotGrabber mProfilerScreenshotGrabber;
+ RefPtr<layers::NativeLayer> mNativeLayerForEntireWindow;
+ RefPtr<layers::SurfacePoolHandle> mSurfacePoolHandle;
+
+ struct TileKeyHashFn {
+ std::size_t operator()(const TileKey& aId) const {
+ return HashGeneric(aId.mX, aId.mY);
+ }
+ };
+
+ struct Surface {
+ explicit Surface(wr::DeviceIntSize aTileSize, bool aIsOpaque)
+ : mTileSize(aTileSize), mIsOpaque(aIsOpaque) {}
+ gfx::IntSize TileSize() {
+ return gfx::IntSize(mTileSize.width, mTileSize.height);
+ }
+
+ // External images can change size depending on which image
+ // is attached, so mTileSize will be 0,0 when mIsExternal
+ // is true.
+ wr::DeviceIntSize mTileSize;
+ bool mIsOpaque;
+ bool mIsExternal = false;
+ std::unordered_map<TileKey, RefPtr<layers::NativeLayer>, TileKeyHashFn>
+ mNativeLayers;
+ };
+
+ struct SurfaceIdHashFn {
+ std::size_t operator()(const wr::NativeSurfaceId& aId) const {
+ return HashGeneric(wr::AsUint64(aId));
+ }
+ };
+
+ // Used in native compositor mode:
+ RefPtr<layers::NativeLayer> mCurrentlyBoundNativeLayer;
+ nsTArray<RefPtr<layers::NativeLayer>> mAddedLayers;
+ uint64_t mTotalTilePixelCount = 0;
+ uint64_t mAddedTilePixelCount = 0;
+ uint64_t mAddedClippedPixelCount = 0;
+ uint64_t mDrawnPixelCount = 0;
+ gfx::IntRect mVisibleBounds;
+ std::unordered_map<wr::NativeSurfaceId, Surface, SurfaceIdHashFn> mSurfaces;
+ TimeStamp mBeginFrameTimeStamp;
+};
+
+static inline bool operator==(const RenderCompositorNative::TileKey& a0,
+ const RenderCompositorNative::TileKey& a1) {
+ return a0.mX == a1.mX && a0.mY == a1.mY;
+}
+
+// RenderCompositorNativeOGL is a NativeLayer compositor that exposes an
+// OpenGL framebuffer for the respective NativeLayer bound to each tile.
+class RenderCompositorNativeOGL : public RenderCompositorNative {
+ public:
+ static UniquePtr<RenderCompositor> Create(
+ RefPtr<widget::CompositorWidget>&& aWidget, nsACString& aError);
+
+ RenderCompositorNativeOGL(RefPtr<widget::CompositorWidget>&& aWidget,
+ RefPtr<gl::GLContext>&& aGL);
+ virtual ~RenderCompositorNativeOGL();
+
+ bool WaitForGPU() override;
+
+ gl::GLContext* gl() const override { return mGL; }
+
+ void Bind(wr::NativeTileId aId, wr::DeviceIntPoint* aOffset, uint32_t* aFboId,
+ wr::DeviceIntRect aDirtyRect,
+ wr::DeviceIntRect aValidRect) override;
+ void Unbind() override;
+
+ protected:
+ void InsertFrameDoneSync();
+
+ bool InitDefaultFramebuffer(const gfx::IntRect& aBounds) override;
+ void DoSwap() override;
+ void DoFlush() override;
+
+ RefPtr<gl::GLContext> mGL;
+
+ // Used to apply back-pressure in WaitForGPU().
+ GLsync mPreviousFrameDoneSync = nullptr;
+ GLsync mThisFrameDoneSync = nullptr;
+};
+
+// RenderCompositorNativeSWGL is a NativeLayer compositor that only
+// deals with mapping the underlying buffer for SWGL usage of a tile.
+class RenderCompositorNativeSWGL : public RenderCompositorNative {
+ public:
+ static UniquePtr<RenderCompositor> Create(
+ RefPtr<widget::CompositorWidget>&& aWidget, nsACString& aError);
+
+ RenderCompositorNativeSWGL(RefPtr<widget::CompositorWidget>&& aWidget,
+ void* aContext);
+ virtual ~RenderCompositorNativeSWGL();
+
+ void* swgl() const override { return mContext; }
+
+ bool MakeCurrent() override;
+
+ void CancelFrame() override;
+
+ layers::WebRenderBackend BackendType() const override {
+ return layers::WebRenderBackend::SOFTWARE;
+ }
+
+ // Maps an underlying layer and sets aData to the top left pixel of
+ // aValidRect. The row stride is set to aStride, note this doesn't
+ // mean there are always aStride bytes available per row (the
+ // last row will fall short if aValidRect is not at X==0).
+ bool MapTile(wr::NativeTileId aId, wr::DeviceIntRect aDirtyRect,
+ wr::DeviceIntRect aValidRect, void** aData,
+ int32_t* aStride) override;
+ void UnmapTile() override;
+
+ protected:
+ bool InitDefaultFramebuffer(const gfx::IntRect& aBounds) override;
+ void DoSwap() override;
+
+ bool MapNativeLayer(layers::NativeLayer* aLayer,
+ const gfx::IntRect& aDirtyRect,
+ const gfx::IntRect& aValidRect);
+ void UnmapNativeLayer();
+
+ void* mContext = nullptr;
+ RefPtr<gfx::DrawTarget> mLayerTarget;
+ uint8_t* mLayerData = nullptr;
+ uint8_t* mLayerValidRectData = nullptr;
+ int32_t mLayerStride = 0;
+};
+
+} // namespace wr
+} // namespace mozilla
+
+#endif
diff --git a/gfx/webrender_bindings/RenderCompositorOGL.cpp b/gfx/webrender_bindings/RenderCompositorOGL.cpp
new file mode 100644
index 0000000000..bcd5849ea1
--- /dev/null
+++ b/gfx/webrender_bindings/RenderCompositorOGL.cpp
@@ -0,0 +1,131 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "RenderCompositorOGL.h"
+
+#include "GLContext.h"
+#include "GLContextEGL.h"
+#include "GLContextProvider.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/webrender/RenderThread.h"
+#include "mozilla/widget/CompositorWidget.h"
+
+namespace mozilla {
+namespace wr {
+
+/* static */
+UniquePtr<RenderCompositor> RenderCompositorOGL::Create(
+ RefPtr<widget::CompositorWidget>&& aWidget, nsACString& aError) {
+ RefPtr<gl::GLContext> gl = RenderThread::Get()->SharedGL();
+ if (!gl) {
+ gl = gl::GLContextProvider::CreateForCompositorWidget(
+ aWidget, /* aWebRender */ true, /* aForceAccelerated */ true);
+ RenderThread::MaybeEnableGLDebugMessage(gl);
+ }
+ if (!gl || !gl->MakeCurrent()) {
+ gfxCriticalNote << "Failed GL context creation for WebRender: "
+ << gfx::hexa(gl.get());
+ return nullptr;
+ }
+ return MakeUnique<RenderCompositorOGL>(std::move(gl), std::move(aWidget));
+}
+
+RenderCompositorOGL::RenderCompositorOGL(
+ RefPtr<gl::GLContext>&& aGL, RefPtr<widget::CompositorWidget>&& aWidget)
+ : RenderCompositor(std::move(aWidget)), mGL(aGL) {
+ MOZ_ASSERT(mGL);
+
+ mIsEGL = aGL->GetContextType() == mozilla::gl::GLContextType::EGL;
+}
+
+RenderCompositorOGL::~RenderCompositorOGL() {
+ if (!mGL->MakeCurrent()) {
+ gfxCriticalNote
+ << "Failed to make render context current during destroying.";
+ // Leak resources!
+ return;
+ }
+}
+
+bool RenderCompositorOGL::BeginFrame() {
+ if (!mGL->MakeCurrent()) {
+ gfxCriticalNote << "Failed to make render context current, can't draw.";
+ return false;
+ }
+
+ mGL->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mGL->GetDefaultFramebuffer());
+
+ return true;
+}
+
+RenderedFrameId RenderCompositorOGL::EndFrame(
+ const nsTArray<DeviceIntRect>& aDirtyRects) {
+ RenderedFrameId frameId = GetNextRenderFrameId();
+ if (UsePartialPresent() && aDirtyRects.Length() > 0) {
+ gfx::IntRegion bufferInvalid;
+ const auto bufferSize = GetBufferSize();
+ for (const DeviceIntRect& rect : aDirtyRects) {
+ const auto left = std::max(0, std::min(bufferSize.width, rect.origin.x));
+ const auto top = std::max(0, std::min(bufferSize.height, rect.origin.y));
+
+ const auto right = std::min(bufferSize.width,
+ std::max(0, rect.origin.x + rect.size.width));
+ const auto bottom = std::min(
+ bufferSize.height, std::max(0, rect.origin.y + rect.size.height));
+
+ const auto width = right - left;
+ const auto height = bottom - top;
+
+ bufferInvalid.OrWith(
+ gfx::IntRect(left, (GetBufferSize().height - bottom), width, height));
+ }
+ gl()->SetDamage(bufferInvalid);
+ }
+ mGL->SwapBuffers();
+ return frameId;
+}
+
+void RenderCompositorOGL::Pause() {}
+
+bool RenderCompositorOGL::Resume() { return true; }
+
+LayoutDeviceIntSize RenderCompositorOGL::GetBufferSize() {
+ return mWidget->GetClientSize();
+}
+
+CompositorCapabilities RenderCompositorOGL::GetCompositorCapabilities() {
+ CompositorCapabilities caps;
+
+ caps.virtual_surface_size = 0;
+
+ return caps;
+}
+
+uint32_t RenderCompositorOGL::GetMaxPartialPresentRects() {
+ return gfx::gfxVars::WebRenderMaxPartialPresentRects();
+}
+
+bool RenderCompositorOGL::RequestFullRender() { return false; }
+
+bool RenderCompositorOGL::UsePartialPresent() {
+ return gfx::gfxVars::WebRenderMaxPartialPresentRects() > 0;
+}
+
+bool RenderCompositorOGL::ShouldDrawPreviousPartialPresentRegions() {
+ return true;
+}
+
+size_t RenderCompositorOGL::GetBufferAge() const {
+ if (!StaticPrefs::
+ gfx_webrender_allow_partial_present_buffer_age_AtStartup()) {
+ return 0;
+ }
+ return gl()->GetBufferAge();
+}
+
+} // namespace wr
+} // namespace mozilla
diff --git a/gfx/webrender_bindings/RenderCompositorOGL.h b/gfx/webrender_bindings/RenderCompositorOGL.h
new file mode 100644
index 0000000000..9b127517cb
--- /dev/null
+++ b/gfx/webrender_bindings/RenderCompositorOGL.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef MOZILLA_GFX_RENDERCOMPOSITOR_OGL_H
+#define MOZILLA_GFX_RENDERCOMPOSITOR_OGL_H
+
+#include "GLTypes.h"
+#include "mozilla/webrender/RenderCompositor.h"
+
+namespace mozilla {
+namespace wr {
+
+class RenderCompositorOGL : public RenderCompositor {
+ public:
+ static UniquePtr<RenderCompositor> Create(
+ RefPtr<widget::CompositorWidget>&& aWidget, nsACString& aError);
+
+ RenderCompositorOGL(RefPtr<gl::GLContext>&& aGL,
+ RefPtr<widget::CompositorWidget>&& aWidget);
+ virtual ~RenderCompositorOGL();
+
+ bool BeginFrame() override;
+ RenderedFrameId EndFrame(const nsTArray<DeviceIntRect>& aDirtyRects) final;
+ void Pause() override;
+ bool Resume() override;
+
+ gl::GLContext* gl() const override { return mGL; }
+
+ LayoutDeviceIntSize GetBufferSize() override;
+
+ // Interface for wr::Compositor
+ CompositorCapabilities GetCompositorCapabilities() override;
+
+ // Interface for partial present
+ bool UsePartialPresent() override;
+ bool RequestFullRender() override;
+ uint32_t GetMaxPartialPresentRects() override;
+ bool ShouldDrawPreviousPartialPresentRegions() override;
+ size_t GetBufferAge() const override;
+
+ protected:
+ RefPtr<gl::GLContext> mGL;
+ bool mIsEGL;
+};
+
+} // namespace wr
+} // namespace mozilla
+
+#endif
diff --git a/gfx/webrender_bindings/RenderCompositorRecordedFrame.h b/gfx/webrender_bindings/RenderCompositorRecordedFrame.h
new file mode 100644
index 0000000000..f7a516107b
--- /dev/null
+++ b/gfx/webrender_bindings/RenderCompositorRecordedFrame.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef MOZILLA_GFX_RENDERCOMPOSITOR_RECORDEDFRAME_H
+#define MOZILLA_GFX_RENDERCOMPOSITOR_RECORDEDFRAME_H
+
+#include "mozilla/layers/CompositionRecorder.h"
+
+namespace mozilla {
+
+namespace wr {
+
+class RenderCompositorRecordedFrame final : public layers::RecordedFrame {
+ public:
+ RenderCompositorRecordedFrame(
+ const TimeStamp& aTimeStamp,
+ RefPtr<layers::profiler_screenshots::AsyncReadbackBuffer>&& aBuffer)
+ : RecordedFrame(aTimeStamp), mBuffer(aBuffer) {}
+
+ virtual already_AddRefed<gfx::DataSourceSurface> GetSourceSurface() override {
+ if (mSurface) {
+ return do_AddRef(mSurface);
+ }
+
+ gfx::IntSize size = mBuffer->Size();
+ mSurface = gfx::Factory::CreateDataSourceSurface(
+ size, gfx::SurfaceFormat::B8G8R8A8,
+ /* aZero = */ false);
+
+ if (!mBuffer->MapAndCopyInto(mSurface, size)) {
+ mSurface = nullptr;
+ return nullptr;
+ }
+
+ return do_AddRef(mSurface);
+ }
+
+ private:
+ RefPtr<layers::profiler_screenshots::AsyncReadbackBuffer> mBuffer;
+ RefPtr<gfx::DataSourceSurface> mSurface;
+};
+
+} // namespace wr
+} // namespace mozilla
+
+#endif
diff --git a/gfx/webrender_bindings/RenderCompositorSWGL.cpp b/gfx/webrender_bindings/RenderCompositorSWGL.cpp
new file mode 100644
index 0000000000..ec5be61f4d
--- /dev/null
+++ b/gfx/webrender_bindings/RenderCompositorSWGL.cpp
@@ -0,0 +1,242 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "RenderCompositorSWGL.h"
+
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/widget/CompositorWidget.h"
+
+namespace mozilla {
+using namespace gfx;
+
+namespace wr {
+
+/* static */
+UniquePtr<RenderCompositor> RenderCompositorSWGL::Create(
+ RefPtr<widget::CompositorWidget>&& aWidget, nsACString& aError) {
+ void* ctx = wr_swgl_create_context();
+ if (!ctx) {
+ gfxCriticalNote << "Failed SWGL context creation for WebRender";
+ return nullptr;
+ }
+ return MakeUnique<RenderCompositorSWGL>(std::move(aWidget), ctx);
+}
+
+RenderCompositorSWGL::RenderCompositorSWGL(
+ RefPtr<widget::CompositorWidget>&& aWidget, void* aContext)
+ : RenderCompositor(std::move(aWidget)), mContext(aContext) {
+ MOZ_ASSERT(mContext);
+}
+
+RenderCompositorSWGL::~RenderCompositorSWGL() {
+ wr_swgl_destroy_context(mContext);
+}
+
+void RenderCompositorSWGL::ClearMappedBuffer() {
+ mMappedData = nullptr;
+ mMappedStride = 0;
+ mDT = nullptr;
+}
+
+bool RenderCompositorSWGL::MakeCurrent() {
+ wr_swgl_make_current(mContext);
+ return true;
+}
+
+bool RenderCompositorSWGL::BeginFrame() {
+ // Set up a temporary region representing the entire window surface in case a
+ // dirty region is not supplied.
+ ClearMappedBuffer();
+ mRegion = LayoutDeviceIntRect(LayoutDeviceIntPoint(), GetBufferSize());
+ wr_swgl_make_current(mContext);
+ return true;
+}
+
+bool RenderCompositorSWGL::AllocateMappedBuffer(
+ const wr::DeviceIntRect* aOpaqueRects, size_t aNumOpaqueRects) {
+ // Request a new draw target to use from the widget...
+ MOZ_ASSERT(!mDT);
+ layers::BufferMode bufferMode = layers::BufferMode::BUFFERED;
+ mDT = mWidget->StartRemoteDrawingInRegion(mRegion, &bufferMode);
+ if (!mDT) {
+ return false;
+ }
+ mWidget->ClearBeforePaint(mDT, mRegion);
+ // Attempt to lock the underlying buffer directly from the draw target.
+ // Verify that the size at least matches what the widget claims and that
+ // the format is BGRA8 as SWGL requires.
+ uint8_t* data = nullptr;
+ gfx::IntSize size;
+ int32_t stride = 0;
+ gfx::SurfaceFormat format = gfx::SurfaceFormat::UNKNOWN;
+ if (bufferMode != layers::BufferMode::BUFFERED && !mSurface &&
+ mDT->LockBits(&data, &size, &stride, &format) &&
+ (format != gfx::SurfaceFormat::B8G8R8A8 &&
+ format != gfx::SurfaceFormat::B8G8R8X8)) {
+ // We tried to lock the DT and it succeeded, but the size or format
+ // of the data is not compatible, so just release it and fall back below...
+ mDT->ReleaseBits(data);
+ data = nullptr;
+ }
+ LayoutDeviceIntRect bounds = mRegion.GetBounds();
+ // If locking succeeded above, just use that.
+ if (data) {
+ mMappedData = data;
+ mMappedStride = stride;
+ // Disambiguate whether the widget's draw target has its origin at zero or
+ // if it is offset to the dirty region origin. The DT might either enclose
+ // only the region itself, the region including the origin, or the entire
+ // widget. Thus, if the DT doesn't only enclose the region, we assume it
+ // contains the origin.
+ if (size != bounds.Size().ToUnknownSize()) {
+ // Update the bounds to include zero if the origin is at zero.
+ bounds.ExpandToEnclose(LayoutDeviceIntPoint(0, 0));
+ }
+ } else {
+ // If we couldn't lock the DT above, then allocate a data surface and map
+ // that for usage with SWGL.
+ size = bounds.Size().ToUnknownSize();
+ if (!mSurface || mSurface->GetSize() != size) {
+ mSurface = gfx::Factory::CreateDataSourceSurface(
+ size, gfx::SurfaceFormat::B8G8R8A8);
+ }
+ gfx::DataSourceSurface::MappedSurface map = {nullptr, 0};
+ if (!mSurface || !mSurface->Map(gfx::DataSourceSurface::READ_WRITE, &map)) {
+ // We failed mapping the data surface, so need to cancel the frame.
+ mWidget->EndRemoteDrawingInRegion(mDT, mRegion);
+ ClearMappedBuffer();
+ return false;
+ }
+ mMappedData = map.mData;
+ mMappedStride = map.mStride;
+ }
+ MOZ_ASSERT(mMappedData != nullptr && mMappedStride > 0);
+ wr_swgl_init_default_framebuffer(mContext, bounds.x, bounds.y, bounds.width,
+ bounds.height, mMappedStride, mMappedData);
+
+ LayoutDeviceIntRegion opaque;
+ for (size_t i = 0; i < aNumOpaqueRects; i++) {
+ const auto& rect = aOpaqueRects[i];
+ opaque.OrWith(LayoutDeviceIntRect(rect.origin.x, rect.origin.y,
+ rect.size.width, rect.size.height));
+ }
+
+ RefPtr<DrawTarget> dt = gfx::Factory::CreateDrawTargetForData(
+ BackendType::SKIA, mMappedData, bounds.Size().ToUnknownSize(),
+ mMappedStride, SurfaceFormat::B8G8R8A8, false);
+
+ LayoutDeviceIntRegion clear;
+ clear.Sub(mRegion, opaque);
+ for (auto iter = clear.RectIter(); !iter.Done(); iter.Next()) {
+ dt->ClearRect(
+ IntRectToRect((iter.Get() - bounds.TopLeft()).ToUnknownRect()));
+ }
+
+ return true;
+}
+
+void RenderCompositorSWGL::StartCompositing(
+ const wr::DeviceIntRect* aDirtyRects, size_t aNumDirtyRects,
+ const wr::DeviceIntRect* aOpaqueRects, size_t aNumOpaqueRects) {
+ if (mDT) {
+ // Cancel any existing buffers that might accidentally be left from updates
+ CommitMappedBuffer(false);
+ // Reset the region to the widget bounds
+ mRegion = LayoutDeviceIntRect(LayoutDeviceIntPoint(), GetBufferSize());
+ }
+ if (aNumDirtyRects) {
+ // Install the dirty rects into the bounds of the existing region
+ auto bounds = mRegion.GetBounds();
+ mRegion.SetEmpty();
+ for (size_t i = 0; i < aNumDirtyRects; i++) {
+ const auto& rect = aDirtyRects[i];
+ mRegion.OrWith(LayoutDeviceIntRect(rect.origin.x, rect.origin.y,
+ rect.size.width, rect.size.height));
+ }
+ // Ensure the region lies within the widget bounds
+ mRegion.AndWith(bounds);
+ }
+ // Now that the dirty rects have been supplied and the composition region
+ // is known, allocate and install a framebuffer encompassing the composition
+ // region.
+ if (!AllocateMappedBuffer(aOpaqueRects, aNumOpaqueRects)) {
+ gfxCriticalNote
+ << "RenderCompositorSWGL failed mapping default framebuffer";
+ // If allocation of the mapped default framebuffer failed, then just install
+ // a small temporary framebuffer so compositing can still proceed.
+ wr_swgl_init_default_framebuffer(mContext, 0, 0, 2, 2, 0, nullptr);
+ }
+}
+
+void RenderCompositorSWGL::CommitMappedBuffer(bool aDirty) {
+ if (!mDT) {
+ return;
+ }
+ // Clear out the old framebuffer in case something tries to access it after
+ // the frame.
+ wr_swgl_init_default_framebuffer(mContext, 0, 0, 0, 0, 0, nullptr);
+ // If we have a draw target at this point, mapping must have succeeded.
+ MOZ_ASSERT(mMappedData != nullptr);
+ if (mSurface) {
+ // If we're using a data surface, unmap it and draw it to the DT if there
+ // are any supplied dirty rects.
+ mSurface->Unmap();
+ if (aDirty) {
+ // The temporary source surface is always a partial region of the widget
+ // that is offset from the origin to the actual bounds of the dirty
+ // region. The destination DT may also be an offset partial region, but we
+ // must check to see if its size matches the region bounds to verify this.
+ LayoutDeviceIntRect bounds = mRegion.GetBounds();
+ gfx::IntPoint srcOffset = bounds.TopLeft().ToUnknownPoint();
+ gfx::IntPoint dstOffset = mDT->GetSize() == bounds.Size().ToUnknownSize()
+ ? srcOffset
+ : gfx::IntPoint(0, 0);
+ for (auto iter = mRegion.RectIter(); !iter.Done(); iter.Next()) {
+ gfx::IntRect dirtyRect = iter.Get().ToUnknownRect();
+ mDT->CopySurface(mSurface, dirtyRect - srcOffset,
+ dirtyRect.TopLeft() - dstOffset);
+ }
+ }
+ } else {
+ // Otherwise, we had locked the DT directly. Just release the data.
+ mDT->ReleaseBits(mMappedData);
+ }
+ // Done with the DT. Hand it back to the widget and clear out any trace of it.
+ mWidget->EndRemoteDrawingInRegion(mDT, mRegion);
+ ClearMappedBuffer();
+}
+
+void RenderCompositorSWGL::CancelFrame() { CommitMappedBuffer(false); }
+
+RenderedFrameId RenderCompositorSWGL::EndFrame(
+ const nsTArray<DeviceIntRect>& aDirtyRects) {
+ // Dirty rects have already been set inside StartCompositing. We need to keep
+ // those dirty rects exactly the same here so we supply the same exact region
+ // to EndRemoteDrawingInRegion as for StartRemoteDrawingInRegion.
+ RenderedFrameId frameId = GetNextRenderFrameId();
+ CommitMappedBuffer();
+ return frameId;
+}
+
+void RenderCompositorSWGL::Pause() {}
+
+bool RenderCompositorSWGL::Resume() { return true; }
+
+LayoutDeviceIntSize RenderCompositorSWGL::GetBufferSize() {
+ return mWidget->GetClientSize();
+}
+
+CompositorCapabilities RenderCompositorSWGL::GetCompositorCapabilities() {
+ CompositorCapabilities caps;
+
+ // don't use virtual surfaces
+ caps.virtual_surface_size = 0;
+
+ return caps;
+}
+
+} // namespace wr
+} // namespace mozilla
diff --git a/gfx/webrender_bindings/RenderCompositorSWGL.h b/gfx/webrender_bindings/RenderCompositorSWGL.h
new file mode 100644
index 0000000000..b0f53520e0
--- /dev/null
+++ b/gfx/webrender_bindings/RenderCompositorSWGL.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef MOZILLA_GFX_RENDERCOMPOSITOR_SWGL_H
+#define MOZILLA_GFX_RENDERCOMPOSITOR_SWGL_H
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/webrender/RenderCompositor.h"
+
+namespace mozilla {
+
+namespace wr {
+
+class RenderCompositorSWGL : public RenderCompositor {
+ public:
+ static UniquePtr<RenderCompositor> Create(
+ RefPtr<widget::CompositorWidget>&& aWidget, nsACString& aError);
+
+ RenderCompositorSWGL(RefPtr<widget::CompositorWidget>&& aWidget,
+ void* aContext);
+ virtual ~RenderCompositorSWGL();
+
+ void* swgl() const override { return mContext; }
+
+ bool MakeCurrent() override;
+
+ bool BeginFrame() override;
+ void CancelFrame() override;
+ RenderedFrameId EndFrame(const nsTArray<DeviceIntRect>& aDirtyRects) final;
+
+ void StartCompositing(const wr::DeviceIntRect* aDirtyRects,
+ size_t aNumDirtyRects,
+ const wr::DeviceIntRect* aOpaqueRects,
+ size_t aNumOpaqueRects) override;
+
+ bool UsePartialPresent() override { return true; }
+
+ void Pause() override;
+ bool Resume() override;
+
+ layers::WebRenderBackend BackendType() const override {
+ return layers::WebRenderBackend::SOFTWARE;
+ }
+ layers::WebRenderCompositor CompositorType() const override {
+ return layers::WebRenderCompositor::SOFTWARE;
+ }
+
+ bool SurfaceOriginIsTopLeft() override { return true; }
+
+ LayoutDeviceIntSize GetBufferSize() override;
+
+ bool SupportsExternalBufferTextures() const override { return true; }
+
+ // Interface for wr::Compositor
+ CompositorCapabilities GetCompositorCapabilities() override;
+
+ private:
+ void* mContext = nullptr;
+ RefPtr<gfx::DrawTarget> mDT;
+ LayoutDeviceIntRegion mRegion;
+ RefPtr<gfx::DataSourceSurface> mSurface;
+ uint8_t* mMappedData = nullptr;
+ int32_t mMappedStride = 0;
+
+ void ClearMappedBuffer();
+
+ bool AllocateMappedBuffer(const wr::DeviceIntRect* aOpaqueRects,
+ size_t aNumOpaqueRects);
+
+ void CommitMappedBuffer(bool aDirty = true);
+};
+
+} // namespace wr
+} // namespace mozilla
+
+#endif
diff --git a/gfx/webrender_bindings/RenderD3D11TextureHost.cpp b/gfx/webrender_bindings/RenderD3D11TextureHost.cpp
new file mode 100644
index 0000000000..c94692e94e
--- /dev/null
+++ b/gfx/webrender_bindings/RenderD3D11TextureHost.cpp
@@ -0,0 +1,708 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "RenderD3D11TextureHost.h"
+
+#include <d3d11.h>
+
+#include "GLContextEGL.h"
+#include "GLLibraryEGL.h"
+#include "ScopedGLHelpers.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/gfx/Logging.h"
+
+namespace mozilla {
+namespace wr {
+
+RenderDXGITextureHost::RenderDXGITextureHost(WindowsHandle aHandle,
+ gfx::SurfaceFormat aFormat,
+ gfx::YUVColorSpace aYUVColorSpace,
+ gfx::ColorRange aColorRange,
+ gfx::IntSize aSize)
+ : mHandle(aHandle),
+ mSurface(0),
+ mStream(0),
+ mTextureHandle{0},
+ mFormat(aFormat),
+ mYUVColorSpace(aYUVColorSpace),
+ mColorRange(aColorRange),
+ mSize(aSize),
+ mLocked(false) {
+ MOZ_COUNT_CTOR_INHERITED(RenderDXGITextureHost, RenderTextureHost);
+ MOZ_ASSERT((mFormat != gfx::SurfaceFormat::NV12 &&
+ mFormat != gfx::SurfaceFormat::P010 &&
+ mFormat != gfx::SurfaceFormat::P016) ||
+ (mSize.width % 2 == 0 && mSize.height % 2 == 0));
+ MOZ_ASSERT(aHandle);
+}
+
+RenderDXGITextureHost::~RenderDXGITextureHost() {
+ MOZ_COUNT_DTOR_INHERITED(RenderDXGITextureHost, RenderTextureHost);
+ DeleteTextureHandle();
+}
+
+ID3D11Texture2D* RenderDXGITextureHost::GetD3D11Texture2DWithGL() {
+ if (mTexture) {
+ return mTexture;
+ }
+
+ if (!mGL) {
+ // SharedGL is always used on Windows with ANGLE.
+ mGL = RenderThread::Get()->SharedGL();
+ }
+
+ if (!EnsureD3D11Texture2DWithGL()) {
+ return nullptr;
+ }
+
+ return mTexture;
+}
+
+size_t RenderDXGITextureHost::GetPlaneCount() const {
+ if (mFormat == gfx::SurfaceFormat::NV12 ||
+ mFormat == gfx::SurfaceFormat::P010 ||
+ mFormat == gfx::SurfaceFormat::P016) {
+ return 2;
+ }
+ return 1;
+}
+
+template <typename T>
+static bool MapTexture(T* aHost, RenderCompositor* aCompositor,
+ RefPtr<ID3D11Texture2D>& aTexture,
+ RefPtr<ID3D11DeviceContext>& aDeviceContext,
+ RefPtr<ID3D11Texture2D>& aCpuTexture,
+ D3D11_MAPPED_SUBRESOURCE& aMappedSubresource) {
+ RenderCompositorD3D11SWGL* compositor =
+ aCompositor->AsRenderCompositorD3D11SWGL();
+ if (!compositor) {
+ return false;
+ }
+
+ if (!aHost->EnsureD3D11Texture2D(compositor->GetDevice())) {
+ return false;
+ }
+
+ if (!aHost->LockInternal()) {
+ return false;
+ }
+
+ D3D11_TEXTURE2D_DESC textureDesc = {0};
+ aTexture->GetDesc(&textureDesc);
+
+ compositor->GetDevice()->GetImmediateContext(getter_AddRefs(aDeviceContext));
+
+ textureDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
+ textureDesc.Usage = D3D11_USAGE_STAGING;
+ textureDesc.BindFlags = 0;
+ textureDesc.MiscFlags = 0;
+ textureDesc.MipLevels = 1;
+ HRESULT hr = compositor->GetDevice()->CreateTexture2D(
+ &textureDesc, nullptr, getter_AddRefs(aCpuTexture));
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ aDeviceContext->CopyResource(aCpuTexture, aTexture);
+ aHost->Unlock();
+
+ hr = aDeviceContext->Map(aCpuTexture, 0, D3D11_MAP_READ, 0,
+ &aMappedSubresource);
+ return SUCCEEDED(hr);
+}
+
+bool RenderDXGITextureHost::MapPlane(RenderCompositor* aCompositor,
+ uint8_t aChannelIndex,
+ PlaneInfo& aPlaneInfo) {
+ // TODO: We currently readback from the GPU texture into a new
+ // staging texture every time this is mapped. We might be better
+ // off retaining the mapped memory to trade performance for memory
+ // usage.
+ if (!mCpuTexture && !MapTexture(this, aCompositor, mTexture, mDeviceContext,
+ mCpuTexture, mMappedSubresource)) {
+ return false;
+ }
+
+ aPlaneInfo.mSize = GetSize(aChannelIndex);
+ aPlaneInfo.mStride = mMappedSubresource.RowPitch;
+ aPlaneInfo.mData = mMappedSubresource.pData;
+
+ // If this is the second plane, then offset the data pointer by the
+ // size of the first plane.
+ if (aChannelIndex == 1) {
+ aPlaneInfo.mData =
+ (uint8_t*)aPlaneInfo.mData + aPlaneInfo.mStride * GetSize(0).height;
+ }
+ return true;
+}
+
+void RenderDXGITextureHost::UnmapPlanes() {
+ mMappedSubresource.pData = nullptr;
+ if (mCpuTexture) {
+ mDeviceContext->Unmap(mCpuTexture, 0);
+ mCpuTexture = nullptr;
+ }
+ mDeviceContext = nullptr;
+}
+
+bool RenderDXGITextureHost::EnsureD3D11Texture2DWithGL() {
+ if (mTexture) {
+ return true;
+ }
+
+ const auto& gle = gl::GLContextEGL::Cast(mGL);
+ const auto& egl = gle->mEgl;
+
+ // Fetch the D3D11 device.
+ EGLDeviceEXT eglDevice = nullptr;
+ egl->fQueryDisplayAttribEXT(LOCAL_EGL_DEVICE_EXT, (EGLAttrib*)&eglDevice);
+ MOZ_ASSERT(eglDevice);
+ ID3D11Device* device = nullptr;
+ egl->mLib->fQueryDeviceAttribEXT(eglDevice, LOCAL_EGL_D3D11_DEVICE_ANGLE,
+ (EGLAttrib*)&device);
+ // There's a chance this might fail if we end up on d3d9 angle for some
+ // reason.
+ if (!device) {
+ gfxCriticalNote << "RenderDXGITextureHost device is not available";
+ return false;
+ }
+
+ return EnsureD3D11Texture2D(device);
+}
+
+bool RenderDXGITextureHost::EnsureD3D11Texture2D(ID3D11Device* aDevice) {
+ if (mTexture) {
+ RefPtr<ID3D11Device> device;
+ mTexture->GetDevice(getter_AddRefs(device));
+ if (aDevice != device) {
+ gfxCriticalNote << "RenderDXGITextureHost uses obsoleted device";
+ return false;
+ }
+ return true;
+ }
+
+ // Get the D3D11 texture from shared handle.
+ HRESULT hr = aDevice->OpenSharedResource(
+ (HANDLE)mHandle, __uuidof(ID3D11Texture2D),
+ (void**)(ID3D11Texture2D**)getter_AddRefs(mTexture));
+ if (FAILED(hr)) {
+ MOZ_ASSERT(false,
+ "RenderDXGITextureHost::EnsureLockable(): Failed to open shared "
+ "texture");
+ gfxCriticalNote
+ << "RenderDXGITextureHost Failed to open shared texture, hr="
+ << gfx::hexa(hr);
+ return false;
+ }
+ MOZ_ASSERT(mTexture.get());
+ mTexture->QueryInterface((IDXGIKeyedMutex**)getter_AddRefs(mKeyedMutex));
+ return true;
+}
+
+bool RenderDXGITextureHost::EnsureLockable(wr::ImageRendering aRendering) {
+ if (mTextureHandle[0]) {
+ // Update filter if filter was changed.
+ if (IsFilterUpdateNecessary(aRendering)) {
+ ActivateBindAndTexParameteri(mGL, LOCAL_GL_TEXTURE0,
+ LOCAL_GL_TEXTURE_EXTERNAL_OES,
+ mTextureHandle[0], aRendering);
+ // Cache new rendering filter.
+ mCachedRendering = aRendering;
+ // NV12 and P016 uses two handles.
+ if (mFormat == gfx::SurfaceFormat::NV12 ||
+ mFormat == gfx::SurfaceFormat::P010 ||
+ mFormat == gfx::SurfaceFormat::P016) {
+ ActivateBindAndTexParameteri(mGL, LOCAL_GL_TEXTURE1,
+ LOCAL_GL_TEXTURE_EXTERNAL_OES,
+ mTextureHandle[1], aRendering);
+ }
+ }
+ return true;
+ }
+
+ const auto& gle = gl::GLContextEGL::Cast(mGL);
+ const auto& egl = gle->mEgl;
+
+ // We use EGLStream to get the converted gl handle from d3d texture. The
+ // NV_stream_consumer_gltexture_yuv and ANGLE_stream_producer_d3d_texture
+ // could support nv12 and rgb d3d texture format.
+ if (!egl->IsExtensionSupported(
+ gl::EGLExtension::NV_stream_consumer_gltexture_yuv) ||
+ !egl->IsExtensionSupported(
+ gl::EGLExtension::ANGLE_stream_producer_d3d_texture)) {
+ gfxCriticalNote << "RenderDXGITextureHost egl extensions are not suppored";
+ return false;
+ }
+
+ // Get the D3D11 texture from shared handle.
+ if (!EnsureD3D11Texture2DWithGL()) {
+ return false;
+ }
+
+ // Create the EGLStream.
+ mStream = egl->fCreateStreamKHR(nullptr);
+ MOZ_ASSERT(mStream);
+
+ bool ok = true;
+ if (mFormat != gfx::SurfaceFormat::NV12 &&
+ mFormat != gfx::SurfaceFormat::P010 &&
+ mFormat != gfx::SurfaceFormat::P016) {
+ // The non-nv12 format.
+
+ mGL->fGenTextures(1, mTextureHandle);
+ ActivateBindAndTexParameteri(mGL, LOCAL_GL_TEXTURE0,
+ LOCAL_GL_TEXTURE_EXTERNAL_OES,
+ mTextureHandle[0], aRendering);
+ // Cache new rendering filter.
+ mCachedRendering = aRendering;
+ ok &=
+ bool(egl->fStreamConsumerGLTextureExternalAttribsNV(mStream, nullptr));
+ ok &= bool(egl->fCreateStreamProducerD3DTextureANGLE(mStream, nullptr));
+ } else {
+ // The nv12/p016 format.
+
+ // Setup the NV12 stream consumer/producer.
+ EGLAttrib consumerAttributes[] = {
+ LOCAL_EGL_COLOR_BUFFER_TYPE,
+ LOCAL_EGL_YUV_BUFFER_EXT,
+ LOCAL_EGL_YUV_NUMBER_OF_PLANES_EXT,
+ 2,
+ LOCAL_EGL_YUV_PLANE0_TEXTURE_UNIT_NV,
+ 0,
+ LOCAL_EGL_YUV_PLANE1_TEXTURE_UNIT_NV,
+ 1,
+ LOCAL_EGL_NONE,
+ };
+ mGL->fGenTextures(2, mTextureHandle);
+ ActivateBindAndTexParameteri(mGL, LOCAL_GL_TEXTURE0,
+ LOCAL_GL_TEXTURE_EXTERNAL_OES,
+ mTextureHandle[0], aRendering);
+ ActivateBindAndTexParameteri(mGL, LOCAL_GL_TEXTURE1,
+ LOCAL_GL_TEXTURE_EXTERNAL_OES,
+ mTextureHandle[1], aRendering);
+ // Cache new rendering filter.
+ mCachedRendering = aRendering;
+ ok &= bool(egl->fStreamConsumerGLTextureExternalAttribsNV(
+ mStream, consumerAttributes));
+ ok &= bool(egl->fCreateStreamProducerD3DTextureANGLE(mStream, nullptr));
+ }
+
+ // Insert the d3d texture.
+ ok &= bool(
+ egl->fStreamPostD3DTextureANGLE(mStream, (void*)mTexture.get(), nullptr));
+
+ if (!ok) {
+ gfxCriticalNote << "RenderDXGITextureHost init stream failed";
+ DeleteTextureHandle();
+ return false;
+ }
+
+ // Now, we could get the gl handle from the stream.
+ MOZ_ALWAYS_TRUE(egl->fStreamConsumerAcquireKHR(mStream));
+
+ return true;
+}
+
+wr::WrExternalImage RenderDXGITextureHost::Lock(uint8_t aChannelIndex,
+ gl::GLContext* aGL,
+ wr::ImageRendering aRendering) {
+ if (mGL.get() != aGL) {
+ // Release the texture handle in the previous gl context.
+ DeleteTextureHandle();
+ mGL = aGL;
+ }
+
+ if (!mGL) {
+ // XXX Software WebRender is not handled yet.
+ // Software WebRender does not provide GLContext
+ gfxCriticalNoteOnce
+ << "Software WebRender is not suppored by RenderDXGITextureHost.";
+ return InvalidToWrExternalImage();
+ }
+
+ if (!EnsureLockable(aRendering)) {
+ return InvalidToWrExternalImage();
+ }
+
+ if (!LockInternal()) {
+ return InvalidToWrExternalImage();
+ }
+
+ gfx::IntSize size = GetSize(aChannelIndex);
+ return NativeTextureToWrExternalImage(GetGLHandle(aChannelIndex), 0, 0,
+ size.width, size.height);
+}
+
+bool RenderDXGITextureHost::LockInternal() {
+ if (!mLocked) {
+ if (mKeyedMutex) {
+ HRESULT hr = mKeyedMutex->AcquireSync(0, 10000);
+ if (hr != S_OK) {
+ gfxCriticalError() << "RenderDXGITextureHost AcquireSync timeout, hr="
+ << gfx::hexa(hr);
+ return false;
+ }
+ }
+ mLocked = true;
+ }
+ return true;
+}
+
+void RenderDXGITextureHost::Unlock() {
+ if (mLocked) {
+ if (mKeyedMutex) {
+ mKeyedMutex->ReleaseSync(0);
+ }
+ mLocked = false;
+ }
+}
+
+void RenderDXGITextureHost::ClearCachedResources() {
+ DeleteTextureHandle();
+ mGL = nullptr;
+}
+
+void RenderDXGITextureHost::DeleteTextureHandle() {
+ if (mTextureHandle[0] == 0) {
+ return;
+ }
+
+ MOZ_ASSERT(mGL.get());
+ if (!mGL) {
+ return;
+ }
+
+ if (mGL->MakeCurrent()) {
+ mGL->fDeleteTextures(2, mTextureHandle);
+
+ const auto& gle = gl::GLContextEGL::Cast(mGL);
+ const auto& egl = gle->mEgl;
+ if (mSurface) {
+ egl->fDestroySurface(mSurface);
+ }
+ if (mStream) {
+ egl->fDestroyStreamKHR(mStream);
+ }
+ }
+
+ for (int i = 0; i < 2; ++i) {
+ mTextureHandle[i] = 0;
+ }
+
+ mTexture = nullptr;
+ mKeyedMutex = nullptr;
+ mSurface = 0;
+ mStream = 0;
+}
+
+GLuint RenderDXGITextureHost::GetGLHandle(uint8_t aChannelIndex) const {
+ MOZ_ASSERT(((mFormat == gfx::SurfaceFormat::NV12 ||
+ mFormat == gfx::SurfaceFormat::P010 ||
+ mFormat == gfx::SurfaceFormat::P016) &&
+ aChannelIndex < 2) ||
+ aChannelIndex < 1);
+ return mTextureHandle[aChannelIndex];
+}
+
+gfx::IntSize RenderDXGITextureHost::GetSize(uint8_t aChannelIndex) const {
+ MOZ_ASSERT(((mFormat == gfx::SurfaceFormat::NV12 ||
+ mFormat == gfx::SurfaceFormat::P010 ||
+ mFormat == gfx::SurfaceFormat::P016) &&
+ aChannelIndex < 2) ||
+ aChannelIndex < 1);
+
+ if (aChannelIndex == 0) {
+ return mSize;
+ } else {
+ // The CbCr channel size is a half of Y channel size in NV12 format.
+ return mSize / 2;
+ }
+}
+
+RenderDXGIYCbCrTextureHost::RenderDXGIYCbCrTextureHost(
+ WindowsHandle (&aHandles)[3], gfx::YUVColorSpace aYUVColorSpace,
+ gfx::ColorDepth aColorDepth, gfx::ColorRange aColorRange,
+ gfx::IntSize aSizeY, gfx::IntSize aSizeCbCr)
+ : mHandles{aHandles[0], aHandles[1], aHandles[2]},
+ mSurfaces{0},
+ mStreams{0},
+ mTextureHandles{0},
+ mYUVColorSpace(aYUVColorSpace),
+ mColorDepth(aColorDepth),
+ mColorRange(aColorRange),
+ mSizeY(aSizeY),
+ mSizeCbCr(aSizeCbCr),
+ mLocked(false) {
+ MOZ_COUNT_CTOR_INHERITED(RenderDXGIYCbCrTextureHost, RenderTextureHost);
+ // Assume the chroma planes are rounded up if the luma plane is odd sized.
+ MOZ_ASSERT((mSizeCbCr.width == mSizeY.width ||
+ mSizeCbCr.width == (mSizeY.width + 1) >> 1) &&
+ (mSizeCbCr.height == mSizeY.height ||
+ mSizeCbCr.height == (mSizeY.height + 1) >> 1));
+ MOZ_ASSERT(aHandles[0] && aHandles[1] && aHandles[2]);
+}
+
+bool RenderDXGIYCbCrTextureHost::MapPlane(RenderCompositor* aCompositor,
+ uint8_t aChannelIndex,
+ PlaneInfo& aPlaneInfo) {
+ D3D11_MAPPED_SUBRESOURCE mappedSubresource;
+ if (!MapTexture(this, aCompositor, mTextures[aChannelIndex], mDeviceContext,
+ mCpuTexture[aChannelIndex], mappedSubresource)) {
+ return false;
+ }
+
+ aPlaneInfo.mSize = GetSize(aChannelIndex);
+ aPlaneInfo.mStride = mappedSubresource.RowPitch;
+ aPlaneInfo.mData = mappedSubresource.pData;
+ return true;
+}
+
+void RenderDXGIYCbCrTextureHost::UnmapPlanes() {
+ for (uint32_t i = 0; i < 3; i++) {
+ if (mCpuTexture[i]) {
+ mDeviceContext->Unmap(mCpuTexture[i], 0);
+ mCpuTexture[i] = nullptr;
+ }
+ }
+ mDeviceContext = nullptr;
+}
+
+RenderDXGIYCbCrTextureHost::~RenderDXGIYCbCrTextureHost() {
+ MOZ_COUNT_DTOR_INHERITED(RenderDXGIYCbCrTextureHost, RenderTextureHost);
+ DeleteTextureHandle();
+}
+
+bool RenderDXGIYCbCrTextureHost::EnsureLockable(wr::ImageRendering aRendering) {
+ if (mTextureHandles[0]) {
+ // Update filter if filter was changed.
+ if (IsFilterUpdateNecessary(aRendering)) {
+ for (int i = 0; i < 3; ++i) {
+ ActivateBindAndTexParameteri(mGL, LOCAL_GL_TEXTURE0 + i,
+ LOCAL_GL_TEXTURE_EXTERNAL_OES,
+ mTextureHandles[i], aRendering);
+ // Cache new rendering filter.
+ mCachedRendering = aRendering;
+ }
+ }
+ return true;
+ }
+
+ const auto& gle = gl::GLContextEGL::Cast(mGL);
+ const auto& egl = gle->mEgl;
+
+ // The eglCreatePbufferFromClientBuffer doesn't support R8 format, so we
+ // use EGLStream to get the converted gl handle from d3d R8 texture.
+
+ if (!egl->IsExtensionSupported(
+ gl::EGLExtension::NV_stream_consumer_gltexture_yuv) ||
+ !egl->IsExtensionSupported(
+ gl::EGLExtension::ANGLE_stream_producer_d3d_texture)) {
+ gfxCriticalNote
+ << "RenderDXGIYCbCrTextureHost egl extensions are not suppored";
+ return false;
+ }
+
+ // Fetch the D3D11 device.
+ EGLDeviceEXT eglDevice = nullptr;
+ egl->fQueryDisplayAttribEXT(LOCAL_EGL_DEVICE_EXT, (EGLAttrib*)&eglDevice);
+ MOZ_ASSERT(eglDevice);
+ ID3D11Device* device = nullptr;
+ egl->mLib->fQueryDeviceAttribEXT(eglDevice, LOCAL_EGL_D3D11_DEVICE_ANGLE,
+ (EGLAttrib*)&device);
+ // There's a chance this might fail if we end up on d3d9 angle for some
+ // reason.
+ if (!device) {
+ gfxCriticalNote << "RenderDXGIYCbCrTextureHost device is not available";
+ return false;
+ }
+
+ EnsureD3D11Texture2D(device);
+
+ mGL->fGenTextures(3, mTextureHandles);
+ bool ok = true;
+ for (int i = 0; i < 3; ++i) {
+ ActivateBindAndTexParameteri(mGL, LOCAL_GL_TEXTURE0 + i,
+ LOCAL_GL_TEXTURE_EXTERNAL_OES,
+ mTextureHandles[i], aRendering);
+ // Cache new rendering filter.
+ mCachedRendering = aRendering;
+
+ // Create the EGLStream.
+ mStreams[i] = egl->fCreateStreamKHR(nullptr);
+ MOZ_ASSERT(mStreams[i]);
+
+ ok &= bool(
+ egl->fStreamConsumerGLTextureExternalAttribsNV(mStreams[i], nullptr));
+ ok &= bool(egl->fCreateStreamProducerD3DTextureANGLE(mStreams[i], nullptr));
+
+ // Insert the R8 texture.
+ ok &= bool(egl->fStreamPostD3DTextureANGLE(
+ mStreams[i], (void*)mTextures[i].get(), nullptr));
+
+ // Now, we could get the R8 gl handle from the stream.
+ MOZ_ALWAYS_TRUE(egl->fStreamConsumerAcquireKHR(mStreams[i]));
+ }
+
+ if (!ok) {
+ gfxCriticalNote << "RenderDXGIYCbCrTextureHost init stream failed";
+ DeleteTextureHandle();
+ return false;
+ }
+
+ return true;
+}
+
+bool RenderDXGIYCbCrTextureHost::EnsureD3D11Texture2D(ID3D11Device* aDevice) {
+ if (mTextures[0]) {
+ RefPtr<ID3D11Device> device;
+ mTextures[0]->GetDevice(getter_AddRefs(device));
+ if (aDevice != device) {
+ gfxCriticalNote << "RenderDXGIYCbCrTextureHost uses obsoleted device";
+ return false;
+ }
+ }
+
+ if (mTextureHandles[0]) {
+ return true;
+ }
+
+ for (int i = 0; i < 3; ++i) {
+ // Get the R8 D3D11 texture from shared handle.
+ HRESULT hr = aDevice->OpenSharedResource(
+ (HANDLE)mHandles[i], __uuidof(ID3D11Texture2D),
+ (void**)(ID3D11Texture2D**)getter_AddRefs(mTextures[i]));
+ if (FAILED(hr)) {
+ NS_WARNING(
+ "RenderDXGIYCbCrTextureHost::EnsureLockable(): Failed to open "
+ "shared "
+ "texture");
+ gfxCriticalNote
+ << "RenderDXGIYCbCrTextureHost Failed to open shared texture, hr="
+ << gfx::hexa(hr);
+ return false;
+ }
+ }
+
+ for (int i = 0; i < 3; ++i) {
+ mTextures[i]->QueryInterface(
+ (IDXGIKeyedMutex**)getter_AddRefs(mKeyedMutexs[i]));
+ }
+ return true;
+}
+
+bool RenderDXGIYCbCrTextureHost::LockInternal() {
+ if (!mLocked) {
+ if (mKeyedMutexs[0]) {
+ for (const auto& mutex : mKeyedMutexs) {
+ HRESULT hr = mutex->AcquireSync(0, 10000);
+ if (hr != S_OK) {
+ gfxCriticalError()
+ << "RenderDXGIYCbCrTextureHost AcquireSync timeout, hr="
+ << gfx::hexa(hr);
+ return false;
+ }
+ }
+ }
+ mLocked = true;
+ }
+ return true;
+}
+
+wr::WrExternalImage RenderDXGIYCbCrTextureHost::Lock(
+ uint8_t aChannelIndex, gl::GLContext* aGL, wr::ImageRendering aRendering) {
+ if (mGL.get() != aGL) {
+ // Release the texture handle in the previous gl context.
+ DeleteTextureHandle();
+ mGL = aGL;
+ }
+
+ if (!mGL) {
+ // XXX Software WebRender is not handled yet.
+ // Software WebRender does not provide GLContext
+ gfxCriticalNoteOnce << "Software WebRender is not suppored by "
+ "RenderDXGIYCbCrTextureHost.";
+ return InvalidToWrExternalImage();
+ }
+
+ if (!EnsureLockable(aRendering)) {
+ return InvalidToWrExternalImage();
+ }
+
+ if (!LockInternal()) {
+ return InvalidToWrExternalImage();
+ }
+
+ gfx::IntSize size = GetSize(aChannelIndex);
+ return NativeTextureToWrExternalImage(GetGLHandle(aChannelIndex), 0, 0,
+ size.width, size.height);
+}
+
+void RenderDXGIYCbCrTextureHost::Unlock() {
+ if (mLocked) {
+ if (mKeyedMutexs[0]) {
+ for (const auto& mutex : mKeyedMutexs) {
+ mutex->ReleaseSync(0);
+ }
+ }
+ mLocked = false;
+ }
+}
+
+void RenderDXGIYCbCrTextureHost::ClearCachedResources() {
+ DeleteTextureHandle();
+ mGL = nullptr;
+}
+
+GLuint RenderDXGIYCbCrTextureHost::GetGLHandle(uint8_t aChannelIndex) const {
+ MOZ_ASSERT(aChannelIndex < 3);
+
+ return mTextureHandles[aChannelIndex];
+}
+
+gfx::IntSize RenderDXGIYCbCrTextureHost::GetSize(uint8_t aChannelIndex) const {
+ MOZ_ASSERT(aChannelIndex < 3);
+
+ if (aChannelIndex == 0) {
+ return mSizeY;
+ } else {
+ return mSizeCbCr;
+ }
+}
+
+void RenderDXGIYCbCrTextureHost::DeleteTextureHandle() {
+ if (mTextureHandles[0] == 0) {
+ return;
+ }
+
+ MOZ_ASSERT(mGL.get());
+ if (!mGL) {
+ return;
+ }
+
+ if (mGL->MakeCurrent()) {
+ mGL->fDeleteTextures(3, mTextureHandles);
+
+ const auto& gle = gl::GLContextEGL::Cast(mGL);
+ const auto& egl = gle->mEgl;
+ for (int i = 0; i < 3; ++i) {
+ mTextureHandles[i] = 0;
+ mTextures[i] = nullptr;
+ mKeyedMutexs[i] = nullptr;
+
+ if (mSurfaces[i]) {
+ egl->fDestroySurface(mSurfaces[i]);
+ mSurfaces[i] = 0;
+ }
+ if (mStreams[i]) {
+ egl->fDestroyStreamKHR(mStreams[i]);
+ mStreams[i] = 0;
+ }
+ }
+ }
+}
+
+} // namespace wr
+} // namespace mozilla
diff --git a/gfx/webrender_bindings/RenderD3D11TextureHost.h b/gfx/webrender_bindings/RenderD3D11TextureHost.h
new file mode 100644
index 0000000000..853fe896d2
--- /dev/null
+++ b/gfx/webrender_bindings/RenderD3D11TextureHost.h
@@ -0,0 +1,181 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef MOZILLA_GFX_RENDERD3D11TEXTUREHOST_H
+#define MOZILLA_GFX_RENDERD3D11TEXTUREHOST_H
+
+#include "GLTypes.h"
+#include "RenderTextureHostSWGL.h"
+
+struct ID3D11Texture2D;
+struct IDXGIKeyedMutex;
+
+namespace mozilla {
+
+namespace wr {
+
+class RenderDXGITextureHost final : public RenderTextureHostSWGL {
+ public:
+ explicit RenderDXGITextureHost(WindowsHandle aHandle,
+ gfx::SurfaceFormat aFormat,
+ gfx::YUVColorSpace aYUVColorSpace,
+ gfx::ColorRange aColorRange,
+ gfx::IntSize aSize);
+
+ wr::WrExternalImage Lock(uint8_t aChannelIndex, gl::GLContext* aGL,
+ wr::ImageRendering aRendering) override;
+ void Unlock() override;
+ void ClearCachedResources() override;
+
+ gfx::IntSize GetSize(uint8_t aChannelIndex) const;
+ GLuint GetGLHandle(uint8_t aChannelIndex) const;
+
+ bool SyncObjectNeeded() override { return true; }
+
+ RenderDXGITextureHost* AsRenderDXGITextureHost() override { return this; }
+
+ gfx::ColorRange GetColorRange() const { return mColorRange; }
+
+ ID3D11Texture2D* GetD3D11Texture2DWithGL();
+ ID3D11Texture2D* GetD3D11Texture2D() { return mTexture; }
+
+ // RenderTextureHostSWGL
+ gfx::SurfaceFormat GetFormat() const override { return mFormat; }
+ gfx::ColorDepth GetColorDepth() const override {
+ if (mFormat == gfx::SurfaceFormat::P010) {
+ return gfx::ColorDepth::COLOR_10;
+ }
+ if (mFormat == gfx::SurfaceFormat::P016) {
+ return gfx::ColorDepth::COLOR_16;
+ }
+ return gfx::ColorDepth::COLOR_8;
+ }
+ size_t GetPlaneCount() const override;
+ bool MapPlane(RenderCompositor* aCompositor, uint8_t aChannelIndex,
+ PlaneInfo& aPlaneInfo) override;
+ void UnmapPlanes() override;
+ gfx::YUVColorSpace GetYUVColorSpace() const override {
+ return mYUVColorSpace;
+ }
+
+ bool EnsureD3D11Texture2D(ID3D11Device* aDevice);
+ bool LockInternal();
+
+ private:
+ virtual ~RenderDXGITextureHost();
+
+ bool EnsureD3D11Texture2DWithGL();
+ bool EnsureLockable(wr::ImageRendering aRendering);
+
+ void DeleteTextureHandle();
+
+ RefPtr<gl::GLContext> mGL;
+
+ WindowsHandle mHandle;
+ RefPtr<ID3D11Texture2D> mTexture;
+ RefPtr<IDXGIKeyedMutex> mKeyedMutex;
+
+ // Temporary state between MapPlane and UnmapPlanes.
+ RefPtr<ID3D11DeviceContext> mDeviceContext;
+ RefPtr<ID3D11Texture2D> mCpuTexture;
+ D3D11_MAPPED_SUBRESOURCE mMappedSubresource;
+
+ EGLSurface mSurface;
+ EGLStreamKHR mStream;
+
+ // We could use NV12 format for this texture. So, we might have 2 gl texture
+ // handles for Y and CbCr data.
+ GLuint mTextureHandle[2];
+
+ const gfx::SurfaceFormat mFormat;
+ const gfx::YUVColorSpace mYUVColorSpace;
+ const gfx::ColorRange mColorRange;
+ const gfx::IntSize mSize;
+
+ bool mLocked;
+};
+
+class RenderDXGIYCbCrTextureHost final : public RenderTextureHostSWGL {
+ public:
+ explicit RenderDXGIYCbCrTextureHost(WindowsHandle (&aHandles)[3],
+ gfx::YUVColorSpace aYUVColorSpace,
+ gfx::ColorDepth aColorDepth,
+ gfx::ColorRange aColorRange,
+ gfx::IntSize aSizeY,
+ gfx::IntSize aSizeCbCr);
+
+ RenderDXGIYCbCrTextureHost* AsRenderDXGIYCbCrTextureHost() override {
+ return this;
+ }
+
+ wr::WrExternalImage Lock(uint8_t aChannelIndex, gl::GLContext* aGL,
+ wr::ImageRendering aRendering) override;
+ void Unlock() override;
+ void ClearCachedResources() override;
+
+ gfx::IntSize GetSize(uint8_t aChannelIndex) const;
+ GLuint GetGLHandle(uint8_t aChannelIndex) const;
+
+ bool SyncObjectNeeded() override { return true; }
+
+ gfx::ColorRange GetColorRange() const { return mColorRange; }
+
+ // RenderTextureHostSWGL
+ gfx::SurfaceFormat GetFormat() const override {
+ return gfx::SurfaceFormat::YUV;
+ }
+ gfx::ColorDepth GetColorDepth() const override { return mColorDepth; }
+ size_t GetPlaneCount() const override { return 3; }
+ bool MapPlane(RenderCompositor* aCompositor, uint8_t aChannelIndex,
+ PlaneInfo& aPlaneInfo) override;
+ void UnmapPlanes() override;
+ gfx::YUVColorSpace GetYUVColorSpace() const override {
+ return mYUVColorSpace;
+ }
+
+ bool EnsureD3D11Texture2D(ID3D11Device* aDevice);
+ bool LockInternal();
+
+ ID3D11Texture2D* GetD3D11Texture2D(uint8_t aChannelIndex) {
+ return mTextures[aChannelIndex];
+ }
+
+ private:
+ virtual ~RenderDXGIYCbCrTextureHost();
+
+ bool EnsureLockable(wr::ImageRendering aRendering);
+
+ void DeleteTextureHandle();
+
+ RefPtr<gl::GLContext> mGL;
+
+ WindowsHandle mHandles[3];
+ RefPtr<ID3D11Texture2D> mTextures[3];
+ RefPtr<IDXGIKeyedMutex> mKeyedMutexs[3];
+
+ EGLSurface mSurfaces[3];
+ EGLStreamKHR mStreams[3];
+
+ // The gl handles for Y, Cb and Cr data.
+ GLuint mTextureHandles[3];
+
+ // Temporary state between MapPlane and UnmapPlanes.
+ RefPtr<ID3D11DeviceContext> mDeviceContext;
+ RefPtr<ID3D11Texture2D> mCpuTexture[3];
+
+ gfx::YUVColorSpace mYUVColorSpace;
+ gfx::ColorDepth mColorDepth;
+ gfx::ColorRange mColorRange;
+ gfx::IntSize mSizeY;
+ gfx::IntSize mSizeCbCr;
+
+ bool mLocked;
+};
+
+} // namespace wr
+} // namespace mozilla
+
+#endif // MOZILLA_GFX_RENDERD3D11TEXTUREHOST_H
diff --git a/gfx/webrender_bindings/RenderDMABUFTextureHost.cpp b/gfx/webrender_bindings/RenderDMABUFTextureHost.cpp
new file mode 100644
index 0000000000..22646f1225
--- /dev/null
+++ b/gfx/webrender_bindings/RenderDMABUFTextureHost.cpp
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "RenderDMABUFTextureHost.h"
+
+#include "GLContextEGL.h"
+#include "mozilla/gfx/Logging.h"
+#include "ScopedGLHelpers.h"
+
+namespace mozilla::wr {
+
+RenderDMABUFTextureHost::RenderDMABUFTextureHost(DMABufSurface* aSurface)
+ : mSurface(aSurface) {
+ MOZ_COUNT_CTOR_INHERITED(RenderDMABUFTextureHost, RenderTextureHost);
+}
+
+RenderDMABUFTextureHost::~RenderDMABUFTextureHost() {
+ MOZ_COUNT_DTOR_INHERITED(RenderDMABUFTextureHost, RenderTextureHost);
+ DeleteTextureHandle();
+}
+
+wr::WrExternalImage RenderDMABUFTextureHost::Lock(
+ uint8_t aChannelIndex, gl::GLContext* aGL, wr::ImageRendering aRendering) {
+ if (mGL.get() != aGL) {
+ if (mGL) {
+ // This should not happen. EGLImage is created only in
+ // parent process.
+ MOZ_ASSERT_UNREACHABLE("Unexpected GL context");
+ return InvalidToWrExternalImage();
+ }
+ mGL = aGL;
+ }
+
+ if (!mGL || !mGL->MakeCurrent()) {
+ return InvalidToWrExternalImage();
+ }
+
+ bool bindTexture = IsFilterUpdateNecessary(aRendering);
+
+ if (!mSurface->GetTexture(aChannelIndex)) {
+ if (!mSurface->CreateTexture(mGL, aChannelIndex)) {
+ return InvalidToWrExternalImage();
+ }
+ bindTexture = true;
+ }
+
+ if (bindTexture) {
+ // Cache new rendering filter.
+ mCachedRendering = aRendering;
+ ActivateBindAndTexParameteri(mGL, LOCAL_GL_TEXTURE0, LOCAL_GL_TEXTURE_2D,
+ mSurface->GetTexture(aChannelIndex),
+ aRendering);
+ }
+
+ return NativeTextureToWrExternalImage(mSurface->GetTexture(aChannelIndex), 0,
+ 0, mSurface->GetWidth(aChannelIndex),
+ mSurface->GetHeight(aChannelIndex));
+}
+
+void RenderDMABUFTextureHost::Unlock() {}
+
+void RenderDMABUFTextureHost::DeleteTextureHandle() {
+ mSurface->ReleaseTextures();
+}
+
+void RenderDMABUFTextureHost::ClearCachedResources() {
+ DeleteTextureHandle();
+ mGL = nullptr;
+}
+
+} // namespace mozilla::wr
diff --git a/gfx/webrender_bindings/RenderDMABUFTextureHost.h b/gfx/webrender_bindings/RenderDMABUFTextureHost.h
new file mode 100644
index 0000000000..b7dc1ee85b
--- /dev/null
+++ b/gfx/webrender_bindings/RenderDMABUFTextureHost.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef MOZILLA_GFX_RENDERDMABUFTEXTUREHOST_H
+#define MOZILLA_GFX_RENDERDMABUFTEXTUREHOST_H
+
+#include "mozilla/layers/TextureHostOGL.h"
+#include "RenderTextureHost.h"
+#include "mozilla/widget/DMABufSurface.h"
+
+namespace mozilla {
+
+namespace layers {
+class SurfaceDescriptorDMABuf;
+}
+
+namespace wr {
+
+class RenderDMABUFTextureHost final : public RenderTextureHost {
+ public:
+ explicit RenderDMABUFTextureHost(DMABufSurface* aSurface);
+
+ wr::WrExternalImage Lock(uint8_t aChannelIndex, gl::GLContext* aGL,
+ wr::ImageRendering aRendering) override;
+ void Unlock() override;
+ void ClearCachedResources() override;
+
+ size_t Bytes() override {
+ return mSurface->GetWidth() * mSurface->GetHeight() *
+ BytesPerPixel(mSurface->GetFormat());
+ }
+
+ private:
+ virtual ~RenderDMABUFTextureHost();
+ void DeleteTextureHandle();
+
+ RefPtr<DMABufSurface> mSurface;
+ RefPtr<gl::GLContext> mGL;
+};
+
+} // namespace wr
+} // namespace mozilla
+
+#endif // MOZILLA_GFX_RENDERDMABUFTEXTUREHOST_H
diff --git a/gfx/webrender_bindings/RenderEGLImageTextureHost.cpp b/gfx/webrender_bindings/RenderEGLImageTextureHost.cpp
new file mode 100644
index 0000000000..d2a50b7476
--- /dev/null
+++ b/gfx/webrender_bindings/RenderEGLImageTextureHost.cpp
@@ -0,0 +1,102 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "RenderEGLImageTextureHost.h"
+
+#include "mozilla/gfx/Logging.h"
+#include "GLContextEGL.h"
+#include "GLLibraryEGL.h"
+
+namespace mozilla {
+namespace wr {
+
+RenderEGLImageTextureHost::RenderEGLImageTextureHost(EGLImage aImage,
+ EGLSync aSync,
+ gfx::IntSize aSize)
+ : mImage(aImage),
+ mSync(aSync),
+ mSize(aSize),
+ mTextureTarget(LOCAL_GL_TEXTURE_2D),
+ mTextureHandle(0) {
+ MOZ_COUNT_CTOR_INHERITED(RenderEGLImageTextureHost, RenderTextureHost);
+}
+
+RenderEGLImageTextureHost::~RenderEGLImageTextureHost() {
+ MOZ_COUNT_DTOR_INHERITED(RenderEGLImageTextureHost, RenderTextureHost);
+ DeleteTextureHandle();
+}
+
+wr::WrExternalImage RenderEGLImageTextureHost::Lock(
+ uint8_t aChannelIndex, gl::GLContext* aGL, wr::ImageRendering aRendering) {
+ MOZ_ASSERT(aChannelIndex == 0);
+
+ if (mGL.get() != aGL) {
+ if (mGL) {
+ // This should not happen. SharedSurface_EGLImage is created only in
+ // parent process.
+ MOZ_ASSERT_UNREACHABLE("Unexpected GL context");
+ return InvalidToWrExternalImage();
+ }
+ mGL = aGL;
+ }
+
+ if (!mImage || !mGL || !mGL->MakeCurrent()) {
+ return InvalidToWrExternalImage();
+ }
+
+ EGLint status = LOCAL_EGL_CONDITION_SATISFIED;
+ if (mSync) {
+ const auto& gle = gl::GLContextEGL::Cast(mGL);
+ const auto& egl = gle->mEgl;
+ MOZ_ASSERT(egl->IsExtensionSupported(gl::EGLExtension::KHR_fence_sync));
+ status = egl->fClientWaitSync(mSync, 0, LOCAL_EGL_FOREVER);
+ // We do not need to delete sync here. It is deleted by
+ // SharedSurface_EGLImage.
+ mSync = 0;
+ }
+
+ if (status != LOCAL_EGL_CONDITION_SATISFIED) {
+ MOZ_ASSERT(
+ status != 0,
+ "ClientWaitSync generated an error. Has mSync already been destroyed?");
+ return InvalidToWrExternalImage();
+ }
+
+ if (!mTextureHandle) {
+ mTextureTarget = mGL->GetPreferredEGLImageTextureTarget();
+ MOZ_ASSERT(mTextureTarget == LOCAL_GL_TEXTURE_2D ||
+ mTextureTarget == LOCAL_GL_TEXTURE_EXTERNAL);
+
+ mGL->fGenTextures(1, &mTextureHandle);
+ // Cache rendering filter.
+ mCachedRendering = aRendering;
+ ActivateBindAndTexParameteri(mGL, LOCAL_GL_TEXTURE0, mTextureTarget,
+ mTextureHandle, aRendering);
+ mGL->fEGLImageTargetTexture2D(mTextureTarget, mImage);
+ } else if (IsFilterUpdateNecessary(aRendering)) {
+ // Cache new rendering filter.
+ mCachedRendering = aRendering;
+ ActivateBindAndTexParameteri(mGL, LOCAL_GL_TEXTURE0, mTextureTarget,
+ mTextureHandle, aRendering);
+ }
+
+ return NativeTextureToWrExternalImage(mTextureHandle, 0, 0, mSize.width,
+ mSize.height);
+}
+
+void RenderEGLImageTextureHost::Unlock() {}
+
+void RenderEGLImageTextureHost::DeleteTextureHandle() {
+ if (mTextureHandle) {
+ // XXX recycle gl texture, since SharedSurface_EGLImage and
+ // RenderEGLImageTextureHost is not recycled.
+ mGL->fDeleteTextures(1, &mTextureHandle);
+ mTextureHandle = 0;
+ }
+}
+
+} // namespace wr
+} // namespace mozilla
diff --git a/gfx/webrender_bindings/RenderEGLImageTextureHost.h b/gfx/webrender_bindings/RenderEGLImageTextureHost.h
new file mode 100644
index 0000000000..7ec836821b
--- /dev/null
+++ b/gfx/webrender_bindings/RenderEGLImageTextureHost.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef MOZILLA_GFX_RENDEREGLIMAGETEXTUREHOST_H
+#define MOZILLA_GFX_RENDEREGLIMAGETEXTUREHOST_H
+
+#include "mozilla/layers/TextureHostOGL.h"
+#include "RenderTextureHost.h"
+
+namespace mozilla {
+
+namespace wr {
+
+// RenderEGLImageTextureHost is created only for SharedSurface_EGLImage that is
+// created in parent process.
+class RenderEGLImageTextureHost final : public RenderTextureHost {
+ public:
+ RenderEGLImageTextureHost(EGLImage aImage, EGLSync aSync, gfx::IntSize aSize);
+
+ wr::WrExternalImage Lock(uint8_t aChannelIndex, gl::GLContext* aGL,
+ wr::ImageRendering aRendering) override;
+ void Unlock() override;
+ size_t Bytes() override {
+ // XXX: we don't have a format so we can't get bpp.
+ return mSize.width * mSize.height;
+ }
+
+ private:
+ virtual ~RenderEGLImageTextureHost();
+ void DeleteTextureHandle();
+
+ const EGLImage mImage;
+ EGLSync mSync;
+ const gfx::IntSize mSize;
+
+ RefPtr<gl::GLContext> mGL;
+ GLenum mTextureTarget;
+ GLuint mTextureHandle;
+};
+
+} // namespace wr
+} // namespace mozilla
+
+#endif // MOZILLA_GFX_RENDEREGLIMAGETEXTUREHOST_H
diff --git a/gfx/webrender_bindings/RenderExternalTextureHost.cpp b/gfx/webrender_bindings/RenderExternalTextureHost.cpp
new file mode 100644
index 0000000000..792e0021b7
--- /dev/null
+++ b/gfx/webrender_bindings/RenderExternalTextureHost.cpp
@@ -0,0 +1,202 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "RenderExternalTextureHost.h"
+
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/layers/ImageDataSerializer.h"
+
+#include "GLContext.h"
+
+namespace mozilla {
+namespace wr {
+
+RenderExternalTextureHost::RenderExternalTextureHost(
+ uint8_t* aBuffer, const layers::BufferDescriptor& aDescriptor)
+ : mBuffer(aBuffer),
+ mDescriptor(aDescriptor),
+ mInitialized(false),
+ mTextureUpdateNeeded(true) {
+ MOZ_COUNT_CTOR_INHERITED(RenderExternalTextureHost, RenderTextureHost);
+
+ switch (mDescriptor.type()) {
+ case layers::BufferDescriptor::TYCbCrDescriptor: {
+ const layers::YCbCrDescriptor& ycbcr = mDescriptor.get_YCbCrDescriptor();
+ mSize = ycbcr.ySize();
+ mFormat = gfx::SurfaceFormat::YUV;
+ break;
+ }
+ case layers::BufferDescriptor::TRGBDescriptor: {
+ const layers::RGBDescriptor& rgb = mDescriptor.get_RGBDescriptor();
+ mSize = rgb.size();
+ mFormat = rgb.format();
+ break;
+ }
+ default:
+ gfxCriticalError() << "Bad buffer host descriptor "
+ << (int)mDescriptor.type();
+ MOZ_CRASH("GFX: Bad descriptor");
+ }
+}
+
+RenderExternalTextureHost::~RenderExternalTextureHost() {
+ MOZ_COUNT_DTOR_INHERITED(RenderExternalTextureHost, RenderTextureHost);
+
+ if (NS_WARN_IF(!IsReadyForDeletion())) {
+ gfxCriticalNote << "RenderExternalTextureHost sync failed";
+ }
+
+ DeleteTextures();
+}
+
+bool RenderExternalTextureHost::CreateSurfaces() {
+ if (!IsYUV()) {
+ mSurfaces[0] = gfx::Factory::CreateWrappingDataSourceSurface(
+ GetBuffer(),
+ layers::ImageDataSerializer::GetRGBStride(
+ mDescriptor.get_RGBDescriptor()),
+ mSize, mFormat);
+ } else {
+ const layers::YCbCrDescriptor& desc = mDescriptor.get_YCbCrDescriptor();
+ const gfx::SurfaceFormat surfaceFormat =
+ SurfaceFormatForColorDepth(desc.colorDepth());
+
+ mSurfaces[0] = gfx::Factory::CreateWrappingDataSourceSurface(
+ layers::ImageDataSerializer::GetYChannel(GetBuffer(), desc),
+ desc.yStride(), desc.ySize(), surfaceFormat);
+ mSurfaces[1] = gfx::Factory::CreateWrappingDataSourceSurface(
+ layers::ImageDataSerializer::GetCbChannel(GetBuffer(), desc),
+ desc.cbCrStride(), desc.cbCrSize(), surfaceFormat);
+ mSurfaces[2] = gfx::Factory::CreateWrappingDataSourceSurface(
+ layers::ImageDataSerializer::GetCrChannel(GetBuffer(), desc),
+ desc.cbCrStride(), desc.cbCrSize(), surfaceFormat);
+ }
+
+ for (size_t i = 0; i < PlaneCount(); ++i) {
+ if (NS_WARN_IF(!mSurfaces[i])) {
+ gfxCriticalNote << "Surface is null";
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void RenderExternalTextureHost::DeleteSurfaces() {
+ for (size_t i = 0; i < PlaneCount(); ++i) {
+ mSurfaces[i] = nullptr;
+ }
+}
+
+void RenderExternalTextureHost::DeleteTextures() {
+ for (size_t i = 0; i < PlaneCount(); ++i) {
+ mTextureSources[i] = nullptr;
+ mImages[i] = InvalidToWrExternalImage();
+ }
+}
+
+bool RenderExternalTextureHost::InitializeIfNeeded() {
+ if (mInitialized) {
+ return true;
+ }
+
+ if (!GetBuffer()) {
+ // We hit some problems to get the shmem.
+ gfxCriticalNote << "GetBuffer Failed";
+ return false;
+ }
+
+ if (!CreateSurfaces()) {
+ DeleteSurfaces();
+ return false;
+ }
+
+ mInitialized = true;
+ return mInitialized;
+}
+
+bool RenderExternalTextureHost::IsReadyForDeletion() {
+ if (!mInitialized) {
+ return true;
+ }
+
+ auto& textureSource = mTextureSources[0];
+ if (textureSource) {
+ return textureSource->Sync(false);
+ }
+
+ return true;
+}
+
+wr::WrExternalImage RenderExternalTextureHost::Lock(
+ uint8_t aChannelIndex, gl::GLContext* aGL, wr::ImageRendering aRendering) {
+ if (mGL.get() != aGL) {
+ mGL = aGL;
+ mGL->MakeCurrent();
+ }
+
+ if (!mGL || !mGL->MakeCurrent()) {
+ return InvalidToWrExternalImage();
+ }
+
+ if (!InitializeIfNeeded()) {
+ return InvalidToWrExternalImage();
+ }
+
+ UpdateTextures(aRendering);
+ return mImages[aChannelIndex];
+}
+
+void RenderExternalTextureHost::PrepareForUse() { mTextureUpdateNeeded = true; }
+
+void RenderExternalTextureHost::Unlock() {}
+
+void RenderExternalTextureHost::UpdateTexture(size_t aIndex) {
+ MOZ_ASSERT(mSurfaces[aIndex]);
+
+ auto& texture = mTextureSources[aIndex];
+
+ if (texture) {
+ texture->Update(mSurfaces[aIndex]);
+ } else {
+ texture = new layers::DirectMapTextureSource(mGL, mSurfaces[aIndex]);
+
+ const GLuint handle = texture->GetTextureHandle();
+ const gfx::IntSize& size = texture->GetSize();
+ mImages[aIndex] =
+ NativeTextureToWrExternalImage(handle, 0, 0, size.width, size.height);
+ }
+
+ MOZ_ASSERT(mGL->GetError() == LOCAL_GL_NO_ERROR);
+}
+
+void RenderExternalTextureHost::UpdateTextures(wr::ImageRendering aRendering) {
+ const bool renderingChanged = IsFilterUpdateNecessary(aRendering);
+
+ if (!mTextureUpdateNeeded && !renderingChanged) {
+ // Nothing to do here.
+ return;
+ }
+
+ for (size_t i = 0; i < PlaneCount(); ++i) {
+ if (mTextureUpdateNeeded) {
+ UpdateTexture(i);
+ }
+
+ if (renderingChanged) {
+ const GLuint handle = mTextureSources[i]->GetTextureHandle();
+ ActivateBindAndTexParameteri(mGL, LOCAL_GL_TEXTURE0,
+ LOCAL_GL_TEXTURE_RECTANGLE_ARB, handle,
+ aRendering);
+ }
+ }
+
+ mTextureSources[0]->MaybeFenceTexture();
+ mTextureUpdateNeeded = false;
+}
+
+} // namespace wr
+} // namespace mozilla
diff --git a/gfx/webrender_bindings/RenderExternalTextureHost.h b/gfx/webrender_bindings/RenderExternalTextureHost.h
new file mode 100644
index 0000000000..008acef470
--- /dev/null
+++ b/gfx/webrender_bindings/RenderExternalTextureHost.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef MOZILLA_GFX_RENDEREXTERNALTEXTUREHOST_H
+#define MOZILLA_GFX_RENDEREXTERNALTEXTUREHOST_H
+
+#include "mozilla/layers/TextureHostOGL.h"
+#include "RenderTextureHost.h"
+
+namespace mozilla {
+namespace wr {
+
+/**
+ * RenderExternalTextureHost manages external textures used by WebRender on Mac.
+ * The motivation for this is to be able to use Apple Client Storage OpenGL
+ * extension, which makes it possible to avoid some copies during texture
+ * upload. This is especially helpful for high resolution video.
+ */
+class RenderExternalTextureHost final : public RenderTextureHost {
+ public:
+ RenderExternalTextureHost(uint8_t* aBuffer,
+ const layers::BufferDescriptor& aDescriptor);
+
+ wr::WrExternalImage Lock(uint8_t aChannelIndex, gl::GLContext* aGL,
+ wr::ImageRendering aRendering) override;
+ void Unlock() override;
+ void PrepareForUse() override;
+ size_t Bytes() override {
+ return mSize.width * mSize.height * BytesPerPixel(mFormat);
+ }
+
+ private:
+ ~RenderExternalTextureHost();
+
+ bool CreateSurfaces();
+ void DeleteSurfaces();
+ void DeleteTextures();
+
+ uint8_t* GetBuffer() const { return mBuffer; }
+ bool InitializeIfNeeded();
+ bool IsReadyForDeletion();
+ bool IsYUV() const { return mFormat == gfx::SurfaceFormat::YUV; }
+ size_t PlaneCount() const { return IsYUV() ? 3 : 1; }
+ void UpdateTexture(size_t aIndex);
+ void UpdateTextures(wr::ImageRendering aRendering);
+
+ uint8_t* mBuffer;
+ layers::BufferDescriptor mDescriptor;
+
+ bool mInitialized;
+ bool mTextureUpdateNeeded;
+
+ gfx::IntSize mSize;
+ gfx::SurfaceFormat mFormat;
+
+ RefPtr<gl::GLContext> mGL;
+ RefPtr<gfx::DataSourceSurface> mSurfaces[3];
+ RefPtr<layers::DirectMapTextureSource> mTextureSources[3];
+ wr::WrExternalImage mImages[3];
+};
+
+} // namespace wr
+} // namespace mozilla
+
+#endif // MOZILLA_GFX_RENDEREXTERNALTEXTUREHOST_H
diff --git a/gfx/webrender_bindings/RenderMacIOSurfaceTextureHost.cpp b/gfx/webrender_bindings/RenderMacIOSurfaceTextureHost.cpp
new file mode 100644
index 0000000000..6d2f5505c8
--- /dev/null
+++ b/gfx/webrender_bindings/RenderMacIOSurfaceTextureHost.cpp
@@ -0,0 +1,165 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "RenderMacIOSurfaceTextureHost.h"
+
+#include "GLContextCGL.h"
+#include "mozilla/gfx/Logging.h"
+#include "ScopedGLHelpers.h"
+
+namespace mozilla {
+namespace wr {
+
+static CGLError CreateTextureForPlane(uint8_t aPlaneID, gl::GLContext* aGL,
+ MacIOSurface* aSurface, GLuint* aTexture,
+ wr::ImageRendering aRendering) {
+ MOZ_ASSERT(aGL && aSurface && aTexture);
+
+ aGL->fGenTextures(1, aTexture);
+ ActivateBindAndTexParameteri(aGL, LOCAL_GL_TEXTURE0,
+ LOCAL_GL_TEXTURE_RECTANGLE_ARB, *aTexture,
+ aRendering);
+ aGL->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB, LOCAL_GL_TEXTURE_WRAP_T,
+ LOCAL_GL_CLAMP_TO_EDGE);
+ aGL->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB, LOCAL_GL_TEXTURE_WRAP_S,
+ LOCAL_GL_CLAMP_TO_EDGE);
+
+ CGLError result = kCGLNoError;
+ gfx::SurfaceFormat readFormat = gfx::SurfaceFormat::UNKNOWN;
+ result = aSurface->CGLTexImageIOSurface2D(
+ aGL, gl::GLContextCGL::Cast(aGL)->GetCGLContext(), aPlaneID, &readFormat);
+ // If this is a yuv format, the Webrender only supports YUV422 interleaving
+ // format.
+ MOZ_ASSERT(aSurface->GetFormat() != gfx::SurfaceFormat::YUV422 ||
+ readFormat == gfx::SurfaceFormat::YUV422);
+
+ return result;
+}
+
+RenderMacIOSurfaceTextureHost::RenderMacIOSurfaceTextureHost(
+ MacIOSurface* aSurface)
+ : mSurface(aSurface), mTextureHandles{0, 0, 0} {
+ MOZ_COUNT_CTOR_INHERITED(RenderMacIOSurfaceTextureHost, RenderTextureHost);
+}
+
+RenderMacIOSurfaceTextureHost::~RenderMacIOSurfaceTextureHost() {
+ MOZ_COUNT_DTOR_INHERITED(RenderMacIOSurfaceTextureHost, RenderTextureHost);
+ DeleteTextureHandle();
+}
+
+GLuint RenderMacIOSurfaceTextureHost::GetGLHandle(uint8_t aChannelIndex) const {
+ MOZ_ASSERT(mSurface);
+ MOZ_ASSERT((mSurface->GetPlaneCount() == 0)
+ ? (aChannelIndex == mSurface->GetPlaneCount())
+ : (aChannelIndex < mSurface->GetPlaneCount()));
+ return mTextureHandles[aChannelIndex];
+}
+
+gfx::IntSize RenderMacIOSurfaceTextureHost::GetSize(
+ uint8_t aChannelIndex) const {
+ MOZ_ASSERT(mSurface);
+ MOZ_ASSERT((mSurface->GetPlaneCount() == 0)
+ ? (aChannelIndex == mSurface->GetPlaneCount())
+ : (aChannelIndex < mSurface->GetPlaneCount()));
+
+ if (!mSurface) {
+ return gfx::IntSize();
+ }
+ return gfx::IntSize(mSurface->GetDevicePixelWidth(aChannelIndex),
+ mSurface->GetDevicePixelHeight(aChannelIndex));
+}
+
+wr::WrExternalImage RenderMacIOSurfaceTextureHost::Lock(
+ uint8_t aChannelIndex, gl::GLContext* aGL, wr::ImageRendering aRendering) {
+ if (mGL.get() != aGL) {
+ // release the texture handle in the previous gl context
+ DeleteTextureHandle();
+ mGL = aGL;
+ mGL->MakeCurrent();
+ }
+
+ if (!mSurface || !mGL || !mGL->MakeCurrent()) {
+ return InvalidToWrExternalImage();
+ }
+
+ if (!mTextureHandles[0]) {
+ MOZ_ASSERT(gl::GLContextCGL::Cast(mGL.get())->GetCGLContext());
+
+ mCachedRendering = aRendering;
+ // The result of GetPlaneCount() is 0 for single plane format, but it will
+ // be 2 if the format has 2 planar data.
+ CreateTextureForPlane(0, mGL, mSurface, &(mTextureHandles[0]), aRendering);
+ for (size_t i = 1; i < mSurface->GetPlaneCount(); ++i) {
+ CreateTextureForPlane(i, mGL, mSurface, &(mTextureHandles[i]),
+ aRendering);
+ }
+ // update filter if filter was changed
+ } else if (IsFilterUpdateNecessary(aRendering)) {
+ ActivateBindAndTexParameteri(aGL, LOCAL_GL_TEXTURE0,
+ LOCAL_GL_TEXTURE_RECTANGLE_ARB,
+ mTextureHandles[0], aRendering);
+ // Cache new rendering filter.
+ mCachedRendering = aRendering;
+ for (size_t i = 1; i < mSurface->GetPlaneCount(); ++i) {
+ ActivateBindAndTexParameteri(aGL, LOCAL_GL_TEXTURE0,
+ LOCAL_GL_TEXTURE_RECTANGLE_ARB,
+ mTextureHandles[i], aRendering);
+ }
+ }
+
+ gfx::IntSize size = GetSize(aChannelIndex);
+ return NativeTextureToWrExternalImage(GetGLHandle(aChannelIndex), 0, 0,
+ size.width, size.height);
+}
+
+void RenderMacIOSurfaceTextureHost::Unlock() {}
+
+void RenderMacIOSurfaceTextureHost::DeleteTextureHandle() {
+ if (mTextureHandles[0] != 0 && mGL && mGL->MakeCurrent()) {
+ // Calling glDeleteTextures on 0 isn't an error. So, just make them a single
+ // call.
+ mGL->fDeleteTextures(3, mTextureHandles);
+ for (size_t i = 0; i < 3; ++i) {
+ mTextureHandles[i] = 0;
+ }
+ }
+}
+
+size_t RenderMacIOSurfaceTextureHost::GetPlaneCount() const {
+ size_t planeCount = mSurface->GetPlaneCount();
+ return planeCount > 0 ? planeCount : 1;
+}
+
+gfx::SurfaceFormat RenderMacIOSurfaceTextureHost::GetFormat() const {
+ return mSurface->GetFormat();
+}
+
+gfx::ColorDepth RenderMacIOSurfaceTextureHost::GetColorDepth() const {
+ return gfx::ColorDepth::COLOR_8;
+}
+
+gfx::YUVColorSpace RenderMacIOSurfaceTextureHost::GetYUVColorSpace() const {
+ return mSurface->GetYUVColorSpace();
+}
+
+bool RenderMacIOSurfaceTextureHost::MapPlane(RenderCompositor* aCompositor,
+ uint8_t aChannelIndex,
+ PlaneInfo& aPlaneInfo) {
+ if (!aChannelIndex) {
+ mSurface->Lock();
+ }
+ aPlaneInfo.mData = mSurface->GetBaseAddressOfPlane(aChannelIndex);
+ aPlaneInfo.mStride = mSurface->GetBytesPerRow(aChannelIndex);
+ aPlaneInfo.mSize =
+ gfx::IntSize(mSurface->GetDevicePixelWidth(aChannelIndex),
+ mSurface->GetDevicePixelHeight(aChannelIndex));
+ return true;
+}
+
+void RenderMacIOSurfaceTextureHost::UnmapPlanes() { mSurface->Unlock(); }
+
+} // namespace wr
+} // namespace mozilla
diff --git a/gfx/webrender_bindings/RenderMacIOSurfaceTextureHost.h b/gfx/webrender_bindings/RenderMacIOSurfaceTextureHost.h
new file mode 100644
index 0000000000..3db642ed45
--- /dev/null
+++ b/gfx/webrender_bindings/RenderMacIOSurfaceTextureHost.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef MOZILLA_GFX_RENDERMACIOSURFACETEXTUREHOST_H
+#define MOZILLA_GFX_RENDERMACIOSURFACETEXTUREHOST_H
+
+#include "mozilla/gfx/MacIOSurface.h"
+#include "mozilla/layers/TextureHostOGL.h"
+#include "RenderTextureHostSWGL.h"
+
+namespace mozilla {
+
+namespace layers {
+class SurfaceDescriptorMacIOSurface;
+}
+
+namespace wr {
+
+class RenderMacIOSurfaceTextureHost final : public RenderTextureHostSWGL {
+ public:
+ explicit RenderMacIOSurfaceTextureHost(MacIOSurface* aSurface);
+
+ wr::WrExternalImage Lock(uint8_t aChannelIndex, gl::GLContext* aGL,
+ wr::ImageRendering aRendering) override;
+ void Unlock() override;
+
+ gfx::IntSize GetSize(uint8_t aChannelIndex) const;
+ GLuint GetGLHandle(uint8_t aChannelIndex) const;
+
+ RenderMacIOSurfaceTextureHost* AsRenderMacIOSurfaceTextureHost() override {
+ return this;
+ }
+
+ MacIOSurface* GetSurface() { return mSurface; }
+
+ // RenderTextureHostSWGL
+ size_t GetPlaneCount() const override;
+ gfx::SurfaceFormat GetFormat() const override;
+ gfx::ColorDepth GetColorDepth() const override;
+ gfx::YUVColorSpace GetYUVColorSpace() const override;
+ bool MapPlane(RenderCompositor* aCompositor, uint8_t aChannelIndex,
+ PlaneInfo& aPlaneInfo) override;
+ void UnmapPlanes() override;
+
+ private:
+ virtual ~RenderMacIOSurfaceTextureHost();
+ void DeleteTextureHandle();
+
+ RefPtr<MacIOSurface> mSurface;
+ RefPtr<gl::GLContext> mGL;
+ GLuint mTextureHandles[3];
+};
+
+} // namespace wr
+} // namespace mozilla
+
+#endif // MOZILLA_GFX_RENDERMACIOSURFACETEXTUREHOST_H
diff --git a/gfx/webrender_bindings/RenderSharedSurfaceTextureHost.cpp b/gfx/webrender_bindings/RenderSharedSurfaceTextureHost.cpp
new file mode 100644
index 0000000000..0c6b09dd98
--- /dev/null
+++ b/gfx/webrender_bindings/RenderSharedSurfaceTextureHost.cpp
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "RenderSharedSurfaceTextureHost.h"
+
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/layers/ImageDataSerializer.h"
+#include "mozilla/layers/SourceSurfaceSharedData.h"
+
+namespace mozilla {
+namespace wr {
+
+RenderSharedSurfaceTextureHost::RenderSharedSurfaceTextureHost(
+ gfx::SourceSurfaceSharedDataWrapper* aSurface)
+ : mSurface(aSurface), mMap(), mLocked(false) {
+ MOZ_COUNT_CTOR_INHERITED(RenderSharedSurfaceTextureHost, RenderTextureHost);
+ MOZ_ASSERT(aSurface);
+}
+
+RenderSharedSurfaceTextureHost::~RenderSharedSurfaceTextureHost() {
+ MOZ_COUNT_DTOR_INHERITED(RenderSharedSurfaceTextureHost, RenderTextureHost);
+}
+
+wr::WrExternalImage RenderSharedSurfaceTextureHost::Lock(
+ uint8_t aChannelIndex, gl::GLContext* aGL, wr::ImageRendering aRendering) {
+ if (!mLocked) {
+ if (NS_WARN_IF(!mSurface->Map(gfx::DataSourceSurface::MapType::READ_WRITE,
+ &mMap))) {
+ return InvalidToWrExternalImage();
+ }
+ mLocked = true;
+ }
+
+ return RawDataToWrExternalImage(mMap.mData,
+ mMap.mStride * mSurface->GetSize().height);
+}
+
+void RenderSharedSurfaceTextureHost::Unlock() {
+ if (mLocked) {
+ mSurface->Unmap();
+ mLocked = false;
+ }
+}
+
+size_t RenderSharedSurfaceTextureHost::Bytes() {
+ return mSurface->Stride() * mSurface->GetSize().height;
+}
+
+} // namespace wr
+} // namespace mozilla
diff --git a/gfx/webrender_bindings/RenderSharedSurfaceTextureHost.h b/gfx/webrender_bindings/RenderSharedSurfaceTextureHost.h
new file mode 100644
index 0000000000..8fdb096d54
--- /dev/null
+++ b/gfx/webrender_bindings/RenderSharedSurfaceTextureHost.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef MOZILLA_GFX_RENDERSHAREDSURFACETEXTUREHOST_H
+#define MOZILLA_GFX_RENDERSHAREDSURFACETEXTUREHOST_H
+
+#include "RenderTextureHost.h"
+
+namespace mozilla {
+namespace gfx {
+class SourceSurfaceSharedDataWrapper;
+}
+
+namespace wr {
+
+/**
+ * This class allows for surfaces managed by SharedSurfacesParent to be inserted
+ * into the render texture cache by wrapping an existing surface wrapper. These
+ * surfaces are backed by BGRA/X shared memory buffers.
+ */
+class RenderSharedSurfaceTextureHost final : public RenderTextureHost {
+ public:
+ explicit RenderSharedSurfaceTextureHost(
+ gfx::SourceSurfaceSharedDataWrapper* aSurface);
+
+ wr::WrExternalImage Lock(uint8_t aChannelIndex, gl::GLContext* aGL,
+ wr::ImageRendering aRendering) override;
+ void Unlock() override;
+ size_t Bytes() override;
+
+ private:
+ virtual ~RenderSharedSurfaceTextureHost();
+
+ RefPtr<gfx::SourceSurfaceSharedDataWrapper> mSurface;
+ gfx::DataSourceSurface::MappedSurface mMap;
+ bool mLocked;
+};
+
+} // namespace wr
+} // namespace mozilla
+
+#endif // MOZILLA_GFX_RENDERSHAREDSURFACETEXTUREHOST_H
diff --git a/gfx/webrender_bindings/RenderSharedSurfaceTextureHostSWGL.cpp b/gfx/webrender_bindings/RenderSharedSurfaceTextureHostSWGL.cpp
new file mode 100644
index 0000000000..8de1aaa979
--- /dev/null
+++ b/gfx/webrender_bindings/RenderSharedSurfaceTextureHostSWGL.cpp
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "RenderSharedSurfaceTextureHostSWGL.h"
+
+#include "mozilla/layers/SourceSurfaceSharedData.h"
+
+namespace mozilla {
+namespace wr {
+
+RenderSharedSurfaceTextureHostSWGL::RenderSharedSurfaceTextureHostSWGL(
+ gfx::SourceSurfaceSharedDataWrapper* aSurface)
+ : mSurface(aSurface) {
+ MOZ_COUNT_CTOR_INHERITED(RenderSharedSurfaceTextureHostSWGL,
+ RenderTextureHostSWGL);
+ MOZ_ASSERT(aSurface);
+}
+
+RenderSharedSurfaceTextureHostSWGL::~RenderSharedSurfaceTextureHostSWGL() {
+ MOZ_COUNT_DTOR_INHERITED(RenderSharedSurfaceTextureHostSWGL,
+ RenderTextureHostSWGL);
+}
+
+size_t RenderSharedSurfaceTextureHostSWGL::GetPlaneCount() const { return 1; }
+
+gfx::SurfaceFormat RenderSharedSurfaceTextureHostSWGL::GetFormat() const {
+ return mSurface->GetFormat();
+}
+
+gfx::ColorDepth RenderSharedSurfaceTextureHostSWGL::GetColorDepth() const {
+ return gfx::ColorDepth::COLOR_8;
+}
+
+bool RenderSharedSurfaceTextureHostSWGL::MapPlane(RenderCompositor* aCompositor,
+ uint8_t aChannelIndex,
+ PlaneInfo& aPlaneInfo) {
+ if (NS_WARN_IF(
+ !mSurface->Map(gfx::DataSourceSurface::MapType::READ, &mMap))) {
+ return false;
+ }
+ aPlaneInfo.mData = mMap.mData;
+ aPlaneInfo.mStride = mMap.mStride;
+ aPlaneInfo.mSize = mSurface->GetSize();
+ return true;
+}
+
+void RenderSharedSurfaceTextureHostSWGL::UnmapPlanes() { mSurface->Unmap(); }
+
+} // namespace wr
+} // namespace mozilla
diff --git a/gfx/webrender_bindings/RenderSharedSurfaceTextureHostSWGL.h b/gfx/webrender_bindings/RenderSharedSurfaceTextureHostSWGL.h
new file mode 100644
index 0000000000..e61a09ca83
--- /dev/null
+++ b/gfx/webrender_bindings/RenderSharedSurfaceTextureHostSWGL.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef MOZILLA_GFX_RENDERSHAREDSURFACETEXTUREHOSTSWGL_H
+#define MOZILLA_GFX_RENDERSHAREDSURFACETEXTUREHOSTSWGL_H
+
+#include "RenderTextureHostSWGL.h"
+
+namespace mozilla {
+namespace gfx {
+class SourceSurfaceSharedDataWrapper;
+}
+
+namespace wr {
+
+/**
+ * This class allows for surfaces managed by SharedSurfacesParent to be inserted
+ * into the render texture cache by wrapping an existing surface wrapper. These
+ * surfaces are backed by BGRA/X shared memory buffers.
+ */
+class RenderSharedSurfaceTextureHostSWGL final : public RenderTextureHostSWGL {
+ public:
+ explicit RenderSharedSurfaceTextureHostSWGL(
+ gfx::SourceSurfaceSharedDataWrapper* aSurface);
+
+ size_t GetPlaneCount() const override;
+
+ gfx::SurfaceFormat GetFormat() const override;
+
+ gfx::ColorDepth GetColorDepth() const override;
+
+ bool MapPlane(RenderCompositor* aCompositor, uint8_t aChannelIndex,
+ PlaneInfo& aPlaneInfo) override;
+
+ void UnmapPlanes() override;
+
+ private:
+ virtual ~RenderSharedSurfaceTextureHostSWGL();
+
+ RefPtr<gfx::SourceSurfaceSharedDataWrapper> mSurface;
+ gfx::DataSourceSurface::MappedSurface mMap;
+};
+
+} // namespace wr
+} // namespace mozilla
+
+#endif // MOZILLA_GFX_RENDERSHAREDSURFACETEXTUREHOSTSWGL_H
diff --git a/gfx/webrender_bindings/RenderTextureHost.cpp b/gfx/webrender_bindings/RenderTextureHost.cpp
new file mode 100644
index 0000000000..79d33139dd
--- /dev/null
+++ b/gfx/webrender_bindings/RenderTextureHost.cpp
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "RenderTextureHost.h"
+
+#include "GLContext.h"
+#include "RenderThread.h"
+
+namespace mozilla {
+namespace wr {
+
+void ActivateBindAndTexParameteri(gl::GLContext* aGL, GLenum aActiveTexture,
+ GLenum aBindTarget, GLuint aBindTexture,
+ wr::ImageRendering aRendering) {
+ aGL->fActiveTexture(aActiveTexture);
+ aGL->fBindTexture(aBindTarget, aBindTexture);
+ aGL->fTexParameteri(aBindTarget, LOCAL_GL_TEXTURE_MIN_FILTER,
+ aRendering == wr::ImageRendering::Pixelated
+ ? LOCAL_GL_NEAREST
+ : LOCAL_GL_LINEAR);
+ aGL->fTexParameteri(aBindTarget, LOCAL_GL_TEXTURE_MAG_FILTER,
+ aRendering == wr::ImageRendering::Pixelated
+ ? LOCAL_GL_NEAREST
+ : LOCAL_GL_LINEAR);
+}
+
+RenderTextureHost::RenderTextureHost()
+ : mCachedRendering(wr::ImageRendering::Auto) {
+ MOZ_COUNT_CTOR(RenderTextureHost);
+}
+
+RenderTextureHost::~RenderTextureHost() {
+ MOZ_ASSERT(RenderThread::IsInRenderThread());
+ MOZ_COUNT_DTOR(RenderTextureHost);
+}
+
+bool RenderTextureHost::IsFilterUpdateNecessary(wr::ImageRendering aRendering) {
+ return mCachedRendering != aRendering;
+}
+
+wr::WrExternalImage RenderTextureHost::Lock(uint8_t aChannelIndex,
+ gl::GLContext* aGL,
+ wr::ImageRendering aRendering) {
+ return InvalidToWrExternalImage();
+}
+
+wr::WrExternalImage RenderTextureHost::LockSWGL(uint8_t aChannelIndex,
+ void* aContext,
+ RenderCompositor* aCompositor,
+ wr::ImageRendering aRendering) {
+ return InvalidToWrExternalImage();
+}
+
+} // namespace wr
+} // namespace mozilla
diff --git a/gfx/webrender_bindings/RenderTextureHost.h b/gfx/webrender_bindings/RenderTextureHost.h
new file mode 100644
index 0000000000..a57a749612
--- /dev/null
+++ b/gfx/webrender_bindings/RenderTextureHost.h
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef MOZILLA_GFX_RENDERTEXTUREHOST_H
+#define MOZILLA_GFX_RENDERTEXTUREHOST_H
+
+#include "GLConsts.h"
+#include "GLTypes.h"
+#include "nsISupportsImpl.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/layers/LayersSurfaces.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/webrender/webrender_ffi.h" // for wr::ImageRendering
+#include "mozilla/webrender/WebRenderTypes.h"
+
+namespace mozilla {
+
+namespace gl {
+class GLContext;
+}
+
+namespace wr {
+
+class RenderCompositor;
+class RenderDXGITextureHost;
+class RenderDXGIYCbCrTextureHost;
+class RenderMacIOSurfaceTextureHost;
+class RenderBufferTextureHost;
+class RenderTextureHostSWGL;
+
+void ActivateBindAndTexParameteri(gl::GLContext* aGL, GLenum aActiveTexture,
+ GLenum aBindTarget, GLuint aBindTexture,
+ wr::ImageRendering aRendering);
+
+class RenderTextureHost {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RenderTextureHost)
+
+ public:
+ RenderTextureHost();
+
+ virtual wr::WrExternalImage Lock(uint8_t aChannelIndex, gl::GLContext* aGL,
+ wr::ImageRendering aRendering);
+
+ virtual void Unlock() {}
+
+ virtual wr::WrExternalImage LockSWGL(uint8_t aChannelIndex, void* aContext,
+ RenderCompositor* aCompositor,
+ wr::ImageRendering aRendering);
+
+ virtual void UnlockSWGL() {}
+
+ virtual void ClearCachedResources() {}
+
+ // Called asynchronouly when corresponding TextureHost's mCompositableCount
+ // becomes from 0 to 1. For now, it is used only for
+ // SurfaceTextureHost/RenderAndroidSurfaceTextureHost.
+ virtual void PrepareForUse() {}
+ // Called asynchronouly when corresponding TextureHost's is actually going to
+ // be used by WebRender. For now, it is used only for
+ // SurfaceTextureHost/RenderAndroidSurfaceTextureHost.
+ virtual void NotifyForUse() {}
+ // Called asynchronouly when corresponding TextureHost's mCompositableCount
+ // becomes 0. For now, it is used only for
+ // SurfaceTextureHost/RenderAndroidSurfaceTextureHost.
+ virtual void NotifyNotUsed() {}
+ // Returns true when RenderTextureHost needs SyncObjectHost::Synchronize()
+ // call, before its usage.
+ virtual bool SyncObjectNeeded() { return false; }
+
+ virtual size_t Bytes() = 0;
+
+ virtual RenderDXGITextureHost* AsRenderDXGITextureHost() { return nullptr; }
+ virtual RenderDXGIYCbCrTextureHost* AsRenderDXGIYCbCrTextureHost() {
+ return nullptr;
+ }
+
+ virtual RenderMacIOSurfaceTextureHost* AsRenderMacIOSurfaceTextureHost() {
+ return nullptr;
+ }
+
+ virtual RenderTextureHostSWGL* AsRenderTextureHostSWGL() { return nullptr; }
+
+ protected:
+ virtual ~RenderTextureHost();
+
+ bool IsFilterUpdateNecessary(wr::ImageRendering aRendering);
+
+ wr::ImageRendering mCachedRendering;
+};
+
+} // namespace wr
+} // namespace mozilla
+
+#endif // MOZILLA_GFX_RENDERTEXTUREHOST_H
diff --git a/gfx/webrender_bindings/RenderTextureHostSWGL.cpp b/gfx/webrender_bindings/RenderTextureHostSWGL.cpp
new file mode 100644
index 0000000000..f0853edda3
--- /dev/null
+++ b/gfx/webrender_bindings/RenderTextureHostSWGL.cpp
@@ -0,0 +1,207 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "RenderTextureHostSWGL.h"
+#include "RenderThread.h"
+
+namespace mozilla {
+namespace wr {
+
+bool RenderTextureHostSWGL::UpdatePlanes(RenderCompositor* aCompositor,
+ wr::ImageRendering aRendering) {
+ wr_swgl_make_current(mContext);
+ size_t planeCount = GetPlaneCount();
+ bool filterUpdate = IsFilterUpdateNecessary(aRendering);
+ if (mPlanes.size() < planeCount) {
+ mPlanes.reserve(planeCount);
+ while (mPlanes.size() < planeCount) {
+ mPlanes.push_back(PlaneInfo(wr_swgl_gen_texture(mContext)));
+ }
+ filterUpdate = true;
+ }
+ gfx::SurfaceFormat format = GetFormat();
+ gfx::ColorDepth colorDepth = GetColorDepth();
+ for (size_t i = 0; i < planeCount; i++) {
+ PlaneInfo& plane = mPlanes[i];
+ if (!MapPlane(aCompositor, i, plane)) {
+ if (i > 0) {
+ UnmapPlanes();
+ }
+ return false;
+ }
+ GLenum internalFormat = 0;
+ switch (format) {
+ case gfx::SurfaceFormat::B8G8R8A8:
+ case gfx::SurfaceFormat::B8G8R8X8:
+ MOZ_ASSERT(colorDepth == gfx::ColorDepth::COLOR_8);
+ internalFormat = LOCAL_GL_RGBA8;
+ break;
+ case gfx::SurfaceFormat::YUV:
+ switch (colorDepth) {
+ case gfx::ColorDepth::COLOR_8:
+ internalFormat = LOCAL_GL_R8;
+ break;
+ case gfx::ColorDepth::COLOR_10:
+ case gfx::ColorDepth::COLOR_12:
+ case gfx::ColorDepth::COLOR_16:
+ internalFormat = LOCAL_GL_R16;
+ break;
+ default:
+ MOZ_RELEASE_ASSERT(false, "Unhandled YUV color depth");
+ break;
+ }
+ break;
+ case gfx::SurfaceFormat::NV12:
+ MOZ_ASSERT(colorDepth == gfx::ColorDepth::COLOR_8);
+ internalFormat = i > 0 ? LOCAL_GL_RG8 : LOCAL_GL_R8;
+ break;
+ case gfx::SurfaceFormat::YUV422:
+ MOZ_ASSERT(colorDepth == gfx::ColorDepth::COLOR_8);
+ internalFormat = LOCAL_GL_RGB_RAW_422_APPLE;
+ break;
+ default:
+ MOZ_RELEASE_ASSERT(false, "Unhandled external image format");
+ break;
+ }
+ wr_swgl_set_texture_buffer(mContext, plane.mTexture, internalFormat,
+ plane.mSize.width, plane.mSize.height,
+ plane.mStride, plane.mData, 0, 0);
+ }
+ if (filterUpdate) {
+ mCachedRendering = aRendering;
+ GLenum filter = aRendering == wr::ImageRendering::Pixelated
+ ? LOCAL_GL_NEAREST
+ : LOCAL_GL_LINEAR;
+ for (const auto& plane : mPlanes) {
+ wr_swgl_set_texture_parameter(mContext, plane.mTexture,
+ LOCAL_GL_TEXTURE_MIN_FILTER, filter);
+ wr_swgl_set_texture_parameter(mContext, plane.mTexture,
+ LOCAL_GL_TEXTURE_MAG_FILTER, filter);
+ }
+ }
+ return true;
+}
+
+bool RenderTextureHostSWGL::SetContext(void* aContext) {
+ if (mContext != aContext) {
+ CleanupPlanes();
+ mContext = aContext;
+ wr_swgl_reference_context(mContext);
+ }
+ return mContext != nullptr;
+}
+
+wr::WrExternalImage RenderTextureHostSWGL::LockSWGL(
+ uint8_t aChannelIndex, void* aContext, RenderCompositor* aCompositor,
+ wr::ImageRendering aRendering) {
+ if (!SetContext(aContext)) {
+ return InvalidToWrExternalImage();
+ }
+ if (!mLocked) {
+ if (!UpdatePlanes(aCompositor, aRendering)) {
+ return InvalidToWrExternalImage();
+ }
+ mLocked = true;
+ }
+ if (aChannelIndex >= mPlanes.size()) {
+ return InvalidToWrExternalImage();
+ }
+ const PlaneInfo& plane = mPlanes[aChannelIndex];
+ return NativeTextureToWrExternalImage(plane.mTexture, 0, 0, plane.mSize.width,
+ plane.mSize.height);
+}
+
+void RenderTextureHostSWGL::UnlockSWGL() {
+ if (mLocked) {
+ mLocked = false;
+ UnmapPlanes();
+ }
+}
+
+void RenderTextureHostSWGL::CleanupPlanes() {
+ if (!mContext) {
+ return;
+ }
+ if (!mPlanes.empty()) {
+ wr_swgl_make_current(mContext);
+ for (const auto& plane : mPlanes) {
+ wr_swgl_delete_texture(mContext, plane.mTexture);
+ }
+ mPlanes.clear();
+ }
+ wr_swgl_destroy_context(mContext);
+ mContext = nullptr;
+}
+
+RenderTextureHostSWGL::~RenderTextureHostSWGL() { CleanupPlanes(); }
+
+bool RenderTextureHostSWGL::LockSWGLCompositeSurface(
+ void* aContext, wr::WrSWGLCompositeSurfaceInfo* aInfo) {
+ if (!SetContext(aContext)) {
+ return false;
+ }
+ if (!mLocked) {
+ if (!UpdatePlanes(nullptr, mCachedRendering)) {
+ return false;
+ }
+ mLocked = true;
+ }
+ MOZ_ASSERT(mPlanes.size() <= 3);
+ for (size_t i = 0; i < mPlanes.size(); i++) {
+ aInfo->textures[i] = mPlanes[i].mTexture;
+ }
+ switch (GetFormat()) {
+ case gfx::SurfaceFormat::YUV:
+ case gfx::SurfaceFormat::NV12:
+ case gfx::SurfaceFormat::YUV422: {
+ aInfo->yuv_planes = mPlanes.size();
+ auto colorSpace = GetYUVColorSpace();
+ MOZ_ASSERT(colorSpace != gfx::YUVColorSpace::UNKNOWN);
+ aInfo->color_space = ToWrYuvColorSpace(colorSpace);
+ auto colorDepth = GetColorDepth();
+ MOZ_ASSERT(colorDepth != gfx::ColorDepth::UNKNOWN);
+ aInfo->color_depth = ToWrColorDepth(colorDepth);
+ break;
+ }
+ case gfx::SurfaceFormat::B8G8R8A8:
+ case gfx::SurfaceFormat::B8G8R8X8:
+ break;
+ default:
+ MOZ_RELEASE_ASSERT(false, "Unhandled external image format");
+ break;
+ }
+ aInfo->size.width = mPlanes[0].mSize.width;
+ aInfo->size.height = mPlanes[0].mSize.height;
+ return true;
+}
+
+bool wr_swgl_lock_composite_surface(void* aContext, wr::ExternalImageId aId,
+ wr::WrSWGLCompositeSurfaceInfo* aInfo) {
+ RenderTextureHost* texture = RenderThread::Get()->GetRenderTexture(aId);
+ if (!texture) {
+ return false;
+ }
+ RenderTextureHostSWGL* swglTex = texture->AsRenderTextureHostSWGL();
+ if (!swglTex) {
+ return false;
+ }
+ return swglTex->LockSWGLCompositeSurface(aContext, aInfo);
+}
+
+void wr_swgl_unlock_composite_surface(void* aContext, wr::ExternalImageId aId) {
+ RenderTextureHost* texture = RenderThread::Get()->GetRenderTexture(aId);
+ if (!texture) {
+ return;
+ }
+ RenderTextureHostSWGL* swglTex = texture->AsRenderTextureHostSWGL();
+ if (!swglTex) {
+ return;
+ }
+ swglTex->UnlockSWGL();
+}
+
+} // namespace wr
+} // namespace mozilla
diff --git a/gfx/webrender_bindings/RenderTextureHostSWGL.h b/gfx/webrender_bindings/RenderTextureHostSWGL.h
new file mode 100644
index 0000000000..f0aba00e93
--- /dev/null
+++ b/gfx/webrender_bindings/RenderTextureHostSWGL.h
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef MOZILLA_GFX_RENDERTEXTUREHOSTSWGL_H
+#define MOZILLA_GFX_RENDERTEXTUREHOSTSWGL_H
+
+#include "GLTypes.h"
+#include "RenderTextureHost.h"
+
+namespace mozilla {
+namespace wr {
+
+class RenderTextureHostSWGL : public RenderTextureHost {
+ public:
+ RenderTextureHostSWGL() {}
+
+ wr::WrExternalImage LockSWGL(uint8_t aChannelIndex, void* aContext,
+ RenderCompositor* aCompositor,
+ wr::ImageRendering aRendering) override;
+
+ void UnlockSWGL() override;
+
+ RenderTextureHostSWGL* AsRenderTextureHostSWGL() override { return this; }
+
+ virtual size_t GetPlaneCount() const = 0;
+
+ virtual gfx::SurfaceFormat GetFormat() const = 0;
+
+ virtual gfx::ColorDepth GetColorDepth() const {
+ return gfx::ColorDepth::UNKNOWN;
+ }
+
+ virtual gfx::YUVColorSpace GetYUVColorSpace() const {
+ return gfx::YUVColorSpace::UNKNOWN;
+ }
+
+ struct PlaneInfo {
+ explicit PlaneInfo(GLuint aTexture) : mTexture(aTexture) {}
+
+ GLuint mTexture = 0;
+ void* mData = nullptr;
+ int32_t mStride = 0;
+ gfx::IntSize mSize;
+ };
+
+ virtual bool MapPlane(RenderCompositor* aCompositor, uint8_t aChannelIndex,
+ PlaneInfo& aPlaneInfo) = 0;
+
+ virtual void UnmapPlanes() = 0;
+
+ // Lock this texture host as an attached external image for a SWGL compositor
+ // surface. See swgl_bindings.rs for a description of the resulting
+ // WrSWGLCompositeSurfaceInfo. This is paired with a call to UnlockSWGL when
+ // composition is done.
+ bool LockSWGLCompositeSurface(void* aContext,
+ wr::WrSWGLCompositeSurfaceInfo* aInfo);
+
+ size_t Bytes() override {
+ size_t bytes = 0;
+ for (auto& plane : mPlanes) {
+ bytes += plane.mStride * plane.mSize.height;
+ }
+ return bytes;
+ }
+
+ protected:
+ bool mLocked = false;
+ void* mContext = nullptr;
+ std::vector<PlaneInfo> mPlanes;
+
+ bool SetContext(void* aContext);
+
+ bool UpdatePlanes(RenderCompositor* aCompositor,
+ wr::ImageRendering aRendering);
+
+ void CleanupPlanes();
+
+ virtual ~RenderTextureHostSWGL();
+};
+
+} // namespace wr
+} // namespace mozilla
+
+#endif // MOZILLA_GFX_RENDERTEXTUREHOSTSWGL_H
diff --git a/gfx/webrender_bindings/RenderTextureHostWrapper.cpp b/gfx/webrender_bindings/RenderTextureHostWrapper.cpp
new file mode 100644
index 0000000000..84ad644a4d
--- /dev/null
+++ b/gfx/webrender_bindings/RenderTextureHostWrapper.cpp
@@ -0,0 +1,137 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "RenderTextureHostWrapper.h"
+
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/webrender/RenderThread.h"
+
+namespace mozilla {
+namespace wr {
+
+RenderTextureHostWrapper::RenderTextureHostWrapper(
+ ExternalImageId aExternalImageId)
+ : mExternalImageId(aExternalImageId) {
+ MOZ_COUNT_CTOR_INHERITED(RenderTextureHostWrapper, RenderTextureHost);
+}
+
+RenderTextureHostWrapper::~RenderTextureHostWrapper() {
+ MOZ_COUNT_DTOR_INHERITED(RenderTextureHostWrapper, RenderTextureHost);
+}
+
+void RenderTextureHostWrapper::EnsureTextureHost() const {
+ if (!mTextureHost) {
+ mTextureHost = RenderThread::Get()->GetRenderTexture(mExternalImageId);
+ MOZ_ASSERT(mTextureHost);
+ if (!mTextureHost) {
+ gfxCriticalNoteOnce << "Failed to get RenderTextureHost for extId:"
+ << AsUint64(mExternalImageId);
+ }
+ }
+}
+
+wr::WrExternalImage RenderTextureHostWrapper::Lock(
+ uint8_t aChannelIndex, gl::GLContext* aGL, wr::ImageRendering aRendering) {
+ EnsureTextureHost();
+ if (!mTextureHost) {
+ return InvalidToWrExternalImage();
+ }
+
+ return mTextureHost->Lock(aChannelIndex, aGL, aRendering);
+}
+
+void RenderTextureHostWrapper::Unlock() {
+ if (mTextureHost) {
+ mTextureHost->Unlock();
+ }
+}
+
+void RenderTextureHostWrapper::ClearCachedResources() {
+ if (mTextureHost) {
+ mTextureHost->ClearCachedResources();
+ }
+}
+
+RenderMacIOSurfaceTextureHost*
+RenderTextureHostWrapper::AsRenderMacIOSurfaceTextureHost() {
+ EnsureTextureHost();
+ if (!mTextureHost) {
+ return nullptr;
+ }
+ return mTextureHost->AsRenderMacIOSurfaceTextureHost();
+}
+
+RenderDXGITextureHost* RenderTextureHostWrapper::AsRenderDXGITextureHost() {
+ EnsureTextureHost();
+ if (!mTextureHost) {
+ return nullptr;
+ }
+ return mTextureHost->AsRenderDXGITextureHost();
+}
+
+RenderDXGIYCbCrTextureHost*
+RenderTextureHostWrapper::AsRenderDXGIYCbCrTextureHost() {
+ EnsureTextureHost();
+ if (!mTextureHost) {
+ return nullptr;
+ }
+ return mTextureHost->AsRenderDXGIYCbCrTextureHost();
+}
+
+RenderTextureHostSWGL* RenderTextureHostWrapper::EnsureRenderTextureHostSWGL()
+ const {
+ EnsureTextureHost();
+ if (!mTextureHost) {
+ return nullptr;
+ }
+ return mTextureHost->AsRenderTextureHostSWGL();
+}
+
+size_t RenderTextureHostWrapper::GetPlaneCount() const {
+ if (RenderTextureHostSWGL* swglHost = EnsureRenderTextureHostSWGL()) {
+ return swglHost->GetPlaneCount();
+ }
+ return 0;
+}
+
+gfx::SurfaceFormat RenderTextureHostWrapper::GetFormat() const {
+ if (RenderTextureHostSWGL* swglHost = EnsureRenderTextureHostSWGL()) {
+ return swglHost->GetFormat();
+ }
+ return gfx::SurfaceFormat::UNKNOWN;
+}
+
+gfx::ColorDepth RenderTextureHostWrapper::GetColorDepth() const {
+ if (RenderTextureHostSWGL* swglHost = EnsureRenderTextureHostSWGL()) {
+ return swglHost->GetColorDepth();
+ }
+ return gfx::ColorDepth::UNKNOWN;
+}
+
+gfx::YUVColorSpace RenderTextureHostWrapper::GetYUVColorSpace() const {
+ if (RenderTextureHostSWGL* swglHost = EnsureRenderTextureHostSWGL()) {
+ return swglHost->GetYUVColorSpace();
+ }
+ return gfx::YUVColorSpace::UNKNOWN;
+}
+
+bool RenderTextureHostWrapper::MapPlane(RenderCompositor* aCompositor,
+ uint8_t aChannelIndex,
+ PlaneInfo& aPlaneInfo) {
+ if (RenderTextureHostSWGL* swglHost = EnsureRenderTextureHostSWGL()) {
+ return swglHost->MapPlane(aCompositor, aChannelIndex, aPlaneInfo);
+ }
+ return false;
+}
+
+void RenderTextureHostWrapper::UnmapPlanes() {
+ if (RenderTextureHostSWGL* swglHost = EnsureRenderTextureHostSWGL()) {
+ swglHost->UnmapPlanes();
+ }
+}
+
+} // namespace wr
+} // namespace mozilla
diff --git a/gfx/webrender_bindings/RenderTextureHostWrapper.h b/gfx/webrender_bindings/RenderTextureHostWrapper.h
new file mode 100644
index 0000000000..cce0694bc6
--- /dev/null
+++ b/gfx/webrender_bindings/RenderTextureHostWrapper.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef MOZILLA_GFX_RENDERTEXTUREHOSTWRAPPER_H
+#define MOZILLA_GFX_RENDERTEXTUREHOSTWRAPPER_H
+
+#include "RenderTextureHostSWGL.h"
+
+namespace mozilla {
+
+namespace wr {
+
+/**
+ * RenderTextureHost of GPUVideoTextureHost.
+ *
+ * GPUVideoTextureHost wraps TextureHost. This class wraps RenderTextureHost of
+ * the wrapped TextureHost. Lifetime of the wrapped TextureHost is usually
+ * longer than GPUVideoTextureHost and the wrapped TextureHost is used by
+ * multiple GPUVideoTextureHosts. This class is used to reduce recreations of
+ * the wrappded RenderTextureHost. Initializations of some
+ * RenderTextureHosts(RenderDXGITextureHost and
+ * RenderDXGIYCbCrTextureHost) have overhead.
+ */
+class RenderTextureHostWrapper final : public RenderTextureHostSWGL {
+ public:
+ explicit RenderTextureHostWrapper(ExternalImageId aExternalImageId);
+
+ // RenderTextureHost
+ wr::WrExternalImage Lock(uint8_t aChannelIndex, gl::GLContext* aGL,
+ wr::ImageRendering aRendering) override;
+ void Unlock() override;
+ void ClearCachedResources() override;
+ RenderMacIOSurfaceTextureHost* AsRenderMacIOSurfaceTextureHost() override;
+ RenderDXGITextureHost* AsRenderDXGITextureHost() override;
+ RenderDXGIYCbCrTextureHost* AsRenderDXGIYCbCrTextureHost() override;
+
+ // RenderTextureHostSWGL
+ size_t GetPlaneCount() const override;
+ gfx::SurfaceFormat GetFormat() const override;
+ gfx::ColorDepth GetColorDepth() const override;
+ gfx::YUVColorSpace GetYUVColorSpace() const override;
+ bool MapPlane(RenderCompositor* aCompositor, uint8_t aChannelIndex,
+ PlaneInfo& aPlaneInfo) override;
+ void UnmapPlanes() override;
+
+ private:
+ ~RenderTextureHostWrapper() override;
+
+ void EnsureTextureHost() const;
+ RenderTextureHostSWGL* EnsureRenderTextureHostSWGL() const;
+
+ const ExternalImageId mExternalImageId;
+ mutable RefPtr<RenderTextureHost> mTextureHost;
+};
+
+} // namespace wr
+} // namespace mozilla
+
+#endif // MOZILLA_GFX_RENDERTEXTUREHOSTWRAPPER_H
diff --git a/gfx/webrender_bindings/RenderThread.cpp b/gfx/webrender_bindings/RenderThread.cpp
new file mode 100644
index 0000000000..5b4dc3676e
--- /dev/null
+++ b/gfx/webrender_bindings/RenderThread.cpp
@@ -0,0 +1,1194 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "base/task.h"
+#include "GeckoProfiler.h"
+#include "gfxPlatform.h"
+#include "GLContext.h"
+#include "RenderThread.h"
+#include "nsThreadUtils.h"
+#include "transport/runnable_utils.h"
+#include "mozilla/layers/AsyncImagePipelineManager.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/gfx/GPUParent.h"
+#include "mozilla/gfx/GPUProcessManager.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/CompositorManagerParent.h"
+#include "mozilla/layers/WebRenderBridgeParent.h"
+#include "mozilla/layers/SharedSurfacesParent.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/webrender/RendererOGL.h"
+#include "mozilla/webrender/RenderTextureHost.h"
+#include "mozilla/widget/CompositorWidget.h"
+
+#ifdef XP_WIN
+# include "GLContextEGL.h"
+# include "GLLibraryEGL.h"
+# include "mozilla/widget/WinCompositorWindowThread.h"
+# include "mozilla/gfx/DeviceManagerDx.h"
+//# include "nsWindowsHelpers.h"
+//# include <d3d11.h>
+#endif
+
+#ifdef MOZ_WIDGET_ANDROID
+# include "GLLibraryEGL.h"
+# include "mozilla/webrender/RenderAndroidSurfaceTextureHost.h"
+#endif
+
+#ifdef MOZ_WIDGET_GTK
+# include <gdk/gdkx.h>
+#endif
+
+#ifdef MOZ_WAYLAND
+# include "GLLibraryEGL.h"
+#endif
+
+using namespace mozilla;
+
+static already_AddRefed<gl::GLContext> CreateGLContext(nsACString& aError);
+
+MOZ_DEFINE_MALLOC_SIZE_OF(WebRenderRendererMallocSizeOf)
+
+namespace mozilla {
+namespace wr {
+
+static StaticRefPtr<RenderThread> sRenderThread;
+
+RenderThread::RenderThread(base::Thread* aThread)
+ : mThread(aThread),
+ mThreadPool(false),
+ mThreadPoolLP(true),
+ mWindowInfos("RenderThread.mWindowInfos"),
+ mRenderTextureMapLock("RenderThread.mRenderTextureMapLock"),
+ mHasShutdown(false),
+ mHandlingDeviceReset(false),
+ mHandlingWebRenderError(false) {}
+
+RenderThread::~RenderThread() {
+ MOZ_ASSERT(mRenderTexturesDeferred.empty());
+ delete mThread;
+}
+
+// static
+RenderThread* RenderThread::Get() { return sRenderThread; }
+
+// static
+void RenderThread::Start() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!sRenderThread);
+
+ base::Thread* thread = new base::Thread("Renderer");
+
+ base::Thread::Options options;
+ // TODO(nical): The compositor thread has a bunch of specific options, see
+ // which ones make sense here.
+ if (!thread->StartWithOptions(options)) {
+ delete thread;
+ return;
+ }
+
+ sRenderThread = new RenderThread(thread);
+#ifdef XP_WIN
+ widget::WinCompositorWindowThread::Start();
+#endif
+ layers::SharedSurfacesParent::Initialize();
+
+ RefPtr<Runnable> runnable = WrapRunnable(
+ RefPtr<RenderThread>(sRenderThread.get()), &RenderThread::InitDeviceTask);
+ sRenderThread->Loop()->PostTask(runnable.forget());
+}
+
+// static
+void RenderThread::ShutDown() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(sRenderThread);
+
+ {
+ MutexAutoLock lock(sRenderThread->mRenderTextureMapLock);
+ sRenderThread->mHasShutdown = true;
+ }
+
+ layers::SynchronousTask task("RenderThread");
+ RefPtr<Runnable> runnable =
+ WrapRunnable(RefPtr<RenderThread>(sRenderThread.get()),
+ &RenderThread::ShutDownTask, &task);
+ sRenderThread->Loop()->PostTask(runnable.forget());
+ task.Wait();
+
+ sRenderThread = nullptr;
+#ifdef XP_WIN
+ if (widget::WinCompositorWindowThread::Get()) {
+ widget::WinCompositorWindowThread::ShutDown();
+ }
+#endif
+}
+
+extern void ClearAllBlobImageResources();
+
+void RenderThread::ShutDownTask(layers::SynchronousTask* aTask) {
+ layers::AutoCompleteTask complete(aTask);
+ MOZ_ASSERT(IsInRenderThread());
+
+ // Let go of our handle to the (internally ref-counted) thread pool.
+ mThreadPool.Release();
+ mThreadPoolLP.Release();
+
+ // Releasing on the render thread will allow us to avoid dispatching to remove
+ // remaining textures from the texture map.
+ layers::SharedSurfacesParent::Shutdown();
+
+ ClearAllBlobImageResources();
+ ClearSharedGL();
+ ClearSharedSurfacePool();
+}
+
+// static
+MessageLoop* RenderThread::Loop() {
+ return sRenderThread ? sRenderThread->mThread->message_loop() : nullptr;
+}
+
+// static
+bool RenderThread::IsInRenderThread() {
+ return sRenderThread &&
+ sRenderThread->mThread->thread_id() == PlatformThread::CurrentId();
+}
+
+void RenderThread::DoAccumulateMemoryReport(
+ MemoryReport aReport,
+ const RefPtr<MemoryReportPromise::Private>& aPromise) {
+ MOZ_ASSERT(IsInRenderThread());
+
+ for (auto& r : mRenderers) {
+ r.second->AccumulateMemoryReport(&aReport);
+ }
+
+ // Note memory used by the shader cache, which is shared across all WR
+ // instances.
+ MOZ_ASSERT(aReport.shader_cache == 0);
+ if (mProgramCache) {
+ aReport.shader_cache = wr_program_cache_report_memory(
+ mProgramCache->Raw(), &WebRenderRendererMallocSizeOf);
+ }
+
+ size_t renderTextureMemory = 0;
+ {
+ MutexAutoLock lock(mRenderTextureMapLock);
+ for (const auto& entry : mRenderTextures) {
+ renderTextureMemory += entry.second->Bytes();
+ }
+ }
+ aReport.render_texture_hosts = renderTextureMemory;
+
+ aPromise->Resolve(aReport, __func__);
+}
+
+// static
+RefPtr<MemoryReportPromise> RenderThread::AccumulateMemoryReport(
+ MemoryReport aInitial) {
+ RefPtr<MemoryReportPromise::Private> p =
+ new MemoryReportPromise::Private(__func__);
+ MOZ_ASSERT(!IsInRenderThread());
+ if (!Get() || !Get()->Loop()) {
+ // This happens when the GPU process fails to start and we fall back to the
+ // basic compositor in the parent process. We could assert against this if
+ // we made the webrender detection code in gfxPlatform.cpp smarter. See bug
+ // 1494430 comment 12.
+ NS_WARNING("No render thread, returning empty memory report");
+ p->Resolve(aInitial, __func__);
+ return p;
+ }
+
+ Get()->Loop()->PostTask(
+ NewRunnableMethod<MemoryReport, RefPtr<MemoryReportPromise::Private>>(
+ "wr::RenderThread::DoAccumulateMemoryReport", Get(),
+ &RenderThread::DoAccumulateMemoryReport, aInitial, p));
+
+ return p;
+}
+
+void RenderThread::AddRenderer(wr::WindowId aWindowId,
+ UniquePtr<RendererOGL> aRenderer) {
+ MOZ_ASSERT(IsInRenderThread());
+
+ if (mHasShutdown) {
+ return;
+ }
+
+ mRenderers[aWindowId] = std::move(aRenderer);
+
+ auto windows = mWindowInfos.Lock();
+ windows->emplace(AsUint64(aWindowId), new WindowInfo());
+}
+
+void RenderThread::RemoveRenderer(wr::WindowId aWindowId) {
+ MOZ_ASSERT(IsInRenderThread());
+
+ if (mHasShutdown) {
+ return;
+ }
+
+ mRenderers.erase(aWindowId);
+
+ if (mRenderers.size() == 0 && mHandlingDeviceReset) {
+ mHandlingDeviceReset = false;
+ }
+
+ auto windows = mWindowInfos.Lock();
+ auto it = windows->find(AsUint64(aWindowId));
+ MOZ_ASSERT(it != windows->end());
+ WindowInfo* toDelete = it->second;
+ windows->erase(it);
+ delete toDelete;
+}
+
+RendererOGL* RenderThread::GetRenderer(wr::WindowId aWindowId) {
+ MOZ_ASSERT(IsInRenderThread());
+
+ auto it = mRenderers.find(aWindowId);
+ MOZ_ASSERT(it != mRenderers.end());
+
+ if (it == mRenderers.end()) {
+ return nullptr;
+ }
+
+ return it->second.get();
+}
+
+size_t RenderThread::RendererCount() {
+ MOZ_ASSERT(IsInRenderThread());
+ return mRenderers.size();
+}
+
+void RenderThread::BeginRecordingForWindow(wr::WindowId aWindowId,
+ const TimeStamp& aRecordingStart,
+ wr::PipelineId aRootPipelineId) {
+ MOZ_ASSERT(IsInRenderThread());
+ RendererOGL* renderer = GetRenderer(aWindowId);
+ MOZ_ASSERT(renderer);
+
+ renderer->BeginRecording(aRecordingStart, aRootPipelineId);
+}
+
+void RenderThread::WriteCollectedFramesForWindow(wr::WindowId aWindowId) {
+ MOZ_ASSERT(IsInRenderThread());
+
+ RendererOGL* renderer = GetRenderer(aWindowId);
+ MOZ_ASSERT(renderer);
+ renderer->WriteCollectedFrames();
+}
+
+Maybe<layers::CollectedFrames> RenderThread::GetCollectedFramesForWindow(
+ wr::WindowId aWindowId) {
+ MOZ_ASSERT(IsInRenderThread());
+
+ RendererOGL* renderer = GetRenderer(aWindowId);
+ MOZ_ASSERT(renderer);
+ return renderer->GetCollectedFrames();
+}
+
+void RenderThread::HandleFrameOneDoc(wr::WindowId aWindowId, bool aRender) {
+ if (mHasShutdown) {
+ return;
+ }
+
+ if (!IsInRenderThread()) {
+ Loop()->PostTask(NewRunnableMethod<wr::WindowId, bool>(
+ "wr::RenderThread::HandleFrameOneDoc", this,
+ &RenderThread::HandleFrameOneDoc, aWindowId, aRender));
+ return;
+ }
+
+ if (IsDestroyed(aWindowId)) {
+ return;
+ }
+
+ if (mHandlingDeviceReset) {
+ return;
+ }
+
+ bool render = false;
+ PendingFrameInfo frame;
+ { // scope lock
+ auto windows = mWindowInfos.Lock();
+ auto it = windows->find(AsUint64(aWindowId));
+ if (it == windows->end()) {
+ MOZ_ASSERT(false);
+ return;
+ }
+
+ WindowInfo* info = it->second;
+ PendingFrameInfo& frameInfo = info->mPendingFrames.front();
+ frameInfo.mFrameNeedsRender |= aRender;
+ render = frameInfo.mFrameNeedsRender;
+
+ frame = frameInfo;
+ }
+
+ // It is for ensuring that PrepareForUse() is called before
+ // RenderTextureHost::Lock().
+ HandleRenderTextureOps();
+
+ UpdateAndRender(aWindowId, frame.mStartId, frame.mStartTime, render,
+ /* aReadbackSize */ Nothing(),
+ /* aReadbackFormat */ Nothing(),
+ /* aReadbackBuffer */ Nothing());
+
+ { // scope lock
+ auto windows = mWindowInfos.Lock();
+ auto it = windows->find(AsUint64(aWindowId));
+ if (it == windows->end()) {
+ MOZ_ASSERT(false);
+ return;
+ }
+ WindowInfo* info = it->second;
+ info->mPendingFrames.pop();
+ }
+
+ // The start time is from WebRenderBridgeParent::CompositeToTarget. From that
+ // point until now (when the frame is finally pushed to the screen) is
+ // equivalent to the COMPOSITE_TIME metric in the non-WR codepath.
+ mozilla::Telemetry::AccumulateTimeDelta(mozilla::Telemetry::COMPOSITE_TIME,
+ frame.mStartTime);
+}
+
+void RenderThread::SetClearColor(wr::WindowId aWindowId, wr::ColorF aColor) {
+ if (mHasShutdown) {
+ return;
+ }
+
+ if (!IsInRenderThread()) {
+ Loop()->PostTask(NewRunnableMethod<wr::WindowId, wr::ColorF>(
+ "wr::RenderThread::SetClearColor", this, &RenderThread::SetClearColor,
+ aWindowId, aColor));
+ return;
+ }
+
+ if (IsDestroyed(aWindowId)) {
+ return;
+ }
+
+ auto it = mRenderers.find(aWindowId);
+ MOZ_ASSERT(it != mRenderers.end());
+ if (it != mRenderers.end()) {
+ wr_renderer_set_clear_color(it->second->GetRenderer(), aColor);
+ }
+}
+
+void RenderThread::SetProfilerUI(wr::WindowId aWindowId, nsCString aUI) {
+ if (mHasShutdown) {
+ return;
+ }
+
+ if (!IsInRenderThread()) {
+ Loop()->PostTask(NewRunnableMethod<wr::WindowId, nsCString>(
+ "wr::RenderThread::SetProfilerUI", this, &RenderThread::SetProfilerUI,
+ aWindowId, aUI));
+ return;
+ }
+
+ auto it = mRenderers.find(aWindowId);
+ if (it != mRenderers.end()) {
+ it->second->SetProfilerUI(aUI);
+ }
+}
+
+void RenderThread::RunEvent(wr::WindowId aWindowId,
+ UniquePtr<RendererEvent> aEvent) {
+ if (!IsInRenderThread()) {
+ Loop()->PostTask(
+ NewRunnableMethod<wr::WindowId, UniquePtr<RendererEvent>&&>(
+ "wr::RenderThread::RunEvent", this, &RenderThread::RunEvent,
+ aWindowId, std::move(aEvent)));
+ return;
+ }
+
+ aEvent->Run(*this, aWindowId);
+ aEvent = nullptr;
+}
+
+static void NotifyDidRender(layers::CompositorBridgeParent* aBridge,
+ RefPtr<const WebRenderPipelineInfo> aInfo,
+ VsyncId aCompositeStartId,
+ TimeStamp aCompositeStart, TimeStamp aRenderStart,
+ TimeStamp aEnd, bool aRender,
+ RendererStats aStats) {
+ if (aRender && aBridge->GetWrBridge()) {
+ // We call this here to mimic the behavior in LayerManagerComposite, as to
+ // not change what Talos measures. That is, we do not record an empty frame
+ // as a frame.
+ aBridge->GetWrBridge()->RecordFrame();
+ }
+
+ aBridge->NotifyDidRender(aCompositeStartId, aCompositeStart, aRenderStart,
+ aEnd, &aStats);
+
+ for (const auto& epoch : aInfo->Raw().epochs) {
+ aBridge->NotifyPipelineRendered(epoch.pipeline_id, epoch.epoch,
+ aCompositeStartId, aCompositeStart,
+ aRenderStart, aEnd, &aStats);
+ }
+
+ if (aBridge->GetWrBridge()) {
+ aBridge->GetWrBridge()->CompositeIfNeeded();
+ }
+}
+
+static void NotifyDidStartRender(layers::CompositorBridgeParent* aBridge) {
+ // Starting a render will change mIsRendering, and potentially
+ // change whether we can allow the bridge to intiate another frame.
+ if (aBridge->GetWrBridge()) {
+ aBridge->GetWrBridge()->CompositeIfNeeded();
+ }
+}
+
+void RenderThread::UpdateAndRender(
+ wr::WindowId aWindowId, const VsyncId& aStartId,
+ const TimeStamp& aStartTime, bool aRender,
+ const Maybe<gfx::IntSize>& aReadbackSize,
+ const Maybe<wr::ImageFormat>& aReadbackFormat,
+ const Maybe<Range<uint8_t>>& aReadbackBuffer, bool* aNeedsYFlip) {
+ AUTO_PROFILER_TRACING_MARKER("Paint", "Composite", GRAPHICS);
+ MOZ_ASSERT(IsInRenderThread());
+ MOZ_ASSERT(aRender || aReadbackBuffer.isNothing());
+
+ auto it = mRenderers.find(aWindowId);
+ MOZ_ASSERT(it != mRenderers.end());
+ if (it == mRenderers.end()) {
+ return;
+ }
+
+ TimeStamp start = TimeStamp::Now();
+
+ auto& renderer = it->second;
+
+ if (renderer->IsPaused()) {
+ aRender = false;
+ }
+
+ layers::CompositorThread()->Dispatch(
+ NewRunnableFunction("NotifyDidStartRenderRunnable", &NotifyDidStartRender,
+ renderer->GetCompositorBridge()));
+
+ wr::RenderedFrameId latestFrameId;
+ RendererStats stats = {0};
+ if (aRender) {
+ latestFrameId = renderer->UpdateAndRender(
+ aReadbackSize, aReadbackFormat, aReadbackBuffer, aNeedsYFlip, &stats);
+ } else {
+ renderer->Update();
+ }
+ // Check graphics reset status even when rendering is skipped.
+ renderer->CheckGraphicsResetStatus("PostUpdate", /* aForce */ false);
+
+ TimeStamp end = TimeStamp::Now();
+ RefPtr<const WebRenderPipelineInfo> info = renderer->FlushPipelineInfo();
+
+ layers::CompositorThread()->Dispatch(
+ NewRunnableFunction("NotifyDidRenderRunnable", &NotifyDidRender,
+ renderer->GetCompositorBridge(), info, aStartId,
+ aStartTime, start, end, aRender, stats));
+
+ if (latestFrameId.IsValid()) {
+ renderer->MaybeRecordFrame(info);
+ }
+
+ ipc::FileDescriptor fenceFd;
+
+ if (latestFrameId.IsValid()) {
+ fenceFd = renderer->GetAndResetReleaseFence();
+
+ // Wait for GPU after posting NotifyDidRender, since the wait is not
+ // necessary for the NotifyDidRender.
+ // The wait is necessary for Textures recycling of AsyncImagePipelineManager
+ // and for avoiding GPU queue is filled with too much tasks.
+ // WaitForGPU's implementation is different for each platform.
+ renderer->WaitForGPU();
+ } else {
+ // Update frame id for NotifyPipelinesUpdated() when rendering does not
+ // happen, either because rendering was not requested or the frame was
+ // canceled. Rendering can sometimes be canceled if UpdateAndRender is
+ // called when the window is not yet ready (not mapped or 0 size).
+ latestFrameId = renderer->UpdateFrameId();
+ }
+
+ RenderedFrameId lastCompletedFrameId = renderer->GetLastCompletedFrameId();
+
+ RefPtr<layers::AsyncImagePipelineManager> pipelineMgr =
+ renderer->GetCompositorBridge()->GetAsyncImagePipelineManager();
+ // pipelineMgr should always be non-null here because it is only nulled out
+ // after the WebRenderAPI instance for the CompositorBridgeParent is
+ // destroyed, and that destruction blocks until the renderer thread has
+ // removed the relevant renderer. And after that happens we should never reach
+ // this code at all; it would bail out at the mRenderers.find check above.
+ MOZ_ASSERT(pipelineMgr);
+ pipelineMgr->NotifyPipelinesUpdated(info, latestFrameId, lastCompletedFrameId,
+ std::move(fenceFd));
+}
+
+void RenderThread::Pause(wr::WindowId aWindowId) {
+ MOZ_ASSERT(IsInRenderThread());
+
+ auto it = mRenderers.find(aWindowId);
+ MOZ_ASSERT(it != mRenderers.end());
+ if (it == mRenderers.end()) {
+ return;
+ }
+ auto& renderer = it->second;
+ renderer->Pause();
+}
+
+bool RenderThread::Resume(wr::WindowId aWindowId) {
+ MOZ_ASSERT(IsInRenderThread());
+
+ auto it = mRenderers.find(aWindowId);
+ MOZ_ASSERT(it != mRenderers.end());
+ if (it == mRenderers.end()) {
+ return false;
+ }
+ auto& renderer = it->second;
+ return renderer->Resume();
+}
+
+bool RenderThread::TooManyPendingFrames(wr::WindowId aWindowId) {
+ const int64_t maxFrameCount = 1;
+
+ // Too many pending frames if pending frames exit more than maxFrameCount
+ // or if RenderBackend is still processing a frame.
+
+ auto windows = mWindowInfos.Lock();
+ auto it = windows->find(AsUint64(aWindowId));
+ if (it == windows->end()) {
+ MOZ_ASSERT(false);
+ return true;
+ }
+ WindowInfo* info = it->second;
+
+ if (info->PendingCount() > maxFrameCount) {
+ return true;
+ }
+ // If there is no ongoing frame build, we accept a new frame.
+ return info->mPendingFrameBuild > 0;
+}
+
+bool RenderThread::IsDestroyed(wr::WindowId aWindowId) {
+ auto windows = mWindowInfos.Lock();
+ auto it = windows->find(AsUint64(aWindowId));
+ if (it == windows->end()) {
+ return true;
+ }
+
+ return it->second->mIsDestroyed;
+}
+
+void RenderThread::SetDestroyed(wr::WindowId aWindowId) {
+ auto windows = mWindowInfos.Lock();
+ auto it = windows->find(AsUint64(aWindowId));
+ if (it == windows->end()) {
+ MOZ_ASSERT(false);
+ return;
+ }
+ it->second->mIsDestroyed = true;
+}
+
+void RenderThread::IncPendingFrameCount(wr::WindowId aWindowId,
+ const VsyncId& aStartId,
+ const TimeStamp& aStartTime) {
+ auto windows = mWindowInfos.Lock();
+ auto it = windows->find(AsUint64(aWindowId));
+ if (it == windows->end()) {
+ MOZ_ASSERT(false);
+ return;
+ }
+ it->second->mPendingFrameBuild++;
+ it->second->mPendingFrames.push(
+ PendingFrameInfo{aStartTime, aStartId, false});
+}
+
+void RenderThread::DecPendingFrameBuildCount(wr::WindowId aWindowId) {
+ auto windows = mWindowInfos.Lock();
+ auto it = windows->find(AsUint64(aWindowId));
+ if (it == windows->end()) {
+ MOZ_ASSERT(false);
+ return;
+ }
+ WindowInfo* info = it->second;
+ MOZ_RELEASE_ASSERT(info->mPendingFrameBuild >= 1);
+ info->mPendingFrameBuild--;
+}
+
+void RenderThread::RegisterExternalImage(
+ uint64_t aExternalImageId, already_AddRefed<RenderTextureHost> aTexture) {
+ MutexAutoLock lock(mRenderTextureMapLock);
+
+ if (mHasShutdown) {
+ return;
+ }
+ MOZ_ASSERT(mRenderTextures.find(aExternalImageId) == mRenderTextures.end());
+ RefPtr<RenderTextureHost> texture = aTexture;
+ if (texture->SyncObjectNeeded()) {
+ mSyncObjectNeededRenderTextures.emplace(aExternalImageId, texture);
+ }
+ mRenderTextures.emplace(aExternalImageId, texture);
+}
+
+void RenderThread::UnregisterExternalImage(uint64_t aExternalImageId) {
+ MutexAutoLock lock(mRenderTextureMapLock);
+ if (mHasShutdown) {
+ return;
+ }
+ auto it = mRenderTextures.find(aExternalImageId);
+ if (it == mRenderTextures.end()) {
+ return;
+ }
+
+ auto& texture = it->second;
+ if (texture->SyncObjectNeeded()) {
+ MOZ_RELEASE_ASSERT(
+ mSyncObjectNeededRenderTextures.erase(aExternalImageId) == 1);
+ }
+
+ if (!IsInRenderThread()) {
+ // The RenderTextureHost should be released in render thread. So, post the
+ // deletion task here.
+ // The shmem and raw buffer are owned by compositor ipc channel. It's
+ // possible that RenderTextureHost is still exist after the shmem/raw buffer
+ // deletion. Then the buffer in RenderTextureHost becomes invalid. It's fine
+ // for this situation. Gecko will only release the buffer if WR doesn't need
+ // it. So, no one will access the invalid buffer in RenderTextureHost.
+ RefPtr<RenderTextureHost> texture = it->second;
+ mRenderTextures.erase(it);
+ mRenderTexturesDeferred.emplace_back(std::move(texture));
+ Loop()->PostTask(NewRunnableMethod(
+ "RenderThread::DeferredRenderTextureHostDestroy", this,
+ &RenderThread::DeferredRenderTextureHostDestroy));
+ } else {
+ mRenderTextures.erase(it);
+ }
+}
+
+void RenderThread::PrepareForUse(uint64_t aExternalImageId) {
+ AddRenderTextureOp(RenderTextureOp::PrepareForUse, aExternalImageId);
+}
+
+void RenderThread::NotifyNotUsed(uint64_t aExternalImageId) {
+ AddRenderTextureOp(RenderTextureOp::NotifyNotUsed, aExternalImageId);
+}
+
+void RenderThread::NotifyForUse(uint64_t aExternalImageId) {
+ AddRenderTextureOp(RenderTextureOp::NotifyForUse, aExternalImageId);
+}
+
+void RenderThread::AddRenderTextureOp(RenderTextureOp aOp,
+ uint64_t aExternalImageId) {
+ MOZ_ASSERT(!IsInRenderThread());
+
+ MutexAutoLock lock(mRenderTextureMapLock);
+
+ auto it = mRenderTextures.find(aExternalImageId);
+ MOZ_ASSERT(it != mRenderTextures.end());
+ if (it == mRenderTextures.end()) {
+ return;
+ }
+
+ RefPtr<RenderTextureHost> texture = it->second;
+ mRenderTextureOps.emplace_back(aOp, std::move(texture));
+ Loop()->PostTask(NewRunnableMethod("RenderThread::HandleRenderTextureOps",
+ this,
+ &RenderThread::HandleRenderTextureOps));
+}
+
+void RenderThread::HandleRenderTextureOps() {
+ MOZ_ASSERT(IsInRenderThread());
+
+ std::list<std::pair<RenderTextureOp, RefPtr<RenderTextureHost>>>
+ renderTextureOps;
+ {
+ MutexAutoLock lock(mRenderTextureMapLock);
+ mRenderTextureOps.swap(renderTextureOps);
+ }
+
+ for (auto& it : renderTextureOps) {
+ switch (it.first) {
+ case RenderTextureOp::PrepareForUse:
+ it.second->PrepareForUse();
+ break;
+ case RenderTextureOp::NotifyForUse:
+ it.second->NotifyForUse();
+ break;
+ case RenderTextureOp::NotifyNotUsed:
+ it.second->NotifyNotUsed();
+ break;
+ }
+ }
+}
+
+void RenderThread::UnregisterExternalImageDuringShutdown(
+ uint64_t aExternalImageId) {
+ MOZ_ASSERT(IsInRenderThread());
+ MutexAutoLock lock(mRenderTextureMapLock);
+ MOZ_ASSERT(mHasShutdown);
+ MOZ_ASSERT(mRenderTextures.find(aExternalImageId) != mRenderTextures.end());
+ mRenderTextures.erase(aExternalImageId);
+}
+
+bool RenderThread::SyncObjectNeeded() {
+ MOZ_ASSERT(IsInRenderThread());
+ MutexAutoLock lock(mRenderTextureMapLock);
+ return !mSyncObjectNeededRenderTextures.empty();
+}
+
+void RenderThread::DeferredRenderTextureHostDestroy() {
+ MutexAutoLock lock(mRenderTextureMapLock);
+ mRenderTexturesDeferred.clear();
+}
+
+RenderTextureHost* RenderThread::GetRenderTexture(
+ wr::ExternalImageId aExternalImageId) {
+ MOZ_ASSERT(IsInRenderThread());
+
+ MutexAutoLock lock(mRenderTextureMapLock);
+ auto it = mRenderTextures.find(AsUint64(aExternalImageId));
+ MOZ_ASSERT(it != mRenderTextures.end());
+ if (it == mRenderTextures.end()) {
+ return nullptr;
+ }
+ return it->second;
+}
+
+void RenderThread::InitDeviceTask() {
+ MOZ_ASSERT(IsInRenderThread());
+ MOZ_ASSERT(!mSharedGL);
+
+ if (gfx::gfxVars::UseSoftwareWebRender()) {
+ // Ensure we don't instantiate any shared GL context when SW-WR is used.
+ return;
+ }
+
+ nsAutoCString err;
+ mSharedGL = CreateGLContext(err);
+ if (gfx::gfxVars::UseWebRenderProgramBinaryDisk()) {
+ mProgramCache = MakeUnique<WebRenderProgramCache>(ThreadPool().Raw());
+ }
+ // Query the shared GL context to force the
+ // lazy initialization to happen now.
+ SharedGL();
+}
+
+#ifndef XP_WIN
+static DeviceResetReason GLenumToResetReason(GLenum aReason) {
+ switch (aReason) {
+ case LOCAL_GL_NO_ERROR:
+ return DeviceResetReason::FORCED_RESET;
+ case LOCAL_GL_INNOCENT_CONTEXT_RESET_ARB:
+ return DeviceResetReason::DRIVER_ERROR;
+ case LOCAL_GL_PURGED_CONTEXT_RESET_NV:
+ return DeviceResetReason::NVIDIA_VIDEO;
+ case LOCAL_GL_GUILTY_CONTEXT_RESET_ARB:
+ return DeviceResetReason::RESET;
+ case LOCAL_GL_UNKNOWN_CONTEXT_RESET_ARB:
+ return DeviceResetReason::UNKNOWN;
+ case LOCAL_GL_OUT_OF_MEMORY:
+ return DeviceResetReason::OUT_OF_MEMORY;
+ default:
+ return DeviceResetReason::OTHER;
+ }
+}
+#endif
+
+void RenderThread::HandleDeviceReset(const char* aWhere,
+ layers::CompositorBridgeParent* aBridge,
+ GLenum aReason) {
+ MOZ_ASSERT(IsInRenderThread());
+
+ if (mHandlingDeviceReset) {
+ return;
+ }
+
+#ifndef XP_WIN
+ // On Windows, see DeviceManagerDx::MaybeResetAndReacquireDevices.
+ gfx::GPUProcessManager::RecordDeviceReset(GLenumToResetReason(aReason));
+#endif
+
+ {
+ MutexAutoLock lock(mRenderTextureMapLock);
+ mRenderTexturesDeferred.clear();
+ for (const auto& entry : mRenderTextures) {
+ entry.second->ClearCachedResources();
+ }
+ }
+
+ mHandlingDeviceReset = aReason != LOCAL_GL_NO_ERROR;
+ if (mHandlingDeviceReset) {
+ // All RenderCompositors will be destroyed by the GPUProcessManager in
+ // either OnRemoteProcessDeviceReset via the GPUChild, or
+ // OnInProcessDeviceReset here directly.
+ gfxCriticalNote << "GFX: RenderThread detected a device reset in "
+ << aWhere;
+ if (XRE_IsGPUProcess()) {
+ gfx::GPUParent::GetSingleton()->NotifyDeviceReset();
+ } else {
+#ifndef XP_WIN
+ // FIXME(aosmond): Do we need to do this on Windows? nsWindow::OnPaint
+ // seems to do its own detection for the parent process.
+ bool guilty = aReason == LOCAL_GL_GUILTY_CONTEXT_RESET_ARB;
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "gfx::GPUProcessManager::OnInProcessDeviceReset", [guilty]() -> void {
+ gfx::GPUProcessManager::Get()->OnInProcessDeviceReset(guilty);
+ }));
+#endif
+ }
+ }
+}
+
+bool RenderThread::IsHandlingDeviceReset() {
+ MOZ_ASSERT(IsInRenderThread());
+ return mHandlingDeviceReset;
+}
+
+void RenderThread::SimulateDeviceReset() {
+ if (!IsInRenderThread()) {
+ Loop()->PostTask(NewRunnableMethod("RenderThread::SimulateDeviceReset",
+ this,
+ &RenderThread::SimulateDeviceReset));
+ } else {
+ // When this function is called GPUProcessManager::SimulateDeviceReset()
+ // already triggers destroying all CompositorSessions before re-creating
+ // them.
+ HandleDeviceReset("SimulateDeviceReset", nullptr, LOCAL_GL_NO_ERROR);
+ }
+}
+
+static void DoNotifyWebRenderError(WebRenderError aError) {
+ layers::CompositorManagerParent::NotifyWebRenderError(aError);
+}
+
+void RenderThread::NotifyWebRenderError(WebRenderError aError) {
+ MOZ_ASSERT(IsInRenderThread());
+
+ layers::CompositorThread()->Dispatch(NewRunnableFunction(
+ "DoNotifyWebRenderErrorRunnable", &DoNotifyWebRenderError, aError));
+}
+
+void RenderThread::HandleWebRenderError(WebRenderError aError) {
+ if (mHandlingWebRenderError) {
+ return;
+ }
+
+ NotifyWebRenderError(aError);
+
+ {
+ MutexAutoLock lock(mRenderTextureMapLock);
+ mRenderTexturesDeferred.clear();
+ for (const auto& entry : mRenderTextures) {
+ entry.second->ClearCachedResources();
+ }
+ }
+ mHandlingWebRenderError = true;
+ // WebRender is going to be disabled by
+ // GPUProcessManager::NotifyWebRenderError()
+}
+
+bool RenderThread::IsHandlingWebRenderError() {
+ MOZ_ASSERT(IsInRenderThread());
+ return mHandlingWebRenderError;
+}
+
+gl::GLContext* RenderThread::SharedGL() {
+ nsAutoCString err;
+ auto gl = SharedGL(err);
+ if (!err.IsEmpty()) {
+ gfxCriticalNote << err.get();
+ }
+ return gl;
+}
+
+gl::GLContext* RenderThread::SharedGL(nsACString& aError) {
+ MOZ_ASSERT(IsInRenderThread());
+ if (!mSharedGL) {
+ mSharedGL = CreateGLContext(aError);
+ mShaders = nullptr;
+ }
+ if (mSharedGL && !mShaders) {
+ mShaders = MakeUnique<WebRenderShaders>(mSharedGL, mProgramCache.get());
+ }
+
+ return mSharedGL.get();
+}
+
+void RenderThread::ClearSharedGL() {
+ MOZ_ASSERT(IsInRenderThread());
+ if (mSurfacePool) {
+ mSurfacePool->DestroyGLResourcesForContext(mSharedGL);
+ }
+ mShaders = nullptr;
+ mSharedGL = nullptr;
+}
+
+RefPtr<layers::SurfacePool> RenderThread::SharedSurfacePool() {
+#ifdef XP_MACOSX
+ if (!mSurfacePool) {
+ size_t poolSizeLimit =
+ StaticPrefs::gfx_webrender_compositor_surface_pool_size_AtStartup();
+ mSurfacePool = layers::SurfacePool::Create(poolSizeLimit);
+ }
+#endif
+ return mSurfacePool;
+}
+
+void RenderThread::ClearSharedSurfacePool() { mSurfacePool = nullptr; }
+
+static void GLAPIENTRY DebugMessageCallback(GLenum aSource, GLenum aType,
+ GLuint aId, GLenum aSeverity,
+ GLsizei aLength,
+ const GLchar* aMessage,
+ const GLvoid* aUserParam) {
+ constexpr const char* kContextLost = "Context has been lost.";
+
+ if (StaticPrefs::gfx_webrender_gl_debug_message_critical_note_AtStartup() &&
+ aSeverity == LOCAL_GL_DEBUG_SEVERITY_HIGH) {
+ auto message = std::string(aMessage, aLength);
+ // When content lost happned, error messages are flooded by its message.
+ if (message != kContextLost) {
+ gfxCriticalNote << message;
+ } else {
+ gfxCriticalNoteOnce << message;
+ }
+ }
+
+ if (StaticPrefs::gfx_webrender_gl_debug_message_print_AtStartup()) {
+ gl::GLContext* gl = (gl::GLContext*)aUserParam;
+ gl->DebugCallback(aSource, aType, aId, aSeverity, aLength, aMessage);
+ }
+}
+
+// static
+void RenderThread::MaybeEnableGLDebugMessage(gl::GLContext* aGLContext) {
+ if (!aGLContext) {
+ return;
+ }
+
+ bool enableDebugMessage =
+ StaticPrefs::gfx_webrender_gl_debug_message_critical_note_AtStartup() ||
+ StaticPrefs::gfx_webrender_gl_debug_message_print_AtStartup();
+
+ if (enableDebugMessage &&
+ aGLContext->IsExtensionSupported(gl::GLContext::KHR_debug)) {
+ aGLContext->fEnable(LOCAL_GL_DEBUG_OUTPUT);
+ aGLContext->fDisable(LOCAL_GL_DEBUG_OUTPUT_SYNCHRONOUS);
+ aGLContext->fDebugMessageCallback(&DebugMessageCallback, (void*)aGLContext);
+ aGLContext->fDebugMessageControl(LOCAL_GL_DONT_CARE, LOCAL_GL_DONT_CARE,
+ LOCAL_GL_DONT_CARE, 0, nullptr, true);
+ }
+}
+
+WebRenderShaders::WebRenderShaders(gl::GLContext* gl,
+ WebRenderProgramCache* programCache) {
+ mGL = gl;
+ mShaders =
+ wr_shaders_new(gl, programCache ? programCache->Raw() : nullptr,
+ StaticPrefs::gfx_webrender_precache_shaders_AtStartup());
+}
+
+WebRenderShaders::~WebRenderShaders() {
+ wr_shaders_delete(mShaders, mGL.get());
+}
+
+WebRenderThreadPool::WebRenderThreadPool(bool low_priority) {
+ mThreadPool = wr_thread_pool_new(low_priority);
+}
+
+WebRenderThreadPool::~WebRenderThreadPool() { Release(); }
+
+void WebRenderThreadPool::Release() {
+ if (mThreadPool) {
+ wr_thread_pool_delete(mThreadPool);
+ mThreadPool = nullptr;
+ }
+}
+
+WebRenderProgramCache::WebRenderProgramCache(wr::WrThreadPool* aThreadPool) {
+ MOZ_ASSERT(aThreadPool);
+
+ nsAutoString path;
+ if (gfx::gfxVars::UseWebRenderProgramBinaryDisk()) {
+ path.Append(gfx::gfxVars::ProfDirectory());
+ }
+ mProgramCache = wr_program_cache_new(&path, aThreadPool);
+ if (gfx::gfxVars::UseWebRenderProgramBinaryDisk()) {
+ wr_try_load_startup_shaders_from_disk(mProgramCache);
+ }
+}
+
+WebRenderProgramCache::~WebRenderProgramCache() {
+ wr_program_cache_delete(mProgramCache);
+}
+
+} // namespace wr
+} // namespace mozilla
+
+#ifdef XP_WIN
+static already_AddRefed<gl::GLContext> CreateGLContextANGLE(
+ nsACString& aError) {
+ const RefPtr<ID3D11Device> d3d11Device =
+ gfx::DeviceManagerDx::Get()->GetCompositorDevice();
+ if (!d3d11Device) {
+ aError.Assign("RcANGLE(no compositor device for EGLDisplay)"_ns);
+ return nullptr;
+ }
+
+ nsCString failureId;
+ const auto lib = gl::DefaultEglLibrary(&failureId);
+ if (!lib) {
+ aError.Assign(
+ nsPrintfCString("RcANGLE(load EGL lib failed: %s)", failureId.get()));
+ return nullptr;
+ }
+
+ const auto egl = lib->CreateDisplay(d3d11Device.get());
+ if (!egl) {
+ aError.Assign(nsPrintfCString("RcANGLE(create EGLDisplay failed: %s)",
+ failureId.get()));
+ return nullptr;
+ }
+
+ gl::CreateContextFlags flags = gl::CreateContextFlags::PREFER_ES3 |
+ gl::CreateContextFlags::PREFER_ROBUSTNESS;
+
+ if (egl->IsExtensionSupported(
+ gl::EGLExtension::MOZ_create_context_provoking_vertex_dont_care)) {
+ flags |= gl::CreateContextFlags::PROVOKING_VERTEX_DONT_CARE;
+ }
+
+ // Create GLContext with dummy EGLSurface, the EGLSurface is not used.
+ // Instread we override it with EGLSurface of SwapChain's back buffer.
+
+ const auto dummySize = mozilla::gfx::IntSize(16, 16);
+ auto gl = gl::GLContextEGL::CreateEGLPBufferOffscreenContext(
+ egl, {flags}, dummySize, &failureId);
+ if (!gl || !gl->IsANGLE()) {
+ aError.Assign(nsPrintfCString("RcANGLE(create GL context failed: %x, %s)",
+ gl.get(), failureId.get()));
+ return nullptr;
+ }
+
+ if (!gl->MakeCurrent()) {
+ aError.Assign(
+ nsPrintfCString("RcANGLE(make current GL context failed: %x, %x)",
+ gl.get(), gl->mEgl->mLib->fGetError()));
+ return nullptr;
+ }
+
+ return gl.forget();
+}
+#endif
+
+#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WAYLAND)
+static already_AddRefed<gl::GLContext> CreateGLContextEGL() {
+ // Create GLContext with dummy EGLSurface.
+ RefPtr<gl::GLContext> gl =
+ gl::GLContextProviderEGL::CreateForCompositorWidget(
+ nullptr, /* aWebRender */ true, /* aForceAccelerated */ true);
+ if (!gl || !gl->MakeCurrent()) {
+ gfxCriticalNote << "Failed GL context creation for WebRender: "
+ << gfx::hexa(gl.get());
+ return nullptr;
+ }
+ return gl.forget();
+}
+#endif
+
+#ifdef XP_MACOSX
+static already_AddRefed<gl::GLContext> CreateGLContextCGL() {
+ nsCString failureUnused;
+ return gl::GLContextProvider::CreateHeadless(
+ {gl::CreateContextFlags::ALLOW_OFFLINE_RENDERER |
+ gl::CreateContextFlags::FORCE_ENABLE_HARDWARE},
+ &failureUnused);
+}
+#endif
+
+static already_AddRefed<gl::GLContext> CreateGLContext(nsACString& aError) {
+ RefPtr<gl::GLContext> gl;
+
+#ifdef XP_WIN
+ if (gfx::gfxVars::UseWebRenderANGLE()) {
+ gl = CreateGLContextANGLE(aError);
+ }
+#elif defined(MOZ_WIDGET_ANDROID)
+ gl = CreateGLContextEGL();
+#elif defined(MOZ_WAYLAND)
+ if (gdk_display_get_default() &&
+ !GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
+ gl = CreateGLContextEGL();
+ }
+#elif XP_MACOSX
+ gl = CreateGLContextCGL();
+#endif
+
+ wr::RenderThread::MaybeEnableGLDebugMessage(gl);
+
+ return gl.forget();
+}
+
+extern "C" {
+
+void wr_notifier_wake_up(mozilla::wr::WrWindowId aWindowId,
+ bool aCompositeNeeded) {
+ mozilla::wr::RenderThread::Get()->IncPendingFrameCount(aWindowId, VsyncId(),
+ TimeStamp::Now());
+ mozilla::wr::RenderThread::Get()->DecPendingFrameBuildCount(aWindowId);
+ mozilla::wr::RenderThread::Get()->HandleFrameOneDoc(aWindowId,
+ aCompositeNeeded);
+}
+
+void wr_notifier_new_frame_ready(mozilla::wr::WrWindowId aWindowId) {
+ mozilla::wr::RenderThread::Get()->DecPendingFrameBuildCount(aWindowId);
+ mozilla::wr::RenderThread::Get()->HandleFrameOneDoc(aWindowId,
+ /* aRender */ true);
+}
+
+void wr_notifier_nop_frame_done(mozilla::wr::WrWindowId aWindowId) {
+ mozilla::wr::RenderThread::Get()->DecPendingFrameBuildCount(aWindowId);
+ mozilla::wr::RenderThread::Get()->HandleFrameOneDoc(aWindowId,
+ /* aRender */ false);
+}
+
+void wr_notifier_external_event(mozilla::wr::WrWindowId aWindowId,
+ size_t aRawEvent) {
+ mozilla::UniquePtr<mozilla::wr::RendererEvent> evt(
+ reinterpret_cast<mozilla::wr::RendererEvent*>(aRawEvent));
+ mozilla::wr::RenderThread::Get()->RunEvent(mozilla::wr::WindowId(aWindowId),
+ std::move(evt));
+}
+
+void wr_schedule_render(mozilla::wr::WrWindowId aWindowId) {
+ RefPtr<mozilla::layers::CompositorBridgeParent> cbp = mozilla::layers::
+ CompositorBridgeParent::GetCompositorBridgeParentFromWindowId(aWindowId);
+ if (cbp) {
+ cbp->ScheduleRenderOnCompositorThread();
+ }
+}
+
+static void NotifyDidSceneBuild(RefPtr<layers::CompositorBridgeParent> aBridge,
+ RefPtr<const wr::WebRenderPipelineInfo> aInfo) {
+ aBridge->NotifyDidSceneBuild(aInfo);
+}
+
+void wr_finished_scene_build(mozilla::wr::WrWindowId aWindowId,
+ mozilla::wr::WrPipelineInfo* aInfo) {
+ RefPtr<mozilla::layers::CompositorBridgeParent> cbp = mozilla::layers::
+ CompositorBridgeParent::GetCompositorBridgeParentFromWindowId(aWindowId);
+ RefPtr<wr::WebRenderPipelineInfo> info = new wr::WebRenderPipelineInfo();
+ info->Raw() = std::move(*aInfo);
+ if (cbp) {
+ layers::CompositorThread()->Dispatch(NewRunnableFunction(
+ "NotifyDidSceneBuild", &NotifyDidSceneBuild, cbp, info));
+ }
+}
+
+} // extern C
diff --git a/gfx/webrender_bindings/RenderThread.h b/gfx/webrender_bindings/RenderThread.h
new file mode 100644
index 0000000000..1b3c5ab16c
--- /dev/null
+++ b/gfx/webrender_bindings/RenderThread.h
@@ -0,0 +1,371 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef MOZILLA_LAYERS_RENDERTHREAD_H
+#define MOZILLA_LAYERS_RENDERTHREAD_H
+
+#include "base/basictypes.h" // for DISALLOW_EVIL_CONSTRUCTORS
+#include "base/platform_thread.h" // for PlatformThreadId
+#include "base/thread.h" // for Thread
+#include "base/message_loop.h"
+#include "GLTypes.h" // for GLenum
+#include "nsISupportsImpl.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/DataMutex.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/webrender/webrender_ffi.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/webrender/WebRenderTypes.h"
+#include "mozilla/layers/CompositionRecorder.h"
+#include "mozilla/layers/SynchronousTask.h"
+#include "mozilla/VsyncDispatcher.h"
+
+#include <list>
+#include <queue>
+#include <unordered_map>
+
+namespace mozilla {
+namespace gl {
+class GLContext;
+} // namespace gl
+namespace layers {
+class CompositorBridgeParent;
+class SurfacePool;
+} // namespace layers
+namespace wr {
+
+typedef MozPromise<MemoryReport, bool, true> MemoryReportPromise;
+
+class RendererOGL;
+class RenderTextureHost;
+class RenderThread;
+
+/// A rayon thread pool that is shared by all WebRender instances within a
+/// process.
+class WebRenderThreadPool {
+ public:
+ explicit WebRenderThreadPool(bool low_priority);
+
+ ~WebRenderThreadPool();
+
+ wr::WrThreadPool* Raw() {
+ // If this pointer is null we are likely at some late shutdown stage,
+ // when threads are no longer safe to interact with.
+ MOZ_RELEASE_ASSERT(mThreadPool);
+ return mThreadPool;
+ }
+
+ /// Prematurely destroys this handle to the thread pool.
+ /// After calling this the object is useless.
+ void Release();
+
+ protected:
+ wr::WrThreadPool* mThreadPool;
+};
+
+class WebRenderProgramCache final {
+ public:
+ explicit WebRenderProgramCache(wr::WrThreadPool* aThreadPool);
+
+ ~WebRenderProgramCache();
+
+ wr::WrProgramCache* Raw() { return mProgramCache; }
+
+ protected:
+ wr::WrProgramCache* mProgramCache;
+};
+
+class WebRenderShaders final {
+ public:
+ WebRenderShaders(gl::GLContext* gl, WebRenderProgramCache* programCache);
+ ~WebRenderShaders();
+
+ wr::WrShaders* RawShaders() { return mShaders; }
+
+ protected:
+ RefPtr<gl::GLContext> mGL;
+ wr::WrShaders* mShaders;
+};
+
+class WebRenderPipelineInfo final {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebRenderPipelineInfo);
+
+ const wr::WrPipelineInfo& Raw() const { return mPipelineInfo; }
+ wr::WrPipelineInfo& Raw() { return mPipelineInfo; }
+
+ protected:
+ ~WebRenderPipelineInfo() = default;
+ wr::WrPipelineInfo mPipelineInfo;
+};
+
+/// Base class for an event that can be scheduled to run on the render thread.
+///
+/// The event can be passed through the same channels as regular WebRender
+/// messages to preserve ordering.
+class RendererEvent {
+ public:
+ virtual ~RendererEvent() = default;
+ virtual void Run(RenderThread& aRenderThread, wr::WindowId aWindow) = 0;
+};
+
+/// The render thread is where WebRender issues all of its GPU work, and as much
+/// as possible this thread should only serve this purpose.
+///
+/// The render thread owns the different RendererOGLs (one per window) and
+/// implements the RenderNotifier api exposed by the WebRender bindings.
+///
+/// We should generally avoid posting tasks to the render thread's event loop
+/// directly and instead use the RendererEvent mechanism which avoids races
+/// between the events and WebRender's own messages.
+///
+/// The GL context(s) should be created and used on this thread only.
+/// XXX - I've tried to organize code so that we can potentially avoid making
+/// this a singleton since this bad habit has a tendency to bite us later, but
+/// I haven't gotten all the way there either, in order to focus on the more
+/// important pieces first. So we are a bit in-between (this is totally a
+/// singleton but in some places we pretend it's not). Hopefully we can evolve
+/// this in a way that keeps the door open to removing the singleton bits.
+class RenderThread final {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_MAIN_THREAD(RenderThread)
+
+ public:
+ /// Can be called from any thread.
+ static RenderThread* Get();
+
+ /// Can only be called from the main thread.
+ static void Start();
+
+ /// Can only be called from the main thread.
+ static void ShutDown();
+
+ /// Can be called from any thread.
+ /// In most cases it is best to post RendererEvents through WebRenderAPI
+ /// instead of scheduling directly to this message loop (so as to preserve the
+ /// ordering of the messages).
+ static MessageLoop* Loop();
+
+ /// Can be called from any thread.
+ static bool IsInRenderThread();
+
+ // Can be called from any thread. Dispatches an event to the Renderer thread
+ // to iterate over all Renderers, accumulates memory statistics, and resolves
+ // the return promise.
+ static RefPtr<MemoryReportPromise> AccumulateMemoryReport(
+ MemoryReport aInitial);
+
+ /// Can only be called from the render thread.
+ void AddRenderer(wr::WindowId aWindowId, UniquePtr<RendererOGL> aRenderer);
+
+ /// Can only be called from the render thread.
+ void RemoveRenderer(wr::WindowId aWindowId);
+
+ /// Can only be called from the render thread.
+ RendererOGL* GetRenderer(wr::WindowId aWindowId);
+
+ // RenderNotifier implementation
+
+ /// Automatically forwarded to the render thread. Will trigger a render for
+ /// the current pending frame once one call per document in that pending
+ /// frame has been received.
+ void HandleFrameOneDoc(wr::WindowId aWindowId, bool aRender);
+
+ /// Automatically forwarded to the render thread.
+ void SetClearColor(wr::WindowId aWindowId, wr::ColorF aColor);
+
+ /// Automatically forwarded to the render thread.
+ void SetProfilerUI(wr::WindowId aWindowId, nsCString aUI);
+
+ /// Automatically forwarded to the render thread.
+ void PipelineSizeChanged(wr::WindowId aWindowId, uint64_t aPipelineId,
+ float aWidth, float aHeight);
+
+ /// Automatically forwarded to the render thread.
+ void RunEvent(wr::WindowId aWindowId, UniquePtr<RendererEvent> aCallBack);
+
+ /// Can only be called from the render thread.
+ void UpdateAndRender(wr::WindowId aWindowId, const VsyncId& aStartId,
+ const TimeStamp& aStartTime, bool aRender,
+ const Maybe<gfx::IntSize>& aReadbackSize,
+ const Maybe<wr::ImageFormat>& aReadbackFormat,
+ const Maybe<Range<uint8_t>>& aReadbackBuffer,
+ bool* aNeedsYFlip = nullptr);
+
+ void Pause(wr::WindowId aWindowId);
+ bool Resume(wr::WindowId aWindowId);
+
+ /// Can be called from any thread.
+ void RegisterExternalImage(uint64_t aExternalImageId,
+ already_AddRefed<RenderTextureHost> aTexture);
+
+ /// Can be called from any thread.
+ void UnregisterExternalImage(uint64_t aExternalImageId);
+
+ /// Can be called from any thread.
+ void PrepareForUse(uint64_t aExternalImageId);
+
+ /// Can be called from any thread.
+ void NotifyNotUsed(uint64_t aExternalImageId);
+
+ /// Can be called from any thread.
+ void NotifyForUse(uint64_t aExternalImageId);
+
+ void HandleRenderTextureOps();
+
+ /// Can only be called from the render thread.
+ void UnregisterExternalImageDuringShutdown(uint64_t aExternalImageId);
+
+ /// Can only be called from the render thread.
+ RenderTextureHost* GetRenderTexture(ExternalImageId aExternalImageId);
+
+ /// Can be called from any thread.
+ bool IsDestroyed(wr::WindowId aWindowId);
+ /// Can be called from any thread.
+ void SetDestroyed(wr::WindowId aWindowId);
+ /// Can be called from any thread.
+ bool TooManyPendingFrames(wr::WindowId aWindowId);
+ /// Can be called from any thread.
+ void IncPendingFrameCount(wr::WindowId aWindowId, const VsyncId& aStartId,
+ const TimeStamp& aStartTime);
+ /// Can be called from any thread.
+ void DecPendingFrameBuildCount(wr::WindowId aWindowId);
+
+ /// Can be called from any thread.
+ WebRenderThreadPool& ThreadPool() { return mThreadPool; }
+
+ /// Thread pool for low priority scene building
+ /// Can be called from any thread.
+ WebRenderThreadPool& ThreadPoolLP() { return mThreadPoolLP; }
+
+ /// Returns the cache used to serialize shader programs to disk, if enabled.
+ ///
+ /// Can only be called from the render thread.
+ WebRenderProgramCache* GetProgramCache() {
+ MOZ_ASSERT(IsInRenderThread());
+ return mProgramCache.get();
+ }
+
+ /// Can only be called from the render thread.
+ WebRenderShaders* GetShaders() {
+ MOZ_ASSERT(IsInRenderThread());
+ return mShaders.get();
+ }
+
+ /// Can only be called from the render thread.
+ gl::GLContext* SharedGL(nsACString& aError);
+ gl::GLContext* SharedGL();
+ void ClearSharedGL();
+ RefPtr<layers::SurfacePool> SharedSurfacePool();
+ void ClearSharedSurfacePool();
+
+ /// Can only be called from the render thread.
+ void HandleDeviceReset(const char* aWhere,
+ layers::CompositorBridgeParent* aBridge,
+ GLenum aReason);
+ /// Can only be called from the render thread.
+ bool IsHandlingDeviceReset();
+ /// Can be called from any thread.
+ void SimulateDeviceReset();
+
+ /// Can only be called from the render thread.
+ void NotifyWebRenderError(WebRenderError aError);
+
+ /// Can only be called from the render thread.
+ void HandleWebRenderError(WebRenderError aError);
+ /// Can only be called from the render thread.
+ bool IsHandlingWebRenderError();
+
+ /// Can only be called from the render thread.
+ bool SyncObjectNeeded();
+
+ size_t RendererCount();
+
+ void BeginRecordingForWindow(wr::WindowId aWindowId,
+ const TimeStamp& aRecordingStart,
+ wr::PipelineId aRootPipelineId);
+
+ void WriteCollectedFramesForWindow(wr::WindowId aWindowId);
+
+ Maybe<layers::CollectedFrames> GetCollectedFramesForWindow(
+ wr::WindowId aWindowId);
+
+ static void MaybeEnableGLDebugMessage(gl::GLContext* aGLContext);
+
+ private:
+ enum class RenderTextureOp {
+ PrepareForUse,
+ NotifyForUse,
+ NotifyNotUsed,
+ };
+
+ explicit RenderThread(base::Thread* aThread);
+
+ void DeferredRenderTextureHostDestroy();
+ void ShutDownTask(layers::SynchronousTask* aTask);
+ void InitDeviceTask();
+
+ void DoAccumulateMemoryReport(MemoryReport,
+ const RefPtr<MemoryReportPromise::Private>&);
+
+ void AddRenderTextureOp(RenderTextureOp aOp, uint64_t aExternalImageId);
+
+ ~RenderThread();
+
+ base::Thread* const mThread;
+
+ WebRenderThreadPool mThreadPool;
+ WebRenderThreadPool mThreadPoolLP;
+
+ UniquePtr<WebRenderProgramCache> mProgramCache;
+ UniquePtr<WebRenderShaders> mShaders;
+
+ // An optional shared GLContext to be used for all
+ // windows.
+ RefPtr<gl::GLContext> mSharedGL;
+
+ RefPtr<layers::SurfacePool> mSurfacePool;
+
+ std::map<wr::WindowId, UniquePtr<RendererOGL>> mRenderers;
+
+ struct PendingFrameInfo {
+ TimeStamp mStartTime;
+ VsyncId mStartId;
+ bool mFrameNeedsRender = false;
+ };
+
+ struct WindowInfo {
+ int64_t PendingCount() { return mPendingFrames.size(); }
+ // If mIsRendering is true, mPendingFrames.front() is currently being
+ // rendered.
+ std::queue<PendingFrameInfo> mPendingFrames;
+ uint8_t mPendingFrameBuild = 0;
+ bool mIsDestroyed = false;
+ };
+
+ DataMutex<std::unordered_map<uint64_t, WindowInfo*>> mWindowInfos;
+
+ Mutex mRenderTextureMapLock;
+ std::unordered_map<uint64_t, RefPtr<RenderTextureHost>> mRenderTextures;
+ std::unordered_map<uint64_t, RefPtr<RenderTextureHost>>
+ mSyncObjectNeededRenderTextures;
+ std::list<std::pair<RenderTextureOp, RefPtr<RenderTextureHost>>>
+ mRenderTextureOps;
+
+ // Used to remove all RenderTextureHost that are going to be removed by
+ // a deferred callback and remove them right away without waiting for the
+ // callback. On device reset we have to remove all GL related resources right
+ // away.
+ std::list<RefPtr<RenderTextureHost>> mRenderTexturesDeferred;
+ bool mHasShutdown;
+
+ bool mHandlingDeviceReset;
+ bool mHandlingWebRenderError;
+};
+
+} // namespace wr
+} // namespace mozilla
+
+#endif
diff --git a/gfx/webrender_bindings/RendererOGL.cpp b/gfx/webrender_bindings/RendererOGL.cpp
new file mode 100644
index 0000000000..0e1bd7a812
--- /dev/null
+++ b/gfx/webrender_bindings/RendererOGL.cpp
@@ -0,0 +1,434 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "RendererOGL.h"
+
+#include "base/task.h"
+#include "GLContext.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/gfx/Types.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "mozilla/layers/LayersTypes.h"
+#include "mozilla/layers/ProfilerScreenshots.h"
+#include "mozilla/webrender/RenderCompositor.h"
+#include "mozilla/webrender/RenderTextureHost.h"
+#include "mozilla/widget/CompositorWidget.h"
+
+namespace mozilla {
+namespace wr {
+
+class RendererRecordedFrame final : public layers::RecordedFrame {
+ public:
+ RendererRecordedFrame(const TimeStamp& aTimeStamp, wr::Renderer* aRenderer,
+ const wr::RecordedFrameHandle aHandle,
+ const gfx::IntSize& aSize)
+ : RecordedFrame(aTimeStamp),
+ mRenderer(aRenderer),
+ mSize(aSize),
+ mHandle(aHandle) {}
+
+ already_AddRefed<gfx::DataSourceSurface> GetSourceSurface() override {
+ if (!mSurface) {
+ mSurface = gfx::Factory::CreateDataSourceSurface(
+ mSize, gfx::SurfaceFormat::B8G8R8A8, /* aZero = */ false);
+
+ gfx::DataSourceSurface::ScopedMap map(mSurface,
+ gfx::DataSourceSurface::WRITE);
+
+ if (!wr_renderer_map_recorded_frame(mRenderer, mHandle, map.GetData(),
+ map.GetStride() * mSize.height,
+ map.GetStride())) {
+ return nullptr;
+ }
+ }
+
+ return do_AddRef(mSurface);
+ }
+
+ private:
+ wr::Renderer* mRenderer;
+ RefPtr<gfx::DataSourceSurface> mSurface;
+ gfx::IntSize mSize;
+ wr::RecordedFrameHandle mHandle;
+};
+
+wr::WrExternalImage wr_renderer_lock_external_image(
+ void* aObj, wr::ExternalImageId aId, uint8_t aChannelIndex,
+ wr::ImageRendering aRendering) {
+ RendererOGL* renderer = reinterpret_cast<RendererOGL*>(aObj);
+ RenderTextureHost* texture = renderer->GetRenderTexture(aId);
+ MOZ_ASSERT(texture);
+ if (!texture) {
+ gfxCriticalNoteOnce << "Failed to lock ExternalImage for extId:"
+ << AsUint64(aId);
+ return InvalidToWrExternalImage();
+ }
+ if (auto* gl = renderer->gl()) {
+ return texture->Lock(aChannelIndex, gl, aRendering);
+ } else if (auto* swgl = renderer->swgl()) {
+ return texture->LockSWGL(aChannelIndex, swgl, renderer->GetCompositor(),
+ aRendering);
+ } else {
+ gfxCriticalNoteOnce
+ << "No GL or SWGL context available to lock ExternalImage for extId:"
+ << AsUint64(aId);
+ return InvalidToWrExternalImage();
+ }
+}
+
+void wr_renderer_unlock_external_image(void* aObj, wr::ExternalImageId aId,
+ uint8_t aChannelIndex) {
+ RendererOGL* renderer = reinterpret_cast<RendererOGL*>(aObj);
+ RenderTextureHost* texture = renderer->GetRenderTexture(aId);
+ MOZ_ASSERT(texture);
+ if (!texture) {
+ return;
+ }
+ if (renderer->gl()) {
+ texture->Unlock();
+ } else if (renderer->swgl()) {
+ texture->UnlockSWGL();
+ }
+}
+
+RendererOGL::RendererOGL(RefPtr<RenderThread>&& aThread,
+ UniquePtr<RenderCompositor> aCompositor,
+ wr::WindowId aWindowId, wr::Renderer* aRenderer,
+ layers::CompositorBridgeParent* aBridge)
+ : mThread(aThread),
+ mCompositor(std::move(aCompositor)),
+ mRenderer(aRenderer),
+ mBridge(aBridge),
+ mWindowId(aWindowId),
+ mDisableNativeCompositor(false) {
+ MOZ_ASSERT(mThread);
+ MOZ_ASSERT(mCompositor);
+ MOZ_ASSERT(mRenderer);
+ MOZ_ASSERT(mBridge);
+ MOZ_COUNT_CTOR(RendererOGL);
+}
+
+RendererOGL::~RendererOGL() {
+ MOZ_COUNT_DTOR(RendererOGL);
+ if (!mCompositor->MakeCurrent()) {
+ gfxCriticalNote
+ << "Failed to make render context current during destroying.";
+ // Leak resources!
+ } else {
+ wr_renderer_delete(mRenderer);
+ }
+}
+
+wr::WrExternalImageHandler RendererOGL::GetExternalImageHandler() {
+ return wr::WrExternalImageHandler{
+ this,
+ };
+}
+
+void RendererOGL::Update() {
+ mCompositor->Update();
+ if (mCompositor->MakeCurrent()) {
+ wr_renderer_update(mRenderer);
+ }
+}
+
+static void DoWebRenderDisableNativeCompositor(
+ layers::CompositorBridgeParent* aBridge) {
+ aBridge->NotifyWebRenderDisableNativeCompositor();
+}
+
+RenderedFrameId RendererOGL::UpdateAndRender(
+ const Maybe<gfx::IntSize>& aReadbackSize,
+ const Maybe<wr::ImageFormat>& aReadbackFormat,
+ const Maybe<Range<uint8_t>>& aReadbackBuffer, bool* aNeedsYFlip,
+ RendererStats* aOutStats) {
+ mozilla::widget::WidgetRenderingContext widgetContext;
+
+#if defined(XP_MACOSX)
+ widgetContext.mGL = mCompositor->gl();
+#endif
+
+ if (!mCompositor->GetWidget()->PreRender(&widgetContext)) {
+ // XXX This could cause oom in webrender since pending_texture_updates is
+ // not handled. It needs to be addressed.
+ return RenderedFrameId();
+ ;
+ }
+ // XXX set clear color if MOZ_WIDGET_ANDROID is defined.
+
+ if (mThread->IsHandlingDeviceReset() || !mCompositor->BeginFrame()) {
+ CheckGraphicsResetStatus("BeginFrame", /* aForce */ true);
+ mCompositor->GetWidget()->PostRender(&widgetContext);
+ return RenderedFrameId();
+ }
+
+ auto size = mCompositor->GetBufferSize();
+ auto bufferAge = mCompositor->GetBufferAge();
+
+ wr_renderer_update(mRenderer);
+
+ bool fullRender = mCompositor->RequestFullRender();
+ // When we're rendering to an external target, we want to render everything.
+ if (mCompositor->UsePartialPresent() &&
+ (aReadbackBuffer.isSome() || layers::ProfilerScreenshots::IsEnabled())) {
+ fullRender = true;
+ }
+ if (fullRender) {
+ wr_renderer_force_redraw(mRenderer);
+ }
+
+ nsTArray<DeviceIntRect> dirtyRects;
+ if (!wr_renderer_render(mRenderer, size.width, size.height, bufferAge,
+ aOutStats, &dirtyRects)) {
+ mCompositor->CancelFrame();
+ RenderThread::Get()->HandleWebRenderError(WebRenderError::RENDER);
+ mCompositor->GetWidget()->PostRender(&widgetContext);
+ return RenderedFrameId();
+ }
+
+ if (aReadbackBuffer.isSome()) {
+ MOZ_ASSERT(aReadbackSize.isSome());
+ MOZ_ASSERT(aReadbackFormat.isSome());
+ if (!mCompositor->MaybeReadback(aReadbackSize.ref(), aReadbackFormat.ref(),
+ aReadbackBuffer.ref(), aNeedsYFlip)) {
+ wr_renderer_readback(mRenderer, aReadbackSize.ref().width,
+ aReadbackSize.ref().height, aReadbackFormat.ref(),
+ &aReadbackBuffer.ref()[0],
+ aReadbackBuffer.ref().length());
+ if (aNeedsYFlip) {
+ *aNeedsYFlip = !mCompositor->SurfaceOriginIsTopLeft();
+ }
+ }
+ }
+
+ if (size.Width() != 0 && size.Height() != 0) {
+ if (!mCompositor->MaybeGrabScreenshot(size.ToUnknownSize())) {
+ mScreenshotGrabber.MaybeGrabScreenshot(this, size.ToUnknownSize());
+ }
+ }
+
+ RenderedFrameId frameId = mCompositor->EndFrame(dirtyRects);
+
+ mCompositor->GetWidget()->PostRender(&widgetContext);
+
+#if defined(ENABLE_FRAME_LATENCY_LOG)
+ if (mFrameStartTime) {
+ uint32_t latencyMs =
+ round((TimeStamp::Now() - mFrameStartTime).ToMilliseconds());
+ printf_stderr("generate frame latencyMs latencyMs %d\n", latencyMs);
+ }
+ // Clear frame start time
+ mFrameStartTime = TimeStamp();
+#endif
+
+ if (!mCompositor->MaybeProcessScreenshotQueue()) {
+ mScreenshotGrabber.MaybeProcessQueue(this);
+ }
+
+ // TODO: Flush pending actions such as texture deletions/unlocks and
+ // textureHosts recycling.
+
+ return frameId;
+}
+
+bool RendererOGL::EnsureAsyncScreenshot() {
+ if (mCompositor->SupportAsyncScreenshot()) {
+ return true;
+ }
+ if (!mDisableNativeCompositor) {
+ layers::CompositorThread()->Dispatch(
+ NewRunnableFunction("DoWebRenderDisableNativeCompositorRunnable",
+ &DoWebRenderDisableNativeCompositor, mBridge));
+
+ mDisableNativeCompositor = true;
+ gfxCriticalNote << "Disable native compositor for async screenshot";
+ }
+ return false;
+}
+
+void RendererOGL::CheckGraphicsResetStatus(const char* aCaller, bool aForce) {
+ if (mCompositor) {
+ auto reason = mCompositor->IsContextLost(aForce);
+ if (reason != LOCAL_GL_NO_ERROR) {
+ RenderThread::Get()->HandleDeviceReset(aCaller, mBridge, reason);
+ }
+ }
+}
+
+void RendererOGL::WaitForGPU() {
+ if (!mCompositor->WaitForGPU()) {
+ CheckGraphicsResetStatus("WaitForGPU", /* aForce */ true);
+ }
+}
+
+ipc::FileDescriptor RendererOGL::GetAndResetReleaseFence() {
+ return mCompositor->GetAndResetReleaseFence();
+}
+
+RenderedFrameId RendererOGL::GetLastCompletedFrameId() {
+ return mCompositor->GetLastCompletedFrameId();
+}
+
+RenderedFrameId RendererOGL::UpdateFrameId() {
+ return mCompositor->UpdateFrameId();
+}
+
+void RendererOGL::Pause() { mCompositor->Pause(); }
+
+bool RendererOGL::Resume() { return mCompositor->Resume(); }
+
+bool RendererOGL::IsPaused() { return mCompositor->IsPaused(); }
+
+layers::SyncObjectHost* RendererOGL::GetSyncObject() const {
+ return mCompositor->GetSyncObject();
+}
+
+gl::GLContext* RendererOGL::gl() const { return mCompositor->gl(); }
+
+void* RendererOGL::swgl() const { return mCompositor->swgl(); }
+
+void RendererOGL::SetFrameStartTime(const TimeStamp& aTime) {
+ if (mFrameStartTime) {
+ // frame start time is already set. This could happen when multiple
+ // generate frame requests are merged by webrender.
+ return;
+ }
+ mFrameStartTime = aTime;
+}
+
+void RendererOGL::BeginRecording(const TimeStamp& aRecordingStart,
+ wr::PipelineId aRootPipelineId) {
+ MOZ_ASSERT(!mCompositionRecorder);
+
+ mRootPipelineId = aRootPipelineId;
+ mCompositionRecorder =
+ MakeUnique<layers::CompositionRecorder>(aRecordingStart);
+ mCompositor->MaybeRequestAllowFrameRecording(true);
+}
+
+void RendererOGL::MaybeRecordFrame(const WebRenderPipelineInfo* aPipelineInfo) {
+ if (!mCompositionRecorder || !EnsureAsyncScreenshot()) {
+ return;
+ }
+
+ if (!mRenderer || !aPipelineInfo || !DidPaintContent(aPipelineInfo)) {
+ return;
+ }
+
+ if (mCompositor->MaybeRecordFrame(*mCompositionRecorder)) {
+ return;
+ }
+
+ wr::RecordedFrameHandle handle{0};
+ gfx::IntSize size(0, 0);
+
+ if (wr_renderer_record_frame(mRenderer, wr::ImageFormat::BGRA8, &handle,
+ &size.width, &size.height)) {
+ RefPtr<layers::RecordedFrame> frame =
+ new RendererRecordedFrame(TimeStamp::Now(), mRenderer, handle, size);
+
+ mCompositionRecorder->RecordFrame(frame);
+ }
+}
+
+bool RendererOGL::DidPaintContent(const WebRenderPipelineInfo* aFrameEpochs) {
+ const wr::WrPipelineInfo& info = aFrameEpochs->Raw();
+ bool didPaintContent = false;
+
+ // Check if a non-root pipeline has updated to a new epoch.
+ // We treat all non-root pipelines as "content" pipelines, even if they're
+ // not fed by content paints, such as videos (see bug 1665512).
+ for (const auto& epoch : info.epochs) {
+ const wr::PipelineId pipelineId = epoch.pipeline_id;
+
+ if (pipelineId == mRootPipelineId) {
+ continue;
+ }
+
+ const auto it = mContentPipelineEpochs.find(AsUint64(pipelineId));
+ if (it == mContentPipelineEpochs.end() || it->second != epoch.epoch) {
+ // This pipeline has updated since last render or has newly rendered.
+ didPaintContent = true;
+ mContentPipelineEpochs[AsUint64(pipelineId)] = epoch.epoch;
+ }
+ }
+
+ for (const auto& removedPipeline : info.removed_pipelines) {
+ const wr::PipelineId pipelineId = removedPipeline.pipeline_id;
+ if (pipelineId == mRootPipelineId) {
+ continue;
+ }
+ mContentPipelineEpochs.erase(AsUint64(pipelineId));
+ }
+
+ return didPaintContent;
+}
+void RendererOGL::WriteCollectedFrames() {
+ if (!mCompositionRecorder) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ false,
+ "Attempted to write frames from a window that was not recording.");
+ return;
+ }
+
+ mCompositionRecorder->WriteCollectedFrames();
+
+ wr_renderer_release_composition_recorder_structures(mRenderer);
+
+ mCompositor->MaybeRequestAllowFrameRecording(false);
+ mCompositionRecorder = nullptr;
+}
+
+Maybe<layers::CollectedFrames> RendererOGL::GetCollectedFrames() {
+ if (!mCompositionRecorder) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ false, "Attempted to get frames from a window that was not recording.");
+ return Nothing();
+ }
+
+ layers::CollectedFrames frames = mCompositionRecorder->GetCollectedFrames();
+
+ wr_renderer_release_composition_recorder_structures(mRenderer);
+
+ mCompositor->MaybeRequestAllowFrameRecording(false);
+ mCompositionRecorder = nullptr;
+
+ return Some(std::move(frames));
+}
+
+RefPtr<WebRenderPipelineInfo> RendererOGL::FlushPipelineInfo() {
+ RefPtr<WebRenderPipelineInfo> info = new WebRenderPipelineInfo();
+ wr_renderer_flush_pipeline_info(mRenderer, &info->Raw());
+ return info;
+}
+
+RenderTextureHost* RendererOGL::GetRenderTexture(
+ wr::ExternalImageId aExternalImageId) {
+ return mThread->GetRenderTexture(aExternalImageId);
+}
+
+void RendererOGL::AccumulateMemoryReport(MemoryReport* aReport) {
+ wr_renderer_accumulate_memory_report(GetRenderer(), aReport);
+
+ LayoutDeviceIntSize size = mCompositor->GetBufferSize();
+
+ // Assume BGRA8 for the format since it's not exposed anywhere,
+ // and all compositor backends should be using that.
+ uintptr_t swapChainSize = size.width * size.height *
+ BytesPerPixel(gfx::SurfaceFormat::B8G8R8A8) *
+ (mCompositor->UseTripleBuffering() ? 3 : 2);
+ aReport->swap_chain += swapChainSize;
+}
+
+void RendererOGL::SetProfilerUI(const nsCString& aUI) {
+ wr_renderer_set_profiler_ui(GetRenderer(), (const uint8_t*)aUI.get(),
+ aUI.Length());
+}
+
+} // namespace wr
+} // namespace mozilla
diff --git a/gfx/webrender_bindings/RendererOGL.h b/gfx/webrender_bindings/RendererOGL.h
new file mode 100644
index 0000000000..97af04c4e1
--- /dev/null
+++ b/gfx/webrender_bindings/RendererOGL.h
@@ -0,0 +1,168 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef MOZILLA_LAYERS_RENDEREROGL_H
+#define MOZILLA_LAYERS_RENDEREROGL_H
+
+#include "mozilla/ipc/FileDescriptor.h"
+#include "mozilla/layers/CompositorTypes.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/webrender/RenderThread.h"
+#include "mozilla/webrender/WebRenderTypes.h"
+#include "mozilla/webrender/webrender_ffi.h"
+#include "mozilla/webrender/RendererScreenshotGrabber.h"
+
+namespace mozilla {
+
+namespace gfx {
+class DrawTarget;
+}
+
+namespace gl {
+class GLContext;
+}
+
+namespace layers {
+class CompositorBridgeParent;
+class SyncObjectHost;
+} // namespace layers
+
+namespace widget {
+class CompositorWidget;
+}
+
+namespace wr {
+
+class RenderCompositor;
+class RenderTextureHost;
+
+/// Owns the WebRender renderer and GL context.
+///
+/// There is one renderer per window, all owned by the render thread.
+/// This class is a similar abstraction to CompositorOGL except that it is used
+/// on the render thread instead of the compositor thread.
+class RendererOGL {
+ friend wr::WrExternalImage LockExternalImage(void* aObj,
+ wr::ExternalImageId aId,
+ uint8_t aChannelIndex,
+ wr::ImageRendering);
+ friend void UnlockExternalImage(void* aObj, wr::ExternalImageId aId,
+ uint8_t aChannelIndex);
+
+ public:
+ wr::WrExternalImageHandler GetExternalImageHandler();
+
+ /// This can be called on the render thread only.
+ void Update();
+
+ /// This can be called on the render thread only.
+ RenderedFrameId UpdateAndRender(const Maybe<gfx::IntSize>& aReadbackSize,
+ const Maybe<wr::ImageFormat>& aReadbackFormat,
+ const Maybe<Range<uint8_t>>& aReadbackBuffer,
+ bool* aNeedsYFlip, RendererStats* aOutStats);
+
+ /// This can be called on the render thread only.
+ void WaitForGPU();
+
+ /// This can be called on the render thread only.
+ ipc::FileDescriptor GetAndResetReleaseFence();
+
+ /// This can be called on the render thread only.
+ RenderedFrameId GetLastCompletedFrameId();
+
+ /// This can be called on the render thread only.
+ RenderedFrameId UpdateFrameId();
+
+ /// This can be called on the render thread only.
+ void SetProfilerEnabled(bool aEnabled);
+
+ /// This can be called on the render thread only.
+ void SetFrameStartTime(const TimeStamp& aTime);
+
+ /// These can be called on the render thread only.
+ void BeginRecording(const TimeStamp& aRecordingStart,
+ wr::PipelineId aPipelineId);
+ void MaybeRecordFrame(const WebRenderPipelineInfo* aPipelineInfo);
+ void WriteCollectedFrames();
+ Maybe<layers::CollectedFrames> GetCollectedFrames();
+
+ /// This can be called on the render thread only.
+ ~RendererOGL();
+
+ /// This can be called on the render thread only.
+ RendererOGL(RefPtr<RenderThread>&& aThread,
+ UniquePtr<RenderCompositor> aCompositor, wr::WindowId aWindowId,
+ wr::Renderer* aRenderer, layers::CompositorBridgeParent* aBridge);
+
+ /// This can be called on the render thread only.
+ void Pause();
+
+ /// This can be called on the render thread only.
+ bool Resume();
+
+ /// This can be called on the render thread only.
+ bool IsPaused();
+
+ /// This can be called on the render thread only.
+ void CheckGraphicsResetStatus(const char* aCaller, bool aForce);
+
+ layers::SyncObjectHost* GetSyncObject() const;
+
+ layers::CompositorBridgeParent* GetCompositorBridge() { return mBridge; }
+
+ RefPtr<WebRenderPipelineInfo> FlushPipelineInfo();
+
+ RenderTextureHost* GetRenderTexture(wr::ExternalImageId aExternalImageId);
+
+ RenderCompositor* GetCompositor() { return mCompositor.get(); }
+
+ void AccumulateMemoryReport(MemoryReport* aReport);
+
+ void SetProfilerUI(const nsCString& aUI);
+
+ wr::Renderer* GetRenderer() { return mRenderer; }
+
+ gl::GLContext* gl() const;
+
+ void* swgl() const;
+
+ bool EnsureAsyncScreenshot();
+
+ protected:
+ /**
+ * Determine if any content pipelines updated, and update
+ * mContentPipelineEpochs.
+ */
+ bool DidPaintContent(const wr::WebRenderPipelineInfo* aFrameEpochs);
+
+ RefPtr<RenderThread> mThread;
+ UniquePtr<RenderCompositor> mCompositor;
+ UniquePtr<layers::CompositionRecorder> mCompositionRecorder; // can be null
+ wr::Renderer* mRenderer;
+ layers::CompositorBridgeParent* mBridge;
+ wr::WindowId mWindowId;
+ TimeStamp mFrameStartTime;
+
+ bool mDisableNativeCompositor;
+
+ RendererScreenshotGrabber mScreenshotGrabber;
+
+ // The id of the root WebRender pipeline.
+ //
+ // All other pipelines are considered content.
+ wr::PipelineId mRootPipelineId;
+
+ // A mapping of wr::PipelineId to the epochs when last they updated.
+ //
+ // We need to use uint64_t here since wr::PipelineId is not default
+ // constructable.
+ std::unordered_map<uint64_t, wr::Epoch> mContentPipelineEpochs;
+};
+
+} // namespace wr
+} // namespace mozilla
+
+#endif
diff --git a/gfx/webrender_bindings/RendererScreenshotGrabber.cpp b/gfx/webrender_bindings/RendererScreenshotGrabber.cpp
new file mode 100644
index 0000000000..0c8b8042d4
--- /dev/null
+++ b/gfx/webrender_bindings/RendererScreenshotGrabber.cpp
@@ -0,0 +1,106 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "RendererScreenshotGrabber.h"
+
+#include "RendererOGL.h"
+
+#include "mozilla/gfx/2D.h"
+
+using mozilla::layers::ProfilerScreenshots;
+
+namespace mozilla {
+namespace wr {
+
+RendererScreenshotGrabber::RendererScreenshotGrabber() {
+ mMaxScreenshotSize = ProfilerScreenshots::ScreenshotSize();
+}
+
+void RendererScreenshotGrabber::MaybeGrabScreenshot(
+ RendererOGL* aRendererOGL, const gfx::IntSize& aWindowSize) {
+ bool isEnabled =
+ ProfilerScreenshots::IsEnabled() && aRendererOGL->EnsureAsyncScreenshot();
+
+ if (isEnabled) {
+ if (!mProfilerScreenshots) {
+ mProfilerScreenshots = new ProfilerScreenshots();
+ }
+
+ GrabScreenshot(aRendererOGL->GetRenderer(), aWindowSize);
+ } else if (mProfilerScreenshots) {
+ Destroy(aRendererOGL->GetRenderer());
+ }
+}
+
+void RendererScreenshotGrabber::MaybeProcessQueue(RendererOGL* aRendererOGL) {
+ bool isEnabled =
+ ProfilerScreenshots::IsEnabled() && aRendererOGL->EnsureAsyncScreenshot();
+
+ if (isEnabled) {
+ if (!mProfilerScreenshots) {
+ mProfilerScreenshots = new ProfilerScreenshots();
+ }
+
+ ProcessQueue(aRendererOGL->GetRenderer());
+ } else if (mProfilerScreenshots) {
+ Destroy(aRendererOGL->GetRenderer());
+ }
+}
+
+void RendererScreenshotGrabber::Destroy(Renderer* aRenderer) {
+ mQueue.Clear();
+ mCurrentFrameQueueItem = Nothing();
+ mProfilerScreenshots = nullptr;
+
+ wr_renderer_release_profiler_structures(aRenderer);
+}
+
+void RendererScreenshotGrabber::GrabScreenshot(
+ Renderer* aRenderer, const gfx::IntSize& aWindowSize) {
+ gfx::IntSize screenshotSize;
+
+ AsyncScreenshotHandle handle = wr_renderer_get_screenshot_async(
+ aRenderer, 0, 0, aWindowSize.width, aWindowSize.height,
+ mMaxScreenshotSize.width, mMaxScreenshotSize.height, ImageFormat::BGRA8,
+ &screenshotSize.width, &screenshotSize.height);
+
+ mCurrentFrameQueueItem = Some(QueueItem{
+ TimeStamp::Now(),
+ handle,
+ screenshotSize,
+ aWindowSize,
+ reinterpret_cast<uintptr_t>(this),
+ });
+}
+
+void RendererScreenshotGrabber::ProcessQueue(Renderer* aRenderer) {
+ for (const auto& item : mQueue) {
+ mProfilerScreenshots->SubmitScreenshot(
+ item.mWindowIdentifier, item.mWindowSize, item.mScreenshotSize,
+ item.mTimeStamp,
+ [&item, aRenderer](gfx::DataSourceSurface* aTargetSurface) {
+ gfx::DataSourceSurface::ScopedMap map(aTargetSurface,
+ gfx::DataSourceSurface::WRITE);
+ int32_t destStride = map.GetStride();
+
+ bool success = wr_renderer_map_and_recycle_screenshot(
+ aRenderer, item.mHandle, map.GetData(),
+ destStride * aTargetSurface->GetSize().height, destStride);
+
+ return success;
+ });
+ }
+
+ mQueue.Clear();
+
+ if (mCurrentFrameQueueItem) {
+ mQueue.AppendElement(*mCurrentFrameQueueItem);
+ mCurrentFrameQueueItem = Nothing();
+ }
+}
+
+} // namespace wr
+} // namespace mozilla
diff --git a/gfx/webrender_bindings/RendererScreenshotGrabber.h b/gfx/webrender_bindings/RendererScreenshotGrabber.h
new file mode 100644
index 0000000000..ff68f9f804
--- /dev/null
+++ b/gfx/webrender_bindings/RendererScreenshotGrabber.h
@@ -0,0 +1,102 @@
+/* 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/. */
+
+#ifndef mozilla_layers_RendererScreenshotGrabber_h
+#define mozilla_layers_RendererScreenshotGrabber_h
+
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/layers/ProfilerScreenshots.h"
+#include "mozilla/webrender/webrender_ffi.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace wr {
+
+struct Renderer;
+class RendererOGL;
+
+/**
+ * Used by |RendererOGL| to grab screenshots from WebRender and submit them to
+ * the Gecko profiler.
+ *
+ * If the profiler is not running or the screenshots feature is disabled, no
+ * work will be done.
+ */
+class RendererScreenshotGrabber final {
+ public:
+ RendererScreenshotGrabber();
+
+ /**
+ * Grab a screenshot from WebRender if we are profiling and screenshots are
+ * enabled.
+ *
+ * The captured screenshot will not be mapped until the second call to
+ * |MaybeProcessQueue| after this call to |MaybeGrabScreenshot|.
+ */
+ void MaybeGrabScreenshot(RendererOGL* aRendererOGL,
+ const gfx::IntSize& aWindowSize);
+
+ /**
+ * Process the screenshots pending in the queue if we are profiling and
+ * screenshots are enabled.
+ */
+ void MaybeProcessQueue(RendererOGL* aRenderer);
+
+ private:
+ /**
+ * Drop all our allocated memory when we are no longer profiling.
+ *
+ * This will also instruct WebRender to drop all its Gecko profiler
+ * associated memory.
+ */
+ void Destroy(Renderer* aRenderer);
+
+ /**
+ * Actually grab a screenshot from WebRender.
+ */
+ void GrabScreenshot(Renderer* aRenderer, const gfx::IntSize& aWindowSize);
+
+ /**
+ * Process the screenshots pending in the queue.
+ */
+ void ProcessQueue(Renderer* aRenderer);
+
+ struct QueueItem {
+ mozilla::TimeStamp mTimeStamp;
+ AsyncScreenshotHandle mHandle;
+ gfx::IntSize mScreenshotSize;
+ gfx::IntSize mWindowSize;
+ uintptr_t mWindowIdentifier;
+ };
+
+ /**
+ * The maximum size for screenshots, as dictated by
+ * |ProfilerScrenshots::ScreenshotSize|.
+ */
+ gfx::IntSize mMaxScreenshotSize;
+
+ /**
+ * The queue of screenshots waiting to be processed and submitted.
+ */
+ nsTArray<QueueItem> mQueue;
+
+ /**
+ * The queue item for the current frame. This will be inserted into the queue
+ * after a call to |MaybeProcessQueue| so it will be not be processed until
+ * the next frame.
+ */
+ Maybe<QueueItem> mCurrentFrameQueueItem;
+
+ /**
+ * Our handle to the profiler screenshots object.
+ */
+ RefPtr<mozilla::layers::ProfilerScreenshots> mProfilerScreenshots;
+};
+
+} // namespace wr
+} // namespace mozilla
+
+#endif // mozilla_layers_RendererScreenshotGrabber_h
diff --git a/gfx/webrender_bindings/WebRenderAPI.cpp b/gfx/webrender_bindings/WebRenderAPI.cpp
new file mode 100644
index 0000000000..5504ba66e4
--- /dev/null
+++ b/gfx/webrender_bindings/WebRenderAPI.cpp
@@ -0,0 +1,1669 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "WebRenderAPI.h"
+
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/ipc/ByteBuf.h"
+#include "mozilla/webrender/RendererOGL.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "mozilla/ToString.h"
+#include "mozilla/webrender/RenderCompositor.h"
+#include "mozilla/widget/CompositorWidget.h"
+#include "mozilla/layers/SynchronousTask.h"
+#include "TextDrawTarget.h"
+#include "malloc_decls.h"
+
+// clang-format off
+#define WRDL_LOG(...)
+//#define WRDL_LOG(...) printf_stderr("WRDL(%p): " __VA_ARGS__)
+//#define WRDL_LOG(...) if (XRE_IsContentProcess()) printf_stderr("WRDL(%p): " __VA_ARGS__)
+// clang-format on
+
+namespace mozilla {
+using namespace layers;
+
+namespace wr {
+
+MOZ_DEFINE_MALLOC_SIZE_OF(WebRenderMallocSizeOf)
+MOZ_DEFINE_MALLOC_ENCLOSING_SIZE_OF(WebRenderMallocEnclosingSizeOf)
+
+enum SideBitsPacked {
+ eSideBitsPackedTop = 0x1000,
+ eSideBitsPackedRight = 0x2000,
+ eSideBitsPackedBottom = 0x4000,
+ eSideBitsPackedLeft = 0x8000
+};
+
+static uint16_t SideBitsToHitInfoBits(SideBits aSideBits) {
+ uint16_t ret = 0;
+ if (aSideBits & SideBits::eTop) {
+ ret |= eSideBitsPackedTop;
+ }
+ if (aSideBits & SideBits::eRight) {
+ ret |= eSideBitsPackedRight;
+ }
+ if (aSideBits & SideBits::eBottom) {
+ ret |= eSideBitsPackedBottom;
+ }
+ if (aSideBits & SideBits::eLeft) {
+ ret |= eSideBitsPackedLeft;
+ }
+ return ret;
+}
+
+class NewRenderer : public RendererEvent {
+ public:
+ NewRenderer(wr::DocumentHandle** aDocHandle,
+ layers::CompositorBridgeParent* aBridge,
+ WebRenderBackend* aBackend, WebRenderCompositor* aCompositor,
+ int32_t* aMaxTextureSize, bool* aUseANGLE, bool* aUseDComp,
+ bool* aUseTripleBuffering, bool* aSupportsExternalBufferTextures,
+ RefPtr<widget::CompositorWidget>&& aWidget,
+ layers::SynchronousTask* aTask, LayoutDeviceIntSize aSize,
+ layers::WindowKind aWindowKind, layers::SyncHandle* aHandle,
+ nsACString* aError)
+ : mDocHandle(aDocHandle),
+ mBackend(aBackend),
+ mCompositor(aCompositor),
+ mMaxTextureSize(aMaxTextureSize),
+ mUseANGLE(aUseANGLE),
+ mUseDComp(aUseDComp),
+ mUseTripleBuffering(aUseTripleBuffering),
+ mSupportsExternalBufferTextures(aSupportsExternalBufferTextures),
+ mBridge(aBridge),
+ mCompositorWidget(std::move(aWidget)),
+ mTask(aTask),
+ mSize(aSize),
+ mWindowKind(aWindowKind),
+ mSyncHandle(aHandle),
+ mError(aError) {
+ MOZ_COUNT_CTOR(NewRenderer);
+ }
+
+ MOZ_COUNTED_DTOR(NewRenderer)
+
+ void Run(RenderThread& aRenderThread, WindowId aWindowId) override {
+ layers::AutoCompleteTask complete(mTask);
+
+ UniquePtr<RenderCompositor> compositor =
+ RenderCompositor::Create(std::move(mCompositorWidget), *mError);
+ if (!compositor) {
+ if (!mError->IsEmpty()) {
+ gfxCriticalNote << mError->BeginReading();
+ }
+ return;
+ }
+
+ *mBackend = compositor->BackendType();
+ *mCompositor = compositor->CompositorType();
+ *mUseANGLE = compositor->UseANGLE();
+ *mUseDComp = compositor->UseDComp();
+ *mUseTripleBuffering = compositor->UseTripleBuffering();
+ *mSupportsExternalBufferTextures =
+ compositor->SupportsExternalBufferTextures();
+
+ // Only allow the panic on GL error functionality in nightly builds,
+ // since it (deliberately) crashes the GPU process if any GL call
+ // returns an error code.
+ bool panic_on_gl_error = false;
+#ifdef NIGHTLY_BUILD
+ panic_on_gl_error =
+ StaticPrefs::gfx_webrender_panic_on_gl_error_AtStartup();
+#endif
+
+ bool isMainWindow = true; // TODO!
+ bool supportLowPriorityTransactions = isMainWindow;
+ bool supportLowPriorityThreadpool =
+ supportLowPriorityTransactions &&
+ StaticPrefs::gfx_webrender_enable_low_priority_pool();
+ wr::Renderer* wrRenderer = nullptr;
+ char* errorMessage = nullptr;
+ int picTileWidth = StaticPrefs::gfx_webrender_picture_tile_width();
+ int picTileHeight = StaticPrefs::gfx_webrender_picture_tile_height();
+ auto* swgl = compositor->swgl();
+ auto* gl = (compositor->gl() && !swgl) ? compositor->gl() : nullptr;
+ auto* progCache = (aRenderThread.GetProgramCache() && !swgl)
+ ? aRenderThread.GetProgramCache()->Raw()
+ : nullptr;
+ auto* shaders = (aRenderThread.GetShaders() && !swgl)
+ ? aRenderThread.GetShaders()->RawShaders()
+ : nullptr;
+
+ if (!wr_window_new(
+ aWindowId, mSize.width, mSize.height,
+ mWindowKind == WindowKind::MAIN, supportLowPriorityTransactions,
+ supportLowPriorityThreadpool, gfx::gfxVars::UseGLSwizzle(),
+ gfx::gfxVars::UseWebRenderScissoredCacheClears(),
+#ifdef NIGHTLY_BUILD
+ StaticPrefs::gfx_webrender_start_debug_server(),
+#else
+ false,
+#endif
+ swgl, gl, compositor->SurfaceOriginIsTopLeft(), progCache, shaders,
+ aRenderThread.ThreadPool().Raw(),
+ aRenderThread.ThreadPoolLP().Raw(), &WebRenderMallocSizeOf,
+ &WebRenderMallocEnclosingSizeOf, 0, compositor.get(),
+ compositor->ShouldUseNativeCompositor(),
+ compositor->GetMaxUpdateRects(), compositor->UsePartialPresent(),
+ compositor->GetMaxPartialPresentRects(),
+ compositor->ShouldDrawPreviousPartialPresentRegions(), mDocHandle,
+ &wrRenderer, mMaxTextureSize, &errorMessage,
+ StaticPrefs::gfx_webrender_enable_gpu_markers_AtStartup(),
+ panic_on_gl_error, picTileWidth, picTileHeight)) {
+ // wr_window_new puts a message into gfxCriticalNote if it returns false
+ MOZ_ASSERT(errorMessage);
+ mError->AssignASCII(errorMessage);
+ wr_api_free_error_msg(errorMessage);
+ return;
+ }
+ MOZ_ASSERT(wrRenderer);
+
+ RefPtr<RenderThread> thread = &aRenderThread;
+ auto renderer =
+ MakeUnique<RendererOGL>(std::move(thread), std::move(compositor),
+ aWindowId, wrRenderer, mBridge);
+ if (wrRenderer && renderer) {
+ wr::WrExternalImageHandler handler = renderer->GetExternalImageHandler();
+ wr_renderer_set_external_image_handler(wrRenderer, &handler);
+ }
+
+ if (renderer) {
+ layers::SyncObjectHost* syncObj = renderer->GetSyncObject();
+ if (syncObj) {
+ *mSyncHandle = syncObj->GetSyncHandle();
+ }
+ }
+
+ aRenderThread.AddRenderer(aWindowId, std::move(renderer));
+ }
+
+ private:
+ wr::DocumentHandle** mDocHandle;
+ WebRenderBackend* mBackend;
+ WebRenderCompositor* mCompositor;
+ int32_t* mMaxTextureSize;
+ bool* mUseANGLE;
+ bool* mUseDComp;
+ bool* mUseTripleBuffering;
+ bool* mSupportsExternalBufferTextures;
+ layers::CompositorBridgeParent* mBridge;
+ RefPtr<widget::CompositorWidget> mCompositorWidget;
+ layers::SynchronousTask* mTask;
+ LayoutDeviceIntSize mSize;
+ layers::WindowKind mWindowKind;
+ layers::SyncHandle* mSyncHandle;
+ nsACString* mError;
+};
+
+class RemoveRenderer : public RendererEvent {
+ public:
+ explicit RemoveRenderer(layers::SynchronousTask* aTask) : mTask(aTask) {
+ MOZ_COUNT_CTOR(RemoveRenderer);
+ }
+
+ MOZ_COUNTED_DTOR_OVERRIDE(RemoveRenderer)
+
+ void Run(RenderThread& aRenderThread, WindowId aWindowId) override {
+ aRenderThread.RemoveRenderer(aWindowId);
+ layers::AutoCompleteTask complete(mTask);
+ }
+
+ private:
+ layers::SynchronousTask* mTask;
+};
+
+TransactionBuilder::TransactionBuilder(bool aUseSceneBuilderThread)
+ : mUseSceneBuilderThread(aUseSceneBuilderThread) {
+ mTxn = wr_transaction_new(mUseSceneBuilderThread);
+}
+
+TransactionBuilder::~TransactionBuilder() { wr_transaction_delete(mTxn); }
+
+void TransactionBuilder::SetLowPriority(bool aIsLowPriority) {
+ wr_transaction_set_low_priority(mTxn, aIsLowPriority);
+}
+
+void TransactionBuilder::UpdateEpoch(PipelineId aPipelineId, Epoch aEpoch) {
+ wr_transaction_update_epoch(mTxn, aPipelineId, aEpoch);
+}
+
+void TransactionBuilder::SetRootPipeline(PipelineId aPipelineId) {
+ wr_transaction_set_root_pipeline(mTxn, aPipelineId);
+}
+
+void TransactionBuilder::RemovePipeline(PipelineId aPipelineId) {
+ wr_transaction_remove_pipeline(mTxn, aPipelineId);
+}
+
+void TransactionBuilder::SetDisplayList(
+ const gfx::DeviceColor& aBgColor, Epoch aEpoch,
+ const wr::LayoutSize& aViewportSize, wr::WrPipelineId pipeline_id,
+ wr::BuiltDisplayListDescriptor dl_descriptor, wr::Vec<uint8_t>& dl_data) {
+ wr_transaction_set_display_list(mTxn, aEpoch, ToColorF(aBgColor),
+ aViewportSize, pipeline_id, dl_descriptor,
+ &dl_data.inner);
+}
+
+void TransactionBuilder::ClearDisplayList(Epoch aEpoch,
+ wr::WrPipelineId aPipelineId) {
+ wr_transaction_clear_display_list(mTxn, aEpoch, aPipelineId);
+}
+
+void TransactionBuilder::GenerateFrame(const VsyncId& aVsyncId) {
+ wr_transaction_generate_frame(mTxn, aVsyncId.mId);
+}
+
+void TransactionBuilder::InvalidateRenderedFrame() {
+ wr_transaction_invalidate_rendered_frame(mTxn);
+}
+
+void TransactionBuilder::UpdateDynamicProperties(
+ const nsTArray<wr::WrOpacityProperty>& aOpacityArray,
+ const nsTArray<wr::WrTransformProperty>& aTransformArray,
+ const nsTArray<wr::WrColorProperty>& aColorArray) {
+ wr_transaction_update_dynamic_properties(
+ mTxn, aOpacityArray.IsEmpty() ? nullptr : aOpacityArray.Elements(),
+ aOpacityArray.Length(),
+ aTransformArray.IsEmpty() ? nullptr : aTransformArray.Elements(),
+ aTransformArray.Length(),
+ aColorArray.IsEmpty() ? nullptr : aColorArray.Elements(),
+ aColorArray.Length());
+}
+
+bool TransactionBuilder::IsEmpty() const {
+ return wr_transaction_is_empty(mTxn);
+}
+
+bool TransactionBuilder::IsResourceUpdatesEmpty() const {
+ return wr_transaction_resource_updates_is_empty(mTxn);
+}
+
+bool TransactionBuilder::IsRenderedFrameInvalidated() const {
+ return wr_transaction_is_rendered_frame_invalidated(mTxn);
+}
+
+void TransactionBuilder::SetDocumentView(
+ const LayoutDeviceIntRect& aDocumentRect) {
+ wr::DeviceIntRect wrDocRect;
+ wrDocRect.origin.x = aDocumentRect.x;
+ wrDocRect.origin.y = aDocumentRect.y;
+ wrDocRect.size.width = aDocumentRect.width;
+ wrDocRect.size.height = aDocumentRect.height;
+ wr_transaction_set_document_view(mTxn, &wrDocRect);
+}
+
+void TransactionBuilder::UpdateScrollPosition(
+ const wr::WrPipelineId& aPipelineId,
+ const layers::ScrollableLayerGuid::ViewID& aScrollId,
+ const wr::LayoutPoint& aScrollPosition) {
+ wr_transaction_scroll_layer(mTxn, aPipelineId, aScrollId, aScrollPosition);
+}
+
+TransactionWrapper::TransactionWrapper(Transaction* aTxn) : mTxn(aTxn) {}
+
+void TransactionWrapper::UpdateDynamicProperties(
+ const nsTArray<wr::WrOpacityProperty>& aOpacityArray,
+ const nsTArray<wr::WrTransformProperty>& aTransformArray,
+ const nsTArray<wr::WrColorProperty>& aColorArray) {
+ wr_transaction_update_dynamic_properties(
+ mTxn, aOpacityArray.IsEmpty() ? nullptr : aOpacityArray.Elements(),
+ aOpacityArray.Length(),
+ aTransformArray.IsEmpty() ? nullptr : aTransformArray.Elements(),
+ aTransformArray.Length(),
+ aColorArray.IsEmpty() ? nullptr : aColorArray.Elements(),
+ aColorArray.Length());
+}
+
+void TransactionWrapper::AppendTransformProperties(
+ const nsTArray<wr::WrTransformProperty>& aTransformArray) {
+ wr_transaction_append_transform_properties(
+ mTxn, aTransformArray.IsEmpty() ? nullptr : aTransformArray.Elements(),
+ aTransformArray.Length());
+}
+
+void TransactionWrapper::UpdateScrollPosition(
+ const wr::WrPipelineId& aPipelineId,
+ const layers::ScrollableLayerGuid::ViewID& aScrollId,
+ const wr::LayoutPoint& aScrollPosition) {
+ wr_transaction_scroll_layer(mTxn, aPipelineId, aScrollId, aScrollPosition);
+}
+
+void TransactionWrapper::UpdatePinchZoom(float aZoom) {
+ wr_transaction_pinch_zoom(mTxn, aZoom);
+}
+
+void TransactionWrapper::UpdateIsTransformAsyncZooming(uint64_t aAnimationId,
+ bool aIsZooming) {
+ wr_transaction_set_is_transform_async_zooming(mTxn, aAnimationId, aIsZooming);
+}
+
+/*static*/
+already_AddRefed<WebRenderAPI> WebRenderAPI::Create(
+ layers::CompositorBridgeParent* aBridge,
+ RefPtr<widget::CompositorWidget>&& aWidget, const wr::WrWindowId& aWindowId,
+ LayoutDeviceIntSize aSize, layers::WindowKind aWindowKind,
+ nsACString& aError) {
+ MOZ_ASSERT(aBridge);
+ MOZ_ASSERT(aWidget);
+ static_assert(
+ sizeof(size_t) == sizeof(uintptr_t),
+ "The FFI bindings assume size_t is the same size as uintptr_t!");
+
+ wr::DocumentHandle* docHandle = nullptr;
+ WebRenderBackend backend = WebRenderBackend::HARDWARE;
+ WebRenderCompositor compositor = WebRenderCompositor::DRAW;
+ int32_t maxTextureSize = 0;
+ bool useANGLE = false;
+ bool useDComp = false;
+ bool useTripleBuffering = false;
+ bool supportsExternalBufferTextures = false;
+ layers::SyncHandle syncHandle = 0;
+
+ // Dispatch a synchronous task because the DocumentHandle object needs to be
+ // created on the render thread. If need be we could delay waiting on this
+ // task until the next time we need to access the DocumentHandle object.
+ layers::SynchronousTask task("Create Renderer");
+ auto event = MakeUnique<NewRenderer>(
+ &docHandle, aBridge, &backend, &compositor, &maxTextureSize, &useANGLE,
+ &useDComp, &useTripleBuffering, &supportsExternalBufferTextures,
+ std::move(aWidget), &task, aSize, aWindowKind, &syncHandle, &aError);
+ RenderThread::Get()->RunEvent(aWindowId, std::move(event));
+
+ task.Wait();
+
+ if (!docHandle) {
+ return nullptr;
+ }
+
+ return RefPtr<WebRenderAPI>(
+ new WebRenderAPI(docHandle, aWindowId, backend, compositor,
+ maxTextureSize, useANGLE, useDComp,
+ useTripleBuffering,
+ supportsExternalBufferTextures, syncHandle))
+ .forget();
+}
+
+already_AddRefed<WebRenderAPI> WebRenderAPI::Clone() {
+ wr::DocumentHandle* docHandle = nullptr;
+ wr_api_clone(mDocHandle, &docHandle);
+
+ RefPtr<WebRenderAPI> renderApi =
+ new WebRenderAPI(docHandle, mId, mBackend, mCompositor, mMaxTextureSize,
+ mUseANGLE, mUseDComp, mUseTripleBuffering,
+ mSupportsExternalBufferTextures, mSyncHandle);
+ renderApi->mRootApi = this; // Hold root api
+ renderApi->mRootDocumentApi = this;
+
+ return renderApi.forget();
+}
+
+wr::WrIdNamespace WebRenderAPI::GetNamespace() {
+ return wr_api_get_namespace(mDocHandle);
+}
+
+WebRenderAPI::WebRenderAPI(wr::DocumentHandle* aHandle, wr::WindowId aId,
+ WebRenderBackend aBackend,
+ WebRenderCompositor aCompositor,
+ uint32_t aMaxTextureSize, bool aUseANGLE,
+ bool aUseDComp, bool aUseTripleBuffering,
+ bool aSupportsExternalBufferTextures,
+ layers::SyncHandle aSyncHandle)
+ : mDocHandle(aHandle),
+ mId(aId),
+ mBackend(aBackend),
+ mCompositor(aCompositor),
+ mMaxTextureSize(aMaxTextureSize),
+ mUseANGLE(aUseANGLE),
+ mUseDComp(aUseDComp),
+ mUseTripleBuffering(aUseTripleBuffering),
+ mSupportsExternalBufferTextures(aSupportsExternalBufferTextures),
+ mCaptureSequence(false),
+ mSyncHandle(aSyncHandle) {}
+
+WebRenderAPI::~WebRenderAPI() {
+ if (!mRootDocumentApi) {
+ wr_api_delete_document(mDocHandle);
+ }
+
+ if (!mRootApi) {
+ RenderThread::Get()->SetDestroyed(GetId());
+
+ layers::SynchronousTask task("Destroy WebRenderAPI");
+ auto event = MakeUnique<RemoveRenderer>(&task);
+ RunOnRenderThread(std::move(event));
+ task.Wait();
+
+ wr_api_shut_down(mDocHandle);
+ }
+
+ wr_api_delete(mDocHandle);
+}
+
+void WebRenderAPI::UpdateDebugFlags(uint32_t aFlags) {
+ wr_api_set_debug_flags(mDocHandle, wr::DebugFlags{aFlags});
+}
+
+void WebRenderAPI::SendTransaction(TransactionBuilder& aTxn) {
+ wr_api_send_transaction(mDocHandle, aTxn.Raw(), aTxn.UseSceneBuilderThread());
+}
+
+SideBits ExtractSideBitsFromHitInfoBits(uint16_t& aHitInfoBits) {
+ SideBits sideBits = SideBits::eNone;
+ if (aHitInfoBits & eSideBitsPackedTop) {
+ sideBits |= SideBits::eTop;
+ }
+ if (aHitInfoBits & eSideBitsPackedRight) {
+ sideBits |= SideBits::eRight;
+ }
+ if (aHitInfoBits & eSideBitsPackedBottom) {
+ sideBits |= SideBits::eBottom;
+ }
+ if (aHitInfoBits & eSideBitsPackedLeft) {
+ sideBits |= SideBits::eLeft;
+ }
+
+ aHitInfoBits &= 0x0fff;
+ return sideBits;
+}
+
+std::vector<WrHitResult> WebRenderAPI::HitTest(const wr::WorldPoint& aPoint) {
+ static_assert(gfx::DoesCompositorHitTestInfoFitIntoBits<12>(),
+ "CompositorHitTestFlags MAX value has to be less than number "
+ "of bits in uint16_t minus 4 for SideBitsPacked");
+
+ nsTArray<wr::HitResult> wrResults;
+ wr_api_hit_test(mDocHandle, aPoint, &wrResults);
+
+ std::vector<WrHitResult> geckoResults;
+ for (wr::HitResult wrResult : wrResults) {
+ WrHitResult geckoResult;
+ geckoResult.mLayersId = wr::AsLayersId(wrResult.pipeline_id);
+ geckoResult.mScrollId =
+ static_cast<layers::ScrollableLayerGuid::ViewID>(wrResult.scroll_id);
+ geckoResult.mSideBits = ExtractSideBitsFromHitInfoBits(wrResult.hit_info);
+ geckoResult.mHitInfo.deserialize(wrResult.hit_info);
+ geckoResults.push_back(geckoResult);
+ }
+ return geckoResults;
+}
+
+void WebRenderAPI::Readback(const TimeStamp& aStartTime, gfx::IntSize size,
+ const gfx::SurfaceFormat& aFormat,
+ const Range<uint8_t>& buffer, bool* aNeedsYFlip) {
+ class Readback : public RendererEvent {
+ public:
+ explicit Readback(layers::SynchronousTask* aTask, TimeStamp aStartTime,
+ gfx::IntSize aSize, const gfx::SurfaceFormat& aFormat,
+ const Range<uint8_t>& aBuffer, bool* aNeedsYFlip)
+ : mTask(aTask),
+ mStartTime(aStartTime),
+ mSize(aSize),
+ mFormat(aFormat),
+ mBuffer(aBuffer),
+ mNeedsYFlip(aNeedsYFlip) {
+ MOZ_COUNT_CTOR(Readback);
+ }
+
+ MOZ_COUNTED_DTOR_OVERRIDE(Readback)
+
+ void Run(RenderThread& aRenderThread, WindowId aWindowId) override {
+ aRenderThread.UpdateAndRender(aWindowId, VsyncId(), mStartTime,
+ /* aRender */ true, Some(mSize),
+ wr::SurfaceFormatToImageFormat(mFormat),
+ Some(mBuffer), mNeedsYFlip);
+ layers::AutoCompleteTask complete(mTask);
+ }
+
+ layers::SynchronousTask* mTask;
+ TimeStamp mStartTime;
+ gfx::IntSize mSize;
+ gfx::SurfaceFormat mFormat;
+ const Range<uint8_t>& mBuffer;
+ bool* mNeedsYFlip;
+ };
+
+ // Disable debug flags during readback. See bug 1436020.
+ UpdateDebugFlags(0);
+
+ layers::SynchronousTask task("Readback");
+ auto event = MakeUnique<Readback>(&task, aStartTime, size, aFormat, buffer,
+ aNeedsYFlip);
+ // This event will be passed from wr_backend thread to renderer thread. That
+ // implies that all frame data have been processed when the renderer runs this
+ // read-back event. Then, we could make sure this read-back event gets the
+ // latest result.
+ RunOnRenderThread(std::move(event));
+
+ task.Wait();
+
+ UpdateDebugFlags(gfx::gfxVars::WebRenderDebugFlags());
+}
+
+void WebRenderAPI::ClearAllCaches() { wr_api_clear_all_caches(mDocHandle); }
+
+void WebRenderAPI::EnableNativeCompositor(bool aEnable) {
+ wr_api_enable_native_compositor(mDocHandle, aEnable);
+}
+
+void WebRenderAPI::EnableMultithreading(bool aEnable) {
+ wr_api_enable_multithreading(mDocHandle, aEnable);
+}
+
+void WebRenderAPI::SetBatchingLookback(uint32_t aCount) {
+ wr_api_set_batching_lookback(mDocHandle, aCount);
+}
+
+void WebRenderAPI::SetClearColor(const gfx::DeviceColor& aColor) {
+ RenderThread::Get()->SetClearColor(mId, ToColorF(aColor));
+}
+
+void WebRenderAPI::SetProfilerUI(const nsCString& aUIString) {
+ RenderThread::Get()->SetProfilerUI(mId, aUIString);
+}
+
+void WebRenderAPI::Pause() {
+ class PauseEvent : public RendererEvent {
+ public:
+ explicit PauseEvent(layers::SynchronousTask* aTask) : mTask(aTask) {
+ MOZ_COUNT_CTOR(PauseEvent);
+ }
+
+ MOZ_COUNTED_DTOR_OVERRIDE(PauseEvent)
+
+ void Run(RenderThread& aRenderThread, WindowId aWindowId) override {
+ aRenderThread.Pause(aWindowId);
+ layers::AutoCompleteTask complete(mTask);
+ }
+
+ layers::SynchronousTask* mTask;
+ };
+
+ layers::SynchronousTask task("Pause");
+ auto event = MakeUnique<PauseEvent>(&task);
+ // This event will be passed from wr_backend thread to renderer thread. That
+ // implies that all frame data have been processed when the renderer runs this
+ // event.
+ RunOnRenderThread(std::move(event));
+
+ task.Wait();
+}
+
+bool WebRenderAPI::Resume() {
+ class ResumeEvent : public RendererEvent {
+ public:
+ explicit ResumeEvent(layers::SynchronousTask* aTask, bool* aResult)
+ : mTask(aTask), mResult(aResult) {
+ MOZ_COUNT_CTOR(ResumeEvent);
+ }
+
+ MOZ_COUNTED_DTOR_OVERRIDE(ResumeEvent)
+
+ void Run(RenderThread& aRenderThread, WindowId aWindowId) override {
+ *mResult = aRenderThread.Resume(aWindowId);
+ layers::AutoCompleteTask complete(mTask);
+ }
+
+ layers::SynchronousTask* mTask;
+ bool* mResult;
+ };
+
+ bool result = false;
+ layers::SynchronousTask task("Resume");
+ auto event = MakeUnique<ResumeEvent>(&task, &result);
+ // This event will be passed from wr_backend thread to renderer thread. That
+ // implies that all frame data have been processed when the renderer runs this
+ // event.
+ RunOnRenderThread(std::move(event));
+
+ task.Wait();
+ return result;
+}
+
+void WebRenderAPI::NotifyMemoryPressure() {
+ wr_api_notify_memory_pressure(mDocHandle);
+}
+
+void WebRenderAPI::AccumulateMemoryReport(MemoryReport* aReport) {
+ wr_api_accumulate_memory_report(mDocHandle, aReport, &WebRenderMallocSizeOf,
+ &WebRenderMallocEnclosingSizeOf);
+}
+
+void WebRenderAPI::WakeSceneBuilder() { wr_api_wake_scene_builder(mDocHandle); }
+
+void WebRenderAPI::FlushSceneBuilder() {
+ wr_api_flush_scene_builder(mDocHandle);
+}
+
+void WebRenderAPI::WaitFlushed() {
+ class WaitFlushedEvent : public RendererEvent {
+ public:
+ explicit WaitFlushedEvent(layers::SynchronousTask* aTask) : mTask(aTask) {
+ MOZ_COUNT_CTOR(WaitFlushedEvent);
+ }
+
+ MOZ_COUNTED_DTOR_OVERRIDE(WaitFlushedEvent)
+
+ void Run(RenderThread& aRenderThread, WindowId aWindowId) override {
+ layers::AutoCompleteTask complete(mTask);
+ }
+
+ layers::SynchronousTask* mTask;
+ };
+
+ layers::SynchronousTask task("WaitFlushed");
+ auto event = MakeUnique<WaitFlushedEvent>(&task);
+ // This event will be passed from wr_backend thread to renderer thread. That
+ // implies that all frame data have been processed when the renderer runs this
+ // event.
+ RunOnRenderThread(std::move(event));
+
+ task.Wait();
+}
+
+void WebRenderAPI::Capture() {
+ // see CaptureBits
+ // SCENE | FRAME | TILE_CACHE
+ uint8_t bits = 15; // TODO: get from JavaScript
+ const char* path = "wr-capture"; // TODO: get from JavaScript
+ wr_api_capture(mDocHandle, path, bits);
+}
+
+void WebRenderAPI::ToggleCaptureSequence() {
+ mCaptureSequence = !mCaptureSequence;
+ if (mCaptureSequence) {
+ uint8_t bits = 9; // TODO: get from JavaScript
+ const char* path = "wr-capture-sequence"; // TODO: get from JavaScript
+ wr_api_start_capture_sequence(mDocHandle, path, bits);
+ } else {
+ wr_api_stop_capture_sequence(mDocHandle);
+ }
+}
+
+void WebRenderAPI::BeginRecording(const TimeStamp& aRecordingStart,
+ wr::PipelineId aRootPipelineId) {
+ class BeginRecordingEvent final : public RendererEvent {
+ public:
+ explicit BeginRecordingEvent(const TimeStamp& aRecordingStart,
+ wr::PipelineId aRootPipelineId)
+ : mRecordingStart(aRecordingStart), mRootPipelineId(aRootPipelineId) {
+ MOZ_COUNT_CTOR(BeginRecordingEvent);
+ }
+
+ ~BeginRecordingEvent() { MOZ_COUNT_DTOR(BeginRecordingEvent); }
+
+ void Run(RenderThread& aRenderThread, WindowId aWindowId) override {
+ aRenderThread.BeginRecordingForWindow(aWindowId, mRecordingStart,
+ mRootPipelineId);
+ }
+
+ private:
+ TimeStamp mRecordingStart;
+ wr::PipelineId mRootPipelineId;
+ };
+
+ auto event =
+ MakeUnique<BeginRecordingEvent>(aRecordingStart, aRootPipelineId);
+ RunOnRenderThread(std::move(event));
+}
+
+RefPtr<WebRenderAPI::WriteCollectedFramesPromise>
+WebRenderAPI::WriteCollectedFrames() {
+ class WriteCollectedFramesEvent final : public RendererEvent {
+ public:
+ explicit WriteCollectedFramesEvent() {
+ MOZ_COUNT_CTOR(WriteCollectedFramesEvent);
+ }
+
+ MOZ_COUNTED_DTOR(WriteCollectedFramesEvent)
+
+ void Run(RenderThread& aRenderThread, WindowId aWindowId) override {
+ aRenderThread.WriteCollectedFramesForWindow(aWindowId);
+ mPromise.Resolve(true, __func__);
+ }
+
+ RefPtr<WebRenderAPI::WriteCollectedFramesPromise> GetPromise() {
+ return mPromise.Ensure(__func__);
+ }
+
+ private:
+ MozPromiseHolder<WebRenderAPI::WriteCollectedFramesPromise> mPromise;
+ };
+
+ auto event = MakeUnique<WriteCollectedFramesEvent>();
+ auto promise = event->GetPromise();
+
+ RunOnRenderThread(std::move(event));
+ return promise;
+}
+
+RefPtr<WebRenderAPI::GetCollectedFramesPromise>
+WebRenderAPI::GetCollectedFrames() {
+ class GetCollectedFramesEvent final : public RendererEvent {
+ public:
+ explicit GetCollectedFramesEvent() {
+ MOZ_COUNT_CTOR(GetCollectedFramesEvent);
+ }
+
+ MOZ_COUNTED_DTOR(GetCollectedFramesEvent);
+
+ void Run(RenderThread& aRenderThread, WindowId aWindowId) override {
+ Maybe<layers::CollectedFrames> frames =
+ aRenderThread.GetCollectedFramesForWindow(aWindowId);
+
+ if (frames) {
+ mPromise.Resolve(std::move(*frames), __func__);
+ } else {
+ mPromise.Reject(NS_ERROR_UNEXPECTED, __func__);
+ }
+ }
+
+ RefPtr<WebRenderAPI::GetCollectedFramesPromise> GetPromise() {
+ return mPromise.Ensure(__func__);
+ }
+
+ private:
+ MozPromiseHolder<WebRenderAPI::GetCollectedFramesPromise> mPromise;
+ };
+
+ auto event = MakeUnique<GetCollectedFramesEvent>();
+ auto promise = event->GetPromise();
+
+ RunOnRenderThread(std::move(event));
+ return promise;
+}
+
+void TransactionBuilder::Clear() { wr_resource_updates_clear(mTxn); }
+
+void TransactionBuilder::Notify(wr::Checkpoint aWhen,
+ UniquePtr<NotificationHandler> aEvent) {
+ wr_transaction_notify(mTxn, aWhen,
+ reinterpret_cast<uintptr_t>(aEvent.release()));
+}
+
+void TransactionBuilder::AddImage(ImageKey key,
+ const ImageDescriptor& aDescriptor,
+ wr::Vec<uint8_t>& aBytes) {
+ wr_resource_updates_add_image(mTxn, key, &aDescriptor, &aBytes.inner);
+}
+
+void TransactionBuilder::AddBlobImage(BlobImageKey key,
+ const ImageDescriptor& aDescriptor,
+ wr::Vec<uint8_t>& aBytes,
+ const wr::DeviceIntRect& aVisibleRect) {
+ wr_resource_updates_add_blob_image(mTxn, key, &aDescriptor, &aBytes.inner,
+ aVisibleRect);
+}
+
+void TransactionBuilder::AddExternalImage(ImageKey key,
+ const ImageDescriptor& aDescriptor,
+ ExternalImageId aExtID,
+ wr::ExternalImageType aImageType,
+ uint8_t aChannelIndex) {
+ wr_resource_updates_add_external_image(mTxn, key, &aDescriptor, aExtID,
+ &aImageType, aChannelIndex);
+}
+
+void TransactionBuilder::AddExternalImageBuffer(
+ ImageKey aKey, const ImageDescriptor& aDescriptor,
+ ExternalImageId aHandle) {
+ auto channelIndex = 0;
+ AddExternalImage(aKey, aDescriptor, aHandle, wr::ExternalImageType::Buffer(),
+ channelIndex);
+}
+
+void TransactionBuilder::UpdateImageBuffer(ImageKey aKey,
+ const ImageDescriptor& aDescriptor,
+ wr::Vec<uint8_t>& aBytes) {
+ wr_resource_updates_update_image(mTxn, aKey, &aDescriptor, &aBytes.inner);
+}
+
+void TransactionBuilder::UpdateBlobImage(BlobImageKey aKey,
+ const ImageDescriptor& aDescriptor,
+ wr::Vec<uint8_t>& aBytes,
+ const wr::DeviceIntRect& aVisibleRect,
+ const wr::LayoutIntRect& aDirtyRect) {
+ wr_resource_updates_update_blob_image(mTxn, aKey, &aDescriptor, &aBytes.inner,
+ aVisibleRect, aDirtyRect);
+}
+
+void TransactionBuilder::UpdateExternalImage(ImageKey aKey,
+ const ImageDescriptor& aDescriptor,
+ ExternalImageId aExtID,
+ wr::ExternalImageType aImageType,
+ uint8_t aChannelIndex) {
+ wr_resource_updates_update_external_image(mTxn, aKey, &aDescriptor, aExtID,
+ &aImageType, aChannelIndex);
+}
+
+void TransactionBuilder::UpdateExternalImageWithDirtyRect(
+ ImageKey aKey, const ImageDescriptor& aDescriptor, ExternalImageId aExtID,
+ wr::ExternalImageType aImageType, const wr::DeviceIntRect& aDirtyRect,
+ uint8_t aChannelIndex) {
+ wr_resource_updates_update_external_image_with_dirty_rect(
+ mTxn, aKey, &aDescriptor, aExtID, &aImageType, aChannelIndex, aDirtyRect);
+}
+
+void TransactionBuilder::SetBlobImageVisibleArea(
+ BlobImageKey aKey, const wr::DeviceIntRect& aArea) {
+ wr_resource_updates_set_blob_image_visible_area(mTxn, aKey, &aArea);
+}
+
+void TransactionBuilder::DeleteImage(ImageKey aKey) {
+ wr_resource_updates_delete_image(mTxn, aKey);
+}
+
+void TransactionBuilder::DeleteBlobImage(BlobImageKey aKey) {
+ wr_resource_updates_delete_blob_image(mTxn, aKey);
+}
+
+void TransactionBuilder::AddRawFont(wr::FontKey aKey, wr::Vec<uint8_t>& aBytes,
+ uint32_t aIndex) {
+ wr_resource_updates_add_raw_font(mTxn, aKey, &aBytes.inner, aIndex);
+}
+
+void TransactionBuilder::AddFontDescriptor(wr::FontKey aKey,
+ wr::Vec<uint8_t>& aBytes,
+ uint32_t aIndex) {
+ wr_resource_updates_add_font_descriptor(mTxn, aKey, &aBytes.inner, aIndex);
+}
+
+void TransactionBuilder::DeleteFont(wr::FontKey aKey) {
+ wr_resource_updates_delete_font(mTxn, aKey);
+}
+
+void TransactionBuilder::AddFontInstance(
+ wr::FontInstanceKey aKey, wr::FontKey aFontKey, float aGlyphSize,
+ const wr::FontInstanceOptions* aOptions,
+ const wr::FontInstancePlatformOptions* aPlatformOptions,
+ wr::Vec<uint8_t>& aVariations) {
+ wr_resource_updates_add_font_instance(mTxn, aKey, aFontKey, aGlyphSize,
+ aOptions, aPlatformOptions,
+ &aVariations.inner);
+}
+
+void TransactionBuilder::DeleteFontInstance(wr::FontInstanceKey aKey) {
+ wr_resource_updates_delete_font_instance(mTxn, aKey);
+}
+
+void TransactionBuilder::UpdateQualitySettings(
+ bool aForceSubpixelAAWherePossible) {
+ wr_transaction_set_quality_settings(mTxn, aForceSubpixelAAWherePossible);
+}
+
+class FrameStartTime : public RendererEvent {
+ public:
+ explicit FrameStartTime(const TimeStamp& aTime) : mTime(aTime) {
+ MOZ_COUNT_CTOR(FrameStartTime);
+ }
+
+ MOZ_COUNTED_DTOR_OVERRIDE(FrameStartTime)
+
+ void Run(RenderThread& aRenderThread, WindowId aWindowId) override {
+ auto renderer = aRenderThread.GetRenderer(aWindowId);
+ if (renderer) {
+ renderer->SetFrameStartTime(mTime);
+ }
+ }
+
+ private:
+ TimeStamp mTime;
+};
+
+void WebRenderAPI::SetFrameStartTime(const TimeStamp& aTime) {
+ auto event = MakeUnique<FrameStartTime>(aTime);
+ RunOnRenderThread(std::move(event));
+}
+
+void WebRenderAPI::RunOnRenderThread(UniquePtr<RendererEvent> aEvent) {
+ auto event = reinterpret_cast<uintptr_t>(aEvent.release());
+ wr_api_send_external_event(mDocHandle, event);
+}
+
+DisplayListBuilder::DisplayListBuilder(PipelineId aId, size_t aCapacity,
+ layers::DisplayItemCache* aCache)
+ : mCurrentSpaceAndClipChain(wr::RootScrollNodeWithChain()),
+ mActiveFixedPosTracker(nullptr),
+ mPipelineId(aId),
+ mDisplayItemCache(aCache) {
+ MOZ_COUNT_CTOR(DisplayListBuilder);
+ mWrState = wr_state_new(aId, aCapacity);
+
+ if (mDisplayItemCache && mDisplayItemCache->IsEnabled()) {
+ mDisplayItemCache->SetPipelineId(aId);
+ }
+}
+
+DisplayListBuilder::~DisplayListBuilder() {
+ MOZ_COUNT_DTOR(DisplayListBuilder);
+ wr_state_delete(mWrState);
+}
+
+void DisplayListBuilder::Save() { wr_dp_save(mWrState); }
+void DisplayListBuilder::Restore() { wr_dp_restore(mWrState); }
+void DisplayListBuilder::ClearSave() { wr_dp_clear_save(mWrState); }
+
+usize DisplayListBuilder::Dump(usize aIndent, const Maybe<usize>& aStart,
+ const Maybe<usize>& aEnd) {
+ return wr_dump_display_list(mWrState, aIndent, aStart.ptrOr(nullptr),
+ aEnd.ptrOr(nullptr));
+}
+
+void DisplayListBuilder::DumpSerializedDisplayList() {
+ wr_dump_serialized_display_list(mWrState);
+}
+
+void DisplayListBuilder::Finalize(BuiltDisplayList& aOutDisplayList) {
+ wr_api_finalize_builder(mWrState, &aOutDisplayList.dl_desc,
+ &aOutDisplayList.dl.inner);
+}
+
+void DisplayListBuilder::Finalize(layers::DisplayListData& aOutTransaction) {
+ if (mDisplayItemCache && mDisplayItemCache->IsEnabled()) {
+ wr_dp_set_cache_size(mWrState, mDisplayItemCache->CurrentSize());
+ }
+
+ wr::VecU8 dl;
+ wr_api_finalize_builder(mWrState, &aOutTransaction.mDLDesc, &dl.inner);
+ aOutTransaction.mDL.emplace(dl.inner.data, dl.inner.length,
+ dl.inner.capacity);
+ aOutTransaction.mRemotePipelineIds = std::move(mRemotePipelineIds);
+ dl.inner.capacity = 0;
+ dl.inner.data = nullptr;
+}
+
+Maybe<wr::WrSpatialId> DisplayListBuilder::PushStackingContext(
+ const wr::StackingContextParams& aParams, const wr::LayoutRect& aBounds,
+ const wr::RasterSpace& aRasterSpace) {
+ MOZ_ASSERT(mClipChainLeaf.isNothing(),
+ "Non-empty leaf from clip chain given, but not used with SC!");
+
+ wr::LayoutTransform matrix;
+ const gfx::Matrix4x4* transform = aParams.mTransformPtr;
+ if (transform) {
+ matrix = ToLayoutTransform(*transform);
+ }
+ const wr::LayoutTransform* maybeTransform = transform ? &matrix : nullptr;
+ WRDL_LOG("PushStackingContext b=%s t=%s\n", mWrState,
+ ToString(aBounds).c_str(),
+ transform ? ToString(*transform).c_str() : "none");
+
+ auto spatialId = wr_dp_push_stacking_context(
+ mWrState, aBounds, mCurrentSpaceAndClipChain.space, &aParams,
+ maybeTransform, aParams.mFilters.Elements(), aParams.mFilters.Length(),
+ aParams.mFilterDatas.Elements(), aParams.mFilterDatas.Length(),
+ aRasterSpace);
+
+ return spatialId.id != 0 ? Some(spatialId) : Nothing();
+}
+
+void DisplayListBuilder::PopStackingContext(bool aIsReferenceFrame) {
+ WRDL_LOG("PopStackingContext\n", mWrState);
+ wr_dp_pop_stacking_context(mWrState, aIsReferenceFrame);
+}
+
+wr::WrClipChainId DisplayListBuilder::DefineClipChain(
+ const nsTArray<wr::WrClipId>& aClips, bool aParentWithCurrentChain) {
+ CancelGroup();
+
+ const uint64_t* parent = nullptr;
+ if (aParentWithCurrentChain &&
+ mCurrentSpaceAndClipChain.clip_chain != wr::ROOT_CLIP_CHAIN) {
+ parent = &mCurrentSpaceAndClipChain.clip_chain;
+ }
+ uint64_t clipchainId = wr_dp_define_clipchain(
+ mWrState, parent, aClips.Elements(), aClips.Length());
+ WRDL_LOG("DefineClipChain id=%" PRIu64 " clips=%zu\n", mWrState, clipchainId,
+ aClips.Length());
+ return wr::WrClipChainId{clipchainId};
+}
+
+wr::WrClipId DisplayListBuilder::DefineClip(
+ const Maybe<wr::WrSpaceAndClip>& aParent, const wr::LayoutRect& aClipRect,
+ const nsTArray<wr::ComplexClipRegion>* aComplex) {
+ CancelGroup();
+
+ WrClipId clipId;
+ if (aParent) {
+ clipId = wr_dp_define_clip_with_parent_clip(
+ mWrState, aParent.ptr(), aClipRect,
+ aComplex ? aComplex->Elements() : nullptr,
+ aComplex ? aComplex->Length() : 0);
+ } else {
+ clipId = wr_dp_define_clip_with_parent_clip_chain(
+ mWrState, &mCurrentSpaceAndClipChain, aClipRect,
+ aComplex ? aComplex->Elements() : nullptr,
+ aComplex ? aComplex->Length() : 0);
+ }
+
+ WRDL_LOG("DefineClip id=%zu p=%s r=%s complex=%zu\n", mWrState, clipId.id,
+ aParent ? ToString(aParent->clip.id).c_str() : "(nil)",
+ ToString(aClipRect).c_str(), aComplex ? aComplex->Length() : 0);
+
+ return clipId;
+}
+
+wr::WrClipId DisplayListBuilder::DefineImageMaskClip(
+ const wr::ImageMask& aMask) {
+ CancelGroup();
+
+ WrClipId clipId = wr_dp_define_image_mask_clip_with_parent_clip_chain(
+ mWrState, &mCurrentSpaceAndClipChain, aMask);
+
+ return clipId;
+}
+
+wr::WrClipId DisplayListBuilder::DefineRoundedRectClip(
+ const wr::ComplexClipRegion& aComplex) {
+ CancelGroup();
+
+ WrClipId clipId = wr_dp_define_rounded_rect_clip_with_parent_clip_chain(
+ mWrState, &mCurrentSpaceAndClipChain, aComplex);
+
+ return clipId;
+}
+
+wr::WrClipId DisplayListBuilder::DefineRectClip(wr::LayoutRect aClipRect) {
+ CancelGroup();
+
+ WrClipId clipId = wr_dp_define_rect_clip_with_parent_clip_chain(
+ mWrState, &mCurrentSpaceAndClipChain, aClipRect);
+
+ return clipId;
+}
+
+wr::WrSpatialId DisplayListBuilder::DefineStickyFrame(
+ const wr::LayoutRect& aContentRect, const float* aTopMargin,
+ const float* aRightMargin, const float* aBottomMargin,
+ const float* aLeftMargin, const StickyOffsetBounds& aVerticalBounds,
+ const StickyOffsetBounds& aHorizontalBounds,
+ const wr::LayoutVector2D& aAppliedOffset) {
+ auto spatialId = wr_dp_define_sticky_frame(
+ mWrState, mCurrentSpaceAndClipChain.space, aContentRect, aTopMargin,
+ aRightMargin, aBottomMargin, aLeftMargin, aVerticalBounds,
+ aHorizontalBounds, aAppliedOffset);
+
+ WRDL_LOG("DefineSticky id=%zu c=%s t=%s r=%s b=%s l=%s v=%s h=%s a=%s\n",
+ mWrState, spatialId.id, ToString(aContentRect).c_str(),
+ aTopMargin ? ToString(*aTopMargin).c_str() : "none",
+ aRightMargin ? ToString(*aRightMargin).c_str() : "none",
+ aBottomMargin ? ToString(*aBottomMargin).c_str() : "none",
+ aLeftMargin ? ToString(*aLeftMargin).c_str() : "none",
+ ToString(aVerticalBounds).c_str(),
+ ToString(aHorizontalBounds).c_str(),
+ ToString(aAppliedOffset).c_str());
+
+ return spatialId;
+}
+
+Maybe<wr::WrSpaceAndClip> DisplayListBuilder::GetScrollIdForDefinedScrollLayer(
+ layers::ScrollableLayerGuid::ViewID aViewId) const {
+ if (aViewId == layers::ScrollableLayerGuid::NULL_SCROLL_ID) {
+ return Some(wr::RootScrollNode());
+ }
+
+ auto it = mScrollIds.find(aViewId);
+ if (it == mScrollIds.end()) {
+ return Nothing();
+ }
+
+ return Some(it->second);
+}
+
+wr::WrSpaceAndClip DisplayListBuilder::DefineScrollLayer(
+ const layers::ScrollableLayerGuid::ViewID& aViewId,
+ const Maybe<wr::WrSpaceAndClip>& aParent,
+ const wr::LayoutRect& aContentRect, const wr::LayoutRect& aClipRect,
+ const wr::LayoutPoint& aScrollOffset) {
+ auto it = mScrollIds.find(aViewId);
+ if (it != mScrollIds.end()) {
+ return it->second;
+ }
+
+ // We haven't defined aViewId before, so let's define it now.
+ wr::WrSpaceAndClip defaultParent = wr::RootScrollNode();
+ // Note: we are currently ignoring the clipId on the stack here
+ defaultParent.space = mCurrentSpaceAndClipChain.space;
+
+ auto spaceAndClip = wr_dp_define_scroll_layer(
+ mWrState, aViewId, aParent ? aParent.ptr() : &defaultParent, aContentRect,
+ aClipRect, aScrollOffset);
+
+ WRDL_LOG("DefineScrollLayer id=%" PRIu64 "/%zu p=%s co=%s cl=%s\n", mWrState,
+ aViewId, spaceAndClip.space.id,
+ aParent ? ToString(aParent->space.id).c_str() : "(nil)",
+ ToString(aContentRect).c_str(), ToString(aClipRect).c_str());
+
+ mScrollIds[aViewId] = spaceAndClip;
+ return spaceAndClip;
+}
+
+void DisplayListBuilder::PushRect(const wr::LayoutRect& aBounds,
+ const wr::LayoutRect& aClip,
+ bool aIsBackfaceVisible,
+ const wr::ColorF& aColor) {
+ wr::LayoutRect clip = MergeClipLeaf(aClip);
+ WRDL_LOG("PushRect b=%s cl=%s c=%s\n", mWrState, ToString(aBounds).c_str(),
+ ToString(clip).c_str(), ToString(aColor).c_str());
+ wr_dp_push_rect(mWrState, aBounds, clip, aIsBackfaceVisible,
+ &mCurrentSpaceAndClipChain, aColor);
+}
+
+void DisplayListBuilder::PushRoundedRect(const wr::LayoutRect& aBounds,
+ const wr::LayoutRect& aClip,
+ bool aIsBackfaceVisible,
+ const wr::ColorF& aColor) {
+ wr::LayoutRect clip = MergeClipLeaf(aClip);
+ WRDL_LOG("PushRoundedRect b=%s cl=%s c=%s\n", mWrState,
+ ToString(aBounds).c_str(), ToString(clip).c_str(),
+ ToString(aColor).c_str());
+
+ // Draw the rounded rectangle as a border with rounded corners. We could also
+ // draw this as a rectangle clipped to a rounded rectangle, but:
+ // - clips are not cached; borders are
+ // - a simple border like this will be drawn as an image
+ // - Processing lots of clips is not WebRender's strong point.
+ //
+ // Made the borders thicker than one half the width/height, to avoid
+ // little white dots at the center at some magnifications.
+ wr::BorderSide side = {aColor, wr::BorderStyle::Solid};
+ float h = aBounds.size.width * 0.6f;
+ float v = aBounds.size.height * 0.6f;
+ wr::LayoutSideOffsets widths = {v, h, v, h};
+ wr::BorderRadius radii = {{h, v}, {h, v}, {h, v}, {h, v}};
+
+ // Anti-aliased borders are required for rounded borders.
+ wr_dp_push_border(mWrState, aBounds, clip, aIsBackfaceVisible,
+ &mCurrentSpaceAndClipChain, wr::AntialiasBorder::Yes,
+ widths, side, side, side, side, radii);
+}
+
+void DisplayListBuilder::PushHitTest(
+ const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
+ bool aIsBackfaceVisible,
+ const layers::ScrollableLayerGuid::ViewID& aScrollId,
+ gfx::CompositorHitTestInfo aHitInfo, SideBits aSideBits) {
+ wr::LayoutRect clip = MergeClipLeaf(aClip);
+ WRDL_LOG("PushHitTest b=%s cl=%s\n", mWrState, ToString(aBounds).c_str(),
+ ToString(clip).c_str());
+
+ static_assert(gfx::DoesCompositorHitTestInfoFitIntoBits<12>(),
+ "CompositorHitTestFlags MAX value has to be less than number "
+ "of bits in uint16_t minus 4 for SideBitsPacked");
+
+ uint16_t hitInfoBits = static_cast<uint16_t>(aHitInfo.serialize()) |
+ SideBitsToHitInfoBits(aSideBits);
+
+ wr_dp_push_hit_test(mWrState, aBounds, clip, aIsBackfaceVisible,
+ &mCurrentSpaceAndClipChain, aScrollId, hitInfoBits);
+}
+
+void DisplayListBuilder::PushRectWithAnimation(
+ const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
+ bool aIsBackfaceVisible, const wr::ColorF& aColor,
+ const WrAnimationProperty* aAnimation) {
+ wr::LayoutRect clip = MergeClipLeaf(aClip);
+ WRDL_LOG("PushRectWithAnimation b=%s cl=%s c=%s\n", mWrState,
+ ToString(aBounds).c_str(), ToString(clip).c_str(),
+ ToString(aColor).c_str());
+
+ wr_dp_push_rect_with_animation(mWrState, aBounds, clip, aIsBackfaceVisible,
+ &mCurrentSpaceAndClipChain, aColor,
+ aAnimation);
+}
+
+void DisplayListBuilder::PushClearRect(const wr::LayoutRect& aBounds) {
+ wr::LayoutRect clip = MergeClipLeaf(aBounds);
+ WRDL_LOG("PushClearRect b=%s c=%s\n", mWrState, ToString(aBounds).c_str(),
+ ToString(clip).c_str());
+ wr_dp_push_clear_rect(mWrState, aBounds, clip, &mCurrentSpaceAndClipChain);
+}
+
+void DisplayListBuilder::PushClearRectWithComplexRegion(
+ const wr::LayoutRect& aBounds, const wr::ComplexClipRegion& aRegion) {
+ wr::LayoutRect clip = MergeClipLeaf(aBounds);
+ WRDL_LOG("PushClearRectWithComplexRegion b=%s c=%s\n", mWrState,
+ ToString(aBounds).c_str(), ToString(clip).c_str());
+
+ // TODO(gw): This doesn't pass the complex region through to WR, as clear
+ // rects with complex clips are currently broken. This is the
+ // only place they are used, and they are used only for a single
+ // case (close buttons on Win7 machines). We might be able to
+ // get away with not supporting this at all in WR, using the
+ // non-clipped clear rect is an improvement for now, at least.
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=1636683 for
+ // more information.
+ AutoTArray<wr::ComplexClipRegion, 1> clips;
+ auto clipId = DefineClip(Nothing(), aBounds, &clips);
+ auto spaceAndClip = WrSpaceAndClip{mCurrentSpaceAndClipChain.space, clipId};
+
+ wr_dp_push_clear_rect_with_parent_clip(mWrState, aBounds, clip,
+ &spaceAndClip);
+}
+
+void DisplayListBuilder::PushBackdropFilter(
+ const wr::LayoutRect& aBounds, const wr::ComplexClipRegion& aRegion,
+ const nsTArray<wr::FilterOp>& aFilters,
+ const nsTArray<wr::WrFilterData>& aFilterDatas, bool aIsBackfaceVisible) {
+ wr::LayoutRect clip = MergeClipLeaf(aBounds);
+ WRDL_LOG("PushBackdropFilter b=%s c=%s\n", mWrState,
+ ToString(aBounds).c_str(), ToString(clip).c_str());
+
+ AutoTArray<wr::ComplexClipRegion, 1> clips;
+ clips.AppendElement(aRegion);
+ auto clipId = DefineClip(Nothing(), aBounds, &clips);
+ auto spaceAndClip = WrSpaceAndClip{mCurrentSpaceAndClipChain.space, clipId};
+
+ wr_dp_push_backdrop_filter_with_parent_clip(
+ mWrState, aBounds, clip, aIsBackfaceVisible, &spaceAndClip,
+ aFilters.Elements(), aFilters.Length(), aFilterDatas.Elements(),
+ aFilterDatas.Length());
+}
+
+void DisplayListBuilder::PushLinearGradient(
+ const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
+ bool aIsBackfaceVisible, const wr::LayoutPoint& aStartPoint,
+ const wr::LayoutPoint& aEndPoint, const nsTArray<wr::GradientStop>& aStops,
+ wr::ExtendMode aExtendMode, const wr::LayoutSize aTileSize,
+ const wr::LayoutSize aTileSpacing) {
+ wr_dp_push_linear_gradient(
+ mWrState, aBounds, MergeClipLeaf(aClip), aIsBackfaceVisible,
+ &mCurrentSpaceAndClipChain, aStartPoint, aEndPoint, aStops.Elements(),
+ aStops.Length(), aExtendMode, aTileSize, aTileSpacing);
+}
+
+void DisplayListBuilder::PushRadialGradient(
+ const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
+ bool aIsBackfaceVisible, const wr::LayoutPoint& aCenter,
+ const wr::LayoutSize& aRadius, const nsTArray<wr::GradientStop>& aStops,
+ wr::ExtendMode aExtendMode, const wr::LayoutSize aTileSize,
+ const wr::LayoutSize aTileSpacing) {
+ wr_dp_push_radial_gradient(
+ mWrState, aBounds, MergeClipLeaf(aClip), aIsBackfaceVisible,
+ &mCurrentSpaceAndClipChain, aCenter, aRadius, aStops.Elements(),
+ aStops.Length(), aExtendMode, aTileSize, aTileSpacing);
+}
+
+void DisplayListBuilder::PushConicGradient(
+ const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
+ bool aIsBackfaceVisible, const wr::LayoutPoint& aCenter, const float aAngle,
+ const nsTArray<wr::GradientStop>& aStops, wr::ExtendMode aExtendMode,
+ const wr::LayoutSize aTileSize, const wr::LayoutSize aTileSpacing) {
+ wr_dp_push_conic_gradient(mWrState, aBounds, MergeClipLeaf(aClip),
+ aIsBackfaceVisible, &mCurrentSpaceAndClipChain,
+ aCenter, aAngle, aStops.Elements(), aStops.Length(),
+ aExtendMode, aTileSize, aTileSpacing);
+}
+
+void DisplayListBuilder::PushImage(
+ const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
+ bool aIsBackfaceVisible, wr::ImageRendering aFilter, wr::ImageKey aImage,
+ bool aPremultipliedAlpha, const wr::ColorF& aColor,
+ bool aPreferCompositorSurface, bool aSupportsExternalCompositing) {
+ wr::LayoutRect clip = MergeClipLeaf(aClip);
+ WRDL_LOG("PushImage b=%s cl=%s\n", mWrState, ToString(aBounds).c_str(),
+ ToString(clip).c_str());
+ wr_dp_push_image(mWrState, aBounds, clip, aIsBackfaceVisible,
+ &mCurrentSpaceAndClipChain, aFilter, aImage,
+ aPremultipliedAlpha, aColor, aPreferCompositorSurface,
+ aSupportsExternalCompositing);
+}
+
+void DisplayListBuilder::PushRepeatingImage(
+ const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
+ bool aIsBackfaceVisible, const wr::LayoutSize& aStretchSize,
+ const wr::LayoutSize& aTileSpacing, wr::ImageRendering aFilter,
+ wr::ImageKey aImage, bool aPremultipliedAlpha, const wr::ColorF& aColor) {
+ wr::LayoutRect clip = MergeClipLeaf(aClip);
+ WRDL_LOG("PushImage b=%s cl=%s s=%s t=%s\n", mWrState,
+ ToString(aBounds).c_str(), ToString(clip).c_str(),
+ ToString(aStretchSize).c_str(), ToString(aTileSpacing).c_str());
+ wr_dp_push_repeating_image(
+ mWrState, aBounds, clip, aIsBackfaceVisible, &mCurrentSpaceAndClipChain,
+ aStretchSize, aTileSpacing, aFilter, aImage, aPremultipliedAlpha, aColor);
+}
+
+void DisplayListBuilder::PushYCbCrPlanarImage(
+ const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
+ bool aIsBackfaceVisible, wr::ImageKey aImageChannel0,
+ wr::ImageKey aImageChannel1, wr::ImageKey aImageChannel2,
+ wr::WrColorDepth aColorDepth, wr::WrYuvColorSpace aColorSpace,
+ wr::WrColorRange aColorRange, wr::ImageRendering aRendering,
+ bool aPreferCompositorSurface, bool aSupportsExternalCompositing) {
+ wr_dp_push_yuv_planar_image(
+ mWrState, aBounds, MergeClipLeaf(aClip), aIsBackfaceVisible,
+ &mCurrentSpaceAndClipChain, aImageChannel0, aImageChannel1,
+ aImageChannel2, aColorDepth, aColorSpace, aColorRange, aRendering,
+ aPreferCompositorSurface, aSupportsExternalCompositing);
+}
+
+void DisplayListBuilder::PushNV12Image(
+ const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
+ bool aIsBackfaceVisible, wr::ImageKey aImageChannel0,
+ wr::ImageKey aImageChannel1, wr::WrColorDepth aColorDepth,
+ wr::WrYuvColorSpace aColorSpace, wr::WrColorRange aColorRange,
+ wr::ImageRendering aRendering, bool aPreferCompositorSurface,
+ bool aSupportsExternalCompositing) {
+ wr_dp_push_yuv_NV12_image(
+ mWrState, aBounds, MergeClipLeaf(aClip), aIsBackfaceVisible,
+ &mCurrentSpaceAndClipChain, aImageChannel0, aImageChannel1, aColorDepth,
+ aColorSpace, aColorRange, aRendering, aPreferCompositorSurface,
+ aSupportsExternalCompositing);
+}
+
+void DisplayListBuilder::PushYCbCrInterleavedImage(
+ const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
+ bool aIsBackfaceVisible, wr::ImageKey aImageChannel0,
+ wr::WrColorDepth aColorDepth, wr::WrYuvColorSpace aColorSpace,
+ wr::WrColorRange aColorRange, wr::ImageRendering aRendering,
+ bool aPreferCompositorSurface, bool aSupportsExternalCompositing) {
+ wr_dp_push_yuv_interleaved_image(
+ mWrState, aBounds, MergeClipLeaf(aClip), aIsBackfaceVisible,
+ &mCurrentSpaceAndClipChain, aImageChannel0, aColorDepth, aColorSpace,
+ aColorRange, aRendering, aPreferCompositorSurface,
+ aSupportsExternalCompositing);
+}
+
+void DisplayListBuilder::PushIFrame(const wr::LayoutRect& aBounds,
+ bool aIsBackfaceVisible,
+ PipelineId aPipeline,
+ bool aIgnoreMissingPipeline) {
+ mRemotePipelineIds.AppendElement(aPipeline);
+ wr_dp_push_iframe(mWrState, aBounds, MergeClipLeaf(aBounds),
+ aIsBackfaceVisible, &mCurrentSpaceAndClipChain, aPipeline,
+ aIgnoreMissingPipeline);
+}
+
+void DisplayListBuilder::PushBorder(const wr::LayoutRect& aBounds,
+ const wr::LayoutRect& aClip,
+ bool aIsBackfaceVisible,
+ const wr::LayoutSideOffsets& aWidths,
+ const Range<const wr::BorderSide>& aSides,
+ const wr::BorderRadius& aRadius,
+ wr::AntialiasBorder aAntialias) {
+ MOZ_ASSERT(aSides.length() == 4);
+ if (aSides.length() != 4) {
+ return;
+ }
+ wr_dp_push_border(mWrState, aBounds, MergeClipLeaf(aClip), aIsBackfaceVisible,
+ &mCurrentSpaceAndClipChain, aAntialias, aWidths, aSides[0],
+ aSides[1], aSides[2], aSides[3], aRadius);
+}
+
+void DisplayListBuilder::PushBorderImage(const wr::LayoutRect& aBounds,
+ const wr::LayoutRect& aClip,
+ bool aIsBackfaceVisible,
+ const wr::WrBorderImage& aParams) {
+ wr_dp_push_border_image(mWrState, aBounds, MergeClipLeaf(aClip),
+ aIsBackfaceVisible, &mCurrentSpaceAndClipChain,
+ &aParams);
+}
+
+void DisplayListBuilder::PushBorderGradient(
+ const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
+ bool aIsBackfaceVisible, const wr::LayoutSideOffsets& aWidths,
+ const int32_t aWidth, const int32_t aHeight, bool aFill,
+ const wr::DeviceIntSideOffsets& aSlice, const wr::LayoutPoint& aStartPoint,
+ const wr::LayoutPoint& aEndPoint, const nsTArray<wr::GradientStop>& aStops,
+ wr::ExtendMode aExtendMode, const wr::LayoutSideOffsets& aOutset) {
+ wr_dp_push_border_gradient(mWrState, aBounds, MergeClipLeaf(aClip),
+ aIsBackfaceVisible, &mCurrentSpaceAndClipChain,
+ aWidths, aWidth, aHeight, aFill, aSlice,
+ aStartPoint, aEndPoint, aStops.Elements(),
+ aStops.Length(), aExtendMode, aOutset);
+}
+
+void DisplayListBuilder::PushBorderRadialGradient(
+ const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
+ bool aIsBackfaceVisible, const wr::LayoutSideOffsets& aWidths, bool aFill,
+ const wr::LayoutPoint& aCenter, const wr::LayoutSize& aRadius,
+ const nsTArray<wr::GradientStop>& aStops, wr::ExtendMode aExtendMode,
+ const wr::LayoutSideOffsets& aOutset) {
+ wr_dp_push_border_radial_gradient(
+ mWrState, aBounds, MergeClipLeaf(aClip), aIsBackfaceVisible,
+ &mCurrentSpaceAndClipChain, aWidths, aFill, aCenter, aRadius,
+ aStops.Elements(), aStops.Length(), aExtendMode, aOutset);
+}
+
+void DisplayListBuilder::PushBorderConicGradient(
+ const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
+ bool aIsBackfaceVisible, const wr::LayoutSideOffsets& aWidths, bool aFill,
+ const wr::LayoutPoint& aCenter, const float aAngle,
+ const nsTArray<wr::GradientStop>& aStops, wr::ExtendMode aExtendMode,
+ const wr::LayoutSideOffsets& aOutset) {
+ wr_dp_push_border_conic_gradient(
+ mWrState, aBounds, MergeClipLeaf(aClip), aIsBackfaceVisible,
+ &mCurrentSpaceAndClipChain, aWidths, aFill, aCenter, aAngle,
+ aStops.Elements(), aStops.Length(), aExtendMode, aOutset);
+}
+
+void DisplayListBuilder::PushText(const wr::LayoutRect& aBounds,
+ const wr::LayoutRect& aClip,
+ bool aIsBackfaceVisible,
+ const wr::ColorF& aColor,
+ wr::FontInstanceKey aFontKey,
+ Range<const wr::GlyphInstance> aGlyphBuffer,
+ const wr::GlyphOptions* aGlyphOptions) {
+ wr_dp_push_text(mWrState, aBounds, MergeClipLeaf(aClip), aIsBackfaceVisible,
+ &mCurrentSpaceAndClipChain, aColor, aFontKey,
+ &aGlyphBuffer[0], aGlyphBuffer.length(), aGlyphOptions);
+}
+
+void DisplayListBuilder::PushLine(const wr::LayoutRect& aClip,
+ bool aIsBackfaceVisible,
+ const wr::Line& aLine) {
+ wr::LayoutRect clip = MergeClipLeaf(aClip);
+ wr_dp_push_line(mWrState, &clip, aIsBackfaceVisible,
+ &mCurrentSpaceAndClipChain, &aLine.bounds,
+ aLine.wavyLineThickness, aLine.orientation, &aLine.color,
+ aLine.style);
+}
+
+void DisplayListBuilder::PushShadow(const wr::LayoutRect& aRect,
+ const wr::LayoutRect& aClip,
+ bool aIsBackfaceVisible,
+ const wr::Shadow& aShadow,
+ bool aShouldInflate) {
+ // Local clip_rects are translated inside of shadows, as they are assumed to
+ // be part of the element drawing itself, and not a parent frame clipping it.
+ // As such, it is not sound to apply the MergeClipLeaf optimization inside of
+ // shadows. So we disable the optimization when we encounter a shadow.
+ // Shadows don't span frames, so we don't have to worry about MergeClipLeaf
+ // being re-enabled mid-shadow. The optimization is restored in PopAllShadows.
+ SuspendClipLeafMerging();
+ wr_dp_push_shadow(mWrState, aRect, aClip, aIsBackfaceVisible,
+ &mCurrentSpaceAndClipChain, aShadow, aShouldInflate);
+}
+
+void DisplayListBuilder::PopAllShadows() {
+ wr_dp_pop_all_shadows(mWrState);
+ ResumeClipLeafMerging();
+}
+
+void DisplayListBuilder::SuspendClipLeafMerging() {
+ if (mClipChainLeaf) {
+ // No one should reinitialize mClipChainLeaf while we're suspended
+ MOZ_ASSERT(!mSuspendedClipChainLeaf);
+
+ mSuspendedClipChainLeaf = mClipChainLeaf;
+ mSuspendedSpaceAndClipChain = Some(mCurrentSpaceAndClipChain);
+
+ auto clipId = DefineRectClip(*mClipChainLeaf);
+ auto clipChainId = DefineClipChain({clipId}, true);
+
+ mCurrentSpaceAndClipChain.clip_chain = clipChainId.id;
+ mClipChainLeaf = Nothing();
+ }
+}
+
+void DisplayListBuilder::ResumeClipLeafMerging() {
+ if (mSuspendedClipChainLeaf) {
+ mCurrentSpaceAndClipChain = *mSuspendedSpaceAndClipChain;
+ mClipChainLeaf = mSuspendedClipChainLeaf;
+
+ mSuspendedClipChainLeaf = Nothing();
+ mSuspendedSpaceAndClipChain = Nothing();
+ }
+}
+
+void DisplayListBuilder::PushBoxShadow(
+ const wr::LayoutRect& aRect, const wr::LayoutRect& aClip,
+ bool aIsBackfaceVisible, const wr::LayoutRect& aBoxBounds,
+ const wr::LayoutVector2D& aOffset, const wr::ColorF& aColor,
+ const float& aBlurRadius, const float& aSpreadRadius,
+ const wr::BorderRadius& aBorderRadius,
+ const wr::BoxShadowClipMode& aClipMode) {
+ wr_dp_push_box_shadow(mWrState, aRect, MergeClipLeaf(aClip),
+ aIsBackfaceVisible, &mCurrentSpaceAndClipChain,
+ aBoxBounds, aOffset, aColor, aBlurRadius, aSpreadRadius,
+ aBorderRadius, aClipMode);
+}
+
+void DisplayListBuilder::StartGroup(nsPaintedDisplayItem* aItem) {
+ if (!mDisplayItemCache || mDisplayItemCache->IsFull()) {
+ return;
+ }
+
+ MOZ_ASSERT(!mCurrentCacheSlot);
+ mCurrentCacheSlot = mDisplayItemCache->AssignSlot(aItem);
+
+ if (mCurrentCacheSlot) {
+ wr_dp_start_item_group(mWrState);
+ }
+}
+
+void DisplayListBuilder::CancelGroup(const bool aDiscard) {
+ if (!mDisplayItemCache || !mCurrentCacheSlot) {
+ return;
+ }
+
+ wr_dp_cancel_item_group(mWrState, aDiscard);
+ mCurrentCacheSlot = Nothing();
+}
+
+void DisplayListBuilder::FinishGroup() {
+ if (!mDisplayItemCache || !mCurrentCacheSlot) {
+ return;
+ }
+
+ MOZ_ASSERT(mCurrentCacheSlot);
+
+ if (wr_dp_finish_item_group(mWrState, mCurrentCacheSlot.ref())) {
+ mDisplayItemCache->MarkSlotOccupied(mCurrentCacheSlot.ref(),
+ CurrentSpaceAndClipChain());
+ mDisplayItemCache->Stats().AddCached();
+ }
+
+ mCurrentCacheSlot = Nothing();
+}
+
+bool DisplayListBuilder::ReuseItem(nsPaintedDisplayItem* aItem) {
+ if (!mDisplayItemCache) {
+ return false;
+ }
+
+ mDisplayItemCache->Stats().AddTotal();
+
+ if (mDisplayItemCache->IsEmpty()) {
+ return false;
+ }
+
+ Maybe<uint16_t> slot =
+ mDisplayItemCache->CanReuseItem(aItem, CurrentSpaceAndClipChain());
+
+ if (slot) {
+ mDisplayItemCache->Stats().AddReused();
+ wr_dp_push_reuse_items(mWrState, slot.ref());
+ return true;
+ }
+
+ return false;
+}
+
+Maybe<layers::ScrollableLayerGuid::ViewID>
+DisplayListBuilder::GetContainingFixedPosScrollTarget(
+ const ActiveScrolledRoot* aAsr) {
+ return mActiveFixedPosTracker
+ ? mActiveFixedPosTracker->GetScrollTargetForASR(aAsr)
+ : Nothing();
+}
+
+Maybe<SideBits> DisplayListBuilder::GetContainingFixedPosSideBits(
+ const ActiveScrolledRoot* aAsr) {
+ return mActiveFixedPosTracker
+ ? mActiveFixedPosTracker->GetSideBitsForASR(aAsr)
+ : Nothing();
+}
+
+DisplayListBuilder::FixedPosScrollTargetTracker::FixedPosScrollTargetTracker(
+ DisplayListBuilder& aBuilder, const ActiveScrolledRoot* aAsr,
+ layers::ScrollableLayerGuid::ViewID aScrollId, SideBits aSideBits)
+ : mParentTracker(aBuilder.mActiveFixedPosTracker),
+ mBuilder(aBuilder),
+ mAsr(aAsr),
+ mScrollId(aScrollId),
+ mSideBits(aSideBits) {
+ aBuilder.mActiveFixedPosTracker = this;
+}
+
+DisplayListBuilder::FixedPosScrollTargetTracker::
+ ~FixedPosScrollTargetTracker() {
+ mBuilder.mActiveFixedPosTracker = mParentTracker;
+}
+
+Maybe<layers::ScrollableLayerGuid::ViewID>
+DisplayListBuilder::FixedPosScrollTargetTracker::GetScrollTargetForASR(
+ const ActiveScrolledRoot* aAsr) {
+ return aAsr == mAsr ? Some(mScrollId) : Nothing();
+}
+
+Maybe<SideBits>
+DisplayListBuilder::FixedPosScrollTargetTracker::GetSideBitsForASR(
+ const ActiveScrolledRoot* aAsr) {
+ return aAsr == mAsr ? Some(mSideBits) : Nothing();
+}
+
+already_AddRefed<gfxContext> DisplayListBuilder::GetTextContext(
+ wr::IpcResourceUpdateQueue& aResources,
+ const layers::StackingContextHelper& aSc,
+ layers::RenderRootStateManager* aManager, nsDisplayItem* aItem,
+ nsRect& aBounds, const gfx::Point& aDeviceOffset) {
+ if (!mCachedTextDT) {
+ mCachedTextDT = new layout::TextDrawTarget(*this, aResources, aSc, aManager,
+ aItem, aBounds);
+ mCachedContext = gfxContext::CreateOrNull(mCachedTextDT, aDeviceOffset);
+ } else {
+ mCachedTextDT->Reinitialize(aResources, aSc, aManager, aItem, aBounds);
+ mCachedContext->SetDeviceOffset(aDeviceOffset);
+ mCachedContext->SetMatrix(gfx::Matrix());
+ }
+
+ RefPtr<gfxContext> tmp = mCachedContext;
+ return tmp.forget();
+}
+
+} // namespace wr
+} // namespace mozilla
+
+extern "C" {
+
+void wr_transaction_notification_notified(uintptr_t aHandler,
+ mozilla::wr::Checkpoint aWhen) {
+ auto handler = reinterpret_cast<mozilla::wr::NotificationHandler*>(aHandler);
+ handler->Notify(aWhen);
+ // TODO: it would be better to get a callback when the object is destroyed on
+ // the rust side and delete then.
+ delete handler;
+}
+
+void wr_register_thread_local_arena() {
+#ifdef MOZ_MEMORY
+ jemalloc_thread_local_arena(true);
+#endif
+}
+
+} // extern C
diff --git a/gfx/webrender_bindings/WebRenderAPI.h b/gfx/webrender_bindings/WebRenderAPI.h
new file mode 100644
index 0000000000..9ab65b0784
--- /dev/null
+++ b/gfx/webrender_bindings/WebRenderAPI.h
@@ -0,0 +1,784 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef MOZILLA_LAYERS_WEBRENDERAPI_H
+#define MOZILLA_LAYERS_WEBRENDERAPI_H
+
+#include <vector>
+#include <unordered_map>
+#include <unordered_set>
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/gfx/CompositorHitTestInfo.h"
+#include "mozilla/layers/IpcResourceUpdateQueue.h"
+#include "mozilla/layers/ScrollableLayerGuid.h"
+#include "mozilla/layers/SyncObject.h"
+#include "mozilla/layers/CompositionRecorder.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/Range.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/webrender/webrender_ffi.h"
+#include "mozilla/webrender/WebRenderTypes.h"
+#include "GLTypes.h"
+#include "Units.h"
+
+class gfxContext;
+class nsDisplayItem;
+class nsPaintedDisplayItem;
+class nsDisplayTransform;
+
+#undef None
+
+namespace mozilla {
+
+struct ActiveScrolledRoot;
+
+namespace widget {
+class CompositorWidget;
+}
+
+namespace layers {
+class CompositorBridgeParent;
+class DisplayItemCache;
+class WebRenderBridgeParent;
+class RenderRootStateManager;
+class StackingContextHelper;
+struct DisplayListData;
+} // namespace layers
+
+namespace layout {
+class TextDrawTarget;
+}
+
+namespace wr {
+
+class DisplayListBuilder;
+class RendererOGL;
+class RendererEvent;
+
+// This isn't part of WR's API, but we define it here to simplify layout's
+// logic and data plumbing.
+struct Line {
+ wr::LayoutRect bounds;
+ float wavyLineThickness;
+ wr::LineOrientation orientation;
+ wr::ColorF color;
+ wr::LineStyle style;
+};
+
+/// A handler that can be bundled into a transaction and notified at specific
+/// points in the rendering pipeline, such as after scene building or after
+/// frame building.
+///
+/// If for any reason the handler is dropped before reaching the requested
+/// point, it is notified with the value Checkpoint::TransactionDropped.
+/// So it is safe to assume that the handler will be notified "at some point".
+class NotificationHandler {
+ public:
+ virtual void Notify(wr::Checkpoint aCheckpoint) = 0;
+ virtual ~NotificationHandler() = default;
+};
+
+struct WrHitResult {
+ layers::LayersId mLayersId;
+ layers::ScrollableLayerGuid::ViewID mScrollId;
+ gfx::CompositorHitTestInfo mHitInfo;
+ SideBits mSideBits;
+};
+
+class TransactionBuilder final {
+ public:
+ explicit TransactionBuilder(bool aUseSceneBuilderThread = true);
+
+ ~TransactionBuilder();
+
+ void SetLowPriority(bool aIsLowPriority);
+
+ void UpdateEpoch(PipelineId aPipelineId, Epoch aEpoch);
+
+ void SetRootPipeline(PipelineId aPipelineId);
+
+ void RemovePipeline(PipelineId aPipelineId);
+
+ void SetDisplayList(const gfx::DeviceColor& aBgColor, Epoch aEpoch,
+ const wr::LayoutSize& aViewportSize,
+ wr::WrPipelineId pipeline_id,
+ wr::BuiltDisplayListDescriptor dl_descriptor,
+ wr::Vec<uint8_t>& dl_data);
+
+ void ClearDisplayList(Epoch aEpoch, wr::WrPipelineId aPipeline);
+
+ void GenerateFrame(const VsyncId& aVsyncId);
+
+ void InvalidateRenderedFrame();
+
+ void UpdateDynamicProperties(
+ const nsTArray<wr::WrOpacityProperty>& aOpacityArray,
+ const nsTArray<wr::WrTransformProperty>& aTransformArray,
+ const nsTArray<wr::WrColorProperty>& aColorArray);
+
+ void SetDocumentView(const LayoutDeviceIntRect& aDocRect);
+
+ void UpdateScrollPosition(
+ const wr::WrPipelineId& aPipelineId,
+ const layers::ScrollableLayerGuid::ViewID& aScrollId,
+ const wr::LayoutPoint& aScrollPosition);
+
+ bool IsEmpty() const;
+
+ bool IsResourceUpdatesEmpty() const;
+
+ bool IsRenderedFrameInvalidated() const;
+
+ void AddImage(wr::ImageKey aKey, const ImageDescriptor& aDescriptor,
+ wr::Vec<uint8_t>& aBytes);
+
+ void AddBlobImage(wr::BlobImageKey aKey, const ImageDescriptor& aDescriptor,
+ wr::Vec<uint8_t>& aBytes,
+ const wr::DeviceIntRect& aVisibleRect);
+
+ void AddExternalImageBuffer(ImageKey key, const ImageDescriptor& aDescriptor,
+ ExternalImageId aHandle);
+
+ void AddExternalImage(ImageKey key, const ImageDescriptor& aDescriptor,
+ ExternalImageId aExtID,
+ wr::ExternalImageType aImageType,
+ uint8_t aChannelIndex = 0);
+
+ void UpdateImageBuffer(wr::ImageKey aKey, const ImageDescriptor& aDescriptor,
+ wr::Vec<uint8_t>& aBytes);
+
+ void UpdateBlobImage(wr::BlobImageKey aKey,
+ const ImageDescriptor& aDescriptor,
+ wr::Vec<uint8_t>& aBytes,
+ const wr::DeviceIntRect& aVisibleRect,
+ const wr::LayoutIntRect& aDirtyRect);
+
+ void UpdateExternalImage(ImageKey aKey, const ImageDescriptor& aDescriptor,
+ ExternalImageId aExtID,
+ wr::ExternalImageType aImageType,
+ uint8_t aChannelIndex = 0);
+
+ void UpdateExternalImageWithDirtyRect(ImageKey aKey,
+ const ImageDescriptor& aDescriptor,
+ ExternalImageId aExtID,
+ wr::ExternalImageType aImageType,
+ const wr::DeviceIntRect& aDirtyRect,
+ uint8_t aChannelIndex = 0);
+
+ void SetBlobImageVisibleArea(BlobImageKey aKey,
+ const wr::DeviceIntRect& aArea);
+
+ void DeleteImage(wr::ImageKey aKey);
+
+ void DeleteBlobImage(wr::BlobImageKey aKey);
+
+ void AddRawFont(wr::FontKey aKey, wr::Vec<uint8_t>& aBytes, uint32_t aIndex);
+
+ void AddFontDescriptor(wr::FontKey aKey, wr::Vec<uint8_t>& aBytes,
+ uint32_t aIndex);
+
+ void DeleteFont(wr::FontKey aKey);
+
+ void AddFontInstance(wr::FontInstanceKey aKey, wr::FontKey aFontKey,
+ float aGlyphSize,
+ const wr::FontInstanceOptions* aOptions,
+ const wr::FontInstancePlatformOptions* aPlatformOptions,
+ wr::Vec<uint8_t>& aVariations);
+
+ void DeleteFontInstance(wr::FontInstanceKey aKey);
+
+ void UpdateQualitySettings(bool aForceSubpixelAAWherePossible);
+
+ void Notify(wr::Checkpoint aWhen, UniquePtr<NotificationHandler> aHandler);
+
+ void Clear();
+
+ bool UseSceneBuilderThread() const { return mUseSceneBuilderThread; }
+ Transaction* Raw() { return mTxn; }
+
+ protected:
+ bool mUseSceneBuilderThread;
+ Transaction* mTxn;
+};
+
+class TransactionWrapper final {
+ public:
+ explicit TransactionWrapper(Transaction* aTxn);
+
+ void UpdateDynamicProperties(
+ const nsTArray<wr::WrOpacityProperty>& aOpacityArray,
+ const nsTArray<wr::WrTransformProperty>& aTransformArray,
+ const nsTArray<wr::WrColorProperty>& aColorArray);
+ void AppendTransformProperties(
+ const nsTArray<wr::WrTransformProperty>& aTransformArray);
+ void UpdateScrollPosition(
+ const wr::WrPipelineId& aPipelineId,
+ const layers::ScrollableLayerGuid::ViewID& aScrollId,
+ const wr::LayoutPoint& aScrollPosition);
+ void UpdatePinchZoom(float aZoom);
+ void UpdateIsTransformAsyncZooming(uint64_t aAnimationId, bool aIsZooming);
+
+ private:
+ Transaction* mTxn;
+};
+
+class WebRenderAPI final {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebRenderAPI);
+
+ public:
+ /// This can be called on the compositor thread only.
+ static already_AddRefed<WebRenderAPI> Create(
+ layers::CompositorBridgeParent* aBridge,
+ RefPtr<widget::CompositorWidget>&& aWidget,
+ const wr::WrWindowId& aWindowId, LayoutDeviceIntSize aSize,
+ layers::WindowKind aWindowKind, nsACString& aError);
+
+ already_AddRefed<WebRenderAPI> Clone();
+
+ wr::WindowId GetId() const { return mId; }
+
+ /// Do a non-blocking hit-testing query on a shared version of the hit
+ /// testing information.
+ std::vector<WrHitResult> HitTest(const wr::WorldPoint& aPoint);
+
+ void SendTransaction(TransactionBuilder& aTxn);
+
+ void SetFrameStartTime(const TimeStamp& aTime);
+
+ void RunOnRenderThread(UniquePtr<RendererEvent> aEvent);
+
+ void Readback(const TimeStamp& aStartTime, gfx::IntSize aSize,
+ const gfx::SurfaceFormat& aFormat,
+ const Range<uint8_t>& aBuffer, bool* aNeedsYFlip);
+
+ void ClearAllCaches();
+ void EnableNativeCompositor(bool aEnable);
+ void EnableMultithreading(bool aEnable);
+ void SetBatchingLookback(uint32_t aCount);
+
+ void SetClearColor(const gfx::DeviceColor& aColor);
+ void SetProfilerUI(const nsCString& aUIString);
+
+ void Pause();
+ bool Resume();
+
+ void WakeSceneBuilder();
+ void FlushSceneBuilder();
+
+ void NotifyMemoryPressure();
+ void AccumulateMemoryReport(wr::MemoryReport*);
+
+ wr::WrIdNamespace GetNamespace();
+ layers::WebRenderBackend GetBackendType() { return mBackend; }
+ layers::WebRenderCompositor GetCompositorType() { return mCompositor; }
+ uint32_t GetMaxTextureSize() const { return mMaxTextureSize; }
+ bool GetUseANGLE() const { return mUseANGLE; }
+ bool GetUseDComp() const { return mUseDComp; }
+ bool GetUseTripleBuffering() const { return mUseTripleBuffering; }
+ bool SupportsExternalBufferTextures() const {
+ return mSupportsExternalBufferTextures;
+ }
+ layers::SyncHandle GetSyncHandle() const { return mSyncHandle; }
+
+ void Capture();
+
+ void ToggleCaptureSequence();
+
+ void BeginRecording(const TimeStamp& aRecordingStart,
+ wr::PipelineId aRootPipelineId);
+
+ typedef MozPromise<bool, nsresult, true> WriteCollectedFramesPromise;
+ typedef MozPromise<layers::CollectedFrames, nsresult, true>
+ GetCollectedFramesPromise;
+
+ /**
+ * Write the frames collected since the call to BeginRecording() to disk.
+ *
+ * If there is not currently a recorder, this is a no-op.
+ */
+ RefPtr<WriteCollectedFramesPromise> WriteCollectedFrames();
+
+ /**
+ * Return the frames collected since the call to BeginRecording() encoded
+ * as data URIs.
+ *
+ * If there is not currently a recorder, this is a no-op and the promise will
+ * be rejected.
+ */
+ RefPtr<GetCollectedFramesPromise> GetCollectedFrames();
+
+ protected:
+ WebRenderAPI(wr::DocumentHandle* aHandle, wr::WindowId aId,
+ layers::WebRenderBackend aBackend,
+ layers::WebRenderCompositor aCompositor,
+ uint32_t aMaxTextureSize, bool aUseANGLE, bool aUseDComp,
+ bool aUseTripleBuffering, bool aSupportsExternalBufferTextures,
+ layers::SyncHandle aSyncHandle);
+
+ ~WebRenderAPI();
+ // Should be used only for shutdown handling
+ void WaitFlushed();
+
+ void UpdateDebugFlags(uint32_t aFlags);
+
+ wr::DocumentHandle* mDocHandle;
+ wr::WindowId mId;
+ layers::WebRenderBackend mBackend;
+ layers::WebRenderCompositor mCompositor;
+ int32_t mMaxTextureSize;
+ bool mUseANGLE;
+ bool mUseDComp;
+ bool mUseTripleBuffering;
+ bool mSupportsExternalBufferTextures;
+ bool mCaptureSequence;
+ layers::SyncHandle mSyncHandle;
+
+ // We maintain alive the root api to know when to shut the render backend
+ // down, and the root api for the document to know when to delete the
+ // document. mRootApi is null for the api object that owns the channel (and is
+ // responsible for shutting it down), and mRootDocumentApi is null for the api
+ // object owning (and responsible for destroying) a given document. All api
+ // objects in the same window use the same channel, and some api objects write
+ // to the same document (but there is only one owner for each channel and for
+ // each document).
+ RefPtr<wr::WebRenderAPI> mRootApi;
+ RefPtr<wr::WebRenderAPI> mRootDocumentApi;
+
+ friend class DisplayListBuilder;
+ friend class layers::WebRenderBridgeParent;
+};
+
+// This is a RAII class that automatically sends the transaction on
+// destruction. This is useful for code that has multiple exit points and we
+// want to ensure that the stuff accumulated in the transaction gets sent
+// regardless of which exit we take. Note that if the caller explicitly calls
+// mApi->SendTransaction() that's fine too because that empties out the
+// TransactionBuilder and leaves it as a valid empty transaction, so calling
+// SendTransaction on it again ends up being a no-op.
+class MOZ_RAII AutoTransactionSender {
+ public:
+ AutoTransactionSender(WebRenderAPI* aApi, TransactionBuilder* aTxn)
+ : mApi(aApi), mTxn(aTxn) {}
+
+ ~AutoTransactionSender() { mApi->SendTransaction(*mTxn); }
+
+ private:
+ WebRenderAPI* mApi;
+ TransactionBuilder* mTxn;
+};
+
+/**
+ * A set of optional parameters for stacking context creation.
+ */
+struct MOZ_STACK_CLASS StackingContextParams : public WrStackingContextParams {
+ StackingContextParams()
+ : WrStackingContextParams{
+ WrStackingContextClip::None(),
+ nullptr,
+ nullptr,
+ nullptr,
+ wr::TransformStyle::Flat,
+ wr::WrReferenceFrameKind::Transform,
+ nullptr,
+ /* prim_flags = */ wr::PrimitiveFlags::IS_BACKFACE_VISIBLE,
+ wr::MixBlendMode::Normal,
+ wr::StackingContextFlags{0}} {}
+
+ void SetPreserve3D(bool aPreserve) {
+ transform_style =
+ aPreserve ? wr::TransformStyle::Preserve3D : wr::TransformStyle::Flat;
+ }
+
+ nsTArray<wr::FilterOp> mFilters;
+ nsTArray<wr::WrFilterData> mFilterDatas;
+ wr::LayoutRect mBounds = wr::ToLayoutRect(LayoutDeviceRect());
+ const gfx::Matrix4x4* mBoundTransform = nullptr;
+ const gfx::Matrix4x4* mTransformPtr = nullptr;
+ Maybe<nsDisplayTransform*> mDeferredTransformItem;
+ // Whether the stacking context is possibly animated. This alters how
+ // coordinates are transformed/snapped to invalidate less when transforms
+ // change frequently.
+ bool mAnimated = false;
+ // Whether items should be rasterized in a local space that is (mostly)
+ // invariant to transforms, i.e. disabling subpixel AA and screen space pixel
+ // snapping on text runs that would only make sense in screen space.
+ bool mRasterizeLocally = false;
+};
+
+/// This is a simple C++ wrapper around WrState defined in the rust bindings.
+/// We may want to turn this into a direct wrapper on top of
+/// WebRenderFrameBuilder instead, so the interface may change a bit.
+class DisplayListBuilder final {
+ public:
+ explicit DisplayListBuilder(wr::PipelineId aId, size_t aCapacity = 0,
+ layers::DisplayItemCache* aCache = nullptr);
+ DisplayListBuilder(DisplayListBuilder&&) = default;
+
+ ~DisplayListBuilder();
+
+ void Save();
+ void Restore();
+ void ClearSave();
+
+ usize Dump(usize aIndent, const Maybe<usize>& aStart,
+ const Maybe<usize>& aEnd);
+ void DumpSerializedDisplayList();
+
+ void Finalize(wr::BuiltDisplayList& aOutDisplayList);
+ void Finalize(layers::DisplayListData& aOutTransaction);
+
+ Maybe<wr::WrSpatialId> PushStackingContext(
+ const StackingContextParams& aParams, const wr::LayoutRect& aBounds,
+ const wr::RasterSpace& aRasterSpace);
+ void PopStackingContext(bool aIsReferenceFrame);
+
+ wr::WrClipChainId DefineClipChain(const nsTArray<wr::WrClipId>& aClips,
+ bool aParentWithCurrentChain = false);
+
+ wr::WrClipId DefineClip(
+ const Maybe<wr::WrSpaceAndClip>& aParent, const wr::LayoutRect& aClipRect,
+ const nsTArray<wr::ComplexClipRegion>* aComplex = nullptr);
+
+ wr::WrClipId DefineImageMaskClip(const wr::ImageMask& aMask);
+ wr::WrClipId DefineRoundedRectClip(const wr::ComplexClipRegion& aComplex);
+ wr::WrClipId DefineRectClip(wr::LayoutRect aClipRect);
+
+ wr::WrSpatialId DefineStickyFrame(const wr::LayoutRect& aContentRect,
+ const float* aTopMargin,
+ const float* aRightMargin,
+ const float* aBottomMargin,
+ const float* aLeftMargin,
+ const StickyOffsetBounds& aVerticalBounds,
+ const StickyOffsetBounds& aHorizontalBounds,
+ const wr::LayoutVector2D& aAppliedOffset);
+
+ Maybe<wr::WrSpaceAndClip> GetScrollIdForDefinedScrollLayer(
+ layers::ScrollableLayerGuid::ViewID aViewId) const;
+ wr::WrSpaceAndClip DefineScrollLayer(
+ const layers::ScrollableLayerGuid::ViewID& aViewId,
+ const Maybe<wr::WrSpaceAndClip>& aParent,
+ const wr::LayoutRect& aContentRect, const wr::LayoutRect& aClipRect,
+ const wr::LayoutPoint& aScrollOffset);
+
+ void PushRect(const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
+ bool aIsBackfaceVisible, const wr::ColorF& aColor);
+ void PushRectWithAnimation(const wr::LayoutRect& aBounds,
+ const wr::LayoutRect& aClip,
+ bool aIsBackfaceVisible, const wr::ColorF& aColor,
+ const WrAnimationProperty* aAnimation);
+ void PushRoundedRect(const wr::LayoutRect& aBounds,
+ const wr::LayoutRect& aClip, bool aIsBackfaceVisible,
+ const wr::ColorF& aColor);
+ void PushHitTest(const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
+ bool aIsBackfaceVisible,
+ const layers::ScrollableLayerGuid::ViewID& aScrollId,
+ gfx::CompositorHitTestInfo aHitInfo, SideBits aSideBits);
+ void PushClearRect(const wr::LayoutRect& aBounds);
+ void PushClearRectWithComplexRegion(const wr::LayoutRect& aBounds,
+ const wr::ComplexClipRegion& aRegion);
+
+ void PushBackdropFilter(const wr::LayoutRect& aBounds,
+ const wr::ComplexClipRegion& aRegion,
+ const nsTArray<wr::FilterOp>& aFilters,
+ const nsTArray<wr::WrFilterData>& aFilterDatas,
+ bool aIsBackfaceVisible);
+
+ void PushLinearGradient(const wr::LayoutRect& aBounds,
+ const wr::LayoutRect& aClip, bool aIsBackfaceVisible,
+ const wr::LayoutPoint& aStartPoint,
+ const wr::LayoutPoint& aEndPoint,
+ const nsTArray<wr::GradientStop>& aStops,
+ wr::ExtendMode aExtendMode,
+ const wr::LayoutSize aTileSize,
+ const wr::LayoutSize aTileSpacing);
+
+ void PushRadialGradient(const wr::LayoutRect& aBounds,
+ const wr::LayoutRect& aClip, bool aIsBackfaceVisible,
+ const wr::LayoutPoint& aCenter,
+ const wr::LayoutSize& aRadius,
+ const nsTArray<wr::GradientStop>& aStops,
+ wr::ExtendMode aExtendMode,
+ const wr::LayoutSize aTileSize,
+ const wr::LayoutSize aTileSpacing);
+
+ void PushConicGradient(const wr::LayoutRect& aBounds,
+ const wr::LayoutRect& aClip, bool aIsBackfaceVisible,
+ const wr::LayoutPoint& aCenter, const float aAngle,
+ const nsTArray<wr::GradientStop>& aStops,
+ wr::ExtendMode aExtendMode,
+ const wr::LayoutSize aTileSize,
+ const wr::LayoutSize aTileSpacing);
+
+ void PushImage(const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
+ bool aIsBackfaceVisible, wr::ImageRendering aFilter,
+ wr::ImageKey aImage, bool aPremultipliedAlpha = true,
+ const wr::ColorF& aColor = wr::ColorF{1.0f, 1.0f, 1.0f, 1.0f},
+ bool aPreferCompositorSurface = false,
+ bool aSupportsExternalCompositing = false);
+
+ void PushRepeatingImage(
+ const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
+ bool aIsBackfaceVisible, const wr::LayoutSize& aStretchSize,
+ const wr::LayoutSize& aTileSpacing, wr::ImageRendering aFilter,
+ wr::ImageKey aImage, bool aPremultipliedAlpha = true,
+ const wr::ColorF& aColor = wr::ColorF{1.0f, 1.0f, 1.0f, 1.0f});
+
+ void PushYCbCrPlanarImage(
+ const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
+ bool aIsBackfaceVisible, wr::ImageKey aImageChannel0,
+ wr::ImageKey aImageChannel1, wr::ImageKey aImageChannel2,
+ wr::WrColorDepth aColorDepth, wr::WrYuvColorSpace aColorSpace,
+ wr::WrColorRange aColorRange, wr::ImageRendering aFilter,
+ bool aPreferCompositorSurface = false,
+ bool aSupportsExternalCompositing = false);
+
+ void PushNV12Image(const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
+ bool aIsBackfaceVisible, wr::ImageKey aImageChannel0,
+ wr::ImageKey aImageChannel1, wr::WrColorDepth aColorDepth,
+ wr::WrYuvColorSpace aColorSpace,
+ wr::WrColorRange aColorRange, wr::ImageRendering aFilter,
+ bool aPreferCompositorSurface = false,
+ bool aSupportsExternalCompositing = false);
+
+ void PushYCbCrInterleavedImage(
+ const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
+ bool aIsBackfaceVisible, wr::ImageKey aImageChannel0,
+ wr::WrColorDepth aColorDepth, wr::WrYuvColorSpace aColorSpace,
+ wr::WrColorRange aColorRange, wr::ImageRendering aFilter,
+ bool aPreferCompositorSurface = false,
+ bool aSupportsExternalCompositing = false);
+
+ void PushIFrame(const wr::LayoutRect& aBounds, bool aIsBackfaceVisible,
+ wr::PipelineId aPipeline, bool aIgnoreMissingPipeline);
+
+ // XXX WrBorderSides are passed with Range.
+ // It is just to bypass compiler bug. See Bug 1357734.
+ void PushBorder(const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
+ bool aIsBackfaceVisible, const wr::LayoutSideOffsets& aWidths,
+ const Range<const wr::BorderSide>& aSides,
+ const wr::BorderRadius& aRadius,
+ wr::AntialiasBorder = wr::AntialiasBorder::Yes);
+
+ void PushBorderImage(const wr::LayoutRect& aBounds,
+ const wr::LayoutRect& aClip, bool aIsBackfaceVisible,
+ const wr::WrBorderImage& aParams);
+
+ void PushBorderGradient(const wr::LayoutRect& aBounds,
+ const wr::LayoutRect& aClip, bool aIsBackfaceVisible,
+ const wr::LayoutSideOffsets& aWidths,
+ const int32_t aWidth, const int32_t aHeight,
+ bool aFill, const wr::DeviceIntSideOffsets& aSlice,
+ const wr::LayoutPoint& aStartPoint,
+ const wr::LayoutPoint& aEndPoint,
+ const nsTArray<wr::GradientStop>& aStops,
+ wr::ExtendMode aExtendMode,
+ const wr::LayoutSideOffsets& aOutset);
+
+ void PushBorderRadialGradient(
+ const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
+ bool aIsBackfaceVisible, const wr::LayoutSideOffsets& aWidths, bool aFill,
+ const wr::LayoutPoint& aCenter, const wr::LayoutSize& aRadius,
+ const nsTArray<wr::GradientStop>& aStops, wr::ExtendMode aExtendMode,
+ const wr::LayoutSideOffsets& aOutset);
+
+ void PushBorderConicGradient(
+ const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
+ bool aIsBackfaceVisible, const wr::LayoutSideOffsets& aWidths, bool aFill,
+ const wr::LayoutPoint& aCenter, const float aAngle,
+ const nsTArray<wr::GradientStop>& aStops, wr::ExtendMode aExtendMode,
+ const wr::LayoutSideOffsets& aOutset);
+
+ void PushText(const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
+ bool aIsBackfaceVisible, const wr::ColorF& aColor,
+ wr::FontInstanceKey aFontKey,
+ Range<const wr::GlyphInstance> aGlyphBuffer,
+ const wr::GlyphOptions* aGlyphOptions = nullptr);
+
+ void PushLine(const wr::LayoutRect& aClip, bool aIsBackfaceVisible,
+ const wr::Line& aLine);
+
+ void PushShadow(const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
+ bool aIsBackfaceVisible, const wr::Shadow& aShadow,
+ bool aShouldInflate);
+
+ void PopAllShadows();
+
+ void PushBoxShadow(const wr::LayoutRect& aRect, const wr::LayoutRect& aClip,
+ bool aIsBackfaceVisible, const wr::LayoutRect& aBoxBounds,
+ const wr::LayoutVector2D& aOffset,
+ const wr::ColorF& aColor, const float& aBlurRadius,
+ const float& aSpreadRadius,
+ const wr::BorderRadius& aBorderRadius,
+ const wr::BoxShadowClipMode& aClipMode);
+
+ /**
+ * Notifies the DisplayListBuilder that it can group together WR display items
+ * that are pushed until |CancelGroup()| or |FinishGroup()| call.
+ */
+ void StartGroup(nsPaintedDisplayItem* aItem);
+
+ /**
+ * Cancels grouping of the display items and discards all the display items
+ * pushed between the |StartGroup()| and |CancelGroup()| calls.
+ */
+ void CancelGroup(const bool aDiscard = false);
+
+ /**
+ * Finishes the display item group. The group is stored in WebRender backend,
+ * and can be reused with |ReuseItem()|, if the Gecko display item is reused.
+ */
+ void FinishGroup();
+
+ /**
+ * Try to reuse the previously created WebRender display items for the given
+ * Gecko display item |aItem|.
+ * Returns true if the items were reused, otherwise returns false.
+ */
+ bool ReuseItem(nsPaintedDisplayItem* aItem);
+
+ uint64_t CurrentClipChainId() const {
+ return mCurrentSpaceAndClipChain.clip_chain;
+ }
+
+ const wr::WrSpaceAndClipChain& CurrentSpaceAndClipChain() const {
+ return mCurrentSpaceAndClipChain;
+ }
+
+ const wr::PipelineId& CurrentPipelineId() const { return mPipelineId; }
+
+ // Checks to see if the innermost enclosing fixed pos item has the same
+ // ASR. If so, it returns the scroll target for that fixed-pos item.
+ // Otherwise, it returns Nothing().
+ Maybe<layers::ScrollableLayerGuid::ViewID> GetContainingFixedPosScrollTarget(
+ const ActiveScrolledRoot* aAsr);
+
+ Maybe<SideBits> GetContainingFixedPosSideBits(const ActiveScrolledRoot* aAsr);
+
+ already_AddRefed<gfxContext> GetTextContext(
+ wr::IpcResourceUpdateQueue& aResources,
+ const layers::StackingContextHelper& aSc,
+ layers::RenderRootStateManager* aManager, nsDisplayItem* aItem,
+ nsRect& aBounds, const gfx::Point& aDeviceOffset);
+
+ // Try to avoid using this when possible.
+ wr::WrState* Raw() { return mWrState; }
+
+ void SetClipChainLeaf(const Maybe<wr::LayoutRect>& aClipRect) {
+ mClipChainLeaf = aClipRect;
+ }
+
+ // A chain of RAII objects, each holding a (ASR, ViewID, SideBits) tuple of
+ // data. The topmost object is pointed to by the mActiveFixedPosTracker
+ // pointer in the wr::DisplayListBuilder.
+ class MOZ_RAII FixedPosScrollTargetTracker final {
+ public:
+ FixedPosScrollTargetTracker(DisplayListBuilder& aBuilder,
+ const ActiveScrolledRoot* aAsr,
+ layers::ScrollableLayerGuid::ViewID aScrollId,
+ SideBits aSideBits);
+ ~FixedPosScrollTargetTracker();
+ Maybe<layers::ScrollableLayerGuid::ViewID> GetScrollTargetForASR(
+ const ActiveScrolledRoot* aAsr);
+ Maybe<SideBits> GetSideBitsForASR(const ActiveScrolledRoot* aAsr);
+
+ private:
+ FixedPosScrollTargetTracker* mParentTracker;
+ DisplayListBuilder& mBuilder;
+ const ActiveScrolledRoot* mAsr;
+ layers::ScrollableLayerGuid::ViewID mScrollId;
+ SideBits mSideBits;
+ };
+
+ protected:
+ wr::LayoutRect MergeClipLeaf(const wr::LayoutRect& aClip) {
+ if (mClipChainLeaf) {
+ return wr::IntersectLayoutRect(*mClipChainLeaf, aClip);
+ }
+ return aClip;
+ }
+
+ // See the implementation of PushShadow for details on these methods.
+ void SuspendClipLeafMerging();
+ void ResumeClipLeafMerging();
+
+ wr::WrState* mWrState;
+
+ // Track each scroll id that we encountered. We use this structure to
+ // ensure that we don't define a particular scroll layer multiple times,
+ // as that results in undefined behaviour in WR.
+ std::unordered_map<layers::ScrollableLayerGuid::ViewID, wr::WrSpaceAndClip>
+ mScrollIds;
+
+ wr::WrSpaceAndClipChain mCurrentSpaceAndClipChain;
+
+ // Contains the current leaf of the clip chain to be merged with the
+ // display item's clip rect when pushing an item. May be set to Nothing() if
+ // there is no clip rect to merge with.
+ Maybe<wr::LayoutRect> mClipChainLeaf;
+
+ // Versions of the above that are on hold while SuspendClipLeafMerging is on
+ // (see the implementation of PushShadow for details).
+ Maybe<wr::WrSpaceAndClipChain> mSuspendedSpaceAndClipChain;
+ Maybe<wr::LayoutRect> mSuspendedClipChainLeaf;
+
+ RefPtr<layout::TextDrawTarget> mCachedTextDT;
+ RefPtr<gfxContext> mCachedContext;
+
+ FixedPosScrollTargetTracker* mActiveFixedPosTracker;
+
+ wr::PipelineId mPipelineId;
+ wr::LayoutSize mContentSize;
+
+ nsTArray<wr::PipelineId> mRemotePipelineIds;
+
+ layers::DisplayItemCache* mDisplayItemCache;
+ Maybe<uint16_t> mCurrentCacheSlot;
+
+ friend class WebRenderAPI;
+ friend class SpaceAndClipChainHelper;
+};
+
+// This is a RAII class that overrides the current Wr's SpatialId and
+// ClipChainId.
+class MOZ_RAII SpaceAndClipChainHelper final {
+ public:
+ SpaceAndClipChainHelper(DisplayListBuilder& aBuilder,
+ wr::WrSpaceAndClipChain aSpaceAndClipChain)
+ : mBuilder(aBuilder),
+ mOldSpaceAndClipChain(aBuilder.mCurrentSpaceAndClipChain) {
+ aBuilder.mCurrentSpaceAndClipChain = aSpaceAndClipChain;
+ }
+ SpaceAndClipChainHelper(DisplayListBuilder& aBuilder,
+ wr::WrSpatialId aSpatialId)
+ : mBuilder(aBuilder),
+ mOldSpaceAndClipChain(aBuilder.mCurrentSpaceAndClipChain) {
+ aBuilder.mCurrentSpaceAndClipChain.space = aSpatialId;
+ }
+ SpaceAndClipChainHelper(DisplayListBuilder& aBuilder,
+ wr::WrClipChainId aClipChainId)
+ : mBuilder(aBuilder),
+ mOldSpaceAndClipChain(aBuilder.mCurrentSpaceAndClipChain) {
+ aBuilder.mCurrentSpaceAndClipChain.clip_chain = aClipChainId.id;
+ }
+
+ ~SpaceAndClipChainHelper() {
+ mBuilder.mCurrentSpaceAndClipChain = mOldSpaceAndClipChain;
+ }
+
+ private:
+ SpaceAndClipChainHelper(const SpaceAndClipChainHelper&) = delete;
+
+ DisplayListBuilder& mBuilder;
+ wr::WrSpaceAndClipChain mOldSpaceAndClipChain;
+};
+
+Maybe<wr::ImageFormat> SurfaceFormatToImageFormat(gfx::SurfaceFormat aFormat);
+
+} // namespace wr
+} // namespace mozilla
+
+#endif
diff --git a/gfx/webrender_bindings/WebRenderTypes.cpp b/gfx/webrender_bindings/WebRenderTypes.cpp
new file mode 100644
index 0000000000..9de77ea6e7
--- /dev/null
+++ b/gfx/webrender_bindings/WebRenderTypes.cpp
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "WebRenderTypes.h"
+
+#include "mozilla/ipc/ByteBuf.h"
+#include "nsStyleConsts.h"
+
+namespace mozilla {
+namespace wr {
+
+WindowId NewWindowId() {
+ static uint64_t sNextId = 1;
+
+ WindowId id;
+ id.mHandle = sNextId++;
+ return id;
+}
+
+BorderStyle ToBorderStyle(StyleBorderStyle aStyle) {
+ switch (aStyle) {
+ case StyleBorderStyle::None:
+ return wr::BorderStyle::None;
+ case StyleBorderStyle::Solid:
+ return wr::BorderStyle::Solid;
+ case StyleBorderStyle::Double:
+ return wr::BorderStyle::Double;
+ case StyleBorderStyle::Dotted:
+ return wr::BorderStyle::Dotted;
+ case StyleBorderStyle::Dashed:
+ return wr::BorderStyle::Dashed;
+ case StyleBorderStyle::Hidden:
+ return wr::BorderStyle::Hidden;
+ case StyleBorderStyle::Groove:
+ return wr::BorderStyle::Groove;
+ case StyleBorderStyle::Ridge:
+ return wr::BorderStyle::Ridge;
+ case StyleBorderStyle::Inset:
+ return wr::BorderStyle::Inset;
+ case StyleBorderStyle::Outset:
+ return wr::BorderStyle::Outset;
+ default:
+ MOZ_ASSERT(false);
+ }
+ return wr::BorderStyle::None;
+}
+
+wr::RepeatMode ToRepeatMode(StyleBorderImageRepeat aRepeat) {
+ switch (aRepeat) {
+ case StyleBorderImageRepeat::Stretch:
+ return wr::RepeatMode::Stretch;
+ case StyleBorderImageRepeat::Repeat:
+ return wr::RepeatMode::Repeat;
+ case StyleBorderImageRepeat::Round:
+ return wr::RepeatMode::Round;
+ case StyleBorderImageRepeat::Space:
+ return wr::RepeatMode::Space;
+ default:
+ MOZ_ASSERT(false);
+ }
+
+ return wr::RepeatMode::Stretch;
+}
+
+void Assign_WrVecU8(wr::WrVecU8& aVec, mozilla::ipc::ByteBuf&& aOther) {
+ aVec.data = aOther.mData;
+ aVec.length = aOther.mLen;
+ aVec.capacity = aOther.mCapacity;
+ aOther.mData = nullptr;
+ aOther.mLen = 0;
+ aOther.mCapacity = 0;
+}
+
+WrSpaceAndClip RootScrollNode() {
+ WrSpaceAndClip sac;
+ sac.clip = wr_root_clip_id();
+ sac.space = wr_root_scroll_node_id();
+ return sac;
+}
+
+WrSpaceAndClipChain RootScrollNodeWithChain() {
+ WrSpaceAndClipChain sacc;
+ sacc.clip_chain = wr::ROOT_CLIP_CHAIN;
+ sacc.space = wr_root_scroll_node_id();
+ return sacc;
+}
+
+} // namespace wr
+} // namespace mozilla
diff --git a/gfx/webrender_bindings/WebRenderTypes.h b/gfx/webrender_bindings/WebRenderTypes.h
new file mode 100644
index 0000000000..9189565c5c
--- /dev/null
+++ b/gfx/webrender_bindings/WebRenderTypes.h
@@ -0,0 +1,833 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef GFX_WEBRENDERTYPES_H
+#define GFX_WEBRENDERTYPES_H
+
+#include "ImageTypes.h"
+#include "mozilla/webrender/webrender_ffi.h"
+#include "mozilla/EnumSet.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/gfx/Matrix.h"
+#include "mozilla/gfx/Types.h"
+#include "mozilla/gfx/Tools.h"
+#include "mozilla/gfx/Rect.h"
+#include "mozilla/layers/LayersTypes.h"
+#include "mozilla/PodOperations.h"
+#include "mozilla/Range.h"
+#include "mozilla/TypeTraits.h"
+#include "Units.h"
+
+namespace mozilla {
+
+enum class StyleBorderStyle : uint8_t;
+enum class StyleBorderImageRepeat : uint8_t;
+
+namespace ipc {
+class ByteBuf;
+} // namespace ipc
+
+namespace wr {
+
+// Using uintptr_t in C++ code for "size" types seems weird, so let's use a
+// better-sounding typedef. The name comes from the fact that we generally
+// have to deal with uintptr_t because that's what rust's usize maps to.
+typedef uintptr_t usize;
+
+typedef wr::WrWindowId WindowId;
+typedef wr::WrRemovedPipeline RemovedPipeline;
+
+class RenderedFrameIdType {};
+typedef layers::BaseTransactionId<RenderedFrameIdType> RenderedFrameId;
+
+typedef mozilla::Maybe<mozilla::wr::IdNamespace> MaybeIdNamespace;
+typedef mozilla::Maybe<mozilla::wr::ImageMask> MaybeImageMask;
+typedef Maybe<ExternalImageId> MaybeExternalImageId;
+
+typedef Maybe<FontInstanceOptions> MaybeFontInstanceOptions;
+typedef Maybe<FontInstancePlatformOptions> MaybeFontInstancePlatformOptions;
+
+struct ExternalImageKeyPair {
+ ImageKey key;
+ ExternalImageId id;
+};
+
+/* Generate a brand new window id and return it. */
+WindowId NewWindowId();
+
+inline DebugFlags NewDebugFlags(uint32_t aFlags) { return {aFlags}; }
+
+inline Maybe<wr::ImageFormat> SurfaceFormatToImageFormat(
+ gfx::SurfaceFormat aFormat) {
+ switch (aFormat) {
+ case gfx::SurfaceFormat::R8G8B8X8:
+ // WebRender not support RGBX8. Assert here.
+ MOZ_ASSERT(false);
+ return Nothing();
+ case gfx::SurfaceFormat::R8G8B8A8:
+ return Some(wr::ImageFormat::RGBA8);
+ case gfx::SurfaceFormat::B8G8R8X8:
+ // TODO: WebRender will have a BGRA + opaque flag for this but does not
+ // have it yet (cf. issue #732).
+ case gfx::SurfaceFormat::B8G8R8A8:
+ return Some(wr::ImageFormat::BGRA8);
+ case gfx::SurfaceFormat::A8:
+ return Some(wr::ImageFormat::R8);
+ case gfx::SurfaceFormat::A16:
+ return Some(wr::ImageFormat::R16);
+ case gfx::SurfaceFormat::R8G8:
+ return Some(wr::ImageFormat::RG8);
+ case gfx::SurfaceFormat::R16G16:
+ return Some(wr::ImageFormat::RG16);
+ case gfx::SurfaceFormat::UNKNOWN:
+ default:
+ return Nothing();
+ }
+}
+
+inline gfx::SurfaceFormat ImageFormatToSurfaceFormat(ImageFormat aFormat) {
+ switch (aFormat) {
+ case ImageFormat::BGRA8:
+ return gfx::SurfaceFormat::B8G8R8A8;
+ case ImageFormat::R8:
+ return gfx::SurfaceFormat::A8;
+ case ImageFormat::R16:
+ return gfx::SurfaceFormat::A16;
+ default:
+ return gfx::SurfaceFormat::UNKNOWN;
+ }
+}
+
+struct ImageDescriptor : public wr::WrImageDescriptor {
+ // We need a default constructor for ipdl serialization.
+ ImageDescriptor() {
+ format = (ImageFormat)0;
+ width = 0;
+ height = 0;
+ stride = 0;
+ opacity = OpacityType::HasAlphaChannel;
+ prefer_compositor_surface = false;
+ }
+
+ ImageDescriptor(const gfx::IntSize& aSize, gfx::SurfaceFormat aFormat,
+ bool aPreferCompositorSurface = false) {
+ format = wr::SurfaceFormatToImageFormat(aFormat).value();
+ width = aSize.width;
+ height = aSize.height;
+ stride = 0;
+ opacity = gfx::IsOpaque(aFormat) ? OpacityType::Opaque
+ : OpacityType::HasAlphaChannel;
+ prefer_compositor_surface = aPreferCompositorSurface;
+ }
+
+ ImageDescriptor(const gfx::IntSize& aSize, uint32_t aByteStride,
+ gfx::SurfaceFormat aFormat,
+ bool aPreferCompositorSurface = false) {
+ format = wr::SurfaceFormatToImageFormat(aFormat).value();
+ width = aSize.width;
+ height = aSize.height;
+ stride = aByteStride;
+ opacity = gfx::IsOpaque(aFormat) ? OpacityType::Opaque
+ : OpacityType::HasAlphaChannel;
+ prefer_compositor_surface = aPreferCompositorSurface;
+ }
+
+ ImageDescriptor(const gfx::IntSize& aSize, uint32_t aByteStride,
+ gfx::SurfaceFormat aFormat, OpacityType aOpacity,
+ bool aPreferCompositorSurface = false) {
+ format = wr::SurfaceFormatToImageFormat(aFormat).value();
+ width = aSize.width;
+ height = aSize.height;
+ stride = aByteStride;
+ opacity = aOpacity;
+ prefer_compositor_surface = aPreferCompositorSurface;
+ }
+};
+
+inline uint64_t AsUint64(const NativeSurfaceId& aId) {
+ return static_cast<uint64_t>(aId._0);
+}
+
+// Whenever possible, use wr::WindowId instead of manipulating uint64_t.
+inline uint64_t AsUint64(const WindowId& aId) {
+ return static_cast<uint64_t>(aId.mHandle);
+}
+
+// Whenever possible, use wr::ImageKey instead of manipulating uint64_t.
+inline uint64_t AsUint64(const ImageKey& aId) {
+ return (static_cast<uint64_t>(aId.mNamespace.mHandle) << 32) +
+ static_cast<uint64_t>(aId.mHandle);
+}
+
+inline ImageKey AsImageKey(const uint64_t& aId) {
+ ImageKey imageKey;
+ imageKey.mNamespace.mHandle = aId >> 32;
+ imageKey.mHandle = aId;
+ return imageKey;
+}
+
+// Whenever possible, use wr::FontKey instead of manipulating uint64_t.
+inline uint64_t AsUint64(const FontKey& aId) {
+ return (static_cast<uint64_t>(aId.mNamespace.mHandle) << 32) +
+ static_cast<uint64_t>(aId.mHandle);
+}
+
+inline FontKey AsFontKey(const uint64_t& aId) {
+ FontKey fontKey;
+ fontKey.mNamespace.mHandle = aId >> 32;
+ fontKey.mHandle = aId;
+ return fontKey;
+}
+
+// Whenever possible, use wr::FontInstanceKey instead of manipulating uint64_t.
+inline uint64_t AsUint64(const FontInstanceKey& aId) {
+ return (static_cast<uint64_t>(aId.mNamespace.mHandle) << 32) +
+ static_cast<uint64_t>(aId.mHandle);
+}
+
+inline FontInstanceKey AsFontInstanceKey(const uint64_t& aId) {
+ FontInstanceKey instanceKey;
+ instanceKey.mNamespace.mHandle = aId >> 32;
+ instanceKey.mHandle = aId;
+ return instanceKey;
+}
+
+// Whenever possible, use wr::PipelineId instead of manipulating uint64_t.
+inline uint64_t AsUint64(const PipelineId& aId) {
+ return (static_cast<uint64_t>(aId.mNamespace) << 32) +
+ static_cast<uint64_t>(aId.mHandle);
+}
+
+inline PipelineId AsPipelineId(const uint64_t& aId) {
+ PipelineId pipeline;
+ pipeline.mNamespace = aId >> 32;
+ pipeline.mHandle = aId;
+ return pipeline;
+}
+
+inline mozilla::layers::LayersId AsLayersId(const PipelineId& aId) {
+ return mozilla::layers::LayersId{AsUint64(aId)};
+}
+
+inline PipelineId AsPipelineId(const mozilla::layers::LayersId& aId) {
+ return AsPipelineId(uint64_t(aId));
+}
+
+inline ImageRendering ToImageRendering(gfx::SamplingFilter aFilter) {
+ return aFilter == gfx::SamplingFilter::POINT ? ImageRendering::Pixelated
+ : ImageRendering::Auto;
+}
+
+static inline FontRenderMode ToFontRenderMode(gfx::AntialiasMode aMode,
+ bool aPermitSubpixelAA = true) {
+ switch (aMode) {
+ case gfx::AntialiasMode::NONE:
+ return FontRenderMode::Mono;
+ case gfx::AntialiasMode::GRAY:
+ return FontRenderMode::Alpha;
+ case gfx::AntialiasMode::SUBPIXEL:
+ default:
+ return aPermitSubpixelAA ? FontRenderMode::Subpixel
+ : FontRenderMode::Alpha;
+ }
+}
+
+static inline MixBlendMode ToMixBlendMode(gfx::CompositionOp compositionOp) {
+ switch (compositionOp) {
+ case gfx::CompositionOp::OP_MULTIPLY:
+ return MixBlendMode::Multiply;
+ case gfx::CompositionOp::OP_SCREEN:
+ return MixBlendMode::Screen;
+ case gfx::CompositionOp::OP_OVERLAY:
+ return MixBlendMode::Overlay;
+ case gfx::CompositionOp::OP_DARKEN:
+ return MixBlendMode::Darken;
+ case gfx::CompositionOp::OP_LIGHTEN:
+ return MixBlendMode::Lighten;
+ case gfx::CompositionOp::OP_COLOR_DODGE:
+ return MixBlendMode::ColorDodge;
+ case gfx::CompositionOp::OP_COLOR_BURN:
+ return MixBlendMode::ColorBurn;
+ case gfx::CompositionOp::OP_HARD_LIGHT:
+ return MixBlendMode::HardLight;
+ case gfx::CompositionOp::OP_SOFT_LIGHT:
+ return MixBlendMode::SoftLight;
+ case gfx::CompositionOp::OP_DIFFERENCE:
+ return MixBlendMode::Difference;
+ case gfx::CompositionOp::OP_EXCLUSION:
+ return MixBlendMode::Exclusion;
+ case gfx::CompositionOp::OP_HUE:
+ return MixBlendMode::Hue;
+ case gfx::CompositionOp::OP_SATURATION:
+ return MixBlendMode::Saturation;
+ case gfx::CompositionOp::OP_COLOR:
+ return MixBlendMode::Color;
+ case gfx::CompositionOp::OP_LUMINOSITY:
+ return MixBlendMode::Luminosity;
+ default:
+ return MixBlendMode::Normal;
+ }
+}
+
+static inline wr::ColorF ToColorF(const gfx::DeviceColor& color) {
+ wr::ColorF c;
+ c.r = color.r;
+ c.g = color.g;
+ c.b = color.b;
+ c.a = color.a;
+ return c;
+}
+
+static inline wr::ColorU ToColorU(const gfx::DeviceColor& color) {
+ wr::ColorU c;
+ c.r = uint8_t(color.r * 255.0f);
+ c.g = uint8_t(color.g * 255.0f);
+ c.b = uint8_t(color.b * 255.0f);
+ c.a = uint8_t(color.a * 255.0f);
+ return c;
+}
+
+static inline wr::LayoutPoint ToLayoutPoint(
+ const mozilla::LayoutDevicePoint& point) {
+ wr::LayoutPoint p;
+ p.x = point.x;
+ p.y = point.y;
+ return p;
+}
+
+static inline wr::LayoutPoint ToLayoutPoint(
+ const mozilla::LayoutDeviceIntPoint& point) {
+ return ToLayoutPoint(LayoutDevicePoint(point));
+}
+
+static inline wr::WorldPoint ToWorldPoint(const mozilla::ScreenPoint& point) {
+ wr::WorldPoint p;
+ p.x = point.x;
+ p.y = point.y;
+ return p;
+}
+
+static inline wr::LayoutVector2D ToLayoutVector2D(
+ const mozilla::LayoutDevicePoint& point) {
+ wr::LayoutVector2D p;
+ p.x = point.x;
+ p.y = point.y;
+ return p;
+}
+
+static inline wr::LayoutVector2D ToLayoutVector2D(
+ const mozilla::LayoutDeviceIntPoint& point) {
+ return ToLayoutVector2D(LayoutDevicePoint(point));
+}
+
+static inline wr::LayoutRect ToLayoutRect(
+ const mozilla::LayoutDeviceRect& rect) {
+ wr::LayoutRect r;
+ r.origin.x = rect.X();
+ r.origin.y = rect.Y();
+ r.size.width = rect.Width();
+ r.size.height = rect.Height();
+ return r;
+}
+
+static inline wr::LayoutRect ToLayoutRect(const gfx::Rect& rect) {
+ wr::LayoutRect r;
+ r.origin.x = rect.X();
+ r.origin.y = rect.Y();
+ r.size.width = rect.Width();
+ r.size.height = rect.Height();
+ return r;
+}
+
+static inline wr::DeviceIntRect ToDeviceIntRect(
+ const mozilla::ImageIntRect& rect) {
+ wr::DeviceIntRect r;
+ r.origin.x = rect.X();
+ r.origin.y = rect.Y();
+ r.size.width = rect.Width();
+ r.size.height = rect.Height();
+ return r;
+}
+
+// TODO: should be const LayoutDeviceIntRect instead of ImageIntRect
+static inline wr::LayoutIntRect ToLayoutIntRect(
+ const mozilla::ImageIntRect& rect) {
+ wr::LayoutIntRect r;
+ r.origin.x = rect.X();
+ r.origin.y = rect.Y();
+ r.size.width = rect.Width();
+ r.size.height = rect.Height();
+ return r;
+}
+
+static inline wr::LayoutRect ToLayoutRect(
+ const mozilla::LayoutDeviceIntRect& rect) {
+ return ToLayoutRect(IntRectToRect(rect));
+}
+
+static inline wr::LayoutRect IntersectLayoutRect(const wr::LayoutRect& aRect,
+ const wr::LayoutRect& aOther) {
+ wr::LayoutRect r;
+ r.origin.x = std::max(aRect.origin.x, aOther.origin.x);
+ r.origin.y = std::max(aRect.origin.y, aOther.origin.y);
+ r.size.width = std::min(aRect.origin.x + aRect.size.width,
+ aOther.origin.x + aOther.size.width) -
+ r.origin.x;
+ r.size.height = std::min(aRect.origin.y + aRect.size.height,
+ aOther.origin.y + aOther.size.height) -
+ r.origin.y;
+ if (r.size.width < 0 || r.size.height < 0) {
+ r.size.width = 0;
+ r.size.height = 0;
+ }
+ return r;
+}
+
+static inline wr::LayoutSize ToLayoutSize(
+ const mozilla::LayoutDeviceSize& size) {
+ wr::LayoutSize ls;
+ ls.width = size.width;
+ ls.height = size.height;
+ return ls;
+}
+
+static inline wr::ComplexClipRegion ToComplexClipRegion(
+ const gfx::RoundedRect& rect) {
+ wr::ComplexClipRegion ret;
+ ret.rect = ToLayoutRect(rect.rect);
+ ret.radii.top_left = ToLayoutSize(LayoutDeviceSize::FromUnknownSize(
+ rect.corners.radii[mozilla::eCornerTopLeft]));
+ ret.radii.top_right = ToLayoutSize(LayoutDeviceSize::FromUnknownSize(
+ rect.corners.radii[mozilla::eCornerTopRight]));
+ ret.radii.bottom_left = ToLayoutSize(LayoutDeviceSize::FromUnknownSize(
+ rect.corners.radii[mozilla::eCornerBottomLeft]));
+ ret.radii.bottom_right = ToLayoutSize(LayoutDeviceSize::FromUnknownSize(
+ rect.corners.radii[mozilla::eCornerBottomRight]));
+ ret.mode = wr::ClipMode::Clip;
+ return ret;
+}
+
+static inline wr::ComplexClipRegion SimpleRadii(const wr::LayoutRect& aRect,
+ float aRadii) {
+ wr::ComplexClipRegion ret;
+ wr::LayoutSize radii{aRadii, aRadii};
+ ret.rect = aRect;
+ ret.radii.top_left = radii;
+ ret.radii.top_right = radii;
+ ret.radii.bottom_left = radii;
+ ret.radii.bottom_right = radii;
+ ret.mode = wr::ClipMode::Clip;
+ return ret;
+}
+
+static inline wr::LayoutSize ToLayoutSize(
+ const mozilla::LayoutDeviceIntSize& size) {
+ return ToLayoutSize(LayoutDeviceSize(size));
+}
+
+template <class S, class T>
+static inline wr::LayoutTransform ToLayoutTransform(
+ const gfx::Matrix4x4Typed<S, T>& m) {
+ wr::LayoutTransform transform;
+ transform.m11 = m._11;
+ transform.m12 = m._12;
+ transform.m13 = m._13;
+ transform.m14 = m._14;
+ transform.m21 = m._21;
+ transform.m22 = m._22;
+ transform.m23 = m._23;
+ transform.m24 = m._24;
+ transform.m31 = m._31;
+ transform.m32 = m._32;
+ transform.m33 = m._33;
+ transform.m34 = m._34;
+ transform.m41 = m._41;
+ transform.m42 = m._42;
+ transform.m43 = m._43;
+ transform.m44 = m._44;
+ return transform;
+}
+
+wr::BorderStyle ToBorderStyle(StyleBorderStyle style);
+
+static inline wr::BorderSide ToBorderSide(const gfx::DeviceColor& color,
+ StyleBorderStyle style) {
+ wr::BorderSide bs;
+ bs.color = ToColorF(color);
+ bs.style = ToBorderStyle(style);
+ return bs;
+}
+
+static inline wr::BorderRadius EmptyBorderRadius() {
+ wr::BorderRadius br;
+ PodZero(&br);
+ return br;
+}
+
+static inline wr::BorderRadius ToBorderRadius(
+ const mozilla::LayoutDeviceSize& topLeft,
+ const mozilla::LayoutDeviceSize& topRight,
+ const mozilla::LayoutDeviceSize& bottomLeft,
+ const mozilla::LayoutDeviceSize& bottomRight) {
+ wr::BorderRadius br;
+ br.top_left = ToLayoutSize(topLeft);
+ br.top_right = ToLayoutSize(topRight);
+ br.bottom_left = ToLayoutSize(bottomLeft);
+ br.bottom_right = ToLayoutSize(bottomRight);
+ return br;
+}
+
+static inline wr::ComplexClipRegion ToComplexClipRegion(
+ const nsRect& aRect, const nscoord* aRadii, int32_t aAppUnitsPerDevPixel) {
+ wr::ComplexClipRegion ret;
+ ret.rect =
+ ToLayoutRect(LayoutDeviceRect::FromAppUnits(aRect, aAppUnitsPerDevPixel));
+ ret.radii = ToBorderRadius(
+ LayoutDeviceSize::FromAppUnits(
+ nsSize(aRadii[eCornerTopLeftX], aRadii[eCornerTopLeftY]),
+ aAppUnitsPerDevPixel),
+ LayoutDeviceSize::FromAppUnits(
+ nsSize(aRadii[eCornerTopRightX], aRadii[eCornerTopRightY]),
+ aAppUnitsPerDevPixel),
+ LayoutDeviceSize::FromAppUnits(
+ nsSize(aRadii[eCornerBottomLeftX], aRadii[eCornerBottomLeftY]),
+ aAppUnitsPerDevPixel),
+ LayoutDeviceSize::FromAppUnits(
+ nsSize(aRadii[eCornerBottomRightX], aRadii[eCornerBottomRightY]),
+ aAppUnitsPerDevPixel));
+ ret.mode = ClipMode::Clip;
+ return ret;
+}
+
+static inline wr::LayoutSideOffsets ToBorderWidths(float top, float right,
+ float bottom, float left) {
+ wr::LayoutSideOffsets bw;
+ bw.top = top;
+ bw.right = right;
+ bw.bottom = bottom;
+ bw.left = left;
+ return bw;
+}
+
+static inline wr::DeviceIntSideOffsets ToDeviceIntSideOffsets(int32_t top,
+ int32_t right,
+ int32_t bottom,
+ int32_t left) {
+ wr::DeviceIntSideOffsets offset;
+ offset.top = top;
+ offset.right = right;
+ offset.bottom = bottom;
+ offset.left = left;
+ return offset;
+}
+
+static inline wr::LayoutSideOffsets ToLayoutSideOffsets(float top, float right,
+ float bottom,
+ float left) {
+ wr::LayoutSideOffsets offset;
+ offset.top = top;
+ offset.right = right;
+ offset.bottom = bottom;
+ offset.left = left;
+ return offset;
+}
+
+wr::RepeatMode ToRepeatMode(StyleBorderImageRepeat);
+
+template <class S, class T>
+static inline wr::WrTransformProperty ToWrTransformProperty(
+ uint64_t id, const gfx::Matrix4x4Typed<S, T>& transform) {
+ wr::WrTransformProperty prop;
+ prop.id = id;
+ prop.value = ToLayoutTransform(transform);
+ return prop;
+}
+
+static inline wr::WrOpacityProperty ToWrOpacityProperty(uint64_t id,
+ const float opacity) {
+ wr::WrOpacityProperty prop;
+ prop.id = id;
+ prop.value = opacity;
+ return prop;
+}
+
+static inline wr::WrColorProperty ToWrColorProperty(
+ uint64_t id, const gfx::DeviceColor& color) {
+ wr::WrColorProperty prop;
+ prop.id = id;
+ prop.value = ToColorF(color);
+ return prop;
+}
+
+// Whenever possible, use wr::ExternalImageId instead of manipulating uint64_t.
+inline uint64_t AsUint64(const ExternalImageId& aId) {
+ return static_cast<uint64_t>(aId._0);
+}
+
+static inline ExternalImageId ToExternalImageId(uint64_t aID) {
+ ExternalImageId Id;
+ Id._0 = aID;
+ return Id;
+}
+
+static inline wr::WrExternalImage RawDataToWrExternalImage(const uint8_t* aBuff,
+ size_t size) {
+ return wr::WrExternalImage{
+ wr::WrExternalImageType::RawData, 0, 0.0f, 0.0f, 0.0f, 0.0f, aBuff, size};
+}
+
+static inline wr::WrExternalImage NativeTextureToWrExternalImage(
+ uint32_t aHandle, float u0, float v0, float u1, float v1) {
+ return wr::WrExternalImage{wr::WrExternalImageType::NativeTexture,
+ aHandle,
+ u0,
+ v0,
+ u1,
+ v1,
+ nullptr,
+ 0};
+}
+
+static inline wr::WrExternalImage InvalidToWrExternalImage() {
+ return wr::WrExternalImage{
+ wr::WrExternalImageType::Invalid, 0, 0, 0, 0, 0, nullptr, 0};
+}
+
+inline wr::ByteSlice RangeToByteSlice(mozilla::Range<uint8_t> aRange) {
+ return wr::ByteSlice{aRange.begin().get(), aRange.length()};
+}
+
+inline mozilla::Range<const uint8_t> ByteSliceToRange(wr::ByteSlice aWrSlice) {
+ return mozilla::Range<const uint8_t>(aWrSlice.buffer, aWrSlice.len);
+}
+
+inline mozilla::Range<uint8_t> MutByteSliceToRange(wr::MutByteSlice aWrSlice) {
+ return mozilla::Range<uint8_t>(aWrSlice.buffer, aWrSlice.len);
+}
+
+void Assign_WrVecU8(wr::WrVecU8& aVec, mozilla::ipc::ByteBuf&& aOther);
+
+template <typename T>
+struct Vec;
+
+template <>
+struct Vec<uint8_t> final {
+ wr::WrVecU8 inner;
+ Vec() { SetEmpty(); }
+ Vec(Vec&) = delete;
+ Vec(Vec&& src) {
+ inner = src.inner;
+ src.SetEmpty();
+ }
+
+ explicit Vec(mozilla::ipc::ByteBuf&& aSrc) {
+ Assign_WrVecU8(inner, std::move(aSrc));
+ }
+
+ Vec& operator=(Vec&& src) {
+ inner = src.inner;
+ src.SetEmpty();
+ return *this;
+ }
+
+ wr::WrVecU8 Extract() {
+ wr::WrVecU8 ret = inner;
+ SetEmpty();
+ return ret;
+ }
+
+ void SetEmpty() {
+ inner.data = (uint8_t*)1;
+ inner.capacity = 0;
+ inner.length = 0;
+ }
+
+ uint8_t* Data() { return inner.data; }
+
+ size_t Length() { return inner.length; }
+
+ size_t Capacity() { return inner.capacity; }
+
+ Range<uint8_t> GetRange() { return Range<uint8_t>(Data(), Length()); }
+
+ void PushBytes(Range<uint8_t> aBytes) {
+ wr_vec_u8_push_bytes(&inner, RangeToByteSlice(aBytes));
+ }
+
+ void Reserve(size_t aLength) { wr_vec_u8_reserve(&inner, aLength); }
+
+ ~Vec() {
+ if (inner.data) {
+ wr_vec_u8_free(inner);
+ }
+ }
+};
+
+struct ByteBuffer {
+ ByteBuffer(size_t aLength, uint8_t* aData)
+ : mLength(aLength), mData(aData), mOwned(false) {}
+
+ // XXX: this is a bit of hack that assumes
+ // the allocators are the same
+ explicit ByteBuffer(VecU8&& vec) {
+ if (vec.inner.capacity) {
+ mLength = vec.inner.length;
+ mData = vec.inner.data;
+ vec.inner.data = nullptr;
+ vec.inner.capacity = 0;
+ mOwned = true;
+ } else {
+ mOwned = false;
+ mData = nullptr;
+ mLength = 0;
+ }
+ }
+
+ ByteBuffer(ByteBuffer&& aFrom)
+ : mLength(aFrom.mLength), mData(aFrom.mData), mOwned(aFrom.mOwned) {
+ aFrom.mLength = 0;
+ aFrom.mData = nullptr;
+ aFrom.mOwned = false;
+ }
+
+ ByteBuffer(ByteBuffer& aFrom)
+ : mLength(aFrom.mLength), mData(aFrom.mData), mOwned(aFrom.mOwned) {
+ aFrom.mLength = 0;
+ aFrom.mData = nullptr;
+ aFrom.mOwned = false;
+ }
+
+ ByteBuffer() : mLength(0), mData(nullptr), mOwned(false) {}
+
+ bool Allocate(size_t aLength) {
+ MOZ_ASSERT(mData == nullptr);
+ mData = (uint8_t*)malloc(aLength);
+ if (!mData) {
+ return false;
+ }
+ mLength = aLength;
+ mOwned = true;
+ return true;
+ }
+
+ ~ByteBuffer() {
+ if (mData && mOwned) {
+ free(mData);
+ }
+ }
+
+ const Range<uint8_t> AsSlice() const {
+ return Range<uint8_t>(mData, mLength);
+ }
+
+ Range<uint8_t> AsSlice() { return Range<uint8_t>(mData, mLength); }
+
+ bool operator==(const ByteBuffer& other) const {
+ return mLength == other.mLength && !(memcmp(mData, other.mData, mLength));
+ }
+
+ size_t mLength;
+ uint8_t* mData;
+ bool mOwned;
+};
+
+struct BuiltDisplayList {
+ wr::VecU8 dl;
+ wr::BuiltDisplayListDescriptor dl_desc;
+};
+
+// Corresponds to a clip id for a clip chain in webrender. Similar to
+// WrClipId but a separate struct so we don't get them mixed up in C++.
+struct WrClipChainId {
+ uint64_t id;
+
+ bool operator==(const WrClipChainId& other) const { return id == other.id; }
+
+ static WrClipChainId Empty() {
+ WrClipChainId id = {0};
+ return id;
+ }
+};
+
+WrSpaceAndClip RootScrollNode();
+WrSpaceAndClipChain RootScrollNodeWithChain();
+
+enum class WebRenderError : int8_t {
+ INITIALIZE = 0,
+ MAKE_CURRENT,
+ RENDER,
+ NEW_SURFACE,
+ VIDEO_OVERLAY,
+ EXCESSIVE_RESETS,
+
+ Sentinel /* this must be last for serialization purposes. */
+};
+
+static inline wr::WrYuvColorSpace ToWrYuvColorSpace(
+ gfx::YUVColorSpace aYUVColorSpace) {
+ switch (aYUVColorSpace) {
+ case gfx::YUVColorSpace::BT601:
+ return wr::WrYuvColorSpace::Rec601;
+ case gfx::YUVColorSpace::BT709:
+ return wr::WrYuvColorSpace::Rec709;
+ case gfx::YUVColorSpace::BT2020:
+ return wr::WrYuvColorSpace::Rec2020;
+ case gfx::YUVColorSpace::Identity:
+ return wr::WrYuvColorSpace::Identity;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Tried to convert invalid YUVColorSpace.");
+ }
+ return wr::WrYuvColorSpace::Rec601;
+}
+
+static inline wr::WrColorDepth ToWrColorDepth(gfx::ColorDepth aColorDepth) {
+ switch (aColorDepth) {
+ case gfx::ColorDepth::COLOR_8:
+ return wr::WrColorDepth::Color8;
+ case gfx::ColorDepth::COLOR_10:
+ return wr::WrColorDepth::Color10;
+ case gfx::ColorDepth::COLOR_12:
+ return wr::WrColorDepth::Color12;
+ case gfx::ColorDepth::COLOR_16:
+ return wr::WrColorDepth::Color16;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Tried to convert invalid color depth value.");
+ }
+ return wr::WrColorDepth::Color8;
+}
+
+static inline wr::WrColorRange ToWrColorRange(gfx::ColorRange aColorRange) {
+ switch (aColorRange) {
+ case gfx::ColorRange::LIMITED:
+ return wr::WrColorRange::Limited;
+ case gfx::ColorRange::FULL:
+ return wr::WrColorRange::Full;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Tried to convert invalid color range value.");
+ return wr ::WrColorRange::Limited;
+ }
+}
+
+static inline wr::SyntheticItalics DegreesToSyntheticItalics(float aDegrees) {
+ wr::SyntheticItalics synthetic_italics;
+ synthetic_italics.angle =
+ int16_t(std::min(std::max(aDegrees, -89.0f), 89.0f) * 256.0f);
+ return synthetic_italics;
+}
+
+} // namespace wr
+} // namespace mozilla
+
+namespace std {
+template <>
+struct hash<mozilla::wr::WrSpatialId> {
+ std::size_t operator()(mozilla::wr::WrSpatialId const& aKey) const noexcept {
+ return std::hash<size_t>{}(aKey.id);
+ }
+};
+} // namespace std
+
+#endif /* GFX_WEBRENDERTYPES_H */
diff --git a/gfx/webrender_bindings/cbindgen.toml b/gfx/webrender_bindings/cbindgen.toml
new file mode 100644
index 0000000000..b7f81cd238
--- /dev/null
+++ b/gfx/webrender_bindings/cbindgen.toml
@@ -0,0 +1,47 @@
+header = """/* 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/. */"""
+autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen.
+ * To generate this file:
+ * 1. Get the latest cbindgen using `cargo install --force cbindgen`
+ * a. Alternatively, you can clone `https://github.com/eqrion/cbindgen` and use a tagged release
+ * 2. Run `rustup run nightly cbindgen toolkit/library/rust/ --lockfile Cargo.lock --crate webrender_bindings -o gfx/webrender_bindings/webrender_ffi_generated.h`
+ */"""
+include_version = true
+braces = "SameLine"
+line_length = 100
+tab_width = 2
+language = "C++"
+namespaces = ["mozilla", "wr"]
+
+[export]
+item_types = ["globals", "enums", "structs", "unions", "typedefs", "opaque", "functions", "constants"]
+
+[parse]
+parse_deps = true
+include = ["log", "euclid", "webrender", "webrender_api"]
+
+[fn]
+args = "Vertical"
+rename_args = "GeckoCase"
+
+[struct]
+associated_constants_in_body = true
+derive_eq = true
+derive_ostream = true
+
+[enum]
+add_sentinel = true
+derive_helper_methods = true
+derive_ostream = true
+
+[macro_expansion]
+bitflags = true
+
+[defines]
+"target_os = windows" = "XP_WIN"
+"target_os = macos" = "XP_MACOSX"
+"target_os = android" = "ANDROID"
+
+[export.rename]
+"ThinVec" = "nsTArray"
diff --git a/gfx/webrender_bindings/moz.build b/gfx/webrender_bindings/moz.build
new file mode 100644
index 0000000000..864256c2e9
--- /dev/null
+++ b/gfx/webrender_bindings/moz.build
@@ -0,0 +1,123 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Graphics: WebRender")
+
+EXPORTS.mozilla.webrender += [
+ "RenderBufferTextureHost.h",
+ "RenderBufferTextureHostSWGL.h",
+ "RenderCompositor.h",
+ "RenderCompositorEGL.h",
+ "RenderCompositorOGL.h",
+ "RenderCompositorSWGL.h",
+ "RenderEGLImageTextureHost.h",
+ "RendererOGL.h",
+ "RendererScreenshotGrabber.h",
+ "RenderExternalTextureHost.h",
+ "RenderSharedSurfaceTextureHost.h",
+ "RenderSharedSurfaceTextureHostSWGL.h",
+ "RenderTextureHost.h",
+ "RenderTextureHostSWGL.h",
+ "RenderTextureHostWrapper.h",
+ "RenderThread.h",
+ "webrender_ffi.h",
+ "WebRenderAPI.h",
+ "WebRenderTypes.h",
+]
+
+UNIFIED_SOURCES += [
+ "Moz2DImageRenderer.cpp",
+ "RenderBufferTextureHost.cpp",
+ "RenderBufferTextureHostSWGL.cpp",
+ "RenderCompositor.cpp",
+ "RenderCompositorEGL.cpp",
+ "RenderCompositorOGL.cpp",
+ "RenderCompositorSWGL.cpp",
+ "RenderEGLImageTextureHost.cpp",
+ "RendererOGL.cpp",
+ "RendererScreenshotGrabber.cpp",
+ "RenderExternalTextureHost.cpp",
+ "RenderSharedSurfaceTextureHost.cpp",
+ "RenderSharedSurfaceTextureHostSWGL.cpp",
+ "RenderTextureHost.cpp",
+ "RenderTextureHostSWGL.cpp",
+ "RenderTextureHostWrapper.cpp",
+ "RenderThread.cpp",
+ "WebRenderAPI.cpp",
+ "WebRenderTypes.cpp",
+]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
+ EXPORTS.mozilla.webrender += [
+ "RenderCompositorNative.h",
+ "RenderMacIOSurfaceTextureHost.h",
+ ]
+ UNIFIED_SOURCES += [
+ "RenderCompositorNative.cpp",
+ "RenderMacIOSurfaceTextureHost.cpp",
+ ]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "android":
+ EXPORTS.mozilla.webrender += [
+ "RenderAndroidHardwareBufferTextureHost.h",
+ "RenderAndroidSurfaceTextureHost.h",
+ ]
+ UNIFIED_SOURCES += [
+ "RenderAndroidHardwareBufferTextureHost.cpp",
+ "RenderAndroidSurfaceTextureHost.cpp",
+ ]
+
+if CONFIG["MOZ_ENABLE_D3D10_LAYER"]:
+ DEFINES["MOZ_ENABLE_D3D10_LAYER"] = True
+ EXPORTS.mozilla.webrender += [
+ "DCLayerTree.h",
+ "RenderCompositorANGLE.h",
+ "RenderCompositorD3D11SWGL.h",
+ "RenderD3D11TextureHost.h",
+ ]
+ UNIFIED_SOURCES += [
+ "RenderCompositorD3D11SWGL.cpp",
+ "RenderD3D11TextureHost.cpp",
+ ]
+ SOURCES += [
+ "DCLayerTree.cpp",
+ "RenderCompositorANGLE.cpp",
+ ]
+
+if CONFIG["MOZ_WAYLAND"]:
+ EXPORTS.mozilla.webrender += [
+ "RenderDMABUFTextureHost.h",
+ ]
+ SOURCES += [
+ "RenderDMABUFTextureHost.cpp",
+ ]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] in ("android", "gtk"):
+ CXXFLAGS += CONFIG["MOZ_CAIRO_CFLAGS"]
+ CXXFLAGS += CONFIG["CAIRO_FT_CFLAGS"]
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ EXPORTS.mozilla.webrender += [
+ "!webrender_ffi_generated.h",
+ ]
+
+ CbindgenHeader(
+ "webrender_ffi_generated.h",
+ inputs=[
+ "/gfx/webrender_bindings",
+ "/gfx/wr/webrender",
+ "/gfx/wr/webrender_api",
+ ],
+ )
+
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+CXXFLAGS += CONFIG["TK_CFLAGS"]
+CXXFLAGS += CONFIG["MOZ_CAIRO_CFLAGS"]
diff --git a/gfx/webrender_bindings/rustfmt.toml b/gfx/webrender_bindings/rustfmt.toml
new file mode 100644
index 0000000000..1d15550e6c
--- /dev/null
+++ b/gfx/webrender_bindings/rustfmt.toml
@@ -0,0 +1,16 @@
+ideal_width = 80
+max_width = 120
+
+newline_style = "Unix"
+normalize_comments = false
+
+force_explicit_abi = true
+fn_args_density = "Vertical"
+
+chain_indent = "Visual"
+chain_one_line_max = 90
+
+struct_lit_multiline_style = "ForceMulti"
+
+match_block_trailing_comma = true
+where_trailing_comma = true
diff --git a/gfx/webrender_bindings/src/bindings.rs b/gfx/webrender_bindings/src/bindings.rs
new file mode 100644
index 0000000000..ef78776a40
--- /dev/null
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -0,0 +1,4011 @@
+/* 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 gleam::gl;
+use std::cell::RefCell;
+#[cfg(not(target_os = "macos"))]
+use std::ffi::OsString;
+use std::ffi::{CStr, CString};
+use std::io::Cursor;
+use std::marker::PhantomData;
+use std::ops::Range;
+#[cfg(target_os = "android")]
+use std::os::raw::c_int;
+use std::os::raw::{c_char, c_float, c_void};
+#[cfg(not(any(target_os = "macos", target_os = "windows")))]
+use std::os::unix::ffi::OsStringExt;
+#[cfg(target_os = "windows")]
+use std::os::windows::ffi::OsStringExt;
+use std::path::PathBuf;
+use std::rc::Rc;
+use std::sync::atomic::{AtomicUsize, Ordering};
+use std::sync::Arc;
+use std::time::Duration;
+use std::{env, mem, ptr, slice};
+use thin_vec::ThinVec;
+
+use euclid::SideOffsets2D;
+use moz2d_renderer::Moz2dBlobImageHandler;
+use nsstring::nsAString;
+use num_cpus;
+use program_cache::{remove_disk_cache, WrProgramCache};
+use rayon;
+use swgl_bindings::SwCompositor;
+use tracy_rs::register_thread_with_profiler;
+use webrender::{
+ api::units::*, api::*, render_api::*, set_profiler_hooks, AsyncPropertySampler, AsyncScreenshotHandle, Compositor,
+ CompositorCapabilities, CompositorConfig, CompositorSurfaceTransform, DebugFlags, Device, NativeSurfaceId,
+ NativeSurfaceInfo, NativeTileId, PartialPresentCompositor, PipelineInfo, ProfilerHooks, RecordedFrameHandle,
+ Renderer, RendererOptions, RendererStats, SceneBuilderHooks, ShaderPrecacheFlags, Shaders, SharedShaders,
+ TextureCacheConfig, ThreadListener, UploadMethod, ONE_TIME_USAGE_HINT,
+};
+use wr_malloc_size_of::MallocSizeOfOps;
+
+#[cfg(target_os = "macos")]
+use core_foundation::string::CFString;
+#[cfg(target_os = "macos")]
+use core_graphics::font::CGFont;
+
+extern "C" {
+ #[cfg(target_os = "android")]
+ fn __android_log_write(prio: c_int, tag: *const c_char, text: *const c_char) -> c_int;
+}
+
+/// The unique id for WR resource identification.
+static NEXT_NAMESPACE_ID: AtomicUsize = AtomicUsize::new(1);
+
+/// Special value handled in this wrapper layer to signify a redundant clip chain.
+pub const ROOT_CLIP_CHAIN: u64 = !0;
+
+fn next_namespace_id() -> IdNamespace {
+ IdNamespace(NEXT_NAMESPACE_ID.fetch_add(1, Ordering::Relaxed) as u32)
+}
+
+/// Whether a border should be antialiased.
+#[repr(C)]
+#[derive(Eq, PartialEq, Copy, Clone)]
+pub enum AntialiasBorder {
+ No = 0,
+ Yes,
+}
+
+/// Used to indicate if an image is opaque, or has an alpha channel.
+#[repr(u8)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum OpacityType {
+ Opaque = 0,
+ HasAlphaChannel = 1,
+}
+
+/// cbindgen:field-names=[mHandle]
+/// cbindgen:derive-lt=true
+/// cbindgen:derive-lte=true
+/// cbindgen:derive-neq=true
+type WrEpoch = Epoch;
+/// cbindgen:field-names=[mHandle]
+/// cbindgen:derive-lt=true
+/// cbindgen:derive-lte=true
+/// cbindgen:derive-neq=true
+pub type WrIdNamespace = IdNamespace;
+
+/// cbindgen:field-names=[mNamespace, mHandle]
+type WrDocumentId = DocumentId;
+/// cbindgen:field-names=[mNamespace, mHandle]
+type WrPipelineId = PipelineId;
+/// cbindgen:field-names=[mNamespace, mHandle]
+/// cbindgen:derive-neq=true
+type WrImageKey = ImageKey;
+/// cbindgen:field-names=[mNamespace, mHandle]
+pub type WrFontKey = FontKey;
+/// cbindgen:field-names=[mNamespace, mHandle]
+pub type WrFontInstanceKey = FontInstanceKey;
+/// cbindgen:field-names=[mNamespace, mHandle]
+type WrYuvColorSpace = YuvColorSpace;
+/// cbindgen:field-names=[mNamespace, mHandle]
+type WrColorDepth = ColorDepth;
+/// cbindgen:field-names=[mNamespace, mHandle]
+type WrColorRange = ColorRange;
+
+#[repr(C)]
+pub struct WrSpaceAndClip {
+ space: WrSpatialId,
+ clip: WrClipId,
+}
+
+impl WrSpaceAndClip {
+ fn from_webrender(sac: SpaceAndClipInfo) -> Self {
+ WrSpaceAndClip {
+ space: WrSpatialId { id: sac.spatial_id.0 },
+ clip: WrClipId::from_webrender(sac.clip_id),
+ }
+ }
+
+ fn to_webrender(&self, pipeline_id: WrPipelineId) -> SpaceAndClipInfo {
+ SpaceAndClipInfo {
+ spatial_id: self.space.to_webrender(pipeline_id),
+ clip_id: self.clip.to_webrender(pipeline_id),
+ }
+ }
+}
+
+#[inline]
+fn clip_chain_id_to_webrender(id: u64, pipeline_id: WrPipelineId) -> ClipId {
+ if id == ROOT_CLIP_CHAIN {
+ ClipId::root(pipeline_id)
+ } else {
+ ClipId::ClipChain(ClipChainId(id, pipeline_id))
+ }
+}
+
+#[repr(C)]
+pub struct WrSpaceAndClipChain {
+ space: WrSpatialId,
+ clip_chain: u64,
+}
+
+impl WrSpaceAndClipChain {
+ fn to_webrender(&self, pipeline_id: WrPipelineId) -> SpaceAndClipInfo {
+ //Warning: special case here to support dummy clip chain
+ SpaceAndClipInfo {
+ spatial_id: self.space.to_webrender(pipeline_id),
+ clip_id: clip_chain_id_to_webrender(self.clip_chain, pipeline_id),
+ }
+ }
+}
+
+#[repr(C)]
+pub enum WrStackingContextClip {
+ None,
+ ClipId(WrClipId),
+ ClipChain(u64),
+}
+
+impl WrStackingContextClip {
+ fn to_webrender(&self, pipeline_id: WrPipelineId) -> Option<ClipId> {
+ match *self {
+ WrStackingContextClip::None => None,
+ WrStackingContextClip::ClipChain(id) => Some(clip_chain_id_to_webrender(id, pipeline_id)),
+ WrStackingContextClip::ClipId(id) => Some(id.to_webrender(pipeline_id)),
+ }
+ }
+}
+
+unsafe fn make_slice<'a, T>(ptr: *const T, len: usize) -> &'a [T] {
+ if ptr.is_null() {
+ &[]
+ } else {
+ slice::from_raw_parts(ptr, len)
+ }
+}
+
+unsafe fn make_slice_mut<'a, T>(ptr: *mut T, len: usize) -> &'a mut [T] {
+ if ptr.is_null() {
+ &mut []
+ } else {
+ slice::from_raw_parts_mut(ptr, len)
+ }
+}
+
+pub struct DocumentHandle {
+ api: RenderApi,
+ document_id: DocumentId,
+ // One of the two options below is Some and the other None at all times.
+ // It would be nice to model with an enum, however it is tricky to express moving
+ // a variant's content into another variant without movign the containing enum.
+ hit_tester_request: Option<HitTesterRequest>,
+ hit_tester: Option<Arc<dyn ApiHitTester>>,
+}
+
+impl DocumentHandle {
+ pub fn new(
+ api: RenderApi,
+ hit_tester: Option<Arc<dyn ApiHitTester>>,
+ size: DeviceIntSize,
+ id: u32,
+ ) -> DocumentHandle {
+ let doc = api.add_document_with_id(size, id);
+ let hit_tester_request = if hit_tester.is_none() {
+ // Request the hit tester early to reduce the likelihood of blocking on the
+ // first hit testing query.
+ Some(api.request_hit_tester(doc))
+ } else {
+ None
+ };
+
+ DocumentHandle {
+ api,
+ document_id: doc,
+ hit_tester_request,
+ hit_tester,
+ }
+ }
+
+ fn ensure_hit_tester(&mut self) {
+ if self.hit_tester.is_none() {
+ self.hit_tester = Some(self.hit_tester_request.take().unwrap().resolve());
+ }
+ }
+}
+
+#[repr(C)]
+pub struct WrVecU8 {
+ data: *mut u8,
+ length: usize,
+ capacity: usize,
+}
+
+impl WrVecU8 {
+ fn to_vec(self) -> Vec<u8> {
+ unsafe { Vec::from_raw_parts(self.data, self.length, self.capacity) }
+ }
+
+ // Equivalent to `to_vec` but clears self instead of consuming the value.
+ fn flush_into_vec(&mut self) -> Vec<u8> {
+ self.convert_into_vec::<u8>()
+ }
+
+ // Like flush_into_vec, but also does an unsafe conversion to the desired type.
+ fn convert_into_vec<T>(&mut self) -> Vec<T> {
+ let vec = unsafe {
+ Vec::from_raw_parts(
+ self.data as *mut T,
+ self.length / mem::size_of::<T>(),
+ self.capacity / mem::size_of::<T>(),
+ )
+ };
+ self.data = ptr::null_mut();
+ self.length = 0;
+ self.capacity = 0;
+ vec
+ }
+
+ fn from_vec(mut v: Vec<u8>) -> WrVecU8 {
+ let w = WrVecU8 {
+ data: v.as_mut_ptr(),
+ length: v.len(),
+ capacity: v.capacity(),
+ };
+ mem::forget(v);
+ w
+ }
+
+ fn reserve(&mut self, len: usize) {
+ let mut vec = self.flush_into_vec();
+ vec.reserve(len);
+ *self = Self::from_vec(vec);
+ }
+
+ fn push_bytes(&mut self, bytes: &[u8]) {
+ let mut vec = self.flush_into_vec();
+ vec.extend_from_slice(bytes);
+ *self = Self::from_vec(vec);
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn wr_vec_u8_push_bytes(v: &mut WrVecU8, bytes: ByteSlice) {
+ v.push_bytes(bytes.as_slice());
+}
+
+#[no_mangle]
+pub extern "C" fn wr_vec_u8_reserve(v: &mut WrVecU8, len: usize) {
+ v.reserve(len);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_vec_u8_free(v: WrVecU8) {
+ v.to_vec();
+}
+
+#[repr(C)]
+pub struct ByteSlice<'a> {
+ buffer: *const u8,
+ len: usize,
+ _phantom: PhantomData<&'a ()>,
+}
+
+impl<'a> ByteSlice<'a> {
+ pub fn new(slice: &'a [u8]) -> ByteSlice<'a> {
+ ByteSlice {
+ buffer: slice.as_ptr(),
+ len: slice.len(),
+ _phantom: PhantomData,
+ }
+ }
+
+ pub fn as_slice(&self) -> &'a [u8] {
+ unsafe { make_slice(self.buffer, self.len) }
+ }
+}
+
+#[repr(C)]
+pub struct MutByteSlice<'a> {
+ buffer: *mut u8,
+ len: usize,
+ _phantom: PhantomData<&'a ()>,
+}
+
+impl<'a> MutByteSlice<'a> {
+ pub fn new(slice: &'a mut [u8]) -> MutByteSlice<'a> {
+ let len = slice.len();
+ MutByteSlice {
+ buffer: slice.as_mut_ptr(),
+ len,
+ _phantom: PhantomData,
+ }
+ }
+
+ pub fn as_mut_slice(&mut self) -> &'a mut [u8] {
+ unsafe { make_slice_mut(self.buffer, self.len) }
+ }
+}
+
+#[repr(C)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub struct WrImageDescriptor {
+ pub format: ImageFormat,
+ pub width: i32,
+ pub height: i32,
+ pub stride: i32,
+ pub opacity: OpacityType,
+ // TODO(gw): Remove this flag (use prim flags instead).
+ pub prefer_compositor_surface: bool,
+}
+
+impl<'a> Into<ImageDescriptor> for &'a WrImageDescriptor {
+ fn into(self) -> ImageDescriptor {
+ let mut flags = ImageDescriptorFlags::empty();
+
+ if self.opacity == OpacityType::Opaque {
+ flags |= ImageDescriptorFlags::IS_OPAQUE;
+ }
+
+ ImageDescriptor {
+ size: DeviceIntSize::new(self.width, self.height),
+ stride: if self.stride != 0 { Some(self.stride) } else { None },
+ format: self.format,
+ offset: 0,
+ flags,
+ }
+ }
+}
+
+#[repr(u32)]
+#[allow(dead_code)]
+enum WrExternalImageType {
+ RawData,
+ NativeTexture,
+ Invalid,
+}
+
+#[repr(C)]
+struct WrExternalImage {
+ image_type: WrExternalImageType,
+
+ // external texture handle
+ handle: u32,
+ // external texture coordinate
+ u0: f32,
+ v0: f32,
+ u1: f32,
+ v1: f32,
+
+ // external image buffer
+ buff: *const u8,
+ size: usize,
+}
+
+extern "C" {
+ fn wr_renderer_lock_external_image(
+ renderer: *mut c_void,
+ external_image_id: ExternalImageId,
+ channel_index: u8,
+ rendering: ImageRendering,
+ ) -> WrExternalImage;
+ fn wr_renderer_unlock_external_image(renderer: *mut c_void, external_image_id: ExternalImageId, channel_index: u8);
+}
+
+#[repr(C)]
+#[derive(Copy, Clone, Debug)]
+pub struct WrExternalImageHandler {
+ external_image_obj: *mut c_void,
+}
+
+impl ExternalImageHandler for WrExternalImageHandler {
+ fn lock(&mut self, id: ExternalImageId, channel_index: u8, rendering: ImageRendering) -> ExternalImage {
+ let image = unsafe { wr_renderer_lock_external_image(self.external_image_obj, id, channel_index, rendering) };
+ ExternalImage {
+ uv: TexelRect::new(image.u0, image.v0, image.u1, image.v1),
+ source: match image.image_type {
+ WrExternalImageType::NativeTexture => ExternalImageSource::NativeTexture(image.handle),
+ WrExternalImageType::RawData => {
+ ExternalImageSource::RawData(unsafe { make_slice(image.buff, image.size) })
+ }
+ WrExternalImageType::Invalid => ExternalImageSource::Invalid,
+ },
+ }
+ }
+
+ fn unlock(&mut self, id: ExternalImageId, channel_index: u8) {
+ unsafe {
+ wr_renderer_unlock_external_image(self.external_image_obj, id, channel_index);
+ }
+ }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+// Used for ComponentTransfer only
+pub struct WrFilterData {
+ funcR_type: ComponentTransferFuncType,
+ R_values: *mut c_float,
+ R_values_count: usize,
+ funcG_type: ComponentTransferFuncType,
+ G_values: *mut c_float,
+ G_values_count: usize,
+ funcB_type: ComponentTransferFuncType,
+ B_values: *mut c_float,
+ B_values_count: usize,
+ funcA_type: ComponentTransferFuncType,
+ A_values: *mut c_float,
+ A_values_count: usize,
+}
+
+#[repr(u32)]
+#[derive(Debug)]
+pub enum WrAnimationType {
+ Transform = 0,
+ Opacity = 1,
+ BackgroundColor = 2,
+}
+
+#[repr(C)]
+pub struct WrAnimationProperty {
+ effect_type: WrAnimationType,
+ id: u64,
+}
+
+/// cbindgen:derive-eq=false
+#[repr(C)]
+#[derive(Debug)]
+pub struct WrAnimationPropertyValue<T> {
+ pub id: u64,
+ pub value: T,
+}
+
+pub type WrTransformProperty = WrAnimationPropertyValue<LayoutTransform>;
+pub type WrOpacityProperty = WrAnimationPropertyValue<f32>;
+pub type WrColorProperty = WrAnimationPropertyValue<ColorF>;
+
+/// cbindgen:field-names=[mHandle]
+/// cbindgen:derive-lt=true
+/// cbindgen:derive-lte=true
+#[repr(C)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub struct WrWindowId(u64);
+
+#[repr(C)]
+#[derive(Debug)]
+pub struct WrComputedTransformData {
+ pub scale_from: LayoutSize,
+ pub vertical_flip: bool,
+ pub rotation: WrRotation,
+}
+
+fn get_proc_address(glcontext_ptr: *mut c_void, name: &str) -> *const c_void {
+ extern "C" {
+ fn get_proc_address_from_glcontext(glcontext_ptr: *mut c_void, procname: *const c_char) -> *const c_void;
+ }
+
+ let symbol_name = CString::new(name).unwrap();
+ let symbol = unsafe { get_proc_address_from_glcontext(glcontext_ptr, symbol_name.as_ptr()) };
+
+ if symbol.is_null() {
+ // XXX Bug 1322949 Make whitelist for extensions
+ warn!("Could not find symbol {:?} by glcontext", symbol_name);
+ }
+
+ symbol as *const _
+}
+
+#[repr(C)]
+pub enum TelemetryProbe {
+ SceneBuildTime = 0,
+ SceneSwapTime = 1,
+ FrameBuildTime = 2,
+}
+
+extern "C" {
+ fn is_in_compositor_thread() -> bool;
+ fn is_in_render_thread() -> bool;
+ fn is_in_main_thread() -> bool;
+ fn is_glcontext_gles(glcontext_ptr: *mut c_void) -> bool;
+ fn is_glcontext_angle(glcontext_ptr: *mut c_void) -> bool;
+ fn gfx_wr_resource_path_override() -> *const c_char;
+ fn gfx_wr_use_optimized_shaders() -> bool;
+ // TODO: make gfx_critical_error() work.
+ // We still have problem to pass the error message from render/render_backend
+ // thread to main thread now.
+ #[allow(dead_code)]
+ fn gfx_critical_error(msg: *const c_char);
+ fn gfx_critical_note(msg: *const c_char);
+ fn record_telemetry_time(probe: TelemetryProbe, time_ns: u64);
+ fn gfx_wr_set_crash_annotation(annotation: CrashAnnotation, value: *const c_char);
+ fn gfx_wr_clear_crash_annotation(annotation: CrashAnnotation);
+}
+
+struct CppNotifier {
+ window_id: WrWindowId,
+}
+
+unsafe impl Send for CppNotifier {}
+
+extern "C" {
+ fn wr_notifier_wake_up(window_id: WrWindowId, composite_needed: bool);
+ fn wr_notifier_new_frame_ready(window_id: WrWindowId);
+ fn wr_notifier_nop_frame_done(window_id: WrWindowId);
+ fn wr_notifier_external_event(window_id: WrWindowId, raw_event: usize);
+ fn wr_schedule_render(window_id: WrWindowId);
+ // NOTE: This moves away from pipeline_info.
+ fn wr_finished_scene_build(window_id: WrWindowId, pipeline_info: &mut WrPipelineInfo);
+
+ fn wr_transaction_notification_notified(handler: usize, when: Checkpoint);
+}
+
+impl RenderNotifier for CppNotifier {
+ fn clone(&self) -> Box<dyn RenderNotifier> {
+ Box::new(CppNotifier {
+ window_id: self.window_id,
+ })
+ }
+
+ fn wake_up(&self, composite_needed: bool) {
+ unsafe {
+ wr_notifier_wake_up(self.window_id, composite_needed);
+ }
+ }
+
+ fn new_frame_ready(&self, _: DocumentId, _scrolled: bool, composite_needed: bool, render_time_ns: Option<u64>) {
+ unsafe {
+ if let Some(time) = render_time_ns {
+ record_telemetry_time(TelemetryProbe::FrameBuildTime, time);
+ }
+ if composite_needed {
+ wr_notifier_new_frame_ready(self.window_id);
+ } else {
+ wr_notifier_nop_frame_done(self.window_id);
+ }
+ }
+ }
+
+ fn external_event(&self, event: ExternalEvent) {
+ unsafe {
+ wr_notifier_external_event(self.window_id, event.unwrap());
+ }
+ }
+}
+
+struct MozCrashAnnotator;
+
+unsafe impl Send for MozCrashAnnotator {}
+
+impl CrashAnnotator for MozCrashAnnotator {
+ fn set(&self, annotation: CrashAnnotation, value: &str) {
+ let value = CString::new(value).unwrap();
+ unsafe {
+ gfx_wr_set_crash_annotation(annotation, value.as_ptr());
+ }
+ }
+
+ fn clear(&self, annotation: CrashAnnotation) {
+ unsafe {
+ gfx_wr_clear_crash_annotation(annotation);
+ }
+ }
+
+ fn box_clone(&self) -> Box<dyn CrashAnnotator> {
+ Box::new(MozCrashAnnotator)
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn wr_renderer_set_clear_color(renderer: &mut Renderer, color: ColorF) {
+ renderer.set_clear_color(Some(color));
+}
+
+#[no_mangle]
+pub extern "C" fn wr_renderer_set_external_image_handler(
+ renderer: &mut Renderer,
+ external_image_handler: &mut WrExternalImageHandler,
+) {
+ renderer.set_external_image_handler(Box::new(*external_image_handler));
+}
+
+#[no_mangle]
+pub extern "C" fn wr_renderer_update(renderer: &mut Renderer) {
+ renderer.update();
+}
+
+#[no_mangle]
+pub extern "C" fn wr_renderer_render(
+ renderer: &mut Renderer,
+ width: i32,
+ height: i32,
+ buffer_age: usize,
+ out_stats: &mut RendererStats,
+ out_dirty_rects: &mut ThinVec<DeviceIntRect>,
+) -> bool {
+ match renderer.render(DeviceIntSize::new(width, height), buffer_age) {
+ Ok(results) => {
+ *out_stats = results.stats;
+ out_dirty_rects.extend(results.dirty_rects);
+ true
+ }
+ Err(errors) => {
+ for e in errors {
+ warn!(" Failed to render: {:?}", e);
+ let msg = CString::new(format!("wr_renderer_render: {:?}", e)).unwrap();
+ unsafe {
+ gfx_critical_note(msg.as_ptr());
+ }
+ }
+ false
+ }
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn wr_renderer_force_redraw(renderer: &mut Renderer) {
+ renderer.force_redraw();
+}
+
+#[no_mangle]
+pub extern "C" fn wr_renderer_record_frame(
+ renderer: &mut Renderer,
+ image_format: ImageFormat,
+ out_handle: &mut RecordedFrameHandle,
+ out_width: &mut i32,
+ out_height: &mut i32,
+) -> bool {
+ if let Some((handle, size)) = renderer.record_frame(image_format) {
+ *out_handle = handle;
+ *out_width = size.width;
+ *out_height = size.height;
+
+ true
+ } else {
+ false
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn wr_renderer_map_recorded_frame(
+ renderer: &mut Renderer,
+ handle: RecordedFrameHandle,
+ dst_buffer: *mut u8,
+ dst_buffer_len: usize,
+ dst_stride: usize,
+) -> bool {
+ renderer.map_recorded_frame(
+ handle,
+ unsafe { make_slice_mut(dst_buffer, dst_buffer_len) },
+ dst_stride,
+ )
+}
+
+#[no_mangle]
+pub extern "C" fn wr_renderer_release_composition_recorder_structures(renderer: &mut Renderer) {
+ renderer.release_composition_recorder_structures();
+}
+
+#[no_mangle]
+pub extern "C" fn wr_renderer_get_screenshot_async(
+ renderer: &mut Renderer,
+ window_x: i32,
+ window_y: i32,
+ window_width: i32,
+ window_height: i32,
+ buffer_width: i32,
+ buffer_height: i32,
+ image_format: ImageFormat,
+ screenshot_width: *mut i32,
+ screenshot_height: *mut i32,
+) -> AsyncScreenshotHandle {
+ assert!(!screenshot_width.is_null());
+ assert!(!screenshot_height.is_null());
+
+ let (handle, size) = renderer.get_screenshot_async(
+ DeviceIntRect::new(
+ DeviceIntPoint::new(window_x, window_y),
+ DeviceIntSize::new(window_width, window_height),
+ ),
+ DeviceIntSize::new(buffer_width, buffer_height),
+ image_format,
+ );
+
+ unsafe {
+ *screenshot_width = size.width;
+ *screenshot_height = size.height;
+ }
+
+ handle
+}
+
+#[no_mangle]
+pub extern "C" fn wr_renderer_map_and_recycle_screenshot(
+ renderer: &mut Renderer,
+ handle: AsyncScreenshotHandle,
+ dst_buffer: *mut u8,
+ dst_buffer_len: usize,
+ dst_stride: usize,
+) -> bool {
+ renderer.map_and_recycle_screenshot(
+ handle,
+ unsafe { make_slice_mut(dst_buffer, dst_buffer_len) },
+ dst_stride,
+ )
+}
+
+#[no_mangle]
+pub extern "C" fn wr_renderer_release_profiler_structures(renderer: &mut Renderer) {
+ renderer.release_profiler_structures();
+}
+
+// Call wr_renderer_render() before calling this function.
+#[no_mangle]
+pub unsafe extern "C" fn wr_renderer_readback(
+ renderer: &mut Renderer,
+ width: i32,
+ height: i32,
+ format: ImageFormat,
+ dst_buffer: *mut u8,
+ buffer_size: usize,
+) {
+ assert!(is_in_render_thread());
+
+ let mut slice = make_slice_mut(dst_buffer, buffer_size);
+ renderer.read_pixels_into(FramebufferIntSize::new(width, height).into(), format, &mut slice);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn wr_renderer_set_profiler_ui(renderer: &mut Renderer, ui_str: *const u8, ui_str_len: usize) {
+ let slice = std::slice::from_raw_parts(ui_str, ui_str_len);
+ if let Ok(ui_str) = std::str::from_utf8(slice) {
+ renderer.set_profiler_ui(ui_str);
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn wr_renderer_delete(renderer: *mut Renderer) {
+ let renderer = Box::from_raw(renderer);
+ renderer.deinit();
+ // let renderer go out of scope and get dropped
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn wr_renderer_accumulate_memory_report(renderer: &mut Renderer, report: &mut MemoryReport) {
+ *report += renderer.report_memory();
+}
+
+// cbindgen doesn't support tuples, so we have a little struct instead, with
+// an Into implementation to convert from the tuple to the struct.
+#[repr(C)]
+pub struct WrPipelineEpoch {
+ pipeline_id: WrPipelineId,
+ document_id: WrDocumentId,
+ epoch: WrEpoch,
+}
+
+impl<'a> From<(&'a (WrPipelineId, WrDocumentId), &'a WrEpoch)> for WrPipelineEpoch {
+ fn from(tuple: (&(WrPipelineId, WrDocumentId), &WrEpoch)) -> WrPipelineEpoch {
+ WrPipelineEpoch {
+ pipeline_id: (tuple.0).0,
+ document_id: (tuple.0).1,
+ epoch: *tuple.1,
+ }
+ }
+}
+
+#[repr(C)]
+pub struct WrPipelineIdAndEpoch {
+ pipeline_id: WrPipelineId,
+ epoch: WrEpoch,
+}
+
+impl<'a> From<(&WrPipelineId, &WrEpoch)> for WrPipelineIdAndEpoch {
+ fn from(tuple: (&WrPipelineId, &WrEpoch)) -> WrPipelineIdAndEpoch {
+ WrPipelineIdAndEpoch {
+ pipeline_id: *tuple.0,
+ epoch: *tuple.1,
+ }
+ }
+}
+
+#[repr(C)]
+pub struct WrRemovedPipeline {
+ pipeline_id: WrPipelineId,
+ document_id: WrDocumentId,
+}
+
+impl<'a> From<&'a (WrPipelineId, WrDocumentId)> for WrRemovedPipeline {
+ fn from(tuple: &(WrPipelineId, WrDocumentId)) -> WrRemovedPipeline {
+ WrRemovedPipeline {
+ pipeline_id: tuple.0,
+ document_id: tuple.1,
+ }
+ }
+}
+
+#[repr(C)]
+pub struct WrPipelineInfo {
+ /// This contains an entry for each pipeline that was rendered, along with
+ /// the epoch at which it was rendered. Rendered pipelines include the root
+ /// pipeline and any other pipelines that were reachable via IFrame display
+ /// items from the root pipeline.
+ epochs: ThinVec<WrPipelineEpoch>,
+ /// This contains an entry for each pipeline that was removed during the
+ /// last transaction. These pipelines would have been explicitly removed by
+ /// calling remove_pipeline on the transaction object; the pipeline showing
+ /// up in this array means that the data structures have been torn down on
+ /// the webrender side, and so any remaining data structures on the caller
+ /// side can now be torn down also.
+ removed_pipelines: ThinVec<WrRemovedPipeline>,
+}
+
+impl WrPipelineInfo {
+ fn new(info: &PipelineInfo) -> Self {
+ WrPipelineInfo {
+ epochs: info.epochs.iter().map(WrPipelineEpoch::from).collect(),
+ removed_pipelines: info.removed_pipelines.iter().map(WrRemovedPipeline::from).collect(),
+ }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn wr_renderer_flush_pipeline_info(renderer: &mut Renderer, out: &mut WrPipelineInfo) {
+ let info = renderer.flush_pipeline_info();
+ *out = WrPipelineInfo::new(&info);
+}
+
+extern "C" {
+ pub fn gecko_profiler_start_marker(name: *const c_char);
+ pub fn gecko_profiler_end_marker(name: *const c_char);
+ pub fn gecko_profiler_event_marker(name: *const c_char);
+ pub fn gecko_profiler_add_text_marker(
+ name: *const c_char,
+ text_bytes: *const c_char,
+ text_len: usize,
+ microseconds: u64,
+ );
+ pub fn gecko_profiler_thread_is_being_profiled() -> bool;
+}
+
+/// Simple implementation of the WR ProfilerHooks trait to allow profile
+/// markers to be seen in the Gecko profiler.
+struct GeckoProfilerHooks;
+
+impl ProfilerHooks for GeckoProfilerHooks {
+ fn begin_marker(&self, label: &CStr) {
+ unsafe {
+ gecko_profiler_start_marker(label.as_ptr());
+ }
+ }
+
+ fn end_marker(&self, label: &CStr) {
+ unsafe {
+ gecko_profiler_end_marker(label.as_ptr());
+ }
+ }
+
+ fn event_marker(&self, label: &CStr) {
+ unsafe {
+ gecko_profiler_event_marker(label.as_ptr());
+ }
+ }
+
+ fn add_text_marker(&self, label: &CStr, text: &str, duration: Duration) {
+ unsafe {
+ // NB: This can be as_micros() once we require Rust 1.33.
+ let micros = duration.subsec_micros() as u64 + duration.as_secs() * 1000 * 1000;
+ let text_bytes = text.as_bytes();
+ gecko_profiler_add_text_marker(
+ label.as_ptr(),
+ text_bytes.as_ptr() as *const c_char,
+ text_bytes.len(),
+ micros,
+ );
+ }
+ }
+
+ fn thread_is_being_profiled(&self) -> bool {
+ unsafe { gecko_profiler_thread_is_being_profiled() }
+ }
+}
+
+static PROFILER_HOOKS: GeckoProfilerHooks = GeckoProfilerHooks {};
+
+#[allow(improper_ctypes)] // this is needed so that rustc doesn't complain about passing the &mut Transaction to an extern function
+extern "C" {
+ // These callbacks are invoked from the scene builder thread (aka the APZ
+ // updater thread)
+ fn apz_register_updater(window_id: WrWindowId);
+ fn apz_pre_scene_swap(window_id: WrWindowId);
+ fn apz_post_scene_swap(window_id: WrWindowId, pipeline_info: &WrPipelineInfo);
+ fn apz_run_updater(window_id: WrWindowId);
+ fn apz_deregister_updater(window_id: WrWindowId);
+
+ // These callbacks are invoked from the render backend thread (aka the APZ
+ // sampler thread)
+ fn apz_register_sampler(window_id: WrWindowId);
+ fn apz_sample_transforms(window_id: WrWindowId, generated_frame_id: *const u64, transaction: &mut Transaction);
+ fn apz_deregister_sampler(window_id: WrWindowId);
+
+ fn omta_register_sampler(window_id: WrWindowId);
+ fn omta_sample(window_id: WrWindowId, transaction: &mut Transaction);
+ fn omta_deregister_sampler(window_id: WrWindowId);
+}
+
+struct APZCallbacks {
+ window_id: WrWindowId,
+}
+
+impl APZCallbacks {
+ pub fn new(window_id: WrWindowId) -> Self {
+ APZCallbacks { window_id }
+ }
+}
+
+impl SceneBuilderHooks for APZCallbacks {
+ fn register(&self) {
+ unsafe { apz_register_updater(self.window_id) }
+ }
+
+ fn pre_scene_build(&self) {
+ unsafe {
+ gecko_profiler_start_marker(b"SceneBuilding\0".as_ptr() as *const c_char);
+ }
+ }
+
+ fn pre_scene_swap(&self, scenebuild_time: u64) {
+ unsafe {
+ record_telemetry_time(TelemetryProbe::SceneBuildTime, scenebuild_time);
+ apz_pre_scene_swap(self.window_id);
+ }
+ }
+
+ fn post_scene_swap(&self, _document_ids: &Vec<DocumentId>, info: PipelineInfo, sceneswap_time: u64) {
+ let mut info = WrPipelineInfo::new(&info);
+ unsafe {
+ record_telemetry_time(TelemetryProbe::SceneSwapTime, sceneswap_time);
+ apz_post_scene_swap(self.window_id, &info);
+ }
+
+ // After a scene swap we should schedule a render for the next vsync,
+ // otherwise there's no guarantee that the new scene will get rendered
+ // anytime soon
+ unsafe { wr_finished_scene_build(self.window_id, &mut info) }
+ unsafe {
+ gecko_profiler_end_marker(b"SceneBuilding\0".as_ptr() as *const c_char);
+ }
+ }
+
+ fn post_resource_update(&self, _document_ids: &Vec<DocumentId>) {
+ unsafe { wr_schedule_render(self.window_id) }
+ unsafe {
+ gecko_profiler_end_marker(b"SceneBuilding\0".as_ptr() as *const c_char);
+ }
+ }
+
+ fn post_empty_scene_build(&self) {
+ unsafe {
+ gecko_profiler_end_marker(b"SceneBuilding\0".as_ptr() as *const c_char);
+ }
+ }
+
+ fn poke(&self) {
+ unsafe { apz_run_updater(self.window_id) }
+ }
+
+ fn deregister(&self) {
+ unsafe { apz_deregister_updater(self.window_id) }
+ }
+}
+
+struct SamplerCallback {
+ window_id: WrWindowId,
+}
+
+impl SamplerCallback {
+ pub fn new(window_id: WrWindowId) -> Self {
+ SamplerCallback { window_id }
+ }
+}
+
+impl AsyncPropertySampler for SamplerCallback {
+ fn register(&self) {
+ unsafe {
+ apz_register_sampler(self.window_id);
+ omta_register_sampler(self.window_id);
+ }
+ }
+
+ fn sample(&self, _document_id: DocumentId, generated_frame_id: Option<u64>) -> Vec<FrameMsg> {
+ let generated_frame_id_value;
+ let generated_frame_id: *const u64 = match generated_frame_id {
+ Some(id) => {
+ generated_frame_id_value = id;
+ &generated_frame_id_value
+ }
+ None => ptr::null_mut(),
+ };
+ let mut transaction = Transaction::new();
+ unsafe {
+ // XXX: When we implement scroll-linked animations, we will probably
+ // need to call apz_sample_transforms prior to omta_sample.
+ omta_sample(self.window_id, &mut transaction);
+ apz_sample_transforms(self.window_id, generated_frame_id, &mut transaction)
+ };
+ transaction.get_frame_ops()
+ }
+
+ fn deregister(&self) {
+ unsafe {
+ apz_deregister_sampler(self.window_id);
+ omta_deregister_sampler(self.window_id);
+ }
+ }
+}
+
+extern "C" {
+ fn gecko_profiler_register_thread(name: *const ::std::os::raw::c_char);
+ fn gecko_profiler_unregister_thread();
+ fn wr_register_thread_local_arena();
+}
+
+pub struct GeckoProfilerThreadListener {}
+
+impl GeckoProfilerThreadListener {
+ pub fn new() -> GeckoProfilerThreadListener {
+ GeckoProfilerThreadListener {}
+ }
+}
+
+impl ThreadListener for GeckoProfilerThreadListener {
+ fn thread_started(&self, thread_name: &str) {
+ let name = CString::new(thread_name).unwrap();
+ unsafe {
+ // gecko_profiler_register_thread copies the passed name here.
+ gecko_profiler_register_thread(name.as_ptr());
+ }
+ }
+
+ fn thread_stopped(&self, _: &str) {
+ unsafe {
+ gecko_profiler_unregister_thread();
+ }
+ }
+}
+
+pub struct WrThreadPool(Arc<rayon::ThreadPool>);
+
+#[no_mangle]
+pub extern "C" fn wr_thread_pool_new(low_priority: bool) -> *mut WrThreadPool {
+ // Clamp the number of workers between 1 and 8. We get diminishing returns
+ // with high worker counts and extra overhead because of rayon and font
+ // management.
+ let num_threads = num_cpus::get().max(2).min(8);
+
+ let priority_tag = if low_priority { "LP" } else { "" };
+
+ let worker = rayon::ThreadPoolBuilder::new()
+ .thread_name(move |idx| format!("WRWorker{}#{}", priority_tag, idx))
+ .num_threads(num_threads)
+ .start_handler(move |idx| unsafe {
+ wr_register_thread_local_arena();
+ let name = format!("WRWorker{}#{}", priority_tag, idx);
+ register_thread_with_profiler(name.clone());
+ let name = CString::new(name).unwrap();
+ gecko_profiler_register_thread(name.as_ptr());
+ })
+ .exit_handler(|_idx| unsafe {
+ gecko_profiler_unregister_thread();
+ })
+ .build();
+
+ let workers = Arc::new(worker.unwrap());
+
+ // This effectively leaks the thread pool. Not great but we only create one and it lives
+ // for as long as the browser.
+ // Do this to avoid intermittent race conditions with nsThreadManager shutdown.
+ // A better fix would involve removing the dependency between implicit nsThreadManager
+ // and webrender's threads, or be able to synchronously terminate rayon's thread pool.
+ mem::forget(Arc::clone(&workers));
+
+ Box::into_raw(Box::new(WrThreadPool(workers)))
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn wr_thread_pool_delete(thread_pool: *mut WrThreadPool) {
+ Box::from_raw(thread_pool);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn wr_program_cache_new(
+ prof_path: &nsAString,
+ thread_pool: *mut WrThreadPool,
+) -> *mut WrProgramCache {
+ let workers = &(*thread_pool).0;
+ let program_cache = WrProgramCache::new(prof_path, workers);
+ Box::into_raw(Box::new(program_cache))
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn wr_program_cache_delete(program_cache: *mut WrProgramCache) {
+ Box::from_raw(program_cache);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn wr_try_load_startup_shaders_from_disk(program_cache: *mut WrProgramCache) {
+ (*program_cache).try_load_startup_shaders_from_disk();
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn remove_program_binary_disk_cache(prof_path: &nsAString) -> bool {
+ match remove_disk_cache(prof_path) {
+ Ok(_) => true,
+ Err(_) => {
+ error!("Failed to remove program binary disk cache");
+ false
+ }
+ }
+}
+
+// This matches IsEnvSet in gfxEnv.h
+fn env_var_to_bool(key: &'static str) -> bool {
+ env::var(key).ok().map_or(false, |v| !v.is_empty())
+}
+
+// Call MakeCurrent before this.
+fn wr_device_new(gl_context: *mut c_void, pc: Option<&mut WrProgramCache>) -> Device {
+ assert!(unsafe { is_in_render_thread() });
+
+ let gl;
+ if unsafe { is_glcontext_gles(gl_context) } {
+ gl = unsafe { gl::GlesFns::load_with(|symbol| get_proc_address(gl_context, symbol)) };
+ } else {
+ gl = unsafe { gl::GlFns::load_with(|symbol| get_proc_address(gl_context, symbol)) };
+ }
+
+ let version = gl.get_string(gl::VERSION);
+
+ info!("WebRender - OpenGL version new {}", version);
+
+ let upload_method = if unsafe { is_glcontext_angle(gl_context) } {
+ UploadMethod::Immediate
+ } else {
+ UploadMethod::PixelBuffer(ONE_TIME_USAGE_HINT)
+ };
+
+ let resource_override_path = unsafe {
+ let override_charptr = gfx_wr_resource_path_override();
+ if override_charptr.is_null() {
+ None
+ } else {
+ match CStr::from_ptr(override_charptr).to_str() {
+ Ok(override_str) => Some(PathBuf::from(override_str)),
+ _ => None,
+ }
+ }
+ };
+
+ let use_optimized_shaders = unsafe { gfx_wr_use_optimized_shaders() };
+
+ let cached_programs = match pc {
+ Some(cached_programs) => Some(Rc::clone(cached_programs.rc_get())),
+ None => None,
+ };
+
+ Device::new(
+ gl,
+ Some(Box::new(MozCrashAnnotator)),
+ resource_override_path,
+ use_optimized_shaders,
+ upload_method,
+ cached_programs,
+ true,
+ true,
+ None,
+ false,
+ false,
+ )
+}
+
+extern "C" {
+ fn wr_compositor_create_surface(
+ compositor: *mut c_void,
+ id: NativeSurfaceId,
+ virtual_offset: DeviceIntPoint,
+ tile_size: DeviceIntSize,
+ is_opaque: bool,
+ );
+ fn wr_compositor_create_external_surface(compositor: *mut c_void, id: NativeSurfaceId, is_opaque: bool);
+ fn wr_compositor_destroy_surface(compositor: *mut c_void, id: NativeSurfaceId);
+ fn wr_compositor_create_tile(compositor: *mut c_void, id: NativeSurfaceId, x: i32, y: i32);
+ fn wr_compositor_destroy_tile(compositor: *mut c_void, id: NativeSurfaceId, x: i32, y: i32);
+ fn wr_compositor_attach_external_image(
+ compositor: *mut c_void,
+ id: NativeSurfaceId,
+ external_image: ExternalImageId,
+ );
+ fn wr_compositor_bind(
+ compositor: *mut c_void,
+ id: NativeTileId,
+ offset: &mut DeviceIntPoint,
+ fbo_id: &mut u32,
+ dirty_rect: DeviceIntRect,
+ valid_rect: DeviceIntRect,
+ );
+ fn wr_compositor_unbind(compositor: *mut c_void);
+ fn wr_compositor_begin_frame(compositor: *mut c_void);
+ fn wr_compositor_add_surface(
+ compositor: *mut c_void,
+ id: NativeSurfaceId,
+ transform: &CompositorSurfaceTransform,
+ clip_rect: DeviceIntRect,
+ image_rendering: ImageRendering,
+ );
+ fn wr_compositor_start_compositing(
+ compositor: *mut c_void,
+ dirty_rects: *const DeviceIntRect,
+ num_dirty_rects: usize,
+ opaque_rects: *const DeviceIntRect,
+ num_opaque_rects: usize,
+ );
+ fn wr_compositor_end_frame(compositor: *mut c_void);
+ fn wr_compositor_enable_native_compositor(compositor: *mut c_void, enable: bool);
+ fn wr_compositor_deinit(compositor: *mut c_void);
+ fn wr_compositor_get_capabilities(compositor: *mut c_void) -> CompositorCapabilities;
+ fn wr_compositor_map_tile(
+ compositor: *mut c_void,
+ id: NativeTileId,
+ dirty_rect: DeviceIntRect,
+ valid_rect: DeviceIntRect,
+ data: &mut *mut c_void,
+ stride: &mut i32,
+ );
+ fn wr_compositor_unmap_tile(compositor: *mut c_void);
+
+ fn wr_partial_present_compositor_set_buffer_damage_region(
+ compositor: *mut c_void,
+ rects: *const DeviceIntRect,
+ n_rects: usize,
+ );
+}
+
+pub struct WrCompositor(*mut c_void);
+
+impl Compositor for WrCompositor {
+ fn create_surface(
+ &mut self,
+ id: NativeSurfaceId,
+ virtual_offset: DeviceIntPoint,
+ tile_size: DeviceIntSize,
+ is_opaque: bool,
+ ) {
+ unsafe {
+ wr_compositor_create_surface(self.0, id, virtual_offset, tile_size, is_opaque);
+ }
+ }
+
+ fn create_external_surface(&mut self, id: NativeSurfaceId, is_opaque: bool) {
+ unsafe {
+ wr_compositor_create_external_surface(self.0, id, is_opaque);
+ }
+ }
+
+ fn destroy_surface(&mut self, id: NativeSurfaceId) {
+ unsafe {
+ wr_compositor_destroy_surface(self.0, id);
+ }
+ }
+
+ fn create_tile(&mut self, id: NativeTileId) {
+ unsafe {
+ wr_compositor_create_tile(self.0, id.surface_id, id.x, id.y);
+ }
+ }
+
+ fn destroy_tile(&mut self, id: NativeTileId) {
+ unsafe {
+ wr_compositor_destroy_tile(self.0, id.surface_id, id.x, id.y);
+ }
+ }
+
+ fn attach_external_image(&mut self, id: NativeSurfaceId, external_image: ExternalImageId) {
+ unsafe {
+ wr_compositor_attach_external_image(self.0, id, external_image);
+ }
+ }
+
+ fn bind(&mut self, id: NativeTileId, dirty_rect: DeviceIntRect, valid_rect: DeviceIntRect) -> NativeSurfaceInfo {
+ let mut surface_info = NativeSurfaceInfo {
+ origin: DeviceIntPoint::zero(),
+ fbo_id: 0,
+ };
+
+ unsafe {
+ wr_compositor_bind(
+ self.0,
+ id,
+ &mut surface_info.origin,
+ &mut surface_info.fbo_id,
+ dirty_rect,
+ valid_rect,
+ );
+ }
+
+ surface_info
+ }
+
+ fn unbind(&mut self) {
+ unsafe {
+ wr_compositor_unbind(self.0);
+ }
+ }
+
+ fn begin_frame(&mut self) {
+ unsafe {
+ wr_compositor_begin_frame(self.0);
+ }
+ }
+
+ fn add_surface(
+ &mut self,
+ id: NativeSurfaceId,
+ transform: CompositorSurfaceTransform,
+ clip_rect: DeviceIntRect,
+ image_rendering: ImageRendering,
+ ) {
+ unsafe {
+ wr_compositor_add_surface(self.0, id, &transform, clip_rect, image_rendering);
+ }
+ }
+
+ fn start_compositing(
+ &mut self,
+ dirty_rects: &[DeviceIntRect],
+ opaque_rects: &[DeviceIntRect],
+ ) {
+ unsafe {
+ wr_compositor_start_compositing(
+ self.0,
+ dirty_rects.as_ptr(),
+ dirty_rects.len(),
+ opaque_rects.as_ptr(),
+ opaque_rects.len(),
+ );
+ }
+ }
+
+ fn end_frame(&mut self) {
+ unsafe {
+ wr_compositor_end_frame(self.0);
+ }
+ }
+
+ fn enable_native_compositor(&mut self, enable: bool) {
+ unsafe {
+ wr_compositor_enable_native_compositor(self.0, enable);
+ }
+ }
+
+ fn deinit(&mut self) {
+ unsafe {
+ wr_compositor_deinit(self.0);
+ }
+ }
+
+ fn get_capabilities(&self) -> CompositorCapabilities {
+ unsafe { wr_compositor_get_capabilities(self.0) }
+ }
+}
+
+pub struct WrPartialPresentCompositor(*mut c_void);
+
+impl PartialPresentCompositor for WrPartialPresentCompositor {
+ fn set_buffer_damage_region(&mut self, rects: &[DeviceIntRect]) {
+ unsafe {
+ wr_partial_present_compositor_set_buffer_damage_region(self.0, rects.as_ptr(), rects.len());
+ }
+ }
+}
+
+/// Information about the underlying data buffer of a mapped tile.
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct MappedTileInfo {
+ pub data: *mut c_void,
+ pub stride: i32,
+}
+
+/// WrCompositor-specific extensions to the basic Compositor interface.
+impl WrCompositor {
+ /// Map a tile's underlying buffer so it can be used as the backing for
+ /// a SWGL framebuffer. This is intended to be a replacement for 'bind'
+ /// in any compositors that intend to directly interoperate with SWGL
+ /// while supporting some form of native layers.
+ pub fn map_tile(
+ &mut self,
+ id: NativeTileId,
+ dirty_rect: DeviceIntRect,
+ valid_rect: DeviceIntRect,
+ ) -> Option<MappedTileInfo> {
+ let mut tile_info = MappedTileInfo {
+ data: ptr::null_mut(),
+ stride: 0,
+ };
+
+ unsafe {
+ wr_compositor_map_tile(
+ self.0,
+ id,
+ dirty_rect,
+ valid_rect,
+ &mut tile_info.data,
+ &mut tile_info.stride,
+ );
+ }
+
+ if tile_info.data != ptr::null_mut() && tile_info.stride != 0 {
+ Some(tile_info)
+ } else {
+ None
+ }
+ }
+
+ /// Unmap a tile that was was previously mapped via map_tile to signal
+ /// that SWGL is done rendering to the buffer.
+ pub fn unmap_tile(&mut self) {
+ unsafe {
+ wr_compositor_unmap_tile(self.0);
+ }
+ }
+}
+
+/// A wrapper around a strong reference to a Shaders object.
+pub struct WrShaders(SharedShaders);
+
+// Call MakeCurrent before this.
+#[no_mangle]
+pub extern "C" fn wr_window_new(
+ window_id: WrWindowId,
+ window_width: i32,
+ window_height: i32,
+ is_main_window: bool,
+ support_low_priority_transactions: bool,
+ support_low_priority_threadpool: bool,
+ allow_texture_swizzling: bool,
+ allow_scissored_cache_clears: bool,
+ start_debug_server: bool,
+ swgl_context: *mut c_void,
+ gl_context: *mut c_void,
+ surface_origin_is_top_left: bool,
+ program_cache: Option<&mut WrProgramCache>,
+ shaders: Option<&mut WrShaders>,
+ thread_pool: *mut WrThreadPool,
+ thread_pool_low_priority: *mut WrThreadPool,
+ size_of_op: VoidPtrToSizeFn,
+ enclosing_size_of_op: VoidPtrToSizeFn,
+ document_id: u32,
+ compositor: *mut c_void,
+ use_native_compositor: bool,
+ max_update_rects: usize,
+ use_partial_present: bool,
+ max_partial_present_rects: usize,
+ draw_previous_partial_present_regions: bool,
+ out_handle: &mut *mut DocumentHandle,
+ out_renderer: &mut *mut Renderer,
+ out_max_texture_size: *mut i32,
+ out_err: &mut *mut c_char,
+ enable_gpu_markers: bool,
+ panic_on_gl_error: bool,
+ picture_tile_width: i32,
+ picture_tile_height: i32,
+) -> bool {
+ assert!(unsafe { is_in_render_thread() });
+
+ let native_gl = if gl_context == ptr::null_mut() {
+ None
+ } else if unsafe { is_glcontext_gles(gl_context) } {
+ unsafe { Some(gl::GlesFns::load_with(|symbol| get_proc_address(gl_context, symbol))) }
+ } else {
+ unsafe { Some(gl::GlFns::load_with(|symbol| get_proc_address(gl_context, symbol))) }
+ };
+
+ let software = swgl_context != ptr::null_mut();
+ let (gl, sw_gl) = if software {
+ let ctx = swgl::Context::from(swgl_context);
+ ctx.make_current();
+ (Rc::new(ctx) as Rc<dyn gl::Gl>, Some(ctx))
+ } else {
+ (
+ native_gl
+ .as_ref()
+ .expect("Native GL context required when not using SWGL!")
+ .clone(),
+ None,
+ )
+ };
+
+ let version = gl.get_string(gl::VERSION);
+
+ info!("WebRender - OpenGL version new {}", version);
+
+ let workers = unsafe { Arc::clone(&(*thread_pool).0) };
+ let workers_low_priority = unsafe {
+ if support_low_priority_threadpool {
+ Arc::clone(&(*thread_pool_low_priority).0)
+ } else {
+ Arc::clone(&(*thread_pool).0)
+ }
+ };
+
+ let upload_method = if gl_context != ptr::null_mut() && unsafe { is_glcontext_angle(gl_context) } {
+ UploadMethod::Immediate
+ } else {
+ UploadMethod::PixelBuffer(ONE_TIME_USAGE_HINT)
+ };
+
+ let precache_flags = if env_var_to_bool("MOZ_WR_PRECACHE_SHADERS") {
+ ShaderPrecacheFlags::FULL_COMPILE
+ } else {
+ ShaderPrecacheFlags::empty()
+ };
+
+ let cached_programs = match program_cache {
+ Some(program_cache) => Some(Rc::clone(&program_cache.rc_get())),
+ None => None,
+ };
+
+ let color = if cfg!(target_os = "android") {
+ // The color is for avoiding black flash before receiving display list.
+ ColorF::new(1.0, 1.0, 1.0, 1.0)
+ } else {
+ ColorF::new(0.0, 0.0, 0.0, 0.0)
+ };
+
+ let compositor_config = if software {
+ CompositorConfig::Native {
+ max_update_rects: 1,
+ compositor: Box::new(SwCompositor::new(
+ sw_gl.unwrap(),
+ native_gl,
+ WrCompositor(compositor),
+ use_native_compositor,
+ )),
+ }
+ } else if use_native_compositor {
+ CompositorConfig::Native {
+ max_update_rects,
+ compositor: Box::new(WrCompositor(compositor)),
+ }
+ } else {
+ CompositorConfig::Draw {
+ max_partial_present_rects,
+ draw_previous_partial_present_regions,
+ partial_present: if use_partial_present {
+ Some(Box::new(WrPartialPresentCompositor(compositor)))
+ } else {
+ None
+ },
+ }
+ };
+
+ let picture_tile_size = if picture_tile_width > 0 && picture_tile_height > 0 {
+ Some(DeviceIntSize::new(picture_tile_width, picture_tile_height))
+ } else {
+ None
+ };
+
+ let texture_cache_config = if is_main_window {
+ TextureCacheConfig::DEFAULT
+ } else {
+ TextureCacheConfig {
+ color8_linear_texture_size: 512,
+ color8_nearest_texture_size: 512,
+ color8_glyph_texture_size: 512,
+ alpha8_texture_size: 512,
+ alpha8_glyph_texture_size: 512,
+ alpha16_texture_size: 512,
+ }
+ };
+
+ let opts = RendererOptions {
+ enable_aa: true,
+ force_subpixel_aa: false,
+ enable_subpixel_aa: cfg!(not(target_os = "android")),
+ support_low_priority_transactions,
+ allow_texture_swizzling,
+ blob_image_handler: Some(Box::new(Moz2dBlobImageHandler::new(
+ workers.clone(),
+ workers_low_priority,
+ ))),
+ crash_annotator: Some(Box::new(MozCrashAnnotator)),
+ workers: Some(workers),
+ thread_listener: Some(Box::new(GeckoProfilerThreadListener::new())),
+ size_of_op: Some(size_of_op),
+ enclosing_size_of_op: Some(enclosing_size_of_op),
+ cached_programs,
+ resource_override_path: unsafe {
+ let override_charptr = gfx_wr_resource_path_override();
+ if override_charptr.is_null() {
+ None
+ } else {
+ match CStr::from_ptr(override_charptr).to_str() {
+ Ok(override_str) => Some(PathBuf::from(override_str)),
+ _ => None,
+ }
+ }
+ },
+ use_optimized_shaders: unsafe { gfx_wr_use_optimized_shaders() },
+ renderer_id: Some(window_id.0),
+ upload_method,
+ scene_builder_hooks: Some(Box::new(APZCallbacks::new(window_id))),
+ sampler: Some(Box::new(SamplerCallback::new(window_id))),
+ max_texture_size: Some(8192), // Moz2D doesn't like textures bigger than this
+ clear_color: Some(color),
+ precache_flags,
+ namespace_alloc_by_client: true,
+ // SWGL doesn't support the GL_ALWAYS depth comparison function used by
+ // `clear_caches_with_quads`, but scissored clears work well.
+ clear_caches_with_quads: !software && !allow_scissored_cache_clears,
+ start_debug_server,
+ surface_origin_is_top_left,
+ compositor_config,
+ enable_gpu_markers,
+ panic_on_gl_error,
+ picture_tile_size,
+ texture_cache_config,
+ ..Default::default()
+ };
+
+ // Ensure the WR profiler callbacks are hooked up to the Gecko profiler.
+ set_profiler_hooks(Some(&PROFILER_HOOKS));
+
+ let window_size = DeviceIntSize::new(window_width, window_height);
+ let notifier = Box::new(CppNotifier { window_id });
+ let (renderer, sender) = match Renderer::new(gl, notifier, opts, shaders.map(|sh| &sh.0)) {
+ Ok((renderer, sender)) => (renderer, sender),
+ Err(e) => {
+ warn!(" Failed to create a Renderer: {:?}", e);
+ let msg = CString::new(format!("wr_window_new: {:?}", e)).unwrap();
+ unsafe {
+ gfx_critical_note(msg.as_ptr());
+ }
+ *out_err = msg.into_raw();
+ return false;
+ }
+ };
+
+ unsafe {
+ *out_max_texture_size = renderer.get_max_texture_size();
+ }
+ *out_handle = Box::into_raw(Box::new(DocumentHandle::new(
+ sender.create_api_by_client(next_namespace_id()),
+ None,
+ window_size,
+ document_id,
+ )));
+ *out_renderer = Box::into_raw(Box::new(renderer));
+
+ true
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn wr_api_free_error_msg(msg: *mut c_char) {
+ if msg != ptr::null_mut() {
+ CString::from_raw(msg);
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn wr_api_delete_document(dh: &mut DocumentHandle) {
+ dh.api.delete_document(dh.document_id);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_api_clone(dh: &mut DocumentHandle, out_handle: &mut *mut DocumentHandle) {
+ assert!(unsafe { is_in_compositor_thread() });
+
+ dh.ensure_hit_tester();
+
+ let handle = DocumentHandle {
+ api: dh.api.create_sender().create_api_by_client(next_namespace_id()),
+ document_id: dh.document_id,
+ hit_tester: dh.hit_tester.clone(),
+ hit_tester_request: None,
+ };
+ *out_handle = Box::into_raw(Box::new(handle));
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn wr_api_delete(dh: *mut DocumentHandle) {
+ let _ = Box::from_raw(dh);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn wr_api_shut_down(dh: &mut DocumentHandle) {
+ dh.api.shut_down(true);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn wr_api_notify_memory_pressure(dh: &mut DocumentHandle) {
+ dh.api.notify_memory_pressure();
+}
+
+#[no_mangle]
+pub extern "C" fn wr_api_set_debug_flags(dh: &mut DocumentHandle, flags: DebugFlags) {
+ dh.api.set_debug_flags(flags);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn wr_api_accumulate_memory_report(
+ dh: &mut DocumentHandle,
+ report: &mut MemoryReport,
+ // we manually expand VoidPtrToSizeFn here because cbindgen otherwise fails to fold the Option<fn()>
+ // https://github.com/eqrion/cbindgen/issues/552
+ size_of_op: unsafe extern "C" fn(ptr: *const c_void) -> usize,
+ enclosing_size_of_op: Option<unsafe extern "C" fn(ptr: *const c_void) -> usize>,
+) {
+ let ops = MallocSizeOfOps::new(size_of_op, enclosing_size_of_op);
+ *report += dh.api.report_memory(ops);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn wr_api_clear_all_caches(dh: &mut DocumentHandle) {
+ dh.api.send_debug_cmd(DebugCommand::ClearCaches(ClearCache::all()));
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn wr_api_enable_native_compositor(dh: &mut DocumentHandle, enable: bool) {
+ dh.api.send_debug_cmd(DebugCommand::EnableNativeCompositor(enable));
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn wr_api_enable_multithreading(dh: &mut DocumentHandle, enable: bool) {
+ dh.api.send_debug_cmd(DebugCommand::EnableMultithreading(enable));
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn wr_api_set_batching_lookback(dh: &mut DocumentHandle, count: u32) {
+ dh.api.send_debug_cmd(DebugCommand::SetBatchingLookback(count));
+}
+
+fn make_transaction(do_async: bool) -> Transaction {
+ let mut transaction = Transaction::new();
+ // Ensure that we either use async scene building or not based on the
+ // gecko pref, regardless of what the default is. We can remove this once
+ // the scene builder thread is enabled everywhere and working well.
+ if do_async {
+ transaction.use_scene_builder_thread();
+ } else {
+ transaction.skip_scene_builder();
+ }
+ transaction
+}
+
+#[no_mangle]
+pub extern "C" fn wr_transaction_new(do_async: bool) -> *mut Transaction {
+ Box::into_raw(Box::new(make_transaction(do_async)))
+}
+
+#[no_mangle]
+pub extern "C" fn wr_transaction_delete(txn: *mut Transaction) {
+ unsafe {
+ let _ = Box::from_raw(txn);
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn wr_transaction_set_low_priority(txn: &mut Transaction, low_priority: bool) {
+ txn.set_low_priority(low_priority);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_transaction_is_empty(txn: &Transaction) -> bool {
+ txn.is_empty()
+}
+
+#[no_mangle]
+pub extern "C" fn wr_transaction_resource_updates_is_empty(txn: &Transaction) -> bool {
+ txn.resource_updates.is_empty()
+}
+
+#[no_mangle]
+pub extern "C" fn wr_transaction_is_rendered_frame_invalidated(txn: &Transaction) -> bool {
+ txn.invalidate_rendered_frame
+}
+
+#[no_mangle]
+pub extern "C" fn wr_transaction_notify(txn: &mut Transaction, when: Checkpoint, event: usize) {
+ struct GeckoNotification(usize);
+ impl NotificationHandler for GeckoNotification {
+ fn notify(&self, when: Checkpoint) {
+ unsafe {
+ wr_transaction_notification_notified(self.0, when);
+ }
+ }
+ }
+
+ let handler = Box::new(GeckoNotification(event));
+ txn.notify(NotificationRequest::new(when, handler));
+}
+
+#[no_mangle]
+pub extern "C" fn wr_transaction_update_epoch(txn: &mut Transaction, pipeline_id: WrPipelineId, epoch: WrEpoch) {
+ txn.update_epoch(pipeline_id, epoch);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_transaction_set_root_pipeline(txn: &mut Transaction, pipeline_id: WrPipelineId) {
+ txn.set_root_pipeline(pipeline_id);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_transaction_remove_pipeline(txn: &mut Transaction, pipeline_id: WrPipelineId) {
+ txn.remove_pipeline(pipeline_id);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_transaction_set_display_list(
+ txn: &mut Transaction,
+ epoch: WrEpoch,
+ background: ColorF,
+ viewport_size: LayoutSize,
+ pipeline_id: WrPipelineId,
+ dl_descriptor: BuiltDisplayListDescriptor,
+ dl_data: &mut WrVecU8,
+) {
+ let color = if background.a == 0.0 { None } else { Some(background) };
+
+ // See the documentation of set_display_list in api.rs. I don't think
+ // it makes a difference in gecko at the moment(until APZ is figured out)
+ // but I suppose it is a good default.
+ let preserve_frame_state = true;
+
+ let dl_vec = dl_data.flush_into_vec();
+ let dl = BuiltDisplayList::from_data(dl_vec, dl_descriptor);
+
+ txn.set_display_list(epoch, color, viewport_size, (pipeline_id, dl), preserve_frame_state);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_transaction_set_document_view(txn: &mut Transaction, doc_rect: &DeviceIntRect) {
+ txn.set_document_view(*doc_rect, 1.0);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_transaction_generate_frame(txn: &mut Transaction, id: u64) {
+ txn.generate_frame(id);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_transaction_invalidate_rendered_frame(txn: &mut Transaction) {
+ txn.invalidate_rendered_frame();
+}
+
+fn wr_animation_properties_into_vec<T>(
+ animation_array: *const WrAnimationPropertyValue<T>,
+ array_count: usize,
+ vec: &mut Vec<PropertyValue<T>>,
+) where
+ T: Copy,
+{
+ if array_count > 0 {
+ debug_assert!(
+ vec.capacity() - vec.len() >= array_count,
+ "The Vec should have fufficient free capacity"
+ );
+ let slice = unsafe { make_slice(animation_array, array_count) };
+
+ for element in slice.iter() {
+ let prop = PropertyValue {
+ key: PropertyBindingKey::new(element.id),
+ value: element.value,
+ };
+
+ vec.push(prop);
+ }
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn wr_transaction_update_dynamic_properties(
+ txn: &mut Transaction,
+ opacity_array: *const WrOpacityProperty,
+ opacity_count: usize,
+ transform_array: *const WrTransformProperty,
+ transform_count: usize,
+ color_array: *const WrColorProperty,
+ color_count: usize,
+) {
+ let mut properties = DynamicProperties {
+ transforms: Vec::with_capacity(transform_count),
+ floats: Vec::with_capacity(opacity_count),
+ colors: Vec::with_capacity(color_count),
+ };
+
+ wr_animation_properties_into_vec(transform_array, transform_count, &mut properties.transforms);
+
+ wr_animation_properties_into_vec(opacity_array, opacity_count, &mut properties.floats);
+
+ wr_animation_properties_into_vec(color_array, color_count, &mut properties.colors);
+
+ txn.update_dynamic_properties(properties);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_transaction_append_transform_properties(
+ txn: &mut Transaction,
+ transform_array: *const WrTransformProperty,
+ transform_count: usize,
+) {
+ if transform_count == 0 {
+ return;
+ }
+
+ let mut transforms = Vec::with_capacity(transform_count);
+ wr_animation_properties_into_vec(transform_array, transform_count, &mut transforms);
+
+ txn.append_dynamic_transform_properties(transforms);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_transaction_scroll_layer(
+ txn: &mut Transaction,
+ pipeline_id: WrPipelineId,
+ scroll_id: u64,
+ new_scroll_origin: LayoutPoint,
+) {
+ let scroll_id = ExternalScrollId(scroll_id, pipeline_id);
+ txn.scroll_node_with_id(new_scroll_origin, scroll_id, ScrollClamping::NoClamping);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_transaction_pinch_zoom(txn: &mut Transaction, pinch_zoom: f32) {
+ txn.set_pinch_zoom(ZoomFactor::new(pinch_zoom));
+}
+
+#[no_mangle]
+pub extern "C" fn wr_transaction_set_is_transform_async_zooming(
+ txn: &mut Transaction,
+ animation_id: u64,
+ is_zooming: bool,
+) {
+ txn.set_is_transform_async_zooming(is_zooming, PropertyBindingId::new(animation_id));
+}
+
+#[no_mangle]
+pub extern "C" fn wr_transaction_set_quality_settings(txn: &mut Transaction, force_subpixel_aa_where_possible: bool) {
+ txn.set_quality_settings(QualitySettings {
+ force_subpixel_aa_where_possible,
+ });
+}
+
+#[no_mangle]
+pub extern "C" fn wr_resource_updates_add_image(
+ txn: &mut Transaction,
+ image_key: WrImageKey,
+ descriptor: &WrImageDescriptor,
+ bytes: &mut WrVecU8,
+) {
+ txn.add_image(
+ image_key,
+ descriptor.into(),
+ ImageData::new(bytes.flush_into_vec()),
+ None,
+ );
+}
+
+#[no_mangle]
+pub extern "C" fn wr_resource_updates_add_blob_image(
+ txn: &mut Transaction,
+ image_key: BlobImageKey,
+ descriptor: &WrImageDescriptor,
+ bytes: &mut WrVecU8,
+ visible_rect: DeviceIntRect,
+) {
+ txn.add_blob_image(
+ image_key,
+ descriptor.into(),
+ Arc::new(bytes.flush_into_vec()),
+ visible_rect,
+ if descriptor.format == ImageFormat::BGRA8 {
+ Some(256)
+ } else {
+ None
+ },
+ );
+}
+
+#[no_mangle]
+pub extern "C" fn wr_resource_updates_add_external_image(
+ txn: &mut Transaction,
+ image_key: WrImageKey,
+ descriptor: &WrImageDescriptor,
+ external_image_id: ExternalImageId,
+ image_type: &ExternalImageType,
+ channel_index: u8,
+) {
+ txn.add_image(
+ image_key,
+ descriptor.into(),
+ ImageData::External(ExternalImageData {
+ id: external_image_id,
+ channel_index,
+ image_type: *image_type,
+ }),
+ None,
+ );
+}
+
+#[no_mangle]
+pub extern "C" fn wr_resource_updates_update_image(
+ txn: &mut Transaction,
+ key: WrImageKey,
+ descriptor: &WrImageDescriptor,
+ bytes: &mut WrVecU8,
+) {
+ txn.update_image(
+ key,
+ descriptor.into(),
+ ImageData::new(bytes.flush_into_vec()),
+ &DirtyRect::All,
+ );
+}
+
+#[no_mangle]
+pub extern "C" fn wr_resource_updates_set_blob_image_visible_area(
+ txn: &mut Transaction,
+ key: BlobImageKey,
+ area: &DeviceIntRect,
+) {
+ txn.set_blob_image_visible_area(key, *area);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_resource_updates_update_external_image(
+ txn: &mut Transaction,
+ key: WrImageKey,
+ descriptor: &WrImageDescriptor,
+ external_image_id: ExternalImageId,
+ image_type: &ExternalImageType,
+ channel_index: u8,
+) {
+ txn.update_image(
+ key,
+ descriptor.into(),
+ ImageData::External(ExternalImageData {
+ id: external_image_id,
+ channel_index,
+ image_type: *image_type,
+ }),
+ &DirtyRect::All,
+ );
+}
+
+#[no_mangle]
+pub extern "C" fn wr_resource_updates_update_external_image_with_dirty_rect(
+ txn: &mut Transaction,
+ key: WrImageKey,
+ descriptor: &WrImageDescriptor,
+ external_image_id: ExternalImageId,
+ image_type: &ExternalImageType,
+ channel_index: u8,
+ dirty_rect: DeviceIntRect,
+) {
+ txn.update_image(
+ key,
+ descriptor.into(),
+ ImageData::External(ExternalImageData {
+ id: external_image_id,
+ channel_index,
+ image_type: *image_type,
+ }),
+ &DirtyRect::Partial(dirty_rect),
+ );
+}
+
+#[no_mangle]
+pub extern "C" fn wr_resource_updates_update_blob_image(
+ txn: &mut Transaction,
+ image_key: BlobImageKey,
+ descriptor: &WrImageDescriptor,
+ bytes: &mut WrVecU8,
+ visible_rect: DeviceIntRect,
+ dirty_rect: LayoutIntRect,
+) {
+ txn.update_blob_image(
+ image_key,
+ descriptor.into(),
+ Arc::new(bytes.flush_into_vec()),
+ visible_rect,
+ &DirtyRect::Partial(dirty_rect),
+ );
+}
+
+#[no_mangle]
+pub extern "C" fn wr_resource_updates_delete_image(txn: &mut Transaction, key: WrImageKey) {
+ txn.delete_image(key);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_resource_updates_delete_blob_image(txn: &mut Transaction, key: BlobImageKey) {
+ txn.delete_blob_image(key);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_api_send_transaction(dh: &mut DocumentHandle, transaction: &mut Transaction, is_async: bool) {
+ if transaction.is_empty() {
+ return;
+ }
+ let new_txn = make_transaction(is_async);
+ let txn = mem::replace(transaction, new_txn);
+ dh.api.send_transaction(dh.document_id, txn);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn wr_transaction_clear_display_list(
+ txn: &mut Transaction,
+ epoch: WrEpoch,
+ pipeline_id: WrPipelineId,
+) {
+ let preserve_frame_state = true;
+ let frame_builder = WebRenderFrameBuilder::new(pipeline_id);
+
+ txn.set_display_list(
+ epoch,
+ None,
+ LayoutSize::new(0.0, 0.0),
+ frame_builder.dl_builder.finalize(),
+ preserve_frame_state,
+ );
+}
+
+#[no_mangle]
+pub extern "C" fn wr_api_send_external_event(dh: &mut DocumentHandle, evt: usize) {
+ assert!(unsafe { !is_in_render_thread() });
+
+ dh.api.send_external_event(ExternalEvent::from_raw(evt));
+}
+
+#[no_mangle]
+pub extern "C" fn wr_resource_updates_add_raw_font(
+ txn: &mut Transaction,
+ key: WrFontKey,
+ bytes: &mut WrVecU8,
+ index: u32,
+) {
+ txn.add_raw_font(key, bytes.flush_into_vec(), index);
+}
+
+fn generate_capture_path(path: *const c_char) -> Option<PathBuf> {
+ use std::fs::{create_dir_all, File};
+ use std::io::Write;
+
+ let cstr = unsafe { CStr::from_ptr(path) };
+ let local_dir = PathBuf::from(&*cstr.to_string_lossy());
+
+ // On Android we need to write into a particular folder on external
+ // storage so that (a) it can be written without requiring permissions
+ // and (b) it can be pulled off via `adb pull`. This env var is set
+ // in GeckoLoader.java.
+ // When running in Firefox CI, the MOZ_UPLOAD_DIR variable is set to a path
+ // that taskcluster will export artifacts from, so let's put it there.
+ let mut path = if let Ok(storage_path) = env::var("PUBLIC_STORAGE") {
+ PathBuf::from(storage_path).join(local_dir)
+ } else if let Ok(storage_path) = env::var("MOZ_UPLOAD_DIR") {
+ PathBuf::from(storage_path).join(local_dir)
+ } else if let Some(storage_path) = dirs::home_dir() {
+ storage_path.join(local_dir)
+ } else {
+ local_dir
+ };
+
+ // Increment the extension until we find a fresh path
+ while path.is_dir() {
+ let count: u32 = path
+ .extension()
+ .and_then(|x| x.to_str())
+ .and_then(|x| x.parse().ok())
+ .unwrap_or(0);
+ path.set_extension((count + 1).to_string());
+ }
+
+ // Use warn! so that it gets emitted to logcat on android as well
+ let border = "--------------------------\n";
+ warn!("{} Capturing WR state to: {:?}\n{}", &border, &path, &border);
+
+ let _ = create_dir_all(&path);
+ match File::create(path.join("wr.txt")) {
+ Ok(mut file) => {
+ // The Gecko HG revision is available at compile time
+ if let Some(moz_revision) = option_env!("GECKO_HEAD_REV") {
+ writeln!(file, "mozilla-central {}", moz_revision).unwrap();
+ }
+ Some(path)
+ }
+ Err(e) => {
+ warn!("Unable to create path '{:?}' for capture: {:?}", path, e);
+ None
+ }
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn wr_api_capture(dh: &mut DocumentHandle, path: *const c_char, bits_raw: u32) {
+ if let Some(path) = generate_capture_path(path) {
+ let bits = CaptureBits::from_bits(bits_raw as _).unwrap();
+ dh.api.save_capture(path, bits);
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn wr_api_start_capture_sequence(dh: &mut DocumentHandle, path: *const c_char, bits_raw: u32) {
+ if let Some(path) = generate_capture_path(path) {
+ let bits = CaptureBits::from_bits(bits_raw as _).unwrap();
+ dh.api.start_capture_sequence(path, bits);
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn wr_api_stop_capture_sequence(dh: &mut DocumentHandle) {
+ let border = "--------------------------\n";
+ warn!("{} Stop capturing WR state\n{}", &border, &border);
+ dh.api.stop_capture_sequence();
+}
+
+#[cfg(target_os = "windows")]
+fn read_font_descriptor(bytes: &mut WrVecU8, index: u32) -> NativeFontHandle {
+ let wchars = bytes.convert_into_vec::<u16>();
+ NativeFontHandle {
+ path: PathBuf::from(OsString::from_wide(&wchars)),
+ index,
+ }
+}
+
+#[cfg(target_os = "macos")]
+fn read_font_descriptor(bytes: &mut WrVecU8, _index: u32) -> NativeFontHandle {
+ let chars = bytes.flush_into_vec();
+ let name = String::from_utf8(chars).unwrap();
+ let font = match CGFont::from_name(&CFString::new(&*name)) {
+ Ok(font) => font,
+ Err(_) => {
+ // If for some reason we failed to load a font descriptor, then our
+ // only options are to either abort or substitute a fallback font.
+ // It is preferable to use a fallback font instead so that rendering
+ // can at least still proceed in some fashion without erroring.
+ // Lucida Grande is the fallback font in Gecko, so use that here.
+ CGFont::from_name(&CFString::from_static_string("Lucida Grande"))
+ .expect("Failed reading font descriptor and could not load fallback font")
+ }
+ };
+ NativeFontHandle(font)
+}
+
+#[cfg(not(any(target_os = "macos", target_os = "windows")))]
+fn read_font_descriptor(bytes: &mut WrVecU8, index: u32) -> NativeFontHandle {
+ let chars = bytes.flush_into_vec();
+ NativeFontHandle {
+ path: PathBuf::from(OsString::from_vec(chars)),
+ index,
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn wr_resource_updates_add_font_descriptor(
+ txn: &mut Transaction,
+ key: WrFontKey,
+ bytes: &mut WrVecU8,
+ index: u32,
+) {
+ let native_font_handle = read_font_descriptor(bytes, index);
+ txn.add_native_font(key, native_font_handle);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_resource_updates_delete_font(txn: &mut Transaction, key: WrFontKey) {
+ txn.delete_font(key);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_resource_updates_add_font_instance(
+ txn: &mut Transaction,
+ key: WrFontInstanceKey,
+ font_key: WrFontKey,
+ glyph_size: f32,
+ options: *const FontInstanceOptions,
+ platform_options: *const FontInstancePlatformOptions,
+ variations: &mut WrVecU8,
+) {
+ txn.add_font_instance(
+ key,
+ font_key,
+ glyph_size,
+ unsafe { options.as_ref().cloned() },
+ unsafe { platform_options.as_ref().cloned() },
+ variations.convert_into_vec::<FontVariation>(),
+ );
+}
+
+#[no_mangle]
+pub extern "C" fn wr_resource_updates_delete_font_instance(txn: &mut Transaction, key: WrFontInstanceKey) {
+ txn.delete_font_instance(key);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_resource_updates_clear(txn: &mut Transaction) {
+ txn.resource_updates.clear();
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn wr_api_get_namespace(dh: &mut DocumentHandle) -> WrIdNamespace {
+ dh.api.get_namespace_id()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn wr_api_wake_scene_builder(dh: &mut DocumentHandle) {
+ dh.api.wake_scene_builder();
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn wr_api_flush_scene_builder(dh: &mut DocumentHandle) {
+ dh.api.flush_scene_builder();
+}
+
+// RenderThread WIP notes:
+// In order to separate the compositor thread (or ipc receiver) and the render
+// thread, some of the logic below needs to be rewritten. In particular
+// the WrWindowState and Notifier implementations aren't designed to work with
+// a separate render thread.
+// As part of that I am moving the bindings closer to WebRender's API boundary,
+// and moving more of the logic in C++ land.
+// This work is tracked by bug 1328602.
+//
+// See RenderThread.h for some notes about how the pieces fit together.
+
+pub struct WebRenderFrameBuilder {
+ pub root_pipeline_id: WrPipelineId,
+ pub dl_builder: DisplayListBuilder,
+}
+
+impl WebRenderFrameBuilder {
+ pub fn new(root_pipeline_id: WrPipelineId) -> WebRenderFrameBuilder {
+ WebRenderFrameBuilder {
+ root_pipeline_id,
+ dl_builder: DisplayListBuilder::new(root_pipeline_id),
+ }
+ }
+ pub fn with_capacity(root_pipeline_id: WrPipelineId, capacity: usize) -> WebRenderFrameBuilder {
+ WebRenderFrameBuilder {
+ root_pipeline_id,
+ dl_builder: DisplayListBuilder::with_capacity(root_pipeline_id, capacity),
+ }
+ }
+}
+
+pub struct WrState {
+ pipeline_id: WrPipelineId,
+ frame_builder: WebRenderFrameBuilder,
+}
+
+#[no_mangle]
+pub extern "C" fn wr_state_new(pipeline_id: WrPipelineId, capacity: usize) -> *mut WrState {
+ assert!(unsafe { !is_in_render_thread() });
+
+ let state = Box::new(WrState {
+ pipeline_id,
+ frame_builder: WebRenderFrameBuilder::with_capacity(pipeline_id, capacity),
+ });
+
+ Box::into_raw(state)
+}
+
+#[no_mangle]
+pub extern "C" fn wr_state_delete(state: *mut WrState) {
+ assert!(unsafe { !is_in_render_thread() });
+
+ unsafe {
+ Box::from_raw(state);
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_save(state: &mut WrState) {
+ state.frame_builder.dl_builder.save();
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_restore(state: &mut WrState) {
+ state.frame_builder.dl_builder.restore();
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_clear_save(state: &mut WrState) {
+ state.frame_builder.dl_builder.clear_save();
+}
+
+#[repr(u8)]
+#[derive(PartialEq, Eq, Debug)]
+pub enum WrReferenceFrameKind {
+ Transform,
+ Perspective,
+ Zoom,
+}
+
+#[repr(u8)]
+#[derive(PartialEq, Eq, Debug)]
+pub enum WrRotation {
+ Degree0,
+ Degree90,
+ Degree180,
+ Degree270,
+}
+
+/// IMPORTANT: If you add fields to this struct, you need to also add initializers
+/// for those fields in WebRenderAPI.h.
+#[repr(C)]
+pub struct WrStackingContextParams {
+ pub clip: WrStackingContextClip,
+ pub animation: *const WrAnimationProperty,
+ pub opacity: *const f32,
+ pub computed_transform: *const WrComputedTransformData,
+ pub transform_style: TransformStyle,
+ pub reference_frame_kind: WrReferenceFrameKind,
+ pub scrolling_relative_to: *const u64,
+ pub prim_flags: PrimitiveFlags,
+ pub mix_blend_mode: MixBlendMode,
+ pub flags: StackingContextFlags,
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_push_stacking_context(
+ state: &mut WrState,
+ mut bounds: LayoutRect,
+ spatial_id: WrSpatialId,
+ params: &WrStackingContextParams,
+ transform: *const LayoutTransform,
+ filters: *const FilterOp,
+ filter_count: usize,
+ filter_datas: *const WrFilterData,
+ filter_datas_count: usize,
+ glyph_raster_space: RasterSpace,
+) -> WrSpatialId {
+ debug_assert!(unsafe { !is_in_render_thread() });
+
+ let c_filters = unsafe { make_slice(filters, filter_count) };
+ let mut filters: Vec<FilterOp> = c_filters.iter().copied().collect();
+
+ let c_filter_datas = unsafe { make_slice(filter_datas, filter_datas_count) };
+ let r_filter_datas: Vec<FilterData> = c_filter_datas
+ .iter()
+ .map(|c_filter_data| FilterData {
+ func_r_type: c_filter_data.funcR_type,
+ r_values: unsafe { make_slice(c_filter_data.R_values, c_filter_data.R_values_count).to_vec() },
+ func_g_type: c_filter_data.funcG_type,
+ g_values: unsafe { make_slice(c_filter_data.G_values, c_filter_data.G_values_count).to_vec() },
+ func_b_type: c_filter_data.funcB_type,
+ b_values: unsafe { make_slice(c_filter_data.B_values, c_filter_data.B_values_count).to_vec() },
+ func_a_type: c_filter_data.funcA_type,
+ a_values: unsafe { make_slice(c_filter_data.A_values, c_filter_data.A_values_count).to_vec() },
+ })
+ .collect();
+
+ let transform_ref = unsafe { transform.as_ref() };
+ let mut transform_binding = match transform_ref {
+ Some(t) => Some(PropertyBinding::Value(*t)),
+ None => None,
+ };
+
+ let computed_ref = unsafe { params.computed_transform.as_ref() };
+ let opacity_ref = unsafe { params.opacity.as_ref() };
+ let mut has_opacity_animation = false;
+ let anim = unsafe { params.animation.as_ref() };
+ if let Some(anim) = anim {
+ debug_assert!(anim.id > 0);
+ match anim.effect_type {
+ WrAnimationType::Opacity => {
+ filters.push(FilterOp::Opacity(
+ PropertyBinding::Binding(
+ PropertyBindingKey::new(anim.id),
+ // We have to set the static opacity value as
+ // the value for the case where the animation is
+ // in not in-effect (e.g. in the delay phase
+ // with no corresponding fill mode).
+ opacity_ref.cloned().unwrap_or(1.0),
+ ),
+ 1.0,
+ ));
+ has_opacity_animation = true;
+ }
+ WrAnimationType::Transform => {
+ transform_binding = Some(PropertyBinding::Binding(
+ PropertyBindingKey::new(anim.id),
+ // Same as above opacity case.
+ transform_ref.cloned().unwrap_or(LayoutTransform::identity()),
+ ));
+ }
+ _ => unreachable!("{:?} should not create a stacking context", anim.effect_type),
+ }
+ }
+
+ if let Some(opacity) = opacity_ref {
+ if !has_opacity_animation && *opacity < 1.0 {
+ filters.push(FilterOp::Opacity(PropertyBinding::Value(*opacity), *opacity));
+ }
+ }
+
+ let mut wr_spatial_id = spatial_id.to_webrender(state.pipeline_id);
+ let wr_clip_id = params.clip.to_webrender(state.pipeline_id);
+
+ // Note: 0 has special meaning in WR land, standing for ROOT_REFERENCE_FRAME.
+ // However, it is never returned by `push_reference_frame`, and we need to return
+ // an option here across FFI, so we take that 0 value for the None semantics.
+ // This is resolved into proper `Maybe<WrSpatialId>` inside `WebRenderAPI::PushStackingContext`.
+ let mut result = WrSpatialId { id: 0 };
+ if let Some(transform_binding) = transform_binding {
+ let scrolling_relative_to = match unsafe { params.scrolling_relative_to.as_ref() } {
+ Some(scroll_id) => {
+ debug_assert_eq!(params.reference_frame_kind, WrReferenceFrameKind::Perspective);
+ Some(ExternalScrollId(*scroll_id, state.pipeline_id))
+ }
+ None => None,
+ };
+
+ let reference_frame_kind = match params.reference_frame_kind {
+ WrReferenceFrameKind::Transform => ReferenceFrameKind::Transform,
+ WrReferenceFrameKind::Perspective => ReferenceFrameKind::Perspective { scrolling_relative_to },
+ WrReferenceFrameKind::Zoom => ReferenceFrameKind::Zoom,
+ };
+ wr_spatial_id = state.frame_builder.dl_builder.push_reference_frame(
+ bounds.origin,
+ wr_spatial_id,
+ params.transform_style,
+ transform_binding,
+ reference_frame_kind,
+ );
+
+ bounds.origin = LayoutPoint::zero();
+ result.id = wr_spatial_id.0;
+ assert_ne!(wr_spatial_id.0, 0);
+ } else if let Some(data) = computed_ref {
+ let rotation = match data.rotation {
+ WrRotation::Degree0 => Rotation::Degree0,
+ WrRotation::Degree90 => Rotation::Degree90,
+ WrRotation::Degree180 => Rotation::Degree180,
+ WrRotation::Degree270 => Rotation::Degree270,
+ };
+ wr_spatial_id = state.frame_builder.dl_builder.push_computed_frame(
+ bounds.origin,
+ wr_spatial_id,
+ Some(data.scale_from),
+ data.vertical_flip,
+ rotation,
+ );
+
+ bounds.origin = LayoutPoint::zero();
+ result.id = wr_spatial_id.0;
+ assert_ne!(wr_spatial_id.0, 0);
+ }
+
+ state.frame_builder.dl_builder.push_stacking_context(
+ bounds.origin,
+ wr_spatial_id,
+ params.prim_flags,
+ wr_clip_id,
+ params.transform_style,
+ params.mix_blend_mode,
+ &filters,
+ &r_filter_datas,
+ &[],
+ glyph_raster_space,
+ params.flags,
+ );
+
+ result
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_pop_stacking_context(state: &mut WrState, is_reference_frame: bool) {
+ debug_assert!(unsafe { !is_in_render_thread() });
+ state.frame_builder.dl_builder.pop_stacking_context();
+ if is_reference_frame {
+ state.frame_builder.dl_builder.pop_reference_frame();
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_define_clipchain(
+ state: &mut WrState,
+ parent_clipchain_id: *const u64,
+ clips: *const WrClipId,
+ clips_count: usize,
+) -> u64 {
+ debug_assert!(unsafe { is_in_main_thread() });
+ let parent = unsafe { parent_clipchain_id.as_ref() }.map(|id| ClipChainId(*id, state.pipeline_id));
+
+ let pipeline_id = state.pipeline_id;
+ let clips = unsafe { make_slice(clips, clips_count) }
+ .iter()
+ .map(|clip_id| clip_id.to_webrender(pipeline_id));
+
+ let clipchain_id = state.frame_builder.dl_builder.define_clip_chain(parent, clips);
+ assert!(clipchain_id.1 == state.pipeline_id);
+ clipchain_id.0
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_define_clip_with_parent_clip(
+ state: &mut WrState,
+ parent: &WrSpaceAndClip,
+ clip_rect: LayoutRect,
+ complex: *const ComplexClipRegion,
+ complex_count: usize,
+) -> WrClipId {
+ wr_dp_define_clip_impl(
+ &mut state.frame_builder,
+ parent.to_webrender(state.pipeline_id),
+ clip_rect,
+ unsafe { make_slice(complex, complex_count) },
+ )
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_define_clip_with_parent_clip_chain(
+ state: &mut WrState,
+ parent: &WrSpaceAndClipChain,
+ clip_rect: LayoutRect,
+ complex: *const ComplexClipRegion,
+ complex_count: usize,
+) -> WrClipId {
+ wr_dp_define_clip_impl(
+ &mut state.frame_builder,
+ parent.to_webrender(state.pipeline_id),
+ clip_rect,
+ unsafe { make_slice(complex, complex_count) },
+ )
+}
+
+fn wr_dp_define_clip_impl(
+ frame_builder: &mut WebRenderFrameBuilder,
+ parent: SpaceAndClipInfo,
+ clip_rect: LayoutRect,
+ complex_regions: &[ComplexClipRegion],
+) -> WrClipId {
+ debug_assert!(unsafe { is_in_main_thread() });
+ let clip_id = frame_builder
+ .dl_builder
+ .define_clip(&parent, clip_rect, complex_regions.iter().cloned());
+ WrClipId::from_webrender(clip_id)
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_define_image_mask_clip_with_parent_clip_chain(
+ state: &mut WrState,
+ parent: &WrSpaceAndClipChain,
+ mask: ImageMask,
+) -> WrClipId {
+ debug_assert!(unsafe { is_in_main_thread() });
+
+ let clip_id = state
+ .frame_builder
+ .dl_builder
+ .define_clip_image_mask(&parent.to_webrender(state.pipeline_id), mask);
+ WrClipId::from_webrender(clip_id)
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_define_rounded_rect_clip_with_parent_clip_chain(
+ state: &mut WrState,
+ parent: &WrSpaceAndClipChain,
+ complex: ComplexClipRegion,
+) -> WrClipId {
+ debug_assert!(unsafe { is_in_main_thread() });
+
+ let clip_id = state
+ .frame_builder
+ .dl_builder
+ .define_clip_rounded_rect(&parent.to_webrender(state.pipeline_id), complex);
+ WrClipId::from_webrender(clip_id)
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_define_rect_clip_with_parent_clip_chain(
+ state: &mut WrState,
+ parent: &WrSpaceAndClipChain,
+ clip_rect: LayoutRect,
+) -> WrClipId {
+ debug_assert!(unsafe { is_in_main_thread() });
+
+ let clip_id = state
+ .frame_builder
+ .dl_builder
+ .define_clip_rect(&parent.to_webrender(state.pipeline_id), clip_rect);
+ WrClipId::from_webrender(clip_id)
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_define_sticky_frame(
+ state: &mut WrState,
+ parent_spatial_id: WrSpatialId,
+ content_rect: LayoutRect,
+ top_margin: *const f32,
+ right_margin: *const f32,
+ bottom_margin: *const f32,
+ left_margin: *const f32,
+ vertical_bounds: StickyOffsetBounds,
+ horizontal_bounds: StickyOffsetBounds,
+ applied_offset: LayoutVector2D,
+) -> WrSpatialId {
+ assert!(unsafe { is_in_main_thread() });
+ let spatial_id = state.frame_builder.dl_builder.define_sticky_frame(
+ parent_spatial_id.to_webrender(state.pipeline_id),
+ content_rect,
+ SideOffsets2D::new(
+ unsafe { top_margin.as_ref() }.cloned(),
+ unsafe { right_margin.as_ref() }.cloned(),
+ unsafe { bottom_margin.as_ref() }.cloned(),
+ unsafe { left_margin.as_ref() }.cloned(),
+ ),
+ vertical_bounds,
+ horizontal_bounds,
+ applied_offset,
+ );
+
+ WrSpatialId { id: spatial_id.0 }
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_define_scroll_layer(
+ state: &mut WrState,
+ external_scroll_id: u64,
+ parent: &WrSpaceAndClip,
+ content_rect: LayoutRect,
+ clip_rect: LayoutRect,
+ scroll_offset: LayoutPoint,
+) -> WrSpaceAndClip {
+ assert!(unsafe { is_in_main_thread() });
+
+ let space_and_clip = state.frame_builder.dl_builder.define_scroll_frame(
+ &parent.to_webrender(state.pipeline_id),
+ ExternalScrollId(external_scroll_id, state.pipeline_id),
+ content_rect,
+ clip_rect,
+ ScrollSensitivity::Script,
+ // TODO(gw): We should also update the Gecko-side APIs to provide
+ // this as a vector rather than a point.
+ scroll_offset.to_vector(),
+ );
+
+ WrSpaceAndClip::from_webrender(space_and_clip)
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_push_iframe(
+ state: &mut WrState,
+ rect: LayoutRect,
+ clip: LayoutRect,
+ _is_backface_visible: bool,
+ parent: &WrSpaceAndClipChain,
+ pipeline_id: WrPipelineId,
+ ignore_missing_pipeline: bool,
+) {
+ debug_assert!(unsafe { is_in_main_thread() });
+
+ state.frame_builder.dl_builder.push_iframe(
+ rect,
+ clip,
+ &parent.to_webrender(state.pipeline_id),
+ pipeline_id,
+ ignore_missing_pipeline,
+ );
+}
+
+// A helper fn to construct a PrimitiveFlags
+fn prim_flags(is_backface_visible: bool, prefer_compositor_surface: bool) -> PrimitiveFlags {
+ let mut flags = PrimitiveFlags::empty();
+
+ if is_backface_visible {
+ flags |= PrimitiveFlags::IS_BACKFACE_VISIBLE;
+ }
+
+ if prefer_compositor_surface {
+ flags |= PrimitiveFlags::PREFER_COMPOSITOR_SURFACE;
+ }
+
+ flags
+}
+
+fn prim_flags2(
+ is_backface_visible: bool,
+ prefer_compositor_surface: bool,
+ supports_external_compositing: bool,
+) -> PrimitiveFlags {
+ let mut flags = PrimitiveFlags::empty();
+
+ if supports_external_compositing {
+ flags |= PrimitiveFlags::SUPPORTS_EXTERNAL_COMPOSITOR_SURFACE;
+ }
+
+ flags | prim_flags(is_backface_visible, prefer_compositor_surface)
+}
+
+fn common_item_properties_for_rect(
+ state: &mut WrState,
+ clip_rect: LayoutRect,
+ is_backface_visible: bool,
+ parent: &WrSpaceAndClipChain,
+) -> CommonItemProperties {
+ let space_and_clip = parent.to_webrender(state.pipeline_id);
+
+ CommonItemProperties {
+ // NB: the damp-e10s talos-test will frequently crash on startup if we
+ // early-return here for empty rects. I couldn't figure out why, but
+ // it's pretty harmless to feed these through, so, uh, we do?
+ clip_rect,
+ clip_id: space_and_clip.clip_id,
+ spatial_id: space_and_clip.spatial_id,
+ flags: prim_flags(is_backface_visible, /* prefer_compositor_surface */ false),
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_push_rect(
+ state: &mut WrState,
+ rect: LayoutRect,
+ clip: LayoutRect,
+ is_backface_visible: bool,
+ parent: &WrSpaceAndClipChain,
+ color: ColorF,
+) {
+ debug_assert!(unsafe { !is_in_render_thread() });
+
+ let prim_info = common_item_properties_for_rect(state, clip, is_backface_visible, parent);
+
+ state.frame_builder.dl_builder.push_rect(&prim_info, rect, color);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_push_rect_with_animation(
+ state: &mut WrState,
+ rect: LayoutRect,
+ clip: LayoutRect,
+ is_backface_visible: bool,
+ parent: &WrSpaceAndClipChain,
+ color: ColorF,
+ animation: *const WrAnimationProperty,
+) {
+ debug_assert!(unsafe { !is_in_render_thread() });
+
+ let prim_info = common_item_properties_for_rect(state, clip, is_backface_visible, parent);
+
+ let anim = unsafe { animation.as_ref() };
+ if let Some(anim) = anim {
+ debug_assert!(anim.id > 0);
+ match anim.effect_type {
+ WrAnimationType::BackgroundColor => state.frame_builder.dl_builder.push_rect_with_animation(
+ &prim_info,
+ rect,
+ PropertyBinding::Binding(PropertyBindingKey::new(anim.id), color),
+ ),
+ _ => unreachable!("Didn't expect {:?} animation", anim.effect_type),
+ }
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_push_rect_with_parent_clip(
+ state: &mut WrState,
+ rect: LayoutRect,
+ clip_rect: LayoutRect,
+ is_backface_visible: bool,
+ parent: &WrSpaceAndClip,
+ color: ColorF,
+) {
+ debug_assert!(unsafe { !is_in_render_thread() });
+
+ let space_and_clip = parent.to_webrender(state.pipeline_id);
+
+ let prim_info = CommonItemProperties {
+ clip_rect,
+ clip_id: space_and_clip.clip_id,
+ spatial_id: space_and_clip.spatial_id,
+ flags: prim_flags(is_backface_visible, /* prefer_compositor_surface */ false),
+ };
+
+ state.frame_builder.dl_builder.push_rect(&prim_info, rect, color);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_push_backdrop_filter_with_parent_clip(
+ state: &mut WrState,
+ rect: LayoutRect,
+ clip: LayoutRect,
+ is_backface_visible: bool,
+ parent: &WrSpaceAndClip,
+ filters: *const FilterOp,
+ filter_count: usize,
+ filter_datas: *const WrFilterData,
+ filter_datas_count: usize,
+) {
+ debug_assert!(unsafe { !is_in_render_thread() });
+
+ let c_filters = unsafe { make_slice(filters, filter_count) };
+ let filters: Vec<FilterOp> = c_filters.iter().copied().collect();
+
+ let c_filter_datas = unsafe { make_slice(filter_datas, filter_datas_count) };
+ let filter_datas: Vec<FilterData> = c_filter_datas
+ .iter()
+ .map(|c_filter_data| FilterData {
+ func_r_type: c_filter_data.funcR_type,
+ r_values: unsafe { make_slice(c_filter_data.R_values, c_filter_data.R_values_count).to_vec() },
+ func_g_type: c_filter_data.funcG_type,
+ g_values: unsafe { make_slice(c_filter_data.G_values, c_filter_data.G_values_count).to_vec() },
+ func_b_type: c_filter_data.funcB_type,
+ b_values: unsafe { make_slice(c_filter_data.B_values, c_filter_data.B_values_count).to_vec() },
+ func_a_type: c_filter_data.funcA_type,
+ a_values: unsafe { make_slice(c_filter_data.A_values, c_filter_data.A_values_count).to_vec() },
+ })
+ .collect();
+
+ let space_and_clip = parent.to_webrender(state.pipeline_id);
+
+ let clip_rect = clip.intersection(&rect);
+ if clip_rect.is_none() {
+ return;
+ }
+
+ let prim_info = CommonItemProperties {
+ clip_rect: clip_rect.unwrap(),
+ clip_id: space_and_clip.clip_id,
+ spatial_id: space_and_clip.spatial_id,
+ flags: prim_flags(is_backface_visible, /* prefer_compositor_surface */ false),
+ };
+
+ state
+ .frame_builder
+ .dl_builder
+ .push_backdrop_filter(&prim_info, &filters, &filter_datas, &[]);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_push_clear_rect(
+ state: &mut WrState,
+ rect: LayoutRect,
+ clip_rect: LayoutRect,
+ parent: &WrSpaceAndClipChain,
+) {
+ debug_assert!(unsafe { !is_in_render_thread() });
+
+ let space_and_clip = parent.to_webrender(state.pipeline_id);
+
+ let prim_info = CommonItemProperties {
+ clip_rect,
+ clip_id: space_and_clip.clip_id,
+ spatial_id: space_and_clip.spatial_id,
+ flags: prim_flags(true, /* prefer_compositor_surface */ false),
+ };
+
+ state.frame_builder.dl_builder.push_clear_rect(&prim_info, rect);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_push_hit_test(
+ state: &mut WrState,
+ rect: LayoutRect,
+ clip: LayoutRect,
+ is_backface_visible: bool,
+ parent: &WrSpaceAndClipChain,
+ scroll_id: u64,
+ hit_info: u16,
+) {
+ debug_assert!(unsafe { !is_in_render_thread() });
+
+ let space_and_clip = parent.to_webrender(state.pipeline_id);
+
+ let clip_rect = clip.intersection(&rect);
+ if clip_rect.is_none() {
+ return;
+ }
+ let tag = (scroll_id, hit_info);
+
+ let prim_info = CommonItemProperties {
+ clip_rect: clip_rect.unwrap(),
+ clip_id: space_and_clip.clip_id,
+ spatial_id: space_and_clip.spatial_id,
+ flags: prim_flags(is_backface_visible, /* prefer_compositor_surface */ false),
+ };
+
+ state.frame_builder.dl_builder.push_hit_test(&prim_info, tag);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_push_clear_rect_with_parent_clip(
+ state: &mut WrState,
+ rect: LayoutRect,
+ clip_rect: LayoutRect,
+ parent: &WrSpaceAndClip,
+) {
+ debug_assert!(unsafe { !is_in_render_thread() });
+
+ let space_and_clip = parent.to_webrender(state.pipeline_id);
+
+ let prim_info = CommonItemProperties {
+ clip_rect,
+ clip_id: space_and_clip.clip_id,
+ spatial_id: space_and_clip.spatial_id,
+ flags: prim_flags(true, /* prefer_compositor_surface */ false),
+ };
+
+ state.frame_builder.dl_builder.push_clear_rect(&prim_info, rect);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_push_image(
+ state: &mut WrState,
+ bounds: LayoutRect,
+ clip: LayoutRect,
+ is_backface_visible: bool,
+ parent: &WrSpaceAndClipChain,
+ image_rendering: ImageRendering,
+ key: WrImageKey,
+ premultiplied_alpha: bool,
+ color: ColorF,
+ prefer_compositor_surface: bool,
+ supports_external_compositing: bool,
+) {
+ debug_assert!(unsafe { is_in_main_thread() || is_in_compositor_thread() });
+
+ let space_and_clip = parent.to_webrender(state.pipeline_id);
+
+ let prim_info = CommonItemProperties {
+ clip_rect: clip,
+ clip_id: space_and_clip.clip_id,
+ spatial_id: space_and_clip.spatial_id,
+ flags: prim_flags2(
+ is_backface_visible,
+ prefer_compositor_surface,
+ supports_external_compositing,
+ ),
+ };
+
+ let alpha_type = if premultiplied_alpha {
+ AlphaType::PremultipliedAlpha
+ } else {
+ AlphaType::Alpha
+ };
+
+ state
+ .frame_builder
+ .dl_builder
+ .push_image(&prim_info, bounds, image_rendering, alpha_type, key, color);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_push_repeating_image(
+ state: &mut WrState,
+ bounds: LayoutRect,
+ clip: LayoutRect,
+ is_backface_visible: bool,
+ parent: &WrSpaceAndClipChain,
+ stretch_size: LayoutSize,
+ tile_spacing: LayoutSize,
+ image_rendering: ImageRendering,
+ key: WrImageKey,
+ premultiplied_alpha: bool,
+ color: ColorF,
+) {
+ debug_assert!(unsafe { is_in_main_thread() || is_in_compositor_thread() });
+
+ let space_and_clip = parent.to_webrender(state.pipeline_id);
+
+ let prim_info = CommonItemProperties {
+ clip_rect: clip,
+ clip_id: space_and_clip.clip_id,
+ spatial_id: space_and_clip.spatial_id,
+ flags: prim_flags(is_backface_visible, /* prefer_compositor_surface */ false),
+ };
+
+ let alpha_type = if premultiplied_alpha {
+ AlphaType::PremultipliedAlpha
+ } else {
+ AlphaType::Alpha
+ };
+
+ state.frame_builder.dl_builder.push_repeating_image(
+ &prim_info,
+ bounds,
+ stretch_size,
+ tile_spacing,
+ image_rendering,
+ alpha_type,
+ key,
+ color,
+ );
+}
+
+/// Push a 3 planar yuv image.
+#[no_mangle]
+pub extern "C" fn wr_dp_push_yuv_planar_image(
+ state: &mut WrState,
+ bounds: LayoutRect,
+ clip: LayoutRect,
+ is_backface_visible: bool,
+ parent: &WrSpaceAndClipChain,
+ image_key_0: WrImageKey,
+ image_key_1: WrImageKey,
+ image_key_2: WrImageKey,
+ color_depth: WrColorDepth,
+ color_space: WrYuvColorSpace,
+ color_range: WrColorRange,
+ image_rendering: ImageRendering,
+ prefer_compositor_surface: bool,
+ supports_external_compositing: bool,
+) {
+ debug_assert!(unsafe { is_in_main_thread() || is_in_compositor_thread() });
+
+ let space_and_clip = parent.to_webrender(state.pipeline_id);
+
+ let prim_info = CommonItemProperties {
+ clip_rect: clip,
+ clip_id: space_and_clip.clip_id,
+ spatial_id: space_and_clip.spatial_id,
+ flags: prim_flags2(
+ is_backface_visible,
+ prefer_compositor_surface,
+ supports_external_compositing,
+ ),
+ };
+
+ state.frame_builder.dl_builder.push_yuv_image(
+ &prim_info,
+ bounds,
+ YuvData::PlanarYCbCr(image_key_0, image_key_1, image_key_2),
+ color_depth,
+ color_space,
+ color_range,
+ image_rendering,
+ );
+}
+
+/// Push a 2 planar NV12 image.
+#[no_mangle]
+pub extern "C" fn wr_dp_push_yuv_NV12_image(
+ state: &mut WrState,
+ bounds: LayoutRect,
+ clip: LayoutRect,
+ is_backface_visible: bool,
+ parent: &WrSpaceAndClipChain,
+ image_key_0: WrImageKey,
+ image_key_1: WrImageKey,
+ color_depth: WrColorDepth,
+ color_space: WrYuvColorSpace,
+ color_range: WrColorRange,
+ image_rendering: ImageRendering,
+ prefer_compositor_surface: bool,
+ supports_external_compositing: bool,
+) {
+ debug_assert!(unsafe { is_in_main_thread() || is_in_compositor_thread() });
+
+ let space_and_clip = parent.to_webrender(state.pipeline_id);
+
+ let prim_info = CommonItemProperties {
+ clip_rect: clip,
+ clip_id: space_and_clip.clip_id,
+ spatial_id: space_and_clip.spatial_id,
+ flags: prim_flags2(
+ is_backface_visible,
+ prefer_compositor_surface,
+ supports_external_compositing,
+ ),
+ };
+
+ state.frame_builder.dl_builder.push_yuv_image(
+ &prim_info,
+ bounds,
+ YuvData::NV12(image_key_0, image_key_1),
+ color_depth,
+ color_space,
+ color_range,
+ image_rendering,
+ );
+}
+
+/// Push a yuv interleaved image.
+#[no_mangle]
+pub extern "C" fn wr_dp_push_yuv_interleaved_image(
+ state: &mut WrState,
+ bounds: LayoutRect,
+ clip: LayoutRect,
+ is_backface_visible: bool,
+ parent: &WrSpaceAndClipChain,
+ image_key_0: WrImageKey,
+ color_depth: WrColorDepth,
+ color_space: WrYuvColorSpace,
+ color_range: WrColorRange,
+ image_rendering: ImageRendering,
+ prefer_compositor_surface: bool,
+ supports_external_compositing: bool,
+) {
+ debug_assert!(unsafe { is_in_main_thread() || is_in_compositor_thread() });
+
+ let space_and_clip = parent.to_webrender(state.pipeline_id);
+
+ let prim_info = CommonItemProperties {
+ clip_rect: clip,
+ clip_id: space_and_clip.clip_id,
+ spatial_id: space_and_clip.spatial_id,
+ flags: prim_flags2(
+ is_backface_visible,
+ prefer_compositor_surface,
+ supports_external_compositing,
+ ),
+ };
+
+ state.frame_builder.dl_builder.push_yuv_image(
+ &prim_info,
+ bounds,
+ YuvData::InterleavedYCbCr(image_key_0),
+ color_depth,
+ color_space,
+ color_range,
+ image_rendering,
+ );
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_push_text(
+ state: &mut WrState,
+ bounds: LayoutRect,
+ clip: LayoutRect,
+ is_backface_visible: bool,
+ parent: &WrSpaceAndClipChain,
+ color: ColorF,
+ font_key: WrFontInstanceKey,
+ glyphs: *const GlyphInstance,
+ glyph_count: u32,
+ glyph_options: *const GlyphOptions,
+) {
+ debug_assert!(unsafe { is_in_main_thread() });
+
+ let glyph_slice = unsafe { make_slice(glyphs, glyph_count as usize) };
+
+ let space_and_clip = parent.to_webrender(state.pipeline_id);
+
+ let prim_info = CommonItemProperties {
+ clip_rect: clip,
+ spatial_id: space_and_clip.spatial_id,
+ clip_id: space_and_clip.clip_id,
+ flags: prim_flags(is_backface_visible, /* prefer_compositor_surface */ false),
+ };
+
+ state
+ .frame_builder
+ .dl_builder
+ .push_text(&prim_info, bounds, &glyph_slice, font_key, color, unsafe {
+ glyph_options.as_ref().cloned()
+ });
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_push_shadow(
+ state: &mut WrState,
+ _bounds: LayoutRect,
+ _clip: LayoutRect,
+ _is_backface_visible: bool,
+ parent: &WrSpaceAndClipChain,
+ shadow: Shadow,
+ should_inflate: bool,
+) {
+ debug_assert!(unsafe { is_in_main_thread() });
+
+ state
+ .frame_builder
+ .dl_builder
+ .push_shadow(&parent.to_webrender(state.pipeline_id), shadow, should_inflate);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_pop_all_shadows(state: &mut WrState) {
+ debug_assert!(unsafe { is_in_main_thread() });
+
+ state.frame_builder.dl_builder.pop_all_shadows();
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_push_line(
+ state: &mut WrState,
+ clip: &LayoutRect,
+ is_backface_visible: bool,
+ parent: &WrSpaceAndClipChain,
+ bounds: &LayoutRect,
+ wavy_line_thickness: f32,
+ orientation: LineOrientation,
+ color: &ColorF,
+ style: LineStyle,
+) {
+ debug_assert!(unsafe { is_in_main_thread() });
+
+ let space_and_clip = parent.to_webrender(state.pipeline_id);
+
+ let prim_info = CommonItemProperties {
+ clip_rect: *clip,
+ clip_id: space_and_clip.clip_id,
+ spatial_id: space_and_clip.spatial_id,
+ flags: prim_flags(is_backface_visible, /* prefer_compositor_surface */ false),
+ };
+
+ state
+ .frame_builder
+ .dl_builder
+ .push_line(&prim_info, bounds, wavy_line_thickness, orientation, color, style);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_push_border(
+ state: &mut WrState,
+ rect: LayoutRect,
+ clip: LayoutRect,
+ is_backface_visible: bool,
+ parent: &WrSpaceAndClipChain,
+ do_aa: AntialiasBorder,
+ widths: LayoutSideOffsets,
+ top: BorderSide,
+ right: BorderSide,
+ bottom: BorderSide,
+ left: BorderSide,
+ radius: BorderRadius,
+) {
+ debug_assert!(unsafe { is_in_main_thread() });
+
+ let border_details = BorderDetails::Normal(NormalBorder {
+ left,
+ right,
+ top,
+ bottom,
+ radius,
+ do_aa: do_aa == AntialiasBorder::Yes,
+ });
+
+ let space_and_clip = parent.to_webrender(state.pipeline_id);
+
+ let prim_info = CommonItemProperties {
+ clip_rect: clip,
+ clip_id: space_and_clip.clip_id,
+ spatial_id: space_and_clip.spatial_id,
+ flags: prim_flags(is_backface_visible, /* prefer_compositor_surface */ false),
+ };
+
+ state
+ .frame_builder
+ .dl_builder
+ .push_border(&prim_info, rect, widths, border_details);
+}
+
+#[repr(C)]
+pub struct WrBorderImage {
+ widths: LayoutSideOffsets,
+ image: WrImageKey,
+ width: i32,
+ height: i32,
+ fill: bool,
+ slice: DeviceIntSideOffsets,
+ outset: LayoutSideOffsets,
+ repeat_horizontal: RepeatMode,
+ repeat_vertical: RepeatMode,
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_push_border_image(
+ state: &mut WrState,
+ rect: LayoutRect,
+ clip: LayoutRect,
+ is_backface_visible: bool,
+ parent: &WrSpaceAndClipChain,
+ params: &WrBorderImage,
+) {
+ debug_assert!(unsafe { is_in_main_thread() });
+ let border_details = BorderDetails::NinePatch(NinePatchBorder {
+ source: NinePatchBorderSource::Image(params.image),
+ width: params.width,
+ height: params.height,
+ slice: params.slice,
+ fill: params.fill,
+ outset: params.outset,
+ repeat_horizontal: params.repeat_horizontal,
+ repeat_vertical: params.repeat_vertical,
+ });
+ let space_and_clip = parent.to_webrender(state.pipeline_id);
+
+ let prim_info = CommonItemProperties {
+ clip_rect: clip,
+ clip_id: space_and_clip.clip_id,
+ spatial_id: space_and_clip.spatial_id,
+ flags: prim_flags(is_backface_visible, /* prefer_compositor_surface */ false),
+ };
+
+ state
+ .frame_builder
+ .dl_builder
+ .push_border(&prim_info, rect, params.widths, border_details);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_push_border_gradient(
+ state: &mut WrState,
+ rect: LayoutRect,
+ clip: LayoutRect,
+ is_backface_visible: bool,
+ parent: &WrSpaceAndClipChain,
+ widths: LayoutSideOffsets,
+ width: i32,
+ height: i32,
+ fill: bool,
+ slice: DeviceIntSideOffsets,
+ start_point: LayoutPoint,
+ end_point: LayoutPoint,
+ stops: *const GradientStop,
+ stops_count: usize,
+ extend_mode: ExtendMode,
+ outset: LayoutSideOffsets,
+) {
+ debug_assert!(unsafe { is_in_main_thread() });
+
+ let stops_slice = unsafe { make_slice(stops, stops_count) };
+ let stops_vector = stops_slice.to_owned();
+
+ let gradient = state
+ .frame_builder
+ .dl_builder
+ .create_gradient(start_point, end_point, stops_vector, extend_mode);
+
+ let border_details = BorderDetails::NinePatch(NinePatchBorder {
+ source: NinePatchBorderSource::Gradient(gradient),
+ width,
+ height,
+ slice,
+ fill,
+ outset,
+ repeat_horizontal: RepeatMode::Stretch,
+ repeat_vertical: RepeatMode::Stretch,
+ });
+
+ let space_and_clip = parent.to_webrender(state.pipeline_id);
+
+ let prim_info = CommonItemProperties {
+ clip_rect: clip,
+ clip_id: space_and_clip.clip_id,
+ spatial_id: space_and_clip.spatial_id,
+ flags: prim_flags(is_backface_visible, /* prefer_compositor_surface */ false),
+ };
+
+ state
+ .frame_builder
+ .dl_builder
+ .push_border(&prim_info, rect, widths, border_details);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_push_border_radial_gradient(
+ state: &mut WrState,
+ rect: LayoutRect,
+ clip: LayoutRect,
+ is_backface_visible: bool,
+ parent: &WrSpaceAndClipChain,
+ widths: LayoutSideOffsets,
+ fill: bool,
+ center: LayoutPoint,
+ radius: LayoutSize,
+ stops: *const GradientStop,
+ stops_count: usize,
+ extend_mode: ExtendMode,
+ outset: LayoutSideOffsets,
+) {
+ debug_assert!(unsafe { is_in_main_thread() });
+
+ let stops_slice = unsafe { make_slice(stops, stops_count) };
+ let stops_vector = stops_slice.to_owned();
+
+ let slice = SideOffsets2D::new(
+ widths.top as i32,
+ widths.right as i32,
+ widths.bottom as i32,
+ widths.left as i32,
+ );
+
+ let gradient = state
+ .frame_builder
+ .dl_builder
+ .create_radial_gradient(center, radius, stops_vector, extend_mode);
+
+ let border_details = BorderDetails::NinePatch(NinePatchBorder {
+ source: NinePatchBorderSource::RadialGradient(gradient),
+ width: rect.size.width as i32,
+ height: rect.size.height as i32,
+ slice,
+ fill,
+ outset,
+ repeat_horizontal: RepeatMode::Stretch,
+ repeat_vertical: RepeatMode::Stretch,
+ });
+
+ let space_and_clip = parent.to_webrender(state.pipeline_id);
+
+ let prim_info = CommonItemProperties {
+ clip_rect: clip,
+ clip_id: space_and_clip.clip_id,
+ spatial_id: space_and_clip.spatial_id,
+ flags: prim_flags(is_backface_visible, /* prefer_compositor_surface */ false),
+ };
+
+ state
+ .frame_builder
+ .dl_builder
+ .push_border(&prim_info, rect, widths, border_details);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_push_border_conic_gradient(
+ state: &mut WrState,
+ rect: LayoutRect,
+ clip: LayoutRect,
+ is_backface_visible: bool,
+ parent: &WrSpaceAndClipChain,
+ widths: LayoutSideOffsets,
+ fill: bool,
+ center: LayoutPoint,
+ angle: f32,
+ stops: *const GradientStop,
+ stops_count: usize,
+ extend_mode: ExtendMode,
+ outset: LayoutSideOffsets,
+) {
+ debug_assert!(unsafe { is_in_main_thread() });
+
+ let stops_slice = unsafe { make_slice(stops, stops_count) };
+ let stops_vector = stops_slice.to_owned();
+
+ let slice = SideOffsets2D::new(
+ widths.top as i32,
+ widths.right as i32,
+ widths.bottom as i32,
+ widths.left as i32,
+ );
+
+ let gradient = state
+ .frame_builder
+ .dl_builder
+ .create_conic_gradient(center, angle, stops_vector, extend_mode);
+
+ let border_details = BorderDetails::NinePatch(NinePatchBorder {
+ source: NinePatchBorderSource::ConicGradient(gradient),
+ width: rect.size.width as i32,
+ height: rect.size.height as i32,
+ slice,
+ fill,
+ outset,
+ repeat_horizontal: RepeatMode::Stretch,
+ repeat_vertical: RepeatMode::Stretch,
+ });
+
+ let space_and_clip = parent.to_webrender(state.pipeline_id);
+
+ let prim_info = CommonItemProperties {
+ clip_rect: clip,
+ clip_id: space_and_clip.clip_id,
+ spatial_id: space_and_clip.spatial_id,
+ flags: prim_flags(is_backface_visible, /* prefer_compositor_surface */ false),
+ };
+
+ state
+ .frame_builder
+ .dl_builder
+ .push_border(&prim_info, rect, widths, border_details);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_push_linear_gradient(
+ state: &mut WrState,
+ rect: LayoutRect,
+ clip: LayoutRect,
+ is_backface_visible: bool,
+ parent: &WrSpaceAndClipChain,
+ start_point: LayoutPoint,
+ end_point: LayoutPoint,
+ stops: *const GradientStop,
+ stops_count: usize,
+ extend_mode: ExtendMode,
+ tile_size: LayoutSize,
+ tile_spacing: LayoutSize,
+) {
+ debug_assert!(unsafe { is_in_main_thread() });
+
+ let stops_slice = unsafe { make_slice(stops, stops_count) };
+ let stops_vector = stops_slice.to_owned();
+
+ let gradient = state
+ .frame_builder
+ .dl_builder
+ .create_gradient(start_point, end_point, stops_vector, extend_mode);
+
+ let space_and_clip = parent.to_webrender(state.pipeline_id);
+
+ let prim_info = CommonItemProperties {
+ clip_rect: clip,
+ clip_id: space_and_clip.clip_id,
+ spatial_id: space_and_clip.spatial_id,
+ flags: prim_flags(is_backface_visible, /* prefer_compositor_surface */ false),
+ };
+
+ state
+ .frame_builder
+ .dl_builder
+ .push_gradient(&prim_info, rect, gradient, tile_size, tile_spacing);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_push_radial_gradient(
+ state: &mut WrState,
+ rect: LayoutRect,
+ clip: LayoutRect,
+ is_backface_visible: bool,
+ parent: &WrSpaceAndClipChain,
+ center: LayoutPoint,
+ radius: LayoutSize,
+ stops: *const GradientStop,
+ stops_count: usize,
+ extend_mode: ExtendMode,
+ tile_size: LayoutSize,
+ tile_spacing: LayoutSize,
+) {
+ debug_assert!(unsafe { is_in_main_thread() });
+
+ let stops_slice = unsafe { make_slice(stops, stops_count) };
+ let stops_vector = stops_slice.to_owned();
+
+ let gradient = state
+ .frame_builder
+ .dl_builder
+ .create_radial_gradient(center, radius, stops_vector, extend_mode);
+
+ let space_and_clip = parent.to_webrender(state.pipeline_id);
+
+ let prim_info = CommonItemProperties {
+ clip_rect: clip,
+ clip_id: space_and_clip.clip_id,
+ spatial_id: space_and_clip.spatial_id,
+ flags: prim_flags(is_backface_visible, /* prefer_compositor_surface */ false),
+ };
+
+ state
+ .frame_builder
+ .dl_builder
+ .push_radial_gradient(&prim_info, rect, gradient, tile_size, tile_spacing);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_push_conic_gradient(
+ state: &mut WrState,
+ rect: LayoutRect,
+ clip: LayoutRect,
+ is_backface_visible: bool,
+ parent: &WrSpaceAndClipChain,
+ center: LayoutPoint,
+ angle: f32,
+ stops: *const GradientStop,
+ stops_count: usize,
+ extend_mode: ExtendMode,
+ tile_size: LayoutSize,
+ tile_spacing: LayoutSize,
+) {
+ debug_assert!(unsafe { is_in_main_thread() });
+
+ let stops_slice = unsafe { make_slice(stops, stops_count) };
+ let stops_vector = stops_slice.to_owned();
+
+ let gradient = state
+ .frame_builder
+ .dl_builder
+ .create_conic_gradient(center, angle, stops_vector, extend_mode);
+
+ let space_and_clip = parent.to_webrender(state.pipeline_id);
+
+ let prim_info = CommonItemProperties {
+ clip_rect: clip,
+ clip_id: space_and_clip.clip_id,
+ spatial_id: space_and_clip.spatial_id,
+ flags: prim_flags(is_backface_visible, /* prefer_compositor_surface */ false),
+ };
+
+ state
+ .frame_builder
+ .dl_builder
+ .push_conic_gradient(&prim_info, rect, gradient, tile_size, tile_spacing);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_push_box_shadow(
+ state: &mut WrState,
+ _rect: LayoutRect,
+ clip: LayoutRect,
+ is_backface_visible: bool,
+ parent: &WrSpaceAndClipChain,
+ box_bounds: LayoutRect,
+ offset: LayoutVector2D,
+ color: ColorF,
+ blur_radius: f32,
+ spread_radius: f32,
+ border_radius: BorderRadius,
+ clip_mode: BoxShadowClipMode,
+) {
+ debug_assert!(unsafe { is_in_main_thread() });
+
+ let space_and_clip = parent.to_webrender(state.pipeline_id);
+
+ let prim_info = CommonItemProperties {
+ clip_rect: clip,
+ clip_id: space_and_clip.clip_id,
+ spatial_id: space_and_clip.spatial_id,
+ flags: prim_flags(is_backface_visible, /* prefer_compositor_surface */ false),
+ };
+
+ state.frame_builder.dl_builder.push_box_shadow(
+ &prim_info,
+ box_bounds,
+ offset,
+ color,
+ blur_radius,
+ spread_radius,
+ border_radius,
+ clip_mode,
+ );
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_start_item_group(state: &mut WrState) {
+ state.frame_builder.dl_builder.start_item_group();
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_cancel_item_group(state: &mut WrState, discard: bool) {
+ state.frame_builder.dl_builder.cancel_item_group(discard);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_finish_item_group(state: &mut WrState, key: ItemKey) -> bool {
+ state.frame_builder.dl_builder.finish_item_group(key)
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_push_reuse_items(state: &mut WrState, key: ItemKey) {
+ state.frame_builder.dl_builder.push_reuse_items(key);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dp_set_cache_size(state: &mut WrState, cache_size: usize) {
+ state.frame_builder.dl_builder.set_cache_size(cache_size);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dump_display_list(
+ state: &mut WrState,
+ indent: usize,
+ start: *const usize,
+ end: *const usize,
+) -> usize {
+ let start = unsafe { start.as_ref().cloned() };
+ let end = unsafe { end.as_ref().cloned() };
+ let range = Range { start, end };
+ let mut sink = Cursor::new(Vec::new());
+ let index = state
+ .frame_builder
+ .dl_builder
+ .emit_display_list(indent, range, &mut sink);
+
+ // For Android, dump to logcat instead of stderr. This is the same as
+ // what printf_stderr does on the C++ side.
+
+ #[cfg(target_os = "android")]
+ unsafe {
+ let gecko = CString::new("Gecko").unwrap();
+ let sink = CString::new(sink.into_inner()).unwrap();
+ __android_log_write(4 /* info */, gecko.as_ptr(), sink.as_ptr());
+ }
+
+ #[cfg(not(target_os = "android"))]
+ eprint!("{}", String::from_utf8(sink.into_inner()).unwrap());
+
+ index
+}
+
+#[no_mangle]
+pub extern "C" fn wr_dump_serialized_display_list(state: &mut WrState) {
+ state.frame_builder.dl_builder.dump_serialized_display_list();
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn wr_api_finalize_builder(
+ state: &mut WrState,
+ dl_descriptor: &mut BuiltDisplayListDescriptor,
+ dl_data: &mut WrVecU8,
+) {
+ let frame_builder = mem::replace(&mut state.frame_builder, WebRenderFrameBuilder::new(state.pipeline_id));
+ let (_, dl) = frame_builder.dl_builder.finalize();
+ let (data, descriptor) = dl.into_data();
+ *dl_data = WrVecU8::from_vec(data);
+ *dl_descriptor = descriptor;
+}
+
+#[repr(C)]
+pub struct HitResult {
+ pipeline_id: WrPipelineId,
+ scroll_id: u64,
+ hit_info: u16,
+}
+
+#[no_mangle]
+pub extern "C" fn wr_api_hit_test(dh: &mut DocumentHandle, point: WorldPoint, out_results: &mut ThinVec<HitResult>) {
+ dh.ensure_hit_tester();
+
+ let result = dh.hit_tester.as_ref().unwrap().hit_test(None, point);
+
+ for item in &result.items {
+ out_results.push(HitResult {
+ pipeline_id: item.pipeline,
+ scroll_id: item.tag.0,
+ hit_info: item.tag.1,
+ });
+ }
+}
+
+pub type VecU8 = Vec<u8>;
+pub type ArcVecU8 = Arc<VecU8>;
+
+#[no_mangle]
+pub extern "C" fn wr_add_ref_arc(arc: &ArcVecU8) -> *const VecU8 {
+ Arc::into_raw(arc.clone())
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn wr_dec_ref_arc(arc: *const VecU8) {
+ Arc::from_raw(arc);
+}
+
+// TODO: nical
+// Update for the new blob image interface changes.
+//
+extern "C" {
+ // TODO: figure out the API for tiled blob images.
+ pub fn wr_moz2d_render_cb(
+ blob: ByteSlice,
+ format: ImageFormat,
+ render_rect: &LayoutIntRect,
+ visible_rect: &DeviceIntRect,
+ tile_size: u16,
+ tile_offset: &TileOffset,
+ dirty_rect: Option<&LayoutIntRect>,
+ output: MutByteSlice,
+ ) -> bool;
+}
+
+#[no_mangle]
+pub extern "C" fn wr_root_scroll_node_id() -> WrSpatialId {
+ // The PipelineId doesn't matter here, since we just want the numeric part of the id
+ // produced for any given root reference frame.
+ WrSpatialId {
+ id: SpatialId::root_scroll_node(PipelineId(0, 0)).0,
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn wr_root_clip_id() -> WrClipId {
+ // The PipelineId doesn't matter here, since we just want the numeric part of the id
+ // produced for any given root reference frame.
+ WrClipId::from_webrender(ClipId::root(PipelineId(0, 0)))
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct WrClipId {
+ id: usize,
+}
+
+impl WrClipId {
+ fn to_webrender(&self, pipeline_id: WrPipelineId) -> ClipId {
+ ClipId::Clip(self.id, pipeline_id)
+ }
+
+ fn from_webrender(clip_id: ClipId) -> Self {
+ match clip_id {
+ ClipId::Clip(id, _) => WrClipId { id },
+ ClipId::ClipChain(_) => panic!("Unexpected clip chain"),
+ }
+ }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct WrSpatialId {
+ id: usize,
+}
+
+impl WrSpatialId {
+ fn to_webrender(&self, pipeline_id: WrPipelineId) -> SpatialId {
+ SpatialId::new(self.id, pipeline_id)
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn wr_device_delete(device: *mut Device) {
+ Box::from_raw(device);
+}
+
+// Call MakeCurrent before this.
+#[no_mangle]
+pub extern "C" fn wr_shaders_new(
+ gl_context: *mut c_void,
+ program_cache: Option<&mut WrProgramCache>,
+ precache_shaders: bool,
+) -> *mut WrShaders {
+ let mut device = wr_device_new(gl_context, program_cache);
+
+ let precache_flags = if precache_shaders || env_var_to_bool("MOZ_WR_PRECACHE_SHADERS") {
+ ShaderPrecacheFlags::FULL_COMPILE
+ } else {
+ ShaderPrecacheFlags::ASYNC_COMPILE
+ };
+
+ let opts = RendererOptions {
+ precache_flags,
+ ..Default::default()
+ };
+
+ let gl_type = device.gl().get_type();
+ device.begin_frame();
+
+ let shaders = Rc::new(RefCell::new(match Shaders::new(&mut device, gl_type, &opts) {
+ Ok(shaders) => shaders,
+ Err(e) => {
+ warn!(" Failed to create a Shaders: {:?}", e);
+ let msg = CString::new(format!("wr_shaders_new: {:?}", e)).unwrap();
+ unsafe {
+ gfx_critical_note(msg.as_ptr());
+ }
+ return ptr::null_mut();
+ }
+ }));
+
+ let shaders = WrShaders(shaders);
+
+ device.end_frame();
+ Box::into_raw(Box::new(shaders))
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn wr_shaders_delete(shaders: *mut WrShaders, gl_context: *mut c_void) {
+ let mut device = wr_device_new(gl_context, None);
+ let shaders = Box::from_raw(shaders);
+ if let Ok(shaders) = Rc::try_unwrap(shaders.0) {
+ shaders.into_inner().deinit(&mut device);
+ }
+ // let shaders go out of scope and get dropped
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn wr_program_cache_report_memory(
+ cache: *const WrProgramCache,
+ size_of_op: VoidPtrToSizeFn,
+) -> usize {
+ (*cache).program_cache.report_memory(size_of_op)
+}
diff --git a/gfx/webrender_bindings/src/lib.rs b/gfx/webrender_bindings/src/lib.rs
new file mode 100644
index 0000000000..b9b565f297
--- /dev/null
+++ b/gfx/webrender_bindings/src/lib.rs
@@ -0,0 +1,42 @@
+/* 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/. */
+
+#![deny(warnings)]
+
+extern crate app_units;
+extern crate bincode;
+extern crate euclid;
+extern crate fxhash;
+extern crate gleam;
+extern crate nsstring;
+extern crate num_cpus;
+extern crate rayon;
+extern crate swgl;
+extern crate thin_vec;
+extern crate tracy_rs;
+extern crate uuid;
+extern crate webrender;
+extern crate wr_malloc_size_of;
+
+#[macro_use]
+extern crate log;
+
+#[cfg(target_os = "windows")]
+extern crate dwrote;
+#[cfg(target_os = "windows")]
+extern crate winapi;
+
+#[cfg(target_os = "macos")]
+extern crate core_foundation;
+#[cfg(target_os = "macos")]
+extern crate core_graphics;
+#[cfg(target_os = "macos")]
+extern crate foreign_types;
+
+mod program_cache;
+
+#[allow(non_snake_case)]
+pub mod bindings;
+pub mod moz2d_renderer;
+mod swgl_bindings;
diff --git a/gfx/webrender_bindings/src/moz2d_renderer.rs b/gfx/webrender_bindings/src/moz2d_renderer.rs
new file mode 100644
index 0000000000..f7be60fdff
--- /dev/null
+++ b/gfx/webrender_bindings/src/moz2d_renderer.rs
@@ -0,0 +1,895 @@
+/* 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/. */
+#![deny(missing_docs)]
+
+//! Provides the webrender-side implementation of gecko blob images.
+//!
+//! Pretty much this is just a shim that calls back into Moz2DImageRenderer, but
+//! it also handles merging "partial" blob images (see `merge_blob_images`) and
+//! registering fonts found in the blob (see `prepare_request`).
+
+use bindings::{
+ gecko_profiler_end_marker, gecko_profiler_start_marker, wr_moz2d_render_cb, ArcVecU8, ByteSlice, MutByteSlice,
+};
+use rayon::prelude::*;
+use rayon::ThreadPool;
+use webrender::api::units::{BlobDirtyRect, BlobToDeviceTranslation, DeviceIntRect};
+use webrender::api::*;
+
+use euclid::Rect;
+use std;
+use std::collections::btree_map::BTreeMap;
+use std::collections::hash_map;
+use std::collections::hash_map::HashMap;
+use std::collections::Bound::Included;
+use std::i32;
+use std::mem;
+use std::os::raw::{c_char, c_void};
+use std::ptr;
+use std::sync::Arc;
+
+#[cfg(target_os = "windows")]
+use dwrote;
+
+#[cfg(target_os = "macos")]
+use foreign_types::ForeignType;
+
+#[cfg(not(any(target_os = "macos", target_os = "windows")))]
+use std::ffi::CString;
+#[cfg(not(any(target_os = "macos", target_os = "windows")))]
+use std::os::unix::ffi::OsStrExt;
+
+/// Local print-debugging utility
+macro_rules! dlog {
+ ($($e:expr),*) => { {$(let _ = $e;)*} }
+ //($($t:tt)*) => { println!($($t)*) }
+}
+
+/// Debug prints a blob's item bounds, indicating whether the bounds are dirty or not.
+fn dump_bounds(blob: &[u8], dirty_rect: Box2d) {
+ let mut index = BlobReader::new(blob);
+ while index.reader.has_more() {
+ let e = index.read_entry();
+ dlog!(
+ " {:?} {}",
+ e.bounds,
+ if e.bounds.contained_by(&dirty_rect) { "*" } else { "" }
+ );
+ }
+}
+
+/// Debug prints a blob's metadata.
+fn dump_index(blob: &[u8]) {
+ let mut index = BlobReader::new(blob);
+ // we might get an empty result here because sub groups are not tightly bound
+ // and we'll sometimes have display items that end up with empty bounds in
+ // the blob image.
+ while index.reader.has_more() {
+ let e = index.read_entry();
+ dlog!("result bounds: {} {} {:?}", e.end, e.extra_end, e.bounds);
+ }
+}
+
+/// Handles the interpretation and rasterization of gecko-based (moz2d) WR blob images.
+pub struct Moz2dBlobImageHandler {
+ workers: Arc<ThreadPool>,
+ workers_low_priority: Arc<ThreadPool>,
+ blob_commands: HashMap<BlobImageKey, BlobCommand>,
+ enable_multithreading: bool,
+}
+
+/// Transmute some bytes into a value.
+///
+/// FIXME: kill this with fire and/or do a super robust security audit
+unsafe fn convert_from_bytes<T: Copy>(slice: &[u8]) -> T {
+ assert!(mem::size_of::<T>() <= slice.len());
+ ptr::read_unaligned(slice.as_ptr() as *const T)
+}
+
+/// Transmute a value into some bytes.
+fn convert_to_bytes<T>(x: &T) -> &[u8] {
+ unsafe {
+ let ip: *const T = x;
+ let bp: *const u8 = ip as *const _;
+ ::std::slice::from_raw_parts(bp, mem::size_of::<T>())
+ }
+}
+
+/// A simple helper for deserializing a bunch of transmuted POD data from bytes.
+struct BufReader<'a> {
+ /// The buffer to read from.
+ buf: &'a [u8],
+ /// Where we currently are reading from.
+ pos: usize,
+}
+
+impl<'a> BufReader<'a> {
+ /// Creates a reader over the given input.
+ fn new(buf: &'a [u8]) -> BufReader<'a> {
+ BufReader { buf, pos: 0 }
+ }
+
+ /// Transmute-deserializes a value of type T from the stream.
+ ///
+ /// !!! SUPER DANGEROUS !!!
+ ///
+ /// To limit the scope of this unsafety, please don't call this directly.
+ /// Make a helper method for each whitelisted type.
+ unsafe fn read<T: Copy>(&mut self) -> T {
+ let ret = convert_from_bytes(&self.buf[self.pos..]);
+ self.pos += mem::size_of::<T>();
+ ret
+ }
+
+ /// Deserializes a BlobFont.
+ fn read_blob_font(&mut self) -> BlobFont {
+ unsafe { self.read::<BlobFont>() }
+ }
+
+ /// Deserializes a usize.
+ fn read_usize(&mut self) -> usize {
+ unsafe { self.read::<usize>() }
+ }
+
+ /// Deserializes a Box2d.
+ fn read_box(&mut self) -> Box2d {
+ unsafe { self.read::<Box2d>() }
+ }
+
+ /// Returns whether the buffer has more data to deserialize.
+ fn has_more(&self) -> bool {
+ self.pos < self.buf.len()
+ }
+}
+
+/// Reads the metadata of a blob image.
+///
+/// Blob stream format:
+/// { data[..], index[..], offset in the stream of the index array }
+///
+/// An 'item' has 'data' and 'extra_data'
+/// - In our case the 'data' is the stream produced by DrawTargetRecording
+/// and the 'extra_data' includes things like webrender font keys
+///
+/// The index is an array of entries of the following form:
+/// { end, extra_end, bounds }
+///
+/// - end is the offset of the end of an item's data
+/// an item's data goes from the begining of the stream or
+/// the begining of the last item til end
+/// - extra_end is the offset of the end of an item's extra data
+/// an item's extra data goes from 'end' until 'extra_end'
+/// - bounds is a set of 4 ints {x1, y1, x2, y2 }
+///
+/// The offsets in the index should be monotonically increasing.
+///
+/// Design rationale:
+/// - the index is smaller so we append it to the end of the data array
+/// during construction. This makes it more likely that we'll fit inside
+/// the data Vec
+/// - we use indices/offsets instead of sizes to avoid having to deal with any
+/// arithmetic that might overflow.
+struct BlobReader<'a> {
+ /// The buffer of the blob.
+ reader: BufReader<'a>,
+ /// Where the buffer head is.
+ begin: usize,
+}
+
+#[derive(PartialEq, Debug, Eq, Clone, Copy)]
+struct IntPoint {
+ x: i32,
+ y: i32,
+}
+
+/// The metadata for each display item in a blob image (doesn't match the serialized layout).
+///
+/// See BlobReader above for detailed docs of the blob image format.
+struct Entry {
+ /// The bounds of the display item.
+ bounds: Box2d,
+ /// Where the item's recorded drawing commands start.
+ begin: usize,
+ /// Where the item's recorded drawing commands end, and its extra data starts.
+ end: usize,
+ /// Where the item's extra data ends, and the next item's `begin`.
+ extra_end: usize,
+}
+
+impl<'a> BlobReader<'a> {
+ /// Creates a new BlobReader for the given buffer.
+ fn new(buf: &'a [u8]) -> BlobReader<'a> {
+ // The offset of the index is at the end of the buffer.
+ let index_offset_pos = buf.len() - mem::size_of::<usize>();
+ assert!(index_offset_pos < buf.len());
+ let index_offset = unsafe { convert_from_bytes::<usize>(&buf[index_offset_pos..]) };
+
+ BlobReader {
+ reader: BufReader::new(&buf[index_offset..index_offset_pos]),
+ begin: 0,
+ }
+ }
+
+ /// Reads the next display item's metadata.
+ fn read_entry(&mut self) -> Entry {
+ let end = self.reader.read_usize();
+ let extra_end = self.reader.read_usize();
+ let bounds = self.reader.read_box();
+ let ret = Entry {
+ begin: self.begin,
+ end,
+ extra_end,
+ bounds,
+ };
+ self.begin = extra_end;
+ ret
+ }
+}
+
+/// Writes new blob images.
+///
+/// In our case this is the result of merging an old one and a new one
+struct BlobWriter {
+ /// The buffer that the data and extra data for the items is accumulated.
+ data: Vec<u8>,
+ /// The buffer that the metadata for the items is accumulated.
+ index: Vec<u8>,
+}
+
+impl BlobWriter {
+ /// Creates an empty BlobWriter.
+ fn new() -> BlobWriter {
+ BlobWriter {
+ data: Vec::new(),
+ index: Vec::new(),
+ }
+ }
+
+ /// Writes a display item to the blob.
+ fn new_entry(&mut self, extra_size: usize, bounds: Box2d, data: &[u8]) {
+ self.data.extend_from_slice(data);
+ // Write 'end' to the index: the offset where the regular data ends and the extra data starts.
+ self.index
+ .extend_from_slice(convert_to_bytes(&(self.data.len() - extra_size)));
+ // Write 'extra_end' to the index: the offset where the extra data ends.
+ self.index.extend_from_slice(convert_to_bytes(&self.data.len()));
+ // XXX: we can aggregate these writes
+ // Write the bounds to the index.
+ self.index.extend_from_slice(convert_to_bytes(&bounds.x1));
+ self.index.extend_from_slice(convert_to_bytes(&bounds.y1));
+ self.index.extend_from_slice(convert_to_bytes(&bounds.x2));
+ self.index.extend_from_slice(convert_to_bytes(&bounds.y2));
+ }
+
+ /// Completes the blob image, producing a single buffer containing it.
+ fn finish(mut self) -> Vec<u8> {
+ // Append the index to the end of the buffer
+ // and then append the offset to the beginning of the index.
+ let index_begin = self.data.len();
+ self.data.extend_from_slice(&self.index);
+ self.data.extend_from_slice(convert_to_bytes(&index_begin));
+ self.data
+ }
+}
+
+#[derive(Debug, Eq, PartialEq, Clone, Copy, Ord, PartialOrd)]
+#[repr(C)]
+/// A two-points representation of a rectangle.
+struct Box2d {
+ /// Top-left x
+ x1: i32,
+ /// Top-left y
+ y1: i32,
+ /// Bottom-right x
+ x2: i32,
+ /// Bottom-right y
+ y2: i32,
+}
+
+impl Box2d {
+ /// Returns whether `self` is contained by `other` (inclusive).
+ /// an empty self is contained in every rect
+ fn contained_by(&self, other: &Box2d) -> bool {
+ self.is_empty() || (self.x1 >= other.x1 && self.x2 <= other.x2 && self.y1 >= other.y1 && self.y2 <= other.y2)
+ }
+
+ fn intersection(&self, other: &Box2d) -> Box2d {
+ let result = Box2d {
+ x1: self.x1.max(other.x1),
+ y1: self.y1.max(other.y1),
+ x2: self.x2.min(other.x2),
+ y2: self.y2.min(other.y2),
+ };
+ if self.is_empty() || other.is_empty() {
+ assert!(result.is_empty());
+ }
+ result
+ }
+
+ fn is_empty(&self) -> bool {
+ self.x2 <= self.x1 || self.y2 <= self.y1
+ }
+}
+
+impl<T> From<Rect<i32, T>> for Box2d {
+ fn from(rect: Rect<i32, T>) -> Box2d {
+ (&rect).into()
+ }
+}
+
+impl<T> From<&Rect<i32, T>> for Box2d {
+ fn from(rect: &Rect<i32, T>) -> Box2d {
+ Box2d {
+ x1: rect.min_x(),
+ y1: rect.min_y(),
+ x2: rect.max_x(),
+ y2: rect.max_y(),
+ }
+ }
+}
+
+/// Provides an API for looking up the display items in a blob image by bounds, yielding items
+/// with equal bounds in their original relative ordering.
+///
+/// This is used to implement `merge_blobs_images`.
+///
+/// We use a BTree as a kind of multi-map, by appending an integer "cache_order" to the key.
+/// This lets us use multiple items with matching bounds in the map and allows
+/// us to fetch and remove them while retaining the ordering of the original list.
+struct CachedReader<'a> {
+ /// Wrapped reader.
+ reader: BlobReader<'a>,
+ /// Cached entries that have been read but not yet requested by our consumer.
+ cache: BTreeMap<(Box2d, u32), Entry>,
+ /// The current number of internally read display items, used to preserve list order.
+ cache_index_counter: u32,
+}
+
+impl<'a> CachedReader<'a> {
+ /// Creates a new CachedReader.
+ pub fn new(buf: &'a [u8]) -> CachedReader {
+ CachedReader {
+ reader: BlobReader::new(buf),
+ cache: BTreeMap::new(),
+ cache_index_counter: 0,
+ }
+ }
+
+ /// Tries to find the given bounds in the cache of internally read items, removing it if found.
+ fn take_entry_with_bounds_from_cache(&mut self, bounds: &Box2d) -> Option<Entry> {
+ if self.cache.is_empty() {
+ return None;
+ }
+
+ let key_to_delete = match self
+ .cache
+ .range((Included((*bounds, 0u32)), Included((*bounds, std::u32::MAX))))
+ .next()
+ {
+ Some((&key, _)) => key,
+ None => return None,
+ };
+
+ Some(
+ self.cache
+ .remove(&key_to_delete)
+ .expect("We just got this key from range, it needs to be present"),
+ )
+ }
+
+ /// Yields the next item in the blob image with the given bounds.
+ ///
+ /// If the given bounds aren't found in the blob, this panics. `merge_blob_images` should
+ /// avoid this by construction if the blob images are well-formed.
+ pub fn next_entry_with_bounds(&mut self, bounds: &Box2d, ignore_rect: &Box2d) -> Entry {
+ if let Some(entry) = self.take_entry_with_bounds_from_cache(bounds) {
+ return entry;
+ }
+
+ loop {
+ // This will panic if we run through the whole list without finding our bounds.
+ let old = self.reader.read_entry();
+ if old.bounds == *bounds {
+ return old;
+ } else if !old.bounds.contained_by(&ignore_rect) {
+ self.cache.insert((old.bounds, self.cache_index_counter), old);
+ self.cache_index_counter += 1;
+ }
+ }
+ }
+}
+
+/// Merges a new partial blob image into an existing complete one.
+///
+/// A blob image represents a recording of the drawing commands needed to render
+/// (part of) a display list. A partial blob image is a diff between the old display
+/// list and a new one. It contains an entry for every display item in the new list, but
+/// the actual drawing commands are missing for any item that isn't strictly contained
+/// in the dirty rect. This is possible because not being contained in the dirty
+/// rect implies that the item is unchanged between the old and new list, so we can
+/// just grab the drawing commands from the old list.
+///
+/// The dirty rect strictly contains the bounds of every item that has been inserted
+/// into or deleted from the old list to create the new list. (For simplicity
+/// you may think of any other update as deleting and reinserting the item).
+///
+/// Partial blobs are based on gecko's "retained display list" system, and
+/// in particular rely on one key property: if two items have overlapping bounds
+/// and *aren't* contained in the dirty rect, then their relative order in both
+/// the old and new list will not change. This lets us uniquely identify a display
+/// item using only its bounds and relative order in the list.
+///
+/// That is, the first non-dirty item in the new list with bounds (10, 15, 100, 100)
+/// is *also* the first non-dirty item in the old list with those bounds.
+///
+/// Note that *every* item contained inside the dirty rect will be fully recorded in
+/// the new list, even if it is actually unchanged from the old list.
+///
+/// All of this together gives us a fairly simple merging algorithm: all we need
+/// to do is walk through the new (partial) list, determine which of the two lists
+/// has the recording for that item, and copy the recording into the result.
+///
+/// If an item is contained in the dirty rect, then the new list contains the
+/// correct recording for that item, so we always copy it from there. Otherwise, we find
+/// the first not-yet-copied item with those bounds in the old list and copy that.
+/// Any items found in the old list but not the new one can be safely assumed to
+/// have been deleted.
+fn merge_blob_images(
+ old_buf: &[u8],
+ new_buf: &[u8],
+ dirty_rect: Box2d,
+ old_visible_rect: Box2d,
+ new_visible_rect: Box2d,
+) -> Vec<u8> {
+ let mut result = BlobWriter::new();
+ dlog!("dirty rect: {:?}", dirty_rect);
+ dlog!("old:");
+ dump_bounds(old_buf, dirty_rect);
+ dlog!("new:");
+ dump_bounds(new_buf, dirty_rect);
+ dlog!("old visibile rect: {:?}", old_visible_rect);
+ dlog!("new visibile rect: {:?}", new_visible_rect);
+
+ let mut old_reader = CachedReader::new(old_buf);
+ let mut new_reader = BlobReader::new(new_buf);
+ let preserved_rect = old_visible_rect.intersection(&new_visible_rect);
+
+ // Loop over both new and old entries merging them.
+ // Both new and old must have the same number of entries that
+ // overlap but are not contained by the dirty rect, and they
+ // must be in the same order.
+ while new_reader.reader.has_more() {
+ let new = new_reader.read_entry();
+ dlog!("bounds: {} {} {:?}", new.end, new.extra_end, new.bounds);
+ let preserved_bounds = new.bounds.intersection(&preserved_rect);
+ if preserved_bounds.contained_by(&dirty_rect) {
+ result.new_entry(new.extra_end - new.end, new.bounds, &new_buf[new.begin..new.extra_end]);
+ } else {
+ let old = old_reader.next_entry_with_bounds(&new.bounds, &dirty_rect);
+ result.new_entry(old.extra_end - old.end, new.bounds, &old_buf[old.begin..old.extra_end])
+ }
+ }
+
+ // XXX: future work: ensure that items that have been deleted but aren't in the blob's visible
+ // rect don't affect the dirty rect -- this allows us to scroll content out of view while only
+ // updating the areas where items have been scrolled *into* view. This is very important for
+ // the performance of blobs that are larger than the viewport. When this is done this
+ // assertion will need to be modified to factor in the visible rect, or removed.
+
+ // Ensure all remaining items will be discarded
+ while old_reader.reader.reader.has_more() {
+ let old = old_reader.reader.read_entry();
+ dlog!("new bounds: {} {} {:?}", old.end, old.extra_end, old.bounds);
+ //assert!(old.bounds.contained_by(&dirty_rect));
+ }
+
+ //assert!(old_reader.cache.is_empty());
+
+ let result = result.finish();
+ dump_index(&result);
+ result
+}
+
+/// A font used by a blob image.
+#[repr(C)]
+#[derive(Copy, Clone)]
+struct BlobFont {
+ /// The font key.
+ font_instance_key: FontInstanceKey,
+ /// A pointer to the scaled font.
+ scaled_font_ptr: u64,
+}
+
+/// A blob image and extra data provided by webrender on how to rasterize it.
+#[derive(Clone)]
+struct BlobCommand {
+ /// The blob.
+ data: Arc<BlobImageData>,
+ /// What part of the blob should be rasterized (visible_rect's top-left corresponds to
+ /// (0,0) in the blob's rasterization)
+ visible_rect: DeviceIntRect,
+ /// The size of the tiles to use in rasterization.
+ tile_size: TileSize,
+}
+
+struct Job {
+ request: BlobImageRequest,
+ descriptor: BlobImageDescriptor,
+ commands: Arc<BlobImageData>,
+ dirty_rect: BlobDirtyRect,
+ visible_rect: DeviceIntRect,
+ tile_size: TileSize,
+}
+
+/// Rasterizes gecko blob images.
+struct Moz2dBlobRasterizer {
+ /// Pool of rasterizers.
+ workers: Arc<ThreadPool>,
+ /// Pool of low priority rasterizers.
+ workers_low_priority: Arc<ThreadPool>,
+ /// Blobs to rasterize.
+ blob_commands: HashMap<BlobImageKey, BlobCommand>,
+ ///
+ enable_multithreading: bool,
+}
+
+struct GeckoProfilerMarker {
+ name: &'static [u8],
+}
+
+impl GeckoProfilerMarker {
+ pub fn new(name: &'static [u8]) -> GeckoProfilerMarker {
+ unsafe {
+ gecko_profiler_start_marker(name.as_ptr() as *const c_char);
+ }
+ GeckoProfilerMarker { name }
+ }
+}
+
+impl Drop for GeckoProfilerMarker {
+ fn drop(&mut self) {
+ unsafe {
+ gecko_profiler_end_marker(self.name.as_ptr() as *const c_char);
+ }
+ }
+}
+
+impl AsyncBlobImageRasterizer for Moz2dBlobRasterizer {
+ fn rasterize(
+ &mut self,
+ requests: &[BlobImageParams],
+ low_priority: bool,
+ ) -> Vec<(BlobImageRequest, BlobImageResult)> {
+ // All we do here is spin up our workers to callback into gecko to replay the drawing commands.
+ let _marker = GeckoProfilerMarker::new(b"BlobRasterization\0");
+
+ let requests: Vec<Job> = requests
+ .iter()
+ .map(|params| {
+ let command = &self.blob_commands[&params.request.key];
+ let blob = Arc::clone(&command.data);
+ assert!(params.descriptor.rect.size.width > 0 && params.descriptor.rect.size.height > 0);
+
+ Job {
+ request: params.request,
+ descriptor: params.descriptor,
+ commands: blob,
+ visible_rect: command.visible_rect,
+ dirty_rect: params.dirty_rect,
+ tile_size: command.tile_size,
+ }
+ })
+ .collect();
+
+ // If we don't have a lot of blobs it is probably not worth the initial cost
+ // of installing work on rayon's thread pool so we do it serially on this thread.
+ let should_parallelize = if !self.enable_multithreading {
+ false
+ } else if low_priority {
+ requests.len() > 2
+ } else {
+ // For high priority requests we don't "risk" the potential priority inversion of
+ // dispatching to a thread pool full of low priority jobs unless it is really
+ // appealing.
+ requests.len() > 4
+ };
+
+ if should_parallelize {
+ // Parallel version synchronously installs a job on the thread pool which will
+ // try to do the work in parallel.
+ // This thread is blocked until the thread pool is done doing the work.
+ let lambda = || requests.into_par_iter().map(rasterize_blob).collect();
+ if low_priority {
+ //TODO --bpe runtime flag to A/B test these two
+ self.workers_low_priority.install(lambda)
+ //self.workers.install(lambda)
+ } else {
+ self.workers.install(lambda)
+ }
+ } else {
+ requests.into_iter().map(rasterize_blob).collect()
+ }
+ }
+}
+
+// a cross platform wrapper that creates an autorelease pool
+// on macOS
+fn autoreleasepool<T, F: FnOnce() -> T>(f: F) -> T {
+ #[cfg(target_os = "macos")]
+ {
+ objc::rc::autoreleasepool(f)
+ }
+ #[cfg(not(target_os = "macos"))]
+ {
+ f()
+ }
+}
+
+fn rasterize_blob(job: Job) -> (BlobImageRequest, BlobImageResult) {
+ let descriptor = job.descriptor;
+ let buf_size =
+ (descriptor.rect.size.width * descriptor.rect.size.height * descriptor.format.bytes_per_pixel()) as usize;
+
+ let mut output = vec![0u8; buf_size];
+
+ let dirty_rect = match job.dirty_rect {
+ DirtyRect::Partial(rect) => Some(rect),
+ DirtyRect::All => None,
+ };
+ assert!(descriptor.rect.size.width > 0 && descriptor.rect.size.height > 0);
+
+ let result = autoreleasepool(|| {
+ unsafe {
+ if wr_moz2d_render_cb(
+ ByteSlice::new(&job.commands[..]),
+ descriptor.format,
+ &descriptor.rect,
+ &job.visible_rect,
+ job.tile_size,
+ &job.request.tile,
+ dirty_rect.as_ref(),
+ MutByteSlice::new(output.as_mut_slice()),
+ ) {
+ // We want the dirty rect local to the tile rather than the whole image.
+ // TODO(nical): move that up and avoid recomupting the tile bounds in the callback
+ let dirty_rect = job.dirty_rect.to_subrect_of(&descriptor.rect);
+ let tx: BlobToDeviceTranslation = (-descriptor.rect.origin.to_vector()).into();
+ let rasterized_rect = tx.transform_rect(&dirty_rect);
+
+ Ok(RasterizedBlobImage {
+ rasterized_rect,
+ data: Arc::new(output),
+ })
+ } else {
+ panic!("Moz2D replay problem");
+ }
+ }
+ });
+
+ (job.request, result)
+}
+
+impl BlobImageHandler for Moz2dBlobImageHandler {
+ fn create_similar(&self) -> Box<dyn BlobImageHandler> {
+ Box::new(Self::new(
+ Arc::clone(&self.workers),
+ Arc::clone(&self.workers_low_priority),
+ ))
+ }
+
+ fn add(&mut self, key: BlobImageKey, data: Arc<BlobImageData>, visible_rect: &DeviceIntRect, tile_size: TileSize) {
+ {
+ let index = BlobReader::new(&data);
+ assert!(index.reader.has_more());
+ }
+ self.blob_commands.insert(
+ key,
+ BlobCommand {
+ data: Arc::clone(&data),
+ visible_rect: *visible_rect,
+ tile_size,
+ },
+ );
+ }
+
+ fn update(
+ &mut self,
+ key: BlobImageKey,
+ data: Arc<BlobImageData>,
+ visible_rect: &DeviceIntRect,
+ dirty_rect: &BlobDirtyRect,
+ ) {
+ match self.blob_commands.entry(key) {
+ hash_map::Entry::Occupied(mut e) => {
+ let command = e.get_mut();
+ let dirty_rect = if let DirtyRect::Partial(rect) = *dirty_rect {
+ Box2d {
+ x1: rect.min_x(),
+ y1: rect.min_y(),
+ x2: rect.max_x(),
+ y2: rect.max_y(),
+ }
+ } else {
+ Box2d {
+ x1: i32::MIN,
+ y1: i32::MIN,
+ x2: i32::MAX,
+ y2: i32::MAX,
+ }
+ };
+ command.data = Arc::new(merge_blob_images(
+ &command.data,
+ &data,
+ dirty_rect,
+ command.visible_rect.into(),
+ visible_rect.into(),
+ ));
+ command.visible_rect = *visible_rect;
+ }
+ _ => {
+ panic!("missing image key");
+ }
+ }
+ }
+
+ fn delete(&mut self, key: BlobImageKey) {
+ self.blob_commands.remove(&key);
+ }
+
+ fn create_blob_rasterizer(&mut self) -> Box<dyn AsyncBlobImageRasterizer> {
+ Box::new(Moz2dBlobRasterizer {
+ workers: Arc::clone(&self.workers),
+ workers_low_priority: Arc::clone(&self.workers_low_priority),
+ blob_commands: self.blob_commands.clone(),
+ enable_multithreading: self.enable_multithreading,
+ })
+ }
+
+ fn delete_font(&mut self, font: FontKey) {
+ unsafe {
+ DeleteFontData(font);
+ }
+ }
+
+ fn delete_font_instance(&mut self, key: FontInstanceKey) {
+ unsafe {
+ DeleteBlobFont(key);
+ }
+ }
+
+ fn clear_namespace(&mut self, namespace: IdNamespace) {
+ unsafe {
+ ClearBlobImageResources(namespace);
+ }
+ }
+
+ fn prepare_resources(&mut self, resources: &dyn BlobImageResources, requests: &[BlobImageParams]) {
+ for params in requests {
+ let commands = &self.blob_commands[&params.request.key];
+ let blob = Arc::clone(&commands.data);
+ self.prepare_request(&blob, resources);
+ }
+ }
+
+ fn enable_multithreading(&mut self, enable: bool) {
+ self.enable_multithreading = enable;
+ }
+}
+
+use bindings::{WrFontInstanceKey, WrFontKey, WrIdNamespace};
+
+#[allow(improper_ctypes)] // this is needed so that rustc doesn't complain about passing the &Arc<Vec> to an extern function
+extern "C" {
+ fn HasFontData(key: WrFontKey) -> bool;
+ fn AddFontData(key: WrFontKey, data: *const u8, size: usize, index: u32, vec: &ArcVecU8);
+ fn AddNativeFontHandle(key: WrFontKey, handle: *mut c_void, index: u32);
+ fn DeleteFontData(key: WrFontKey);
+ fn AddBlobFont(
+ instance_key: WrFontInstanceKey,
+ font_key: WrFontKey,
+ size: f32,
+ options: Option<&FontInstanceOptions>,
+ platform_options: Option<&FontInstancePlatformOptions>,
+ variations: *const FontVariation,
+ num_variations: usize,
+ );
+ fn DeleteBlobFont(key: WrFontInstanceKey);
+ fn ClearBlobImageResources(namespace: WrIdNamespace);
+
+}
+
+impl Moz2dBlobImageHandler {
+ /// Create a new BlobImageHandler with the given thread pool.
+ pub fn new(workers: Arc<ThreadPool>, workers_low_priority: Arc<ThreadPool>) -> Self {
+ Moz2dBlobImageHandler {
+ blob_commands: HashMap::new(),
+ workers,
+ workers_low_priority,
+ enable_multithreading: true,
+ }
+ }
+
+ /// Does early preprocessing of a blob's resources.
+ ///
+ /// Currently just sets up fonts found in the blob.
+ fn prepare_request(&self, blob: &[u8], resources: &dyn BlobImageResources) {
+ #[cfg(target_os = "windows")]
+ fn process_native_font_handle(key: FontKey, handle: &NativeFontHandle) {
+ let file = dwrote::FontFile::new_from_path(&handle.path).unwrap();
+ let face = file
+ .create_face(handle.index, dwrote::DWRITE_FONT_SIMULATIONS_NONE)
+ .unwrap();
+ unsafe { AddNativeFontHandle(key, face.as_ptr() as *mut c_void, 0) };
+ }
+
+ #[cfg(target_os = "macos")]
+ fn process_native_font_handle(key: FontKey, handle: &NativeFontHandle) {
+ unsafe { AddNativeFontHandle(key, handle.0.as_ptr() as *mut c_void, 0) };
+ }
+
+ #[cfg(not(any(target_os = "macos", target_os = "windows")))]
+ fn process_native_font_handle(key: FontKey, handle: &NativeFontHandle) {
+ let cstr = CString::new(handle.path.as_os_str().as_bytes()).unwrap();
+ unsafe { AddNativeFontHandle(key, cstr.as_ptr() as *mut c_void, handle.index) };
+ }
+
+ fn process_fonts(
+ mut extra_data: BufReader,
+ resources: &dyn BlobImageResources,
+ unscaled_fonts: &mut Vec<FontKey>,
+ scaled_fonts: &mut Vec<FontInstanceKey>,
+ ) {
+ let font_count = extra_data.read_usize();
+ for _ in 0..font_count {
+ let font = extra_data.read_blob_font();
+ if scaled_fonts.contains(&font.font_instance_key) {
+ continue;
+ }
+ scaled_fonts.push(font.font_instance_key);
+ if let Some(instance) = resources.get_font_instance_data(font.font_instance_key) {
+ if !unscaled_fonts.contains(&instance.font_key) {
+ unscaled_fonts.push(instance.font_key);
+ if !unsafe { HasFontData(instance.font_key) } {
+ let template = resources.get_font_data(instance.font_key);
+ match template {
+ &FontTemplate::Raw(ref data, ref index) => unsafe {
+ AddFontData(instance.font_key, data.as_ptr(), data.len(), *index, data);
+ },
+ &FontTemplate::Native(ref handle) => {
+ process_native_font_handle(instance.font_key, handle);
+ }
+ }
+ }
+ }
+ unsafe {
+ AddBlobFont(
+ font.font_instance_key,
+ instance.font_key,
+ instance.size,
+ instance.options.as_ref(),
+ instance.platform_options.as_ref(),
+ instance.variations.as_ptr(),
+ instance.variations.len(),
+ );
+ }
+ }
+ }
+ }
+
+ {
+ let mut index = BlobReader::new(blob);
+ let mut unscaled_fonts = Vec::new();
+ let mut scaled_fonts = Vec::new();
+ while index.reader.pos < index.reader.buf.len() {
+ let e = index.read_entry();
+ process_fonts(
+ BufReader::new(&blob[e.end..e.extra_end]),
+ resources,
+ &mut unscaled_fonts,
+ &mut scaled_fonts,
+ );
+ }
+ }
+ }
+}
diff --git a/gfx/webrender_bindings/src/program_cache.rs b/gfx/webrender_bindings/src/program_cache.rs
new file mode 100644
index 0000000000..8008603eb8
--- /dev/null
+++ b/gfx/webrender_bindings/src/program_cache.rs
@@ -0,0 +1,356 @@
+/* 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::cell::RefCell;
+use std::ffi::OsString;
+use std::fs::{create_dir_all, read_dir, read_to_string, File};
+use std::io::{Error, ErrorKind};
+use std::io::{Read, Write};
+use std::path::PathBuf;
+use std::rc::Rc;
+use std::sync::Arc;
+
+use bincode;
+use fxhash;
+use nsstring::nsAString;
+use rayon::ThreadPool;
+use webrender::{ProgramBinary, ProgramCache, ProgramCacheObserver, ProgramSourceDigest};
+
+const MAX_LOAD_TIME_MS: u64 = 400;
+
+fn deserialize_program_binary(path: &PathBuf) -> Result<Arc<ProgramBinary>, Error> {
+ let mut buf = vec![];
+ let mut file = File::open(path)?;
+ file.read_to_end(&mut buf)?;
+
+ if buf.len() <= 8 + 4 {
+ return Err(Error::new(ErrorKind::InvalidData, "File size is too small"));
+ }
+ let magic = &buf[0..4];
+ let hash = &buf[4..8 + 4];
+ let data = &buf[8 + 4..];
+
+ // Check if magic + version are correct.
+ let mv: u32 = bincode::deserialize(&magic).unwrap();
+ if mv != MAGIC_AND_VERSION {
+ return Err(Error::new(
+ ErrorKind::InvalidData,
+ "File data is invalid (magic+version)",
+ ));
+ }
+
+ // Check if hash is correct
+ let hash: u64 = bincode::deserialize(&hash).unwrap();
+ let hash_data = fxhash::hash64(&data);
+ if hash != hash_data {
+ return Err(Error::new(ErrorKind::InvalidData, "File data is invalid (hash)"));
+ }
+
+ // Deserialize ProgramBinary
+ let binary = match bincode::deserialize(&data) {
+ Ok(binary) => binary,
+ Err(_) => {
+ return Err(Error::new(
+ ErrorKind::InvalidData,
+ "Failed to deserialize ProgramBinary",
+ ))
+ }
+ };
+
+ Ok(Arc::new(binary))
+}
+
+#[cfg(target_os = "windows")]
+fn get_cache_path_from_prof_path(prof_path: &nsAString) -> Option<PathBuf> {
+ if prof_path.is_empty() {
+ // Empty means that we do not use disk cache.
+ return None;
+ }
+
+ use std::os::windows::prelude::*;
+
+ let prof_path = OsString::from_wide(prof_path.as_ref());
+ let mut cache_path = PathBuf::from(&prof_path);
+ cache_path.push("shader-cache");
+
+ Some(cache_path)
+}
+
+#[cfg(not(target_os = "windows"))]
+fn get_cache_path_from_prof_path(prof_path: &nsAString) -> Option<PathBuf> {
+ if prof_path.is_empty() {
+ // Empty means that we do not use disk cache.
+ return None;
+ }
+
+ let utf8 = String::from_utf16(prof_path.as_ref()).unwrap();
+ let prof_path = OsString::from(utf8);
+ let mut cache_path = PathBuf::from(&prof_path);
+ cache_path.push("shader-cache");
+
+ Some(cache_path)
+}
+
+struct WrProgramBinaryDiskCache {
+ cache_path: PathBuf,
+ workers: Arc<ThreadPool>,
+ cached_shader_filenames: Vec<OsString>,
+}
+
+// Magic number + version. Increment the version when the binary format changes.
+const MAGIC: u32 = 0xB154AD30; // BI-SHADE + version.
+const VERSION: u32 = 2;
+const MAGIC_AND_VERSION: u32 = MAGIC + VERSION;
+
+const WHITELIST_FILENAME: &str = "startup_shaders";
+const WHITELIST_SEPARATOR: &str = "\n";
+
+/// Helper to convert a closure returning a `Result` to one that returns void.
+/// This allows the enclosed code to use the question-mark operator in a
+/// context where the calling function doesn't expect a `Result`.
+#[allow(unused_must_use)]
+fn result_to_void<F: FnOnce() -> Result<(), ()>>(f: F) {
+ f();
+}
+
+impl WrProgramBinaryDiskCache {
+ #[allow(dead_code)]
+ fn new(cache_path: PathBuf, workers: &Arc<ThreadPool>) -> Self {
+ WrProgramBinaryDiskCache {
+ cache_path,
+ workers: Arc::clone(workers),
+ cached_shader_filenames: Vec::new(),
+ }
+ }
+
+ /// Saves the specified binaries to the on-disk shader cache.
+ fn save_shaders_to_disk(&mut self, entries: Vec<Arc<ProgramBinary>>) {
+ info!("Saving binaries to on-disk shader cache");
+
+ // Write the entries to disk on a worker thread.
+ for entry in entries {
+ let file_name = entry.source_digest().to_string();
+ let file_path = self.cache_path.join(&file_name);
+
+ self.workers.spawn(move || {
+ result_to_void(move || {
+ info!("Writing shader: {}", file_name);
+
+ use std::time::Instant;
+ let start = Instant::now();
+
+ let data: Vec<u8> =
+ bincode::serialize(&*entry).map_err(|e| error!("shader-cache: Failed to serialize: {}", e))?;
+
+ let mut file =
+ File::create(&file_path).map_err(|e| error!("shader-cache: Failed to create file: {}", e))?;
+
+ // Write magic + version.
+ let mv = MAGIC_AND_VERSION;
+ let mv = bincode::serialize(&mv).unwrap();
+ assert!(mv.len() == 4);
+ file.write_all(&mv)
+ .map_err(|e| error!("shader-cache: Failed to write magic + version: {}", e))?;
+
+ // Write hash
+ let hash = fxhash::hash64(&data);
+ let hash = bincode::serialize(&hash).unwrap();
+ assert!(hash.len() == 8);
+ file.write_all(&hash)
+ .map_err(|e| error!("shader-cache: Failed to write hash: {}", e))?;
+
+ // Write serialized data
+ file.write_all(&data)
+ .map_err(|e| error!("shader-cache: Failed to write program binary: {}", e))?;
+
+ info!("Wrote shader {} in {:?}", file_name, start.elapsed());
+ Ok(())
+ })
+ });
+ }
+ }
+
+ /// Writes the whitelist containing the set of startup shaders to disk.
+ fn set_startup_shaders(&mut self, entries: Vec<Arc<ProgramBinary>>) {
+ let whitelist = entries
+ .iter()
+ .map(|e| e.source_digest().to_string())
+ .collect::<Vec<String>>()
+ .join(WHITELIST_SEPARATOR);
+
+ let mut whitelist_path = self.cache_path.clone();
+ whitelist_path.push(WHITELIST_FILENAME);
+ self.workers.spawn(move || {
+ result_to_void(move || {
+ info!("Writing startup shader whitelist");
+ File::create(&whitelist_path)
+ .and_then(|mut file| file.write_all(whitelist.as_bytes()))
+ .map_err(|e| error!("shader-cache: Failed to write startup whitelist: {}", e))?;
+ Ok(())
+ })
+ });
+ }
+
+ pub fn try_load_shader_from_disk(&mut self, filename: &str, program_cache: &Rc<ProgramCache>) {
+ if let Some(index) = self.cached_shader_filenames.iter().position(|e| e == filename) {
+ let mut path = self.cache_path.clone();
+ path.push(filename);
+
+ self.cached_shader_filenames.swap_remove(index);
+
+ info!("Loading shader: {}", filename);
+
+ match deserialize_program_binary(&path) {
+ Ok(program) => {
+ program_cache.load_program_binary(program);
+ }
+ Err(err) => {
+ error!("shader-cache: Failed to deserialize program binary: {}", err);
+ }
+ };
+ } else {
+ info!("shader-cache: Program binary not found in disk cache");
+ }
+ }
+
+ pub fn try_load_startup_shaders_from_disk(&mut self, program_cache: &Rc<ProgramCache>) {
+ use std::time::Instant;
+ let start = Instant::now();
+
+ // Load and parse the whitelist if it exists
+ let mut whitelist_path = self.cache_path.clone();
+ whitelist_path.push(WHITELIST_FILENAME);
+ let whitelist = match read_to_string(&whitelist_path) {
+ Ok(whitelist) => whitelist
+ .split(WHITELIST_SEPARATOR)
+ .map(|s| s.to_string())
+ .collect::<Vec<String>>(),
+ Err(err) => {
+ info!("shader-cache: Could not read startup whitelist: {}", err);
+ Vec::new()
+ }
+ };
+ info!("Loaded startup shader whitelist in {:?}", start.elapsed());
+
+ self.cached_shader_filenames = read_dir(&self.cache_path)
+ .map_err(|err| {
+ error!(
+ "shader-cache: Error reading directory whilst loading startup shaders: {}",
+ err
+ )
+ })
+ .into_iter()
+ .flatten()
+ .filter_map(Result::ok)
+ .map(|entry| entry.file_name())
+ .filter(|filename| filename != WHITELIST_FILENAME)
+ .collect::<Vec<OsString>>();
+
+ // Load whitelisted program binaries if they exist
+ for entry in &whitelist {
+ self.try_load_shader_from_disk(&entry, program_cache);
+
+ let elapsed = start.elapsed();
+ info!("Loaded shader in {:?}", elapsed);
+ let elapsed_ms = (elapsed.as_secs() * 1_000) + elapsed.subsec_millis() as u64;
+
+ if elapsed_ms > MAX_LOAD_TIME_MS {
+ // Loading the startup shaders is taking too long, so bail out now.
+ // Additionally clear the list of remaining shaders cached on disk,
+ // so that we do not attempt to load any on demand during rendering.
+ error!("shader-cache: Timed out before finishing loads");
+ self.cached_shader_filenames.clear();
+ break;
+ }
+ }
+ }
+}
+
+pub struct WrProgramCacheObserver {
+ disk_cache: Rc<RefCell<WrProgramBinaryDiskCache>>,
+}
+
+impl WrProgramCacheObserver {
+ #[allow(dead_code)]
+ fn new(disk_cache: Rc<RefCell<WrProgramBinaryDiskCache>>) -> Self {
+ WrProgramCacheObserver { disk_cache }
+ }
+}
+
+impl ProgramCacheObserver for WrProgramCacheObserver {
+ fn save_shaders_to_disk(&self, entries: Vec<Arc<ProgramBinary>>) {
+ self.disk_cache.borrow_mut().save_shaders_to_disk(entries);
+ }
+
+ fn set_startup_shaders(&self, entries: Vec<Arc<ProgramBinary>>) {
+ self.disk_cache.borrow_mut().set_startup_shaders(entries);
+ }
+
+ fn try_load_shader_from_disk(&self, digest: &ProgramSourceDigest, program_cache: &Rc<ProgramCache>) {
+ let filename = digest.to_string();
+ self.disk_cache
+ .borrow_mut()
+ .try_load_shader_from_disk(&filename, program_cache);
+ }
+
+ fn notify_program_binary_failed(&self, _program_binary: &Arc<ProgramBinary>) {
+ error!("shader-cache: Failed program_binary");
+ }
+}
+
+pub struct WrProgramCache {
+ pub program_cache: Rc<ProgramCache>,
+ disk_cache: Option<Rc<RefCell<WrProgramBinaryDiskCache>>>,
+}
+
+impl WrProgramCache {
+ pub fn new(prof_path: &nsAString, workers: &Arc<ThreadPool>) -> Self {
+ let cache_path = get_cache_path_from_prof_path(prof_path);
+ let use_disk_cache = cache_path.as_ref().map_or(false, |p| create_dir_all(p).is_ok());
+ let (disk_cache, program_cache_observer) = if use_disk_cache {
+ let cache = Rc::new(RefCell::new(WrProgramBinaryDiskCache::new(
+ cache_path.unwrap(),
+ workers,
+ )));
+ let obs = Box::new(WrProgramCacheObserver::new(Rc::clone(&cache))) as Box<dyn ProgramCacheObserver>;
+ (Some(cache), Some(obs))
+ } else {
+ (None, None)
+ };
+ let program_cache = ProgramCache::new(program_cache_observer);
+
+ WrProgramCache {
+ program_cache,
+ disk_cache,
+ }
+ }
+
+ pub fn rc_get(&self) -> &Rc<ProgramCache> {
+ &self.program_cache
+ }
+
+ pub fn try_load_startup_shaders_from_disk(&self) {
+ if let Some(ref disk_cache) = self.disk_cache {
+ disk_cache
+ .borrow_mut()
+ .try_load_startup_shaders_from_disk(&self.program_cache);
+ } else {
+ error!("shader-cache: Shader disk cache is not supported");
+ }
+ }
+}
+
+pub fn remove_disk_cache(prof_path: &nsAString) -> Result<(), Error> {
+ use std::fs::remove_dir_all;
+ use std::time::Instant;
+
+ if let Some(cache_path) = get_cache_path_from_prof_path(prof_path) {
+ if cache_path.exists() {
+ let start = Instant::now();
+ remove_dir_all(&cache_path)?;
+ info!("removed all disk cache shaders in {:?}", start.elapsed());
+ }
+ }
+ Ok(())
+}
diff --git a/gfx/webrender_bindings/src/swgl_bindings.rs b/gfx/webrender_bindings/src/swgl_bindings.rs
new file mode 100644
index 0000000000..c5a9434b9b
--- /dev/null
+++ b/gfx/webrender_bindings/src/swgl_bindings.rs
@@ -0,0 +1,1766 @@
+/* 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 bindings::{GeckoProfilerThreadListener, WrCompositor};
+use gleam::{gl, gl::GLenum, gl::Gl};
+use std::cell::{Cell, UnsafeCell};
+use std::collections::{hash_map::HashMap, VecDeque};
+use std::ops::{Deref, DerefMut};
+use std::os::raw::c_void;
+use std::ptr;
+use std::rc::Rc;
+use std::sync::atomic::{AtomicIsize, AtomicPtr, AtomicU32, AtomicU8, Ordering};
+use std::sync::{Arc, Condvar, Mutex, MutexGuard};
+use std::thread;
+use webrender::{
+ api::units::*, api::ColorDepth, api::ExternalImageId, api::ImageRendering, api::YuvColorSpace, Compositor,
+ CompositorCapabilities, CompositorSurfaceTransform, NativeSurfaceId, NativeSurfaceInfo, NativeTileId,
+ ThreadListener,
+};
+
+#[no_mangle]
+pub extern "C" fn wr_swgl_create_context() -> *mut c_void {
+ swgl::Context::create().into()
+}
+
+#[no_mangle]
+pub extern "C" fn wr_swgl_reference_context(ctx: *mut c_void) {
+ swgl::Context::from(ctx).reference();
+}
+
+#[no_mangle]
+pub extern "C" fn wr_swgl_destroy_context(ctx: *mut c_void) {
+ swgl::Context::from(ctx).destroy();
+}
+
+#[no_mangle]
+pub extern "C" fn wr_swgl_make_current(ctx: *mut c_void) {
+ swgl::Context::from(ctx).make_current();
+}
+
+#[no_mangle]
+pub extern "C" fn wr_swgl_init_default_framebuffer(
+ ctx: *mut c_void,
+ x: i32,
+ y: i32,
+ width: i32,
+ height: i32,
+ stride: i32,
+ buf: *mut c_void,
+) {
+ swgl::Context::from(ctx).init_default_framebuffer(x, y, width, height, stride, buf);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_swgl_gen_texture(ctx: *mut c_void) -> u32 {
+ swgl::Context::from(ctx).gen_textures(1)[0]
+}
+
+#[no_mangle]
+pub extern "C" fn wr_swgl_delete_texture(ctx: *mut c_void, tex: u32) {
+ swgl::Context::from(ctx).delete_textures(&[tex]);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_swgl_set_texture_parameter(ctx: *mut c_void, tex: u32, pname: u32, param: i32) {
+ swgl::Context::from(ctx).set_texture_parameter(tex, pname, param);
+}
+
+#[no_mangle]
+pub extern "C" fn wr_swgl_set_texture_buffer(
+ ctx: *mut c_void,
+ tex: u32,
+ internal_format: u32,
+ width: i32,
+ height: i32,
+ stride: i32,
+ buf: *mut c_void,
+ min_width: i32,
+ min_height: i32,
+) {
+ swgl::Context::from(ctx).set_texture_buffer(
+ tex,
+ internal_format,
+ width,
+ height,
+ stride,
+ buf,
+ min_width,
+ min_height,
+ );
+}
+
+/// Descriptor for a locked surface that will be directly composited by SWGL.
+#[repr(C)]
+struct WrSWGLCompositeSurfaceInfo {
+ /// The number of YUV planes in the surface. 0 indicates non-YUV BGRA.
+ /// 1 is interleaved YUV. 2 is NV12. 3 is planar YUV.
+ yuv_planes: u32,
+ /// Textures for planes of the surface, or 0 if not applicable.
+ textures: [u32; 3],
+ /// Color space of surface if using a YUV format.
+ color_space: YuvColorSpace,
+ /// Color depth of surface if using a YUV format.
+ color_depth: ColorDepth,
+ /// The actual source surface size before transformation.
+ size: DeviceIntSize,
+}
+
+extern "C" {
+ fn wr_swgl_lock_composite_surface(
+ ctx: *mut c_void,
+ external_image_id: ExternalImageId,
+ composite_info: *mut WrSWGLCompositeSurfaceInfo,
+ ) -> bool;
+ fn wr_swgl_unlock_composite_surface(ctx: *mut c_void, external_image_id: ExternalImageId);
+}
+
+pub struct SwTile {
+ x: i32,
+ y: i32,
+ fbo_id: u32,
+ color_id: u32,
+ tex_id: u32,
+ pbo_id: u32,
+ dirty_rect: DeviceIntRect,
+ valid_rect: DeviceIntRect,
+ /// Composition of tiles must be ordered such that any tiles that may overlap
+ /// an invalidated tile in an earlier surface only get drawn after that tile
+ /// is actually updated. We store a count of the number of overlapping invalid
+ /// here, that gets decremented when the invalid tiles are finally updated so
+ /// that we know when it is finally safe to draw. Must use a Cell as we might
+ /// be analyzing multiple tiles and surfaces
+ overlaps: Cell<u32>,
+ /// Whether the tile's contents has been invalidated
+ invalid: Cell<bool>,
+ /// Graph node for job dependencies of this tile
+ graph_node: SwCompositeGraphNodeRef,
+}
+
+impl SwTile {
+ fn new(x: i32, y: i32) -> Self {
+ SwTile {
+ x,
+ y,
+ fbo_id: 0,
+ color_id: 0,
+ tex_id: 0,
+ pbo_id: 0,
+ dirty_rect: DeviceIntRect::zero(),
+ valid_rect: DeviceIntRect::zero(),
+ overlaps: Cell::new(0),
+ invalid: Cell::new(false),
+ graph_node: SwCompositeGraphNode::new(),
+ }
+ }
+
+ fn origin(&self, surface: &SwSurface) -> DeviceIntPoint {
+ DeviceIntPoint::new(self.x * surface.tile_size.width, self.y * surface.tile_size.height)
+ }
+
+ /// Bounds used for determining overlap dependencies. This may either be the
+ /// full tile bounds or the actual valid rect, depending on whether the tile
+ /// is invalidated this frame. These bounds are more conservative as such and
+ /// may differ from the precise bounds used to actually composite the tile.
+ fn overlap_rect(
+ &self,
+ surface: &SwSurface,
+ transform: &CompositorSurfaceTransform,
+ clip_rect: &DeviceIntRect,
+ ) -> Option<DeviceIntRect> {
+ let origin = self.origin(surface);
+ let bounds = self.valid_rect.translate(origin.to_vector());
+ let device_rect = transform.outer_transformed_rect(&bounds.to_f32())?.round_out().to_i32();
+ device_rect.intersection(clip_rect)
+ }
+
+ /// Determine if the tile's bounds may overlap the dependency rect if it were
+ /// to be composited at the given position.
+ fn may_overlap(
+ &self,
+ surface: &SwSurface,
+ transform: &CompositorSurfaceTransform,
+ clip_rect: &DeviceIntRect,
+ dep_rect: &DeviceIntRect,
+ ) -> bool {
+ self.overlap_rect(surface, transform, clip_rect)
+ .map_or(false, |r| r.intersects(dep_rect))
+ }
+
+ /// Get valid source and destination rectangles for composition of the tile
+ /// within a surface, bounded by the clipping rectangle. May return None if
+ /// it falls outside of the clip rect.
+ fn composite_rects(
+ &self,
+ surface: &SwSurface,
+ transform: &CompositorSurfaceTransform,
+ clip_rect: &DeviceIntRect,
+ ) -> Option<(DeviceIntRect, DeviceIntRect, bool)> {
+ // Offset the valid rect to the appropriate surface origin.
+ let valid = self.valid_rect.translate(self.origin(surface).to_vector());
+ // The destination rect is the valid rect transformed and then clipped.
+ let dest_rect = transform.outer_transformed_rect(&valid.to_f32())?.round_out().to_i32();
+ if !dest_rect.intersects(clip_rect) {
+ return None;
+ }
+ // To get a valid source rect, we need to inverse transform the clipped destination rect to find out the effect
+ // of the clip rect in source-space. After this, we subtract off the source-space valid rect origin to get
+ // a source rect that is now relative to the surface origin rather than absolute.
+ let inv_transform = transform.inverse()?;
+ let src_rect = inv_transform
+ .outer_transformed_rect(&dest_rect.to_f32())?
+ .round()
+ .to_i32()
+ .translate(-valid.origin.to_vector());
+ Some((src_rect, dest_rect, transform.m22 < 0.0))
+ }
+}
+
+pub struct SwSurface {
+ tile_size: DeviceIntSize,
+ is_opaque: bool,
+ tiles: Vec<SwTile>,
+ /// An attached external image for this surface.
+ external_image: Option<ExternalImageId>,
+ /// Descriptor for the external image if successfully locked for composite.
+ composite_surface: Option<WrSWGLCompositeSurfaceInfo>,
+}
+
+impl SwSurface {
+ fn new(tile_size: DeviceIntSize, is_opaque: bool) -> Self {
+ SwSurface {
+ tile_size,
+ is_opaque,
+ tiles: Vec::new(),
+ external_image: None,
+ composite_surface: None,
+ }
+ }
+}
+
+fn image_rendering_to_gl_filter(filter: ImageRendering) -> gl::GLenum {
+ match filter {
+ ImageRendering::Pixelated => gl::NEAREST,
+ ImageRendering::Auto | ImageRendering::CrispEdges => gl::LINEAR,
+ }
+}
+
+struct DrawTileHelper {
+ gl: Rc<dyn gl::Gl>,
+ prog: u32,
+ quad_vbo: u32,
+ quad_vao: u32,
+ dest_matrix_loc: i32,
+ tex_matrix_loc: i32,
+}
+
+impl DrawTileHelper {
+ fn new(gl: Rc<dyn gl::Gl>) -> Self {
+ let quad_vbo = gl.gen_buffers(1)[0];
+ gl.bind_buffer(gl::ARRAY_BUFFER, quad_vbo);
+ let quad_data: [f32; 8] = [0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0];
+ gl::buffer_data(&*gl, gl::ARRAY_BUFFER, &quad_data, gl::STATIC_DRAW);
+
+ let quad_vao = gl.gen_vertex_arrays(1)[0];
+ gl.bind_vertex_array(quad_vao);
+ gl.enable_vertex_attrib_array(0);
+ gl.vertex_attrib_pointer(0, 2, gl::FLOAT, false, 0, 0);
+ gl.bind_vertex_array(0);
+
+ let version = match gl.get_type() {
+ gl::GlType::Gl => "#version 150",
+ gl::GlType::Gles => "#version 300 es",
+ };
+ let vert_source = "
+ in vec2 aVert;
+ uniform mat3 uDestMatrix;
+ uniform mat3 uTexMatrix;
+ out vec2 vTexCoord;
+ void main(void) {
+ gl_Position = vec4((uDestMatrix * vec3(aVert, 1.0)).xy, 0.0, 1.0);
+ vTexCoord = (uTexMatrix * vec3(aVert, 1.0)).xy;
+ }
+ ";
+ let vs = gl.create_shader(gl::VERTEX_SHADER);
+ gl.shader_source(vs, &[version.as_bytes(), vert_source.as_bytes()]);
+ gl.compile_shader(vs);
+ let frag_source = "
+ #ifdef GL_ES
+ #ifdef GL_FRAGMENT_PRECISION_HIGH
+ precision highp float;
+ #else
+ precision mediump float;
+ #endif
+ #endif
+ in vec2 vTexCoord;
+ out vec4 oFragColor;
+ uniform sampler2D uTex;
+ void main(void) {
+ oFragColor = texture(uTex, vTexCoord);
+ }
+ ";
+ let fs = gl.create_shader(gl::FRAGMENT_SHADER);
+ gl.shader_source(fs, &[version.as_bytes(), frag_source.as_bytes()]);
+ gl.compile_shader(fs);
+
+ let prog = gl.create_program();
+ gl.attach_shader(prog, vs);
+ gl.attach_shader(prog, fs);
+ gl.bind_attrib_location(prog, 0, "aVert");
+ gl.link_program(prog);
+
+ let mut status = [0];
+ unsafe {
+ gl.get_program_iv(prog, gl::LINK_STATUS, &mut status);
+ }
+ assert!(status[0] != 0);
+
+ //println!("vert: {}", gl.get_shader_info_log(vs));
+ //println!("frag: {}", gl.get_shader_info_log(fs));
+ //println!("status: {}, {}", status[0], gl.get_program_info_log(prog));
+
+ gl.use_program(prog);
+ let dest_matrix_loc = gl.get_uniform_location(prog, "uDestMatrix");
+ assert!(dest_matrix_loc != -1);
+ let tex_matrix_loc = gl.get_uniform_location(prog, "uTexMatrix");
+ assert!(tex_matrix_loc != -1);
+ let tex_loc = gl.get_uniform_location(prog, "uTex");
+ assert!(tex_loc != -1);
+ gl.uniform_1i(tex_loc, 0);
+ gl.use_program(0);
+
+ gl.delete_shader(vs);
+ gl.delete_shader(fs);
+
+ DrawTileHelper {
+ gl,
+ prog,
+ quad_vao,
+ quad_vbo,
+ dest_matrix_loc,
+ tex_matrix_loc,
+ }
+ }
+
+ fn deinit(&self) {
+ self.gl.delete_program(self.prog);
+ self.gl.delete_vertex_arrays(&[self.quad_vao]);
+ self.gl.delete_buffers(&[self.quad_vbo]);
+ }
+
+ fn enable(&self, viewport: &DeviceIntRect) {
+ self.gl.viewport(
+ viewport.origin.x,
+ viewport.origin.y,
+ viewport.size.width,
+ viewport.size.height,
+ );
+ self.gl.bind_vertex_array(self.quad_vao);
+ self.gl.use_program(self.prog);
+ self.gl.active_texture(gl::TEXTURE0);
+ }
+
+ fn draw(
+ &self,
+ viewport: &DeviceIntRect,
+ dest: &DeviceIntRect,
+ src: &DeviceIntRect,
+ _clip: &DeviceIntRect,
+ surface: &SwSurface,
+ tile: &SwTile,
+ flip_y: bool,
+ filter: GLenum,
+ ) {
+ let dx = dest.origin.x as f32 / viewport.size.width as f32;
+ let dy = dest.origin.y as f32 / viewport.size.height as f32;
+ let dw = dest.size.width as f32 / viewport.size.width as f32;
+ let dh = dest.size.height as f32 / viewport.size.height as f32;
+ self.gl.uniform_matrix_3fv(
+ self.dest_matrix_loc,
+ false,
+ &[
+ 2.0 * dw,
+ 0.0,
+ 0.0,
+ 0.0,
+ if flip_y { 2.0 * dh } else { -2.0 * dh },
+ 0.0,
+ -1.0 + 2.0 * dx,
+ if flip_y { -1.0 + 2.0 * dy } else { 1.0 - 2.0 * dy },
+ 1.0,
+ ],
+ );
+ let sx = src.origin.x as f32 / surface.tile_size.width as f32;
+ let sy = src.origin.y as f32 / surface.tile_size.height as f32;
+ let sw = src.size.width as f32 / surface.tile_size.width as f32;
+ let sh = src.size.height as f32 / surface.tile_size.height as f32;
+ self.gl
+ .uniform_matrix_3fv(self.tex_matrix_loc, false, &[sw, 0.0, 0.0, 0.0, sh, 0.0, sx, sy, 1.0]);
+
+ self.gl.bind_texture(gl::TEXTURE_2D, tile.tex_id);
+ self.gl
+ .tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, filter as gl::GLint);
+ self.gl
+ .tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, filter as gl::GLint);
+ self.gl.draw_arrays(gl::TRIANGLE_STRIP, 0, 4);
+ }
+
+ fn disable(&self) {
+ self.gl.use_program(0);
+ self.gl.bind_vertex_array(0);
+ }
+}
+
+/// A source for a composite job which can either be a single BGRA locked SWGL
+/// resource or a collection of SWGL resources representing a YUV surface.
+#[derive(Clone)]
+enum SwCompositeSource {
+ BGRA(swgl::LockedResource),
+ YUV(
+ swgl::LockedResource,
+ swgl::LockedResource,
+ swgl::LockedResource,
+ YuvColorSpace,
+ ColorDepth,
+ ),
+}
+
+/// Mark ExternalImage's renderer field as safe to send to SwComposite thread.
+unsafe impl Send for SwCompositeSource {}
+
+/// A tile composition job to be processed by the SwComposite thread.
+/// Stores relevant details about the tile and where to composite it.
+#[derive(Clone)]
+struct SwCompositeJob {
+ /// Locked texture that will be unlocked immediately following the job
+ locked_src: SwCompositeSource,
+ /// Locked framebuffer that may be shared among many jobs
+ locked_dst: swgl::LockedResource,
+ src_rect: DeviceIntRect,
+ dst_rect: DeviceIntRect,
+ clipped_dst: DeviceIntRect,
+ opaque: bool,
+ flip_y: bool,
+ filter: ImageRendering,
+ /// The total number of bands for this job
+ num_bands: u8,
+}
+
+impl SwCompositeJob {
+ /// Process a composite job
+ fn process(&self, band_index: u8) {
+ // Calculate the Y extents for the job's band, starting at the current index and spanning to
+ // the following index.
+ let band_index = band_index as i32;
+ let num_bands = self.num_bands as i32;
+ let band_offset = (self.clipped_dst.size.height * band_index) / num_bands;
+ let band_height = (self.clipped_dst.size.height * (band_index + 1)) / num_bands - band_offset;
+ // Create a rect that is the intersection of the band with the clipped dest
+ let band_clip = DeviceIntRect::new(
+ DeviceIntPoint::new(self.clipped_dst.origin.x, self.clipped_dst.origin.y + band_offset),
+ DeviceIntSize::new(self.clipped_dst.size.width, band_height),
+ );
+ match self.locked_src {
+ SwCompositeSource::BGRA(ref resource) => {
+ self.locked_dst.composite(
+ resource,
+ self.src_rect.origin.x,
+ self.src_rect.origin.y,
+ self.src_rect.size.width,
+ self.src_rect.size.height,
+ self.dst_rect.origin.x,
+ self.dst_rect.origin.y,
+ self.dst_rect.size.width,
+ self.dst_rect.size.height,
+ self.opaque,
+ self.flip_y,
+ image_rendering_to_gl_filter(self.filter),
+ band_clip.origin.x,
+ band_clip.origin.y,
+ band_clip.size.width,
+ band_clip.size.height,
+ );
+ }
+ SwCompositeSource::YUV(ref y, ref u, ref v, color_space, color_depth) => {
+ let swgl_color_space = match color_space {
+ YuvColorSpace::Rec601 => swgl::YUVColorSpace::Rec601,
+ YuvColorSpace::Rec709 => swgl::YUVColorSpace::Rec709,
+ YuvColorSpace::Rec2020 => swgl::YUVColorSpace::Rec2020,
+ YuvColorSpace::Identity => swgl::YUVColorSpace::Identity,
+ };
+ self.locked_dst.composite_yuv(
+ y,
+ u,
+ v,
+ swgl_color_space,
+ color_depth.bit_depth(),
+ self.src_rect.origin.x,
+ self.src_rect.origin.y,
+ self.src_rect.size.width,
+ self.src_rect.size.height,
+ self.dst_rect.origin.x,
+ self.dst_rect.origin.y,
+ self.dst_rect.size.width,
+ self.dst_rect.size.height,
+ self.flip_y,
+ band_clip.origin.x,
+ band_clip.origin.y,
+ band_clip.size.width,
+ band_clip.size.height,
+ );
+ }
+ }
+ }
+}
+
+/// A reference to a SwCompositeGraph node that can be passed from the render
+/// thread to the SwComposite thread. Consistency of mutation is ensured in
+/// SwCompositeGraphNode via use of Atomic operations that prevent more than
+/// one thread from mutating SwCompositeGraphNode at once. This avoids using
+/// messy and not-thread-safe RefCells or expensive Mutexes inside the graph
+/// node and at least signals to the compiler that potentially unsafe coercions
+/// are occurring.
+#[derive(Clone)]
+struct SwCompositeGraphNodeRef(Arc<UnsafeCell<SwCompositeGraphNode>>);
+
+impl SwCompositeGraphNodeRef {
+ fn new(graph_node: SwCompositeGraphNode) -> Self {
+ SwCompositeGraphNodeRef(Arc::new(UnsafeCell::new(graph_node)))
+ }
+
+ fn get(&self) -> &SwCompositeGraphNode {
+ unsafe { &*self.0.get() }
+ }
+
+ fn get_mut(&self) -> &mut SwCompositeGraphNode {
+ unsafe { &mut *self.0.get() }
+ }
+
+ fn get_ptr_mut(&self) -> *mut SwCompositeGraphNode {
+ self.0.get()
+ }
+}
+
+unsafe impl Send for SwCompositeGraphNodeRef {}
+
+impl Deref for SwCompositeGraphNodeRef {
+ type Target = SwCompositeGraphNode;
+
+ fn deref(&self) -> &Self::Target {
+ self.get()
+ }
+}
+
+impl DerefMut for SwCompositeGraphNodeRef {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ self.get_mut()
+ }
+}
+
+/// Dependency graph of composite jobs to be completed. Keeps a list of child jobs that are dependent on the completion of this job.
+/// Also keeps track of the number of parent jobs that this job is dependent upon before it can be processed. Once there are no more
+/// in-flight parent jobs that it depends on, the graph node is finally added to the job queue for processing.
+struct SwCompositeGraphNode {
+ /// Job to be queued for this graph node once ready.
+ job: Option<SwCompositeJob>,
+ /// The maximum number of available bands associated with this job.
+ max_bands: AtomicU8,
+ /// The number of remaining bands associated with this job. When this is
+ /// non-zero and the node has no more parents left, then the node is being
+ /// actively used by the composite thread to process jobs. Once it hits
+ /// zero, the owning thread (which brought it to zero) can safely retire
+ /// the node as no other thread is using it.
+ remaining_bands: AtomicU8,
+ /// The number of bands that have been taken for processing.
+ band_index: AtomicU8,
+ /// Count of parents this graph node depends on. While this is non-zero the
+ /// node must ensure that it is only being actively mutated by the render
+ /// thread and otherwise never being accessed by the render thread.
+ parents: AtomicU32,
+ /// Graph nodes of child jobs that are dependent on this job
+ children: Vec<SwCompositeGraphNodeRef>,
+}
+
+unsafe impl Sync for SwCompositeGraphNode {}
+
+impl SwCompositeGraphNode {
+ fn new() -> SwCompositeGraphNodeRef {
+ SwCompositeGraphNodeRef::new(SwCompositeGraphNode {
+ job: None,
+ max_bands: AtomicU8::new(0),
+ remaining_bands: AtomicU8::new(0),
+ band_index: AtomicU8::new(0),
+ parents: AtomicU32::new(0),
+ children: Vec::new(),
+ })
+ }
+
+ /// Reset the node's state for a new frame
+ fn reset(&mut self) {
+ self.job = None;
+ self.max_bands.store(0, Ordering::SeqCst);
+ self.remaining_bands.store(0, Ordering::SeqCst);
+ self.band_index.store(0, Ordering::SeqCst);
+ // Initialize parents to 1 as sentinel dependency for uninitialized job
+ // to avoid queuing unitialized job as unblocked child dependency.
+ self.parents.store(1, Ordering::SeqCst);
+ self.children.clear();
+ }
+
+ /// Add a dependent child node to dependency list. Update its parent count.
+ fn add_child(&mut self, child: SwCompositeGraphNodeRef) {
+ child.parents.fetch_add(1, Ordering::SeqCst);
+ self.children.push(child);
+ }
+
+ /// Install a job for this node. Return whether or not the job has any unprocessed parents
+ /// that would block immediate composition.
+ fn set_job(&mut self, job: SwCompositeJob, num_bands: u8) -> bool {
+ self.job = Some(job);
+ self.max_bands.store(num_bands, Ordering::SeqCst);
+ self.remaining_bands.store(num_bands, Ordering::SeqCst);
+ // Subtract off the sentinel parent dependency now that job is initialized and check
+ // whether there are any remaining parent dependencies to see if this job is ready.
+ self.parents.fetch_sub(1, Ordering::SeqCst) <= 1
+ }
+
+ fn take_band(&self) -> Option<u8> {
+ let band_index = self.band_index.fetch_add(1, Ordering::SeqCst);
+ if band_index < self.max_bands.load(Ordering::SeqCst) {
+ Some(band_index)
+ } else {
+ None
+ }
+ }
+
+ /// Try to take the job from this node for processing and then process it within the current band.
+ fn process_job(&self, band_index: u8) {
+ if let Some(ref job) = self.job {
+ job.process(band_index);
+ }
+ }
+
+ /// After processing a band, check all child dependencies and remove this parent from
+ /// their dependency counts. If applicable, queue the new child bands for composition.
+ fn unblock_children(&mut self, thread: &SwCompositeThread) {
+ if self.remaining_bands.fetch_sub(1, Ordering::SeqCst) > 1 {
+ return;
+ }
+ // Clear the job to release any locked resources.
+ self.job = None;
+ let mut lock = None;
+ for child in self.children.drain(..) {
+ // Remove the child's parent dependency on this node. If there are no more
+ // parent dependencies left, send the child job bands for composition.
+ if child.parents.fetch_sub(1, Ordering::SeqCst) <= 1 {
+ if lock.is_none() {
+ lock = Some(thread.lock());
+ }
+ thread.send_job(lock.as_mut().unwrap(), child);
+ }
+ }
+ }
+}
+
+/// The SwComposite thread processes a queue of composite jobs, also signaling
+/// via a condition when all available jobs have been processed, as tracked by
+/// the job count.
+struct SwCompositeThread {
+ /// Queue of available composite jobs
+ jobs: Mutex<SwCompositeJobQueue>,
+ /// Cache of the current job being processed. This maintains a pointer to
+ /// the contents of the SwCompositeGraphNodeRef, which is safe due to the
+ /// fact that SwCompositor maintains a strong reference to the contents
+ /// in an SwTile to keep it alive while this is in use.
+ current_job: AtomicPtr<SwCompositeGraphNode>,
+ /// Count of unprocessed jobs still in the queue
+ job_count: AtomicIsize,
+ /// Condition signaled when either there are jobs available to process or
+ /// there are no more jobs left to process. Otherwise stated, this signals
+ /// when the job queue transitions from an empty to non-empty state or from
+ /// a non-empty to empty state.
+ jobs_available: Condvar,
+}
+
+/// The SwCompositeThread struct is shared between the SwComposite thread
+/// and the rendering thread so that both ends can access the job queue.
+unsafe impl Sync for SwCompositeThread {}
+
+/// A FIFO queue of composite jobs to be processed.
+type SwCompositeJobQueue = VecDeque<SwCompositeGraphNodeRef>;
+
+/// Locked access to the composite job queue.
+type SwCompositeThreadLock<'a> = MutexGuard<'a, SwCompositeJobQueue>;
+
+impl SwCompositeThread {
+ /// Create the SwComposite thread. Requires a SWGL context in which
+ /// to do the composition.
+ fn new() -> Arc<SwCompositeThread> {
+ let info = Arc::new(SwCompositeThread {
+ jobs: Mutex::new(SwCompositeJobQueue::new()),
+ current_job: AtomicPtr::new(ptr::null_mut()),
+ job_count: AtomicIsize::new(0),
+ jobs_available: Condvar::new(),
+ });
+ let result = info.clone();
+ let thread_name = "SwComposite";
+ thread::Builder::new()
+ .name(thread_name.into())
+ // The composite thread only calls into SWGL to composite, and we
+ // have potentially many composite threads for different windows,
+ // so using the default stack size is excessive. A reasonably small
+ // stack size should be more than enough for SWGL and reduce memory
+ // overhead.
+ .stack_size(32 * 1024)
+ .spawn(move || {
+ let thread_listener = GeckoProfilerThreadListener::new();
+ thread_listener.thread_started(thread_name);
+ // Process any available jobs. This will return a non-Ok
+ // result when the job queue is dropped, causing the thread
+ // to eventually exit.
+ while let Some((job, band)) = info.take_job(true) {
+ info.process_job(job, band);
+ }
+ thread_listener.thread_stopped(thread_name);
+ })
+ .expect("Failed creating SwComposite thread");
+ result
+ }
+
+ fn deinit(&self) {
+ // Force the job count to be negative to signal the thread needs to exit.
+ self.job_count.store(isize::MIN / 2, Ordering::SeqCst);
+ // Wake up the thread in case it is blocked waiting for new jobs
+ self.jobs_available.notify_all();
+ }
+
+ /// Process a job contained in a dependency graph node received from the job queue.
+ /// Any child dependencies will be unblocked as appropriate after processing. The
+ /// job count will be updated to reflect this.
+ fn process_job(&self, graph_node: &mut SwCompositeGraphNode, band: u8) {
+ // Do the actual processing of the job contained in this node.
+ graph_node.process_job(band);
+ // Unblock any child dependencies now that this job has been processed.
+ graph_node.unblock_children(self);
+ // Decrement the job count.
+ self.job_count.fetch_sub(1, Ordering::SeqCst);
+ }
+
+ /// Queue a tile for composition by adding to the queue and increasing the job count.
+ fn queue_composite(
+ &self,
+ locked_src: SwCompositeSource,
+ locked_dst: swgl::LockedResource,
+ src_rect: DeviceIntRect,
+ dst_rect: DeviceIntRect,
+ clip_rect: DeviceIntRect,
+ opaque: bool,
+ flip_y: bool,
+ filter: ImageRendering,
+ mut graph_node: SwCompositeGraphNodeRef,
+ job_queue: &mut SwCompositeJobQueue,
+ ) {
+ // For jobs that would span a sufficiently large destination rectangle, split
+ // it into multiple horizontal bands so that multiple threads can process them.
+ let clipped_dst = match dst_rect.intersection(&clip_rect) {
+ Some(clipped_dst) => clipped_dst,
+ None => return,
+ };
+
+ let num_bands = if clipped_dst.size.width >= 64 && clipped_dst.size.height >= 64 {
+ (clipped_dst.size.height / 64).min(4) as u8
+ } else {
+ 1
+ };
+ let job = SwCompositeJob {
+ locked_src,
+ locked_dst,
+ src_rect,
+ dst_rect,
+ clipped_dst,
+ opaque,
+ flip_y,
+ filter,
+ num_bands,
+ };
+ self.job_count.fetch_add(num_bands as isize, Ordering::SeqCst);
+ if graph_node.set_job(job, num_bands) {
+ self.send_job(job_queue, graph_node);
+ }
+ }
+
+ fn start_compositing(&self) {
+ // Initialize the job count to 1 to prevent spurious signaling of job completion
+ // in the middle of queuing compositing jobs until we're actually waiting for
+ // composition.
+ self.job_count.store(1, Ordering::SeqCst);
+ }
+
+ /// Lock the thread for access to the job queue.
+ fn lock(&self) -> SwCompositeThreadLock {
+ self.jobs.lock().unwrap()
+ }
+
+ /// Send a job to the composite thread by adding it to the job queue.
+ /// Signal that this job has been added in case the queue was empty and the
+ /// SwComposite thread is waiting for jobs.
+ fn send_job(&self, queue: &mut SwCompositeJobQueue, job: SwCompositeGraphNodeRef) {
+ if queue.is_empty() {
+ self.jobs_available.notify_all();
+ }
+ queue.push_back(job);
+ }
+
+ /// Try to get a band of work from the currently cached job when available.
+ /// If there is a job, but it has no available bands left, null out the job
+ /// so that other threads do not bother checking the job.
+ fn try_take_job(&self) -> Option<(&mut SwCompositeGraphNode, u8)> {
+ let current_job_ptr = self.current_job.load(Ordering::SeqCst);
+ if let Some(current_job) = unsafe { current_job_ptr.as_mut() } {
+ if let Some(band) = current_job.take_band() {
+ return Some((current_job, band));
+ }
+ self.current_job
+ .compare_and_swap(current_job_ptr, ptr::null_mut(), Ordering::Relaxed);
+ }
+ return None;
+ }
+
+ /// Take a job from the queue. Optionally block waiting for jobs to become
+ /// available if this is called from the SwComposite thread.
+ fn take_job(&self, wait: bool) -> Option<(&mut SwCompositeGraphNode, u8)> {
+ // First try checking the cached job outside the scope of the mutex.
+ // For jobs that have multiple bands, this allows us to avoid having
+ // to lock the mutex multiple times to check the job for each band.
+ if let Some((job, band)) = self.try_take_job() {
+ return Some((job, band));
+ }
+ // Lock the job queue while checking for available jobs. The lock
+ // won't be held while the job is processed later outside of this
+ // function so that other threads can pull from the queue meanwhile.
+ let mut jobs = self.lock();
+ loop {
+ // While inside the mutex, check the cached job again to see if it
+ // has been updated.
+ if let Some((job, band)) = self.try_take_job() {
+ return Some((job, band));
+ }
+ // If no cached job was available, try to take a job from the queue
+ // and install it as the current job.
+ if let Some(job) = jobs.pop_front() {
+ self.current_job.store(job.get_ptr_mut(), Ordering::SeqCst);
+ continue;
+ }
+ // Otherwise, the job queue is currently empty. Depending on the
+ // value of the job count we may either wait for jobs to become
+ // available or exit.
+ match self.job_count.load(Ordering::SeqCst) {
+ // If we completed all available jobs, signal completion. If
+ // waiting inside the SwCompositeThread, then block waiting for
+ // more jobs to become available in a new frame. Otherwise,
+ // return immediately.
+ 0 => {
+ self.jobs_available.notify_all();
+ if !wait {
+ return None;
+ }
+ }
+ // A negative job count signals to exit immediately.
+ job_count if job_count < 0 => return None,
+ _ => {}
+ }
+ // The SwCompositeThread needs to wait for jobs to become
+ // available to avoid busy waiting on the queue.
+ jobs = self.jobs_available.wait(jobs).unwrap();
+ }
+ }
+
+ /// Wait for all queued composition jobs to be processed.
+ /// Instead of blocking on the SwComposite thread to complete all jobs,
+ /// this may steal some jobs and attempt to process them while waiting.
+ /// This may optionally process jobs synchronously. When normally doing
+ /// asynchronous processing, the graph dependencies are relied upon to
+ /// properly order the jobs, which makes it safe for the render thread
+ /// to steal jobs from the composite thread without violating those
+ /// dependencies. Synchronous processing just disables this job stealing
+ /// so that the composite thread always handles the jobs in the order
+ /// they were queued without having to rely upon possibly unavailable
+ /// graph dependencies.
+ fn wait_for_composites(&self, sync: bool) {
+ // Subtract off the bias to signal we're now waiting on composition and
+ // need to know if jobs are completed.
+ self.job_count.fetch_sub(1, Ordering::SeqCst);
+ // If processing asynchronously, try to steal jobs from the composite
+ // thread if it is busy.
+ if !sync {
+ while let Some((job, band)) = self.take_job(false) {
+ self.process_job(job, band);
+ }
+ // Once there are no more jobs, just fall through to waiting
+ // synchronously for the composite thread to finish processing.
+ }
+ // If processing synchronously, just wait for the composite thread
+ // to complete processing any in-flight jobs, then bail.
+ let mut jobs = self.lock();
+ // If the job count is non-zero here, then there are in-flight jobs.
+ while self.job_count.load(Ordering::SeqCst) > 0 {
+ jobs = self.jobs_available.wait(jobs).unwrap();
+ }
+ }
+
+ /// Check if there is a non-zero job count (including sentinel job) that
+ /// would indicate we are starting to already process jobs in the composite
+ /// thread.
+ fn is_busy_compositing(&self) -> bool {
+ self.job_count.load(Ordering::SeqCst) > 0
+ }
+}
+
+/// Adapter for RenderCompositors to work with SWGL that shuttles between
+/// WebRender and the RenderCompositr via the Compositor API.
+pub struct SwCompositor {
+ gl: swgl::Context,
+ native_gl: Option<Rc<dyn gl::Gl>>,
+ compositor: WrCompositor,
+ use_native_compositor: bool,
+ surfaces: HashMap<NativeSurfaceId, SwSurface>,
+ frame_surfaces: Vec<(
+ NativeSurfaceId,
+ CompositorSurfaceTransform,
+ DeviceIntRect,
+ ImageRendering,
+ )>,
+ /// Any surface added after we're already compositing (i.e. debug overlay)
+ /// needs to be processed after those frame surfaces. For simplicity we
+ /// store them in a separate queue that gets processed later.
+ late_surfaces: Vec<(
+ NativeSurfaceId,
+ CompositorSurfaceTransform,
+ DeviceIntRect,
+ ImageRendering,
+ )>,
+ cur_tile: NativeTileId,
+ draw_tile: Option<DrawTileHelper>,
+ /// The maximum tile size required for any of the allocated surfaces.
+ max_tile_size: DeviceIntSize,
+ /// Reuse the same depth texture amongst all tiles in all surfaces.
+ /// This depth texture must be big enough to accommodate the largest used
+ /// tile size for any surface. The maximum requested tile size is tracked
+ /// to ensure that this depth texture is at least that big.
+ depth_id: u32,
+ /// Instance of the SwComposite thread, only created if we are not relying
+ /// on OpenGL compositing or a native RenderCompositor.
+ composite_thread: Option<Arc<SwCompositeThread>>,
+ /// SWGL locked resource for sharing framebuffer with SwComposite thread
+ locked_framebuffer: Option<swgl::LockedResource>,
+}
+
+impl SwCompositor {
+ pub fn new(
+ gl: swgl::Context,
+ native_gl: Option<Rc<dyn gl::Gl>>,
+ compositor: WrCompositor,
+ use_native_compositor: bool,
+ ) -> Self {
+ let depth_id = gl.gen_textures(1)[0];
+ // Only create the SwComposite thread if we're neither using OpenGL composition nor a native
+ // render compositor. Thus, we are compositing into the main software framebuffer, which in
+ // that case benefits from compositing asynchronously while we are updating tiles.
+ assert!(native_gl.is_none() || !use_native_compositor);
+ let composite_thread = if native_gl.is_none() && !use_native_compositor {
+ Some(SwCompositeThread::new())
+ } else {
+ None
+ };
+ SwCompositor {
+ gl,
+ compositor,
+ use_native_compositor,
+ surfaces: HashMap::new(),
+ frame_surfaces: Vec::new(),
+ late_surfaces: Vec::new(),
+ cur_tile: NativeTileId {
+ surface_id: NativeSurfaceId(0),
+ x: 0,
+ y: 0,
+ },
+ draw_tile: native_gl.as_ref().map(|gl| DrawTileHelper::new(gl.clone())),
+ native_gl,
+ max_tile_size: DeviceIntSize::zero(),
+ depth_id,
+ composite_thread,
+ locked_framebuffer: None,
+ }
+ }
+
+ fn deinit_shader(&mut self) {
+ if let Some(draw_tile) = &self.draw_tile {
+ draw_tile.deinit();
+ }
+ self.draw_tile = None;
+ }
+
+ fn deinit_tile(&self, tile: &SwTile) {
+ self.gl.delete_framebuffers(&[tile.fbo_id]);
+ self.gl.delete_textures(&[tile.color_id]);
+ if let Some(native_gl) = &self.native_gl {
+ native_gl.delete_textures(&[tile.tex_id]);
+ native_gl.delete_buffers(&[tile.pbo_id]);
+ }
+ }
+
+ fn deinit_surface(&self, surface: &SwSurface) {
+ for tile in &surface.tiles {
+ self.deinit_tile(tile);
+ }
+ }
+
+ /// Reset tile dependency state for a new frame.
+ fn reset_overlaps(&mut self) {
+ for surface in self.surfaces.values_mut() {
+ for tile in &mut surface.tiles {
+ tile.overlaps.set(0);
+ tile.invalid.set(false);
+ tile.graph_node.reset();
+ }
+ }
+ }
+
+ /// Computes an overlap count for a tile that falls within the given composite
+ /// destination rectangle. This requires checking all surfaces currently queued for
+ /// composition so far in this frame and seeing if they have any invalidated tiles
+ /// whose destination rectangles would also overlap the supplied tile. If so, then the
+ /// increment the overlap count to account for all such dependencies on invalid tiles.
+ /// Tiles with the same overlap count will still be drawn with a stable ordering in
+ /// the order the surfaces were queued, so it is safe to ignore other possible sources
+ /// of composition ordering dependencies, as the later queued tile will still be drawn
+ /// later than the blocking tiles within that stable order. We assume that the tile's
+ /// surface hasn't yet been added to the current frame list of surfaces to composite
+ /// so that we only process potential blockers from surfaces that would come earlier
+ /// in composition.
+ fn init_overlaps(
+ &self,
+ overlap_id: &NativeSurfaceId,
+ overlap_surface: &SwSurface,
+ overlap_tile: &SwTile,
+ overlap_transform: &CompositorSurfaceTransform,
+ overlap_clip_rect: &DeviceIntRect,
+ ) {
+ // Record an extra overlap for an invalid tile to track the tile's dependency
+ // on its own future update.
+ let mut overlaps = if overlap_tile.invalid.get() { 1 } else { 0 };
+
+ let overlap_rect = match overlap_tile.overlap_rect(overlap_surface, overlap_transform, overlap_clip_rect) {
+ Some(overlap_rect) => overlap_rect,
+ None => {
+ overlap_tile.overlaps.set(overlaps);
+ return;
+ }
+ };
+
+ for &(ref id, ref transform, ref clip_rect, _) in &self.frame_surfaces {
+ // We only want to consider surfaces that were added before the current one we're
+ // checking for overlaps. If we find that surface, then we're done.
+ if id == overlap_id {
+ break;
+ }
+ // If the surface's clip rect doesn't overlap the tile's rect,
+ // then there is no need to check any tiles within the surface.
+ if !overlap_rect.intersects(clip_rect) {
+ continue;
+ }
+ if let Some(surface) = self.surfaces.get(id) {
+ for tile in &surface.tiles {
+ // If there is a deferred tile that might overlap the destination rectangle,
+ // record the overlap.
+ if tile.may_overlap(surface, transform, clip_rect, &overlap_rect) {
+ if tile.overlaps.get() > 0 {
+ overlaps += 1;
+ }
+ // Regardless of whether this tile is deferred, if it has dependency
+ // overlaps, then record that it is potentially a dependency parent.
+ tile.graph_node.get_mut().add_child(overlap_tile.graph_node.clone());
+ }
+ }
+ }
+ }
+ if overlaps > 0 {
+ // Has a dependency on some invalid tiles, so need to defer composition.
+ overlap_tile.overlaps.set(overlaps);
+ }
+ }
+
+ /// Helper function that queues a composite job to the current locked framebuffer
+ fn queue_composite(
+ &self,
+ surface: &SwSurface,
+ transform: &CompositorSurfaceTransform,
+ clip_rect: &DeviceIntRect,
+ filter: ImageRendering,
+ tile: &SwTile,
+ job_queue: &mut SwCompositeJobQueue,
+ ) {
+ if let Some(ref composite_thread) = self.composite_thread {
+ if let Some((src_rect, dst_rect, flip_y)) = tile.composite_rects(surface, transform, clip_rect) {
+ let source = if surface.external_image.is_some() {
+ // If the surface has an attached external image, lock any textures supplied in the descriptor.
+ match surface.composite_surface {
+ Some(ref info) => match info.yuv_planes {
+ 0 => match self.gl.lock_texture(info.textures[0]) {
+ Some(texture) => SwCompositeSource::BGRA(texture),
+ None => return,
+ },
+ 3 => match (
+ self.gl.lock_texture(info.textures[0]),
+ self.gl.lock_texture(info.textures[1]),
+ self.gl.lock_texture(info.textures[2]),
+ ) {
+ (Some(y_texture), Some(u_texture), Some(v_texture)) => SwCompositeSource::YUV(
+ y_texture,
+ u_texture,
+ v_texture,
+ info.color_space,
+ info.color_depth,
+ ),
+ _ => return,
+ },
+ _ => panic!("unsupported number of YUV planes: {}", info.yuv_planes),
+ },
+ None => return,
+ }
+ } else if let Some(texture) = self.gl.lock_texture(tile.color_id) {
+ // Lock the texture representing the picture cache tile.
+ SwCompositeSource::BGRA(texture)
+ } else {
+ return;
+ };
+ if let Some(ref framebuffer) = self.locked_framebuffer {
+ composite_thread.queue_composite(
+ source,
+ framebuffer.clone(),
+ src_rect,
+ dst_rect,
+ *clip_rect,
+ surface.is_opaque,
+ flip_y,
+ filter,
+ tile.graph_node.clone(),
+ job_queue,
+ );
+ }
+ }
+ }
+ }
+
+ /// Lock a surface with an attached external image for compositing.
+ fn try_lock_composite_surface(&mut self, id: &NativeSurfaceId) {
+ if let Some(surface) = self.surfaces.get_mut(id) {
+ if let Some(external_image) = surface.external_image {
+ // If the surface has an attached external image, attempt to lock the external image
+ // for compositing. Yields a descriptor of textures and data necessary for their
+ // interpretation on success.
+ let mut info = WrSWGLCompositeSurfaceInfo {
+ yuv_planes: 0,
+ textures: [0; 3],
+ color_space: YuvColorSpace::Identity,
+ color_depth: ColorDepth::Color8,
+ size: DeviceIntSize::zero(),
+ };
+ assert!(!surface.tiles.is_empty());
+ let mut tile = &mut surface.tiles[0];
+ if unsafe { wr_swgl_lock_composite_surface(self.gl.into(), external_image, &mut info) } {
+ tile.valid_rect = DeviceIntRect::from_size(info.size);
+ surface.composite_surface = Some(info);
+ } else {
+ tile.valid_rect = DeviceIntRect::zero();
+ surface.composite_surface = None;
+ }
+ }
+ }
+ }
+
+ /// Look for any attached external images that have been locked and then unlock them.
+ fn unlock_composite_surfaces(&mut self) {
+ for &(ref id, _, _, _) in self.frame_surfaces.iter().chain(self.late_surfaces.iter()) {
+ if let Some(surface) = self.surfaces.get_mut(id) {
+ if let Some(external_image) = surface.external_image {
+ if surface.composite_surface.is_some() {
+ unsafe { wr_swgl_unlock_composite_surface(self.gl.into(), external_image) };
+ surface.composite_surface = None;
+ }
+ }
+ }
+ }
+ }
+
+ /// Issue composites for any tiles that are no longer blocked following a tile update.
+ /// We process all surfaces and tiles in the order they were queued.
+ fn flush_composites(&self, tile_id: &NativeTileId, surface: &SwSurface, tile: &SwTile) {
+ let composite_thread = match &self.composite_thread {
+ Some(composite_thread) => composite_thread,
+ None => return,
+ };
+
+ // Look for the tile in the frame list and composite it if it has no dependencies.
+ let mut frame_surfaces = self
+ .frame_surfaces
+ .iter()
+ .skip_while(|&(ref id, _, _, _)| *id != tile_id.surface_id);
+ let (overlap_rect, mut lock) = match frame_surfaces.next() {
+ Some(&(_, ref transform, ref clip_rect, filter)) => {
+ // Remove invalid tile's update dependency.
+ if tile.invalid.get() {
+ tile.overlaps.set(tile.overlaps.get() - 1);
+ }
+ // If the tile still has overlaps, keep deferring it till later.
+ if tile.overlaps.get() > 0 {
+ return;
+ }
+ // Otherwise, the tile's dependencies are all resolved, so composite it.
+ let mut lock = composite_thread.lock();
+ self.queue_composite(surface, transform, clip_rect, filter, tile, &mut lock);
+ // Finally, get the tile's overlap rect used for tracking dependencies
+ match tile.overlap_rect(surface, transform, clip_rect) {
+ Some(overlap_rect) => (overlap_rect, lock),
+ None => return,
+ }
+ }
+ None => return,
+ };
+
+ // Accumulate rects whose dependencies have been satisfied from this update.
+ // Store the union of all these bounds to quickly reject unaffected tiles.
+ let mut flushed_bounds = overlap_rect;
+ let mut flushed_rects = vec![overlap_rect];
+
+ // Check surfaces following the update in the frame list and see if they would overlap it.
+ for &(ref id, ref transform, ref clip_rect, filter) in frame_surfaces {
+ // If the clip rect doesn't overlap the conservative bounds, we can skip the whole surface.
+ if !flushed_bounds.intersects(clip_rect) {
+ continue;
+ }
+ if let Some(surface) = self.surfaces.get(&id) {
+ // Search through the surface's tiles for any blocked on this update and queue jobs for them.
+ for tile in &surface.tiles {
+ let mut overlaps = tile.overlaps.get();
+ // Only check tiles that have existing unresolved dependencies
+ if overlaps == 0 {
+ continue;
+ }
+ // Get this tile's overlap rect for tracking dependencies
+ let overlap_rect = match tile.overlap_rect(surface, transform, clip_rect) {
+ Some(overlap_rect) => overlap_rect,
+ None => continue,
+ };
+ // Do a quick check to see if the tile overlaps the conservative bounds.
+ if !overlap_rect.intersects(&flushed_bounds) {
+ continue;
+ }
+ // Decrement the overlap count if this tile is dependent on any flushed rects.
+ for flushed_rect in &flushed_rects {
+ if overlap_rect.intersects(flushed_rect) {
+ overlaps -= 1;
+ }
+ }
+ if overlaps != tile.overlaps.get() {
+ // If the overlap count changed, this tile had a dependency on some flush rects.
+ // If the count hit zero, it is ready to composite.
+ tile.overlaps.set(overlaps);
+ if overlaps == 0 {
+ self.queue_composite(surface, transform, clip_rect, filter, tile, &mut lock);
+ // Record that the tile got flushed to update any downwind dependencies.
+ flushed_bounds = flushed_bounds.union(&overlap_rect);
+ flushed_rects.push(overlap_rect);
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+impl Compositor for SwCompositor {
+ fn create_surface(
+ &mut self,
+ id: NativeSurfaceId,
+ virtual_offset: DeviceIntPoint,
+ tile_size: DeviceIntSize,
+ is_opaque: bool,
+ ) {
+ if self.use_native_compositor {
+ self.compositor.create_surface(id, virtual_offset, tile_size, is_opaque);
+ }
+ self.max_tile_size = DeviceIntSize::new(
+ self.max_tile_size.width.max(tile_size.width),
+ self.max_tile_size.height.max(tile_size.height),
+ );
+ self.surfaces.insert(id, SwSurface::new(tile_size, is_opaque));
+ }
+
+ fn create_external_surface(&mut self, id: NativeSurfaceId, is_opaque: bool) {
+ if self.use_native_compositor {
+ self.compositor.create_external_surface(id, is_opaque);
+ }
+ self.surfaces
+ .insert(id, SwSurface::new(DeviceIntSize::zero(), is_opaque));
+ }
+
+ fn destroy_surface(&mut self, id: NativeSurfaceId) {
+ if let Some(surface) = self.surfaces.remove(&id) {
+ self.deinit_surface(&surface);
+ }
+ if self.use_native_compositor {
+ self.compositor.destroy_surface(id);
+ }
+ }
+
+ fn deinit(&mut self) {
+ if let Some(ref composite_thread) = self.composite_thread {
+ composite_thread.deinit();
+ }
+
+ for surface in self.surfaces.values() {
+ self.deinit_surface(surface);
+ }
+
+ self.gl.delete_textures(&[self.depth_id]);
+
+ self.deinit_shader();
+
+ if self.use_native_compositor {
+ self.compositor.deinit();
+ }
+ }
+
+ fn create_tile(&mut self, id: NativeTileId) {
+ if self.use_native_compositor {
+ self.compositor.create_tile(id);
+ }
+ if let Some(surface) = self.surfaces.get_mut(&id.surface_id) {
+ let mut tile = SwTile::new(id.x, id.y);
+ tile.color_id = self.gl.gen_textures(1)[0];
+ tile.fbo_id = self.gl.gen_framebuffers(1)[0];
+ self.gl.bind_framebuffer(gl::DRAW_FRAMEBUFFER, tile.fbo_id);
+ self.gl.framebuffer_texture_2d(
+ gl::DRAW_FRAMEBUFFER,
+ gl::COLOR_ATTACHMENT0,
+ gl::TEXTURE_2D,
+ tile.color_id,
+ 0,
+ );
+ self.gl.framebuffer_texture_2d(
+ gl::DRAW_FRAMEBUFFER,
+ gl::DEPTH_ATTACHMENT,
+ gl::TEXTURE_2D,
+ self.depth_id,
+ 0,
+ );
+ self.gl.bind_framebuffer(gl::DRAW_FRAMEBUFFER, 0);
+
+ if let Some(native_gl) = &self.native_gl {
+ tile.tex_id = native_gl.gen_textures(1)[0];
+ native_gl.bind_texture(gl::TEXTURE_2D, tile.tex_id);
+ native_gl.tex_image_2d(
+ gl::TEXTURE_2D,
+ 0,
+ gl::RGBA8 as gl::GLint,
+ surface.tile_size.width,
+ surface.tile_size.height,
+ 0,
+ gl::RGBA,
+ gl::UNSIGNED_BYTE,
+ None,
+ );
+ native_gl.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as gl::GLint);
+ native_gl.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as gl::GLint);
+ native_gl.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as gl::GLint);
+ native_gl.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as gl::GLint);
+ native_gl.bind_texture(gl::TEXTURE_2D, 0);
+
+ tile.pbo_id = native_gl.gen_buffers(1)[0];
+ native_gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, tile.pbo_id);
+ native_gl.buffer_data_untyped(
+ gl::PIXEL_UNPACK_BUFFER,
+ surface.tile_size.area() as isize * 4,
+ ptr::null(),
+ gl::DYNAMIC_DRAW,
+ );
+ native_gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, 0);
+ }
+
+ surface.tiles.push(tile);
+ }
+ }
+
+ fn destroy_tile(&mut self, id: NativeTileId) {
+ if let Some(surface) = self.surfaces.get_mut(&id.surface_id) {
+ if let Some(idx) = surface.tiles.iter().position(|t| t.x == id.x && t.y == id.y) {
+ let tile = surface.tiles.remove(idx);
+ self.deinit_tile(&tile);
+ }
+ }
+ if self.use_native_compositor {
+ self.compositor.destroy_tile(id);
+ }
+ }
+
+ fn attach_external_image(&mut self, id: NativeSurfaceId, external_image: ExternalImageId) {
+ if self.use_native_compositor {
+ self.compositor.attach_external_image(id, external_image);
+ }
+ if let Some(surface) = self.surfaces.get_mut(&id) {
+ // Surfaces with attached external images have a single tile at the origin encompassing
+ // the entire surface.
+ assert!(surface.tile_size.is_empty());
+ surface.external_image = Some(external_image);
+ if surface.tiles.is_empty() {
+ surface.tiles.push(SwTile::new(0, 0));
+ }
+ }
+ }
+
+ fn invalidate_tile(&mut self, id: NativeTileId, valid_rect: DeviceIntRect) {
+ if self.use_native_compositor {
+ self.compositor.invalidate_tile(id, valid_rect);
+ }
+ if let Some(surface) = self.surfaces.get_mut(&id.surface_id) {
+ if let Some(tile) = surface.tiles.iter_mut().find(|t| t.x == id.x && t.y == id.y) {
+ tile.invalid.set(true);
+ tile.valid_rect = valid_rect;
+ }
+ }
+ }
+
+ fn bind(&mut self, id: NativeTileId, dirty_rect: DeviceIntRect, valid_rect: DeviceIntRect) -> NativeSurfaceInfo {
+ let mut surface_info = NativeSurfaceInfo {
+ origin: DeviceIntPoint::zero(),
+ fbo_id: 0,
+ };
+
+ self.cur_tile = id;
+
+ if let Some(surface) = self.surfaces.get_mut(&id.surface_id) {
+ if let Some(tile) = surface.tiles.iter_mut().find(|t| t.x == id.x && t.y == id.y) {
+ tile.dirty_rect = dirty_rect;
+ assert_eq!(tile.valid_rect, valid_rect);
+ if valid_rect.is_empty() {
+ return surface_info;
+ }
+
+ let mut stride = 0;
+ let mut buf = ptr::null_mut();
+ if self.use_native_compositor {
+ if let Some(tile_info) = self.compositor.map_tile(id, dirty_rect, valid_rect) {
+ stride = tile_info.stride;
+ buf = tile_info.data;
+ }
+ } else if let Some(native_gl) = &self.native_gl {
+ if tile.pbo_id != 0 {
+ native_gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, tile.pbo_id);
+ buf = native_gl.map_buffer_range(
+ gl::PIXEL_UNPACK_BUFFER,
+ 0,
+ valid_rect.size.area() as isize * 4,
+ gl::MAP_WRITE_BIT | gl::MAP_INVALIDATE_BUFFER_BIT,
+ ); // | gl::MAP_UNSYNCHRONIZED_BIT);
+ if buf != ptr::null_mut() {
+ stride = valid_rect.size.width * 4;
+ } else {
+ native_gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, 0);
+ native_gl.delete_buffers(&[tile.pbo_id]);
+ tile.pbo_id = 0;
+ }
+ }
+ }
+ self.gl.set_texture_buffer(
+ tile.color_id,
+ gl::RGBA8,
+ valid_rect.size.width,
+ valid_rect.size.height,
+ stride,
+ buf,
+ surface.tile_size.width,
+ surface.tile_size.height,
+ );
+ // Reallocate the shared depth buffer to fit the valid rect, but within
+ // a buffer sized to actually fit at least the maximum possible tile size.
+ // The maximum tile size is supplied to avoid reallocation by ensuring the
+ // allocated buffer is actually big enough to accommodate the largest tile
+ // size requested by any used surface, even though supplied valid rect may
+ // actually be much smaller than this. This will only force a texture
+ // reallocation inside SWGL if the maximum tile size has grown since the
+ // last time it was supplied, instead simply reusing the buffer if the max
+ // tile size is not bigger than what was previously allocated.
+ self.gl.set_texture_buffer(
+ self.depth_id,
+ gl::DEPTH_COMPONENT16,
+ valid_rect.size.width,
+ valid_rect.size.height,
+ 0,
+ ptr::null_mut(),
+ self.max_tile_size.width,
+ self.max_tile_size.height,
+ );
+ surface_info.fbo_id = tile.fbo_id;
+ surface_info.origin -= valid_rect.origin.to_vector();
+ }
+ }
+
+ surface_info
+ }
+
+ fn unbind(&mut self) {
+ let id = self.cur_tile;
+ if let Some(surface) = self.surfaces.get(&id.surface_id) {
+ if let Some(tile) = surface.tiles.iter().find(|t| t.x == id.x && t.y == id.y) {
+ if tile.valid_rect.is_empty() {
+ self.flush_composites(&id, surface, tile);
+ return;
+ }
+
+ // get the color buffer even if we have a self.compositor, to make
+ // sure that any delayed clears are resolved
+ let (swbuf, _, _, stride) = self.gl.get_color_buffer(tile.fbo_id, true);
+
+ if self.use_native_compositor {
+ self.compositor.unmap_tile();
+ return;
+ }
+
+ let native_gl = match &self.native_gl {
+ Some(native_gl) => native_gl,
+ None => {
+ // If we're not relying on a native compositor or OpenGL compositing,
+ // then composite any tiles that are dependent on this tile being
+ // updated but are otherwise ready to composite.
+ self.flush_composites(&id, surface, tile);
+ return;
+ }
+ };
+
+ assert!(stride % 4 == 0);
+ let buf = if tile.pbo_id != 0 {
+ native_gl.unmap_buffer(gl::PIXEL_UNPACK_BUFFER);
+ std::ptr::null_mut::<c_void>()
+ } else {
+ swbuf
+ };
+ let dirty = tile.dirty_rect;
+ let src = unsafe {
+ (buf as *mut u32).offset(
+ (dirty.origin.y - tile.valid_rect.origin.y) as isize * (stride / 4) as isize
+ + (dirty.origin.x - tile.valid_rect.origin.x) as isize,
+ )
+ };
+ native_gl.active_texture(gl::TEXTURE0);
+ native_gl.bind_texture(gl::TEXTURE_2D, tile.tex_id);
+ native_gl.pixel_store_i(gl::UNPACK_ROW_LENGTH, stride / 4);
+ native_gl.tex_sub_image_2d_pbo(
+ gl::TEXTURE_2D,
+ 0,
+ dirty.origin.x,
+ dirty.origin.y,
+ dirty.size.width,
+ dirty.size.height,
+ gl::BGRA,
+ gl::UNSIGNED_BYTE,
+ src as _,
+ );
+ native_gl.pixel_store_i(gl::UNPACK_ROW_LENGTH, 0);
+ if tile.pbo_id != 0 {
+ native_gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, 0);
+ }
+
+ native_gl.bind_texture(gl::TEXTURE_2D, 0);
+ }
+ }
+ }
+
+ fn begin_frame(&mut self) {
+ if self.use_native_compositor {
+ self.compositor.begin_frame();
+ }
+ self.frame_surfaces.clear();
+ self.late_surfaces.clear();
+
+ self.reset_overlaps();
+ }
+
+ fn add_surface(
+ &mut self,
+ id: NativeSurfaceId,
+ transform: CompositorSurfaceTransform,
+ clip_rect: DeviceIntRect,
+ filter: ImageRendering,
+ ) {
+ if self.use_native_compositor {
+ self.compositor.add_surface(id, transform, clip_rect, filter);
+ }
+
+ if self.composite_thread.is_some() {
+ // If the surface has an attached external image, try to lock that now.
+ self.try_lock_composite_surface(&id);
+
+ // If we're already busy compositing, then add to the queue of late
+ // surfaces instead of trying to sort into the main frame queue.
+ // These late surfaces will not have any overlap tracking done for
+ // them and must be processed synchronously at the end of the frame.
+ if self.composite_thread.as_ref().unwrap().is_busy_compositing() {
+ self.late_surfaces.push((id, transform, clip_rect, filter));
+ return;
+ }
+ }
+
+ self.frame_surfaces.push((id, transform, clip_rect, filter));
+ }
+
+ /// Now that all the dependency graph nodes have been built, start queuing
+ /// composition jobs. Any surfaces that get added after this point in the
+ /// frame will not have overlap dependencies assigned and so must instead
+ /// be added to the late_surfaces queue to be processed at the end of the
+ /// frame.
+ fn start_compositing(
+ &mut self,
+ dirty_rects: &[DeviceIntRect],
+ _opaque_rects: &[DeviceIntRect],
+ ) {
+ // Opaque rects are currently only computed here, not by WR itself, so we
+ // ignore the passed parameter and forward our own version onto the native
+ // compositor.
+ let mut opaque_rects : Vec<DeviceIntRect> = Vec::new();
+ for &(ref id, ref transform, ref clip_rect, _filter) in &self.frame_surfaces {
+ if let Some(surface) = self.surfaces.get(id) {
+ if !surface.is_opaque {
+ continue;
+ }
+
+ for tile in &surface.tiles {
+ if let Some(rect) = tile.overlap_rect(surface, transform, clip_rect) {
+ opaque_rects.push(rect);
+ }
+ }
+ }
+ }
+
+ self.compositor.start_compositing(dirty_rects, &opaque_rects);
+
+ if dirty_rects.len() == 1 {
+ // Factor dirty rect into surface clip rects and discard surfaces that are
+ // entirely clipped out.
+ for &mut (ref _id, ref _transform, ref mut clip_rect, _filter) in &mut self.frame_surfaces {
+ *clip_rect = clip_rect.intersection(&dirty_rects[0]).unwrap_or_default();
+ }
+ self.frame_surfaces
+ .retain(|&(_, _, clip_rect, _)| !clip_rect.is_empty());
+ }
+
+ if let Some(ref composite_thread) = self.composite_thread {
+ // Compute overlap dependencies for surfaces.
+ for &(ref id, ref transform, ref clip_rect, _filter) in &self.frame_surfaces {
+ if let Some(surface) = self.surfaces.get(id) {
+ for tile in &surface.tiles {
+ self.init_overlaps(id, surface, tile, transform, clip_rect);
+ }
+ }
+ }
+
+ self.locked_framebuffer = self.gl.lock_framebuffer(0);
+
+ composite_thread.start_compositing();
+ // Issue any initial composite jobs for the SwComposite thread.
+ let mut lock = composite_thread.lock();
+ for &(ref id, ref transform, ref clip_rect, filter) in &self.frame_surfaces {
+ if let Some(surface) = self.surfaces.get(id) {
+ for tile in &surface.tiles {
+ if tile.overlaps.get() == 0 {
+ // Not dependent on any tiles, so go ahead and composite now.
+ self.queue_composite(surface, transform, clip_rect, filter, tile, &mut lock);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ fn end_frame(&mut self) {
+ if self.use_native_compositor {
+ self.compositor.end_frame();
+ } else if let Some(native_gl) = &self.native_gl {
+ let (_, fw, fh, _) = self.gl.get_color_buffer(0, false);
+ let viewport = DeviceIntRect::from_size(DeviceIntSize::new(fw, fh));
+ let draw_tile = self.draw_tile.as_ref().unwrap();
+ draw_tile.enable(&viewport);
+ let mut blend = false;
+ native_gl.blend_func(gl::ONE, gl::ONE_MINUS_SRC_ALPHA);
+ for &(ref id, ref transform, ref clip_rect, filter) in &self.frame_surfaces {
+ if let Some(surface) = self.surfaces.get(id) {
+ if surface.is_opaque {
+ if blend {
+ native_gl.disable(gl::BLEND);
+ blend = false;
+ }
+ } else if !blend {
+ native_gl.enable(gl::BLEND);
+ blend = true;
+ }
+ for tile in &surface.tiles {
+ if let Some((src_rect, dst_rect, flip_y)) = tile.composite_rects(surface, transform, clip_rect)
+ {
+ draw_tile.draw(
+ &viewport,
+ &dst_rect,
+ &src_rect,
+ clip_rect,
+ surface,
+ tile,
+ flip_y,
+ image_rendering_to_gl_filter(filter),
+ );
+ }
+ }
+ }
+ }
+ if blend {
+ native_gl.disable(gl::BLEND);
+ }
+ draw_tile.disable();
+ } else if let Some(ref composite_thread) = self.composite_thread {
+ // Need to wait for the SwComposite thread to finish any queued jobs.
+ composite_thread.wait_for_composites(false);
+
+ if !self.late_surfaces.is_empty() {
+ // All of the main frame surface have been processed by now. But if there
+ // are any late surfaces, we need to kick off a new synchronous composite
+ // phase. These late surfaces don't have any overlap/dependency tracking,
+ // so we just queue them directly and wait synchronously for the composite
+ // thread to process them in order.
+ composite_thread.start_compositing();
+ {
+ let mut lock = composite_thread.lock();
+ for &(ref id, ref transform, ref clip_rect, filter) in &self.late_surfaces {
+ if let Some(surface) = self.surfaces.get(id) {
+ for tile in &surface.tiles {
+ self.queue_composite(surface, transform, clip_rect, filter, tile, &mut lock);
+ }
+ }
+ }
+ }
+ composite_thread.wait_for_composites(true);
+ }
+
+ self.locked_framebuffer = None;
+
+ self.unlock_composite_surfaces();
+ }
+ }
+
+ fn enable_native_compositor(&mut self, enable: bool) {
+ self.compositor.enable_native_compositor(enable);
+ self.use_native_compositor = enable;
+ }
+
+ fn get_capabilities(&self) -> CompositorCapabilities {
+ self.compositor.get_capabilities()
+ }
+}
diff --git a/gfx/webrender_bindings/webrender_ffi.h b/gfx/webrender_bindings/webrender_ffi.h
new file mode 100644
index 0000000000..882b8436ca
--- /dev/null
+++ b/gfx/webrender_bindings/webrender_ffi.h
@@ -0,0 +1,130 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef WR_h
+#define WR_h
+
+#include "mozilla/gfx/Types.h"
+#include "nsTArray.h"
+
+extern "C" {
+
+// ----
+// Functions invoked from Rust code
+// ----
+
+bool is_in_compositor_thread();
+bool is_in_main_thread();
+bool is_in_render_thread();
+bool is_glcontext_gles(void* glcontext_ptr);
+bool is_glcontext_angle(void* glcontext_ptr);
+bool gfx_use_wrench();
+const char* gfx_wr_resource_path_override();
+bool gfx_wr_use_optimized_shaders();
+void gfx_critical_note(const char* msg);
+void gfx_critical_error(const char* msg);
+void gecko_printf_stderr_output(const char* msg);
+void* get_proc_address_from_glcontext(void* glcontext_ptr,
+ const char* procname);
+void gecko_profiler_register_thread(const char* threadname);
+void gecko_profiler_unregister_thread();
+
+void gecko_profiler_start_marker(const char* name);
+void gecko_profiler_end_marker(const char* name);
+void gecko_profiler_event_marker(const char* name);
+void gecko_profiler_add_text_marker(const char* name, const char* text_ptr,
+ size_t text_len, uint64_t microseconds);
+bool gecko_profiler_thread_is_being_profiled();
+
+// IMPORTANT: Keep this synchronized with enumerate_interners in
+// gfx/wr/webrender_api
+#define WEBRENDER_FOR_EACH_INTERNER(macro) \
+ macro(clip); \
+ macro(prim); \
+ macro(normal_border); \
+ macro(image_border); \
+ macro(image); \
+ macro(yuv_image); \
+ macro(line_decoration); \
+ macro(linear_grad); \
+ macro(radial_grad); \
+ macro(conic_grad); \
+ macro(picture); \
+ macro(text_run); \
+ macro(filterdata); \
+ macro(backdrop);
+
+// Prelude of types necessary before including webrender_ffi_generated.h
+namespace mozilla {
+namespace wr {
+
+// Because this struct is macro-generated on the Rust side, cbindgen can't see
+// it. Work around that by re-declaring it here.
+#define DECLARE_MEMBER(id) uintptr_t id;
+struct InternerSubReport {
+ WEBRENDER_FOR_EACH_INTERNER(DECLARE_MEMBER)
+};
+
+#undef DECLARE_MEMBER
+
+struct Transaction;
+struct WrWindowId;
+struct DocumentId;
+struct WrPipelineInfo;
+
+struct WrPipelineIdAndEpoch;
+using WrPipelineIdEpochs = nsTArray<WrPipelineIdAndEpoch>;
+
+const uint64_t ROOT_CLIP_CHAIN = ~0;
+
+} // namespace wr
+} // namespace mozilla
+
+void apz_register_updater(mozilla::wr::WrWindowId aWindowId);
+void apz_pre_scene_swap(mozilla::wr::WrWindowId aWindowId);
+void apz_post_scene_swap(mozilla::wr::WrWindowId aWindowId,
+ const mozilla::wr::WrPipelineInfo* aInfo);
+void apz_run_updater(mozilla::wr::WrWindowId aWindowId);
+void apz_deregister_updater(mozilla::wr::WrWindowId aWindowId);
+
+void apz_register_sampler(mozilla::wr::WrWindowId aWindowId);
+void apz_sample_transforms(mozilla::wr::WrWindowId aWindowId,
+ const uint64_t* aGeneratedFrameId,
+ mozilla::wr::Transaction* aTransaction);
+void apz_deregister_sampler(mozilla::wr::WrWindowId aWindowId);
+
+void omta_register_sampler(mozilla::wr::WrWindowId aWindowId);
+void omta_sample(mozilla::wr::WrWindowId aWindowId,
+ mozilla::wr::Transaction* aTransaction);
+void omta_deregister_sampler(mozilla::wr::WrWindowId aWindowId);
+} // extern "C"
+
+// Work-around wingdi.h define which conflcits with WR color constant
+#pragma push_macro("TRANSPARENT")
+#undef TRANSPARENT
+
+#include "webrender_ffi_generated.h"
+
+#pragma pop_macro("TRANSPARENT")
+
+// More functions invoked from Rust code. These are down here because they
+// refer to data structures from webrender_ffi_generated.h
+extern "C" {
+void record_telemetry_time(mozilla::wr::TelemetryProbe aProbe,
+ uint64_t aTimeNs);
+}
+
+namespace mozilla {
+namespace wr {
+
+// Cast a blob image key into a regular image for use in
+// a display item.
+inline ImageKey AsImageKey(BlobImageKey aKey) { return aKey._0; }
+
+} // namespace wr
+} // namespace mozilla
+
+#endif // WR_h