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