Adding Skia Recorder to Render Engine
To start recording run:
adb shell setprop debug.renderengine.capture_skia_ms 1000
File will be stored in the /data directory on the device:
adb shell ls -al /data/user/
adb pull /data/user/re_skiacapture_<timestamp>.mskp
The recording will stop after MS specified. To reset the
recording, set the capture_skia_ms flag to a new time.
When recording is finished, the capture_skia_ms flag will
be set to 0 to avoid circular recording.
Bug: 173805658
Test: Turn on the flag. Turn off the flag. Pull the file from
the device. Open it in Skia Debugger.
Change-Id: Iea478245c267fb4b26f980417d96acd7273bfb55
diff --git a/libs/renderengine/skia/debug/SkiaCapture.cpp b/libs/renderengine/skia/debug/SkiaCapture.cpp
new file mode 100644
index 0000000..8006a11
--- /dev/null
+++ b/libs/renderengine/skia/debug/SkiaCapture.cpp
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2020 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 "SkiaCapture.h"
+
+#undef LOG_TAG
+#define LOG_TAG "RenderEngine"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <log/log.h>
+#include <renderengine/RenderEngine.h>
+#include <utils/Trace.h>
+
+#include "CommonPool.h"
+#include "src/utils/SkMultiPictureDocument.h"
+
+namespace android {
+namespace renderengine {
+namespace skia {
+
+// The root of the filename to write a recorded SKP to. In order for this file to
+// be written to /data/user/, user must run 'adb shell setenforce 0' in the device.
+static const std::string CAPTURED_FILENAME_BASE = "/data/user/re_skiacapture";
+
+SkiaCapture::~SkiaCapture() {
+ mTimer.stop();
+}
+
+SkCanvas* SkiaCapture::tryCapture(SkSurface* surface) {
+ ATRACE_CALL();
+
+ // If we are not running yet, set up.
+ if (!mCaptureRunning) {
+ mTimerInterval = std::chrono::milliseconds(
+ base::GetIntProperty(PROPERTY_DEBUG_RENDERENGINE_CAPTURE_SKIA_MS, 0));
+ // Set up the multi-frame capture. If we fail to set it up, then just return canvas.
+ // If interval is 0, return surface.
+ if (CC_LIKELY(mTimerInterval == 0ms || !setupMultiFrameCapture())) {
+ return surface->getCanvas();
+ }
+ // Start the new timer. When timer expires, write to file.
+ mTimer.setTimeout(
+ [this] {
+ endCapture();
+ writeToFile();
+ // To avoid going in circles, set the flag to 0. This way the capture can be
+ // restarted just by setting the flag and without restarting the process.
+ base::SetProperty(PROPERTY_DEBUG_RENDERENGINE_CAPTURE_SKIA_MS, "0");
+ },
+ mTimerInterval);
+ }
+
+ // Create a canvas pointer, fill it.
+ SkCanvas* pictureCanvas = mMultiPic->beginPage(surface->width(), surface->height());
+
+ // 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();
+}
+
+void SkiaCapture::endCapture() {
+ ATRACE_CALL();
+ // Don't end anything if we are not running.
+ if (!mCaptureRunning) {
+ return;
+ }
+ // Reset the canvas pointer.
+ mNwayCanvas.reset();
+ // End page.
+ if (mMultiPic) {
+ mMultiPic->endPage();
+ }
+}
+
+void SkiaCapture::writeToFile() {
+ ATRACE_CALL();
+ // 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.");
+ });
+ mCaptureRunning = false;
+}
+
+bool SkiaCapture::setupMultiFrameCapture() {
+ ATRACE_CALL();
+ ALOGD("Set up multi-frame capture, ms = %llu", mTimerInterval.count());
+
+ std::string captureFile;
+ // Attach a timestamp to the file.
+ base::StringAppendF(&captureFile, "%s_%lld.mskp", CAPTURED_FILENAME_BASE.c_str(),
+ std::chrono::steady_clock::now().time_since_epoch().count());
+ auto stream = std::make_unique<SkFILEWStream>(captureFile.c_str());
+ // We own this stream and need to hold it until close() finishes.
+ if (stream->isValid()) {
+ mOpenMultiPicStream = std::move(stream);
+ mSerialContext.reset(new SkSharingSerialContext());
+ SkSerialProcs procs;
+ procs.fImageProc = SkSharingSerialContext::serializeImage;
+ procs.fImageCtx = mSerialContext.get();
+ procs.fTypefaceProc = [](SkTypeface* tf, void* ctx) {
+ return tf->serialize(SkTypeface::SerializeBehavior::kDoIncludeData);
+ };
+ // SkDocuments don't take ownership 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);
+ mCaptureRunning = true;
+ return true;
+ } else {
+ ALOGE("Could not open \"%s\" for writing.", captureFile.c_str());
+ return false;
+ }
+}
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android