summaryrefslogtreecommitdiffstats
path: root/gfx/webrender_bindings/DCLayerTree.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/webrender_bindings/DCLayerTree.cpp')
-rw-r--r--gfx/webrender_bindings/DCLayerTree.cpp1042
1 files changed, 1042 insertions, 0 deletions
diff --git a/gfx/webrender_bindings/DCLayerTree.cpp b/gfx/webrender_bindings/DCLayerTree.cpp
new file mode 100644
index 0000000000..809117a359
--- /dev/null
+++ b/gfx/webrender_bindings/DCLayerTree.cpp
@@ -0,0 +1,1042 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DCLayerTree.h"
+
+#include "GLContext.h"
+#include "GLContextEGL.h"
+#include "mozilla/gfx/DeviceManagerDx.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/webrender/RenderD3D11TextureHost.h"
+#include "mozilla/webrender/RenderTextureHost.h"
+#include "mozilla/webrender/RenderThread.h"
+#include "mozilla/Telemetry.h"
+#include "nsPrintfCString.h"
+
+#undef _WIN32_WINNT
+#define _WIN32_WINNT _WIN32_WINNT_WINBLUE
+#undef NTDDI_VERSION
+#define NTDDI_VERSION NTDDI_WINBLUE
+
+#include <d3d11.h>
+#include <d3d11_1.h>
+#include <dcomp.h>
+#include <dxgi1_2.h>
+
+namespace mozilla {
+namespace wr {
+
+/* static */
+UniquePtr<DCLayerTree> DCLayerTree::Create(gl::GLContext* aGL,
+ EGLConfig aEGLConfig,
+ ID3D11Device* aDevice,
+ ID3D11DeviceContext* aCtx,
+ HWND aHwnd, nsACString& aError) {
+ RefPtr<IDCompositionDevice2> dCompDevice =
+ gfx::DeviceManagerDx::Get()->GetDirectCompositionDevice();
+ if (!dCompDevice) {
+ aError.Assign("DCLayerTree(no device)"_ns);
+ return nullptr;
+ }
+
+ auto layerTree =
+ MakeUnique<DCLayerTree>(aGL, aEGLConfig, aDevice, aCtx, dCompDevice);
+ if (!layerTree->Initialize(aHwnd, aError)) {
+ return nullptr;
+ }
+
+ return layerTree;
+}
+
+DCLayerTree::DCLayerTree(gl::GLContext* aGL, EGLConfig aEGLConfig,
+ ID3D11Device* aDevice, ID3D11DeviceContext* aCtx,
+ IDCompositionDevice2* aCompositionDevice)
+ : mGL(aGL),
+ mEGLConfig(aEGLConfig),
+ mDevice(aDevice),
+ mCtx(aCtx),
+ mCompositionDevice(aCompositionDevice),
+ mVideoOverlaySupported(false),
+ mDebugCounter(false),
+ mDebugVisualRedrawRegions(false),
+ mEGLImage(EGL_NO_IMAGE),
+ mColorRBO(0),
+ mPendingCommit(false) {}
+
+DCLayerTree::~DCLayerTree() { ReleaseNativeCompositorResources(); }
+
+void DCLayerTree::ReleaseNativeCompositorResources() {
+ const auto gl = GetGLContext();
+
+ DestroyEGLSurface();
+
+ // Delete any cached FBO objects
+ for (auto it = mFrameBuffers.begin(); it != mFrameBuffers.end(); ++it) {
+ gl->fDeleteRenderbuffers(1, &it->depthRboId);
+ gl->fDeleteFramebuffers(1, &it->fboId);
+ }
+}
+
+bool DCLayerTree::Initialize(HWND aHwnd, nsACString& aError) {
+ HRESULT hr;
+
+ RefPtr<IDCompositionDesktopDevice> desktopDevice;
+ hr = mCompositionDevice->QueryInterface(
+ (IDCompositionDesktopDevice**)getter_AddRefs(desktopDevice));
+ if (FAILED(hr)) {
+ aError.Assign(nsPrintfCString(
+ "DCLayerTree(get IDCompositionDesktopDevice failed %x)", hr));
+ return false;
+ }
+
+ hr = desktopDevice->CreateTargetForHwnd(aHwnd, TRUE,
+ getter_AddRefs(mCompositionTarget));
+ if (FAILED(hr)) {
+ aError.Assign(nsPrintfCString(
+ "DCLayerTree(create DCompositionTarget failed %x)", hr));
+ return false;
+ }
+
+ hr = mCompositionDevice->CreateVisual(getter_AddRefs(mRootVisual));
+ if (FAILED(hr)) {
+ aError.Assign(nsPrintfCString(
+ "DCLayerTree(create root DCompositionVisual failed %x)", hr));
+ return false;
+ }
+
+ hr =
+ mCompositionDevice->CreateVisual(getter_AddRefs(mDefaultSwapChainVisual));
+ if (FAILED(hr)) {
+ aError.Assign(nsPrintfCString(
+ "DCLayerTree(create swap chain DCompositionVisual failed %x)", hr));
+ return false;
+ }
+
+ if (gfx::gfxVars::UseWebRenderDCompVideoOverlayWin()) {
+ if (!InitializeVideoOverlaySupport()) {
+ RenderThread::Get()->HandleWebRenderError(WebRenderError::VIDEO_OVERLAY);
+ }
+ }
+
+ mCompositionTarget->SetRoot(mRootVisual);
+ // Set interporation mode to nearest, to ensure 1:1 sampling.
+ // By default, a visual inherits the interpolation mode of the parent visual.
+ // If no visuals set the interpolation mode, the default for the entire visual
+ // tree is nearest neighbor interpolation.
+ mRootVisual->SetBitmapInterpolationMode(
+ DCOMPOSITION_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR);
+ return true;
+}
+
+bool DCLayerTree::InitializeVideoOverlaySupport() {
+ HRESULT hr;
+
+ hr = mDevice->QueryInterface(
+ (ID3D11VideoDevice**)getter_AddRefs(mVideoDevice));
+ if (FAILED(hr)) {
+ gfxCriticalNote << "Failed to get D3D11VideoDevice: " << gfx::hexa(hr);
+ return false;
+ }
+
+ hr =
+ mCtx->QueryInterface((ID3D11VideoContext**)getter_AddRefs(mVideoContext));
+ if (FAILED(hr)) {
+ gfxCriticalNote << "Failed to get D3D11VideoContext: " << gfx::hexa(hr);
+ return false;
+ }
+
+ // XXX When video is rendered to DXGI_FORMAT_B8G8R8A8_UNORM SwapChain with
+ // VideoProcessor, it seems that we do not need to check
+ // IDXGIOutput3::CheckOverlaySupport().
+ // If we want to yuv at DecodeSwapChain, its support seems necessary.
+
+ mVideoOverlaySupported = true;
+ return true;
+}
+
+DCSurface* DCLayerTree::GetSurface(wr::NativeSurfaceId aId) const {
+ auto surface_it = mDCSurfaces.find(aId);
+ MOZ_RELEASE_ASSERT(surface_it != mDCSurfaces.end());
+ return surface_it->second.get();
+}
+
+void DCLayerTree::SetDefaultSwapChain(IDXGISwapChain1* aSwapChain) {
+ mRootVisual->AddVisual(mDefaultSwapChainVisual, TRUE, nullptr);
+ mDefaultSwapChainVisual->SetContent(aSwapChain);
+ // Default SwapChain's visual does not need linear interporation.
+ mDefaultSwapChainVisual->SetBitmapInterpolationMode(
+ DCOMPOSITION_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR);
+ mPendingCommit = true;
+}
+
+void DCLayerTree::MaybeUpdateDebug() {
+ bool updated = false;
+ updated |= MaybeUpdateDebugCounter();
+ updated |= MaybeUpdateDebugVisualRedrawRegions();
+ if (updated) {
+ mPendingCommit = true;
+ }
+}
+
+void DCLayerTree::MaybeCommit() {
+ if (!mPendingCommit) {
+ return;
+ }
+ mCompositionDevice->Commit();
+}
+
+void DCLayerTree::WaitForCommitCompletion() {
+ mCompositionDevice->WaitForCommitCompletion();
+}
+
+void DCLayerTree::DisableNativeCompositor() {
+ MOZ_ASSERT(mCurrentSurface.isNothing());
+ MOZ_ASSERT(mCurrentLayers.empty());
+
+ ReleaseNativeCompositorResources();
+ mPrevLayers.clear();
+ mRootVisual->RemoveAllVisuals();
+}
+
+bool DCLayerTree::MaybeUpdateDebugCounter() {
+ bool debugCounter = StaticPrefs::gfx_webrender_debug_dcomp_counter();
+ if (mDebugCounter == debugCounter) {
+ return false;
+ }
+
+ RefPtr<IDCompositionDeviceDebug> debugDevice;
+ HRESULT hr = mCompositionDevice->QueryInterface(
+ (IDCompositionDeviceDebug**)getter_AddRefs(debugDevice));
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ if (debugCounter) {
+ debugDevice->EnableDebugCounters();
+ } else {
+ debugDevice->DisableDebugCounters();
+ }
+
+ mDebugCounter = debugCounter;
+ return true;
+}
+
+bool DCLayerTree::MaybeUpdateDebugVisualRedrawRegions() {
+ bool debugVisualRedrawRegions =
+ StaticPrefs::gfx_webrender_debug_dcomp_redraw_regions();
+ if (mDebugVisualRedrawRegions == debugVisualRedrawRegions) {
+ return false;
+ }
+
+ RefPtr<IDCompositionVisualDebug> visualDebug;
+ HRESULT hr = mRootVisual->QueryInterface(
+ (IDCompositionVisualDebug**)getter_AddRefs(visualDebug));
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ if (debugVisualRedrawRegions) {
+ visualDebug->EnableRedrawRegions();
+ } else {
+ visualDebug->DisableRedrawRegions();
+ }
+
+ mDebugVisualRedrawRegions = debugVisualRedrawRegions;
+ return true;
+}
+
+void DCLayerTree::CompositorBeginFrame() { mCurrentFrame++; }
+
+void DCLayerTree::CompositorEndFrame() {
+ auto start = TimeStamp::Now();
+ // Check if the visual tree of surfaces is the same as last frame.
+ bool same = mPrevLayers == mCurrentLayers;
+
+ if (!same) {
+ // If not, we need to rebuild the visual tree. Note that addition or
+ // removal of tiles no longer needs to rebuild the main visual tree
+ // here, since they are added as children of the surface visual.
+ mRootVisual->RemoveAllVisuals();
+ }
+
+ for (auto it = mCurrentLayers.begin(); it != mCurrentLayers.end(); ++it) {
+ auto surface_it = mDCSurfaces.find(*it);
+ MOZ_RELEASE_ASSERT(surface_it != mDCSurfaces.end());
+ const auto surface = surface_it->second.get();
+ // Ensure surface is trimmed to updated tile valid rects
+ surface->UpdateAllocatedRect();
+ if (!same) {
+ // Add surfaces in z-order they were added to the scene.
+ const auto visual = surface->GetVisual();
+ mRootVisual->AddVisual(visual, FALSE, nullptr);
+ }
+ }
+
+ mPrevLayers.swap(mCurrentLayers);
+ mCurrentLayers.clear();
+
+ mCompositionDevice->Commit();
+
+ auto end = TimeStamp::Now();
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::COMPOSITE_SWAP_TIME,
+ (end - start).ToMilliseconds() * 10.);
+
+ // Remove any framebuffers that haven't been
+ // used in the last 60 frames.
+ //
+ // This should use nsTArray::RemoveElementsBy once
+ // CachedFrameBuffer is able to properly destroy
+ // itself in the destructor.
+ const auto gl = GetGLContext();
+ for (uint32_t i = 0, len = mFrameBuffers.Length(); i < len; ++i) {
+ auto& fb = mFrameBuffers[i];
+ if ((mCurrentFrame - fb.lastFrameUsed) > 60) {
+ gl->fDeleteRenderbuffers(1, &fb.depthRboId);
+ gl->fDeleteFramebuffers(1, &fb.fboId);
+ mFrameBuffers.UnorderedRemoveElementAt(i);
+ --i; // Examine the element again, if necessary.
+ --len;
+ }
+ }
+}
+
+void DCLayerTree::Bind(wr::NativeTileId aId, wr::DeviceIntPoint* aOffset,
+ uint32_t* aFboId, wr::DeviceIntRect aDirtyRect,
+ wr::DeviceIntRect aValidRect) {
+ auto surface = GetSurface(aId.surface_id);
+ auto tile = surface->GetTile(aId.x, aId.y);
+ wr::DeviceIntPoint targetOffset{0, 0};
+
+ gfx::IntRect validRect(aValidRect.origin.x, aValidRect.origin.y,
+ aValidRect.size.width, aValidRect.size.height);
+ if (!tile->mValidRect.IsEqualEdges(validRect)) {
+ tile->mValidRect = validRect;
+ surface->DirtyAllocatedRect();
+ }
+ wr::DeviceIntSize tileSize = surface->GetTileSize();
+ RefPtr<IDCompositionSurface> compositionSurface =
+ surface->GetCompositionSurface();
+ wr::DeviceIntPoint virtualOffset = surface->GetVirtualOffset();
+ targetOffset.x = virtualOffset.x + tileSize.width * aId.x;
+ targetOffset.y = virtualOffset.y + tileSize.height * aId.y;
+
+ *aFboId = CreateEGLSurfaceForCompositionSurface(
+ aDirtyRect, aOffset, compositionSurface, targetOffset);
+ mCurrentSurface = Some(compositionSurface);
+}
+
+void DCLayerTree::Unbind() {
+ if (mCurrentSurface.isNothing()) {
+ return;
+ }
+
+ RefPtr<IDCompositionSurface> surface = mCurrentSurface.ref();
+ surface->EndDraw();
+
+ DestroyEGLSurface();
+ mCurrentSurface = Nothing();
+}
+
+void DCLayerTree::CreateSurface(wr::NativeSurfaceId aId,
+ wr::DeviceIntPoint aVirtualOffset,
+ wr::DeviceIntSize aTileSize, bool aIsOpaque) {
+ auto it = mDCSurfaces.find(aId);
+ MOZ_RELEASE_ASSERT(it == mDCSurfaces.end());
+ if (it != mDCSurfaces.end()) {
+ // DCSurface already exists.
+ return;
+ }
+
+ // Tile size needs to be positive.
+ if (aTileSize.width <= 0 || aTileSize.height <= 0) {
+ gfxCriticalNote << "TileSize is not positive aId: " << wr::AsUint64(aId)
+ << " aTileSize(" << aTileSize.width << ","
+ << aTileSize.height << ")";
+ }
+
+ auto surface =
+ MakeUnique<DCSurface>(aTileSize, aVirtualOffset, aIsOpaque, this);
+ if (!surface->Initialize()) {
+ gfxCriticalNote << "Failed to initialize DCSurface: " << wr::AsUint64(aId);
+ return;
+ }
+
+ mDCSurfaces[aId] = std::move(surface);
+}
+
+void DCLayerTree::CreateExternalSurface(wr::NativeSurfaceId aId,
+ bool aIsOpaque) {
+ auto it = mDCSurfaces.find(aId);
+ MOZ_RELEASE_ASSERT(it == mDCSurfaces.end());
+
+ auto surface = MakeUnique<DCSurfaceVideo>(aIsOpaque, this);
+ if (!surface->Initialize()) {
+ gfxCriticalNote << "Failed to initialize DCSurfaceVideo: "
+ << wr::AsUint64(aId);
+ return;
+ }
+
+ mDCSurfaces[aId] = std::move(surface);
+}
+
+void DCLayerTree::DestroySurface(NativeSurfaceId aId) {
+ auto surface_it = mDCSurfaces.find(aId);
+ MOZ_RELEASE_ASSERT(surface_it != mDCSurfaces.end());
+ auto surface = surface_it->second.get();
+
+ mRootVisual->RemoveVisual(surface->GetVisual());
+ mDCSurfaces.erase(surface_it);
+}
+
+void DCLayerTree::CreateTile(wr::NativeSurfaceId aId, int aX, int aY) {
+ auto surface = GetSurface(aId);
+ surface->CreateTile(aX, aY);
+}
+
+void DCLayerTree::DestroyTile(wr::NativeSurfaceId aId, int aX, int aY) {
+ auto surface = GetSurface(aId);
+ surface->DestroyTile(aX, aY);
+}
+
+void DCLayerTree::AttachExternalImage(wr::NativeSurfaceId aId,
+ wr::ExternalImageId aExternalImage) {
+ auto surface_it = mDCSurfaces.find(aId);
+ MOZ_RELEASE_ASSERT(surface_it != mDCSurfaces.end());
+ auto* surfaceVideo = surface_it->second->AsDCSurfaceVideo();
+ MOZ_RELEASE_ASSERT(surfaceVideo);
+
+ surfaceVideo->AttachExternalImage(aExternalImage);
+}
+
+template <typename T>
+static inline D2D1_RECT_F D2DRect(const T& aRect) {
+ return D2D1::RectF(aRect.X(), aRect.Y(), aRect.XMost(), aRect.YMost());
+}
+
+static inline D2D1_MATRIX_3X2_F D2DMatrix(const gfx::Matrix& aTransform) {
+ return D2D1::Matrix3x2F(aTransform._11, aTransform._12, aTransform._21,
+ aTransform._22, aTransform._31, aTransform._32);
+}
+
+void DCLayerTree::AddSurface(wr::NativeSurfaceId aId,
+ const wr::CompositorSurfaceTransform& aTransform,
+ wr::DeviceIntRect aClipRect,
+ wr::ImageRendering aImageRendering) {
+ auto it = mDCSurfaces.find(aId);
+ MOZ_RELEASE_ASSERT(it != mDCSurfaces.end());
+ const auto surface = it->second.get();
+ const auto visual = surface->GetVisual();
+
+ wr::DeviceIntPoint virtualOffset = surface->GetVirtualOffset();
+
+ gfx::Matrix transform(aTransform.m11, aTransform.m12, aTransform.m21,
+ aTransform.m22, aTransform.m41, aTransform.m42);
+ transform.PreTranslate(-virtualOffset.x, -virtualOffset.y);
+
+ // The DirectComposition API applies clipping *before* any transforms/offset,
+ // whereas we want the clip applied after.
+ // Right now, we only support rectilinear transforms, and then we transform
+ // our clip into pre-transform coordinate space for it to be applied there.
+ // DirectComposition does have an option for pre-transform clipping, if you
+ // create an explicit IDCompositionEffectGroup object and set a 3D transform
+ // on that. I suspect that will perform worse though, so we should only do
+ // that for complex transforms (which are never provided right now).
+ MOZ_ASSERT(transform.IsRectilinear());
+ gfx::Rect clip = transform.Inverse().TransformBounds(
+ gfx::Rect(aClipRect.origin.x, aClipRect.origin.y, aClipRect.size.width,
+ aClipRect.size.height));
+ // Set the clip rect - converting from world space to the pre-offset space
+ // that DC requires for rectangle clips.
+ visual->SetClip(D2DRect(clip));
+
+ // TODO: The input matrix is a 4x4, but we only support a 3x2 at
+ // the D3D API level (unless we QI to IDCompositionVisual3, which might
+ // not be available?).
+ // Should we assert here, or restrict at the WR API level.
+ visual->SetTransform(D2DMatrix(transform));
+
+ if (aImageRendering == wr::ImageRendering::Auto) {
+ visual->SetBitmapInterpolationMode(
+ DCOMPOSITION_BITMAP_INTERPOLATION_MODE_LINEAR);
+ } else {
+ visual->SetBitmapInterpolationMode(
+ DCOMPOSITION_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR);
+ }
+
+ mCurrentLayers.push_back(aId);
+}
+
+GLuint DCLayerTree::GetOrCreateFbo(int aWidth, int aHeight) {
+ const auto gl = GetGLContext();
+ GLuint fboId = 0;
+
+ // Check if we have a cached FBO with matching dimensions
+ for (auto it = mFrameBuffers.begin(); it != mFrameBuffers.end(); ++it) {
+ if (it->width == aWidth && it->height == aHeight) {
+ fboId = it->fboId;
+ it->lastFrameUsed = mCurrentFrame;
+ break;
+ }
+ }
+
+ // If not, create a new FBO with attached depth buffer
+ if (fboId == 0) {
+ // Create the depth buffer
+ GLuint depthRboId;
+ gl->fGenRenderbuffers(1, &depthRboId);
+ gl->fBindRenderbuffer(LOCAL_GL_RENDERBUFFER, depthRboId);
+ gl->fRenderbufferStorage(LOCAL_GL_RENDERBUFFER, LOCAL_GL_DEPTH_COMPONENT24,
+ aWidth, aHeight);
+
+ // Create the framebuffer and attach the depth buffer to it
+ gl->fGenFramebuffers(1, &fboId);
+ gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, fboId);
+ gl->fFramebufferRenderbuffer(LOCAL_GL_DRAW_FRAMEBUFFER,
+ LOCAL_GL_DEPTH_ATTACHMENT,
+ LOCAL_GL_RENDERBUFFER, depthRboId);
+
+ // Store this in the cache for future calls.
+ // TODO(gw): Maybe we should periodically scan this list and remove old
+ // entries that
+ // haven't been used for some time?
+ DCLayerTree::CachedFrameBuffer frame_buffer_info;
+ frame_buffer_info.width = aWidth;
+ frame_buffer_info.height = aHeight;
+ frame_buffer_info.fboId = fboId;
+ frame_buffer_info.depthRboId = depthRboId;
+ frame_buffer_info.lastFrameUsed = mCurrentFrame;
+ mFrameBuffers.AppendElement(frame_buffer_info);
+ }
+
+ return fboId;
+}
+
+bool DCLayerTree::EnsureVideoProcessor(const gfx::IntSize& aVideoSize) {
+ HRESULT hr;
+
+ if (!mVideoDevice || !mVideoContext) {
+ return false;
+ }
+
+ if (mVideoProcessor && aVideoSize == mVideoSize) {
+ return true;
+ }
+
+ mVideoProcessor = nullptr;
+ mVideoProcessorEnumerator = nullptr;
+
+ D3D11_VIDEO_PROCESSOR_CONTENT_DESC desc = {};
+ desc.InputFrameFormat = D3D11_VIDEO_FRAME_FORMAT_PROGRESSIVE;
+ desc.InputFrameRate.Numerator = 60;
+ desc.InputFrameRate.Denominator = 1;
+ desc.InputWidth = aVideoSize.width;
+ desc.InputHeight = aVideoSize.height;
+ desc.OutputFrameRate.Numerator = 60;
+ desc.OutputFrameRate.Denominator = 1;
+ desc.OutputWidth = aVideoSize.width;
+ desc.OutputHeight = aVideoSize.height;
+ desc.Usage = D3D11_VIDEO_USAGE_PLAYBACK_NORMAL;
+
+ hr = mVideoDevice->CreateVideoProcessorEnumerator(
+ &desc, getter_AddRefs(mVideoProcessorEnumerator));
+ if (FAILED(hr)) {
+ gfxCriticalNote << "Failed to create VideoProcessorEnumerator: "
+ << gfx::hexa(hr);
+ return false;
+ }
+
+ hr = mVideoDevice->CreateVideoProcessor(mVideoProcessorEnumerator, 0,
+ getter_AddRefs(mVideoProcessor));
+ if (FAILED(hr)) {
+ mVideoProcessor = nullptr;
+ mVideoProcessorEnumerator = nullptr;
+ gfxCriticalNote << "Failed to create VideoProcessor: " << gfx::hexa(hr);
+ return false;
+ }
+
+ // Reduce power cosumption
+ // By default, the driver might perform certain processing tasks automatically
+ mVideoContext->VideoProcessorSetStreamAutoProcessingMode(mVideoProcessor, 0,
+ FALSE);
+
+ mVideoSize = aVideoSize;
+ return true;
+}
+
+DCSurface::DCSurface(wr::DeviceIntSize aTileSize,
+ wr::DeviceIntPoint aVirtualOffset, bool aIsOpaque,
+ DCLayerTree* aDCLayerTree)
+ : mDCLayerTree(aDCLayerTree),
+ mTileSize(aTileSize),
+ mIsOpaque(aIsOpaque),
+ mAllocatedRectDirty(true),
+ mVirtualOffset(aVirtualOffset) {}
+
+DCSurface::~DCSurface() {}
+
+bool DCSurface::Initialize() {
+ HRESULT hr;
+ const auto dCompDevice = mDCLayerTree->GetCompositionDevice();
+ hr = dCompDevice->CreateVisual(getter_AddRefs(mVisual));
+ if (FAILED(hr)) {
+ gfxCriticalNote << "Failed to create DCompositionVisual: " << gfx::hexa(hr);
+ return false;
+ }
+
+ DXGI_ALPHA_MODE alpha_mode =
+ mIsOpaque ? DXGI_ALPHA_MODE_IGNORE : DXGI_ALPHA_MODE_PREMULTIPLIED;
+
+ hr = dCompDevice->CreateVirtualSurface(
+ VIRTUAL_SURFACE_SIZE, VIRTUAL_SURFACE_SIZE, DXGI_FORMAT_B8G8R8A8_UNORM,
+ alpha_mode, getter_AddRefs(mVirtualSurface));
+ MOZ_ASSERT(SUCCEEDED(hr));
+
+ // Bind the surface memory to this visual
+ hr = mVisual->SetContent(mVirtualSurface);
+ MOZ_ASSERT(SUCCEEDED(hr));
+
+ return true;
+}
+
+void DCSurface::CreateTile(int aX, int aY) {
+ TileKey key(aX, aY);
+ MOZ_RELEASE_ASSERT(mDCTiles.find(key) == mDCTiles.end());
+
+ auto tile = MakeUnique<DCTile>(mDCLayerTree);
+ if (!tile->Initialize(aX, aY, mTileSize, mIsOpaque)) {
+ gfxCriticalNote << "Failed to initialize DCTile: " << aX << aY;
+ return;
+ }
+
+ mAllocatedRectDirty = true;
+
+ mDCTiles[key] = std::move(tile);
+}
+
+void DCSurface::DestroyTile(int aX, int aY) {
+ TileKey key(aX, aY);
+ mAllocatedRectDirty = true;
+ mDCTiles.erase(key);
+}
+
+void DCSurface::DirtyAllocatedRect() { mAllocatedRectDirty = true; }
+
+void DCSurface::UpdateAllocatedRect() {
+ if (mAllocatedRectDirty) {
+ // The virtual surface may have holes in it (for example, an empty tile
+ // that has no primitives). Instead of trimming to a single bounding
+ // rect, supply the rect of each valid tile to handle this case.
+ std::vector<RECT> validRects;
+
+ for (auto it = mDCTiles.begin(); it != mDCTiles.end(); ++it) {
+ auto tile = GetTile(it->first.mX, it->first.mY);
+ RECT rect;
+
+ rect.left = (LONG)(mVirtualOffset.x + it->first.mX * mTileSize.width +
+ tile->mValidRect.x);
+ rect.top = (LONG)(mVirtualOffset.y + it->first.mY * mTileSize.height +
+ tile->mValidRect.y);
+ rect.right = rect.left + tile->mValidRect.width;
+ rect.bottom = rect.top + tile->mValidRect.height;
+
+ validRects.push_back(rect);
+ }
+
+ mVirtualSurface->Trim(validRects.data(), validRects.size());
+ mAllocatedRectDirty = false;
+ }
+}
+
+DCTile* DCSurface::GetTile(int aX, int aY) const {
+ TileKey key(aX, aY);
+ auto tile_it = mDCTiles.find(key);
+ MOZ_RELEASE_ASSERT(tile_it != mDCTiles.end());
+ return tile_it->second.get();
+}
+
+DCSurfaceVideo::DCSurfaceVideo(bool aIsOpaque, DCLayerTree* aDCLayerTree)
+ : DCSurface(wr::DeviceIntSize{}, wr::DeviceIntPoint{}, aIsOpaque,
+ aDCLayerTree) {}
+
+void DCSurfaceVideo::AttachExternalImage(wr::ExternalImageId aExternalImage) {
+ RenderTextureHost* texture =
+ RenderThread::Get()->GetRenderTexture(aExternalImage);
+ MOZ_RELEASE_ASSERT(texture);
+
+ if (mPrevTexture == texture) {
+ return;
+ }
+
+ // XXX if software decoded video frame format is nv12, it could be used as
+ // video overlay.
+ if (!texture || !texture->AsRenderDXGITextureHost() ||
+ texture->AsRenderDXGITextureHost()->GetFormat() !=
+ gfx::SurfaceFormat::NV12) {
+ gfxCriticalNote << "Unsupported RenderTexture for overlay: "
+ << gfx::hexa(texture);
+ return;
+ }
+
+ gfx::IntSize size = texture->AsRenderDXGITextureHost()->GetSize(0);
+ if (!mVideoSwapChain || mSwapChainSize != size) {
+ ReleaseDecodeSwapChainResources();
+ CreateVideoSwapChain(texture);
+ }
+
+ if (!mVideoSwapChain) {
+ gfxCriticalNote << "Failed to create VideoSwapChain";
+ RenderThread::Get()->NotifyWebRenderError(
+ wr::WebRenderError::VIDEO_OVERLAY);
+ return;
+ }
+
+ mVisual->SetContent(mVideoSwapChain);
+
+ if (!CallVideoProcessorBlt(texture)) {
+ RenderThread::Get()->NotifyWebRenderError(
+ wr::WebRenderError::VIDEO_OVERLAY);
+ return;
+ }
+
+ mVideoSwapChain->Present(0, 0);
+ mPrevTexture = texture;
+}
+
+bool DCSurfaceVideo::CreateVideoSwapChain(RenderTextureHost* aTexture) {
+ const auto device = mDCLayerTree->GetDevice();
+
+ RefPtr<IDXGIDevice> dxgiDevice;
+ device->QueryInterface((IDXGIDevice**)getter_AddRefs(dxgiDevice));
+
+ RefPtr<IDXGIFactoryMedia> dxgiFactoryMedia;
+ {
+ RefPtr<IDXGIAdapter> adapter;
+ dxgiDevice->GetAdapter(getter_AddRefs(adapter));
+ adapter->GetParent(
+ IID_PPV_ARGS((IDXGIFactoryMedia**)getter_AddRefs(dxgiFactoryMedia)));
+ }
+
+ mSwapChainSurfaceHandle = gfx::DeviceManagerDx::CreateDCompSurfaceHandle();
+ if (!mSwapChainSurfaceHandle) {
+ gfxCriticalNote << "Failed to create DCompSurfaceHandle";
+ return false;
+ }
+
+ gfx::IntSize size = aTexture->AsRenderDXGITextureHost()->GetSize(0);
+ DXGI_ALPHA_MODE alpha_mode =
+ mIsOpaque ? DXGI_ALPHA_MODE_IGNORE : DXGI_ALPHA_MODE_PREMULTIPLIED;
+
+ DXGI_SWAP_CHAIN_DESC1 desc = {};
+ desc.Width = size.width;
+ desc.Height = size.height;
+ desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
+ desc.Stereo = FALSE;
+ desc.SampleDesc.Count = 1;
+ desc.BufferCount = 2;
+ desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
+ desc.Scaling = DXGI_SCALING_STRETCH;
+ desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
+ desc.Flags = 0;
+ desc.AlphaMode = alpha_mode;
+
+ HRESULT hr;
+ hr = dxgiFactoryMedia->CreateSwapChainForCompositionSurfaceHandle(
+ device, mSwapChainSurfaceHandle, &desc, nullptr,
+ getter_AddRefs(mVideoSwapChain));
+
+ if (FAILED(hr)) {
+ gfxCriticalNote << "Failed to create video SwapChain: " << gfx::hexa(hr);
+ return false;
+ }
+
+ mSwapChainSize = size;
+ return true;
+}
+
+static Maybe<DXGI_COLOR_SPACE_TYPE> GetSourceDXGIColorSpace(
+ const gfx::YUVColorSpace aYUVColorSpace,
+ const gfx::ColorRange aColorRange) {
+ if (aYUVColorSpace == gfx::YUVColorSpace::BT601) {
+ if (aColorRange == gfx::ColorRange::FULL) {
+ return Some(DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P601);
+ } else {
+ return Some(DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P601);
+ }
+ } else if (aYUVColorSpace == gfx::YUVColorSpace::BT709) {
+ if (aColorRange == gfx::ColorRange::FULL) {
+ return Some(DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P709);
+ } else {
+ return Some(DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P709);
+ }
+ } else if (aYUVColorSpace == gfx::YUVColorSpace::BT2020) {
+ if (aColorRange == gfx::ColorRange::FULL) {
+ // XXX Add SMPTEST2084 handling. HDR content is not handled yet by
+ // video overlay.
+ return Some(DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P2020);
+ } else {
+ return Some(DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P2020);
+ }
+ }
+
+ return Nothing();
+}
+
+bool DCSurfaceVideo::CallVideoProcessorBlt(RenderTextureHost* aTexture) {
+ HRESULT hr;
+ const auto videoDevice = mDCLayerTree->GetVideoDevice();
+ const auto videoContext = mDCLayerTree->GetVideoContext();
+ const auto texture = aTexture->AsRenderDXGITextureHost();
+
+ Maybe<DXGI_COLOR_SPACE_TYPE> sourceColorSpace = GetSourceDXGIColorSpace(
+ texture->GetYUVColorSpace(), texture->GetColorRange());
+ if (sourceColorSpace.isNothing()) {
+ gfxCriticalNote << "Unsupported color space";
+ return false;
+ }
+
+ RefPtr<ID3D11Texture2D> texture2D = texture->GetD3D11Texture2DWithGL();
+ if (!texture2D) {
+ gfxCriticalNote << "Failed to get D3D11Texture2D";
+ return false;
+ }
+
+ if (!mVideoSwapChain) {
+ return false;
+ }
+
+ if (!mDCLayerTree->EnsureVideoProcessor(mSwapChainSize)) {
+ gfxCriticalNote << "EnsureVideoProcessor Failed";
+ return false;
+ }
+
+ RefPtr<IDXGISwapChain3> swapChain3;
+ mVideoSwapChain->QueryInterface(
+ (IDXGISwapChain3**)getter_AddRefs(swapChain3));
+ if (!swapChain3) {
+ gfxCriticalNote << "Failed to get IDXGISwapChain3";
+ return false;
+ }
+
+ RefPtr<ID3D11VideoContext1> videoContext1;
+ videoContext->QueryInterface(
+ (ID3D11VideoContext1**)getter_AddRefs(videoContext1));
+ if (!videoContext1) {
+ gfxCriticalNote << "Failed to get ID3D11VideoContext1";
+ return false;
+ }
+
+ const auto videoProcessor = mDCLayerTree->GetVideoProcessor();
+ const auto videoProcessorEnumerator =
+ mDCLayerTree->GetVideoProcessorEnumerator();
+
+ DXGI_COLOR_SPACE_TYPE inputColorSpace = sourceColorSpace.ref();
+ videoContext1->VideoProcessorSetStreamColorSpace1(videoProcessor, 0,
+ inputColorSpace);
+ // XXX when content is hdr or yuv swapchain, it need to use other color space.
+ DXGI_COLOR_SPACE_TYPE outputColorSpace =
+ DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
+ hr = swapChain3->SetColorSpace1(outputColorSpace);
+ if (FAILED(hr)) {
+ gfxCriticalNote << "SetColorSpace1 failed: " << gfx::hexa(hr);
+ return false;
+ }
+ videoContext1->VideoProcessorSetOutputColorSpace1(videoProcessor,
+ outputColorSpace);
+
+ D3D11_VIDEO_PROCESSOR_INPUT_VIEW_DESC inputDesc = {};
+ inputDesc.ViewDimension = D3D11_VPIV_DIMENSION_TEXTURE2D;
+ inputDesc.Texture2D.ArraySlice = 0;
+
+ RefPtr<ID3D11VideoProcessorInputView> inputView;
+ hr = videoDevice->CreateVideoProcessorInputView(
+ texture2D, videoProcessorEnumerator, &inputDesc,
+ getter_AddRefs(inputView));
+ if (FAILED(hr)) {
+ gfxCriticalNote << "ID3D11VideoProcessorInputView creation failed: "
+ << gfx::hexa(hr);
+ return false;
+ }
+
+ D3D11_VIDEO_PROCESSOR_STREAM stream = {};
+ stream.Enable = true;
+ stream.OutputIndex = 0;
+ stream.InputFrameOrField = 0;
+ stream.PastFrames = 0;
+ stream.FutureFrames = 0;
+ stream.pInputSurface = inputView.get();
+
+ RECT destRect;
+ destRect.left = 0;
+ destRect.top = 0;
+ destRect.right = mSwapChainSize.width;
+ destRect.bottom = mSwapChainSize.height;
+
+ videoContext->VideoProcessorSetOutputTargetRect(videoProcessor, TRUE,
+ &destRect);
+ videoContext->VideoProcessorSetStreamDestRect(videoProcessor, 0, TRUE,
+ &destRect);
+ RECT sourceRect;
+ sourceRect.left = 0;
+ sourceRect.top = 0;
+ sourceRect.right = mSwapChainSize.width;
+ sourceRect.bottom = mSwapChainSize.height;
+ videoContext->VideoProcessorSetStreamSourceRect(videoProcessor, 0, TRUE,
+ &sourceRect);
+
+ if (!mOutputView) {
+ RefPtr<ID3D11Texture2D> backBuf;
+ mVideoSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D),
+ (void**)getter_AddRefs(backBuf));
+
+ D3D11_VIDEO_PROCESSOR_OUTPUT_VIEW_DESC outputDesc = {};
+ outputDesc.ViewDimension = D3D11_VPOV_DIMENSION_TEXTURE2D;
+ outputDesc.Texture2D.MipSlice = 0;
+
+ hr = videoDevice->CreateVideoProcessorOutputView(
+ backBuf, videoProcessorEnumerator, &outputDesc,
+ getter_AddRefs(mOutputView));
+ if (FAILED(hr)) {
+ gfxCriticalNote << "ID3D11VideoProcessorOutputView creation failed: "
+ << gfx::hexa(hr);
+ return false;
+ }
+ }
+
+ hr = videoContext->VideoProcessorBlt(videoProcessor, mOutputView, 0, 1,
+ &stream);
+ if (FAILED(hr)) {
+ gfxCriticalNote << "VideoProcessorBlt failed: " << gfx::hexa(hr);
+ return false;
+ }
+
+ return true;
+}
+
+void DCSurfaceVideo::ReleaseDecodeSwapChainResources() {
+ mOutputView = nullptr;
+ mVideoSwapChain = nullptr;
+ mDecodeSwapChain = nullptr;
+ mDecodeResource = nullptr;
+ if (mSwapChainSurfaceHandle) {
+ ::CloseHandle(mSwapChainSurfaceHandle);
+ mSwapChainSurfaceHandle = 0;
+ }
+ mSwapChainSize = gfx::IntSize();
+}
+
+DCTile::DCTile(DCLayerTree* aDCLayerTree) : mDCLayerTree(aDCLayerTree) {}
+
+DCTile::~DCTile() {}
+
+bool DCTile::Initialize(int aX, int aY, wr::DeviceIntSize aSize,
+ bool aIsOpaque) {
+ if (aSize.width <= 0 || aSize.height <= 0) {
+ return false;
+ }
+
+ // Initially, the entire tile is considered valid, unless it is set by
+ // the SetTileProperties method.
+ mValidRect.x = 0;
+ mValidRect.y = 0;
+ mValidRect.width = aSize.width;
+ mValidRect.height = aSize.height;
+
+ return true;
+}
+
+GLuint DCLayerTree::CreateEGLSurfaceForCompositionSurface(
+ wr::DeviceIntRect aDirtyRect, wr::DeviceIntPoint* aOffset,
+ RefPtr<IDCompositionSurface> aCompositionSurface,
+ wr::DeviceIntPoint aSurfaceOffset) {
+ MOZ_ASSERT(aCompositionSurface.get());
+
+ HRESULT hr;
+ const auto gl = GetGLContext();
+ RefPtr<ID3D11Texture2D> backBuf;
+ POINT offset;
+
+ RECT update_rect;
+ update_rect.left = aSurfaceOffset.x + aDirtyRect.origin.x;
+ update_rect.top = aSurfaceOffset.y + aDirtyRect.origin.y;
+ update_rect.right = update_rect.left + aDirtyRect.size.width;
+ update_rect.bottom = update_rect.top + aDirtyRect.size.height;
+
+ hr = aCompositionSurface->BeginDraw(&update_rect, __uuidof(ID3D11Texture2D),
+ (void**)getter_AddRefs(backBuf), &offset);
+ if (FAILED(hr)) {
+ gfxCriticalNote << "DCompositionSurface::BeginDraw failed: "
+ << gfx::hexa(hr);
+ RenderThread::Get()->HandleWebRenderError(WebRenderError::NEW_SURFACE);
+ return false;
+ }
+
+ // DC includes the origin of the dirty / update rect in the draw offset,
+ // undo that here since WR expects it to be an absolute offset.
+ offset.x -= aDirtyRect.origin.x;
+ offset.y -= aDirtyRect.origin.y;
+
+ D3D11_TEXTURE2D_DESC desc;
+ backBuf->GetDesc(&desc);
+
+ const auto& gle = gl::GLContextEGL::Cast(gl);
+ const auto& egl = gle->mEgl;
+
+ const auto buffer = reinterpret_cast<EGLClientBuffer>(backBuf.get());
+
+ // Construct an EGLImage wrapper around the D3D texture for ANGLE.
+ const EGLint attribs[] = {LOCAL_EGL_NONE};
+ mEGLImage = egl->fCreateImage(EGL_NO_CONTEXT, LOCAL_EGL_D3D11_TEXTURE_ANGLE,
+ buffer, attribs);
+
+ // Get the current FBO and RBO id, so we can restore them later
+ GLint currentFboId, currentRboId;
+ gl->fGetIntegerv(LOCAL_GL_DRAW_FRAMEBUFFER_BINDING, &currentFboId);
+ gl->fGetIntegerv(LOCAL_GL_RENDERBUFFER_BINDING, &currentRboId);
+
+ // Create a render buffer object that is backed by the EGL image.
+ gl->fGenRenderbuffers(1, &mColorRBO);
+ gl->fBindRenderbuffer(LOCAL_GL_RENDERBUFFER, mColorRBO);
+ gl->fEGLImageTargetRenderbufferStorage(LOCAL_GL_RENDERBUFFER, mEGLImage);
+
+ // Get or create an FBO for the specified dimensions
+ GLuint fboId = GetOrCreateFbo(desc.Width, desc.Height);
+
+ // Attach the new renderbuffer to the FBO
+ gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, fboId);
+ gl->fFramebufferRenderbuffer(LOCAL_GL_DRAW_FRAMEBUFFER,
+ LOCAL_GL_COLOR_ATTACHMENT0,
+ LOCAL_GL_RENDERBUFFER, mColorRBO);
+
+ // Restore previous FBO and RBO bindings
+ gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, currentFboId);
+ gl->fBindRenderbuffer(LOCAL_GL_RENDERBUFFER, currentRboId);
+
+ aOffset->x = offset.x;
+ aOffset->y = offset.y;
+
+ return fboId;
+}
+
+void DCLayerTree::DestroyEGLSurface() {
+ const auto gl = GetGLContext();
+
+ if (mColorRBO) {
+ gl->fDeleteRenderbuffers(1, &mColorRBO);
+ mColorRBO = 0;
+ }
+
+ if (mEGLImage) {
+ const auto& gle = gl::GLContextEGL::Cast(gl);
+ const auto& egl = gle->mEgl;
+ egl->fDestroyImage(mEGLImage);
+ mEGLImage = EGL_NO_IMAGE;
+ }
+}
+
+} // namespace wr
+} // namespace mozilla