/* -*- 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/. */ #ifdef MOZ_WAYLAND # include "WaylandVsyncSource.h" # include "nsThreadUtils.h" # include "nsISupportsImpl.h" # include "MainThreadUtils.h" # include using namespace mozilla::widget; namespace mozilla { static void WaylandVsyncSourceCallbackHandler(void* aData, struct wl_callback* aCallback, uint32_t aTime) { WaylandVsyncSource::WaylandDisplay* context = (WaylandVsyncSource::WaylandDisplay*)aData; wl_callback_destroy(aCallback); context->FrameCallback(aTime); } static void WaylandVsyncSourceCallbackHandler(void* aData, uint32_t aTime) { WaylandVsyncSource::WaylandDisplay* context = (WaylandVsyncSource::WaylandDisplay*)aData; context->FrameCallback(aTime); } static const struct wl_callback_listener WaylandVsyncSourceCallbackListener = { WaylandVsyncSourceCallbackHandler}; WaylandVsyncSource::WaylandDisplay::WaylandDisplay() : mMutex("WaylandVsyncSource"), mIsShutdown(false), mVsyncEnabled(false), mMonitorEnabled(false), mCallbackRequested(false), mContainer(nullptr), mVsyncRate(TimeDuration::FromMilliseconds(1000.0 / 60.0)), mLastVsyncTimeStamp(TimeStamp::Now()) { MOZ_ASSERT(NS_IsMainThread()); } void WaylandVsyncSource::WaylandDisplay::MaybeUpdateSource( MozContainer* aContainer) { MutexAutoLock lock(mMutex); if (aContainer == mContainer) { return; } mNativeLayerRoot = nullptr; mContainer = aContainer; if (mMonitorEnabled) { mCallbackRequested = false; Refresh(lock); } } void WaylandVsyncSource::WaylandDisplay::MaybeUpdateSource( const RefPtr& aNativeLayerRoot) { MutexAutoLock lock(mMutex); if (aNativeLayerRoot == mNativeLayerRoot) { return; } mNativeLayerRoot = aNativeLayerRoot; mContainer = nullptr; if (mMonitorEnabled) { mCallbackRequested = false; Refresh(lock); } } void WaylandVsyncSource::WaylandDisplay::Refresh( const MutexAutoLock& aProofOfLock) { if (!(mContainer || mNativeLayerRoot) || !mMonitorEnabled || !mVsyncEnabled || mCallbackRequested) { // We don't need to do anything because: // * We are unwanted by our widget or monitor, or // * The last frame callback hasn't yet run to see that it had been shut // down, so we can reuse it after having set mVsyncEnabled to true. return; } if (mContainer) { struct wl_surface* surface = moz_container_wayland_surface_lock(mContainer); if (!surface) { // The surface hasn't been created yet. Try again when the surface is // ready. RefPtr self(this); moz_container_wayland_add_initial_draw_callback( mContainer, [self]() -> void { MutexAutoLock lock(self->mMutex); self->Refresh(lock); }); return; } moz_container_wayland_surface_unlock(mContainer, &surface); } // Vsync is enabled, but we don't have a callback configured. Set one up so // we can get to work. SetupFrameCallback(aProofOfLock); mLastVsyncTimeStamp = TimeStamp::Now(); TimeStamp outputTimestamp = mLastVsyncTimeStamp + GetVsyncRate(); { MutexAutoUnlock unlock(mMutex); NotifyVsync(mLastVsyncTimeStamp, outputTimestamp); } } void WaylandVsyncSource::WaylandDisplay::EnableMonitor() { MutexAutoLock lock(mMutex); if (mMonitorEnabled) { return; } mMonitorEnabled = true; Refresh(lock); } void WaylandVsyncSource::WaylandDisplay::DisableMonitor() { MutexAutoLock lock(mMutex); if (!mMonitorEnabled) { return; } mMonitorEnabled = false; mCallbackRequested = false; } void WaylandVsyncSource::WaylandDisplay::SetupFrameCallback( const MutexAutoLock& aProofOfLock) { MOZ_ASSERT(!mCallbackRequested); if (mNativeLayerRoot) { mNativeLayerRoot->RequestFrameCallback(&WaylandVsyncSourceCallbackHandler, this); } else { struct wl_surface* surface = moz_container_wayland_surface_lock(mContainer); if (!surface) { // We don't have a surface, either due to being called before it was made // available in the mozcontainer, or after it was destroyed. We're all // done regardless. return; } wl_callback* callback = wl_surface_frame(surface); wl_callback_add_listener(callback, &WaylandVsyncSourceCallbackListener, this); wl_surface_commit(surface); wl_display_flush(WaylandDisplayGet()->GetDisplay()); moz_container_wayland_surface_unlock(mContainer, &surface); } mCallbackRequested = true; } void WaylandVsyncSource::WaylandDisplay::FrameCallback(uint32_t aTime) { MutexAutoLock lock(mMutex); mCallbackRequested = false; if (!mVsyncEnabled || !mMonitorEnabled) { // We are unwanted by either our creator or our consumer, so we just stop // here without setting up a new frame callback. return; } // Configure our next frame callback. SetupFrameCallback(lock); int64_t tick = BaseTimeDurationPlatformUtils::TicksFromMilliseconds(aTime); TimeStamp callbackTimeStamp = TimeStamp::FromSystemTime(tick); double duration = (TimeStamp::Now() - callbackTimeStamp).ToMilliseconds(); TimeStamp vsyncTimestamp; if (duration < 50 && duration > -50) { vsyncTimestamp = callbackTimeStamp; } else { vsyncTimestamp = TimeStamp::Now(); } CalculateVsyncRate(lock, vsyncTimestamp); mLastVsyncTimeStamp = vsyncTimestamp; TimeStamp outputTimestamp = vsyncTimestamp + GetVsyncRate(); { MutexAutoUnlock unlock(mMutex); NotifyVsync(mLastVsyncTimeStamp, outputTimestamp); } } TimeDuration WaylandVsyncSource::WaylandDisplay::GetVsyncRate() { return mVsyncRate; } void WaylandVsyncSource::WaylandDisplay::CalculateVsyncRate( const MutexAutoLock& aProofOfLock, TimeStamp aVsyncTimestamp) { double duration = (aVsyncTimestamp - mLastVsyncTimeStamp).ToMilliseconds(); double curVsyncRate = mVsyncRate.ToMilliseconds(); if (duration > curVsyncRate) { double correction = fmin(curVsyncRate, (duration - curVsyncRate) / 10); mVsyncRate += TimeDuration::FromMilliseconds(correction); } else { double correction = fmin(curVsyncRate / 2, (curVsyncRate - duration) / 10); mVsyncRate -= TimeDuration::FromMilliseconds(correction); } } void WaylandVsyncSource::WaylandDisplay::EnableVsync() { MOZ_ASSERT(NS_IsMainThread()); MutexAutoLock lock(mMutex); if (mVsyncEnabled || mIsShutdown) { return; } mVsyncEnabled = true; Refresh(lock); } void WaylandVsyncSource::WaylandDisplay::DisableVsync() { MutexAutoLock lock(mMutex); mVsyncEnabled = false; mCallbackRequested = false; } bool WaylandVsyncSource::WaylandDisplay::IsVsyncEnabled() { MutexAutoLock lock(mMutex); return mVsyncEnabled; } void WaylandVsyncSource::WaylandDisplay::Shutdown() { MOZ_ASSERT(NS_IsMainThread()); MutexAutoLock lock(mMutex); mContainer = nullptr; mNativeLayerRoot = nullptr; mIsShutdown = true; mVsyncEnabled = false; mCallbackRequested = false; } } // namespace mozilla #endif // MOZ_WAYLAND