/* -*- 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 namespace mozilla { namespace widget { using namespace mozilla::gfx; using namespace mozilla; WinCompositorWidget::WinCompositorWidget( const WinCompositorWidgetInitData& aInitData, const layers::CompositorOptions& aOptions) : CompositorWidget(aOptions), mSetParentCompleted(false), mWidgetKey(aInitData.widgetKey()), mWnd(reinterpret_cast(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 WinCompositorWidget::StartRemoteDrawing() { MutexAutoLock lock(mTransparentSurfaceLock); MOZ_ASSERT(!mCompositeDC); RefPtr 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 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(scanLine) > height / 2) { // No need to defer. return false; } return true; } already_AddRefed WinCompositorWidget::GetBackBufferDrawTarget( gfx::DrawTarget* aScreenTarget, const gfx::IntRect& aRect, bool* aOutIsCleared) { MOZ_ASSERT(!mLockedBackBufferData); RefPtr 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 dataTarget = Factory::CreateDrawTargetForData( BackendType::CAIRO, destData, destSize, destStride, destFormat); mLockedBackBufferData = destData; return dataTarget.forget(); } already_AddRefed 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 WinCompositorWidget::EnsureTransparentSurface() { mTransparentSurfaceLock.AssertCurrentThreadOwns(); MOZ_ASSERT(mTransparencyMode == eTransparencyTransparent); IntSize size = GetClientSize().ToUnknownSize(); if (!mTransparentSurface || mTransparentSurface->GetSize() != size) { mTransparentSurface = nullptr; mMemoryDC = nullptr; CreateTransparentSurface(size); } RefPtr surface = mTransparentSurface; return surface.forget(); } void WinCompositorWidget::CreateTransparentSurface(const gfx::IntSize& aSize) { mTransparentSurfaceLock.AssertCurrentThreadOwns(); MOZ_ASSERT(!mTransparentSurface && !mMemoryDC); RefPtr 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 = 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; } LayoutDeviceIntSize size = GetClientSize(); if (mLastCompositorWndSize == size) { 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 ::SetParent() is called in parent // process. After the ::SetParent() call, composition is scheduled in // CompositorWidgetParent::UpdateCompositorWnd(). if (!mSetParentCompleted) { // ::SetParent() is not completed yet. return; } MOZ_ASSERT(mWnd == ::GetParent(mCompositorWnds.mCompositorWnd)); // 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