| /* |
| * 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) |