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

Commit 7bc3bc60 authored by Stan Iliev's avatar Stan Iliev
Browse files

Implement HW Bitmap for Skia pipeline

Implement HW Bitmap for Skia pipeline. Use new Skia
SkImage::MakeFromAHardwareBuffer API, which will enable to
record HW Bitmap into a picture. Move logic that uploads
SkBitmap into a GraphicBuffer into pipeline specific classes.

Test: All CTS and other tests pass for HWUI pipleine. For Skia
pipeline graphics CTS tests pass, 2 UIRendering CTS tests which
excise HW bitmaps with color spaces fail, bitmapShaderEglImage
macrobench fails (to be fixed by a CL in Skia), HWUI unit tests
pass, no EGL leaks found.

Change-Id: Id5926d7cccd81af8b55400f44fb524a427543d05
parent a9861756
Loading
Loading
Loading
Loading
+6 −3
Original line number Diff line number Diff line
@@ -63,14 +63,17 @@ static jlong Shader_getNativeFinalizer(JNIEnv*, jobject) {
static jlong BitmapShader_constructor(JNIEnv* env, jobject o, jlong matrixPtr, jobject jbitmap,
        jint tileModeX, jint tileModeY) {
    const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
    SkBitmap bitmap;
    sk_sp<SkImage> image;
    if (jbitmap) {
        // Only pass a valid SkBitmap object to the constructor if the Bitmap exists. Otherwise,
        // we'll pass an empty SkBitmap to avoid crashing/excepting for compatibility.
        android::bitmap::toBitmap(env, jbitmap).getSkBitmapForShaders(&bitmap);
        image = android::bitmap::toBitmap(env, jbitmap).makeImage();
    }

    sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(bitmap, kNever_SkCopyPixelsMode);
    if (!image.get()) {
        SkBitmap bitmap;
        image = SkMakeImageFromRasterBitmap(bitmap, kNever_SkCopyPixelsMode);
    }
    sk_sp<SkShader> baseShader = image->makeShader(
            (SkShader::TileMode)tileModeX, (SkShader::TileMode)tileModeY);

+23 −180
Original line number Diff line number Diff line
@@ -26,16 +26,12 @@
#include <log/log.h>
#include <cutils/ashmem.h>

#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>

#include <private/gui/ComposerService.h>
#include <binder/IServiceManager.h>
#include <ui/PixelFormat.h>

#include <SkCanvas.h>
#include <SkImagePriv.h>

namespace android {

@@ -89,171 +85,6 @@ static sk_sp<Bitmap> allocateHeapBitmap(size_t size, const SkImageInfo& info, si
    return sk_sp<Bitmap>(new Bitmap(addr, size, info, rowBytes, std::move(ctable)));
}

#define FENCE_TIMEOUT 2000000000

// TODO: handle SRGB sanely
static PixelFormat internalFormatToPixelFormat(GLint internalFormat) {
    switch (internalFormat) {
    case GL_LUMINANCE:
        return PIXEL_FORMAT_RGBA_8888;
    case GL_SRGB8_ALPHA8:
        return PIXEL_FORMAT_RGBA_8888;
    case GL_RGBA:
        return PIXEL_FORMAT_RGBA_8888;
    case GL_RGB:
        return PIXEL_FORMAT_RGB_565;
    case GL_RGBA16F:
        return PIXEL_FORMAT_RGBA_FP16;
    default:
        LOG_ALWAYS_FATAL("Unsupported bitmap colorType: %d", internalFormat);
        return PIXEL_FORMAT_UNKNOWN;
    }
}

class AutoEglFence {
public:
    AutoEglFence(EGLDisplay display)
            : mDisplay(display) {
        fence = eglCreateSyncKHR(mDisplay, EGL_SYNC_FENCE_KHR, NULL);
    }

    ~AutoEglFence() {
        if (fence != EGL_NO_SYNC_KHR) {
            eglDestroySyncKHR(mDisplay, fence);
        }
    }

    EGLSyncKHR fence = EGL_NO_SYNC_KHR;
private:
    EGLDisplay mDisplay = EGL_NO_DISPLAY;
};

class AutoEglImage {
public:
    AutoEglImage(EGLDisplay display, EGLClientBuffer clientBuffer)
            : mDisplay(display) {
        EGLint imageAttrs[] = { EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE };
        image = eglCreateImageKHR(display, EGL_NO_CONTEXT,
                EGL_NATIVE_BUFFER_ANDROID, clientBuffer, imageAttrs);
    }

    ~AutoEglImage() {
        if (image != EGL_NO_IMAGE_KHR) {
            eglDestroyImageKHR(mDisplay, image);
        }
    }

    EGLImageKHR image = EGL_NO_IMAGE_KHR;
private:
    EGLDisplay mDisplay = EGL_NO_DISPLAY;
};

class AutoGlTexture {
public:
    AutoGlTexture(uirenderer::Caches& caches)
            : mCaches(caches) {
        glGenTextures(1, &mTexture);
        caches.textureState().bindTexture(mTexture);
    }

    ~AutoGlTexture() {
        mCaches.textureState().deleteTexture(mTexture);
    }

private:
    uirenderer::Caches& mCaches;
    GLuint mTexture = 0;
};

static bool uploadBitmapToGraphicBuffer(uirenderer::Caches& caches, SkBitmap& bitmap,
        GraphicBuffer& buffer, GLint format, GLint type) {
    EGLDisplay display = eglGetCurrentDisplay();
    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());
        return false;
    }
    AutoGlTexture glTexture(caches);
    glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, autoImage.image);

    GL_CHECKPOINT(MODERATE);

    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bitmap.width(), bitmap.height(),
            format, type, bitmap.getPixels());

    GL_CHECKPOINT(MODERATE);

    // The fence is used to wait for the texture upload to finish
    // properly. We cannot rely on glFlush() and glFinish() as
    // some drivers completely ignore these API calls
    AutoEglFence autoFence(display);
    if (autoFence.fence == EGL_NO_SYNC_KHR) {
        LOG_ALWAYS_FATAL("Could not create sync fence %#x", eglGetError());
        return false;
    }
    // The flag EGL_SYNC_FLUSH_COMMANDS_BIT_KHR will trigger a
    // pipeline flush (similar to what a glFlush() would do.)
    EGLint waitStatus = eglClientWaitSyncKHR(display, autoFence.fence,
            EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, FENCE_TIMEOUT);
    if (waitStatus != EGL_CONDITION_SATISFIED_KHR) {
        LOG_ALWAYS_FATAL("Failed to wait for the fence %#x", eglGetError());
        return false;
    }
    return true;
}

