/*
* Copyright (C) 2018-2024 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(DISPLAY_LINK)
#include "Logging.h"
#include
#include
#include
#include
#include
namespace WebKit {
using namespace WebCore;
constexpr unsigned maxFireCountWithoutObservers { 20 };
WTF_MAKE_TZONE_ALLOCATED_IMPL(DisplayLink);
WTF_MAKE_TZONE_ALLOCATED_IMPL(DisplayLink::Client);
DisplayLink::DisplayLink(PlatformDisplayID displayID)
: m_displayID(displayID)
{
platformInitialize();
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);
platformFinalize();
}
void DisplayLink::addObserver(Client& client, DisplayLinkObserverID observerID, 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_clientsLock };
m_clients.ensure(client, [] {
return ClientInfo { };
}).iterator->value.observers.append({ observerID, preferredFramesPerSecond });
}
if (!platformIsRunning()) {
LOG_WITH_STREAM(DisplayLink, stream << "[UI ] DisplayLink for display " << m_displayID << " starting DisplayLink with fps " << m_displayNominalFramesPerSecond);
m_currentUpdate = { 0, m_displayNominalFramesPerSecond };
platformStart();
}
}
void DisplayLink::removeObserver(Client& client, DisplayLinkObserverID observerID)
{
ASSERT(RunLoop::isMain());
Locker locker { m_clientsLock };
auto it = m_clients.find(client);
if (it == m_clients.end())
return;
auto& clientInfo = it->value;
bool removed = clientInfo.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);
removeInfoForClientIfUnused(client);
// We do not stop the display link right away when |m_clients| 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::removeClient(Client& client)
{
ASSERT(RunLoop::isMain());
Locker locker { m_clientsLock };
m_clients.remove(client);
// We do not stop the display link right away when |m_clients| 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.
}
bool DisplayLink::removeInfoForClientIfUnused(Client& client)
{
auto it = m_clients.find(client);
if (it == m_clients.end())
return false;
auto& clientInfo = it->value;
if (clientInfo.observers.isEmpty() && !clientInfo.fullSpeedUpdatesClientCount) {
m_clients.remove(it);
return true;
}
return false;
}
void DisplayLink::incrementFullSpeedRequestClientCount(Client& client)
{
Locker locker { m_clientsLock };
auto& clientInfo = m_clients.ensure(client, [] {
return ClientInfo { };
}).iterator->value;
++clientInfo.fullSpeedUpdatesClientCount;
}
void DisplayLink::decrementFullSpeedRequestClientCount(Client& client)
{
Locker locker { m_clientsLock };
auto it = m_clients.find(client);
if (it == m_clients.end())
return;
auto& clientInfo = it->value;
ASSERT(clientInfo.fullSpeedUpdatesClientCount);
--clientInfo.fullSpeedUpdatesClientCount;
removeInfoForClientIfUnused(client);
}
void DisplayLink::displayPropertiesChanged()
{
// FIXME: Detect whether the refresh frequency changed.
}
void DisplayLink::setObserverPreferredFramesPerSecond(Client& client, DisplayLinkObserverID observerID, FramesPerSecond preferredFramesPerSecond)
{
LOG_WITH_STREAM(DisplayLink, stream << "[UI ] DisplayLink " << this << " setPreferredFramesPerSecond - display " << m_displayID << " observer " << observerID << " fps " << preferredFramesPerSecond);
Locker locker { m_clientsLock };
auto it = m_clients.find(client);
if (it == m_clients.end())
return;
auto& clientInfo = it->value;
auto index = clientInfo.observers.findIf([observerID](const auto& observer) {
return observer.observerID == observerID;
});
if (index != notFound)
clientInfo.observers[index].preferredFramesPerSecond = preferredFramesPerSecond;
}
void DisplayLink::notifyObserversDisplayDidRefresh()
{
ASSERT(!RunLoop::isMain());
Locker locker { m_clientsLock };
tracePoint(DisplayLinkUpdate);
auto maxFramesPerSecond = [](const Vector& observers) {
std::optional observersMaxFramesPerSecond;
for (const auto& observer : observers)
observersMaxFramesPerSecond = std::max(observersMaxFramesPerSecond.value_or(0), observer.preferredFramesPerSecond);
return observersMaxFramesPerSecond;
};
bool anyConnectionHadObservers = false;
for (auto& [client, clientInfo] : m_clients) {
if (clientInfo.observers.isEmpty())
continue;
anyConnectionHadObservers = true;
auto observersMaxFramesPerSecond = maxFramesPerSecond(clientInfo.observers);
bool anyObserverWantsCallback = m_currentUpdate.relevantForUpdateFrequency(observersMaxFramesPerSecond.value_or(FullSpeedFramesPerSecond));
LOG_WITH_STREAM(DisplayLink, stream << "[UI ] DisplayLink " << this << " for display " << m_displayID << " (display fps " << m_displayNominalFramesPerSecond << ") update " << m_currentUpdate << " " << clientInfo.observers.size()
<< " observers, maxFramesPerSecond " << observersMaxFramesPerSecond << " full speed client count " << clientInfo.fullSpeedUpdatesClientCount << " relevant " << anyObserverWantsCallback);
if (clientInfo.fullSpeedUpdatesClientCount || anyObserverWantsCallback)
CheckedRef { client }->displayLinkFired(m_displayID, m_currentUpdate, clientInfo.fullSpeedUpdatesClientCount, anyObserverWantsCallback);
}
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 DisplayLink");
platformStop();
}
return;
}
m_fireCountWithoutObservers = 0;
}
DisplayLink& DisplayLinkCollection::displayLinkForDisplay(PlatformDisplayID displayID)
{
if (auto* displayLink = existingDisplayLinkForDisplay(displayID))
return *displayLink;
auto displayLink = makeUnique(displayID);
auto displayLinkPtr = displayLink.get();
add(WTF::move(displayLink));
return *displayLinkPtr;
}
DisplayLink* DisplayLinkCollection::existingDisplayLinkForDisplay(PlatformDisplayID displayID) const
{
for (auto& displayLink : m_displayLinks) {
if (displayLink->displayID() == displayID)
return displayLink.get();
}
return nullptr;
}
void DisplayLinkCollection::add(std::unique_ptr&& displayLink)
{
ASSERT(!m_displayLinks.containsIf([&](auto &entry) { return entry->displayID() == displayLink->displayID(); }));
m_displayLinks.append(WTF::move(displayLink));
}
std::optional DisplayLinkCollection::nominalFramesPerSecondForDisplay(PlatformDisplayID displayID)
{
// Note that this may create a DisplayLink with no observers, but it's highly likely that we'll soon call startDisplayLink() for it.
auto& displayLink = displayLinkForDisplay(displayID);
return displayLink.nominalFramesPerSecond();
}
void DisplayLinkCollection::startDisplayLink(DisplayLink::Client& client, DisplayLinkObserverID observerID, PlatformDisplayID displayID, FramesPerSecond preferredFramesPerSecond)
{
auto& displayLink = displayLinkForDisplay(displayID);
displayLink.addObserver(client, observerID, preferredFramesPerSecond);
}
void DisplayLinkCollection::stopDisplayLink(DisplayLink::Client& client, DisplayLinkObserverID observerID, PlatformDisplayID displayID)
{
if (auto* displayLink = existingDisplayLinkForDisplay(displayID))
displayLink->removeObserver(client, observerID);
// FIXME: Remove unused display links?
}
void DisplayLinkCollection::stopDisplayLinks(DisplayLink::Client& client)
{
for (auto& displayLink : m_displayLinks)
displayLink->removeClient(client);
// FIXME: Remove unused display links?
}
void DisplayLinkCollection::setDisplayLinkPreferredFramesPerSecond(DisplayLink::Client& client, DisplayLinkObserverID observerID, PlatformDisplayID displayID, FramesPerSecond preferredFramesPerSecond)
{
LOG_WITH_STREAM(DisplayLink, stream << "[UI ] WebProcessPool::setDisplayLinkPreferredFramesPerSecond - display " << displayID << " observer " << observerID << " fps " << preferredFramesPerSecond);
if (auto* displayLink = existingDisplayLinkForDisplay(displayID))
displayLink->setObserverPreferredFramesPerSecond(client, observerID, preferredFramesPerSecond);
}
void DisplayLinkCollection::setDisplayLinkForDisplayWantsFullSpeedUpdates(DisplayLink::Client& client, PlatformDisplayID displayID, bool wantsFullSpeedUpdates)
{
if (auto* displayLink = existingDisplayLinkForDisplay(displayID)) {
if (wantsFullSpeedUpdates)
displayLink->incrementFullSpeedRequestClientCount(client);
else
displayLink->decrementFullSpeedRequestClientCount(client);
}
}
} // namespace WebKit
#endif // HAVE(DISPLAY_LINK)