Bug 1771374 - Create swapchain with premult-alpha if hasAlpha. r=gfx-reviewers,lsalzman
[gecko.git] / gfx / webrender_bindings / DCLayerTree.cpp
blob611dbe8e3b677d83fd98c0aa6ca1af9f6c647e38
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"
27 #include "WinUtils.h"
29 #undef _WIN32_WINNT
30 #define _WIN32_WINNT _WIN32_WINNT_WINBLUE
31 #undef NTDDI_VERSION
32 #define NTDDI_VERSION NTDDI_WINBLUE
34 #include <d3d11.h>
35 #include <d3d11_1.h>
36 #include <dcomp.h>
37 #include <dxgi1_2.h>
39 namespace mozilla {
40 namespace wr {
42 extern LazyLogModule gRenderThreadLog;
43 #define LOG(...) MOZ_LOG(gRenderThreadLog, LogLevel::Debug, (__VA_ARGS__))
45 UniquePtr<GpuOverlayInfo> DCLayerTree::sGpuOverlayInfo;
47 /* static */
48 UniquePtr<DCLayerTree> DCLayerTree::Create(gl::GLContext* aGL,
49 EGLConfig aEGLConfig,
50 ID3D11Device* aDevice,
51 ID3D11DeviceContext* aCtx,
52 HWND aHwnd, nsACString& aError) {
53 RefPtr<IDCompositionDevice2> dCompDevice =
54 gfx::DeviceManagerDx::Get()->GetDirectCompositionDevice();
55 if (!dCompDevice) {
56 aError.Assign("DCLayerTree(no device)"_ns);
57 return nullptr;
60 auto layerTree =
61 MakeUnique<DCLayerTree>(aGL, aEGLConfig, aDevice, aCtx, dCompDevice);
62 if (!layerTree->Initialize(aHwnd, aError)) {
63 return nullptr;
66 return layerTree;
69 void DCLayerTree::Shutdown() { DCLayerTree::sGpuOverlayInfo = nullptr; }
71 DCLayerTree::DCLayerTree(gl::GLContext* aGL, EGLConfig aEGLConfig,
72 ID3D11Device* aDevice, ID3D11DeviceContext* aCtx,
73 IDCompositionDevice2* aCompositionDevice)
74 : mGL(aGL),
75 mEGLConfig(aEGLConfig),
76 mDevice(aDevice),
77 mCtx(aCtx),
78 mCompositionDevice(aCompositionDevice),
79 mDebugCounter(false),
80 mDebugVisualRedrawRegions(false),
81 mEGLImage(EGL_NO_IMAGE),
82 mColorRBO(0),
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();
96 DestroyEGLSurface();
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) {
106 HRESULT hr;
108 RefPtr<IDCompositionDesktopDevice> desktopDevice;
109 hr = mCompositionDevice->QueryInterface(
110 (IDCompositionDesktopDevice**)getter_AddRefs(desktopDevice));
111 if (FAILED(hr)) {
112 aError.Assign(nsPrintfCString(
113 "DCLayerTree(get IDCompositionDesktopDevice failed %lx)", hr));
114 return false;
117 hr = desktopDevice->CreateTargetForHwnd(aHwnd, TRUE,
118 getter_AddRefs(mCompositionTarget));
119 if (FAILED(hr)) {
120 aError.Assign(nsPrintfCString(
121 "DCLayerTree(create DCompositionTarget failed %lx)", hr));
122 return false;
125 hr = mCompositionDevice->CreateVisual(getter_AddRefs(mRootVisual));
126 if (FAILED(hr)) {
127 aError.Assign(nsPrintfCString(
128 "DCLayerTree(create root DCompositionVisual failed %lx)", hr));
129 return false;
132 hr =
133 mCompositionDevice->CreateVisual(getter_AddRefs(mDefaultSwapChainVisual));
134 if (FAILED(hr)) {
135 aError.Assign(nsPrintfCString(
136 "DCLayerTree(create swap chain DCompositionVisual failed %lx)", hr));
137 return false;
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);
157 return true;
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)))) {
175 return false;
178 if (FAILED(output4->CheckOverlayColorSpaceSupport(
179 aDxgiFormat, aDxgiColorSpace, aD3d11Device,
180 &colorSpaceSupportFlags))) {
181 return false;
184 return (colorSpaceSupportFlags &
185 DXGI_OVERLAY_COLOR_SPACE_SUPPORT_FLAG_PRESENT);
188 bool DCLayerTree::InitializeVideoOverlaySupport() {
189 MOZ_ASSERT(IsWin10AnniversaryUpdateOrLater());
191 HRESULT hr;
193 hr = mDevice->QueryInterface(
194 (ID3D11VideoDevice**)getter_AddRefs(mVideoDevice));
195 if (FAILED(hr)) {
196 gfxCriticalNote << "Failed to get D3D11VideoDevice: " << gfx::hexa(hr);
197 return false;
200 hr =
201 mCtx->QueryInterface((ID3D11VideoContext**)getter_AddRefs(mVideoContext));
202 if (FAILED(hr)) {
203 gfxCriticalNote << "Failed to get D3D11VideoContext: " << gfx::hexa(hr);
204 return false;
207 if (sGpuOverlayInfo) {
208 return true;
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));
218 unsigned int i = 0;
219 while (true) {
220 RefPtr<IDXGIOutput> output;
221 if (FAILED(adapter->EnumOutputs(i++, getter_AddRefs(output)))) {
222 break;
224 RefPtr<IDXGIOutput3> output3;
225 if (FAILED(output->QueryInterface(__uuidof(IDXGIOutput3),
226 getter_AddRefs(output3)))) {
227 break;
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) {
267 break;
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());
284 return true;
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();
308 if (updated) {
309 mPendingCommit = true;
313 void DCLayerTree::MaybeCommit() {
314 if (!mPendingCommit) {
315 return;
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();
329 mPrevLayers.clear();
330 mRootVisual->RemoveAllVisuals();
333 bool DCLayerTree::MaybeUpdateDebugCounter() {
334 bool debugCounter = StaticPrefs::gfx_webrender_debug_dcomp_counter();
335 if (mDebugCounter == debugCounter) {
336 return false;
339 RefPtr<IDCompositionDeviceDebug> debugDevice;
340 HRESULT hr = mCompositionDevice->QueryInterface(
341 (IDCompositionDeviceDebug**)getter_AddRefs(debugDevice));
342 if (FAILED(hr)) {
343 return false;
346 if (debugCounter) {
347 debugDevice->EnableDebugCounters();
348 } else {
349 debugDevice->DisableDebugCounters();
352 mDebugCounter = debugCounter;
353 return true;
356 bool DCLayerTree::MaybeUpdateDebugVisualRedrawRegions() {
357 bool debugVisualRedrawRegions =
358 StaticPrefs::gfx_webrender_debug_dcomp_redraw_regions();
359 if (mDebugVisualRedrawRegions == debugVisualRedrawRegions) {
360 return false;
363 RefPtr<IDCompositionVisualDebug> visualDebug;
364 HRESULT hr = mRootVisual->QueryInterface(
365 (IDCompositionVisualDebug**)getter_AddRefs(visualDebug));
366 if (FAILED(hr)) {
367 return false;
370 if (debugVisualRedrawRegions) {
371 visualDebug->EnableRedrawRegions();
372 } else {
373 visualDebug->DisableRedrawRegions();
376 mDebugVisualRedrawRegions = debugVisualRedrawRegions;
377 return true;
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;
387 if (!same) {
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();
400 if (!same) {
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.
430 --len;
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()) {
462 return;
465 RefPtr<IDCompositionSurface> surface = mCurrentSurface.ref();
466 surface->EndDraw();
468 DestroyEGLSurface();
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.
479 return;
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 << ")";
489 auto surface =
490 MakeUnique<DCSurface>(aTileSize, aVirtualOffset, aIsOpaque, this);
491 if (!surface->Initialize()) {
492 gfxCriticalNote << "Failed to initialize DCSurface: " << wr::AsUint64(aId);
493 return;
496 mDCSurfaces[aId] = std::move(surface);
499 void DCLayerTree::CreateExternalSurface(wr::NativeSurfaceId aId,
500 bool aIsOpaque) {
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);
508 return;
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();
568 if (surfaceSc) {
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);
601 } else {
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();
611 GLuint fboId = 0;
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) {
616 fboId = it->fboId;
617 it->lastFrameUsed = mCurrentFrame;
618 break;
622 // If not, create a new FBO with attached depth buffer
623 if (fboId == 0) {
624 // Create the depth buffer
625 GLuint depthRboId;
626 gl->fGenRenderbuffers(1, &depthRboId);
627 gl->fBindRenderbuffer(LOCAL_GL_RENDERBUFFER, depthRboId);
628 gl->fRenderbufferStorage(LOCAL_GL_RENDERBUFFER, LOCAL_GL_DEPTH_COMPONENT24,
629 aWidth, aHeight);
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
640 // entries that
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);
651 return fboId;
654 bool DCLayerTree::EnsureVideoProcessorAtLeast(const gfx::IntSize& aInputSize,
655 const gfx::IntSize& aOutputSize) {
656 HRESULT hr;
658 if (!mVideoDevice || !mVideoContext) {
659 return false;
662 if (mVideoProcessor && (aInputSize <= mVideoInputSize) &&
663 (aOutputSize <= mVideoOutputSize)) {
664 return true;
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));
684 if (FAILED(hr)) {
685 gfxCriticalNote << "Failed to create VideoProcessorEnumerator: "
686 << gfx::hexa(hr);
687 return false;
690 hr = mVideoDevice->CreateVideoProcessor(mVideoProcessorEnumerator, 0,
691 getter_AddRefs(mVideoProcessor));
692 if (FAILED(hr)) {
693 mVideoProcessor = nullptr;
694 mVideoProcessorEnumerator = nullptr;
695 gfxCriticalNote << "Failed to create VideoProcessor: " << gfx::hexa(hr);
696 return false;
699 // Reduce power cosumption
700 // By default, the driver might perform certain processing tasks automatically
701 mVideoContext->VideoProcessorSetStreamAutoProcessingMode(mVideoProcessor, 0,
702 FALSE);
704 mVideoInputSize = aInputSize;
705 mVideoOutputSize = aOutputSize;
707 return true;
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;
736 info.mNv12Overlay =
737 FlagsToOverlaySupportType(sGpuOverlayInfo->mNv12OverlaySupportFlags,
738 /* aSoftwareOverlaySupported */ false);
739 info.mYuy2Overlay =
740 FlagsToOverlaySupportType(sGpuOverlayInfo->mYuy2OverlaySupportFlags,
741 /* aSoftwareOverlaySupported */ false);
742 info.mBgra8Overlay =
743 FlagsToOverlaySupportType(sGpuOverlayInfo->mBgra8OverlaySupportFlags,
744 /* aSoftwareOverlaySupported */ true);
745 info.mRgb10a2Overlay =
746 FlagsToOverlaySupportType(sGpuOverlayInfo->mRgb10a2OverlaySupportFlags,
747 /* aSoftwareOverlaySupported */ false);
749 return info;
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() {
764 HRESULT hr;
765 const auto dCompDevice = mDCLayerTree->GetCompositionDevice();
766 hr = dCompDevice->CreateVisual(getter_AddRefs(mVisual));
767 if (FAILED(hr)) {
768 gfxCriticalNote << "Failed to create DCompositionVisual: " << gfx::hexa(hr);
769 return false;
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));
784 return true;
787 void DCSurface::CreateTile(int aX, int aY) {
788 TileKey key(aX, 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;
794 return;
797 mAllocatedRectDirty = true;
799 mDCTiles[key] = std::move(tile);
802 void DCSurface::DestroyTile(int aX, int aY) {
803 TileKey key(aX, aY);
804 mAllocatedRectDirty = true;
805 mDCTiles.erase(key);
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);
819 RECT rect;
821 rect.left = (LONG)(mVirtualOffset.x + it->first.mX * mTileSize.width +
822 tile->mValidRect.x);
823 rect.top = (LONG)(mVirtualOffset.y + it->first.mY * mTileSize.height +
824 tile->mValidRect.y);
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 {
837 TileKey key(aX, aY);
838 auto tile_it = mDCTiles.find(key);
839 MOZ_RELEASE_ASSERT(tile_it != mDCTiles.end());
840 return tile_it->second.get();
843 // -
844 // DCSurfaceSwapChain
846 DCSurfaceSwapChain::DCSurfaceSwapChain(bool aIsOpaque,
847 DCLayerTree* aDCLayerTree)
848 : DCSurface(wr::DeviceIntSize{}, wr::DeviceIntPoint{}, aIsOpaque,
849 aDCLayerTree) {
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) {
857 return true;
859 return false;
862 bool IsYuv(const gfx::SurfaceFormat aFormat) {
863 switch (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:
869 return true;
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:
891 return false;
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();
903 if (!textureDxgi) {
904 gfxCriticalNote << "Unsupported RenderTexture for overlay: "
905 << gfx::hexa(texture);
906 return;
909 if (mSrc && mSrc->texture == textureDxgi) {
910 return; // Dupe.
913 Src src = {};
914 src.texture = textureDxgi;
915 src.format = src.texture->GetFormat();
916 src.size = src.texture->GetSize(0);
917 src.space = {
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;
926 mSrc = Some(src);
929 // -
931 Maybe<DCSurfaceSwapChain::Dest> CreateSwapChain(ID3D11Device&, gfx::IntSize,
932 DXGI_FORMAT, DXGI_ALPHA_MODE,
933 DXGI_COLOR_SPACE_TYPE);
935 // -
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) {
942 return {};
943 } else if (*space.yuvRange == gfx::ColorRange::FULL) {
944 return Some(DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P601);
945 } else {
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);
954 } else {
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);
964 } else {
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
973 // video overlay.
974 return Some(DXGI_COLOR_SPACE_YCBCR_FULL_G22_LEFT_P2020);
975 } else {
976 return Some(DXGI_COLOR_SPACE_YCBCR_STUDIO_G22_LEFT_P2020);
978 case gfx::ColorSpace2::DISPLAY_P3:
979 return {};
981 MOZ_ASSERT_UNREACHABLE();
984 // -
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);
994 return ret;
996 case gfx::ColorSpace2::DISPLAY_P3:
997 ret.chrom = color::Chromaticities::DisplayP3();
998 ret.tf = {color::PiecewiseGammaDesc::DisplayP3()};
999 MOZ_ASSERT(!space.yuvRange);
1000 return ret;
1002 case gfx::ColorSpace2::BT601_525:
1003 ret.chrom = color::Chromaticities::Rec601_525_Ntsc();
1004 ret.tf = {color::PiecewiseGammaDesc::Rec709()};
1005 if (space.yuvRange) {
1006 ret.yuv = {
1007 {color::YuvLumaCoeffs::Rec709(), color::YcbcrDesc::Narrow8()}};
1008 if (*space.yuvRange == gfx::ColorRange::FULL) {
1009 ret.yuv->ycbcr = color::YcbcrDesc::Full8();
1012 return ret;
1014 case gfx::ColorSpace2::BT709:
1015 ret.chrom = color::Chromaticities::Rec709();
1016 ret.tf = {color::PiecewiseGammaDesc::Rec709()};
1017 if (space.yuvRange) {
1018 ret.yuv = {
1019 {color::YuvLumaCoeffs::Rec709(), color::YcbcrDesc::Narrow8()}};
1020 if (*space.yuvRange == gfx::ColorRange::FULL) {
1021 ret.yuv->ycbcr = color::YcbcrDesc::Full8();
1024 return ret;
1026 case gfx::ColorSpace2::BT2020:
1027 ret.chrom = color::Chromaticities::Rec2020();
1028 ret.tf = {color::PiecewiseGammaDesc::Rec2020_10bit()};
1029 if (space.yuvRange) {
1030 ret.yuv = {
1031 {color::YuvLumaCoeffs::Rec709(), color::YcbcrDesc::Narrow8()}};
1032 if (*space.yuvRange == gfx::ColorRange::FULL) {
1033 ret.yuv->ycbcr = color::YcbcrDesc::Full8();
1036 return ret;
1038 MOZ_ASSERT_UNREACHABLE();
1041 // -
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;
1061 } else {
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:
1079 break;
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
1088 // though.
1090 // Let's do DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P2020 +
1091 // DXGI_FORMAT_R10G10B10A2_UNORM
1092 plan = {
1093 // Rec2020 g2.2 rgb10
1094 plan.srcSpace,
1095 {color::Chromaticities::Rec2020(),
1096 {color::PiecewiseGammaDesc::Srgb()}},
1097 DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P2020, // Note that this is srgb
1098 // gamma!
1099 DXGI_FORMAT_R10G10B10A2_UNORM,
1101 plan = {
1102 // Actually, that doesn't work, so use scRGB g1.0 rgb16f
1103 plan.srcSpace,
1104 {color::Chromaticities::Rec709(), {}},
1105 DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709, // scRGB
1106 DXGI_FORMAT_R16G16B16A16_FLOAT,
1108 break;
1110 if (!doColorManagement) {
1111 plan.dstSpace = plan.srcSpace;
1112 plan.dstSpace.yuv = {};
1115 return {{}, Some(plan)};
1118 // -
1120 Maybe<gfx::Matrix> DCSurfaceSwapChain::EnsurePresented(
1121 const gfx::Matrix& aTransform) {
1122 MOZ_RELEASE_ASSERT(mSrc);
1123 const auto& srcSize = mSrc->size;
1125 // -
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) {
1159 dstSize.width += 1;
1161 if (dstSize.height % 2 == 1) {
1162 dstSize.height += 1;
1165 // -
1167 if (mDest && mDest->dest->size != dstSize) {
1168 mDest = Nothing();
1170 if (mDest && mDest->srcSpace != mSrc->space) {
1171 mDest = Nothing();
1174 // -
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.
1185 // From sotaro:
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
1202 // the list again.
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;
1217 if (needsPresent) {
1218 // Reuse previous method?
1219 if (mDest) {
1220 if (!CallBlit()) {
1221 mDest = Nothing();
1225 if (!mDest) {
1226 mDest.emplace();
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();
1242 if (!mDest->dest) {
1243 mDest->dest =
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);
1256 if (!CallBlit()) {
1257 RenderThread::Get()->NotifyWebRenderError(
1258 wr::WebRenderError::VIDEO_OVERLAY);
1259 return {};
1262 const auto hr = mDest->dest->swapChain->Present(0, 0);
1263 if (FAILED(hr)) {
1264 gfxCriticalNoteOnce << "video Present failed: " << gfx::hexa(hr);
1267 mSrc->needsPresent = false;
1268 mDest->needsPresent = false;
1271 // -
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));
1291 adapter->GetParent(
1292 IID_PPV_ARGS((IDXGIFactoryMedia**)getter_AddRefs(dxgiFactoryMedia)));
1294 RefPtr<IDXGIFactory2> dxgiFactory2;
1296 RefPtr<IDXGIAdapter> adapter;
1297 dxgiDevice->GetAdapter(getter_AddRefs(adapter));
1298 adapter->GetParent(
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";
1306 return {};
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;
1327 HRESULT hr;
1328 bool useSurfaceHandle = true;
1329 if (useSurfaceHandle) {
1330 hr = dxgiFactoryMedia->CreateSwapChainForCompositionSurfaceHandle(
1331 &device, *swapChain.swapChainSurfaceHandle, &desc, nullptr,
1332 getter_AddRefs(swapChain1));
1333 } else {
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));
1340 if (FAILED(hr)) {
1341 gfxCriticalNote << "Failed to create output SwapChain: " << gfx::hexa(hr)
1342 << " " << swapChain.size;
1343 return {};
1346 swapChain1->QueryInterface(
1347 static_cast<IDXGISwapChain3**>(getter_AddRefs(swapChain.swapChain)));
1348 if (!swapChain.swapChain) {
1349 gfxCriticalNote << "Failed to get IDXGISwapChain3";
1350 return {};
1353 const auto cmsMode = GfxColorManagementMode();
1354 const bool doColorManagement = cmsMode != CMSMode::Off;
1355 if (doColorManagement) {
1356 hr = swapChain.swapChain->SetColorSpace1(aColorSpace);
1357 if (FAILED(hr)) {
1358 gfxCriticalNote << "SetColorSpace1 failed: " << gfx::hexa(hr);
1359 return {};
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);
1372 HRESULT hr;
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";
1380 return false;
1382 const auto videoProcessor = mDCLayerTree->GetVideoProcessor();
1383 const auto videoProcessorEnumerator =
1384 mDCLayerTree->GetVideoProcessorEnumerator();
1386 // -
1387 // Set color spaces
1390 RefPtr<ID3D11VideoContext1> videoContext1;
1391 videoContext->QueryInterface(
1392 (ID3D11VideoContext1**)getter_AddRefs(videoContext1));
1393 if (!videoContext1) {
1394 gfxCriticalNote << "Failed to get ID3D11VideoContext1";
1395 return false;
1397 const auto& plan = *mDest->plan.videoProcessor;
1398 videoContext1->VideoProcessorSetStreamColorSpace1(videoProcessor, 0,
1399 plan.srcSpace);
1400 videoContext1->VideoProcessorSetOutputColorSpace1(videoProcessor,
1401 mDest->dest->space);
1404 // -
1405 // inputView
1407 RefPtr<ID3D11Texture2D> texture2D = texture->GetD3D11Texture2DWithGL();
1408 if (!texture2D) {
1409 gfxCriticalNote << "Failed to get D3D11Texture2D";
1410 return false;
1413 if (!mSrc->texture->LockInternal()) {
1414 gfxCriticalNote << "CallVideoProcessorBlt LockInternal failed.";
1415 return false;
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));
1427 if (FAILED(hr)) {
1428 gfxCriticalNote << "ID3D11VideoProcessorInputView creation failed: "
1429 << gfx::hexa(hr);
1430 return false;
1433 // -
1434 // outputView
1436 RECT destRect;
1437 destRect.left = 0;
1438 destRect.top = 0;
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,
1444 &destRect);
1445 videoContext->VideoProcessorSetStreamDestRect(videoProcessor, 0, TRUE,
1446 &destRect);
1447 RECT sourceRect;
1448 sourceRect.left = 0;
1449 sourceRect.top = 0;
1450 sourceRect.right = mSrc->size.width;
1451 sourceRect.bottom = mSrc->size.height;
1452 videoContext->VideoProcessorSetStreamSourceRect(videoProcessor, 0, TRUE,
1453 &sourceRect);
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));
1468 if (FAILED(hr)) {
1469 gfxCriticalNote << "ID3D11VideoProcessorOutputView creation failed: "
1470 << gfx::hexa(hr);
1471 return false;
1475 // -
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
1488 // render into.
1489 hr = videoContext->VideoProcessorBlt(videoProcessor, outputView, 0, 1,
1490 &stream);
1491 if (FAILED(hr)) {
1492 gfxCriticalNote << "VideoProcessorBlt failed: " << gfx::hexa(hr);
1493 return false;
1496 return true;
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;
1515 return ret;
1518 // -
1519 // Bind LUT
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) {
1528 if (!mDest->lut) {
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);
1536 // -
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);
1547 // -
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});
1559 return true;
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));
1585 // -
1586 // Draw
1588 auto lutUintIfNeeded = Some(LUT_TEX_UNIT);
1589 auto fragConvert = gl::kFragConvert_ColorLut;
1590 if (!mDest->lut) {
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));
1617 return true;
1620 // -
1622 DCTile::DCTile(DCLayerTree* aDCLayerTree) : mDCLayerTree(aDCLayerTree) {}
1624 DCTile::~DCTile() {}
1626 bool DCTile::Initialize(int aX, int aY, wr::DeviceIntSize aSize,
1627 bool aIsOpaque) {
1628 if (aSize.width <= 0 || aSize.height <= 0) {
1629 return false;
1632 // Initially, the entire tile is considered valid, unless it is set by
1633 // the SetTileProperties method.
1634 mValidRect.x = 0;
1635 mValidRect.y = 0;
1636 mValidRect.width = aSize.width;
1637 mValidRect.height = aSize.height;
1639 return true;
1642 GLuint DCLayerTree::CreateEGLSurfaceForCompositionSurface(
1643 wr::DeviceIntRect aDirtyRect, wr::DeviceIntPoint* aOffset,
1644 RefPtr<IDCompositionSurface> aCompositionSurface,
1645 wr::DeviceIntPoint aSurfaceOffset) {
1646 MOZ_ASSERT(aCompositionSurface.get());
1648 HRESULT hr;
1649 const auto gl = GetGLContext();
1650 RefPtr<ID3D11Texture2D> backBuf;
1651 POINT offset;
1653 RECT update_rect;
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);
1661 if (FAILED(hr)) {
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);
1667 return false;
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,
1686 buffer, attribs);
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, &currentFboId);
1691 gl->fGetIntegerv(LOCAL_GL_RENDERBUFFER_BINDING, &currentRboId);
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;
1714 return fboId;
1717 void DCLayerTree::DestroyEGLSurface() {
1718 const auto gl = GetGLContext();
1720 if (mColorRBO) {
1721 gl->fDeleteRenderbuffers(1, &mColorRBO);
1722 mColorRBO = 0;
1725 if (mEGLImage) {
1726 const auto& gle = gl::GLContextEGL::Cast(gl);
1727 const auto& egl = gle->mEgl;
1728 egl->fDestroyImage(mEGLImage);
1729 mEGLImage = EGL_NO_IMAGE;
1733 } // namespace wr
1734 } // namespace mozilla