blob: 40f5cf299d2bbd7c5b91e8f7c0ad5772107eb1a6 [file] [log] [blame]
Ana Krulec70d15b1b2020-12-01 10:05:15 -08001/*
2 * Copyright 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "SkiaCapture.h"
18
19#undef LOG_TAG
20#define LOG_TAG "RenderEngine"
21#define ATRACE_TAG ATRACE_TAG_GRAPHICS
22
23#include <android-base/properties.h>
24#include <android-base/stringprintf.h>
25#include <log/log.h>
26#include <renderengine/RenderEngine.h>
27#include <utils/Trace.h>
28
29#include "CommonPool.h"
30#include "src/utils/SkMultiPictureDocument.h"
31
32namespace android {
33namespace renderengine {
34namespace skia {
35
36// The root of the filename to write a recorded SKP to. In order for this file to
37// be written to /data/user/, user must run 'adb shell setenforce 0' in the device.
38static const std::string CAPTURED_FILENAME_BASE = "/data/user/re_skiacapture";
39
40SkiaCapture::~SkiaCapture() {
41 mTimer.stop();
42}
43
Derek Sollenberger76664d62021-02-04 11:13:09 -050044SkCanvas* SkiaCapture::tryCapture(SkSurface* surface) NO_THREAD_SAFETY_ANALYSIS {
Ana Krulec70d15b1b2020-12-01 10:05:15 -080045 ATRACE_CALL();
46
47 // If we are not running yet, set up.
Derek Sollenberger76664d62021-02-04 11:13:09 -050048 if (CC_LIKELY(!mCaptureRunning)) {
Ana Krulec70d15b1b2020-12-01 10:05:15 -080049 mTimerInterval = std::chrono::milliseconds(
50 base::GetIntProperty(PROPERTY_DEBUG_RENDERENGINE_CAPTURE_SKIA_MS, 0));
51 // Set up the multi-frame capture. If we fail to set it up, then just return canvas.
52 // If interval is 0, return surface.
53 if (CC_LIKELY(mTimerInterval == 0ms || !setupMultiFrameCapture())) {
54 return surface->getCanvas();
55 }
56 // Start the new timer. When timer expires, write to file.
57 mTimer.setTimeout(
58 [this] {
Derek Sollenberger76664d62021-02-04 11:13:09 -050059 const std::scoped_lock lock(mMutex);
60 LOG_ALWAYS_FATAL_IF(mCurrentPageCanvas != nullptr);
Ana Krulec70d15b1b2020-12-01 10:05:15 -080061 writeToFile();
62 // To avoid going in circles, set the flag to 0. This way the capture can be
63 // restarted just by setting the flag and without restarting the process.
64 base::SetProperty(PROPERTY_DEBUG_RENDERENGINE_CAPTURE_SKIA_MS, "0");
65 },
66 mTimerInterval);
67 }
68
Derek Sollenberger76664d62021-02-04 11:13:09 -050069 mMutex.lock();
70
Ana Krulec70d15b1b2020-12-01 10:05:15 -080071 // Create a canvas pointer, fill it.
Derek Sollenberger76664d62021-02-04 11:13:09 -050072 mCurrentPageCanvas = mMultiPic->beginPage(surface->width(), surface->height());
Ana Krulec70d15b1b2020-12-01 10:05:15 -080073
74 // Setting up an nway canvas is common to any kind of capture.
75 mNwayCanvas = std::make_unique<SkNWayCanvas>(surface->width(), surface->height());
76 mNwayCanvas->addCanvas(surface->getCanvas());
Derek Sollenberger76664d62021-02-04 11:13:09 -050077 mNwayCanvas->addCanvas(mCurrentPageCanvas);
Ana Krulec70d15b1b2020-12-01 10:05:15 -080078
79 return mNwayCanvas.get();
80}
81
Derek Sollenberger76664d62021-02-04 11:13:09 -050082void SkiaCapture::endCapture() NO_THREAD_SAFETY_ANALYSIS {
Ana Krulec70d15b1b2020-12-01 10:05:15 -080083 ATRACE_CALL();
84 // Don't end anything if we are not running.
Derek Sollenberger76664d62021-02-04 11:13:09 -050085 if (CC_LIKELY(!mCaptureRunning)) {
Ana Krulec70d15b1b2020-12-01 10:05:15 -080086 return;
87 }
88 // Reset the canvas pointer.
Derek Sollenberger76664d62021-02-04 11:13:09 -050089 mCurrentPageCanvas = nullptr;
Ana Krulec70d15b1b2020-12-01 10:05:15 -080090 mNwayCanvas.reset();
91 // End page.
92 if (mMultiPic) {
93 mMultiPic->endPage();
94 }
Derek Sollenberger76664d62021-02-04 11:13:09 -050095 mMutex.unlock();
96}
97
98SkCanvas* SkiaCapture::tryOffscreenCapture(SkSurface* surface, OffscreenState* state) {
99 ATRACE_CALL();
100 // Don't start anything if we are not running.
101 if (CC_LIKELY(!mCaptureRunning)) {
102 return surface->getCanvas();
103 }
104
105 // Create a canvas pointer, fill it.
106 state->offscreenRecorder = std::make_unique<SkPictureRecorder>();
107 SkCanvas* pictureCanvas =
108 state->offscreenRecorder->beginRecording(surface->width(), surface->height());
109
110 // Setting up an nway canvas is common to any kind of capture.
111 state->offscreenCanvas = std::make_unique<SkNWayCanvas>(surface->width(), surface->height());
112 state->offscreenCanvas->addCanvas(surface->getCanvas());
113 state->offscreenCanvas->addCanvas(pictureCanvas);
114
115 return state->offscreenCanvas.get();
116}
117
118uint64_t SkiaCapture::endOffscreenCapture(OffscreenState* state) {
119 ATRACE_CALL();
120 // Don't end anything if we are not running.
121 if (CC_LIKELY(!mCaptureRunning)) {
122 return 0;
123 }
124
125 // compute the uniqueID for this capture
126 static std::atomic<uint64_t> nextID{1};
127 const uint64_t uniqueID = nextID.fetch_add(1, std::memory_order_relaxed);
128
129 // Reset the canvas pointer as we are no longer drawing into it
130 state->offscreenCanvas.reset();
131
132 // Record the offscreen as a picture in the currently active page.
133 SkRect bounds =
134 SkRect::Make(state->offscreenRecorder->getRecordingCanvas()->imageInfo().dimensions());
135 mCurrentPageCanvas
136 ->drawAnnotation(bounds,
137 String8::format("OffscreenLayerDraw|%" PRId64, uniqueID).c_str(),
138 nullptr);
139 mCurrentPageCanvas->drawPicture(state->offscreenRecorder->finishRecordingAsPicture());
140
141 // Reset the offscreen picture recorder
142 state->offscreenRecorder.reset();
143
144 return uniqueID;
Ana Krulec70d15b1b2020-12-01 10:05:15 -0800145}
146
147void SkiaCapture::writeToFile() {
148 ATRACE_CALL();
149 // Pass mMultiPic and mOpenMultiPicStream to a background thread, which will
150 // handle the heavyweight serialization work and destroy them.
151 // mOpenMultiPicStream is released to a bare pointer because keeping it in
152 // a smart pointer makes the lambda non-copyable. The lambda is only called
153 // once, so this is safe.
154 SkFILEWStream* stream = mOpenMultiPicStream.release();
155 CommonPool::post([doc = std::move(mMultiPic), stream] {
156 ALOGD("Finalizing multi frame SKP");
157 doc->close();
158 delete stream;
159 ALOGD("Multi frame SKP complete.");
160 });
161 mCaptureRunning = false;
162}
163
164bool SkiaCapture::setupMultiFrameCapture() {
165 ATRACE_CALL();
166 ALOGD("Set up multi-frame capture, ms = %llu", mTimerInterval.count());
167
168 std::string captureFile;
169 // Attach a timestamp to the file.
170 base::StringAppendF(&captureFile, "%s_%lld.mskp", CAPTURED_FILENAME_BASE.c_str(),
171 std::chrono::steady_clock::now().time_since_epoch().count());
172 auto stream = std::make_unique<SkFILEWStream>(captureFile.c_str());
173 // We own this stream and need to hold it until close() finishes.
174 if (stream->isValid()) {
175 mOpenMultiPicStream = std::move(stream);
176 mSerialContext.reset(new SkSharingSerialContext());
177 SkSerialProcs procs;
178 procs.fImageProc = SkSharingSerialContext::serializeImage;
179 procs.fImageCtx = mSerialContext.get();
180 procs.fTypefaceProc = [](SkTypeface* tf, void* ctx) {
181 return tf->serialize(SkTypeface::SerializeBehavior::kDoIncludeData);
182 };
183 // SkDocuments don't take ownership of the streams they write.
184 // we need to keep it until after mMultiPic.close()
185 // procs is passed as a pointer, but just as a method of having an optional default.
Nathaniel Nifong98fabaf2020-12-17 12:10:46 -0500186 // procs doesn't need to outlive this Make call
187 // The last argument is a callback for the endPage behavior.
188 // See SkSharingProc.h for more explanation of this callback.
189 mMultiPic = SkMakeMultiPictureDocument(
190 mOpenMultiPicStream.get(), &procs,
191 [sharingCtx = mSerialContext.get()](const SkPicture* pic) {
192 SkSharingSerialContext::collectNonTextureImagesFromPicture(pic, sharingCtx);
193 });
Ana Krulec70d15b1b2020-12-01 10:05:15 -0800194 mCaptureRunning = true;
195 return true;
196 } else {
197 ALOGE("Could not open \"%s\" for writing.", captureFile.c_str());
198 return false;
199 }
200}
201
202} // namespace skia
203} // namespace renderengine
204} // namespace android