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 "GraphicsLayerTextureMapper.h"
30#include "PlatformContextCairo.h"
31#include "Settings.h"
32#include "TextureMapperGL.h"
33#include "TextureMapperLayer.h"
34#include "webkitwebviewprivate.h"
35#include <wtf/CurrentTime.h>
36
37#if USE(OPENGL_ES_2)
38#include <GLES2/gl2.h>
39#else
40#include <GL/gl.h>
41#endif
42
43#include <cairo.h>
44#include <gdk/gdk.h>
45#include <gtk/gtk.h>
46
47const double gFramesPerSecond = 60;
48
49// There seems to be a delicate balance between the main loop being flooded
50// with motion events (that force flushes) and starving the main loop of events
51// with flush callbacks. This delay is entirely empirical.
52const double gScheduleDelay = (1.0 / (gFramesPerSecond / 3.0));
53
54using namespace WebCore;
55
56namespace WebKit {
57
58AcceleratedCompositingContext::AcceleratedCompositingContext(WebKitWebView* webView)
59    : m_webView(webView)
60    , m_layerFlushTimerCallbackId(0)
61    , m_lastFlushTime(0)
62    , m_redrawPendingTime(0)
63    , m_needsExtraFlush(false)
64{
65}
66
67static IntSize getWebViewSize(WebKitWebView* webView)
68{
69    GtkAllocation allocation;
70    gtk_widget_get_allocation(GTK_WIDGET(webView), &allocation);
71    return IntSize(allocation.width, allocation.height);
72}
73
74void redirectedWindowDamagedCallback(void* data)
75{
76    gtk_widget_queue_draw(GTK_WIDGET(data));
77}
78
79void AcceleratedCompositingContext::initialize()
80{
81    if (m_rootLayer)
82        return;
83
84    IntSize pageSize = getWebViewSize(m_webView);
85    if (!m_redirectedWindow) {
86        if (m_redirectedWindow = RedirectedXCompositeWindow::create(pageSize))
87            m_redirectedWindow->setDamageNotifyCallback(redirectedWindowDamagedCallback, m_webView);
88    } else
89        m_redirectedWindow->resize(pageSize);
90
91    if (!m_redirectedWindow)
92        return;
93
94    m_rootLayer = GraphicsLayer::create(0, this);
95    m_rootLayer->setDrawsContent(false);
96    m_rootLayer->setSize(pageSize);
97
98    // The non-composited contents are a child of the root layer.
99    m_nonCompositedContentLayer = GraphicsLayer::create(0, this);
100    m_nonCompositedContentLayer->setDrawsContent(true);
101    m_nonCompositedContentLayer->setContentsOpaque(!m_webView->priv->transparent);
102    m_nonCompositedContentLayer->setSize(pageSize);
103    if (core(m_webView)->settings()->acceleratedDrawingEnabled())
104        m_nonCompositedContentLayer->setAcceleratesDrawing(true);
105
106#ifndef NDEBUG
107    m_rootLayer->setName("Root layer");
108    m_nonCompositedContentLayer->setName("Non-composited content");
109#endif
110
111    m_rootLayer->addChild(m_nonCompositedContentLayer.get());
112    m_nonCompositedContentLayer->setNeedsDisplay();
113
114    // The creation of the TextureMapper needs an active OpenGL context.
115    GLContext* context = m_redirectedWindow->context();
116    context->makeContextCurrent();
117
118    m_textureMapper = TextureMapperGL::create();
119    static_cast<TextureMapperGL*>(m_textureMapper.get())->setEnableEdgeDistanceAntialiasing(true);
120    toTextureMapperLayer(m_rootLayer.get())->setTextureMapper(m_textureMapper.get());
121
122    scheduleLayerFlush();
123}
124
125AcceleratedCompositingContext::~AcceleratedCompositingContext()
126{
127    stopAnyPendingLayerFlush();
128}
129
130void AcceleratedCompositingContext::stopAnyPendingLayerFlush()
131{
132    if (!m_layerFlushTimerCallbackId)
133        return;
134    g_source_remove(m_layerFlushTimerCallbackId);
135    m_layerFlushTimerCallbackId = 0;
136}
137
138bool AcceleratedCompositingContext::enabled()
139{
140    return m_redirectedWindow && m_rootLayer && m_textureMapper;
141}
142
143bool AcceleratedCompositingContext::renderLayersToWindow(cairo_t* cr, const IntRect& clipRect)
144{
145    m_redrawPendingTime = 0;
146
147    if (!enabled())
148        return false;
149
150    cairo_surface_t* windowSurface = m_redirectedWindow->cairoSurfaceForWidget(GTK_WIDGET(m_webView));
151    if (!windowSurface)
152        return true;
153
154    cairo_rectangle(cr, clipRect.x(), clipRect.y(), clipRect.width(), clipRect.height());
155    cairo_set_source_surface(cr, windowSurface, 0, 0);
156    cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
157    cairo_fill(cr);
158
159    if (!m_layerFlushTimerCallbackId && (toTextureMapperLayer(m_rootLayer.get())->descendantsOrSelfHaveRunningAnimations() || m_needsExtraFlush)) {
160        m_needsExtraFlush = false;
161        double nextFlush = max((1 / gFramesPerSecond) - (currentTime() - m_lastFlushTime), 0.0);
162        m_layerFlushTimerCallbackId = g_timeout_add_full(GDK_PRIORITY_EVENTS, 1000 * nextFlush, reinterpret_cast<GSourceFunc>(layerFlushTimerFiredCallback), this, 0);
163    }
164
165    return true;
166}
167
168GLContext* AcceleratedCompositingContext::prepareForRendering()
169{
170    if (!enabled())
171        return 0;
172
173    GLContext* context = m_redirectedWindow->context();
174    if (!context)
175        return 0;
176
177    if (!context->makeContextCurrent())
178        return 0;
179
180    return context;
181}
182
183void AcceleratedCompositingContext::compositeLayersToContext(CompositePurpose purpose)
184{
185    GLContext* context = prepareForRendering();
186    if (!context)
187        return;
188
189    const IntSize& windowSize = m_redirectedWindow->size();
190    glViewport(0, 0, windowSize.width(), windowSize.height());
191
192    if (purpose == ForResize) {
193        glClearColor(1, 1, 1, 0);
194        glClear(GL_COLOR_BUFFER_BIT);
195    }
196
197    m_textureMapper->beginPainting();
198    toTextureMapperLayer(m_rootLayer.get())->paint();
199    m_fpsCounter.updateFPSAndDisplay(m_textureMapper.get());
200    m_textureMapper->endPainting();
201
202    context->swapBuffers();
203}
204
205void 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
229void 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        if (m_redirectedWindow)
240            m_redirectedWindow->resize(IntSize(1, 1));
241        m_rootLayer = nullptr;
242        m_nonCompositedContentLayer = nullptr;
243        m_textureMapper = nullptr;
244        return;
245    }
246
247    // Add the accelerated layer tree hierarchy.
248    initialize();
249    if (!m_redirectedWindow)
250        return;
251
252    m_nonCompositedContentLayer->removeAllChildren();
253    m_nonCompositedContentLayer->addChild(graphicsLayer);
254
255    stopAnyPendingLayerFlush();
256
257    // FIXME: Two flushes seem necessary to get the proper rendering in some cases. It's unclear
258    // if this is a bug with the RedirectedXComposite window or with this class.
259    m_needsExtraFlush = true;
260    scheduleLayerFlush();
261
262    m_layerFlushTimerCallbackId = g_timeout_add_full(GDK_PRIORITY_EVENTS, 500, reinterpret_cast<GSourceFunc>(layerFlushTimerFiredCallback), this, 0);
263}
264
265void AcceleratedCompositingContext::setNonCompositedContentsNeedDisplay(const IntRect& rect)
266{
267    if (!m_rootLayer)
268        return;
269    if (rect.isEmpty()) {
270        m_rootLayer->setNeedsDisplay();
271        return;
272    }
273    m_nonCompositedContentLayer->setNeedsDisplayInRect(rect);
274    scheduleLayerFlush();
275}
276
277void AcceleratedCompositingContext::resizeRootLayer(const IntSize& newSize)
278{
279    if (!enabled())
280        return;
281
282    if (m_rootLayer->size() == newSize)
283        return;
284
285    m_redirectedWindow->resize(newSize);
286    m_rootLayer->setSize(newSize);
287
288    // If the newSize exposes new areas of the non-composited content a setNeedsDisplay is needed
289    // for those newly exposed areas.
290    FloatSize oldSize = m_nonCompositedContentLayer->size();
291    m_nonCompositedContentLayer->setSize(newSize);
292
293    if (newSize.width() > oldSize.width()) {
294        float height = std::min(static_cast<float>(newSize.height()), oldSize.height());
295        m_nonCompositedContentLayer->setNeedsDisplayInRect(FloatRect(oldSize.width(), 0, newSize.width() - oldSize.width(), height));
296    }
297
298    if (newSize.height() > oldSize.height())
299        m_nonCompositedContentLayer->setNeedsDisplayInRect(FloatRect(0, oldSize.height(), newSize.width(), newSize.height() - oldSize.height()));
300
301    m_nonCompositedContentLayer->setNeedsDisplayInRect(IntRect(IntPoint(), newSize));
302    compositeLayersToContext(ForResize);
303    scheduleLayerFlush();
304}
305
306void AcceleratedCompositingContext::scrollNonCompositedContents(const IntRect& scrollRect, const IntSize& scrollOffset)
307{
308    m_nonCompositedContentLayer->setNeedsDisplayInRect(scrollRect);
309    scheduleLayerFlush();
310}
311
312gboolean AcceleratedCompositingContext::layerFlushTimerFiredCallback(AcceleratedCompositingContext* context)
313{
314    context->layerFlushTimerFired();
315    return FALSE;
316}
317
318void AcceleratedCompositingContext::scheduleLayerFlush()
319{
320    if (!enabled())
321        return;
322
323    if (m_layerFlushTimerCallbackId)
324        return;
325
326    // We use a GLib timer because otherwise GTK+ event handling during dragging can
327    // starve WebCore timers, which have a lower priority.
328    double nextFlush = max(gScheduleDelay - (currentTime() - m_lastFlushTime), 0.0);
329    m_layerFlushTimerCallbackId = g_timeout_add_full(GDK_PRIORITY_EVENTS, nextFlush * 1000, reinterpret_cast<GSourceFunc>(layerFlushTimerFiredCallback), this, 0);
330}
331
332bool AcceleratedCompositingContext::flushPendingLayerChanges()
333{
334    m_rootLayer->flushCompositingStateForThisLayerOnly();
335    m_nonCompositedContentLayer->flushCompositingStateForThisLayerOnly();
336    return core(m_webView)->mainFrame()->view()->flushCompositingStateIncludingSubframes();
337}
338
339void AcceleratedCompositingContext::flushAndRenderLayers()
340{
341    if (!enabled())
342        return;
343
344    Frame* frame = core(m_webView)->mainFrame();
345    if (!frame || !frame->contentRenderer() || !frame->view())
346        return;
347    frame->view()->updateLayoutAndStyleIfNeededRecursive();
348
349    if (!enabled())
350        return;
351
352    GLContext* context = m_redirectedWindow->context();
353    if (context && !context->makeContextCurrent())
354        return;
355
356    if (!flushPendingLayerChanges())
357        return;
358
359    m_lastFlushTime = currentTime();
360    compositeLayersToContext();
361
362    // If it's been a long time since we've actually painted, which means that events might
363    // be starving the main loop, we should force a draw now. This seems to prevent display
364    // lag on http://2012.beercamp.com.
365    if (m_redrawPendingTime && currentTime() - m_redrawPendingTime > gScheduleDelay) {
366        gtk_widget_queue_draw(GTK_WIDGET(m_webView));
367        gdk_window_process_updates(gtk_widget_get_window(GTK_WIDGET(m_webView)), FALSE);
368    } else if (!m_redrawPendingTime)
369        m_redrawPendingTime = currentTime();
370}
371
372void AcceleratedCompositingContext::layerFlushTimerFired()
373{
374    m_layerFlushTimerCallbackId = 0;
375    flushAndRenderLayers();
376}
377
378void AcceleratedCompositingContext::notifyAnimationStarted(const GraphicsLayer*, double time)
379{
380
381}
382void AcceleratedCompositingContext::notifyFlushRequired(const GraphicsLayer*)
383{
384
385}
386
387void AcceleratedCompositingContext::paintContents(const GraphicsLayer*, GraphicsContext& context, GraphicsLayerPaintingPhase, const IntRect& rectToPaint)
388{
389    context.save();
390    context.clip(rectToPaint);
391    core(m_webView)->mainFrame()->view()->paint(&context, rectToPaint);
392    context.restore();
393}
394
395} // namespace WebKit
396
397#endif // USE(ACCELERATED_COMPOSITING) && USE(TEXTURE_MAPPER_GL)
398