• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 "SkCanvas.h"
31 #include "SkRect.h"
32 #include "SkTypeface.h"
33 #include "include/docs/SkMultiPictureDocument.h"
34 #include <sys/stat.h>
35 
36 namespace android {
37 namespace renderengine {
38 namespace skia {
39 
40 // The root of the filename to write a recorded SKP to. In order for this file to
41 // be written, user must run 'adb shell setenforce 0' on the device. Note: This
42 // is handled by record.sh. FIXME(b/296282988): With updated selinux policies,
43 // 'adb shell setenforce 0' should be unnecessary.
44 static const std::string CAPTURED_FILE_DIR = "/data/misc/mskps";
45 
~SkiaCapture()46 SkiaCapture::~SkiaCapture() {
47     mTimer.stop();
48 }
49 
tryCapture(SkSurface * surface)50 SkCanvas* SkiaCapture::tryCapture(SkSurface* surface) NO_THREAD_SAFETY_ANALYSIS {
51     ATRACE_CALL();
52 
53     // If we are not running yet, set up.
54     if (CC_LIKELY(!mCaptureRunning)) {
55         mTimerInterval = std::chrono::milliseconds(
56                 base::GetIntProperty(PROPERTY_DEBUG_RENDERENGINE_CAPTURE_SKIA_MS, 0));
57         // Set up the multi-frame capture. If we fail to set it up, then just return canvas.
58         // If interval is 0, return surface.
59         if (CC_LIKELY(mTimerInterval == 0ms || !setupMultiFrameCapture())) {
60             return surface->getCanvas();
61         }
62         // Start the new timer. When timer expires, write to file.
63         mTimer.setTimeout(
64                 [this] {
65                     const std::scoped_lock lock(mMutex);
66                     LOG_ALWAYS_FATAL_IF(mCurrentPageCanvas != nullptr);
67                     writeToFile();
68                     // To avoid going in circles, set the flag to 0. This way the capture can be
69                     // restarted just by setting the flag and without restarting the process.
70                     base::SetProperty(PROPERTY_DEBUG_RENDERENGINE_CAPTURE_SKIA_MS, "0");
71                 },
72                 mTimerInterval);
73     }
74 
75     mMutex.lock();
76 
77     // Create a canvas pointer, fill it.
78     mCurrentPageCanvas = mMultiPic->beginPage(surface->width(), surface->height());
79 
80     // Setting up an nway canvas is common to any kind of capture.
81     mNwayCanvas = std::make_unique<SkNWayCanvas>(surface->width(), surface->height());
82     mNwayCanvas->addCanvas(surface->getCanvas());
83     mNwayCanvas->addCanvas(mCurrentPageCanvas);
84 
85     return mNwayCanvas.get();
86 }
87 
endCapture()88 void SkiaCapture::endCapture() NO_THREAD_SAFETY_ANALYSIS {
89     ATRACE_CALL();
90     // Don't end anything if we are not running.
91     if (CC_LIKELY(!mCaptureRunning)) {
92         return;
93     }
94     // Reset the canvas pointer.
95     mCurrentPageCanvas = nullptr;
96     mNwayCanvas.reset();
97     // End page.
98     if (mMultiPic) {
99         mMultiPic->endPage();
100     }
101     mMutex.unlock();
102 }
103 
tryOffscreenCapture(SkSurface * surface,OffscreenState * state)104 SkCanvas* SkiaCapture::tryOffscreenCapture(SkSurface* surface, OffscreenState* state) {
105     ATRACE_CALL();
106     // Don't start anything if we are not running.
107     if (CC_LIKELY(!mCaptureRunning)) {
108         return surface->getCanvas();
109     }
110 
111     // Create a canvas pointer, fill it.
112     state->offscreenRecorder = std::make_unique<SkPictureRecorder>();
113     SkCanvas* pictureCanvas =
114             state->offscreenRecorder->beginRecording(surface->width(), surface->height());
115 
116     // Setting up an nway canvas is common to any kind of capture.
117     state->offscreenCanvas = std::make_unique<SkNWayCanvas>(surface->width(), surface->height());
118     state->offscreenCanvas->addCanvas(surface->getCanvas());
119     state->offscreenCanvas->addCanvas(pictureCanvas);
120 
121     return state->offscreenCanvas.get();
122 }
123 
endOffscreenCapture(OffscreenState * state)124 uint64_t SkiaCapture::endOffscreenCapture(OffscreenState* state) {
125     ATRACE_CALL();
126     // Don't end anything if we are not running.
127     if (CC_LIKELY(!mCaptureRunning)) {
128         return 0;
129     }
130 
131     // compute the uniqueID for this capture
132     static std::atomic<uint64_t> nextID{1};
133     const uint64_t uniqueID = nextID.fetch_add(1, std::memory_order_relaxed);
134 
135     // Reset the canvas pointer as we are no longer drawing into it
136     state->offscreenCanvas.reset();
137 
138     // Record the offscreen as a picture in the currently active page.
139     SkRect bounds =
140             SkRect::Make(state->offscreenRecorder->getRecordingCanvas()->imageInfo().dimensions());
141     mCurrentPageCanvas
142             ->drawAnnotation(bounds,
143                              String8::format("OffscreenLayerDraw|%" PRId64, uniqueID).c_str(),
144                              nullptr);
145     mCurrentPageCanvas->drawPicture(state->offscreenRecorder->finishRecordingAsPicture());
146 
147     // Reset the offscreen picture recorder
148     state->offscreenRecorder.reset();
149 
150     return uniqueID;
151 }
152 
writeToFile()153 void SkiaCapture::writeToFile() {
154     ATRACE_CALL();
155     // Pass mMultiPic and mOpenMultiPicStream to a background thread, which will
156     // handle the heavyweight serialization work and destroy them.
157     // mOpenMultiPicStream is released to a bare pointer because keeping it in
158     // a smart pointer makes the lambda non-copyable. The lambda is only called
159     // once, so this is safe.
160     SkFILEWStream* stream = mOpenMultiPicStream.release();
161     CommonPool::post([doc = std::move(mMultiPic), stream, name = std::move(mCaptureFile)] {
162         ALOGD("Finalizing multi frame SKP");
163         doc->close();
164         delete stream;
165         ALOGD("Multi frame SKP saved to %s.", name.c_str());
166         base::SetProperty(PROPERTY_DEBUG_RENDERENGINE_CAPTURE_FILENAME, name);
167     });
168     mCaptureRunning = false;
169 }
170 
setupMultiFrameCapture()171 bool SkiaCapture::setupMultiFrameCapture() {
172     ATRACE_CALL();
173     ALOGD("Set up multi-frame capture, ms = %llu", mTimerInterval.count());
174     base::SetProperty(PROPERTY_DEBUG_RENDERENGINE_CAPTURE_FILENAME, "");
175 
176     mkdir(CAPTURED_FILE_DIR.c_str(), 0700);
177 
178     const std::scoped_lock lock(mMutex);
179     mCaptureFile.clear();
180     base::StringAppendF(&mCaptureFile, "%s/re_skiacapture_%lld.mskp", CAPTURED_FILE_DIR.c_str(),
181                         std::chrono::steady_clock::now().time_since_epoch().count());
182     auto stream = std::make_unique<SkFILEWStream>(mCaptureFile.c_str());
183     // We own this stream and need to hold it until close() finishes.
184     if (stream->isValid()) {
185         mOpenMultiPicStream = std::move(stream);
186         mSerialContext.reset(new SkSharingSerialContext());
187         SkSerialProcs procs;
188         procs.fImageProc = SkSharingSerialContext::serializeImage;
189         procs.fImageCtx = mSerialContext.get();
190         procs.fTypefaceProc = [](SkTypeface* tf, void* ctx) {
191             return tf->serialize(SkTypeface::SerializeBehavior::kDoIncludeData);
192         };
193         // SkDocuments don't take ownership of the streams they write.
194         // we need to keep it until after mMultiPic.close()
195         // procs is passed as a pointer, but just as a method of having an optional default.
196         // procs doesn't need to outlive this Make call
197         // The last argument is a callback for the endPage behavior.
198         // See SkSharingProc.h for more explanation of this callback.
199         mMultiPic = SkMultiPictureDocument::Make(
200                 mOpenMultiPicStream.get(), &procs,
201                 [sharingCtx = mSerialContext.get()](const SkPicture* pic) {
202                     SkSharingSerialContext::collectNonTextureImagesFromPicture(pic, sharingCtx);
203                 });
204         mCaptureRunning = true;
205         return true;
206     } else {
207         ALOGE("Could not open \"%s\" for writing.", mCaptureFile.c_str());
208         return false;
209     }
210 }
211 
212 } // namespace skia
213 } // namespace renderengine
214 } // namespace android
215