95da644e368a1d74c031d1728a53687c8f234283
[WebKit-https.git] / Source / WebKit / gtk / WebCoreSupport / AcceleratedCompositingContextGL.cpp
1 /*
2  * Copyright (C) 2012 Igalia, S.L.
3  *
4  *  This library is free software; you can redistribute it and/or
5  *  modify it under the terms of the GNU Lesser General Public
6  *  License as published by the Free Software Foundation; either
7  *  version 2 of the License, or (at your option) any later version.
8  *
9  *  This library is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  *  Lesser General Public License for more details.
13  *
14  *  You should have received a copy of the GNU Lesser General Public
15  *  License along with this library; if not, write to the Free Software
16  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
17  */
18
19 #include "config.h"
20 #include "AcceleratedCompositingContext.h"
21
22 #if USE(ACCELERATED_COMPOSITING) && USE(TEXTURE_MAPPER_GL)
23
24 #include "CairoUtilities.h"
25 #include "Chrome.h"
26 #include "ChromeClientGtk.h"
27 #include "Frame.h"
28 #include "FrameView.h"
29 #include "PlatformContextCairo.h"
30 #include "Settings.h"
31 #include "TextureMapperGL.h"
32 #include "TextureMapperLayer.h"
33 #include "webkitwebviewprivate.h"
34 #include <GL/gl.h>
35 #include <cairo.h>
36 #include <gdk/gdk.h>
37 #include <gtk/gtk.h>
38
39 const double gFramesPerSecond = 60;
40
41 // There seems to be a delicate balance between the main loop being flooded
42 // with motion events (that force flushes) and starving the main loop of events
43 // with flush callbacks. This delay is entirely empirical.
44 const double gScheduleDelay = (1.0 / (gFramesPerSecond / 3.0));
45
46 using namespace WebCore;
47
48 namespace WebKit {
49
50 AcceleratedCompositingContext::AcceleratedCompositingContext(WebKitWebView* webView)
51     : m_webView(webView)
52     , m_layerFlushTimerCallbackId(0)
53     , m_lastFlushTime(0)
54     , m_redrawPendingTime(0)
55     , m_needsExtraFlush(false)
56 {
57 }
58
59 static IntSize getWebViewSize(WebKitWebView* webView)
60 {
61     GtkAllocation allocation;
62     gtk_widget_get_allocation(GTK_WIDGET(webView), &allocation);
63     return IntSize(allocation.width, allocation.height);
64 }
65
66 void AcceleratedCompositingContext::initialize()
67 {
68     if (m_rootLayer)
69         return;
70
71     IntSize pageSize = getWebViewSize(m_webView);
72     if (!m_redirectedWindow)
73         m_redirectedWindow = RedirectedXCompositeWindow::create(pageSize);
74     else
75         m_redirectedWindow->resize(pageSize);
76
77     m_rootLayer = GraphicsLayer::create(this);
78     m_rootLayer->setDrawsContent(false);
79     m_rootLayer->setSize(pageSize);
80
81     // The non-composited contents are a child of the root layer.
82     m_nonCompositedContentLayer = GraphicsLayer::create(this);
83     m_nonCompositedContentLayer->setDrawsContent(true);
84     m_nonCompositedContentLayer->setContentsOpaque(!m_webView->priv->transparent);
85     m_nonCompositedContentLayer->setSize(pageSize);
86     if (core(m_webView)->settings()->acceleratedDrawingEnabled())
87         m_nonCompositedContentLayer->setAcceleratesDrawing(true);
88
89 #ifndef NDEBUG
90     m_rootLayer->setName("Root layer");
91     m_nonCompositedContentLayer->setName("Non-composited content");
92 #endif
93
94     m_rootLayer->addChild(m_nonCompositedContentLayer.get());
95     m_nonCompositedContentLayer->setNeedsDisplay();
96
97     // The creation of the TextureMapper needs an active OpenGL context.
98     GLContext* context = m_redirectedWindow->context();
99     context->makeContextCurrent();
100
101     m_textureMapper = TextureMapperGL::create();
102     static_cast<TextureMapperGL*>(m_textureMapper.get())->setEnableEdgeDistanceAntialiasing(true);
103     toTextureMapperLayer(m_rootLayer.get())->setTextureMapper(m_textureMapper.get());
104
105     scheduleLayerFlush();
106 }
107
108 AcceleratedCompositingContext::~AcceleratedCompositingContext()
109 {
110     stopAnyPendingLayerFlush();
111 }
112
113 void AcceleratedCompositingContext::stopAnyPendingLayerFlush()
114 {
115     if (!m_layerFlushTimerCallbackId)
116         return;
117     g_source_remove(m_layerFlushTimerCallbackId);
118     m_layerFlushTimerCallbackId = 0;
119 }
120
121 bool AcceleratedCompositingContext::enabled()
122 {
123     return m_rootLayer && m_textureMapper;
124 }
125
126 bool AcceleratedCompositingContext::renderLayersToWindow(cairo_t* cr, const IntRect& clipRect)
127 {
128     m_redrawPendingTime = 0;
129
130     if (!enabled())
131         return false;
132
133     // It's important to paint a white background (if the WebView isn't transparent), because when growing
134     // the redirected window, the usable size of the window may be smaller than the allocation of our widget.
135     // We don't want to show artifacts in that case.
136     IntSize usableWindowSize = m_redirectedWindow->usableSize();
137     if (usableWindowSize != m_redirectedWindow->size()) {
138         if (!m_webView->priv->transparent) {
139             cairo_set_source_rgb(cr, 1, 1, 1);
140             cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
141         } else
142             cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
143         cairo_paint(cr);
144     }
145
146     cairo_surface_t* windowSurface = m_redirectedWindow->cairoSurfaceForWidget(GTK_WIDGET(m_webView));
147     if (!windowSurface)
148         return true;
149
150     cairo_rectangle(cr, clipRect.x(), clipRect.y(), clipRect.width(), clipRect.height());
151     cairo_set_source_surface(cr, windowSurface, 0, 0);
152     cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
153     cairo_fill(cr);
154
155     if (!m_layerFlushTimerCallbackId && toTextureMapperLayer(m_rootLayer.get())->descendantsOrSelfHaveRunningAnimations() || m_needsExtraFlush) {
156         m_needsExtraFlush = false;
157         double nextFlush = max((1 / gFramesPerSecond) - (currentTime() - m_lastFlushTime), 0.0);
158         m_layerFlushTimerCallbackId = g_timeout_add_full(GDK_PRIORITY_EVENTS, 1000 * nextFlush, reinterpret_cast<GSourceFunc>(layerFlushTimerFiredCallback), this, 0);
159     }
160
161     return true;
162 }
163
164 GLContext* AcceleratedCompositingContext::prepareForRendering()
165 {
166     if (!enabled())
167         return 0;
168
169     GLContext* context = m_redirectedWindow->context();
170     if (!context)
171         return 0;
172
173     if (!context->makeContextCurrent())
174         return 0;
175
176     return context;
177 }
178
179 void AcceleratedCompositingContext::compositeLayersToContext()
180 {
181     GLContext* context = prepareForRendering();
182     if (!context)
183         return;
184
185     const IntSize& windowSize = m_redirectedWindow->size();
186     glViewport(0, 0, windowSize.width(), windowSize.height());
187
188     m_textureMapper->beginPainting();
189     toTextureMapperLayer(m_rootLayer.get())->paint();
190     m_textureMapper->endPainting();
191
192     context->swapBuffers();
193
194     // FIXME: It seems that when using double-buffering (and on some drivers single-buffering)
195     // and XComposite window redirection, two swap buffers are required to force the pixmap
196     // to update. This isn't a problem during animations, because swapBuffer is continuously
197     // called. For non-animation situations we use this terrible hack until we can get to the
198     // bottom of the issue.
199     if (!toTextureMapperLayer(m_rootLayer.get())->descendantsOrSelfHaveRunningAnimations()) {
200         context->swapBuffers();
201         context->swapBuffers();
202     }
203 }
204
205 void AcceleratedCompositingContext::clearEverywhere()
206 {
207     GLContext* context = prepareForRendering();
208     if (!context)
209         return;
210
211     const IntSize& windowSize = m_redirectedWindow->size();
212     glViewport(0, 0, windowSize.width(), windowSize.height());
213     glClearColor(1, 1, 1, 1);
214     glClear(GL_COLOR_BUFFER_BIT);
215
216     context->swapBuffers();
217
218     // FIXME: It seems that when using double-buffering (and on some drivers single-buffering)
219     // and XComposite window redirection, two swap buffers are required to force the pixmap
220     // to update. This isn't a problem during animations, because swapBuffer is continuously
221     // called. For non-animation situations we use this terrible hack until we can get to the
222     // bottom of the issue.
223     if (!toTextureMapperLayer(m_rootLayer.get())->descendantsOrSelfHaveRunningAnimations()) {
224         context->swapBuffers();
225         context->swapBuffers();
226     }
227 }
228
229 void AcceleratedCompositingContext::setRootCompositingLayer(GraphicsLayer* graphicsLayer)
230 {
231     // Clearing everywhere when turning on or off the layer tree prevents us from flashing
232     // old content before the first flush.
233     clearEverywhere();
234
235     if (!graphicsLayer) {
236         stopAnyPendingLayerFlush();
237
238         // Shrink the offscreen window to save memory while accelerated compositing is turned off.
239         m_redirectedWindow->resize(IntSize(1, 1));
240         m_rootLayer = nullptr;
241         m_nonCompositedContentLayer = nullptr;
242         m_textureMapper = nullptr;
243         return;
244     }
245
246     // Add the accelerated layer tree hierarchy.
247     initialize();
248     m_nonCompositedContentLayer->removeAllChildren();
249     m_nonCompositedContentLayer->addChild(graphicsLayer);
250
251     stopAnyPendingLayerFlush();
252
253     // FIXME: Two flushes seem necessary to get the proper rendering in some cases. It's unclear
254     // if this is a bug with the RedirectedXComposite window or with this class.
255     m_needsExtraFlush = true;
256     scheduleLayerFlush();
257
258     m_layerFlushTimerCallbackId = g_timeout_add_full(GDK_PRIORITY_EVENTS, 500, reinterpret_cast<GSourceFunc>(layerFlushTimerFiredCallback), this, 0);
259 }
260
261 void AcceleratedCompositingContext::setNonCompositedContentsNeedDisplay(const IntRect& rect)
262 {
263     if (!m_rootLayer)
264         return;
265     if (rect.isEmpty()) {
266         m_rootLayer->setNeedsDisplay();
267         return;
268     }
269     m_nonCompositedContentLayer->setNeedsDisplayInRect(rect);
270     scheduleLayerFlush();
271 }
272
273 void AcceleratedCompositingContext::resizeRootLayer(const IntSize& newSize)
274 {
275     if (!enabled())
276         return;
277
278     if (m_rootLayer->size() == newSize)
279         return;
280
281     m_redirectedWindow->resize(newSize);
282     m_rootLayer->setSize(newSize);
283
284     // If the newSize exposes new areas of the non-composited content a setNeedsDisplay is needed
285     // for those newly exposed areas.
286     FloatSize oldSize = m_nonCompositedContentLayer->size();
287     m_nonCompositedContentLayer->setSize(newSize);
288
289     if (newSize.width() > oldSize.width()) {
290         float height = std::min(static_cast<float>(newSize.height()), oldSize.height());
291         m_nonCompositedContentLayer->setNeedsDisplayInRect(FloatRect(oldSize.width(), 0, newSize.width() - oldSize.width(), height));
292     }
293
294     if (newSize.height() > oldSize.height())
295         m_nonCompositedContentLayer->setNeedsDisplayInRect(FloatRect(0, oldSize.height(), newSize.width(), newSize.height() - oldSize.height()));
296
297     m_nonCompositedContentLayer->setNeedsDisplayInRect(IntRect(IntPoint(), newSize));
298     flushAndRenderLayers();
299 }
300
301 void AcceleratedCompositingContext::scrollNonCompositedContents(const IntRect& scrollRect, const IntSize& scrollOffset)
302 {
303     m_nonCompositedContentLayer->setNeedsDisplayInRect(scrollRect);
304     scheduleLayerFlush();
305 }
306
307 gboolean AcceleratedCompositingContext::layerFlushTimerFiredCallback(AcceleratedCompositingContext* context)
308 {
309     context->layerFlushTimerFired();
310     return FALSE;
311 }
312
313 void AcceleratedCompositingContext::scheduleLayerFlush()
314 {
315     if (!enabled())
316         return;
317
318     if (m_layerFlushTimerCallbackId)
319         return;
320
321     // We use a GLib timer because otherwise GTK+ event handling during dragging can
322     // starve WebCore timers, which have a lower priority.
323     double nextFlush = max(gScheduleDelay - (currentTime() - m_lastFlushTime), 0.0);
324     m_layerFlushTimerCallbackId = g_timeout_add_full(GDK_PRIORITY_EVENTS, nextFlush * 1000, reinterpret_cast<GSourceFunc>(layerFlushTimerFiredCallback), this, 0);
325 }
326
327 bool AcceleratedCompositingContext::flushPendingLayerChanges()
328 {
329     m_rootLayer->syncCompositingStateForThisLayerOnly();
330     m_nonCompositedContentLayer->syncCompositingStateForThisLayerOnly();
331     return core(m_webView)->mainFrame()->view()->syncCompositingStateIncludingSubframes();
332 }
333
334 void AcceleratedCompositingContext::flushAndRenderLayers()
335 {
336     if (!enabled())
337         return;
338
339     Frame* frame = core(m_webView)->mainFrame();
340     if (!frame || !frame->contentRenderer() || !frame->view())
341         return;
342     frame->view()->updateLayoutAndStyleIfNeededRecursive();
343
344     GLContext* context = m_redirectedWindow->context();
345     if (context && !context->makeContextCurrent())
346         return;
347
348     if (!flushPendingLayerChanges())
349         return;
350
351     m_lastFlushTime = currentTime();
352     compositeLayersToContext();
353
354     gtk_widget_queue_draw(GTK_WIDGET(m_webView));
355
356     // If it's been a long time since we've actually painted, which means that events might
357     // be starving the main loop, we should force a draw now. This seems to prevent display
358     // lag on http://2012.beercamp.com.
359     if (m_redrawPendingTime && currentTime() - m_redrawPendingTime > gScheduleDelay)
360         gdk_window_process_updates(gtk_widget_get_window(GTK_WIDGET(m_webView)), FALSE);
361     else if (!m_redrawPendingTime)
362         m_redrawPendingTime = currentTime();
363 }
364
365 void AcceleratedCompositingContext::layerFlushTimerFired()
366 {
367     m_layerFlushTimerCallbackId = 0;
368     flushAndRenderLayers();
369 }
370
371 void AcceleratedCompositingContext::notifyAnimationStarted(const GraphicsLayer*, double time)
372 {
373
374 }
375 void AcceleratedCompositingContext::notifySyncRequired(const GraphicsLayer*)
376 {
377
378 }
379
380 void AcceleratedCompositingContext::paintContents(const GraphicsLayer*, GraphicsContext& context, GraphicsLayerPaintingPhase, const IntRect& rectToPaint)
381 {
382     context.save();
383     context.clip(rectToPaint);
384     core(m_webView)->mainFrame()->view()->paint(&context, rectToPaint);
385     context.restore();
386 }
387
388 bool AcceleratedCompositingContext::showDebugBorders(const GraphicsLayer*) const
389 {
390     return false;
391 }
392
393 bool AcceleratedCompositingContext::showRepaintCounter(const GraphicsLayer*) const
394 {
395     return false;
396 }
397
398 } // namespace WebKit
399
400 #endif // USE(ACCELERATED_COMPOSITING) && USE(TEXTURE_MAPPER_GL)