gecko-dev/widget/windows/WinCompositorWidget.cpp

373 строки
11 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 "WinCompositorWidget.h"
#include "mozilla/StaticPrefs_layers.h"
#include "mozilla/gfx/DeviceManagerDx.h"
#include "mozilla/gfx/Point.h"
#include "mozilla/layers/Compositor.h"
#include "mozilla/layers/CompositorThread.h"
#include "mozilla/webrender/RenderThread.h"
#include "mozilla/widget/PlatformWidgetTypes.h"
#include "nsWindow.h"
#include "VsyncDispatcher.h"
#include "WinCompositorWindowThread.h"
#include "VRShMem.h"
#include <ddraw.h>
namespace mozilla {
namespace widget {
using namespace mozilla::gfx;
using namespace mozilla;
WinCompositorWidget::WinCompositorWidget(
const WinCompositorWidgetInitData& aInitData,
const layers::CompositorOptions& aOptions)
: CompositorWidget(aOptions),
mWidgetKey(aInitData.widgetKey()),
mWnd(reinterpret_cast<HWND>(aInitData.hWnd())),
mCompositorWnds(nullptr, nullptr),
mTransparentSurfaceLock("mTransparentSurfaceLock"),
mTransparencyMode(aInitData.transparencyMode()),
mMemoryDC(nullptr),
mCompositeDC(nullptr),
mLockedBackBufferData(nullptr) {
MOZ_ASSERT(mWnd && ::IsWindow(mWnd));
// mNotDeferEndRemoteDrawing is set on the main thread during init,
// but is only accessed after on the compositor thread.
mNotDeferEndRemoteDrawing =
StaticPrefs::layers_offmainthreadcomposition_frame_rate() == 0 ||
gfxPlatform::IsInLayoutAsapMode() || gfxPlatform::ForceSoftwareVsync();
}
WinCompositorWidget::~WinCompositorWidget() { DestroyCompositorWindow(); }
void WinCompositorWidget::OnDestroyWindow() {
MutexAutoLock lock(mTransparentSurfaceLock);
mTransparentSurface = nullptr;
mMemoryDC = nullptr;
}
bool WinCompositorWidget::PreRender(WidgetRenderingContext* aContext) {
// This can block waiting for WM_SETTEXT to finish
// Using PreRender is unnecessarily pessimistic because
// we technically only need to block during the present call
// not all of compositor rendering
mPresentLock.Enter();
return true;
}
void WinCompositorWidget::PostRender(WidgetRenderingContext* aContext) {
mPresentLock.Leave();
}
LayoutDeviceIntSize WinCompositorWidget::GetClientSize() {
RECT r;
if (!::GetClientRect(mWnd, &r)) {
return LayoutDeviceIntSize();
}
return LayoutDeviceIntSize(r.right - r.left, r.bottom - r.top);
}
already_AddRefed<gfx::DrawTarget> WinCompositorWidget::StartRemoteDrawing() {
MutexAutoLock lock(mTransparentSurfaceLock);
MOZ_ASSERT(!mCompositeDC);
RefPtr<gfxASurface> surf;
if (mTransparencyMode == eTransparencyTransparent) {
surf = EnsureTransparentSurface();
}
// Must call this after EnsureTransparentSurface(), since it could update
// the DC.
HDC dc = GetWindowSurface();
if (!surf) {
if (!dc) {
return nullptr;
}
uint32_t flags = (mTransparencyMode == eTransparencyOpaque)
? 0
: gfxWindowsSurface::FLAG_IS_TRANSPARENT;
surf = new gfxWindowsSurface(dc, flags);
}
IntSize size = surf->GetSize();
if (size.width <= 0 || size.height <= 0) {
if (dc) {
FreeWindowSurface(dc);
}
return nullptr;
}
RefPtr<DrawTarget> dt =
mozilla::gfx::Factory::CreateDrawTargetForCairoSurface(
surf->CairoSurface(), size);
if (dt) {
mCompositeDC = dc;
} else {
FreeWindowSurface(dc);
}
return dt.forget();
}
void WinCompositorWidget::EndRemoteDrawing() {
MOZ_ASSERT(!mLockedBackBufferData);
if (mTransparencyMode == eTransparencyTransparent) {
MOZ_ASSERT(mTransparentSurface);
RedrawTransparentWindow();
}
if (mCompositeDC) {
FreeWindowSurface(mCompositeDC);
}
mCompositeDC = nullptr;
}
bool WinCompositorWidget::NeedsToDeferEndRemoteDrawing() {
if (mNotDeferEndRemoteDrawing) {
return false;
}
IDirectDraw7* ddraw = DeviceManagerDx::Get()->GetDirectDraw();
if (!ddraw) {
return false;
}
DWORD scanLine = 0;
int height = ::GetSystemMetrics(SM_CYSCREEN);
HRESULT ret = ddraw->GetScanLine(&scanLine);
if (ret == DDERR_VERTICALBLANKINPROGRESS) {
scanLine = 0;
} else if (ret != DD_OK) {
return false;
}
// Check if there is a risk of tearing with GDI.
if (static_cast<int>(scanLine) > height / 2) {
// No need to defer.
return false;
}
return true;
}
already_AddRefed<gfx::DrawTarget> WinCompositorWidget::GetBackBufferDrawTarget(
gfx::DrawTarget* aScreenTarget, const gfx::IntRect& aRect,
bool* aOutIsCleared) {
MOZ_ASSERT(!mLockedBackBufferData);
RefPtr<gfx::DrawTarget> target = CompositorWidget::GetBackBufferDrawTarget(
aScreenTarget, aRect, aOutIsCleared);
if (!target) {
return nullptr;
}
MOZ_ASSERT(target->GetBackendType() == BackendType::CAIRO);
uint8_t* destData;
IntSize destSize;
int32_t destStride;
SurfaceFormat destFormat;
if (!target->LockBits(&destData, &destSize, &destStride, &destFormat)) {
// LockBits is not supported. Use original DrawTarget.
return target.forget();
}
RefPtr<gfx::DrawTarget> dataTarget = Factory::CreateDrawTargetForData(
BackendType::CAIRO, destData, destSize, destStride, destFormat);
mLockedBackBufferData = destData;
return dataTarget.forget();
}
already_AddRefed<gfx::SourceSurface>
WinCompositorWidget::EndBackBufferDrawing() {
if (mLockedBackBufferData) {
MOZ_ASSERT(mLastBackBuffer);
mLastBackBuffer->ReleaseBits(mLockedBackBufferData);
mLockedBackBufferData = nullptr;
}
return CompositorWidget::EndBackBufferDrawing();
}
bool WinCompositorWidget::InitCompositor(layers::Compositor* aCompositor) {
if (aCompositor->GetBackendType() == layers::LayersBackend::LAYERS_BASIC) {
DeviceManagerDx::Get()->InitializeDirectDraw();
}
return true;
}
uintptr_t WinCompositorWidget::GetWidgetKey() { return mWidgetKey; }
void WinCompositorWidget::EnterPresentLock() { mPresentLock.Enter(); }
void WinCompositorWidget::LeavePresentLock() { mPresentLock.Leave(); }
RefPtr<gfxASurface> WinCompositorWidget::EnsureTransparentSurface() {
mTransparentSurfaceLock.AssertCurrentThreadOwns();
MOZ_ASSERT(mTransparencyMode == eTransparencyTransparent);
IntSize size = GetClientSize().ToUnknownSize();
if (!mTransparentSurface || mTransparentSurface->GetSize() != size) {
mTransparentSurface = nullptr;
mMemoryDC = nullptr;
CreateTransparentSurface(size);
}
RefPtr<gfxASurface> surface = mTransparentSurface;
return surface.forget();
}
void WinCompositorWidget::CreateTransparentSurface(const gfx::IntSize& aSize) {
mTransparentSurfaceLock.AssertCurrentThreadOwns();
MOZ_ASSERT(!mTransparentSurface && !mMemoryDC);
RefPtr<gfxWindowsSurface> surface =
new gfxWindowsSurface(aSize, SurfaceFormat::A8R8G8B8_UINT32);
mTransparentSurface = surface;
mMemoryDC = surface->GetDC();
}
void WinCompositorWidget::UpdateTransparency(nsTransparencyMode aMode) {
MutexAutoLock lock(mTransparentSurfaceLock);
if (mTransparencyMode == aMode) {
return;
}
mTransparencyMode = aMode;
mTransparentSurface = nullptr;
mMemoryDC = nullptr;
if (mTransparencyMode == eTransparencyTransparent) {
EnsureTransparentSurface();
}
}
bool WinCompositorWidget::HasGlass() const {
MOZ_ASSERT(layers::CompositorThreadHolder::IsInCompositorThread() ||
wr::RenderThread::IsInRenderThread());
nsTransparencyMode transparencyMode = mTransparencyMode;
return transparencyMode == eTransparencyGlass ||
transparencyMode == eTransparencyBorderlessGlass;
}
void WinCompositorWidget::ClearTransparentWindow() {
MutexAutoLock lock(mTransparentSurfaceLock);
if (!mTransparentSurface) {
return;
}
EnsureTransparentSurface();
IntSize size = mTransparentSurface->GetSize();
if (!size.IsEmpty()) {
RefPtr<DrawTarget> drawTarget =
gfxPlatform::CreateDrawTargetForSurface(mTransparentSurface, size);
if (!drawTarget) {
return;
}
drawTarget->ClearRect(Rect(0, 0, size.width, size.height));
RedrawTransparentWindow();
}
}
bool WinCompositorWidget::RedrawTransparentWindow() {
MOZ_ASSERT(mTransparencyMode == eTransparencyTransparent);
LayoutDeviceIntSize size = GetClientSize();
::GdiFlush();
BLENDFUNCTION bf = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA};
SIZE winSize = {size.width, size.height};
POINT srcPos = {0, 0};
HWND hWnd = WinUtils::GetTopLevelHWND(mWnd, true);
RECT winRect;
::GetWindowRect(hWnd, &winRect);
// perform the alpha blend
return !!::UpdateLayeredWindow(hWnd, nullptr, (POINT*)&winRect, &winSize,
mMemoryDC, &srcPos, 0, &bf, ULW_ALPHA);
}
HDC WinCompositorWidget::GetWindowSurface() {
return eTransparencyTransparent == mTransparencyMode ? mMemoryDC
: ::GetDC(mWnd);
}
void WinCompositorWidget::FreeWindowSurface(HDC dc) {
if (eTransparencyTransparent != mTransparencyMode) ::ReleaseDC(mWnd, dc);
}
bool WinCompositorWidget::IsHidden() const { return ::IsIconic(mWnd); }
void WinCompositorWidget::EnsureCompositorWindow() {
if (mCompositorWnds.mCompositorWnd || mCompositorWnds.mInitialParentWnd) {
return;
}
mCompositorWnds = WinCompositorWindowThread::CreateCompositorWindow();
UpdateCompositorWnd(mCompositorWnds.mCompositorWnd, mWnd);
MOZ_ASSERT(mCompositorWnds.mCompositorWnd);
MOZ_ASSERT(mCompositorWnds.mInitialParentWnd);
}
void WinCompositorWidget::DestroyCompositorWindow() {
if (!mCompositorWnds.mCompositorWnd && !mCompositorWnds.mInitialParentWnd) {
return;
}
WinCompositorWindowThread::DestroyCompositorWindow(mCompositorWnds);
mCompositorWnds = WinCompositorWnds(nullptr, nullptr);
}
void WinCompositorWidget::UpdateCompositorWndSizeIfNecessary() {
if (!mCompositorWnds.mCompositorWnd) {
return;
}
// This code is racing with the compositor, which needs to reparent
// the compositor surface to the actual window (mWnd). To avoid racing
// mutations, we refuse to proceed until GetParent returns the
// expected result. It's ok to fail to resize here since this function is
// called pretty frequently, and the reparenting race only happens when the
// window is being created for the first time.
HWND realParent = ::GetParent(mCompositorWnds.mCompositorWnd);
if (realParent != mWnd) {
return;
}
LayoutDeviceIntSize size = GetClientSize();
if (mLastCompositorWndSize == size) {
return;
}
// Force a resize and redraw (but not a move, activate, etc.).
if (!::SetWindowPos(mCompositorWnds.mCompositorWnd, nullptr, 0, 0, size.width,
size.height,
SWP_NOACTIVATE | SWP_NOCOPYBITS |
SWP_NOOWNERZORDER | SWP_NOZORDER)) {
return;
}
mLastCompositorWndSize = size;
}
// Creates a new instance of FxROutputHandler so that this compositor widget
// can send its output to Firefox Reality for Desktop.
void WinCompositorWidget::RequestFxrOutput() {
MOZ_ASSERT(mFxrHandler == nullptr);
mFxrHandler.reset(new FxROutputHandler());
}
} // namespace widget
} // namespace mozilla