diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /gfx/webrender_bindings/RenderCompositorANGLE.cpp | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gfx/webrender_bindings/RenderCompositorANGLE.cpp')
-rw-r--r-- | gfx/webrender_bindings/RenderCompositorANGLE.cpp | 1025 |
1 files changed, 1025 insertions, 0 deletions
diff --git a/gfx/webrender_bindings/RenderCompositorANGLE.cpp b/gfx/webrender_bindings/RenderCompositorANGLE.cpp new file mode 100644 index 0000000000..e01ac6a187 --- /dev/null +++ b/gfx/webrender_bindings/RenderCompositorANGLE.cpp @@ -0,0 +1,1025 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "RenderCompositorANGLE.h" + +#include "GLContext.h" +#include "GLContextEGL.h" +#include "GLContextProvider.h" +#include "mozilla/gfx/DeviceManagerDx.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/gfx/StackArray.h" +#include "mozilla/layers/HelpersD3D11.h" +#include "mozilla/layers/SyncObject.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/webrender/DCLayerTree.h" +#include "mozilla/webrender/RenderThread.h" +#include "mozilla/widget/CompositorWidget.h" +#include "mozilla/widget/WinCompositorWidget.h" +#include "mozilla/WindowsVersion.h" +#include "mozilla/Telemetry.h" +#include "nsPrintfCString.h" +#include "FxROutputHandler.h" + +#include <d3d11.h> +#include <dcomp.h> +#include <dxgi1_2.h> + +// Flag for PrintWindow() that is defined in Winuser.h. It is defined since +// Windows 8.1. This allows PrintWindow to capture window content that is +// rendered with DirectComposition. +#undef PW_RENDERFULLCONTENT +#define PW_RENDERFULLCONTENT 0x00000002 + +namespace mozilla { +namespace wr { + +extern LazyLogModule gRenderThreadLog; +#define LOG(...) MOZ_LOG(gRenderThreadLog, LogLevel::Debug, (__VA_ARGS__)) + +/* static */ +UniquePtr<RenderCompositor> RenderCompositorANGLE::Create( + const RefPtr<widget::CompositorWidget>& aWidget, nsACString& aError) { + RefPtr<gl::GLContext> gl = RenderThread::Get()->SingletonGL(aError); + if (!gl) { + if (aError.IsEmpty()) { + aError.Assign("RcANGLE(no shared GL)"_ns); + } else { + aError.Append("(Create)"_ns); + } + return nullptr; + } + + UniquePtr<RenderCompositorANGLE> compositor = + MakeUnique<RenderCompositorANGLE>(aWidget, std::move(gl)); + if (!compositor->Initialize(aError)) { + return nullptr; + } + return compositor; +} + +RenderCompositorANGLE::RenderCompositorANGLE( + const RefPtr<widget::CompositorWidget>& aWidget, + RefPtr<gl::GLContext>&& aGL) + : RenderCompositor(aWidget), mGL(aGL) { + MOZ_ASSERT(mGL); + LOG("RenderCompositorANGLE::RenderCompositorANGLE()"); +} + +RenderCompositorANGLE::~RenderCompositorANGLE() { + LOG("RenderCompositorANGLE::~RenderCompositorANGLE()"); + + DestroyEGLSurface(); + MOZ_ASSERT(!mEGLSurface); +} + +ID3D11Device* RenderCompositorANGLE::GetDeviceOfEGLDisplay(nsACString& aError) { + const auto& gle = gl::GLContextEGL::Cast(mGL); + const auto& egl = gle->mEgl; + MOZ_ASSERT(egl); + if (!egl || + !egl->mLib->IsExtensionSupported(gl::EGLLibExtension::EXT_device_query)) { + aError.Assign("RcANGLE(no EXT_device_query support)"_ns); + return nullptr; + } + + // Fetch the D3D11 device. + EGLDeviceEXT eglDevice = nullptr; + egl->fQueryDisplayAttribEXT(LOCAL_EGL_DEVICE_EXT, (EGLAttrib*)&eglDevice); + MOZ_ASSERT(eglDevice); + ID3D11Device* device = nullptr; + egl->mLib->fQueryDeviceAttribEXT(eglDevice, LOCAL_EGL_D3D11_DEVICE_ANGLE, + (EGLAttrib*)&device); + if (!device) { + aError.Assign("RcANGLE(get D3D11Device from EGLDisplay failed)"_ns); + return nullptr; + } + return device; +} + +bool RenderCompositorANGLE::Initialize(nsACString& aError) { + // TODO(aosmond): This causes us to lose WebRender because it is unable to + // distinguish why we failed and retry once the reset is complete. This does + // appear to happen in the wild, so we really should try to do something + // differently here. + if (RenderThread::Get()->IsHandlingDeviceReset()) { + aError.Assign("RcANGLE(waiting device reset)"_ns); + return false; + } + + // Force enable alpha channel to make sure ANGLE use correct framebuffer + // formart + const auto& gle = gl::GLContextEGL::Cast(mGL); + const auto& egl = gle->mEgl; + if (!gl::CreateConfig(*egl, &mEGLConfig, /* bpp */ 32, + /* enableDepthBuffer */ false, mGL->IsGLES())) { + aError.Assign("RcANGLE(create EGLConfig failed)"_ns); + return false; + } + MOZ_ASSERT(mEGLConfig); + + mDevice = GetDeviceOfEGLDisplay(aError); + + if (!mDevice) { + return false; + } + + mDevice->GetImmediateContext(getter_AddRefs(mCtx)); + if (!mCtx) { + aError.Assign("RcANGLE(get immediate context failed)"_ns); + return false; + } + + // Disable native compositor when fast snapshot is needed. + // Taking snapshot of native compositor is very slow on Windows. + if (mWidget->GetCompositorOptions().NeedFastSnaphot()) { + mUseNativeCompositor = false; + } + + // Create DCLayerTree when DirectComposition is used. + if (gfx::gfxVars::UseWebRenderDCompWin()) { + HWND compositorHwnd = GetCompositorHwnd(); + if (compositorHwnd) { + mDCLayerTree = DCLayerTree::Create(mGL, mEGLConfig, mDevice, mCtx, + compositorHwnd, aError); + if (!mDCLayerTree) { + return false; + } + } else { + aError.Assign("RcANGLE(no compositor window)"_ns); + return false; + } + } + + // Create SwapChain when compositor is not used + if (!UseCompositor()) { + if (!CreateSwapChain(aError)) { + // SwapChain creation failed. + return false; + } + } + + mSyncObject = layers::SyncObjectHost::CreateSyncObjectHost(mDevice); + if (!mSyncObject->Init()) { + // Some errors occur. Clear the mSyncObject here. + // Then, there will be no texture synchronization. + aError.Assign("RcANGLE(create SyncObject failed)"_ns); + return false; + } + + InitializeUsePartialPresent(); + + return true; +} + +HWND RenderCompositorANGLE::GetCompositorHwnd() { + HWND hwnd = 0; + + if (XRE_IsGPUProcess()) { + hwnd = mWidget->AsWindows()->GetCompositorHwnd(); + } else if ( + StaticPrefs:: + gfx_webrender_enabled_no_gpu_process_with_angle_win_AtStartup()) { + MOZ_ASSERT(XRE_IsParentProcess()); + + // When GPU process does not exist, we do not need to use compositor window. + hwnd = mWidget->AsWindows()->GetHwnd(); + } + + return hwnd; +} + +bool RenderCompositorANGLE::CreateSwapChain(nsACString& aError) { + MOZ_ASSERT(!UseCompositor()); + + mFirstPresent = true; + HWND hwnd = mWidget->AsWindows()->GetHwnd(); + + RefPtr<IDXGIDevice> dxgiDevice; + mDevice->QueryInterface((IDXGIDevice**)getter_AddRefs(dxgiDevice)); + + RefPtr<IDXGIFactory> dxgiFactory; + { + RefPtr<IDXGIAdapter> adapter; + dxgiDevice->GetAdapter(getter_AddRefs(adapter)); + + adapter->GetParent( + IID_PPV_ARGS((IDXGIFactory**)getter_AddRefs(dxgiFactory))); + } + + RefPtr<IDXGIFactory2> dxgiFactory2; + HRESULT hr = dxgiFactory->QueryInterface( + (IDXGIFactory2**)getter_AddRefs(dxgiFactory2)); + if (FAILED(hr)) { + dxgiFactory2 = nullptr; + } + + CreateSwapChainForDCompIfPossible(dxgiFactory2); + if (gfx::gfxVars::UseWebRenderDCompWin() && !mSwapChain) { + MOZ_ASSERT(GetCompositorHwnd()); + aError.Assign("RcANGLE(create swapchain for dcomp failed)"_ns); + return false; + } + + if (!mSwapChain && dxgiFactory2) { + RefPtr<IDXGISwapChain1> swapChain1; + bool useTripleBuffering = false; + + DXGI_SWAP_CHAIN_DESC1 desc{}; + desc.Width = 0; + desc.Height = 0; + desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + desc.SampleDesc.Count = 1; + desc.SampleDesc.Quality = 0; + desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + + bool useFlipSequential = gfx::gfxVars::UseWebRenderFlipSequentialWin(); + if (useFlipSequential && !mWidget->AsWindows()->GetCompositorHwnd()) { + useFlipSequential = false; + gfxCriticalNoteOnce << "FLIP_SEQUENTIAL needs CompositorHwnd. Fallback"; + } + + if (useFlipSequential) { + useTripleBuffering = gfx::gfxVars::UseWebRenderTripleBufferingWin(); + if (useTripleBuffering) { + desc.BufferCount = 3; + } else { + desc.BufferCount = 2; + } + desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; + desc.Scaling = DXGI_SCALING_NONE; + } else { + desc.BufferCount = 1; + desc.SwapEffect = DXGI_SWAP_EFFECT_SEQUENTIAL; + desc.Scaling = DXGI_SCALING_STRETCH; + } + desc.Flags = 0; + + hr = dxgiFactory2->CreateSwapChainForHwnd( + mDevice, hwnd, &desc, nullptr, nullptr, getter_AddRefs(swapChain1)); + if (SUCCEEDED(hr) && swapChain1) { + DXGI_RGBA color = {1.0f, 1.0f, 1.0f, 1.0f}; + swapChain1->SetBackgroundColor(&color); + mSwapChain = swapChain1; + mSwapChain1 = swapChain1; + mUseTripleBuffering = useTripleBuffering; + } else if (useFlipSequential) { + gfxCriticalNoteOnce << "FLIP_SEQUENTIAL is not supported. Fallback"; + } + } + + if (!mSwapChain) { + if (mWidget->AsWindows()->GetCompositorHwnd()) { + // Destroy compositor window. + mWidget->AsWindows()->DestroyCompositorWindow(); + hwnd = mWidget->AsWindows()->GetHwnd(); + } + + DXGI_SWAP_CHAIN_DESC swapDesc{}; + swapDesc.BufferDesc.Width = 0; + swapDesc.BufferDesc.Height = 0; + swapDesc.BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + swapDesc.BufferDesc.RefreshRate.Numerator = 60; + swapDesc.BufferDesc.RefreshRate.Denominator = 1; + swapDesc.SampleDesc.Count = 1; + swapDesc.SampleDesc.Quality = 0; + swapDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + swapDesc.BufferCount = 1; + swapDesc.OutputWindow = hwnd; + swapDesc.Windowed = TRUE; + swapDesc.Flags = 0; + swapDesc.SwapEffect = DXGI_SWAP_EFFECT_SEQUENTIAL; + + HRESULT hr = dxgiFactory->CreateSwapChain(dxgiDevice, &swapDesc, + getter_AddRefs(mSwapChain)); + if (FAILED(hr)) { + aError.Assign( + nsPrintfCString("RcANGLE(swap chain create failed %lx)", hr)); + return false; + } + + RefPtr<IDXGISwapChain1> swapChain1; + hr = mSwapChain->QueryInterface( + (IDXGISwapChain1**)getter_AddRefs(swapChain1)); + if (SUCCEEDED(hr)) { + mSwapChain1 = swapChain1; + } + } + + // We need this because we don't want DXGI to respond to Alt+Enter. + dxgiFactory->MakeWindowAssociation(hwnd, DXGI_MWA_NO_WINDOW_CHANGES); + + if (!ResizeBufferIfNeeded()) { + aError.Assign("RcANGLE(resize buffer failed)"_ns); + return false; + } + + return true; +} + +void RenderCompositorANGLE::CreateSwapChainForDCompIfPossible( + IDXGIFactory2* aDXGIFactory2) { + if (!aDXGIFactory2 || !mDCLayerTree) { + return; + } + + HWND hwnd = GetCompositorHwnd(); + if (!hwnd) { + // When DirectComposition or DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL is used, + // compositor window needs to exist. + if (gfx::gfxVars::UseWebRenderDCompWin() || + gfx::gfxVars::UseWebRenderFlipSequentialWin()) { + gfxCriticalNote << "Compositor window was not created"; + } + return; + } + + // When compositor is enabled, CompositionSurface is used for rendering. + // It does not support triple buffering. + bool useTripleBuffering = + gfx::gfxVars::UseWebRenderTripleBufferingWin() && !UseCompositor(); + RefPtr<IDXGISwapChain1> swapChain1 = + CreateSwapChainForDComp(useTripleBuffering); + if (swapChain1) { + mSwapChain = swapChain1; + mSwapChain1 = swapChain1; + mUseTripleBuffering = useTripleBuffering; + mDCLayerTree->SetDefaultSwapChain(swapChain1); + } else { + // Clear CLayerTree on falire + mDCLayerTree = nullptr; + } +} + +RefPtr<IDXGISwapChain1> RenderCompositorANGLE::CreateSwapChainForDComp( + bool aUseTripleBuffering) { + HRESULT hr; + RefPtr<IDXGIDevice> dxgiDevice; + mDevice->QueryInterface((IDXGIDevice**)getter_AddRefs(dxgiDevice)); + + RefPtr<IDXGIFactory> dxgiFactory; + { + RefPtr<IDXGIAdapter> adapter; + dxgiDevice->GetAdapter(getter_AddRefs(adapter)); + + adapter->GetParent( + IID_PPV_ARGS((IDXGIFactory**)getter_AddRefs(dxgiFactory))); + } + + RefPtr<IDXGIFactory2> dxgiFactory2; + hr = dxgiFactory->QueryInterface( + (IDXGIFactory2**)getter_AddRefs(dxgiFactory2)); + if (FAILED(hr)) { + return nullptr; + } + + RefPtr<IDXGISwapChain1> swapChain1; + DXGI_SWAP_CHAIN_DESC1 desc{}; + // DXGI does not like 0x0 swapchains. Swap chain creation failed when 0x0 was + // set. + desc.Width = 1; + desc.Height = 1; + desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + desc.SampleDesc.Count = 1; + desc.SampleDesc.Quality = 0; + desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + if (aUseTripleBuffering) { + desc.BufferCount = 3; + } else { + desc.BufferCount = 2; + } + // DXGI_SCALING_NONE caused swap chain creation failure. + desc.Scaling = DXGI_SCALING_STRETCH; + desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; + desc.AlphaMode = DXGI_ALPHA_MODE_IGNORE; + desc.Flags = 0; + + hr = dxgiFactory2->CreateSwapChainForComposition(mDevice, &desc, nullptr, + getter_AddRefs(swapChain1)); + if (SUCCEEDED(hr) && swapChain1) { + DXGI_RGBA color = {1.0f, 1.0f, 1.0f, 1.0f}; + swapChain1->SetBackgroundColor(&color); + return swapChain1; + } + + return nullptr; +} + +bool RenderCompositorANGLE::BeginFrame() { + mWidget->AsWindows()->UpdateCompositorWndSizeIfNecessary(); + + if (!UseCompositor() && !ResizeBufferIfNeeded()) { + return false; + } + + if (!MakeCurrent()) { + gfxCriticalNote << "Failed to make render context current, can't draw."; + return false; + } + + if (RenderThread::Get()->SyncObjectNeeded() && mSyncObject) { + if (!mSyncObject->Synchronize(/* aFallible */ true)) { + // It's timeout or other error. Handle the device-reset here. + RenderThread::Get()->HandleDeviceReset( + "SyncObject", LOCAL_GL_UNKNOWN_CONTEXT_RESET_ARB); + return false; + } + } + return true; +} + +RenderedFrameId RenderCompositorANGLE::EndFrame( + const nsTArray<DeviceIntRect>& aDirtyRects) { + RenderedFrameId frameId = GetNextRenderFrameId(); + InsertGraphicsCommandsFinishedWaitQuery(frameId); + + if (!UseCompositor()) { + auto start = TimeStamp::Now(); + if (mWidget->AsWindows()->HasFxrOutputHandler()) { + // There is a Firefox Reality handler for this swapchain. Update this + // window's contents to the VR window. + FxROutputHandler* fxrHandler = + mWidget->AsWindows()->GetFxrOutputHandler(); + if (fxrHandler->TryInitialize(mSwapChain, mDevice)) { + fxrHandler->UpdateOutput(mCtx); + } + } + + const UINT interval = + mFirstPresent || + StaticPrefs:: + gfx_webrender_dcomp_video_swap_chain_present_interval_0() + ? 0 + : 1; + const UINT flags = 0; + + const LayoutDeviceIntSize& bufferSize = mBufferSize.ref(); + if (mUsePartialPresent && mSwapChain1) { + // Clear full render flag. + mFullRender = false; + // If there is no diry rect, we skip SwapChain present. + if (!aDirtyRects.IsEmpty()) { + int rectsCount = 0; + StackArray<RECT, 1> rects(aDirtyRects.Length()); + + for (size_t i = 0; i < aDirtyRects.Length(); ++i) { + const DeviceIntRect& rect = aDirtyRects[i]; + // Clip rect to bufferSize + int left = std::max(0, std::min(rect.min.x, bufferSize.width)); + int top = std::max(0, std::min(rect.min.y, bufferSize.height)); + int right = std::max(0, std::min(rect.max.x, bufferSize.width)); + int bottom = std::max(0, std::min(rect.max.y, bufferSize.height)); + + // When rect is not empty, the rect could be passed to Present1(). + if (left < right && top < bottom) { + rects[rectsCount].left = left; + rects[rectsCount].top = top; + rects[rectsCount].right = right; + rects[rectsCount].bottom = bottom; + rectsCount++; + } + } + + if (rectsCount > 0) { + DXGI_PRESENT_PARAMETERS params; + PodZero(¶ms); + params.DirtyRectsCount = rectsCount; + params.pDirtyRects = rects.data(); + + HRESULT hr; + hr = mSwapChain1->Present1(interval, flags, ¶ms); + if (FAILED(hr) && hr != DXGI_STATUS_OCCLUDED) { + gfxCriticalNote << "Present1 failed: " << gfx::hexa(hr); + mFullRender = true; + } + } + } + } else { + mSwapChain->Present(interval, flags); + } + auto end = TimeStamp::Now(); + mozilla::Telemetry::Accumulate(mozilla::Telemetry::COMPOSITE_SWAP_TIME, + (end - start).ToMilliseconds() * 10.); + + if (mFirstPresent && mDCLayerTree) { + // Wait for the GPU to finish executing its commands before + // committing the DirectComposition tree, or else the swapchain + // may flicker black when it's first presented. + RefPtr<IDXGIDevice2> dxgiDevice2; + mDevice->QueryInterface((IDXGIDevice2**)getter_AddRefs(dxgiDevice2)); + MOZ_ASSERT(dxgiDevice2); + + HANDLE event = ::CreateEvent(nullptr, false, false, nullptr); + HRESULT hr = dxgiDevice2->EnqueueSetEvent(event); + if (SUCCEEDED(hr)) { + DebugOnly<DWORD> result = ::WaitForSingleObject(event, INFINITE); + MOZ_ASSERT(result == WAIT_OBJECT_0); + } else { + gfxCriticalNoteOnce << "EnqueueSetEvent failed: " << gfx::hexa(hr); + } + ::CloseHandle(event); + } + mFirstPresent = false; + } + + if (mDisablingNativeCompositor) { + // During disabling native compositor, we need to wait all gpu tasks + // complete. Otherwise, rendering window could cause white flash. + WaitForPreviousGraphicsCommandsFinishedQuery(/* aWaitAll */ true); + mDisablingNativeCompositor = false; + } + + if (mDCLayerTree) { + mDCLayerTree->MaybeUpdateDebug(); + mDCLayerTree->MaybeCommit(); + } + + return frameId; +} + +bool RenderCompositorANGLE::WaitForGPU() { + // Note: this waits on the query we inserted in the previous frame, + // not the one we just inserted now. Example: + // Insert query #1 + // Present #1 + // (first frame, no wait) + // Insert query #2 + // Present #2 + // Wait for query #1. + // Insert query #3 + // Present #3 + // Wait for query #2. + // + // This ensures we're done reading textures before swapping buffers. + if (!StaticPrefs::gfx_webrender_wait_gpu_finished_disabled_AtStartup()) { + return WaitForPreviousGraphicsCommandsFinishedQuery(); + } + return true; +} + +bool RenderCompositorANGLE::ResizeBufferIfNeeded() { + MOZ_ASSERT(mSwapChain); + + LayoutDeviceIntSize size = mWidget->GetClientSize(); + + // DXGI does not like 0x0 swapchains. ResizeBuffers() failed when 0x0 was set + // when DComp is used. + size.width = std::max(size.width, 1); + size.height = std::max(size.height, 1); + + if (mBufferSize.isSome() && mBufferSize.ref() == size) { + MOZ_ASSERT(mEGLSurface); + return true; + } + + // Release EGLSurface of back buffer before calling ResizeBuffers(). + DestroyEGLSurface(); + + mBufferSize = Some(size); + + if (!CreateEGLSurface()) { + mBufferSize.reset(); + return false; + } + + if (mUsePartialPresent) { + mFullRender = true; + } + return true; +} + +bool RenderCompositorANGLE::CreateEGLSurface() { + MOZ_ASSERT(mBufferSize.isSome()); + MOZ_ASSERT(mEGLSurface == EGL_NO_SURFACE); + + HRESULT hr; + RefPtr<ID3D11Texture2D> backBuf; + + if (mBufferSize.isNothing()) { + gfxCriticalNote << "Buffer size is invalid"; + return false; + } + + const LayoutDeviceIntSize& size = mBufferSize.ref(); + + // Resize swap chain + DXGI_SWAP_CHAIN_DESC desc; + hr = mSwapChain->GetDesc(&desc); + if (FAILED(hr)) { + gfxCriticalNote << "Failed to read swap chain description: " + << gfx::hexa(hr) << " Size : " << size; + return false; + } + hr = mSwapChain->ResizeBuffers(desc.BufferCount, size.width, size.height, + DXGI_FORMAT_B8G8R8A8_UNORM, 0); + if (FAILED(hr)) { + gfxCriticalNote << "Failed to resize swap chain buffers: " << gfx::hexa(hr) + << " Size : " << size; + return false; + } + + hr = mSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), + (void**)getter_AddRefs(backBuf)); + if (hr == DXGI_ERROR_INVALID_CALL) { + // This happens on some GPUs/drivers when there's a TDR. + if (mDevice->GetDeviceRemovedReason() != S_OK) { + gfxCriticalError() << "GetBuffer returned invalid call: " << gfx::hexa(hr) + << " Size : " << size; + return false; + } + } + + const EGLint pbuffer_attribs[]{LOCAL_EGL_WIDTH, size.width, LOCAL_EGL_HEIGHT, + size.height, LOCAL_EGL_NONE}; + + const auto buffer = reinterpret_cast<EGLClientBuffer>(backBuf.get()); + + const auto& gle = gl::GLContextEGL::Cast(mGL); + const auto& egl = gle->mEgl; + const EGLSurface surface = egl->fCreatePbufferFromClientBuffer( + LOCAL_EGL_D3D_TEXTURE_ANGLE, buffer, mEGLConfig, pbuffer_attribs); + + if (!surface) { + EGLint err = egl->mLib->fGetError(); + gfxCriticalError() << "Failed to create Pbuffer of back buffer error: " + << gfx::hexa(err) << " Size : " << size; + return false; + } + + mEGLSurface = surface; + + return true; +} + +void RenderCompositorANGLE::DestroyEGLSurface() { + // Release EGLSurface of back buffer before calling ResizeBuffers(). + if (mEGLSurface) { + const auto& gle = gl::GLContextEGL::Cast(gl()); + const auto& egl = gle->mEgl; + gle->SetEGLSurfaceOverride(EGL_NO_SURFACE); + egl->fDestroySurface(mEGLSurface); + mEGLSurface = nullptr; + } +} + +void RenderCompositorANGLE::Pause() {} + +bool RenderCompositorANGLE::Resume() { return true; } + +void RenderCompositorANGLE::Update() { + // Update compositor window's size if it exists. + // It needs to be called here, since OS might update compositor + // window's size at unexpected timing. + mWidget->AsWindows()->UpdateCompositorWndSizeIfNecessary(); +} + +bool RenderCompositorANGLE::MakeCurrent() { + gl::GLContextEGL::Cast(gl())->SetEGLSurfaceOverride(mEGLSurface); + return gl()->MakeCurrent(); +} + +LayoutDeviceIntSize RenderCompositorANGLE::GetBufferSize() { + if (!UseCompositor()) { + MOZ_ASSERT(mBufferSize.isSome()); + if (mBufferSize.isNothing()) { + return LayoutDeviceIntSize(); + } + return mBufferSize.ref(); + } else { + auto size = mWidget->GetClientSize(); + // This size is used for WR DEBUG_OVERLAY. Its DCTile does not like 0. + size.width = std::max(size.width, 1); + size.height = std::max(size.height, 1); + return size; + } +} + +RefPtr<ID3D11Query> RenderCompositorANGLE::GetD3D11Query() { + RefPtr<ID3D11Query> query; + + if (mRecycledQuery) { + query = mRecycledQuery.forget(); + return query; + } + + CD3D11_QUERY_DESC desc(D3D11_QUERY_EVENT); + HRESULT hr = mDevice->CreateQuery(&desc, getter_AddRefs(query)); + if (FAILED(hr) || !query) { + gfxWarning() << "Could not create D3D11_QUERY_EVENT: " << gfx::hexa(hr); + return nullptr; + } + return query; +} + +void RenderCompositorANGLE::InsertGraphicsCommandsFinishedWaitQuery( + RenderedFrameId aFrameId) { + RefPtr<ID3D11Query> query; + query = GetD3D11Query(); + if (!query) { + return; + } + + mCtx->End(query); + mCtx->Flush(); + mWaitForPresentQueries.emplace(aFrameId, query); +} + +bool RenderCompositorANGLE::WaitForPreviousGraphicsCommandsFinishedQuery( + bool aWaitAll) { + size_t waitLatency = mUseTripleBuffering ? 3 : 2; + if (aWaitAll) { + waitLatency = 1; + } + + while (mWaitForPresentQueries.size() >= waitLatency) { + auto queryPair = mWaitForPresentQueries.front(); + BOOL result; + bool ret = + layers::WaitForFrameGPUQuery(mDevice, mCtx, queryPair.second, &result); + + if (!ret) { + mWaitForPresentQueries.pop(); + return false; + } + + // Recycle query for later use. + mRecycledQuery = queryPair.second; + mLastCompletedFrameId = queryPair.first; + mWaitForPresentQueries.pop(); + } + return true; +} + +RenderedFrameId RenderCompositorANGLE::GetLastCompletedFrameId() { + while (!mWaitForPresentQueries.empty()) { + auto queryPair = mWaitForPresentQueries.front(); + if (mCtx->GetData(queryPair.second, nullptr, 0, + D3D11_ASYNC_GETDATA_DONOTFLUSH) != S_OK) { + break; + } + + mRecycledQuery = queryPair.second; + mLastCompletedFrameId = queryPair.first; + mWaitForPresentQueries.pop(); + } + + nsPrintfCString marker("Pending frames %u", + (uint32_t)mWaitForPresentQueries.size()); + PROFILER_MARKER_TEXT("GetLastCompletedFrameId", GRAPHICS, {}, marker); + + return mLastCompletedFrameId; +} + +RenderedFrameId RenderCompositorANGLE::UpdateFrameId() { + RenderedFrameId frameId = GetNextRenderFrameId(); + InsertGraphicsCommandsFinishedWaitQuery(frameId); + return frameId; +} + +GLenum RenderCompositorANGLE::IsContextLost(bool aForce) { + // glGetGraphicsResetStatus does not always work to detect timeout detection + // and recovery (TDR). On Windows, ANGLE itself is just relying upon the same + // API, so we should not need to check it separately. + auto reason = mDevice->GetDeviceRemovedReason(); + switch (reason) { + case S_OK: + return LOCAL_GL_NO_ERROR; + case DXGI_ERROR_DEVICE_REMOVED: + case DXGI_ERROR_DRIVER_INTERNAL_ERROR: + NS_WARNING("Device reset due to system / different device"); + return LOCAL_GL_INNOCENT_CONTEXT_RESET_ARB; + case DXGI_ERROR_DEVICE_HUNG: + case DXGI_ERROR_DEVICE_RESET: + case DXGI_ERROR_INVALID_CALL: + gfxCriticalError() << "Device reset due to WR device: " + << gfx::hexa(reason); + return LOCAL_GL_GUILTY_CONTEXT_RESET_ARB; + default: + gfxCriticalError() << "Device reset with WR device unexpected reason: " + << gfx::hexa(reason); + return LOCAL_GL_UNKNOWN_CONTEXT_RESET_ARB; + } +} + +bool RenderCompositorANGLE::UseCompositor() { + if (!mUseNativeCompositor) { + return false; + } + + if (!mDCLayerTree || !gfx::gfxVars::UseWebRenderCompositor()) { + return false; + } + return true; +} + +bool RenderCompositorANGLE::SupportAsyncScreenshot() { + return !UseCompositor() && !mDisablingNativeCompositor; +} + +bool RenderCompositorANGLE::ShouldUseNativeCompositor() { + return UseCompositor(); +} + +void RenderCompositorANGLE::CompositorBeginFrame() { + mDCLayerTree->CompositorBeginFrame(); +} + +void RenderCompositorANGLE::CompositorEndFrame() { + mDCLayerTree->CompositorEndFrame(); +} + +void RenderCompositorANGLE::Bind(wr::NativeTileId aId, + wr::DeviceIntPoint* aOffset, uint32_t* aFboId, + wr::DeviceIntRect aDirtyRect, + wr::DeviceIntRect aValidRect) { + mDCLayerTree->Bind(aId, aOffset, aFboId, aDirtyRect, aValidRect); +} + +void RenderCompositorANGLE::Unbind() { mDCLayerTree->Unbind(); } + +void RenderCompositorANGLE::CreateSurface(wr::NativeSurfaceId aId, + wr::DeviceIntPoint aVirtualOffset, + wr::DeviceIntSize aTileSize, + bool aIsOpaque) { + mDCLayerTree->CreateSurface(aId, aVirtualOffset, aTileSize, aIsOpaque); +} + +void RenderCompositorANGLE::CreateExternalSurface(wr::NativeSurfaceId aId, + bool aIsOpaque) { + mDCLayerTree->CreateExternalSurface(aId, aIsOpaque); +} + +void RenderCompositorANGLE::DestroySurface(NativeSurfaceId aId) { + mDCLayerTree->DestroySurface(aId); +} + +void RenderCompositorANGLE::CreateTile(wr::NativeSurfaceId aId, int aX, + int aY) { + mDCLayerTree->CreateTile(aId, aX, aY); +} + +void RenderCompositorANGLE::DestroyTile(wr::NativeSurfaceId aId, int aX, + int aY) { + mDCLayerTree->DestroyTile(aId, aX, aY); +} + +void RenderCompositorANGLE::AttachExternalImage( + wr::NativeSurfaceId aId, wr::ExternalImageId aExternalImage) { + mDCLayerTree->AttachExternalImage(aId, aExternalImage); +} + +void RenderCompositorANGLE::AddSurface( + wr::NativeSurfaceId aId, const wr::CompositorSurfaceTransform& aTransform, + wr::DeviceIntRect aClipRect, wr::ImageRendering aImageRendering) { + mDCLayerTree->AddSurface(aId, aTransform, aClipRect, aImageRendering); +} + +void RenderCompositorANGLE::GetCompositorCapabilities( + CompositorCapabilities* aCaps) { + RenderCompositor::GetCompositorCapabilities(aCaps); + + if (StaticPrefs::gfx_webrender_dcomp_use_virtual_surfaces_AtStartup()) { + aCaps->virtual_surface_size = VIRTUAL_SURFACE_SIZE; + } else { + aCaps->virtual_surface_size = 0; + } + // DComp video overlay does not support negative scaling. See Bug 1831820 + aCaps->supports_external_compositor_surface_negative_scaling = false; +} + +void RenderCompositorANGLE::EnableNativeCompositor(bool aEnable) { + // XXX Re-enable native compositor is not handled yet. + MOZ_RELEASE_ASSERT(!mDisablingNativeCompositor); + MOZ_RELEASE_ASSERT(!aEnable); + LOG("RenderCompositorANGLE::EnableNativeCompositor() aEnable %d", aEnable); + + if (!UseCompositor()) { + return; + } + + mUseNativeCompositor = false; + mDCLayerTree->DisableNativeCompositor(); + + DestroyEGLSurface(); + mBufferSize.reset(); + + RefPtr<IDXGISwapChain1> swapChain1 = + CreateSwapChainForDComp(mUseTripleBuffering); + if (swapChain1) { + mSwapChain = swapChain1; + mDCLayerTree->SetDefaultSwapChain(swapChain1); + ResizeBufferIfNeeded(); + } else { + gfxCriticalNote << "Failed to re-create SwapChain"; + RenderThread::Get()->HandleWebRenderError(WebRenderError::NEW_SURFACE); + return; + } + mDisablingNativeCompositor = true; +} + +void RenderCompositorANGLE::InitializeUsePartialPresent() { + // Even when mSwapChain1 is null, we could enable WR partial present, since + // when mSwapChain1 is null, SwapChain is blit model swap chain with one + // buffer. + if (UseCompositor() || mWidget->AsWindows()->HasFxrOutputHandler() || + gfx::gfxVars::WebRenderMaxPartialPresentRects() <= 0) { + mUsePartialPresent = false; + } else { + mUsePartialPresent = true; + } +} + +bool RenderCompositorANGLE::UsePartialPresent() { return mUsePartialPresent; } + +bool RenderCompositorANGLE::RequestFullRender() { return mFullRender; } + +uint32_t RenderCompositorANGLE::GetMaxPartialPresentRects() { + if (!mUsePartialPresent) { + return 0; + } + return gfx::gfxVars::WebRenderMaxPartialPresentRects(); +} + +bool RenderCompositorANGLE::MaybeReadback( + const gfx::IntSize& aReadbackSize, const wr::ImageFormat& aReadbackFormat, + const Range<uint8_t>& aReadbackBuffer, bool* aNeedsYFlip) { + MOZ_ASSERT(aReadbackFormat == wr::ImageFormat::BGRA8); + + if (!UseCompositor()) { + return false; + } + + auto start = TimeStamp::Now(); + + HDC nulldc = ::GetDC(NULL); + HDC dc = ::CreateCompatibleDC(nulldc); + ::ReleaseDC(nullptr, nulldc); + if (!dc) { + gfxCriticalError() << "CreateCompatibleDC failed"; + return false; + } + + BITMAPV4HEADER header; + memset(&header, 0, sizeof(BITMAPV4HEADER)); + header.bV4Size = sizeof(BITMAPV4HEADER); + header.bV4Width = aReadbackSize.width; + header.bV4Height = -LONG(aReadbackSize.height); // top-to-buttom DIB + header.bV4Planes = 1; + header.bV4BitCount = 32; + header.bV4V4Compression = BI_BITFIELDS; + header.bV4RedMask = 0x00FF0000; + header.bV4GreenMask = 0x0000FF00; + header.bV4BlueMask = 0x000000FF; + header.bV4AlphaMask = 0xFF000000; + + void* readbackBits = nullptr; + HBITMAP bitmap = + ::CreateDIBSection(dc, reinterpret_cast<BITMAPINFO*>(&header), + DIB_RGB_COLORS, &readbackBits, nullptr, 0); + if (!bitmap) { + ::DeleteDC(dc); + gfxCriticalError() << "CreateDIBSection failed"; + return false; + } + + ::SelectObject(dc, bitmap); + + UINT flags = PW_CLIENTONLY | PW_RENDERFULLCONTENT; + HWND hwnd = mWidget->AsWindows()->GetHwnd(); + + mDCLayerTree->WaitForCommitCompletion(); + + BOOL result = ::PrintWindow(hwnd, dc, flags); + if (!result) { + ::DeleteObject(bitmap); + ::DeleteDC(dc); + gfxCriticalError() << "PrintWindow failed"; + return false; + } + + ::GdiFlush(); + + memcpy(&aReadbackBuffer[0], readbackBits, aReadbackBuffer.length()); + + ::DeleteObject(bitmap); + ::DeleteDC(dc); + + uint32_t latencyMs = round((TimeStamp::Now() - start).ToMilliseconds()); + if (latencyMs > 500) { + gfxCriticalNote << "Readback took too long: " << latencyMs << " ms"; + } + + if (aNeedsYFlip) { + *aNeedsYFlip = false; + } + + return true; +} + +} // namespace wr +} // namespace mozilla |