[GTK] Use XDamage to simplify RedirectedXCompositeWindow
[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 redirectedWindowDamagedCallback(void* data)
67 {
68     gtk_widget_queue_draw(GTK_WIDGET(data));
69 }
70
71 void AcceleratedCompositingContext::initialize()
72 {
73     if (m_rootLayer)
74         return;
75
76     IntSize pageSize = getWebViewSize(m_webView);
77     if (!m_redirectedWindow) {
78         if (m_redirectedWindow = RedirectedXCompositeWindow::create(pageSize))
79             m_redirectedWindow->setDamageNotifyCallback(redirectedWindowDamagedCallback, m_webView);
80     } else
81         m_redirectedWindow->resize(pageSize);
82
83     if (!m_redirectedWindow)
84         return;
85
86     m_rootLayer = GraphicsLayer::create(this);
87     m_rootLayer->setDrawsContent(false);
88     m_rootLayer->setSize(pageSize);
89
90     // The non-composited contents are a child of the root layer.
91     m_nonCompositedContentLayer = GraphicsLayer::create(this);
92     m_nonCompositedContentLayer->setDrawsContent(true);
93     m_nonCompositedContentLayer->setContentsOpaque(!m_webView->priv->transparent);
94     m_nonCompositedContentLayer->setSize(pageSize);
95     if (core(m_webView)->settings()->acceleratedDrawingEnabled())
96         m_nonCompositedContentLayer->setAcceleratesDrawing(true);
97
98 #ifndef NDEBUG
99     m_rootLayer->setName("Root layer");
100     m_nonCompositedContentLayer->setName("Non-composited content");
101 #endif
102
103     m_rootLayer->addChild(m_nonCompositedContentLayer.get());
104     m_nonCompositedContentLayer->setNeedsDisplay();
105
106     // The creation of the TextureMapper needs an active OpenGL context.
107     GLContext* context = m_redirectedWindow->context();
108     context->makeContextCurrent();
109
110     m_textureMapper = TextureMapperGL::create();
111     static_cast<TextureMapperGL*>(m_textureMapper.get())->setEnableEdgeDistanceAntialiasing(true);
112     toTextureMapperLayer(m_rootLayer.get())->setTextureMapper(m_textureMapper.get());
113
114     scheduleLayerFlush();
115 }
116
117 AcceleratedCompositingContext::~AcceleratedCompositingContext()
118 {
119     stopAnyPendingLayerFlush();
120 }
121
122 void AcceleratedCompositingContext::stopAnyPendingLayerFlush()
123 {
124     if (!m_layerFlushTimerCallbackId)
125         return;
126     g_source_remove(m_layerFlushTimerCallbackId);
127     m_layerFlushTimerCallbackId = 0;
128 }
129
130 bool AcceleratedCompositingContext::enabled()
131 {
132     return m_redirectedWindow && m_rootLayer && m_textureMapper;
133 }
134
135 bool AcceleratedCompositingContext::renderLayersToWindow(cairo_t* cr, const IntRect& clipRect)
136 {
137     m_redrawPendingTime = 0;
138
139     if (!enabled())
140         return false;
141
142     cairo_surface_t* windowSurface = m_redirectedWindow->cairoSurfaceForWidget(GTK_WIDGET(m_webView));
143     if (!windowSurface)
144         return true;
145
146     cairo_rectangle(cr, clipRect.x(), clipRect.y(), clipRect.width(), clipRect.height());
147     cairo_set_source_surface(cr, windowSurface, 0, 0);
148     cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
149     cairo_fill(cr);
150
151     if (!m_layerFlushTimerCallbackId && toTextureMapperLayer(m_rootLayer.get())->descendantsOrSelfHaveRunningAnimations() || m_needsExtraFlush) {
152         m_needsExtraFlush = false;
153         double nextFlush = max((1 / gFramesPerSecond) - (currentTime() - m_lastFlushTime), 0.0);
154         m_layerFlushTimerCallbackId = g_timeout_add_full(GDK_PRIORITY_EVENTS, 1000 * nextFlush, reinterpret_cast<GSourceFunc>(layerFlushTimerFiredCallback), this, 0);
155     }
156
157     return true;
158 }
159
160 GLContext* AcceleratedCompositingContext::prepareForRendering()
161 {
162     if (!enabled())
163         return 0;
164
165     GLContext* context = m_redirectedWindow->context();
166     if (!context)
167         return 0;
168
169     if (!context->makeContextCurrent())
170         return 0;
171
172     return context;
173 }
174
175 void AcceleratedCompositingContext::compositeLayersToContext(CompositePurpose purpose)
176 {
177     GLContext* context = prepareForRendering();
178     if (!context)
179         return;
180
181     const IntSize& windowSize = m_redirectedWindow->size();
182     glViewport(0, 0, windowSize.width(), windowSize.height());
183
184     if (purpose == ForResize) {
185         glClearColor(1, 1, 1, 0);
186         glClear(GL_COLOR_BUFFER_BIT);
187     }
188
189     m_textureMapper->beginPainting();
190     toTextureMapperLayer(m_rootLayer.get())->paint();
191     m_textureMapper->endPainting();
192
193     context->swapBuffers();
194 }
195
196 void AcceleratedCompositingContext::clearEverywhere()
197 {
198     GLContext* context = prepareForRendering();
199     if (!context)
200         return;
201
202     const IntSize& windowSize = m_redirectedWindow->size();
203     glViewport(0, 0, windowSize.width(), windowSize.height());
204     glClearColor(1, 1, 1, 1);
205     glClear(GL_COLOR_BUFFER_BIT);
206
207     context->swapBuffers();
208
209     // FIXME: It seems that when using double-buffering (and on some drivers single-buffering)
210     // and XComposite window redirection, two swap buffers are required to force the pixmap
211     // to update. This isn't a problem during animations, because swapBuffer is continuously
212     // called. For non-animation situations we use this terrible hack until we can get to the
213     // bottom of the issue.
214     if (!toTextureMapperLayer(m_rootLayer.get())->descendantsOrSelfHaveRunningAnimations()) {
215         context->swapBuffers();
216         context->swapBuffers();
217     }
218 }
219
220 void AcceleratedCompositingContext::setRootCompositingLayer(GraphicsLayer* graphicsLayer)
221 {
222     // Clearing everywhere when turning on or off the layer tree prevents us from flashing
223     // old content before the first flush.
224     clearEverywhere();
225
226     if (!graphicsLayer) {
227         stopAnyPendingLayerFlush();
228
229         // Shrink the offscreen window to save memory while accelerated compositing is turned off.
230         if (m_redirectedWindow)
231             m_redirectedWindow->resize(IntSize(1, 1));
232         m_rootLayer = nullptr;
233         m_nonCompositedContentLayer = nullptr;
234         m_textureMapper = nullptr;
235         return;
236     }
237
238     // Add the accelerated layer tree hierarchy.
239     initialize();
240     if (!m_redirectedWindow)
241         return;
242
243     m_nonCompositedContentLayer->removeAllChildren();
244     m_nonCompositedContentLayer->addChild(graphicsLayer);
245
246     stopAnyPendingLayerFlush();
247
248     // FIXME: Two flushes seem necessary to get the proper rendering in some cases. It's unclear
249     // if this is a bug with the RedirectedXComposite window or with this class.
250     m_needsExtraFlush = true;
251     scheduleLayerFlush();
252
253     m_layerFlushTimerCallbackId = g_timeout_add_full(GDK_PRIORITY_EVENTS, 500, reinterpret_cast<GSourceFunc>(layerFlushTimerFiredCallback), this, 0);
254 }
255
256 void AcceleratedCompositingContext::setNonCompositedContentsNeedDisplay(const IntRect& rect)
257 {
258     if (!m_rootLayer)
259         return;
260     if (rect.isEmpty()) {
261         m_rootLayer->setNeedsDisplay();
262         return;
263     }
264     m_nonCompositedContentLayer->setNeedsDisplayInRect(rect);
265     scheduleLayerFlush();
266 }
267
268 void AcceleratedCompositingContext::resizeRootLayer(const IntSize& newSize)
269 {
270     if (!enabled())
271         return;
272
273     if (m_rootLayer->size() == newSize)
274         return;
275
276     m_redirectedWindow->resize(newSize);
277     m_rootLayer->setSize(newSize);
278
279     // If the newSize exposes new areas of the non-composited content a setNeedsDisplay is needed
280     // for those newly exposed areas.
281     FloatSize oldSize = m_nonCompositedContentLayer->size();
282     m_nonCompositedContentLayer->setSize(newSize);
283
284     if (newSize.width() > oldSize.width()) {
285         float height = std::min(static_cast<float>(newSize.height()), oldSize.height());
286         m_nonCompositedContentLayer->setNeedsDisplayInRect(FloatRect(oldSize.width(), 0, newSize.width() - oldSize.width(), height));
287     }
288
289     if (newSize.height() > oldSize.height())
290         m_nonCompositedContentLayer->setNeedsDisplayInRect(FloatRect(0, oldSize.height(), newSize.width(), newSize.height() - oldSize.height()));
291
292     m_nonCompositedContentLayer->setNeedsDisplayInRect(IntRect(IntPoint(), newSize));
293     compositeLayersToContext(ForResize);
294     scheduleLayerFlush();
295 }
296
297 void AcceleratedCompositingContext::scrollNonCompositedContents(const IntRect& scrollRect, const IntSize& scrollOffset)
298 {
299     m_nonCompositedContentLayer->setNeedsDisplayInRect(scrollRect);
300     scheduleLayerFlush();
301 }
302
303 gboolean AcceleratedCompositingContext::layerFlushTimerFiredCallback(AcceleratedCompositingContext* context)
304 {
305     context->layerFlushTimerFired();
306     return FALSE;
307 }
308
309 void AcceleratedCompositingContext::scheduleLayerFlush()
310 {
311     if (!enabled())
312         return;
313
314     if (m_layerFlushTimerCallbackId)
315         return;
316
317     // We use a GLib timer because otherwise GTK+ event handling during dragging can
318     // starve WebCore timers, which have a lower priority.
319     double nextFlush = max(gScheduleDelay - (currentTime() - m_lastFlushTime), 0.0);
320     m_layerFlushTimerCallbackId = g_timeout_add_full(GDK_PRIORITY_EVENTS, nextFlush * 1000, reinterpret_cast<GSourceFunc>(layerFlushTimerFiredCallback), this, 0);
321 }
322
323 bool AcceleratedCompositingContext::flushPendingLayerChanges()
324 {
325     m_rootLayer->syncCompositingStateForThisLayerOnly();
326     m_nonCompositedContentLayer->syncCompositingStateForThisLayerOnly();
327     return core(m_webView)->mainFrame()->view()->syncCompositingStateIncludingSubframes();
328 }
329
330 void AcceleratedCompositingContext::flushAndRenderLayers()
331 {
332     if (!enabled())
333         return;
334
335     Frame* frame = core(m_webView)->mainFrame();
336     if (!frame || !frame->contentRenderer() || !frame->view())
337         return;
338     frame->view()->updateLayoutAndStyleIfNeededRecursive();
339
340     GLContext* context = m_redirectedWindow->context();
341     if (context && !context->makeContextCurrent())
342         return;
343
344     if (!flushPendingLayerChanges())
345         return;
346
347     m_lastFlushTime = currentTime();
348     compositeLayersToContext();
349
350     // If it's been a long time since we've actually painted, which means that events might
351     // be starving the main loop, we should force a draw now. This seems to prevent display
352     // lag on http://2012.beercamp.com.
353     if (m_redrawPendingTime && currentTime() - m_redrawPendingTime > gScheduleDelay) {
354         gtk_widget_queue_draw(GTK_WIDGET(m_webView));
355         gdk_window_process_updates(gtk_widget_get_window(GTK_WIDGET(m_webView)), FALSE);
356     } else if (!m_redrawPendingTime)
357         m_redrawPendingTime = currentTime();
358 }
359
360 void AcceleratedCompositingContext::layerFlushTimerFired()
361 {
362     m_layerFlushTimerCallbackId = 0;
363     flushAndRenderLayers();
364 }
365
366 void AcceleratedCompositingContext::notifyAnimationStarted(const GraphicsLayer*, double time)
367 {
368
369 }
370 void AcceleratedCompositingContext::notifySyncRequired(const GraphicsLayer*)
371 {
372
373 }
374
375 void AcceleratedCompositingContext::paintContents(const GraphicsLayer*, GraphicsContext& context, GraphicsLayerPaintingPhase, const IntRect& rectToPaint)
376 {
377     context.save();
378     context.clip(rectToPaint);
379     core(m_webView)->mainFrame()->view()->paint(&context, rectToPaint);
380     context.restore();
381 }
382
383 bool AcceleratedCompositingContext::showDebugBorders(const GraphicsLayer*) const
384 {
385     return false;
386 }
387
388 bool AcceleratedCompositingContext::showRepaintCounter(const GraphicsLayer*) const
389 {
390     return false;
391 }
392
393 } // namespace WebKit
394
395 #endif // USE(ACCELERATED_COMPOSITING) && USE(TEXTURE_MAPPER_GL)