A better HW Bitmap uploader
Move all HW bitmap upload operations off of RenderThread. Ensure EGL context outlives all upload requests Bug: 79250950 Test: builds, boots, systrace is good, CTS bitmap tests pass Change-Id: I5ace6c516d33b1afdf1a407cd8b183f6b60c22c1
This commit is contained in:
@@ -206,6 +206,7 @@ cc_defaults {
|
||||
"FrameInfoVisualizer.cpp",
|
||||
"GlLayer.cpp",
|
||||
"GpuMemoryTracker.cpp",
|
||||
"HardwareBitmapUploader.cpp",
|
||||
"Interpolator.cpp",
|
||||
"JankTracker.cpp",
|
||||
"Layer.cpp",
|
||||
|
||||
@@ -44,9 +44,7 @@ Caches* Caches::sInstance = nullptr;
|
||||
// Constructors/destructor
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Caches::Caches(RenderState& renderState)
|
||||
: mRenderState(&renderState)
|
||||
, mInitialized(false) {
|
||||
Caches::Caches(RenderState& renderState) : mRenderState(&renderState), mInitialized(false) {
|
||||
INIT_LOGD("Creating OpenGL renderer caches");
|
||||
init();
|
||||
initConstraints();
|
||||
@@ -153,8 +151,7 @@ void Caches::dumpMemoryUsage(String8& log) {
|
||||
// Memory management
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Caches::clearGarbage() {
|
||||
}
|
||||
void Caches::clearGarbage() {}
|
||||
|
||||
void Caches::flush(FlushMode mode) {
|
||||
clearGarbage();
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
static constexpr android::DisplayInfo sDummyDisplay {
|
||||
static constexpr android::DisplayInfo sDummyDisplay{
|
||||
1080, // w
|
||||
1920, // h
|
||||
320.0, // xdpi
|
||||
|
||||
@@ -25,8 +25,7 @@
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
CopyResult EglReadback::copySurfaceInto(Surface& surface, const Rect& srcRect,
|
||||
SkBitmap* bitmap) {
|
||||
CopyResult EglReadback::copySurfaceInto(Surface& surface, const Rect& srcRect, SkBitmap* bitmap) {
|
||||
ATRACE_CALL();
|
||||
// Setup the source
|
||||
sp<GraphicBuffer> sourceBuffer;
|
||||
@@ -55,9 +54,8 @@ CopyResult EglReadback::copySurfaceInto(Surface& surface, const Rect& srcRect,
|
||||
return copyGraphicBufferInto(sourceBuffer.get(), texTransform, srcRect, bitmap);
|
||||
}
|
||||
|
||||
CopyResult EglReadback::copyGraphicBufferInto(GraphicBuffer* graphicBuffer,
|
||||
Matrix4& texTransform, const Rect& srcRect,
|
||||
SkBitmap* bitmap) {
|
||||
CopyResult EglReadback::copyGraphicBufferInto(GraphicBuffer* graphicBuffer, Matrix4& texTransform,
|
||||
const Rect& srcRect, SkBitmap* bitmap) {
|
||||
mRenderThread.requireGlContext();
|
||||
// TODO: Can't use Image helper since it forces GL_TEXTURE_2D usage via
|
||||
// GL_OES_EGL_image, which doesn't work since we need samplerExternalOES
|
||||
|
||||
292
libs/hwui/HardwareBitmapUploader.cpp
Normal file
292
libs/hwui/HardwareBitmapUploader.cpp
Normal file
@@ -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
|
||||
28
libs/hwui/HardwareBitmapUploader.h
Normal file
28
libs/hwui/HardwareBitmapUploader.h
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <hwui/Bitmap.h>
|
||||
|
||||
namespace android::uirenderer {
|
||||
|
||||
class HardwareBitmapUploader {
|
||||
public:
|
||||
static sk_sp<Bitmap> allocateHardwareBitmap(const SkBitmap& sourceBitmap);
|
||||
};
|
||||
|
||||
}; // namespace android::uirenderer
|
||||
@@ -19,9 +19,9 @@
|
||||
#include <GpuMemoryTracker.h>
|
||||
#include <utils/RefBase.h>
|
||||
|
||||
#include <SkBlendMode.h>
|
||||
#include <SkColorFilter.h>
|
||||
#include <SkColorSpace.h>
|
||||
#include <SkBlendMode.h>
|
||||
#include <SkPaint.h>
|
||||
|
||||
#include "Matrix.h"
|
||||
@@ -95,8 +95,7 @@ public:
|
||||
void postDecStrong();
|
||||
|
||||
protected:
|
||||
Layer(RenderState& renderState, Api api, sk_sp<SkColorFilter>, int alpha,
|
||||
SkBlendMode mode);
|
||||
Layer(RenderState& renderState, Api api, sk_sp<SkColorFilter>, int alpha, SkBlendMode mode);
|
||||
|
||||
RenderState& mRenderState;
|
||||
|
||||
|
||||
@@ -53,9 +53,8 @@ static inline int NumDistinctRects(const SkCanvas::Lattice& lattice) {
|
||||
return xRects * yRects;
|
||||
}
|
||||
|
||||
static inline void SetLatticeFlags(SkCanvas::Lattice* lattice,
|
||||
SkCanvas::Lattice::RectType* flags, int numFlags, const Res_png_9patch& chunk,
|
||||
SkColor* colors) {
|
||||
static inline void SetLatticeFlags(SkCanvas::Lattice* lattice, SkCanvas::Lattice::RectType* flags,
|
||||
int numFlags, const Res_png_9patch& chunk, SkColor* colors) {
|
||||
lattice->fRectTypes = flags;
|
||||
lattice->fColors = colors;
|
||||
sk_bzero(flags, numFlags * sizeof(SkCanvas::Lattice::RectType));
|
||||
|
||||
@@ -42,9 +42,8 @@ public:
|
||||
mBounds.set(left, top, right, bottom);
|
||||
mRadius = radius;
|
||||
|
||||
|
||||
// Reuse memory if previous outline was the same shape (rect or round rect).
|
||||
if ( mPath.countVerbs() > 10) {
|
||||
if (mPath.countVerbs() > 10) {
|
||||
mPath.reset();
|
||||
} else {
|
||||
mPath.rewind();
|
||||
|
||||
@@ -210,9 +210,8 @@ void PathParser::validateVerbAndPoints(char verb, size_t points, PathParser::Par
|
||||
if (numberOfPointsExpected > 0) {
|
||||
result->failureMessage += "a multiple of ";
|
||||
}
|
||||
result->failureMessage += std::to_string(numberOfPointsExpected)
|
||||
+ " floats. However, " + std::to_string(points)
|
||||
+ " float(s) are found. ";
|
||||
result->failureMessage += std::to_string(numberOfPointsExpected) + " floats. However, " +
|
||||
std::to_string(points) + " float(s) are found. ";
|
||||
}
|
||||
|
||||
void PathParser::getPathDataFromAsciiString(PathData* data, ParseResult* result,
|
||||
@@ -242,8 +241,8 @@ void PathParser::getPathDataFromAsciiString(PathData* data, ParseResult* result,
|
||||
validateVerbAndPoints(pathStr[start], points.size(), result);
|
||||
if (result->failureOccurred) {
|
||||
// If either verb or points is not valid, return immediately.
|
||||
result->failureMessage += "Failure occurred at position " +
|
||||
std::to_string(start) + " of path: " + pathStr;
|
||||
result->failureMessage += "Failure occurred at position " + std::to_string(start) +
|
||||
" of path: " + pathStr;
|
||||
return;
|
||||
}
|
||||
data->verbs.push_back(pathStr[start]);
|
||||
@@ -257,8 +256,8 @@ void PathParser::getPathDataFromAsciiString(PathData* data, ParseResult* result,
|
||||
validateVerbAndPoints(pathStr[start], 0, result);
|
||||
if (result->failureOccurred) {
|
||||
// If either verb or points is not valid, return immediately.
|
||||
result->failureMessage += "Failure occurred at position " +
|
||||
std::to_string(start) + " of path: " + pathStr;
|
||||
result->failureMessage += "Failure occurred at position " + std::to_string(start) +
|
||||
" of path: " + pathStr;
|
||||
return;
|
||||
}
|
||||
data->verbs.push_back(pathStr[start]);
|
||||
|
||||
@@ -104,8 +104,8 @@ void ProfileData::dump(int fd) const {
|
||||
dprintf(fd, "\nStats since: %" PRIu64 "ns", mStatStartTime);
|
||||
dprintf(fd, "\nTotal frames rendered: %u", mTotalFrameCount);
|
||||
dprintf(fd, "\nJanky frames: %u (%.2f%%)", mJankFrameCount,
|
||||
mTotalFrameCount == 0 ? 0.0f :
|
||||
(float)mJankFrameCount / (float)mTotalFrameCount * 100.0f);
|
||||
mTotalFrameCount == 0 ? 0.0f
|
||||
: (float)mJankFrameCount / (float)mTotalFrameCount * 100.0f);
|
||||
dprintf(fd, "\n50th percentile: %ums", findPercentile(50));
|
||||
dprintf(fd, "\n90th percentile: %ums", findPercentile(90));
|
||||
dprintf(fd, "\n95th percentile: %ums", findPercentile(95));
|
||||
|
||||
@@ -328,9 +328,7 @@ public:
|
||||
|
||||
bool isPivotExplicitlySet() const { return mPrimitiveFields.mPivotExplicitlySet; }
|
||||
|
||||
bool resetPivot() {
|
||||
return RP_SET_AND_DIRTY(mPrimitiveFields.mPivotExplicitlySet, false);
|
||||
}
|
||||
bool resetPivot() { return RP_SET_AND_DIRTY(mPrimitiveFields.mPivotExplicitlySet, false); }
|
||||
|
||||
bool setCameraDistance(float distance) {
|
||||
if (distance != getCameraDistance()) {
|
||||
@@ -510,17 +508,13 @@ public:
|
||||
getOutline().getAlpha() != 0.0f;
|
||||
}
|
||||
|
||||
SkColor getSpotShadowColor() const {
|
||||
return mPrimitiveFields.mSpotShadowColor;
|
||||
}
|
||||
SkColor getSpotShadowColor() const { return mPrimitiveFields.mSpotShadowColor; }
|
||||
|
||||
bool setSpotShadowColor(SkColor shadowColor) {
|
||||
return RP_SET(mPrimitiveFields.mSpotShadowColor, shadowColor);
|
||||
}
|
||||
|
||||
SkColor getAmbientShadowColor() const {
|
||||
return mPrimitiveFields.mAmbientShadowColor;
|
||||
}
|
||||
SkColor getAmbientShadowColor() const { return mPrimitiveFields.mAmbientShadowColor; }
|
||||
|
||||
bool setAmbientShadowColor(SkColor shadowColor) {
|
||||
return RP_SET(mPrimitiveFields.mAmbientShadowColor, shadowColor);
|
||||
|
||||
@@ -736,8 +736,8 @@ void SkiaCanvas::drawGlyphs(ReadGlyphFunc glyphFunc, int count, const SkPaint& p
|
||||
SkASSERT(paintCopy.getTextEncoding() == SkPaint::kGlyphID_TextEncoding);
|
||||
// Stroke with a hairline is drawn on HW with a fill style for compatibility with Android O and
|
||||
// older.
|
||||
if (!mCanvasOwned && sApiLevel <= 27 && paintCopy.getStrokeWidth() <= 0
|
||||
&& paintCopy.getStyle() == SkPaint::kStroke_Style) {
|
||||
if (!mCanvasOwned && sApiLevel <= 27 && paintCopy.getStrokeWidth() <= 0 &&
|
||||
paintCopy.getStyle() == SkPaint::kStroke_Style) {
|
||||
paintCopy.setStyle(SkPaint::kFill_Style);
|
||||
}
|
||||
|
||||
|
||||
@@ -558,8 +558,8 @@ void Tree::draw(SkCanvas* canvas, const SkRect& bounds) {
|
||||
SkRect src;
|
||||
sk_sp<SkSurface> vdSurface = mCache.getSurface(&src);
|
||||
if (vdSurface) {
|
||||
canvas->drawImageRect(vdSurface->makeImageSnapshot().get(), src,
|
||||
bounds, getPaint(), SkCanvas::kFast_SrcRectConstraint);
|
||||
canvas->drawImageRect(vdSurface->makeImageSnapshot().get(), src, bounds, getPaint(),
|
||||
SkCanvas::kFast_SrcRectConstraint);
|
||||
} else {
|
||||
// Handle the case when VectorDrawableAtlas has been destroyed, because of memory pressure.
|
||||
// We render the VD into a temporary standalone buffer and mark the frame as dirty. Next
|
||||
@@ -570,8 +570,8 @@ void Tree::draw(SkCanvas* canvas, const SkRect& bounds) {
|
||||
|
||||
int scaledWidth = SkScalarCeilToInt(mProperties.getScaledWidth());
|
||||
int scaledHeight = SkScalarCeilToInt(mProperties.getScaledHeight());
|
||||
canvas->drawBitmapRect(skiaBitmap, SkRect::MakeWH(scaledWidth, scaledHeight),
|
||||
bounds, getPaint(), SkCanvas::kFast_SrcRectConstraint);
|
||||
canvas->drawBitmapRect(skiaBitmap, SkRect::MakeWH(scaledWidth, scaledHeight), bounds,
|
||||
getPaint(), SkCanvas::kFast_SrcRectConstraint);
|
||||
mCache.clear();
|
||||
markDirty();
|
||||
}
|
||||
|
||||
@@ -102,9 +102,7 @@ public:
|
||||
Snapshot decodeNextFrame();
|
||||
Snapshot reset();
|
||||
|
||||
size_t byteSize() const {
|
||||
return sizeof(this) + mBytesUsed;
|
||||
}
|
||||
size_t byteSize() const { return sizeof(this) + mBytesUsed; }
|
||||
|
||||
protected:
|
||||
virtual void onDraw(SkCanvas* canvas) override;
|
||||
|
||||
@@ -16,9 +16,8 @@
|
||||
#include "Bitmap.h"
|
||||
|
||||
#include "Caches.h"
|
||||
#include "renderthread/EglManager.h"
|
||||
#include "HardwareBitmapUploader.h"
|
||||
#include "renderthread/RenderProxy.h"
|
||||
#include "renderthread/RenderThread.h"
|
||||
#include "utils/Color.h"
|
||||
|
||||
#include <sys/mman.h>
|
||||
@@ -85,7 +84,7 @@ static sk_sp<Bitmap> allocateHeapBitmap(size_t size, const SkImageInfo& info, si
|
||||
}
|
||||
|
||||
sk_sp<Bitmap> Bitmap::allocateHardwareBitmap(SkBitmap& bitmap) {
|
||||
return uirenderer::renderthread::RenderProxy::allocateHardwareBitmap(bitmap);
|
||||
return uirenderer::HardwareBitmapUploader::allocateHardwareBitmap(bitmap);
|
||||
}
|
||||
|
||||
sk_sp<Bitmap> Bitmap::allocateHeapBitmap(SkBitmap* bitmap) {
|
||||
@@ -206,7 +205,7 @@ Bitmap::Bitmap(GraphicBuffer* buffer, const SkImageInfo& info)
|
||||
buffer->incStrong(buffer);
|
||||
setImmutable(); // HW bitmaps are always immutable
|
||||
mImage = SkImage::MakeFromAHardwareBuffer(reinterpret_cast<AHardwareBuffer*>(buffer),
|
||||
mInfo.alphaType(), mInfo.refColorSpace());
|
||||
mInfo.alphaType(), mInfo.refColorSpace());
|
||||
}
|
||||
|
||||
Bitmap::~Bitmap() {
|
||||
@@ -286,9 +285,8 @@ void Bitmap::setAlphaType(SkAlphaType alphaType) {
|
||||
void Bitmap::getSkBitmap(SkBitmap* outBitmap) {
|
||||
outBitmap->setHasHardwareMipMap(mHasHardwareMipMap);
|
||||
if (isHardware()) {
|
||||
outBitmap->allocPixels(SkImageInfo::Make(info().width(), info().height(),
|
||||
info().colorType(), info().alphaType(),
|
||||
nullptr));
|
||||
outBitmap->allocPixels(SkImageInfo::Make(info().width(), info().height(),
|
||||
info().colorType(), info().alphaType(), nullptr));
|
||||
uirenderer::renderthread::RenderProxy::copyGraphicBufferInto(graphicBuffer(), outBitmap);
|
||||
if (mInfo.colorSpace()) {
|
||||
sk_sp<SkPixelRef> pixelRef = sk_ref_sp(outBitmap->pixelRef());
|
||||
|
||||
@@ -65,9 +65,7 @@ public:
|
||||
Bitmap(void* address, int fd, size_t mappedSize, const SkImageInfo& info, size_t rowBytes);
|
||||
Bitmap(GraphicBuffer* buffer, const SkImageInfo& info);
|
||||
|
||||
int rowBytesAsPixels() const {
|
||||
return rowBytes() >> mInfo.shiftPerPixel();
|
||||
}
|
||||
int rowBytesAsPixels() const { return rowBytes() >> mInfo.shiftPerPixel(); }
|
||||
|
||||
void reconfigure(const SkImageInfo& info, size_t rowBytes);
|
||||
void reconfigure(const SkImageInfo& info);
|
||||
|
||||
@@ -158,9 +158,8 @@ void Canvas::drawText(const uint16_t* text, int textSize, int start, int count,
|
||||
// minikin may modify the original paint
|
||||
Paint paint(origPaint);
|
||||
|
||||
minikin::Layout layout =
|
||||
MinikinUtils::doLayout(&paint, bidiFlags, typeface, text, textSize, start, count,
|
||||
contextStart, contextCount, mt);
|
||||
minikin::Layout layout = MinikinUtils::doLayout(&paint, bidiFlags, typeface, text, textSize,
|
||||
start, count, contextStart, contextCount, mt);
|
||||
|
||||
x += MinikinUtils::xOffsetForTextAlign(&paint, layout);
|
||||
|
||||
@@ -207,11 +206,11 @@ void Canvas::drawTextOnPath(const uint16_t* text, int count, minikin::Bidi bidiF
|
||||
const SkPath& path, float hOffset, float vOffset, const Paint& paint,
|
||||
const Typeface* typeface) {
|
||||
Paint paintCopy(paint);
|
||||
minikin::Layout layout = MinikinUtils::doLayout(&paintCopy, bidiFlags, typeface,
|
||||
text, count, // text buffer
|
||||
0, count, // draw range
|
||||
0, count, // context range
|
||||
nullptr);
|
||||
minikin::Layout layout =
|
||||
MinikinUtils::doLayout(&paintCopy, bidiFlags, typeface, text, count, // text buffer
|
||||
0, count, // draw range
|
||||
0, count, // context range
|
||||
nullptr);
|
||||
hOffset += MinikinUtils::hOffsetForTextAlign(&paintCopy, layout, path);
|
||||
|
||||
// Set align to left for drawing, as we don't want individual
|
||||
|
||||
@@ -135,9 +135,8 @@ uint32_t MinikinFontSkia::packPaintFlags(const SkPaint* paint) {
|
||||
SkPaint::Hinting hinting = paint->getHinting();
|
||||
// select only flags that might affect text layout
|
||||
flags &= (SkPaint::kAntiAlias_Flag | SkPaint::kFakeBoldText_Flag | SkPaint::kLinearText_Flag |
|
||||
SkPaint::kSubpixelText_Flag |
|
||||
SkPaint::kEmbeddedBitmapText_Flag | SkPaint::kAutoHinting_Flag |
|
||||
SkPaint::kVerticalText_Flag);
|
||||
SkPaint::kSubpixelText_Flag | SkPaint::kEmbeddedBitmapText_Flag |
|
||||
SkPaint::kAutoHinting_Flag | SkPaint::kVerticalText_Flag);
|
||||
flags |= (hinting << 16);
|
||||
return flags;
|
||||
}
|
||||
|
||||
@@ -25,11 +25,11 @@
|
||||
#define _ANDROID_GRAPHICS_MINIKIN_UTILS_H_
|
||||
|
||||
#include <cutils/compiler.h>
|
||||
#include <log/log.h>
|
||||
#include <minikin/Layout.h>
|
||||
#include "MinikinSkia.h"
|
||||
#include "Paint.h"
|
||||
#include "Typeface.h"
|
||||
#include <log/log.h>
|
||||
|
||||
namespace minikin {
|
||||
class MeasuredText;
|
||||
|
||||
@@ -132,8 +132,10 @@ Typeface* Typeface::createFromFamilies(std::vector<std::shared_ptr<minikin::Font
|
||||
bool italicFromFont;
|
||||
|
||||
const minikin::FontStyle defaultStyle;
|
||||
const minikin::MinikinFont* mf = families.empty() ? nullptr
|
||||
: families[0]->getClosestMatch(defaultStyle).font->typeface().get();
|
||||
const minikin::MinikinFont* mf =
|
||||
families.empty()
|
||||
? nullptr
|
||||
: families[0]->getClosestMatch(defaultStyle).font->typeface().get();
|
||||
if (mf != nullptr) {
|
||||
SkTypeface* skTypeface = reinterpret_cast<const MinikinFontSkia*>(mf)->GetSkTypeface();
|
||||
const SkFontStyle& style = skTypeface->fontStyle();
|
||||
|
||||
@@ -251,210 +251,6 @@ void SkiaOpenGLPipeline::invokeFunctor(const RenderThread& thread, Functor* func
|
||||
}
|
||||
}
|
||||
|
||||
#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 bool gpuSupportsHalfFloatTextures(renderthread::RenderThread& renderThread) {
|
||||
static bool isSupported = renderThread.queue().runSync([&renderThread]() -> bool {
|
||||
renderThread.requireGlContext();
|
||||
sk_sp<GrContext> grContext = sk_ref_sp(renderThread.getGrContext());
|
||||
if (!grContext->colorTypeSupportedAsImage(kRGBA_F16_SkColorType)) {
|
||||
return false;
|
||||
}
|
||||
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();
|
||||
return error != OK;
|
||||
});
|
||||
return isSupported;
|
||||
}
|
||||
|
||||
static FormatInfo determineFormat(renderthread::RenderThread& renderThread,
|
||||
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 = gpuSupportsHalfFloatTextures(renderThread);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
sk_sp<Bitmap> SkiaOpenGLPipeline::allocateHardwareBitmap(renderthread::RenderThread& thread,
|
||||
const SkBitmap& sourceBitmap) {
|
||||
ATRACE_CALL();
|
||||
|
||||
LOG_ALWAYS_FATAL_IF(thread.isCurrent(), "Must not be called on RenderThread");
|
||||
|
||||
FormatInfo format = determineFormat(thread, sourceBitmap);
|
||||
if (!format.valid) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
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 = thread.queue().runSync([&]() -> EGLDisplay {
|
||||
thread.requireGlContext();
|
||||
return 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;
|
||||
}
|
||||
|
||||
{
|
||||
ATRACE_FORMAT("CPU -> gralloc transfer (%dx%d)", bitmap.width(), bitmap.height());
|
||||
EGLSyncKHR fence = thread.queue().runSync([&]() -> EGLSyncKHR {
|
||||
thread.requireGlContext();
|
||||
sk_sp<GrContext> grContext = sk_ref_sp(thread.getGrContext());
|
||||
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();
|
||||
grContext->resetContext(kTextureBinding_GrGLBackendState);
|
||||
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 skiapipeline */
|
||||
} /* namespace uirenderer */
|
||||
} /* namespace android */
|
||||
|
||||
@@ -50,10 +50,6 @@ public:
|
||||
|
||||
static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor);
|
||||
|
||||
// May be called by any thread except RenderThread.
|
||||
static sk_sp<Bitmap> allocateHardwareBitmap(renderthread::RenderThread& thread,
|
||||
const SkBitmap& skBitmap);
|
||||
|
||||
private:
|
||||
renderthread::EglManager& mEglManager;
|
||||
EGLSurface mEglSurface = EGL_NO_SURFACE;
|
||||
|
||||
@@ -64,6 +64,8 @@ public:
|
||||
|
||||
void fence();
|
||||
|
||||
EGLDisplay eglDisplay() const { return mEglDisplay; }
|
||||
|
||||
private:
|
||||
|
||||
void initExtensions();
|
||||
|
||||
@@ -319,17 +319,6 @@ void RenderProxy::prepareToDraw(Bitmap& bitmap) {
|
||||
}
|
||||
}
|
||||
|
||||
sk_sp<Bitmap> RenderProxy::allocateHardwareBitmap(SkBitmap& bitmap) {
|
||||
auto& thread = RenderThread::getInstance();
|
||||
if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaGL) {
|
||||
return skiapipeline::SkiaOpenGLPipeline::allocateHardwareBitmap(thread, bitmap);
|
||||
} else {
|
||||
return thread.queue().runSync([&]() -> auto {
|
||||
return thread.allocateHardwareBitmap(bitmap);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
int RenderProxy::copyGraphicBufferInto(GraphicBuffer* buffer, SkBitmap* bitmap) {
|
||||
RenderThread& thread = RenderThread::getInstance();
|
||||
if (gettid() == thread.getTid()) {
|
||||
|
||||
@@ -123,8 +123,6 @@ public:
|
||||
int bottom, SkBitmap* bitmap);
|
||||
ANDROID_API static void prepareToDraw(Bitmap& bitmap);
|
||||
|
||||
static sk_sp<Bitmap> allocateHardwareBitmap(SkBitmap& bitmap);
|
||||
|
||||
static int copyGraphicBufferInto(GraphicBuffer* buffer, SkBitmap* bitmap);
|
||||
|
||||
static void onBitmapDestroyed(uint32_t pixelRefId);
|
||||
|
||||
@@ -47,6 +47,8 @@ public:
|
||||
|
||||
void join() { Thread::join(); }
|
||||
|
||||
bool isRunning() const { return Thread::isRunning(); }
|
||||
|
||||
protected:
|
||||
void waitForWork() {
|
||||
nsecs_t nextWakeup;
|
||||
|
||||
Reference in New Issue
Block a user