| /* |
| * Copyright (C) 2018 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" |
| |
| #if ENABLE(GRAPHICS_CONTEXT_3D) |
| #include "GraphicsContext3DManager.h" |
| |
| #include "GraphicsContext3D.h" |
| #include "Logging.h" |
| |
| #if HAVE(APPLE_GRAPHICS_CONTROL) |
| #include <sys/sysctl.h> |
| #endif |
| |
| #if PLATFORM(MAC) && USE(OPENGL) |
| #include "SwitchingGPUClient.h" |
| #include <OpenGL/OpenGL.h> |
| #endif |
| |
| namespace WebCore { |
| |
| #if HAVE(APPLE_GRAPHICS_CONTROL) |
| |
| enum { |
| kAGCOpen, |
| kAGCClose |
| }; |
| |
| static io_connect_t attachToAppleGraphicsControl() |
| { |
| mach_port_t masterPort = MACH_PORT_NULL; |
| |
| if (IOMasterPort(MACH_PORT_NULL, &masterPort) != KERN_SUCCESS) |
| return MACH_PORT_NULL; |
| |
| CFDictionaryRef classToMatch = IOServiceMatching("AppleGraphicsControl"); |
| if (!classToMatch) |
| return MACH_PORT_NULL; |
| |
| kern_return_t kernResult; |
| io_iterator_t iterator; |
| if ((kernResult = IOServiceGetMatchingServices(masterPort, classToMatch, &iterator)) != KERN_SUCCESS) |
| return MACH_PORT_NULL; |
| |
| io_service_t serviceObject = IOIteratorNext(iterator); |
| IOObjectRelease(iterator); |
| if (!serviceObject) |
| return MACH_PORT_NULL; |
| |
| io_connect_t dataPort; |
| IOObjectRetain(serviceObject); |
| kernResult = IOServiceOpen(serviceObject, mach_task_self(), 0, &dataPort); |
| IOObjectRelease(serviceObject); |
| |
| return (kernResult == KERN_SUCCESS) ? dataPort : MACH_PORT_NULL; |
| } |
| |
| static bool hasMuxCapability() |
| { |
| io_connect_t dataPort = attachToAppleGraphicsControl(); |
| |
| if (dataPort == MACH_PORT_NULL) |
| return false; |
| |
| bool result; |
| if (IOConnectCallScalarMethod(dataPort, kAGCOpen, nullptr, 0, nullptr, nullptr) == KERN_SUCCESS) { |
| IOConnectCallScalarMethod(dataPort, kAGCClose, nullptr, 0, nullptr, nullptr); |
| result = true; |
| } else |
| result = false; |
| |
| IOServiceClose(dataPort); |
| |
| if (result) { |
| // This is detecting Mac hardware with an Intel g575 GPU, which |
| // we don't want to make available to muxing. |
| // Based on information from Apple's OpenGL team, such devices |
| // have four or fewer processors. |
| // <rdar://problem/30060378> |
| int names[2] = { CTL_HW, HW_NCPU }; |
| int cpuCount; |
| size_t cpuCountLength = sizeof(cpuCount); |
| sysctl(names, 2, &cpuCount, &cpuCountLength, nullptr, 0); |
| result = cpuCount > 4; |
| } |
| |
| return result; |
| } |
| |
| bool hasLowAndHighPowerGPUs() |
| { |
| static bool canMux = hasMuxCapability(); |
| return canMux; |
| } |
| #endif // HAVE(APPLE_GRAPHICS_CONTROL) |
| |
| GraphicsContext3DManager& GraphicsContext3DManager::sharedManager() |
| { |
| static NeverDestroyed<GraphicsContext3DManager> s_manager; |
| return s_manager; |
| } |
| |
| #if PLATFORM(MAC) |
| void GraphicsContext3DManager::displayWasReconfigured(CGDirectDisplayID, CGDisplayChangeSummaryFlags flags, void*) |
| { |
| LOG(WebGL, "GraphicsContext3DManager::displayWasReconfigured"); |
| if (flags & kCGDisplaySetModeFlag) |
| GraphicsContext3DManager::sharedManager().updateAllContexts(); |
| } |
| #endif |
| |
| void GraphicsContext3DManager::updateAllContexts() |
| { |
| // FIXME: determine whether to do anything when using ANGLE. |
| #if PLATFORM(MAC) && USE(OPENGL) |
| for (const auto& context : m_contexts) { |
| context->updateCGLContext(); |
| context->dispatchContextChangedNotification(); |
| } |
| #endif |
| } |
| |
| #if PLATFORM(MAC) |
| void GraphicsContext3DManager::screenDidChange(PlatformDisplayID displayID, const HostWindow* window) |
| { |
| for (const auto& contextAndWindow : m_contextWindowMap) { |
| if (contextAndWindow.value == window) { |
| contextAndWindow.key->screenDidChange(displayID); |
| LOG(WebGL, "Changing context (%p) to display (%d).", contextAndWindow.key, displayID); |
| } |
| } |
| } |
| #endif |
| |
| void GraphicsContext3DManager::addContext(GraphicsContext3D* context, HostWindow* window) |
| { |
| ASSERT(context); |
| if (!context) |
| return; |
| |
| #if PLATFORM(MAC) && !ENABLE(WEBPROCESS_WINDOWSERVER_BLOCKING) |
| if (!m_contexts.size()) |
| CGDisplayRegisterReconfigurationCallback(displayWasReconfigured, nullptr); |
| #endif |
| |
| ASSERT(!m_contexts.contains(context)); |
| m_contexts.append(context); |
| m_contextWindowMap.set(context, window); |
| } |
| |
| void GraphicsContext3DManager::removeContext(GraphicsContext3D* context) |
| { |
| if (!m_contexts.contains(context)) |
| return; |
| m_contexts.removeFirst(context); |
| m_contextWindowMap.remove(context); |
| removeContextRequiringHighPerformance(context); |
| |
| #if PLATFORM(MAC) && !ENABLE(WEBPROCESS_WINDOWSERVER_BLOCKING) |
| if (!m_contexts.size()) |
| CGDisplayRemoveReconfigurationCallback(displayWasReconfigured, nullptr); |
| #endif |
| } |
| |
| HostWindow* GraphicsContext3DManager::hostWindowForContext(GraphicsContext3D* context) const |
| { |
| ASSERT(m_contextWindowMap.contains(context)); |
| return m_contextWindowMap.get(context); |
| } |
| |
| void GraphicsContext3DManager::addContextRequiringHighPerformance(GraphicsContext3D* context) |
| { |
| ASSERT(context); |
| if (!context) |
| return; |
| |
| ASSERT(m_contexts.contains(context)); |
| ASSERT(!m_contextsRequiringHighPerformance.contains(context)); |
| |
| LOG(WebGL, "This context (%p) requires the high-performance GPU.", context); |
| m_contextsRequiringHighPerformance.add(context); |
| |
| updateHighPerformanceState(); |
| } |
| |
| void GraphicsContext3DManager::removeContextRequiringHighPerformance(GraphicsContext3D* context) |
| { |
| if (!context) |
| return; |
| |
| if (!m_contextsRequiringHighPerformance.contains(context)) |
| return; |
| |
| LOG(WebGL, "This context (%p) no longer requires the high-performance GPU.", context); |
| m_contextsRequiringHighPerformance.remove(context); |
| |
| updateHighPerformanceState(); |
| } |
| |
| void GraphicsContext3DManager::updateHighPerformanceState() |
| { |
| #if PLATFORM(MAC) && USE(OPENGL) |
| if (!hasLowAndHighPowerGPUs()) |
| return; |
| |
| if (m_contextsRequiringHighPerformance.size()) { |
| |
| if (m_disableHighPerformanceGPUTimer.isActive()) { |
| LOG(WebGL, "Cancel pending timer for turning off high-performance GPU."); |
| m_disableHighPerformanceGPUTimer.stop(); |
| } |
| |
| if (!m_requestingHighPerformance) { |
| LOG(WebGL, "Request the high-performance GPU."); |
| m_requestingHighPerformance = true; |
| #if PLATFORM(MAC) |
| SwitchingGPUClient::singleton().requestHighPerformanceGPU(); |
| #endif |
| } |
| |
| } else { |
| // Don't immediately turn off the high-performance GPU. The user might be |
| // swapping back and forth between tabs or windows, and we don't want to cause |
| // churn if we can avoid it. |
| if (!m_disableHighPerformanceGPUTimer.isActive()) { |
| LOG(WebGL, "Set a timer to release the high-performance GPU."); |
| // FIXME: Expose this value as a Setting, which would require this class |
| // to reference a frame, page or document. |
| static const Seconds timeToKeepHighPerformanceGPUAlive { 10_s }; |
| m_disableHighPerformanceGPUTimer.startOneShot(timeToKeepHighPerformanceGPUAlive); |
| } |
| } |
| #endif |
| } |
| |
| void GraphicsContext3DManager::disableHighPerformanceGPUTimerFired() |
| { |
| if (m_contextsRequiringHighPerformance.size()) |
| return; |
| |
| m_requestingHighPerformance = false; |
| #if PLATFORM(MAC) && USE(OPENGL) |
| SwitchingGPUClient::singleton().releaseHighPerformanceGPU(); |
| #endif |
| } |
| |
| void GraphicsContext3DManager::recycleContextIfNecessary() |
| { |
| if (hasTooManyContexts()) { |
| LOG(WebGL, "GraphicsContext3DManager recycled context (%p).", m_contexts[0]); |
| m_contexts[0]->recycleContext(); |
| } |
| } |
| |
| } // namespace WebCore |
| |
| #endif // ENABLE(GRAPHICS_CONTEXT_3D) |