[CoordinatedGraphics] The compositing loop is still running even after exiting AC...
[WebKit-https.git] / Source / WebKit / Shared / CoordinatedGraphics / threadedcompositor / ThreadedCompositor.cpp
1 /*
2  * Copyright (C) 2014 Igalia S.L.
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 #include "ThreadedCompositor.h"
28
29 #if USE(COORDINATED_GRAPHICS)
30
31 #include "CompositingRunLoop.h"
32 #include "ThreadedDisplayRefreshMonitor.h"
33 #include <WebCore/PlatformDisplay.h>
34 #include <WebCore/TransformationMatrix.h>
35 #include <wtf/SetForScope.h>
36
37 #if USE(LIBEPOXY)
38 #include <epoxy/gl.h>
39 #elif USE(OPENGL_ES)
40 #include <GLES2/gl2.h>
41 #else
42 #include <GL/gl.h>
43 #endif
44
45 namespace WebKit {
46 using namespace WebCore;
47
48 Ref<ThreadedCompositor> ThreadedCompositor::create(Client& client, ThreadedDisplayRefreshMonitor::Client& displayRefreshMonitorClient, PlatformDisplayID displayID, const IntSize& viewportSize, float scaleFactor, ShouldDoFrameSync doFrameSync, TextureMapper::PaintFlags paintFlags)
49 {
50     return adoptRef(*new ThreadedCompositor(client, displayRefreshMonitorClient, displayID, viewportSize, scaleFactor, doFrameSync, paintFlags));
51 }
52
53 ThreadedCompositor::ThreadedCompositor(Client& client, ThreadedDisplayRefreshMonitor::Client& displayRefreshMonitorClient, PlatformDisplayID displayID, const IntSize& viewportSize, float scaleFactor, ShouldDoFrameSync doFrameSync, TextureMapper::PaintFlags paintFlags)
54     : m_client(client)
55     , m_doFrameSync(doFrameSync)
56     , m_paintFlags(paintFlags)
57     , m_compositingRunLoop(std::make_unique<CompositingRunLoop>([this] { renderLayerTree(); }))
58 #if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
59     , m_displayRefreshMonitor(ThreadedDisplayRefreshMonitor::create(displayID, displayRefreshMonitorClient))
60 #endif
61 {
62     {
63         // Locking isn't really necessary here, but it's done for consistency.
64         LockHolder locker(m_attributes.lock);
65         m_attributes.viewportSize = viewportSize;
66         m_attributes.scaleFactor = scaleFactor;
67         m_attributes.needsResize = !viewportSize.isEmpty();
68     }
69
70     m_compositingRunLoop->performTaskSync([this, protectedThis = makeRef(*this)] {
71         m_scene = adoptRef(new CoordinatedGraphicsScene(this));
72         m_nativeSurfaceHandle = m_client.nativeSurfaceHandleForCompositing();
73
74         m_scene->setActive(!!m_nativeSurfaceHandle);
75         if (m_nativeSurfaceHandle)
76             createGLContext();
77     });
78 }
79
80 ThreadedCompositor::~ThreadedCompositor()
81 {
82 }
83
84 void ThreadedCompositor::createGLContext()
85 {
86     ASSERT(!RunLoop::isMain());
87
88     ASSERT(m_nativeSurfaceHandle);
89
90     m_context = GLContext::createContextForWindow(reinterpret_cast<GLNativeWindowType>(m_nativeSurfaceHandle), &PlatformDisplay::sharedDisplayForCompositing());
91     if (!m_context)
92         return;
93
94     if (!m_context->makeContextCurrent())
95         return;
96
97     if (m_doFrameSync == ShouldDoFrameSync::No)
98         m_context->swapInterval(0);
99 }
100
101 void ThreadedCompositor::invalidate()
102 {
103     m_scene->detach();
104     m_compositingRunLoop->stopUpdates();
105 #if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
106     m_displayRefreshMonitor->invalidate();
107 #endif
108     m_compositingRunLoop->performTaskSync([this, protectedThis = makeRef(*this)] {
109         if (!m_context || !m_context->makeContextCurrent())
110             return;
111         m_scene->purgeGLResources();
112         m_context = nullptr;
113         m_client.didDestroyGLContext();
114         m_scene = nullptr;
115     });
116     m_compositingRunLoop = nullptr;
117 }
118
119 void ThreadedCompositor::suspend()
120 {
121     if (++m_suspendedCount > 1)
122         return;
123
124     m_compositingRunLoop->stopUpdates();
125     m_compositingRunLoop->performTaskSync([this, protectedThis = makeRef(*this)] {
126         m_scene->setActive(false);
127     });
128 }
129
130 void ThreadedCompositor::resume()
131 {
132     ASSERT(m_suspendedCount > 0);
133     if (--m_suspendedCount > 0)
134         return;
135
136     m_compositingRunLoop->performTaskSync([this, protectedThis = makeRef(*this)] {
137         m_scene->setActive(true);
138     });
139 }
140
141 void ThreadedCompositor::setNativeSurfaceHandleForCompositing(uint64_t handle)
142 {
143     m_compositingRunLoop->stopUpdates();
144     m_compositingRunLoop->performTaskSync([this, protectedThis = makeRef(*this), handle] {
145         // A new native handle can't be set without destroying the previous one first if any.
146         ASSERT(!!handle ^ !!m_nativeSurfaceHandle);
147         m_nativeSurfaceHandle = handle;
148
149         m_scene->setActive(!!m_nativeSurfaceHandle);
150         if (m_nativeSurfaceHandle)
151             createGLContext();
152         else
153             m_context = nullptr;
154     });
155 }
156
157 void ThreadedCompositor::setScaleFactor(float scale)
158 {
159     LockHolder locker(m_attributes.lock);
160     m_attributes.scaleFactor = scale;
161     m_compositingRunLoop->scheduleUpdate();
162 }
163
164 void ThreadedCompositor::setScrollPosition(const IntPoint& scrollPosition, float scale)
165 {
166     LockHolder locker(m_attributes.lock);
167     m_attributes.scrollPosition = scrollPosition;
168     m_attributes.scaleFactor = scale;
169     m_compositingRunLoop->scheduleUpdate();
170 }
171
172 void ThreadedCompositor::setViewportSize(const IntSize& viewportSize, float scale)
173 {
174     LockHolder locker(m_attributes.lock);
175     m_attributes.viewportSize = viewportSize;
176     m_attributes.scaleFactor = scale;
177     m_attributes.needsResize = true;
178     m_compositingRunLoop->scheduleUpdate();
179 }
180
181 void ThreadedCompositor::updateViewport()
182 {
183     m_compositingRunLoop->scheduleUpdate();
184 }
185
186 void ThreadedCompositor::forceRepaint()
187 {
188     // FIXME: Enable this for WPE once it's possible to do these forced updates
189     // in a way that doesn't starve out the underlying graphics buffers.
190 #if PLATFORM(GTK)
191     m_compositingRunLoop->performTaskSync([this, protectedThis = makeRef(*this)] {
192         SetForScope<bool> change(m_inForceRepaint, true);
193         renderLayerTree();
194     });
195 #endif
196 }
197
198 void ThreadedCompositor::renderLayerTree()
199 {
200     if (!m_scene || !m_scene->isActive())
201         return;
202
203     if (!m_context || !m_context->makeContextCurrent())
204         return;
205
206     m_client.willRenderFrame();
207
208     // Retrieve the scene attributes in a thread-safe manner.
209     WebCore::IntSize viewportSize;
210     WebCore::IntPoint scrollPosition;
211     float scaleFactor;
212     bool needsResize;
213
214     Vector<WebCore::CoordinatedGraphicsState> states;
215
216     {
217         LockHolder locker(m_attributes.lock);
218         viewportSize = m_attributes.viewportSize;
219         scrollPosition = m_attributes.scrollPosition;
220         scaleFactor = m_attributes.scaleFactor;
221         needsResize = m_attributes.needsResize;
222
223         states = WTFMove(m_attributes.states);
224
225         if (!states.isEmpty()) {
226             // Client has to be notified upon finishing this scene update.
227             m_attributes.clientRendersNextFrame = true;
228
229             // Coordinate scene update completion with the client in case of changed or updated platform layers.
230             // But do not change coordinateUpdateCompletionWithClient while in force repaint because that
231             // demands immediate scene update completion regardless of platform layers.
232             // FIXME: Check whether we still need all this coordinateUpdateCompletionWithClient logic.
233             // https://bugs.webkit.org/show_bug.cgi?id=188839
234             // Relatedly, we should only ever operate with a single Nicosia::Scene object, not with a Vector
235             // of CoordinatedGraphicsState instances (which at this time will all contain RefPtr to the same
236             // Nicosia::State object anyway).
237             if (!m_inForceRepaint) {
238                 bool coordinateUpdate = false;
239                 for (auto& state : states) {
240                     state.nicosia.scene->accessState(
241                         [&coordinateUpdate](Nicosia::Scene::State& state)
242                         {
243                             coordinateUpdate |= state.platformLayerUpdated;
244                             state.platformLayerUpdated = false;
245                         });
246                 }
247                 m_attributes.coordinateUpdateCompletionWithClient = coordinateUpdate;
248             }
249         }
250
251         // Reset the needsResize attribute to false.
252         m_attributes.needsResize = false;
253     }
254
255     if (needsResize) {
256         m_client.resize(viewportSize);
257         glViewport(0, 0, viewportSize.width(), viewportSize.height());
258     }
259
260     TransformationMatrix viewportTransform;
261     viewportTransform.scale(scaleFactor);
262     viewportTransform.translate(-scrollPosition.x(), -scrollPosition.y());
263
264     glClearColor(0, 0, 0, 0);
265     glClear(GL_COLOR_BUFFER_BIT);
266
267     m_scene->applyStateChanges(states);
268     m_scene->paintToCurrentGLContext(viewportTransform, FloatRect { FloatPoint { }, viewportSize }, m_paintFlags);
269
270     m_context->swapBuffers();
271
272     if (m_scene->isActive())
273         m_client.didRenderFrame();
274 }
275
276 void ThreadedCompositor::sceneUpdateFinished()
277 {
278     // The composition has finished. Now we have to determine how to manage
279     // the scene update completion.
280
281     // The DisplayRefreshMonitor will be used to dispatch a callback on the client thread if:
282     //  - clientRendersNextFrame is true (i.e. client has to be notified about the finished update), or
283     //  - a DisplayRefreshMonitor callback was requested from the Web engine
284     bool shouldDispatchDisplayRefreshCallback { false };
285
286     // If coordinateUpdateCompletionWithClient is true, the scene update completion has to be
287     // delayed until the DisplayRefreshMonitor callback.
288     bool shouldCoordinateUpdateCompletionWithClient { false };
289
290     {
291         LockHolder locker(m_attributes.lock);
292         shouldDispatchDisplayRefreshCallback = m_attributes.clientRendersNextFrame
293 #if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
294             || m_displayRefreshMonitor->requiresDisplayRefreshCallback();
295 #else
296             ;
297 #endif
298         shouldCoordinateUpdateCompletionWithClient = m_attributes.coordinateUpdateCompletionWithClient;
299     }
300
301     LockHolder stateLocker(m_compositingRunLoop->stateLock());
302
303 #if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
304     // Schedule the DisplayRefreshMonitor callback, if necessary.
305     if (shouldDispatchDisplayRefreshCallback)
306         m_displayRefreshMonitor->dispatchDisplayRefreshCallback();
307 #endif
308
309     // Mark the scene update as completed if no coordination is required and if not in a forced repaint.
310     if (!shouldCoordinateUpdateCompletionWithClient && !m_inForceRepaint)
311         m_compositingRunLoop->updateCompleted(stateLocker);
312
313     // Independent of the scene update, the composition itself is now completed.
314     m_compositingRunLoop->compositionCompleted(stateLocker);
315 }
316
317 void ThreadedCompositor::updateSceneState(const CoordinatedGraphicsState& state)
318 {
319     LockHolder locker(m_attributes.lock);
320     m_attributes.states.append(state);
321     m_compositingRunLoop->scheduleUpdate();
322 }
323
324 #if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
325 RefPtr<WebCore::DisplayRefreshMonitor> ThreadedCompositor::displayRefreshMonitor(PlatformDisplayID)
326 {
327     return m_displayRefreshMonitor.copyRef();
328 }
329
330 void ThreadedCompositor::handleDisplayRefreshMonitorUpdate()
331 {
332     // Retrieve coordinateUpdateCompletionWithClient.
333     bool coordinateUpdateCompletionWithClient { false };
334     {
335         LockHolder locker(m_attributes.lock);
336         coordinateUpdateCompletionWithClient = std::exchange(m_attributes.coordinateUpdateCompletionWithClient, false);
337     }
338
339     LockHolder stateLocker(m_compositingRunLoop->stateLock());
340
341     // If required, mark the current scene update as completed. CompositingRunLoop will take care of
342     // scheduling a new update in case an update was marked as pending due to previous layer flushes
343     // or DisplayRefreshMonitor notifications.
344     if (coordinateUpdateCompletionWithClient)
345         m_compositingRunLoop->updateCompleted(stateLocker);
346 }
347 #endif
348
349 void ThreadedCompositor::frameComplete()
350 {
351     ASSERT(!RunLoop::isMain());
352     sceneUpdateFinished();
353 }
354
355 }
356 #endif // USE(COORDINATED_GRAPHICS)