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/. */
6
7 #include "DCLayerTree.h"
8
9 #include "GLContext.h"
10 #include "GLContextEGL.h"
11 #include "mozilla/gfx/DeviceManagerDx.h"
12 #include "mozilla/gfx/Logging.h"
13 #include "mozilla/gfx/gfxVars.h"
14 #include "mozilla/StaticPrefs_gfx.h"
15 #include "mozilla/webrender/RenderD3D11TextureHost.h"
16 #include "mozilla/webrender/RenderTextureHost.h"
17 #include "mozilla/webrender/RenderThread.h"
18 #include "mozilla/Telemetry.h"
19 #include "nsPrintfCString.h"
20
21 #undef _WIN32_WINNT
22 #define _WIN32_WINNT _WIN32_WINNT_WINBLUE
23 #undef NTDDI_VERSION
24 #define NTDDI_VERSION NTDDI_WINBLUE
25
26 #include <d3d11.h>
27 #include <d3d11_1.h>
28 #include <dcomp.h>
29 #include <dxgi1_2.h>
30
31 namespace mozilla {
32 namespace wr {
33
34 /* static */
Create(gl::GLContext * aGL,EGLConfig aEGLConfig,ID3D11Device * aDevice,ID3D11DeviceContext * aCtx,HWND aHwnd,nsACString & aError)35 UniquePtr<DCLayerTree> DCLayerTree::Create(gl::GLContext* aGL,
36 EGLConfig aEGLConfig,
37 ID3D11Device* aDevice,
38 ID3D11DeviceContext* aCtx,
39 HWND aHwnd, nsACString& aError) {
40 RefPtr<IDCompositionDevice2> dCompDevice =
41 gfx::DeviceManagerDx::Get()->GetDirectCompositionDevice();
42 if (!dCompDevice) {
43 aError.Assign("DCLayerTree(no device)"_ns);
44 return nullptr;
45 }
46
47 auto layerTree =
48 MakeUnique<DCLayerTree>(aGL, aEGLConfig, aDevice, aCtx, dCompDevice);
49 if (!layerTree->Initialize(aHwnd, aError)) {
50 return nullptr;
51 }
52
53 return layerTree;
54 }
55
DCLayerTree(gl::GLContext * aGL,EGLConfig aEGLConfig,ID3D11Device * aDevice,ID3D11DeviceContext * aCtx,IDCompositionDevice2 * aCompositionDevice)56 DCLayerTree::DCLayerTree(gl::GLContext* aGL, EGLConfig aEGLConfig,
57 ID3D11Device* aDevice, ID3D11DeviceContext* aCtx,
58 IDCompositionDevice2* aCompositionDevice)
59 : mGL(aGL),
60 mEGLConfig(aEGLConfig),
61 mDevice(aDevice),
62 mCtx(aCtx),
63 mCompositionDevice(aCompositionDevice),
64 mVideoOverlaySupported(false),
65 mDebugCounter(false),
66 mDebugVisualRedrawRegions(false),
67 mEGLImage(EGL_NO_IMAGE),
68 mColorRBO(0),
69 mPendingCommit(false) {}
70
~DCLayerTree()71 DCLayerTree::~DCLayerTree() { ReleaseNativeCompositorResources(); }
72
ReleaseNativeCompositorResources()73 void DCLayerTree::ReleaseNativeCompositorResources() {
74 const auto gl = GetGLContext();
75
76 DestroyEGLSurface();
77
78 // Delete any cached FBO objects
79 for (auto it = mFrameBuffers.begin(); it != mFrameBuffers.end(); ++it) {
80 gl->fDeleteRenderbuffers(1, &it->depthRboId);
81 gl->fDeleteFramebuffers(1, &it->fboId);
82 }
83 }
84
Initialize(HWND aHwnd,nsACString & aError)85 bool DCLayerTree::Initialize(HWND aHwnd, nsACString& aError) {
86 HRESULT hr;
87
88 RefPtr<IDCompositionDesktopDevice> desktopDevice;
89 hr = mCompositionDevice->QueryInterface(
90 (IDCompositionDesktopDevice**)getter_AddRefs(desktopDevice));
91 if (FAILED(hr)) {
92 aError.Assign(nsPrintfCString(
93 "DCLayerTree(get IDCompositionDesktopDevice failed %x)", hr));
94 return false;
95 }
96
97 hr = desktopDevice->CreateTargetForHwnd(aHwnd, TRUE,
98 getter_AddRefs(mCompositionTarget));
99 if (FAILED(hr)) {
100 aError.Assign(nsPrintfCString(
101 "DCLayerTree(create DCompositionTarget failed %x)", hr));
102 return false;
103 }
104
105 hr = mCompositionDevice->CreateVisual(getter_AddRefs(mRootVisual));
106 if (FAILED(hr)) {
107 aError.Assign(nsPrintfCString(
108 "DCLayerTree(create root DCompositionVisual failed %x)", hr));
109 return false;
110 }
111
112 hr =
113 mCompositionDevice->CreateVisual(getter_AddRefs(mDefaultSwapChainVisual));
114 if (FAILED(hr)) {
115 aError.Assign(nsPrintfCString(
116 "DCLayerTree(create swap chain DCompositionVisual failed %x)", hr));
117 return false;
118 }
119
120 if (gfx::gfxVars::UseWebRenderDCompVideoOverlayWin()) {
121 if (!InitializeVideoOverlaySupport()) {
122 RenderThread::Get()->HandleWebRenderError(WebRenderError::VIDEO_OVERLAY);
123 }
124 }
125
126 mCompositionTarget->SetRoot(mRootVisual);
127 // Set interporation mode to nearest, to ensure 1:1 sampling.
128 // By default, a visual inherits the interpolation mode of the parent visual.
129 // If no visuals set the interpolation mode, the default for the entire visual
130 // tree is nearest neighbor interpolation.
131 mRootVisual->SetBitmapInterpolationMode(
132 DCOMPOSITION_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR);
133 return true;
134 }
135
InitializeVideoOverlaySupport()136 bool DCLayerTree::InitializeVideoOverlaySupport() {
137 HRESULT hr;
138
139 hr = mDevice->QueryInterface(
140 (ID3D11VideoDevice**)getter_AddRefs(mVideoDevice));
141 if (FAILED(hr)) {
142 gfxCriticalNote << "Failed to get D3D11VideoDevice: " << gfx::hexa(hr);
143 return false;
144 }
145
146 hr =
147 mCtx->QueryInterface((ID3D11VideoContext**)getter_AddRefs(mVideoContext));
148 if (FAILED(hr)) {
149 gfxCriticalNote << "Failed to get D3D11VideoContext: " << gfx::hexa(hr);
150 return false;
151 }
152
153 // XXX When video is rendered to DXGI_FORMAT_B8G8R8A8_UNORM SwapChain with
154 // VideoProcessor, it seems that we do not need to check
155 // IDXGIOutput3::CheckOverlaySupport().
156 // If we want to yuv at DecodeSwapChain, its support seems necessary.
157
158 mVideoOverlaySupported = true;
159 return true;
160 }
161
GetSurface(wr::NativeSurfaceId aId) const162 DCSurface* DCLayerTree::GetSurface(wr::NativeSurfaceId aId) const {
163 auto surface_it = mDCSurfaces.find(aId);
164 MOZ_RELEASE_ASSERT(surface_it != mDCSurfaces.end());
165 return surface_it->second.get();
166 }
167
SetDefaultSwapChain(IDXGISwapChain1 * aSwapChain)168 void DCLayerTree::SetDefaultSwapChain(IDXGISwapChain1* aSwapChain) {
169 mRootVisual->AddVisual(mDefaultSwapChainVisual, TRUE, nullptr);
170 mDefaultSwapChainVisual->SetContent(aSwapChain);
171 // Default SwapChain's visual does not need linear interporation.
172 mDefaultSwapChainVisual->SetBitmapInterpolationMode(
173 DCOMPOSITION_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR);
174 mPendingCommit = true;
175 }
176
MaybeUpdateDebug()177 void DCLayerTree::MaybeUpdateDebug() {
178 bool updated = false;
179 updated |= MaybeUpdateDebugCounter();
180 updated |= MaybeUpdateDebugVisualRedrawRegions();
181 if (updated) {
182 mPendingCommit = true;
183 }
184 }
185
MaybeCommit()186 void DCLayerTree::MaybeCommit() {
187 if (!mPendingCommit) {
188 return;
189 }
190 mCompositionDevice->Commit();
191 }
192
WaitForCommitCompletion()193 void DCLayerTree::WaitForCommitCompletion() {
194 mCompositionDevice->WaitForCommitCompletion();
195 }
196
DisableNativeCompositor()197 void DCLayerTree::DisableNativeCompositor() {
198 MOZ_ASSERT(mCurrentSurface.isNothing());
199 MOZ_ASSERT(mCurrentLayers.empty());
200
201 ReleaseNativeCompositorResources();
202 mPrevLayers.clear();
203 mRootVisual->RemoveAllVisuals();
204 }
205
MaybeUpdateDebugCounter()206 bool DCLayerTree::MaybeUpdateDebugCounter() {
207 bool debugCounter = StaticPrefs::gfx_webrender_debug_dcomp_counter();
208 if (mDebugCounter == debugCounter) {
209 return false;
210 }
211
212 RefPtr<IDCompositionDeviceDebug> debugDevice;
213 HRESULT hr = mCompositionDevice->QueryInterface(
214 (IDCompositionDeviceDebug**)getter_AddRefs(debugDevice));
215 if (FAILED(hr)) {
216 return false;
217 }
218
219 if (debugCounter) {
220 debugDevice->EnableDebugCounters();
221 } else {
222 debugDevice->DisableDebugCounters();
223 }
224
225 mDebugCounter = debugCounter;
226 return true;
227 }
228
MaybeUpdateDebugVisualRedrawRegions()229 bool DCLayerTree::MaybeUpdateDebugVisualRedrawRegions() {
230 bool debugVisualRedrawRegions =
231 StaticPrefs::gfx_webrender_debug_dcomp_redraw_regions();
232 if (mDebugVisualRedrawRegions == debugVisualRedrawRegions) {
233 return false;
234 }
235
236 RefPtr<IDCompositionVisualDebug> visualDebug;
237 HRESULT hr = mRootVisual->QueryInterface(
238 (IDCompositionVisualDebug**)getter_AddRefs(visualDebug));
239 if (FAILED(hr)) {
240 return false;
241 }
242
243 if (debugVisualRedrawRegions) {
244 visualDebug->EnableRedrawRegions();
245 } else {
246 visualDebug->DisableRedrawRegions();
247 }
248
249 mDebugVisualRedrawRegions = debugVisualRedrawRegions;
250 return true;
251 }
252
CompositorBeginFrame()253 void DCLayerTree::CompositorBeginFrame() { mCurrentFrame++; }
254
CompositorEndFrame()255 void DCLayerTree::CompositorEndFrame() {
256 auto start = TimeStamp::Now();
257 // Check if the visual tree of surfaces is the same as last frame.
258 bool same = mPrevLayers == mCurrentLayers;
259
260 if (!same) {
261 // If not, we need to rebuild the visual tree. Note that addition or
262 // removal of tiles no longer needs to rebuild the main visual tree
263 // here, since they are added as children of the surface visual.
264 mRootVisual->RemoveAllVisuals();
265 }
266
267 for (auto it = mCurrentLayers.begin(); it != mCurrentLayers.end(); ++it) {
268 auto surface_it = mDCSurfaces.find(*it);
269 MOZ_RELEASE_ASSERT(surface_it != mDCSurfaces.end());
270 const auto surface = surface_it->second.get();
271 // Ensure surface is trimmed to updated tile valid rects
272 surface->UpdateAllocatedRect();
273 if (!same) {
274 // Add surfaces in z-order they were added to the scene.
275 const auto visual = surface->GetVisual();
276 mRootVisual->AddVisual(visual, FALSE, nullptr);
277 }
278 }
279
280 mPrevLayers.swap(mCurrentLayers);
281 mCurrentLayers.clear();
282
283 mCompositionDevice->Commit();
284
285 auto end = TimeStamp::Now();
286 mozilla::Telemetry::Accumulate(mozilla::Telemetry::COMPOSITE_SWAP_TIME,
287 (end - start).ToMilliseconds() * 10.);
288
289 // Remove any framebuffers that haven't been
290 // used in the last 60 frames.
291 //
292 // This should use nsTArray::RemoveElementsBy once
293 // CachedFrameBuffer is able to properly destroy
294 // itself in the destructor.
295 const auto gl = GetGLContext();
296 for (uint32_t i = 0, len = mFrameBuffers.Length(); i < len; ++i) {
297 auto& fb = mFrameBuffers[i];
298 if ((mCurrentFrame - fb.lastFrameUsed) > 60) {
299 gl->fDeleteRenderbuffers(1, &fb.depthRboId);
300 gl->fDeleteFramebuffers(1, &fb.fboId);
301 mFrameBuffers.UnorderedRemoveElementAt(i);
302 --i; // Examine the element again, if necessary.
303 --len;
304 }
305 }
306 }
307
Bind(wr::NativeTileId aId,wr::DeviceIntPoint * aOffset,uint32_t * aFboId,wr::DeviceIntRect aDirtyRect,wr::DeviceIntRect aValidRect)308 void DCLayerTree::Bind(wr::NativeTileId aId, wr::DeviceIntPoint* aOffset,
309 uint32_t* aFboId, wr::DeviceIntRect aDirtyRect,
310 wr::DeviceIntRect aValidRect) {
311 auto surface = GetSurface(aId.surface_id);
312 auto tile = surface->GetTile(aId.x, aId.y);
313 wr::DeviceIntPoint targetOffset{0, 0};
314
315 gfx::IntRect validRect(aValidRect.min.x, aValidRect.min.y, aValidRect.width(),
316 aValidRect.height());
317 if (!tile->mValidRect.IsEqualEdges(validRect)) {
318 tile->mValidRect = validRect;
319 surface->DirtyAllocatedRect();
320 }
321 wr::DeviceIntSize tileSize = surface->GetTileSize();
322 RefPtr<IDCompositionSurface> compositionSurface =
323 surface->GetCompositionSurface();
324 wr::DeviceIntPoint virtualOffset = surface->GetVirtualOffset();
325 targetOffset.x = virtualOffset.x + tileSize.width * aId.x;
326 targetOffset.y = virtualOffset.y + tileSize.height * aId.y;
327
328 *aFboId = CreateEGLSurfaceForCompositionSurface(
329 aDirtyRect, aOffset, compositionSurface, targetOffset);
330 mCurrentSurface = Some(compositionSurface);
331 }
332
Unbind()333 void DCLayerTree::Unbind() {
334 if (mCurrentSurface.isNothing()) {
335 return;
336 }
337
338 RefPtr<IDCompositionSurface> surface = mCurrentSurface.ref();
339 surface->EndDraw();
340
341 DestroyEGLSurface();
342 mCurrentSurface = Nothing();
343 }
344
CreateSurface(wr::NativeSurfaceId aId,wr::DeviceIntPoint aVirtualOffset,wr::DeviceIntSize aTileSize,bool aIsOpaque)345 void DCLayerTree::CreateSurface(wr::NativeSurfaceId aId,
346 wr::DeviceIntPoint aVirtualOffset,
347 wr::DeviceIntSize aTileSize, bool aIsOpaque) {
348 auto it = mDCSurfaces.find(aId);
349 MOZ_RELEASE_ASSERT(it == mDCSurfaces.end());
350 if (it != mDCSurfaces.end()) {
351 // DCSurface already exists.
352 return;
353 }
354
355 // Tile size needs to be positive.
356 if (aTileSize.width <= 0 || aTileSize.height <= 0) {
357 gfxCriticalNote << "TileSize is not positive aId: " << wr::AsUint64(aId)
358 << " aTileSize(" << aTileSize.width << ","
359 << aTileSize.height << ")";
360 }
361
362 auto surface =
363 MakeUnique<DCSurface>(aTileSize, aVirtualOffset, aIsOpaque, this);
364 if (!surface->Initialize()) {
365 gfxCriticalNote << "Failed to initialize DCSurface: " << wr::AsUint64(aId);
366 return;
367 }
368
369 mDCSurfaces[aId] = std::move(surface);
370 }
371
CreateExternalSurface(wr::NativeSurfaceId aId,bool aIsOpaque)372 void DCLayerTree::CreateExternalSurface(wr::NativeSurfaceId aId,
373 bool aIsOpaque) {
374 auto it = mDCSurfaces.find(aId);
375 MOZ_RELEASE_ASSERT(it == mDCSurfaces.end());
376
377 auto surface = MakeUnique<DCSurfaceVideo>(aIsOpaque, this);
378 if (!surface->Initialize()) {
379 gfxCriticalNote << "Failed to initialize DCSurfaceVideo: "
380 << wr::AsUint64(aId);
381 return;
382 }
383
384 mDCSurfaces[aId] = std::move(surface);
385 }
386
DestroySurface(NativeSurfaceId aId)387 void DCLayerTree::DestroySurface(NativeSurfaceId aId) {
388 auto surface_it = mDCSurfaces.find(aId);
389 MOZ_RELEASE_ASSERT(surface_it != mDCSurfaces.end());
390 auto surface = surface_it->second.get();
391
392 mRootVisual->RemoveVisual(surface->GetVisual());
393 mDCSurfaces.erase(surface_it);
394 }
395
CreateTile(wr::NativeSurfaceId aId,int aX,int aY)396 void DCLayerTree::CreateTile(wr::NativeSurfaceId aId, int aX, int aY) {
397 auto surface = GetSurface(aId);
398 surface->CreateTile(aX, aY);
399 }
400
DestroyTile(wr::NativeSurfaceId aId,int aX,int aY)401 void DCLayerTree::DestroyTile(wr::NativeSurfaceId aId, int aX, int aY) {
402 auto surface = GetSurface(aId);
403 surface->DestroyTile(aX, aY);
404 }
405
AttachExternalImage(wr::NativeSurfaceId aId,wr::ExternalImageId aExternalImage)406 void DCLayerTree::AttachExternalImage(wr::NativeSurfaceId aId,
407 wr::ExternalImageId aExternalImage) {
408 auto surface_it = mDCSurfaces.find(aId);
409 MOZ_RELEASE_ASSERT(surface_it != mDCSurfaces.end());
410 auto* surfaceVideo = surface_it->second->AsDCSurfaceVideo();
411 MOZ_RELEASE_ASSERT(surfaceVideo);
412
413 surfaceVideo->AttachExternalImage(aExternalImage);
414 }
415
416 template <typename T>
D2DRect(const T & aRect)417 static inline D2D1_RECT_F D2DRect(const T& aRect) {
418 return D2D1::RectF(aRect.X(), aRect.Y(), aRect.XMost(), aRect.YMost());
419 }
420
D2DMatrix(const gfx::Matrix & aTransform)421 static inline D2D1_MATRIX_3X2_F D2DMatrix(const gfx::Matrix& aTransform) {
422 return D2D1::Matrix3x2F(aTransform._11, aTransform._12, aTransform._21,
423 aTransform._22, aTransform._31, aTransform._32);
424 }
425
AddSurface(wr::NativeSurfaceId aId,const wr::CompositorSurfaceTransform & aTransform,wr::DeviceIntRect aClipRect,wr::ImageRendering aImageRendering)426 void DCLayerTree::AddSurface(wr::NativeSurfaceId aId,
427 const wr::CompositorSurfaceTransform& aTransform,
428 wr::DeviceIntRect aClipRect,
429 wr::ImageRendering aImageRendering) {
430 auto it = mDCSurfaces.find(aId);
431 MOZ_RELEASE_ASSERT(it != mDCSurfaces.end());
432 const auto surface = it->second.get();
433 const auto visual = surface->GetVisual();
434
435 wr::DeviceIntPoint virtualOffset = surface->GetVirtualOffset();
436
437 gfx::Matrix transform(aTransform.m11, aTransform.m12, aTransform.m21,
438 aTransform.m22, aTransform.m41, aTransform.m42);
439 transform.PreTranslate(-virtualOffset.x, -virtualOffset.y);
440
441 // The DirectComposition API applies clipping *before* any transforms/offset,
442 // whereas we want the clip applied after.
443 // Right now, we only support rectilinear transforms, and then we transform
444 // our clip into pre-transform coordinate space for it to be applied there.
445 // DirectComposition does have an option for pre-transform clipping, if you
446 // create an explicit IDCompositionEffectGroup object and set a 3D transform
447 // on that. I suspect that will perform worse though, so we should only do
448 // that for complex transforms (which are never provided right now).
449 MOZ_ASSERT(transform.IsRectilinear());
450 gfx::Rect clip = transform.Inverse().TransformBounds(gfx::Rect(
451 aClipRect.min.x, aClipRect.min.y, aClipRect.width(), aClipRect.height()));
452 // Set the clip rect - converting from world space to the pre-offset space
453 // that DC requires for rectangle clips.
454 visual->SetClip(D2DRect(clip));
455
456 // TODO: The input matrix is a 4x4, but we only support a 3x2 at
457 // the D3D API level (unless we QI to IDCompositionVisual3, which might
458 // not be available?).
459 // Should we assert here, or restrict at the WR API level.
460 visual->SetTransform(D2DMatrix(transform));
461
462 if (aImageRendering == wr::ImageRendering::Auto) {
463 visual->SetBitmapInterpolationMode(
464 DCOMPOSITION_BITMAP_INTERPOLATION_MODE_LINEAR);
465 } else {
466 visual->SetBitmapInterpolationMode(
467 DCOMPOSITION_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR);
468 }
469
470 mCurrentLayers.push_back(aId);
471 }
472
GetOrCreateFbo(int aWidth,int aHeight)473 GLuint DCLayerTree::GetOrCreateFbo(int aWidth, int aHeight) {
474 const auto gl = GetGLContext();
475 GLuint fboId = 0;
476
477 // Check if we have a cached FBO with matching dimensions
478 for (auto it = mFrameBuffers.begin(); it != mFrameBuffers.end(); ++it) {
479 if (it->width == aWidth && it->height == aHeight) {
480 fboId = it->fboId;
481 it->lastFrameUsed = mCurrentFrame;
482 break;
483 }
484 }
485
486 // If not, create a new FBO with attached depth buffer
487 if (fboId == 0) {
488 // Create the depth buffer
489 GLuint depthRboId;
490 gl->fGenRenderbuffers(1, &depthRboId);
491 gl->fBindRenderbuffer(LOCAL_GL_RENDERBUFFER, depthRboId);
492 gl->fRenderbufferStorage(LOCAL_GL_RENDERBUFFER, LOCAL_GL_DEPTH_COMPONENT24,
493 aWidth, aHeight);
494
495 // Create the framebuffer and attach the depth buffer to it
496 gl->fGenFramebuffers(1, &fboId);
497 gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, fboId);
498 gl->fFramebufferRenderbuffer(LOCAL_GL_DRAW_FRAMEBUFFER,
499 LOCAL_GL_DEPTH_ATTACHMENT,
500 LOCAL_GL_RENDERBUFFER, depthRboId);
501
502 // Store this in the cache for future calls.
503 // TODO(gw): Maybe we should periodically scan this list and remove old
504 // entries that
505 // haven't been used for some time?
506 DCLayerTree::CachedFrameBuffer frame_buffer_info;
507 frame_buffer_info.width = aWidth;
508 frame_buffer_info.height = aHeight;
509 frame_buffer_info.fboId = fboId;
510 frame_buffer_info.depthRboId = depthRboId;
511 frame_buffer_info.lastFrameUsed = mCurrentFrame;
512 mFrameBuffers.AppendElement(frame_buffer_info);
513 }
514
515 return fboId;
516 }
517
EnsureVideoProcessor(const gfx::IntSize & aVideoSize)518 bool DCLayerTree::EnsureVideoProcessor(const gfx::IntSize& aVideoSize) {
519 HRESULT hr;
520
521 if (!mVideoDevice || !mVideoContext) {
522 return false;
523 }
524
525 if (mVideoProcessor && aVideoSize == mVideoSize) {
526 return true;
527 }
528
529 mVideoProcessor = nullptr;
530 mVideoProcessorEnumerator = nullptr;
531
532 D3D11_VIDEO_PROCESSOR_CONTENT_DESC desc = {};
533 desc.InputFrameFormat = D3D11_VIDEO_FRAME_FORMAT_PROGRESSIVE;
534 desc.InputFrameRate.Numerator = 60;
535 desc.InputFrameRate.Denominator = 1;
536 desc.InputWidth = aVideoSize.width;
537 desc.InputHeight = aVideoSize.height;
538 desc.OutputFrameRate.Numerator = 60;
539 desc.OutputFrameRate.Denominator = 1;
540 desc.OutputWidth = aVideoSize.width;
541 desc.OutputHeight = aVideoSize.height;
542 desc.Usage = D3D11_VIDEO_USAGE_PLAYBACK_NORMAL;
543
544 hr = mVideoDevice->CreateVideoProcessorEnumerator(
545 &desc, getter_AddRefs(mVideoProcessorEnumerator));
546 if (FAILED(hr)) {
547 gfxCriticalNote << "Failed to create VideoProcessorEnumerator: "
548 << gfx::hexa(hr);
549 return false;
550 }
551
552 hr = mVideoDevice->CreateVideoProcessor(mVideoProcessorEnumerator, 0,
553 getter_AddRefs(mVideoProcessor));
554 if (FAILED(hr)) {
555 mVideoProcessor = nullptr;
556 mVideoProcessorEnumerator = nullptr;
557 gfxCriticalNote << "Failed to create VideoProcessor: " << gfx::hexa(hr);
558 return false;
559 }
560
561 // Reduce power cosumption
562 // By default, the driver might perform certain processing tasks automatically
563 mVideoContext->VideoProcessorSetStreamAutoProcessingMode(mVideoProcessor, 0,
564 FALSE);
565
566 mVideoSize = aVideoSize;
567 return true;
568 }
569
DCSurface(wr::DeviceIntSize aTileSize,wr::DeviceIntPoint aVirtualOffset,bool aIsOpaque,DCLayerTree * aDCLayerTree)570 DCSurface::DCSurface(wr::DeviceIntSize aTileSize,
571 wr::DeviceIntPoint aVirtualOffset, bool aIsOpaque,
572 DCLayerTree* aDCLayerTree)
573 : mDCLayerTree(aDCLayerTree),
574 mTileSize(aTileSize),
575 mIsOpaque(aIsOpaque),
576 mAllocatedRectDirty(true),
577 mVirtualOffset(aVirtualOffset) {}
578
~DCSurface()579 DCSurface::~DCSurface() {}
580
Initialize()581 bool DCSurface::Initialize() {
582 HRESULT hr;
583 const auto dCompDevice = mDCLayerTree->GetCompositionDevice();
584 hr = dCompDevice->CreateVisual(getter_AddRefs(mVisual));
585 if (FAILED(hr)) {
586 gfxCriticalNote << "Failed to create DCompositionVisual: " << gfx::hexa(hr);
587 return false;
588 }
589
590 DXGI_ALPHA_MODE alpha_mode =
591 mIsOpaque ? DXGI_ALPHA_MODE_IGNORE : DXGI_ALPHA_MODE_PREMULTIPLIED;
592
593 hr = dCompDevice->CreateVirtualSurface(
594 VIRTUAL_SURFACE_SIZE, VIRTUAL_SURFACE_SIZE, DXGI_FORMAT_R8G8B8A8_UNORM,
595 alpha_mode, getter_AddRefs(mVirtualSurface));
596 MOZ_ASSERT(SUCCEEDED(hr));
597
598 // Bind the surface memory to this visual
599 hr = mVisual->SetContent(mVirtualSurface);
600 MOZ_ASSERT(SUCCEEDED(hr));
601
602 return true;
603 }
604
CreateTile(int aX,int aY)605 void DCSurface::CreateTile(int aX, int aY) {
606 TileKey key(aX, aY);
607 MOZ_RELEASE_ASSERT(mDCTiles.find(key) == mDCTiles.end());
608
609 auto tile = MakeUnique<DCTile>(mDCLayerTree);
610 if (!tile->Initialize(aX, aY, mTileSize, mIsOpaque)) {
611 gfxCriticalNote << "Failed to initialize DCTile: " << aX << aY;
612 return;
613 }
614
615 mAllocatedRectDirty = true;
616
617 mDCTiles[key] = std::move(tile);
618 }
619
DestroyTile(int aX,int aY)620 void DCSurface::DestroyTile(int aX, int aY) {
621 TileKey key(aX, aY);
622 mAllocatedRectDirty = true;
623 mDCTiles.erase(key);
624 }
625
DirtyAllocatedRect()626 void DCSurface::DirtyAllocatedRect() { mAllocatedRectDirty = true; }
627
UpdateAllocatedRect()628 void DCSurface::UpdateAllocatedRect() {
629 if (mAllocatedRectDirty) {
630 // The virtual surface may have holes in it (for example, an empty tile
631 // that has no primitives). Instead of trimming to a single bounding
632 // rect, supply the rect of each valid tile to handle this case.
633 std::vector<RECT> validRects;
634
635 for (auto it = mDCTiles.begin(); it != mDCTiles.end(); ++it) {
636 auto tile = GetTile(it->first.mX, it->first.mY);
637 RECT rect;
638
639 rect.left = (LONG)(mVirtualOffset.x + it->first.mX * mTileSize.width +
640 tile->mValidRect.x);
641 rect.top = (LONG)(mVirtualOffset.y + it->first.mY * mTileSize.height +
642 tile->mValidRect.y);
643 rect.right = rect.left + tile->mValidRect.width;
644 rect.bottom = rect.top + tile->mValidRect.height;
645
646 validRects.push_back(rect);
647 }
648
649 mVirtualSurface->Trim(validRects.data(), validRects.size());
650 mAllocatedRectDirty = false;
651 }
652 }
653
GetTile(int aX,int aY) const654 DCTile* DCSurface::GetTile(int aX, int aY) const {
655 TileKey key(aX, aY);
656 auto tile_it = mDCTiles.find(key);
657 MOZ_RELEASE_ASSERT(tile_it != mDCTiles.end());
658 return tile_it->second.get();
659 }
660
DCSurfaceVideo(bool aIsOpaque,DCLayerTree * aDCLayerTree)661 DCSurfaceVideo::DCSurfaceVideo(bool aIsOpaque, DCLayerTree* aDCLayerTree)
662 : DCSurface(wr::DeviceIntSize{}, wr::DeviceIntPoint{}, aIsOpaque,
663 aDCLayerTree) {}
664
AttachExternalImage(wr::ExternalImageId aExternalImage)665 void DCSurfaceVideo::AttachExternalImage(wr::ExternalImageId aExternalImage) {
666 RenderTextureHost* texture =
667 RenderThread::Get()->GetRenderTexture(aExternalImage);
668 MOZ_RELEASE_ASSERT(texture);
669
670 if (mPrevTexture == texture) {
671 return;
672 }
673
674 // XXX if software decoded video frame format is nv12, it could be used as
675 // video overlay.
676 if (!texture || !texture->AsRenderDXGITextureHost() ||
677 texture->AsRenderDXGITextureHost()->GetFormat() !=
678 gfx::SurfaceFormat::NV12) {
679 gfxCriticalNote << "Unsupported RenderTexture for overlay: "
680 << gfx::hexa(texture);
681 return;
682 }
683
684 gfx::IntSize size = texture->AsRenderDXGITextureHost()->GetSize(0);
685 if (!mVideoSwapChain || mSwapChainSize != size) {
686 ReleaseDecodeSwapChainResources();
687 CreateVideoSwapChain(texture);
688 }
689
690 if (!mVideoSwapChain) {
691 gfxCriticalNote << "Failed to create VideoSwapChain";
692 RenderThread::Get()->NotifyWebRenderError(
693 wr::WebRenderError::VIDEO_OVERLAY);
694 return;
695 }
696
697 mVisual->SetContent(mVideoSwapChain);
698
699 if (!CallVideoProcessorBlt(texture)) {
700 RenderThread::Get()->NotifyWebRenderError(
701 wr::WebRenderError::VIDEO_OVERLAY);
702 return;
703 }
704
705 mVideoSwapChain->Present(0, 0);
706 mPrevTexture = texture;
707 }
708
CreateVideoSwapChain(RenderTextureHost * aTexture)709 bool DCSurfaceVideo::CreateVideoSwapChain(RenderTextureHost* aTexture) {
710 const auto device = mDCLayerTree->GetDevice();
711
712 RefPtr<IDXGIDevice> dxgiDevice;
713 device->QueryInterface((IDXGIDevice**)getter_AddRefs(dxgiDevice));
714
715 RefPtr<IDXGIFactoryMedia> dxgiFactoryMedia;
716 {
717 RefPtr<IDXGIAdapter> adapter;
718 dxgiDevice->GetAdapter(getter_AddRefs(adapter));
719 adapter->GetParent(
720 IID_PPV_ARGS((IDXGIFactoryMedia**)getter_AddRefs(dxgiFactoryMedia)));
721 }
722
723 mSwapChainSurfaceHandle = gfx::DeviceManagerDx::CreateDCompSurfaceHandle();
724 if (!mSwapChainSurfaceHandle) {
725 gfxCriticalNote << "Failed to create DCompSurfaceHandle";
726 return false;
727 }
728
729 gfx::IntSize size = aTexture->AsRenderDXGITextureHost()->GetSize(0);
730 DXGI_ALPHA_MODE alpha_mode =
731 mIsOpaque ? DXGI_ALPHA_MODE_IGNORE : DXGI_ALPHA_MODE_PREMULTIPLIED;
732
733 DXGI_SWAP_CHAIN_DESC1 desc = {};
734 desc.Width = size.width;
735 desc.Height = size.height;
736 desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
737 desc.Stereo = FALSE;
738 desc.SampleDesc.Count = 1;
739 desc.BufferCount = 2;
740 desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
741 desc.Scaling = DXGI_SCALING_STRETCH;
742 desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
743 desc.Flags = 0;
744 desc.AlphaMode = alpha_mode;
745
746 HRESULT hr;
747 hr = dxgiFactoryMedia->CreateSwapChainForCompositionSurfaceHandle(
748 device, mSwapChainSurfaceHandle, &desc, nullptr,
749 getter_AddRefs(mVideoSwapChain));
750
751 if (FAILED(hr)) {
752 gfxCriticalNote << "Failed to create video SwapChain: " << gfx::hexa(hr);
753 return false;
754 }
755
756 mSwapChainSize = size;
757 return true;
758 }
759
760 // TODO: Replace with YUVRangedColorSpace
GetSourceDXGIColorSpace(const gfx::YUVColorSpace aYUVColorSpace,const gfx::ColorRange aColorRange)761 static Maybe<DXGI_COLOR_SPACE_TYPE> GetSourceDXGIColorSpace(
762 const gfx::YUVColorSpace aYUVColorSpace,
763 const gfx::ColorRange aColorRange) {
764 if (aYUVColorSpace == gfx::YUVColorSpace::BT601) {
765 if (aColorRange == gfx::ColorRange::FULL) {
766 return Some(DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P601);
767 } else {
768 return Some(DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P601);
769 }
770 } else if (aYUVColorSpace == gfx::YUVColorSpace::BT709) {
771 if (aColorRange == gfx::ColorRange::FULL) {
772 return Some(DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P709);
773 } else {
774 return Some(DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P709);
775 }
776 } else if (aYUVColorSpace == gfx::YUVColorSpace::BT2020) {
777 if (aColorRange == gfx::ColorRange::FULL) {
778 // XXX Add SMPTEST2084 handling. HDR content is not handled yet by
779 // video overlay.
780 return Some(DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P2020);
781 } else {
782 return Some(DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P2020);
783 }
784 }
785
786 return Nothing();
787 }
788
GetSourceDXGIColorSpace(const gfx::YUVRangedColorSpace aYUVColorSpace)789 static Maybe<DXGI_COLOR_SPACE_TYPE> GetSourceDXGIColorSpace(
790 const gfx::YUVRangedColorSpace aYUVColorSpace) {
791 const auto info = FromYUVRangedColorSpace(aYUVColorSpace);
792 return GetSourceDXGIColorSpace(info.space, info.range);
793 }
794
CallVideoProcessorBlt(RenderTextureHost * aTexture)795 bool DCSurfaceVideo::CallVideoProcessorBlt(RenderTextureHost* aTexture) {
796 HRESULT hr;
797 const auto videoDevice = mDCLayerTree->GetVideoDevice();
798 const auto videoContext = mDCLayerTree->GetVideoContext();
799 const auto texture = aTexture->AsRenderDXGITextureHost();
800
801 Maybe<DXGI_COLOR_SPACE_TYPE> sourceColorSpace =
802 GetSourceDXGIColorSpace(texture->GetYUVColorSpace());
803 if (sourceColorSpace.isNothing()) {
804 gfxCriticalNote << "Unsupported color space";
805 return false;
806 }
807
808 RefPtr<ID3D11Texture2D> texture2D = texture->GetD3D11Texture2DWithGL();
809 if (!texture2D) {
810 gfxCriticalNote << "Failed to get D3D11Texture2D";
811 return false;
812 }
813
814 if (!mVideoSwapChain) {
815 return false;
816 }
817
818 if (!mDCLayerTree->EnsureVideoProcessor(mSwapChainSize)) {
819 gfxCriticalNote << "EnsureVideoProcessor Failed";
820 return false;
821 }
822
823 RefPtr<IDXGISwapChain3> swapChain3;
824 mVideoSwapChain->QueryInterface(
825 (IDXGISwapChain3**)getter_AddRefs(swapChain3));
826 if (!swapChain3) {
827 gfxCriticalNote << "Failed to get IDXGISwapChain3";
828 return false;
829 }
830
831 RefPtr<ID3D11VideoContext1> videoContext1;
832 videoContext->QueryInterface(
833 (ID3D11VideoContext1**)getter_AddRefs(videoContext1));
834 if (!videoContext1) {
835 gfxCriticalNote << "Failed to get ID3D11VideoContext1";
836 return false;
837 }
838
839 const auto videoProcessor = mDCLayerTree->GetVideoProcessor();
840 const auto videoProcessorEnumerator =
841 mDCLayerTree->GetVideoProcessorEnumerator();
842
843 DXGI_COLOR_SPACE_TYPE inputColorSpace = sourceColorSpace.ref();
844 videoContext1->VideoProcessorSetStreamColorSpace1(videoProcessor, 0,
845 inputColorSpace);
846 // XXX when content is hdr or yuv swapchain, it need to use other color space.
847 DXGI_COLOR_SPACE_TYPE outputColorSpace =
848 DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
849 hr = swapChain3->SetColorSpace1(outputColorSpace);
850 if (FAILED(hr)) {
851 gfxCriticalNote << "SetColorSpace1 failed: " << gfx::hexa(hr);
852 return false;
853 }
854 videoContext1->VideoProcessorSetOutputColorSpace1(videoProcessor,
855 outputColorSpace);
856
857 D3D11_VIDEO_PROCESSOR_INPUT_VIEW_DESC inputDesc = {};
858 inputDesc.ViewDimension = D3D11_VPIV_DIMENSION_TEXTURE2D;
859 inputDesc.Texture2D.ArraySlice = 0;
860
861 RefPtr<ID3D11VideoProcessorInputView> inputView;
862 hr = videoDevice->CreateVideoProcessorInputView(
863 texture2D, videoProcessorEnumerator, &inputDesc,
864 getter_AddRefs(inputView));
865 if (FAILED(hr)) {
866 gfxCriticalNote << "ID3D11VideoProcessorInputView creation failed: "
867 << gfx::hexa(hr);
868 return false;
869 }
870
871 D3D11_VIDEO_PROCESSOR_STREAM stream = {};
872 stream.Enable = true;
873 stream.OutputIndex = 0;
874 stream.InputFrameOrField = 0;
875 stream.PastFrames = 0;
876 stream.FutureFrames = 0;
877 stream.pInputSurface = inputView.get();
878
879 RECT destRect;
880 destRect.left = 0;
881 destRect.top = 0;
882 destRect.right = mSwapChainSize.width;
883 destRect.bottom = mSwapChainSize.height;
884
885 videoContext->VideoProcessorSetOutputTargetRect(videoProcessor, TRUE,
886 &destRect);
887 videoContext->VideoProcessorSetStreamDestRect(videoProcessor, 0, TRUE,
888 &destRect);
889 RECT sourceRect;
890 sourceRect.left = 0;
891 sourceRect.top = 0;
892 sourceRect.right = mSwapChainSize.width;
893 sourceRect.bottom = mSwapChainSize.height;
894 videoContext->VideoProcessorSetStreamSourceRect(videoProcessor, 0, TRUE,
895 &sourceRect);
896
897 if (!mOutputView) {
898 RefPtr<ID3D11Texture2D> backBuf;
899 mVideoSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D),
900 (void**)getter_AddRefs(backBuf));
901
902 D3D11_VIDEO_PROCESSOR_OUTPUT_VIEW_DESC outputDesc = {};
903 outputDesc.ViewDimension = D3D11_VPOV_DIMENSION_TEXTURE2D;
904 outputDesc.Texture2D.MipSlice = 0;
905
906 hr = videoDevice->CreateVideoProcessorOutputView(
907 backBuf, videoProcessorEnumerator, &outputDesc,
908 getter_AddRefs(mOutputView));
909 if (FAILED(hr)) {
910 gfxCriticalNote << "ID3D11VideoProcessorOutputView creation failed: "
911 << gfx::hexa(hr);
912 return false;
913 }
914 }
915
916 hr = videoContext->VideoProcessorBlt(videoProcessor, mOutputView, 0, 1,
917 &stream);
918 if (FAILED(hr)) {
919 gfxCriticalNote << "VideoProcessorBlt failed: " << gfx::hexa(hr);
920 return false;
921 }
922
923 return true;
924 }
925
ReleaseDecodeSwapChainResources()926 void DCSurfaceVideo::ReleaseDecodeSwapChainResources() {
927 mOutputView = nullptr;
928 mVideoSwapChain = nullptr;
929 mDecodeSwapChain = nullptr;
930 mDecodeResource = nullptr;
931 if (mSwapChainSurfaceHandle) {
932 ::CloseHandle(mSwapChainSurfaceHandle);
933 mSwapChainSurfaceHandle = 0;
934 }
935 mSwapChainSize = gfx::IntSize();
936 }
937
DCTile(DCLayerTree * aDCLayerTree)938 DCTile::DCTile(DCLayerTree* aDCLayerTree) : mDCLayerTree(aDCLayerTree) {}
939
~DCTile()940 DCTile::~DCTile() {}
941
Initialize(int aX,int aY,wr::DeviceIntSize aSize,bool aIsOpaque)942 bool DCTile::Initialize(int aX, int aY, wr::DeviceIntSize aSize,
943 bool aIsOpaque) {
944 if (aSize.width <= 0 || aSize.height <= 0) {
945 return false;
946 }
947
948 // Initially, the entire tile is considered valid, unless it is set by
949 // the SetTileProperties method.
950 mValidRect.x = 0;
951 mValidRect.y = 0;
952 mValidRect.width = aSize.width;
953 mValidRect.height = aSize.height;
954
955 return true;
956 }
957
CreateEGLSurfaceForCompositionSurface(wr::DeviceIntRect aDirtyRect,wr::DeviceIntPoint * aOffset,RefPtr<IDCompositionSurface> aCompositionSurface,wr::DeviceIntPoint aSurfaceOffset)958 GLuint DCLayerTree::CreateEGLSurfaceForCompositionSurface(
959 wr::DeviceIntRect aDirtyRect, wr::DeviceIntPoint* aOffset,
960 RefPtr<IDCompositionSurface> aCompositionSurface,
961 wr::DeviceIntPoint aSurfaceOffset) {
962 MOZ_ASSERT(aCompositionSurface.get());
963
964 HRESULT hr;
965 const auto gl = GetGLContext();
966 RefPtr<ID3D11Texture2D> backBuf;
967 POINT offset;
968
969 RECT update_rect;
970 update_rect.left = aSurfaceOffset.x + aDirtyRect.min.x;
971 update_rect.top = aSurfaceOffset.y + aDirtyRect.min.y;
972 update_rect.right = aSurfaceOffset.x + aDirtyRect.max.x;
973 update_rect.bottom = aSurfaceOffset.y + aDirtyRect.max.y;
974
975 hr = aCompositionSurface->BeginDraw(&update_rect, __uuidof(ID3D11Texture2D),
976 (void**)getter_AddRefs(backBuf), &offset);
977 if (FAILED(hr)) {
978 gfxCriticalNote << "DCompositionSurface::BeginDraw failed: "
979 << gfx::hexa(hr);
980 RenderThread::Get()->HandleWebRenderError(WebRenderError::BEGIN_DRAW);
981 return false;
982 }
983
984 // DC includes the origin of the dirty / update rect in the draw offset,
985 // undo that here since WR expects it to be an absolute offset.
986 offset.x -= aDirtyRect.min.x;
987 offset.y -= aDirtyRect.min.y;
988
989 D3D11_TEXTURE2D_DESC desc;
990 backBuf->GetDesc(&desc);
991
992 const auto& gle = gl::GLContextEGL::Cast(gl);
993 const auto& egl = gle->mEgl;
994
995 const auto buffer = reinterpret_cast<EGLClientBuffer>(backBuf.get());
996
997 // Construct an EGLImage wrapper around the D3D texture for ANGLE.
998 const EGLint attribs[] = {LOCAL_EGL_NONE};
999 mEGLImage = egl->fCreateImage(EGL_NO_CONTEXT, LOCAL_EGL_D3D11_TEXTURE_ANGLE,
1000 buffer, attribs);
1001
1002 // Get the current FBO and RBO id, so we can restore them later
1003 GLint currentFboId, currentRboId;
1004 gl->fGetIntegerv(LOCAL_GL_DRAW_FRAMEBUFFER_BINDING, ¤tFboId);
1005 gl->fGetIntegerv(LOCAL_GL_RENDERBUFFER_BINDING, ¤tRboId);
1006
1007 // Create a render buffer object that is backed by the EGL image.
1008 gl->fGenRenderbuffers(1, &mColorRBO);
1009 gl->fBindRenderbuffer(LOCAL_GL_RENDERBUFFER, mColorRBO);
1010 gl->fEGLImageTargetRenderbufferStorage(LOCAL_GL_RENDERBUFFER, mEGLImage);
1011
1012 // Get or create an FBO for the specified dimensions
1013 GLuint fboId = GetOrCreateFbo(desc.Width, desc.Height);
1014
1015 // Attach the new renderbuffer to the FBO
1016 gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, fboId);
1017 gl->fFramebufferRenderbuffer(LOCAL_GL_DRAW_FRAMEBUFFER,
1018 LOCAL_GL_COLOR_ATTACHMENT0,
1019 LOCAL_GL_RENDERBUFFER, mColorRBO);
1020
1021 // Restore previous FBO and RBO bindings
1022 gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, currentFboId);
1023 gl->fBindRenderbuffer(LOCAL_GL_RENDERBUFFER, currentRboId);
1024
1025 aOffset->x = offset.x;
1026 aOffset->y = offset.y;
1027
1028 return fboId;
1029 }
1030
DestroyEGLSurface()1031 void DCLayerTree::DestroyEGLSurface() {
1032 const auto gl = GetGLContext();
1033
1034 if (mColorRBO) {
1035 gl->fDeleteRenderbuffers(1, &mColorRBO);
1036 mColorRBO = 0;
1037 }
1038
1039 if (mEGLImage) {
1040 const auto& gle = gl::GLContextEGL::Cast(gl);
1041 const auto& egl = gle->mEgl;
1042 egl->fDestroyImage(mEGLImage);
1043 mEGLImage = EGL_NO_IMAGE;
1044 }
1045 }
1046
1047 } // namespace wr
1048 } // namespace mozilla
1049