diff options
Diffstat (limited to 'gfx/webrender_bindings')
69 files changed, 24810 insertions, 0 deletions
diff --git a/gfx/webrender_bindings/Cargo.toml b/gfx/webrender_bindings/Cargo.toml new file mode 100644 index 0000000000..69a7eef1c0 --- /dev/null +++ b/gfx/webrender_bindings/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "webrender_bindings" +version = "0.1.0" +authors = ["The Mozilla Project Developers"] +license = "MPL-2.0" + +[dependencies] +dirs = "4" +rayon = "1" +num_cpus = "1.7.0" +tracy-rs = "0.1" +euclid = { version = "0.22.5", features = ["serde"] } +app_units = "0.7" +gleam = "0.15" +log = "0.4" +nsstring = { path = "../../xpcom/rust/nsstring" } +bincode = "1.0" +uuid = { version = "1.0", 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" } +gecko-profiler = { path = "../../tools/profiler/rust-api" } +remove_dir_all = "0.5.3" + +[dependencies.webrender] +path = "../wr/webrender" +version = "0.62.0" +default-features = false +features = ["capture", "serialize_program", "gecko", "sw_compositor"] + +[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..0ededcb042 --- /dev/null +++ b/gfx/webrender_bindings/DCLayerTree.cpp @@ -0,0 +1,1922 @@ +/* -*- 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 "mozilla/gfx/AllOfDcomp.h" +#include <d3d11.h> +#include <d3d11_1.h> +#include <dxgi1_2.h> + +// - + +#include "gfxWindowsPlatform.h" +#include "GLContext.h" +#include "GLContextEGL.h" +#include "mozilla/gfx/DeviceManagerDx.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/gfx/GPUParent.h" +#include "mozilla/gfx/Matrix.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/webrender/RenderD3D11TextureHost.h" +#include "mozilla/webrender/RenderDcompSurfaceTextureHost.h" +#include "mozilla/webrender/RenderTextureHost.h" +#include "mozilla/webrender/RenderThread.h" +#include "mozilla/WindowsVersion.h" +#include "mozilla/Telemetry.h" +#include "nsPrintfCString.h" +#include "WinUtils.h" + +// - + +#if defined(__MINGW32__) // 64 defines both 32 and 64 +// We need to fake some things, while we wait on updates to mingw's dcomp.h +// header. Just enough that we can successfully fail to work there. +# define MOZ_MINGW_DCOMP_H_INCOMPLETE +struct IDCompositionColorMatrixEffect : public IDCompositionFilterEffect {}; +struct IDCompositionTableTransferEffect : public IDCompositionFilterEffect {}; +#endif // defined(__MINGW32__) + +namespace mozilla { +namespace wr { + +extern LazyLogModule gRenderThreadLog; +#define LOG(...) MOZ_LOG(gRenderThreadLog, LogLevel::Debug, (__VA_ARGS__)) + +#define LOG_H(msg, ...) \ + MOZ_LOG(gDcompSurface, LogLevel::Debug, \ + ("DCSurfaceHandle=%p, " msg, this, ##__VA_ARGS__)) + +UniquePtr<GpuOverlayInfo> DCLayerTree::sGpuOverlayInfo; + +/* 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; +} + +void DCLayerTree::Shutdown() { DCLayerTree::sGpuOverlayInfo = nullptr; } + +DCLayerTree::DCLayerTree(gl::GLContext* aGL, EGLConfig aEGLConfig, + ID3D11Device* aDevice, ID3D11DeviceContext* aCtx, + IDCompositionDevice2* aCompositionDevice) + : mGL(aGL), + mEGLConfig(aEGLConfig), + mDevice(aDevice), + mCtx(aCtx), + mCompositionDevice(aCompositionDevice), + mDebugCounter(false), + mDebugVisualRedrawRegions(false), + mEGLImage(EGL_NO_IMAGE), + mColorRBO(0), + mPendingCommit(false) { + LOG("DCLayerTree::DCLayerTree()"); +} + +DCLayerTree::~DCLayerTree() { + LOG("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 %lx)", hr)); + return false; + } + + hr = desktopDevice->CreateTargetForHwnd(aHwnd, TRUE, + getter_AddRefs(mCompositionTarget)); + if (FAILED(hr)) { + aError.Assign(nsPrintfCString( + "DCLayerTree(create DCompositionTarget failed %lx)", hr)); + return false; + } + + hr = mCompositionDevice->CreateVisual(getter_AddRefs(mRootVisual)); + if (FAILED(hr)) { + aError.Assign(nsPrintfCString( + "DCLayerTree(create root DCompositionVisual failed %lx)", hr)); + return false; + } + + hr = + mCompositionDevice->CreateVisual(getter_AddRefs(mDefaultSwapChainVisual)); + if (FAILED(hr)) { + aError.Assign(nsPrintfCString( + "DCLayerTree(create swap chain DCompositionVisual failed %lx)", hr)); + return false; + } + + if (gfx::gfxVars::UseWebRenderDCompVideoOverlayWin()) { + if (!InitializeVideoOverlaySupport()) { + RenderThread::Get()->HandleWebRenderError(WebRenderError::VIDEO_OVERLAY); + } + } + if (!sGpuOverlayInfo) { + // Set default if sGpuOverlayInfo was not set. + sGpuOverlayInfo = MakeUnique<GpuOverlayInfo>(); + } + + // Initialize SwapChainInfo + SupportsSwapChainTearing(); + + 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 FlagsSupportsOverlays(UINT flags) { + return (flags & (DXGI_OVERLAY_SUPPORT_FLAG_DIRECT | + DXGI_OVERLAY_SUPPORT_FLAG_SCALING)); +} + +// A warpper of IDXGIOutput4::CheckOverlayColorSpaceSupport() +bool CheckOverlayColorSpaceSupport(DXGI_FORMAT aDxgiFormat, + DXGI_COLOR_SPACE_TYPE aDxgiColorSpace, + RefPtr<IDXGIOutput> aOutput, + RefPtr<ID3D11Device> aD3d11Device) { + UINT colorSpaceSupportFlags = 0; + RefPtr<IDXGIOutput4> output4; + + if (FAILED(aOutput->QueryInterface(__uuidof(IDXGIOutput4), + getter_AddRefs(output4)))) { + return false; + } + + if (FAILED(output4->CheckOverlayColorSpaceSupport( + aDxgiFormat, aDxgiColorSpace, aD3d11Device, + &colorSpaceSupportFlags))) { + return false; + } + + return (colorSpaceSupportFlags & + DXGI_OVERLAY_COLOR_SPACE_SUPPORT_FLAG_PRESENT); +} + +bool DCLayerTree::InitializeVideoOverlaySupport() { + MOZ_ASSERT(IsWin10AnniversaryUpdateOrLater()); + + 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; + } + + if (sGpuOverlayInfo) { + return true; + } + + UniquePtr<GpuOverlayInfo> info = MakeUnique<GpuOverlayInfo>(); + + RefPtr<IDXGIDevice> dxgiDevice; + RefPtr<IDXGIAdapter> adapter; + mDevice->QueryInterface((IDXGIDevice**)getter_AddRefs(dxgiDevice)); + dxgiDevice->GetAdapter(getter_AddRefs(adapter)); + + unsigned int i = 0; + while (true) { + RefPtr<IDXGIOutput> output; + if (FAILED(adapter->EnumOutputs(i++, getter_AddRefs(output)))) { + break; + } + RefPtr<IDXGIOutput3> output3; + if (FAILED(output->QueryInterface(__uuidof(IDXGIOutput3), + getter_AddRefs(output3)))) { + break; + } + + output3->CheckOverlaySupport(DXGI_FORMAT_NV12, mDevice, + &info->mNv12OverlaySupportFlags); + output3->CheckOverlaySupport(DXGI_FORMAT_YUY2, mDevice, + &info->mYuy2OverlaySupportFlags); + output3->CheckOverlaySupport(DXGI_FORMAT_B8G8R8A8_UNORM, mDevice, + &info->mBgra8OverlaySupportFlags); + output3->CheckOverlaySupport(DXGI_FORMAT_R10G10B10A2_UNORM, mDevice, + &info->mRgb10a2OverlaySupportFlags); + + if (FlagsSupportsOverlays(info->mNv12OverlaySupportFlags)) { + // NV12 format is preferred if it's supported. + info->mOverlayFormatUsed = DXGI_FORMAT_NV12; + info->mSupportsHardwareOverlays = true; + } + + if (!info->mSupportsHardwareOverlays && + FlagsSupportsOverlays(info->mYuy2OverlaySupportFlags)) { + // If NV12 isn't supported, fallback to YUY2 if it's supported. + info->mOverlayFormatUsed = DXGI_FORMAT_YUY2; + info->mSupportsHardwareOverlays = true; + } + + // RGB10A2 overlay is used for displaying HDR content. In Intel's + // platform, RGB10A2 overlay is enabled only when + // DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020 is supported. + if (FlagsSupportsOverlays(info->mRgb10a2OverlaySupportFlags)) { + if (!CheckOverlayColorSpaceSupport( + DXGI_FORMAT_R10G10B10A2_UNORM, + DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020, output, mDevice)) + info->mRgb10a2OverlaySupportFlags = 0; + } + + // Early out after the first output that reports overlay support. All + // outputs are expected to report the same overlay support according to + // Microsoft's WDDM documentation: + // https://docs.microsoft.com/en-us/windows-hardware/drivers/display/multiplane-overlay-hardware-requirements + if (info->mSupportsHardwareOverlays) { + break; + } + } + + if (!StaticPrefs::gfx_webrender_dcomp_video_yuv_overlay_win_AtStartup()) { + info->mOverlayFormatUsed = DXGI_FORMAT_B8G8R8A8_UNORM; + info->mSupportsHardwareOverlays = false; + } + + info->mSupportsOverlays = info->mSupportsHardwareOverlays; + + sGpuOverlayInfo = std::move(info); + + if (auto* gpuParent = gfx::GPUParent::GetSingleton()) { + gpuParent->NotifyOverlayInfo(GetOverlayInfo()); + } + + 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) { + LOG("DCLayerTree::SetDefaultSwapChain()"); + + 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}; + + // If tile owns an IDCompositionSurface we use it, otherwise we're using an + // IDCompositionVirtualSurface owned by the DCSurface. + RefPtr<IDCompositionSurface> compositionSurface; + if (surface->mIsVirtualSurface) { + gfx::IntRect validRect(aValidRect.min.x, aValidRect.min.y, + aValidRect.width(), aValidRect.height()); + if (!tile->mValidRect.IsEqualEdges(validRect)) { + tile->mValidRect = validRect; + surface->DirtyAllocatedRect(); + } + wr::DeviceIntSize tileSize = surface->GetTileSize(); + compositionSurface = surface->GetCompositionSurface(); + wr::DeviceIntPoint virtualOffset = surface->GetVirtualOffset(); + targetOffset.x = virtualOffset.x + tileSize.width * aId.x; + targetOffset.y = virtualOffset.y + tileSize.height * aId.y; + } else { + compositionSurface = tile->Bind(aValidRect); + } + + if (tile->mNeedsFullDraw) { + // dcomp requires that the first BeginDraw on a non-virtual surface is the + // full size of the pixel buffer. + auto tileSize = surface->GetTileSize(); + aDirtyRect.min.x = 0; + aDirtyRect.min.y = 0; + aDirtyRect.max.x = tileSize.width; + aDirtyRect.max.y = tileSize.height; + tile->mNeedsFullDraw = false; + } + + *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 << ")"; + } + + bool isVirtualSurface = + StaticPrefs::gfx_webrender_dcomp_use_virtual_surfaces_AtStartup(); + auto surface = MakeUnique<DCSurface>(aTileSize, aVirtualOffset, + isVirtualSurface, 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<DCExternalSurfaceWrapper>(aIsOpaque, this); + if (!surface->Initialize()) { + gfxCriticalNote << "Failed to initialize DCExternalSurfaceWrapper: " + << 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, int32_t aX, int32_t aY) { + auto surface = GetSurface(aId); + surface->CreateTile(aX, aY); +} + +void DCLayerTree::DestroyTile(wr::NativeSurfaceId aId, int32_t aX, int32_t 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()); + surface_it->second->AttachExternalImage(aExternalImage); +} + +void DCExternalSurfaceWrapper::AttachExternalImage( + wr::ExternalImageId aExternalImage) { + if (auto* surface = EnsureSurfaceForExternalImage(aExternalImage)) { + surface->AttachExternalImage(aExternalImage); + } +} + +template <class ToT> +struct QI { + template <class FromT> + [[nodiscard]] static inline RefPtr<ToT> From(FromT* const from) { + RefPtr<ToT> to; + (void)from->QueryInterface(static_cast<ToT**>(getter_AddRefs(to))); + return to; + } +}; + +DCSurface* DCExternalSurfaceWrapper::EnsureSurfaceForExternalImage( + wr::ExternalImageId aExternalImage) { + if (mSurface) { + return mSurface.get(); + } + + // Create a new surface based on the texture type. + RenderTextureHost* texture = + RenderThread::Get()->GetRenderTexture(aExternalImage); + if (texture && texture->AsRenderDXGITextureHost()) { + mSurface.reset(new DCSurfaceVideo(mIsOpaque, mDCLayerTree)); + if (!mSurface->Initialize()) { + gfxCriticalNote << "Failed to initialize DCSurfaceVideo: " + << wr::AsUint64(aExternalImage); + mSurface = nullptr; + } + } else if (texture && texture->AsRenderDcompSurfaceTextureHost()) { + mSurface.reset(new DCSurfaceHandle(mIsOpaque, mDCLayerTree)); + if (!mSurface->Initialize()) { + gfxCriticalNote << "Failed to initialize DCSurfaceHandle: " + << wr::AsUint64(aExternalImage); + mSurface = nullptr; + } + } + if (!mSurface) { + gfxCriticalNote << "Failed to create a surface for external image: " + << gfx::hexa(texture); + return nullptr; + } + + // Add surface's visual which will contain video data to our root visual. + const auto surfaceVisual = mSurface->GetVisual(); + mVisual->AddVisual(surfaceVisual, true, nullptr); + + // - + // Apply color management. + + [&]() { + const auto cmsMode = GfxColorManagementMode(); + if (cmsMode == CMSMode::Off) return; + + const auto dcomp = mDCLayerTree->GetCompositionDevice(); + const auto dcomp3 = QI<IDCompositionDevice3>::From(dcomp); + if (!dcomp3) { + NS_WARNING( + "No IDCompositionDevice3, cannot use dcomp for color management."); + return; + } + + // - + + const auto cspace = [&]() { + const auto rangedCspace = texture->GetYUVColorSpace(); + const auto info = FromYUVRangedColorSpace(rangedCspace); + auto ret = ToColorSpace2(info.space); + if (ret == gfx::ColorSpace2::Display && cmsMode == CMSMode::All) { + ret = gfx::ColorSpace2::SRGB; + } + return ret; + }(); + + const bool rec709GammaAsSrgb = + StaticPrefs::gfx_color_management_rec709_gamma_as_srgb(); + const bool rec2020GammaAsRec709 = + StaticPrefs::gfx_color_management_rec2020_gamma_as_rec709(); + + auto cspaceDesc = color::ColorspaceDesc{}; + switch (cspace) { + case gfx::ColorSpace2::Display: + return; // No color management needed! + case gfx::ColorSpace2::SRGB: + cspaceDesc.chrom = color::Chromaticities::Srgb(); + cspaceDesc.tf = color::PiecewiseGammaDesc::Srgb(); + break; + + case gfx::ColorSpace2::DISPLAY_P3: + cspaceDesc.chrom = color::Chromaticities::DisplayP3(); + cspaceDesc.tf = color::PiecewiseGammaDesc::DisplayP3(); + break; + + case gfx::ColorSpace2::BT601_525: + cspaceDesc.chrom = color::Chromaticities::Rec601_525_Ntsc(); + if (rec709GammaAsSrgb) { + cspaceDesc.tf = color::PiecewiseGammaDesc::Srgb(); + } else { + cspaceDesc.tf = color::PiecewiseGammaDesc::Rec709(); + } + break; + + case gfx::ColorSpace2::BT709: + cspaceDesc.chrom = color::Chromaticities::Rec709(); + if (rec709GammaAsSrgb) { + cspaceDesc.tf = color::PiecewiseGammaDesc::Srgb(); + } else { + cspaceDesc.tf = color::PiecewiseGammaDesc::Rec709(); + } + break; + + case gfx::ColorSpace2::BT2020: + cspaceDesc.chrom = color::Chromaticities::Rec2020(); + if (rec2020GammaAsRec709 && rec709GammaAsSrgb) { + cspaceDesc.tf = color::PiecewiseGammaDesc::Srgb(); + } else if (rec2020GammaAsRec709) { + cspaceDesc.tf = color::PiecewiseGammaDesc::Rec709(); + } else { + // Just Rec709 with slightly more precision. + cspaceDesc.tf = color::PiecewiseGammaDesc::Rec2020_12bit(); + } + break; + } + + const auto cprofileIn = color::ColorProfileDesc::From(cspaceDesc); + auto cprofileOut = mDCLayerTree->OutputColorProfile(); + bool pretendSrgb = true; + if (pretendSrgb) { + cprofileOut = color::ColorProfileDesc::From({ + color::Chromaticities::Srgb(), + color::PiecewiseGammaDesc::Srgb(), + }); + } + const auto conversion = color::ColorProfileConversionDesc::From({ + .src = cprofileIn, + .dst = cprofileOut, + }); + + // - + + auto chain = ColorManagementChain::From(*dcomp3, conversion); + mCManageChain = Some(chain); + + surfaceVisual->SetEffect(mCManageChain->last.get()); + }(); + + return mSurface.get(); +} + +void DCExternalSurfaceWrapper::PresentExternalSurface(gfx::Matrix& aTransform) { + MOZ_ASSERT(mSurface); + if (auto* surface = mSurface->AsDCSurfaceVideo()) { + if (surface->CalculateSwapChainSize(aTransform)) { + surface->PresentVideo(); + } + } else if (auto* surface = mSurface->AsDCSurfaceHandle()) { + surface->PresentSurfaceHandle(); + } +} + +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(); + + float sx = aTransform.scale.x; + float sy = aTransform.scale.y; + float tx = aTransform.offset.x; + float ty = aTransform.offset.y; + gfx::Matrix transform(sx, 0.0, 0.0, sy, tx, ty); + + surface->PresentExternalSurface(transform); + + 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.min.x, aClipRect.min.y, aClipRect.width(), aClipRect.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& aInputSize, + const gfx::IntSize& aOutputSize) { + HRESULT hr; + + if (!mVideoDevice || !mVideoContext) { + return false; + } + + if (mVideoProcessor && (aInputSize <= mVideoInputSize) && + (aOutputSize <= mVideoOutputSize)) { + 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 = aInputSize.width; + desc.InputHeight = aInputSize.height; + desc.OutputFrameRate.Numerator = 60; + desc.OutputFrameRate.Denominator = 1; + desc.OutputWidth = aOutputSize.width; + desc.OutputHeight = aOutputSize.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); + + mVideoInputSize = aInputSize; + mVideoOutputSize = aOutputSize; + + return true; +} + +bool DCLayerTree::SupportsHardwareOverlays() { + return sGpuOverlayInfo->mSupportsHardwareOverlays; +} + +bool DCLayerTree::SupportsSwapChainTearing() { + RefPtr<ID3D11Device> device = mDevice; + static const bool supported = [device] { + RefPtr<IDXGIDevice> dxgiDevice; + RefPtr<IDXGIAdapter> adapter; + device->QueryInterface((IDXGIDevice**)getter_AddRefs(dxgiDevice)); + dxgiDevice->GetAdapter(getter_AddRefs(adapter)); + + RefPtr<IDXGIFactory5> dxgiFactory; + HRESULT hr = adapter->GetParent( + IID_PPV_ARGS((IDXGIFactory5**)getter_AddRefs(dxgiFactory))); + if (FAILED(hr)) { + return false; + } + + BOOL presentAllowTearing = FALSE; + hr = dxgiFactory->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, + &presentAllowTearing, + sizeof(presentAllowTearing)); + if (FAILED(hr)) { + return false; + } + + if (auto* gpuParent = gfx::GPUParent::GetSingleton()) { + gpuParent->NotifySwapChainInfo( + layers::SwapChainInfo(!!presentAllowTearing)); + } else if (XRE_IsParentProcess()) { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + } + return !!presentAllowTearing; + }(); + return supported; +} + +DXGI_FORMAT DCLayerTree::GetOverlayFormatForSDR() { + return sGpuOverlayInfo->mOverlayFormatUsed; +} + +static layers::OverlaySupportType FlagsToOverlaySupportType( + UINT aFlags, bool aSoftwareOverlaySupported) { + if (aFlags & DXGI_OVERLAY_SUPPORT_FLAG_SCALING) { + return layers::OverlaySupportType::Scaling; + } + if (aFlags & DXGI_OVERLAY_SUPPORT_FLAG_DIRECT) { + return layers::OverlaySupportType::Direct; + } + if (aSoftwareOverlaySupported) { + return layers::OverlaySupportType::Software; + } + return layers::OverlaySupportType::None; +} + +layers::OverlayInfo DCLayerTree::GetOverlayInfo() { + layers::OverlayInfo info; + + info.mSupportsOverlays = sGpuOverlayInfo->mSupportsHardwareOverlays; + info.mNv12Overlay = + FlagsToOverlaySupportType(sGpuOverlayInfo->mNv12OverlaySupportFlags, + /* aSoftwareOverlaySupported */ false); + info.mYuy2Overlay = + FlagsToOverlaySupportType(sGpuOverlayInfo->mYuy2OverlaySupportFlags, + /* aSoftwareOverlaySupported */ false); + info.mBgra8Overlay = + FlagsToOverlaySupportType(sGpuOverlayInfo->mBgra8OverlaySupportFlags, + /* aSoftwareOverlaySupported */ true); + info.mRgb10a2Overlay = + FlagsToOverlaySupportType(sGpuOverlayInfo->mRgb10a2OverlaySupportFlags, + /* aSoftwareOverlaySupported */ false); + + return info; +} + +DCSurface::DCSurface(wr::DeviceIntSize aTileSize, + wr::DeviceIntPoint aVirtualOffset, bool aIsVirtualSurface, + bool aIsOpaque, DCLayerTree* aDCLayerTree) + : mIsVirtualSurface(aIsVirtualSurface), + mDCLayerTree(aDCLayerTree), + mTileSize(aTileSize), + mIsOpaque(aIsOpaque), + mAllocatedRectDirty(true), + mVirtualOffset(aVirtualOffset) {} + +DCSurface::~DCSurface() {} + +bool DCSurface::Initialize() { + // Create a visual for tiles to attach to, whether virtual or not. + 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; + } + + // If virtual surface is enabled, create and attach to visual, in this case + // the tiles won't own visuals or surfaces. + if (mIsVirtualSurface) { + 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_R8G8B8A8_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(int32_t aX, int32_t aY) { + TileKey key(aX, aY); + MOZ_RELEASE_ASSERT(mDCTiles.find(key) == mDCTiles.end()); + + auto tile = MakeUnique<DCTile>(mDCLayerTree); + if (!tile->Initialize(aX, aY, mTileSize, mIsVirtualSurface, mIsOpaque, + mVisual)) { + gfxCriticalNote << "Failed to initialize DCTile: " << aX << aY; + return; + } + + if (mIsVirtualSurface) { + mAllocatedRectDirty = true; + } else { + mVisual->AddVisual(tile->GetVisual(), false, nullptr); + } + + mDCTiles[key] = std::move(tile); +} + +void DCSurface::DestroyTile(int32_t aX, int32_t aY) { + TileKey key(aX, aY); + if (mIsVirtualSurface) { + mAllocatedRectDirty = true; + } else { + auto tile = GetTile(aX, aY); + mVisual->RemoveVisual(tile->GetVisual()); + } + mDCTiles.erase(key); +} + +void DCSurface::DirtyAllocatedRect() { mAllocatedRectDirty = true; } + +void DCSurface::UpdateAllocatedRect() { + if (mAllocatedRectDirty) { + if (mVirtualSurface) { + // 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()); + } + // When not using a virtual surface, we still want to reset this + mAllocatedRectDirty = false; + } +} + +DCTile* DCSurface::GetTile(int32_t aX, int32_t 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{}, false, aIsOpaque, + aDCLayerTree) {} + +DCSurfaceVideo::~DCSurfaceVideo() { + ReleaseDecodeSwapChainResources(); + MOZ_ASSERT(!mSwapChainSurfaceHandle); +} + +bool IsYUVSwapChainFormat(DXGI_FORMAT aFormat) { + if (aFormat == DXGI_FORMAT_NV12 || aFormat == DXGI_FORMAT_YUY2) { + return true; + } + return false; +} + +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->GetFormat() != gfx::SurfaceFormat::NV12) { + gfxCriticalNote << "Unsupported RenderTexture for overlay: " + << gfx::hexa(texture); + return; + } + + mRenderTextureHost = texture; +} + +bool DCSurfaceVideo::CalculateSwapChainSize(gfx::Matrix& aTransform) { + if (!mRenderTextureHost) { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + return false; + } + + mVideoSize = mRenderTextureHost->AsRenderDXGITextureHost()->GetSize(0); + + // When RenderTextureHost, swapChainSize or VideoSwapChain are updated, + // DCSurfaceVideo::PresentVideo() needs to be called. + bool needsToPresent = mPrevTexture != mRenderTextureHost; + gfx::IntSize swapChainSize = mVideoSize; + gfx::Matrix transform = aTransform; + + // When video is rendered to axis aligned integer rectangle, video scaling + // could be done by VideoProcessor + bool scaleVideoAtVideoProcessor = false; + if (StaticPrefs::gfx_webrender_dcomp_video_vp_scaling_win_AtStartup() && + aTransform.PreservesAxisAlignedRectangles()) { + gfx::Size scaledSize = gfx::Size(mVideoSize) * aTransform.ScaleFactors(); + gfx::IntSize size(int32_t(std::round(scaledSize.width)), + int32_t(std::round(scaledSize.height))); + if (gfx::FuzzyEqual(scaledSize.width, size.width, 0.1f) && + gfx::FuzzyEqual(scaledSize.height, size.height, 0.1f)) { + scaleVideoAtVideoProcessor = true; + swapChainSize = size; + } + } + + if (scaleVideoAtVideoProcessor) { + // 4:2:2 subsampled formats like YUY2 must have an even width, and 4:2:0 + // subsampled formats like NV12 must have an even width and height. + if (swapChainSize.width % 2 == 1) { + swapChainSize.width += 1; + } + if (swapChainSize.height % 2 == 1) { + swapChainSize.height += 1; + } + transform = gfx::Matrix::Translation(aTransform.GetTranslation()); + } + + if (!mVideoSwapChain || mSwapChainSize != swapChainSize) { + needsToPresent = true; + ReleaseDecodeSwapChainResources(); + // Update mSwapChainSize before creating SwapChain + mSwapChainSize = swapChainSize; + + auto swapChainFormat = GetSwapChainFormat(); + bool useYUVSwapChain = IsYUVSwapChainFormat(swapChainFormat); + if (useYUVSwapChain) { + // Tries to create YUV SwapChain + CreateVideoSwapChain(); + if (!mVideoSwapChain) { + mFailedYuvSwapChain = true; + ReleaseDecodeSwapChainResources(); + + gfxCriticalNote << "Fallback to RGB SwapChain"; + } + } + // Tries to create RGB SwapChain + if (!mVideoSwapChain) { + CreateVideoSwapChain(); + } + } + + aTransform = transform; + + return needsToPresent; +} + +void DCSurfaceVideo::PresentVideo() { + if (!mRenderTextureHost) { + return; + } + + if (!mVideoSwapChain) { + gfxCriticalNote << "Failed to create VideoSwapChain"; + RenderThread::Get()->NotifyWebRenderError( + wr::WebRenderError::VIDEO_OVERLAY); + return; + } + + mVisual->SetContent(mVideoSwapChain); + + if (!CallVideoProcessorBlt()) { + auto swapChainFormat = GetSwapChainFormat(); + bool useYUVSwapChain = IsYUVSwapChainFormat(swapChainFormat); + if (useYUVSwapChain) { + mFailedYuvSwapChain = true; + ReleaseDecodeSwapChainResources(); + return; + } + RenderThread::Get()->NotifyWebRenderError( + wr::WebRenderError::VIDEO_OVERLAY); + return; + } + + auto start = TimeStamp::Now(); + HRESULT hr = mVideoSwapChain->Present(0, 0); + auto end = TimeStamp::Now(); + + if (FAILED(hr) && hr != DXGI_STATUS_OCCLUDED) { + gfxCriticalNoteOnce << "video Present failed: " << gfx::hexa(hr); + } + + mPrevTexture = mRenderTextureHost; + + // Disable video overlay if mVideoSwapChain->Present() is too slow. It drops + // fps. + + if (!StaticPrefs::gfx_webrender_dcomp_video_check_slow_present()) { + return; + } + + const auto maxWaitDurationMs = 2.0; + const auto maxSlowPresentCount = 5; + const auto duration = (end - start).ToMilliseconds(); + + if (duration > maxWaitDurationMs) { + mSlowPresentCount++; + } else { + mSlowPresentCount = 0; + } + + if (mSlowPresentCount > maxSlowPresentCount) { + gfxCriticalNoteOnce << "Video swapchain present is slow"; + RenderThread::Get()->HandleWebRenderError(WebRenderError::VIDEO_OVERLAY); + } +} + +DXGI_FORMAT DCSurfaceVideo::GetSwapChainFormat() { + if (mFailedYuvSwapChain || !mDCLayerTree->SupportsHardwareOverlays()) { + return DXGI_FORMAT_B8G8R8A8_UNORM; + } + return mDCLayerTree->GetOverlayFormatForSDR(); +} + +bool DCSurfaceVideo::CreateVideoSwapChain() { + MOZ_ASSERT(mRenderTextureHost); + + 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; + } + + auto swapChainFormat = GetSwapChainFormat(); + + DXGI_SWAP_CHAIN_DESC1 desc = {}; + desc.Width = mSwapChainSize.width; + desc.Height = mSwapChainSize.height; + desc.Format = swapChainFormat; + 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 = DXGI_SWAP_CHAIN_FLAG_FULLSCREEN_VIDEO; + if (IsYUVSwapChainFormat(swapChainFormat)) { + desc.Flags |= DXGI_SWAP_CHAIN_FLAG_YUV_VIDEO; + } + desc.AlphaMode = DXGI_ALPHA_MODE_IGNORE; + + HRESULT hr; + hr = dxgiFactoryMedia->CreateSwapChainForCompositionSurfaceHandle( + device, mSwapChainSurfaceHandle, &desc, nullptr, + getter_AddRefs(mVideoSwapChain)); + + if (FAILED(hr)) { + gfxCriticalNote << "Failed to create video SwapChain: " << gfx::hexa(hr) + << " " << mSwapChainSize; + return false; + } + + mSwapChainFormat = swapChainFormat; + return true; +} + +// TODO: Replace with YUVRangedColorSpace +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(); +} + +static Maybe<DXGI_COLOR_SPACE_TYPE> GetSourceDXGIColorSpace( + const gfx::YUVRangedColorSpace aYUVColorSpace) { + const auto info = FromYUVRangedColorSpace(aYUVColorSpace); + return GetSourceDXGIColorSpace(info.space, info.range); +} + +static void SetNvidiaVideoSuperRes(ID3D11VideoContext* videoContext, + ID3D11VideoProcessor* videoProcessor, + bool enabled) { + LOG("SetNvidiaVideoSuperRes() enabled=%d", enabled); + + // Undocumented NVIDIA driver constants + constexpr GUID nvGUID = {0xD43CE1B3, + 0x1F4B, + 0x48AC, + {0xBA, 0xEE, 0xC3, 0xC2, 0x53, 0x75, 0xE6, 0xF7}}; + + constexpr UINT nvExtensionVersion = 0x1; + constexpr UINT nvExtensionMethodSuperResolution = 0x2; + struct { + UINT version; + UINT method; + UINT enable; + } streamExtensionInfo = {nvExtensionVersion, nvExtensionMethodSuperResolution, + enabled ? 0 : 1u}; + + HRESULT hr; + hr = videoContext->VideoProcessorSetStreamExtension( + videoProcessor, 0, &nvGUID, sizeof(streamExtensionInfo), + &streamExtensionInfo); + + // Ignore errors as could be unsupported + if (FAILED(hr)) { + LOG("SetNvidiaVideoSuperRes() error: %lx", hr); + return; + } +} + +static UINT GetVendorId(ID3D11VideoDevice* const videoDevice) { + RefPtr<IDXGIDevice> dxgiDevice; + RefPtr<IDXGIAdapter> adapter; + videoDevice->QueryInterface((IDXGIDevice**)getter_AddRefs(dxgiDevice)); + dxgiDevice->GetAdapter(getter_AddRefs(adapter)); + + DXGI_ADAPTER_DESC adapterDesc; + adapter->GetDesc(&adapterDesc); + + return adapterDesc.VendorId; +} + +bool DCSurfaceVideo::CallVideoProcessorBlt() { + MOZ_ASSERT(mRenderTextureHost); + + HRESULT hr; + const auto videoDevice = mDCLayerTree->GetVideoDevice(); + const auto videoContext = mDCLayerTree->GetVideoContext(); + const auto texture = mRenderTextureHost->AsRenderDXGITextureHost(); + + Maybe<DXGI_COLOR_SPACE_TYPE> sourceColorSpace = + GetSourceDXGIColorSpace(texture->GetYUVColorSpace()); + 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(mVideoSize, 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); + + DXGI_COLOR_SPACE_TYPE outputColorSpace = + IsYUVSwapChainFormat(mSwapChainFormat) + ? inputColorSpace + : DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709; + hr = swapChain3->SetColorSpace1(outputColorSpace); + if (FAILED(hr)) { + gfxCriticalNoteOnce << "SetColorSpace1 failed: " << gfx::hexa(hr); + RenderThread::Get()->NotifyWebRenderError( + wr::WebRenderError::VIDEO_OVERLAY); + return false; + } + videoContext1->VideoProcessorSetOutputColorSpace1(videoProcessor, + outputColorSpace); + + D3D11_VIDEO_PROCESSOR_INPUT_VIEW_DESC inputDesc = {}; + inputDesc.ViewDimension = D3D11_VPIV_DIMENSION_TEXTURE2D; + inputDesc.Texture2D.ArraySlice = texture->ArrayIndex(); + + 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 = mVideoSize.width; + sourceRect.bottom = mVideoSize.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; + } + } + + const UINT vendorId = GetVendorId(videoDevice); + if (vendorId == 0x10DE && + StaticPrefs::gfx_webrender_super_resolution_nvidia_AtStartup()) { + SetNvidiaVideoSuperRes(videoContext, videoProcessor, true); + } + + 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; + } +} + +DCSurfaceHandle::DCSurfaceHandle(bool aIsOpaque, DCLayerTree* aDCLayerTree) + : DCSurface(wr::DeviceIntSize{}, wr::DeviceIntPoint{}, false, aIsOpaque, + aDCLayerTree) {} + +void DCSurfaceHandle::AttachExternalImage(wr::ExternalImageId aExternalImage) { + RenderTextureHost* texture = + RenderThread::Get()->GetRenderTexture(aExternalImage); + RenderDcompSurfaceTextureHost* renderTexture = + texture ? texture->AsRenderDcompSurfaceTextureHost() : nullptr; + if (!renderTexture) { + gfxCriticalNote << "Unsupported RenderTexture for DCSurfaceHandle: " + << gfx::hexa(texture); + return; + } + + const auto handle = renderTexture->GetDcompSurfaceHandle(); + if (GetSurfaceHandle() == handle) { + return; + } + + LOG_H("AttachExternalImage, ext-image=%" PRIu64 ", texture=%p, handle=%p", + wr::AsUint64(aExternalImage), renderTexture, handle); + mDcompTextureHost = renderTexture; +} + +HANDLE DCSurfaceHandle::GetSurfaceHandle() const { + if (mDcompTextureHost) { + return mDcompTextureHost->GetDcompSurfaceHandle(); + } + return nullptr; +} + +IDCompositionSurface* DCSurfaceHandle::EnsureSurface() { + if (auto* surface = mDcompTextureHost->GetSurface()) { + return surface; + } + + // Texture host hasn't created the surface yet, ask it to create a new one. + RefPtr<IDCompositionDevice> device; + HRESULT hr = mDCLayerTree->GetCompositionDevice()->QueryInterface( + (IDCompositionDevice**)getter_AddRefs(device)); + if (FAILED(hr)) { + gfxCriticalNote + << "Failed to convert IDCompositionDevice2 to IDCompositionDevice: " + << gfx::hexa(hr); + return nullptr; + } + + return mDcompTextureHost->CreateSurfaceFromDevice(device); +} + +void DCSurfaceHandle::PresentSurfaceHandle() { + LOG_H("PresentSurfaceHandle"); + if (IDCompositionSurface* surface = EnsureSurface()) { + LOG_H("Set surface %p to visual", surface); + mVisual->SetContent(surface); + } else { + mVisual->SetContent(nullptr); + } +} + +DCTile::DCTile(DCLayerTree* aDCLayerTree) : mDCLayerTree(aDCLayerTree) {} + +DCTile::~DCTile() {} + +bool DCTile::Initialize(int aX, int aY, wr::DeviceIntSize aSize, + bool aIsVirtualSurface, bool aIsOpaque, + RefPtr<IDCompositionVisual2> mSurfaceVisual) { + if (aSize.width <= 0 || aSize.height <= 0) { + return false; + } + + mSize = aSize; + mIsOpaque = aIsOpaque; + mIsVirtualSurface = aIsVirtualSurface; + mNeedsFullDraw = !aIsVirtualSurface; + + if (aIsVirtualSurface) { + // 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; + } else { + HRESULT hr; + const auto dCompDevice = mDCLayerTree->GetCompositionDevice(); + // Create the visual and put it in the tree under the surface visual + hr = dCompDevice->CreateVisual(getter_AddRefs(mVisual)); + if (FAILED(hr)) { + gfxCriticalNote << "Failed to CreateVisual for DCTile: " << gfx::hexa(hr); + return false; + } + mSurfaceVisual->AddVisual(mVisual, false, nullptr); + // Position the tile relative to the surface visual + mVisual->SetOffsetX(aX * aSize.width); + mVisual->SetOffsetY(aY * aSize.height); + // Clip the visual so it doesn't show anything until we update it + D2D_RECT_F clip = {0, 0, 0, 0}; + mVisual->SetClip(clip); + // Create the underlying pixel buffer. + mCompositionSurface = CreateCompositionSurface(aSize, aIsOpaque); + if (!mCompositionSurface) { + return false; + } + hr = mVisual->SetContent(mCompositionSurface); + if (FAILED(hr)) { + gfxCriticalNote << "Failed to SetContent for DCTile: " << gfx::hexa(hr); + return false; + } + } + + return true; +} + +RefPtr<IDCompositionSurface> DCTile::CreateCompositionSurface( + wr::DeviceIntSize aSize, bool aIsOpaque) { + HRESULT hr; + const auto dCompDevice = mDCLayerTree->GetCompositionDevice(); + const auto alphaMode = + aIsOpaque ? DXGI_ALPHA_MODE_IGNORE : DXGI_ALPHA_MODE_PREMULTIPLIED; + RefPtr<IDCompositionSurface> compositionSurface; + + hr = dCompDevice->CreateSurface(aSize.width, aSize.height, + DXGI_FORMAT_R8G8B8A8_UNORM, alphaMode, + getter_AddRefs(compositionSurface)); + if (FAILED(hr)) { + gfxCriticalNote << "Failed to CreateSurface for DCTile: " << gfx::hexa(hr); + return nullptr; + } + return compositionSurface; +} + +RefPtr<IDCompositionSurface> DCTile::Bind(wr::DeviceIntRect aValidRect) { + if (mVisual != nullptr) { + // Tile owns a visual, set the size of the visual to match the portion we + // want to be visible. + D2D_RECT_F clip_rect; + clip_rect.left = aValidRect.min.x; + clip_rect.top = aValidRect.min.y; + clip_rect.right = aValidRect.max.x; + clip_rect.bottom = aValidRect.max.y; + mVisual->SetClip(clip_rect); + } + return mCompositionSurface; +} + +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.min.x; + update_rect.top = aSurfaceOffset.y + aDirtyRect.min.y; + update_rect.right = aSurfaceOffset.x + aDirtyRect.max.x; + update_rect.bottom = aSurfaceOffset.y + aDirtyRect.max.y; + hr = aCompositionSurface->BeginDraw(&update_rect, __uuidof(ID3D11Texture2D), + (void**)getter_AddRefs(backBuf), &offset); + + if (FAILED(hr)) { + LayoutDeviceIntRect rect = widget::WinUtils::ToIntRect(update_rect); + + gfxCriticalNote << "DCompositionSurface::BeginDraw failed: " + << gfx::hexa(hr) << " " << rect; + RenderThread::Get()->HandleWebRenderError(WebRenderError::BEGIN_DRAW); + 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.min.x; + offset.y -= aDirtyRect.min.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; + } +} + +// - + +color::ColorProfileDesc DCLayerTree::QueryOutputColorProfile() { + // GPU process can't simply init gfxPlatform, (and we don't need most of it) + // but we do need gfxPlatform::GetCMSOutputProfile(). + // So we steal what we need through the window: + const auto outputProfileData = + gfxWindowsPlatform::GetPlatformCMSOutputProfileData_Impl(); + + const auto qcmsProfile = qcms_profile_from_memory( + outputProfileData.Elements(), outputProfileData.Length()); + const auto release = MakeScopeExit([&]() { + if (qcmsProfile) { + qcms_profile_release(qcmsProfile); + } + }); + + const bool print = gfxEnv::MOZ_GL_SPEW(); + + const auto ret = [&]() { + if (qcmsProfile) { + return color::ColorProfileDesc::From(*qcmsProfile); + } + if (print) { + printf_stderr( + "Missing or failed to load display color profile, defaulting to " + "sRGB.\n"); + } + const auto MISSING_PROFILE_DEFAULT_SPACE = color::ColorspaceDesc{ + color::Chromaticities::Srgb(), + color::PiecewiseGammaDesc::Srgb(), + }; + return color::ColorProfileDesc::From(MISSING_PROFILE_DEFAULT_SPACE); + }(); + + if (print) { + const auto gammaGuess = color::GuessGamma(ret.linearFromTf.r); + printf_stderr( + "Display profile:\n" + " Approx Gamma: %f\n" + " XYZ-D65 Red : %f, %f, %f\n" + " XYZ-D65 Green: %f, %f, %f\n" + " XYZ-D65 Blue : %f, %f, %f\n", + gammaGuess, ret.xyzd65FromLinearRgb.at(0, 0), + ret.xyzd65FromLinearRgb.at(0, 1), ret.xyzd65FromLinearRgb.at(0, 2), + + ret.xyzd65FromLinearRgb.at(1, 0), ret.xyzd65FromLinearRgb.at(1, 1), + ret.xyzd65FromLinearRgb.at(1, 2), + + ret.xyzd65FromLinearRgb.at(2, 0), ret.xyzd65FromLinearRgb.at(2, 1), + ret.xyzd65FromLinearRgb.at(2, 2)); + } + + return ret; +} + +inline D2D1_MATRIX_5X4_F to_D2D1_MATRIX_5X4_F(const color::mat4& m) { + return D2D1_MATRIX_5X4_F{{{ + m.rows[0][0], + m.rows[1][0], + m.rows[2][0], + m.rows[3][0], + m.rows[0][1], + m.rows[1][1], + m.rows[2][1], + m.rows[3][1], + m.rows[0][2], + m.rows[1][2], + m.rows[2][2], + m.rows[3][2], + m.rows[0][3], + m.rows[1][3], + m.rows[2][3], + m.rows[3][3], + 0, + 0, + 0, + 0, + }}}; +} + +ColorManagementChain ColorManagementChain::From( + IDCompositionDevice3& dcomp, + const color::ColorProfileConversionDesc& conv) { + auto ret = ColorManagementChain{}; + +#if !defined(MOZ_MINGW_DCOMP_H_INCOMPLETE) + + const auto Append = [&](const RefPtr<IDCompositionFilterEffect>& afterLast) { + if (ret.last) { + afterLast->SetInput(0, ret.last, 0); + } + ret.last = afterLast; + }; + + const auto MaybeAppendColorMatrix = [&](const color::mat4& m) { + RefPtr<IDCompositionColorMatrixEffect> e; + if (approx(m, color::mat4::Identity())) return e; + dcomp.CreateColorMatrixEffect(getter_AddRefs(e)); + MOZ_ASSERT(e); + if (!e) return e; + e->SetMatrix(to_D2D1_MATRIX_5X4_F(m)); + Append(e); + return e; + }; + const auto MaybeAppendTableTransfer = [&](const color::RgbTransferTables& t) { + RefPtr<IDCompositionTableTransferEffect> e; + if (!t.r.size() && !t.g.size() && !t.b.size()) return e; + dcomp.CreateTableTransferEffect(getter_AddRefs(e)); + MOZ_ASSERT(e); + if (!e) return e; + e->SetRedTable(t.r.data(), t.r.size()); + e->SetGreenTable(t.g.data(), t.g.size()); + e->SetBlueTable(t.b.data(), t.b.size()); + Append(e); + return e; + }; + + ret.srcRgbFromSrcYuv = MaybeAppendColorMatrix(conv.srcRgbFromSrcYuv); + ret.srcLinearFromSrcTf = MaybeAppendTableTransfer(conv.srcLinearFromSrcTf); + ret.dstLinearFromSrcLinear = + MaybeAppendColorMatrix(color::mat4(conv.dstLinearFromSrcLinear)); + ret.dstTfFromDstLinear = MaybeAppendTableTransfer(conv.dstTfFromDstLinear); + +#endif // !defined(MOZ_MINGW_DCOMP_H_INCOMPLETE) + + return ret; +} + +ColorManagementChain::~ColorManagementChain() = default; + +} // namespace wr +} // namespace mozilla + +#undef LOG_H diff --git a/gfx/webrender_bindings/DCLayerTree.h b/gfx/webrender_bindings/DCLayerTree.h new file mode 100644 index 0000000000..2dd3a061ac --- /dev/null +++ b/gfx/webrender_bindings/DCLayerTree.h @@ -0,0 +1,461 @@ +/* -*- 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 <dxgiformat.h> +#include <unordered_map> +#include <vector> +#include <windows.h> + +#include "Colorspaces.h" +#include "GLTypes.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/layers/OverlayInfo.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 IDCompositionColorMatrixEffect; +struct IDCompositionFilterEffect; +struct IDCompositionTableTransferEffect; +struct IDCompositionDevice2; +struct IDCompositionDevice3; +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 DCSurfaceHandle; +class RenderTextureHost; +class RenderDcompSurfaceTextureHost; + +struct GpuOverlayInfo { + bool mSupportsOverlays = false; + bool mSupportsHardwareOverlays = false; + DXGI_FORMAT mOverlayFormatUsed = DXGI_FORMAT_B8G8R8A8_UNORM; + DXGI_FORMAT mOverlayFormatUsedHdr = DXGI_FORMAT_R10G10B10A2_UNORM; + UINT mNv12OverlaySupportFlags = 0; + UINT mYuy2OverlaySupportFlags = 0; + UINT mBgra8OverlaySupportFlags = 0; + UINT mRgb10a2OverlaySupportFlags = 0; +}; + +// - + +struct ColorManagementChain { + RefPtr<IDCompositionColorMatrixEffect> srcRgbFromSrcYuv; + RefPtr<IDCompositionTableTransferEffect> srcLinearFromSrcTf; + RefPtr<IDCompositionColorMatrixEffect> dstLinearFromSrcLinear; + RefPtr<IDCompositionTableTransferEffect> dstTfFromDstLinear; + RefPtr<IDCompositionFilterEffect> last; + + static ColorManagementChain From(IDCompositionDevice3& dcomp, + const color::ColorProfileConversionDesc&); + + ~ColorManagementChain(); +}; + +// - + +/** + * 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); + + static void Shutdown(); + + 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& aInputSize, + const gfx::IntSize& aOutputSize); + + DCSurface* GetSurface(wr::NativeSurfaceId aId) const; + + // Get or create an FBO with depth buffer suitable for specified dimensions + GLuint GetOrCreateFbo(int aWidth, int aHeight); + + bool SupportsHardwareOverlays(); + DXGI_FORMAT GetOverlayFormatForSDR(); + + bool SupportsSwapChainTearing(); + + 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(); + layers::OverlayInfo GetOverlayInfo(); + + 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 mVideoInputSize; + gfx::IntSize mVideoOutputSize; + + 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; + + static color::ColorProfileDesc QueryOutputColorProfile(); + + mutable Maybe<color::ColorProfileDesc> mOutputColorProfile; + + public: + const color::ColorProfileDesc& OutputColorProfile() const { + if (!mOutputColorProfile) { + mOutputColorProfile = Some(QueryOutputColorProfile()); + } + return *mOutputColorProfile; + } + + protected: + static UniquePtr<GpuOverlayInfo> sGpuOverlayInfo; +}; + +/** + 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: + const bool mIsVirtualSurface; + + explicit DCSurface(wr::DeviceIntSize aTileSize, + wr::DeviceIntPoint aVirtualOffset, bool aIsVirtualSurface, + 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(); + + // Implement these if the inherited surface supports attaching external image. + virtual void AttachExternalImage(wr::ExternalImageId aExternalImage) { + MOZ_RELEASE_ASSERT(true, "Not support attaching external image"); + } + virtual void PresentExternalSurface(gfx::Matrix& aTransform) { + MOZ_RELEASE_ASSERT(true, "Not support presenting external surface"); + } + + virtual DCSurfaceVideo* AsDCSurfaceVideo() { return nullptr; } + virtual DCSurfaceHandle* AsDCSurfaceHandle() { 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. + // + // However when using a virtual surface, it is directly attached to this + // visual and the tiles do not own visuals. + // + // Whether mIsVirtualSurface is enabled is decided at DCSurface creation + // time based on the pref gfx.webrender.dcomp-use-virtual-surfaces + RefPtr<IDCompositionVisual2> mVisual; + + wr::DeviceIntSize mTileSize; + bool mIsOpaque; + bool mAllocatedRectDirty; + std::unordered_map<TileKey, UniquePtr<DCTile>, TileKeyHashFn> mDCTiles; + wr::DeviceIntPoint mVirtualOffset; + RefPtr<IDCompositionVirtualSurface> mVirtualSurface; +}; + +/** + * A wrapper surface which can contain either a DCVideo or a DCSurfaceHandle. + */ +class DCExternalSurfaceWrapper : public DCSurface { + public: + DCExternalSurfaceWrapper(bool aIsOpaque, DCLayerTree* aDCLayerTree) + : DCSurface(wr::DeviceIntSize{}, wr::DeviceIntPoint{}, + false /* virtual surface */, false /* opaque */, + aDCLayerTree), + mIsOpaque(aIsOpaque) {} + ~DCExternalSurfaceWrapper() = default; + + void AttachExternalImage(wr::ExternalImageId aExternalImage) override; + + void PresentExternalSurface(gfx::Matrix& aTransform) override; + + DCSurfaceVideo* AsDCSurfaceVideo() override { + return mSurface ? mSurface->AsDCSurfaceVideo() : nullptr; + } + + DCSurfaceHandle* AsDCSurfaceHandle() override { + return mSurface ? mSurface->AsDCSurfaceHandle() : nullptr; + } + + private: + DCSurface* EnsureSurfaceForExternalImage(wr::ExternalImageId aExternalImage); + + UniquePtr<DCSurface> mSurface; + const bool mIsOpaque; + Maybe<ColorManagementChain> mCManageChain; +}; + +class DCSurfaceVideo : public DCSurface { + public: + DCSurfaceVideo(bool aIsOpaque, DCLayerTree* aDCLayerTree); + + void AttachExternalImage(wr::ExternalImageId aExternalImage) override; + bool CalculateSwapChainSize(gfx::Matrix& aTransform); + void PresentVideo(); + + DCSurfaceVideo* AsDCSurfaceVideo() override { return this; } + + protected: + virtual ~DCSurfaceVideo(); + + DXGI_FORMAT GetSwapChainFormat(); + bool CreateVideoSwapChain(); + bool CallVideoProcessorBlt(); + void ReleaseDecodeSwapChainResources(); + + RefPtr<ID3D11VideoProcessorOutputView> mOutputView; + RefPtr<IDXGIResource> mDecodeResource; + RefPtr<IDXGISwapChain1> mVideoSwapChain; + RefPtr<IDXGIDecodeSwapChain> mDecodeSwapChain; + HANDLE mSwapChainSurfaceHandle = 0; + gfx::IntSize mVideoSize; + gfx::IntSize mSwapChainSize; + DXGI_FORMAT mSwapChainFormat = DXGI_FORMAT_B8G8R8A8_UNORM; + bool mFailedYuvSwapChain = false; + RefPtr<RenderTextureHost> mRenderTextureHost; + RefPtr<RenderTextureHost> mPrevTexture; + int mSlowPresentCount = 0; +}; + +/** + * A DC surface contains a IDCompositionSurface that is directly constructed by + * a handle. This is used by the Media Foundataion media engine, which would + * store the decoded video content in the surface. + */ +class DCSurfaceHandle : public DCSurface { + public: + DCSurfaceHandle(bool aIsOpaque, DCLayerTree* aDCLayerTree); + ~DCSurfaceHandle() = default; + + void AttachExternalImage(wr::ExternalImageId aExternalImage) override; + void PresentSurfaceHandle(); + + DCSurfaceHandle* AsDCSurfaceHandle() override { return this; } + + protected: + HANDLE GetSurfaceHandle() const; + IDCompositionSurface* EnsureSurface(); + + RefPtr<RenderDcompSurfaceTextureHost> mDcompTextureHost; +}; + +class DCTile { + public: + gfx::IntRect mValidRect; + + DCLayerTree* mDCLayerTree; + // Indicates that when the first BeginDraw occurs on the surface it must be + // full size - required by dcomp on non-virtual surfaces. + bool mNeedsFullDraw; + + explicit DCTile(DCLayerTree* aDCLayerTree); + ~DCTile(); + bool Initialize(int aX, int aY, wr::DeviceIntSize aSize, + bool aIsVirtualSurface, bool aIsOpaque, + RefPtr<IDCompositionVisual2> mSurfaceVisual); + RefPtr<IDCompositionSurface> Bind(wr::DeviceIntRect aValidRect); + IDCompositionVisual2* GetVisual() { return mVisual; } + + protected: + // Size in pixels of this tile, some may be unused. Set by Initialize. + wr::DeviceIntSize mSize; + // Whether the tile is composited as opaque (ignores alpha) or transparent. + // Set by Initialize. + bool mIsOpaque; + // Some code paths differ based on whether parent surface is virtual. + bool mIsVirtualSurface; + // Visual that displays the composition surface, or NULL if the tile belongs + // to a virtual surface. + RefPtr<IDCompositionVisual2> mVisual; + // Surface for the visual, or NULL if the tile has not had its first Bind or + // belongs to a virtual surface. + RefPtr<IDCompositionSurface> mCompositionSurface; + + RefPtr<IDCompositionSurface> CreateCompositionSurface(wr::DeviceIntSize aSize, + bool aIsOpaque); +}; + +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..500e7bf1c3 --- /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_DARWIN +# 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_DARWIN + 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_DARWIN + 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->width(), aRenderRect->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->min.x, aRenderRect->min.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->min.x, aDirtyRect->min.y, aDirtyRect->width(), + aDirtyRect->height()); + dt->PushClipRect(dirty); + bounds = + bounds.Intersect(IntRect(aDirtyRect->min.x, aDirtyRect->min.y, + aDirtyRect->width(), aDirtyRect->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_debug_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..7b4db85f4b --- /dev/null +++ b/gfx/webrender_bindings/RenderAndroidHardwareBufferTextureHost.cpp @@ -0,0 +1,246 @@ +/* -*- 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 "mozilla/webrender/RenderThread.h" +#include "GLContextEGL.h" +#include "GLLibraryEGL.h" +#include "GLReadTexImageHelper.h" +#include "OGLShaderConfig.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() { + 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) { + 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); + return true; +} + +wr::WrExternalImage RenderAndroidHardwareBufferTextureHost::Lock( + uint8_t aChannelIndex, gl::GLContext* aGL) { + 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()) { + return InvalidToWrExternalImage(); + } + + const auto uvs = GetUvCoords(GetSize()); + return NativeTextureToWrExternalImage( + mTextureHandle, uvs.first.x, uvs.first.y, uvs.second.x, uvs.second.y); +} + +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; +} + +gfx::SurfaceFormat RenderAndroidHardwareBufferTextureHost::GetFormat() const { + MOZ_ASSERT(mAndroidHardwareBuffer->mFormat == gfx::SurfaceFormat::R8G8B8A8 || + mAndroidHardwareBuffer->mFormat == gfx::SurfaceFormat::R8G8B8X8); + + if (mAndroidHardwareBuffer->mFormat == gfx::SurfaceFormat::R8G8B8A8) { + return gfx::SurfaceFormat::B8G8R8A8; + } + + if (mAndroidHardwareBuffer->mFormat == gfx::SurfaceFormat::R8G8B8X8) { + return gfx::SurfaceFormat::B8G8R8X8; + } + + gfxCriticalNoteOnce + << "Unexpected color format of RenderAndroidSurfaceTextureHost"; + + return gfx::SurfaceFormat::UNKNOWN; +} + +already_AddRefed<DataSourceSurface> +RenderAndroidHardwareBufferTextureHost::ReadTexImage() { + if (!mGL) { + mGL = RenderThread::Get()->SingletonGL(); + if (!mGL) { + return nullptr; + } + } + + if (!EnsureLockable()) { + return nullptr; + } + + /* Allocate resulting image surface */ + int32_t stride = GetSize().width * BytesPerPixel(GetFormat()); + RefPtr<DataSourceSurface> surf = Factory::CreateDataSourceSurfaceWithStride( + GetSize(), GetFormat(), stride); + if (!surf) { + return nullptr; + } + + layers::ShaderConfigOGL config = layers::ShaderConfigFromTargetAndFormat( + LOCAL_GL_TEXTURE_EXTERNAL, mAndroidHardwareBuffer->mFormat); + int shaderConfig = config.mFeatures; + + bool ret = mGL->ReadTexImageHelper()->ReadTexImage( + surf, mTextureHandle, LOCAL_GL_TEXTURE_EXTERNAL, GetSize(), shaderConfig, + /* aYInvert */ false); + if (!ret) { + return nullptr; + } + + return surf.forget(); +} + +bool RenderAndroidHardwareBufferTextureHost::MapPlane( + RenderCompositor* aCompositor, uint8_t aChannelIndex, + PlaneInfo& aPlaneInfo) { + RefPtr<gfx::DataSourceSurface> readback = ReadTexImage(); + if (!readback) { + return false; + } + + DataSourceSurface::MappedSurface map; + if (!readback->Map(DataSourceSurface::MapType::READ, &map)) { + return false; + } + + mReadback = readback; + aPlaneInfo.mSize = GetSize(); + aPlaneInfo.mStride = map.mStride; + aPlaneInfo.mData = map.mData; + return true; +} + +void RenderAndroidHardwareBufferTextureHost::UnmapPlanes() { + if (mReadback) { + mReadback->Unmap(); + mReadback = nullptr; + } +} + +} // namespace wr +} // namespace mozilla diff --git a/gfx/webrender_bindings/RenderAndroidHardwareBufferTextureHost.h b/gfx/webrender_bindings/RenderAndroidHardwareBufferTextureHost.h new file mode 100644 index 0000000000..cd79c791ed --- /dev/null +++ b/gfx/webrender_bindings/RenderAndroidHardwareBufferTextureHost.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_RenderAndroidHardwareBufferTextureHost_H +#define MOZILLA_GFX_RenderAndroidHardwareBufferTextureHost_H + +#include "GLContextTypes.h" +#include "GLTypes.h" +#include "RenderTextureHostSWGL.h" + +namespace mozilla { + +namespace layers { +class AndroidHardwareBuffer; +} + +namespace wr { + +class RenderAndroidHardwareBufferTextureHost final + : public RenderTextureHostSWGL { + public: + explicit RenderAndroidHardwareBufferTextureHost( + layers::AndroidHardwareBuffer* aAndroidHardwareBuffer); + + wr::WrExternalImage Lock(uint8_t aChannelIndex, gl::GLContext* aGL) override; + void Unlock() override; + + size_t Bytes() override; + + RenderAndroidHardwareBufferTextureHost* + AsRenderAndroidHardwareBufferTextureHost() override { + return this; + } + + // RenderTextureHostSWGL + gfx::SurfaceFormat GetFormat() const override; + gfx::ColorDepth GetColorDepth() const override { + return gfx::ColorDepth::COLOR_8; + } + size_t GetPlaneCount() const override { return 1; } + bool MapPlane(RenderCompositor* aCompositor, uint8_t aChannelIndex, + PlaneInfo& aPlaneInfo) override; + void UnmapPlanes() override; + + layers::AndroidHardwareBuffer* GetAndroidHardwareBuffer() { + return mAndroidHardwareBuffer; + } + + gfx::IntSize GetSize() const; + + private: + virtual ~RenderAndroidHardwareBufferTextureHost(); + bool EnsureLockable(); + void DestroyEGLImage(); + void DeleteTextureHandle(); + already_AddRefed<gfx::DataSourceSurface> ReadTexImage(); + + const RefPtr<layers::AndroidHardwareBuffer> mAndroidHardwareBuffer; + + RefPtr<gl::GLContext> mGL; + EGLImage mEGLImage; + GLuint mTextureHandle; + + RefPtr<gfx::DataSourceSurface> mReadback; +}; + +} // 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..c41e244e06 --- /dev/null +++ b/gfx/webrender_bindings/RenderAndroidSurfaceTextureHost.cpp @@ -0,0 +1,325 @@ +/* -*- 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 "GLReadTexImageHelper.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/webrender/RenderThread.h" +#include "GLContext.h" +#include "AndroidSurfaceTexture.h" + +namespace mozilla { +namespace wr { + +RenderAndroidSurfaceTextureHost::RenderAndroidSurfaceTextureHost( + const java::GeckoSurfaceTexture::GlobalRef& aSurfTex, gfx::IntSize aSize, + gfx::SurfaceFormat aFormat, bool aContinuousUpdate, + Maybe<gfx::Matrix4x4> aTransformOverride, bool aIsRemoteTexture) + : mSurfTex(aSurfTex), + mSize(aSize), + mFormat(aFormat), + mContinuousUpdate(aContinuousUpdate), + mTransformOverride(aTransformOverride), + mPrepareStatus(STATUS_NONE), + mAttachedToGLContext(false), + mIsRemoteTexture(aIsRemoteTexture) { + 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) { + MOZ_ASSERT(aChannelIndex == 0); + MOZ_ASSERT((mPrepareStatus == STATUS_PREPARED) || + (!mSurfTex->IsSingleBuffer() && + mPrepareStatus == STATUS_UPDATE_TEX_IMAGE_NEEDED) || + mIsRemoteTexture); + + if (mIsRemoteTexture) { + EnsureAttachedToGLContext(); + } + + if (mGL.get() != aGL) { + // This should not happen. On android, SingletonGL is used. + MOZ_ASSERT_UNREACHABLE("Unexpected GL context"); + return InvalidToWrExternalImage(); + } + + if (!mSurfTex || !mGL || !mGL->MakeCurrent()) { + return InvalidToWrExternalImage(); + } + + MOZ_ASSERT(mAttachedToGLContext); + if (!mAttachedToGLContext) { + return InvalidToWrExternalImage(); + } + + UpdateTexImageIfNecessary(); + + const auto uvs = GetUvCoords(mSize); + return NativeTextureToWrExternalImage(mSurfTex->GetTexName(), uvs.first.x, + uvs.first.y, uvs.second.x, + uvs.second.y); +} + +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()->SingletonGL(); + } + + 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); + + 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 (mIsRemoteTexture) { + UpdateTexImageIfNecessary(); + } + + 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; +} + +void RenderAndroidSurfaceTextureHost::UpdateTexImageIfNecessary() { + if (mIsRemoteTexture) { + EnsureAttachedToGLContext(); + if (mPrepareStatus == STATUS_NONE) { + PrepareForUse(); + } + if (mPrepareStatus == STATUS_MIGHT_BE_USED_BY_WR) { + NotifyForUse(); + } + } + + 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; + } +} + +gfx::SurfaceFormat RenderAndroidSurfaceTextureHost::GetFormat() const { + MOZ_ASSERT(mFormat == gfx::SurfaceFormat::R8G8B8A8 || + mFormat == gfx::SurfaceFormat::R8G8B8X8); + + if (mFormat == gfx::SurfaceFormat::R8G8B8A8) { + return gfx::SurfaceFormat::B8G8R8A8; + } + + if (mFormat == gfx::SurfaceFormat::R8G8B8X8) { + return gfx::SurfaceFormat::B8G8R8X8; + } + + gfxCriticalNoteOnce + << "Unexpected color format of RenderAndroidSurfaceTextureHost"; + + return gfx::SurfaceFormat::UNKNOWN; +} + +already_AddRefed<DataSourceSurface> +RenderAndroidSurfaceTextureHost::ReadTexImage() { + if (!mGL) { + mGL = RenderThread::Get()->SingletonGL(); + if (!mGL) { + return nullptr; + } + } + + /* Allocate resulting image surface */ + int32_t stride = mSize.width * BytesPerPixel(GetFormat()); + RefPtr<DataSourceSurface> surf = + Factory::CreateDataSourceSurfaceWithStride(mSize, GetFormat(), stride); + if (!surf) { + return nullptr; + } + + layers::ShaderConfigOGL config = layers::ShaderConfigFromTargetAndFormat( + LOCAL_GL_TEXTURE_EXTERNAL, mFormat); + int shaderConfig = config.mFeatures; + + bool ret = mGL->ReadTexImageHelper()->ReadTexImage( + surf, mSurfTex->GetTexName(), LOCAL_GL_TEXTURE_EXTERNAL, mSize, + shaderConfig, /* aYInvert */ false); + if (!ret) { + return nullptr; + } + + return surf.forget(); +} + +bool RenderAndroidSurfaceTextureHost::MapPlane(RenderCompositor* aCompositor, + uint8_t aChannelIndex, + PlaneInfo& aPlaneInfo) { + UpdateTexImageIfNecessary(); + + RefPtr<gfx::DataSourceSurface> readback = ReadTexImage(); + if (!readback) { + return false; + } + + DataSourceSurface::MappedSurface map; + if (!readback->Map(DataSourceSurface::MapType::READ, &map)) { + return false; + } + + mReadback = readback; + aPlaneInfo.mSize = mSize; + aPlaneInfo.mStride = map.mStride; + aPlaneInfo.mData = map.mData; + return true; +} + +void RenderAndroidSurfaceTextureHost::UnmapPlanes() { + if (mReadback) { + mReadback->Unmap(); + mReadback = nullptr; + } +} + +std::pair<gfx::Point, gfx::Point> RenderAndroidSurfaceTextureHost::GetUvCoords( + gfx::IntSize aTextureSize) const { + gfx::Matrix4x4 transform; + + // GetTransformMatrix() returns the transform set by the producer side of the + // SurfaceTexture that must be applied to texture coordinates when + // sampling. In some cases we may have set an override value, such as in + // AndroidNativeWindowTextureData where we own the producer side, or for + // MediaCodec output on devices where where we know the value is incorrect. + if (mTransformOverride) { + transform = *mTransformOverride; + } else if (mSurfTex) { + const auto& surf = java::sdk::SurfaceTexture::LocalRef( + java::sdk::SurfaceTexture::Ref::From(mSurfTex)); + gl::AndroidSurfaceTexture::GetTransformMatrix(surf, &transform); + } + + // We expect this transform to always be rectilinear, usually just a + // y-flip and sometimes an x and y scale. This allows this function + // to simply transform and return 2 points here instead of 4. + MOZ_ASSERT(transform.IsRectilinear(), + "Unexpected non-rectilinear transform returned from " + "SurfaceTexture.GetTransformMatrix()"); + + transform.PostScale(aTextureSize.width, aTextureSize.height, 0.0); + + gfx::Point uv0 = gfx::Point(0.0, 0.0); + gfx::Point uv1 = gfx::Point(1.0, 1.0); + uv0 = transform.TransformPoint(uv0); + uv1 = transform.TransformPoint(uv1); + + return std::make_pair(uv0, uv1); +} + +} // namespace wr +} // namespace mozilla diff --git a/gfx/webrender_bindings/RenderAndroidSurfaceTextureHost.h b/gfx/webrender_bindings/RenderAndroidSurfaceTextureHost.h new file mode 100644 index 0000000000..1a6238a72f --- /dev/null +++ b/gfx/webrender_bindings/RenderAndroidSurfaceTextureHost.h @@ -0,0 +1,96 @@ +/* -*- 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 "RenderTextureHostSWGL.h" + +namespace mozilla { + +namespace gfx { +class DataSourceSurface; +} + +namespace wr { + +class RenderAndroidSurfaceTextureHost final : public RenderTextureHostSWGL { + public: + explicit RenderAndroidSurfaceTextureHost( + const java::GeckoSurfaceTexture::GlobalRef& aSurfTex, gfx::IntSize aSize, + gfx::SurfaceFormat aFormat, bool aContinuousUpdate, + Maybe<gfx::Matrix4x4> aTransformOverride, bool aIsRemoteTexture); + + wr::WrExternalImage Lock(uint8_t aChannelIndex, gl::GLContext* aGL) override; + void Unlock() override; + + size_t Bytes() override { + return mSize.width * mSize.height * BytesPerPixel(mFormat); + } + + void PrepareForUse() override; + void NotifyForUse() override; + void NotifyNotUsed() override; + + // RenderTextureHostSWGL + gfx::SurfaceFormat GetFormat() const override; + gfx::ColorDepth GetColorDepth() const override { + return gfx::ColorDepth::COLOR_8; + } + size_t GetPlaneCount() const override { return 1; } + bool MapPlane(RenderCompositor* aCompositor, uint8_t aChannelIndex, + PlaneInfo& aPlaneInfo) override; + void UnmapPlanes() override; + + RenderAndroidSurfaceTextureHost* AsRenderAndroidSurfaceTextureHost() + override { + return this; + } + + void UpdateTexImageIfNecessary(); + + 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; + const Maybe<gfx::Matrix4x4> mTransformOverride; + + private: + virtual ~RenderAndroidSurfaceTextureHost(); + bool EnsureAttachedToGLContext(); + + already_AddRefed<gfx::DataSourceSurface> ReadTexImage(); + + // Returns the UV coordinates to be used when sampling the texture, taking in + // to account the SurfaceTexture's transform if applicable. + std::pair<gfx::Point, gfx::Point> GetUvCoords( + gfx::IntSize aTextureSize) const override; + + enum PrepareStatus { + STATUS_NONE, + STATUS_MIGHT_BE_USED_BY_WR, + STATUS_UPDATE_TEX_IMAGE_NEEDED, + STATUS_PREPARED + }; + + PrepareStatus mPrepareStatus; + bool mAttachedToGLContext; + + RefPtr<gl::GLContext> mGL; + + RefPtr<gfx::DataSourceSurface> mReadback; + + bool mIsRemoteTexture; +}; + +} // 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..920556c7ef --- /dev/null +++ b/gfx/webrender_bindings/RenderBufferTextureHost.cpp @@ -0,0 +1,254 @@ +/* -*- 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) { + if (!mLocked) { + if (!GetBuffer()) { + if (!mDestroyed) { + // 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, &mMap))) { + mSurface = nullptr; + gfxCriticalNote << "Failed to map Surface"; + return InvalidToWrExternalImage(); + } + } else { + const layers::YCbCrDescriptor& desc = mDescriptor.get_YCbCrDescriptor(); + auto cbcrSize = layers::ImageDataSerializer::GetCroppedCbCrSize(desc); + + mYSurface = gfx::Factory::CreateWrappingDataSourceSurface( + layers::ImageDataSerializer::GetYChannel(GetBuffer(), desc), + desc.yStride(), desc.display().Size(), gfx::SurfaceFormat::A8); + mCbSurface = gfx::Factory::CreateWrappingDataSourceSurface( + layers::ImageDataSerializer::GetCbChannel(GetBuffer(), desc), + desc.cbCrStride(), cbcrSize, gfx::SurfaceFormat::A8); + mCrSurface = gfx::Factory::CreateWrappingDataSourceSurface( + layers::ImageDataSerializer::GetCrChannel(GetBuffer(), desc), + desc.cbCrStride(), 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, &mYMap) || + !mCbSurface->Map(gfx::DataSourceSurface::MapType::READ, + &mCbMap) || + !mCrSurface->Map(gfx::DataSourceSurface::MapType::READ, + &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); + } + } +} + +size_t RenderBufferTextureHost::GetPlaneCount() const { + switch (mDescriptor.type()) { + case layers::BufferDescriptor::TYCbCrDescriptor: + return 3; + default: + return 1; + } +} + +gfx::SurfaceFormat RenderBufferTextureHost::GetFormat() const { + switch (mDescriptor.type()) { + case layers::BufferDescriptor::TYCbCrDescriptor: + return gfx::SurfaceFormat::YUV; + default: + return mDescriptor.get_RGBDescriptor().format(); + } +} + +gfx::ColorDepth RenderBufferTextureHost::GetColorDepth() const { + switch (mDescriptor.type()) { + case layers::BufferDescriptor::TYCbCrDescriptor: + return mDescriptor.get_YCbCrDescriptor().colorDepth(); + default: + return gfx::ColorDepth::COLOR_8; + } +} + +gfx::YUVRangedColorSpace RenderBufferTextureHost::GetYUVColorSpace() const { + switch (mDescriptor.type()) { + case layers::BufferDescriptor::TYCbCrDescriptor: + return gfx::GetYUVRangedColorSpace(mDescriptor.get_YCbCrDescriptor()); + default: + return gfx::YUVRangedColorSpace::Default; + } +} + +bool RenderBufferTextureHost::MapPlane(RenderCompositor* aCompositor, + uint8_t aChannelIndex, + PlaneInfo& aPlaneInfo) { + if (!mBuffer) { + if (!mDestroyed) { + // 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.display().Size(); + break; + case 1: + aPlaneInfo.mData = + layers::ImageDataSerializer::GetCbChannel(mBuffer, desc); + aPlaneInfo.mStride = desc.cbCrStride(); + aPlaneInfo.mSize = + layers::ImageDataSerializer::GetCroppedCbCrSize(desc); + break; + case 2: + aPlaneInfo.mData = + layers::ImageDataSerializer::GetCrChannel(mBuffer, desc); + aPlaneInfo.mStride = desc.cbCrStride(); + aPlaneInfo.mSize = + layers::ImageDataSerializer::GetCroppedCbCrSize(desc); + 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 RenderBufferTextureHost::UnmapPlanes() {} + +void RenderBufferTextureHost::Destroy() { + mBuffer = nullptr; + mDestroyed = true; +} + +} // namespace wr +} // namespace mozilla diff --git a/gfx/webrender_bindings/RenderBufferTextureHost.h b/gfx/webrender_bindings/RenderBufferTextureHost.h new file mode 100644 index 0000000000..0fd2ef26b7 --- /dev/null +++ b/gfx/webrender_bindings/RenderBufferTextureHost.h @@ -0,0 +1,81 @@ +/* -*- 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 "RenderTextureHostSWGL.h" + +namespace mozilla { +namespace wr { + +class RenderBufferTextureHost final : public RenderTextureHostSWGL { + public: + RenderBufferTextureHost(uint8_t* aBuffer, + const layers::BufferDescriptor& aDescriptor); + + // RenderTextureHost + wr::WrExternalImage Lock(uint8_t aChannelIndex, gl::GLContext* aGL) 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); + + // RenderTextureHostSWGL + size_t GetPlaneCount() const override; + + gfx::SurfaceFormat GetFormat() const override; + + gfx::ColorDepth GetColorDepth() const override; + + gfx::YUVRangedColorSpace GetYUVColorSpace() const override; + + bool MapPlane(RenderCompositor* aCompositor, uint8_t aChannelIndex, + PlaneInfo& aPlaneInfo) override; + + void UnmapPlanes() override; + + void Destroy() override; + + 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; + + bool mDestroyed = false; +}; + +} // namespace wr +} // namespace mozilla + +#endif // MOZILLA_GFX_RENDERBUFFERTEXTUREHOST_H diff --git a/gfx/webrender_bindings/RenderCompositor.cpp b/gfx/webrender_bindings/RenderCompositor.cpp new file mode 100644 index 0000000000..f4211328f9 --- /dev/null +++ b/gfx/webrender_bindings/RenderCompositor.cpp @@ -0,0 +1,286 @@ +/* -*- 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 "gfxPlatform.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/RenderCompositorLayersSWGL.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/widget/WinCompositorWidget.h" +#endif + +#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WAYLAND) || defined(MOZ_X11) +# include "mozilla/webrender/RenderCompositorEGL.h" +#endif + +#ifdef MOZ_WAYLAND +# include "mozilla/webrender/RenderCompositorNative.h" +#endif + +#ifdef XP_MACOSX +# include "mozilla/webrender/RenderCompositorNative.h" +#endif + +namespace mozilla::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_backdrop_surface(void* aCompositor, + wr::NativeSurfaceId aId, + wr::ColorF aColor) { + RenderCompositor* compositor = static_cast<RenderCompositor*>(aCompositor); + compositor->CreateBackdropSurface(aId, aColor); +} + +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, wr::ColorF aClearColor, + const wr::DeviceIntRect* aDirtyRects, + size_t aNumDirtyRects, + const wr::DeviceIntRect* aOpaqueRects, + size_t aNumOpaqueRects) { + RenderCompositor* compositor = static_cast<RenderCompositor*>(aCompositor); + compositor->StartCompositing(aClearColor, 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); +} + +void wr_compositor_get_capabilities(void* aCompositor, + CompositorCapabilities* aCaps) { + RenderCompositor* compositor = static_cast<RenderCompositor*>(aCompositor); + compositor->GetCompositorCapabilities(aCaps); +} + +void wr_compositor_get_window_visibility(void* aCompositor, + WindowVisibility* aVisibility) { + RenderCompositor* compositor = static_cast<RenderCompositor*>(aCompositor); + compositor->GetWindowVisibility(aVisibility); +} + +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 aNRects) { + RenderCompositor* compositor = static_cast<RenderCompositor*>(aCompositor); + compositor->SetBufferDamageRegion(aRects, aNRects); +} + +/* static */ +UniquePtr<RenderCompositor> RenderCompositor::Create( + const RefPtr<widget::CompositorWidget>& aWidget, nsACString& aError) { + if (aWidget->GetCompositorOptions().UseSoftwareWebRender()) { +#ifdef XP_MACOSX + // Mac uses NativeLayerCA + if (!gfxPlatform::IsHeadless()) { + return RenderCompositorNativeSWGL::Create(aWidget, aError); + } +#elif defined(MOZ_WAYLAND) + if (gfx::gfxVars::UseWebRenderCompositor()) { + return RenderCompositorNativeSWGL::Create(aWidget, aError); + } +#endif + UniquePtr<RenderCompositor> comp = + RenderCompositorLayersSWGL::Create(aWidget, aError); + if (comp) { + return comp; + } +#if defined(MOZ_WIDGET_ANDROID) + // On Android, we do not want to fallback from RenderCompositorOGLSWGL to + // RenderCompositorSWGL. + if (aWidget->GetCompositorOptions().AllowSoftwareWebRenderOGL()) { + return nullptr; + } +#endif + return RenderCompositorSWGL::Create(aWidget, aError); + } + +#ifdef XP_WIN + if (gfx::gfxVars::UseWebRenderANGLE()) { + return RenderCompositorANGLE::Create(aWidget, aError); + } +#endif + +#if defined(MOZ_WAYLAND) + if (gfx::gfxVars::UseWebRenderCompositor()) { + return RenderCompositorNativeOGL::Create(aWidget, aError); + } +#endif + +#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WAYLAND) || defined(MOZ_X11) + 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(aWidget, aError); +#else + return RenderCompositorOGL::Create(aWidget, aError); +#endif +} + +RenderCompositor::RenderCompositor( + const RefPtr<widget::CompositorWidget>& aWidget) + : mWidget(aWidget) {} + +RenderCompositor::~RenderCompositor() = default; + +bool RenderCompositor::MakeCurrent() { return gl()->MakeCurrent(); } + +void RenderCompositor::GetCompositorCapabilities( + CompositorCapabilities* aCaps) { + if (StaticPrefs::gfx_webrender_compositor_max_update_rects_AtStartup() > 0) { + aCaps->max_update_rects = 1; + } else { + aCaps->max_update_rects = 0; + } +} + +void RenderCompositor::GetWindowVisibility(WindowVisibility* aVisibility) { +#ifdef XP_WIN + auto* widget = mWidget->AsWindows(); + if (!widget) { + return; + } + aVisibility->size_mode = ToWrWindowSizeMode(widget->GetWindowSizeMode()); + aVisibility->is_fully_occluded = widget->GetWindowIsFullyOccluded(); +#endif +} + +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 mozilla::wr diff --git a/gfx/webrender_bindings/RenderCompositor.h b/gfx/webrender_bindings/RenderCompositor.h new file mode 100644 index 0000000000..89b06395c1 --- /dev/null +++ b/gfx/webrender_bindings/RenderCompositor.h @@ -0,0 +1,222 @@ +/* -*- 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 RenderCompositorLayersSWGL; +class RenderCompositorD3D11SWGL; + +class RenderCompositor { + public: + static UniquePtr<RenderCompositor> Create( + const RefPtr<widget::CompositorWidget>& aWidget, nsACString& aError); + + RenderCompositor(const 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; + } + + virtual RenderCompositorLayersSWGL* AsRenderCompositorLayersSWGL() { + 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; } + + // 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 CreateBackdropSurface(wr::NativeSurfaceId aId, + wr::ColorF aColor) {} + 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(wr::ColorF aClearColor, + const wr::DeviceIntRect* aDirtyRects, + size_t aNumDirtyRects, + const wr::DeviceIntRect* aOpaqueRects, + size_t aNumOpaqueRects) {} + virtual void EnableNativeCompositor(bool aEnable) {} + virtual void DeInit() {} + // Overrides any of the default compositor capabilities for behavior this + // compositor might require. + virtual void GetCompositorCapabilities(CompositorCapabilities* aCaps); + + virtual void GetWindowVisibility(WindowVisibility* aVisibility); + + // 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..2ea2bf1124 --- /dev/null +++ b/gfx/webrender_bindings/RenderCompositorANGLE.cpp @@ -0,0 +1,1059 @@ +/* -*- 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/ProfilerMarkers.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 { + +extern LazyLogModule gRenderThreadLog; +#define LOG(...) MOZ_LOG(gRenderThreadLog, LogLevel::Debug, (__VA_ARGS__)) + +/* static */ +UniquePtr<RenderCompositor> RenderCompositorANGLE::Create( + const RefPtr<widget::CompositorWidget>& aWidget, nsACString& aError) { + RefPtr<gl::GLContext> gl = RenderThread::Get()->SingletonGL(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>(aWidget, std::move(gl)); + if (!compositor->Initialize(aError)) { + return nullptr; + } + return compositor; +} + +RenderCompositorANGLE::RenderCompositorANGLE( + const RefPtr<widget::CompositorWidget>& aWidget, + RefPtr<gl::GLContext>&& aGL) + : RenderCompositor(aWidget), + mGL(aGL), + mEGLConfig(nullptr), + mEGLSurface(nullptr), + mUseTripleBuffering(false), + mUseAlpha(false), + mUseNativeCompositor(true), + mUsePartialPresent(false), + mFullRender(false), + mDisablingNativeCompositor(false) { + MOZ_ASSERT(mGL); + LOG("RenderCompositorANGLE::RenderCompositorANGLE()"); +} + +RenderCompositorANGLE::~RenderCompositorANGLE() { + LOG("RenderCompositorANGLE::~RenderCompositorANGLE()"); + + DestroyEGLSurface(); + MOZ_ASSERT(!mEGLSurface); +} + +ID3D11Device* RenderCompositorANGLE::GetDeviceOfEGLDisplay(nsACString& aError) { + const auto& gle = gl::GLContextEGL::Cast(mGL); + const auto& egl = gle->mEgl; + MOZ_ASSERT(egl); + if (!egl || + !egl->mLib->IsExtensionSupported(gl::EGLLibExtension::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::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; + } + + // Force enable alpha channel to make sure ANGLE use correct framebuffer + // formart + const auto& gle = gl::GLContextEGL::Cast(mGL); + const auto& egl = gle->mEgl; + if (!gl::CreateConfig(*egl, &mEGLConfig, /* bpp */ 32, + /* enableDepthBuffer */ false, mGL->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; + } + + // Disable native compositor when fast snapshot is needed. + // Taking snapshot of native compositor is very slow on Windows. + if (mWidget->GetCompositorOptions().NeedFastSnaphot()) { + mUseNativeCompositor = false; + } + + // Create DCLayerTree when DirectComposition is used. + if (gfx::gfxVars::UseWebRenderDCompWin()) { + HWND compositorHwnd = GetCompositorHwnd(); + if (compositorHwnd) { + mDCLayerTree = DCLayerTree::Create(mGL, 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; + + bool useFlipSequential = gfx::gfxVars::UseWebRenderFlipSequentialWin(); + if (useFlipSequential && !mWidget->AsWindows()->GetCompositorHwnd()) { + useFlipSequential = false; + gfxCriticalNoteOnce << "FLIP_SEQUENTIAL needs CompositorHwnd. Fallback"; + } + + if (useFlipSequential) { + useTripleBuffering = gfx::gfxVars::UseWebRenderTripleBufferingWin(); + if (useTripleBuffering) { + desc.BufferCount = 3; + } else { + desc.BufferCount = 2; + } + desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; + desc.Scaling = DXGI_SCALING_NONE; + } else { + desc.BufferCount = 1; + desc.SwapEffect = DXGI_SWAP_EFFECT_SEQUENTIAL; + desc.Scaling = DXGI_SCALING_STRETCH; + } + 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 (useFlipSequential) { + gfxCriticalNoteOnce << "FLIP_SEQUENTIAL is not supported. Fallback"; + } + } + + if (!mSwapChain) { + if (mWidget->AsWindows()->GetCompositorHwnd()) { + // Destroy compositor window. + mWidget->AsWindows()->DestroyCompositorWindow(); + hwnd = mWidget->AsWindows()->GetHwnd(); + } + + 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 %lx)", 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", 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.min.x, bufferSize.width)); + int top = std::max(0, std::min(rect.min.y, bufferSize.height)); + int right = std::max(0, std::min(rect.max.x, bufferSize.width)); + int bottom = std::max(0, std::min(rect.max.y, 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. + if (!StaticPrefs::gfx_webrender_wait_gpu_finished_disabled_AtStartup()) { + return WaitForPreviousGraphicsCommandsFinishedQuery(); + } + return true; +} + +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_NONE}; + + const auto buffer = reinterpret_cast<EGLClientBuffer>(backBuf.get()); + + const auto& gle = gl::GLContextEGL::Cast(mGL); + 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); + mCtx->Flush(); + 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, + D3D11_ASYNC_GETDATA_DONOTFLUSH) != S_OK) { + break; + } + + mRecycledQuery = queryPair.second; + mLastCompletedFrameId = queryPair.first; + mWaitForPresentQueries.pop(); + } + + nsPrintfCString marker("Pending frames %u", + (uint32_t)mWaitForPresentQueries.size()); + PROFILER_MARKER_TEXT("GetLastCompletedFrameId", GRAPHICS, {}, marker); + + 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(); +} + +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); +} + +void RenderCompositorANGLE::GetCompositorCapabilities( + CompositorCapabilities* aCaps) { + RenderCompositor::GetCompositorCapabilities(aCaps); + + if (StaticPrefs::gfx_webrender_dcomp_use_virtual_surfaces_AtStartup()) { + aCaps->virtual_surface_size = VIRTUAL_SURFACE_SIZE; + } else { + aCaps->virtual_surface_size = 0; + } + // DComp video overlay does not support negative scaling. See Bug 1831820 + aCaps->supports_external_compositor_surface_negative_scaling = false; +} + +void RenderCompositorANGLE::EnableNativeCompositor(bool aEnable) { + // XXX Re-enable native compositor is not handled yet. + MOZ_RELEASE_ASSERT(!mDisablingNativeCompositor); + MOZ_RELEASE_ASSERT(!aEnable); + LOG("RenderCompositorANGLE::EnableNativeCompositor() aEnable %d", 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..cb1203e3fe --- /dev/null +++ b/gfx/webrender_bindings/RenderCompositorANGLE.h @@ -0,0 +1,161 @@ +/* -*- 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( + const RefPtr<widget::CompositorWidget>& aWidget, nsACString& aError); + + explicit RenderCompositorANGLE( + const RefPtr<widget::CompositorWidget>& aWidget, + RefPtr<gl::GLContext>&& aGL); + 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 mGL; } + + 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; + + // 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; + void GetCompositorCapabilities(CompositorCapabilities* aCaps) 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); + RefPtr<ID3D11Query> GetD3D11Query(); + void ReleaseNativeCompositorResources(); + HWND GetCompositorHwnd(); + + RefPtr<gl::GLContext> mGL; + + 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..1fdae182c2 --- /dev/null +++ b/gfx/webrender_bindings/RenderCompositorD3D11SWGL.cpp @@ -0,0 +1,484 @@ +/* -*- 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 "gfxConfig.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/widget/CompositorWidget.h" +#include "mozilla/layers/Effects.h" +#include "mozilla/webrender/RenderD3D11TextureHost.h" +#include "RenderCompositorRecordedFrame.h" +#include "RenderThread.h" + +namespace mozilla { +using namespace layers; + +namespace wr { + +extern LazyLogModule gRenderThreadLog; +#define LOG(...) MOZ_LOG(gRenderThreadLog, LogLevel::Debug, (__VA_ARGS__)) + +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( + const RefPtr<widget::CompositorWidget>& aWidget, nsACString& aError) { + if (!aWidget->GetCompositorOptions().AllowSoftwareWebRenderD3D11() || + !gfx::gfxConfig::IsEnabled(gfx::Feature::D3D11_COMPOSITING)) { + return nullptr; + } + + void* ctx = wr_swgl_create_context(); + if (!ctx) { + gfxCriticalNote << "Failed SWGL context creation for WebRender"; + return nullptr; + } + + RefPtr<CompositorD3D11> compositor = MakeAndAddRef<CompositorD3D11>(aWidget); + nsCString log; + if (!compositor->Initialize(&log)) { + gfxCriticalNote << "Failed to initialize CompositorD3D11 for SWGL: " + << log.get(); + return nullptr; + } + return MakeUnique<RenderCompositorD3D11SWGL>(compositor, aWidget, ctx); +} + +RenderCompositorD3D11SWGL::RenderCompositorD3D11SWGL( + CompositorD3D11* aCompositor, + const RefPtr<widget::CompositorWidget>& aWidget, void* aContext) + : RenderCompositorLayersSWGL(aCompositor, aWidget, aContext) { + LOG("RenderCompositorD3D11SWGL::RenderCompositorD3D11SWGL()"); + + mSyncObject = GetCompositorD3D11()->GetSyncObject(); +} + +RenderCompositorD3D11SWGL::~RenderCompositorD3D11SWGL() { + LOG("RenderCompositorD3D11SWGL::~RenderCompositorD3D11SWGL()"); +} + +bool RenderCompositorD3D11SWGL::BeginFrame() { + if (!RenderCompositorLayersSWGL::BeginFrame()) { + return false; + } + + mUploadMode = GetUploadMode(); + return true; +} + +void RenderCompositorD3D11SWGL::HandleExternalImage( + RenderTextureHost* aExternalImage, FrameSurface& aFrameSurface) { + // 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 = aExternalImage->AsRenderDXGITextureHost()) { + if (!host->EnsureD3D11Texture2D(GetDevice())) { + return; + } + + layer = new DataTextureSourceD3D11(GetDevice(), host->GetFormat(), + host->GetD3D11Texture2D()); + if (host->GetFormat() == gfx::SurfaceFormat::NV12 || + host->GetFormat() == gfx::SurfaceFormat::P010 || + host->GetFormat() == gfx::SurfaceFormat::P016) { + const auto yuv = FromYUVRangedColorSpace(host->GetYUVColorSpace()); + texturedEffect = + new EffectNV12(layer, yuv.space, yuv.range, host->GetColorDepth(), + aFrameSurface.mFilter); + } else { + MOZ_ASSERT(host->GetFormat() == gfx::SurfaceFormat::B8G8R8X8 || + host->GetFormat() == gfx::SurfaceFormat::B8G8R8A8); + texturedEffect = CreateTexturedEffect(host->GetFormat(), layer, + aFrameSurface.mFilter, true); + } + size = host->GetSize(0); + host->LockInternal(); + } else if (auto* host = aExternalImage->AsRenderDXGIYCbCrTextureHost()) { + if (!host->EnsureD3D11Texture2D(GetDevice())) { + return; + } + + layer = new DataTextureSourceD3D11(GetDevice(), gfx::SurfaceFormat::A8, + host->GetD3D11Texture2D(0)); + RefPtr<DataTextureSourceD3D11> u = new DataTextureSourceD3D11( + GetDevice(), gfx::SurfaceFormat::A8, host->GetD3D11Texture2D(1)); + layer->SetNextSibling(u); + RefPtr<DataTextureSourceD3D11> v = new DataTextureSourceD3D11( + GetDevice(), gfx::SurfaceFormat::A8, host->GetD3D11Texture2D(2)); + u->SetNextSibling(v); + + const auto yuv = FromYUVRangedColorSpace(host->GetYUVColorSpace()); + texturedEffect = + new EffectYCbCr(layer, yuv.space, yuv.range, host->GetColorDepth(), + aFrameSurface.mFilter); + size = host->GetSize(0); + host->LockInternal(); + } + + gfx::Rect drawRect(0, 0, size.width, size.height); + + EffectChain effect; + effect.mPrimaryEffect = texturedEffect; + mCompositor->DrawQuad(drawRect, aFrameSurface.mClipRect, effect, 1.0, + aFrameSurface.mTransform, drawRect); + + if (auto* host = aExternalImage->AsRenderDXGITextureHost()) { + host->Unlock(); + } else if (auto* host = aExternalImage->AsRenderDXGIYCbCrTextureHost()) { + host->Unlock(); + } +} + +void RenderCompositorD3D11SWGL::Pause() {} + +bool RenderCompositorD3D11SWGL::Resume() { return true; } + +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; + } +} + +UniquePtr<RenderCompositorLayersSWGL::Surface> +RenderCompositorD3D11SWGL::DoCreateSurface(wr::DeviceIntSize aTileSize, + bool aIsOpaque) { + return MakeUnique<SurfaceD3D11SWGL>(aTileSize, aIsOpaque); +} + +SurfaceD3D11SWGL::SurfaceD3D11SWGL(wr::DeviceIntSize aTileSize, bool aIsOpaque) + : Surface(aTileSize, aIsOpaque) {} + +RenderCompositorD3D11SWGL::TileD3D11::TileD3D11( + layers::DataTextureSourceD3D11* aTexture, ID3D11Texture2D* aStagingTexture, + gfx::DataSourceSurface* aDataSourceSurface, Surface* aOwner, + RenderCompositorD3D11SWGL* aRenderCompositor) + : Tile(), + mTexture(aTexture), + mStagingTexture(aStagingTexture), + mSurface(aDataSourceSurface), + mOwner(aOwner->AsSurfaceD3D11SWGL()), + mRenderCompositor(aRenderCompositor) {} + +bool RenderCompositorD3D11SWGL::TileD3D11::Map(wr::DeviceIntRect aDirtyRect, + wr::DeviceIntRect aValidRect, + void** aData, int32_t* aStride) { + const UploadMode uploadMode = mRenderCompositor->GetUploadMode(); + const gfx::IntSize tileSize = mOwner->TileSize(); + + if (!IsValid()) { + return false; + } + + // 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 (uploadMode == Upload_Immediate) { + if (mStagingTexture) { + MOZ_ASSERT(!mSurface); + mStagingTexture = nullptr; + mSurface = mRenderCompositor->CreateStagingSurface(tileSize); + } + } else { + if (mSurface) { + MOZ_ASSERT(!mStagingTexture); + mSurface = nullptr; + mStagingTexture = mRenderCompositor->CreateStagingTexture(tileSize); + } + } + + mRenderCompositor->mCurrentStagingTexture = mStagingTexture; + mRenderCompositor->mCurrentStagingTextureIsTemp = false; + + if (uploadMode == Upload_Immediate) { + gfx::DataSourceSurface::MappedSurface map; + if (!mSurface->Map(gfx::DataSourceSurface::READ_WRITE, &map)) { + return false; + } + + *aData = map.mData + aValidRect.min.y * map.mStride + aValidRect.min.x * 4; + *aStride = map.mStride; + // Ensure our mapped data is accessible by writing to the beginning and end + // of the dirty region. See bug 171519 + uint32_t* probeData = (uint32_t*)map.mData + + aDirtyRect.min.y * (map.mStride / 4) + + aDirtyRect.min.x; + *probeData = 0; + uint32_t* probeDataEnd = (uint32_t*)map.mData + + (aDirtyRect.max.y - 1) * (map.mStride / 4) + + (aDirtyRect.max.x - 1); + *probeDataEnd = 0; + + mValidRect = gfx::Rect(aValidRect.min.x, aValidRect.min.y, + aValidRect.width(), aValidRect.height()); + return true; + } + + if (!mRenderCompositor->mCurrentStagingTexture) { + return false; + } + + RefPtr<ID3D11DeviceContext> context; + mRenderCompositor->GetDevice()->GetImmediateContext(getter_AddRefs(context)); + + D3D11_MAPPED_SUBRESOURCE mappedSubresource; + + bool shouldBlock = uploadMode == Upload_Staging; + + HRESULT hr = context->Map( + mRenderCompositor->mCurrentStagingTexture, 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. + mRenderCompositor->mCurrentStagingTextureIsTemp = true; + + // Try grabbing a texture from the staging pool and see if we can use that. + if (uploadMode == Upload_StagingPooled && mOwner->mStagingPool.Length()) { + mRenderCompositor->mCurrentStagingTexture = + mOwner->mStagingPool.ElementAt(0); + mOwner->mStagingPool.RemoveElementAt(0); + hr = context->Map(mRenderCompositor->mCurrentStagingTexture, 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) { + mOwner->mStagingPool.AppendElement( + mRenderCompositor->mCurrentStagingTexture); + } + } + + // No staging textures, or we tried one and it was busy. Allocate a brand + // new one instead. + if (hr == DXGI_ERROR_WAS_STILL_DRAWING) { + mRenderCompositor->mCurrentStagingTexture = + mRenderCompositor->CreateStagingTexture(tileSize); + if (!mRenderCompositor->mCurrentStagingTexture) { + return false; + } + hr = context->Map(mRenderCompositor->mCurrentStagingTexture, 0, + D3D11_MAP_READ_WRITE, 0, &mappedSubresource); + } + } + if (!SUCCEEDED(hr)) { + gfxCriticalError() << "Failed to map tile: " << gfx::hexa(hr); + // This is only expected to fail if we hit a device reset. + MOZ_RELEASE_ASSERT( + mRenderCompositor->GetDevice()->GetDeviceRemovedReason() != S_OK); + return false; + } + + // 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.min.y * mappedSubresource.RowPitch + aValidRect.min.x * 4; + *aStride = mappedSubresource.RowPitch; + + // Ensure our mapped data is accessible by writing to the beginning and end + // of the dirty region. See bug 171519 + uint32_t* probeData = (uint32_t*)mappedSubresource.pData + + aDirtyRect.min.y * (mappedSubresource.RowPitch / 4) + + aDirtyRect.min.x; + *probeData = 0; + uint32_t* probeDataEnd = + (uint32_t*)mappedSubresource.pData + + (aDirtyRect.max.y - 1) * (mappedSubresource.RowPitch / 4) + + (aDirtyRect.max.x - 1); + *probeDataEnd = 0; + + // Store the new valid rect, so that we can composite only those pixels + mValidRect = gfx::Rect(aValidRect.min.x, aValidRect.min.y, aValidRect.width(), + aValidRect.height()); + + return true; +} + +void RenderCompositorD3D11SWGL::TileD3D11::Unmap( + const gfx::IntRect& aDirtyRect) { + const UploadMode uploadMode = mRenderCompositor->GetUploadMode(); + + if (!IsValid()) { + return; + } + + if (mSurface) { + MOZ_ASSERT(uploadMode == Upload_Immediate); + mSurface->Unmap(); + nsIntRegion dirty(aDirtyRect); + // 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. + mTexture->Update(mSurface, &dirty); + return; + } + + if (!mRenderCompositor->mCurrentStagingTexture) { + return; + } + + RefPtr<ID3D11DeviceContext> context; + mRenderCompositor->GetDevice()->GetImmediateContext(getter_AddRefs(context)); + + context->Unmap(mRenderCompositor->mCurrentStagingTexture, 0); + + D3D11_BOX box; + box.front = 0; + box.back = 1; + box.left = aDirtyRect.X(); + box.top = aDirtyRect.Y(); + box.right = aDirtyRect.XMost(); + box.bottom = aDirtyRect.YMost(); + + context->CopySubresourceRegion( + mTexture->GetD3D11Texture(), 0, aDirtyRect.x, aDirtyRect.y, 0, + mRenderCompositor->mCurrentStagingTexture, 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 (mRenderCompositor->mCurrentStagingTextureIsTemp && + uploadMode == Upload_StagingPooled) { + static const uint32_t kMaxPoolSize = 5; + if (mOwner->mStagingPool.Length() < kMaxPoolSize) { + mOwner->mStagingPool.AppendElement( + mRenderCompositor->mCurrentStagingTexture); + } + } + + mRenderCompositor->mCurrentStagingTexture = nullptr; + mRenderCompositor->mCurrentStagingTextureIsTemp = false; +} + +bool RenderCompositorD3D11SWGL::TileD3D11::IsValid() { return !!mTexture; } + +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 = + GetDevice()->CreateTexture2D(&desc, nullptr, getter_AddRefs(cpuTexture)); + MOZ_ASSERT(SUCCEEDED(hr)); + if (!cpuTexture) { + gfxCriticalNote << "Failed to create StagingTexture: " << aSize; + RenderThread::Get()->HandleWebRenderError(WebRenderError::NEW_SURFACE); + } + return cpuTexture.forget(); +} + +already_AddRefed<gfx::DataSourceSurface> +RenderCompositorD3D11SWGL::CreateStagingSurface(const gfx::IntSize aSize) { + return gfx::Factory::CreateDataSourceSurface(aSize, + gfx::SurfaceFormat::B8G8R8A8); +} + +UniquePtr<RenderCompositorLayersSWGL::Tile> +RenderCompositorD3D11SWGL::DoCreateTile(Surface* aSurface) { + MOZ_RELEASE_ASSERT(aSurface); + + const auto tileSize = aSurface->TileSize(); + + if (mUploadMode == Upload_Immediate) { + RefPtr<DataTextureSourceD3D11> source = + new DataTextureSourceD3D11(gfx::SurfaceFormat::B8G8R8A8, mCompositor, + layers::TextureFlags::NO_FLAGS); + RefPtr<gfx::DataSourceSurface> surf = CreateStagingSurface(tileSize); + return MakeUnique<TileD3D11>(source, nullptr, surf, aSurface, this); + } + + MOZ_ASSERT(mUploadMode == Upload_Staging || + mUploadMode == Upload_StagingNoBlock || + mUploadMode == Upload_StagingPooled); + + CD3D11_TEXTURE2D_DESC desc(DXGI_FORMAT_B8G8R8A8_UNORM, tileSize.width, + tileSize.height, 1, 1); + + RefPtr<ID3D11Texture2D> texture; + DebugOnly<HRESULT> hr = + GetDevice()->CreateTexture2D(&desc, nullptr, getter_AddRefs(texture)); + MOZ_ASSERT(SUCCEEDED(hr)); + if (!texture) { + gfxCriticalNote << "Failed to allocate Texture2D: " << aSurface->TileSize(); + RenderThread::Get()->HandleWebRenderError(WebRenderError::NEW_SURFACE); + return MakeUnique<TileD3D11>(nullptr, nullptr, nullptr, aSurface, this); + } + + RefPtr<DataTextureSourceD3D11> source = new DataTextureSourceD3D11( + GetDevice(), gfx::SurfaceFormat::B8G8R8A8, texture); + + RefPtr<ID3D11Texture2D> cpuTexture = CreateStagingTexture(tileSize); + return MakeUnique<TileD3D11>(source, cpuTexture, nullptr, aSurface, this); +} + +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(gfx::SurfaceFormat::B8G8R8A8); + RefPtr<gfx::DrawTarget> dt = gfx::Factory::CreateDrawTargetForData( + gfx::BackendType::SKIA, &aReadbackBuffer[0], aReadbackSize, stride, + gfx::SurfaceFormat::B8G8R8A8, false); + if (!dt) { + return false; + } + + GetCompositorD3D11()->Readback(dt); + 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..2365230f66 --- /dev/null +++ b/gfx/webrender_bindings/RenderCompositorD3D11SWGL.h @@ -0,0 +1,118 @@ +/* -*- 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/gfx/2D.h" +#include "mozilla/layers/ScreenshotGrabber.h" +#include "mozilla/layers/TextureD3D11.h" +#include "mozilla/layers/CompositorD3D11.h" +#include "mozilla/webrender/RenderCompositorLayersSWGL.h" + +namespace mozilla { + +namespace wr { + +class SurfaceD3D11SWGL; + +class RenderCompositorD3D11SWGL : public RenderCompositorLayersSWGL { + public: + static UniquePtr<RenderCompositor> Create( + const RefPtr<widget::CompositorWidget>& aWidget, nsACString& aError); + + RenderCompositorD3D11SWGL(layers::CompositorD3D11* aCompositor, + const RefPtr<widget::CompositorWidget>& aWidget, + void* aContext); + virtual ~RenderCompositorD3D11SWGL(); + + void Pause() override; + bool Resume() override; + + GLenum IsContextLost(bool aForce) override; + + layers::WebRenderCompositor CompositorType() const override { + return layers::WebRenderCompositor::D3D11; + } + RenderCompositorD3D11SWGL* AsRenderCompositorD3D11SWGL() override { + return this; + } + + bool BeginFrame() override; + + bool MaybeReadback(const gfx::IntSize& aReadbackSize, + const wr::ImageFormat& aReadbackFormat, + const Range<uint8_t>& aReadbackBuffer, + bool* aNeedsYFlip) override; + + layers::CompositorD3D11* GetCompositorD3D11() { + return mCompositor->AsCompositorD3D11(); + } + + ID3D11Device* GetDevice() { return GetCompositorD3D11()->GetDevice(); } + + private: + already_AddRefed<ID3D11Texture2D> CreateStagingTexture( + const gfx::IntSize aSize); + already_AddRefed<gfx::DataSourceSurface> CreateStagingSurface( + const gfx::IntSize aSize); + + void HandleExternalImage(RenderTextureHost* aExternalImage, + FrameSurface& aFrameSurface) override; + UniquePtr<RenderCompositorLayersSWGL::Surface> DoCreateSurface( + wr::DeviceIntSize aTileSize, bool aIsOpaque) override; + UniquePtr<RenderCompositorLayersSWGL::Tile> DoCreateTile( + Surface* aSurface) override; + + class TileD3D11 : public RenderCompositorLayersSWGL::Tile { + public: + TileD3D11(layers::DataTextureSourceD3D11* aTexture, + ID3D11Texture2D* aStagingTexture, + gfx::DataSourceSurface* aDataSourceSurface, Surface* aOwner, + RenderCompositorD3D11SWGL* aRenderCompositor); + virtual ~TileD3D11() {} + + bool Map(wr::DeviceIntRect aDirtyRect, wr::DeviceIntRect aValidRect, + void** aData, int32_t* aStride) override; + void Unmap(const gfx::IntRect& aDirtyRect) override; + layers::DataTextureSource* GetTextureSource() override { return mTexture; } + bool IsValid() override; + + private: + RefPtr<layers::DataTextureSourceD3D11> mTexture; + RefPtr<ID3D11Texture2D> mStagingTexture; + RefPtr<gfx::DataSourceSurface> mSurface; + SurfaceD3D11SWGL* mOwner; + RenderCompositorD3D11SWGL* mRenderCompositor; + }; + + enum UploadMode { + Upload_Immediate, + Upload_Staging, + Upload_StagingNoBlock, + Upload_StagingPooled + }; + UploadMode GetUploadMode(); + UploadMode mUploadMode = Upload_Staging; + + RefPtr<ID3D11Texture2D> mCurrentStagingTexture; + bool mCurrentStagingTextureIsTemp = false; +}; + +class SurfaceD3D11SWGL : public RenderCompositorLayersSWGL::Surface { + public: + SurfaceD3D11SWGL(wr::DeviceIntSize aTileSize, bool aIsOpaque); + virtual ~SurfaceD3D11SWGL() {} + + SurfaceD3D11SWGL* AsSurfaceD3D11SWGL() override { return this; } + + nsTArray<RefPtr<ID3D11Texture2D>> mStagingPool; +}; + +} // 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..21c2de1634 --- /dev/null +++ b/gfx/webrender_bindings/RenderCompositorEGL.cpp @@ -0,0 +1,328 @@ +/* -*- 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/WidgetUtilsGtk.h" +# include "mozilla/widget/GtkCompositorWidget.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 { + +extern LazyLogModule gRenderThreadLog; +#define LOG(...) MOZ_LOG(gRenderThreadLog, LogLevel::Debug, (__VA_ARGS__)) + +/* static */ +UniquePtr<RenderCompositor> RenderCompositorEGL::Create( + const RefPtr<widget::CompositorWidget>& aWidget, nsACString& aError) { + if ((kIsWayland || kIsX11) && !gfx::gfxVars::UseEGL()) { + return nullptr; + } + RefPtr<gl::GLContext> gl = RenderThread::Get()->SingletonGL(aError); + if (!gl) { + if (aError.IsEmpty()) { + aError.Assign("RcANGLE(no shared GL)"_ns); + } else { + aError.Append("(Create)"_ns); + } + return nullptr; + } + return MakeUnique<RenderCompositorEGL>(aWidget, std::move(gl)); +} + +EGLSurface RenderCompositorEGL::CreateEGLSurface() { + EGLSurface surface = EGL_NO_SURFACE; + surface = gl::GLContextEGL::CreateEGLSurfaceForCompositorWidget( + mWidget, gl::GLContextEGL::Cast(gl())->mSurfaceConfig); + if (surface == EGL_NO_SURFACE) { + const auto* renderThread = RenderThread::Get(); + gfxCriticalNote << "Failed to create EGLSurface. " + << renderThread->RendererCount() << " renderers, " + << renderThread->ActiveRendererCount() << " active."; + } + return surface; +} + +RenderCompositorEGL::RenderCompositorEGL( + const RefPtr<widget::CompositorWidget>& aWidget, + RefPtr<gl::GLContext>&& aGL) + : RenderCompositor(aWidget), mGL(aGL), mEGLSurface(EGL_NO_SURFACE) { + MOZ_ASSERT(mGL); + LOG("RenderCompositorEGL::RenderCompositorEGL()"); +} + +RenderCompositorEGL::~RenderCompositorEGL() { + LOG("RenderCompositorEGL::~RenderCompositorEGL()"); +#ifdef MOZ_WIDGET_ANDROID + java::GeckoSurfaceTexture::DestroyUnused((int64_t)gl()); +#endif + DestroyEGLSurface(); +} + +bool RenderCompositorEGL::BeginFrame() { + if ((kIsWayland || kIsX11) && mEGLSurface == EGL_NO_SURFACE) { + gfxCriticalNote + << "We don't have EGLSurface to draw into. Called too early?"; + return false; + } +#ifdef MOZ_WAYLAND + if (mWidget->AsGTK()) { + mWidget->AsGTK()->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(); +#ifdef MOZ_WAYLAND + if (mWidget->IsHidden()) { + return frameId; + } +#endif + 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.min.x)); + const auto top = std::max(0, std::min(bufferSize.height, rect.min.y)); + + const auto right = std::min(bufferSize.width, std::max(0, rect.max.x)); + const auto bottom = std::min(bufferSize.height, std::max(0, rect.max.y)); + + 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(); + + auto size = GetBufferSize(); + 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 < size.width || maxTextureSize < size.height) { + gfxCriticalNote << "Too big ANativeWindow size(" << size.width << ", " + << size.height << ") MaxTextureSize " << maxTextureSize; + return false; + } + + mEGLSurface = CreateEGLSurface(); + if (mEGLSurface == EGL_NO_SURFACE) { + // Often when we fail to create an EGL surface it is because the Java + // Surface we have been provided is invalid. Therefore the on the first + // occurence we don't raise a WebRenderError and instead just return + // failure. This allows the widget a chance to request a new Java + // Surface. On subsequent failures, raising the WebRenderError will + // result in the compositor being recreated, falling back through + // webrender configurations, and eventually crashing if we still do not + // succeed. + if (!mHandlingNewSurfaceError) { + mHandlingNewSurfaceError = true; + } else { + RenderThread::Get()->HandleWebRenderError(WebRenderError::NEW_SURFACE); + } + return false; + } + mHandlingNewSurfaceError = false; + + gl::GLContextEGL::Cast(gl())->SetEGLSurfaceOverride(mEGLSurface); + } else if (kIsWayland || kIsX11) { + // 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(); + + const int interval = gfx::gfxVars::SwapIntervalEGL() ? 1 : 0; + egl->fSwapInterval(interval); + } else { + RenderThread::Get()->HandleWebRenderError(WebRenderError::NEW_SURFACE); + return false; + } + } + return true; +} + +bool RenderCompositorEGL::IsPaused() { return mEGLSurface == EGL_NO_SURFACE; } + +bool RenderCompositorEGL::MakeCurrent() { + const auto& gle = gl::GLContextEGL::Cast(gl()); + + gle->SetEGLSurfaceOverride(mEGLSurface); + bool ok = gl()->MakeCurrent(); + if (!gl()->IsGLES() && ok && mEGLSurface != EGL_NO_SURFACE) { + // If we successfully made a surface current, set the draw buffer + // appropriately. It's not well-defined by the EGL spec whether + // eglMakeCurrent should do this automatically. See bug 1646135. + gl()->fDrawBuffer(gl()->IsDoubleBuffered() ? LOCAL_GL_BACK + : LOCAL_GL_FRONT); + } + return ok; +} + +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); + if (!egl->fMakeCurrent(EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) { + const EGLint err = egl->mLib->fGetError(); + gfxCriticalNote << "Error in eglMakeCurrent: " << gfx::hexa(err); + } + if (!egl->fDestroySurface(mEGLSurface)) { + const EGLint err = egl->mLib->fGetError(); + gfxCriticalNote << "Error in eglDestroySurface: " << gfx::hexa(err); + } + 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() { + return mWidget->GetClientSize(); +} + +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].min.x)); + const auto top = + std::max(0, std::min(bufferSize.height, aRects[i].min.y)); + + const auto right = + std::min(bufferSize.width, std::max(0, aRects[i].max.x)); + const auto bottom = + std::min(bufferSize.height, std::max(0, aRects[i].max.y)); + + 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..8e851b2fc0 --- /dev/null +++ b/gfx/webrender_bindings/RenderCompositorEGL.h @@ -0,0 +1,75 @@ +/* -*- 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( + const RefPtr<widget::CompositorWidget>& aWidget, nsACString& aError); + + explicit RenderCompositorEGL(const RefPtr<widget::CompositorWidget>& aWidget, + RefPtr<gl::GLContext>&& aGL); + 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 { return mGL; } + + bool MakeCurrent() override; + + bool UseANGLE() const override { return false; } + + LayoutDeviceIntSize GetBufferSize() 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(); + + RefPtr<gl::GLContext> mGL; + + EGLSurface mEGLSurface; + + // Whether we are in the process of handling a NEW_SURFACE error. On Android + // this is used to allow the widget an opportunity to recover from the first + // instance, before raising a WebRenderError on subsequent occurences. + bool mHandlingNewSurfaceError = false; + + // 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/RenderCompositorLayersSWGL.cpp b/gfx/webrender_bindings/RenderCompositorLayersSWGL.cpp new file mode 100644 index 0000000000..1e4550e9e8 --- /dev/null +++ b/gfx/webrender_bindings/RenderCompositorLayersSWGL.cpp @@ -0,0 +1,482 @@ +/* -*- 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 "RenderCompositorLayersSWGL.h" + +#include "GLContext.h" +#include "GLContextEGL.h" +#include "mozilla/layers/BuildConstants.h" +#include "mozilla/layers/Effects.h" +#include "mozilla/layers/TextureHostOGL.h" +#include "mozilla/widget/CompositorWidget.h" +#include "RenderCompositorRecordedFrame.h" + +#if defined(XP_WIN) +# include "mozilla/webrender/RenderCompositorD3D11SWGL.h" +#else +# include "mozilla/webrender/RenderCompositorOGLSWGL.h" +#endif + +namespace mozilla { +using namespace layers; +using namespace gfx; + +namespace wr { + +UniquePtr<RenderCompositor> RenderCompositorLayersSWGL::Create( + const RefPtr<widget::CompositorWidget>& aWidget, nsACString& aError) { +#ifdef XP_WIN + return RenderCompositorD3D11SWGL::Create(aWidget, aError); +#else + return RenderCompositorOGLSWGL::Create(aWidget, aError); +#endif +} + +RenderCompositorLayersSWGL::RenderCompositorLayersSWGL( + Compositor* aCompositor, const RefPtr<widget::CompositorWidget>& aWidget, + void* aContext) + : RenderCompositor(aWidget), + mCompositor(aCompositor), + mContext(aContext), + mCurrentTileId(wr::NativeTileId()) { + MOZ_ASSERT(mCompositor); + MOZ_ASSERT(mContext); +} + +RenderCompositorLayersSWGL::~RenderCompositorLayersSWGL() { + wr_swgl_destroy_context(mContext); +} + +bool RenderCompositorLayersSWGL::MakeCurrent() { + wr_swgl_make_current(mContext); + return true; +} + +bool RenderCompositorLayersSWGL::BeginFrame() { + MOZ_ASSERT(!mInFrame); + MakeCurrent(); + mInFrame = true; + return true; +} + +void RenderCompositorLayersSWGL::CancelFrame() { + MOZ_ASSERT(mInFrame); + mInFrame = false; + if (mCompositingStarted) { + mCompositor->CancelFrame(); + mCompositingStarted = false; + } +} + +void RenderCompositorLayersSWGL::StartCompositing( + wr::ColorF aClearColor, const wr::DeviceIntRect* aDirtyRects, + size_t aNumDirtyRects, const wr::DeviceIntRect* aOpaqueRects, + size_t aNumOpaqueRects) { + MOZ_RELEASE_ASSERT(!mCompositingStarted); + + if (!mInFrame || aNumDirtyRects == 0) { + return; + } + + gfx::IntRect bounds(gfx::IntPoint(0, 0), GetBufferSize().ToUnknownSize()); + nsIntRegion dirty; + + MOZ_RELEASE_ASSERT(aNumDirtyRects > 0); + for (size_t i = 0; i < aNumDirtyRects; i++) { + const auto& rect = aDirtyRects[i]; + dirty.OrWith( + gfx::IntRect(rect.min.x, rect.min.y, rect.width(), rect.height())); + } + dirty.AndWith(bounds); + + nsIntRegion opaque(bounds); + opaque.SubOut(mWidget->GetTransparentRegion().ToUnknownRegion()); + for (size_t i = 0; i < aNumOpaqueRects; i++) { + const auto& rect = aOpaqueRects[i]; + opaque.OrWith( + gfx::IntRect(rect.min.x, rect.min.y, rect.width(), rect.height())); + } + + mCompositor->SetClearColor(gfx::DeviceColor(aClearColor.r, aClearColor.g, + aClearColor.b, aClearColor.a)); + + if (!mCompositor->BeginFrameForWindow(dirty, Nothing(), bounds, opaque)) { + return; + } + mCompositingStarted = true; +} + +void RenderCompositorLayersSWGL::CompositorEndFrame() { + nsTArray<FrameSurface> frameSurfaces = std::move(mFrameSurfaces); + + if (!mCompositingStarted) { + return; + } + + for (auto& frameSurface : frameSurfaces) { + auto surfaceCursor = mSurfaces.find(frameSurface.mId); + MOZ_RELEASE_ASSERT(surfaceCursor != mSurfaces.end()); + Surface* surface = surfaceCursor->second.get(); + + for (auto it = surface->mTiles.begin(); it != surface->mTiles.end(); ++it) { + if (!it->second->IsValid()) { + continue; + } + + 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 = + new EffectRGB(it->second->GetTextureSource(), + /* aPremultiplied */ true, frameSurface.mFilter); + if (surface->mIsOpaque) { + texturedEffect->mPremultipliedCopy = 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) { + HandleExternalImage(surface->mExternalImage, frameSurface); + } + } +} + +RenderedFrameId RenderCompositorLayersSWGL::EndFrame( + const nsTArray<DeviceIntRect>& aDirtyRects) { + MOZ_ASSERT(mInFrame); + mInFrame = false; + if (mCompositingStarted) { + mCompositor->EndFrame(); + mCompositingStarted = false; + } + return GetNextRenderFrameId(); +} + +LayoutDeviceIntSize RenderCompositorLayersSWGL::GetBufferSize() { + return mWidget->GetClientSize(); +} + +void RenderCompositorLayersSWGL::Bind(wr::NativeTileId aId, + wr::DeviceIntPoint* aOffset, + uint32_t* aFboId, + wr::DeviceIntRect aDirtyRect, + wr::DeviceIntRect aValidRect) { + MOZ_RELEASE_ASSERT(false); +} + +void RenderCompositorLayersSWGL::Unbind() { MOZ_RELEASE_ASSERT(false); } + +bool RenderCompositorLayersSWGL::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.get(); + + auto layerCursor = surface->mTiles.find(TileKey(aId.x, aId.y)); + MOZ_RELEASE_ASSERT(layerCursor != surface->mTiles.end()); + + mCurrentTile = layerCursor->second.get(); + mCurrentTileId = aId; + mCurrentTileDirty = gfx::IntRect(aDirtyRect.min.x, aDirtyRect.min.y, + aDirtyRect.width(), aDirtyRect.height()); + + if (!mCurrentTile->Map(aDirtyRect, aValidRect, aData, aStride)) { + gfxCriticalNote << "MapTile failed aValidRect: " + << gfx::Rect(aValidRect.min.x, aValidRect.min.y, + aValidRect.width(), aValidRect.height()); + return false; + } + + // Store the new valid rect, so that we can composite only those pixels + mCurrentTile->mValidRect = gfx::Rect(aValidRect.min.x, aValidRect.min.y, + aValidRect.width(), aValidRect.height()); + return true; +} + +void RenderCompositorLayersSWGL::UnmapTile() { + mCurrentTile->Unmap(mCurrentTileDirty); + mCurrentTile = nullptr; +} + +void RenderCompositorLayersSWGL::CreateSurface( + wr::NativeSurfaceId aId, wr::DeviceIntPoint aVirtualOffset, + wr::DeviceIntSize aTileSize, bool aIsOpaque) { + MOZ_RELEASE_ASSERT(mSurfaces.find(aId) == mSurfaces.end()); + auto surface = DoCreateSurface(aTileSize, aIsOpaque); + mSurfaces.insert({aId, std::move(surface)}); +} + +UniquePtr<RenderCompositorLayersSWGL::Surface> +RenderCompositorLayersSWGL::DoCreateSurface(wr::DeviceIntSize aTileSize, + bool aIsOpaque) { + return MakeUnique<Surface>(aTileSize, aIsOpaque); +} + +void RenderCompositorLayersSWGL::CreateExternalSurface(wr::NativeSurfaceId aId, + bool aIsOpaque) { + MOZ_RELEASE_ASSERT(mSurfaces.find(aId) == mSurfaces.end()); + auto surface = MakeUnique<Surface>(wr::DeviceIntSize{}, aIsOpaque); + surface->mIsExternal = true; + mSurfaces.insert({aId, std::move(surface)}); +} + +void RenderCompositorLayersSWGL::DestroySurface(NativeSurfaceId aId) { + auto surfaceCursor = mSurfaces.find(aId); + MOZ_RELEASE_ASSERT(surfaceCursor != mSurfaces.end()); + mSurfaces.erase(surfaceCursor); +} + +void RenderCompositorLayersSWGL::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.get(); + MOZ_RELEASE_ASSERT(!surface->mIsExternal); + + auto tile = DoCreateTile(surface); + surface->mTiles.insert({TileKey(aX, aY), std::move(tile)}); +} + +void RenderCompositorLayersSWGL::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.get(); + 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 RenderCompositorLayersSWGL::AttachExternalImage( + wr::NativeSurfaceId aId, wr::ExternalImageId aExternalImage) { + RenderTextureHost* image = + RenderThread::Get()->GetRenderTexture(aExternalImage); + MOZ_ASSERT(image); + if (!image) { + gfxCriticalNoteOnce + << "Failed to get RenderTextureHost for D3D11SWGL extId:" + << AsUint64(aExternalImage); + return; + } +#if defined(XP_WIN) + MOZ_RELEASE_ASSERT(image->AsRenderDXGITextureHost() || + image->AsRenderDXGIYCbCrTextureHost()); +#elif defined(ANDROID) + MOZ_RELEASE_ASSERT(image->AsRenderAndroidHardwareBufferTextureHost() || + image->AsRenderAndroidSurfaceTextureHost() || + image->IsWrappingAsyncRemoteTexture()); +#endif + + auto surfaceCursor = mSurfaces.find(aId); + MOZ_RELEASE_ASSERT(surfaceCursor != mSurfaces.end()); + + Surface* surface = surfaceCursor->second.get(); + surface->mExternalImage = image; + MOZ_RELEASE_ASSERT(surface->mTiles.empty()); + MOZ_RELEASE_ASSERT(surface->mIsExternal); +} + +// static +gfx::SamplingFilter RenderCompositorLayersSWGL::ToSamplingFilter( + wr::ImageRendering aImageRendering) { + if (aImageRendering == wr::ImageRendering::Auto) { + return gfx::SamplingFilter::LINEAR; + } + return gfx::SamplingFilter::POINT; +} + +void RenderCompositorLayersSWGL::AddSurface( + wr::NativeSurfaceId aId, const wr::CompositorSurfaceTransform& aTransform, + wr::DeviceIntRect aClipRect, wr::ImageRendering aImageRendering) { + float sx = aTransform.scale.x; + float sy = aTransform.scale.y; + float tx = aTransform.offset.x; + float ty = aTransform.offset.y; + gfx::Matrix4x4 transform(sx, 0.0, 0.0, 0.0, 0.0, sy, 0.0, 0.0, 0.0, 0.0, 1.0, + 0.0, tx, ty, 0.0, 1.0); + gfx::IntRect clipRect(aClipRect.min.x, aClipRect.min.y, aClipRect.width(), + aClipRect.height()); + + mFrameSurfaces.AppendElement(FrameSurface{aId, transform, clipRect, + ToSamplingFilter(aImageRendering)}); +} + +void RenderCompositorLayersSWGL::MaybeRequestAllowFrameRecording( + bool aWillRecord) { + mCompositor->RequestAllowFrameRecording(aWillRecord); +} + +class WindowLMC : public profiler_screenshots::Window { + public: + explicit WindowLMC(Compositor* aCompositor) : mCompositor(aCompositor) {} + + already_AddRefed<profiler_screenshots::RenderSource> GetWindowContents( + const gfx::IntSize& aWindowSize) override; + already_AddRefed<profiler_screenshots::DownscaleTarget> CreateDownscaleTarget( + const gfx::IntSize& aSize) override; + already_AddRefed<profiler_screenshots::AsyncReadbackBuffer> + CreateAsyncReadbackBuffer(const gfx::IntSize& aSize) override; + + protected: + Compositor* mCompositor; +}; + +class RenderSourceLMC : public profiler_screenshots::RenderSource { + public: + explicit RenderSourceLMC(CompositingRenderTarget* aRT) + : RenderSource(aRT->GetSize()), mRT(aRT) {} + + const auto& RenderTarget() { return mRT; } + + protected: + virtual ~RenderSourceLMC() {} + + RefPtr<CompositingRenderTarget> mRT; +}; + +class DownscaleTargetLMC : public profiler_screenshots::DownscaleTarget { + public: + explicit DownscaleTargetLMC(CompositingRenderTarget* aRT, + Compositor* aCompositor) + : profiler_screenshots::DownscaleTarget(aRT->GetSize()), + mRenderSource(new RenderSourceLMC(aRT)), + mCompositor(aCompositor) {} + + already_AddRefed<profiler_screenshots::RenderSource> AsRenderSource() + override { + return do_AddRef(mRenderSource); + } + + bool DownscaleFrom(profiler_screenshots::RenderSource* aSource, + const IntRect& aSourceRect, + const IntRect& aDestRect) override { + MOZ_RELEASE_ASSERT(aSourceRect.TopLeft() == IntPoint()); + MOZ_RELEASE_ASSERT(aDestRect.TopLeft() == IntPoint()); + RefPtr<CompositingRenderTarget> previousTarget = + mCompositor->GetCurrentRenderTarget(); + + mCompositor->SetRenderTarget(mRenderSource->RenderTarget()); + bool result = mCompositor->BlitRenderTarget( + static_cast<RenderSourceLMC*>(aSource)->RenderTarget(), + aSourceRect.Size(), aDestRect.Size()); + + // Restore the old render target. + mCompositor->SetRenderTarget(previousTarget); + + return result; + } + + protected: + virtual ~DownscaleTargetLMC() {} + + RefPtr<RenderSourceLMC> mRenderSource; + Compositor* mCompositor; +}; + +class AsyncReadbackBufferLMC + : public profiler_screenshots::AsyncReadbackBuffer { + public: + AsyncReadbackBufferLMC(mozilla::layers::AsyncReadbackBuffer* aARB, + Compositor* aCompositor) + : profiler_screenshots::AsyncReadbackBuffer(aARB->GetSize()), + mARB(aARB), + mCompositor(aCompositor) {} + void CopyFrom(profiler_screenshots::RenderSource* aSource) override { + mCompositor->ReadbackRenderTarget( + static_cast<RenderSourceLMC*>(aSource)->RenderTarget(), mARB); + } + bool MapAndCopyInto(DataSourceSurface* aSurface, + const IntSize& aReadSize) override { + return mARB->MapAndCopyInto(aSurface, aReadSize); + } + + protected: + virtual ~AsyncReadbackBufferLMC() {} + + RefPtr<mozilla::layers::AsyncReadbackBuffer> mARB; + Compositor* mCompositor; +}; + +already_AddRefed<profiler_screenshots::RenderSource> +WindowLMC::GetWindowContents(const gfx::IntSize& aWindowSize) { + RefPtr<CompositingRenderTarget> rt = mCompositor->GetWindowRenderTarget(); + if (!rt) { + return nullptr; + } + return MakeAndAddRef<RenderSourceLMC>(rt); +} + +already_AddRefed<profiler_screenshots::DownscaleTarget> +WindowLMC::CreateDownscaleTarget(const gfx::IntSize& aSize) { + RefPtr<CompositingRenderTarget> rt = + mCompositor->CreateRenderTarget(IntRect({}, aSize), INIT_MODE_NONE); + return MakeAndAddRef<DownscaleTargetLMC>(rt, mCompositor); +} + +already_AddRefed<profiler_screenshots::AsyncReadbackBuffer> +WindowLMC::CreateAsyncReadbackBuffer(const gfx::IntSize& aSize) { + RefPtr<AsyncReadbackBuffer> carb = + mCompositor->CreateAsyncReadbackBuffer(aSize); + if (!carb) { + return nullptr; + } + return MakeAndAddRef<AsyncReadbackBufferLMC>(carb, mCompositor); +} + +bool RenderCompositorLayersSWGL::MaybeRecordFrame( + layers::CompositionRecorder& aRecorder) { + 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 RenderCompositorLayersSWGL::MaybeGrabScreenshot( + const gfx::IntSize& aWindowSize) { + if (!mCompositingStarted) { + return true; + } + WindowLMC window(mCompositor); + mProfilerScreenshotGrabber.MaybeGrabScreenshot(window, aWindowSize); + return true; +} + +bool RenderCompositorLayersSWGL::MaybeProcessScreenshotQueue() { + mProfilerScreenshotGrabber.MaybeProcessQueue(); + return true; +} + +} // namespace wr +} // namespace mozilla diff --git a/gfx/webrender_bindings/RenderCompositorLayersSWGL.h b/gfx/webrender_bindings/RenderCompositorLayersSWGL.h new file mode 100644 index 0000000000..3cf6bb44f5 --- /dev/null +++ b/gfx/webrender_bindings/RenderCompositorLayersSWGL.h @@ -0,0 +1,195 @@ +/* -*- 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_Layers_H +#define MOZILLA_GFX_RENDERCOMPOSITOR_Layers_H + +#include <unordered_map> + +#include "mozilla/HashFunctions.h" +#include "mozilla/layers/Compositor.h" +#include "mozilla/layers/ScreenshotGrabber.h" +#include "mozilla/webrender/RenderCompositor.h" +#include "mozilla/webrender/RenderTextureHost.h" + +namespace mozilla { + +namespace wr { + +class SurfaceD3D11SWGL; + +class RenderCompositorLayersSWGL : public RenderCompositor { + public: + static UniquePtr<RenderCompositor> Create( + const RefPtr<widget::CompositorWidget>& aWidget, nsACString& aError); + + RenderCompositorLayersSWGL(layers::Compositor* aCompositor, + const RefPtr<widget::CompositorWidget>& aWidget, + void* aContext); + virtual ~RenderCompositorLayersSWGL(); + + void* swgl() const override { return mContext; } + + bool MakeCurrent() override; + + bool BeginFrame() override; + void CancelFrame() override; + RenderedFrameId EndFrame(const nsTArray<DeviceIntRect>& aDirtyRects) override; + + bool SurfaceOriginIsTopLeft() override { return true; } + + LayoutDeviceIntSize GetBufferSize() override; + + // Should we support this? + bool SupportsExternalBufferTextures() const override { return false; } + + layers::WebRenderBackend BackendType() const override { + return layers::WebRenderBackend::SOFTWARE; + } + + bool ShouldUseNativeCompositor() override { return true; } + + void StartCompositing(wr::ColorF aClearColor, + const wr::DeviceIntRect* aDirtyRects, + size_t aNumDirtyRects, + const wr::DeviceIntRect* aOpaqueRects, + size_t aNumOpaqueRects) override; + + 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 {} + + 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; + }; + + // 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. + class Tile { + public: + Tile() = default; + virtual ~Tile() = default; + + virtual bool Map(wr::DeviceIntRect aDirtyRect, wr::DeviceIntRect aValidRect, + void** aData, int32_t* aStride) = 0; + virtual void Unmap(const gfx::IntRect& aDirtyRect) = 0; + virtual layers::DataTextureSource* GetTextureSource() = 0; + virtual bool IsValid() = 0; + + gfx::Rect mValidRect; + + struct KeyHashFn { + std::size_t operator()(const TileKey& aId) const { + return HashGeneric(aId.mX, aId.mY); + } + }; + }; + + class Surface { + public: + explicit Surface(wr::DeviceIntSize aTileSize, bool aIsOpaque) + : mTileSize(aTileSize), mIsOpaque(aIsOpaque) {} + virtual ~Surface() {} + + gfx::IntSize TileSize() { + return gfx::IntSize(mTileSize.width, mTileSize.height); + } + virtual SurfaceD3D11SWGL* AsSurfaceD3D11SWGL() { return nullptr; } + + // 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, UniquePtr<Tile>, Tile::KeyHashFn> mTiles; + RefPtr<RenderTextureHost> mExternalImage; + + struct IdHashFn { + std::size_t operator()(const wr::NativeSurfaceId& aId) const { + return HashGeneric(wr::AsUint64(aId)); + } + }; + }; + + static gfx::SamplingFilter ToSamplingFilter( + wr::ImageRendering aImageRendering); + + protected: + // 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; + }; + + virtual void HandleExternalImage(RenderTextureHost* aExternalImage, + FrameSurface& aFrameSurface) = 0; + virtual UniquePtr<RenderCompositorLayersSWGL::Surface> DoCreateSurface( + wr::DeviceIntSize aTileSize, bool aIsOpaque); + virtual UniquePtr<RenderCompositorLayersSWGL::Tile> DoCreateTile( + Surface* aSurface) = 0; + + RefPtr<layers::Compositor> mCompositor; + void* mContext = nullptr; + + std::unordered_map<wr::NativeSurfaceId, UniquePtr<Surface>, Surface::IdHashFn> + mSurfaces; + + // Temporary state held between MapTile and UnmapTile + Tile* mCurrentTile = nullptr; + gfx::IntRect mCurrentTileDirty; + wr::NativeTileId mCurrentTileId; + + nsTArray<FrameSurface> mFrameSurfaces; + bool mInFrame = false; + bool mCompositingStarted = false; + + layers::ScreenshotGrabber mProfilerScreenshotGrabber; +}; + +static inline bool operator==(const RenderCompositorLayersSWGL::TileKey& a0, + const RenderCompositorLayersSWGL::TileKey& a1) { + return a0.mX == a1.mX && a0.mY == a1.mY; +} + +} // namespace wr +} // namespace mozilla + +#endif diff --git a/gfx/webrender_bindings/RenderCompositorNative.cpp b/gfx/webrender_bindings/RenderCompositorNative.cpp new file mode 100644 index 0000000000..81cafd1fd6 --- /dev/null +++ b/gfx/webrender_bindings/RenderCompositorNative.cpp @@ -0,0 +1,672 @@ +/* -*- 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/ProfilerLabels.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/gfx/Logging.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::wr { + +extern LazyLogModule gRenderThreadLog; +#define LOG(...) MOZ_LOG(gRenderThreadLog, LogLevel::Debug, (__VA_ARGS__)) + +RenderCompositorNative::RenderCompositorNative( + const RefPtr<widget::CompositorWidget>& aWidget, gl::GLContext* aGL) + : RenderCompositor(aWidget), + mNativeLayerRoot(GetWidget()->GetNativeLayerRoot()) { + LOG("RenderCompositorNative::RenderCompositorNative()"); + +#if defined(XP_MACOSX) || defined(MOZ_WAYLAND) + auto pool = RenderThread::Get()->SharedSurfacePool(); + if (pool) { + mSurfacePoolHandle = pool->GetHandleForGL(aGL); + } +#endif + MOZ_RELEASE_ASSERT(mSurfacePoolHandle); +} + +RenderCompositorNative::~RenderCompositorNative() { + LOG("RRenderCompositorNative::~RenderCompositorNative()"); + + Pause(); + 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 (bufferSize.IsEmpty()) { + return false; + } + if (mNativeLayerForEntireWindow && + mNativeLayerForEntireWindow->GetSize() != bufferSize) { + mNativeLayerRoot->RemoveLayer(mNativeLayerForEntireWindow); + mNativeLayerForEntireWindow = nullptr; + } + if (!mNativeLayerForEntireWindow) { + mNativeLayerForEntireWindow = + mNativeLayerRoot->CreateLayer(bufferSize, false, mSurfacePoolHandle); + 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()) { +#if defined(XP_MACOSX) + return layers::WebRenderCompositor::CORE_ANIMATION; +#elif defined(MOZ_WAYLAND) + return layers::WebRenderCompositor::WAYLAND; +#endif + } + return layers::WebRenderCompositor::DRAW; +} + +LayoutDeviceIntSize RenderCompositorNative::GetBufferSize() { + return mWidget->GetClientSize(); +} + +bool RenderCompositorNative::ShouldUseNativeCompositor() { + return gfx::gfxVars::UseWebRenderCompositor(); +} + +void RenderCompositorNative::GetCompositorCapabilities( + CompositorCapabilities* aCaps) { + RenderCompositor::GetCompositorCapabilities(aCaps); +#if defined(XP_MACOSX) + aCaps->supports_surface_for_backdrop = !gfx::gfxVars::UseSoftwareWebRender(); +#endif +} + +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; +} + +void RenderCompositorNative::CompositorBeginFrame() { + mAddedLayers.Clear(); + mAddedTilePixelCount = 0; + mAddedClippedPixelCount = 0; + mBeginFrameTimeStamp = TimeStamp::Now(); + mSurfacePoolHandle->OnBeginFrame(); + mNativeLayerRoot->PrepareForCommit(); +} + +void RenderCompositorNative::CompositorEndFrame() { + if (profiler_thread_is_being_profiled_for_markers()) { + auto bufferSize = GetBufferSize(); + [[maybe_unused]] 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))); + } + 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::CreateBackdropSurface(wr::NativeSurfaceId aId, + wr::ColorF aColor) { + MOZ_RELEASE_ASSERT(mSurfaces.find(aId) == mSurfaces.end()); + + gfx::DeviceColor color(aColor.r, aColor.g, aColor.b, aColor.a); + RefPtr<layers::NativeLayer> layer = + mNativeLayerRoot->CreateLayerForColor(color); + + Surface surface{DeviceIntSize{}, (aColor.a >= 1.0f)}; + 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; + + float sx = aTransform.scale.x; + float sy = aTransform.scale.y; + float tx = aTransform.offset.x; + float ty = aTransform.offset.y; + gfx::Matrix4x4 transform(sx, 0.0, 0.0, 0.0, 0.0, sy, 0.0, 0.0, 0.0, 0.0, 1.0, + 0.0, tx, ty, 0.0, 1.0); + + 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.min.x, aClipRect.min.y, aClipRect.width(), + aClipRect.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(); + } +} + +/* static */ +UniquePtr<RenderCompositor> RenderCompositorNativeOGL::Create( + const RefPtr<widget::CompositorWidget>& aWidget, nsACString& aError) { + RefPtr<gl::GLContext> gl = RenderThread::Get()->SingletonGL(); + if (!gl) { + gl = gl::GLContextProvider::CreateForCompositorWidget( + aWidget, /* aHardwareWebRender */ 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>(aWidget, std::move(gl)); +} + +RenderCompositorNativeOGL::RenderCompositorNativeOGL( + const RefPtr<widget::CompositorWidget>& aWidget, + RefPtr<gl::GLContext>&& aGL) + : RenderCompositorNative(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.min.x, aValidRect.min.y, aValidRect.width(), + aValidRect.height()); + gfx::IntRect dirtyRect(aDirtyRect.min.x, aDirtyRect.min.y, aDirtyRect.width(), + aDirtyRect.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( + const 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>(aWidget, ctx); +} + +RenderCompositorNativeSWGL::RenderCompositorNativeSWGL( + const RefPtr<widget::CompositorWidget>& aWidget, void* aContext) + : RenderCompositorNative(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.min.x, aDirtyRect.min.y, aDirtyRect.width(), + aDirtyRect.height()); + gfx::IntRect validRect(aValidRect.min.x, aValidRect.min.y, aValidRect.width(), + aValidRect.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 mozilla::wr diff --git a/gfx/webrender_bindings/RenderCompositorNative.h b/gfx/webrender_bindings/RenderCompositorNative.h new file mode 100644 index 0000000000..936fdd3db8 --- /dev/null +++ b/gfx/webrender_bindings/RenderCompositorNative.h @@ -0,0 +1,233 @@ +/* -*- 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 <unordered_map> + +#include "GLTypes.h" +#include "mozilla/HashFunctions.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; + void GetCompositorCapabilities(CompositorCapabilities* aCaps) override; + + bool SurfaceOriginIsTopLeft() override { return true; } + + // 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 CreateBackdropSurface(wr::NativeSurfaceId aId, + wr::ColorF aColor) 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; + + struct TileKey { + TileKey(int32_t aX, int32_t aY) : mX(aX), mY(aY) {} + + int32_t mX; + int32_t mY; + }; + + protected: + explicit RenderCompositorNative( + const 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( + const RefPtr<widget::CompositorWidget>& aWidget, nsACString& aError); + + RenderCompositorNativeOGL(const 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( + const RefPtr<widget::CompositorWidget>& aWidget, nsACString& aError); + + RenderCompositorNativeSWGL(const 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..9653e06d43 --- /dev/null +++ b/gfx/webrender_bindings/RenderCompositorOGL.cpp @@ -0,0 +1,126 @@ +/* -*- 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::wr { + +extern LazyLogModule gRenderThreadLog; +#define LOG(...) MOZ_LOG(gRenderThreadLog, LogLevel::Debug, (__VA_ARGS__)) + +/* static */ +UniquePtr<RenderCompositor> RenderCompositorOGL::Create( + const RefPtr<widget::CompositorWidget>& aWidget, nsACString& aError) { + RefPtr<gl::GLContext> gl = RenderThread::Get()->SingletonGL(); + if (!gl) { + gl = gl::GLContextProvider::CreateForCompositorWidget( + aWidget, /* aHardwareWebRender */ 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), aWidget); +} + +RenderCompositorOGL::RenderCompositorOGL( + RefPtr<gl::GLContext>&& aGL, + const RefPtr<widget::CompositorWidget>& aWidget) + : RenderCompositor(aWidget), mGL(aGL) { + MOZ_ASSERT(mGL); + LOG("RenderCompositorOGL::RenderCompositorOGL()"); + + mIsEGL = aGL->GetContextType() == mozilla::gl::GLContextType::EGL; +} + +RenderCompositorOGL::~RenderCompositorOGL() { + LOG("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.min.x)); + const auto top = std::max(0, std::min(bufferSize.height, rect.min.y)); + + const auto right = std::min(bufferSize.width, std::max(0, rect.max.x)); + const auto bottom = std::min(bufferSize.height, std::max(0, rect.max.y)); + + 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(); +} + +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 mozilla::wr diff --git a/gfx/webrender_bindings/RenderCompositorOGL.h b/gfx/webrender_bindings/RenderCompositorOGL.h new file mode 100644 index 0000000000..78429a2db4 --- /dev/null +++ b/gfx/webrender_bindings/RenderCompositorOGL.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_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( + const RefPtr<widget::CompositorWidget>& aWidget, nsACString& aError); + + RenderCompositorOGL(RefPtr<gl::GLContext>&& aGL, + const 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 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/RenderCompositorOGLSWGL.cpp b/gfx/webrender_bindings/RenderCompositorOGLSWGL.cpp new file mode 100644 index 0000000000..fac9722bbe --- /dev/null +++ b/gfx/webrender_bindings/RenderCompositorOGLSWGL.cpp @@ -0,0 +1,515 @@ +/* -*- 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 "RenderCompositorOGLSWGL.h" + +#include "GLContext.h" +#include "GLContextEGL.h" +#include "mozilla/layers/BuildConstants.h" +#include "mozilla/layers/CompositorOGL.h" +#include "mozilla/layers/Effects.h" +#include "mozilla/layers/TextureHostOGL.h" +#include "mozilla/widget/CompositorWidget.h" +#include "OGLShaderProgram.h" + +#ifdef MOZ_WIDGET_ANDROID +# include "mozilla/java/GeckoSurfaceTextureWrappers.h" +# include "mozilla/webrender/RenderAndroidHardwareBufferTextureHost.h" +# include "mozilla/widget/AndroidCompositorWidget.h" +# include <android/native_window.h> +# include <android/native_window_jni.h> +#endif + +#ifdef MOZ_WIDGET_GTK +# include "mozilla/widget/GtkCompositorWidget.h" +# include <gdk/gdk.h> +# ifdef MOZ_X11 +# include <gdk/gdkx.h> +# endif +#endif + +namespace mozilla { +using namespace layers; +using namespace gfx; +namespace wr { + +extern LazyLogModule gRenderThreadLog; +#define LOG(...) MOZ_LOG(gRenderThreadLog, LogLevel::Debug, (__VA_ARGS__)) + +UniquePtr<RenderCompositor> RenderCompositorOGLSWGL::Create( + const RefPtr<widget::CompositorWidget>& aWidget, nsACString& aError) { + if (!aWidget->GetCompositorOptions().AllowSoftwareWebRenderOGL()) { + return nullptr; + } + + RefPtr<Compositor> compositor; +#ifdef MOZ_WIDGET_ANDROID + RefPtr<gl::GLContext> context = + RenderThread::Get()->SingletonGLForCompositorOGL(); + if (!context) { + gfxCriticalNote << "SingletonGL does not exist for SWGL"; + return nullptr; + } + auto programs = RenderThread::Get()->GetProgramsForCompositorOGL(); + if (!programs) { + gfxCriticalNote << "Failed to get Programs for CompositorOGL for SWGL"; + return nullptr; + } + + nsCString log; + RefPtr<CompositorOGL> compositorOGL; + compositorOGL = new CompositorOGL(aWidget, /* aSurfaceWidth */ -1, + /* aSurfaceHeight */ -1, + /* aUseExternalSurfaceSize */ true); + if (!compositorOGL->Initialize(context, programs, &log)) { + gfxCriticalNote << "Failed to initialize CompositorOGL for SWGL: " + << log.get(); + return nullptr; + } + compositor = compositorOGL; +#elif defined(MOZ_WIDGET_GTK) + nsCString log; + RefPtr<CompositorOGL> compositorOGL; + compositorOGL = new CompositorOGL(aWidget); + if (!compositorOGL->Initialize(&log)) { + gfxCriticalNote << "Failed to initialize CompositorOGL for SWGL: " + << log.get(); + return nullptr; + } + compositor = compositorOGL; +#endif + + if (!compositor) { + return nullptr; + } + + void* ctx = wr_swgl_create_context(); + if (!ctx) { + gfxCriticalNote << "Failed SWGL context creation for WebRender"; + return nullptr; + } + + return MakeUnique<RenderCompositorOGLSWGL>(compositor, aWidget, ctx); +} + +RenderCompositorOGLSWGL::RenderCompositorOGLSWGL( + Compositor* aCompositor, const RefPtr<widget::CompositorWidget>& aWidget, + void* aContext) + : RenderCompositorLayersSWGL(aCompositor, aWidget, aContext) { + LOG("RenderCompositorOGLSWGL::RenderCompositorOGLSWGL()"); +} + +RenderCompositorOGLSWGL::~RenderCompositorOGLSWGL() { + LOG("RRenderCompositorOGLSWGL::~RenderCompositorOGLSWGL()"); +#ifdef MOZ_WIDGET_ANDROID + java::GeckoSurfaceTexture::DestroyUnused((int64_t)GetGLContext()); + DestroyEGLSurface(); +#endif +} + +gl::GLContext* RenderCompositorOGLSWGL::GetGLContext() { + return mCompositor->AsCompositorOGL()->gl(); +} + +bool RenderCompositorOGLSWGL::MakeCurrent() { + GetGLContext()->MakeCurrent(); +#ifdef MOZ_WIDGET_ANDROID + if (GetGLContext()->GetContextType() == gl::GLContextType::EGL) { + gl::GLContextEGL::Cast(GetGLContext())->SetEGLSurfaceOverride(mEGLSurface); + } +#endif + RenderCompositorLayersSWGL::MakeCurrent(); + return true; +} + +EGLSurface RenderCompositorOGLSWGL::CreateEGLSurface() { + MOZ_ASSERT(GetGLContext()->GetContextType() == gl::GLContextType::EGL); + + EGLSurface surface = EGL_NO_SURFACE; + surface = gl::GLContextEGL::CreateEGLSurfaceForCompositorWidget( + mWidget, gl::GLContextEGL::Cast(GetGLContext())->mSurfaceConfig); + if (surface == EGL_NO_SURFACE) { + const auto* renderThread = RenderThread::Get(); + gfxCriticalNote << "Failed to create EGLSurface. " + << renderThread->RendererCount() << " renderers, " + << renderThread->ActiveRendererCount() << " active."; + } + + // The subsequent render after creating a new surface must be a full render. + mFullRender = true; + + return surface; +} + +void RenderCompositorOGLSWGL::DestroyEGLSurface() { + MOZ_ASSERT(GetGLContext()->GetContextType() == gl::GLContextType::EGL); + + const auto& gle = gl::GLContextEGL::Cast(GetGLContext()); + const auto& egl = gle->mEgl; + + // Release EGLSurface of back buffer before calling ResizeBuffers(). + if (mEGLSurface) { + gle->SetEGLSurfaceOverride(EGL_NO_SURFACE); + if (!egl->fMakeCurrent(EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) { + const EGLint err = egl->mLib->fGetError(); + gfxCriticalNote << "Error in eglMakeCurrent: " << gfx::hexa(err); + } + if (!egl->fDestroySurface(mEGLSurface)) { + const EGLint err = egl->mLib->fGetError(); + gfxCriticalNote << "Error in eglDestroySurface: " << gfx::hexa(err); + } + mEGLSurface = EGL_NO_SURFACE; + } +} + +bool RenderCompositorOGLSWGL::BeginFrame() { + MOZ_ASSERT(!mInFrame); + RenderCompositorLayersSWGL::BeginFrame(); + +#ifdef MOZ_WIDGET_ANDROID + java::GeckoSurfaceTexture::DestroyUnused((int64_t)GetGLContext()); + GetGLContext() + ->MakeCurrent(); // DestroyUnused can change the current context! +#endif + + return true; +} + +RenderedFrameId RenderCompositorOGLSWGL::EndFrame( + const nsTArray<DeviceIntRect>& aDirtyRects) { + mFullRender = false; + + return RenderCompositorLayersSWGL::EndFrame(aDirtyRects); +} + +void RenderCompositorOGLSWGL::HandleExternalImage( + RenderTextureHost* aExternalImage, FrameSurface& aFrameSurface) { + MOZ_ASSERT(aExternalImage); + +#ifdef MOZ_WIDGET_ANDROID + GLenum target = + LOCAL_GL_TEXTURE_EXTERNAL; // This is required by SurfaceTexture + GLenum wrapMode = LOCAL_GL_CLAMP_TO_EDGE; + + if (auto* host = aExternalImage->AsRenderAndroidSurfaceTextureHost()) { + host->UpdateTexImageIfNecessary(); + + // We need to hold the texture source separately from the effect, + // since the effect doesn't hold a strong reference. + RefPtr<SurfaceTextureSource> layer = new SurfaceTextureSource( + (TextureSourceProvider*)mCompositor, host->mSurfTex, host->mFormat, + target, wrapMode, host->mSize, host->mTransformOverride); + RefPtr<TexturedEffect> texturedEffect = + CreateTexturedEffect(host->mFormat, layer, aFrameSurface.mFilter, + /* isAlphaPremultiplied */ true); + + gfx::Rect drawRect(0, 0, host->mSize.width, host->mSize.height); + + EffectChain effect; + effect.mPrimaryEffect = texturedEffect; + mCompositor->DrawQuad(drawRect, aFrameSurface.mClipRect, effect, 1.0, + aFrameSurface.mTransform, drawRect); + } else if (auto* host = + aExternalImage->AsRenderAndroidHardwareBufferTextureHost()) { + // We need to hold the texture source separately from the effect, + // since the effect doesn't hold a strong reference. + RefPtr<AndroidHardwareBufferTextureSource> layer = + new AndroidHardwareBufferTextureSource( + (TextureSourceProvider*)mCompositor, + host->GetAndroidHardwareBuffer(), + host->GetAndroidHardwareBuffer()->mFormat, target, wrapMode, + host->GetSize()); + RefPtr<TexturedEffect> texturedEffect = CreateTexturedEffect( + host->GetAndroidHardwareBuffer()->mFormat, layer, aFrameSurface.mFilter, + /* isAlphaPremultiplied */ true); + + gfx::Rect drawRect(0, 0, host->GetSize().width, host->GetSize().height); + + EffectChain effect; + effect.mPrimaryEffect = texturedEffect; + mCompositor->DrawQuad(drawRect, aFrameSurface.mClipRect, effect, 1.0, + aFrameSurface.mTransform, drawRect); + } else if (!aExternalImage->IsWrappingAsyncRemoteTexture()) { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + } +#endif +} + +void RenderCompositorOGLSWGL::GetCompositorCapabilities( + CompositorCapabilities* aCaps) { + RenderCompositor::GetCompositorCapabilities(aCaps); + + // max_update_rects are not yet handled properly + aCaps->max_update_rects = 0; +} + +bool RenderCompositorOGLSWGL::RequestFullRender() { return mFullRender; } + +void RenderCompositorOGLSWGL::Pause() { +#ifdef MOZ_WIDGET_ANDROID + DestroyEGLSurface(); +#elif defined(MOZ_WIDGET_GTK) + mCompositor->Pause(); +#endif +} + +bool RenderCompositorOGLSWGL::Resume() { +#ifdef MOZ_WIDGET_ANDROID + // Destroy EGLSurface if it exists. + DestroyEGLSurface(); + + auto size = GetBufferSize(); + GLint maxTextureSize = 0; + GetGLContext()->fGetIntegerv(LOCAL_GL_MAX_TEXTURE_SIZE, + (GLint*)&maxTextureSize); + + // When window size is too big, hardware buffer allocation could fail. + if (maxTextureSize < size.width || maxTextureSize < size.height) { + gfxCriticalNote << "Too big ANativeWindow size(" << size.width << ", " + << size.height << ") MaxTextureSize " << maxTextureSize; + return false; + } + + mEGLSurface = CreateEGLSurface(); + if (mEGLSurface == EGL_NO_SURFACE) { + // Often when we fail to create an EGL surface it is because the + // Java Surface we have been provided is invalid. Therefore the on + // the first occurence we don't raise a WebRenderError and instead + // just return failure. This allows the widget a chance to request + // a new Java Surface. On subsequent failures, raising the + // WebRenderError will result in the compositor being recreated, + // falling back through webrender configurations, and eventually + // crashing if we still do not succeed. + if (!mHandlingNewSurfaceError) { + mHandlingNewSurfaceError = true; + } else { + RenderThread::Get()->HandleWebRenderError(WebRenderError::NEW_SURFACE); + } + return false; + } + mHandlingNewSurfaceError = false; + + gl::GLContextEGL::Cast(GetGLContext())->SetEGLSurfaceOverride(mEGLSurface); + mCompositor->SetDestinationSurfaceSize(size.ToUnknownSize()); +#elif defined(MOZ_WIDGET_GTK) + bool resumed = mCompositor->Resume(); + if (!resumed) { + RenderThread::Get()->HandleWebRenderError(WebRenderError::NEW_SURFACE); + return false; + } +#endif + return true; +} + +bool RenderCompositorOGLSWGL::IsPaused() { +#ifdef MOZ_WIDGET_ANDROID + return mEGLSurface == EGL_NO_SURFACE; +#endif + return false; +} + +LayoutDeviceIntSize RenderCompositorOGLSWGL::GetBufferSize() { + return mWidget->GetClientSize(); +} + +UniquePtr<RenderCompositorLayersSWGL::Tile> +RenderCompositorOGLSWGL::DoCreateTile(Surface* aSurface) { + auto source = MakeRefPtr<TextureImageTextureSourceOGL>( + mCompositor->AsCompositorOGL(), layers::TextureFlags::NO_FLAGS); + + return MakeUnique<TileOGL>(std::move(source), aSurface->TileSize()); +} + +bool RenderCompositorOGLSWGL::MaybeReadback( + const gfx::IntSize& aReadbackSize, const wr::ImageFormat& aReadbackFormat, + const Range<uint8_t>& aReadbackBuffer, bool* aNeedsYFlip) { +#ifdef MOZ_WIDGET_ANDROID + MOZ_ASSERT(aReadbackFormat == wr::ImageFormat::RGBA8); + const GLenum format = LOCAL_GL_RGBA; +#else + MOZ_ASSERT(aReadbackFormat == wr::ImageFormat::BGRA8); + const GLenum format = LOCAL_GL_BGRA; +#endif + + GetGLContext()->fReadPixels(0, 0, aReadbackSize.width, aReadbackSize.height, + format, LOCAL_GL_UNSIGNED_BYTE, + &aReadbackBuffer[0]); + + if (aNeedsYFlip) { + *aNeedsYFlip = true; + } + + return true; +} + +// This is a DataSourceSurface that represents a 0-based PBO for GLTextureImage. +class PBOUnpackSurface : public gfx::DataSourceSurface { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PBOUnpackSurface, override) + + explicit PBOUnpackSurface(const gfx::IntSize& aSize) : mSize(aSize) {} + + uint8_t* GetData() override { return nullptr; } + int32_t Stride() override { return mSize.width * sizeof(uint32_t); } + gfx::SurfaceType GetType() const override { + return gfx::SurfaceType::DATA_ALIGNED; + } + gfx::IntSize GetSize() const override { return mSize; } + gfx::SurfaceFormat GetFormat() const override { + return gfx::SurfaceFormat::B8G8R8A8; + } + + // PBO offsets need to start from a 0 address, but DataSourceSurface::Map + // checks for failure by comparing the address against nullptr. Override Map + // to work around this. + bool Map(MapType, MappedSurface* aMappedSurface) override { + aMappedSurface->mData = GetData(); + aMappedSurface->mStride = Stride(); + return true; + } + + void Unmap() override {} + + private: + gfx::IntSize mSize; +}; + +RenderCompositorOGLSWGL::TileOGL::TileOGL( + RefPtr<layers::TextureImageTextureSourceOGL>&& aTexture, + const gfx::IntSize& aSize) + : mTexture(aTexture) { + auto* gl = mTexture->gl(); + if (gl && gl->HasPBOState() && gl->MakeCurrent()) { + mSurface = new PBOUnpackSurface(aSize); + // Create a PBO large enough to encompass any valid rects within the tile. + gl->fGenBuffers(1, &mPBO); + gl->fBindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, mPBO); + gl->fBufferData(LOCAL_GL_PIXEL_UNPACK_BUFFER, + mSurface->Stride() * aSize.height, nullptr, + LOCAL_GL_DYNAMIC_DRAW); + gl->fBindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, 0); + } else { + // Couldn't allocate a PBO, so just use a memory surface instead. + mSurface = gfx::Factory::CreateDataSourceSurface( + aSize, gfx::SurfaceFormat::B8G8R8A8); + } +} + +RenderCompositorOGLSWGL::TileOGL::~TileOGL() { + if (mPBO) { + auto* gl = mTexture->gl(); + if (gl && gl->MakeCurrent()) { + gl->fDeleteBuffers(1, &mPBO); + mPBO = 0; + } + } +} + +layers::DataTextureSource* +RenderCompositorOGLSWGL::TileOGL::GetTextureSource() { + return mTexture.get(); +} + +bool RenderCompositorOGLSWGL::TileOGL::Map(wr::DeviceIntRect aDirtyRect, + wr::DeviceIntRect aValidRect, + void** aData, int32_t* aStride) { + if (mPBO) { + auto* gl = mTexture->gl(); + if (!gl) { + return false; + } + // Map the PBO, but only within the range of the buffer that spans from the + // linear start offset to the linear end offset. Since we don't care about + // the previous contents of the buffer, we can just tell OpenGL to + // invalidate the entire buffer, even though we're only mapping a sub-range. + gl->fBindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, mPBO); + size_t stride = mSurface->Stride(); + size_t offset = + stride * aValidRect.min.y + aValidRect.min.x * sizeof(uint32_t); + size_t length = stride * (aValidRect.height() - 1) + + (aValidRect.width()) * sizeof(uint32_t); + void* data = gl->fMapBufferRange( + LOCAL_GL_PIXEL_UNPACK_BUFFER, offset, length, + LOCAL_GL_MAP_WRITE_BIT | LOCAL_GL_MAP_INVALIDATE_BUFFER_BIT); + gl->fBindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, 0); + if (!data) { + return false; + } + *aData = data; + *aStride = stride; + } else { + // No PBO is available, so just directly write to the memory surface. + gfx::DataSourceSurface::MappedSurface map; + if (!mSurface->Map(gfx::DataSourceSurface::READ_WRITE, &map)) { + return false; + } + // Verify that we're not somehow using a PBOUnpackSurface. + MOZ_ASSERT(map.mData != nullptr); + // glTex(Sub)Image on ES doesn't support arbitrary strides without + // the EXT_unpack_subimage extension. To avoid needing to make a + // copy of the data we'll always draw it with stride = bpp*width + // unless we're uploading the entire texture. + if (!mTexture->IsValid()) { + // If we don't have a texture we need to position our + // data in the correct spot because we're going to upload + // the entire surface + *aData = map.mData + aValidRect.min.y * map.mStride + + aValidRect.min.x * sizeof(uint32_t); + + *aStride = map.mStride; + mSubSurface = nullptr; + } else { + // Otherwise, we can just use the top left as a scratch space + *aData = map.mData; + *aStride = aDirtyRect.width() * BytesPerPixel(mSurface->GetFormat()); + mSubSurface = Factory::CreateWrappingDataSourceSurface( + (uint8_t*)*aData, *aStride, + IntSize(aDirtyRect.width(), aDirtyRect.height()), + mSurface->GetFormat()); + } + } + return true; +} + +void RenderCompositorOGLSWGL::TileOGL::Unmap(const gfx::IntRect& aDirtyRect) { + nsIntRegion dirty(aDirtyRect); + if (mPBO) { + // If there is a PBO, it must be unmapped before it can be sourced from. + // Leave the PBO bound before the call to Update so that the texture uploads + // will source from it. + auto* gl = mTexture->gl(); + if (!gl) { + return; + } + gl->fBindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, mPBO); + gl->fUnmapBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER); + mTexture->Update(mSurface, &dirty); + gl->fBindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, 0); + } else { + if (mSubSurface) { + mSurface->Unmap(); + // Our subsurface has a stride = aDirtyRect.width + // We use a negative offset to move it to match + // the dirty rect's top-left. These two offsets + // will cancel each other out by the time we reach + // TexSubImage. + IntPoint srcOffset = {0, 0}; + IntPoint dstOffset = aDirtyRect.TopLeft(); + // adjust the dirty region to be relative to the dstOffset + dirty.MoveBy(-dstOffset); + mTexture->Update(mSubSurface, &dirty, &srcOffset, &dstOffset); + mSubSurface = nullptr; + } else { + mSurface->Unmap(); + mTexture->Update(mSurface, &dirty); + } + } +} + +} // namespace wr +} // namespace mozilla diff --git a/gfx/webrender_bindings/RenderCompositorOGLSWGL.h b/gfx/webrender_bindings/RenderCompositorOGLSWGL.h new file mode 100644 index 0000000000..7752574311 --- /dev/null +++ b/gfx/webrender_bindings/RenderCompositorOGLSWGL.h @@ -0,0 +1,104 @@ +/* -*- 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_SWGL_H +#define MOZILLA_GFX_RENDERCOMPOSITOR_OGL_SWGL_H + +#include "mozilla/layers/Compositor.h" +#include "mozilla/webrender/RenderCompositorLayersSWGL.h" + +namespace mozilla { + +namespace layers { +class TextureImageTextureSourceOGL; +} + +namespace wr { + +class RenderCompositorOGLSWGL : public RenderCompositorLayersSWGL { + public: + static UniquePtr<RenderCompositor> Create( + const RefPtr<widget::CompositorWidget>& aWidget, nsACString& aError); + + RenderCompositorOGLSWGL(layers::Compositor* aCompositor, + const RefPtr<widget::CompositorWidget>& aWidget, + void* aContext); + virtual ~RenderCompositorOGLSWGL(); + + gl::GLContext* GetGLContext(); + + bool MakeCurrent() override; + + bool BeginFrame() override; + RenderedFrameId EndFrame(const nsTArray<DeviceIntRect>& aDirtyRects) override; + + void GetCompositorCapabilities(CompositorCapabilities* aCaps) override; + + // Returns true for requesting rendering during readback. + // RenderCompositorOGLSWGL::MaybeReadback() requests rendering. + // This value is not used by WebRender, since native compositor API is used + // for sw-wr. + bool UsePartialPresent() override { return true; } + bool RequestFullRender() override; + + void Pause() override; + bool Resume() override; + bool IsPaused() override; + + LayoutDeviceIntSize GetBufferSize() override; + + layers::WebRenderCompositor CompositorType() const override { + return layers::WebRenderCompositor::OPENGL; + } + + bool MaybeReadback(const gfx::IntSize& aReadbackSize, + const wr::ImageFormat& aReadbackFormat, + const Range<uint8_t>& aReadbackBuffer, + bool* aNeedsYFlip) override; + + private: + void HandleExternalImage(RenderTextureHost* aExternalImage, + FrameSurface& aFrameSurface) override; + UniquePtr<RenderCompositorLayersSWGL::Tile> DoCreateTile( + Surface* aSurface) override; + + EGLSurface CreateEGLSurface(); + void DestroyEGLSurface(); + + EGLSurface mEGLSurface = EGL_NO_SURFACE; + bool mFullRender = false; + +#ifdef MOZ_WIDGET_ANDROID + // Whether we are in the process of handling a NEW_SURFACE error. On Android + // this is used to allow the widget an opportunity to recover from the first + // instance, before raising a WebRenderError on subsequent occurences. + bool mHandlingNewSurfaceError = false; +#endif + + class TileOGL : public RenderCompositorLayersSWGL::Tile { + public: + TileOGL(RefPtr<layers::TextureImageTextureSourceOGL>&& aTexture, + const gfx::IntSize& aSize); + virtual ~TileOGL(); + + bool Map(wr::DeviceIntRect aDirtyRect, wr::DeviceIntRect aValidRect, + void** aData, int32_t* aStride) override; + void Unmap(const gfx::IntRect& aDirtyRect) override; + layers::DataTextureSource* GetTextureSource() override; + bool IsValid() override { return true; } + + private: + RefPtr<layers::TextureImageTextureSourceOGL> mTexture; + RefPtr<gfx::DataSourceSurface> mSurface; + RefPtr<gfx::DataSourceSurface> mSubSurface; + GLuint mPBO = 0; + }; +}; + +} // 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..1eec99bb3a --- /dev/null +++ b/gfx/webrender_bindings/RenderCompositorSWGL.cpp @@ -0,0 +1,315 @@ +/* -*- 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" + +#ifdef MOZ_WIDGET_GTK +# include "mozilla/WidgetUtilsGtk.h" +#endif + +namespace mozilla { +using namespace gfx; + +namespace wr { + +extern LazyLogModule gRenderThreadLog; +#define LOG(...) MOZ_LOG(gRenderThreadLog, LogLevel::Debug, (__VA_ARGS__)) + +/* static */ +UniquePtr<RenderCompositor> RenderCompositorSWGL::Create( + const 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>(aWidget, ctx); +} + +RenderCompositorSWGL::RenderCompositorSWGL( + const RefPtr<widget::CompositorWidget>& aWidget, void* aContext) + : RenderCompositor(aWidget), mContext(aContext) { + MOZ_ASSERT(mContext); + LOG("RenderCompositorSWGL::RenderCompositorSWGL()"); +} + +RenderCompositorSWGL::~RenderCompositorSWGL() { + LOG("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() { + mRenderWidgetSize = Some(mWidget->GetClientSize()); +#ifdef MOZ_WAYLAND + if (mLastRenderWidgetSize != mRenderWidgetSize.value()) { + mLastRenderWidgetSize = mRenderWidgetSize.value(); + mRequestFullRender = true; + } +#endif + // Set up a temporary region representing the entire window surface in case a + // dirty region is not supplied. + ClearMappedBuffer(); + mDirtyRegion = 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(mDirtyRegion, &bufferMode); + if (!mDT) { + gfxCriticalNoteOnce + << "RenderCompositorSWGL failed mapping default framebuffer, no dt"; + return false; + } + // 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 = mDirtyRegion.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)); + } + // Sometimes we end up racing on the widget size, and it can shrink between + // BeginFrame and StartCompositing. We calculated our dirty region based on + // the previous widget size, so we need to clamp the bounds here to ensure + // we remain within the buffer. + bounds.IntersectRect( + bounds, + LayoutDeviceIntRect(bounds.TopLeft(), + LayoutDeviceIntSize(size.width, size.height))); + } 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, mDirtyRegion); + ClearMappedBuffer(); + gfxCriticalNoteOnce + << "RenderCompositorSWGL failed mapping default framebuffer, no surf"; + 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.min.x, rect.min.y, rect.width(), + rect.height())); + } + + LayoutDeviceIntRegion clear = mWidget->GetTransparentRegion(); + clear.AndWith(mDirtyRegion); + clear.SubOut(opaque); + for (auto iter = clear.RectIter(); !iter.Done(); iter.Next()) { + const auto& rect = iter.Get(); + wr_swgl_clear_color_rect(mContext, 0, rect.x, rect.y, rect.width, + rect.height, 0, 0, 0, 0); + } + + return true; +} + +void RenderCompositorSWGL::StartCompositing( + wr::ColorF aClearColor, 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 + mDirtyRegion = LayoutDeviceIntRect(LayoutDeviceIntPoint(), GetBufferSize()); + } + if (aNumDirtyRects) { + // Install the dirty rects into the bounds of the existing region + auto bounds = mDirtyRegion.GetBounds(); + mDirtyRegion.SetEmpty(); + for (size_t i = 0; i < aNumDirtyRects; i++) { + const auto& rect = aDirtyRects[i]; + mDirtyRegion.OrWith(LayoutDeviceIntRect(rect.min.x, rect.min.y, + rect.width(), rect.height())); + } + // Ensure the region lies within the widget bounds + mDirtyRegion.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 (mDirtyRegion.IsEmpty() || + !AllocateMappedBuffer(aOpaqueRects, aNumOpaqueRects)) { + // If allocation of the mapped default framebuffer failed, then just install + // a temporary framebuffer (with a minimum size of 2x2) so compositing can + // still proceed. + auto bounds = mDirtyRegion.GetBounds(); + bounds.width = std::max(bounds.width, 2); + bounds.height = std::max(bounds.height, 2); + wr_swgl_init_default_framebuffer(mContext, bounds.x, bounds.y, bounds.width, + bounds.height, 0, nullptr); + } +} + +void RenderCompositorSWGL::CommitMappedBuffer(bool aDirty) { + if (!mDT) { + mDirtyRegion.SetEmpty(); + return; + } + // Force any delayed clears to resolve. + if (aDirty) { + wr_swgl_resolve_framebuffer(mContext, 0); + } + // 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 = mDirtyRegion.GetBounds(); + gfx::IntPoint srcOffset = bounds.TopLeft().ToUnknownPoint(); + gfx::IntPoint dstOffset = mDT->GetSize() == bounds.Size().ToUnknownSize() + ? srcOffset + : gfx::IntPoint(0, 0); + for (auto iter = mDirtyRegion.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); + } + mDT->Flush(); + + // Done with the DT. Hand it back to the widget and clear out any trace of it. + mWidget->EndRemoteDrawingInRegion(mDT, mDirtyRegion); + mDirtyRegion.SetEmpty(); + ClearMappedBuffer(); +} + +void RenderCompositorSWGL::CancelFrame() { + CommitMappedBuffer(false); + mRenderWidgetSize = Nothing(); +} + +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(); + mRenderWidgetSize = Nothing(); + return frameId; +} + +bool RenderCompositorSWGL::RequestFullRender() { +#ifdef MOZ_WIDGET_ANDROID + // XXX Add partial present support. + return true; +#endif +#ifdef MOZ_WAYLAND + // We're requested to do full render after Resume() on Wayland. + if (mRequestFullRender) { + mRequestFullRender = false; + return true; + } +#endif + return false; +} + +void RenderCompositorSWGL::Pause() {} + +bool RenderCompositorSWGL::Resume() { +#ifdef MOZ_WAYLAND + mRequestFullRender = true; +#endif + return true; +} + +LayoutDeviceIntSize RenderCompositorSWGL::GetBufferSize() { + // If we're between BeginFrame() and EndFrame()/CancelFrame() calls + // return recent rendering size instead of actual underlying widget + // size. It prevents possible rendering artifacts if widget size was changed. + return mRenderWidgetSize ? mRenderWidgetSize.value() + : mWidget->GetClientSize(); +} + +void RenderCompositorSWGL::GetCompositorCapabilities( + CompositorCapabilities* aCaps) { + // Always support a single update rect for SwCompositor + aCaps->max_update_rects = 1; + + // On uncomposited desktops such as X11 without compositor or Window 7 with + // Aero disabled we need to force a full redraw when the window contents may + // be damaged. +#ifdef MOZ_WIDGET_GTK + aCaps->redraw_on_invalidation = widget::GdkIsX11Display(); +#else + aCaps->redraw_on_invalidation = true; +#endif +} + +} // namespace wr +} // namespace mozilla diff --git a/gfx/webrender_bindings/RenderCompositorSWGL.h b/gfx/webrender_bindings/RenderCompositorSWGL.h new file mode 100644 index 0000000000..2b3ce35454 --- /dev/null +++ b/gfx/webrender_bindings/RenderCompositorSWGL.h @@ -0,0 +1,90 @@ +/* -*- 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( + const RefPtr<widget::CompositorWidget>& aWidget, nsACString& aError); + + RenderCompositorSWGL(const 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(wr::ColorF aClearColor, + const wr::DeviceIntRect* aDirtyRects, + size_t aNumDirtyRects, + const wr::DeviceIntRect* aOpaqueRects, + size_t aNumOpaqueRects) override; + + bool UsePartialPresent() override { return true; } + bool RequestFullRender() override; + + 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 + void GetCompositorCapabilities(CompositorCapabilities* aCaps) override; + + private: + void* mContext = nullptr; + RefPtr<gfx::DrawTarget> mDT; + LayoutDeviceIntRegion mDirtyRegion; + // Keep consistent buffer size between BeginFrame and EndFrame/CancelFrame + // calls to make sure we don't change buffer size during rendering. + Maybe<LayoutDeviceIntSize> mRenderWidgetSize; + RefPtr<gfx::DataSourceSurface> mSurface; + uint8_t* mMappedData = nullptr; + int32_t mMappedStride = 0; +#ifdef MOZ_WAYLAND + // On Wayland we need to request full render if widget size is changed + // because SW rendering backend reallocates and clears target surface. + LayoutDeviceIntSize mLastRenderWidgetSize; + bool mRequestFullRender = false; +#endif + + 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..232f8572cb --- /dev/null +++ b/gfx/webrender_bindings/RenderD3D11TextureHost.cpp @@ -0,0 +1,705 @@ +/* -*- 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 "GLContextEGL.h" +#include "GLLibraryEGL.h" +#include "RenderThread.h" +#include "RenderCompositor.h" +#include "RenderCompositorD3D11SWGL.h" +#include "ScopedGLHelpers.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/layers/TextureD3D11.h" + +namespace mozilla { +namespace wr { + +RenderDXGITextureHost::RenderDXGITextureHost( + WindowsHandle aHandle, + Maybe<layers::GpuProcessTextureId>& aGpuProcessTextureId, + uint32_t aArrayIndex, gfx::SurfaceFormat aFormat, + gfx::ColorSpace2 aColorSpace, gfx::ColorRange aColorRange, + gfx::IntSize aSize) + : mHandle(aHandle), + mGpuProcessTextureId(aGpuProcessTextureId), + mArrayIndex(aArrayIndex), + mSurface(0), + mStream(0), + mTextureHandle{0}, + mFormat(aFormat), + mColorSpace(aColorSpace), + 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 && aGpuProcessTextureId.isNothing()) || + (!aHandle && aGpuProcessTextureId.isSome())); +} + +RenderDXGITextureHost::~RenderDXGITextureHost() { + MOZ_COUNT_DTOR_INHERITED(RenderDXGITextureHost, RenderTextureHost); + DeleteTextureHandle(); +} + +ID3D11Texture2D* RenderDXGITextureHost::GetD3D11Texture2DWithGL() { + if (mTexture) { + return mTexture; + } + + if (!mGL) { + // SingletonGL is always used on Windows with ANGLE. + mGL = RenderThread::Get()->SingletonGL(); + } + + 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) { + if (!aCompositor) { + return false; + } + + 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; + } + + if (mGpuProcessTextureId.isSome()) { + auto* textureMap = layers::GpuProcessD3D11TextureMap::Get(); + if (textureMap) { + RefPtr<ID3D11Texture2D> texture; + mTexture = textureMap->GetTexture(mGpuProcessTextureId.ref()); + if (mTexture) { + return true; + } + } + return false; + } + + 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() { + if (mTextureHandle[0]) { + 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]); + 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]); + ActivateBindAndTexParameteri(mGL, LOCAL_GL_TEXTURE1, + LOCAL_GL_TEXTURE_EXTERNAL_OES, + mTextureHandle[1]); + ok &= bool(egl->fStreamConsumerGLTextureExternalAttribsNV( + mStream, consumerAttributes)); + ok &= bool(egl->fCreateStreamProducerD3DTextureANGLE(mStream, nullptr)); + } + + const EGLAttrib frameAttributes[] = { + LOCAL_EGL_D3D_TEXTURE_SUBRESOURCE_ID_ANGLE, + static_cast<EGLAttrib>(mArrayIndex), + LOCAL_EGL_NONE, + }; + + // Insert the d3d texture. + ok &= bool(egl->fStreamPostD3DTextureANGLE(mStream, (void*)mTexture.get(), + frameAttributes)); + + 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) { + 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()) { + return InvalidToWrExternalImage(); + } + + if (!LockInternal()) { + return InvalidToWrExternalImage(); + } + + const auto uvs = GetUvCoords(GetSize(aChannelIndex)); + return NativeTextureToWrExternalImage(GetGLHandle(aChannelIndex), uvs.first.x, + uvs.first.y, uvs.second.x, + uvs.second.y); +} + +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() { + if (mTextureHandles[0]) { + 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]); + + // 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) { + 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()) { + return InvalidToWrExternalImage(); + } + + if (!LockInternal()) { + return InvalidToWrExternalImage(); + } + + const auto uvs = GetUvCoords(GetSize(aChannelIndex)); + return NativeTextureToWrExternalImage(GetGLHandle(aChannelIndex), uvs.first.x, + uvs.first.y, uvs.second.x, + uvs.second.y); +} + +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..83a7d3a279 --- /dev/null +++ b/gfx/webrender_bindings/RenderD3D11TextureHost.h @@ -0,0 +1,213 @@ +/* -*- 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" + +#include <d3d11.h> + +struct ID3D11Texture2D; +struct IDXGIKeyedMutex; + +namespace mozilla { + +namespace wr { + +class RenderDXGITextureHost final : public RenderTextureHostSWGL { + public: + RenderDXGITextureHost( + WindowsHandle aHandle, + Maybe<layers::GpuProcessTextureId>& aGpuProcessTextureId, + uint32_t aArrayIndex, gfx::SurfaceFormat aFormat, gfx::ColorSpace2, + gfx::ColorRange aColorRange, gfx::IntSize aSize); + + wr::WrExternalImage Lock(uint8_t aChannelIndex, gl::GLContext* aGL) 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::YUVRangedColorSpace GetYUVColorSpace() const override { + return ToYUVRangedColorSpace(ToYUVColorSpace(mColorSpace), mColorRange); + } + + bool EnsureD3D11Texture2D(ID3D11Device* aDevice); + bool LockInternal(); + + size_t Bytes() override { + size_t bytes = 0; + + size_t bpp = GetPlaneCount() > 1 + ? (GetColorDepth() == gfx::ColorDepth::COLOR_8 ? 1 : 2) + : 4; + + for (size_t i = 0; i < GetPlaneCount(); i++) { + gfx::IntSize size = GetSize(i); + bytes += size.width * size.height * bpp; + } + return bytes; + } + + uint32_t ArrayIndex() const { return mArrayIndex; } + + private: + virtual ~RenderDXGITextureHost(); + + bool EnsureD3D11Texture2DWithGL(); + bool EnsureLockable(); + + void DeleteTextureHandle(); + + RefPtr<gl::GLContext> mGL; + + WindowsHandle mHandle; + Maybe<layers::GpuProcessTextureId> mGpuProcessTextureId; + RefPtr<ID3D11Texture2D> mTexture; + uint32_t mArrayIndex = 0; + 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]; + + public: + const gfx::SurfaceFormat mFormat; + const gfx::ColorSpace2 mColorSpace; + const gfx::ColorRange mColorRange; + const gfx::IntSize mSize; + + private: + 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) 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::YUVRangedColorSpace GetYUVColorSpace() const override { + return ToYUVRangedColorSpace(mYUVColorSpace, GetColorRange()); + } + + bool EnsureD3D11Texture2D(ID3D11Device* aDevice); + bool LockInternal(); + + ID3D11Texture2D* GetD3D11Texture2D(uint8_t aChannelIndex) { + return mTextures[aChannelIndex]; + } + + size_t Bytes() override { + size_t bytes = 0; + + size_t bpp = mColorDepth == gfx::ColorDepth::COLOR_8 ? 1 : 2; + + for (size_t i = 0; i < GetPlaneCount(); i++) { + gfx::IntSize size = GetSize(i); + bytes += size.width * size.height * bpp; + } + return bytes; + } + + private: + virtual ~RenderDXGIYCbCrTextureHost(); + + bool EnsureLockable(); + + 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..f52535b95a --- /dev/null +++ b/gfx/webrender_bindings/RenderDMABUFTextureHost.cpp @@ -0,0 +1,67 @@ +/* -*- 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) { + 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(); + } + + if (!mSurface->GetTexture(aChannelIndex)) { + if (!mSurface->CreateTexture(mGL, aChannelIndex)) { + return InvalidToWrExternalImage(); + } + ActivateBindAndTexParameteri(mGL, LOCAL_GL_TEXTURE0, LOCAL_GL_TEXTURE_2D, + mSurface->GetTexture(aChannelIndex)); + } + + const auto uvs = GetUvCoords(gfx::IntSize( + mSurface->GetWidth(aChannelIndex), mSurface->GetHeight(aChannelIndex))); + return NativeTextureToWrExternalImage(mSurface->GetTexture(aChannelIndex), + uvs.first.x, uvs.first.y, uvs.second.x, + uvs.second.y); +} + +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..a38eeaf596 --- /dev/null +++ b/gfx/webrender_bindings/RenderDMABUFTextureHost.h @@ -0,0 +1,46 @@ +/* -*- 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) 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/RenderDcompSurfaceTextureHost.cpp b/gfx/webrender_bindings/RenderDcompSurfaceTextureHost.cpp new file mode 100644 index 0000000000..999421e793 --- /dev/null +++ b/gfx/webrender_bindings/RenderDcompSurfaceTextureHost.cpp @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "RenderDcompSurfaceTextureHost.h" + +#undef _WIN32_WINNT +#define _WIN32_WINNT _WIN32_WINNT_WINBLUE +#undef NTDDI_VERSION +#define NTDDI_VERSION NTDDI_WINBLUE + +#include <dcomp.h> + +#define LOG(msg, ...) \ + MOZ_LOG(gDcompSurface, LogLevel::Debug, \ + ("RenderDcompSurfaceTextureHost=%p, handle=%p, size=[%u,%u] " msg, \ + this, this->mHandle, this->mSize.Width(), this->mSize.Height(), \ + ##__VA_ARGS__)) + +namespace mozilla::wr { + +RenderDcompSurfaceTextureHost::RenderDcompSurfaceTextureHost( + HANDLE aHandle, gfx::IntSize aSize, gfx::SurfaceFormat aFormat) + : mHandle(aHandle), mSize(aSize), mFormat(aFormat) { + MOZ_ASSERT(aHandle && aHandle != INVALID_HANDLE_VALUE); +} + +IDCompositionSurface* RenderDcompSurfaceTextureHost::CreateSurfaceFromDevice( + IDCompositionDevice* aDevice) { + // Already created surface, no need to recreate it again. + if (mDcompSurface) { + return mDcompSurface; + } + + auto* surface = + static_cast<IDCompositionSurface**>(getter_AddRefs(mDcompSurface)); + auto hr = aDevice->CreateSurfaceFromHandle( + mHandle, reinterpret_cast<IUnknown**>(surface)); + if (FAILED(hr)) { + LOG("Failed to create surface from Dcomp handle %p, hr=%lx", mHandle, hr); + return nullptr; + } + LOG("Created surface %p correctly", surface); + return mDcompSurface; +} + +} // namespace mozilla::wr + +#undef LOG diff --git a/gfx/webrender_bindings/RenderDcompSurfaceTextureHost.h b/gfx/webrender_bindings/RenderDcompSurfaceTextureHost.h new file mode 100644 index 0000000000..7533e7c75f --- /dev/null +++ b/gfx/webrender_bindings/RenderDcompSurfaceTextureHost.h @@ -0,0 +1,70 @@ +/* -*- Mode: C++; tab-width: ; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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_RENDERDCOMPSURFACETEXTUREHOST_H +#define MOZILLA_GFX_RENDERDCOMPSURFACETEXTUREHOST_H + +#include "GLTypes.h" +#include "RenderTextureHostSWGL.h" +#include "mozilla/webrender/RenderThread.h" + +struct IDCompositionDevice; +struct IDCompositionSurface; + +inline mozilla::LazyLogModule gDcompSurface("DcompSurface"); + +namespace mozilla::wr { + +/** + * A render texture host is responsible to create a dcomp surface from an + * existing dcomp handle. Currently usage is that MF media engine will create + * a surface handle from another remote process, and we reconstruct the surface + * and use it in the DCLayerTree in the GPU process. + */ +class RenderDcompSurfaceTextureHost final : public RenderTextureHostSWGL { + public: + RenderDcompSurfaceTextureHost(HANDLE aHandle, gfx::IntSize aSize, + gfx::SurfaceFormat aFormat); + + // RenderTextureHost + RenderDcompSurfaceTextureHost* AsRenderDcompSurfaceTextureHost() override { + return this; + } + + // RenderTextureHostSWGL + gfx::SurfaceFormat GetFormat() const override { return mFormat; } + gfx::ColorDepth GetColorDepth() const override { + return gfx::ColorDepth::COLOR_8; + } + size_t GetPlaneCount() const override { return 1; } + bool MapPlane(RenderCompositor* aCompositor, uint8_t aChannelIndex, + PlaneInfo& aPlaneInfo) override { + return false; + } + void UnmapPlanes() override {} + gfx::YUVRangedColorSpace GetYUVColorSpace() const override { + return gfx::YUVRangedColorSpace::GbrIdentity; + } + size_t Bytes() override { return 0; } + + gfx::IntSize GetSize() const { return mSize; }; + + HANDLE GetDcompSurfaceHandle() const { return mHandle; } + + // Not thread-safe. They should only be called on the renderer thread on the + // GPU process. + IDCompositionSurface* CreateSurfaceFromDevice(IDCompositionDevice* aDevice); + IDCompositionSurface* GetSurface() const { return mDcompSurface; }; + + private: + const HANDLE mHandle; + const gfx::IntSize mSize; + const gfx::SurfaceFormat mFormat; + RefPtr<IDCompositionSurface> mDcompSurface; +}; + +} // namespace mozilla::wr + +#endif // MOZILLA_GFX_RENDERDCOMPSURFACETEXTUREHOST_H diff --git a/gfx/webrender_bindings/RenderEGLImageTextureHost.cpp b/gfx/webrender_bindings/RenderEGLImageTextureHost.cpp new file mode 100644 index 0000000000..6ca9e74072 --- /dev/null +++ b/gfx/webrender_bindings/RenderEGLImageTextureHost.cpp @@ -0,0 +1,96 @@ +/* -*- 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) { + 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); + ActivateBindAndTexParameteri(mGL, LOCAL_GL_TEXTURE0, mTextureTarget, + mTextureHandle); + mGL->fEGLImageTargetTexture2D(mTextureTarget, mImage); + } + + const auto uvs = GetUvCoords(mSize); + return NativeTextureToWrExternalImage( + mTextureHandle, uvs.first.x, uvs.first.y, uvs.second.x, uvs.second.y); +} + +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..c2d1b512d2 --- /dev/null +++ b/gfx/webrender_bindings/RenderEGLImageTextureHost.h @@ -0,0 +1,46 @@ +/* -*- 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) 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..db8107ff0a --- /dev/null +++ b/gfx/webrender_bindings/RenderExternalTextureHost.cpp @@ -0,0 +1,265 @@ +/* -*- 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.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"); + } +} + +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()); + auto cbcrSize = layers::ImageDataSerializer::GetCroppedCbCrSize(desc); + + mSurfaces[0] = gfx::Factory::CreateWrappingDataSourceSurface( + layers::ImageDataSerializer::GetYChannel(GetBuffer(), desc), + desc.yStride(), desc.display().Size(), surfaceFormat); + mSurfaces[1] = gfx::Factory::CreateWrappingDataSourceSurface( + layers::ImageDataSerializer::GetCbChannel(GetBuffer(), desc), + desc.cbCrStride(), cbcrSize, surfaceFormat); + mSurfaces[2] = gfx::Factory::CreateWrappingDataSourceSurface( + layers::ImageDataSerializer::GetCrChannel(GetBuffer(), desc), + desc.cbCrStride(), 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) { + if (mGL.get() != aGL) { + mGL = aGL; + mGL->MakeCurrent(); + } + + if (!mGL || !mGL->MakeCurrent()) { + return InvalidToWrExternalImage(); + } + + if (!InitializeIfNeeded()) { + return InvalidToWrExternalImage(); + } + + UpdateTextures(); + 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 auto uvs = GetUvCoords(texture->GetSize()); + mImages[aIndex] = NativeTextureToWrExternalImage( + handle, uvs.first.x, uvs.first.y, uvs.second.x, uvs.second.y); + } + + MOZ_ASSERT(mGL->GetError() == LOCAL_GL_NO_ERROR); +} + +void RenderExternalTextureHost::UpdateTextures() { + if (!mTextureUpdateNeeded) { + // Nothing to do here. + return; + } + + for (size_t i = 0; i < PlaneCount(); ++i) { + UpdateTexture(i); + } + + mTextureSources[0]->MaybeFenceTexture(); + mTextureUpdateNeeded = false; +} + +size_t RenderExternalTextureHost::GetPlaneCount() const { return PlaneCount(); } + +gfx::SurfaceFormat RenderExternalTextureHost::GetFormat() const { + return mFormat; +} + +gfx::ColorDepth RenderExternalTextureHost::GetColorDepth() const { + switch (mDescriptor.type()) { + case layers::BufferDescriptor::TYCbCrDescriptor: + return mDescriptor.get_YCbCrDescriptor().colorDepth(); + default: + return gfx::ColorDepth::COLOR_8; + } +} + +gfx::YUVRangedColorSpace RenderExternalTextureHost::GetYUVColorSpace() const { + switch (mDescriptor.type()) { + case layers::BufferDescriptor::TYCbCrDescriptor: + return gfx::GetYUVRangedColorSpace(mDescriptor.get_YCbCrDescriptor()); + default: + return gfx::YUVRangedColorSpace::Default; + } +} + +bool RenderExternalTextureHost::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.display().Size(); + break; + case 1: + aPlaneInfo.mData = + layers::ImageDataSerializer::GetCbChannel(mBuffer, desc); + aPlaneInfo.mStride = desc.cbCrStride(); + aPlaneInfo.mSize = + layers::ImageDataSerializer::GetCroppedCbCrSize(desc); + break; + case 2: + aPlaneInfo.mData = + layers::ImageDataSerializer::GetCrChannel(mBuffer, desc); + aPlaneInfo.mStride = desc.cbCrStride(); + aPlaneInfo.mSize = + layers::ImageDataSerializer::GetCroppedCbCrSize(desc); + 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 RenderExternalTextureHost::UnmapPlanes() {} + +} // namespace wr +} // namespace mozilla diff --git a/gfx/webrender_bindings/RenderExternalTextureHost.h b/gfx/webrender_bindings/RenderExternalTextureHost.h new file mode 100644 index 0000000000..c3b13250c1 --- /dev/null +++ b/gfx/webrender_bindings/RenderExternalTextureHost.h @@ -0,0 +1,81 @@ +/* -*- 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 "RenderTextureHostSWGL.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 RenderTextureHostSWGL { + public: + RenderExternalTextureHost(uint8_t* aBuffer, + const layers::BufferDescriptor& aDescriptor); + + wr::WrExternalImage Lock(uint8_t aChannelIndex, gl::GLContext* aGL) override; + void Unlock() override; + void PrepareForUse() override; + size_t Bytes() override { + return mSize.width * mSize.height * BytesPerPixel(mFormat); + } + + // RenderTextureHostSWGL + size_t GetPlaneCount() const override; + + gfx::SurfaceFormat GetFormat() const override; + + gfx::ColorDepth GetColorDepth() const override; + + gfx::YUVRangedColorSpace GetYUVColorSpace() const override; + + bool MapPlane(RenderCompositor* aCompositor, uint8_t aChannelIndex, + PlaneInfo& aPlaneInfo) override; + + void UnmapPlanes() override; + + 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(); + + 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..bb0575949b --- /dev/null +++ b/gfx/webrender_bindings/RenderMacIOSurfaceTextureHost.cpp @@ -0,0 +1,157 @@ +/* -*- 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) { + MOZ_ASSERT(aGL && aSurface && aTexture); + + aGL->fGenTextures(1, aTexture); + ActivateBindAndTexParameteri(aGL, LOCAL_GL_TEXTURE0, + LOCAL_GL_TEXTURE_RECTANGLE_ARB, *aTexture); + 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)); +} + +size_t RenderMacIOSurfaceTextureHost::Bytes() { + return mSurface->GetAllocSize(); +} + +wr::WrExternalImage RenderMacIOSurfaceTextureHost::Lock(uint8_t aChannelIndex, + gl::GLContext* aGL) { + 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()); + + // 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])); + for (size_t i = 1; i < mSurface->GetPlaneCount(); ++i) { + CreateTextureForPlane(i, mGL, mSurface, &(mTextureHandles[i])); + } + } + + const auto uvs = GetUvCoords(GetSize(aChannelIndex)); + return NativeTextureToWrExternalImage(GetGLHandle(aChannelIndex), uvs.first.x, + uvs.first.y, uvs.second.x, + uvs.second.y); +} + +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 mSurface->GetColorDepth(); +} + +gfx::YUVRangedColorSpace RenderMacIOSurfaceTextureHost::GetYUVColorSpace() + const { + return ToYUVRangedColorSpace(mSurface->GetYUVColorSpace(), + mSurface->GetColorRange()); +} + +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..173cd3c223 --- /dev/null +++ b/gfx/webrender_bindings/RenderMacIOSurfaceTextureHost.h @@ -0,0 +1,61 @@ +/* -*- 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) override; + void Unlock() override; + + gfx::IntSize GetSize(uint8_t aChannelIndex) const; + GLuint GetGLHandle(uint8_t aChannelIndex) const; + + RenderMacIOSurfaceTextureHost* AsRenderMacIOSurfaceTextureHost() override { + return this; + } + + size_t Bytes() override; + + MacIOSurface* GetSurface() { return mSurface; } + + // RenderTextureHostSWGL + size_t GetPlaneCount() const override; + gfx::SurfaceFormat GetFormat() const override; + gfx::ColorDepth GetColorDepth() const override; + gfx::YUVRangedColorSpace 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..db085f8f2f --- /dev/null +++ b/gfx/webrender_bindings/RenderSharedSurfaceTextureHost.cpp @@ -0,0 +1,78 @@ +/* -*- 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) { + if (!mLocked) { + if (NS_WARN_IF( + !mSurface->Map(gfx::DataSourceSurface::MapType::READ, &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; +} + +size_t RenderSharedSurfaceTextureHost::GetPlaneCount() const { return 1; } + +gfx::SurfaceFormat RenderSharedSurfaceTextureHost::GetFormat() const { + return mSurface->GetFormat(); +} + +gfx::ColorDepth RenderSharedSurfaceTextureHost::GetColorDepth() const { + return gfx::ColorDepth::COLOR_8; +} + +bool RenderSharedSurfaceTextureHost::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 RenderSharedSurfaceTextureHost::UnmapPlanes() { mSurface->Unmap(); } + +} // namespace wr +} // namespace mozilla diff --git a/gfx/webrender_bindings/RenderSharedSurfaceTextureHost.h b/gfx/webrender_bindings/RenderSharedSurfaceTextureHost.h new file mode 100644 index 0000000000..2575abdac3 --- /dev/null +++ b/gfx/webrender_bindings/RenderSharedSurfaceTextureHost.h @@ -0,0 +1,57 @@ +/* -*- 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 "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 RenderSharedSurfaceTextureHost final : public RenderTextureHostSWGL { + public: + explicit RenderSharedSurfaceTextureHost( + gfx::SourceSurfaceSharedDataWrapper* aSurface); + + // RenderTextureHost + wr::WrExternalImage Lock(uint8_t aChannelIndex, gl::GLContext* aGL) override; + void Unlock() override; + size_t Bytes() override; + + // RenderTextureHostSWGL + 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 ~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/RenderTextureHost.cpp b/gfx/webrender_bindings/RenderTextureHost.cpp new file mode 100644 index 0000000000..71391bd1f7 --- /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) { + aGL->fActiveTexture(aActiveTexture); + aGL->fBindTexture(aBindTarget, aBindTexture); + // Initialize the mip filters to linear by default. + aGL->fTexParameteri(aBindTarget, LOCAL_GL_TEXTURE_MIN_FILTER, + LOCAL_GL_LINEAR); + aGL->fTexParameteri(aBindTarget, LOCAL_GL_TEXTURE_MAG_FILTER, + LOCAL_GL_LINEAR); +} + +RenderTextureHost::RenderTextureHost() : mIsFromDRMSource(false) { + MOZ_COUNT_CTOR(RenderTextureHost); +} + +RenderTextureHost::~RenderTextureHost() { + MOZ_ASSERT(RenderThread::IsInRenderThread()); + MOZ_COUNT_DTOR(RenderTextureHost); +} + +wr::WrExternalImage RenderTextureHost::Lock(uint8_t aChannelIndex, + gl::GLContext* aGL) { + return InvalidToWrExternalImage(); +} + +wr::WrExternalImage RenderTextureHost::LockSWGL(uint8_t aChannelIndex, + void* aContext, + RenderCompositor* aCompositor) { + return InvalidToWrExternalImage(); +} + +std::pair<gfx::Point, gfx::Point> RenderTextureHost::GetUvCoords( + gfx::IntSize aTextureSize) const { + return std::make_pair(gfx::Point(0.0, 0.0), + gfx::Point(static_cast<float>(aTextureSize.width), + static_cast<float>(aTextureSize.height))); +} + +void RenderTextureHost::Destroy() { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); +} + +} // namespace wr +} // namespace mozilla diff --git a/gfx/webrender_bindings/RenderTextureHost.h b/gfx/webrender_bindings/RenderTextureHost.h new file mode 100644 index 0000000000..19680cc7fa --- /dev/null +++ b/gfx/webrender_bindings/RenderTextureHost.h @@ -0,0 +1,135 @@ +/* -*- 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" +#include "mozilla/webrender/WebRenderTypes.h" + +namespace mozilla { + +namespace gl { +class GLContext; +} + +namespace wr { + +class RenderAndroidHardwareBufferTextureHost; +class RenderAndroidSurfaceTextureHost; +class RenderCompositor; +class RenderDXGITextureHost; +class RenderDXGIYCbCrTextureHost; +class RenderDcompSurfaceTextureHost; +class RenderMacIOSurfaceTextureHost; +class RenderBufferTextureHost; +class RenderTextureHostSWGL; +class RenderTextureHostWrapper; + +void ActivateBindAndTexParameteri(gl::GLContext* aGL, GLenum aActiveTexture, + GLenum aBindTarget, GLuint aBindTexture); + +class RenderTextureHost { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RenderTextureHost) + + public: + RenderTextureHost(); + + virtual gfx::SurfaceFormat GetFormat() const { + return gfx::SurfaceFormat::UNKNOWN; + } + + virtual gfx::YUVRangedColorSpace GetYUVColorSpace() const { + return gfx::YUVRangedColorSpace::Default; + } + + virtual wr::WrExternalImage Lock(uint8_t aChannelIndex, gl::GLContext* aGL); + + virtual void Unlock() {} + + virtual wr::WrExternalImage LockSWGL(uint8_t aChannelIndex, void* aContext, + RenderCompositor* aCompositor); + + 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; } + // Returns true when this texture was generated from a DRM-protected source. + bool IsFromDRMSource() { return mIsFromDRMSource; } + void SetIsFromDRMSource(bool aIsFromDRMSource) { + mIsFromDRMSource = aIsFromDRMSource; + } + + virtual size_t Bytes() = 0; + + virtual RenderDXGITextureHost* AsRenderDXGITextureHost() { return nullptr; } + virtual RenderDXGIYCbCrTextureHost* AsRenderDXGIYCbCrTextureHost() { + return nullptr; + } + + virtual RenderMacIOSurfaceTextureHost* AsRenderMacIOSurfaceTextureHost() { + return nullptr; + } + + virtual RenderAndroidHardwareBufferTextureHost* + AsRenderAndroidHardwareBufferTextureHost() { + return nullptr; + } + + virtual RenderAndroidSurfaceTextureHost* AsRenderAndroidSurfaceTextureHost() { + return nullptr; + } + + virtual RenderTextureHostSWGL* AsRenderTextureHostSWGL() { return nullptr; } + + virtual RenderDcompSurfaceTextureHost* AsRenderDcompSurfaceTextureHost() { + return nullptr; + } + + virtual bool IsWrappingAsyncRemoteTexture() { return false; } + + virtual void Destroy(); + + protected: + virtual ~RenderTextureHost(); + + // Returns the UV coordinates to be used when sampling the texture, in pixels. + // For most implementations these will be (0, 0) and (size.x, size.y), but + // some texture types (such as RenderAndroidSurfaceTextureHost) require an + // additional transform to be applied to the coordinates. + virtual std::pair<gfx::Point, gfx::Point> GetUvCoords( + gfx::IntSize aTextureSize) const; + + bool mIsFromDRMSource; + + friend class RenderTextureHostWrapper; +}; + +} // 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..ad2d960837 --- /dev/null +++ b/gfx/webrender_bindings/RenderTextureHostSWGL.cpp @@ -0,0 +1,231 @@ +/* -*- 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 "mozilla/gfx/Logging.h" +#include "mozilla/layers/TextureHost.h" +#include "RenderThread.h" + +namespace mozilla { +namespace wr { + +bool RenderTextureHostSWGL::UpdatePlanes(RenderCompositor* aCompositor) { + wr_swgl_make_current(mContext); + size_t planeCount = GetPlaneCount(); + bool texInit = false; + if (mPlanes.size() < planeCount) { + mPlanes.reserve(planeCount); + while (mPlanes.size() < planeCount) { + mPlanes.push_back(PlaneInfo(wr_swgl_gen_texture(mContext))); + } + texInit = 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; + } + break; + case gfx::SurfaceFormat::NV12: + switch (colorDepth) { + case gfx::ColorDepth::COLOR_8: + internalFormat = i > 0 ? LOCAL_GL_RG8 : LOCAL_GL_R8; + break; + case gfx::ColorDepth::COLOR_10: + case gfx::ColorDepth::COLOR_12: + case gfx::ColorDepth::COLOR_16: + internalFormat = i > 0 ? LOCAL_GL_RG16 : LOCAL_GL_R16; + break; + } + break; + case gfx::SurfaceFormat::P010: + MOZ_ASSERT(colorDepth == gfx::ColorDepth::COLOR_10); + internalFormat = i > 0 ? LOCAL_GL_RG16 : LOCAL_GL_R16; + 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 (texInit) { + // Initialize the mip filters to linear by default. + for (const auto& plane : mPlanes) { + wr_swgl_set_texture_parameter(mContext, plane.mTexture, + LOCAL_GL_TEXTURE_MIN_FILTER, + LOCAL_GL_LINEAR); + wr_swgl_set_texture_parameter(mContext, plane.mTexture, + LOCAL_GL_TEXTURE_MAG_FILTER, + LOCAL_GL_LINEAR); + } + } + 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) { + if (!SetContext(aContext)) { + return InvalidToWrExternalImage(); + } + if (!mLocked) { + if (!UpdatePlanes(aCompositor)) { + return InvalidToWrExternalImage(); + } + mLocked = true; + } + if (aChannelIndex >= mPlanes.size()) { + return InvalidToWrExternalImage(); + } + const PlaneInfo& plane = mPlanes[aChannelIndex]; + + const auto uvs = GetUvCoords(plane.mSize); + + // Prefer native textures, unless our backend forbids it. + // If the GetUvCoords call above returned anything other than the default, + // for example if this is a RenderAndroidSurfaceTextureHost, then this won't + // be handled correctly in the RawDataToWrExternalImage path. But we shouldn't + // hit this path in practice with a RenderAndroidSurfaceTextureHost. + layers::TextureHost::NativeTexturePolicy policy = + layers::TextureHost::BackendNativeTexturePolicy( + layers::WebRenderBackend::SOFTWARE, plane.mSize); + return policy == layers::TextureHost::NativeTexturePolicy::FORBID + ? RawDataToWrExternalImage((uint8_t*)plane.mData, + plane.mStride * plane.mSize.height) + : NativeTextureToWrExternalImage(plane.mTexture, uvs.first.x, + uvs.first.y, uvs.second.x, + uvs.second.y); +} + +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::SWGLCompositeSurfaceInfo* aInfo) { + if (!SetContext(aContext)) { + return false; + } + if (!mLocked) { + if (!UpdatePlanes(nullptr)) { + 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::P010: + case gfx::SurfaceFormat::YUV422: { + aInfo->yuv_planes = mPlanes.size(); + auto colorSpace = GetYUVColorSpace(); + aInfo->color_space = ToWrYuvRangedColorSpace(colorSpace); + auto colorDepth = GetColorDepth(); + aInfo->color_depth = ToWrColorDepth(colorDepth); + break; + } + case gfx::SurfaceFormat::B8G8R8A8: + case gfx::SurfaceFormat::B8G8R8X8: + break; + default: + gfxCriticalNote << "Unhandled external image format: " << GetFormat(); + 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::SWGLCompositeSurfaceInfo* 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..b5d1e92056 --- /dev/null +++ b/gfx/webrender_bindings/RenderTextureHostSWGL.h @@ -0,0 +1,80 @@ +/* -*- 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) override; + + void UnlockSWGL() override; + + RenderTextureHostSWGL* AsRenderTextureHostSWGL() override { return this; } + + virtual size_t GetPlaneCount() const = 0; + + virtual gfx::ColorDepth GetColorDepth() const { + return gfx::ColorDepth::COLOR_8; + } + + 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::SWGLCompositeSurfaceInfo* aInfo); + + size_t BytesFromPlanes() { + NS_ASSERTION(mPlanes.size(), "Can't compute bytes without any planes"); + 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); + + 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..7cc03fb978 --- /dev/null +++ b/gfx/webrender_bindings/RenderTextureHostWrapper.cpp @@ -0,0 +1,262 @@ +/* -*- 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/layers/RemoteTextureMap.h" +#include "mozilla/webrender/RenderThread.h" + +namespace mozilla { +namespace wr { + +RenderTextureHostWrapper::RenderTextureHostWrapper( + ExternalImageId aExternalImageId) + : mExternalImageId(aExternalImageId) { + MOZ_COUNT_CTOR_INHERITED(RenderTextureHostWrapper, RenderTextureHost); + EnsureTextureHost(); +} + +RenderTextureHostWrapper::RenderTextureHostWrapper( + const layers::RemoteTextureId aTextureId, + const layers::RemoteTextureOwnerId aOwnerId, const base::ProcessId aForPid) + : mExternalImageId({}), + mTextureId(Some(aTextureId)), + mOwnerId(Some(aOwnerId)), + mForPid(Some(aForPid)) { + MOZ_COUNT_CTOR_INHERITED(RenderTextureHostWrapper, RenderTextureHost); +} + +RenderTextureHostWrapper::~RenderTextureHostWrapper() { + MOZ_COUNT_DTOR_INHERITED(RenderTextureHostWrapper, RenderTextureHost); +} + +void RenderTextureHostWrapper::EnsureTextureHost() const { + MOZ_ASSERT(mTextureId.isNothing()); + + if (mTextureHost) { + return; + } + + mTextureHost = RenderThread::Get()->GetRenderTexture(mExternalImageId); + MOZ_ASSERT(mTextureHost); + if (!mTextureHost) { + gfxCriticalNoteOnce << "Failed to get RenderTextureHost for extId:" + << AsUint64(mExternalImageId); + } +} + +void RenderTextureHostWrapper::EnsureRemoteTexture() const { + MOZ_ASSERT(mTextureId.isSome()); + + if (mTextureHost) { + return; + } + + auto externalImageId = + layers::RemoteTextureMap::Get()->GetExternalImageIdOfRemoteTexture( + *mTextureId, *mOwnerId, *mForPid); + if (externalImageId.isNothing()) { + // This could happen with IPC abnormal shutdown + return; + } + + mTextureHost = RenderThread::Get()->GetRenderTexture(*externalImageId); + MOZ_ASSERT(mTextureHost); + if (!mTextureHost) { + gfxCriticalNoteOnce << "Failed to get RenderTextureHost for extId:" + << AsUint64(*externalImageId); + } +} + +wr::WrExternalImage RenderTextureHostWrapper::Lock(uint8_t aChannelIndex, + gl::GLContext* aGL) { + if (mTextureId.isSome()) { + EnsureRemoteTexture(); + } + + if (!mTextureHost) { + return InvalidToWrExternalImage(); + } + + return mTextureHost->Lock(aChannelIndex, aGL); +} + +void RenderTextureHostWrapper::Unlock() { + if (mTextureHost) { + mTextureHost->Unlock(); + } +} + +std::pair<gfx::Point, gfx::Point> RenderTextureHostWrapper::GetUvCoords( + gfx::IntSize aTextureSize) const { + if (mTextureHost) { + return mTextureHost->GetUvCoords(aTextureSize); + } + return RenderTextureHost::GetUvCoords(aTextureSize); +} + +void RenderTextureHostWrapper::ClearCachedResources() { + if (mTextureHost) { + mTextureHost->ClearCachedResources(); + } +} + +void RenderTextureHostWrapper::PrepareForUse() { + if (!mTextureHost) { + return; + } + mTextureHost->PrepareForUse(); +} + +void RenderTextureHostWrapper::NotifyForUse() { + if (!mTextureHost) { + return; + } + mTextureHost->NotifyForUse(); +} + +void RenderTextureHostWrapper::NotifyNotUsed() { + if (!mTextureHost) { + return; + } + mTextureHost->NotifyNotUsed(); +} + +bool RenderTextureHostWrapper::SyncObjectNeeded() { return false; } + +RenderMacIOSurfaceTextureHost* +RenderTextureHostWrapper::AsRenderMacIOSurfaceTextureHost() { + if (mTextureId.isSome()) { + EnsureRemoteTexture(); + } + if (!mTextureHost) { + return nullptr; + } + return mTextureHost->AsRenderMacIOSurfaceTextureHost(); +} + +RenderDXGITextureHost* RenderTextureHostWrapper::AsRenderDXGITextureHost() { + if (mTextureId.isSome()) { + EnsureRemoteTexture(); + } + if (!mTextureHost) { + return nullptr; + } + return mTextureHost->AsRenderDXGITextureHost(); +} + +RenderDXGIYCbCrTextureHost* +RenderTextureHostWrapper::AsRenderDXGIYCbCrTextureHost() { + if (mTextureId.isSome()) { + EnsureRemoteTexture(); + } + if (!mTextureHost) { + return nullptr; + } + return mTextureHost->AsRenderDXGIYCbCrTextureHost(); +} + +RenderDcompSurfaceTextureHost* +RenderTextureHostWrapper::AsRenderDcompSurfaceTextureHost() { + if (!mTextureHost) { + return nullptr; + } + return mTextureHost->AsRenderDcompSurfaceTextureHost(); +} + +RenderTextureHostSWGL* RenderTextureHostWrapper::AsRenderTextureHostSWGL() { + if (mTextureId.isSome()) { + EnsureRemoteTexture(); + } + if (!mTextureHost) { + return nullptr; + } + return mTextureHost->AsRenderTextureHostSWGL(); +} + +RenderAndroidHardwareBufferTextureHost* +RenderTextureHostWrapper::AsRenderAndroidHardwareBufferTextureHost() { + if (mTextureId.isSome()) { + EnsureRemoteTexture(); + } + if (!mTextureHost) { + return nullptr; + } + return mTextureHost->AsRenderAndroidHardwareBufferTextureHost(); +} + +RenderAndroidSurfaceTextureHost* +RenderTextureHostWrapper::AsRenderAndroidSurfaceTextureHost() { + if (mTextureId.isSome()) { + EnsureRemoteTexture(); + } + if (!mTextureHost) { + return nullptr; + } + return mTextureHost->AsRenderAndroidSurfaceTextureHost(); +} + +RenderTextureHostSWGL* RenderTextureHostWrapper::EnsureRenderTextureHostSWGL() + const { + if (mTextureId.isSome()) { + EnsureRemoteTexture(); + } + if (!mTextureHost) { + return nullptr; + } + return mTextureHost->AsRenderTextureHostSWGL(); +} + +bool RenderTextureHostWrapper::IsWrappingAsyncRemoteTexture() { + return mTextureId.isSome(); +} + +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::COLOR_8; +} + +gfx::YUVRangedColorSpace RenderTextureHostWrapper::GetYUVColorSpace() const { + if (RenderTextureHostSWGL* swglHost = EnsureRenderTextureHostSWGL()) { + return swglHost->GetYUVColorSpace(); + } + return gfx::YUVRangedColorSpace::Default; +} + +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..b08aaf7c19 --- /dev/null +++ b/gfx/webrender_bindings/RenderTextureHostWrapper.h @@ -0,0 +1,88 @@ +/* -*- 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); + RenderTextureHostWrapper(const layers::RemoteTextureId aTextureId, + const layers::RemoteTextureOwnerId aOwnerId, + const base::ProcessId aForPid); + + // RenderTextureHost + wr::WrExternalImage Lock(uint8_t aChannelIndex, gl::GLContext* aGL) override; + void Unlock() override; + void ClearCachedResources() override; + void PrepareForUse() override; + void NotifyForUse() override; + void NotifyNotUsed() override; + bool SyncObjectNeeded() override; + RenderMacIOSurfaceTextureHost* AsRenderMacIOSurfaceTextureHost() override; + RenderDXGITextureHost* AsRenderDXGITextureHost() override; + RenderDXGIYCbCrTextureHost* AsRenderDXGIYCbCrTextureHost() override; + RenderDcompSurfaceTextureHost* AsRenderDcompSurfaceTextureHost() override; + RenderAndroidHardwareBufferTextureHost* + AsRenderAndroidHardwareBufferTextureHost() override; + RenderAndroidSurfaceTextureHost* AsRenderAndroidSurfaceTextureHost() override; + RenderTextureHostSWGL* AsRenderTextureHostSWGL() override; + bool IsWrappingAsyncRemoteTexture() override; + + // RenderTextureHostSWGL + size_t GetPlaneCount() const override; + gfx::SurfaceFormat GetFormat() const override; + gfx::ColorDepth GetColorDepth() const override; + gfx::YUVRangedColorSpace GetYUVColorSpace() const override; + bool MapPlane(RenderCompositor* aCompositor, uint8_t aChannelIndex, + PlaneInfo& aPlaneInfo) override; + void UnmapPlanes() override; + + // This is just a wrapper, so doesn't need to report the + // size of the wrapped object (which reports itself). + size_t Bytes() override { return 0; } + + protected: + // RenderTextureHost + std::pair<gfx::Point, gfx::Point> GetUvCoords( + gfx::IntSize aTextureSize) const override; + + private: + ~RenderTextureHostWrapper() override; + + void EnsureTextureHost() const; + void EnsureRemoteTexture() const; + RenderTextureHostSWGL* EnsureRenderTextureHostSWGL() const; + + ExternalImageId mExternalImageId; + mutable RefPtr<RenderTextureHost> mTextureHost; + + Maybe<layers::RemoteTextureId> mTextureId; + Maybe<layers::RemoteTextureOwnerId> mOwnerId; + Maybe<base::ProcessId> mForPid; +}; + +} // 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..ec47c9a213 --- /dev/null +++ b/gfx/webrender_bindings/RenderThread.cpp @@ -0,0 +1,1648 @@ +/* -*- 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 "nsThread.h" +#include "nsThreadUtils.h" +#include "transport/runnable_utils.h" +#include "mozilla/BackgroundHangMonitor.h" +#include "mozilla/layers/AsyncImagePipelineManager.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/gfx/GPUParent.h" +#include "mozilla/gfx/GPUProcessManager.h" +#include "mozilla/glean/GleanMetrics.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/layers/SurfacePool.h" +#include "mozilla/layers/SynchronousTask.h" +#include "mozilla/PerfStats.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Telemetry.h" +#include "mozilla/webrender/RendererOGL.h" +#include "mozilla/webrender/RenderTextureHost.h" +#include "mozilla/widget/CompositorWidget.h" +#include "OGLShaderProgram.h" + +#ifdef XP_WIN +# include "GLContextEGL.h" +# include "GLLibraryEGL.h" +# include "mozilla/widget/WinCompositorWindowThread.h" +# include "mozilla/gfx/DeviceManagerDx.h" +# include "mozilla/webrender/DCLayerTree.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 "mozilla/WidgetUtilsGtk.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::wr { + +LazyLogModule gRenderThreadLog("RenderThread"); +// Should be called only on RenderThread, since LazyLogModule is not thread safe +#define LOG(...) MOZ_LOG(gRenderThreadLog, LogLevel::Debug, (__VA_ARGS__)) + +static StaticRefPtr<RenderThread> sRenderThread; +static mozilla::BackgroundHangMonitor* sBackgroundHangMonitor; +#ifdef DEBUG +static bool sRenderThreadEverStarted = false; +#endif + +RenderThread::RenderThread(RefPtr<nsIThread> aThread) + : mThread(std::move(aThread)), + mThreadPool(false), + mThreadPoolLP(true), + mSingletonGLIsForHardwareWebRender(true), + mWindowInfos("RenderThread.mWindowInfos"), + mRenderTextureMapLock("RenderThread.mRenderTextureMapLock"), + mHasShutdown(false), + mHandlingDeviceReset(false), + mHandlingWebRenderError(false) {} + +RenderThread::~RenderThread() { MOZ_ASSERT(mRenderTexturesDeferred.empty()); } + +// static +RenderThread* RenderThread::Get() { return sRenderThread; } + +// static +void RenderThread::Start(uint32_t aNamespace) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!sRenderThread); + +#ifdef DEBUG + // Check to ensure nobody will try to ever start us more than once during + // the process' lifetime (in particular after ShutDown). + MOZ_ASSERT(!sRenderThreadEverStarted); + sRenderThreadEverStarted = true; +#endif + + // When the CanvasRenderer thread is disabled, WebGL may be handled on this + // thread, requiring a bigger stack size. See: CanvasManagerParent::Init + // + // This is 4M, which is higher than the default 256K. + // Increased with bug 1753349 to accommodate the `chromium/5359` branch of + // ANGLE, which has large peak stack usage for some pathological shader + // compilations. + // + // Previously increased to 512K to accommodate Mesa in bug 1753340. + // + // Previously increased to 320K to avoid a stack overflow in the + // Intel Vulkan driver initialization in bug 1716120. + // + // Note: we only override it if it's limited already. + uint32_t stackSize = nsIThreadManager::DEFAULT_STACK_SIZE; + if (stackSize && !gfx::gfxVars::SupportsThreadsafeGL()) { + stackSize = std::max(stackSize, 4096U << 10); + } + + RefPtr<nsIThread> thread; + nsresult rv = NS_NewNamedThread( + "Renderer", getter_AddRefs(thread), + NS_NewRunnableFunction( + "Renderer::BackgroundHanSetup", + []() { + sBackgroundHangMonitor = new mozilla::BackgroundHangMonitor( + "Render", + /* Timeout values are powers-of-two to enable us get better + data. 128ms is chosen for transient hangs because 8Hz should + be the minimally acceptable goal for Render + responsiveness (normal goal is 60Hz). */ + 128, + /* 2048ms is chosen for permanent hangs because it's longer than + * most Render hangs seen in the wild, but is short enough + * to not miss getting native hang stacks. */ + 2048); + nsCOMPtr<nsIThread> thread = NS_GetCurrentThread(); + nsThread* nsthread = static_cast<nsThread*>(thread.get()); + nsthread->SetUseHangMonitor(true); + nsthread->SetPriority(nsISupportsPriority::PRIORITY_HIGH); + }), + {.stackSize = stackSize}); + + if (NS_FAILED(rv)) { + gfxCriticalNote << "Failed to create Renderer thread: " + << gfx::hexa((uint32_t)rv); + 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->PostRunnable(runnable.forget()); +} + +// static +void RenderThread::ShutDown() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(sRenderThread); + + { + MutexAutoLock lock(sRenderThread->mRenderTextureMapLock); + sRenderThread->mHasShutdown = true; + } + + RefPtr<Runnable> runnable = WrapRunnable( + RefPtr<RenderThread>(sRenderThread.get()), &RenderThread::ShutDownTask); + sRenderThread->PostRunnable(runnable.forget()); + + // This will empty the thread queue and thus run the above runnable while + // spinning the MT event loop. + nsCOMPtr<nsIThread> oldThread = sRenderThread->GetRenderThread(); + oldThread->Shutdown(); + + layers::SharedSurfacesParent::Shutdown(); + +#ifdef XP_WIN + if (widget::WinCompositorWindowThread::Get()) { + widget::WinCompositorWindowThread::ShutDown(); + } +#endif + + // We null this out only after we finished shutdown to give everbody the + // chance to check for sRenderThread->mHasShutdown. Hopefully everybody + // checks this before using us! + sRenderThread = nullptr; +} + +extern void ClearAllBlobImageResources(); + +void RenderThread::ShutDownTask() { + MOZ_ASSERT(IsInRenderThread()); + LOG("RenderThread::ShutDownTask()"); + + { + // Clear RenderTextureHosts + MutexAutoLock lock(mRenderTextureMapLock); + mRenderTexturesDeferred.clear(); + mRenderTextures.clear(); + mSyncObjectNeededRenderTextures.clear(); + mRenderTextureOps.clear(); + } + + // 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::ShutdownRenderThread(); + +#ifdef XP_WIN + DCLayerTree::Shutdown(); +#endif + + ClearAllBlobImageResources(); + ClearSingletonGL(); + ClearSharedSurfacePool(); +} + +// static +bool RenderThread::IsInRenderThread() { + return sRenderThread && sRenderThread->mThread == NS_GetCurrentThread(); +} + +// static +already_AddRefed<nsIThread> RenderThread::GetRenderThread() { + nsCOMPtr<nsIThread> thread; + if (sRenderThread) { + thread = sRenderThread->mThread; + } + return thread.forget(); +} + +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()) { + // 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()->PostRunnable( + 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()); + LOG("RenderThread::AddRenderer() aWindowId %" PRIx64 "", AsUint64(aWindowId)); + + if (mHasShutdown) { + return; + } + + mRenderers[aWindowId] = std::move(aRenderer); + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::GraphicsNumRenderers, + (unsigned int)mRenderers.size()); + + auto windows = mWindowInfos.Lock(); + windows->emplace(AsUint64(aWindowId), new WindowInfo()); + mWrNotifierEventsQueues.emplace(AsUint64(aWindowId), + new std::queue<WrNotifierEvent>); +} + +void RenderThread::RemoveRenderer(wr::WindowId aWindowId) { + MOZ_ASSERT(IsInRenderThread()); + LOG("RenderThread::RemoveRenderer() aWindowId %" PRIx64 "", + AsUint64(aWindowId)); + + if (mHasShutdown) { + return; + } + + mRenderers.erase(aWindowId); + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::GraphicsNumRenderers, + (unsigned int)mRenderers.size()); + + if (mRenderers.empty()) { + if (mHandlingDeviceReset) { + ClearSingletonGL(); + } + mHandlingDeviceReset = false; + mHandlingWebRenderError = false; + } + + auto windows = mWindowInfos.Lock(); + auto it = windows->find(AsUint64(aWindowId)); + MOZ_ASSERT(it != windows->end()); + windows->erase(it); + + // Defer std::deque<WrNotifierEvent> remove, RemoveRenderer() is called in + // HandleWrNotifierEvents(). + RefPtr<Runnable> runnable = + NS_NewRunnableFunction("RenderThread::RemoveRenderer", [aWindowId]() { + auto* self = RenderThread::Get(); + auto it = self->mWrNotifierEventsQueues.find(AsUint64(aWindowId)); + if (it == self->mWrNotifierEventsQueues.end()) { + return; + } + self->mWrNotifierEventsQueues.erase(it); + }); + RenderThread::Get()->PostRunnable(runnable.forget()); +} + +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() const { + MOZ_ASSERT(IsInRenderThread()); + return mRenderers.size(); +} + +size_t RenderThread::ActiveRendererCount() const { + MOZ_ASSERT(IsInRenderThread()); + size_t num_active = 0; + for (const auto& it : mRenderers) { + if (!it.second->IsPaused()) { + num_active++; + } + } + return num_active; +} + +void RenderThread::WrNotifierEvent_WakeUp(WrWindowId aWindowId, + bool aCompositeNeeded) { + auto windows = mWindowInfos.Lock(); + auto it = windows->find(AsUint64(aWindowId)); + if (it == windows->end()) { + MOZ_ASSERT(false); + return; + } + + WindowInfo* info = it->second.get(); + + info->mPendingWrNotifierEvents.emplace( + WrNotifierEvent::WakeUp(aCompositeNeeded)); + PostWrNotifierEvents(aWindowId, info); +} + +void RenderThread::WrNotifierEvent_NewFrameReady(WrWindowId aWindowId, + bool aCompositeNeeded, + FramePublishId aPublishId) { + auto windows = mWindowInfos.Lock(); + auto it = windows->find(AsUint64(aWindowId)); + if (it == windows->end()) { + MOZ_ASSERT(false); + return; + } + WindowInfo* info = it->second.get(); + + info->mPendingWrNotifierEvents.emplace( + WrNotifierEvent::NewFrameReady(aCompositeNeeded, aPublishId)); + PostWrNotifierEvents(aWindowId, info); +} + +void RenderThread::WrNotifierEvent_ExternalEvent(WrWindowId aWindowId, + size_t aRawEvent) { + UniquePtr<RendererEvent> evt(reinterpret_cast<RendererEvent*>(aRawEvent)); + { + auto windows = mWindowInfos.Lock(); + auto it = windows->find(AsUint64(aWindowId)); + if (it == windows->end()) { + MOZ_ASSERT(false); + return; + } + WindowInfo* info = it->second.get(); + + info->mPendingWrNotifierEvents.emplace( + WrNotifierEvent::ExternalEvent(std::move(evt))); + PostWrNotifierEvents(aWindowId, info); + } +} + +void RenderThread::PostWrNotifierEvents(WrWindowId aWindowId) { + { + auto windows = mWindowInfos.Lock(); + auto it = windows->find(AsUint64(aWindowId)); + if (it == windows->end()) { + MOZ_ASSERT(false); + return; + } + WindowInfo* info = it->second.get(); + PostWrNotifierEvents(aWindowId, info); + } +} + +void RenderThread::PostWrNotifierEvents(WrWindowId aWindowId, + WindowInfo* aInfo) { + // Runnable has already been triggered. + if (aInfo->mWrNotifierEventsRunnable) { + return; + } + + // Runnable has not been triggered yet. + RefPtr<nsIRunnable> runnable = NewRunnableMethod<WrWindowId>( + "RenderThread::HandleWrNotifierEvents", this, + &RenderThread::HandleWrNotifierEvents, aWindowId); + aInfo->mWrNotifierEventsRunnable = runnable; + PostRunnable(runnable.forget()); +} + +void RenderThread::HandleWrNotifierEvents(WrWindowId aWindowId) { + MOZ_ASSERT(IsInRenderThread()); + + auto eventsIt = mWrNotifierEventsQueues.find(AsUint64(aWindowId)); + if (eventsIt == mWrNotifierEventsQueues.end()) { + return; + } + auto* events = eventsIt->second.get(); + + { + auto windows = mWindowInfos.Lock(); + auto infoIt = windows->find(AsUint64(aWindowId)); + if (infoIt == windows->end()) { + MOZ_ASSERT(false); + return; + } + WindowInfo* info = infoIt->second.get(); + info->mWrNotifierEventsRunnable = nullptr; + + if (events->empty() && !info->mPendingWrNotifierEvents.empty()) { + events->swap(info->mPendingWrNotifierEvents); + } + } + + bool handleNext = true; + + while (!events->empty() && handleNext) { + auto& front = events->front(); + switch (front.mTag) { + case WrNotifierEvent::Tag::WakeUp: + WrNotifierEvent_HandleWakeUp(aWindowId, front.CompositeNeeded()); + handleNext = false; + break; + case WrNotifierEvent::Tag::NewFrameReady: + WrNotifierEvent_HandleNewFrameReady(aWindowId, front.CompositeNeeded(), + front.PublishId()); + handleNext = false; + break; + case WrNotifierEvent::Tag::ExternalEvent: + WrNotifierEvent_HandleExternalEvent(aWindowId, front.ExternalEvent()); + break; + } + events->pop(); + } + + { + auto windows = mWindowInfos.Lock(); + auto it = windows->find(AsUint64(aWindowId)); + if (it == windows->end()) { + return; + } + WindowInfo* info = it->second.get(); + + if (!events->empty() || !info->mPendingWrNotifierEvents.empty()) { + PostWrNotifierEvents(aWindowId, info); + } + } +} + +void RenderThread::WrNotifierEvent_HandleWakeUp(wr::WindowId aWindowId, + bool aCompositeNeeded) { + MOZ_ASSERT(IsInRenderThread()); + + bool isTrackedFrame = false; + HandleFrameOneDoc(aWindowId, aCompositeNeeded, isTrackedFrame, Nothing()); +} + +void RenderThread::WrNotifierEvent_HandleNewFrameReady( + wr::WindowId aWindowId, bool aCompositeNeeded, FramePublishId aPublishId) { + MOZ_ASSERT(IsInRenderThread()); + + bool isTrackedFrame = true; + HandleFrameOneDoc(aWindowId, aCompositeNeeded, isTrackedFrame, + Some(aPublishId)); +} + +void RenderThread::WrNotifierEvent_HandleExternalEvent( + wr::WindowId aWindowId, UniquePtr<RendererEvent> aRendererEvent) { + MOZ_ASSERT(IsInRenderThread()); + + RunEvent(aWindowId, std::move(aRendererEvent)); +} + +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); +} + +Maybe<layers::FrameRecording> RenderThread::EndRecordingForWindow( + wr::WindowId aWindowId) { + MOZ_ASSERT(IsInRenderThread()); + + RendererOGL* renderer = GetRenderer(aWindowId); + MOZ_ASSERT(renderer); + return renderer->EndRecording(); +} + +void RenderThread::HandleFrameOneDoc(wr::WindowId aWindowId, bool aRender, + bool aTrackedFrame, + Maybe<FramePublishId> aPublishId) { + MOZ_ASSERT(IsInRenderThread()); + + if (mHasShutdown) { + return; + } + + HandleFrameOneDocInner(aWindowId, aRender, aTrackedFrame, aPublishId); + + if (aTrackedFrame) { + DecPendingFrameCount(aWindowId); + } +} + +void RenderThread::HandleFrameOneDocInner(wr::WindowId aWindowId, bool aRender, + bool aTrackedFrame, + Maybe<FramePublishId> aPublishId) { + if (IsDestroyed(aWindowId)) { + return; + } + + if (mHandlingDeviceReset) { + return; + } + + bool render = aRender; + PendingFrameInfo frame; + if (aTrackedFrame) { + // scope lock + auto windows = mWindowInfos.Lock(); + auto it = windows->find(AsUint64(aWindowId)); + if (it == windows->end()) { + MOZ_ASSERT(false); + return; + } + + WindowInfo* info = it->second.get(); + PendingFrameInfo& frameInfo = info->mPendingFrames.front(); + + frame = frameInfo; + } else { + // Just give the frame info default values. + frame = {TimeStamp::Now(), VsyncId()}; + } + + // Sadly this doesn't include the lock, since we don't have the frame there + // yet. + glean::wr::time_to_render_start.AccumulateRawDuration(TimeStamp::Now() - + frame.mStartTime); + + // It is for ensuring that PrepareForUse() is called before + // RenderTextureHost::Lock(). + HandleRenderTextureOps(); + + if (aPublishId.isSome()) { + SetFramePublishId(aWindowId, aPublishId.ref()); + } + + UpdateAndRender(aWindowId, frame.mStartId, frame.mStartTime, render, + /* aReadbackSize */ Nothing(), + /* aReadbackFormat */ Nothing(), + /* aReadbackBuffer */ Nothing()); + + // 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. + TimeDuration compositeDuration = TimeStamp::Now() - frame.mStartTime; + mozilla::Telemetry::Accumulate(mozilla::Telemetry::COMPOSITE_TIME, + uint32_t(compositeDuration.ToMilliseconds())); + PerfStats::RecordMeasurement(PerfStats::Metric::Compositing, + compositeDuration); +} + +void RenderThread::SetClearColor(wr::WindowId aWindowId, wr::ColorF aColor) { + if (mHasShutdown) { + return; + } + + if (!IsInRenderThread()) { + PostRunnable(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, + const nsACString& aUI) { + if (mHasShutdown) { + return; + } + + if (!IsInRenderThread()) { + PostRunnable(NewRunnableMethod<wr::WindowId, nsCString>( + "wr::RenderThread::SetProfilerUI", this, &RenderThread::SetProfilerUI, + aWindowId, nsCString(aUI))); + return; + } + + auto it = mRenderers.find(aWindowId); + if (it != mRenderers.end()) { + it->second->SetProfilerUI(aUI); + } +} + +void RenderThread::PostEvent(wr::WindowId aWindowId, + UniquePtr<RendererEvent> aEvent) { + PostRunnable(NewRunnableMethod<wr::WindowId, UniquePtr<RendererEvent>&&>( + "wr::RenderThread::PostEvent", this, &RenderThread::RunEvent, aWindowId, + std::move(aEvent))); +} + +void RenderThread::RunEvent(wr::WindowId aWindowId, + UniquePtr<RendererEvent> aEvent) { + MOZ_ASSERT(IsInRenderThread()); + + aEvent->Run(*this, aWindowId); + aEvent = nullptr; +} + +static void NotifyDidRender(layers::CompositorBridgeParent* aBridge, + const 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()->RetrySkippedComposite(); + } +} + +static void NotifyDidStartRender(layers::CompositorBridgeParent* aBridge) { + if (aBridge->GetWrBridge()) { + aBridge->GetWrBridge()->RetrySkippedComposite(); + } +} + +void RenderThread::SetFramePublishId(wr::WindowId aWindowId, + FramePublishId aPublishId) { + MOZ_ASSERT(IsInRenderThread()); + + auto it = mRenderers.find(aWindowId); + MOZ_ASSERT(it != mRenderers.end()); + if (it == mRenderers.end()) { + return; + } + auto& renderer = it->second; + + renderer->SetFramePublishId(aPublishId); +} + +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_LABEL("RenderThread::UpdateAndRender", 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; + + std::string markerName = "Composite #" + std::to_string(AsUint64(aWindowId)); + AutoProfilerTracing tracingCompositeMarker( + "Paint", markerName.c_str(), geckoprofiler::category::GRAPHICS, + Some(renderer->GetCompositorBridge()->GetInnerWindowId())); + + if (renderer->IsPaused()) { + aRender = false; + } + LOG("RenderThread::UpdateAndRender() aWindowId %" PRIx64 " aRender %d", + AsUint64(aWindowId), aRender); + + 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->GetLastPipelineInfo(); + + layers::CompositorThread()->Dispatch( + NewRunnableFunction("NotifyDidRenderRunnable", &NotifyDidRender, + renderer->GetCompositorBridge(), info, aStartId, + aStartTime, start, end, aRender, stats)); + + 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. + auto timerId = glean::wr::gpu_wait_time.Start(); + renderer->WaitForGPU(); + glean::wr::gpu_wait_time.StopAndAccumulate(std::move(timerId)); + } 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()); + LOG("RenderThread::Pause() aWindowId %" PRIx64 "", AsUint64(aWindowId)); + + auto it = mRenderers.find(aWindowId); + MOZ_ASSERT(it != mRenderers.end()); + if (it == mRenderers.end()) { + gfxCriticalNote << "RenderThread cannot find renderer for window " + << gfx::hexa(aWindowId) << " to pause."; + return; + } + auto& renderer = it->second; + renderer->Pause(); + + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::GraphicsNumActiveRenderers, + (unsigned int)ActiveRendererCount()); +} + +bool RenderThread::Resume(wr::WindowId aWindowId) { + MOZ_ASSERT(IsInRenderThread()); + LOG("enderThread::Resume() aWindowId %" PRIx64 "", AsUint64(aWindowId)); + + auto it = mRenderers.find(aWindowId); + MOZ_ASSERT(it != mRenderers.end()); + if (it == mRenderers.end()) { + gfxCriticalNote << "RenderThread cannot find renderer for window " + << gfx::hexa(aWindowId) << " to resume."; + return false; + } + auto& renderer = it->second; + bool resumed = renderer->Resume(); + + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::GraphicsNumActiveRenderers, + (unsigned int)ActiveRendererCount()); + + return resumed; +} + +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.get(); + + 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}); +} + +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.get(); + MOZ_RELEASE_ASSERT(info->mPendingFrameBuild >= 1); + info->mPendingFrameBuild--; +} + +void RenderThread::DecPendingFrameCount(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.get(); + info->mPendingFrames.pop(); +} + +void RenderThread::RegisterExternalImage( + const wr::ExternalImageId& 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( + const wr::ExternalImageId& 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)); + PostRunnable(NewRunnableMethod( + "RenderThread::DeferredRenderTextureHostDestroy", this, + &RenderThread::DeferredRenderTextureHostDestroy)); + } else { + mRenderTextures.erase(it); + } +} + +void RenderThread::DestroyExternalImagesSyncWait( + const std::vector<wr::ExternalImageId>&& aIds) { + if (!IsInRenderThread()) { + layers::SynchronousTask task("Destroy external images"); + + RefPtr<Runnable> runnable = NS_NewRunnableFunction( + "RenderThread::DestroyExternalImagesSyncWait::Runnable", + [&task, ids = std::move(aIds)]() { + layers::AutoCompleteTask complete(&task); + RenderThread::Get()->DestroyExternalImages(std::move(ids)); + }); + + PostRunnable(runnable.forget()); + task.Wait(); + return; + } + DestroyExternalImages(std::move(aIds)); +} + +void RenderThread::DestroyExternalImages( + const std::vector<wr::ExternalImageId>&& aIds) { + MOZ_ASSERT(IsInRenderThread()); + + std::vector<RefPtr<RenderTextureHost>> hosts; + { + MutexAutoLock lock(mRenderTextureMapLock); + if (mHasShutdown) { + return; + } + + for (auto& id : aIds) { + auto it = mRenderTextures.find(id); + if (it == mRenderTextures.end()) { + continue; + } + hosts.emplace_back(it->second); + } + } + + for (auto& host : hosts) { + host->Destroy(); + } +} + +void RenderThread::PrepareForUse(const wr::ExternalImageId& aExternalImageId) { + AddRenderTextureOp(RenderTextureOp::PrepareForUse, aExternalImageId); +} + +void RenderThread::NotifyNotUsed(const wr::ExternalImageId& aExternalImageId) { + AddRenderTextureOp(RenderTextureOp::NotifyNotUsed, aExternalImageId); +} + +void RenderThread::NotifyForUse(const wr::ExternalImageId& aExternalImageId) { + AddRenderTextureOp(RenderTextureOp::NotifyForUse, aExternalImageId); +} + +void RenderThread::AddRenderTextureOp( + RenderTextureOp aOp, const wr::ExternalImageId& 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)); + + if (mRenderTextureOpsRunnable) { + // Runnable was already triggered + return; + } + + RefPtr<nsIRunnable> runnable = + NewRunnableMethod("RenderThread::HandleRenderTextureOps", this, + &RenderThread::HandleRenderTextureOps); + mRenderTextureOpsRunnable = runnable; + PostRunnable(runnable.forget()); +} + +void RenderThread::HandleRenderTextureOps() { + MOZ_ASSERT(IsInRenderThread()); + + std::list<std::pair<RenderTextureOp, RefPtr<RenderTextureHost>>> + renderTextureOps; + { + MutexAutoLock lock(mRenderTextureMapLock); + mRenderTextureOps.swap(renderTextureOps); + mRenderTextureOpsRunnable = nullptr; + } + + 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( + const wr::ExternalImageId& 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( + const wr::ExternalImageId& aExternalImageId) { + MutexAutoLock lock(mRenderTextureMapLock); + auto it = mRenderTextures.find(aExternalImageId); + MOZ_ASSERT(it != mRenderTextures.end()); + if (it == mRenderTextures.end()) { + return nullptr; + } + return it->second; +} + +void RenderThread::InitDeviceTask() { + MOZ_ASSERT(IsInRenderThread()); + MOZ_ASSERT(!mSingletonGL); + LOG("RenderThread::InitDeviceTask()"); + + if (gfx::gfxVars::UseSoftwareWebRender()) { + // Ensure we don't instantiate any shared GL context when SW-WR is used. + return; + } + + nsAutoCString err; + CreateSingletonGL(err); + if (gfx::gfxVars::UseWebRenderProgramBinaryDisk()) { + mProgramCache = MakeUnique<WebRenderProgramCache>(ThreadPool().Raw()); + } + // Query the shared GL context to force the + // lazy initialization to happen now. + SingletonGL(); +} + +void RenderThread::PostRunnable(already_AddRefed<nsIRunnable> aRunnable) { + nsCOMPtr<nsIRunnable> runnable = aRunnable; + mThread->Dispatch(runnable.forget()); +} + +#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, GLenum aReason) { + MOZ_ASSERT(IsInRenderThread()); + + // This happens only on simulate device reset. + if (aReason == LOCAL_GL_NO_ERROR) { + if (!mHandlingDeviceReset) { + mHandlingDeviceReset = true; + + MutexAutoLock lock(mRenderTextureMapLock); + mRenderTexturesDeferred.clear(); + for (const auto& entry : mRenderTextures) { + entry.second->ClearCachedResources(); + } + + // All RenderCompositors will be destroyed by the GPUProcessManager in + // either OnRemoteProcessDeviceReset via the GPUChild, or + // OnInProcessDeviceReset here directly. + if (XRE_IsGPUProcess()) { + gfx::GPUParent::GetSingleton()->NotifyDeviceReset(); + } else { + NS_DispatchToMainThread(NS_NewRunnableFunction( + "gfx::GPUProcessManager::OnInProcessDeviceReset", []() -> void { + gfx::GPUProcessManager::Get()->OnInProcessDeviceReset( + /* aTrackThreshold */ false); + })); + } + } + return; + } + + if (mHandlingDeviceReset) { + return; + } + + mHandlingDeviceReset = true; + +#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(); + } + } + + // All RenderCompositors will be destroyed by the GPUProcessManager in + // either OnRemoteProcessDeviceReset via the GPUChild, or + // OnInProcessDeviceReset here directly. + // On Windows, device will be re-created before sessions re-creation. + 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()) { + PostRunnable(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", 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::SingletonGL() { + nsAutoCString err; + auto* gl = SingletonGL(err); + if (!err.IsEmpty()) { + gfxCriticalNote << err.get(); + } + return gl; +} + +void RenderThread::CreateSingletonGL(nsACString& aError) { + MOZ_ASSERT(IsInRenderThread()); + LOG("RenderThread::CreateSingletonGL()"); + + mSingletonGL = CreateGLContext(aError); + mSingletonGLIsForHardwareWebRender = !gfx::gfxVars::UseSoftwareWebRender(); +} + +gl::GLContext* RenderThread::SingletonGL(nsACString& aError) { + MOZ_ASSERT(IsInRenderThread()); + if (!mSingletonGL) { + CreateSingletonGL(aError); + mShaders = nullptr; + } + if (mSingletonGL && mSingletonGLIsForHardwareWebRender && !mShaders) { + mShaders = MakeUnique<WebRenderShaders>(mSingletonGL, mProgramCache.get()); + } + + return mSingletonGL.get(); +} + +gl::GLContext* RenderThread::SingletonGLForCompositorOGL() { + MOZ_RELEASE_ASSERT(gfx::gfxVars::UseSoftwareWebRender()); + + if (mSingletonGLIsForHardwareWebRender) { + // Clear singleton GL, since GLContext is for hardware WebRender. + ClearSingletonGL(); + } + return SingletonGL(); +} + +void RenderThread::ClearSingletonGL() { + MOZ_ASSERT(IsInRenderThread()); + LOG("RenderThread::ClearSingletonGL()"); + + if (mSurfacePool) { + mSurfacePool->DestroyGLResourcesForContext(mSingletonGL); + } + if (mProgramsForCompositorOGL) { + mProgramsForCompositorOGL->Clear(); + mProgramsForCompositorOGL = nullptr; + } + mShaders = nullptr; + mSingletonGL = nullptr; +} + +RefPtr<layers::ShaderProgramOGLsHolder> +RenderThread::GetProgramsForCompositorOGL() { + if (!mSingletonGL) { + return nullptr; + } + + if (!mProgramsForCompositorOGL) { + mProgramsForCompositorOGL = + MakeAndAddRef<layers::ShaderProgramOGLsHolder>(mSingletonGL); + } + return mProgramsForCompositorOGL; +} + +RefPtr<layers::SurfacePool> RenderThread::SharedSurfacePool() { +#if defined(XP_MACOSX) || defined(MOZ_WAYLAND) + 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 mozilla::wr + +#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::GLLibraryEGL::Get(&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; + + if (StaticPrefs::gfx_webrender_prefer_robustness_AtStartup()) { + flags |= 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. + + auto gl = gl::GLContextEGL::CreateWithoutSurface(egl, {flags}, &failureId); + if (!gl || !gl->IsANGLE()) { + aError.Assign(nsPrintfCString("RcANGLE(create GL context failed: %p, %s)", + gl.get(), failureId.get())); + return nullptr; + } + + if (!gl->MakeCurrent()) { + aError.Assign( + nsPrintfCString("RcANGLE(make current GL context failed: %p, %x)", + gl.get(), gl->mEgl->mLib->fGetError())); + return nullptr; + } + + return gl.forget(); +} +#endif + +#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WAYLAND) || defined(MOZ_X11) +static already_AddRefed<gl::GLContext> CreateGLContextEGL() { + // Create GLContext with dummy EGLSurface. + bool forHardwareWebRender = true; + // SW-WR uses CompositorOGL in native compositor. + if (gfx::gfxVars::UseSoftwareWebRender()) { + forHardwareWebRender = false; + } + RefPtr<gl::GLContext> gl = + gl::GLContextProviderEGL::CreateForCompositorWidget( + nullptr, forHardwareWebRender, /* aForceAccelerated */ true); + if (!gl || !gl->MakeCurrent()) { + gfxCriticalNote << "Failed GL context creation for hardware WebRender: " + << forHardwareWebRender; + 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::FORBID_SOFTWARE}, + &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) || defined(MOZ_X11) + if (gfx::gfxVars::UseEGL()) { + 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) { + // wake_up is used for things like propagating debug options or memory + // pressure events, so we are not tracking pending frame counts. + mozilla::wr::RenderThread::Get()->WrNotifierEvent_WakeUp(aWindowId, + aCompositeNeeded); +} + +void wr_notifier_new_frame_ready(mozilla::wr::WrWindowId aWindowId, + bool aCompositeNeeded, + mozilla::wr::FramePublishId aPublishId) { + auto* renderThread = mozilla::wr::RenderThread::Get(); + renderThread->DecPendingFrameBuildCount(aWindowId); + + renderThread->WrNotifierEvent_NewFrameReady(aWindowId, aCompositeNeeded, + aPublishId); +} + +void wr_notifier_external_event(mozilla::wr::WrWindowId aWindowId, + size_t aRawEvent) { + mozilla::wr::RenderThread::Get()->WrNotifierEvent_ExternalEvent( + mozilla::wr::WindowId(aWindowId), aRawEvent); +} + +static void NotifyScheduleRender(mozilla::wr::WrWindowId aWindowId, + wr::RenderReasons aReasons) { + RefPtr<mozilla::layers::CompositorBridgeParent> cbp = mozilla::layers:: + CompositorBridgeParent::GetCompositorBridgeParentFromWindowId(aWindowId); + if (cbp) { + cbp->ScheduleComposition(aReasons); + } +} + +void wr_schedule_render(mozilla::wr::WrWindowId aWindowId, + wr::RenderReasons aReasons) { + layers::CompositorThread()->Dispatch(NewRunnableFunction( + "NotifyScheduleRender", &NotifyScheduleRender, aWindowId, aReasons)); +} + +static void NotifyDidSceneBuild( + mozilla::wr::WrWindowId aWindowId, + const RefPtr<const wr::WebRenderPipelineInfo>& aInfo) { + RefPtr<mozilla::layers::CompositorBridgeParent> cbp = mozilla::layers:: + CompositorBridgeParent::GetCompositorBridgeParentFromWindowId(aWindowId); + if (cbp) { + cbp->NotifyDidSceneBuild(aInfo); + } +} + +void wr_finished_scene_build(mozilla::wr::WrWindowId aWindowId, + mozilla::wr::WrPipelineInfo* aPipelineInfo) { + RefPtr<wr::WebRenderPipelineInfo> info = new wr::WebRenderPipelineInfo(); + info->Raw() = std::move(*aPipelineInfo); + layers::CompositorThread()->Dispatch(NewRunnableFunction( + "NotifyDidSceneBuild", &NotifyDidSceneBuild, aWindowId, info)); +} + +} // extern C diff --git a/gfx/webrender_bindings/RenderThread.h b/gfx/webrender_bindings/RenderThread.h new file mode 100644 index 0000000000..ba4e34b591 --- /dev/null +++ b/gfx/webrender_bindings/RenderThread.h @@ -0,0 +1,489 @@ +/* -*- 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/UniquePtr.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 ShaderProgramOGLsHolder; +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. +/// +/// Callers are not allowed to post tasks to the render thread's event loop +/// directly and must 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(uint32_t aNamespace); + + /// Can only be called from the main thread. + static void ShutDown(); + + /// Can be called from any thread. + static bool IsInRenderThread(); + + /// Can be called from any thread. + static already_AddRefed<nsIThread> GetRenderThread(); + + // 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); + + /// 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, const nsACString& aUI); + + /// Automatically forwarded to the render thread. + void PipelineSizeChanged(wr::WindowId aWindowId, uint64_t aPipelineId, + float aWidth, float aHeight); + + /// Post RendererEvent to the render thread. + void PostEvent(wr::WindowId aWindowId, UniquePtr<RendererEvent> aEvent); + + /// Can only be called from the render thread. + void SetFramePublishId(wr::WindowId aWindowId, FramePublishId aPublishId); + + /// 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(const wr::ExternalImageId& aExternalImageId, + already_AddRefed<RenderTextureHost> aTexture); + + /// Can be called from any thread. + void UnregisterExternalImage(const wr::ExternalImageId& aExternalImageId); + + /// Can be called from any thread. + void DestroyExternalImagesSyncWait( + const std::vector<wr::ExternalImageId>&& aIds); + + /// Can be called from any thread. + void PrepareForUse(const wr::ExternalImageId& aExternalImageId); + + /// Can be called from any thread. + void NotifyNotUsed(const wr::ExternalImageId& aExternalImageId); + + /// Can be called from any thread. + void NotifyForUse(const wr::ExternalImageId& aExternalImageId); + + void HandleRenderTextureOps(); + + /// Can only be called from the render thread. + void UnregisterExternalImageDuringShutdown( + const wr::ExternalImageId& aExternalImageId); + + /// Can only be called from the render thread. + RenderTextureHost* GetRenderTexture( + const wr::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); + void DecPendingFrameCount(wr::WindowId aWindowId); + + // RenderNotifier implementation + void WrNotifierEvent_WakeUp(WrWindowId aWindowId, bool aCompositeNeeded); + void WrNotifierEvent_NewFrameReady(WrWindowId aWindowId, + bool aCompositeNeeded, + FramePublishId aPublishId); + void WrNotifierEvent_ExternalEvent(WrWindowId aWindowId, size_t aRawEvent); + + /// 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* SingletonGL(nsACString& aError); + gl::GLContext* SingletonGL(); + gl::GLContext* SingletonGLForCompositorOGL(); + void ClearSingletonGL(); + RefPtr<layers::SurfacePool> SharedSurfacePool(); + void ClearSharedSurfacePool(); + + RefPtr<layers::ShaderProgramOGLsHolder> GetProgramsForCompositorOGL(); + + /// Can only be called from the render thread. + void HandleDeviceReset(const char* aWhere, 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() const; + size_t ActiveRendererCount() const; + + void BeginRecordingForWindow(wr::WindowId aWindowId, + const TimeStamp& aRecordingStart, + wr::PipelineId aRootPipelineId); + + Maybe<layers::FrameRecording> EndRecordingForWindow(wr::WindowId aWindowId); + + static void MaybeEnableGLDebugMessage(gl::GLContext* aGLContext); + + private: + enum class RenderTextureOp { + PrepareForUse, + NotifyForUse, + NotifyNotUsed, + }; + class WrNotifierEvent { + public: + enum class Tag { + WakeUp, + NewFrameReady, + ExternalEvent, + }; + const Tag mTag; + + private: + WrNotifierEvent(const Tag aTag, const bool aCompositeNeeded) + : mTag(aTag), mCompositeNeeded(aCompositeNeeded) { + MOZ_ASSERT(mTag == Tag::WakeUp); + } + WrNotifierEvent(const Tag aTag, bool aCompositeNeeded, + FramePublishId aPublishId) + : mTag(aTag), + mCompositeNeeded(aCompositeNeeded), + mPublishId(aPublishId) { + MOZ_ASSERT(mTag == Tag::NewFrameReady); + } + WrNotifierEvent(const Tag aTag, UniquePtr<RendererEvent>&& aRendererEvent) + : mTag(aTag), mRendererEvent(std::move(aRendererEvent)) { + MOZ_ASSERT(mTag == Tag::ExternalEvent); + } + + const bool mCompositeNeeded = false; + const FramePublishId mPublishId = FramePublishId::INVALID; + UniquePtr<RendererEvent> mRendererEvent; + + public: + static WrNotifierEvent WakeUp(const bool aCompositeNeeded) { + return WrNotifierEvent(Tag::WakeUp, aCompositeNeeded); + } + + static WrNotifierEvent NewFrameReady(const bool aCompositeNeeded, + const FramePublishId aPublishId) { + return WrNotifierEvent(Tag::NewFrameReady, aCompositeNeeded, aPublishId); + } + + static WrNotifierEvent ExternalEvent( + UniquePtr<RendererEvent>&& aRendererEvent) { + return WrNotifierEvent(Tag::ExternalEvent, std::move(aRendererEvent)); + } + + bool CompositeNeeded() { + if (mTag == Tag::WakeUp || mTag == Tag::NewFrameReady) { + return mCompositeNeeded; + } + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + return false; + } + FramePublishId PublishId() { + if (mTag == Tag::NewFrameReady) { + return mPublishId; + } + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + return FramePublishId::INVALID; + } + UniquePtr<RendererEvent> ExternalEvent() { + if (mTag == Tag::ExternalEvent) { + MOZ_ASSERT(mRendererEvent); + return std::move(mRendererEvent); + } + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + return nullptr; + } + }; + + explicit RenderThread(RefPtr<nsIThread> aThread); + + void HandleFrameOneDocInner(wr::WindowId aWindowId, bool aRender, + bool aTrackedFrame, + Maybe<FramePublishId> aPublishId); + + void DeferredRenderTextureHostDestroy(); + void ShutDownTask(); + void InitDeviceTask(); + void HandleFrameOneDoc(wr::WindowId aWindowId, bool aRender, + bool aTrackedFrame, Maybe<FramePublishId> aPublishId); + void RunEvent(wr::WindowId aWindowId, UniquePtr<RendererEvent> aEvent); + void PostRunnable(already_AddRefed<nsIRunnable> aRunnable); + + void DoAccumulateMemoryReport(MemoryReport, + const RefPtr<MemoryReportPromise::Private>&); + + void AddRenderTextureOp(RenderTextureOp aOp, + const wr::ExternalImageId& aExternalImageId); + + void CreateSingletonGL(nsACString& aError); + + void DestroyExternalImages(const std::vector<wr::ExternalImageId>&& aIds); + + struct WindowInfo; + + void PostWrNotifierEvents(WrWindowId aWindowId); + void PostWrNotifierEvents(WrWindowId aWindowId, WindowInfo* aInfo); + void HandleWrNotifierEvents(WrWindowId aWindowId); + void WrNotifierEvent_HandleWakeUp(wr::WindowId aWindowId, + bool aCompositeNeeded); + void WrNotifierEvent_HandleNewFrameReady(wr::WindowId aWindowId, + bool aCompositeNeeded, + FramePublishId aPublishId); + void WrNotifierEvent_HandleExternalEvent( + wr::WindowId aWindowId, UniquePtr<RendererEvent> aRendererEvent); + + ~RenderThread(); + + RefPtr<nsIThread> const mThread; + + WebRenderThreadPool mThreadPool; + WebRenderThreadPool mThreadPoolLP; + + UniquePtr<WebRenderProgramCache> mProgramCache; + UniquePtr<WebRenderShaders> mShaders; + RefPtr<layers::ShaderProgramOGLsHolder> mProgramsForCompositorOGL; + + // An optional shared GLContext to be used for all + // windows. + RefPtr<gl::GLContext> mSingletonGL; + bool mSingletonGLIsForHardwareWebRender; + + RefPtr<layers::SurfacePool> mSurfacePool; + + std::map<wr::WindowId, UniquePtr<RendererOGL>> mRenderers; + + struct PendingFrameInfo { + TimeStamp mStartTime; + VsyncId mStartId; + }; + + struct WindowInfo { + int64_t PendingCount() { return mPendingFrames.size(); } + std::queue<PendingFrameInfo> mPendingFrames; + uint8_t mPendingFrameBuild = 0; + bool mIsDestroyed = false; + RefPtr<nsIRunnable> mWrNotifierEventsRunnable; + std::queue<WrNotifierEvent> mPendingWrNotifierEvents; + }; + + DataMutex<std::unordered_map<uint64_t, UniquePtr<WindowInfo>>> mWindowInfos; + + std::unordered_map<uint64_t, UniquePtr<std::queue<WrNotifierEvent>>> + mWrNotifierEventsQueues; + + struct ExternalImageIdHashFn { + std::size_t operator()(const wr::ExternalImageId& aId) const { + return HashGeneric(wr::AsUint64(aId)); + } + }; + + Mutex mRenderTextureMapLock MOZ_UNANNOTATED; + std::unordered_map<wr::ExternalImageId, RefPtr<RenderTextureHost>, + ExternalImageIdHashFn> + mRenderTextures; + std::unordered_map<wr::ExternalImageId, RefPtr<RenderTextureHost>, + ExternalImageIdHashFn> + 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; + + RefPtr<nsIRunnable> mRenderTextureOpsRunnable; + + 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..3c4d13e376 --- /dev/null +++ b/gfx/webrender_bindings/RendererOGL.cpp @@ -0,0 +1,430 @@ +/* -*- 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) { + 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); + } else if (auto* swgl = renderer->swgl()) { + return texture->LockSWGL(aChannelIndex, swgl, renderer->GetCompositor()); + } 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), + mLastPipelineInfo(new WebRenderPipelineInfo) { + 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::SetFramePublishId(FramePublishId aPublishId) { + wr_renderer_set_target_frame_publish_id(mRenderer, aPublishId); +} + +void RendererOGL::Update() { + mCompositor->Update(); + if (mCompositor->MakeCurrent()) { + wr_renderer_update(mRenderer); + FlushPipelineInfo(); + } +} + +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; + bool rendered = wr_renderer_render(mRenderer, size.width, size.height, + bufferAge, aOutStats, &dirtyRects); + FlushPipelineInfo(); + if (!rendered) { + 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()); + } + } + + // Frame recording must happen before EndFrame, as we must ensure we read the + // contents of the back buffer before any calls to SwapBuffers which might + // invalidate it. + MaybeRecordFrame(mLastPipelineInfo); + + 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, 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; +} + +Maybe<layers::FrameRecording> RendererOGL::EndRecording() { + if (!mCompositionRecorder) { + MOZ_DIAGNOSTIC_ASSERT( + false, "Attempted to get frames from a window that was not recording."); + return Nothing(); + } + + auto maybeRecording = mCompositionRecorder->GetRecording(); + + wr_renderer_release_composition_recorder_structures(mRenderer); + + mCompositor->MaybeRequestAllowFrameRecording(false); + mCompositionRecorder = nullptr; + + return maybeRecording; +} + +void RendererOGL::FlushPipelineInfo() { + RefPtr<WebRenderPipelineInfo> info = new WebRenderPipelineInfo; + wr_renderer_flush_pipeline_info(mRenderer, &info->Raw()); + mLastPipelineInfo = info; +} + +RenderTextureHost* RendererOGL::GetRenderTexture( + wr::ExternalImageId aExternalImageId) { + return mThread->GetRenderTexture(aExternalImageId); +} + +void RendererOGL::AccumulateMemoryReport(MemoryReport* aReport) { + wr_renderer_accumulate_memory_report(GetRenderer(), aReport, swgl()); + + 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 nsACString& aUI) { + wr_renderer_set_profiler_ui(GetRenderer(), (const uint8_t*)aUI.BeginReading(), + 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..bf79f70d16 --- /dev/null +++ b/gfx/webrender_bindings/RendererOGL.h @@ -0,0 +1,176 @@ +/* -*- 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(); + + void SetFramePublishId(FramePublishId aPublishId); + + /// 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); + + Maybe<layers::FrameRecording> EndRecording(); + + /// 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; } + + void FlushPipelineInfo(); + + RefPtr<const WebRenderPipelineInfo> GetLastPipelineInfo() const { + return mLastPipelineInfo; + } + + RenderTextureHost* GetRenderTexture(wr::ExternalImageId aExternalImageId); + + RenderCompositor* GetCompositor() { return mCompositor.get(); } + + void AccumulateMemoryReport(MemoryReport* aReport); + + void SetProfilerUI(const nsACString& 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; + + RefPtr<WebRenderPipelineInfo> mLastPipelineInfo; +}; + +} // 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..1d001d4727 --- /dev/null +++ b/gfx/webrender_bindings/RendererScreenshotGrabber.cpp @@ -0,0 +1,104 @@ +/* -*- 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, + }); +} + +void RendererScreenshotGrabber::ProcessQueue(Renderer* aRenderer) { + for (const auto& item : mQueue) { + mProfilerScreenshots->SubmitScreenshot( + 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..81f11a392d --- /dev/null +++ b/gfx/webrender_bindings/RendererScreenshotGrabber.h @@ -0,0 +1,101 @@ +/* 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; + }; + + /** + * 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..c07ef299d2 --- /dev/null +++ b/gfx/webrender_bindings/WebRenderAPI.cpp @@ -0,0 +1,1796 @@ +/* -*- 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/ipc/ByteBuf.h" +#include "mozilla/webrender/RendererOGL.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/StaticPrefs_webgl.h" +#include "mozilla/ToString.h" +#include "mozilla/webrender/RenderCompositor.h" +#include "mozilla/widget/CompositorWidget.h" +#include "mozilla/layers/SynchronousTask.h" +#include "nsThreadUtils.h" +#include "TextDrawTarget.h" +#include "malloc_decls.h" +#include "GLContext.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) + +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; + } + + compositor->MakeCurrent(); + + *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; + + // Check That if we are not using SWGL, we have at least a GL or GLES 3.0 + // context. + if (gl && !swgl) { + bool versionCheck = + gl->IsAtLeast(gl::ContextProfile::OpenGLCore, 300) || + gl->IsAtLeast(gl::ContextProfile::OpenGLCompatibility, 300) || + gl->IsAtLeast(gl::ContextProfile::OpenGLES, 300); + + if (!versionCheck) { + gfxCriticalNote << "GL context version (" << gl->Version() + << ") insufficent for hardware WebRender"; + + mError->AssignASCII("GL context version insufficient"); + return; + } + } + + if (!wr_window_new( + aWindowId, mSize.width, mSize.height, + mWindowKind == WindowKind::MAIN, supportLowPriorityTransactions, + supportLowPriorityThreadpool, gfx::gfxVars::UseGLSwizzle(), + gfx::gfxVars::UseWebRenderScissoredCacheClears(), swgl, gl, + compositor->SurfaceOriginIsTopLeft(), progCache, shaders, + aRenderThread.ThreadPool().Raw(), + aRenderThread.ThreadPoolLP().Raw(), &WebRenderMallocSizeOf, + &WebRenderMallocEnclosingSizeOf, 0, compositor.get(), + compositor->ShouldUseNativeCompositor(), + compositor->UsePartialPresent(), + compositor->GetMaxPartialPresentRects(), + compositor->ShouldDrawPreviousPartialPresentRegions(), mDocHandle, + &wrRenderer, mMaxTextureSize, &errorMessage, + StaticPrefs::gfx_webrender_enable_gpu_markers_AtStartup(), + panic_on_gl_error, picTileWidth, picTileHeight, + gfx::gfxVars::WebRenderRequiresHardwareDriver(), + StaticPrefs::gfx_webrender_low_quality_pinch_zoom_AtStartup(), + StaticPrefs::gfx_webrender_max_shared_surface_size_AtStartup())) { + // 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(WebRenderAPI* aApi, + bool aUseSceneBuilderThread) + : mUseSceneBuilderThread(aUseSceneBuilderThread), + mApiBackend(aApi->GetBackendType()) { + 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( + Epoch aEpoch, wr::WrPipelineId pipeline_id, + wr::BuiltDisplayListDescriptor dl_descriptor, + wr::Vec<uint8_t>& dl_items_data, wr::Vec<uint8_t>& dl_cache_data, + wr::Vec<uint8_t>& dl_spatial_tree) { + wr_transaction_set_display_list(mTxn, aEpoch, pipeline_id, dl_descriptor, + &dl_items_data.inner, &dl_cache_data.inner, + &dl_spatial_tree.inner); +} + +void TransactionBuilder::ClearDisplayList(Epoch aEpoch, + wr::WrPipelineId aPipelineId) { + wr_transaction_clear_display_list(mTxn, aEpoch, aPipelineId); +} + +void TransactionBuilder::GenerateFrame(const VsyncId& aVsyncId, + wr::RenderReasons aReasons) { + wr_transaction_generate_frame(mTxn, aVsyncId.mId, aReasons); +} + +void TransactionBuilder::InvalidateRenderedFrame(wr::RenderReasons aReasons) { + wr_transaction_invalidate_rendered_frame(mTxn, aReasons); +} + +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.min.x = aDocumentRect.x; + wrDocRect.min.y = aDocumentRect.y; + wrDocRect.max.x = aDocumentRect.x + aDocumentRect.width; + wrDocRect.max.y = aDocumentRect.y + aDocumentRect.height; + wr_transaction_set_document_view(mTxn, &wrDocRect); +} + +TransactionWrapper::TransactionWrapper(Transaction* aTxn) : mTxn(aTxn) {} + +void TransactionWrapper::AppendDynamicProperties( + const nsTArray<wr::WrOpacityProperty>& aOpacityArray, + const nsTArray<wr::WrTransformProperty>& aTransformArray, + const nsTArray<wr::WrColorProperty>& aColorArray) { + wr_transaction_append_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 nsTArray<wr::SampledScrollOffset>& aSampledOffsets) { + wr_transaction_scroll_layer(mTxn, aPipelineId, aScrollId, &aSampledOffsets); +} + +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()->PostEvent(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), + mRendererDestroyed(false) {} + +WebRenderAPI::~WebRenderAPI() { + if (!mRootDocumentApi) { + wr_api_delete_document(mDocHandle); + } + + if (!mRootApi) { + MOZ_RELEASE_ASSERT(mRendererDestroyed); + wr_api_shut_down(mDocHandle); + } + + wr_api_delete(mDocHandle); +} + +void WebRenderAPI::DestroyRenderer() { + MOZ_RELEASE_ASSERT(!mRootApi); + + RenderThread::Get()->SetDestroyed(GetId()); + // Call wr_api_stop_render_backend() before RemoveRenderer. + wr_api_stop_render_backend(mDocHandle); + + layers::SynchronousTask task("Destroy WebRenderAPI"); + auto event = MakeUnique<RemoveRenderer>(&task); + RunOnRenderThread(std::move(event)); + task.Wait(); + + mRendererDestroyed = true; +} + +void WebRenderAPI::UpdateDebugFlags(uint32_t aFlags) { + wr_api_set_debug_flags(mDocHandle, wr::DebugFlags{aFlags}); +} + +void WebRenderAPI::SendTransaction(TransactionBuilder& aTxn) { + if (mRootApi && mRootApi->mRendererDestroyed) { + return; + } + + if (mPendingRemoteTextureInfoList && + !mPendingRemoteTextureInfoList->mList.empty()) { + mPendingWrTransactionEvents.emplace( + WrTransactionEvent::PendingRemoteTextures( + std::move(mPendingRemoteTextureInfoList))); + } + + if (!mPendingWrTransactionEvents.empty()) { + mPendingWrTransactionEvents.emplace(WrTransactionEvent::Transaction( + aTxn.Take(), aTxn.UseSceneBuilderThread())); + HandleWrTransactionEvents(RemoteTextureWaitType::AsyncWait); + } else { + wr_api_send_transaction(mDocHandle, aTxn.Raw(), + aTxn.UseSceneBuilderThread()); + } +} + +layers::RemoteTextureInfoList* WebRenderAPI::GetPendingRemoteTextureInfoList() { + if (!mRootApi) { + // root api does not support async wait RemoteTexture. + return nullptr; + } + + if (!gfx::gfxVars::UseCanvasRenderThread() || + !StaticPrefs::webgl_out_of_process_async_present() || + gfx::gfxVars::WebglOopAsyncPresentForceSync()) { + return nullptr; + } + + // async remote texture is enabled + MOZ_ASSERT(gfx::gfxVars::UseCanvasRenderThread()); + MOZ_ASSERT(StaticPrefs::webgl_out_of_process_async_present()); + MOZ_ASSERT(!gfx::gfxVars::WebglOopAsyncPresentForceSync()); + + if (!mPendingRemoteTextureInfoList) { + mPendingRemoteTextureInfoList = MakeUnique<layers::RemoteTextureInfoList>(); + } + return mPendingRemoteTextureInfoList.get(); +} + +bool WebRenderAPI::CheckIsRemoteTextureReady( + layers::RemoteTextureInfoList* aList) { + MOZ_ASSERT(layers::CompositorThreadHolder::IsInCompositorThread()); + MOZ_ASSERT(aList); + MOZ_ASSERT(gfx::gfxVars::UseCanvasRenderThread()); + MOZ_ASSERT(StaticPrefs::webgl_out_of_process_async_present()); + MOZ_ASSERT(!gfx::gfxVars::WebglOopAsyncPresentForceSync()); + + RefPtr<WebRenderAPI> self = this; + auto callback = [self](const layers::RemoteTextureInfo&) { + RefPtr<nsIRunnable> runnable = NewRunnableMethod<RemoteTextureWaitType>( + "WebRenderAPI::HandleWrTransactionEvents", self, + &WebRenderAPI::HandleWrTransactionEvents, + RemoteTextureWaitType::AsyncWait); + layers::CompositorThread()->Dispatch(runnable.forget()); + }; + + bool isReady = true; + while (!aList->mList.empty() && isReady) { + auto& front = aList->mList.front(); + isReady &= layers::RemoteTextureMap::Get()->CheckRemoteTextureReady( + front, callback); + if (isReady) { + aList->mList.pop(); + } + } + + return isReady; +} + +void WebRenderAPI::WaitRemoteTextureReady( + layers::RemoteTextureInfoList* aList) { + MOZ_ASSERT(layers::CompositorThreadHolder::IsInCompositorThread()); + MOZ_ASSERT(aList); + MOZ_ASSERT(gfx::gfxVars::UseCanvasRenderThread()); + MOZ_ASSERT(StaticPrefs::webgl_out_of_process_async_present()); + MOZ_ASSERT(!gfx::gfxVars::WebglOopAsyncPresentForceSync()); + + while (!aList->mList.empty()) { + auto& front = aList->mList.front(); + layers::RemoteTextureMap::Get()->WaitRemoteTextureReady(front); + aList->mList.pop(); + } +} + +void WebRenderAPI::FlushPendingWrTransactionEventsWithoutWait() { + HandleWrTransactionEvents(RemoteTextureWaitType::FlushWithoutWait); +} + +void WebRenderAPI::FlushPendingWrTransactionEventsWithWait() { + HandleWrTransactionEvents(RemoteTextureWaitType::FlushWithWait); +} + +void WebRenderAPI::HandleWrTransactionEvents(RemoteTextureWaitType aType) { + auto& events = mPendingWrTransactionEvents; + + while (!events.empty()) { + auto& front = events.front(); + switch (front.mTag) { + case WrTransactionEvent::Tag::Transaction: + wr_api_send_transaction(mDocHandle, front.Transaction(), + front.UseSceneBuilderThread()); + break; + case WrTransactionEvent::Tag::PendingRemoteTextures: + bool isReady = true; + if (aType == RemoteTextureWaitType::AsyncWait) { + isReady = CheckIsRemoteTextureReady(front.RemoteTextureInfoList()); + } else if (aType == RemoteTextureWaitType::FlushWithWait) { + WaitRemoteTextureReady(front.RemoteTextureInfoList()); + } else { + MOZ_ASSERT(aType == RemoteTextureWaitType::FlushWithoutWait); + auto* list = front.RemoteTextureInfoList(); + while (!list->mList.empty()) { + auto& front = list->mList.front(); + layers::RemoteTextureMap::Get()->SuppressRemoteTextureReadyCheck( + front.mTextureId, front.mForPid); + list->mList.pop(); + } + } + if (!isReady) { + return; + } + break; + } + events.pop(); + } +} + +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.mHitInfo.deserialize(wrResult.hit_info & 0x0fff); + geckoResult.mSideBits = static_cast<SideBits>(wrResult.hit_info >> 12); + + if (wrResult.animation_id != 0) { + geckoResult.mAnimationId = Some(wrResult.animation_id); + } else { + geckoResult.mAnimationId = Nothing(); + } + 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::SetBatchingLookback(uint32_t aCount) { + wr_api_set_batching_lookback(mDocHandle, aCount); +} + +void WebRenderAPI::SetBool(wr::BoolParameter aKey, bool aValue) { + wr_api_set_bool(mDocHandle, aKey, aValue); +} + +void WebRenderAPI::SetInt(wr::IntParameter aKey, int32_t aValue) { + wr_api_set_int(mDocHandle, aKey, aValue); +} + +void WebRenderAPI::SetClearColor(const gfx::DeviceColor& aColor) { + RenderThread::Get()->SetClearColor(mId, ToColorF(aColor)); +} + +void WebRenderAPI::SetProfilerUI(const nsACString& 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::StartCaptureSequence(const nsACString& aPath, + uint32_t aFlags) { + if (mCaptureSequence) { + wr_api_stop_capture_sequence(mDocHandle); + } + + wr_api_start_capture_sequence(mDocHandle, PromiseFlatCString(aPath).get(), + aFlags); + + mCaptureSequence = true; +} + +void WebRenderAPI::StopCaptureSequence() { + if (mCaptureSequence) { + wr_api_stop_capture_sequence(mDocHandle); + } + + mCaptureSequence = false; +} + +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::EndRecordingPromise> WebRenderAPI::EndRecording() { + class EndRecordingEvent final : public RendererEvent { + public: + explicit EndRecordingEvent() { MOZ_COUNT_CTOR(EndRecordingEvent); } + + MOZ_COUNTED_DTOR(EndRecordingEvent); + + void Run(RenderThread& aRenderThread, WindowId aWindowId) override { + Maybe<layers::FrameRecording> recording = + aRenderThread.EndRecordingForWindow(aWindowId); + + if (recording) { + mPromise.Resolve(recording.extract(), __func__); + } else { + mPromise.Reject(NS_ERROR_UNEXPECTED, __func__); + } + } + + RefPtr<WebRenderAPI::EndRecordingPromise> GetPromise() { + return mPromise.Ensure(__func__); + } + + private: + MozPromiseHolder<WebRenderAPI::EndRecordingPromise> mPromise; + }; + + auto event = MakeUnique<EndRecordingEvent>(); + auto promise = event->GetPromise(); + + RunOnRenderThread(std::move(event)); + return promise; +} + +void TransactionBuilder::Clear() { wr_resource_updates_clear(mTxn); } + +Transaction* TransactionBuilder::Take() { + Transaction* txn = mTxn; + mTxn = wr_transaction_new(mUseSceneBuilderThread); + return txn; +} + +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, + uint16_t aTileSize, + wr::Vec<uint8_t>& aBytes, + const wr::DeviceIntRect& aVisibleRect) { + wr_resource_updates_add_blob_image(mTxn, key, &aDescriptor, aTileSize, + &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, + WebRenderBackend aBackend) + : mCurrentSpaceAndClipChain(wr::RootScrollNodeWithChain()), + mActiveFixedPosTracker(nullptr), + mPipelineId(aId), + mBackend(aBackend), + mDisplayItemCache(nullptr) { + MOZ_COUNT_CTOR(DisplayListBuilder); + mWrState = wr_state_new(aId); + + 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::Begin(layers::DisplayItemCache* aCache) { + wr_api_begin_builder(mWrState); + + mScrollIds.clear(); + mCurrentSpaceAndClipChain = wr::RootScrollNodeWithChain(); + mClipChainLeaf = Nothing(); + mSuspendedSpaceAndClipChain = Nothing(); + mSuspendedClipChainLeaf = Nothing(); + mCachedTextDT = nullptr; + mCachedContext = nullptr; + mActiveFixedPosTracker = nullptr; + mDisplayItemCache = aCache; + mCurrentCacheSlot = Nothing(); + mRemotePipelineIds.Clear(); +} + +void DisplayListBuilder::End(BuiltDisplayList& aOutDisplayList) { + wr_api_end_builder( + mWrState, &aOutDisplayList.dl_desc, &aOutDisplayList.dl_items.inner, + &aOutDisplayList.dl_cache.inner, &aOutDisplayList.dl_spatial_tree.inner); + + mDisplayItemCache = nullptr; +} + +void DisplayListBuilder::End(layers::DisplayListData& aOutTransaction) { + if (mDisplayItemCache && mDisplayItemCache->IsEnabled()) { + wr_dp_set_cache_size(mWrState, mDisplayItemCache->CurrentSize()); + } + + wr::VecU8 dlItems, dlCache, dlSpatialTree; + wr_api_end_builder(mWrState, &aOutTransaction.mDLDesc, &dlItems.inner, + &dlCache.inner, &dlSpatialTree.inner); + aOutTransaction.mDLItems.emplace(dlItems.inner.data, dlItems.inner.length, + dlItems.inner.capacity); + aOutTransaction.mDLCache.emplace(dlCache.inner.data, dlCache.inner.length, + dlCache.inner.capacity); + aOutTransaction.mDLSpatialTree.emplace(dlSpatialTree.inner.data, + dlSpatialTree.inner.length, + dlSpatialTree.inner.capacity); + aOutTransaction.mRemotePipelineIds = mRemotePipelineIds.Clone(); + dlItems.inner.capacity = 0; + dlItems.inner.data = nullptr; + dlCache.inner.capacity = 0; + dlCache.inner.data = nullptr; + dlSpatialTree.inner.capacity = 0; + dlSpatialTree.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!"); + + WRDL_LOG( + "PushStackingContext b=%s t=%s id=0x%" PRIx64 "\n", mWrState, + ToString(aBounds).c_str(), + aParams.mTransformPtr ? ToString(*aParams.mTransformPtr).c_str() : "none", + aParams.animation ? aParams.animation->id : 0); + + auto spatialId = wr_dp_push_stacking_context( + mWrState, aBounds, mCurrentSpaceAndClipChain.space, &aParams, + aParams.mTransformPtr, 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::DefineImageMaskClip( + const wr::ImageMask& aMask, const nsTArray<wr::LayoutPoint>& aPoints, + wr::FillRule aFillRule) { + CancelGroup(); + + WrClipId clipId = wr_dp_define_image_mask_clip_with_parent_clip_chain( + mWrState, mCurrentSpaceAndClipChain.space, aMask, aPoints.Elements(), + aPoints.Length(), aFillRule); + + return clipId; +} + +wr::WrClipId DisplayListBuilder::DefineRoundedRectClip( + Maybe<wr::WrSpatialId> aSpace, const wr::ComplexClipRegion& aComplex) { + CancelGroup(); + + WrClipId clipId; + if (aSpace) { + clipId = wr_dp_define_rounded_rect_clip(mWrState, *aSpace, aComplex); + } else { + clipId = wr_dp_define_rounded_rect_clip( + mWrState, mCurrentSpaceAndClipChain.space, aComplex); + } + + return clipId; +} + +wr::WrClipId DisplayListBuilder::DefineRectClip(Maybe<wr::WrSpatialId> aSpace, + wr::LayoutRect aClipRect) { + CancelGroup(); + + WrClipId clipId; + if (aSpace) { + clipId = wr_dp_define_rect_clip(mWrState, *aSpace, aClipRect); + } else { + clipId = wr_dp_define_rect_clip(mWrState, mCurrentSpaceAndClipChain.space, + 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, wr::SpatialTreeItemKey aKey) { + auto spatialId = wr_dp_define_sticky_frame( + mWrState, mCurrentSpaceAndClipChain.space, aContentRect, aTopMargin, + aRightMargin, aBottomMargin, aLeftMargin, aVerticalBounds, + aHorizontalBounds, aAppliedOffset, aKey); + + 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::WrSpatialId> 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::WrSpatialId DisplayListBuilder::DefineScrollLayer( + const layers::ScrollableLayerGuid::ViewID& aViewId, + const Maybe<wr::WrSpatialId>& aParent, const wr::LayoutRect& aContentRect, + const wr::LayoutRect& aClipRect, const wr::LayoutVector2D& aScrollOffset, + wr::APZScrollGeneration aScrollOffsetGeneration, + wr::HasScrollLinkedEffect aHasScrollLinkedEffect, + wr::SpatialTreeItemKey aKey) { + 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::WrSpatialId defaultParent = mCurrentSpaceAndClipChain.space; + + auto space = wr_dp_define_scroll_layer( + mWrState, aViewId, aParent ? aParent.ptr() : &defaultParent, aContentRect, + aClipRect, aScrollOffset, aScrollOffsetGeneration, aHasScrollLinkedEffect, + aKey); + + WRDL_LOG("DefineScrollLayer id=%" PRIu64 + "/%zu p=%s co=%s cl=%s generation=%s hasScrollLinkedEffect=%s\n", + mWrState, aViewId, space.id, + aParent ? ToString(aParent->id).c_str() : "(nil)", + ToString(aContentRect).c_str(), ToString(aClipRect).c_str(), + ToString(aScrollOffsetGeneration).c_str(), + ToString(aHasScrollLinkedEffect).c_str()); + + mScrollIds[aViewId] = space; + return space; +} + +void DisplayListBuilder::PushRect(const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, + bool aIsBackfaceVisible, + bool aForceAntiAliasing, bool aIsCheckerboard, + 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, + aForceAntiAliasing, aIsCheckerboard, + &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.width() * 0.6f; + float v = aBounds.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, + const 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()) | + (static_cast<uint16_t>(aSideBits) << 12); + + 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::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()); + + auto clipId = DefineRoundedRectClip(Nothing(), aRegion); + auto clipChainId = DefineClipChain({clipId}, true); + auto spaceAndClip = + WrSpaceAndClipChain{mCurrentSpaceAndClipChain.space, clipChainId.id}; + + wr_dp_push_backdrop_filter(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, bool aForceAntiAliasing, + 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, + aForceAntiAliasing, &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::PushP010Image( + 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_P010_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 LayoutDeviceRect& aDevPxBounds, + bool aIsBackfaceVisible, + PipelineId aPipeline, + bool aIgnoreMissingPipeline) { + mRemotePipelineIds.AppendElement(aPipeline); + // If the incoming bounds size has decimals (As it could when zoom is + // involved), and is pushed straight through here, the compositor would end up + // calculating the destination rect to paint the rendered iframe into + // with those decimal values, rounding the result, instead of snapping. This + // can cause the rendered iframe rect and its destination rect to be + // mismatched, resulting in interpolation artifacts. + auto snapped = aDevPxBounds; + auto tl = snapped.TopLeft().Round(); + auto br = snapped.BottomRight().Round(); + + snapped.SizeTo(LayoutDeviceSize(br.x - tl.x, br.y - tl.y)); + + const auto bounds = wr::ToLayoutRect(snapped); + wr_dp_push_iframe(mWrState, bounds, MergeClipLeaf(bounds), 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) { + wr_dp_push_border_gradient( + mWrState, aBounds, MergeClipLeaf(aClip), aIsBackfaceVisible, + &mCurrentSpaceAndClipChain, aWidths, aWidth, aHeight, aFill, aSlice, + aStartPoint, aEndPoint, aStops.Elements(), aStops.Length(), aExtendMode); +} + +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) { + wr_dp_push_border_radial_gradient( + mWrState, aBounds, MergeClipLeaf(aClip), aIsBackfaceVisible, + &mCurrentSpaceAndClipChain, aWidths, aFill, aCenter, aRadius, + aStops.Elements(), aStops.Length(), aExtendMode); +} + +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) { + wr_dp_push_border_conic_gradient( + mWrState, aBounds, MergeClipLeaf(aClip), aIsBackfaceVisible, + &mCurrentSpaceAndClipChain, aWidths, aFill, aCenter, aAngle, + aStops.Elements(), aStops.Length(), aExtendMode); +} + +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(Nothing(), *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(); +} + +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); + if (mCachedTextDT->IsValid()) { + mCachedContext = MakeUnique<gfxContext>(mCachedTextDT, aDeviceOffset); + } + } else { + mCachedTextDT->Reinitialize(aResources, aSc, aManager, aItem, aBounds); + mCachedContext->SetDeviceOffset(aDeviceOffset); + mCachedContext->SetMatrix(gfx::Matrix()); + } + + return mCachedContext.get(); +} + +void DisplayListBuilder::PushInheritedClipChain( + nsDisplayListBuilder* aBuilder, const DisplayItemClipChain* aClipChain) { + if (!aClipChain || mInheritedClipChain == aClipChain) { + return; + } + if (!mInheritedClipChain) { + mInheritedClipChain = aClipChain; + return; + } + + mInheritedClipChain = + aBuilder->CreateClipChainIntersection(mInheritedClipChain, aClipChain); +} + +} // 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..c276b0e687 --- /dev/null +++ b/gfx/webrender_bindings/WebRenderAPI.h @@ -0,0 +1,913 @@ +/* -*- 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 <queue> +#include <stdint.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/RemoteTextureMap.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/UniquePtr.h" +#include "mozilla/VsyncDispatcher.h" +#include "mozilla/webrender/webrender_ffi.h" +#include "mozilla/webrender/WebRenderTypes.h" +#include "nsString.h" +#include "GLTypes.h" +#include "Units.h" + +class gfxContext; + +#undef None + +namespace mozilla { + +class nsDisplayItem; +class nsPaintedDisplayItem; +class nsDisplayTransform; +class nsDisplayListBuilder; +struct DisplayItemClipChain; + +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; +class WebRenderAPI; + +// 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; + Maybe<uint64_t> mAnimationId; +}; + +class TransactionBuilder final { + public: + explicit TransactionBuilder(WebRenderAPI* aApi, + bool aUseSceneBuilderThread = true); + + ~TransactionBuilder(); + + void SetLowPriority(bool aIsLowPriority); + + void UpdateEpoch(PipelineId aPipelineId, Epoch aEpoch); + + void SetRootPipeline(PipelineId aPipelineId); + + void RemovePipeline(PipelineId aPipelineId); + + void SetDisplayList(Epoch aEpoch, wr::WrPipelineId pipeline_id, + wr::BuiltDisplayListDescriptor dl_descriptor, + wr::Vec<uint8_t>& dl_items_data, + wr::Vec<uint8_t>& dl_cache_data, + wr::Vec<uint8_t>& dl_spatial_tree); + + void ClearDisplayList(Epoch aEpoch, wr::WrPipelineId aPipeline); + + void GenerateFrame(const VsyncId& aVsyncId, wr::RenderReasons aReasons); + + void InvalidateRenderedFrame(wr::RenderReasons aReasons); + + void SetDocumentView(const LayoutDeviceIntRect& aDocRect); + + 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, + uint16_t aTileSize, 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(); + + Transaction* Take(); + + bool UseSceneBuilderThread() const { return mUseSceneBuilderThread; } + layers::WebRenderBackend GetBackendType() { return mApiBackend; } + Transaction* Raw() { return mTxn; } + + protected: + bool mUseSceneBuilderThread; + layers::WebRenderBackend mApiBackend; + Transaction* mTxn; +}; + +class TransactionWrapper final { + public: + explicit TransactionWrapper(Transaction* aTxn); + + void AppendDynamicProperties( + 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 nsTArray<wr::SampledScrollOffset>& aSampledOffsets); + 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(); + + void DestroyRenderer(); + + 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 SetBatchingLookback(uint32_t aCount); + void SetBool(wr::BoolParameter, bool value); + void SetInt(wr::IntParameter, int32_t value); + + void SetClearColor(const gfx::DeviceColor& aColor); + void SetProfilerUI(const nsACString& 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 StartCaptureSequence(const nsACString& aPath, uint32_t aFlags); + void StopCaptureSequence(); + + void BeginRecording(const TimeStamp& aRecordingStart, + wr::PipelineId aRootPipelineId); + + typedef MozPromise<layers::FrameRecording, nsresult, true> + EndRecordingPromise; + + RefPtr<EndRecordingPromise> EndRecording(); + + layers::RemoteTextureInfoList* GetPendingRemoteTextureInfoList(); + + void FlushPendingWrTransactionEventsWithoutWait(); + void FlushPendingWrTransactionEventsWithWait(); + + 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); + bool CheckIsRemoteTextureReady(layers::RemoteTextureInfoList* aList); + void WaitRemoteTextureReady(layers::RemoteTextureInfoList* aList); + + enum class RemoteTextureWaitType : uint8_t { + AsyncWait = 0, + FlushWithWait = 1, + FlushWithoutWait = 2 + }; + + void HandleWrTransactionEvents(RemoteTextureWaitType aType); + + class WrTransactionEvent { + public: + enum class Tag { + Transaction, + PendingRemoteTextures, + }; + const Tag mTag; + + struct TransactionWrapper { + TransactionWrapper(wr::Transaction* aTxn, bool aUseSceneBuilderThread) + : mTxn(aTxn), mUseSceneBuilderThread(aUseSceneBuilderThread) {} + + ~TransactionWrapper() { + if (mTxn) { + wr_transaction_delete(mTxn); + } + } + + wr::Transaction* mTxn; + const bool mUseSceneBuilderThread; + }; + + private: + WrTransactionEvent(const Tag aTag, + UniquePtr<TransactionWrapper>&& aTransaction) + : mTag(aTag), mTransaction(std::move(aTransaction)) { + MOZ_ASSERT(mTag == Tag::Transaction); + } + WrTransactionEvent( + const Tag aTag, + UniquePtr<layers::RemoteTextureInfoList>&& aPendingRemoteTextures) + : mTag(aTag), + mPendingRemoteTextures(std::move(aPendingRemoteTextures)) { + MOZ_ASSERT(mTag == Tag::PendingRemoteTextures); + } + + UniquePtr<TransactionWrapper> mTransaction; + UniquePtr<layers::RemoteTextureInfoList> mPendingRemoteTextures; + + public: + static WrTransactionEvent Transaction(wr::Transaction* aTxn, + bool aUseSceneBuilderThread) { + auto transaction = + MakeUnique<TransactionWrapper>(aTxn, aUseSceneBuilderThread); + return WrTransactionEvent(Tag::Transaction, std::move(transaction)); + } + + static WrTransactionEvent PendingRemoteTextures( + UniquePtr<layers::RemoteTextureInfoList>&& aPendingRemoteTextures) { + return WrTransactionEvent(Tag::PendingRemoteTextures, + std::move(aPendingRemoteTextures)); + } + + wr::Transaction* Transaction() { + if (mTag == Tag::Transaction) { + return mTransaction->mTxn; + } + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + return nullptr; + } + + bool UseSceneBuilderThread() { + if (mTag == Tag::Transaction) { + return mTransaction->mUseSceneBuilderThread; + } + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + return true; + } + + layers::RemoteTextureInfoList* RemoteTextureInfoList() { + if (mTag == Tag::PendingRemoteTextures) { + MOZ_ASSERT(mPendingRemoteTextures); + return mPendingRemoteTextures.get(); + } + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + return nullptr; + } + }; + + 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; + bool mRendererDestroyed; + + UniquePtr<layers::RemoteTextureInfoList> mPendingRemoteTextureInfoList; + std::queue<WrTransactionEvent> mPendingWrTransactionEvents; + + // 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) { + MOZ_RELEASE_ASSERT(mApi); + MOZ_RELEASE_ASSERT(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, + false, + false, + false, + 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; + } + + // Fill this in only if this is for the root StackingContextHelper. + nsIFrame* mRootReferenceFrame = nullptr; + nsTArray<wr::FilterOp> mFilters; + nsTArray<wr::WrFilterData> mFilterDatas; + wr::LayoutRect mBounds = wr::ToLayoutRect(LayoutDeviceRect()); + const gfx::Matrix4x4* mBoundTransform = nullptr; + const wr::WrTransformInfo* mTransformPtr = nullptr; + nsDisplayTransform* mDeferredTransformItem = nullptr; + // 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, + layers::WebRenderBackend aBackend); + 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 Begin(layers::DisplayItemCache* aCache = nullptr); + void End(wr::BuiltDisplayList& aOutDisplayList); + void End(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 DefineImageMaskClip(const wr::ImageMask& aMask, + const nsTArray<wr::LayoutPoint>&, + wr::FillRule); + wr::WrClipId DefineRoundedRectClip(Maybe<wr::WrSpatialId> aSpace, + const wr::ComplexClipRegion& aComplex); + wr::WrClipId DefineRectClip(Maybe<wr::WrSpatialId> aSpace, + 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, wr::SpatialTreeItemKey aKey); + + Maybe<wr::WrSpatialId> GetScrollIdForDefinedScrollLayer( + layers::ScrollableLayerGuid::ViewID aViewId) const; + wr::WrSpatialId DefineScrollLayer( + const layers::ScrollableLayerGuid::ViewID& aViewId, + const Maybe<wr::WrSpatialId>& aParent, const wr::LayoutRect& aContentRect, + const wr::LayoutRect& aClipRect, const wr::LayoutVector2D& aScrollOffset, + wr::APZScrollGeneration aScrollOffsetGeneration, + wr::HasScrollLinkedEffect aHasScrollLinkedEffect, + wr::SpatialTreeItemKey aKey); + + void PushRect(const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip, + bool aIsBackfaceVisible, bool aForceAntiAliasing, + bool aIsCheckerboard, 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, + const gfx::CompositorHitTestInfo& aHitInfo, + SideBits aSideBits); + void PushClearRect(const wr::LayoutRect& aBounds); + + 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, bool aForceAntiAliasing, + 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 PushP010Image(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 LayoutDeviceRect& aDevPxBounds, 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); + + 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); + + 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); + + 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; } + layers::WebRenderBackend GetBackendType() const { return mBackend; } + + // 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); + + 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; + } + + // Used for opacity flattening. When we flatten away an opacity item, + // we push the opacity value onto the builder. + // Descendant items should pull the inherited opacity during + // their CreateWebRenderCommands implementation. This can only happen if all + // descendant items reported supporting this functionality, via + // nsDisplayItem::CanApplyOpacity. + float GetInheritedOpacity() { return mInheritedOpacity; } + void SetInheritedOpacity(float aOpacity) { mInheritedOpacity = aOpacity; } + const DisplayItemClipChain* GetInheritedClipChain() { + return mInheritedClipChain; + } + void PushInheritedClipChain(nsDisplayListBuilder* aBuilder, + const DisplayItemClipChain* aClipChain); + void SetInheritedClipChain(const DisplayItemClipChain* aClipChain) { + mInheritedClipChain = aClipChain; + } + + layers::DisplayItemCache* GetDisplayItemCache() { return mDisplayItemCache; } + + // 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::WrSpatialId> + 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; + mozilla::UniquePtr<gfxContext> mCachedContext; + + FixedPosScrollTargetTracker* mActiveFixedPosTracker; + + wr::PipelineId mPipelineId; + layers::WebRenderBackend mBackend; + + nsTArray<wr::PipelineId> mRemotePipelineIds; + + layers::DisplayItemCache* mDisplayItemCache; + Maybe<uint16_t> mCurrentCacheSlot; + float mInheritedOpacity = 1.0f; + const DisplayItemClipChain* mInheritedClipChain = nullptr; + + 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..3fa81d2a81 --- /dev/null +++ b/gfx/webrender_bindings/WebRenderTypes.cpp @@ -0,0 +1,110 @@ +/* -*- 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; + } + 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; +} + +ImageRendering ToImageRendering(StyleImageRendering aImageRendering) { + switch (aImageRendering) { + case StyleImageRendering::Auto: + case StyleImageRendering::Smooth: + case StyleImageRendering::Optimizequality: + return wr::ImageRendering::Auto; + case StyleImageRendering::CrispEdges: + // FIXME(bug 1728831): Historically we've returned Pixelated here, but + // this should arguably pass CrispEdges to WebRender? + // return wr::ImageRendering::CrispEdges; + [[fallthrough]]; + case StyleImageRendering::Optimizespeed: + case StyleImageRendering::Pixelated: + return wr::ImageRendering::Pixelated; + } + return wr::ImageRendering::Auto; +} + +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; +} + +WrSpatialId RootScrollNode() { return wr_root_scroll_node_id(); } + +WrSpaceAndClipChain RootScrollNodeWithChain() { + WrSpaceAndClipChain sacc; + sacc.clip_chain = wr::ROOT_CLIP_CHAIN; + sacc.space = wr_root_scroll_node_id(); + return sacc; +} + +WrSpaceAndClipChain InvalidScrollNodeWithChain() { + WrSpaceAndClipChain sacc; + sacc.clip_chain = std::numeric_limits<uint64_t>::max(); + 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..ff2c64aefa --- /dev/null +++ b/gfx/webrender_bindings/WebRenderTypes.h @@ -0,0 +1,925 @@ +/* -*- 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/ScrollGeneration.h" +#include "Units.h" +#include "nsIWidgetListener.h" + +namespace mozilla { + +enum class StyleBorderStyle : uint8_t; +enum class StyleBorderImageRepeat : uint8_t; +enum class StyleImageRendering : 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 bool WindowSizeSanityCheck(int32_t aWidth, int32_t aHeight) { + if (aWidth < 0 || aWidth > wr::MAX_RENDER_TASK_SIZE || aHeight < 0 || + aHeight > wr::MAX_RENDER_TASK_SIZE) { + return false; + } + return true; +} + +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; + } +} + +// This extra piece of data is used to differentiate when spatial nodes that are +// created by Gecko that have the same mFrame and PerFrameKey. This currently +// only occurs with sticky display list items that are also zoomable, which +// results in Gecko creating both a sticky spatial node, and then a property +// animated reference frame for APZ +enum class SpatialKeyKind : uint32_t { + Transform, + Perspective, + Scroll, + Sticky, + ImagePipeline, + APZ, +}; + +// Construct a unique, persistent spatial key based on the frame tree pointer, +// per-frame key and a spatial key kind. For now, this covers all the ways Gecko +// creates spatial nodes. In future, we may need to be more clever with the +// SpatialKeyKind. +inline wr::SpatialTreeItemKey SpatialKey(uint64_t aFrame, uint32_t aPerFrameKey, + SpatialKeyKind aKind) { + return wr::SpatialTreeItemKey{ + aFrame, uint64_t(aPerFrameKey) | (uint64_t(aKind) << 32)}; +} + +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).valueOr((ImageFormat)0); + 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).valueOr((ImageFormat)0); + 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).valueOr((ImageFormat)0); + 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)); +} + +ImageRendering ToImageRendering(StyleImageRendering); + +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; + case gfx::CompositionOp::OP_ADD: + return MixBlendMode::PlusLighter; + 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.min.x = rect.X(); + r.min.y = rect.Y(); + r.max.x = rect.X() + rect.Width(); + r.max.y = rect.Y() + rect.Height(); + return r; +} + +static inline wr::LayoutRect ToLayoutRect(const gfx::Rect& rect) { + wr::LayoutRect r; + r.min.x = rect.X(); + r.min.y = rect.Y(); + r.max.x = rect.X() + rect.Width(); + r.max.y = rect.Y() + rect.Height(); + return r; +} + +static inline wr::DeviceIntRect ToDeviceIntRect( + const mozilla::ImageIntRect& rect) { + wr::DeviceIntRect r; + r.min.x = rect.X(); + r.min.y = rect.Y(); + r.max.x = rect.X() + rect.Width(); + r.max.y = rect.Y() + 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.min.x = rect.X(); + r.min.y = rect.Y(); + r.max.x = rect.X() + rect.Width(); + r.max.y = rect.Y() + 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.min.x = std::max(aRect.min.x, aOther.min.x); + r.min.y = std::max(aRect.min.y, aOther.min.y); + r.max.x = std::min(aRect.max.x, aOther.max.x); + r.max.y = std::min(aRect.max.y, aOther.max.y); + + if (r.max.x < r.min.x || r.max.y < r.min.y) { + r.max.x = r.min.x; + r.max.y = r.min.y; + } + 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 LayoutDeviceSize& topLeft, const LayoutDeviceSize& topRight, + const LayoutDeviceSize& bottomLeft, const 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::BorderRadius ToBorderRadius( + const gfx::RectCornerRadii& aRadii) { + return ToBorderRadius(LayoutDeviceSize::FromUnknownSize(aRadii[0]), + LayoutDeviceSize::FromUnknownSize(aRadii[1]), + LayoutDeviceSize::FromUnknownSize(aRadii[3]), + LayoutDeviceSize::FromUnknownSize(aRadii[2])); +} + +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_items; + wr::VecU8 dl_cache; + wr::VecU8 dl_spatial_tree; + 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; + } +}; + +WrSpatialId RootScrollNode(); +WrSpaceAndClipChain RootScrollNodeWithChain(); +WrSpaceAndClipChain InvalidScrollNodeWithChain(); + +enum class WebRenderError : int8_t { + INITIALIZE = 0, + MAKE_CURRENT, + RENDER, + NEW_SURFACE, + BEGIN_DRAW, + 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; +} + +// TODO: Use YUVRangedColorSpace instead of assuming ColorRange::LIMITED. +static inline wr::YuvRangedColorSpace ToWrYuvRangedColorSpace( + gfx::YUVRangedColorSpace aFrom) { + switch (aFrom) { + case gfx::YUVRangedColorSpace::BT601_Narrow: + return wr::YuvRangedColorSpace::Rec601Narrow; + case gfx::YUVRangedColorSpace::BT601_Full: + return wr::YuvRangedColorSpace::Rec601Full; + case gfx::YUVRangedColorSpace::BT709_Narrow: + return wr::YuvRangedColorSpace::Rec709Narrow; + case gfx::YUVRangedColorSpace::BT709_Full: + return wr::YuvRangedColorSpace::Rec709Full; + case gfx::YUVRangedColorSpace::BT2020_Narrow: + return wr::YuvRangedColorSpace::Rec2020Narrow; + case gfx::YUVRangedColorSpace::BT2020_Full: + return wr::YuvRangedColorSpace::Rec2020Full; + case gfx::YUVRangedColorSpace::GbrIdentity: + break; + default: + MOZ_ASSERT_UNREACHABLE("Tried to convert invalid YUVColorSpace."); + break; + } + return wr::YuvRangedColorSpace::GbrIdentity; +} + +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; +} + +static inline wr::WindowSizeMode ToWrWindowSizeMode(nsSizeMode aSizeMode) { + switch (aSizeMode) { + case nsSizeMode_Normal: + return wr::WindowSizeMode::Normal; + case nsSizeMode_Minimized: + return wr::WindowSizeMode::Minimized; + case nsSizeMode_Maximized: + return wr::WindowSizeMode::Maximized; + case nsSizeMode_Fullscreen: + return wr::WindowSizeMode::Fullscreen; + default: + MOZ_ASSERT_UNREACHABLE("Tried to convert invalid size mode."); + return wr::WindowSizeMode::Invalid; + } +} + +static inline wr::APZScrollGeneration ToWrAPZScrollGeneration( + const mozilla::APZScrollGeneration& aGeneration) { + return wr::APZScrollGeneration(aGeneration.Raw()); +} + +static inline wr::HasScrollLinkedEffect ToWrHasScrollLinkedEffect( + bool aHasScrollLinkedEffect) { + return aHasScrollLinkedEffect ? wr::HasScrollLinkedEffect::Yes + : wr::HasScrollLinkedEffect::No; +} + +} // 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..c513ebfd9e --- /dev/null +++ b/gfx/webrender_bindings/cbindgen.toml @@ -0,0 +1,55 @@ +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"] +include = ["POLYGON_CLIP_VERTEX_MAX"] + +[parse] +parse_deps = true +extra_bindings = ["webrender_api"] +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" + +[export.body] +"Box2D" = """ + inline T width() const { return max.x - min.x; } + inline T height() const { return max.y - min.y; } +"""
\ No newline at end of file diff --git a/gfx/webrender_bindings/moz.build b/gfx/webrender_bindings/moz.build new file mode 100644 index 0000000000..61e8e4739d --- /dev/null +++ b/gfx/webrender_bindings/moz.build @@ -0,0 +1,131 @@ +# -*- 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", + "RenderCompositor.h", + "RenderCompositorEGL.h", + "RenderCompositorLayersSWGL.h", + "RenderCompositorOGL.h", + "RenderCompositorOGLSWGL.h", + "RenderCompositorSWGL.h", + "RenderEGLImageTextureHost.h", + "RendererOGL.h", + "RendererScreenshotGrabber.h", + "RenderExternalTextureHost.h", + "RenderSharedSurfaceTextureHost.h", + "RenderTextureHost.h", + "RenderTextureHostSWGL.h", + "RenderTextureHostWrapper.h", + "RenderThread.h", + "webrender_ffi.h", + "WebRenderAPI.h", + "WebRenderTypes.h", +] + +UNIFIED_SOURCES += [ + "Moz2DImageRenderer.cpp", + "RenderBufferTextureHost.cpp", + "RenderCompositor.cpp", + "RenderCompositorEGL.cpp", + "RenderCompositorLayersSWGL.cpp", + "RenderCompositorOGL.cpp", + "RenderCompositorOGLSWGL.cpp", + "RenderCompositorSWGL.cpp", + "RenderEGLImageTextureHost.cpp", + "RendererOGL.cpp", + "RendererScreenshotGrabber.cpp", + "RenderExternalTextureHost.cpp", + "RenderSharedSurfaceTextureHost.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", + "RenderDcompSurfaceTextureHost.h", + ] + UNIFIED_SOURCES += [ + "RenderCompositorD3D11SWGL.cpp", + "RenderD3D11TextureHost.cpp", + ] + SOURCES += [ + "DCLayerTree.cpp", + "RenderCompositorANGLE.cpp", + "RenderDcompSurfaceTextureHost.cpp", + ] + +if CONFIG["MOZ_WAYLAND"]: + EXPORTS.mozilla.webrender += [ + "RenderCompositorNative.h", + "RenderDMABUFTextureHost.h", + ] + SOURCES += [ + "RenderCompositorNative.cpp", + "RenderDMABUFTextureHost.cpp", + ] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] in ("android", "gtk"): + 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" + +LOCAL_INCLUDES += [ + "/gfx/cairo/cairo/src", +] +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": + CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"] + +CXXFLAGS += ["-Werror=switch"] 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..53f59500d0 --- /dev/null +++ b/gfx/webrender_bindings/src/bindings.rs @@ -0,0 +1,4015 @@ +/* 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/. */ + +#![allow(clippy::missing_safety_doc)] +#![allow(clippy::not_unsafe_ptr_arg_deref)] + +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 tracy_rs::register_thread_with_profiler; +use webrender::sw_compositor::SwCompositor; +use webrender::{ + api::units::*, api::*, create_webrender_instance, render_api::*, set_profiler_hooks, AsyncPropertySampler, + AsyncScreenshotHandle, Compositor, CompositorCapabilities, CompositorConfig, CompositorSurfaceTransform, + DebugFlags, Device, MappableCompositor, MappedTileInfo, NativeSurfaceId, NativeSurfaceInfo, NativeTileId, + PartialPresentCompositor, PipelineInfo, ProfilerHooks, RecordedFrameHandle, Renderer, RendererStats, + SWGLCompositeSurfaceInfo, SceneBuilderHooks, ShaderPrecacheFlags, Shaders, SharedShaders, TextureCacheConfig, + UploadMethod, WebRenderOptions, WindowVisibility, ONE_TIME_USAGE_HINT, +}; +use wr_malloc_size_of::MallocSizeOfOps; + +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; + +#[inline] +fn clip_chain_id_to_webrender(id: u64, pipeline_id: WrPipelineId) -> ClipChainId { + if id == ROOT_CLIP_CHAIN { + ClipChainId::INVALID + } else { + 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_chain_id: clip_chain_id_to_webrender(self.clip_chain, pipeline_id), + } + } +} + +#[repr(C)] +pub enum WrStackingContextClip { + None, + ClipChain(u64), +} + +impl WrStackingContextClip { + fn to_webrender(&self, pipeline_id: WrPipelineId) -> Option<ClipChainId> { + match *self { + WrStackingContextClip::None => None, + WrStackingContextClip::ClipChain(id) => { + if id == ROOT_CLIP_CHAIN { + None + } else { + Some(ClipChainId(id, 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 moving 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) -> &Arc<dyn ApiHitTester> { + if let Some(ref ht) = self.hit_tester { + return ht; + } + self.hit_tester = Some(self.hit_tester_request.take().unwrap().resolve()); + self.hit_tester.as_ref().unwrap() + } +} + +#[repr(C)] +pub struct WrVecU8 { + data: *mut u8, + length: usize, + capacity: usize, +} + +impl WrVecU8 { + fn into_vec(self) -> Vec<u8> { + unsafe { Vec::from_raw_parts(self.data, self.length, self.capacity) } + } + + // Equivalent to `into_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.into_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> From<&'a WrImageDescriptor> for ImageDescriptor { + fn from(desc: &'a WrImageDescriptor) -> ImageDescriptor { + let mut flags = ImageDescriptorFlags::empty(); + + if desc.opacity == OpacityType::Opaque { + flags |= ImageDescriptorFlags::IS_OPAQUE; + } + + ImageDescriptor { + size: DeviceIntSize::new(desc.width, desc.height), + stride: if desc.stride != 0 { Some(desc.stride) } else { None }, + format: desc.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, + ) -> 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) -> ExternalImage { + let image = unsafe { wr_renderer_lock_external_image(self.external_image_obj, id, channel_index) }; + 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, + key: SpatialTreeItemKey, +} + +/// 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, + pub key: SpatialTreeItemKey, +} + +#[repr(C)] +pub struct WrTransformInfo { + pub transform: LayoutTransform, + pub key: SpatialTreeItemKey, +} + +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()) }; + 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 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, composite_needed: bool, publish_id: FramePublishId); + fn wr_notifier_external_event(window_id: WrWindowId, raw_event: usize); + fn wr_schedule_render(window_id: WrWindowId, reasons: RenderReasons); + // 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, publish_id: FramePublishId) { + unsafe { + wr_notifier_new_frame_ready(self.window_id, composite_needed, publish_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: &std::ffi::CStr) { + 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(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_set_target_frame_publish_id(renderer: &mut Renderer, publish_id: FramePublishId) { + renderer.set_target_frame_publish_id(publish_id); +} + +#[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::from_origin_and_size( + 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, + swgl: *mut c_void, +) { + *report += renderer.report_memory(swgl); +} + +// 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_thread_is_being_profiled() -> bool; +} + +pub fn gecko_profiler_start_marker(name: &str) { + use gecko_profiler::{gecko_profiler_category, MarkerOptions, MarkerTiming, ProfilerTime, Tracing}; + gecko_profiler::add_marker( + name, + gecko_profiler_category!(Graphics), + MarkerOptions { + timing: MarkerTiming::interval_start(ProfilerTime::now()), + ..Default::default() + }, + Tracing("Webrender".to_string()), + ); +} +pub fn gecko_profiler_end_marker(name: &str) { + use gecko_profiler::{gecko_profiler_category, MarkerOptions, MarkerTiming, ProfilerTime, Tracing}; + gecko_profiler::add_marker( + name, + gecko_profiler_category!(Graphics), + MarkerOptions { + timing: MarkerTiming::interval_end(ProfilerTime::now()), + ..Default::default() + }, + Tracing("Webrender".to_string()), + ); +} + +pub fn gecko_profiler_event_marker(name: &str) { + use gecko_profiler::{gecko_profiler_category, Tracing}; + gecko_profiler::add_marker( + name, + gecko_profiler_category!(Graphics), + Default::default(), + Tracing("Webrender".to_string()), + ); +} + +pub fn gecko_profiler_add_text_marker(name: &str, text: &str, microseconds: f64) { + use gecko_profiler::{gecko_profiler_category, MarkerOptions, MarkerTiming, ProfilerTime}; + if !gecko_profiler::can_accept_markers() { + return; + } + + let now = ProfilerTime::now(); + let start = now.clone().subtract_microseconds(microseconds); + gecko_profiler::add_text_marker( + name, + gecko_profiler_category!(Graphics), + MarkerOptions { + timing: MarkerTiming::interval(start, now), + ..Default::default() + }, + text, + ); +} + +/// 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 register_thread(&self, thread_name: &str) { + gecko_profiler::register_thread(thread_name); + } + + fn unregister_thread(&self) { + gecko_profiler::unregister_thread(); + } + + fn begin_marker(&self, label: &str) { + gecko_profiler_start_marker(label); + } + + fn end_marker(&self, label: &str) { + gecko_profiler_end_marker(label); + } + + fn event_marker(&self, label: &str) { + gecko_profiler_event_marker(label); + } + + fn add_text_marker(&self, label: &str, text: &str, duration: Duration) { + let micros = duration.as_micros() as f64; + gecko_profiler_add_text_marker(label, text, 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) { + gecko_profiler_start_marker("SceneBuilding"); + } + + fn pre_scene_swap(&self) { + unsafe { + apz_pre_scene_swap(self.window_id); + } + } + + fn post_scene_swap(&self, _document_ids: &Vec<DocumentId>, info: PipelineInfo) { + let mut info = WrPipelineInfo::new(&info); + unsafe { + 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) } + gecko_profiler_end_marker("SceneBuilding"); + } + + fn post_resource_update(&self, _document_ids: &Vec<DocumentId>) { + unsafe { wr_schedule_render(self.window_id, RenderReasons::POST_RESOURCE_UPDATES_HOOK) } + gecko_profiler_end_marker("SceneBuilding"); + } + + fn post_empty_scene_build(&self) { + gecko_profiler_end_marker("SceneBuilding"); + } + + 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(); + // Reset the pending properties first because omta_sample and apz_sample_transforms + // may be failed to reset them due to null samplers. + transaction.reset_dynamic_properties(); + unsafe { + apz_sample_transforms(self.window_id, generated_frame_id, &mut transaction); + omta_sample(self.window_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 wr_register_thread_local_arena(); +} + +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 4/8. We get diminishing returns + // with high worker counts and extra overhead because of rayon and font + // management. + + // We clamp to 4 high priority threads because contention and memory usage + // make it not worth going higher + let max = if low_priority { 8 } else { 4 }; + let num_threads = num_cpus::get().min(max); + + 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()); + gecko_profiler::register_thread(&name); + }) + .exit_handler(|_idx| { + gecko_profiler::unregister_thread(); + }) + .build(); + + let workers = Arc::new(worker.unwrap()); + + Box::into_raw(Box::new(WrThreadPool(workers))) +} + +#[no_mangle] +pub unsafe extern "C" fn wr_thread_pool_delete(thread_pool: *mut WrThreadPool) { + mem::drop(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) { + mem::drop(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 = pc.map(|cached_programs| Rc::clone(cached_programs.rc_get())); + + Device::new( + gl, + Some(Box::new(MozCrashAnnotator)), + resource_override_path, + use_optimized_shaders, + upload_method, + 512 * 512, + 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_create_backdrop_surface(compositor: *mut c_void, id: NativeSurfaceId, color: ColorF); + 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, + clear_color: ColorF, + 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, caps: *mut CompositorCapabilities); + fn wr_compositor_get_window_visibility(compositor: *mut c_void, caps: *mut WindowVisibility); + 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, + _device: &mut Device, + 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, _device: &mut Device, id: NativeSurfaceId, is_opaque: bool) { + unsafe { + wr_compositor_create_external_surface(self.0, id, is_opaque); + } + } + + fn create_backdrop_surface(&mut self, _device: &mut Device, id: NativeSurfaceId, color: ColorF) { + unsafe { + wr_compositor_create_backdrop_surface(self.0, id, color); + } + } + + fn destroy_surface(&mut self, _device: &mut Device, id: NativeSurfaceId) { + unsafe { + wr_compositor_destroy_surface(self.0, id); + } + } + + fn create_tile(&mut self, _device: &mut Device, id: NativeTileId) { + unsafe { + wr_compositor_create_tile(self.0, id.surface_id, id.x, id.y); + } + } + + fn destroy_tile(&mut self, _device: &mut Device, id: NativeTileId) { + unsafe { + wr_compositor_destroy_tile(self.0, id.surface_id, id.x, id.y); + } + } + + fn attach_external_image(&mut self, _device: &mut Device, id: NativeSurfaceId, external_image: ExternalImageId) { + unsafe { + wr_compositor_attach_external_image(self.0, id, external_image); + } + } + + fn bind( + &mut self, + _device: &mut Device, + 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, _device: &mut Device) { + unsafe { + wr_compositor_unbind(self.0); + } + } + + fn begin_frame(&mut self, _device: &mut Device) { + unsafe { + wr_compositor_begin_frame(self.0); + } + } + + fn add_surface( + &mut self, + _device: &mut Device, + 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, + _device: &mut Device, + clear_color: ColorF, + dirty_rects: &[DeviceIntRect], + opaque_rects: &[DeviceIntRect], + ) { + unsafe { + wr_compositor_start_compositing( + self.0, + clear_color, + dirty_rects.as_ptr(), + dirty_rects.len(), + opaque_rects.as_ptr(), + opaque_rects.len(), + ); + } + } + + fn end_frame(&mut self, _device: &mut Device) { + unsafe { + wr_compositor_end_frame(self.0); + } + } + + fn enable_native_compositor(&mut self, _device: &mut Device, enable: bool) { + unsafe { + wr_compositor_enable_native_compositor(self.0, enable); + } + } + + fn deinit(&mut self, _device: &mut Device) { + unsafe { + wr_compositor_deinit(self.0); + } + } + + fn get_capabilities(&self, _device: &mut Device) -> CompositorCapabilities { + unsafe { + let mut caps: CompositorCapabilities = Default::default(); + wr_compositor_get_capabilities(self.0, &mut caps); + caps + } + } + + fn get_window_visibility(&self, _device: &mut Device) -> WindowVisibility { + unsafe { + let mut visibility: WindowVisibility = Default::default(); + wr_compositor_get_window_visibility(self.0, &mut visibility); + visibility + } + } +} + +extern "C" { + fn wr_swgl_lock_composite_surface( + ctx: *mut c_void, + external_image_id: ExternalImageId, + composite_info: *mut SWGLCompositeSurfaceInfo, + ) -> bool; + fn wr_swgl_unlock_composite_surface(ctx: *mut c_void, external_image_id: ExternalImageId); +} + +impl MappableCompositor for 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. + fn map_tile( + &mut self, + _device: &mut Device, + 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.is_null() && 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. + fn unmap_tile(&mut self, _device: &mut Device) { + unsafe { + wr_compositor_unmap_tile(self.0); + } + } + + fn lock_composite_surface( + &mut self, + _device: &mut Device, + ctx: *mut c_void, + external_image_id: ExternalImageId, + composite_info: *mut SWGLCompositeSurfaceInfo, + ) -> bool { + unsafe { wr_swgl_lock_composite_surface(ctx, external_image_id, composite_info) } + } + fn unlock_composite_surface(&mut self, _device: &mut Device, ctx: *mut c_void, external_image_id: ExternalImageId) { + unsafe { wr_swgl_unlock_composite_surface(ctx, external_image_id) } + } +} + +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()); + } + } +} + +/// 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, + 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, + 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, + reject_software_rasterizer: bool, + low_quality_pinch_zoom: bool, + max_shared_surface_size: i32, +) -> bool { + assert!(unsafe { is_in_render_thread() }); + + // Ensure the WR profiler callbacks are hooked up to the Gecko profiler. + set_profiler_hooks(Some(&PROFILER_HOOKS)); + + let software = !swgl_context.is_null(); + 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 { + let gl = unsafe { + if gl_context.is_null() { + panic!("Native GL context required when not using SWGL!"); + } else if is_glcontext_gles(gl_context) { + gl::GlesFns::load_with(|symbol| get_proc_address(gl_context, symbol)) + } else { + gl::GlFns::load_with(|symbol| get_proc_address(gl_context, symbol)) + } + }; + (gl, 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.is_null() && 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 = program_cache.map(|program_cache| Rc::clone(&program_cache.rc_get())); + + 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 { + compositor: Box::new(SwCompositor::new( + sw_gl.unwrap(), + Box::new(WrCompositor(compositor)), + use_native_compositor, + )), + } + } else if use_native_compositor { + CompositorConfig::Native { + 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 = WebRenderOptions { + enable_aa: true, + 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), + 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_internal_texture_size: Some(8192), // We want to tile if larger than this + clear_color: color, + precache_flags, + namespace_alloc_by_client: true, + // Font namespace must be allocated by the client + shared_font_namespace: Some(next_namespace_id()), + // 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, + // SWGL supports KHR_blend_equation_advanced safely, but we haven't yet + // tested other HW platforms determine if it is safe to allow them. + allow_advanced_blend_equation: software, + surface_origin_is_top_left, + compositor_config, + enable_gpu_markers, + panic_on_gl_error, + picture_tile_size, + texture_cache_config, + reject_software_rasterizer, + low_quality_pinch_zoom, + max_shared_surface_size, + ..Default::default() + }; + + let window_size = DeviceIntSize::new(window_width, window_height); + let notifier = Box::new(CppNotifier { window_id }); + let (renderer, sender) = match create_webrender_instance(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.is_null() { + drop(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() }); + + let hit_tester = dh.ensure_hit_tester().clone(); + + let handle = DocumentHandle { + api: dh.api.create_sender().create_api_by_client(next_namespace_id()), + document_id: dh.document_id, + hit_tester: Some(hit_tester), + 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_stop_render_backend(dh: &mut DocumentHandle) { + dh.api.stop_render_backend(); +} + +#[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 extern "C" fn wr_api_set_bool(dh: &mut DocumentHandle, param_name: BoolParameter, val: bool) { + dh.api.set_parameter(Parameter::Bool(param_name, val)); +} + +#[no_mangle] +pub extern "C" fn wr_api_set_int(dh: &mut DocumentHandle, param_name: IntParameter, val: i32) { + dh.api.set_parameter(Parameter::Int(param_name, val)); +} + +#[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_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, + pipeline_id: WrPipelineId, + dl_descriptor: BuiltDisplayListDescriptor, + dl_items_data: &mut WrVecU8, + dl_cache_data: &mut WrVecU8, + dl_spatial_tree_data: &mut WrVecU8, +) { + let payload = DisplayListPayload { + items_data: dl_items_data.flush_into_vec(), + cache_data: dl_cache_data.flush_into_vec(), + spatial_tree: dl_spatial_tree_data.flush_into_vec(), + }; + + let dl = BuiltDisplayList::from_data(payload, dl_descriptor); + + txn.set_display_list(epoch, (pipeline_id, dl)); +} + +#[no_mangle] +pub extern "C" fn wr_transaction_set_document_view(txn: &mut Transaction, doc_rect: &DeviceIntRect) { + txn.set_document_view(*doc_rect); +} + +#[no_mangle] +pub extern "C" fn wr_transaction_generate_frame(txn: &mut Transaction, id: u64, reasons: RenderReasons) { + txn.generate_frame(id, reasons); +} + +#[no_mangle] +pub extern "C" fn wr_transaction_invalidate_rendered_frame(txn: &mut Transaction, reasons: RenderReasons) { + txn.invalidate_rendered_frame(reasons); +} + +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_append_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, +) { + if opacity_count == 0 && transform_count == 0 && color_count == 0 { + return; + } + + 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.append_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, + sampled_scroll_offsets: &ThinVec<SampledScrollOffset>, +) { + let scroll_id = ExternalScrollId(scroll_id, pipeline_id); + txn.set_scroll_offsets(scroll_id, sampled_scroll_offsets.to_vec()); +} + +#[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, + tile_size: u16, + 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(tile_size) + } 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 mut frame_builder = WebRenderFrameBuilder::new(pipeline_id); + frame_builder.dl_builder.begin(); + + txn.set_display_list(epoch, frame_builder.dl_builder.end()); +} + +#[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(); + NativeFontHandle { + name: String::from_utf8(chars).unwrap(), + } +} + +#[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 struct WrState { + pipeline_id: WrPipelineId, + frame_builder: WebRenderFrameBuilder, +} + +#[no_mangle] +pub extern "C" fn wr_state_new(pipeline_id: WrPipelineId) -> *mut WrState { + assert!(unsafe { !is_in_render_thread() }); + + let state = Box::new(WrState { + pipeline_id, + frame_builder: WebRenderFrameBuilder::new(pipeline_id), + }); + + Box::into_raw(state) +} + +#[no_mangle] +pub extern "C" fn wr_state_delete(state: *mut WrState) { + assert!(unsafe { !is_in_render_thread() }); + + unsafe { + mem::drop(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, +} + +#[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 is_2d_scale_translation: bool, + pub should_snap: bool, + pub paired_with_perspective: bool, + 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, + bounds: LayoutRect, + spatial_id: WrSpatialId, + params: &WrStackingContextParams, + transform: *const WrTransformInfo, + 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 = transform_ref.map(|info| (PropertyBinding::Value(info.transform), info.key)); + + 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 + .map(|info| info.transform) + .unwrap_or_else(LayoutTransform::identity), + ), + anim.key, + )); + }, + _ => 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); + + let mut origin = bounds.min; + + // 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 { + is_2d_scale_translation: params.is_2d_scale_translation, + should_snap: params.should_snap, + paired_with_perspective: params.paired_with_perspective, + }, + WrReferenceFrameKind::Perspective => ReferenceFrameKind::Perspective { scrolling_relative_to }, + }; + wr_spatial_id = state.frame_builder.dl_builder.push_reference_frame( + origin, + wr_spatial_id, + params.transform_style, + transform_binding.0, + reference_frame_kind, + transform_binding.1, + ); + + 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( + origin, + wr_spatial_id, + Some(data.scale_from), + data.vertical_flip, + rotation, + data.key, + ); + + origin = LayoutPoint::zero(); + result.id = wr_spatial_id.0; + assert_ne!(wr_spatial_id.0, 0); + } + + state.frame_builder.dl_builder.push_stacking_context( + 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_image_mask_clip_with_parent_clip_chain( + state: &mut WrState, + space: WrSpatialId, + mask: ImageMask, + points: *const LayoutPoint, + point_count: usize, + fill_rule: FillRule, +) -> WrClipId { + debug_assert!(unsafe { is_in_main_thread() }); + + let c_points = unsafe { make_slice(points, point_count) }; + let points: Vec<LayoutPoint> = c_points.iter().copied().collect(); + + let clip_id = state.frame_builder.dl_builder.define_clip_image_mask( + space.to_webrender(state.pipeline_id), + mask, + &points, + fill_rule, + ); + WrClipId::from_webrender(clip_id) +} + +#[no_mangle] +pub extern "C" fn wr_dp_define_rounded_rect_clip( + state: &mut WrState, + space: WrSpatialId, + complex: ComplexClipRegion, +) -> WrClipId { + debug_assert!(unsafe { is_in_main_thread() }); + + let clip_id = state + .frame_builder + .dl_builder + .define_clip_rounded_rect(space.to_webrender(state.pipeline_id), complex); + WrClipId::from_webrender(clip_id) +} + +#[no_mangle] +pub extern "C" fn wr_dp_define_rect_clip(state: &mut WrState, space: WrSpatialId, clip_rect: LayoutRect) -> WrClipId { + debug_assert!(unsafe { is_in_main_thread() }); + + let clip_id = state + .frame_builder + .dl_builder + .define_clip_rect(space.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, + key: SpatialTreeItemKey, +) -> 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, + key, + ); + + WrSpatialId { id: spatial_id.0 } +} + +#[no_mangle] +pub extern "C" fn wr_dp_define_scroll_layer( + state: &mut WrState, + external_scroll_id: u64, + parent: &WrSpatialId, + content_rect: LayoutRect, + clip_rect: LayoutRect, + scroll_offset: LayoutVector2D, + scroll_offset_generation: APZScrollGeneration, + has_scroll_linked_effect: HasScrollLinkedEffect, + key: SpatialTreeItemKey, +) -> WrSpatialId { + 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, + scroll_offset, + scroll_offset_generation, + has_scroll_linked_effect, + key, + ); + + WrSpatialId::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_chain_id: space_and_clip.clip_chain_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, + force_antialiasing: bool, + is_checkerboard: bool, + parent: &WrSpaceAndClipChain, + color: ColorF, +) { + debug_assert!(unsafe { !is_in_render_thread() }); + + let mut prim_info = common_item_properties_for_rect(state, clip, is_backface_visible, parent); + if force_antialiasing { + prim_info.flags |= PrimitiveFlags::ANTIALISED; + } + if is_checkerboard { + prim_info.flags |= PrimitiveFlags::CHECKERBOARD_BACKGROUND; + } + + 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_backdrop_filter( + state: &mut WrState, + rect: LayoutRect, + clip: LayoutRect, + is_backface_visible: bool, + parent: &WrSpaceAndClipChain, + 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_chain_id: space_and_clip.clip_chain_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_chain_id: space_and_clip.clip_chain_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 clip_rect = clip.intersection(&rect); + if clip_rect.is_none() { + return; + } + let tag = (scroll_id, hit_info); + + let spatial_id = parent.space.to_webrender(state.pipeline_id); + + let clip_chain_id = if parent.clip_chain == ROOT_CLIP_CHAIN { + ClipChainId::INVALID + } else { + ClipChainId(parent.clip_chain, state.pipeline_id) + }; + + state.frame_builder.dl_builder.push_hit_test( + clip_rect.unwrap(), + clip_chain_id, + spatial_id, + prim_flags(is_backface_visible, false), + tag, + ); +} + +#[no_mangle] +pub extern "C" fn wr_dp_push_image( + state: &mut WrState, + bounds: LayoutRect, + clip: LayoutRect, + is_backface_visible: bool, + force_antialiasing: 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 mut flags = prim_flags2( + is_backface_visible, + prefer_compositor_surface, + supports_external_compositing, + ); + + if force_antialiasing { + flags |= PrimitiveFlags::ANTIALISED; + } + + let prim_info = CommonItemProperties { + clip_rect: clip, + clip_chain_id: space_and_clip.clip_chain_id, + spatial_id: space_and_clip.spatial_id, + flags, + }; + + 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_chain_id: space_and_clip.clip_chain_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_chain_id: space_and_clip.clip_chain_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_chain_id: space_and_clip.clip_chain_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 2 planar P010 image. +#[no_mangle] +pub extern "C" fn wr_dp_push_yuv_P010_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_chain_id: space_and_clip.clip_chain_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::P010(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_chain_id: space_and_clip.clip_chain_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_chain_id: space_and_clip.clip_chain_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_chain_id: space_and_clip.clip_chain_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_chain_id: space_and_clip.clip_chain_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, + image_rendering: ImageRendering, + width: i32, + height: i32, + fill: bool, + slice: DeviceIntSideOffsets, + 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, params.image_rendering), + width: params.width, + height: params.height, + slice: params.slice, + fill: params.fill, + 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_chain_id: space_and_clip.clip_chain_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, +) { + 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, + 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_chain_id: space_and_clip.clip_chain_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, +) { + 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.width() as i32, + height: rect.height() as i32, + slice, + fill, + 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_chain_id: space_and_clip.clip_chain_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, +) { + 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.width() as i32, + height: rect.height() as i32, + slice, + fill, + 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_chain_id: space_and_clip.clip_chain_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_chain_id: space_and_clip.clip_chain_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_chain_id: space_and_clip.clip_chain_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_chain_id: space_and_clip.clip_chain_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_chain_id: space_and_clip.clip_chain_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_begin_builder(state: &mut WrState) { + state.frame_builder.dl_builder.begin(); +} + +#[no_mangle] +pub unsafe extern "C" fn wr_api_end_builder( + state: &mut WrState, + dl_descriptor: &mut BuiltDisplayListDescriptor, + dl_items_data: &mut WrVecU8, + dl_cache_data: &mut WrVecU8, + dl_spatial_tree: &mut WrVecU8, +) { + let (_, dl) = state.frame_builder.dl_builder.end(); + let (payload, descriptor) = dl.into_data(); + *dl_items_data = WrVecU8::from_vec(payload.items_data); + *dl_cache_data = WrVecU8::from_vec(payload.cache_data); + *dl_spatial_tree = WrVecU8::from_vec(payload.spatial_tree); + *dl_descriptor = descriptor; +} + +#[repr(C)] +pub struct HitResult { + pipeline_id: WrPipelineId, + scroll_id: u64, + animation_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>) { + let result = dh.ensure_hit_tester().hit_test(point); + for item in &result.items { + out_results.push(HitResult { + pipeline_id: item.pipeline, + scroll_id: item.tag.0, + animation_id: item.animation_id, + 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(self.id, pipeline_id) + } + + fn from_webrender(clip_id: ClipId) -> Self { + WrClipId { id: clip_id.0 } + } +} + +#[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) + } + + fn from_webrender(id: SpatialId) -> Self { + WrSpatialId { id: id.0 } + } +} + +#[no_mangle] +pub unsafe extern "C" fn wr_device_delete(device: *mut Device) { + mem::drop(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 = WebRenderOptions { + 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..9e94302e0f --- /dev/null +++ b/gfx/webrender_bindings/src/lib.rs @@ -0,0 +1,43 @@ +/* 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 gecko_profiler; +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..4c7a32c912 --- /dev/null +++ b/gfx/webrender_bindings/src/moz2d_renderer.rs @@ -0,0 +1,871 @@ +/* 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 gecko_profiler::gecko_profiler_label; +use rayon::prelude::*; +use rayon::ThreadPool; +use webrender::api::units::{BlobDirtyRect, BlobToDeviceTranslation, DeviceIntRect}; +use webrender::api::*; + +use euclid::point2; +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_void; +use std::ptr; +use std::sync::Arc; + +#[cfg(target_os = "windows")] +use dwrote; + +#[cfg(target_os = "macos")] +use core_foundation::string::CFString; +#[cfg(target_os = "macos")] +use core_graphics::font::CGFont; +#[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: DeviceIntRect) { + let mut index = BlobReader::new(blob); + while index.reader.has_more() { + let e = index.read_entry(); + dlog!( + " {:?} {}", + e.bounds, + if dirty_rect.contains_box(&e.bounds) { "*" } 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 rectangle. + fn read_box(&mut self) -> DeviceIntRect { + unsafe { self.read::<DeviceIntRect>() } + } + + /// 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 { min.x, min.y, max.x, max.y } +/// +/// 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: DeviceIntRect, + /// 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: DeviceIntRect, 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.min.x)); + self.index.extend_from_slice(convert_to_bytes(&bounds.min.y)); + self.index.extend_from_slice(convert_to_bytes(&bounds.max.x)); + self.index.extend_from_slice(convert_to_bytes(&bounds.max.y)); + } + + /// 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(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +struct CacheKey { + x1: i32, + y1: i32, + x2: i32, + y2: i32, + cache_order: u32, +} + +impl CacheKey { + pub fn new(bounds: DeviceIntRect, cache_order: u32) -> Self { + CacheKey { + x1: bounds.min.x, + y1: bounds.min.y, + x2: bounds.max.x, + y2: bounds.max.y, + cache_order, + } + } +} + +/// 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<CacheKey, 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: &DeviceIntRect) -> Option<Entry> { + if self.cache.is_empty() { + return None; + } + + let key_to_delete = match self + .cache + .range(( + Included(CacheKey::new(*bounds, 0u32)), + Included(CacheKey::new(*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: &DeviceIntRect, ignore_rect: &DeviceIntRect) -> 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 !ignore_rect.contains_box(&old.bounds) { + self.cache + .insert(CacheKey::new(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: DeviceIntRect, + old_visible_rect: DeviceIntRect, + new_visible_rect: DeviceIntRect, +) -> 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_unchecked(&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_unchecked(&preserved_rect); + if dirty_rect.contains_box(&preserved_bounds) { + 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!(dirty_rect.contains_box(&old.bounds)); + } + + //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 str, +} + +impl GeckoProfilerMarker { + pub fn new(name: &'static str) -> GeckoProfilerMarker { + gecko_profiler_start_marker(name); + GeckoProfilerMarker { name } + } +} + +impl Drop for GeckoProfilerMarker { + fn drop(&mut self) { + gecko_profiler_end_marker(self.name); + } +} + +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. + gecko_profiler_label!(Graphics, Rasterization); + let _marker = GeckoProfilerMarker::new("BlobRasterization"); + + 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.is_empty()); + + 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) { + gecko_profiler_label!(Graphics, Rasterization); + let descriptor = job.descriptor; + let buf_size = (descriptor.rect.area() * 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.is_empty()); + + 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.min.to_vector()).into(); + let rasterized_rect = tx.transform_box(&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 { + rect.cast_unit() + } else { + DeviceIntRect { + min: point2(i32::MIN, i32::MIN), + max: point2(i32::MAX, i32::MAX), + } + }; + command.data = Arc::new(merge_blob_images( + &command.data, + &data, + dirty_rect, + command.visible_rect, + *visible_rect, + )); + 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) { + let font = match CGFont::from_name(&CFString::new(&handle.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") + }, + }; + unsafe { AddNativeFontHandle(key, font.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).unwrap(); + 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..5db61fc8b5 --- /dev/null +++ b/gfx/webrender_bindings/src/program_cache.rs @@ -0,0 +1,355 @@ +/* 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::{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: &Path) -> 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::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::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..b1e7684a69 --- /dev/null +++ b/gfx/webrender_bindings/src/swgl_bindings.rs @@ -0,0 +1,101 @@ +/* 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::Gl; + +use std::os::raw::c_void; + +#[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_resolve_framebuffer(ctx: *mut c_void, fbo: u32) { + swgl::Context::from(ctx).resolve_framebuffer(fbo); +} + +#[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, + ); +} + +#[no_mangle] +#[allow(clippy::many_single_char_names)] +pub extern "C" fn wr_swgl_clear_color_rect( + ctx: *mut c_void, + fbo: u32, + x: i32, + y: i32, + width: i32, + height: i32, + r: f32, + g: f32, + b: f32, + a: f32, +) { + swgl::Context::from(ctx).clear_color_rect(fbo, x, y, width, height, r, g, b, a); +} diff --git a/gfx/webrender_bindings/webrender_ffi.h b/gfx/webrender_bindings/webrender_ffi.h new file mode 100644 index 0000000000..e9c8be1ef4 --- /dev/null +++ b/gfx/webrender_bindings/webrender_ffi.h @@ -0,0 +1,123 @@ +/* -*- 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); + +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_capture); \ + macro(backdrop_render); \ + macro(polyon); + +// 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>; + +} // 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 Solaris define which conflcits with WR color constant, see +// bug 1773491. +#pragma push_macro("TRANSPARENT") +#undef TRANSPARENT + +#include "webrender_ffi_generated.h" + +#pragma pop_macro("TRANSPARENT") + +template struct mozilla::wr::Point2D<int32_t, mozilla::wr::DevicePixel>; +template struct mozilla::wr::Point2D<int, mozilla::wr::WorldPixel>; +template struct mozilla::wr::Point2D<float, mozilla::wr::WorldPixel>; +template struct mozilla::wr::Box2D<int32_t, mozilla::wr::DevicePixel>; +template struct mozilla::wr::Box2D<int, mozilla::wr::LayoutPixel>; + +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 |