sk_sp<Bitmap> Bitmap::allocateHardwareBitmap(uirenderer::renderthread::RenderThread& renderThread,
        SkBitmap& skBitmap) {
    renderThread.eglManager().initialize();
    uirenderer::Caches& caches = uirenderer::Caches::getInstance();

    const SkImageInfo& info = skBitmap.info();
    if (info.colorType() == kUnknown_SkColorType || info.colorType() == kAlpha_8_SkColorType) {
        ALOGW("unable to create hardware bitmap of colortype: %d", info.colorType());
        return nullptr;
    }

    bool needSRGB = uirenderer::transferFunctionCloseToSRGB(skBitmap.info().colorSpace());
    bool hasLinearBlending = caches.extensions().hasLinearBlending();
    GLint format, type, internalFormat;
    uirenderer::Texture::colorTypeToGlFormatAndType(caches, skBitmap.colorType(),
            needSRGB && hasLinearBlending, &internalFormat, &format, &type);

    PixelFormat pixelFormat = internalFormatToPixelFormat(internalFormat);
    sp<GraphicBuffer> buffer = new GraphicBuffer(info.width(), info.height(), pixelFormat,
            GraphicBuffer::USAGE_HW_TEXTURE |
            GraphicBuffer::USAGE_SW_WRITE_NEVER |
            GraphicBuffer::USAGE_SW_READ_NEVER,
            std::string("Bitmap::allocateHardwareBitmap pid [") + std::to_string(getpid()) + "]");

    status_t error = buffer->initCheck();
    if (error < 0) {
        ALOGW("createGraphicBuffer() failed in GraphicBuffer.create()");
        return nullptr;
    }

    SkBitmap bitmap;
    if (CC_UNLIKELY(uirenderer::Texture::hasUnsupportedColorType(skBitmap.info(),
            hasLinearBlending))) {
        sk_sp<SkColorSpace> sRGB = SkColorSpace::MakeSRGB();
        bitmap = uirenderer::Texture::uploadToN32(skBitmap, hasLinearBlending, std::move(sRGB));
    } else {
        bitmap = skBitmap;
    }

    if (!uploadBitmapToGraphicBuffer(caches, bitmap, *buffer, format, type)) {
        return nullptr;
    }
    return sk_sp<Bitmap>(new Bitmap(buffer.get(), bitmap.info()));
}

