Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 16a9940a authored by Alec Mouri's avatar Alec Mouri
Browse files

[RenderEngine] add an ImageManager thread

Prior to this change, EGLImage management would be performed by both
binder threads and the main rendering thread in a synchronized manner.
But because of BufferQueue implementation details, it's possible for an
app's Surface to be disconnected (destroying all backing EGLImages per
the disconnect api spec), while still latching and presenting the most
recently queued image (which requires recreating the EGLImage on the
main thread), which can cause jank in some scenarios such as app-launch.

To mitigate this, defer EGLImage creation and destruction to a separate
thread behind RenderEngine so that creating the EGLImage should never be
done on the main thread. So scenarios such as app-launch avoid jank by
performing the EGLImage creation asynchronously with the main thread, so
that the creation is done by the time RenderEngine needs to start
rendering.

Bug: 136806342
Bug: 137191934
Test: Launching photos app shows no bindExternalImage* calls
during the rendering path
Test: librenderengine_test
Change-Id: Ib809e36267b5604bbbedd4089d15666b22cabc95
parent 4ede27d7
Loading
Loading
Loading
Loading
+2 −0
Original line number Original line Diff line number Diff line
@@ -26,6 +26,7 @@ cc_defaults {
        "libgui",
        "libgui",
        "liblog",
        "liblog",
        "libnativewindow",
        "libnativewindow",
        "libprocessgroup",
        "libsync",
        "libsync",
        "libui",
        "libui",
        "libutils",
        "libutils",
@@ -51,6 +52,7 @@ filegroup {
        "gl/GLExtensions.cpp",
        "gl/GLExtensions.cpp",
        "gl/GLFramebuffer.cpp",
        "gl/GLFramebuffer.cpp",
        "gl/GLImage.cpp",
        "gl/GLImage.cpp",
        "gl/ImageManager.cpp",
        "gl/Program.cpp",
        "gl/Program.cpp",
        "gl/ProgramCache.cpp",
        "gl/ProgramCache.cpp",
    ],
    ],
+67 −13
Original line number Original line Diff line number Diff line
@@ -19,9 +19,8 @@
#define LOG_TAG "RenderEngine"
#define LOG_TAG "RenderEngine"
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
#define ATRACE_TAG ATRACE_TAG_GRAPHICS


#include "GLESRenderEngine.h"
#include <sched.h>

#include <cmath>
#include <math.h>
#include <fstream>
#include <fstream>
#include <sstream>
#include <sstream>
#include <unordered_set>
#include <unordered_set>
@@ -43,6 +42,7 @@
#include <ui/Region.h>
#include <ui/Region.h>
#include <utils/KeyedVector.h>
#include <utils/KeyedVector.h>
#include <utils/Trace.h>
#include <utils/Trace.h>
#include "GLESRenderEngine.h"
#include "GLExtensions.h"
#include "GLExtensions.h"
#include "GLFramebuffer.h"
#include "GLFramebuffer.h"
#include "GLImage.h"
#include "GLImage.h"
@@ -423,10 +423,13 @@ GLESRenderEngine::GLESRenderEngine(uint32_t featureFlags, EGLDisplay display, EG
        mTraceGpuCompletion = true;
        mTraceGpuCompletion = true;
        mFlushTracer = std::make_unique<FlushTracer>(this);
        mFlushTracer = std::make_unique<FlushTracer>(this);
    }
    }
    mImageManager = std::make_unique<ImageManager>(this);
    mDrawingBuffer = createFramebuffer();
    mDrawingBuffer = createFramebuffer();
}
}


