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

Commit 92eee2c7 authored by Greg Daniel's avatar Greg Daniel Committed by Android (Google) Code Review
Browse files

Merge "Add support for uploading to AHBs using Vulkan."

parents 166e9e4b c073252f
Loading
Loading
Loading
Loading
+267 −111
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@

#include "hwui/Bitmap.h"
#include "renderthread/EglManager.h"
#include "renderthread/VulkanManager.h"
#include "thread/ThreadBase.h"
#include "utils/TimeUtils.h"

@@ -25,7 +26,9 @@
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <GLES3/gl3.h>
#include <GrContext.h>
#include <SkCanvas.h>
#include <SkImage.h>
#include <utils/GLUtils.h>
#include <utils/Trace.h>
#include <utils/TraceUtils.h>
@@ -33,59 +36,248 @@

namespace android::uirenderer {

static std::mutex sLock{};
static sp<ThreadBase> sUploadThread = nullptr;
static renderthread::EglManager sEglManager;
static int sPendingUploads = 0;
static nsecs_t sLastUpload = 0;
class AHBUploader;
// This helper uploader classes allows us to upload using either EGL or Vulkan using the same
// interface.
static sp<AHBUploader> sUploader = nullptr;

static bool shouldTimeOutLocked() {
    nsecs_t durationSince = systemTime() - sLastUpload;
struct FormatInfo {
    PixelFormat pixelFormat;
    GLint format, type;
    VkFormat vkFormat;
    bool isSupported = false;
    bool valid = true;
};

class AHBUploader : public RefBase {
public:
    virtual ~AHBUploader() {}

    // Called to start creation of the Vulkan and EGL contexts on another thread before we actually
    // need to do an upload.
    void initialize() {
        onInitialize();
    }

    void destroy() {
        std::lock_guard _lock{mLock};
        LOG_ALWAYS_FATAL_IF(mPendingUploads, "terminate called while uploads in progress");
        if (mUploadThread) {
            mUploadThread->requestExit();
            mUploadThread->join();
            mUploadThread = nullptr;
        }
        onDestroy();
    }

    bool uploadHardwareBitmap(const SkBitmap& bitmap, const FormatInfo& format,
                              sp<GraphicBuffer> graphicBuffer) {
        ATRACE_CALL();
        beginUpload();
        bool result = onUploadHardwareBitmap(bitmap, format, graphicBuffer);
        endUpload();
        return result;
    }

    void postIdleTimeoutCheck() {
        mUploadThread->queue().postDelayed(5000_ms, [this](){ this->idleTimeoutCheck(); });
    }

protected:
    std::mutex mLock;
    sp<ThreadBase> mUploadThread = nullptr;

private:
    virtual void onInitialize() = 0;
    virtual void onIdle() = 0;
    virtual void onDestroy() = 0;

    virtual bool onUploadHardwareBitmap(const SkBitmap& bitmap, const FormatInfo& format,
                                        sp<GraphicBuffer> graphicBuffer) = 0;
    virtual void onBeginUpload() = 0;

    bool shouldTimeOutLocked() {
        nsecs_t durationSince = systemTime() - mLastUpload;
        return durationSince > 2000_ms;
    }

static void checkIdleTimeout() {
    std::lock_guard _lock{sLock};
    if (sPendingUploads == 0 && shouldTimeOutLocked()) {
        sEglManager.destroy();
    void idleTimeoutCheck() {
        std::lock_guard _lock{mLock};
        if (mPendingUploads == 0 && shouldTimeOutLocked()) {
            onIdle();
        } else {
        sUploadThread->queue().postDelayed(5000_ms, checkIdleTimeout);
            this->postIdleTimeoutCheck();
        }
    }

static void beginUpload() {
    std::lock_guard _lock{sLock};
    sPendingUploads++;
    void beginUpload() {
        std::lock_guard _lock{mLock};
        mPendingUploads++;

    if (!sUploadThread) {
        sUploadThread = new ThreadBase{};
        if (!mUploadThread) {
            mUploadThread = new ThreadBase{};
        }
        if (!mUploadThread->isRunning()) {
            mUploadThread->start("GrallocUploadThread");
        }

        onBeginUpload();
    }

    void endUpload() {
        std::lock_guard _lock{mLock};
        mPendingUploads--;
        mLastUpload = systemTime();
    }

    int mPendingUploads = 0;
    nsecs_t mLastUpload = 0;
};

#define FENCE_TIMEOUT 2000000000

    if (!sUploadThread->isRunning()) {
        sUploadThread->start("GrallocUploadThread");
class EGLUploader : public AHBUploader {
private:
    void onInitialize() override {}
    void onDestroy() override {
        mEglManager.destroy();
    }
    void onIdle() override {
        mEglManager.destroy();
    }

    if (!sEglManager.hasEglContext()) {
        sUploadThread->queue().runSync([]() {
            sEglManager.initialize();
    void onBeginUpload() override {
        if (!mEglManager.hasEglContext()) {
            mUploadThread->queue().runSync([this]() {
                this->mEglManager.initialize();
                glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
            });
        sUploadThread->queue().postDelayed(5000_ms, checkIdleTimeout);

            this->postIdleTimeoutCheck();
        }
    }

static void endUpload() {
    std::lock_guard _lock{sLock};
    sPendingUploads--;
    sLastUpload = systemTime();

    EGLDisplay getUploadEglDisplay() {
        std::lock_guard _lock{mLock};
        LOG_ALWAYS_FATAL_IF(!mEglManager.hasEglContext(), "Forgot to begin an upload?");
        return mEglManager.eglDisplay();
    }

    bool onUploadHardwareBitmap(const SkBitmap& bitmap, const FormatInfo& format,
                                sp<GraphicBuffer> graphicBuffer) override {
        ATRACE_CALL();

        EGLDisplay display = getUploadEglDisplay();

        LOG_ALWAYS_FATAL_IF(display == EGL_NO_DISPLAY, "Failed to get EGL_DEFAULT_DISPLAY! err=%s",
                            uirenderer::renderthread::EglManager::eglErrorString());
        // We use an EGLImage to access the content of the GraphicBuffer
        // The EGL image is later bound to a 2D texture
        EGLClientBuffer clientBuffer = (EGLClientBuffer)graphicBuffer->getNativeBuffer();
        AutoEglImage autoImage(display, clientBuffer);
        if (autoImage.image == EGL_NO_IMAGE_KHR) {
            ALOGW("Could not create EGL image, err =%s",
                  uirenderer::renderthread::EglManager::eglErrorString());
            return false;
        }

static EGLDisplay getUploadEglDisplay() {
    std::lock_guard _lock{sLock};
    LOG_ALWAYS_FATAL_IF(!sEglManager.hasEglContext(), "Forgot to begin an upload?");
    return sEglManager.eglDisplay();
        {
            ATRACE_FORMAT("CPU -> gralloc transfer (%dx%d)", bitmap.width(), bitmap.height());
            EGLSyncKHR fence = mUploadThread->queue().runSync([&]() -> EGLSyncKHR {
                AutoSkiaGlTexture glTexture;
                glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, autoImage.image);
                GL_CHECKPOINT(MODERATE);

                // glTexSubImage2D is synchronous in sense that it memcpy() from pointer that we
                // provide.
                // But asynchronous in sense that driver may upload texture onto hardware buffer
                // when we first use it in drawing
                glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bitmap.width(), bitmap.height(),
                                format.format, format.type, bitmap.getPixels());
                GL_CHECKPOINT(MODERATE);

                EGLSyncKHR uploadFence =
                        eglCreateSyncKHR(eglGetCurrentDisplay(), EGL_SYNC_FENCE_KHR, NULL);
                LOG_ALWAYS_FATAL_IF(uploadFence == EGL_NO_SYNC_KHR,
                                    "Could not create sync fence %#x", eglGetError());
                glFlush();
                return uploadFence;
            });

            EGLint waitStatus = eglClientWaitSyncKHR(display, fence, 0, FENCE_TIMEOUT);
            LOG_ALWAYS_FATAL_IF(waitStatus != EGL_CONDITION_SATISFIED_KHR,
                                "Failed to wait for the fence %#x", eglGetError());

            eglDestroySyncKHR(display, fence);
        }
        return true;
    }

    renderthread::EglManager mEglManager;
};

class VkUploader : public AHBUploader {
private:
    void onInitialize() override {
        std::lock_guard _lock{mLock};
        if (!mUploadThread) {
            mUploadThread = new ThreadBase{};
        }
        if (!mUploadThread->isRunning()) {
            mUploadThread->start("GrallocUploadThread");
        }

        mUploadThread->queue().post([this]() {
            std::lock_guard _lock{mVkLock};
            if (!mVulkanManager.hasVkContext()) {
                mVulkanManager.initialize();
            }
        });
    }
    void onDestroy() override {
        mGrContext.reset();
        mVulkanManager.destroy();
    }
    void onIdle() override {
        mGrContext.reset();
    }

    void onBeginUpload() override {
        {
            std::lock_guard _lock{mVkLock};
            if (!mVulkanManager.hasVkContext()) {
                LOG_ALWAYS_FATAL_IF(mGrContext,
                    "GrContext exists with no VulkanManager for vulkan uploads");
                mUploadThread->queue().runSync([this]() {
                    mVulkanManager.initialize();
                });
            }
        }
        if (!mGrContext) {
            GrContextOptions options;
            mGrContext = mVulkanManager.createContext(options);
            LOG_ALWAYS_FATAL_IF(!mGrContext, "failed to create GrContext for vulkan uploads");
            this->postIdleTimeoutCheck();
        }
    }

    bool onUploadHardwareBitmap(const SkBitmap& bitmap, const FormatInfo& format,
                                sp<GraphicBuffer> graphicBuffer) override {
        ATRACE_CALL();

        std::lock_guard _lock{mLock};

        sk_sp<SkImage> image = SkImage::MakeFromAHardwareBufferWithData(mGrContext.get(),
            bitmap.pixmap(), reinterpret_cast<AHardwareBuffer*>(graphicBuffer.get()));
        return (image.get() != nullptr);
    }

    sk_sp<GrContext> mGrContext;
    renderthread::VulkanManager mVulkanManager;
    std::mutex mVkLock;
};

bool HardwareBitmapUploader::hasFP16Support() {
    static std::once_flag sOnce;
    static bool hasFP16Support = false;
@@ -105,16 +297,7 @@ bool HardwareBitmapUploader::hasFP16Support() {
    return hasFP16Support;
}

#define FENCE_TIMEOUT 2000000000

struct FormatInfo {
    PixelFormat pixelFormat;
    GLint format, type;
    bool isSupported = false;
    bool valid = true;
};

static FormatInfo determineFormat(const SkBitmap& skBitmap) {
static FormatInfo determineFormat(const SkBitmap& skBitmap, bool usingGL) {
    FormatInfo formatInfo;
    switch (skBitmap.info().colorType()) {
        case kRGBA_8888_SkColorType:
@@ -124,15 +307,18 @@ static FormatInfo determineFormat(const SkBitmap& skBitmap) {
            formatInfo.pixelFormat = PIXEL_FORMAT_RGBA_8888;
            formatInfo.format = GL_RGBA;
            formatInfo.type = GL_UNSIGNED_BYTE;
            formatInfo.vkFormat = VK_FORMAT_R8G8B8A8_UNORM;
            break;
        case kRGBA_F16_SkColorType:
            formatInfo.isSupported = HardwareBitmapUploader::hasFP16Support();
            if (formatInfo.isSupported) {
                formatInfo.type = GL_HALF_FLOAT;
                formatInfo.pixelFormat = PIXEL_FORMAT_RGBA_FP16;
                formatInfo.vkFormat = VK_FORMAT_R16G16B16A16_SFLOAT;
            } else {
                formatInfo.type = GL_UNSIGNED_BYTE;
                formatInfo.pixelFormat = PIXEL_FORMAT_RGBA_8888;
                formatInfo.vkFormat = VK_FORMAT_R8G8B8A8_UNORM;
            }
            formatInfo.format = GL_RGBA;
            break;
@@ -141,12 +327,14 @@ static FormatInfo determineFormat(const SkBitmap& skBitmap) {
            formatInfo.pixelFormat = PIXEL_FORMAT_RGB_565;
            formatInfo.format = GL_RGB;
            formatInfo.type = GL_UNSIGNED_SHORT_5_6_5;
            formatInfo.vkFormat = VK_FORMAT_R5G6B5_UNORM_PACK16;
            break;
        case kGray_8_SkColorType:
            formatInfo.isSupported = true;
            formatInfo.isSupported = usingGL;
            formatInfo.pixelFormat = PIXEL_FORMAT_RGBA_8888;
            formatInfo.format = GL_LUMINANCE;
            formatInfo.type = GL_UNSIGNED_BYTE;
            formatInfo.vkFormat = VK_FORMAT_R8G8B8A8_UNORM;
            break;
        default:
            ALOGW("unable to create hardware bitmap of colortype: %d", skBitmap.info().colorType());
@@ -172,29 +360,37 @@ static SkBitmap makeHwCompatible(const FormatInfo& format, const SkBitmap& sourc
    }
}

class ScopedUploadRequest {
public:
    ScopedUploadRequest() { beginUpload(); }
    ~ScopedUploadRequest() { endUpload(); }
};

static void createUploader(bool usingGL) {
    static std::mutex lock;
    std::lock_guard _lock{lock};
    if (!sUploader.get()) {
        if (usingGL) {
            sUploader = new EGLUploader();
        } else {
            sUploader = new VkUploader();
        }
    }
}

sk_sp<Bitmap> HardwareBitmapUploader::allocateHardwareBitmap(const SkBitmap& sourceBitmap) {
    ATRACE_CALL();

    FormatInfo format = determineFormat(sourceBitmap);
    bool usingGL = uirenderer::Properties::getRenderPipelineType() ==
            uirenderer::RenderPipelineType::SkiaGL;

    FormatInfo format = determineFormat(sourceBitmap, usingGL);
    if (!format.valid) {
        return nullptr;
    }

    ScopedUploadRequest _uploadRequest{};

    SkBitmap bitmap = makeHwCompatible(format, sourceBitmap);
    sp<GraphicBuffer> buffer = new GraphicBuffer(
            static_cast<uint32_t>(bitmap.width()), static_cast<uint32_t>(bitmap.height()),
            format.pixelFormat,
            GraphicBuffer::USAGE_HW_TEXTURE | GraphicBuffer::USAGE_SW_WRITE_NEVER |
                    GraphicBuffer::USAGE_SW_READ_NEVER,
            std::string("Bitmap::allocateSkiaHardwareBitmap pid [") + std::to_string(getpid()) +
            std::string("Bitmap::allocateHardwareBitmap pid [") + std::to_string(getpid()) +
                    "]");

    status_t error = buffer->initCheck();
@@ -203,64 +399,24 @@ sk_sp<Bitmap> HardwareBitmapUploader::allocateHardwareBitmap(const SkBitmap& sou
        return nullptr;
    }

    EGLDisplay display = getUploadEglDisplay();
    createUploader(usingGL);

    LOG_ALWAYS_FATAL_IF(display == EGL_NO_DISPLAY, "Failed to get EGL_DEFAULT_DISPLAY! err=%s",
                        uirenderer::renderthread::EglManager::eglErrorString());
    // We use an EGLImage to access the content of the GraphicBuffer
    // The EGL image is later bound to a 2D texture
    EGLClientBuffer clientBuffer = (EGLClientBuffer)buffer->getNativeBuffer();
    AutoEglImage autoImage(display, clientBuffer);
    if (autoImage.image == EGL_NO_IMAGE_KHR) {
        ALOGW("Could not create EGL image, err =%s",
              uirenderer::renderthread::EglManager::eglErrorString());
    if (!sUploader->uploadHardwareBitmap(bitmap, format, buffer)) {
        return nullptr;
    }

    {
        ATRACE_FORMAT("CPU -> gralloc transfer (%dx%d)", bitmap.width(), bitmap.height());
        EGLSyncKHR fence = sUploadThread->queue().runSync([&]() -> EGLSyncKHR {
            AutoSkiaGlTexture glTexture;
            glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, autoImage.image);
            GL_CHECKPOINT(MODERATE);

            // glTexSubImage2D is synchronous in sense that it memcpy() from pointer that we
            // provide.
            // But asynchronous in sense that driver may upload texture onto hardware buffer when we
            // first
            // use it in drawing
            glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bitmap.width(), bitmap.height(), format.format,
                            format.type, bitmap.getPixels());
            GL_CHECKPOINT(MODERATE);

            EGLSyncKHR uploadFence =
                    eglCreateSyncKHR(eglGetCurrentDisplay(), EGL_SYNC_FENCE_KHR, NULL);
            LOG_ALWAYS_FATAL_IF(uploadFence == EGL_NO_SYNC_KHR, "Could not create sync fence %#x",
                                eglGetError());
            glFlush();
            return uploadFence;
        });

        EGLint waitStatus = eglClientWaitSyncKHR(display, fence, 0, FENCE_TIMEOUT);
        LOG_ALWAYS_FATAL_IF(waitStatus != EGL_CONDITION_SATISFIED_KHR,
                            "Failed to wait for the fence %#x", eglGetError());

        eglDestroySyncKHR(display, fence);
    return Bitmap::createFrom(buffer, bitmap.colorType(), bitmap.refColorSpace(),
                              bitmap.alphaType(), Bitmap::computePalette(bitmap));
}

    return Bitmap::createFrom(buffer.get(), bitmap.colorType(), bitmap.refColorSpace(),
                              bitmap.alphaType(), Bitmap::computePalette(bitmap));
void HardwareBitmapUploader::initialize() {
    bool usingGL = uirenderer::Properties::getRenderPipelineType() ==
            uirenderer::RenderPipelineType::SkiaGL;
    createUploader(usingGL);
    sUploader->initialize();
}

void HardwareBitmapUploader::terminate() {
    std::lock_guard _lock{sLock};
    LOG_ALWAYS_FATAL_IF(sPendingUploads, "terminate called while uploads in progress");
    if (sUploadThread) {
        sUploadThread->requestExit();
        sUploadThread->join();
        sUploadThread = nullptr;
    }
    sEglManager.destroy();
    sUploader->destroy();
}

}  // namespace android::uirenderer
+3 −1
Original line number Diff line number Diff line
@@ -22,9 +22,11 @@ namespace android::uirenderer {

class ANDROID_API HardwareBitmapUploader {
public:
    static sk_sp<Bitmap> allocateHardwareBitmap(const SkBitmap& sourceBitmap);
    static void initialize();
    static void terminate();

    static sk_sp<Bitmap> allocateHardwareBitmap(const SkBitmap& sourceBitmap);

    static bool hasFP16Support();
};

+2 −0
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@
#include "utils/FatVector.h"
#include "utils/TimeUtils.h"
#include "utils/TraceUtils.h"
#include "../HardwareBitmapUploader.h"

#ifdef HWUI_GLES_WRAP_ENABLED
#include "debug/GlesDriver.h"
@@ -415,6 +416,7 @@ void RenderThread::preload() {
    if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) {
        requireVkContext();
    }
    HardwareBitmapUploader::initialize();
}

} /* namespace renderthread */