diff options
Diffstat (limited to 'libs/hwui/HardwareBitmapUploader.cpp')
-rw-r--r-- | libs/hwui/HardwareBitmapUploader.cpp | 292 |
1 files changed, 292 insertions, 0 deletions
diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp new file mode 100644 index 000000000000..6408ce6c04f4 --- /dev/null +++ b/libs/hwui/HardwareBitmapUploader.cpp @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2018 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. + */ + +#include "HardwareBitmapUploader.h" + +#include "hwui/Bitmap.h" +#include "renderthread/EglManager.h" +#include "thread/ThreadBase.h" +#include "utils/TimeUtils.h" + +#include <EGL/eglext.h> +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> +#include <GLES3/gl3.h> +#include <SkCanvas.h> +#include <utils/GLUtils.h> +#include <utils/Trace.h> +#include <utils/TraceUtils.h> +#include <thread> + +namespace android::uirenderer { + +static std::mutex sLock{}; +static ThreadBase* sUploadThread = nullptr; +static renderthread::EglManager sEglManager; +static int sPendingUploads = 0; +static nsecs_t sLastUpload = 0; + +static bool shouldTimeOutLocked() { + nsecs_t durationSince = systemTime() - sLastUpload; + return durationSince > 2000_ms; +} + +static void checkIdleTimeout() { + std::lock_guard{sLock}; + if (sPendingUploads == 0 && shouldTimeOutLocked()) { + sEglManager.destroy(); + } else { + sUploadThread->queue().postDelayed(5000_ms, checkIdleTimeout); + } +} + +static void beginUpload() { + std::lock_guard{sLock}; + sPendingUploads++; + + if (!sUploadThread) { + sUploadThread = new ThreadBase{}; + } + + if (!sUploadThread->isRunning()) { + sUploadThread->start("GrallocUploadThread"); + } + + if (!sEglManager.hasEglContext()) { + sUploadThread->queue().runSync([]() { + sEglManager.initialize(); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + }); + sUploadThread->queue().postDelayed(5000_ms, checkIdleTimeout); + } +} + +static void endUpload() { + std::lock_guard{sLock}; + sPendingUploads--; + sLastUpload = systemTime(); +} + +static EGLDisplay getUploadEglDisplay() { + std::lock_guard{sLock}; + LOG_ALWAYS_FATAL_IF(!sEglManager.hasEglContext(), "Forgot to begin an upload?"); + return sEglManager.eglDisplay(); +} + +static bool hasFP16Support() { + static std::once_flag sOnce; + static bool hasFP16Support = false; + + // Gralloc shouldn't let us create a USAGE_HW_TEXTURE if GLES is unable to consume it, so + // we don't need to double-check the GLES version/extension. + std::call_once(sOnce, []() { + sp<GraphicBuffer> buffer = new GraphicBuffer(1, 1, PIXEL_FORMAT_RGBA_FP16, + GraphicBuffer::USAGE_HW_TEXTURE | + GraphicBuffer::USAGE_SW_WRITE_NEVER | + GraphicBuffer::USAGE_SW_READ_NEVER, + "tempFp16Buffer"); + status_t error = buffer->initCheck(); + hasFP16Support = !error; + }); + + return hasFP16Support; +} + +#define FENCE_TIMEOUT 2000000000 + +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; +}; + +struct FormatInfo { + PixelFormat pixelFormat; + GLint format, type; + bool isSupported = false; + bool valid = true; +}; + +static FormatInfo determineFormat(const SkBitmap& skBitmap) { + FormatInfo formatInfo; + // TODO: add support for linear blending (when ANDROID_ENABLE_LINEAR_BLENDING is defined) + switch (skBitmap.info().colorType()) { + case kRGBA_8888_SkColorType: + formatInfo.isSupported = true; + // ARGB_4444 is upconverted to RGBA_8888 + case kARGB_4444_SkColorType: + formatInfo.pixelFormat = PIXEL_FORMAT_RGBA_8888; + formatInfo.format = GL_RGBA; + formatInfo.type = GL_UNSIGNED_BYTE; + break; + case kRGBA_F16_SkColorType: + formatInfo.isSupported = hasFP16Support(); + if (formatInfo.isSupported) { + formatInfo.type = GL_HALF_FLOAT; + formatInfo.pixelFormat = PIXEL_FORMAT_RGBA_FP16; + } else { + formatInfo.type = GL_UNSIGNED_BYTE; + formatInfo.pixelFormat = PIXEL_FORMAT_RGBA_8888; + } + formatInfo.format = GL_RGBA; + break; + case kRGB_565_SkColorType: + formatInfo.isSupported = true; + formatInfo.pixelFormat = PIXEL_FORMAT_RGB_565; + formatInfo.format = GL_RGB; + formatInfo.type = GL_UNSIGNED_SHORT_5_6_5; + break; + case kGray_8_SkColorType: + formatInfo.isSupported = true; + formatInfo.pixelFormat = PIXEL_FORMAT_RGBA_8888; + formatInfo.format = GL_LUMINANCE; + formatInfo.type = GL_UNSIGNED_BYTE; + break; + default: + ALOGW("unable to create hardware bitmap of colortype: %d", skBitmap.info().colorType()); + formatInfo.valid = false; + } + return formatInfo; +} + +static SkBitmap makeHwCompatible(const FormatInfo& format, const SkBitmap& source) { + if (format.isSupported) { + return source; + } else { + SkBitmap bitmap; + const SkImageInfo& info = source.info(); + 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 + source.readPixels(bitmap.info().makeColorSpace(SkColorSpace::MakeSRGB()), + bitmap.getPixels(), bitmap.rowBytes(), 0, 0); + } else { + SkCanvas canvas(bitmap); + canvas.drawBitmap(source, 0.0f, 0.0f, nullptr); + } + return bitmap; + } +} + +class ScopedUploadRequest { +public: + ScopedUploadRequest() { beginUpload(); } + ~ScopedUploadRequest() { endUpload(); } +}; + +sk_sp<Bitmap> HardwareBitmapUploader::allocateHardwareBitmap(const SkBitmap& sourceBitmap) { + ATRACE_CALL(); + + FormatInfo format = determineFormat(sourceBitmap); + 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()) + + "]"); + + status_t error = buffer->initCheck(); + if (error < 0) { + ALOGW("createGraphicBuffer() failed in GraphicBuffer.create()"); + return nullptr; + } + + 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)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; + } + + { + 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 sk_sp<Bitmap>(new Bitmap(buffer.get(), bitmap.info())); +} + +}; // namespace android::uirenderer |