sk_sp<Bitmap> Bitmap::allocateHardwareBitmap(SkBitmap& bitmap) {
    return uirenderer::renderthread::RenderProxy::allocateHardwareBitmap(bitmap);
}
@@ -392,6 +223,12 @@ Bitmap::Bitmap(GraphicBuffer* buffer, const SkImageInfo& info)
        , mPixelStorageType(PixelStorageType::Hardware) {
    mPixelStorage.hardware.buffer = buffer;
    buffer->incStrong(buffer);
    setImmutable(); // HW bitmaps are always immutable
    if (uirenderer::Properties::isSkiaEnabled()) {
        // TODO: add color correctness for Skia pipeline - pass null color space for now
        mImage = SkImage::MakeFromAHardwareBuffer(reinterpret_cast<AHardwareBuffer*>(buffer),
                mInfo.alphaType(), nullptr);
    }
}

Bitmap::~Bitmap() {
@@ -486,16 +323,6 @@ void Bitmap::getSkBitmap(SkBitmap* outBitmap) {
    outBitmap->setPixelRef(sk_ref_sp(this), 0, 0);
}

void Bitmap::getSkBitmapForShaders(SkBitmap* outBitmap) {
    if (isHardware() && uirenderer::Properties::isSkiaEnabled()) {
        getSkBitmap(outBitmap);
    } else {
        outBitmap->setInfo(info(), rowBytes());
        outBitmap->setPixelRef(sk_ref_sp(this), 0, 0);
        outBitmap->setHasHardwareMipMap(mHasHardwareMipMap);
    }
}

void Bitmap::getBounds(SkRect* bounds) const {
    SkASSERT(bounds);
    bounds->set(0, 0, SkIntToScalar(width()), SkIntToScalar(height()));
@@ -508,4 +335,20 @@ GraphicBuffer* Bitmap::graphicBuffer() {
    return nullptr;
}

sk_sp<SkImage> Bitmap::makeImage() {
    sk_sp<SkImage> image = mImage;
    if (!image) {
        SkASSERT(!(isHardware() && uirenderer::Properties::isSkiaEnabled()));
        SkBitmap skiaBitmap;
        skiaBitmap.setInfo(info(), rowBytes());
        skiaBitmap.setPixelRef(sk_ref_sp(this), 0, 0);
        skiaBitmap.setHasHardwareMipMap(mHasHardwareMipMap);
        // Note we don't cache in this case, because the raster image holds a pointer to this Bitmap
        // internally and ~Bitmap won't be invoked.
        // TODO: refactor Bitmap to not derive from SkPixelRef, which would allow caching here.
        image = SkMakeImageFromRasterBitmap(skiaBitmap, kNever_SkCopyPixelsMode);
    }
    return image;
}

} // namespace android
+9 −8
Original line number Diff line number Diff line
@@ -18,10 +18,12 @@
#include <SkBitmap.h>
#include <SkColorSpace.h>
#include <SkColorTable.h>
#include <SkImage.h>
#include <SkImageInfo.h>
#include <SkPixelRef.h>
#include <cutils/compiler.h>
#include <ui/GraphicBuffer.h>
#include <SkImage.h>

namespace android {

@@ -57,15 +59,13 @@ public:

    static sk_sp<Bitmap> createFrom(const SkImageInfo&, SkPixelRef&);

    static sk_sp<Bitmap> allocateHardwareBitmap(uirenderer::renderthread::RenderThread&,
            SkBitmap& bitmap);

    Bitmap(void* address, size_t allocSize, const SkImageInfo& info, size_t rowBytes,
            sk_sp<SkColorTable> ctable);
    Bitmap(void* address, void* context, FreeFunc freeFunc,
            const SkImageInfo& info, size_t rowBytes, sk_sp<SkColorTable> ctable);
    Bitmap(void* address, int fd, size_t mappedSize, const SkImageInfo& info,
            size_t rowBytes, sk_sp<SkColorTable> ctable);
    Bitmap(GraphicBuffer* buffer, const SkImageInfo& info);

    int rowBytesAsPixels() const {
        return rowBytes() >> SkColorTypeShiftPerPixel(mInfo.colorType());
@@ -78,10 +78,6 @@ public:

    void getSkBitmap(SkBitmap* outBitmap);

    // Ugly hack: in case of hardware bitmaps, it sets nullptr as pixels pointer
    // so it would crash if anyone tries to render this bitmap.
    void getSkBitmapForShaders(SkBitmap* outBitmap);

    int getAshmemFd() const;
    size_t getAllocationByteCount() const;

@@ -105,8 +101,11 @@ public:
    }

    GraphicBuffer* graphicBuffer();

    // makeImage creates or returns a cached SkImage. Can be invoked from UI or render thread.
    // Caching is supported only for HW Bitmaps with skia pipeline.
    sk_sp<SkImage> makeImage();
private:
    Bitmap(GraphicBuffer* buffer, const SkImageInfo& info);
    virtual ~Bitmap();
    void* getStorage() const;

@@ -135,6 +134,8 @@ private:
            GraphicBuffer* buffer;
        } hardware;
    } mPixelStorage;

    sk_sp<SkImage> mImage; // Cache is used only for HW Bitmaps with Skia pipeline.
};

} //namespace android
+181 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

