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