GLESRenderEngine::~GLESRenderEngine() {
GLESRenderEngine::~GLESRenderEngine() {
    // Destroy the image manager first.
    mImageManager = nullptr;
    std::lock_guard<std::mutex> lock(mRenderingMutex);
    std::lock_guard<std::mutex> lock(mRenderingMutex);
    unbindFrameBuffer(mDrawingBuffer.get());
    unbindFrameBuffer(mDrawingBuffer.get());
    mDrawingBuffer = nullptr;
    mDrawingBuffer = nullptr;
@@ -619,19 +622,41 @@ void GLESRenderEngine::bindExternalTextureImage(uint32_t texName, const Image& i
status_t GLESRenderEngine::bindExternalTextureBuffer(uint32_t texName,
status_t GLESRenderEngine::bindExternalTextureBuffer(uint32_t texName,
                                                     const sp<GraphicBuffer>& buffer,
                                                     const sp<GraphicBuffer>& buffer,
                                                     const sp<Fence>& bufferFence) {
                                                     const sp<Fence>& bufferFence) {
    if (buffer == nullptr) {
        return BAD_VALUE;
    }

    ATRACE_CALL();
    ATRACE_CALL();
    status_t cacheResult = cacheExternalTextureBuffer(buffer);


    bool found = false;
    {
        std::lock_guard<std::mutex> lock(mRenderingMutex);
        auto cachedImage = mImageCache.find(buffer->getId());
        found = (cachedImage != mImageCache.end());
    }

    // If we couldn't find the image in the cache at this time, then either
    // SurfaceFlinger messed up registering the buffer ahead of time or we got
    // backed up creating other EGLImages.
    if (!found) {
        status_t cacheResult = mImageManager->cache(buffer);
        if (cacheResult != NO_ERROR) {
        if (cacheResult != NO_ERROR) {
            return cacheResult;
            return cacheResult;
        }
        }
    }


    // Whether or not we needed to cache, re-check mImageCache to make sure that
    // there's an EGLImage. The current threading model guarantees that we don't
    // destroy a cached image until it's really not needed anymore (i.e. this
    // function should not be called), so the only possibility is that something
    // terrible went wrong and we should just bind something and move on.
    {
    {
        std::lock_guard<std::mutex> lock(mRenderingMutex);
        std::lock_guard<std::mutex> lock(mRenderingMutex);
        auto cachedImage = mImageCache.find(buffer->getId());
        auto cachedImage = mImageCache.find(buffer->getId());


        if (cachedImage == mImageCache.end()) {
        if (cachedImage == mImageCache.end()) {
            // We failed creating the image if we got here, so bail out.
            // We failed creating the image if we got here, so bail out.
            ALOGE("Failed to create an EGLImage when rendering");
            bindExternalTextureImage(texName, *createImage());
            bindExternalTextureImage(texName, *createImage());
            return NO_INIT;
            return NO_INIT;
        }
        }
@@ -663,7 +688,18 @@ status_t GLESRenderEngine::bindExternalTextureBuffer(uint32_t texName,
    return NO_ERROR;
    return NO_ERROR;
}
}


status_t GLESRenderEngine::cacheExternalTextureBuffer(const sp<GraphicBuffer>& buffer) {
void GLESRenderEngine::cacheExternalTextureBuffer(const sp<GraphicBuffer>& buffer) {
    mImageManager->cacheAsync(buffer, nullptr);
}

std::shared_ptr<ImageManager::Barrier> GLESRenderEngine::cacheExternalTextureBufferForTesting(
        const sp<GraphicBuffer>& buffer) {
    auto barrier = std::make_shared<ImageManager::Barrier>();
    mImageManager->cacheAsync(buffer, barrier);
    return barrier;
}

status_t GLESRenderEngine::cacheExternalTextureBufferInternal(const sp<GraphicBuffer>& buffer) {
    if (buffer == nullptr) {
    if (buffer == nullptr) {
        return BAD_VALUE;
        return BAD_VALUE;
    }
    }
@@ -703,13 +739,31 @@ status_t GLESRenderEngine::cacheExternalTextureBuffer(const sp<GraphicBuffer>& b
}
}


void GLESRenderEngine::unbindExternalTextureBuffer(uint64_t bufferId) {
void GLESRenderEngine::unbindExternalTextureBuffer(uint64_t bufferId) {
    mImageManager->releaseAsync(bufferId, nullptr);
}

std::shared_ptr<ImageManager::Barrier> GLESRenderEngine::unbindExternalTextureBufferForTesting(
        uint64_t bufferId) {
    auto barrier = std::make_shared<ImageManager::Barrier>();
    mImageManager->releaseAsync(bufferId, barrier);
    return barrier;
}

void GLESRenderEngine::unbindExternalTextureBufferInternal(uint64_t bufferId) {
    std::unique_ptr<Image> image;
    {
        std::lock_guard<std::mutex> lock(mRenderingMutex);
        std::lock_guard<std::mutex> lock(mRenderingMutex);
        const auto& cachedImage = mImageCache.find(bufferId);
        const auto& cachedImage = mImageCache.find(bufferId);

        if (cachedImage != mImageCache.end()) {
        if (cachedImage != mImageCache.end()) {
            ALOGV("Destroying image for buffer: %" PRIu64, bufferId);
            ALOGV("Destroying image for buffer: %" PRIu64, bufferId);
            // Move the buffer out of cache first, so that we can destroy
            // without holding the cache's lock.
            image = std::move(cachedImage->second);
            mImageCache.erase(bufferId);
            mImageCache.erase(bufferId);
            return;
            return;
        }
        }
    }
    ALOGV("Failed to find image for buffer: %" PRIu64, bufferId);
    ALOGV("Failed to find image for buffer: %" PRIu64, bufferId);
}
}


+14 −3
Original line number Original line Diff line number Diff line
@@ -17,9 +17,7 @@
#ifndef SF_GLESRENDERENGINE_H_
#ifndef SF_GLESRENDERENGINE_H_
#define SF_GLESRENDERENGINE_H_
#define SF_GLESRENDERENGINE_H_


#include <android-base/thread_annotations.h>
#include <stdint.h>
#include <stdint.h>
#include <sys/types.h>
#include <condition_variable>
#include <condition_variable>
#include <deque>
#include <deque>
#include <mutex>
#include <mutex>
@@ -30,8 +28,11 @@
#include <EGL/egl.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2.h>
#include <android-base/thread_annotations.h>
#include <renderengine/RenderEngine.h>
#include <renderengine/RenderEngine.h>
#include <renderengine/private/Description.h>
#include <renderengine/private/Description.h>
#include <sys/types.h>
#include "ImageManager.h"


#define EGL_NO_CONFIG ((EGLConfig)0)
#define EGL_NO_CONFIG ((EGLConfig)0)


@@ -74,7 +75,7 @@ public:
    void bindExternalTextureImage(uint32_t texName, const Image& image) override;
    void bindExternalTextureImage(uint32_t texName, const Image& image) override;
    status_t bindExternalTextureBuffer(uint32_t texName, const sp<GraphicBuffer>& buffer,
    status_t bindExternalTextureBuffer(uint32_t texName, const sp<GraphicBuffer>& buffer,
                                       const sp<Fence>& fence) EXCLUDES(mRenderingMutex);
                                       const sp<Fence>& fence) EXCLUDES(mRenderingMutex);
    status_t cacheExternalTextureBuffer(const sp<GraphicBuffer>& buffer) EXCLUDES(mRenderingMutex);
    void cacheExternalTextureBuffer(const sp<GraphicBuffer>& buffer) EXCLUDES(mRenderingMutex);
    void unbindExternalTextureBuffer(uint64_t bufferId) EXCLUDES(mRenderingMutex);
    void unbindExternalTextureBuffer(uint64_t bufferId) EXCLUDES(mRenderingMutex);
    status_t bindFrameBuffer(Framebuffer* framebuffer) override;
    status_t bindFrameBuffer(Framebuffer* framebuffer) override;
    void unbindFrameBuffer(Framebuffer* framebuffer) override;
    void unbindFrameBuffer(Framebuffer* framebuffer) override;
@@ -101,6 +102,11 @@ public:
    // Returns true iff mFramebufferImageCache contains an image keyed by bufferId
    // Returns true iff mFramebufferImageCache contains an image keyed by bufferId
    bool isFramebufferImageCachedForTesting(uint64_t bufferId)
    bool isFramebufferImageCachedForTesting(uint64_t bufferId)
            EXCLUDES(mFramebufferImageCacheMutex);
            EXCLUDES(mFramebufferImageCacheMutex);
    // These are wrappers around public methods above, but exposing Barrier
    // objects so that tests can block.
    std::shared_ptr<ImageManager::Barrier> cacheExternalTextureBufferForTesting(
            const sp<GraphicBuffer>& buffer);
    std::shared_ptr<ImageManager::Barrier> unbindExternalTextureBufferForTesting(uint64_t bufferId);


protected:
protected:
    Framebuffer* getFramebufferForDrawing() override;
    Framebuffer* getFramebufferForDrawing() override;
@@ -147,6 +153,9 @@ private:
    void setScissor(const Rect& region);
    void setScissor(const Rect& region);
    void disableScissor();
    void disableScissor();
    bool waitSync(EGLSyncKHR sync, EGLint flags);
    bool waitSync(EGLSyncKHR sync, EGLint flags);
    status_t cacheExternalTextureBufferInternal(const sp<GraphicBuffer>& buffer)
            EXCLUDES(mRenderingMutex);
    void unbindExternalTextureBufferInternal(uint64_t bufferId) EXCLUDES(mRenderingMutex);


    // A data space is considered HDR data space if it has BT2020 color space
    // A data space is considered HDR data space if it has BT2020 color space
    // with PQ or HLG transfer function.
    // with PQ or HLG transfer function.
@@ -250,7 +259,9 @@ private:
        bool mRunning = true;
        bool mRunning = true;
    };
    };
    friend class FlushTracer;
    friend class FlushTracer;
    friend class ImageManager;
    std::unique_ptr<FlushTracer> mFlushTracer;
    std::unique_ptr<FlushTracer> mFlushTracer;
    std::unique_ptr<ImageManager> mImageManager = std::make_unique<ImageManager>(this);
};
};


} // namespace gl
} // namespace gl
+140 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define ATRACE_TAG ATRACE_TAG_GRAPHICS

#include <pthread.h>

#include <processgroup/sched_policy.h>
#include <utils/Trace.h>
#include "GLESRenderEngine.h"
#include "ImageManager.h"

namespace android {
namespace renderengine {
namespace gl {

ImageManager::ImageManager(GLESRenderEngine* engine) : mEngine(engine) {
    pthread_setname_np(mThread.native_handle(), "ImageManager");
    // Use SCHED_FIFO to minimize jitter
    struct sched_param param = {0};
    param.sched_priority = 2;
    if (pthread_setschedparam(mThread.native_handle(), SCHED_FIFO, &param) != 0) {
        ALOGE("Couldn't set SCHED_FIFO for ImageManager");
    }
}

ImageManager::~ImageManager() {
    {
        std::lock_guard<std::mutex> lock(mMutex);
        mRunning = false;
    }
    mCondition.notify_all();
    if (mThread.joinable()) {
        mThread.join();
    }
}

void ImageManager::cacheAsync(const sp<GraphicBuffer>& buffer,
                              const std::shared_ptr<Barrier>& barrier) {
    if (buffer == nullptr) {
        {
            std::lock_guard<std::mutex> lock(barrier->mutex);
            barrier->isOpen = true;
            barrier->result = BAD_VALUE;
        }
        barrier->condition.notify_one();
        return;
    }
    ATRACE_CALL();
    QueueEntry entry = {QueueEntry::Operation::Insert, buffer, buffer->getId(), barrier};
    queueOperation(std::move(entry));
}

status_t ImageManager::cache(const sp<GraphicBuffer>& buffer) {
    ATRACE_CALL();
    auto barrier = std::make_shared<Barrier>();
    cacheAsync(buffer, barrier);
    std::lock_guard<std::mutex> lock(barrier->mutex);
    barrier->condition.wait(barrier->mutex,
                            [&]() REQUIRES(barrier->mutex) { return barrier->isOpen; });
    return barrier->result;
}

void ImageManager::releaseAsync(uint64_t bufferId, const std::shared_ptr<Barrier>& barrier) {
    ATRACE_CALL();
    QueueEntry entry = {QueueEntry::Operation::Delete, nullptr, bufferId, barrier};
    queueOperation(std::move(entry));
}

void ImageManager::queueOperation(const QueueEntry&& entry) {
    {
        std::lock_guard<std::mutex> lock(mMutex);
        mQueue.emplace(entry);
        ATRACE_INT("ImageManagerQueueDepth", mQueue.size());
    }
    mCondition.notify_one();
}

void ImageManager::threadMain() {
    set_sched_policy(0, SP_FOREGROUND);
    bool run;
    {
        std::lock_guard<std::mutex> lock(mMutex);
        run = mRunning;
    }
    while (run) {
        QueueEntry entry;
        {
            std::lock_guard<std::mutex> lock(mMutex);
            mCondition.wait(mMutex,
                            [&]() REQUIRES(mMutex) { return !mQueue.empty() || !mRunning; });
            run = mRunning;

            if (!mRunning) {
                // if mRunning is false, then ImageManager is being destroyed, so
                // bail out now.
                break;
            }

            entry = mQueue.front();
            mQueue.pop();
            ATRACE_INT("ImageManagerQueueDepth", mQueue.size());
        }

        status_t result = NO_ERROR;
        switch (entry.op) {
            case QueueEntry::Operation::Delete:
                mEngine->unbindExternalTextureBufferInternal(entry.bufferId);
                break;
            case QueueEntry::Operation::Insert:
                result = mEngine->cacheExternalTextureBufferInternal(entry.buffer);
                break;
        }
        if (entry.barrier != nullptr) {
            {
                std::lock_guard<std::mutex> entryLock(entry.barrier->mutex);
                entry.barrier->result = result;
                entry.barrier->isOpen = true;
            }
            entry.barrier->condition.notify_one();
        }
    }
}

} // namespace gl
} // namespace renderengine
} // namespace android
+70 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

#include <condition_variable>
#include <mutex>
#include <queue>
#include <thread>

#include <ui/GraphicBuffer.h>

namespace android {
namespace renderengine {
namespace gl {

class GLESRenderEngine;

class ImageManager {
public:
    struct Barrier {
        std::mutex mutex;
        std::condition_variable_any condition;
        bool isOpen GUARDED_BY(mutex) = false;
        status_t result GUARDED_BY(mutex) = NO_ERROR;
    };
    ImageManager(GLESRenderEngine* engine);
    ~ImageManager();
    void cacheAsync(const sp<GraphicBuffer>& buffer, const std::shared_ptr<Barrier>& barrier)
            EXCLUDES(mMutex);
    status_t cache(const sp<GraphicBuffer>& buffer);
    void releaseAsync(uint64_t bufferId, const std::shared_ptr<Barrier>& barrier) EXCLUDES(mMutex);

private:
    struct QueueEntry {
        enum class Operation { Delete, Insert };

        Operation op = Operation::Delete;
        sp<GraphicBuffer> buffer = nullptr;
        uint64_t bufferId = 0;
        std::shared_ptr<Barrier> barrier = nullptr;
    };

    void queueOperation(const QueueEntry&& entry);
    void threadMain();
    GLESRenderEngine* const mEngine;
    std::thread mThread = std::thread([this]() { threadMain(); });
    std::condition_variable_any mCondition;
    std::mutex mMutex;
    std::queue<QueueEntry> mQueue GUARDED_BY(mMutex);

    bool mRunning GUARDED_BY(mMutex) = true;
};

} // namespace gl
} // namespace renderengine
} // namespace android
Loading