#include "SkiaOpenGLPipeline.h"

#include "hwui/Bitmap.h"
#include "DeferredLayerUpdater.h"
#include "GlLayer.h"
#include "LayerDrawable.h"
@@ -197,6 +198,186 @@ void SkiaOpenGLPipeline::invokeFunctor(const RenderThread& thread, Functor* func
    }
}

#define FENCE_TIMEOUT 2000000000

class AutoEglFence {
public:
    AutoEglFence(EGLDisplay display)
            : mDisplay(display) {
        fence = eglCreateSyncKHR(mDisplay, EGL_SYNC_FENCE_KHR, NULL);
    }

    ~AutoEglFence() {
        if (fence != EGL_NO_SYNC_KHR) {
            eglDestroySyncKHR(mDisplay, fence);
        }
    }

    EGLSyncKHR fence = EGL_NO_SYNC_KHR;
private:
    EGLDisplay mDisplay = EGL_NO_DISPLAY;
};

class AutoEglImage {
public:
    AutoEglImage(EGLDisplay display, EGLClientBuffer clientBuffer)
            : mDisplay(display) {
        EGLint imageAttrs[] = { EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE };
        image = eglCreateImageKHR(display, EGL_NO_CONTEXT,
                EGL_NATIVE_BUFFER_ANDROID, clientBuffer, imageAttrs);
    }

    ~AutoEglImage() {
        if (image != EGL_NO_IMAGE_KHR) {
            eglDestroyImageKHR(mDisplay, image);
        }
    }

    EGLImageKHR image = EGL_NO_IMAGE_KHR;
private:
    EGLDisplay mDisplay = EGL_NO_DISPLAY;
};

class AutoSkiaGlTexture {
public:
    AutoSkiaGlTexture() {
        glGenTextures(1, &mTexture);
        glBindTexture(GL_TEXTURE_2D, mTexture);
    }

    ~AutoSkiaGlTexture() {
        glDeleteTextures(1, &mTexture);
    }

private:
    GLuint mTexture = 0;
};

