/* -*- 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 "RenderCompositorLayerNative.h" #include "GLContext.h" #include "GLContextProvider.h" #include "mozilla/ProfilerLabels.h" #include "mozilla/ProfilerMarkers.h" #include "mozilla/gfx/gfxVars.h" #include "mozilla/gfx/Logging.h" #include "mozilla/layers/CompositionRecorder.h" #include "mozilla/layers/GpuFence.h" #include "mozilla/layers/NativeLayer.h" #include "mozilla/layers/ProfilerScreenshots.h" #include "mozilla/layers/SurfacePool.h" #include "mozilla/StaticPrefs_gfx.h" #include "mozilla/webrender/RenderTextureHost.h" #include "mozilla/webrender/RenderThread.h" #include "mozilla/widget/CompositorWidget.h" #include "RenderCompositorRecordedFrame.h" namespace mozilla::wr { extern LazyLogModule gRenderThreadLog; #define LOG(...) MOZ_LOG(gRenderThreadLog, LogLevel::Debug, (__VA_ARGS__)) RenderCompositorLayerNative::RenderCompositorLayerNative( const RefPtr& aWidget, gl::GLContext* aGL) : RenderCompositor(aWidget), mNativeLayerRoot(GetWidget()->GetNativeLayerRoot()) { LOG("RenderCompositorLayerNative::RenderCompositorLayerNative()"); MOZ_ASSERT(mNativeLayerRoot); #if defined(XP_DARWIN) || defined(MOZ_WAYLAND) auto pool = RenderThread::Get()->SharedSurfacePool(); if (pool) { mSurfacePoolHandle = pool->GetHandleForGL(aGL); } #endif MOZ_RELEASE_ASSERT(mSurfacePoolHandle); } RenderCompositorLayerNative::~RenderCompositorLayerNative() { LOG("RRenderCompositorLayerNative::~RenderCompositorLayerNative()"); mProfilerScreenshotGrabber.Destroy(); mNativeLayerRoot->SetLayers({}); mNativeLayerRootSnapshotter = nullptr; mNativeLayerRoot = nullptr; } bool RenderCompositorLayerNative::BeginFrame() { if (!MakeCurrent()) { gfxCriticalNote << "Failed to make render context current, can't draw."; return false; } if (!InitDefaultFramebuffer()) { return false; } return true; } RenderedFrameId RenderCompositorLayerNative::EndFrame( const nsTArray& aDirtyRects) { RenderedFrameId frameId = GetNextRenderFrameId(); DoSwap(); MOZ_ASSERT(mPendingGpuFeces.empty()); return frameId; } void RenderCompositorLayerNative::Pause() {} bool RenderCompositorLayerNative::Resume() { return true; } inline layers::WebRenderCompositor RenderCompositorLayerNative::CompositorType() const { #if defined(XP_DARWIN) return layers::WebRenderCompositor::CORE_ANIMATION; #elif defined(MOZ_WAYLAND) return layers::WebRenderCompositor::WAYLAND; #else MOZ_ASSERT_UNREACHABLE("Unexpected to be called!"); return layers::WebRenderCompositor::Unknown; #endif } LayoutDeviceIntSize RenderCompositorLayerNative::GetBufferSize() { return mWidget->GetClientSize(); } bool RenderCompositorLayerNative::ShouldUseNativeCompositor() { return false; } bool RenderCompositorLayerNative::ShouldUseLayerCompositor() const { return UseLayerCompositor(); } bool RenderCompositorLayerNative::UseLayerCompositor() const { return true; } bool RenderCompositorLayerNative::EnableAsyncScreenshot() { #if defined(XP_DARWIN) // On macOS, NativeLayerRootSnapshotterCA supports to take snapshot with // multiple layers. return true; #else // Request WebRender to use only one layer for content rendering during taking // snapshot. In addition to the content layer, one debug layer could exist. mAsyncScreenshotLastFrameUsed = mCurrentFrame; if (!mEnableAsyncScreenshot) { mEnableAsyncScreenshotInNextFrame = true; return false; } return true; #endif } void RenderCompositorLayerNative::GetCompositorCapabilities( CompositorCapabilities* aCaps) { RenderCompositor::GetCompositorCapabilities(aCaps); } void RenderCompositorLayerNative::GetWindowProperties( WindowProperties* aProperties) { // XXX aProperties->is_opaque = false; aProperties->enable_screenshot = mEnableAsyncScreenshot; } RenderCompositorLayerNative::Surface::~Surface() = default; RenderCompositorLayerNative::Surface::Surface(wr::DeviceIntSize aSize, bool aIsOpaque) : mSize(aSize), mIsOpaque(aIsOpaque) {} bool RenderCompositorLayerNative::MaybeReadback( const gfx::IntSize& aReadbackSize, const wr::ImageFormat& aReadbackFormat, const Range& aReadbackBuffer, bool* aNeedsYFlip) { MOZ_RELEASE_ASSERT(aReadbackFormat == wr::ImageFormat::BGRA8); if (!mNativeLayerRootSnapshotter) { mNativeLayerRootSnapshotter = mNativeLayerRoot->CreateSnapshotter(); if (!mNativeLayerRootSnapshotter) { return false; } } bool success = mNativeLayerRootSnapshotter->ReadbackPixels( aReadbackSize, gfx::SurfaceFormat::B8G8R8A8, aReadbackBuffer); // ReadbackPixels might have changed the current context. Make sure GL is // current again. MakeCurrent(); if (aNeedsYFlip) { *aNeedsYFlip = true; } return success; } bool RenderCompositorLayerNative::MaybeRecordFrame( layers::CompositionRecorder& aRecorder) { if (!mNativeLayerRootSnapshotter) { mNativeLayerRootSnapshotter = mNativeLayerRoot->CreateSnapshotter(); } if (!mNativeLayerRootSnapshotter) { return true; } gfx::IntSize size = GetBufferSize().ToUnknownSize(); RefPtr snapshot = mNativeLayerRootSnapshotter->GetWindowContents(size); if (!snapshot) { return true; } RefPtr buffer = mNativeLayerRootSnapshotter->CreateAsyncReadbackBuffer(size); buffer->CopyFrom(snapshot); RefPtr frame = new RenderCompositorRecordedFrame(TimeStamp::Now(), std::move(buffer)); aRecorder.RecordFrame(frame); // GetWindowContents might have changed the current context. Make sure our // context is current again. MakeCurrent(); return true; } bool RenderCompositorLayerNative::MaybeGrabScreenshot( const gfx::IntSize& aWindowSize) { if (!mozilla::layers::ProfilerScreenshots::IsEnabled()) { return false; } if (!mNativeLayerRootSnapshotter) { mNativeLayerRootSnapshotter = mNativeLayerRoot->CreateSnapshotter(); } if (mNativeLayerRootSnapshotter) { mProfilerScreenshotGrabber.MaybeGrabScreenshot(*mNativeLayerRootSnapshotter, aWindowSize); // MaybeGrabScreenshot might have changed the current context. Make sure our // context is current again. MakeCurrent(); } return true; } bool RenderCompositorLayerNative::MaybeProcessScreenshotQueue() { mProfilerScreenshotGrabber.MaybeProcessQueue(); // MaybeProcessQueue might have changed the current context. Make sure our // context is current again. MakeCurrent(); return true; } void RenderCompositorLayerNative::WaitUntilPresentationFlushed() { mNativeLayerRoot->WaitUntilCommitToScreenHasBeenProcessed(); } void RenderCompositorLayerNative::CompositorBeginFrame() { mAddedLayers.Clear(); mBeginFrameTimeStamp = TimeStamp::Now(); mSurfacePoolHandle->OnBeginFrame(); mNativeLayerRoot->PrepareForCommit(); mCurrentFrame++; if (mEnableAsyncScreenshotInNextFrame) { mEnableAsyncScreenshot = true; mEnableAsyncScreenshotInNextFrame = false; } } void RenderCompositorLayerNative::CompositorEndFrame() { #if defined(XP_DARWIN) // MacOS fails rendering without the flush here. DoFlush(); #endif mNativeLayerRoot->SetLayers(mAddedLayers); mNativeLayerRoot->CommitToScreen(); mSurfacePoolHandle->OnEndFrame(); if (mEnableAsyncScreenshot && (mCurrentFrame - mAsyncScreenshotLastFrameUsed) > 1) { mEnableAsyncScreenshot = false; } } void RenderCompositorLayerNative::BindNativeLayer(wr::NativeSurfaceId aId) { MOZ_RELEASE_ASSERT(!mCurrentlyBoundNativeLayer); auto surfaceCursor = mSurfaces.find(aId); MOZ_RELEASE_ASSERT(surfaceCursor != mSurfaces.end()); Surface& surface = surfaceCursor->second; mCurrentlyBoundNativeLayer = surface.mNativeLayer; } void RenderCompositorLayerNative::UnbindNativeLayer() { MOZ_RELEASE_ASSERT(mCurrentlyBoundNativeLayer); mCurrentlyBoundNativeLayer->NotifySurfaceReady(); mCurrentlyBoundNativeLayer = nullptr; } void RenderCompositorLayerNative::CreateSurface( wr::NativeSurfaceId aId, wr::DeviceIntPoint aVirtualOffset, wr::DeviceIntSize aTileSize, bool aIsOpaque) { MOZ_ASSERT_UNREACHABLE("Unexpected to be called!"); } void RenderCompositorLayerNative::CreateExternalSurface(wr::NativeSurfaceId aId, bool aIsOpaque) { MOZ_RELEASE_ASSERT(mSurfaces.find(aId) == mSurfaces.end()); RefPtr layer = mNativeLayerRoot->CreateLayerForExternalTexture(aIsOpaque); Surface surface{DeviceIntSize{}, aIsOpaque}; surface.mIsExternal = true; surface.mNativeLayer = layer; mSurfaces.insert({aId, std::move(surface)}); } void RenderCompositorLayerNative::CreateSwapChainSurface( wr::NativeSurfaceId aId, wr::DeviceIntSize aSize, bool aIsOpaque, bool aNeedsSyncDcompCommit) { MOZ_RELEASE_ASSERT(mSurfaces.find(aId) == mSurfaces.end()); Surface surface{aSize, aIsOpaque}; surface.mNativeLayer = mNativeLayerRoot->CreateLayer( surface.Size(), aIsOpaque, mSurfacePoolHandle); mSurfaces.insert({aId, std::move(surface)}); } void RenderCompositorLayerNative::ResizeSwapChainSurface( wr::NativeSurfaceId aId, wr::DeviceIntSize aSize) { auto surfaceCursor = mSurfaces.find(aId); MOZ_RELEASE_ASSERT(surfaceCursor != mSurfaces.end()); Surface& surface = surfaceCursor->second; MOZ_ASSERT(!surface.mIsExternal); if (aSize == surface.mSize) { return; } surface.mSize = aSize; surface.mNativeLayer = mNativeLayerRoot->CreateLayer( surface.Size(), surface.mIsOpaque, mSurfacePoolHandle); } void RenderCompositorLayerNative::AttachExternalImage( wr::NativeSurfaceId aId, wr::ExternalImageId aExternalImage) { RenderTextureHost* image = RenderThread::Get()->GetRenderTexture(aExternalImage); MOZ_RELEASE_ASSERT(image); auto surfaceCursor = mSurfaces.find(aId); MOZ_RELEASE_ASSERT(surfaceCursor != mSurfaces.end()); Surface& surface = surfaceCursor->second; MOZ_RELEASE_ASSERT(surface.mNativeLayer); MOZ_RELEASE_ASSERT(surface.mIsExternal); surface.mNativeLayer->AttachExternalImage(image); } void RenderCompositorLayerNativeOGL::AttachExternalImage( wr::NativeSurfaceId aId, wr::ExternalImageId aExternalImage) { RenderTextureHost* image = RenderThread::Get()->GetRenderTexture(aExternalImage); // image->Lock only uses the channel index to populate the returned // `WrExternalImage`. Since we don't use that, it doesn't matter // what channel index we pass. image->Lock(0, mGL); RenderCompositorLayerNative::AttachExternalImage(aId, aExternalImage); } void RenderCompositorLayerNative::DestroySurface(NativeSurfaceId aId) { auto surfaceCursor = mSurfaces.find(aId); MOZ_RELEASE_ASSERT(surfaceCursor != mSurfaces.end()); mSurfaces.erase(surfaceCursor); } void RenderCompositorLayerNative::CreateTile(wr::NativeSurfaceId aId, int aX, int aY) { MOZ_ASSERT_UNREACHABLE("Unexpected to be called!"); } void RenderCompositorLayerNative::DestroyTile(wr::NativeSurfaceId aId, int aX, int aY) { MOZ_ASSERT_UNREACHABLE("Unexpected to be called!"); } /* static */ gfx::SamplingFilter RenderCompositorLayerNative::ToSamplingFilter( wr::ImageRendering aImageRendering) { if (aImageRendering == wr::ImageRendering::Auto) { return gfx::SamplingFilter::LINEAR; } return gfx::SamplingFilter::POINT; } void RenderCompositorLayerNative::AddSurface( wr::NativeSurfaceId aId, const wr::CompositorSurfaceTransform& aTransform, wr::DeviceIntRect aClipRect, wr::ImageRendering aImageRendering, wr::DeviceIntRect aRoundedClipRect, wr::ClipRadius aClipRadius) { MOZ_RELEASE_ASSERT(!mCurrentlyBoundNativeLayer); auto surfaceCursor = mSurfaces.find(aId); MOZ_RELEASE_ASSERT(surfaceCursor != mSurfaces.end()); const Surface& surface = surfaceCursor->second; float sx = aTransform.scale.x; float sy = aTransform.scale.y; float tx = aTransform.offset.x; float ty = aTransform.offset.y; gfx::Matrix4x4 transform(sx, 0.0, 0.0, 0.0, 0.0, sy, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, tx, ty, 0.0, 1.0); RefPtr layer = surface.mNativeLayer; gfx::IntPoint layerPosition(0, 0); layer->SetPosition(layerPosition); gfx::IntRect clipRect(aClipRect.min.x, aClipRect.min.y, aClipRect.width(), aClipRect.height()); layer->SetClipRect(Some(clipRect)); gfx::Rect roundedClipRect(aRoundedClipRect.min.x, aRoundedClipRect.min.y, aRoundedClipRect.width(), aRoundedClipRect.height()); gfx::RectCornerRadii clipRadius(aClipRadius.top_left, aClipRadius.top_right, aClipRadius.bottom_right, aClipRadius.bottom_left); gfx::RoundedRect roundedClip(roundedClipRect, clipRadius); layer->SetRoundedClipRect(Some(roundedClip)); layer->SetTransform(transform); layer->SetSamplingFilter(ToSamplingFilter(aImageRendering)); mAddedLayers.AppendElement(layer); if (surface.mIsExternal) { RefPtr fence = layer->GetGpuFence(); if (fence && BackendType() == layers::WebRenderBackend::HARDWARE) { mPendingGpuFeces.emplace_back(fence); } } } /* static */ UniquePtr RenderCompositorLayerNativeOGL::Create( const RefPtr& aWidget, nsACString& aError) { RefPtr gl = RenderThread::Get()->SingletonGL(); if (!gl) { gl = gl::GLContextProvider::CreateForCompositorWidget( aWidget, /* aHardwareWebRender */ true, /* aForceAccelerated */ true); RenderThread::MaybeEnableGLDebugMessage(gl); } if (!gl || !gl->MakeCurrent()) { gfxCriticalNote << "Failed GL context creation for WebRender: " << gfx::hexa(gl.get()); return nullptr; } return MakeUnique(aWidget, std::move(gl)); } RenderCompositorLayerNativeOGL::RenderCompositorLayerNativeOGL( const RefPtr& aWidget, RefPtr&& aGL) : RenderCompositorLayerNative(aWidget, aGL), mGL(aGL) { MOZ_ASSERT(mGL); } RenderCompositorLayerNativeOGL::~RenderCompositorLayerNativeOGL() { if (!mGL->MakeCurrent()) { gfxCriticalNote << "Failed to make render context current during destroying."; // Leak resources! mPreviousFrameDoneFences = nullptr; mThisFrameDoneFences = nullptr; return; } if (mPreviousFrameDoneFences && mPreviousFrameDoneFences->mSync) { mGL->fDeleteSync(mPreviousFrameDoneFences->mSync); } if (mThisFrameDoneFences && mThisFrameDoneFences->mSync) { mGL->fDeleteSync(mThisFrameDoneFences->mSync); } } bool RenderCompositorLayerNativeOGL::InitDefaultFramebuffer() { mGL->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mGL->GetDefaultFramebuffer()); return true; } void RenderCompositorLayerNativeOGL::DoSwap() { InsertFrameDoneSync(); } void RenderCompositorLayerNativeOGL::DoFlush() { mGL->fFlush(); } void RenderCompositorLayerNativeOGL::InsertFrameDoneSync() { #ifdef XP_DARWIN // Only do this on macOS. // On other platforms, SwapBuffers automatically applies back-pressure. if (mThisFrameDoneFences && mThisFrameDoneFences->mSync) { mGL->fDeleteSync(mThisFrameDoneFences->mSync); } mThisFrameDoneFences = MakeUnique(std::move(mPendingGpuFeces)); mThisFrameDoneFences->mSync = mGL->fFenceSync(LOCAL_GL_SYNC_GPU_COMMANDS_COMPLETE, 0); #endif } bool RenderCompositorLayerNativeOGL::WaitForGPU() { if (mPreviousFrameDoneFences) { bool complete = false; while (!complete) { complete = true; for (const auto& fence : mPreviousFrameDoneFences->mGpuFeces) { if (!fence->HasCompleted()) { complete = false; break; } } if (!complete) { PR_Sleep(PR_MillisecondsToInterval(1)); } } if (mPreviousFrameDoneFences->mSync) { AUTO_PROFILER_LABEL("Waiting for GPU to finish previous frame", GRAPHICS); mGL->fClientWaitSync(mPreviousFrameDoneFences->mSync, LOCAL_GL_SYNC_FLUSH_COMMANDS_BIT, LOCAL_GL_TIMEOUT_IGNORED); mGL->fDeleteSync(mPreviousFrameDoneFences->mSync); } } mPreviousFrameDoneFences = std::move(mThisFrameDoneFences); MOZ_ASSERT(!mThisFrameDoneFences); return true; } void RenderCompositorLayerNativeOGL::BindSwapChain( wr::NativeSurfaceId aId, const wr::DeviceIntRect* aDirtyRects, size_t aNumDirtyRects) { BindNativeLayer(aId); MOZ_ASSERT(mCurrentlyBoundNativeLayer); gfx::IntSize size = mCurrentlyBoundNativeLayer->GetSize(); gfx::IntRect validRect = mCurrentlyBoundNativeLayer->GetRect(); const auto dirtyRect = [&]() { if (aNumDirtyRects > 0) { MOZ_RELEASE_ASSERT(aNumDirtyRects == 1); const auto& rect = aDirtyRects[0]; // Clip rect to bufferSize int left = std::clamp((int)rect.min.x, 0, size.width); int top = std::clamp((int)rect.min.y, 0, size.height); int right = std::clamp((int)rect.max.x, 0, size.width); int bottom = std::clamp((int)rect.max.y, 0, size.height); int width = right - left; int height = bottom - top; MOZ_RELEASE_ASSERT(width >= 0); MOZ_RELEASE_ASSERT(height >= 0); return gfx::IntRect(left, top, width, height); } return gfx::IntRect(0, 0, size.width, size.height); }(); Maybe fbo = mCurrentlyBoundNativeLayer->NextSurfaceAsFramebuffer( validRect, dirtyRect, true); if (!fbo) { // XXX return; } mGL->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, *fbo); } void RenderCompositorLayerNativeOGL::PresentSwapChain( wr::NativeSurfaceId aId, const wr::DeviceIntRect* aDirtyRects, size_t aNumDirtyRects) { mGL->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, 0); UnbindNativeLayer(); } } // namespace mozilla::wr