| Ana Krulec | 70d15b1b | 2020-12-01 10:05:15 -0800 | [diff] [blame] | 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 "src/utils/SkMultiPictureDocument.h" | 
|  | 31 |  | 
|  | 32 | namespace android { | 
|  | 33 | namespace renderengine { | 
|  | 34 | namespace 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. | 
|  | 38 | static const std::string CAPTURED_FILENAME_BASE = "/data/user/re_skiacapture"; | 
|  | 39 |  | 
|  | 40 | SkiaCapture::~SkiaCapture() { | 
|  | 41 | mTimer.stop(); | 
|  | 42 | } | 
|  | 43 |  | 
|  | 44 | SkCanvas* SkiaCapture::tryCapture(SkSurface* surface) { | 
|  | 45 | ATRACE_CALL(); | 
|  | 46 |  | 
|  | 47 | // If we are not running yet, set up. | 
|  | 48 | if (!mCaptureRunning) { | 
|  | 49 | 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] { | 
|  | 59 | endCapture(); | 
|  | 60 | writeToFile(); | 
|  | 61 | // To avoid going in circles, set the flag to 0. This way the capture can be | 
|  | 62 | // restarted just by setting the flag and without restarting the process. | 
|  | 63 | base::SetProperty(PROPERTY_DEBUG_RENDERENGINE_CAPTURE_SKIA_MS, "0"); | 
|  | 64 | }, | 
|  | 65 | mTimerInterval); | 
|  | 66 | } | 
|  | 67 |  | 
|  | 68 | // Create a canvas pointer, fill it. | 
|  | 69 | SkCanvas* pictureCanvas = mMultiPic->beginPage(surface->width(), surface->height()); | 
|  | 70 |  | 
|  | 71 | // Setting up an nway canvas is common to any kind of capture. | 
|  | 72 | mNwayCanvas = std::make_unique<SkNWayCanvas>(surface->width(), surface->height()); | 
|  | 73 | mNwayCanvas->addCanvas(surface->getCanvas()); | 
|  | 74 | mNwayCanvas->addCanvas(pictureCanvas); | 
|  | 75 |  | 
|  | 76 | return mNwayCanvas.get(); | 
|  | 77 | } | 
|  | 78 |  | 
|  | 79 | void SkiaCapture::endCapture() { | 
|  | 80 | ATRACE_CALL(); | 
|  | 81 | // Don't end anything if we are not running. | 
|  | 82 | if (!mCaptureRunning) { | 
|  | 83 | return; | 
|  | 84 | } | 
|  | 85 | // Reset the canvas pointer. | 
|  | 86 | mNwayCanvas.reset(); | 
|  | 87 | // End page. | 
|  | 88 | if (mMultiPic) { | 
|  | 89 | mMultiPic->endPage(); | 
|  | 90 | } | 
|  | 91 | } | 
|  | 92 |  | 
|  | 93 | void SkiaCapture::writeToFile() { | 
|  | 94 | ATRACE_CALL(); | 
|  | 95 | // Pass mMultiPic and mOpenMultiPicStream to a background thread, which will | 
|  | 96 | // handle the heavyweight serialization work and destroy them. | 
|  | 97 | // mOpenMultiPicStream is released to a bare pointer because keeping it in | 
|  | 98 | // a smart pointer makes the lambda non-copyable. The lambda is only called | 
|  | 99 | // once, so this is safe. | 
|  | 100 | SkFILEWStream* stream = mOpenMultiPicStream.release(); | 
|  | 101 | CommonPool::post([doc = std::move(mMultiPic), stream] { | 
|  | 102 | ALOGD("Finalizing multi frame SKP"); | 
|  | 103 | doc->close(); | 
|  | 104 | delete stream; | 
|  | 105 | ALOGD("Multi frame SKP complete."); | 
|  | 106 | }); | 
|  | 107 | mCaptureRunning = false; | 
|  | 108 | } | 
|  | 109 |  | 
|  | 110 | bool SkiaCapture::setupMultiFrameCapture() { | 
|  | 111 | ATRACE_CALL(); | 
|  | 112 | ALOGD("Set up multi-frame capture, ms = %llu", mTimerInterval.count()); | 
|  | 113 |  | 
|  | 114 | std::string captureFile; | 
|  | 115 | // Attach a timestamp to the file. | 
|  | 116 | base::StringAppendF(&captureFile, "%s_%lld.mskp", CAPTURED_FILENAME_BASE.c_str(), | 
|  | 117 | std::chrono::steady_clock::now().time_since_epoch().count()); | 
|  | 118 | auto stream = std::make_unique<SkFILEWStream>(captureFile.c_str()); | 
|  | 119 | // We own this stream and need to hold it until close() finishes. | 
|  | 120 | if (stream->isValid()) { | 
|  | 121 | mOpenMultiPicStream = std::move(stream); | 
|  | 122 | mSerialContext.reset(new SkSharingSerialContext()); | 
|  | 123 | SkSerialProcs procs; | 
|  | 124 | procs.fImageProc = SkSharingSerialContext::serializeImage; | 
|  | 125 | procs.fImageCtx = mSerialContext.get(); | 
|  | 126 | procs.fTypefaceProc = [](SkTypeface* tf, void* ctx) { | 
|  | 127 | return tf->serialize(SkTypeface::SerializeBehavior::kDoIncludeData); | 
|  | 128 | }; | 
|  | 129 | // SkDocuments don't take ownership of the streams they write. | 
|  | 130 | // we need to keep it until after mMultiPic.close() | 
|  | 131 | // procs is passed as a pointer, but just as a method of having an optional default. | 
| Nathaniel Nifong | 98fabaf | 2020-12-17 12:10:46 -0500 | [diff] [blame] | 132 | // procs doesn't need to outlive this Make call | 
|  | 133 | // The last argument is a callback for the endPage behavior. | 
|  | 134 | // See SkSharingProc.h for more explanation of this callback. | 
|  | 135 | mMultiPic = SkMakeMultiPictureDocument( | 
|  | 136 | mOpenMultiPicStream.get(), &procs, | 
|  | 137 | [sharingCtx = mSerialContext.get()](const SkPicture* pic) { | 
|  | 138 | SkSharingSerialContext::collectNonTextureImagesFromPicture(pic, sharingCtx); | 
|  | 139 | }); | 
| Ana Krulec | 70d15b1b | 2020-12-01 10:05:15 -0800 | [diff] [blame] | 140 | mCaptureRunning = true; | 
|  | 141 | return true; | 
|  | 142 | } else { | 
|  | 143 | ALOGE("Could not open \"%s\" for writing.", captureFile.c_str()); | 
|  | 144 | return false; | 
|  | 145 | } | 
|  | 146 | } | 
|  | 147 |  | 
|  | 148 | } // namespace skia | 
|  | 149 | } // namespace renderengine | 
|  | 150 | } // namespace android |