/* -*- 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 "gfxPrefs.h" #include "mozilla/gfx/DeviceManagerDx.h" #include "mozilla/gfx/Point.h" #include "mozilla/layers/Compositor.h" #include "mozilla/widget/PlatformWidgetTypes.h" #include "nsWindow.h" #include "VsyncDispatcher.h" #include namespace mozilla { namespace widget { using namespace mozilla::gfx; WinCompositorWidget::WinCompositorWidget(const CompositorWidgetInitData& aInitData) : mWidgetKey(aInitData.widgetKey()), mWnd(reinterpret_cast(aInitData.hWnd())), mTransparencyMode(static_cast(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 = gfxPrefs::LayersCompositionFrameRate() == 0 || gfxPlatform::IsInLayoutAsapMode() || gfxPlatform::ForceSoftwareVsync(); } void WinCompositorWidget::OnDestroyWindow() { 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() { 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; } MOZ_ASSERT(!mCompositeDC); mCompositeDC = dc; return mozilla::gfx::Factory::CreateDrawTargetForCairoSurface(surf->CairoSurface(), size); } 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 LayoutDeviceIntRect& aRect, const LayoutDeviceIntRect& aClearRect) { MOZ_ASSERT(!mLockedBackBufferData); RefPtr target = CompositorWidget::GetBackBufferDrawTarget(aScreenTarget, aRect, aClearRect); 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() { 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) { MOZ_ASSERT(!mTransparentSurface && !mMemoryDC); RefPtr surface = new gfxWindowsSurface(aSize, SurfaceFormat::A8R8G8B8_UINT32); mTransparentSurface = surface; mMemoryDC = surface->GetDC(); } void WinCompositorWidget::UpdateTransparency(nsTransparencyMode aMode) { if (mTransparencyMode == aMode) { return; } mTransparencyMode = aMode; mTransparentSurface = nullptr; mMemoryDC = nullptr; if (mTransparencyMode == eTransparencyTransparent) { EnsureTransparentSurface(); } } void WinCompositorWidget::ClearTransparentWindow() { 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); } } // namespace widget } // namespace mozilla