/* 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 "mozilla/layers/NativeLayerWayland.h" #include #include #include #include "gfxUtils.h" #include "nsGtkUtils.h" #include "GLContextProvider.h" #include "GLBlitHelper.h" #include "mozilla/gfx/DataSurfaceHelpers.h" #include "mozilla/gfx/Logging.h" #include "mozilla/layers/SurfacePoolWayland.h" #include "mozilla/StaticPrefs_widget.h" #include "mozilla/webrender/RenderThread.h" namespace mozilla::layers { using gfx::BackendType; using gfx::DrawTarget; using gfx::IntPoint; using gfx::IntRect; using gfx::IntRegion; using gfx::IntSize; using gfx::Matrix4x4; using gfx::Point; using gfx::Rect; using gfx::SamplingFilter; using gfx::Size; static const struct wl_callback_listener sFrameListenerNativeLayerWayland = { NativeLayerWayland::FrameCallbackHandler}; CallbackMultiplexHelper::CallbackMultiplexHelper(CallbackFunc aCallbackFunc, void* aCallbackData) : mCallbackFunc(aCallbackFunc), mCallbackData(aCallbackData) {} void CallbackMultiplexHelper::Callback(uint32_t aTime) { if (!mActive) { return; } mActive = false; // This is likely the first of a batch of frame callbacks being processed and // may trigger the setup of a successive one. In order to avoid complexity, // defer calling the callback function until we had a chance to process // all pending frame callbacks. AddRef(); nsCOMPtr runnable = NewRunnableMethod( "layers::CallbackMultiplexHelper::RunCallback", this, &CallbackMultiplexHelper::RunCallback, aTime); MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThreadQueue( runnable.forget(), EventQueuePriority::Vsync)); } void CallbackMultiplexHelper::RunCallback(uint32_t aTime) { mCallbackFunc(mCallbackData, aTime); Release(); } /* static */ already_AddRefed NativeLayerRootWayland::CreateForMozContainer(MozContainer* aContainer) { RefPtr layerRoot = new NativeLayerRootWayland(aContainer); return layerRoot.forget(); } NativeLayerRootWayland::NativeLayerRootWayland(MozContainer* aContainer) : mMutex("NativeLayerRootWayland"), mContainer(aContainer) { g_object_ref(mContainer); } NativeLayerRootWayland::~NativeLayerRootWayland() { GdkWindow* gdkWindow = gtk_widget_get_window(GTK_WIDGET(mContainer)); if (gdkWindow) { GdkFrameClock* frameClock = gdk_window_get_frame_clock(gdkWindow); g_signal_handlers_disconnect_by_data(frameClock, this); } g_object_unref(mContainer); } already_AddRefed NativeLayerRootWayland::CreateLayer( const IntSize& aSize, bool aIsOpaque, SurfacePoolHandle* aSurfacePoolHandle) { RefPtr layer = new NativeLayerWayland( aSize, aIsOpaque, aSurfacePoolHandle->AsSurfacePoolHandleWayland()); return layer.forget(); } already_AddRefed NativeLayerRootWayland::CreateLayerForExternalTexture(bool aIsOpaque) { RefPtr layer = new NativeLayerWayland(aIsOpaque); return layer.forget(); } void NativeLayerRootWayland::AppendLayer(NativeLayer* aLayer) { MOZ_RELEASE_ASSERT(false); MutexAutoLock lock(mMutex); RefPtr layerWayland = aLayer->AsNativeLayerWayland(); MOZ_RELEASE_ASSERT(layerWayland); mSublayers.AppendElement(layerWayland); } void NativeLayerRootWayland::RemoveLayer(NativeLayer* aLayer) { MOZ_RELEASE_ASSERT(false); MutexAutoLock lock(mMutex); RefPtr layerWayland = aLayer->AsNativeLayerWayland(); MOZ_RELEASE_ASSERT(layerWayland); mSublayers.RemoveElement(layerWayland); } void NativeLayerRootWayland::SetLayers( const nsTArray>& aLayers) { MutexAutoLock lock(mMutex); nsTArray> newSublayers(aLayers.Length()); for (const RefPtr& sublayer : aLayers) { RefPtr layer = sublayer->AsNativeLayerWayland(); newSublayers.AppendElement(layer); } if (newSublayers != mSublayers) { for (const RefPtr& layer : mSublayers) { if (!newSublayers.Contains(layer)) { mOldSublayers.AppendElement(layer); } } mSublayers = std::move(newSublayers); mNewLayers = true; } } bool NativeLayerRootWayland::CommitToScreen() { MutexAutoLock lock(mMutex); return CommitToScreen(lock); } bool NativeLayerRootWayland::CommitToScreen(const MutexAutoLock& aProofOfLock) { mFrameInProcess = false; MozContainerSurfaceLock lock(mContainer); struct wl_surface* containerSurface = lock.GetSurface(); if (!containerSurface) { if (!mCallbackRequested) { RefPtr self(this); moz_container_wayland_add_initial_draw_callback_locked( mContainer, [self]() -> void { MutexAutoLock lock(self->mMutex); if (!self->mFrameInProcess) { self->CommitToScreen(lock); } self->mCallbackRequested = false; }); mCallbackRequested = true; } return true; } wl_surface* previousSurface = nullptr; for (RefPtr& layer : mSublayers) { layer->EnsureParentSurface(containerSurface); if (mNewLayers) { wl_subsurface_place_above(layer->mWlSubsurface, previousSurface ? previousSurface : containerSurface); previousSurface = layer->mWlSurface; } MOZ_RELEASE_ASSERT(layer->mTransform.Is2D()); auto transform2D = layer->mTransform.As2D(); Rect surfaceRectClipped = Rect(0, 0, (float)layer->mSize.width, (float)layer->mSize.height); surfaceRectClipped = surfaceRectClipped.Intersect(Rect(layer->mDisplayRect)); transform2D.PostTranslate((float)layer->mPosition.x, (float)layer->mPosition.y); surfaceRectClipped = transform2D.TransformBounds(surfaceRectClipped); if (layer->mClipRect) { surfaceRectClipped = surfaceRectClipped.Intersect(Rect(layer->mClipRect.value())); } if (roundf(surfaceRectClipped.width) > 0 && roundf(surfaceRectClipped.height) > 0) { layer->SetBufferTransformFlipped(transform2D._11 < 0.0, transform2D._22 < 0.0); double bufferScale = moz_container_wayland_get_scale(mContainer); layer->SetSubsurfacePosition(floor(surfaceRectClipped.x / bufferScale), floor(surfaceRectClipped.y / bufferScale)); layer->SetViewportDestinationSize( ceil(surfaceRectClipped.width / bufferScale), ceil(surfaceRectClipped.height / bufferScale)); auto transform2DInversed = transform2D.Inverse(); Rect bufferClip = transform2DInversed.TransformBounds(surfaceRectClipped); layer->SetViewportSourceRect(bufferClip); layer->Commit(); } else { layer->Unmap(); } } if (mNewLayers) { for (RefPtr& layer : mOldSublayers) { layer->Unmap(); } mOldSublayers.Clear(); nsCOMPtr updateLayersRunnable = NewRunnableMethod<>( "layers::NativeLayerRootWayland::UpdateLayersOnMainThread", this, &NativeLayerRootWayland::UpdateLayersOnMainThread); MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThreadQueue( updateLayersRunnable.forget(), EventQueuePriority::Normal)); mNewLayers = false; } if (containerSurface != mWlSurface) { if (!mShmBuffer) { mShmBuffer = widget::WaylandBufferSHM::Create(LayoutDeviceIntSize(1, 1)); mShmBuffer->Clear(); } mShmBuffer->AttachAndCommit(containerSurface); mWlSurface = containerSurface; } else { wl_surface_commit(containerSurface); } wl_display_flush(widget::WaylandDisplayGet()->GetDisplay()); return true; } void NativeLayerRootWayland::RequestFrameCallback(CallbackFunc aCallbackFunc, void* aCallbackData) { MutexAutoLock lock(mMutex); mCallbackMultiplexHelper = new CallbackMultiplexHelper(aCallbackFunc, aCallbackData); for (const RefPtr& layer : mSublayersOnMainThread) { layer->RequestFrameCallback(mCallbackMultiplexHelper); } MozContainerSurfaceLock lockContainer(mContainer); struct wl_surface* wlSurface = lockContainer.GetSurface(); if (wlSurface) { wl_surface_commit(wlSurface); wl_display_flush(widget::WaylandDisplayGet()->GetDisplay()); } } static void sAfterFrameClockAfterPaint( GdkFrameClock* aClock, NativeLayerRootWayland* aNativeLayerRoot) { aNativeLayerRoot->AfterFrameClockAfterPaint(); } void NativeLayerRootWayland::AfterFrameClockAfterPaint() { MutexAutoLock lock(mMutex); MozContainerSurfaceLock lockContainer(mContainer); struct wl_surface* containerSurface = lockContainer.GetSurface(); for (const RefPtr& layer : mSublayersOnMainThread) { wl_surface_commit(layer->mWlSurface); } if (containerSurface) { wl_surface_commit(containerSurface); } } void NativeLayerRootWayland::UpdateLayersOnMainThread() { AssertIsOnMainThread(); MutexAutoLock lock(mMutex); static auto sGdkWaylandWindowAddCallbackSurface = (void (*)(GdkWindow*, struct wl_surface*))dlsym( RTLD_DEFAULT, "gdk_wayland_window_add_frame_callback_surface"); static auto sGdkWaylandWindowRemoveCallbackSurface = (void (*)(GdkWindow*, struct wl_surface*))dlsym( RTLD_DEFAULT, "gdk_wayland_window_remove_frame_callback_surface"); MozContainerSurfaceLock lockContainer(mContainer); struct wl_surface* containerSurface = lockContainer.GetSurface(); GdkWindow* gdkWindow = gtk_widget_get_window(GTK_WIDGET(mContainer)); mSublayersOnMainThread.RemoveElementsBy([&](const auto& layer) { if (!mSublayers.Contains(layer)) { if (layer->IsOpaque() && StaticPrefs::widget_wayland_opaque_region_enabled_AtStartup() && sGdkWaylandWindowAddCallbackSurface && gdkWindow) { sGdkWaylandWindowRemoveCallbackSurface(gdkWindow, layer->mWlSurface); wl_compositor* compositor = widget::WaylandDisplayGet()->GetCompositor(); wl_region* region = wl_compositor_create_region(compositor); wl_surface_set_opaque_region(layer->mWlSurface, region); wl_region_destroy(region); wl_surface_commit(layer->mWlSurface); } return true; } return false; }); for (const RefPtr& layer : mSublayers) { if (!mSublayersOnMainThread.Contains(layer)) { if (layer->IsOpaque() && StaticPrefs::widget_wayland_opaque_region_enabled_AtStartup() && sGdkWaylandWindowRemoveCallbackSurface && gdkWindow) { sGdkWaylandWindowAddCallbackSurface(gdkWindow, layer->mWlSurface); wl_compositor* compositor = widget::WaylandDisplayGet()->GetCompositor(); wl_region* region = wl_compositor_create_region(compositor); wl_region_add(region, 0, 0, INT32_MAX, INT32_MAX); wl_surface_set_opaque_region(layer->mWlSurface, region); wl_region_destroy(region); wl_surface_commit(layer->mWlSurface); } if (mCallbackMultiplexHelper && mCallbackMultiplexHelper->IsActive()) { layer->RequestFrameCallback(mCallbackMultiplexHelper); } mSublayersOnMainThread.AppendElement(layer); } } if (containerSurface) { wl_surface_commit(containerSurface); } if (!mGdkAfterPaintId && gdkWindow) { GdkFrameClock* frameClock = gdk_window_get_frame_clock(gdkWindow); mGdkAfterPaintId = g_signal_connect_after(frameClock, "after-paint", G_CALLBACK(sAfterFrameClockAfterPaint), this); } } NativeLayerWayland::NativeLayerWayland( const IntSize& aSize, bool aIsOpaque, SurfacePoolHandleWayland* aSurfacePoolHandle) : mMutex("NativeLayerWayland"), mSurfacePoolHandle(aSurfacePoolHandle), mSize(aSize), mIsOpaque(aIsOpaque) { MOZ_RELEASE_ASSERT(mSurfacePoolHandle, "Need a non-null surface pool handle."); RefPtr waylandDisplay = widget::WaylandDisplayGet(); wl_compositor* compositor = waylandDisplay->GetCompositor(); mWlSurface = wl_compositor_create_surface(compositor); wl_region* region = wl_compositor_create_region(compositor); wl_surface_set_input_region(mWlSurface, region); wl_region_destroy(region); wp_viewporter* viewporter = waylandDisplay->GetViewporter(); mViewport = wp_viewporter_get_viewport(viewporter, mWlSurface); } NativeLayerWayland::NativeLayerWayland(bool aIsOpaque) : mMutex("NativeLayerWayland"), mSurfacePoolHandle(nullptr), mIsOpaque(aIsOpaque) { MOZ_RELEASE_ASSERT(false); // external image } NativeLayerWayland::~NativeLayerWayland() { MutexAutoLock lock(mMutex); if (mInProgressBuffer) { mSurfacePoolHandle->ReturnBufferToPool(mInProgressBuffer); mInProgressBuffer = nullptr; } if (mFrontBuffer) { mSurfacePoolHandle->ReturnBufferToPool(mFrontBuffer); mFrontBuffer = nullptr; } MozClearPointer(mCallback, wl_callback_destroy); MozClearPointer(mViewport, wp_viewport_destroy); MozClearPointer(mWlSubsurface, wl_subsurface_destroy); MozClearPointer(mWlSurface, wl_surface_destroy); } void NativeLayerWayland::AttachExternalImage( wr::RenderTextureHost* aExternalImage) { MOZ_RELEASE_ASSERT(false); } void NativeLayerWayland::SetSurfaceIsFlipped(bool aIsFlipped) { MutexAutoLock lock(mMutex); if (aIsFlipped != mSurfaceIsFlipped) { mSurfaceIsFlipped = aIsFlipped; } } bool NativeLayerWayland::SurfaceIsFlipped() { MutexAutoLock lock(mMutex); return mSurfaceIsFlipped; } IntSize NativeLayerWayland::GetSize() { MutexAutoLock lock(mMutex); return mSize; } void NativeLayerWayland::SetPosition(const IntPoint& aPosition) { MutexAutoLock lock(mMutex); if (aPosition != mPosition) { mPosition = aPosition; } } IntPoint NativeLayerWayland::GetPosition() { MutexAutoLock lock(mMutex); return mPosition; } void NativeLayerWayland::SetTransform(const Matrix4x4& aTransform) { MutexAutoLock lock(mMutex); MOZ_ASSERT(aTransform.IsRectilinear()); if (aTransform != mTransform) { mTransform = aTransform; } } void NativeLayerWayland::SetSamplingFilter(SamplingFilter aSamplingFilter) { MutexAutoLock lock(mMutex); if (aSamplingFilter != mSamplingFilter) { mSamplingFilter = aSamplingFilter; } } Matrix4x4 NativeLayerWayland::GetTransform() { MutexAutoLock lock(mMutex); return mTransform; } IntRect NativeLayerWayland::GetRect() { MutexAutoLock lock(mMutex); return IntRect(mPosition, mSize); } bool NativeLayerWayland::IsOpaque() { MutexAutoLock lock(mMutex); return mIsOpaque; } void NativeLayerWayland::SetClipRect(const Maybe& aClipRect) { MutexAutoLock lock(mMutex); if (aClipRect != mClipRect) { mClipRect = aClipRect; } } Maybe NativeLayerWayland::ClipRect() { MutexAutoLock lock(mMutex); return mClipRect; } IntRect NativeLayerWayland::CurrentSurfaceDisplayRect() { MutexAutoLock lock(mMutex); return mDisplayRect; } RefPtr NativeLayerWayland::NextSurfaceAsDrawTarget( const IntRect& aDisplayRect, const IntRegion& aUpdateRegion, BackendType aBackendType) { MutexAutoLock lock(mMutex); mDisplayRect = IntRect(aDisplayRect); mDirtyRegion = IntRegion(aUpdateRegion); MOZ_ASSERT(!mInProgressBuffer); if (mFrontBuffer && !mFrontBuffer->IsAttached()) { // the Wayland compositor released the buffer early, we can reuse it mInProgressBuffer = std::move(mFrontBuffer); } else { mInProgressBuffer = mSurfacePoolHandle->ObtainBufferFromPool(mSize); if (mFrontBuffer) { HandlePartialUpdate(lock); mSurfacePoolHandle->ReturnBufferToPool(mFrontBuffer); } } mFrontBuffer = nullptr; if (!mInProgressBuffer) { gfxCriticalError() << "Failed to obtain buffer"; wr::RenderThread::Get()->HandleWebRenderError( wr::WebRenderError::NEW_SURFACE); return nullptr; } return mInProgressBuffer->Lock(); } Maybe NativeLayerWayland::NextSurfaceAsFramebuffer( const IntRect& aDisplayRect, const IntRegion& aUpdateRegion, bool aNeedsDepth) { MutexAutoLock lock(mMutex); mDisplayRect = IntRect(aDisplayRect); mDirtyRegion = IntRegion(aUpdateRegion); MOZ_ASSERT(!mInProgressBuffer); if (mFrontBuffer && !mFrontBuffer->IsAttached()) { // the Wayland compositor released the buffer early, we can reuse it mInProgressBuffer = std::move(mFrontBuffer); mFrontBuffer = nullptr; } else { mInProgressBuffer = mSurfacePoolHandle->ObtainBufferFromPool(mSize); } if (!mInProgressBuffer) { gfxCriticalError() << "Failed to obtain buffer"; wr::RenderThread::Get()->HandleWebRenderError( wr::WebRenderError::NEW_SURFACE); return Nothing(); } // get the framebuffer before handling partial damage so we don't accidently // create one without depth buffer Maybe fbo = mSurfacePoolHandle->GetFramebufferForBuffer( mInProgressBuffer, aNeedsDepth); MOZ_RELEASE_ASSERT(fbo, "GetFramebufferForBuffer failed."); if (mFrontBuffer) { HandlePartialUpdate(lock); mSurfacePoolHandle->ReturnBufferToPool(mFrontBuffer); mFrontBuffer = nullptr; } return fbo; } void NativeLayerWayland::HandlePartialUpdate( const MutexAutoLock& aProofOfLock) { IntRegion copyRegion = IntRegion(mDisplayRect); copyRegion.SubOut(mDirtyRegion); if (!copyRegion.IsEmpty()) { if (mSurfacePoolHandle->gl()) { mSurfacePoolHandle->gl()->MakeCurrent(); for (auto iter = copyRegion.RectIter(); !iter.Done(); iter.Next()) { gfx::IntRect r = iter.Get(); Maybe sourceFB = mSurfacePoolHandle->GetFramebufferForBuffer(mFrontBuffer, false); Maybe destFB = mSurfacePoolHandle->GetFramebufferForBuffer( mInProgressBuffer, false); MOZ_RELEASE_ASSERT(sourceFB && destFB); mSurfacePoolHandle->gl()->BlitHelper()->BlitFramebufferToFramebuffer( sourceFB.value(), destFB.value(), r, r, LOCAL_GL_NEAREST); } } else { RefPtr dataSourceSurface = gfx::CreateDataSourceSurfaceFromData( mSize, mFrontBuffer->GetSurfaceFormat(), (const uint8_t*)mFrontBuffer->GetImageData(), mSize.width * BytesPerPixel(mFrontBuffer->GetSurfaceFormat())); RefPtr dt = mInProgressBuffer->Lock(); for (auto iter = copyRegion.RectIter(); !iter.Done(); iter.Next()) { IntRect r = iter.Get(); dt->CopySurface(dataSourceSurface, r, IntPoint(r.x, r.y)); } } } } void NativeLayerWayland::NotifySurfaceReady() { MOZ_ASSERT(!mFrontBuffer); MOZ_ASSERT(mInProgressBuffer); mFrontBuffer = mInProgressBuffer; mInProgressBuffer = nullptr; } void NativeLayerWayland::DiscardBackbuffers() {} void NativeLayerWayland::Commit() { MutexAutoLock lock(mMutex); if (mDirtyRegion.IsEmpty() && mHasBufferAttached) { wl_surface_commit(mWlSurface); return; } for (auto iter = mDirtyRegion.RectIter(); !iter.Done(); iter.Next()) { IntRect r = iter.Get(); wl_surface_damage_buffer(mWlSurface, r.x, r.y, r.width, r.height); } MOZ_ASSERT(mFrontBuffer); mFrontBuffer->AttachAndCommit(mWlSurface); mHasBufferAttached = true; mDirtyRegion.SetEmpty(); } void NativeLayerWayland::Unmap() { MutexAutoLock lock(mMutex); if (!mHasBufferAttached) { return; } wl_surface_attach(mWlSurface, nullptr, 0, 0); wl_surface_commit(mWlSurface); mHasBufferAttached = false; } void NativeLayerWayland::EnsureParentSurface(wl_surface* aParentSurface) { MutexAutoLock lock(mMutex); if (aParentSurface != mParentWlSurface) { MozClearPointer(mWlSubsurface, wl_subsurface_destroy); mSubsurfacePosition = IntPoint(0, 0); if (aParentSurface) { wl_subcompositor* subcompositor = widget::WaylandDisplayGet()->GetSubcompositor(); mWlSubsurface = wl_subcompositor_get_subsurface(subcompositor, mWlSurface, aParentSurface); } mParentWlSurface = aParentSurface; } } void NativeLayerWayland::SetBufferTransformFlipped(bool aFlippedX, bool aFlippedY) { MutexAutoLock lock(mMutex); if (aFlippedX == mBufferTransformFlippedX && aFlippedY == mBufferTransformFlippedY) { return; } mBufferTransformFlippedX = aFlippedX; mBufferTransformFlippedY = aFlippedY; if (mBufferTransformFlippedY) { if (mBufferTransformFlippedX) { wl_surface_set_buffer_transform(mWlSurface, WL_OUTPUT_TRANSFORM_180); } else { wl_surface_set_buffer_transform(mWlSurface, WL_OUTPUT_TRANSFORM_FLIPPED_180); } } else { if (mBufferTransformFlippedX) { wl_surface_set_buffer_transform(mWlSurface, WL_OUTPUT_TRANSFORM_FLIPPED); } else { wl_surface_set_buffer_transform(mWlSurface, WL_OUTPUT_TRANSFORM_NORMAL); } } } void NativeLayerWayland::SetSubsurfacePosition(int aX, int aY) { MutexAutoLock lock(mMutex); if ((aX == mSubsurfacePosition.x && aY == mSubsurfacePosition.y) || !mWlSubsurface) { return; } mSubsurfacePosition.x = aX; mSubsurfacePosition.y = aY; wl_subsurface_set_position(mWlSubsurface, mSubsurfacePosition.x, mSubsurfacePosition.y); } void NativeLayerWayland::SetViewportSourceRect(const Rect aSourceRect) { MutexAutoLock lock(mMutex); Rect bufferRect = Rect(0, 0, mSize.width, mSize.height); Rect sourceRect = aSourceRect.Intersect(bufferRect); if (mViewportSourceRect == sourceRect) { return; } mViewportSourceRect = sourceRect; wp_viewport_set_source(mViewport, wl_fixed_from_double(mViewportSourceRect.x), wl_fixed_from_double(mViewportSourceRect.y), wl_fixed_from_double(mViewportSourceRect.width), wl_fixed_from_double(mViewportSourceRect.height)); } void NativeLayerWayland::SetViewportDestinationSize(int aWidth, int aHeight) { MutexAutoLock lock(mMutex); if (aWidth == mViewportDestinationSize.width && aHeight == mViewportDestinationSize.height) { return; } mViewportDestinationSize.width = aWidth; mViewportDestinationSize.height = aHeight; wp_viewport_set_destination(mViewport, mViewportDestinationSize.width, mViewportDestinationSize.height); } void NativeLayerWayland::RequestFrameCallback( const RefPtr& aMultiplexHelper) { MutexAutoLock lock(mMutex); MOZ_ASSERT(aMultiplexHelper->IsActive()); // Avoid piling up old helpers if this surface does not receive callbacks // for a longer time mCallbackMultiplexHelpers.RemoveElementsBy( [&](const auto& object) { return !object->IsActive(); }); mCallbackMultiplexHelpers.AppendElement(aMultiplexHelper); if (!mCallback) { mCallback = wl_surface_frame(mWlSurface); wl_callback_add_listener(mCallback, &sFrameListenerNativeLayerWayland, this); wl_surface_commit(mWlSurface); } } void NativeLayerWayland::FrameCallbackHandler(wl_callback* aCallback, uint32_t aTime) { MutexAutoLock lock(mMutex); MOZ_RELEASE_ASSERT(aCallback == mCallback); MozClearPointer(mCallback, wl_callback_destroy); for (const RefPtr& callbackMultiplexHelper : mCallbackMultiplexHelpers) { callbackMultiplexHelper->Callback(aTime); } mCallbackMultiplexHelpers.Clear(); } /* static */ void NativeLayerWayland::FrameCallbackHandler(void* aData, wl_callback* aCallback, uint32_t aTime) { auto surface = reinterpret_cast(aData); surface->FrameCallbackHandler(aCallback, aTime); } } // namespace mozilla::layers