CanvasContext.cpp revision 16efa9a330fcf10a09cc9564e9d319c6f4be2ae1
1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#define LOG_TAG "CanvasContext" 18 19#include "CanvasContext.h" 20 21#include <cutils/properties.h> 22#include <private/hwui/DrawGlInfo.h> 23#include <strings.h> 24 25#include "RenderThread.h" 26#include "../Caches.h" 27#include "../DeferredLayerUpdater.h" 28#include "../LayerRenderer.h" 29#include "../OpenGLRenderer.h" 30#include "../Stencil.h" 31 32#define PROPERTY_RENDER_DIRTY_REGIONS "debug.hwui.render_dirty_regions" 33#define GLES_VERSION 2 34 35#ifdef USE_OPENGL_RENDERER 36// Android-specific addition that is used to show when frames began in systrace 37EGLAPI void EGLAPIENTRY eglBeginFrame(EGLDisplay dpy, EGLSurface surface); 38#endif 39 40namespace android { 41namespace uirenderer { 42namespace renderthread { 43 44#define ERROR_CASE(x) case x: return #x; 45static const char* egl_error_str(EGLint error) { 46 switch (error) { 47 ERROR_CASE(EGL_SUCCESS) 48 ERROR_CASE(EGL_NOT_INITIALIZED) 49 ERROR_CASE(EGL_BAD_ACCESS) 50 ERROR_CASE(EGL_BAD_ALLOC) 51 ERROR_CASE(EGL_BAD_ATTRIBUTE) 52 ERROR_CASE(EGL_BAD_CONFIG) 53 ERROR_CASE(EGL_BAD_CONTEXT) 54 ERROR_CASE(EGL_BAD_CURRENT_SURFACE) 55 ERROR_CASE(EGL_BAD_DISPLAY) 56 ERROR_CASE(EGL_BAD_MATCH) 57 ERROR_CASE(EGL_BAD_NATIVE_PIXMAP) 58 ERROR_CASE(EGL_BAD_NATIVE_WINDOW) 59 ERROR_CASE(EGL_BAD_PARAMETER) 60 ERROR_CASE(EGL_BAD_SURFACE) 61 ERROR_CASE(EGL_CONTEXT_LOST) 62 default: 63 return "Unknown error"; 64 } 65} 66static const char* egl_error_str() { 67 return egl_error_str(eglGetError()); 68} 69 70static bool load_dirty_regions_property() { 71 char buf[PROPERTY_VALUE_MAX]; 72 int len = property_get(PROPERTY_RENDER_DIRTY_REGIONS, buf, "true"); 73 return !strncasecmp("true", buf, len); 74} 75 76// This class contains the shared global EGL objects, such as EGLDisplay 77// and EGLConfig, which are re-used by CanvasContext 78class GlobalContext { 79public: 80 static GlobalContext* get(); 81 82 // Returns true on success, false on failure 83 void initialize(); 84 85 void usePBufferSurface(); 86 EGLSurface createSurface(EGLNativeWindowType window); 87 void destroySurface(EGLSurface surface); 88 89 void destroy(); 90 91 bool isCurrent(EGLSurface surface) { return mCurrentSurface == surface; } 92 void makeCurrent(EGLSurface surface); 93 void beginFrame(EGLSurface surface, EGLint* width, EGLint* height); 94 void swapBuffers(EGLSurface surface); 95 96 bool enableDirtyRegions(EGLSurface surface); 97 98private: 99 GlobalContext(); 100 // GlobalContext is never destroyed, method is purposely not implemented 101 ~GlobalContext(); 102 103 void loadConfig(); 104 void createContext(); 105 void initAtlas(); 106 107 static GlobalContext* sContext; 108 109 EGLDisplay mEglDisplay; 110 EGLConfig mEglConfig; 111 EGLContext mEglContext; 112 EGLSurface mPBufferSurface; 113 114 const bool mRequestDirtyRegions; 115 bool mCanSetDirtyRegions; 116 117 EGLSurface mCurrentSurface; 118}; 119 120GlobalContext* GlobalContext::sContext = 0; 121 122GlobalContext* GlobalContext::get() { 123 if (!sContext) { 124 sContext = new GlobalContext(); 125 } 126 return sContext; 127} 128 129GlobalContext::GlobalContext() 130 : mEglDisplay(EGL_NO_DISPLAY) 131 , mEglConfig(0) 132 , mEglContext(EGL_NO_CONTEXT) 133 , mPBufferSurface(EGL_NO_SURFACE) 134 , mRequestDirtyRegions(load_dirty_regions_property()) 135 , mCurrentSurface(EGL_NO_SURFACE) { 136 mCanSetDirtyRegions = mRequestDirtyRegions; 137 ALOGD("Render dirty regions requested: %s", mRequestDirtyRegions ? "true" : "false"); 138} 139 140void GlobalContext::initialize() { 141 if (mEglDisplay != EGL_NO_DISPLAY) return; 142 143 mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); 144 LOG_ALWAYS_FATAL_IF(mEglDisplay == EGL_NO_DISPLAY, 145 "Failed to get EGL_DEFAULT_DISPLAY! err=%s", egl_error_str()); 146 147 EGLint major, minor; 148 LOG_ALWAYS_FATAL_IF(eglInitialize(mEglDisplay, &major, &minor) == EGL_FALSE, 149 "Failed to initialize display %p! err=%s", mEglDisplay, egl_error_str()); 150 151 ALOGI("Initialized EGL, version %d.%d", (int)major, (int)minor); 152 153 loadConfig(); 154 createContext(); 155 usePBufferSurface(); 156 Caches::getInstance().init(); 157 initAtlas(); 158} 159 160void GlobalContext::loadConfig() { 161 EGLint swapBehavior = mCanSetDirtyRegions ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0; 162 EGLint attribs[] = { 163 EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, 164 EGL_RED_SIZE, 8, 165 EGL_GREEN_SIZE, 8, 166 EGL_BLUE_SIZE, 8, 167 EGL_ALPHA_SIZE, 8, 168 EGL_DEPTH_SIZE, 0, 169 EGL_CONFIG_CAVEAT, EGL_NONE, 170 EGL_STENCIL_SIZE, Stencil::getStencilSize(), 171 EGL_SURFACE_TYPE, EGL_WINDOW_BIT | swapBehavior, 172 EGL_NONE 173 }; 174 175 EGLint num_configs = 1; 176 if (!eglChooseConfig(mEglDisplay, attribs, &mEglConfig, num_configs, &num_configs) 177 || num_configs != 1) { 178 // Failed to get a valid config 179 if (mCanSetDirtyRegions) { 180 ALOGW("Failed to choose config with EGL_SWAP_BEHAVIOR_PRESERVED, retrying without..."); 181 // Try again without dirty regions enabled 182 mCanSetDirtyRegions = false; 183 loadConfig(); 184 } else { 185 LOG_ALWAYS_FATAL("Failed to choose config, error = %s", egl_error_str()); 186 } 187 } 188} 189 190void GlobalContext::createContext() { 191 EGLint attribs[] = { EGL_CONTEXT_CLIENT_VERSION, GLES_VERSION, EGL_NONE }; 192 mEglContext = eglCreateContext(mEglDisplay, mEglConfig, EGL_NO_CONTEXT, attribs); 193 LOG_ALWAYS_FATAL_IF(mEglContext == EGL_NO_CONTEXT, 194 "Failed to create context, error = %s", egl_error_str()); 195} 196 197void GlobalContext::initAtlas() { 198 // TODO implement 199 // For now just run without an atlas 200} 201 202void GlobalContext::usePBufferSurface() { 203 LOG_ALWAYS_FATAL_IF(mEglDisplay == EGL_NO_DISPLAY, 204 "usePBufferSurface() called on uninitialized GlobalContext!"); 205 206 if (mPBufferSurface == EGL_NO_SURFACE) { 207 EGLint attribs[] = { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE }; 208 mPBufferSurface = eglCreatePbufferSurface(mEglDisplay, mEglConfig, attribs); 209 } 210 makeCurrent(mPBufferSurface); 211} 212 213EGLSurface GlobalContext::createSurface(EGLNativeWindowType window) { 214 initialize(); 215 return eglCreateWindowSurface(mEglDisplay, mEglConfig, window, NULL); 216} 217 218void GlobalContext::destroySurface(EGLSurface surface) { 219 if (isCurrent(surface)) { 220 makeCurrent(EGL_NO_SURFACE); 221 } 222 if (!eglDestroySurface(mEglDisplay, surface)) { 223 ALOGW("Failed to destroy surface %p, error=%s", (void*)surface, egl_error_str()); 224 } 225} 226 227void GlobalContext::destroy() { 228 if (mEglDisplay == EGL_NO_DISPLAY) return; 229 230 usePBufferSurface(); 231 if (Caches::hasInstance()) { 232 Caches::getInstance().terminate(); 233 } 234 235 eglDestroyContext(mEglDisplay, mEglContext); 236 eglDestroySurface(mEglDisplay, mPBufferSurface); 237 eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); 238 eglTerminate(mEglDisplay); 239 eglReleaseThread(); 240 241 mEglDisplay = EGL_NO_DISPLAY; 242 mEglContext = EGL_NO_CONTEXT; 243 mPBufferSurface = EGL_NO_SURFACE; 244 mCurrentSurface = EGL_NO_SURFACE; 245} 246 247void GlobalContext::makeCurrent(EGLSurface surface) { 248 if (isCurrent(surface)) return; 249 250 if (surface == EGL_NO_SURFACE) { 251 // If we are setting EGL_NO_SURFACE we don't care about any of the potential 252 // return errors, which would only happen if mEglDisplay had already been 253 // destroyed in which case the current context is already NO_CONTEXT 254 eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); 255 } else if (!eglMakeCurrent(mEglDisplay, surface, surface, mEglContext)) { 256 LOG_ALWAYS_FATAL("Failed to make current on surface %p, error=%s", 257 (void*)surface, egl_error_str()); 258 } 259 mCurrentSurface = surface; 260} 261 262void GlobalContext::beginFrame(EGLSurface surface, EGLint* width, EGLint* height) { 263 LOG_ALWAYS_FATAL_IF(surface == EGL_NO_SURFACE, 264 "Tried to beginFrame on EGL_NO_SURFACE!"); 265 makeCurrent(surface); 266 if (width) { 267 eglQuerySurface(mEglDisplay, surface, EGL_WIDTH, width); 268 } 269 if (height) { 270 eglQuerySurface(mEglDisplay, surface, EGL_HEIGHT, height); 271 } 272 eglBeginFrame(mEglDisplay, surface); 273} 274 275void GlobalContext::swapBuffers(EGLSurface surface) { 276 eglSwapBuffers(mEglDisplay, surface); 277 EGLint err = eglGetError(); 278 // TODO: Check whether we need to special case EGL_CONTEXT_LOST 279 LOG_ALWAYS_FATAL_IF(err != EGL_SUCCESS, 280 "Encountered EGL error %d %s during rendering", err, egl_error_str(err)); 281} 282 283bool GlobalContext::enableDirtyRegions(EGLSurface surface) { 284 if (!mRequestDirtyRegions) return false; 285 286 if (mCanSetDirtyRegions) { 287 if (!eglSurfaceAttrib(mEglDisplay, surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED)) { 288 ALOGW("Failed to set EGL_SWAP_BEHAVIOR on surface %p, error=%s", 289 (void*) surface, egl_error_str()); 290 return false; 291 } 292 return true; 293 } 294 // Perhaps it is already enabled? 295 EGLint value; 296 if (!eglQuerySurface(mEglDisplay, surface, EGL_SWAP_BEHAVIOR, &value)) { 297 ALOGW("Failed to query EGL_SWAP_BEHAVIOR on surface %p, error=%p", 298 (void*) surface, egl_error_str()); 299 return false; 300 } 301 return value == EGL_BUFFER_PRESERVED; 302} 303 304CanvasContext::CanvasContext(bool translucent) 305 : mRenderThread(RenderThread::getInstance()) 306 , mEglSurface(EGL_NO_SURFACE) 307 , mDirtyRegionsEnabled(false) 308 , mOpaque(!translucent) 309 , mCanvas(0) 310 , mHaveNewSurface(false) 311 , mInvokeFunctorsPending(false) 312 , mInvokeFunctorsTask(this) { 313 mGlobalContext = GlobalContext::get(); 314} 315 316CanvasContext::~CanvasContext() { 317 removeFunctorsTask(); 318 destroyCanvas(); 319} 320 321void CanvasContext::destroyCanvas() { 322 if (mCanvas) { 323 delete mCanvas; 324 mCanvas = 0; 325 } 326 setSurface(NULL); 327} 328 329void CanvasContext::setSurface(EGLNativeWindowType window) { 330 if (mEglSurface != EGL_NO_SURFACE) { 331 mGlobalContext->destroySurface(mEglSurface); 332 mEglSurface = EGL_NO_SURFACE; 333 } 334 335 if (window) { 336 mEglSurface = mGlobalContext->createSurface(window); 337 LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE, 338 "Failed to create EGLSurface for window %p, eglErr = %s", 339 (void*) window, egl_error_str()); 340 } 341 342 if (mEglSurface != EGL_NO_SURFACE) { 343 mDirtyRegionsEnabled = mGlobalContext->enableDirtyRegions(mEglSurface); 344 mHaveNewSurface = true; 345 } 346} 347 348void CanvasContext::swapBuffers() { 349 mGlobalContext->swapBuffers(mEglSurface); 350 mHaveNewSurface = false; 351} 352 353void CanvasContext::makeCurrent() { 354 mGlobalContext->makeCurrent(mEglSurface); 355} 356 357bool CanvasContext::initialize(EGLNativeWindowType window) { 358 if (mCanvas) return false; 359 setSurface(window); 360 makeCurrent(); 361 mCanvas = new OpenGLRenderer(); 362 mCanvas->initProperties(); 363 return true; 364} 365 366void CanvasContext::updateSurface(EGLNativeWindowType window) { 367 setSurface(window); 368 makeCurrent(); 369} 370 371void CanvasContext::setup(int width, int height) { 372 if (!mCanvas) return; 373 mCanvas->setViewport(width, height); 374} 375 376void CanvasContext::setDisplayListData(RenderNode* displayList, DisplayListData* newData) { 377 displayList->setData(newData); 378} 379 380void CanvasContext::processLayerUpdates(const Vector<DeferredLayerUpdater*>* layerUpdaters) { 381 mGlobalContext->makeCurrent(mEglSurface); 382 for (size_t i = 0; i < layerUpdaters->size(); i++) { 383 DeferredLayerUpdater* update = layerUpdaters->itemAt(i); 384 LOG_ALWAYS_FATAL_IF(!update->apply(), "Failed to update layer!"); 385 if (update->backingLayer()->deferredUpdateScheduled) { 386 mCanvas->pushLayerUpdate(update->backingLayer()); 387 } 388 } 389} 390 391void CanvasContext::drawDisplayList(RenderNode* displayList, Rect* dirty) { 392 LOG_ALWAYS_FATAL_IF(!mCanvas || mEglSurface == EGL_NO_SURFACE, 393 "drawDisplayList called on a context with no canvas or surface!"); 394 395 EGLint width, height; 396 mGlobalContext->beginFrame(mEglSurface, &width, &height); 397 if (width != mCanvas->getViewportWidth() || height != mCanvas->getViewportHeight()) { 398 mCanvas->setViewport(width, height); 399 dirty = NULL; 400 } else if (!mDirtyRegionsEnabled || mHaveNewSurface) { 401 dirty = NULL; 402 } 403 404 status_t status; 405 if (dirty) { 406 status = mCanvas->prepareDirty(dirty->left, dirty->top, 407 dirty->right, dirty->bottom, mOpaque); 408 } else { 409 status = mCanvas->prepare(mOpaque); 410 } 411 412 Rect outBounds; 413 status |= mCanvas->drawDisplayList(displayList, outBounds); 414 handleFunctorStatus(status, outBounds); 415 416 // TODO: Draw debug info 417 // TODO: Performance tracking 418 419 mCanvas->finish(); 420 421 if (status & DrawGlInfo::kStatusDrew) { 422 swapBuffers(); 423 } 424} 425 426void InvokeFunctorsTask::run() { 427 mContext->invokeFunctors(); 428} 429 430void CanvasContext::attachFunctor(Functor* functor) { 431 if (!mCanvas) return; 432 433 mCanvas->attachFunctor(functor); 434 removeFunctorsTask(); 435 queueFunctorsTask(0); 436} 437 438void CanvasContext::detachFunctor(Functor* functor) { 439 if (!mCanvas) return; 440 441 mCanvas->detachFunctor(functor); 442} 443 444void CanvasContext::invokeFunctors() { 445 mInvokeFunctorsPending = false; 446 447 if (!mCanvas) return; 448 449 makeCurrent(); 450 Rect dirty; 451 int status = mCanvas->invokeFunctors(dirty); 452 handleFunctorStatus(status, dirty); 453} 454 455void CanvasContext::handleFunctorStatus(int status, const Rect& redrawClip) { 456 if (status & DrawGlInfo::kStatusDraw) { 457 // TODO: Invalidate the redrawClip 458 // Do we need to post to ViewRootImpl like the current renderer? 459 // Can we just enqueue ourselves to re-invoke the same display list? 460 // Something else entirely? Does ChromiumView still want this in a 461 // RenderThread world? 462 } 463 464 if (status & DrawGlInfo::kStatusInvoke) { 465 queueFunctorsTask(); 466 } 467} 468 469void CanvasContext::removeFunctorsTask() { 470 if (!mInvokeFunctorsPending) return; 471 472 mInvokeFunctorsPending = false; 473 mRenderThread.remove(&mInvokeFunctorsTask); 474} 475 476void CanvasContext::queueFunctorsTask(int delayMs) { 477 if (mInvokeFunctorsPending) return; 478 479 mInvokeFunctorsPending = true; 480 mRenderThread.queueDelayed(&mInvokeFunctorsTask, delayMs); 481} 482 483bool CanvasContext::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) { 484 requireGlContext(); 485 layer->apply(); 486 return LayerRenderer::copyLayer(layer->backingLayer(), bitmap); 487} 488 489void CanvasContext::runWithGlContext(RenderTask* task) { 490 requireGlContext(); 491 task->run(); 492} 493 494void CanvasContext::requireGlContext() { 495 if (mEglSurface != EGL_NO_SURFACE) { 496 mGlobalContext->makeCurrent(mEglSurface); 497 } else { 498 mGlobalContext->usePBufferSurface(); 499 } 500} 501 502} /* namespace renderthread */ 503} /* namespace uirenderer */ 504} /* namespace android */ 505