1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "DCLayerTree.h"
9 #include "GLBlitHelper.h"
10 #include "GLContext.h"
11 #include "GLContextEGL.h"
12 #include "mozilla/gfx/DeviceManagerDx.h"
13 #include "mozilla/gfx/Logging.h"
14 #include "mozilla/gfx/gfxVars.h"
15 #include "mozilla/gfx/GPUParent.h"
16 #include "mozilla/gfx/Matrix.h"
17 #include "mozilla/layers/HelpersD3D11.h"
18 #include "mozilla/ScopeExit.h"
19 #include "mozilla/StaticPrefs_gfx.h"
20 #include "mozilla/webrender/RenderD3D11TextureHost.h"
21 #include "mozilla/webrender/RenderTextureHost.h"
22 #include "mozilla/webrender/RenderThread.h"
23 #include "mozilla/WindowsVersion.h"
24 #include "mozilla/Telemetry.h"
25 #include "nsPrintfCString.h"
26 #include "ScopedGLHelpers.h"
30 #define _WIN32_WINNT _WIN32_WINNT_WINBLUE
32 #define NTDDI_VERSION NTDDI_WINBLUE
42 extern LazyLogModule gRenderThreadLog
;
43 #define LOG(...) MOZ_LOG(gRenderThreadLog, LogLevel::Debug, (__VA_ARGS__))
45 UniquePtr
<GpuOverlayInfo
> DCLayerTree::sGpuOverlayInfo
;
48 UniquePtr
<DCLayerTree
> DCLayerTree::Create(gl::GLContext
* aGL
,
50 ID3D11Device
* aDevice
,
51 ID3D11DeviceContext
* aCtx
,
52 HWND aHwnd
, nsACString
& aError
) {
53 RefPtr
<IDCompositionDevice2
> dCompDevice
=
54 gfx::DeviceManagerDx::Get()->GetDirectCompositionDevice();
56 aError
.Assign("DCLayerTree(no device)"_ns
);
61 MakeUnique
<DCLayerTree
>(aGL
, aEGLConfig
, aDevice
, aCtx
, dCompDevice
);
62 if (!layerTree
->Initialize(aHwnd
, aError
)) {
69 void DCLayerTree::Shutdown() { DCLayerTree::sGpuOverlayInfo
= nullptr; }
71 DCLayerTree::DCLayerTree(gl::GLContext
* aGL
, EGLConfig aEGLConfig
,
72 ID3D11Device
* aDevice
, ID3D11DeviceContext
* aCtx
,
73 IDCompositionDevice2
* aCompositionDevice
)
75 mEGLConfig(aEGLConfig
),
78 mCompositionDevice(aCompositionDevice
),
80 mDebugVisualRedrawRegions(false),
81 mEGLImage(EGL_NO_IMAGE
),
83 mPendingCommit(false) {
84 LOG("DCLayerTree::DCLayerTree()");
87 DCLayerTree::~DCLayerTree() {
88 LOG("DCLayerTree::~DCLayerTree()");
90 ReleaseNativeCompositorResources();
93 void DCLayerTree::ReleaseNativeCompositorResources() {
94 const auto gl
= GetGLContext();
98 // Delete any cached FBO objects
99 for (auto it
= mFrameBuffers
.begin(); it
!= mFrameBuffers
.end(); ++it
) {
100 gl
->fDeleteRenderbuffers(1, &it
->depthRboId
);
101 gl
->fDeleteFramebuffers(1, &it
->fboId
);
105 bool DCLayerTree::Initialize(HWND aHwnd
, nsACString
& aError
) {
108 RefPtr
<IDCompositionDesktopDevice
> desktopDevice
;
109 hr
= mCompositionDevice
->QueryInterface(
110 (IDCompositionDesktopDevice
**)getter_AddRefs(desktopDevice
));
112 aError
.Assign(nsPrintfCString(
113 "DCLayerTree(get IDCompositionDesktopDevice failed %lx)", hr
));
117 hr
= desktopDevice
->CreateTargetForHwnd(aHwnd
, TRUE
,
118 getter_AddRefs(mCompositionTarget
));
120 aError
.Assign(nsPrintfCString(
121 "DCLayerTree(create DCompositionTarget failed %lx)", hr
));
125 hr
= mCompositionDevice
->CreateVisual(getter_AddRefs(mRootVisual
));
127 aError
.Assign(nsPrintfCString(
128 "DCLayerTree(create root DCompositionVisual failed %lx)", hr
));
133 mCompositionDevice
->CreateVisual(getter_AddRefs(mDefaultSwapChainVisual
));
135 aError
.Assign(nsPrintfCString(
136 "DCLayerTree(create swap chain DCompositionVisual failed %lx)", hr
));
140 if (gfx::gfxVars::UseWebRenderDCompVideoOverlayWin()) {
141 if (!InitializeVideoOverlaySupport()) {
142 RenderThread::Get()->HandleWebRenderError(WebRenderError::VIDEO_OVERLAY
);
145 if (!sGpuOverlayInfo
) {
146 // Set default if sGpuOverlayInfo was not set.
147 sGpuOverlayInfo
= MakeUnique
<GpuOverlayInfo
>();
150 mCompositionTarget
->SetRoot(mRootVisual
);
151 // Set interporation mode to nearest, to ensure 1:1 sampling.
152 // By default, a visual inherits the interpolation mode of the parent visual.
153 // If no visuals set the interpolation mode, the default for the entire visual
154 // tree is nearest neighbor interpolation.
155 mRootVisual
->SetBitmapInterpolationMode(
156 DCOMPOSITION_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR
);
160 bool FlagsSupportsOverlays(UINT flags
) {
161 return (flags
& (DXGI_OVERLAY_SUPPORT_FLAG_DIRECT
|
162 DXGI_OVERLAY_SUPPORT_FLAG_SCALING
));
165 // A warpper of IDXGIOutput4::CheckOverlayColorSpaceSupport()
166 bool CheckOverlayColorSpaceSupport(DXGI_FORMAT aDxgiFormat
,
167 DXGI_COLOR_SPACE_TYPE aDxgiColorSpace
,
168 RefPtr
<IDXGIOutput
> aOutput
,
169 RefPtr
<ID3D11Device
> aD3d11Device
) {
170 UINT colorSpaceSupportFlags
= 0;
171 RefPtr
<IDXGIOutput4
> output4
;
173 if (FAILED(aOutput
->QueryInterface(__uuidof(IDXGIOutput4
),
174 getter_AddRefs(output4
)))) {
178 if (FAILED(output4
->CheckOverlayColorSpaceSupport(
179 aDxgiFormat
, aDxgiColorSpace
, aD3d11Device
,
180 &colorSpaceSupportFlags
))) {
184 return (colorSpaceSupportFlags
&
185 DXGI_OVERLAY_COLOR_SPACE_SUPPORT_FLAG_PRESENT
);
188 bool DCLayerTree::InitializeVideoOverlaySupport() {
189 MOZ_ASSERT(IsWin10AnniversaryUpdateOrLater());
193 hr
= mDevice
->QueryInterface(
194 (ID3D11VideoDevice
**)getter_AddRefs(mVideoDevice
));
196 gfxCriticalNote
<< "Failed to get D3D11VideoDevice: " << gfx::hexa(hr
);
201 mCtx
->QueryInterface((ID3D11VideoContext
**)getter_AddRefs(mVideoContext
));
203 gfxCriticalNote
<< "Failed to get D3D11VideoContext: " << gfx::hexa(hr
);
207 if (sGpuOverlayInfo
) {
211 UniquePtr
<GpuOverlayInfo
> info
= MakeUnique
<GpuOverlayInfo
>();
213 RefPtr
<IDXGIDevice
> dxgiDevice
;
214 RefPtr
<IDXGIAdapter
> adapter
;
215 mDevice
->QueryInterface((IDXGIDevice
**)getter_AddRefs(dxgiDevice
));
216 dxgiDevice
->GetAdapter(getter_AddRefs(adapter
));
220 RefPtr
<IDXGIOutput
> output
;
221 if (FAILED(adapter
->EnumOutputs(i
++, getter_AddRefs(output
)))) {
224 RefPtr
<IDXGIOutput3
> output3
;
225 if (FAILED(output
->QueryInterface(__uuidof(IDXGIOutput3
),
226 getter_AddRefs(output3
)))) {
230 output3
->CheckOverlaySupport(DXGI_FORMAT_NV12
, mDevice
,
231 &info
->mNv12OverlaySupportFlags
);
232 output3
->CheckOverlaySupport(DXGI_FORMAT_YUY2
, mDevice
,
233 &info
->mYuy2OverlaySupportFlags
);
234 output3
->CheckOverlaySupport(DXGI_FORMAT_B8G8R8A8_UNORM
, mDevice
,
235 &info
->mBgra8OverlaySupportFlags
);
236 output3
->CheckOverlaySupport(DXGI_FORMAT_R10G10B10A2_UNORM
, mDevice
,
237 &info
->mRgb10a2OverlaySupportFlags
);
239 if (FlagsSupportsOverlays(info
->mNv12OverlaySupportFlags
)) {
240 // NV12 format is preferred if it's supported.
241 info
->mOverlayFormatUsed
= DXGI_FORMAT_NV12
;
242 info
->mSupportsHardwareOverlays
= true;
245 if (!info
->mSupportsHardwareOverlays
&&
246 FlagsSupportsOverlays(info
->mYuy2OverlaySupportFlags
)) {
247 // If NV12 isn't supported, fallback to YUY2 if it's supported.
248 info
->mOverlayFormatUsed
= DXGI_FORMAT_YUY2
;
249 info
->mSupportsHardwareOverlays
= true;
252 // RGB10A2 overlay is used for displaying HDR content. In Intel's
253 // platform, RGB10A2 overlay is enabled only when
254 // DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020 is supported.
255 if (FlagsSupportsOverlays(info
->mRgb10a2OverlaySupportFlags
)) {
256 if (!CheckOverlayColorSpaceSupport(
257 DXGI_FORMAT_R10G10B10A2_UNORM
,
258 DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020
, output
, mDevice
))
259 info
->mRgb10a2OverlaySupportFlags
= 0;
262 // Early out after the first output that reports overlay support. All
263 // outputs are expected to report the same overlay support according to
264 // Microsoft's WDDM documentation:
265 // https://docs.microsoft.com/en-us/windows-hardware/drivers/display/multiplane-overlay-hardware-requirements
266 if (info
->mSupportsHardwareOverlays
) {
271 if (!StaticPrefs::gfx_webrender_dcomp_video_yuv_overlay_win_AtStartup()) {
272 info
->mOverlayFormatUsed
= DXGI_FORMAT_B8G8R8A8_UNORM
;
273 info
->mSupportsHardwareOverlays
= false;
276 info
->mSupportsOverlays
= info
->mSupportsHardwareOverlays
;
278 sGpuOverlayInfo
= std::move(info
);
280 if (auto* gpuParent
= gfx::GPUParent::GetSingleton()) {
281 gpuParent
->NotifyOverlayInfo(GetOverlayInfo());
287 DCSurface
* DCLayerTree::GetSurface(wr::NativeSurfaceId aId
) const {
288 auto surface_it
= mDCSurfaces
.find(aId
);
289 MOZ_RELEASE_ASSERT(surface_it
!= mDCSurfaces
.end());
290 return surface_it
->second
.get();
293 void DCLayerTree::SetDefaultSwapChain(IDXGISwapChain1
* aSwapChain
) {
294 LOG("DCLayerTree::SetDefaultSwapChain()");
296 mRootVisual
->AddVisual(mDefaultSwapChainVisual
, TRUE
, nullptr);
297 mDefaultSwapChainVisual
->SetContent(aSwapChain
);
298 // Default SwapChain's visual does not need linear interporation.
299 mDefaultSwapChainVisual
->SetBitmapInterpolationMode(
300 DCOMPOSITION_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR
);
301 mPendingCommit
= true;
304 void DCLayerTree::MaybeUpdateDebug() {
305 bool updated
= false;
306 updated
|= MaybeUpdateDebugCounter();
307 updated
|= MaybeUpdateDebugVisualRedrawRegions();
309 mPendingCommit
= true;
313 void DCLayerTree::MaybeCommit() {
314 if (!mPendingCommit
) {
317 mCompositionDevice
->Commit();
320 void DCLayerTree::WaitForCommitCompletion() {
321 mCompositionDevice
->WaitForCommitCompletion();
324 void DCLayerTree::DisableNativeCompositor() {
325 MOZ_ASSERT(mCurrentSurface
.isNothing());
326 MOZ_ASSERT(mCurrentLayers
.empty());
328 ReleaseNativeCompositorResources();
330 mRootVisual
->RemoveAllVisuals();
333 bool DCLayerTree::MaybeUpdateDebugCounter() {
334 bool debugCounter
= StaticPrefs::gfx_webrender_debug_dcomp_counter();
335 if (mDebugCounter
== debugCounter
) {
339 RefPtr
<IDCompositionDeviceDebug
> debugDevice
;
340 HRESULT hr
= mCompositionDevice
->QueryInterface(
341 (IDCompositionDeviceDebug
**)getter_AddRefs(debugDevice
));
347 debugDevice
->EnableDebugCounters();
349 debugDevice
->DisableDebugCounters();
352 mDebugCounter
= debugCounter
;
356 bool DCLayerTree::MaybeUpdateDebugVisualRedrawRegions() {
357 bool debugVisualRedrawRegions
=
358 StaticPrefs::gfx_webrender_debug_dcomp_redraw_regions();
359 if (mDebugVisualRedrawRegions
== debugVisualRedrawRegions
) {
363 RefPtr
<IDCompositionVisualDebug
> visualDebug
;
364 HRESULT hr
= mRootVisual
->QueryInterface(
365 (IDCompositionVisualDebug
**)getter_AddRefs(visualDebug
));
370 if (debugVisualRedrawRegions
) {
371 visualDebug
->EnableRedrawRegions();
373 visualDebug
->DisableRedrawRegions();
376 mDebugVisualRedrawRegions
= debugVisualRedrawRegions
;
380 void DCLayerTree::CompositorBeginFrame() { mCurrentFrame
++; }
382 void DCLayerTree::CompositorEndFrame() {
383 auto start
= TimeStamp::Now();
384 // Check if the visual tree of surfaces is the same as last frame.
385 bool same
= mPrevLayers
== mCurrentLayers
;
388 // If not, we need to rebuild the visual tree. Note that addition or
389 // removal of tiles no longer needs to rebuild the main visual tree
390 // here, since they are added as children of the surface visual.
391 mRootVisual
->RemoveAllVisuals();
394 for (auto it
= mCurrentLayers
.begin(); it
!= mCurrentLayers
.end(); ++it
) {
395 auto surface_it
= mDCSurfaces
.find(*it
);
396 MOZ_RELEASE_ASSERT(surface_it
!= mDCSurfaces
.end());
397 const auto surface
= surface_it
->second
.get();
398 // Ensure surface is trimmed to updated tile valid rects
399 surface
->UpdateAllocatedRect();
401 // Add surfaces in z-order they were added to the scene.
402 const auto visual
= surface
->GetVisual();
403 mRootVisual
->AddVisual(visual
, FALSE
, nullptr);
407 mPrevLayers
.swap(mCurrentLayers
);
408 mCurrentLayers
.clear();
410 mCompositionDevice
->Commit();
412 auto end
= TimeStamp::Now();
413 mozilla::Telemetry::Accumulate(mozilla::Telemetry::COMPOSITE_SWAP_TIME
,
414 (end
- start
).ToMilliseconds() * 10.);
416 // Remove any framebuffers that haven't been
417 // used in the last 60 frames.
419 // This should use nsTArray::RemoveElementsBy once
420 // CachedFrameBuffer is able to properly destroy
421 // itself in the destructor.
422 const auto gl
= GetGLContext();
423 for (uint32_t i
= 0, len
= mFrameBuffers
.Length(); i
< len
; ++i
) {
424 auto& fb
= mFrameBuffers
[i
];
425 if ((mCurrentFrame
- fb
.lastFrameUsed
) > 60) {
426 gl
->fDeleteRenderbuffers(1, &fb
.depthRboId
);
427 gl
->fDeleteFramebuffers(1, &fb
.fboId
);
428 mFrameBuffers
.UnorderedRemoveElementAt(i
);
429 --i
; // Examine the element again, if necessary.
435 void DCLayerTree::Bind(wr::NativeTileId aId
, wr::DeviceIntPoint
* aOffset
,
436 uint32_t* aFboId
, wr::DeviceIntRect aDirtyRect
,
437 wr::DeviceIntRect aValidRect
) {
438 auto surface
= GetSurface(aId
.surface_id
);
439 auto tile
= surface
->GetTile(aId
.x
, aId
.y
);
440 wr::DeviceIntPoint targetOffset
{0, 0};
442 gfx::IntRect
validRect(aValidRect
.min
.x
, aValidRect
.min
.y
, aValidRect
.width(),
443 aValidRect
.height());
444 if (!tile
->mValidRect
.IsEqualEdges(validRect
)) {
445 tile
->mValidRect
= validRect
;
446 surface
->DirtyAllocatedRect();
448 wr::DeviceIntSize tileSize
= surface
->GetTileSize();
449 RefPtr
<IDCompositionSurface
> compositionSurface
=
450 surface
->GetCompositionSurface();
451 wr::DeviceIntPoint virtualOffset
= surface
->GetVirtualOffset();
452 targetOffset
.x
= virtualOffset
.x
+ tileSize
.width
* aId
.x
;
453 targetOffset
.y
= virtualOffset
.y
+ tileSize
.height
* aId
.y
;
455 *aFboId
= CreateEGLSurfaceForCompositionSurface(
456 aDirtyRect
, aOffset
, compositionSurface
, targetOffset
);
457 mCurrentSurface
= Some(compositionSurface
);
460 void DCLayerTree::Unbind() {
461 if (mCurrentSurface
.isNothing()) {
465 RefPtr
<IDCompositionSurface
> surface
= mCurrentSurface
.ref();
469 mCurrentSurface
= Nothing();
472 void DCLayerTree::CreateSurface(wr::NativeSurfaceId aId
,
473 wr::DeviceIntPoint aVirtualOffset
,
474 wr::DeviceIntSize aTileSize
, bool aIsOpaque
) {
475 auto it
= mDCSurfaces
.find(aId
);
476 MOZ_RELEASE_ASSERT(it
== mDCSurfaces
.end());
477 if (it
!= mDCSurfaces
.end()) {
478 // DCSurface already exists.
482 // Tile size needs to be positive.
483 if (aTileSize
.width
<= 0 || aTileSize
.height
<= 0) {
484 gfxCriticalNote
<< "TileSize is not positive aId: " << wr::AsUint64(aId
)
485 << " aTileSize(" << aTileSize
.width
<< ","
486 << aTileSize
.height
<< ")";
490 MakeUnique
<DCSurface
>(aTileSize
, aVirtualOffset
, aIsOpaque
, this);
491 if (!surface
->Initialize()) {
492 gfxCriticalNote
<< "Failed to initialize DCSurface: " << wr::AsUint64(aId
);
496 mDCSurfaces
[aId
] = std::move(surface
);
499 void DCLayerTree::CreateExternalSurface(wr::NativeSurfaceId aId
,
501 auto it
= mDCSurfaces
.find(aId
);
502 MOZ_RELEASE_ASSERT(it
== mDCSurfaces
.end());
504 auto surface
= MakeUnique
<DCSurfaceSwapChain
>(aIsOpaque
, this);
505 if (!surface
->Initialize()) {
506 gfxCriticalNote
<< "Failed to initialize DCSurfaceSwapChain: "
507 << wr::AsUint64(aId
);
511 mDCSurfaces
[aId
] = std::move(surface
);
514 void DCLayerTree::DestroySurface(NativeSurfaceId aId
) {
515 auto surface_it
= mDCSurfaces
.find(aId
);
516 MOZ_RELEASE_ASSERT(surface_it
!= mDCSurfaces
.end());
517 auto surface
= surface_it
->second
.get();
519 mRootVisual
->RemoveVisual(surface
->GetVisual());
520 mDCSurfaces
.erase(surface_it
);
523 void DCLayerTree::CreateTile(wr::NativeSurfaceId aId
, int aX
, int aY
) {
524 auto surface
= GetSurface(aId
);
525 surface
->CreateTile(aX
, aY
);
528 void DCLayerTree::DestroyTile(wr::NativeSurfaceId aId
, int aX
, int aY
) {
529 auto surface
= GetSurface(aId
);
530 surface
->DestroyTile(aX
, aY
);
533 void DCLayerTree::AttachExternalImage(wr::NativeSurfaceId aId
,
534 wr::ExternalImageId aExternalImage
) {
535 auto surface_it
= mDCSurfaces
.find(aId
);
536 MOZ_RELEASE_ASSERT(surface_it
!= mDCSurfaces
.end());
537 auto* surfaceSc
= surface_it
->second
->AsDCSurfaceSwapChain();
538 MOZ_RELEASE_ASSERT(surfaceSc
);
540 surfaceSc
->AttachExternalImage(aExternalImage
);
543 template <typename T
>
544 static inline D2D1_RECT_F
D2DRect(const T
& aRect
) {
545 return D2D1::RectF(aRect
.X(), aRect
.Y(), aRect
.XMost(), aRect
.YMost());
548 static inline D2D1_MATRIX_3X2_F
D2DMatrix(const gfx::Matrix
& aTransform
) {
549 return D2D1::Matrix3x2F(aTransform
._11
, aTransform
._12
, aTransform
._21
,
550 aTransform
._22
, aTransform
._31
, aTransform
._32
);
553 void DCLayerTree::AddSurface(wr::NativeSurfaceId aId
,
554 const wr::CompositorSurfaceTransform
& aTransform
,
555 wr::DeviceIntRect aClipRect
,
556 wr::ImageRendering aImageRendering
) {
557 auto it
= mDCSurfaces
.find(aId
);
558 MOZ_RELEASE_ASSERT(it
!= mDCSurfaces
.end());
559 const auto surface
= it
->second
.get();
560 const auto visual
= surface
->GetVisual();
562 wr::DeviceIntPoint virtualOffset
= surface
->GetVirtualOffset();
564 gfx::Matrix
transform(aTransform
.m11
, aTransform
.m12
, aTransform
.m21
,
565 aTransform
.m22
, aTransform
.m41
, aTransform
.m42
);
567 auto* surfaceSc
= surface
->AsDCSurfaceSwapChain();
569 const auto presentationTransform
= surfaceSc
->EnsurePresented(transform
);
570 if (presentationTransform
) {
571 transform
= *presentationTransform
;
572 } // else EnsurePresented failed, just limp along?
575 transform
.PreTranslate(-virtualOffset
.x
, -virtualOffset
.y
);
577 // The DirectComposition API applies clipping *before* any transforms/offset,
578 // whereas we want the clip applied after.
579 // Right now, we only support rectilinear transforms, and then we transform
580 // our clip into pre-transform coordinate space for it to be applied there.
581 // DirectComposition does have an option for pre-transform clipping, if you
582 // create an explicit IDCompositionEffectGroup object and set a 3D transform
583 // on that. I suspect that will perform worse though, so we should only do
584 // that for complex transforms (which are never provided right now).
585 MOZ_ASSERT(transform
.IsRectilinear());
586 gfx::Rect clip
= transform
.Inverse().TransformBounds(gfx::Rect(
587 aClipRect
.min
.x
, aClipRect
.min
.y
, aClipRect
.width(), aClipRect
.height()));
588 // Set the clip rect - converting from world space to the pre-offset space
589 // that DC requires for rectangle clips.
590 visual
->SetClip(D2DRect(clip
));
592 // TODO: The input matrix is a 4x4, but we only support a 3x2 at
593 // the D3D API level (unless we QI to IDCompositionVisual3, which might
594 // not be available?).
595 // Should we assert here, or restrict at the WR API level.
596 visual
->SetTransform(D2DMatrix(transform
));
598 if (aImageRendering
== wr::ImageRendering::Auto
) {
599 visual
->SetBitmapInterpolationMode(
600 DCOMPOSITION_BITMAP_INTERPOLATION_MODE_LINEAR
);
602 visual
->SetBitmapInterpolationMode(
603 DCOMPOSITION_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR
);
606 mCurrentLayers
.push_back(aId
);
609 GLuint
DCLayerTree::GetOrCreateFbo(int aWidth
, int aHeight
) {
610 const auto gl
= GetGLContext();
613 // Check if we have a cached FBO with matching dimensions
614 for (auto it
= mFrameBuffers
.begin(); it
!= mFrameBuffers
.end(); ++it
) {
615 if (it
->width
== aWidth
&& it
->height
== aHeight
) {
617 it
->lastFrameUsed
= mCurrentFrame
;
622 // If not, create a new FBO with attached depth buffer
624 // Create the depth buffer
626 gl
->fGenRenderbuffers(1, &depthRboId
);
627 gl
->fBindRenderbuffer(LOCAL_GL_RENDERBUFFER
, depthRboId
);
628 gl
->fRenderbufferStorage(LOCAL_GL_RENDERBUFFER
, LOCAL_GL_DEPTH_COMPONENT24
,
631 // Create the framebuffer and attach the depth buffer to it
632 gl
->fGenFramebuffers(1, &fboId
);
633 gl
->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER
, fboId
);
634 gl
->fFramebufferRenderbuffer(LOCAL_GL_DRAW_FRAMEBUFFER
,
635 LOCAL_GL_DEPTH_ATTACHMENT
,
636 LOCAL_GL_RENDERBUFFER
, depthRboId
);
638 // Store this in the cache for future calls.
639 // TODO(gw): Maybe we should periodically scan this list and remove old
641 // haven't been used for some time?
642 DCLayerTree::CachedFrameBuffer frame_buffer_info
;
643 frame_buffer_info
.width
= aWidth
;
644 frame_buffer_info
.height
= aHeight
;
645 frame_buffer_info
.fboId
= fboId
;
646 frame_buffer_info
.depthRboId
= depthRboId
;
647 frame_buffer_info
.lastFrameUsed
= mCurrentFrame
;
648 mFrameBuffers
.AppendElement(frame_buffer_info
);
654 bool DCLayerTree::EnsureVideoProcessorAtLeast(const gfx::IntSize
& aInputSize
,
655 const gfx::IntSize
& aOutputSize
) {
658 if (!mVideoDevice
|| !mVideoContext
) {
662 if (mVideoProcessor
&& (aInputSize
<= mVideoInputSize
) &&
663 (aOutputSize
<= mVideoOutputSize
)) {
667 mVideoProcessor
= nullptr;
668 mVideoProcessorEnumerator
= nullptr;
670 D3D11_VIDEO_PROCESSOR_CONTENT_DESC desc
= {};
671 desc
.InputFrameFormat
= D3D11_VIDEO_FRAME_FORMAT_PROGRESSIVE
;
672 desc
.InputFrameRate
.Numerator
= 60;
673 desc
.InputFrameRate
.Denominator
= 1;
674 desc
.InputWidth
= aInputSize
.width
;
675 desc
.InputHeight
= aInputSize
.height
;
676 desc
.OutputFrameRate
.Numerator
= 60;
677 desc
.OutputFrameRate
.Denominator
= 1;
678 desc
.OutputWidth
= aOutputSize
.width
;
679 desc
.OutputHeight
= aOutputSize
.height
;
680 desc
.Usage
= D3D11_VIDEO_USAGE_PLAYBACK_NORMAL
;
682 hr
= mVideoDevice
->CreateVideoProcessorEnumerator(
683 &desc
, getter_AddRefs(mVideoProcessorEnumerator
));
685 gfxCriticalNote
<< "Failed to create VideoProcessorEnumerator: "
690 hr
= mVideoDevice
->CreateVideoProcessor(mVideoProcessorEnumerator
, 0,
691 getter_AddRefs(mVideoProcessor
));
693 mVideoProcessor
= nullptr;
694 mVideoProcessorEnumerator
= nullptr;
695 gfxCriticalNote
<< "Failed to create VideoProcessor: " << gfx::hexa(hr
);
699 // Reduce power cosumption
700 // By default, the driver might perform certain processing tasks automatically
701 mVideoContext
->VideoProcessorSetStreamAutoProcessingMode(mVideoProcessor
, 0,
704 mVideoInputSize
= aInputSize
;
705 mVideoOutputSize
= aOutputSize
;
710 bool DCLayerTree::SupportsHardwareOverlays() {
711 return sGpuOverlayInfo
->mSupportsHardwareOverlays
;
714 DXGI_FORMAT
DCLayerTree::GetOverlayFormatForSDR() {
715 return sGpuOverlayInfo
->mOverlayFormatUsed
;
718 static layers::OverlaySupportType
FlagsToOverlaySupportType(
719 UINT aFlags
, bool aSoftwareOverlaySupported
) {
720 if (aFlags
& DXGI_OVERLAY_SUPPORT_FLAG_SCALING
) {
721 return layers::OverlaySupportType::Scaling
;
723 if (aFlags
& DXGI_OVERLAY_SUPPORT_FLAG_DIRECT
) {
724 return layers::OverlaySupportType::Direct
;
726 if (aSoftwareOverlaySupported
) {
727 return layers::OverlaySupportType::Software
;
729 return layers::OverlaySupportType::None
;
732 layers::OverlayInfo
DCLayerTree::GetOverlayInfo() {
733 layers::OverlayInfo info
;
735 info
.mSupportsOverlays
= sGpuOverlayInfo
->mSupportsHardwareOverlays
;
737 FlagsToOverlaySupportType(sGpuOverlayInfo
->mNv12OverlaySupportFlags
,
738 /* aSoftwareOverlaySupported */ false);
740 FlagsToOverlaySupportType(sGpuOverlayInfo
->mYuy2OverlaySupportFlags
,
741 /* aSoftwareOverlaySupported */ false);
743 FlagsToOverlaySupportType(sGpuOverlayInfo
->mBgra8OverlaySupportFlags
,
744 /* aSoftwareOverlaySupported */ true);
745 info
.mRgb10a2Overlay
=
746 FlagsToOverlaySupportType(sGpuOverlayInfo
->mRgb10a2OverlaySupportFlags
,
747 /* aSoftwareOverlaySupported */ false);
752 DCSurface::DCSurface(wr::DeviceIntSize aTileSize
,
753 wr::DeviceIntPoint aVirtualOffset
, bool aIsOpaque
,
754 DCLayerTree
* aDCLayerTree
)
755 : mDCLayerTree(aDCLayerTree
),
756 mTileSize(aTileSize
),
757 mIsOpaque(aIsOpaque
),
758 mAllocatedRectDirty(true),
759 mVirtualOffset(aVirtualOffset
) {}
761 DCSurface::~DCSurface() {}
763 bool DCSurface::Initialize() {
765 const auto dCompDevice
= mDCLayerTree
->GetCompositionDevice();
766 hr
= dCompDevice
->CreateVisual(getter_AddRefs(mVisual
));
768 gfxCriticalNote
<< "Failed to create DCompositionVisual: " << gfx::hexa(hr
);
772 DXGI_ALPHA_MODE alpha_mode
=
773 mIsOpaque
? DXGI_ALPHA_MODE_IGNORE
: DXGI_ALPHA_MODE_PREMULTIPLIED
;
775 hr
= dCompDevice
->CreateVirtualSurface(
776 VIRTUAL_SURFACE_SIZE
, VIRTUAL_SURFACE_SIZE
, DXGI_FORMAT_R8G8B8A8_UNORM
,
777 alpha_mode
, getter_AddRefs(mVirtualSurface
));
778 MOZ_ASSERT(SUCCEEDED(hr
));
780 // Bind the surface memory to this visual
781 hr
= mVisual
->SetContent(mVirtualSurface
);
782 MOZ_ASSERT(SUCCEEDED(hr
));
787 void DCSurface::CreateTile(int aX
, int aY
) {
789 MOZ_RELEASE_ASSERT(mDCTiles
.find(key
) == mDCTiles
.end());
791 auto tile
= MakeUnique
<DCTile
>(mDCLayerTree
);
792 if (!tile
->Initialize(aX
, aY
, mTileSize
, mIsOpaque
)) {
793 gfxCriticalNote
<< "Failed to initialize DCTile: " << aX
<< aY
;
797 mAllocatedRectDirty
= true;
799 mDCTiles
[key
] = std::move(tile
);
802 void DCSurface::DestroyTile(int aX
, int aY
) {
804 mAllocatedRectDirty
= true;
808 void DCSurface::DirtyAllocatedRect() { mAllocatedRectDirty
= true; }
810 void DCSurface::UpdateAllocatedRect() {
811 if (mAllocatedRectDirty
) {
812 // The virtual surface may have holes in it (for example, an empty tile
813 // that has no primitives). Instead of trimming to a single bounding
814 // rect, supply the rect of each valid tile to handle this case.
815 std::vector
<RECT
> validRects
;
817 for (auto it
= mDCTiles
.begin(); it
!= mDCTiles
.end(); ++it
) {
818 auto tile
= GetTile(it
->first
.mX
, it
->first
.mY
);
821 rect
.left
= (LONG
)(mVirtualOffset
.x
+ it
->first
.mX
* mTileSize
.width
+
823 rect
.top
= (LONG
)(mVirtualOffset
.y
+ it
->first
.mY
* mTileSize
.height
+
825 rect
.right
= rect
.left
+ tile
->mValidRect
.width
;
826 rect
.bottom
= rect
.top
+ tile
->mValidRect
.height
;
828 validRects
.push_back(rect
);
831 mVirtualSurface
->Trim(validRects
.data(), validRects
.size());
832 mAllocatedRectDirty
= false;
836 DCTile
* DCSurface::GetTile(int aX
, int aY
) const {
838 auto tile_it
= mDCTiles
.find(key
);
839 MOZ_RELEASE_ASSERT(tile_it
!= mDCTiles
.end());
840 return tile_it
->second
.get();
844 // DCSurfaceSwapChain
846 DCSurfaceSwapChain::DCSurfaceSwapChain(bool aIsOpaque
,
847 DCLayerTree
* aDCLayerTree
)
848 : DCSurface(wr::DeviceIntSize
{}, wr::DeviceIntPoint
{}, aIsOpaque
,
850 if (mDCLayerTree
->SupportsHardwareOverlays()) {
851 mOverlayFormat
= Some(mDCLayerTree
->GetOverlayFormatForSDR());
855 bool IsYuv(const DXGI_FORMAT aFormat
) {
856 if (aFormat
== DXGI_FORMAT_NV12
|| aFormat
== DXGI_FORMAT_YUY2
) {
862 bool IsYuv(const gfx::SurfaceFormat aFormat
) {
864 case gfx::SurfaceFormat::NV12
:
865 case gfx::SurfaceFormat::P010
:
866 case gfx::SurfaceFormat::P016
:
867 case gfx::SurfaceFormat::YUV
:
868 case gfx::SurfaceFormat::YUV422
:
871 case gfx::SurfaceFormat::B8G8R8A8
:
872 case gfx::SurfaceFormat::B8G8R8X8
:
873 case gfx::SurfaceFormat::R8G8B8A8
:
874 case gfx::SurfaceFormat::R8G8B8X8
:
875 case gfx::SurfaceFormat::A8R8G8B8
:
876 case gfx::SurfaceFormat::X8R8G8B8
:
878 case gfx::SurfaceFormat::R8G8B8
:
879 case gfx::SurfaceFormat::B8G8R8
:
881 case gfx::SurfaceFormat::R5G6B5_UINT16
:
882 case gfx::SurfaceFormat::A8
:
883 case gfx::SurfaceFormat::A16
:
884 case gfx::SurfaceFormat::R8G8
:
885 case gfx::SurfaceFormat::R16G16
:
887 case gfx::SurfaceFormat::HSV
:
888 case gfx::SurfaceFormat::Lab
:
889 case gfx::SurfaceFormat::Depth
:
890 case gfx::SurfaceFormat::UNKNOWN
:
893 MOZ_ASSERT_UNREACHABLE();
896 void DCSurfaceSwapChain::AttachExternalImage(
897 wr::ExternalImageId aExternalImage
) {
898 RenderTextureHost
* texture
=
899 RenderThread::Get()->GetRenderTexture(aExternalImage
);
900 MOZ_RELEASE_ASSERT(texture
);
902 const auto textureDxgi
= texture
->AsRenderDXGITextureHost();
904 gfxCriticalNote
<< "Unsupported RenderTexture for overlay: "
905 << gfx::hexa(texture
);
909 if (mSrc
&& mSrc
->texture
== textureDxgi
) {
914 src
.texture
= textureDxgi
;
915 src
.format
= src
.texture
->GetFormat();
916 src
.size
= src
.texture
->GetSize(0);
918 src
.texture
->mColorSpace
,
919 IsYuv(src
.format
) ? Some(src
.texture
->mColorRange
) : Nothing(),
921 src
.alphaMode
= DXGI_ALPHA_MODE_IGNORE
;
922 if (gfx::Info(src
.format
).value().hasAlpha
) {
923 src
.alphaMode
= DXGI_ALPHA_MODE_PREMULTIPLIED
;
931 Maybe
<DCSurfaceSwapChain::Dest
> CreateSwapChain(ID3D11Device
&, gfx::IntSize
,
932 DXGI_FORMAT
, DXGI_ALPHA_MODE
,
933 DXGI_COLOR_SPACE_TYPE
);
937 static Maybe
<DXGI_COLOR_SPACE_TYPE
> ExactDXGIColorSpace(
938 const CspaceAndRange
& space
) {
939 switch (space
.space
) {
940 case gfx::ColorSpace2::BT601_525
:
941 if (!space
.yuvRange
) {
943 } else if (*space
.yuvRange
== gfx::ColorRange::FULL
) {
944 return Some(DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P601
);
946 return Some(DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P601
);
948 case gfx::ColorSpace2::UNKNOWN
:
949 case gfx::ColorSpace2::SRGB
: // Gamma ~2.2
950 if (!space
.yuvRange
) {
951 return Some(DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709
);
952 } else if (*space
.yuvRange
== gfx::ColorRange::FULL
) {
953 return Some(DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P709
);
955 return Some(DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P709
);
957 case gfx::ColorSpace2::BT709
: // Gamma ~2.4
958 if (!space
.yuvRange
) {
959 // This should ideally be G24, but that only exists for STUDIO not FULL.
960 return Some(DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709
);
961 } else if (*space
.yuvRange
== gfx::ColorRange::FULL
) {
962 // TODO return Some(DXGI_COLOR_SPACE_YCBCR_FULL_G24_LEFT_P709);
963 return Some(DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P709
);
965 // TODO return Some(DXGI_COLOR_SPACE_YCBCR_STUDIO_G24_LEFT_P709);
966 return Some(DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P709
);
968 case gfx::ColorSpace2::BT2020
:
969 if (!space
.yuvRange
) {
970 return Some(DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P2020
);
971 } else if (*space
.yuvRange
== gfx::ColorRange::FULL
) {
972 // XXX Add SMPTEST2084 handling. HDR content is not handled yet by
974 return Some(DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P2020
);
976 return Some(DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P2020
);
978 case gfx::ColorSpace2::DISPLAY_P3
:
981 MOZ_ASSERT_UNREACHABLE();
986 color::ColorspaceDesc
ToColorspaceDesc(const CspaceAndRange
& space
) {
987 color::ColorspaceDesc ret
= {};
988 switch (space
.space
) {
989 case gfx::ColorSpace2::UNKNOWN
:
990 case gfx::ColorSpace2::SRGB
:
991 ret
.chrom
= color::Chromaticities::Srgb();
992 ret
.tf
= {color::PiecewiseGammaDesc::Srgb()};
993 MOZ_ASSERT(!space
.yuvRange
);
996 case gfx::ColorSpace2::DISPLAY_P3
:
997 ret
.chrom
= color::Chromaticities::DisplayP3();
998 ret
.tf
= {color::PiecewiseGammaDesc::DisplayP3()};
999 MOZ_ASSERT(!space
.yuvRange
);
1002 case gfx::ColorSpace2::BT601_525
:
1003 ret
.chrom
= color::Chromaticities::Rec601_525_Ntsc();
1004 ret
.tf
= {color::PiecewiseGammaDesc::Rec709()};
1005 if (space
.yuvRange
) {
1007 {color::YuvLumaCoeffs::Rec709(), color::YcbcrDesc::Narrow8()}};
1008 if (*space
.yuvRange
== gfx::ColorRange::FULL
) {
1009 ret
.yuv
->ycbcr
= color::YcbcrDesc::Full8();
1014 case gfx::ColorSpace2::BT709
:
1015 ret
.chrom
= color::Chromaticities::Rec709();
1016 ret
.tf
= {color::PiecewiseGammaDesc::Rec709()};
1017 if (space
.yuvRange
) {
1019 {color::YuvLumaCoeffs::Rec709(), color::YcbcrDesc::Narrow8()}};
1020 if (*space
.yuvRange
== gfx::ColorRange::FULL
) {
1021 ret
.yuv
->ycbcr
= color::YcbcrDesc::Full8();
1026 case gfx::ColorSpace2::BT2020
:
1027 ret
.chrom
= color::Chromaticities::Rec2020();
1028 ret
.tf
= {color::PiecewiseGammaDesc::Rec2020_10bit()};
1029 if (space
.yuvRange
) {
1031 {color::YuvLumaCoeffs::Rec709(), color::YcbcrDesc::Narrow8()}};
1032 if (*space
.yuvRange
== gfx::ColorRange::FULL
) {
1033 ret
.yuv
->ycbcr
= color::YcbcrDesc::Full8();
1038 MOZ_ASSERT_UNREACHABLE();
1043 static CspaceTransformPlan
ChooseCspaceTransformPlan(
1044 const CspaceAndRange
& srcSpace
) {
1045 // Unfortunately, it looks like the no-op
1046 // DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709
1047 // => DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709
1048 // transform mis-translates colors if you ask VideoProcessor to resize.
1049 // (jgilbert's RTX 3070 machine "osiris")
1050 // Absent more investigation, let's avoid VP with non-YUV sources for now.
1051 const auto cmsMode
= GfxColorManagementMode();
1052 const bool doColorManagement
= cmsMode
!= CMSMode::Off
;
1053 if (srcSpace
.yuvRange
&& doColorManagement
) {
1054 const auto exactDxgiSpace
= ExactDXGIColorSpace(srcSpace
);
1055 if (exactDxgiSpace
) {
1056 auto plan
= CspaceTransformPlan::WithVideoProcessor
{};
1057 plan
.srcSpace
= *exactDxgiSpace
;
1058 if (srcSpace
.yuvRange
) {
1059 plan
.dstYuvSpace
= Some(plan
.srcSpace
);
1060 plan
.dstRgbSpace
= DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709
;
1062 plan
.dstRgbSpace
= plan
.srcSpace
;
1064 return {Some(plan
), {}};
1068 auto plan
= CspaceTransformPlan::WithGLBlitHelper
{
1069 ToColorspaceDesc(srcSpace
),
1070 {color::Chromaticities::Srgb(), {color::PiecewiseGammaDesc::Srgb()}},
1071 DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709
,
1072 DXGI_FORMAT_B8G8R8A8_UNORM
,
1074 switch (srcSpace
.space
) {
1075 case gfx::ColorSpace2::SRGB
:
1076 case gfx::ColorSpace2::BT601_525
:
1077 case gfx::ColorSpace2::BT709
:
1078 case gfx::ColorSpace2::UNKNOWN
:
1080 case gfx::ColorSpace2::BT2020
:
1081 case gfx::ColorSpace2::DISPLAY_P3
:
1082 // We know our src cspace, and we need to pick a dest cspace.
1083 // The technically-correct thing to do is pick scrgb rgb16f, but
1084 // bandwidth! One option for us is rec2020, which is huge, if the banding
1085 // is acceptable. (We could even use rgb10 for this) EXCEPT,
1086 // display-p3(1,0,0) red is rec2020(0.869 0.175 -0.005). At cost of
1087 // clipping red by up to 0.5%, it's worth considering. Do scrgb for now
1090 // Let's do DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P2020 +
1091 // DXGI_FORMAT_R10G10B10A2_UNORM
1093 // Rec2020 g2.2 rgb10
1095 {color::Chromaticities::Rec2020(),
1096 {color::PiecewiseGammaDesc::Srgb()}},
1097 DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P2020
, // Note that this is srgb
1099 DXGI_FORMAT_R10G10B10A2_UNORM
,
1102 // Actually, that doesn't work, so use scRGB g1.0 rgb16f
1104 {color::Chromaticities::Rec709(), {}},
1105 DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709
, // scRGB
1106 DXGI_FORMAT_R16G16B16A16_FLOAT
,
1110 if (!doColorManagement
) {
1111 plan
.dstSpace
= plan
.srcSpace
;
1112 plan
.dstSpace
.yuv
= {};
1115 return {{}, Some(plan
)};
1120 Maybe
<gfx::Matrix
> DCSurfaceSwapChain::EnsurePresented(
1121 const gfx::Matrix
& aTransform
) {
1122 MOZ_RELEASE_ASSERT(mSrc
);
1123 const auto& srcSize
= mSrc
->size
;
1127 auto dstSize
= srcSize
;
1128 auto presentationTransform
= aTransform
;
1130 // When video is rendered to axis aligned integer rectangle, video scaling
1131 // should be done by stretching in the VideoProcessor.
1132 // Sotaro has observed a reduction in gpu queue tasks by doing scaling
1133 // ourselves (via VideoProcessor) instead of having DComp handle it.
1134 if (StaticPrefs::gfx_webrender_dcomp_video_vp_scaling_win_AtStartup() &&
1135 aTransform
.PreservesAxisAlignedRectangles()) {
1136 const auto absScales
=
1137 aTransform
.ScaleFactors(); // E.g. [2, 0, 0, -2] => [2, 2]
1138 const auto presentationTransformUnscaled
=
1139 presentationTransform
.Copy().PreScale(1 / absScales
.xScale
,
1140 1 / absScales
.yScale
);
1141 const auto dstSizeScaled
=
1142 gfx::IntSize::Round(gfx::Size(dstSize
) * aTransform
.ScaleFactors());
1143 const auto presentSizeOld
=
1144 presentationTransform
.TransformSize(gfx::Size(dstSize
));
1145 const auto presentSizeNew
=
1146 presentationTransformUnscaled
.TransformSize(gfx::Size(dstSizeScaled
));
1147 if (gfx::FuzzyEqual(presentSizeNew
.width
, presentSizeOld
.width
, 0.1f
) &&
1148 gfx::FuzzyEqual(presentSizeNew
.height
, presentSizeOld
.height
, 0.1f
)) {
1149 dstSize
= dstSizeScaled
;
1150 presentationTransform
= presentationTransformUnscaled
;
1154 // 4:2:2 subsampled formats like YUY2 must have an even width, and 4:2:0
1155 // subsampled formats like NV12 must have an even width and height.
1156 // And we should be able to pad width and height because we clip the Visual to
1157 // the unpadded rect. Just do this unconditionally.
1158 if (dstSize
.width
% 2 == 1) {
1161 if (dstSize
.height
% 2 == 1) {
1162 dstSize
.height
+= 1;
1167 if (mDest
&& mDest
->dest
->size
!= dstSize
) {
1170 if (mDest
&& mDest
->srcSpace
!= mSrc
->space
) {
1176 // Ok, we have some options:
1177 // * ID3D11VideoProcessor can change between colorspaces that D3D knows about.
1178 // * But, sometimes VideoProcessor can output YUV, and sometimes it needs
1179 // to output RGB.[1] And we can only find out by trying it.
1180 // * Otherwise, we need to do it ourselves, via GLBlitHelper.
1182 // GLBlitHelper will always work. However, we probably want to prefer to use
1183 // ID3D11VideoProcessor where we can, as an optimization.
1186 // > We use VideoProcessor for scaling reducing GPU usage.
1187 // > "Video Processing" in Windows Task Manager showed that scaling by
1188 // > VideoProcessor used less cpu than just direct composition.
1190 // [1]: KG: I bet whenever we need a colorspace transform, we need to pull out
1191 // to RGB. Otherwise we might need to YUV1->RGB1->RGB2->YUV2, which is
1192 // maybe-wasteful. Maybe it's possible if everything is a linear
1193 // transform to multiply it all into one matrix? Gamma translation is
1194 // non-linear though, and those cannot be a matrix.
1196 // So, in order, try:
1197 // * VideoProcessor w/ YUV output
1198 // * VideoProcessor w/ RGB output
1199 // * GLBlitHelper (RGB output)
1200 // Because these Src->Dest differently, we need to be stateful.
1201 // However, if re-using the previous state fails, we can try from the top of
1204 const auto CallBlit
= [&]() {
1205 if (mDest
->plan
.videoProcessor
) {
1206 return CallVideoProcessorBlt();
1208 MOZ_RELEASE_ASSERT(mDest
->plan
.blitHelper
);
1209 return CallBlitHelper();
1212 bool needsPresent
= mSrc
->needsPresent
;
1213 if (!mDest
|| mDest
->needsPresent
) {
1214 needsPresent
= true;
1218 // Reuse previous method?
1227 mDest
->srcSpace
= mSrc
->space
;
1228 mDest
->plan
= ChooseCspaceTransformPlan(mDest
->srcSpace
);
1230 const auto device
= mDCLayerTree
->GetDevice();
1231 if (mDest
->plan
.videoProcessor
) {
1232 const auto& plan
= *mDest
->plan
.videoProcessor
;
1233 if (mOverlayFormat
&& plan
.dstYuvSpace
) {
1234 mDest
->dest
= CreateSwapChain(*device
, dstSize
, *mOverlayFormat
,
1235 mSrc
->alphaMode
, *plan
.dstYuvSpace
);
1236 // We need to check if this works. If it does, we're going to
1237 // immediately redundently call this again below, but that's ok.
1238 if (!CallVideoProcessorBlt()) {
1239 mDest
->dest
.reset();
1244 CreateSwapChain(*device
, dstSize
, DXGI_FORMAT_B8G8R8A8_UNORM
,
1245 mSrc
->alphaMode
, plan
.dstRgbSpace
);
1247 } else if (mDest
->plan
.blitHelper
) {
1248 const auto& plan
= *mDest
->plan
.blitHelper
;
1249 mDest
->dest
= CreateSwapChain(*device
, dstSize
, plan
.dstDxgiFormat
,
1250 mSrc
->alphaMode
, plan
.dstDxgiSpace
);
1252 MOZ_ASSERT(mDest
->dest
);
1253 mVisual
->SetContent(mDest
->dest
->swapChain
);
1257 RenderThread::Get()->NotifyWebRenderError(
1258 wr::WebRenderError::VIDEO_OVERLAY
);
1262 const auto hr
= mDest
->dest
->swapChain
->Present(0, 0);
1264 gfxCriticalNoteOnce
<< "video Present failed: " << gfx::hexa(hr
);
1267 mSrc
->needsPresent
= false;
1268 mDest
->needsPresent
= false;
1273 return Some(presentationTransform
);
1276 static Maybe
<DCSurfaceSwapChain::Dest
> CreateSwapChain(
1277 ID3D11Device
& device
, const gfx::IntSize aSize
, const DXGI_FORMAT aFormat
,
1278 const DXGI_ALPHA_MODE aAlphaMode
, const DXGI_COLOR_SPACE_TYPE aColorSpace
) {
1279 auto swapChain
= DCSurfaceSwapChain::Dest();
1280 swapChain
.size
= aSize
;
1281 swapChain
.format
= aFormat
;
1282 swapChain
.space
= aColorSpace
;
1284 RefPtr
<IDXGIDevice2
> dxgiDevice
;
1285 device
.QueryInterface((IDXGIDevice2
**)getter_AddRefs(dxgiDevice
));
1287 RefPtr
<IDXGIFactoryMedia
> dxgiFactoryMedia
;
1289 RefPtr
<IDXGIAdapter
> adapter
;
1290 dxgiDevice
->GetAdapter(getter_AddRefs(adapter
));
1292 IID_PPV_ARGS((IDXGIFactoryMedia
**)getter_AddRefs(dxgiFactoryMedia
)));
1294 RefPtr
<IDXGIFactory2
> dxgiFactory2
;
1296 RefPtr
<IDXGIAdapter
> adapter
;
1297 dxgiDevice
->GetAdapter(getter_AddRefs(adapter
));
1299 IID_PPV_ARGS((IDXGIFactory2
**)getter_AddRefs(dxgiFactory2
)));
1302 swapChain
.swapChainSurfaceHandle
=
1303 MakeUnique
<RaiiHANDLE
>(gfx::DeviceManagerDx::CreateDCompSurfaceHandle());
1304 if (!*swapChain
.swapChainSurfaceHandle
) {
1305 gfxCriticalNote
<< "Failed to create DCompSurfaceHandle";
1310 DXGI_SWAP_CHAIN_DESC1 desc
= {};
1311 desc
.Width
= swapChain
.size
.width
;
1312 desc
.Height
= swapChain
.size
.height
;
1313 desc
.Format
= swapChain
.format
;
1314 desc
.Stereo
= FALSE
;
1315 desc
.SampleDesc
.Count
= 1;
1316 desc
.BufferCount
= 2;
1317 desc
.BufferUsage
= DXGI_USAGE_RENDER_TARGET_OUTPUT
;
1318 desc
.Scaling
= DXGI_SCALING_STRETCH
;
1319 desc
.SwapEffect
= DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL
;
1320 desc
.Flags
= DXGI_SWAP_CHAIN_FLAG_FULLSCREEN_VIDEO
;
1321 if (IsYuv(desc
.Format
)) {
1322 desc
.Flags
|= DXGI_SWAP_CHAIN_FLAG_YUV_VIDEO
;
1324 desc
.AlphaMode
= aAlphaMode
; // DXGI_ALPHA_MODE_IGNORE;
1326 RefPtr
<IDXGISwapChain1
> swapChain1
;
1328 bool useSurfaceHandle
= true;
1329 if (useSurfaceHandle
) {
1330 hr
= dxgiFactoryMedia
->CreateSwapChainForCompositionSurfaceHandle(
1331 &device
, *swapChain
.swapChainSurfaceHandle
, &desc
, nullptr,
1332 getter_AddRefs(swapChain1
));
1334 // CreateSwapChainForComposition does not support
1335 // e.g. DXGI_SWAP_CHAIN_FLAG_YUV_VIDEO.
1336 MOZ_ASSERT(!desc
.Flags
);
1337 hr
= dxgiFactory2
->CreateSwapChainForComposition(
1338 &device
, &desc
, nullptr, getter_AddRefs(swapChain1
));
1341 gfxCriticalNote
<< "Failed to create output SwapChain: " << gfx::hexa(hr
)
1342 << " " << swapChain
.size
;
1346 swapChain1
->QueryInterface(
1347 static_cast<IDXGISwapChain3
**>(getter_AddRefs(swapChain
.swapChain
)));
1348 if (!swapChain
.swapChain
) {
1349 gfxCriticalNote
<< "Failed to get IDXGISwapChain3";
1353 const auto cmsMode
= GfxColorManagementMode();
1354 const bool doColorManagement
= cmsMode
!= CMSMode::Off
;
1355 if (doColorManagement
) {
1356 hr
= swapChain
.swapChain
->SetColorSpace1(aColorSpace
);
1358 gfxCriticalNote
<< "SetColorSpace1 failed: " << gfx::hexa(hr
);
1364 return Some(std::move(swapChain
));
1367 bool DCSurfaceSwapChain::CallVideoProcessorBlt() const {
1368 MOZ_RELEASE_ASSERT(mSrc
);
1369 MOZ_RELEASE_ASSERT(mDest
);
1370 MOZ_RELEASE_ASSERT(mDest
->dest
);
1373 const auto videoDevice
= mDCLayerTree
->GetVideoDevice();
1374 const auto videoContext
= mDCLayerTree
->GetVideoContext();
1375 const auto& texture
= mSrc
->texture
;
1377 if (!mDCLayerTree
->EnsureVideoProcessorAtLeast(mSrc
->size
,
1378 mDest
->dest
->size
)) {
1379 gfxCriticalNote
<< "EnsureVideoProcessor Failed";
1382 const auto videoProcessor
= mDCLayerTree
->GetVideoProcessor();
1383 const auto videoProcessorEnumerator
=
1384 mDCLayerTree
->GetVideoProcessorEnumerator();
1390 RefPtr
<ID3D11VideoContext1
> videoContext1
;
1391 videoContext
->QueryInterface(
1392 (ID3D11VideoContext1
**)getter_AddRefs(videoContext1
));
1393 if (!videoContext1
) {
1394 gfxCriticalNote
<< "Failed to get ID3D11VideoContext1";
1397 const auto& plan
= *mDest
->plan
.videoProcessor
;
1398 videoContext1
->VideoProcessorSetStreamColorSpace1(videoProcessor
, 0,
1400 videoContext1
->VideoProcessorSetOutputColorSpace1(videoProcessor
,
1401 mDest
->dest
->space
);
1407 RefPtr
<ID3D11Texture2D
> texture2D
= texture
->GetD3D11Texture2DWithGL();
1409 gfxCriticalNote
<< "Failed to get D3D11Texture2D";
1413 if (!mSrc
->texture
->LockInternal()) {
1414 gfxCriticalNote
<< "CallVideoProcessorBlt LockInternal failed.";
1417 const auto unlock
= MakeScopeExit([&]() { mSrc
->texture
->Unlock(); });
1419 D3D11_VIDEO_PROCESSOR_INPUT_VIEW_DESC inputDesc
= {};
1420 inputDesc
.ViewDimension
= D3D11_VPIV_DIMENSION_TEXTURE2D
;
1421 inputDesc
.Texture2D
.ArraySlice
= texture
->ArrayIndex();
1423 RefPtr
<ID3D11VideoProcessorInputView
> inputView
;
1424 hr
= videoDevice
->CreateVideoProcessorInputView(
1425 texture2D
, videoProcessorEnumerator
, &inputDesc
,
1426 getter_AddRefs(inputView
));
1428 gfxCriticalNote
<< "ID3D11VideoProcessorInputView creation failed: "
1439 destRect
.right
= mDest
->dest
->size
.width
;
1440 destRect
.bottom
= mDest
->dest
->size
.height
;
1441 // KG: This causes stretching if this doesn't match `srcSize`?
1443 videoContext
->VideoProcessorSetOutputTargetRect(videoProcessor
, TRUE
,
1445 videoContext
->VideoProcessorSetStreamDestRect(videoProcessor
, 0, TRUE
,
1448 sourceRect
.left
= 0;
1450 sourceRect
.right
= mSrc
->size
.width
;
1451 sourceRect
.bottom
= mSrc
->size
.height
;
1452 videoContext
->VideoProcessorSetStreamSourceRect(videoProcessor
, 0, TRUE
,
1455 RefPtr
<ID3D11VideoProcessorOutputView
> outputView
;
1457 RefPtr
<ID3D11Texture2D
> backBuf
;
1458 mDest
->dest
->swapChain
->GetBuffer(0, __uuidof(ID3D11Texture2D
),
1459 (void**)getter_AddRefs(backBuf
));
1461 D3D11_VIDEO_PROCESSOR_OUTPUT_VIEW_DESC outputDesc
= {};
1462 outputDesc
.ViewDimension
= D3D11_VPOV_DIMENSION_TEXTURE2D
;
1463 outputDesc
.Texture2D
.MipSlice
= 0;
1465 hr
= videoDevice
->CreateVideoProcessorOutputView(
1466 backBuf
, videoProcessorEnumerator
, &outputDesc
,
1467 getter_AddRefs(outputView
));
1469 gfxCriticalNote
<< "ID3D11VideoProcessorOutputView creation failed: "
1476 // VideoProcessorBlt
1478 D3D11_VIDEO_PROCESSOR_STREAM stream
= {};
1479 stream
.Enable
= true;
1480 stream
.OutputIndex
= 0;
1481 stream
.InputFrameOrField
= 0;
1482 stream
.PastFrames
= 0;
1483 stream
.FutureFrames
= 0;
1484 stream
.pInputSurface
= inputView
.get();
1486 // KG: I guess we always copy here? The ideal case would instead be us getting
1487 // a swapchain from dcomp, and pulling buffers out of it for e.g. webgl to
1489 hr
= videoContext
->VideoProcessorBlt(videoProcessor
, outputView
, 0, 1,
1492 gfxCriticalNote
<< "VideoProcessorBlt failed: " << gfx::hexa(hr
);
1499 bool DCSurfaceSwapChain::CallBlitHelper() const {
1500 MOZ_RELEASE_ASSERT(mSrc
);
1501 MOZ_RELEASE_ASSERT(mDest
);
1502 MOZ_RELEASE_ASSERT(mDest
->dest
);
1503 const auto& plan
= *mDest
->plan
.blitHelper
;
1505 const auto gl
= mDCLayerTree
->GetGLContext();
1506 const auto& blitHelper
= gl
->BlitHelper();
1508 const auto Mat3FromImage
= [](const wr::WrExternalImage
& image
,
1509 const gfx::IntSize
& size
) {
1510 auto ret
= gl::Mat3::I();
1511 ret
.at(0, 0) = (image
.u1
- image
.u0
) / size
.width
; // e.g. 0.8 - 0.1
1512 ret
.at(0, 2) = image
.u0
/ size
.width
; // e.g. 0.1
1513 ret
.at(1, 1) = (image
.v1
- image
.v0
) / size
.height
;
1514 ret
.at(1, 2) = image
.v0
/ size
.height
;
1521 constexpr uint8_t LUT_TEX_UNIT
= 3;
1522 const auto restore3D
=
1523 gl::ScopedSaveMultiTex
{gl
, {LUT_TEX_UNIT
}, LOCAL_GL_TEXTURE_3D
};
1524 const auto restoreExt
=
1525 gl::ScopedSaveMultiTex
{gl
, {0, 1}, LOCAL_GL_TEXTURE_EXTERNAL
};
1527 if (plan
.srcSpace
!= plan
.dstSpace
) {
1529 mDest
->lut
= blitHelper
->GetColorLutTex({plan
.srcSpace
, plan
.dstSpace
});
1531 MOZ_ASSERT(mDest
->lut
);
1532 gl
->fActiveTexture(LOCAL_GL_TEXTURE0
+ LUT_TEX_UNIT
);
1533 gl
->fBindTexture(LOCAL_GL_TEXTURE_3D
, mDest
->lut
->name
);
1537 // Lock and bind src (image1 as needed below)
1539 const auto image0
= mSrc
->texture
->Lock(0, gl
, wr::ImageRendering::Auto
);
1540 const auto size0
= mSrc
->texture
->GetSize(0);
1541 const auto texCoordMat0
= Mat3FromImage(image0
, size0
);
1542 const auto unlock
= MakeScopeExit([&]() { mSrc
->texture
->Unlock(); });
1544 gl
->fActiveTexture(LOCAL_GL_TEXTURE0
+ 0);
1545 gl
->fBindTexture(LOCAL_GL_TEXTURE_EXTERNAL
, image0
.handle
);
1548 // Bind swapchain RT
1550 RefPtr
<ID3D11Texture2D
> backBuf
;
1551 mDest
->dest
->swapChain
->GetBuffer(0, __uuidof(ID3D11Texture2D
),
1552 (void**)getter_AddRefs(backBuf
));
1553 MOZ_RELEASE_ASSERT(backBuf
);
1555 bool clearForTesting
= false;
1556 if (clearForTesting
) {
1557 const auto device
= mDCLayerTree
->GetDevice();
1558 layers::ClearResource(device
, backBuf
, {1, 0, 1, 1});
1562 const auto gle
= gl::GLContextEGL::Cast(gl
);
1564 const auto fb
= gl::ScopedFramebuffer(gl
);
1565 const auto bindFb
= gl::ScopedBindFramebuffer(gl
, fb
);
1567 const auto rb
= gl::ScopedRenderbuffer(gl
);
1569 const auto bindRb
= gl::ScopedBindRenderbuffer(gl
, rb
);
1571 const EGLint attribs
[] = {LOCAL_EGL_NONE
};
1572 const auto image
= gle
->mEgl
->fCreateImage(
1573 0, LOCAL_EGL_D3D11_TEXTURE_ANGLE
,
1574 reinterpret_cast<EGLClientBuffer
>(backBuf
.get()), attribs
);
1575 MOZ_RELEASE_ASSERT(image
);
1576 gl
->fEGLImageTargetRenderbufferStorage(LOCAL_GL_RENDERBUFFER
, image
);
1577 gle
->mEgl
->fDestroyImage(image
); // Release as soon as attached to RB.
1579 gl
->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER
,
1580 LOCAL_GL_COLOR_ATTACHMENT0
,
1581 LOCAL_GL_RENDERBUFFER
, rb
);
1582 MOZ_ASSERT(gl
->IsFramebufferComplete(fb
));
1588 auto lutUintIfNeeded
= Some(LUT_TEX_UNIT
);
1589 auto fragConvert
= gl::kFragConvert_ColorLut
;
1591 lutUintIfNeeded
= Nothing();
1592 fragConvert
= gl::kFragConvert_None
;
1595 const auto baseArgs
= gl::DrawBlitProg::BaseArgs
{
1596 texCoordMat0
, false, mDest
->dest
->size
, {}, lutUintIfNeeded
,
1598 Maybe
<gl::DrawBlitProg::YUVArgs
> yuvArgs
;
1599 auto fragSample
= gl::kFragSample_OnePlane
;
1601 if (IsYuv(mSrc
->format
)) {
1602 const auto image1
= mSrc
->texture
->Lock(1, gl
, wr::ImageRendering::Auto
);
1603 const auto size1
= mSrc
->texture
->GetSize(1);
1604 const auto texCoordMat1
= Mat3FromImage(image1
, size1
);
1606 gl
->fActiveTexture(LOCAL_GL_TEXTURE0
+ 1);
1607 gl
->fBindTexture(LOCAL_GL_TEXTURE_EXTERNAL
, image1
.handle
);
1609 yuvArgs
= Some(gl::DrawBlitProg::YUVArgs
{texCoordMat1
, {}});
1610 fragSample
= gl::kFragSample_TwoPlane
;
1613 const auto dbp
= blitHelper
->GetDrawBlitProg(
1614 {gl::kFragHeader_TexExt
, {fragSample
, fragConvert
}});
1615 dbp
->Draw(baseArgs
, yuvArgs
.ptrOr(nullptr));
1622 DCTile::DCTile(DCLayerTree
* aDCLayerTree
) : mDCLayerTree(aDCLayerTree
) {}
1624 DCTile::~DCTile() {}
1626 bool DCTile::Initialize(int aX
, int aY
, wr::DeviceIntSize aSize
,
1628 if (aSize
.width
<= 0 || aSize
.height
<= 0) {
1632 // Initially, the entire tile is considered valid, unless it is set by
1633 // the SetTileProperties method.
1636 mValidRect
.width
= aSize
.width
;
1637 mValidRect
.height
= aSize
.height
;
1642 GLuint
DCLayerTree::CreateEGLSurfaceForCompositionSurface(
1643 wr::DeviceIntRect aDirtyRect
, wr::DeviceIntPoint
* aOffset
,
1644 RefPtr
<IDCompositionSurface
> aCompositionSurface
,
1645 wr::DeviceIntPoint aSurfaceOffset
) {
1646 MOZ_ASSERT(aCompositionSurface
.get());
1649 const auto gl
= GetGLContext();
1650 RefPtr
<ID3D11Texture2D
> backBuf
;
1654 update_rect
.left
= aSurfaceOffset
.x
+ aDirtyRect
.min
.x
;
1655 update_rect
.top
= aSurfaceOffset
.y
+ aDirtyRect
.min
.y
;
1656 update_rect
.right
= aSurfaceOffset
.x
+ aDirtyRect
.max
.x
;
1657 update_rect
.bottom
= aSurfaceOffset
.y
+ aDirtyRect
.max
.y
;
1659 hr
= aCompositionSurface
->BeginDraw(&update_rect
, __uuidof(ID3D11Texture2D
),
1660 (void**)getter_AddRefs(backBuf
), &offset
);
1662 LayoutDeviceIntRect rect
= widget::WinUtils::ToIntRect(update_rect
);
1664 gfxCriticalNote
<< "DCompositionSurface::BeginDraw failed: "
1665 << gfx::hexa(hr
) << " " << rect
;
1666 RenderThread::Get()->HandleWebRenderError(WebRenderError::BEGIN_DRAW
);
1670 // DC includes the origin of the dirty / update rect in the draw offset,
1671 // undo that here since WR expects it to be an absolute offset.
1672 offset
.x
-= aDirtyRect
.min
.x
;
1673 offset
.y
-= aDirtyRect
.min
.y
;
1675 D3D11_TEXTURE2D_DESC desc
;
1676 backBuf
->GetDesc(&desc
);
1678 const auto& gle
= gl::GLContextEGL::Cast(gl
);
1679 const auto& egl
= gle
->mEgl
;
1681 const auto buffer
= reinterpret_cast<EGLClientBuffer
>(backBuf
.get());
1683 // Construct an EGLImage wrapper around the D3D texture for ANGLE.
1684 const EGLint attribs
[] = {LOCAL_EGL_NONE
};
1685 mEGLImage
= egl
->fCreateImage(EGL_NO_CONTEXT
, LOCAL_EGL_D3D11_TEXTURE_ANGLE
,
1688 // Get the current FBO and RBO id, so we can restore them later
1689 GLint currentFboId
, currentRboId
;
1690 gl
->fGetIntegerv(LOCAL_GL_DRAW_FRAMEBUFFER_BINDING
, ¤tFboId
);
1691 gl
->fGetIntegerv(LOCAL_GL_RENDERBUFFER_BINDING
, ¤tRboId
);
1693 // Create a render buffer object that is backed by the EGL image.
1694 gl
->fGenRenderbuffers(1, &mColorRBO
);
1695 gl
->fBindRenderbuffer(LOCAL_GL_RENDERBUFFER
, mColorRBO
);
1696 gl
->fEGLImageTargetRenderbufferStorage(LOCAL_GL_RENDERBUFFER
, mEGLImage
);
1698 // Get or create an FBO for the specified dimensions
1699 GLuint fboId
= GetOrCreateFbo(desc
.Width
, desc
.Height
);
1701 // Attach the new renderbuffer to the FBO
1702 gl
->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER
, fboId
);
1703 gl
->fFramebufferRenderbuffer(LOCAL_GL_DRAW_FRAMEBUFFER
,
1704 LOCAL_GL_COLOR_ATTACHMENT0
,
1705 LOCAL_GL_RENDERBUFFER
, mColorRBO
);
1707 // Restore previous FBO and RBO bindings
1708 gl
->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER
, currentFboId
);
1709 gl
->fBindRenderbuffer(LOCAL_GL_RENDERBUFFER
, currentRboId
);
1711 aOffset
->x
= offset
.x
;
1712 aOffset
->y
= offset
.y
;
1717 void DCLayerTree::DestroyEGLSurface() {
1718 const auto gl
= GetGLContext();
1721 gl
->fDeleteRenderbuffers(1, &mColorRBO
);
1726 const auto& gle
= gl::GLContextEGL::Cast(gl
);
1727 const auto& egl
= gle
->mEgl
;
1728 egl
->fDestroyImage(mEGLImage
);
1729 mEGLImage
= EGL_NO_IMAGE
;
1734 } // namespace mozilla