sk_sp<Bitmap> SkiaOpenGLPipeline::allocateHardwareBitmap(renderthread::RenderThread& renderThread,
        SkBitmap& skBitmap) {
    renderThread.eglManager().initialize();

    sk_sp<GrContext> grContext = sk_ref_sp(renderThread.getGrContext());
    const SkImageInfo& info = skBitmap.info();
    PixelFormat pixelFormat;
    GLint format, type;
    bool isSupported = false;

    //TODO: add support for linear blending (when ANDROID_ENABLE_LINEAR_BLENDING is defined)
    switch (info.colorType()) {
    case kRGBA_8888_SkColorType:
        isSupported = true;
    // ARGB_4444 and Index_8 are both upconverted to RGBA_8888
    case kIndex_8_SkColorType:
    case kARGB_4444_SkColorType:
        pixelFormat = PIXEL_FORMAT_RGBA_8888;
        format = GL_RGBA;
        type = GL_UNSIGNED_BYTE;
        break;
    case kRGBA_F16_SkColorType:
        isSupported = grContext->caps()->isConfigTexturable(kRGBA_half_GrPixelConfig);
        if (isSupported) {
            type = GL_HALF_FLOAT;
            pixelFormat = PIXEL_FORMAT_RGBA_FP16;
        } else {
            type = GL_UNSIGNED_BYTE;
            pixelFormat = PIXEL_FORMAT_RGBA_8888;
        }
        format = GL_RGBA;
        break;
    case kRGB_565_SkColorType:
        isSupported = true;
        pixelFormat = PIXEL_FORMAT_RGB_565;
        format = GL_RGB;
        type = GL_UNSIGNED_SHORT_5_6_5;
        break;
    case kGray_8_SkColorType:
        isSupported = true;
        pixelFormat = PIXEL_FORMAT_RGBA_8888;
        format = GL_LUMINANCE;
        type = GL_UNSIGNED_BYTE;
        break;
    default:
        ALOGW("unable to create hardware bitmap of colortype: %d", info.colorType());
        return nullptr;
    }

    SkBitmap bitmap;
    if (isSupported) {
        bitmap = skBitmap;
    } else {
        bitmap.allocPixels(SkImageInfo::MakeN32(info.width(), info.height(), info.alphaType(),
                nullptr));
        bitmap.eraseColor(0);
        if (info.colorType() == kRGBA_F16_SkColorType) {
            // Drawing RGBA_F16 onto ARGB_8888 is not supported
            skBitmap.readPixels(bitmap.info().makeColorSpace(SkColorSpace::MakeSRGB()),
                    bitmap.getPixels(), bitmap.rowBytes(), 0, 0);
        } else {
            SkCanvas canvas(bitmap);
            canvas.drawBitmap(skBitmap, 0.0f, 0.0f, nullptr);
        }
    }

    sp<GraphicBuffer> buffer = new GraphicBuffer(info.width(), info.height(), pixelFormat,
            GraphicBuffer::USAGE_HW_TEXTURE |
            GraphicBuffer::USAGE_SW_WRITE_NEVER |
            GraphicBuffer::USAGE_SW_READ_NEVER,
            std::string("Bitmap::allocateSkiaHardwareBitmap pid [") + std::to_string(getpid()) + "]");

    status_t error = buffer->initCheck();
    if (error < 0) {
        ALOGW("createGraphicBuffer() failed in GraphicBuffer.create()");
        return nullptr;
    }

    //upload the bitmap into a texture
    EGLDisplay display = eglGetCurrentDisplay();
    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());
        return nullptr;
    }
    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, info.width(), info.height(), format, type,
            bitmap.getPixels());
    GL_CHECKPOINT(MODERATE);

    // The fence is used to wait for the texture upload to finish
    // properly. We cannot rely on glFlush() and glFinish() as
    // some drivers completely ignore these API calls
    AutoEglFence autoFence(display);
    if (autoFence.fence == EGL_NO_SYNC_KHR) {
        LOG_ALWAYS_FATAL("Could not create sync fence %#x", eglGetError());
        return nullptr;
    }
    // The flag EGL_SYNC_FLUSH_COMMANDS_BIT_KHR will trigger a
    // pipeline flush (similar to what a glFlush() would do.)
    EGLint waitStatus = eglClientWaitSyncKHR(display, autoFence.fence,
            EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, FENCE_TIMEOUT);
    if (waitStatus != EGL_CONDITION_SATISFIED_KHR) {
        LOG_ALWAYS_FATAL("Failed to wait for the fence %#x", eglGetError());
        return nullptr;
    }

    grContext->resetContext(kTextureBinding_GrGLBackendState);

    return sk_sp<Bitmap>(new Bitmap(buffer.get(), bitmap.info()));
}

} /* namespace skiapipeline */
} /* namespace uirenderer */
} /* namespace android */
+5 −0
Original line number Diff line number Diff line
@@ -19,6 +19,9 @@
#include "SkiaPipeline.h"

namespace android {

class Bitmap;

namespace uirenderer {
namespace skiapipeline {

@@ -46,6 +49,8 @@ public:
    bool isContextReady() override;

    static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor);
    static sk_sp<Bitmap> allocateHardwareBitmap(renderthread::RenderThread& thread,
            SkBitmap& skBitmap);

private:
    renderthread::EglManager& mEglManager;
Loading