Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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::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 (auto* fxrHandler = mWidget->AsWindows()->GetFxrOutputHandler()) {
// There is a Firefox Reality handler for this swapchain. Update this
// window's contents to the VR window.
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(&params);
params.DirtyRectsCount = rectsCount;
params.pDirtyRects = rects.data();
HRESULT hr;
hr = mSwapChain1->Present1(interval, flags, &params);
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.
mUsePartialPresent = !UseCompositor() &&
!mWidget->AsWindows()->HasFxrOutputHandler() &&
gfx::gfxVars::WebRenderMaxPartialPresentRects() > 0;
}
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 mozilla::wr