blob: 14df44c52c1f52ea6324f2fa0dde7268222e48b3 [file] [log] [blame]
/*
* Copyright (C) 2018-2022 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "DisplayLink.h"
#if HAVE(CVDISPLAYLINK)
#include "EventDispatcherMessages.h"
#include "Logging.h"
#include "WebProcessMessages.h"
#include <WebCore/AnimationFrameRate.h>
#include <wtf/ProcessPrivilege.h>
#include <wtf/text/TextStream.h>
namespace WebKit {
bool DisplayLink::shouldSendIPCOnBackgroundQueue { false };
constexpr unsigned maxFireCountWithoutObservers { 20 };
DisplayLink::DisplayLink(WebCore::PlatformDisplayID displayID)
: m_displayID(displayID)
{
// FIXME: We can get here with displayID == 0 (webkit.org/b/212120), in which case CVDisplayLinkCreateWithCGDisplay()
// probably defaults to the main screen.
ASSERT(hasProcessPrivilege(ProcessPrivilege::CanCommunicateWithWindowServer));
CVReturn error = CVDisplayLinkCreateWithCGDisplay(displayID, &m_displayLink);
if (error) {
RELEASE_LOG_FAULT(DisplayLink, "Could not create a display link for display %u: error %d", displayID, error);
return;
}
error = CVDisplayLinkSetOutputCallback(m_displayLink, displayLinkCallback, this);
if (error) {
RELEASE_LOG_FAULT(DisplayLink, "DisplayLink: Could not set the display link output callback for display %u: error %d", displayID, error);
return;
}
m_displayNominalFramesPerSecond = nominalFramesPerSecondFromDisplayLink(m_displayLink);
LOG_WITH_STREAM(DisplayLink, stream << "[UI ] Created DisplayLink " << this << " for display " << displayID << " with nominal fps " << m_displayNominalFramesPerSecond);
}
DisplayLink::~DisplayLink()
{
LOG_WITH_STREAM(DisplayLink, stream << "[UI ] Destroying DisplayLink " << this << " for display " << m_displayID);
ASSERT(hasProcessPrivilege(ProcessPrivilege::CanCommunicateWithWindowServer));
ASSERT(m_displayLink);
if (!m_displayLink)
return;
CVDisplayLinkStop(m_displayLink);
CVDisplayLinkRelease(m_displayLink);
}
WebCore::FramesPerSecond DisplayLink::nominalFramesPerSecondFromDisplayLink(CVDisplayLinkRef displayLink)
{
CVTime refreshPeriod = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(displayLink);
if (!refreshPeriod.timeValue)
return WebCore::FullSpeedFramesPerSecond;
WebCore::FramesPerSecond result = round((double)refreshPeriod.timeScale / (double)refreshPeriod.timeValue);
return result ?: WebCore::FullSpeedFramesPerSecond;
}
void DisplayLink::addObserver(IPC::Connection& connection, DisplayLinkObserverID observerID, WebCore::FramesPerSecond preferredFramesPerSecond)
{
ASSERT(RunLoop::isMain());
LOG_WITH_STREAM(DisplayLink, stream << "[UI ] DisplayLink " << this << " for display display " << m_displayID << " add observer " << observerID << " fps " << preferredFramesPerSecond);
{
Locker locker { m_observersLock };
m_observers.ensure(connection.uniqueID(), [] {
return ConnectionClientInfo { };
}).iterator->value.observers.append({ observerID, preferredFramesPerSecond });
}
if (!CVDisplayLinkIsRunning(m_displayLink)) {
LOG_WITH_STREAM(DisplayLink, stream << "[UI ] DisplayLink for display " << m_displayID << " starting CVDisplayLink with fps " << m_displayNominalFramesPerSecond);
m_currentUpdate = { 0, m_displayNominalFramesPerSecond };
CVReturn error = CVDisplayLinkStart(m_displayLink);
if (error)
RELEASE_LOG_FAULT(DisplayLink, "DisplayLink: Could not start the display link: %d", error);
}
}
void DisplayLink::removeObserver(IPC::Connection& connection, DisplayLinkObserverID observerID)
{
ASSERT(RunLoop::isMain());
Locker locker { m_observersLock };
auto it = m_observers.find(connection.uniqueID());
if (it == m_observers.end())
return;
auto& connectionInfo = it->value;
bool removed = connectionInfo.observers.removeFirstMatching([observerID](const auto& value) {
return value.observerID == observerID;
});
ASSERT_UNUSED(removed, removed);
LOG_WITH_STREAM(DisplayLink, stream << "[UI ] DisplayLink " << this << " for display " << m_displayID << " remove observer " << observerID);
removeInfoForConnectionIfPossible(connection);
// We do not stop the display link right away when |m_observers| becomes empty. Instead, we
// let the display link fire up to |maxFireCountWithoutObservers| times without observers to avoid
// killing & restarting too many threads when observers gets removed & added in quick succession.
}
void DisplayLink::removeObservers(IPC::Connection& connection)
{
ASSERT(RunLoop::isMain());
Locker locker { m_observersLock };
m_observers.remove(connection.uniqueID());
// We do not stop the display link right away when |m_observers| becomes empty. Instead, we
// let the display link fire up to |maxFireCountWithoutObservers| times without observers to avoid
// killing & restarting too many threads when observers gets removed & added in quick succession.
}
void DisplayLink::removeInfoForConnectionIfPossible(IPC::Connection& connection)
{
auto it = m_observers.find(connection.uniqueID());
if (it == m_observers.end())
return;
auto& connectionInfo = it->value;
if (connectionInfo.observers.isEmpty() && !connectionInfo.fullSpeedUpdatesClientCount)
m_observers.remove(it);
}
void DisplayLink::incrementFullSpeedRequestClientCount(IPC::Connection& connection)
{
Locker locker { m_observersLock };
auto& connectionInfo = m_observers.ensure(connection.uniqueID(), [] {
return ConnectionClientInfo { };
}).iterator->value;
++connectionInfo.fullSpeedUpdatesClientCount;
}
void DisplayLink::decrementFullSpeedRequestClientCount(IPC::Connection& connection)
{
Locker locker { m_observersLock };
auto it = m_observers.find(connection.uniqueID());
if (it == m_observers.end())
return;
auto& connectionInfo = it->value;
ASSERT(connectionInfo.fullSpeedUpdatesClientCount);
--connectionInfo.fullSpeedUpdatesClientCount;
removeInfoForConnectionIfPossible(connection);
}
void DisplayLink::setPreferredFramesPerSecond(IPC::Connection& connection, DisplayLinkObserverID observerID, WebCore::FramesPerSecond preferredFramesPerSecond)
{
LOG_WITH_STREAM(DisplayLink, stream << "[UI ] DisplayLink " << this << " setPreferredFramesPerSecond - display " << m_displayID << " observer " << observerID << " fps " << preferredFramesPerSecond);
Locker locker { m_observersLock };
auto it = m_observers.find(connection.uniqueID());
if (it == m_observers.end())
return;
auto& connectionInfo = it->value;
auto index = connectionInfo.observers.findIf([observerID](const auto& observer) {
return observer.observerID == observerID;
});
if (index != notFound)
connectionInfo.observers[index].preferredFramesPerSecond = preferredFramesPerSecond;
}
CVReturn DisplayLink::displayLinkCallback(CVDisplayLinkRef displayLinkRef, const CVTimeStamp*, const CVTimeStamp*, CVOptionFlags, CVOptionFlags*, void* data)
{
static_cast<DisplayLink*>(data)->notifyObserversDisplayWasRefreshed();
return kCVReturnSuccess;
}
void DisplayLink::notifyObserversDisplayWasRefreshed()
{
ASSERT(!RunLoop::isMain());
Locker locker { m_observersLock };
auto maxFramesPerSecond = [](const Vector<ObserverInfo>& observers) {
std::optional<WebCore::FramesPerSecond> observersMaxFramesPerSecond;
for (const auto& observer : observers)
observersMaxFramesPerSecond = std::max(observersMaxFramesPerSecond.value_or(0), observer.preferredFramesPerSecond);
return observersMaxFramesPerSecond;
};
bool anyConnectionHadObservers = false;
for (auto& [connectionID, connectionInfo] : m_observers) {
if (connectionInfo.observers.isEmpty())
continue;
anyConnectionHadObservers = true;
auto observersMaxFramesPerSecond = maxFramesPerSecond(connectionInfo.observers);
auto mainThreadWantsUpdate = m_currentUpdate.relevantForUpdateFrequency(observersMaxFramesPerSecond.value_or(WebCore::FullSpeedFramesPerSecond));
LOG_WITH_STREAM(DisplayLink, stream << "[UI ] DisplayLink " << this << " for display " << m_displayID << " (display fps " << m_displayNominalFramesPerSecond << ") update " << m_currentUpdate << " " << connectionInfo.observers.size()
<< " observers, on background queue " << shouldSendIPCOnBackgroundQueue << " maxFramesPerSecond " << observersMaxFramesPerSecond << " full speed clients " << connectionInfo.fullSpeedUpdatesClientCount << " relevant " << mainThreadWantsUpdate);
if (connectionInfo.fullSpeedUpdatesClientCount) {
IPC::Connection::send(connectionID, Messages::EventDispatcher::DisplayWasRefreshed(m_displayID, m_currentUpdate, mainThreadWantsUpdate), 0, { }, Thread::QOS::UserInteractive);
} else if (mainThreadWantsUpdate)
IPC::Connection::send(connectionID, Messages::WebProcess::DisplayWasRefreshed(m_displayID, m_currentUpdate), 0, { }, Thread::QOS::UserInteractive);
}
m_currentUpdate = m_currentUpdate.nextUpdate();
if (!anyConnectionHadObservers) {
if (++m_fireCountWithoutObservers >= maxFireCountWithoutObservers) {
LOG_WITH_STREAM(DisplayLink, stream << "[UI ] DisplayLink for display " << m_displayID << " fired " << m_fireCountWithoutObservers << " times with no observers; stopping CVDisplayLink");
CVDisplayLinkStop(m_displayLink);
}
return;
}
m_fireCountWithoutObservers = 0;
}
} // namespace WebKit
#endif // HAVE(CVDISPLAYLINK)