1 /* 2 * Copyright 2011, The Android Open Source Project 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 * * Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * * 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 THE COPYRIGHT HOLDERS ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 #include "config.h" 27 #include "TransferQueue.h" 28 29 #if USE(ACCELERATED_COMPOSITING) 30 31 #include "BaseTile.h" 32 #include "PaintedSurface.h" 33 #include <android/native_window.h> 34 #include <gui/SurfaceTexture.h> 35 #include <gui/SurfaceTextureClient.h> 36 37 #include <cutils/log.h> 38 #include <wtf/text/CString.h> 39 #define XLOGC(...) android_printLog(ANDROID_LOG_DEBUG, "TransferQueue", __VA_ARGS__) 40 41 #ifdef DEBUG 42 43 #undef XLOG 44 #define XLOG(...) android_printLog(ANDROID_LOG_DEBUG, "TransferQueue", __VA_ARGS__) 45 46 #else 47 48 #undef XLOG 49 #define XLOG(...) 50 51 #endif // DEBUG 52 53 #define ST_BUFFER_NUMBER 6 54 55 // Set this to 1 if we would like to take the new GpuUpload approach which 56 // relied on the glCopyTexSubImage2D instead of a glDraw call 57 #define GPU_UPLOAD_WITHOUT_DRAW 1 58 59 namespace WebCore { 60 61 TransferQueue::TransferQueue() 62 : m_eglSurface(EGL_NO_SURFACE) 63 , m_transferQueueIndex(0) 64 , m_fboID(0) 65 , m_sharedSurfaceTextureId(0) 66 , m_hasGLContext(true) 67 , m_interruptedByRemovingOp(false) 68 , m_currentDisplay(EGL_NO_DISPLAY) 69 , m_currentUploadType(DEFAULT_UPLOAD_TYPE) 70 { 71 memset(&m_GLStateBeforeBlit, 0, sizeof(m_GLStateBeforeBlit)); 72 73 m_emptyItemCount = ST_BUFFER_NUMBER; 74 75 m_transferQueue = new TileTransferData[ST_BUFFER_NUMBER]; 76 } 77 78 TransferQueue::~TransferQueue() 79 { 80 glDeleteFramebuffers(1, &m_fboID); 81 m_fboID = 0; 82 glDeleteTextures(1, &m_sharedSurfaceTextureId); 83 m_sharedSurfaceTextureId = 0; 84 85 delete[] m_transferQueue; 86 } 87 88 void TransferQueue::initSharedSurfaceTextures(int width, int height) 89 { 90 if (!m_sharedSurfaceTextureId) { 91 glGenTextures(1, &m_sharedSurfaceTextureId); 92 m_sharedSurfaceTexture = 93 #if GPU_UPLOAD_WITHOUT_DRAW 94 new android::SurfaceTexture(m_sharedSurfaceTextureId, true, GL_TEXTURE_2D); 95 #else 96 new android::SurfaceTexture(m_sharedSurfaceTextureId); 97 #endif 98 m_ANW = new android::SurfaceTextureClient(m_sharedSurfaceTexture); 99 m_sharedSurfaceTexture->setSynchronousMode(true); 100 m_sharedSurfaceTexture->setBufferCount(ST_BUFFER_NUMBER+1); 101 102 int result = native_window_set_buffers_geometry(m_ANW.get(), 103 width, height, HAL_PIXEL_FORMAT_RGBA_8888); 104 GLUtils::checkSurfaceTextureError("native_window_set_buffers_geometry", result); 105 result = native_window_set_usage(m_ANW.get(), 106 GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN); 107 GLUtils::checkSurfaceTextureError("native_window_set_usage", result); 108 } 109 110 if (!m_fboID) 111 glGenFramebuffers(1, &m_fboID); 112 } 113 114 // When bliting, if the item from the transfer queue is mismatching b/t the 115 // BaseTile and the content, then the item is considered as obsolete, and 116 // the content is discarded. 117 bool TransferQueue::checkObsolete(int index) 118 { 119 BaseTile* baseTilePtr = m_transferQueue[index].savedBaseTilePtr; 120 if (!baseTilePtr) { 121 XLOG("Invalid savedBaseTilePtr , such that the tile is obsolete"); 122 return true; 123 } 124 125 BaseTileTexture* baseTileTexture = baseTilePtr->backTexture(); 126 if (!baseTileTexture) { 127 XLOG("Invalid baseTileTexture , such that the tile is obsolete"); 128 return true; 129 } 130 131 const TextureTileInfo* tileInfo = &m_transferQueue[index].tileInfo; 132 133 if (tileInfo->m_x != baseTilePtr->x() 134 || tileInfo->m_y != baseTilePtr->y() 135 || tileInfo->m_scale != baseTilePtr->scale() 136 || tileInfo->m_painter != baseTilePtr->painter()) { 137 XLOG("Mismatching x, y, scale or painter , such that the tile is obsolete"); 138 return true; 139 } 140 141 return false; 142 } 143 144 void TransferQueue::blitTileFromQueue(GLuint fboID, BaseTileTexture* destTex, 145 GLuint srcTexId, GLenum srcTexTarget, 146 int index) 147 { 148 #if GPU_UPLOAD_WITHOUT_DRAW 149 glBindFramebuffer(GL_FRAMEBUFFER, fboID); 150 glFramebufferTexture2D(GL_FRAMEBUFFER, 151 GL_COLOR_ATTACHMENT0, 152 GL_TEXTURE_2D, 153 srcTexId, 154 0); 155 glBindTexture(GL_TEXTURE_2D, destTex->m_ownTextureId); 156 glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, 157 destTex->getSize().width(), 158 destTex->getSize().height()); 159 #else 160 // Then set up the FBO and copy the SurfTex content in. 161 glBindFramebuffer(GL_FRAMEBUFFER, fboID); 162 glFramebufferTexture2D(GL_FRAMEBUFFER, 163 GL_COLOR_ATTACHMENT0, 164 GL_TEXTURE_2D, 165 destTex->m_ownTextureId, 166 0); 167 setGLStateForCopy(destTex->getSize().width(), 168 destTex->getSize().height()); 169 GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); 170 if (status != GL_FRAMEBUFFER_COMPLETE) { 171 XLOG("Error: glCheckFramebufferStatus failed"); 172 glBindFramebuffer(GL_FRAMEBUFFER, 0); 173 return; 174 } 175 176 // Use empty rect to set up the special matrix to draw. 177 SkRect rect = SkRect::MakeEmpty(); 178 TilesManager::instance()->shader()->drawQuad(rect, srcTexId, 1.0, 179 srcTexTarget, GL_NEAREST); 180 181 // To workaround a sync issue on some platforms, we should insert the sync 182 // here while in the current FBO. 183 // This will essentially kick off the GPU command buffer, and the Tex Gen 184 // thread will then have to wait for this buffer to finish before writing 185 // into the same memory. 186 EGLDisplay dpy = eglGetCurrentDisplay(); 187 if (m_currentDisplay != dpy) 188 m_currentDisplay = dpy; 189 if (m_currentDisplay != EGL_NO_DISPLAY) { 190 if (m_transferQueue[index].m_syncKHR != EGL_NO_SYNC_KHR) 191 eglDestroySyncKHR(m_currentDisplay, m_transferQueue[index].m_syncKHR); 192 m_transferQueue[index].m_syncKHR = eglCreateSyncKHR(m_currentDisplay, 193 EGL_SYNC_FENCE_KHR, 194 0); 195 } 196 GLUtils::checkEglError("CreateSyncKHR"); 197 #endif 198 } 199 200 void TransferQueue::interruptTransferQueue(bool interrupt) 201 { 202 m_transferQueueItemLocks.lock(); 203 m_interruptedByRemovingOp = interrupt; 204 if (m_interruptedByRemovingOp) 205 m_transferQueueItemCond.signal(); 206 m_transferQueueItemLocks.unlock(); 207 } 208 209 // This function must be called inside the m_transferQueueItemLocks, for the 210 // wait, m_interruptedByRemovingOp and getHasGLContext(). 211 // Only called by updateQueueWithBitmap() for now. 212 bool TransferQueue::readyForUpdate() 213 { 214 if (!getHasGLContext()) 215 return false; 216 // Don't use a while loop since when the WebView tear down, the emptyCount 217 // will still be 0, and we bailed out b/c of GL context lost. 218 if (!m_emptyItemCount) { 219 if (m_interruptedByRemovingOp) 220 return false; 221 m_transferQueueItemCond.wait(m_transferQueueItemLocks); 222 if (m_interruptedByRemovingOp) 223 return false; 224 } 225 226 if (!getHasGLContext()) 227 return false; 228 229 // Disable this wait until we figure out why this didn't work on some 230 // drivers b/5332112. 231 #if 0 232 if (m_currentUploadType == GpuUpload 233 && m_currentDisplay != EGL_NO_DISPLAY) { 234 // Check the GPU fence 235 EGLSyncKHR syncKHR = m_transferQueue[getNextTransferQueueIndex()].m_syncKHR; 236 if (syncKHR != EGL_NO_SYNC_KHR) 237 eglClientWaitSyncKHR(m_currentDisplay, 238 syncKHR, 239 EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, 240 EGL_FOREVER_KHR); 241 } 242 GLUtils::checkEglError("WaitSyncKHR"); 243 #endif 244 245 return true; 246 } 247 248 // Both getHasGLContext and setHasGLContext should be called within the lock. 249 bool TransferQueue::getHasGLContext() 250 { 251 return m_hasGLContext; 252 } 253 254 void TransferQueue::setHasGLContext(bool hasContext) 255 { 256 m_hasGLContext = hasContext; 257 } 258 259 // Only called when WebView is destroyed or switching the uploadType. 260 void TransferQueue::discardQueue() 261 { 262 android::Mutex::Autolock lock(m_transferQueueItemLocks); 263 264 for (int i = 0 ; i < ST_BUFFER_NUMBER; i++) 265 if (m_transferQueue[i].status == pendingBlit) 266 m_transferQueue[i].status = pendingDiscard; 267 268 bool GLContextExisted = getHasGLContext(); 269 // Unblock the Tex Gen thread first before Tile Page deletion. 270 // Otherwise, there will be a deadlock while removing operations. 271 setHasGLContext(false); 272 273 // Only signal once when GL context lost. 274 if (GLContextExisted) 275 m_transferQueueItemCond.signal(); 276 } 277 278 // Call on UI thread to copy from the shared Surface Texture to the BaseTile's texture. 279 void TransferQueue::updateDirtyBaseTiles() 280 { 281 android::Mutex::Autolock lock(m_transferQueueItemLocks); 282 283 cleanupTransportQueue(); 284 if (!getHasGLContext()) 285 setHasGLContext(true); 286 287 // Start from the oldest item, we call the updateTexImage to retrive 288 // the texture and blit that into each BaseTile's texture. 289 const int nextItemIndex = getNextTransferQueueIndex(); 290 int index = nextItemIndex; 291 bool usedFboForUpload = false; 292 for (int k = 0; k < ST_BUFFER_NUMBER ; k++) { 293 if (m_transferQueue[index].status == pendingBlit) { 294 bool obsoleteBaseTile = checkObsolete(index); 295 // Save the needed info, update the Surf Tex, clean up the item in 296 // the queue. Then either move on to next item or copy the content. 297 BaseTileTexture* destTexture = 0; 298 if (!obsoleteBaseTile) 299 destTexture = m_transferQueue[index].savedBaseTilePtr->backTexture(); 300 if (m_transferQueue[index].uploadType == GpuUpload) { 301 status_t result = m_sharedSurfaceTexture->updateTexImage(); 302 if (result != OK) 303 XLOGC("unexpected error: updateTexImage return %d", result); 304 } 305 m_transferQueue[index].savedBaseTilePtr = 0; 306 m_transferQueue[index].status = emptyItem; 307 if (obsoleteBaseTile) { 308 XLOG("Warning: the texture is obsolete for this baseTile"); 309 index = (index + 1) % ST_BUFFER_NUMBER; 310 continue; 311 } 312 313 // guarantee that we have a texture to blit into 314 destTexture->requireGLTexture(); 315 316 if (m_transferQueue[index].uploadType == CpuUpload) { 317 // Here we just need to upload the bitmap content to the GL Texture 318 GLUtils::updateTextureWithBitmap(destTexture->m_ownTextureId, 0, 0, 319 *m_transferQueue[index].bitmap); 320 } else { 321 if (!usedFboForUpload) { 322 saveGLState(); 323 usedFboForUpload = true; 324 } 325 blitTileFromQueue(m_fboID, destTexture, 326 m_sharedSurfaceTextureId, 327 m_sharedSurfaceTexture->getCurrentTextureTarget(), 328 index); 329 } 330 331 // After the base tile copied into the GL texture, we need to 332 // update the texture's info such that at draw time, readyFor 333 // will find the latest texture's info 334 // We don't need a map any more, each texture contains its own 335 // texturesTileInfo. 336 destTexture->setOwnTextureTileInfoFromQueue(&m_transferQueue[index].tileInfo); 337 338 XLOG("Blit tile x, y %d %d with dest texture %p to destTexture->m_ownTextureId %d", 339 m_transferQueue[index].tileInfo.m_x, 340 m_transferQueue[index].tileInfo.m_y, 341 destTexture, 342 destTexture->m_ownTextureId); 343 } 344 index = (index + 1) % ST_BUFFER_NUMBER; 345 } 346 347 // Clean up FBO setup. Doing this for both CPU/GPU upload can make the 348 // dynamic switch possible. Moving this out from the loop can save some 349 // milli-seconds. 350 if (usedFboForUpload) { 351 glBindFramebuffer(GL_FRAMEBUFFER, 0); // rebind the standard FBO 352 restoreGLState(); 353 GLUtils::checkGlError("updateDirtyBaseTiles"); 354 } 355 356 m_emptyItemCount = ST_BUFFER_NUMBER; 357 m_transferQueueItemCond.signal(); 358 } 359 360 void TransferQueue::updateQueueWithBitmap(const TileRenderInfo* renderInfo, 361 int x, int y, const SkBitmap& bitmap) 362 { 363 if (!tryUpdateQueueWithBitmap(renderInfo, x, y, bitmap)) { 364 // failed placing bitmap in queue, discard tile's texture so it will be 365 // re-enqueued (and repainted) 366 BaseTile* tile = renderInfo->baseTile; 367 if (tile) 368 tile->backTextureTransferFail(); 369 } 370 } 371 372 bool TransferQueue::tryUpdateQueueWithBitmap(const TileRenderInfo* renderInfo, 373 int x, int y, const SkBitmap& bitmap) 374 { 375 m_transferQueueItemLocks.lock(); 376 bool ready = readyForUpdate(); 377 TextureUploadType currentUploadType = m_currentUploadType; 378 m_transferQueueItemLocks.unlock(); 379 if (!ready) { 380 XLOG("Quit bitmap update: not ready! for tile x y %d %d", 381 renderInfo->x, renderInfo->y); 382 return false; 383 } 384 if (currentUploadType == GpuUpload) { 385 // a) Dequeue the Surface Texture and write into the buffer 386 if (!m_ANW.get()) { 387 XLOG("ERROR: ANW is null"); 388 return false; 389 } 390 391 ANativeWindow_Buffer buffer; 392 if (ANativeWindow_lock(m_ANW.get(), &buffer, 0)) 393 return false; 394 395 uint8_t* img = (uint8_t*)buffer.bits; 396 int row, col; 397 int bpp = 4; // Now we only deal with RGBA8888 format. 398 int width = TilesManager::instance()->tileWidth(); 399 int height = TilesManager::instance()->tileHeight(); 400 if (!x && !y && bitmap.width() == width && bitmap.height() == height) { 401 bitmap.lockPixels(); 402 uint8_t* bitmapOrigin = static_cast<uint8_t*>(bitmap.getPixels()); 403 if (buffer.stride != bitmap.width()) 404 // Copied line by line since we need to handle the offsets and stride. 405 for (row = 0 ; row < bitmap.height(); row ++) { 406 uint8_t* dst = &(img[buffer.stride * row * bpp]); 407 uint8_t* src = &(bitmapOrigin[bitmap.width() * row * bpp]); 408 memcpy(dst, src, bpp * bitmap.width()); 409 } 410 else 411 memcpy(img, bitmapOrigin, bpp * bitmap.width() * bitmap.height()); 412 413 bitmap.unlockPixels(); 414 } else { 415 // TODO: implement the partial invalidate here! 416 XLOG("ERROR: don't expect to get here yet before we support partial inval"); 417 } 418 419 ANativeWindow_unlockAndPost(m_ANW.get()); 420 } 421 422 m_transferQueueItemLocks.lock(); 423 // b) After update the Surface Texture, now udpate the transfer queue info. 424 addItemInTransferQueue(renderInfo, currentUploadType, &bitmap); 425 426 m_transferQueueItemLocks.unlock(); 427 XLOG("Bitmap updated x, y %d %d, baseTile %p", 428 renderInfo->x, renderInfo->y, renderInfo->baseTile); 429 return true; 430 } 431 432 // Note that there should be lock/unlock around this function call. 433 // Currently only called by GLUtils::updateSharedSurfaceTextureWithBitmap. 434 void TransferQueue::addItemInTransferQueue(const TileRenderInfo* renderInfo, 435 TextureUploadType type, 436 const SkBitmap* bitmap) 437 { 438 m_transferQueueIndex = (m_transferQueueIndex + 1) % ST_BUFFER_NUMBER; 439 440 int index = m_transferQueueIndex; 441 if (m_transferQueue[index].savedBaseTilePtr 442 || m_transferQueue[index].status != emptyItem) { 443 XLOG("ERROR update a tile which is dirty already @ index %d", index); 444 } 445 446 m_transferQueue[index].savedBaseTileTexturePtr = renderInfo->baseTile->backTexture(); 447 m_transferQueue[index].savedBaseTilePtr = renderInfo->baseTile; 448 m_transferQueue[index].status = pendingBlit; 449 m_transferQueue[index].uploadType = type; 450 if (type == CpuUpload && bitmap) { 451 // Lazily create the bitmap 452 if (!m_transferQueue[index].bitmap) { 453 m_transferQueue[index].bitmap = new SkBitmap(); 454 int w = bitmap->width(); 455 int h = bitmap->height(); 456 m_transferQueue[index].bitmap->setConfig(bitmap->config(), w, h); 457 } 458 bitmap->copyTo(m_transferQueue[index].bitmap, bitmap->config()); 459 } 460 461 // Now fill the tileInfo. 462 TextureTileInfo* textureInfo = &m_transferQueue[index].tileInfo; 463 464 textureInfo->m_x = renderInfo->x; 465 textureInfo->m_y = renderInfo->y; 466 textureInfo->m_scale = renderInfo->scale; 467 textureInfo->m_painter = renderInfo->tilePainter; 468 469 textureInfo->m_picture = renderInfo->textureInfo->m_pictureCount; 470 471 m_emptyItemCount--; 472 } 473 474 void TransferQueue::setTextureUploadType(TextureUploadType type) 475 { 476 if (m_currentUploadType == type) 477 return; 478 479 discardQueue(); 480 481 android::Mutex::Autolock lock(m_transferQueueItemLocks); 482 m_currentUploadType = type; 483 XLOGC("Now we set the upload to %s", m_currentUploadType == GpuUpload ? "GpuUpload" : "CpuUpload"); 484 } 485 486 // Note: this need to be called within th lock. 487 // Only called by updateDirtyBaseTiles() for now 488 void TransferQueue::cleanupTransportQueue() 489 { 490 int index = getNextTransferQueueIndex(); 491 492 for (int i = 0 ; i < ST_BUFFER_NUMBER; i++) { 493 if (m_transferQueue[index].status == pendingDiscard) { 494 // No matter what the current upload type is, as long as there has 495 // been a Surf Tex enqueue operation, this updateTexImage need to 496 // be called to keep things in sync. 497 if (m_transferQueue[index].uploadType == GpuUpload) { 498 status_t result = m_sharedSurfaceTexture->updateTexImage(); 499 if (result != OK) 500 XLOGC("unexpected error: updateTexImage return %d", result); 501 } 502 503 // since tiles in the queue may be from another webview, remove 504 // their textures so that they will be repainted / retransferred 505 BaseTile* tile = m_transferQueue[index].savedBaseTilePtr; 506 BaseTileTexture* texture = m_transferQueue[index].savedBaseTileTexturePtr; 507 if (tile && texture && texture->owner() == tile) { 508 // since tile destruction removes textures on the UI thread, the 509 // texture->owner ptr guarantees the tile is valid 510 tile->discardBackTexture(); 511 XLOG("transfer queue discarded tile %p, removed texture", tile); 512 } 513 514 m_transferQueue[index].savedBaseTilePtr = 0; 515 m_transferQueue[index].savedBaseTileTexturePtr = 0; 516 m_transferQueue[index].status = emptyItem; 517 } 518 index = (index + 1) % ST_BUFFER_NUMBER; 519 } 520 } 521 522 void TransferQueue::saveGLState() 523 { 524 glGetIntegerv(GL_VIEWPORT, m_GLStateBeforeBlit.viewport); 525 glGetBooleanv(GL_SCISSOR_TEST, m_GLStateBeforeBlit.scissor); 526 glGetBooleanv(GL_DEPTH_TEST, m_GLStateBeforeBlit.depth); 527 #if DEBUG 528 glGetFloatv(GL_COLOR_CLEAR_VALUE, m_GLStateBeforeBlit.clearColor); 529 #endif 530 } 531 532 void TransferQueue::setGLStateForCopy(int width, int height) 533 { 534 // Need to match the texture size. 535 glViewport(0, 0, width, height); 536 glDisable(GL_SCISSOR_TEST); 537 glDisable(GL_DEPTH_TEST); 538 // Clear the content is only for debug purpose. 539 #if DEBUG 540 glClearColor(0, 0, 0, 0); 541 glClear(GL_COLOR_BUFFER_BIT); 542 #endif 543 } 544 545 void TransferQueue::restoreGLState() 546 { 547 glViewport(m_GLStateBeforeBlit.viewport[0], 548 m_GLStateBeforeBlit.viewport[1], 549 m_GLStateBeforeBlit.viewport[2], 550 m_GLStateBeforeBlit.viewport[3]); 551 552 if (m_GLStateBeforeBlit.scissor[0]) 553 glEnable(GL_SCISSOR_TEST); 554 555 if (m_GLStateBeforeBlit.depth[0]) 556 glEnable(GL_DEPTH_TEST); 557 #if DEBUG 558 glClearColor(m_GLStateBeforeBlit.clearColor[0], 559 m_GLStateBeforeBlit.clearColor[1], 560 m_GLStateBeforeBlit.clearColor[2], 561 m_GLStateBeforeBlit.clearColor[3]); 562 #endif 563 } 564 565 int TransferQueue::getNextTransferQueueIndex() 566 { 567 return (m_transferQueueIndex + 1) % ST_BUFFER_NUMBER; 568 } 569 570 } // namespace WebCore 571 572 #endif // USE(ACCELERATED_COMPOSITING 573