CanvasContext.cpp revision 66f0be65a1046f54ddce0498b242c1fa0776b1ea
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    bool hasContext();
86
87    void usePBufferSurface();
88    EGLSurface createSurface(EGLNativeWindowType window);
89    void destroySurface(EGLSurface surface);
90
91    void destroy();
92
93    bool isCurrent(EGLSurface surface) { return mCurrentSurface == surface; }
94    // Returns true if the current surface changed, false if it was already current
95    bool makeCurrent(EGLSurface surface);
96    void beginFrame(EGLSurface surface, EGLint* width, EGLint* height);
97    void swapBuffers(EGLSurface surface);
98
99    bool enableDirtyRegions(EGLSurface surface);
100
101    void setTextureAtlas(const sp<GraphicBuffer>& buffer, int64_t* map, size_t mapSize);
102
103private:
104    GlobalContext();
105    // GlobalContext is never destroyed, method is purposely not implemented
106    ~GlobalContext();
107
108    void loadConfig();
109    void createContext();
110    void initAtlas();
111
112    static GlobalContext* sContext;
113
114    EGLDisplay mEglDisplay;
115    EGLConfig mEglConfig;
116    EGLContext mEglContext;
117    EGLSurface mPBufferSurface;
118
119    const bool mRequestDirtyRegions;
120    bool mCanSetDirtyRegions;
121
122    EGLSurface mCurrentSurface;
123
124    sp<GraphicBuffer> mAtlasBuffer;
125    int64_t* mAtlasMap;
126    size_t mAtlasMapSize;
127};
128
129GlobalContext* GlobalContext::sContext = 0;
130
131GlobalContext* GlobalContext::get() {
132    if (!sContext) {
133        sContext = new GlobalContext();
134    }
135    return sContext;
136}
137
138GlobalContext::GlobalContext()
139        : mEglDisplay(EGL_NO_DISPLAY)
140        , mEglConfig(0)
141        , mEglContext(EGL_NO_CONTEXT)
142        , mPBufferSurface(EGL_NO_SURFACE)
143        , mRequestDirtyRegions(load_dirty_regions_property())
144        , mCurrentSurface(EGL_NO_SURFACE)
145        , mAtlasMap(NULL)
146        , mAtlasMapSize(0) {
147    mCanSetDirtyRegions = mRequestDirtyRegions;
148    ALOGD("Render dirty regions requested: %s", mRequestDirtyRegions ? "true" : "false");
149}
150
151void GlobalContext::initialize() {
152    if (hasContext()) return;
153
154    mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
155    LOG_ALWAYS_FATAL_IF(mEglDisplay == EGL_NO_DISPLAY,
156            "Failed to get EGL_DEFAULT_DISPLAY! err=%s", egl_error_str());
157
158    EGLint major, minor;
159    LOG_ALWAYS_FATAL_IF(eglInitialize(mEglDisplay, &major, &minor) == EGL_FALSE,
160            "Failed to initialize display %p! err=%s", mEglDisplay, egl_error_str());
161
162    ALOGI("Initialized EGL, version %d.%d", (int)major, (int)minor);
163
164    loadConfig();
165    createContext();
166    usePBufferSurface();
167    Caches::getInstance().init();
168    initAtlas();
169}
170
171bool GlobalContext::hasContext() {
172    return mEglDisplay != EGL_NO_DISPLAY;
173}
174
175void GlobalContext::loadConfig() {
176    EGLint swapBehavior = mCanSetDirtyRegions ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0;
177    EGLint attribs[] = {
178            EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
179            EGL_RED_SIZE, 8,
180            EGL_GREEN_SIZE, 8,
181            EGL_BLUE_SIZE, 8,
182            EGL_ALPHA_SIZE, 8,
183            EGL_DEPTH_SIZE, 0,
184            EGL_CONFIG_CAVEAT, EGL_NONE,
185            EGL_STENCIL_SIZE, Stencil::getStencilSize(),
186            EGL_SURFACE_TYPE, EGL_WINDOW_BIT | swapBehavior,
187            EGL_NONE
188    };
189
190    EGLint num_configs = 1;
191    if (!eglChooseConfig(mEglDisplay, attribs, &mEglConfig, num_configs, &num_configs)
192            || num_configs != 1) {
193        // Failed to get a valid config
194        if (mCanSetDirtyRegions) {
195            ALOGW("Failed to choose config with EGL_SWAP_BEHAVIOR_PRESERVED, retrying without...");
196            // Try again without dirty regions enabled
197            mCanSetDirtyRegions = false;
198            loadConfig();
199        } else {
200            LOG_ALWAYS_FATAL("Failed to choose config, error = %s", egl_error_str());
201        }
202    }
203}
204
205void GlobalContext::createContext() {
206    EGLint attribs[] = { EGL_CONTEXT_CLIENT_VERSION, GLES_VERSION, EGL_NONE };
207    mEglContext = eglCreateContext(mEglDisplay, mEglConfig, EGL_NO_CONTEXT, attribs);
208    LOG_ALWAYS_FATAL_IF(mEglContext == EGL_NO_CONTEXT,
209        "Failed to create context, error = %s", egl_error_str());
210}
211
212void GlobalContext::setTextureAtlas(const sp<GraphicBuffer>& buffer,
213        int64_t* map, size_t mapSize) {
214
215    // Already initialized
216    if (mAtlasBuffer.get()) {
217        ALOGW("Multiple calls to setTextureAtlas!");
218        delete map;
219        return;
220    }
221
222    mAtlasBuffer = buffer;
223    mAtlasMap = map;
224    mAtlasMapSize = mapSize;
225
226    if (hasContext()) {
227        usePBufferSurface();
228        initAtlas();
229    }
230}
231
232void GlobalContext::initAtlas() {
233    Caches::getInstance().assetAtlas.init(mAtlasBuffer, mAtlasMap, mAtlasMapSize);
234}
235
236void GlobalContext::usePBufferSurface() {
237    LOG_ALWAYS_FATAL_IF(mEglDisplay == EGL_NO_DISPLAY,
238            "usePBufferSurface() called on uninitialized GlobalContext!");
239
240    if (mPBufferSurface == EGL_NO_SURFACE) {
241        EGLint attribs[] = { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE };
242        mPBufferSurface = eglCreatePbufferSurface(mEglDisplay, mEglConfig, attribs);
243    }
244    makeCurrent(mPBufferSurface);
245}
246
247EGLSurface GlobalContext::createSurface(EGLNativeWindowType window) {
248    initialize();
249    return eglCreateWindowSurface(mEglDisplay, mEglConfig, window, NULL);
250}
251
252void GlobalContext::destroySurface(EGLSurface surface) {
253    if (isCurrent(surface)) {
254        makeCurrent(EGL_NO_SURFACE);
255    }
256    if (!eglDestroySurface(mEglDisplay, surface)) {
257        ALOGW("Failed to destroy surface %p, error=%s", (void*)surface, egl_error_str());
258    }
259}
260
261void GlobalContext::destroy() {
262    if (mEglDisplay == EGL_NO_DISPLAY) return;
263
264    usePBufferSurface();
265    if (Caches::hasInstance()) {
266        Caches::getInstance().terminate();
267    }
268
269    eglDestroyContext(mEglDisplay, mEglContext);
270    eglDestroySurface(mEglDisplay, mPBufferSurface);
271    eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
272    eglTerminate(mEglDisplay);
273    eglReleaseThread();
274
275    mEglDisplay = EGL_NO_DISPLAY;
276    mEglContext = EGL_NO_CONTEXT;
277    mPBufferSurface = EGL_NO_SURFACE;
278    mCurrentSurface = EGL_NO_SURFACE;
279}
280
281bool GlobalContext::makeCurrent(EGLSurface surface) {
282    if (isCurrent(surface)) return false;
283
284    if (surface == EGL_NO_SURFACE) {
285        // If we are setting EGL_NO_SURFACE we don't care about any of the potential
286        // return errors, which would only happen if mEglDisplay had already been
287        // destroyed in which case the current context is already NO_CONTEXT
288        eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
289    } else if (!eglMakeCurrent(mEglDisplay, surface, surface, mEglContext)) {
290        LOG_ALWAYS_FATAL("Failed to make current on surface %p, error=%s",
291                (void*)surface, egl_error_str());
292    }
293    mCurrentSurface = surface;
294    return true;
295}
296
297void GlobalContext::beginFrame(EGLSurface surface, EGLint* width, EGLint* height) {
298    LOG_ALWAYS_FATAL_IF(surface == EGL_NO_SURFACE,
299            "Tried to beginFrame on EGL_NO_SURFACE!");
300    makeCurrent(surface);
301    if (width) {
302        eglQuerySurface(mEglDisplay, surface, EGL_WIDTH, width);
303    }
304    if (height) {
305        eglQuerySurface(mEglDisplay, surface, EGL_HEIGHT, height);
306    }
307    eglBeginFrame(mEglDisplay, surface);
308}
309
310void GlobalContext::swapBuffers(EGLSurface surface) {
311    eglSwapBuffers(mEglDisplay, surface);
312    EGLint err = eglGetError();
313    LOG_ALWAYS_FATAL_IF(err != EGL_SUCCESS,
314            "Encountered EGL error %d %s during rendering", err, egl_error_str(err));
315}
316
317bool GlobalContext::enableDirtyRegions(EGLSurface surface) {
318    if (!mRequestDirtyRegions) return false;
319
320    if (mCanSetDirtyRegions) {
321        if (!eglSurfaceAttrib(mEglDisplay, surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED)) {
322            ALOGW("Failed to set EGL_SWAP_BEHAVIOR on surface %p, error=%s",
323                    (void*) surface, egl_error_str());
324            return false;
325        }
326        return true;
327    }
328    // Perhaps it is already enabled?
329    EGLint value;
330    if (!eglQuerySurface(mEglDisplay, surface, EGL_SWAP_BEHAVIOR, &value)) {
331        ALOGW("Failed to query EGL_SWAP_BEHAVIOR on surface %p, error=%p",
332                (void*) surface, egl_error_str());
333        return false;
334    }
335    return value == EGL_BUFFER_PRESERVED;
336}
337
338CanvasContext::CanvasContext(bool translucent, RenderNode* rootRenderNode)
339        : mRenderThread(RenderThread::getInstance())
340        , mEglSurface(EGL_NO_SURFACE)
341        , mDirtyRegionsEnabled(false)
342        , mOpaque(!translucent)
343        , mCanvas(0)
344        , mHaveNewSurface(false)
345        , mRootRenderNode(rootRenderNode) {
346    mGlobalContext = GlobalContext::get();
347}
348
349CanvasContext::~CanvasContext() {
350    destroyCanvasAndSurface();
351    mRenderThread.removeFrameCallback(this);
352}
353
354void CanvasContext::destroyCanvasAndSurface() {
355    if (mCanvas) {
356        delete mCanvas;
357        mCanvas = 0;
358    }
359    setSurface(NULL);
360}
361
362void CanvasContext::setSurface(EGLNativeWindowType window) {
363    if (mEglSurface != EGL_NO_SURFACE) {
364        mGlobalContext->destroySurface(mEglSurface);
365        mEglSurface = EGL_NO_SURFACE;
366    }
367
368    if (window) {
369        mEglSurface = mGlobalContext->createSurface(window);
370        LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE,
371                "Failed to create EGLSurface for window %p, eglErr = %s",
372                (void*) window, egl_error_str());
373    }
374
375    if (mEglSurface != EGL_NO_SURFACE) {
376        mDirtyRegionsEnabled = mGlobalContext->enableDirtyRegions(mEglSurface);
377        mHaveNewSurface = true;
378        makeCurrent();
379    } else {
380        mRenderThread.removeFrameCallback(this);
381    }
382}
383
384void CanvasContext::swapBuffers() {
385    mGlobalContext->swapBuffers(mEglSurface);
386    mHaveNewSurface = false;
387}
388
389void CanvasContext::requireSurface() {
390    LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE,
391            "requireSurface() called but no surface set!");
392    makeCurrent();
393}
394
395bool CanvasContext::initialize(EGLNativeWindowType window) {
396    if (mCanvas) return false;
397    setSurface(window);
398    mCanvas = new OpenGLRenderer();
399    mCanvas->initProperties();
400    return true;
401}
402
403void CanvasContext::updateSurface(EGLNativeWindowType window) {
404    setSurface(window);
405}
406
407void CanvasContext::pauseSurface(EGLNativeWindowType window) {
408    // TODO: For now we just need a fence, in the future suspend any animations
409    // and such to prevent from trying to render into this surface
410}
411
412void CanvasContext::setup(int width, int height) {
413    if (!mCanvas) return;
414    mCanvas->setViewport(width, height);
415}
416
417void CanvasContext::setOpaque(bool opaque) {
418    mOpaque = opaque;
419}
420
421void CanvasContext::makeCurrent() {
422    // TODO: Figure out why this workaround is needed, see b/13913604
423    // In the meantime this matches the behavior of GLRenderer, so it is not a regression
424    mHaveNewSurface |= mGlobalContext->makeCurrent(mEglSurface);
425}
426
427void CanvasContext::prepareDraw(const Vector<DeferredLayerUpdater*>* layerUpdaters,
428        TreeInfo& info) {
429    LOG_ALWAYS_FATAL_IF(!mCanvas, "Cannot prepareDraw without a canvas!");
430    makeCurrent();
431
432    processLayerUpdates(layerUpdaters, info);
433    if (info.out.hasAnimations) {
434        // TODO: Uh... crap?
435    }
436    prepareTree(info);
437}
438
439void CanvasContext::processLayerUpdates(const Vector<DeferredLayerUpdater*>* layerUpdaters,
440        TreeInfo& info) {
441    for (size_t i = 0; i < layerUpdaters->size(); i++) {
442        DeferredLayerUpdater* update = layerUpdaters->itemAt(i);
443        bool success = update->apply(info);
444        LOG_ALWAYS_FATAL_IF(!success, "Failed to update layer!");
445        if (update->backingLayer()->deferredUpdateScheduled) {
446            mCanvas->pushLayerUpdate(update->backingLayer());
447        }
448    }
449}
450
451void CanvasContext::prepareTree(TreeInfo& info) {
452    mRenderThread.removeFrameCallback(this);
453
454    info.frameTimeMs = mRenderThread.timeLord().frameTimeMs();
455    mRootRenderNode->prepareTree(info);
456
457    if (info.out.hasAnimations) {
458        if (info.out.hasFunctors) {
459            info.out.requiresUiRedraw = true;
460        } else if (!info.out.requiresUiRedraw) {
461            // If animationsNeedsRedraw is set don't bother posting for an RT anim
462            // as we will just end up fighting the UI thread.
463            mRenderThread.postFrameCallback(this);
464        }
465    }
466}
467
468void CanvasContext::draw(Rect* dirty) {
469    LOG_ALWAYS_FATAL_IF(!mCanvas || mEglSurface == EGL_NO_SURFACE,
470            "drawDisplayList called on a context with no canvas or surface!");
471
472    EGLint width, height;
473    mGlobalContext->beginFrame(mEglSurface, &width, &height);
474    if (width != mCanvas->getViewportWidth() || height != mCanvas->getViewportHeight()) {
475        mCanvas->setViewport(width, height);
476        dirty = NULL;
477    } else if (!mDirtyRegionsEnabled || mHaveNewSurface) {
478        dirty = NULL;
479    }
480
481    status_t status;
482    if (dirty && !dirty->isEmpty()) {
483        status = mCanvas->prepareDirty(dirty->left, dirty->top,
484                dirty->right, dirty->bottom, mOpaque);
485    } else {
486        status = mCanvas->prepare(mOpaque);
487    }
488
489    Rect outBounds;
490    status |= mCanvas->drawDisplayList(mRootRenderNode.get(), outBounds);
491
492    // TODO: Draw debug info
493    // TODO: Performance tracking
494
495    mCanvas->finish();
496
497    if (status & DrawGlInfo::kStatusDrew) {
498        swapBuffers();
499    }
500}
501
502// Called by choreographer to do an RT-driven animation
503void CanvasContext::doFrame() {
504    if (CC_UNLIKELY(!mCanvas || mEglSurface == EGL_NO_SURFACE)) {
505        return;
506    }
507
508    ATRACE_CALL();
509
510    TreeInfo info;
511    info.evaluateAnimations = true;
512    info.performStagingPush = false;
513    info.prepareTextures = false;
514
515    prepareTree(info);
516    draw(NULL);
517}
518
519void CanvasContext::invokeFunctor(Functor* functor) {
520    ATRACE_CALL();
521    DrawGlInfo::Mode mode = DrawGlInfo::kModeProcessNoContext;
522    if (mGlobalContext->hasContext()) {
523        requireGlContext();
524        mode = DrawGlInfo::kModeProcess;
525    }
526    (*functor)(mode, NULL);
527
528    if (mCanvas) {
529        mCanvas->resume();
530    }
531}
532
533bool CanvasContext::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) {
534    requireGlContext();
535    TreeInfo info;
536    layer->apply(info);
537    return LayerRenderer::copyLayer(layer->backingLayer(), bitmap);
538}
539
540void CanvasContext::runWithGlContext(RenderTask* task) {
541    requireGlContext();
542    task->run();
543}
544
545Layer* CanvasContext::createRenderLayer(int width, int height) {
546    requireSurface();
547    return LayerRenderer::createRenderLayer(width, height);
548}
549
550Layer* CanvasContext::createTextureLayer() {
551    requireSurface();
552    return LayerRenderer::createTextureLayer();
553}
554
555void CanvasContext::requireGlContext() {
556    if (mEglSurface != EGL_NO_SURFACE) {
557        makeCurrent();
558    } else {
559        mGlobalContext->usePBufferSurface();
560    }
561}
562
563void CanvasContext::setTextureAtlas(const sp<GraphicBuffer>& buffer,
564        int64_t* map, size_t mapSize) {
565    GlobalContext::get()->setTextureAtlas(buffer, map, mapSize);
566}
567
568} /* namespace renderthread */
569} /* namespace uirenderer */
570} /* namespace android */
571