Improve picture capture debug path
* Fixes hardware bitmap capture
* Fixes mutable bitmap capture (no flickering)
* Adds basic single-frame LRU cache to avoid
repeated readbacks of GPU resources
* Does up-front readback of GPU resources
* Moves serialization off RenderThread again thanks
to up-front readback avoiding needing GPU access
off-thread
* Reduces RAM usage & improves performance by serializing
directly to output stream instead of first copying to
a byte[]
Bug: 174223722
Test: PictureCaptureDemo mirrors the content
Change-Id: If7ec208b61d5b917e82087cc312880fc5a38c943
diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp
index 8a8b418..d8735ce 100644
--- a/libs/hwui/Readback.cpp
+++ b/libs/hwui/Readback.cpp
@@ -275,6 +275,14 @@
return copyResult;
}
+CopyResult Readback::copyImageInto(const sk_sp<SkImage>& image, SkBitmap* bitmap) {
+ Rect srcRect;
+ Matrix4 transform;
+ transform.loadScale(1, -1, 1);
+ transform.translate(0, -1);
+ return copyImageInto(image, transform, srcRect, bitmap);
+}
+
CopyResult Readback::copyImageInto(const sk_sp<SkImage>& image, Matrix4& texTransform,
const Rect& srcRect, SkBitmap* bitmap) {
ATRACE_CALL();
diff --git a/libs/hwui/Readback.h b/libs/hwui/Readback.h
index 4cb4bd8..da25269 100644
--- a/libs/hwui/Readback.h
+++ b/libs/hwui/Readback.h
@@ -50,6 +50,7 @@
CopyResult copySurfaceInto(ANativeWindow* window, const Rect& srcRect, SkBitmap* bitmap);
CopyResult copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap);
+ CopyResult copyImageInto(const sk_sp<SkImage>& image, SkBitmap* bitmap);
CopyResult copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap);
diff --git a/libs/hwui/jni/Picture.h b/libs/hwui/jni/Picture.h
index 536f651..87ba397 100644
--- a/libs/hwui/jni/Picture.h
+++ b/libs/hwui/jni/Picture.h
@@ -38,6 +38,7 @@
public:
explicit Picture(const Picture* src = NULL);
explicit Picture(sk_sp<SkPicture>&& src);
+ virtual ~Picture() = default;
Canvas* beginRecording(int width, int height);
@@ -49,7 +50,7 @@
static Picture* CreateFromStream(SkStream* stream);
- void serialize(SkWStream* stream) const;
+ virtual void serialize(SkWStream* stream) const;
void draw(Canvas* canvas);
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index 4289c45..602c32a 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -23,6 +23,8 @@
#include <Picture.h>
#include <Properties.h>
#include <RootRenderNode.h>
+#include <SkImagePriv.h>
+#include <SkSerialProcs.h>
#include <dlfcn.h>
#include <gui/TraceUtils.h>
#include <inttypes.h>
@@ -35,6 +37,7 @@
#include <renderthread/RenderProxy.h>
#include <renderthread/RenderTask.h>
#include <renderthread/RenderThread.h>
+#include <src/image/SkImage_Base.h>
#include <thread/CommonPool.h>
#include <utils/Color.h>
#include <utils/RefBase.h>
@@ -497,6 +500,108 @@
jobject mObject;
};
+using TextureMap = std::unordered_map<uint32_t, sk_sp<SkImage>>;
+
+struct PictureCaptureState {
+ // Each frame we move from the active map to the previous map, essentially an LRU of 1 frame
+ // This avoids repeated readbacks of the same image, but avoids artificially extending the
+ // lifetime of any particular image.
+ TextureMap mActiveMap;
+ TextureMap mPreviousActiveMap;
+};
+
+// TODO: This & Multi-SKP & Single-SKP should all be de-duped into
+// a single "make a SkPicture serailizable-safe" utility somewhere
+class PictureWrapper : public Picture {
+public:
+ PictureWrapper(sk_sp<SkPicture>&& src, const std::shared_ptr<PictureCaptureState>& state)
+ : Picture(), mPicture(std::move(src)) {
+ ATRACE_NAME("Preparing SKP for capture");
+ // Move the active to previous active
+ state->mPreviousActiveMap = std::move(state->mActiveMap);
+ state->mActiveMap.clear();
+ SkSerialProcs tempProc;
+ tempProc.fImageCtx = state.get();
+ tempProc.fImageProc = collectNonTextureImagesProc;
+ auto ns = SkNullWStream();
+ mPicture->serialize(&ns, &tempProc);
+ state->mPreviousActiveMap.clear();
+
+ // Now snapshot a copy of the active map so this PictureWrapper becomes self-sufficient
+ mTextureMap = state->mActiveMap;
+ }
+
+ static sk_sp<SkImage> imageForCache(SkImage* img) {
+ const SkBitmap* bitmap = as_IB(img)->onPeekBitmap();
+ // This is a mutable bitmap pretending to be an immutable SkImage. As we're going to
+ // actually cross thread boundaries here, make a copy so it's immutable proper
+ if (bitmap && !bitmap->isImmutable()) {
+ ATRACE_NAME("Copying mutable bitmap");
+ return SkImage::MakeFromBitmap(*bitmap);
+ }
+ if (img->isTextureBacked()) {
+ ATRACE_NAME("Readback of texture image");
+ return img->makeNonTextureImage();
+ }
+ SkPixmap pm;
+ if (img->isLazyGenerated() && !img->peekPixels(&pm)) {
+ ATRACE_NAME("Readback of HW bitmap");
+ // This is a hardware bitmap probably
+ SkBitmap bm;
+ if (!bm.tryAllocPixels(img->imageInfo())) {
+ // Failed to allocate, just see what happens
+ return sk_ref_sp(img);
+ }
+ if (RenderProxy::copyImageInto(sk_ref_sp(img), &bm)) {
+ // Failed to readback
+ return sk_ref_sp(img);
+ }
+ bm.setImmutable();
+ return SkMakeImageFromRasterBitmap(bm, kNever_SkCopyPixelsMode);
+ }
+ return sk_ref_sp(img);
+ }
+
+ static sk_sp<SkData> collectNonTextureImagesProc(SkImage* img, void* ctx) {
+ PictureCaptureState* context = reinterpret_cast<PictureCaptureState*>(ctx);
+ const uint32_t originalId = img->uniqueID();
+ auto it = context->mActiveMap.find(originalId);
+ if (it == context->mActiveMap.end()) {
+ auto pit = context->mPreviousActiveMap.find(originalId);
+ if (pit == context->mPreviousActiveMap.end()) {
+ context->mActiveMap[originalId] = imageForCache(img);
+ } else {
+ context->mActiveMap[originalId] = pit->second;
+ }
+ }
+ return SkData::MakeEmpty();
+ }
+
+ static sk_sp<SkData> serializeImage(SkImage* img, void* ctx) {
+ PictureWrapper* context = reinterpret_cast<PictureWrapper*>(ctx);
+ const uint32_t id = img->uniqueID();
+ auto iter = context->mTextureMap.find(id);
+ if (iter != context->mTextureMap.end()) {
+ img = iter->second.get();
+ }
+ return img->encodeToData();
+ }
+
+ void serialize(SkWStream* stream) const override {
+ SkSerialProcs procs;
+ procs.fImageProc = serializeImage;
+ procs.fImageCtx = const_cast<PictureWrapper*>(this);
+ procs.fTypefaceProc = [](SkTypeface* tf, void* ctx) {
+ return tf->serialize(SkTypeface::SerializeBehavior::kDoIncludeData);
+ };
+ mPicture->serialize(stream, &procs);
+ }
+
+private:
+ sk_sp<SkPicture> mPicture;
+ TextureMap mTextureMap;
+};
+
static void android_view_ThreadedRenderer_setPictureCapturedCallbackJNI(JNIEnv* env,
jobject clazz, jlong proxyPtr, jobject pictureCallback) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
@@ -507,9 +612,11 @@
LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Unable to get Java VM");
auto globalCallbackRef = std::make_shared<JGlobalRefHolder>(vm,
env->NewGlobalRef(pictureCallback));
- proxy->setPictureCapturedCallback([globalCallbackRef](sk_sp<SkPicture>&& picture) {
+ auto pictureState = std::make_shared<PictureCaptureState>();
+ proxy->setPictureCapturedCallback([globalCallbackRef,
+ pictureState](sk_sp<SkPicture>&& picture) {
JNIEnv* env = getenv(globalCallbackRef->vm());
- Picture* wrapper = new Picture{std::move(picture)};
+ Picture* wrapper = new PictureWrapper{std::move(picture), pictureState};
env->CallStaticVoidMethod(gHardwareRenderer.clazz,
gHardwareRenderer.invokePictureCapturedCallback,
static_cast<jlong>(reinterpret_cast<intptr_t>(wrapper)),
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index a78cd83..9bca4df 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -75,7 +75,9 @@
bool opaque, const LightInfo& lightInfo,
const std::vector<sp<RenderNode>>& renderNodes,
FrameInfoVisualizer* profiler) {
- mEglManager.damageFrame(frame, dirty);
+ if (!isCapturingSkp()) {
+ mEglManager.damageFrame(frame, dirty);
+ }
SkColorType colorType = getSurfaceColorType();
// setup surface for fbo0
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 039b0f9..5462623 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -420,7 +420,7 @@
procs.fTypefaceProc = [](SkTypeface* tf, void* ctx){
return tf->serialize(SkTypeface::SerializeBehavior::kDoIncludeData);
};
- auto data = picture->serialize();
+ auto data = picture->serialize(&procs);
savePictureAsync(data, mCapturedFile);
mCaptureSequence = 0;
mCaptureMode = CaptureMode::None;
@@ -470,8 +470,7 @@
const SkMatrix& preTransform) {
SkAutoCanvasRestore saver(canvas, true);
auto clipRestriction = preTransform.mapRect(clip).roundOut();
- if (CC_UNLIKELY(mCaptureMode == CaptureMode::SingleFrameSKP
- || mCaptureMode == CaptureMode::MultiFrameSKP)) {
+ if (CC_UNLIKELY(isCapturingSkp())) {
canvas->drawAnnotation(SkRect::Make(clipRestriction), "AndroidDeviceClipRestriction",
nullptr);
} else {
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index 4658035..bc8a565 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -80,6 +80,8 @@
SkColorType mSurfaceColorType;
sk_sp<SkColorSpace> mSurfaceColorSpace;
+ bool isCapturingSkp() const { return mCaptureMode != CaptureMode::None; }
+
private:
void renderFrameImpl(const SkRect& clip,
const std::vector<sp<RenderNode>>& nodes, bool opaque,
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index ac19a15..6fd644b 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -390,6 +390,17 @@
}
}
+int RenderProxy::copyImageInto(const sk_sp<SkImage>& image, SkBitmap* bitmap) {
+ RenderThread& thread = RenderThread::getInstance();
+ if (gettid() == thread.getTid()) {
+ // TODO: fix everything that hits this. We should never be triggering a readback ourselves.
+ return (int)thread.readback().copyImageInto(image, bitmap);
+ } else {
+ return thread.queue().runSync(
+ [&]() -> int { return (int)thread.readback().copyImageInto(image, bitmap); });
+ }
+}
+
void RenderProxy::disableVsync() {
Properties::disableVsync = true;
}
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 0681dc5..6d80949 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -136,6 +136,7 @@
static void prepareToDraw(Bitmap& bitmap);
static int copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap);
+ static int copyImageInto(const sk_sp<SkImage>& image, SkBitmap* bitmap);
static void disableVsync();