diff options
Diffstat (limited to 'gfx/webrender_bindings')
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, ¤tFboId); + gl->fGetIntegerv(LOCAL_GL_RENDERBUFFER_BINDING, ¤tRboId); + + // 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(¶ms); + params.DirtyRectsCount = rectsCount; + params.pDirtyRects = rects.data(); + + HRESULT hr; + hr = mSwapChain1->Present1(0, 0, ¶ms); + 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[¶ms.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[¶ms.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 |