1/*
2 * Copyright (C) 2018 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27
28#if ENABLE(GRAPHICS_CONTEXT_3D)
29#include "GraphicsContext3DManager.h"
30
31#include "GraphicsContext3D.h"
32#include "Logging.h"
33
34#if HAVE(APPLE_GRAPHICS_CONTROL)
35#include <sys/sysctl.h>
36#endif
37
38#if PLATFORM(MAC)
39#include "SwitchingGPUClient.h"
40#include <OpenGL/OpenGL.h>
41#endif
42
43namespace WebCore {
44
45#if HAVE(APPLE_GRAPHICS_CONTROL)
46
47enum {
48 kAGCOpen,
49 kAGCClose
50};
51
52static io_connect_t attachToAppleGraphicsControl()
53{
54 mach_port_t masterPort = MACH_PORT_NULL;
55
56 if (IOMasterPort(MACH_PORT_NULL, &masterPort) != KERN_SUCCESS)
57 return MACH_PORT_NULL;
58
59 CFDictionaryRef classToMatch = IOServiceMatching("AppleGraphicsControl");
60 if (!classToMatch)
61 return MACH_PORT_NULL;
62
63 kern_return_t kernResult;
64 io_iterator_t iterator;
65 if ((kernResult = IOServiceGetMatchingServices(masterPort, classToMatch, &iterator)) != KERN_SUCCESS)
66 return MACH_PORT_NULL;
67
68 io_service_t serviceObject = IOIteratorNext(iterator);
69 IOObjectRelease(iterator);
70 if (!serviceObject)
71 return MACH_PORT_NULL;
72
73 io_connect_t dataPort;
74 IOObjectRetain(serviceObject);
75 kernResult = IOServiceOpen(serviceObject, mach_task_self(), 0, &dataPort);
76 IOObjectRelease(serviceObject);
77
78 return (kernResult == KERN_SUCCESS) ? dataPort : MACH_PORT_NULL;
79}
80
81static bool hasMuxCapability()
82{
83 io_connect_t dataPort = attachToAppleGraphicsControl();
84
85 if (dataPort == MACH_PORT_NULL)
86 return false;
87
88 bool result;
89 if (IOConnectCallScalarMethod(dataPort, kAGCOpen, nullptr, 0, nullptr, nullptr) == KERN_SUCCESS) {
90 IOConnectCallScalarMethod(dataPort, kAGCClose, nullptr, 0, nullptr, nullptr);
91 result = true;
92 } else
93 result = false;
94
95 IOServiceClose(dataPort);
96
97 if (result) {
98 // This is detecting Mac hardware with an Intel g575 GPU, which
99 // we don't want to make available to muxing.
100 // Based on information from Apple's OpenGL team, such devices
101 // have four or fewer processors.
102 // <rdar://problem/30060378>
103 int names[2] = { CTL_HW, HW_NCPU };
104 int cpuCount;
105 size_t cpuCountLength = sizeof(cpuCount);
106 sysctl(names, 2, &cpuCount, &cpuCountLength, nullptr, 0);
107 result = cpuCount > 4;
108 }
109
110 return result;
111}
112
113bool hasLowAndHighPowerGPUs()
114{
115 static bool canMux = hasMuxCapability();
116 return canMux;
117}
118#endif // HAVE(APPLE_GRAPHICS_CONTROL)
119
120GraphicsContext3DManager& GraphicsContext3DManager::sharedManager()
121{
122 static NeverDestroyed<GraphicsContext3DManager> s_manager;
123 return s_manager;
124}
125
126#if PLATFORM(MAC)
127void GraphicsContext3DManager::displayWasReconfigured(CGDirectDisplayID, CGDisplayChangeSummaryFlags flags, void*)
128{
129 LOG(WebGL, "GraphicsContext3DManager::displayWasReconfigured");
130 if (flags & kCGDisplaySetModeFlag)
131 GraphicsContext3DManager::sharedManager().updateAllContexts();
132}
133#endif
134
135void GraphicsContext3DManager::updateAllContexts()
136{
137#if PLATFORM(MAC)
138 for (const auto& context : m_contexts) {
139 context->updateCGLContext();
140 context->dispatchContextChangedNotification();
141 }
142#endif
143}
144
145#if PLATFORM(MAC)
146void GraphicsContext3DManager::screenDidChange(PlatformDisplayID displayID, const HostWindow* window)
147{
148 for (const auto& contextAndWindow : m_contextWindowMap) {
149 if (contextAndWindow.value == window) {
150 contextAndWindow.key->screenDidChange(displayID);
151 LOG(WebGL, "Changing context (%p) to display (%d).", contextAndWindow.key, displayID);
152 }
153 }
154}
155#endif
156
157void GraphicsContext3DManager::addContext(GraphicsContext3D* context, HostWindow* window)
158{
159 ASSERT(context);
160 if (!context)
161 return;
162
163#if PLATFORM(MAC) && !ENABLE(WEBPROCESS_WINDOWSERVER_BLOCKING)
164 if (!m_contexts.size())
165 CGDisplayRegisterReconfigurationCallback(displayWasReconfigured, nullptr);
166#endif
167
168 ASSERT(!m_contexts.contains(context));
169 m_contexts.append(context);
170 m_contextWindowMap.set(context, window);
171}
172
173void GraphicsContext3DManager::removeContext(GraphicsContext3D* context)
174{
175 ASSERT(m_contexts.contains(context));
176 m_contexts.removeFirst(context);
177 m_contextWindowMap.remove(context);
178 removeContextRequiringHighPerformance(context);
179
180#if PLATFORM(MAC) && !ENABLE(WEBPROCESS_WINDOWSERVER_BLOCKING)
181 if (!m_contexts.size())
182 CGDisplayRemoveReconfigurationCallback(displayWasReconfigured, nullptr);
183#endif
184}
185
186HostWindow* GraphicsContext3DManager::hostWindowForContext(GraphicsContext3D* context) const
187{
188 ASSERT(m_contextWindowMap.contains(context));
189 return m_contextWindowMap.get(context);
190}
191
192void GraphicsContext3DManager::addContextRequiringHighPerformance(GraphicsContext3D* context)
193{
194 ASSERT(context);
195 if (!context)
196 return;
197
198 ASSERT(m_contexts.contains(context));
199 ASSERT(!m_contextsRequiringHighPerformance.contains(context));
200
201 LOG(WebGL, "This context (%p) requires the high-performance GPU.", context);
202 m_contextsRequiringHighPerformance.add(context);
203
204 updateHighPerformanceState();
205}
206
207void GraphicsContext3DManager::removeContextRequiringHighPerformance(GraphicsContext3D* context)
208{
209 if (!m_contextsRequiringHighPerformance.contains(context))
210 return;
211
212 LOG(WebGL, "This context (%p) no longer requires the high-performance GPU.", context);
213 m_contextsRequiringHighPerformance.remove(context);
214
215 updateHighPerformanceState();
216}
217
218void GraphicsContext3DManager::updateHighPerformanceState()
219{
220#if PLATFORM(MAC)
221 if (!hasLowAndHighPowerGPUs())
222 return;
223
224 if (m_contextsRequiringHighPerformance.size()) {
225
226 if (m_disableHighPerformanceGPUTimer.isActive()) {
227 LOG(WebGL, "Cancel pending timer for turning off high-performance GPU.");
228 m_disableHighPerformanceGPUTimer.stop();
229 }
230
231 if (!m_requestingHighPerformance) {
232 LOG(WebGL, "Request the high-performance GPU.");
233 m_requestingHighPerformance = true;
234#if PLATFORM(MAC)
235 SwitchingGPUClient::singleton().requestHighPerformanceGPU();
236#endif
237 }
238
239 } else {
240 // Don't immediately turn off the high-performance GPU. The user might be
241 // swapping back and forth between tabs or windows, and we don't want to cause
242 // churn if we can avoid it.
243 if (!m_disableHighPerformanceGPUTimer.isActive()) {
244 LOG(WebGL, "Set a timer to release the high-performance GPU.");
245 // FIXME: Expose this value as a Setting, which would require this class
246 // to reference a frame, page or document.
247 static const Seconds timeToKeepHighPerformanceGPUAlive { 10_s };
248 m_disableHighPerformanceGPUTimer.startOneShot(timeToKeepHighPerformanceGPUAlive);
249 }
250 }
251#endif
252}
253
254void GraphicsContext3DManager::disableHighPerformanceGPUTimerFired()
255{
256 if (m_contextsRequiringHighPerformance.size())
257 return;
258
259 m_requestingHighPerformance = false;
260#if PLATFORM(MAC)
261 SwitchingGPUClient::singleton().releaseHighPerformanceGPU();
262#endif
263}
264
265void GraphicsContext3DManager::recycleContextIfNecessary()
266{
267 if (hasTooManyContexts()) {
268 LOG(WebGL, "GraphicsContext3DManager recycled context (%p).", m_contexts[0]);
269 m_contexts[0]->recycleContext();
270 }
271}
272
273} // namespace WebCore
274
275#endif // ENABLE(GRAPHICS_CONTEXT_3D)
276