Merge "Add continuous SKP capture test api"

This commit is contained in:
John Reck
2019-01-23 17:57:42 +00:00
committed by Android (Google) Code Review
22 changed files with 508 additions and 62 deletions

View File

@@ -14619,8 +14619,8 @@ package android.graphics {
public class Picture {
ctor public Picture();
ctor public Picture(android.graphics.Picture);
method public android.graphics.Canvas beginRecording(int, int);
method public void draw(android.graphics.Canvas);
method @NonNull public android.graphics.Canvas beginRecording(int, int);
method public void draw(@NonNull android.graphics.Canvas);
method public void endRecording();
method public int getHeight();
method public int getWidth();
@@ -14872,7 +14872,7 @@ package android.graphics {
}
public final class RenderNode {
ctor public RenderNode(String);
ctor public RenderNode(@Nullable String);
method public int computeApproximateMemoryUsage();
method public void discardDisplayList();
method public void endRecording();

View File

@@ -239,8 +239,8 @@ package android.graphics {
}
public class Picture {
method @Deprecated public static android.graphics.Picture createFromStream(java.io.InputStream);
method @Deprecated public void writeToStream(java.io.OutputStream);
method @Deprecated public static android.graphics.Picture createFromStream(@NonNull java.io.InputStream);
method @Deprecated public void writeToStream(@NonNull java.io.OutputStream);
}
@Deprecated public class PixelXorXfermode extends android.graphics.Xfermode {

View File

@@ -2273,6 +2273,10 @@ package android.view {
method public static int getLongPressTooltipHideTimeout();
}
public class ViewDebug {
method @Nullable public static AutoCloseable startRenderingCommandsCapture(android.view.View, java.util.concurrent.Executor, java.util.function.Function<android.graphics.Picture,java.lang.Boolean>);
}
public interface WindowManager extends android.view.ViewManager {
method public default void setShouldShowIme(int, boolean);
method public default void setShouldShowSystemDecors(int, boolean);

View File

@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.HardwareRenderer;
import android.graphics.Picture;
import android.graphics.Point;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
@@ -553,6 +554,10 @@ public final class ThreadedRenderer extends HardwareRenderer {
dumpProfileInfo(fd, flags);
}
Picture captureRenderingCommands() {
return null;
}
@Override
public boolean loadSystemProperties() {
boolean changed = super.loadSystemProperties();

View File

@@ -17,17 +17,21 @@
package android.view;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.HardwareRenderer;
import android.graphics.Picture;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
import android.graphics.RenderNode;
import android.os.Debug;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -48,16 +52,20 @@ import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;
/**
* Various debugging/tracing tools related to {@link View} and the view hierarchy.
@@ -741,6 +749,123 @@ public class ViewDebug {
root.getViewRootImpl().outputDisplayList(target);
}
private static class PictureCallbackHandler implements AutoCloseable,
HardwareRenderer.PictureCapturedCallback, Runnable {
private final HardwareRenderer mRenderer;
private final Function<Picture, Boolean> mCallback;
private final Executor mExecutor;
private final ReentrantLock mLock = new ReentrantLock(false);
private final ArrayDeque<Picture> mQueue = new ArrayDeque<>(3);
private boolean mStopListening;
private Thread mRenderThread;
private PictureCallbackHandler(HardwareRenderer renderer,
Function<Picture, Boolean> callback, Executor executor) {
mRenderer = renderer;
mCallback = callback;
mExecutor = executor;
mRenderer.setPictureCaptureCallback(this);
}
@Override
public void close() {
mLock.lock();
mStopListening = true;
mLock.unlock();
mRenderer.setPictureCaptureCallback(null);
}
@Override
public void onPictureCaptured(Picture picture) {
mLock.lock();
if (mStopListening) {
mLock.unlock();
mRenderer.setPictureCaptureCallback(null);
return;
}
if (mRenderThread == null) {
mRenderThread = Thread.currentThread();
}
Picture toDestroy = null;
if (mQueue.size() == 3) {
toDestroy = mQueue.removeLast();
}
mQueue.add(picture);
mLock.unlock();
if (toDestroy == null) {
mExecutor.execute(this);
} else {
toDestroy.close();
}
}
@Override
public void run() {
mLock.lock();
final Picture picture = mQueue.poll();
final boolean isStopped = mStopListening;
mLock.unlock();
if (Thread.currentThread() == mRenderThread) {
close();
throw new IllegalStateException(
"ViewDebug#startRenderingCommandsCapture must be given an executor that "
+ "invokes asynchronously");
}
if (isStopped) {
picture.close();
return;
}
final boolean keepReceiving = mCallback.apply(picture);
if (!keepReceiving) {
close();
}
}
}
/**
* Begins capturing the entire rendering commands for the view tree referenced by the given
* view. The view passed may be any View in the tree as long as it is attached. That is,
* {@link View#isAttachedToWindow()} must be true.
*
* Every time a frame is rendered a Picture will be passed to the given callback via the given
* executor. As long as the callback returns 'true' it will continue to receive new frames.
* The system will only invoke the callback at a rate that the callback is able to keep up with.
* That is, if it takes 48ms for the callback to complete and there is a 60fps animation running
* then the callback will only receive 33% of the frames produced.
*
* This method must be called on the same thread as the View tree.
*
* @param tree The View tree to capture the rendering commands.
* @param callback The callback to invoke on every frame produced. Should return true to
* continue receiving new frames, false to stop capturing.
* @param executor The executor to invoke the callback on. Recommend using a background thread
* to avoid stalling the UI thread. Must be an asynchronous invoke or an
* exception will be thrown.
* @return a closeable that can be used to stop capturing. May be invoked on any thread. Note
* that the callback may continue to receive another frame or two depending on thread timings.
* Returns null if the capture stream cannot be started, such as if there's no
* HardwareRenderer for the given view tree.
* @hide
*/
@TestApi
@Nullable
public static AutoCloseable startRenderingCommandsCapture(View tree, Executor executor,
Function<Picture, Boolean> callback) {
final View.AttachInfo attachInfo = tree.mAttachInfo;
if (attachInfo == null) {
throw new IllegalArgumentException("Given view isn't attached");
}
if (attachInfo.mHandler.getLooper() != Looper.myLooper()) {
throw new IllegalStateException("Called on the wrong thread."
+ " Must be called on the thread that owns the given View");
}
final HardwareRenderer renderer = attachInfo.mThreadedRenderer;
if (renderer != null) {
return new PictureCallbackHandler(renderer, callback, executor);
}
return null;
}
private static void capture(View root, final OutputStream clientStream, String parameter)
throws IOException {

View File

@@ -37,6 +37,12 @@ Picture::Picture(const Picture* src) {
}
}
Picture::Picture(sk_sp<SkPicture>&& src) {
mPicture = std::move(src);
mWidth = 0;
mHeight = 0;
}
Canvas* Picture::beginRecording(int width, int height) {
mPicture.reset(NULL);
mRecorder.reset(new SkPictureRecorder);

View File

@@ -37,6 +37,7 @@ class Canvas;
class Picture {
public:
explicit Picture(const Picture* src = NULL);
explicit Picture(sk_sp<SkPicture>&& src);
Canvas* beginRecording(int width, int height);

View File

@@ -48,6 +48,7 @@
#include <FrameInfo.h>
#include <FrameMetricsObserver.h>
#include <IContextFactory.h>
#include <Picture.h>
#include <Properties.h>
#include <PropertyValuesAnimatorSet.h>
#include <RenderNode.h>
@@ -70,6 +71,11 @@ struct {
jmethodID callback;
} gFrameMetricsObserverClassInfo;
struct {
jclass clazz;
jmethodID invokePictureCapturedCallback;
} gHardwareRenderer;
struct {
jmethodID onFrameDraw;
} gFrameDrawingCallback;
@@ -905,6 +911,27 @@ private:
jobject mObject;
};
static void android_view_ThreadedRenderer_setPictureCapturedCallbackJNI(JNIEnv* env,
jobject clazz, jlong proxyPtr, jobject pictureCallback) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
if (!pictureCallback) {
proxy->setPictureCapturedCallback(nullptr);
} else {
JavaVM* vm = nullptr;
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) {
JNIEnv* env = getenv(globalCallbackRef->vm());
Picture* wrapper = new Picture{std::move(picture)};
env->CallStaticVoidMethod(gHardwareRenderer.clazz,
gHardwareRenderer.invokePictureCapturedCallback,
static_cast<jlong>(reinterpret_cast<intptr_t>(wrapper)),
globalCallbackRef->object());
});
}
}
static void android_view_ThreadedRenderer_setFrameCallback(JNIEnv* env,
jobject clazz, jlong proxyPtr, jobject frameCallback) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
@@ -1145,6 +1172,8 @@ static const JNINativeMethod gMethods[] = {
{ "nRemoveRenderNode", "(JJ)V", (void*) android_view_ThreadedRenderer_removeRenderNode},
{ "nDrawRenderNode", "(JJ)V", (void*) android_view_ThreadedRendererd_drawRenderNode},
{ "nSetContentDrawBounds", "(JIIII)V", (void*)android_view_ThreadedRenderer_setContentDrawBounds},
{ "nSetPictureCaptureCallback", "(JLandroid/graphics/HardwareRenderer$PictureCapturedCallback;)V",
(void*) android_view_ThreadedRenderer_setPictureCapturedCallbackJNI },
{ "nSetFrameCallback", "(JLandroid/graphics/HardwareRenderer$FrameDrawingCallback;)V",
(void*)android_view_ThreadedRenderer_setFrameCallback},
{ "nSetFrameCompleteCallback", "(JLandroid/graphics/HardwareRenderer$FrameCompleteCallback;)V",
@@ -1198,6 +1227,13 @@ int register_android_view_ThreadedRenderer(JNIEnv* env) {
gFrameMetricsObserverClassInfo.timingDataBuffer = GetFieldIDOrDie(
env, metricsClass, "mTimingData", "[J");
jclass hardwareRenderer = FindClassOrDie(env,
"android/graphics/HardwareRenderer");
gHardwareRenderer.clazz = reinterpret_cast<jclass>(env->NewGlobalRef(hardwareRenderer));
gHardwareRenderer.invokePictureCapturedCallback = GetStaticMethodIDOrDie(env, hardwareRenderer,
"invokePictureCapturedCallback",
"(JLandroid/graphics/HardwareRenderer$PictureCapturedCallback;)V");
jclass frameCallbackClass = FindClassOrDie(env,
"android/graphics/HardwareRenderer$FrameDrawingCallback");
gFrameDrawingCallback.onFrameDraw = GetMethodIDOrDie(env, frameCallbackClass,

View File

@@ -20,6 +20,7 @@ import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.app.Activity;
import android.app.ActivityManager;
import android.os.IBinder;
@@ -667,6 +668,17 @@ public class HardwareRenderer {
nSetContentDrawBounds(mNativeProxy, left, top, right, bottom);
}
/** @hide */
public void setPictureCaptureCallback(@Nullable PictureCapturedCallback callback) {
nSetPictureCaptureCallback(mNativeProxy, callback);
}
/** called by native */
static void invokePictureCapturedCallback(long picturePtr, PictureCapturedCallback callback) {
Picture picture = new Picture(picturePtr);
callback.onPictureCaptured(picture);
}
/**
* Interface used to receive callbacks when a frame is being drawn.
*
@@ -695,6 +707,17 @@ public class HardwareRenderer {
void onFrameComplete(long frameNr);
}
/**
* Interface for listening to picture captures
* @hide
*/
@TestApi
public interface PictureCapturedCallback {
/** @hide */
@TestApi
void onPictureCaptured(Picture picture);
}
private static void validateAlpha(float alpha, String argumentName) {
if (!(alpha >= 0.0f && alpha <= 1.0f)) {
throw new IllegalArgumentException(argumentName + " must be a valid alpha, "
@@ -998,6 +1021,9 @@ public class HardwareRenderer {
private static native void nSetContentDrawBounds(long nativeProxy, int left,
int top, int right, int bottom);
private static native void nSetPictureCaptureCallback(long nativeProxy,
PictureCapturedCallback callback);
private static native void nSetFrameCallback(long nativeProxy, FrameDrawingCallback callback);
private static native void nSetFrameCompleteCallback(long nativeProxy,

View File

@@ -16,6 +16,7 @@
package android.graphics;
import android.annotation.NonNull;
import android.annotation.UnsupportedAppUsage;
import java.io.InputStream;
@@ -34,7 +35,8 @@ import java.io.OutputStream;
*/
public class Picture {
private PictureCanvas mRecordingCanvas;
@UnsupportedAppUsage
// TODO: Figure out if this was a false-positive
@UnsupportedAppUsage(maxTargetSdk = 28)
private long mNativePicture;
private boolean mRequiresHwAcceleration;
@@ -56,23 +58,43 @@ public class Picture {
this(nativeConstructor(src != null ? src.mNativePicture : 0));
}
private Picture(long nativePicture) {
/** @hide */
public Picture(long nativePicture) {
if (nativePicture == 0) {
throw new RuntimeException();
throw new IllegalArgumentException();
}
mNativePicture = nativePicture;
}
/**
* Immediately releases the backing data of the Picture. This object will no longer
* be usable after calling this, and any further calls on the Picture will throw an
* IllegalStateException.
* // TODO: Support?
* @hide
*/
public void close() {
if (mNativePicture != 0) {
nativeDestructor(mNativePicture);
mNativePicture = 0;
}
}
@Override
protected void finalize() throws Throwable {
try {
nativeDestructor(mNativePicture);
mNativePicture = 0;
close();
} finally {
super.finalize();
}
}
private void verifyValid() {
if (mNativePicture == 0) {
throw new IllegalStateException("Picture is destroyed");
}
}
/**
* To record a picture, call beginRecording() and then draw into the Canvas
* that is returned. Nothing we appear on screen, but all of the draw
@@ -81,7 +103,9 @@ public class Picture {
* that was returned must no longer be used, and nothing should be drawn
* into it.
*/
@NonNull
public Canvas beginRecording(int width, int height) {
verifyValid();
if (mRecordingCanvas != null) {
throw new IllegalStateException("Picture already recording, must call #endRecording()");
}
@@ -98,6 +122,7 @@ public class Picture {
* or {@link Canvas#drawPicture(Picture)} is called.
*/
public void endRecording() {
verifyValid();
if (mRecordingCanvas != null) {
mRequiresHwAcceleration = mRecordingCanvas.mHoldsHwBitmap;
mRecordingCanvas = null;
@@ -110,7 +135,8 @@ public class Picture {
* does not reflect (per se) the content of the picture.
*/
public int getWidth() {
return nativeGetWidth(mNativePicture);
verifyValid();
return nativeGetWidth(mNativePicture);
}
/**
@@ -118,7 +144,8 @@ public class Picture {
* does not reflect (per se) the content of the picture.
*/
public int getHeight() {
return nativeGetHeight(mNativePicture);
verifyValid();
return nativeGetHeight(mNativePicture);
}
/**
@@ -133,6 +160,7 @@ public class Picture {
* false otherwise.
*/
public boolean requiresHardwareAcceleration() {
verifyValid();
return mRequiresHwAcceleration;
}
@@ -149,7 +177,8 @@ public class Picture {
*
* @param canvas The picture is drawn to this canvas
*/
public void draw(Canvas canvas) {
public void draw(@NonNull Canvas canvas) {
verifyValid();
if (mRecordingCanvas != null) {
endRecording();
}
@@ -172,7 +201,7 @@ public class Picture {
* raw or compressed pixels.
*/
@Deprecated
public static Picture createFromStream(InputStream stream) {
public static Picture createFromStream(@NonNull InputStream stream) {
return new Picture(nativeCreateFromStream(stream, new byte[WORKING_STREAM_STORAGE]));
}
@@ -188,10 +217,11 @@ public class Picture {
* Bitmap from which you can persist it as raw or compressed pixels.
*/
@Deprecated
public void writeToStream(OutputStream stream) {
public void writeToStream(@NonNull OutputStream stream) {
verifyValid();
// do explicit check before calling the native method
if (stream == null) {
throw new NullPointerException();
throw new IllegalArgumentException("stream cannot be null");
}
if (!nativeWriteToStream(mNativePicture, stream, new byte[WORKING_STREAM_STORAGE])) {
throw new RuntimeException();

View File

@@ -184,7 +184,7 @@ public final class RenderNode {
*
* @param name The name of the RenderNode, used for debugging purpose. May be null.
*/
public RenderNode(String name) {
public RenderNode(@Nullable String name) {
this(name, null);
}

View File

@@ -551,6 +551,19 @@ void Tree::draw(SkCanvas* canvas, const SkRect& bounds, const SkPaint& inPaint)
SkPaint paint = inPaint;
paint.setAlpha(mProperties.getRootAlpha() * 255);
if (canvas->getGrContext() == nullptr) {
// Recording to picture, don't use the SkSurface which won't work off of renderthread.
Bitmap& bitmap = getBitmapUpdateIfDirty();
SkBitmap skiaBitmap;
bitmap.getSkBitmap(&skiaBitmap);
int scaledWidth = SkScalarCeilToInt(mProperties.getScaledWidth());
int scaledHeight = SkScalarCeilToInt(mProperties.getScaledHeight());
canvas->drawBitmapRect(skiaBitmap, SkRect::MakeWH(scaledWidth, scaledHeight), bounds,
&paint, SkCanvas::kFast_SrcRectConstraint);
return;
}
SkRect src;
sk_sp<SkSurface> vdSurface = mCache.getSurface(&src);
if (vdSurface) {

View File

@@ -74,7 +74,13 @@ static bool GetFboDetails(SkCanvas* canvas, GLuint* outFboID, SkISize* outFboSiz
void GLFunctorDrawable::onDraw(SkCanvas* canvas) {
if (canvas->getGrContext() == nullptr) {
SkDEBUGF(("Attempting to draw GLFunctor into an unsupported surface"));
// We're dumping a picture, render a light-blue rectangle instead
// TODO: Draw the WebView text on top? Seemingly complicated as SkPaint doesn't
// seem to have a default typeface that works. We only ever use drawGlyphs, which
// requires going through minikin & hwui's canvas which we don't have here.
SkPaint paint;
paint.setColor(0xFF81D4FA);
canvas->drawRect(mBounds, paint);
return;
}

View File

@@ -111,7 +111,7 @@ void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque)
const Rect& layerDamage = layers.entries()[i].damage;
SkCanvas* layerCanvas = tryCapture(layerNode->getLayerSurface());
SkCanvas* layerCanvas = layerNode->getLayerSurface()->getCanvas();
int saveCount = layerCanvas->save();
SkASSERT(saveCount == 1);
@@ -139,8 +139,6 @@ void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque)
layerCanvas->restoreToCount(saveCount);
mLightCenter = savedLightCenter;
endCapture(layerNode->getLayerSurface());
// cache the current context so that we can defer flushing it until
// either all the layers have been rendered or the context changes
GrContext* currentContext = layerNode->getLayerSurface()->getCanvas()->getGrContext();
@@ -244,6 +242,7 @@ public:
}
virtual void onProcess(const sp<Task<bool>>& task) override {
ATRACE_NAME("SavePictureTask");
SavePictureTask* t = static_cast<SavePictureTask*>(task.get());
if (0 == access(t->filename.c_str(), F_OK)) {
@@ -265,46 +264,56 @@ public:
SkCanvas* SkiaPipeline::tryCapture(SkSurface* surface) {
if (CC_UNLIKELY(Properties::skpCaptureEnabled)) {
bool recordingPicture = mCaptureSequence > 0;
char prop[PROPERTY_VALUE_MAX] = {'\0'};
if (!recordingPicture) {
if (mCaptureSequence <= 0) {
property_get(PROPERTY_CAPTURE_SKP_FILENAME, prop, "0");
recordingPicture = prop[0] != '0' &&
mCapturedFile != prop; // ensure we capture only once per filename
if (recordingPicture) {
if (prop[0] != '0' && mCapturedFile != prop) {
mCapturedFile = prop;
mCaptureSequence = property_get_int32(PROPERTY_CAPTURE_SKP_FRAMES, 1);
}
}
if (recordingPicture) {
if (mCaptureSequence > 0 || mPictureCapturedCallback) {
mRecorder.reset(new SkPictureRecorder());
return mRecorder->beginRecording(surface->width(), surface->height(), nullptr,
SkPictureRecorder::kPlaybackDrawPicture_RecordFlag);
SkCanvas* pictureCanvas = mRecorder->beginRecording(surface->width(), surface->height(), nullptr,
SkPictureRecorder::kPlaybackDrawPicture_RecordFlag);
mNwayCanvas = std::make_unique<SkNWayCanvas>(surface->width(), surface->height());
mNwayCanvas->addCanvas(surface->getCanvas());
mNwayCanvas->addCanvas(pictureCanvas);
return mNwayCanvas.get();
}
}
return surface->getCanvas();
}
void SkiaPipeline::endCapture(SkSurface* surface) {
mNwayCanvas.reset();
if (CC_UNLIKELY(mRecorder.get())) {
ATRACE_CALL();
sk_sp<SkPicture> picture = mRecorder->finishRecordingAsPicture();
surface->getCanvas()->drawPicture(picture);
if (picture->approximateOpCount() > 0) {
auto data = picture->serialize();
if (mCaptureSequence > 0) {
ATRACE_BEGIN("picture->serialize");
auto data = picture->serialize();
ATRACE_END();
// offload saving to file in a different thread
if (!mSavePictureProcessor.get()) {
TaskManager* taskManager = getTaskManager();
mSavePictureProcessor = new SavePictureProcessor(
taskManager->canRunTasks() ? taskManager : nullptr);
// offload saving to file in a different thread
if (!mSavePictureProcessor.get()) {
TaskManager* taskManager = getTaskManager();
mSavePictureProcessor = new SavePictureProcessor(
taskManager->canRunTasks() ? taskManager : nullptr);
}
if (1 == mCaptureSequence) {
mSavePictureProcessor->savePicture(data, mCapturedFile);
} else {
mSavePictureProcessor->savePicture(
data,
mCapturedFile + "_" + std::to_string(mCaptureSequence));
}
mCaptureSequence--;
}
if (1 == mCaptureSequence) {
mSavePictureProcessor->savePicture(data, mCapturedFile);
} else {
mSavePictureProcessor->savePicture(
data, mCapturedFile + "_" + std::to_string(mCaptureSequence));
if (mPictureCapturedCallback) {
std::invoke(mPictureCapturedCallback, std::move(picture));
}
mCaptureSequence--;
}
mRecorder.reset();
}
@@ -314,6 +323,11 @@ void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& cli
const std::vector<sp<RenderNode>>& nodes, bool opaque,
const Rect& contentDrawBounds, sk_sp<SkSurface> surface,
const SkMatrix& preTransform) {
bool previousSkpEnabled = Properties::skpCaptureEnabled;
if (mPictureCapturedCallback) {
Properties::skpCaptureEnabled = true;
}
renderVectorDrawableCache();
// draw all layers up front
@@ -334,6 +348,8 @@ void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& cli
ATRACE_NAME("flush commands");
surface->getCanvas()->flush();
Properties::skpCaptureEnabled = previousSkpEnabled;
}
namespace {

View File

@@ -105,6 +105,11 @@ public:
mLightCenter = lightGeometry.center;
}
void setPictureCapturedCallback(
const std::function<void(sk_sp<SkPicture>&&)>& callback) override {
mPictureCapturedCallback = callback;
}
protected:
void dumpResourceCacheUsage() const;
void setSurfaceColorProperties(renderthread::ColorMode colorMode);
@@ -163,6 +168,8 @@ private:
* parallel tryCapture calls (not really needed).
*/
std::unique_ptr<SkPictureRecorder> mRecorder;
std::unique_ptr<SkNWayCanvas> mNwayCanvas;
std::function<void(sk_sp<SkPicture>&&)> mPictureCapturedCallback;
static float mLightRadius;
static uint8_t mAmbientShadowAlpha;

View File

@@ -184,6 +184,10 @@ public:
mFrameCompleteCallbacks.push_back(std::move(func));
}
void setPictureCapturedCallback(const std::function<void(sk_sp<SkPicture>&&)>& callback) {
mRenderPipeline->setPictureCapturedCallback(callback);
}
void setForceDark(bool enable) {
mUseForceDark = enable;
}

View File

@@ -59,15 +59,15 @@ public:
virtual MakeCurrentResult makeCurrent() = 0;
virtual Frame getFrame() = 0;
virtual bool draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
const LightGeometry& lightGeometry,
LayerUpdateQueue* layerUpdateQueue, const Rect& contentDrawBounds,
bool opaque, const LightInfo& lightInfo,
const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
const std::vector<sp<RenderNode>>& renderNodes,
FrameInfoVisualizer* profiler) = 0;
virtual bool swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty,
FrameInfo* currentFrameInfo, bool* requireSwap) = 0;
virtual DeferredLayerUpdater* createTextureLayer() = 0;
virtual bool setSurface(ANativeWindow* window, SwapBehavior swapBehavior, ColorMode colorMode) = 0;
virtual bool setSurface(ANativeWindow* window, SwapBehavior swapBehavior,
ColorMode colorMode) = 0;
virtual void onStop() = 0;
virtual bool isSurfaceReady() = 0;
virtual bool isContextReady() = 0;
@@ -85,6 +85,8 @@ public:
virtual SkColorType getSurfaceColorType() const = 0;
virtual sk_sp<SkColorSpace> getSurfaceColorSpace() = 0;
virtual GrSurfaceOrigin getSurfaceOrigin() = 0;
virtual void setPictureCapturedCallback(
const std::function<void(sk_sp<SkPicture>&&)>& callback) = 0;
virtual ~IRenderPipeline() {}
};

View File

@@ -21,6 +21,7 @@
#include "Properties.h"
#include "Readback.h"
#include "Rect.h"
#include "WebViewFunctorManager.h"
#include "pipeline/skia/SkiaOpenGLPipeline.h"
#include "pipeline/skia/VectorDrawableAtlas.h"
#include "renderstate/RenderState.h"
@@ -30,7 +31,6 @@
#include "renderthread/RenderThread.h"
#include "utils/Macros.h"
#include "utils/TimeUtils.h"
#include "WebViewFunctorManager.h"
#include <ui/GraphicBuffer.h>
@@ -147,9 +147,7 @@ void RenderProxy::invokeFunctor(Functor* functor, bool waitForCompletion) {
void RenderProxy::destroyFunctor(int functor) {
ATRACE_CALL();
RenderThread& thread = RenderThread::getInstance();
thread.queue().post([=]() {
WebViewFunctorManager::instance().destroyFunctor(functor);
});
thread.queue().post([=]() { WebViewFunctorManager::instance().destroyFunctor(functor); });
}
DeferredLayerUpdater* RenderProxy::createTextureLayer() {
@@ -164,9 +162,9 @@ void RenderProxy::buildLayer(RenderNode* node) {
bool RenderProxy::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap& bitmap) {
auto& thread = RenderThread::getInstance();
return thread.queue().runSync(
[&]() -> bool { return thread.readback().copyLayerInto(layer, &bitmap)
== CopyResult::Success; });
return thread.queue().runSync([&]() -> bool {
return thread.readback().copyLayerInto(layer, &bitmap) == CopyResult::Success;
});
}
void RenderProxy::pushLayerUpdate(DeferredLayerUpdater* layer) {
@@ -204,9 +202,8 @@ void RenderProxy::fence() {
}
int RenderProxy::maxTextureSize() {
static int maxTextureSize = RenderThread::getInstance().queue().runSync([]() {
return DeviceInfo::get()->maxTextureSize();
});
static int maxTextureSize = RenderThread::getInstance().queue().runSync(
[]() { return DeviceInfo::get()->maxTextureSize(); });
return maxTextureSize;
}
@@ -281,6 +278,12 @@ void RenderProxy::setContentDrawBounds(int left, int top, int right, int bottom)
mDrawFrameTask.setContentDrawBounds(left, top, right, bottom);
}
void RenderProxy::setPictureCapturedCallback(
const std::function<void(sk_sp<SkPicture>&&)>& callback) {
mRenderThread.queue().post(
[ this, cb = callback ]() { mContext->setPictureCapturedCallback(cb); });
}
void RenderProxy::setFrameCallback(std::function<void(int64_t)>&& callback) {
mDrawFrameTask.setFrameCallback(std::move(callback));
}
@@ -302,9 +305,7 @@ void RenderProxy::removeFrameMetricsObserver(FrameMetricsObserver* observerPtr)
}
void RenderProxy::setForceDark(bool enable) {
mRenderThread.queue().post([this, enable]() {
mContext->setForceDark(enable);
});
mRenderThread.queue().post([this, enable]() { mContext->setForceDark(enable); });
}
int RenderProxy::copySurfaceInto(sp<Surface>& surface, int left, int top, int right, int bottom,
@@ -348,9 +349,8 @@ int RenderProxy::copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap) {
// TODO: fix everything that hits this. We should never be triggering a readback ourselves.
return (int)thread.readback().copyHWBitmapInto(hwBitmap, bitmap);
} else {
return thread.queue().runSync([&]() -> int {
return (int)thread.readback().copyHWBitmapInto(hwBitmap, bitmap);
});
return thread.queue().runSync(
[&]() -> int { return (int)thread.readback().copyHWBitmapInto(hwBitmap, bitmap); });
}
}

View File

@@ -114,6 +114,8 @@ public:
ANDROID_API void removeRenderNode(RenderNode* node);
ANDROID_API void drawRenderNode(RenderNode* node);
ANDROID_API void setContentDrawBounds(int left, int top, int right, int bottom);
ANDROID_API void setPictureCapturedCallback(
const std::function<void(sk_sp<SkPicture>&&)>& callback);
ANDROID_API void setFrameCallback(std::function<void(int64_t)>&& callback);
ANDROID_API void setFrameCompleteCallback(std::function<void(int64_t)>&& callback);

View File

@@ -21,6 +21,7 @@ LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_PACKAGE_NAME := HwAccelerationTest
LOCAL_PRIVATE_PLATFORM_APIS := true
LOCAL_CERTIFICATE := platform
LOCAL_MODULE_TAGS := tests

View File

@@ -310,6 +310,15 @@
<category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="PictureCaptureDemo"
android:label="Debug/Picture Capture">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="com.android.test.hwui.TEST" />
</intent-filter>
</activity>
<activity
android:name="SmallCircleActivity"

View File

@@ -0,0 +1,153 @@
/*
* Copyright (C) 2019 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.
*/
package com.android.test.hwui;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Picture;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewDebug;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.widget.ProgressBar;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class PictureCaptureDemo extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
final LinearLayout inner = new LinearLayout(this);
inner.setOrientation(LinearLayout.HORIZONTAL);
ProgressBar spinner = new ProgressBar(this, null, android.R.attr.progressBarStyleLarge);
inner.addView(spinner,
new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
inner.addView(new View(this), new LayoutParams(50, 1));
Picture picture = new Picture();
Canvas canvas = picture.beginRecording(100, 100);
canvas.drawColor(Color.RED);
Paint paint = new Paint();
paint.setTextSize(32);
paint.setColor(Color.BLACK);
canvas.drawText("Hello", 0, 50, paint);
picture.endRecording();
ImageView iv1 = new ImageView(this);
iv1.setImageBitmap(Bitmap.createBitmap(picture, 100, 100, Bitmap.Config.ARGB_8888));
inner.addView(iv1, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
inner.addView(new View(this), new LayoutParams(50, 1));
ImageView iv2 = new ImageView(this);
iv2.setImageBitmap(Bitmap.createBitmap(picture, 100, 100, Bitmap.Config.HARDWARE));
inner.addView(iv2, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
layout.addView(inner,
new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
// For testing with a functor in the tree
WebView wv = new WebView(this);
wv.setWebViewClient(new WebViewClient());
wv.setWebChromeClient(new WebChromeClient());
wv.loadUrl("https://google.com");
layout.addView(wv, new LayoutParams(LayoutParams.MATCH_PARENT, 400));
SurfaceView mySurfaceView = new SurfaceView(this);
layout.addView(mySurfaceView,
new LayoutParams(LayoutParams.MATCH_PARENT, 600));
setContentView(layout);
mySurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
private AutoCloseable mStopCapture;
@Override
public void surfaceCreated(SurfaceHolder holder) {
final Random rand = new Random();
mStopCapture = ViewDebug.startRenderingCommandsCapture(mySurfaceView,
mCaptureThread, (picture) -> {
if (rand.nextInt(20) == 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
Canvas canvas = holder.lockCanvas();
if (canvas == null) {
return false;
}
canvas.drawPicture(picture);
holder.unlockCanvasAndPost(canvas);
picture.close();
return true;
});
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (mStopCapture != null) {
try {
mStopCapture.close();
} catch (Exception e) {
}
mStopCapture = null;
}
}
});
}
ExecutorService mCaptureThread = Executors.newSingleThreadExecutor();
ExecutorService mExecutor = Executors.newSingleThreadExecutor();
Picture deepCopy(Picture src) {
try {
PipedInputStream inputStream = new PipedInputStream();
PipedOutputStream outputStream = new PipedOutputStream(inputStream);
Future<Picture> future = mExecutor.submit(() -> Picture.createFromStream(inputStream));
src.writeToStream(outputStream);
outputStream.close();
return future.get();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}