Loading libs/hwui/pipeline/skia/SkiaPipeline.cpp +117 −35 Original line number Diff line number Diff line Loading @@ -19,14 +19,17 @@ #include <SkImageEncoder.h> #include <SkImageInfo.h> #include <SkImagePriv.h> #include <SkMultiPictureDocument.h> #include <SkOverdrawCanvas.h> #include <SkOverdrawColorFilter.h> #include <SkPicture.h> #include <SkPictureRecorder.h> #include <SkSerialProcs.h> #include "LightingInfo.h" #include "TreeInfo.h" #include "VectorDrawable.h" #include "thread/CommonPool.h" #include "tools/SkSharingProc.h" #include "utils/TraceUtils.h" #include <unistd.h> Loading Loading @@ -99,7 +102,7 @@ void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) SkASSERT(layerNode->getLayerSurface()); SkiaDisplayList* displayList = (SkiaDisplayList*)layerNode->getDisplayList(); if (!displayList || displayList->isEmpty()) { SkDEBUGF(("%p drawLayers(%s) : missing drawable", layerNode, layerNode->getName())); ALOGE("%p drawLayers(%s) : missing drawable", layerNode, layerNode->getName()); return; } Loading Loading @@ -233,58 +236,138 @@ static void savePictureAsync(const sk_sp<SkData>& data, const std::string& filen if (stream.isValid()) { stream.write(data->data(), data->size()); stream.flush(); SkDebugf("SKP Captured Drawing Output (%d bytes) for frame. %s", stream.bytesWritten(), ALOGD("SKP Captured Drawing Output (%zu bytes) for frame. %s", stream.bytesWritten(), filename.c_str()); } }); } SkCanvas* SkiaPipeline::tryCapture(SkSurface* surface) { if (CC_UNLIKELY(Properties::skpCaptureEnabled)) { if (mCaptureSequence <= 0) { // Note multiple SkiaPipeline instances may be loaded if more than one app is visible. // Each instance may observe the filename changing and try to record to a file of the same name. // Only the first one will succeed. There is no scope available here where we could coordinate // to cause this function to return true for only one of the instances. bool SkiaPipeline::shouldStartNewFileCapture() { // Don't start a new file based capture if one is currently ongoing. if (mCaptureMode != CaptureMode::None) { return false; } // A new capture is started when the filename property changes. // Read the filename property. std::string prop = base::GetProperty(PROPERTY_CAPTURE_SKP_FILENAME, "0"); // if the filename property changed to a valid value if (prop[0] != '0' && mCapturedFile != prop) { // remember this new filename mCapturedFile = prop; // and get a property indicating how many frames to capture. mCaptureSequence = base::GetIntProperty(PROPERTY_CAPTURE_SKP_FRAMES, 1); if (mCaptureSequence <= 0) { return false; } else if (mCaptureSequence == 1) { mCaptureMode = CaptureMode::SingleFrameSKP; } else { mCaptureMode = CaptureMode::MultiFrameSKP; } return true; } return false; } // performs the first-frame work of a multi frame SKP capture. Returns true if successful. bool SkiaPipeline::setupMultiFrameCapture() { ALOGD("Set up multi-frame capture, frames = %d", mCaptureSequence); // We own this stream and need to hold it until close() finishes. auto stream = std::make_unique<SkFILEWStream>(mCapturedFile.c_str()); if (stream->isValid()) { mOpenMultiPicStream = std::move(stream); mSerialContext.reset(new SkSharingSerialContext()); SkSerialProcs procs; procs.fImageProc = SkSharingSerialContext::serializeImage; procs.fImageCtx = mSerialContext.get(); // SkDocuments don't take owership of the streams they write. // we need to keep it until after mMultiPic.close() // procs is passed as a pointer, but just as a method of having an optional default. // procs doesn't need to outlive this Make call. mMultiPic = SkMakeMultiPictureDocument(mOpenMultiPicStream.get(), &procs); return true; } else { ALOGE("Could not open \"%s\" for writing.", mCapturedFile.c_str()); mCaptureSequence = 0; mCaptureMode = CaptureMode::None; return false; } } SkCanvas* SkiaPipeline::tryCapture(SkSurface* surface) { if (CC_LIKELY(!Properties::skpCaptureEnabled)) { return surface->getCanvas(); // Bail out early when capture is not turned on. } // Note that shouldStartNewFileCapture tells us if this is the *first* frame of a capture. if (shouldStartNewFileCapture() && mCaptureMode == CaptureMode::MultiFrameSKP) { if (!setupMultiFrameCapture()) { return surface->getCanvas(); } } if (mCaptureSequence > 0 || mPictureCapturedCallback) { // Create a canvas pointer, fill it depending on what kind of capture is requested (if any) SkCanvas* pictureCanvas = nullptr; switch (mCaptureMode) { case CaptureMode::CallbackAPI: case CaptureMode::SingleFrameSKP: mRecorder.reset(new SkPictureRecorder()); SkCanvas* pictureCanvas = mRecorder->beginRecording(surface->width(), surface->height(), nullptr, SkPictureRecorder::kPlaybackDrawPicture_RecordFlag); pictureCanvas = mRecorder->beginRecording(surface->width(), surface->height(), nullptr, SkPictureRecorder::kPlaybackDrawPicture_RecordFlag); break; case CaptureMode::MultiFrameSKP: // If a multi frame recording is active, initialize recording for a single frame of a // multi frame file. pictureCanvas = mMultiPic->beginPage(surface->width(), surface->height()); break; case CaptureMode::None: // Returning here in the non-capture case means we can count on pictureCanvas being // non-null below. return surface->getCanvas(); } // Setting up an nway canvas is common to any kind of capture. 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) { if (CC_LIKELY(mCaptureMode == CaptureMode::None)) { return; } mNwayCanvas.reset(); if (CC_UNLIKELY(mRecorder.get())) { ATRACE_CALL(); sk_sp<SkPicture> picture = mRecorder->finishRecordingAsPicture(); if (picture->approximateOpCount() > 0) { if (mCaptureSequence > 0) { ATRACE_BEGIN("picture->serialize"); auto data = picture->serialize(); ATRACE_END(); // offload saving to file in a different thread if (1 == mCaptureSequence) { savePictureAsync(data, mCapturedFile); } else { savePictureAsync(data, mCapturedFile + "_" + std::to_string(mCaptureSequence)); } if (mCaptureSequence > 0 && mCaptureMode == CaptureMode::MultiFrameSKP) { mMultiPic->endPage(); mCaptureSequence--; if (mCaptureSequence == 0) { mCaptureMode = CaptureMode::None; // Pass mMultiPic and mOpenMultiPicStream to a background thread, which will handle // the heavyweight serialization work and destroy them. mOpenMultiPicStream is released // to a bare pointer because keeping it in a smart pointer makes the lambda // non-copyable. The lambda is only called once, so this is safe. SkFILEWStream* stream = mOpenMultiPicStream.release(); CommonPool::post([doc = std::move(mMultiPic), stream]{ ALOGD("Finalizing multi frame SKP"); doc->close(); delete stream; ALOGD("Multi frame SKP complete."); }); } } else { sk_sp<SkPicture> picture = mRecorder->finishRecordingAsPicture(); if (picture->approximateOpCount() > 0) { if (mPictureCapturedCallback) { std::invoke(mPictureCapturedCallback, std::move(picture)); } else { // single frame skp to file auto data = picture->serialize(); savePictureAsync(data, mCapturedFile); mCaptureSequence = 0; } } mCaptureMode = CaptureMode::None; mRecorder.reset(); } } Loading @@ -305,7 +388,6 @@ void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& cli // initialize the canvas for the current frame, that might be a recording canvas if SKP // capture is enabled. std::unique_ptr<SkPictureRecorder> recorder; SkCanvas* canvas = tryCapture(surface.get()); renderFrameImpl(layers, clip, nodes, opaque, contentDrawBounds, canvas, preTransform); Loading libs/hwui/pipeline/skia/SkiaPipeline.h +45 −5 Original line number Diff line number Diff line Loading @@ -17,12 +17,15 @@ #pragma once #include <SkSurface.h> #include <SkDocument.h> #include <SkMultiPictureDocument.h> #include "Lighting.h" #include "hwui/AnimatedImageDrawable.h" #include "renderthread/CanvasContext.h" #include "renderthread/IRenderPipeline.h" class SkPictureRecorder; struct SkSharingSerialContext; namespace android { namespace uirenderer { Loading Loading @@ -60,9 +63,12 @@ public: void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque); // Sets the recording callback to the provided function and the recording mode // to CallbackAPI void setPictureCapturedCallback( const std::function<void(sk_sp<SkPicture>&&)>& callback) override { mPictureCapturedCallback = callback; mCaptureMode = callback ? CaptureMode::CallbackAPI : CaptureMode::None; } protected: Loading Loading @@ -92,8 +98,18 @@ private: */ void renderVectorDrawableCache(); // Called every frame. Normally returns early with screen canvas. // But when capture is enabled, returns an nwaycanvas where commands are also recorded. SkCanvas* tryCapture(SkSurface* surface); // Called at the end of every frame, closes the recording if necessary. void endCapture(SkSurface* surface); // Determine if a new file-based capture should be started. // If so, sets mCapturedFile and mCaptureSequence and returns true. // Should be called every frame when capture is enabled. // sets mCaptureMode. bool shouldStartNewFileCapture(); // Set up a multi frame capture. bool setupMultiFrameCapture(); std::vector<sk_sp<SkImage>> mPinnedImages; Loading @@ -103,22 +119,46 @@ private: std::vector<VectorDrawableRoot*> mVectorDrawables; // Block of properties used only for debugging to record a SkPicture and save it in a file. // There are three possible ways of recording drawing commands. enum class CaptureMode { // return to this mode when capture stops. None, // A mode where every frame is recorded into an SkPicture and sent to a provided callback, // until that callback is cleared CallbackAPI, // A mode where a finite number of frames are recorded to a file with // SkMultiPictureDocument MultiFrameSKP, // A mode which records a single frame to a normal SKP file. SingleFrameSKP, }; CaptureMode mCaptureMode = CaptureMode::None; /** * mCapturedFile is used to enforce we don't capture more than once for a given name (cause * permissions don't allow to reset a property from render thread). * mCapturedFile - the filename to write a recorded SKP to in either MultiFrameSKP or * SingleFrameSKP mode. */ std::string mCapturedFile; /** * mCaptureSequence counts how many frames are left to take in the sequence. * mCaptureSequence counts down how many frames are left to take in the sequence. Applicable * only to MultiFrameSKP or SingleFrameSKP mode. */ int mCaptureSequence = 0; // Multi frame serialization stream and writer used when serializing more than one frame. std::unique_ptr<SkFILEWStream> mOpenMultiPicStream; sk_sp<SkDocument> mMultiPic; std::unique_ptr<SkSharingSerialContext> mSerialContext; /** * mRecorder holds the current picture recorder. We could store it on the stack to support * parallel tryCapture calls (not really needed). * mRecorder holds the current picture recorder when serializing in either SingleFrameSKP or * CallbackAPI modes. */ std::unique_ptr<SkPictureRecorder> mRecorder; std::unique_ptr<SkNWayCanvas> mNwayCanvas; // Set by setPictureCapturedCallback and when set, CallbackAPI mode recording is ongoing. // Not used in other recording modes. std::function<void(sk_sp<SkPicture>&&)> mPictureCapturedCallback; }; Loading libs/hwui/tests/scripts/skp-capture.sh +42 −28 Original line number Diff line number Diff line Loading @@ -4,6 +4,12 @@ # # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. # # Before this can be used, the device must be rooted and the filesystem must be writable by Skia # - These steps are necessary once after flashing to enable capture - # adb root # adb remount # adb reboot if [ -z "$1" ]; then printf 'Usage:\n skp-capture.sh PACKAGE_NAME OPTIONAL_FRAME_COUNT\n\n' Loading @@ -20,8 +26,8 @@ if ! command -v adb > /dev/null 2>&1; then exit 2 fi fi phase1_timeout_seconds=15 phase2_timeout_seconds=60 phase1_timeout_seconds=60 phase2_timeout_seconds=300 package="$1" filename="$(date '+%H%M%S').skp" remote_path="/data/data/${package}/cache/${filename}" Loading @@ -29,11 +35,14 @@ local_path_prefix="$(date '+%Y-%m-%d_%H%M%S')_${package}" local_path="${local_path_prefix}.skp" enable_capture_key='debug.hwui.capture_skp_enabled' enable_capture_value=$(adb shell "getprop '${enable_capture_key}'") #printf 'captureflag=' "$enable_capture_value" '\n' # TODO(nifong): check if filesystem is writable here with "avbctl get-verity" # result will either start with "verity is disabled" or "verity is enabled" if [ -z "$enable_capture_value" ]; then printf 'Capture SKP property need to be enabled first. Please use\n' printf "\"adb shell setprop debug.hwui.capture_skp_enabled true\" and then restart\n" printf "the process.\n\n" printf 'debug.hwui.capture_skp_enabled was found to be disabled, enabling it now.\n' printf " restart the process you want to capture on the device, then retry this script.\n\n" adb shell "setprop '${enable_capture_key}' true" exit 1 fi if [ ! -z "$2" ]; then Loading @@ -57,12 +66,18 @@ banner() { printf ' %s' "$*" printf '\n=====================\n' } banner '...WAITING...' adb_test_exist() { test '0' = "$(adb shell "test -e \"$1\"; echo \$?")"; banner '...WAITING FOR APP INTERACTION...' # Waiting for nonzero file is an indication that the pipeline has both opened the file and written # the header. With multiple frames this does not occur until the last frame has been recorded, # so we continue to show the "waiting for app interaction" message as long as the app still requires # interaction to draw more frames. adb_test_file_nonzero() { # grab first byte of `du` output X="$(adb shell "du \"$1\" 2> /dev/null | dd bs=1 count=1 2> /dev/null")" test "$X" && test "$X" -ne 0 } timeout=$(( $(date +%s) + $phase1_timeout_seconds)) while ! adb_test_exist "$remote_path"; do while ! adb_test_file_nonzero "$remote_path"; do spin 0.05 if [ $(date +%s) -gt $timeout ] ; then printf '\bTimed out.\n' Loading @@ -72,20 +87,27 @@ while ! adb_test_exist "$remote_path"; do done printf '\b' #read -n1 -r -p "Press any key to continue..." key # Disable further capturing adb shell "setprop '${filename_key}' ''" banner '...SAVING...' adb_test_file_nonzero() { # grab first byte of `du` output X="$(adb shell "du \"$1\" 2> /dev/null | dd bs=1 count=1 2> /dev/null")" test "$X" && test "$X" -ne 0 # return the size of a file in bytes adb_filesize() { adb shell "wc -c \"$1\"" 2> /dev/null | awk '{print $1}' } #adb_filesize() { # adb shell "wc -c \"$1\"" 2> /dev/null | awk '{print $1}' #} timeout=$(( $(date +%s) + $phase2_timeout_seconds)) while ! adb_test_file_nonzero "$remote_path"; do last_size='0' # output of last size check command unstable=true # false once the file size stops changing counter=0 # used to perform size check only 1/sec though we update spinner 20/sec # loop until the file size is unchanged for 1 second. while [ $unstable != 0 ] ; do spin 0.05 counter=$(( $counter+1 )) if ! (( $counter % 20)) ; then new_size=$(adb_filesize "$remote_path") unstable=$(($(adb_filesize "$remote_path") != last_size)) last_size=$new_size fi if [ $(date +%s) -gt $timeout ] ; then printf '\bTimed out.\n' adb shell "setprop '${filename_key}' ''" Loading @@ -94,7 +116,7 @@ while ! adb_test_file_nonzero "$remote_path"; do done printf '\b' adb shell "setprop '${filename_key}' ''" printf "SKP file serialized: %s\n" $(echo $last_size | numfmt --to=iec) i=0; while [ $i -lt 10 ]; do spin 0.10; i=$(($i + 1)); done; echo Loading @@ -105,12 +127,4 @@ if ! [ -f "$local_path" ] ; then fi adb shell rm "$remote_path" printf '\nSKP saved to file:\n %s\n\n' "$local_path" if [ ! -z "$2" ]; then bridge="_" adb shell "setprop 'debug.hwui.capture_skp_frames' ''" for i in $(seq 2 $2); do adb pull "${remote_path}_${i}" "${local_path_prefix}_${i}.skp" adb shell rm "${remote_path}_${i}" done fi Loading
libs/hwui/pipeline/skia/SkiaPipeline.cpp +117 −35 Original line number Diff line number Diff line Loading @@ -19,14 +19,17 @@ #include <SkImageEncoder.h> #include <SkImageInfo.h> #include <SkImagePriv.h> #include <SkMultiPictureDocument.h> #include <SkOverdrawCanvas.h> #include <SkOverdrawColorFilter.h> #include <SkPicture.h> #include <SkPictureRecorder.h> #include <SkSerialProcs.h> #include "LightingInfo.h" #include "TreeInfo.h" #include "VectorDrawable.h" #include "thread/CommonPool.h" #include "tools/SkSharingProc.h" #include "utils/TraceUtils.h" #include <unistd.h> Loading Loading @@ -99,7 +102,7 @@ void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) SkASSERT(layerNode->getLayerSurface()); SkiaDisplayList* displayList = (SkiaDisplayList*)layerNode->getDisplayList(); if (!displayList || displayList->isEmpty()) { SkDEBUGF(("%p drawLayers(%s) : missing drawable", layerNode, layerNode->getName())); ALOGE("%p drawLayers(%s) : missing drawable", layerNode, layerNode->getName()); return; } Loading Loading @@ -233,58 +236,138 @@ static void savePictureAsync(const sk_sp<SkData>& data, const std::string& filen if (stream.isValid()) { stream.write(data->data(), data->size()); stream.flush(); SkDebugf("SKP Captured Drawing Output (%d bytes) for frame. %s", stream.bytesWritten(), ALOGD("SKP Captured Drawing Output (%zu bytes) for frame. %s", stream.bytesWritten(), filename.c_str()); } }); } SkCanvas* SkiaPipeline::tryCapture(SkSurface* surface) { if (CC_UNLIKELY(Properties::skpCaptureEnabled)) { if (mCaptureSequence <= 0) { // Note multiple SkiaPipeline instances may be loaded if more than one app is visible. // Each instance may observe the filename changing and try to record to a file of the same name. // Only the first one will succeed. There is no scope available here where we could coordinate // to cause this function to return true for only one of the instances. bool SkiaPipeline::shouldStartNewFileCapture() { // Don't start a new file based capture if one is currently ongoing. if (mCaptureMode != CaptureMode::None) { return false; } // A new capture is started when the filename property changes. // Read the filename property. std::string prop = base::GetProperty(PROPERTY_CAPTURE_SKP_FILENAME, "0"); // if the filename property changed to a valid value if (prop[0] != '0' && mCapturedFile != prop) { // remember this new filename mCapturedFile = prop; // and get a property indicating how many frames to capture. mCaptureSequence = base::GetIntProperty(PROPERTY_CAPTURE_SKP_FRAMES, 1); if (mCaptureSequence <= 0) { return false; } else if (mCaptureSequence == 1) { mCaptureMode = CaptureMode::SingleFrameSKP; } else { mCaptureMode = CaptureMode::MultiFrameSKP; } return true; } return false; } // performs the first-frame work of a multi frame SKP capture. Returns true if successful. bool SkiaPipeline::setupMultiFrameCapture() { ALOGD("Set up multi-frame capture, frames = %d", mCaptureSequence); // We own this stream and need to hold it until close() finishes. auto stream = std::make_unique<SkFILEWStream>(mCapturedFile.c_str()); if (stream->isValid()) { mOpenMultiPicStream = std::move(stream); mSerialContext.reset(new SkSharingSerialContext()); SkSerialProcs procs; procs.fImageProc = SkSharingSerialContext::serializeImage; procs.fImageCtx = mSerialContext.get(); // SkDocuments don't take owership of the streams they write. // we need to keep it until after mMultiPic.close() // procs is passed as a pointer, but just as a method of having an optional default. // procs doesn't need to outlive this Make call. mMultiPic = SkMakeMultiPictureDocument(mOpenMultiPicStream.get(), &procs); return true; } else { ALOGE("Could not open \"%s\" for writing.", mCapturedFile.c_str()); mCaptureSequence = 0; mCaptureMode = CaptureMode::None; return false; } } SkCanvas* SkiaPipeline::tryCapture(SkSurface* surface) { if (CC_LIKELY(!Properties::skpCaptureEnabled)) { return surface->getCanvas(); // Bail out early when capture is not turned on. } // Note that shouldStartNewFileCapture tells us if this is the *first* frame of a capture. if (shouldStartNewFileCapture() && mCaptureMode == CaptureMode::MultiFrameSKP) { if (!setupMultiFrameCapture()) { return surface->getCanvas(); } } if (mCaptureSequence > 0 || mPictureCapturedCallback) { // Create a canvas pointer, fill it depending on what kind of capture is requested (if any) SkCanvas* pictureCanvas = nullptr; switch (mCaptureMode) { case CaptureMode::CallbackAPI: case CaptureMode::SingleFrameSKP: mRecorder.reset(new SkPictureRecorder()); SkCanvas* pictureCanvas = mRecorder->beginRecording(surface->width(), surface->height(), nullptr, SkPictureRecorder::kPlaybackDrawPicture_RecordFlag); pictureCanvas = mRecorder->beginRecording(surface->width(), surface->height(), nullptr, SkPictureRecorder::kPlaybackDrawPicture_RecordFlag); break; case CaptureMode::MultiFrameSKP: // If a multi frame recording is active, initialize recording for a single frame of a // multi frame file. pictureCanvas = mMultiPic->beginPage(surface->width(), surface->height()); break; case CaptureMode::None: // Returning here in the non-capture case means we can count on pictureCanvas being // non-null below. return surface->getCanvas(); } // Setting up an nway canvas is common to any kind of capture. 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) { if (CC_LIKELY(mCaptureMode == CaptureMode::None)) { return; } mNwayCanvas.reset(); if (CC_UNLIKELY(mRecorder.get())) { ATRACE_CALL(); sk_sp<SkPicture> picture = mRecorder->finishRecordingAsPicture(); if (picture->approximateOpCount() > 0) { if (mCaptureSequence > 0) { ATRACE_BEGIN("picture->serialize"); auto data = picture->serialize(); ATRACE_END(); // offload saving to file in a different thread if (1 == mCaptureSequence) { savePictureAsync(data, mCapturedFile); } else { savePictureAsync(data, mCapturedFile + "_" + std::to_string(mCaptureSequence)); } if (mCaptureSequence > 0 && mCaptureMode == CaptureMode::MultiFrameSKP) { mMultiPic->endPage(); mCaptureSequence--; if (mCaptureSequence == 0) { mCaptureMode = CaptureMode::None; // Pass mMultiPic and mOpenMultiPicStream to a background thread, which will handle // the heavyweight serialization work and destroy them. mOpenMultiPicStream is released // to a bare pointer because keeping it in a smart pointer makes the lambda // non-copyable. The lambda is only called once, so this is safe. SkFILEWStream* stream = mOpenMultiPicStream.release(); CommonPool::post([doc = std::move(mMultiPic), stream]{ ALOGD("Finalizing multi frame SKP"); doc->close(); delete stream; ALOGD("Multi frame SKP complete."); }); } } else { sk_sp<SkPicture> picture = mRecorder->finishRecordingAsPicture(); if (picture->approximateOpCount() > 0) { if (mPictureCapturedCallback) { std::invoke(mPictureCapturedCallback, std::move(picture)); } else { // single frame skp to file auto data = picture->serialize(); savePictureAsync(data, mCapturedFile); mCaptureSequence = 0; } } mCaptureMode = CaptureMode::None; mRecorder.reset(); } } Loading @@ -305,7 +388,6 @@ void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& cli // initialize the canvas for the current frame, that might be a recording canvas if SKP // capture is enabled. std::unique_ptr<SkPictureRecorder> recorder; SkCanvas* canvas = tryCapture(surface.get()); renderFrameImpl(layers, clip, nodes, opaque, contentDrawBounds, canvas, preTransform); Loading
libs/hwui/pipeline/skia/SkiaPipeline.h +45 −5 Original line number Diff line number Diff line Loading @@ -17,12 +17,15 @@ #pragma once #include <SkSurface.h> #include <SkDocument.h> #include <SkMultiPictureDocument.h> #include "Lighting.h" #include "hwui/AnimatedImageDrawable.h" #include "renderthread/CanvasContext.h" #include "renderthread/IRenderPipeline.h" class SkPictureRecorder; struct SkSharingSerialContext; namespace android { namespace uirenderer { Loading Loading @@ -60,9 +63,12 @@ public: void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque); // Sets the recording callback to the provided function and the recording mode // to CallbackAPI void setPictureCapturedCallback( const std::function<void(sk_sp<SkPicture>&&)>& callback) override { mPictureCapturedCallback = callback; mCaptureMode = callback ? CaptureMode::CallbackAPI : CaptureMode::None; } protected: Loading Loading @@ -92,8 +98,18 @@ private: */ void renderVectorDrawableCache(); // Called every frame. Normally returns early with screen canvas. // But when capture is enabled, returns an nwaycanvas where commands are also recorded. SkCanvas* tryCapture(SkSurface* surface); // Called at the end of every frame, closes the recording if necessary. void endCapture(SkSurface* surface); // Determine if a new file-based capture should be started. // If so, sets mCapturedFile and mCaptureSequence and returns true. // Should be called every frame when capture is enabled. // sets mCaptureMode. bool shouldStartNewFileCapture(); // Set up a multi frame capture. bool setupMultiFrameCapture(); std::vector<sk_sp<SkImage>> mPinnedImages; Loading @@ -103,22 +119,46 @@ private: std::vector<VectorDrawableRoot*> mVectorDrawables; // Block of properties used only for debugging to record a SkPicture and save it in a file. // There are three possible ways of recording drawing commands. enum class CaptureMode { // return to this mode when capture stops. None, // A mode where every frame is recorded into an SkPicture and sent to a provided callback, // until that callback is cleared CallbackAPI, // A mode where a finite number of frames are recorded to a file with // SkMultiPictureDocument MultiFrameSKP, // A mode which records a single frame to a normal SKP file. SingleFrameSKP, }; CaptureMode mCaptureMode = CaptureMode::None; /** * mCapturedFile is used to enforce we don't capture more than once for a given name (cause * permissions don't allow to reset a property from render thread). * mCapturedFile - the filename to write a recorded SKP to in either MultiFrameSKP or * SingleFrameSKP mode. */ std::string mCapturedFile; /** * mCaptureSequence counts how many frames are left to take in the sequence. * mCaptureSequence counts down how many frames are left to take in the sequence. Applicable * only to MultiFrameSKP or SingleFrameSKP mode. */ int mCaptureSequence = 0; // Multi frame serialization stream and writer used when serializing more than one frame. std::unique_ptr<SkFILEWStream> mOpenMultiPicStream; sk_sp<SkDocument> mMultiPic; std::unique_ptr<SkSharingSerialContext> mSerialContext; /** * mRecorder holds the current picture recorder. We could store it on the stack to support * parallel tryCapture calls (not really needed). * mRecorder holds the current picture recorder when serializing in either SingleFrameSKP or * CallbackAPI modes. */ std::unique_ptr<SkPictureRecorder> mRecorder; std::unique_ptr<SkNWayCanvas> mNwayCanvas; // Set by setPictureCapturedCallback and when set, CallbackAPI mode recording is ongoing. // Not used in other recording modes. std::function<void(sk_sp<SkPicture>&&)> mPictureCapturedCallback; }; Loading
libs/hwui/tests/scripts/skp-capture.sh +42 −28 Original line number Diff line number Diff line Loading @@ -4,6 +4,12 @@ # # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. # # Before this can be used, the device must be rooted and the filesystem must be writable by Skia # - These steps are necessary once after flashing to enable capture - # adb root # adb remount # adb reboot if [ -z "$1" ]; then printf 'Usage:\n skp-capture.sh PACKAGE_NAME OPTIONAL_FRAME_COUNT\n\n' Loading @@ -20,8 +26,8 @@ if ! command -v adb > /dev/null 2>&1; then exit 2 fi fi phase1_timeout_seconds=15 phase2_timeout_seconds=60 phase1_timeout_seconds=60 phase2_timeout_seconds=300 package="$1" filename="$(date '+%H%M%S').skp" remote_path="/data/data/${package}/cache/${filename}" Loading @@ -29,11 +35,14 @@ local_path_prefix="$(date '+%Y-%m-%d_%H%M%S')_${package}" local_path="${local_path_prefix}.skp" enable_capture_key='debug.hwui.capture_skp_enabled' enable_capture_value=$(adb shell "getprop '${enable_capture_key}'") #printf 'captureflag=' "$enable_capture_value" '\n' # TODO(nifong): check if filesystem is writable here with "avbctl get-verity" # result will either start with "verity is disabled" or "verity is enabled" if [ -z "$enable_capture_value" ]; then printf 'Capture SKP property need to be enabled first. Please use\n' printf "\"adb shell setprop debug.hwui.capture_skp_enabled true\" and then restart\n" printf "the process.\n\n" printf 'debug.hwui.capture_skp_enabled was found to be disabled, enabling it now.\n' printf " restart the process you want to capture on the device, then retry this script.\n\n" adb shell "setprop '${enable_capture_key}' true" exit 1 fi if [ ! -z "$2" ]; then Loading @@ -57,12 +66,18 @@ banner() { printf ' %s' "$*" printf '\n=====================\n' } banner '...WAITING...' adb_test_exist() { test '0' = "$(adb shell "test -e \"$1\"; echo \$?")"; banner '...WAITING FOR APP INTERACTION...' # Waiting for nonzero file is an indication that the pipeline has both opened the file and written # the header. With multiple frames this does not occur until the last frame has been recorded, # so we continue to show the "waiting for app interaction" message as long as the app still requires # interaction to draw more frames. adb_test_file_nonzero() { # grab first byte of `du` output X="$(adb shell "du \"$1\" 2> /dev/null | dd bs=1 count=1 2> /dev/null")" test "$X" && test "$X" -ne 0 } timeout=$(( $(date +%s) + $phase1_timeout_seconds)) while ! adb_test_exist "$remote_path"; do while ! adb_test_file_nonzero "$remote_path"; do spin 0.05 if [ $(date +%s) -gt $timeout ] ; then printf '\bTimed out.\n' Loading @@ -72,20 +87,27 @@ while ! adb_test_exist "$remote_path"; do done printf '\b' #read -n1 -r -p "Press any key to continue..." key # Disable further capturing adb shell "setprop '${filename_key}' ''" banner '...SAVING...' adb_test_file_nonzero() { # grab first byte of `du` output X="$(adb shell "du \"$1\" 2> /dev/null | dd bs=1 count=1 2> /dev/null")" test "$X" && test "$X" -ne 0 # return the size of a file in bytes adb_filesize() { adb shell "wc -c \"$1\"" 2> /dev/null | awk '{print $1}' } #adb_filesize() { # adb shell "wc -c \"$1\"" 2> /dev/null | awk '{print $1}' #} timeout=$(( $(date +%s) + $phase2_timeout_seconds)) while ! adb_test_file_nonzero "$remote_path"; do last_size='0' # output of last size check command unstable=true # false once the file size stops changing counter=0 # used to perform size check only 1/sec though we update spinner 20/sec # loop until the file size is unchanged for 1 second. while [ $unstable != 0 ] ; do spin 0.05 counter=$(( $counter+1 )) if ! (( $counter % 20)) ; then new_size=$(adb_filesize "$remote_path") unstable=$(($(adb_filesize "$remote_path") != last_size)) last_size=$new_size fi if [ $(date +%s) -gt $timeout ] ; then printf '\bTimed out.\n' adb shell "setprop '${filename_key}' ''" Loading @@ -94,7 +116,7 @@ while ! adb_test_file_nonzero "$remote_path"; do done printf '\b' adb shell "setprop '${filename_key}' ''" printf "SKP file serialized: %s\n" $(echo $last_size | numfmt --to=iec) i=0; while [ $i -lt 10 ]; do spin 0.10; i=$(($i + 1)); done; echo Loading @@ -105,12 +127,4 @@ if ! [ -f "$local_path" ] ; then fi adb shell rm "$remote_path" printf '\nSKP saved to file:\n %s\n\n' "$local_path" if [ ! -z "$2" ]; then bridge="_" adb shell "setprop 'debug.hwui.capture_skp_frames' ''" for i in $(seq 2 $2); do adb pull "${remote_path}_${i}" "${local_path_prefix}_${i}.skp" adb shell rm "${remote_path}_${i}" done fi