| /* | |
| * 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 <EGL/egl.h> | |
| #include <EGL/eglext.h> | |
| #include <GLES2/gl2.h> | |
| #include <GLES2/gl2ext.h> | |
| #include <GLES3/gl3.h> | |
| #include <SkBitmap.h> | |
| #include <SkCanvas.h> | |
| #include <SkImage.h> | |
| #include <SkImageAndroid.h> | |
| #include <SkImageInfo.h> | |
| #include <SkRefCnt.h> | |
| #include <gui/TraceUtils.h> | |
| #include <include/gpu/ganesh/GrDirectContext.h> | |
| #include <include/gpu/ganesh/GrTypes.h> | |
| #include <utils/GLUtils.h> | |
| #include <utils/NdkUtils.h> | |
| #include <utils/Trace.h> | |
| #include <thread> | |
| #include "hwui/Bitmap.h" | |
| #include "renderthread/EglManager.h" | |
| #include "renderthread/VulkanManager.h" | |
| #include "thread/ThreadBase.h" | |
| #include "utils/TimeUtils.h" | |
| namespace android::uirenderer { | |
| static constexpr auto kThreadTimeout = 60000_ms; | |
| class AHBUploader; | |
| // This helper uploader classes allows us to upload using either EGL or Vulkan using the same | |
| // interface. | |
| static sp<AHBUploader> sUploader = nullptr; | |
| struct FormatInfo { | |
| AHardwareBuffer_Format bufferFormat; | |
| GLint format, type; | |
| VkFormat vkFormat; | |
| bool isSupported = false; | |
| bool valid = true; | |
| }; | |
| class AHBUploader : public RefBase { | |
| public: | |
| virtual ~AHBUploader() {} | |
| 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, | |
| AHardwareBuffer* ahb) { | |
| ATRACE_CALL(); | |
| beginUpload(); | |
| bool result = onUploadHardwareBitmap(bitmap, format, ahb); | |
| endUpload(); | |
| return result; | |
| } | |
| void postIdleTimeoutCheck() { | |
| mUploadThread->queue().postDelayed(kThreadTimeout, [this]() { this->idleTimeoutCheck(); }); | |
| } | |
| protected: | |
| std::mutex mLock; | |
| sp<ThreadBase> mUploadThread = nullptr; | |
| private: | |
| virtual void onIdle() = 0; | |
| virtual void onDestroy() = 0; | |
| virtual bool onUploadHardwareBitmap(const SkBitmap& bitmap, const FormatInfo& format, | |
| AHardwareBuffer* ahb) = 0; | |
| virtual void onBeginUpload() = 0; | |
| bool shouldTimeOutLocked() { | |
| nsecs_t durationSince = systemTime() - mLastUpload; | |
| return durationSince > kThreadTimeout; | |
| } | |
| void idleTimeoutCheck() { | |
| std::lock_guard _lock{mLock}; | |
| if (mPendingUploads == 0 && shouldTimeOutLocked()) { | |
| onIdle(); | |
| } else { | |
| this->postIdleTimeoutCheck(); | |
| } | |
| } | |
| void beginUpload() { | |
| std::lock_guard _lock{mLock}; | |
| mPendingUploads++; | |
| 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 | |
| class EGLUploader : public AHBUploader { | |
| private: | |
| void onDestroy() override { | |
| mEglManager.destroy(); | |
| } | |
| void onIdle() override { | |
| mEglManager.destroy(); | |
| } | |
| void onBeginUpload() override { | |
| if (!mEglManager.hasEglContext()) { | |
| mUploadThread->queue().runSync([this]() { | |
| this->mEglManager.initialize(); | |
| glPixelStorei(GL_UNPACK_ALIGNMENT, 1); | |
| }); | |
| this->postIdleTimeoutCheck(); | |
| } | |
| } | |
| 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, | |
| AHardwareBuffer* ahb) 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 buffer | |
| // The EGL image is later bound to a 2D texture | |
| const EGLClientBuffer clientBuffer = eglGetNativeClientBufferANDROID(ahb); | |
| 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; | |
| } | |
| { | |
| 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); | |
| if (GLUtils::dumpGLErrors()) { | |
| return EGL_NO_SYNC_KHR; | |
| } | |
| // 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()); | |
| if (GLUtils::dumpGLErrors()) { | |
| return EGL_NO_SYNC_KHR; | |
| } | |
| EGLSyncKHR uploadFence = | |
| eglCreateSyncKHR(eglGetCurrentDisplay(), EGL_SYNC_FENCE_KHR, NULL); | |
| if (uploadFence == EGL_NO_SYNC_KHR) { | |
| ALOGW("Could not create sync fence %#x", eglGetError()); | |
| }; | |
| glFlush(); | |
| GLUtils::dumpGLErrors(); | |
| return uploadFence; | |
| }); | |
| if (fence == EGL_NO_SYNC_KHR) { | |
| return false; | |
| } | |
| EGLint waitStatus = eglClientWaitSyncKHR(display, fence, 0, FENCE_TIMEOUT); | |
| ALOGE_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 onDestroy() override { | |
| std::lock_guard _lock{mVkLock}; | |
| mGrContext.reset(); | |
| mVulkanManagerStrong.clear(); | |
| } | |
| void onIdle() override { | |
| onDestroy(); | |
| } | |
| void onBeginUpload() override {} | |
| bool onUploadHardwareBitmap(const SkBitmap& bitmap, const FormatInfo& format, | |
| AHardwareBuffer* ahb) override { | |
| bool uploadSucceeded = false; | |
| mUploadThread->queue().runSync([this, &uploadSucceeded, bitmap, ahb]() { | |
| ATRACE_CALL(); | |
| std::lock_guard _lock{mVkLock}; | |
| renderthread::VulkanManager* vkManager = getVulkanManager(); | |
| if (!vkManager->hasVkContext()) { | |
| LOG_ALWAYS_FATAL_IF(mGrContext, | |
| "GrContext exists with no VulkanManager for vulkan uploads"); | |
| vkManager->initialize(); | |
| } | |
| if (!mGrContext) { | |
| GrContextOptions options; | |
| mGrContext = vkManager->createContext(options, | |
| renderthread::VulkanManager::ContextType::kUploadThread); | |
| LOG_ALWAYS_FATAL_IF(!mGrContext, "failed to create GrContext for vulkan uploads"); | |
| this->postIdleTimeoutCheck(); | |
| } | |
| sk_sp<SkImage> image = | |
| SkImages::TextureFromAHardwareBufferWithData(mGrContext.get(), bitmap.pixmap(), | |
| ahb); | |
| mGrContext->submit(GrSyncCpu::kYes); | |
| uploadSucceeded = (image.get() != nullptr); | |
| }); | |
| return uploadSucceeded; | |
| } | |
| /* must be called on the upload thread after the vkLock has been acquired */ | |
| renderthread::VulkanManager* getVulkanManager() { | |
| if (!mVulkanManagerStrong) { | |
| mVulkanManagerStrong = mVulkanManagerWeak.promote(); | |
| // create a new manager if we couldn't promote the weak ref | |
| if (!mVulkanManagerStrong) { | |
| mVulkanManagerStrong = renderthread::VulkanManager::getInstance(); | |
| mGrContext.reset(); | |
| mVulkanManagerWeak = mVulkanManagerStrong; | |
| } | |
| } | |
| return mVulkanManagerStrong.get(); | |
| } | |
| sk_sp<GrDirectContext> mGrContext; | |
| sp<renderthread::VulkanManager> mVulkanManagerStrong; | |
| wp<renderthread::VulkanManager> mVulkanManagerWeak; | |
| std::mutex mVkLock; | |
| }; | |
| static bool checkSupport(AHardwareBuffer_Format format) { | |
| AHardwareBuffer_Desc desc = { | |
| .width = 1, | |
| .height = 1, | |
| .layers = 1, | |
| .format = format, | |
| .usage = AHARDWAREBUFFER_USAGE_CPU_READ_NEVER | AHARDWAREBUFFER_USAGE_CPU_WRITE_NEVER | | |
| AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE, | |
| }; | |
| UniqueAHardwareBuffer buffer = allocateAHardwareBuffer(desc); | |
| return buffer != nullptr; | |
| } | |
| bool HardwareBitmapUploader::hasFP16Support() { | |
| static bool hasFP16Support = checkSupport(AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT); | |
| return hasFP16Support; | |
| } | |
| bool HardwareBitmapUploader::has1010102Support() { | |
| static bool has101012Support = checkSupport(AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM); | |
| return has101012Support; | |
| } | |
| bool HardwareBitmapUploader::has10101010Support() { | |
| static bool has1010110Support = checkSupport(AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM); | |
| return has1010110Support; | |
| } | |
| bool HardwareBitmapUploader::hasAlpha8Support() { | |
| static bool hasAlpha8Support = checkSupport(AHARDWAREBUFFER_FORMAT_R8_UNORM); | |
| return hasAlpha8Support; | |
| } | |
| static FormatInfo determineFormat(const SkBitmap& skBitmap, bool usingGL) { | |
| FormatInfo formatInfo; | |
| switch (skBitmap.info().colorType()) { | |
| case kRGBA_8888_SkColorType: | |
| formatInfo.isSupported = true; | |
| [[fallthrough]]; | |
| // ARGB_4444 is upconverted to RGBA_8888 | |
| case kARGB_4444_SkColorType: | |
| formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM; | |
| 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.bufferFormat = AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT; | |
| formatInfo.vkFormat = VK_FORMAT_R16G16B16A16_SFLOAT; | |
| } else { | |
| formatInfo.type = GL_UNSIGNED_BYTE; | |
| formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM; | |
| formatInfo.vkFormat = VK_FORMAT_R8G8B8A8_UNORM; | |
| } | |
| formatInfo.format = GL_RGBA; | |
| break; | |
| case kRGB_565_SkColorType: | |
| formatInfo.isSupported = true; | |
| formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM; | |
| 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 = usingGL; | |
| formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM; | |
| formatInfo.format = GL_LUMINANCE; | |
| formatInfo.type = GL_UNSIGNED_BYTE; | |
| formatInfo.vkFormat = VK_FORMAT_R8G8B8A8_UNORM; | |
| break; | |
| case kRGBA_1010102_SkColorType: | |
| formatInfo.isSupported = HardwareBitmapUploader::has1010102Support(); | |
| if (formatInfo.isSupported) { | |
| formatInfo.type = GL_UNSIGNED_INT_2_10_10_10_REV; | |
| formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM; | |
| formatInfo.vkFormat = VK_FORMAT_A2B10G10R10_UNORM_PACK32; | |
| } else { | |
| formatInfo.type = GL_UNSIGNED_BYTE; | |
| formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM; | |
| formatInfo.vkFormat = VK_FORMAT_R8G8B8A8_UNORM; | |
| } | |
| formatInfo.format = GL_RGBA; | |
| break; | |
| case kRGBA_10x6_SkColorType: | |
| formatInfo.isSupported = HardwareBitmapUploader::has10101010Support(); | |
| if (formatInfo.isSupported) { | |
| formatInfo.type = 0; // Not supported in GL | |
| formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM; | |
| formatInfo.vkFormat = VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16; | |
| } else { | |
| formatInfo.type = GL_UNSIGNED_BYTE; | |
| formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM; | |
| formatInfo.vkFormat = VK_FORMAT_R8G8B8A8_UNORM; | |
| } | |
| formatInfo.format = 0; // Not supported in GL | |
| break; | |
| case kAlpha_8_SkColorType: | |
| formatInfo.isSupported = HardwareBitmapUploader::hasAlpha8Support(); | |
| if (formatInfo.isSupported) { | |
| formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R8_UNORM; | |
| formatInfo.format = GL_RED; | |
| formatInfo.type = GL_UNSIGNED_BYTE; | |
| formatInfo.vkFormat = VK_FORMAT_R8_UNORM; | |
| } else { | |
| formatInfo.type = GL_UNSIGNED_BYTE; | |
| formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM; | |
| formatInfo.vkFormat = VK_FORMAT_R8G8B8A8_UNORM; | |
| formatInfo.format = GL_RGBA; | |
| } | |
| 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; | |
| bitmap.allocPixels(source.info().makeColorType(kN32_SkColorType)); | |
| bitmap.writePixels(source.pixmap()); | |
| return bitmap; | |
| } | |
| } | |
| 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(); | |
| bool usingGL = uirenderer::Properties::getRenderPipelineType() == | |
| uirenderer::RenderPipelineType::SkiaGL; | |
| FormatInfo format = determineFormat(sourceBitmap, usingGL); | |
| if (!format.valid) { | |
| return nullptr; | |
| } | |
| SkBitmap bitmap = makeHwCompatible(format, sourceBitmap); | |
| AHardwareBuffer_Desc desc = { | |
| .width = static_cast<uint32_t>(bitmap.width()), | |
| .height = static_cast<uint32_t>(bitmap.height()), | |
| .layers = 1, | |
| .format = format.bufferFormat, | |
| .usage = AHARDWAREBUFFER_USAGE_CPU_READ_NEVER | AHARDWAREBUFFER_USAGE_CPU_WRITE_NEVER | | |
| AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE, | |
| }; | |
| UniqueAHardwareBuffer ahb = allocateAHardwareBuffer(desc); | |
| if (!ahb) { | |
| ALOGW("allocateHardwareBitmap() failed in AHardwareBuffer_allocate()"); | |
| return nullptr; | |
| }; | |
| createUploader(usingGL); | |
| if (!sUploader->uploadHardwareBitmap(bitmap, format, ahb.get())) { | |
| return nullptr; | |
| } | |
| return Bitmap::createFrom(ahb.get(), bitmap.colorType(), bitmap.refColorSpace(), | |
| bitmap.alphaType(), Bitmap::computePalette(bitmap)); | |
| } | |
| void HardwareBitmapUploader::initialize() { | |
| bool usingGL = uirenderer::Properties::getRenderPipelineType() == | |
| uirenderer::RenderPipelineType::SkiaGL; | |
| createUploader(usingGL); | |
| } | |
| void HardwareBitmapUploader::terminate() { | |
| if (sUploader) { | |
| sUploader->destroy(); | |
| } | |
| } | |
| } // namespace android::uirenderer |