| /* | 
 |  * 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' on the device. | 
 | static const std::string CAPTURED_FILENAME_BASE = "/data/user/re_skiacapture"; | 
 |  | 
 | SkiaCapture::~SkiaCapture() { | 
 |     mTimer.stop(); | 
 | } | 
 |  | 
 | SkCanvas* SkiaCapture::tryCapture(SkSurface* surface) NO_THREAD_SAFETY_ANALYSIS { | 
 |     ATRACE_CALL(); | 
 |  | 
 |     // If we are not running yet, set up. | 
 |     if (CC_LIKELY(!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] { | 
 |                     const std::scoped_lock lock(mMutex); | 
 |                     LOG_ALWAYS_FATAL_IF(mCurrentPageCanvas != nullptr); | 
 |                     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); | 
 |     } | 
 |  | 
 |     mMutex.lock(); | 
 |  | 
 |     // Create a canvas pointer, fill it. | 
 |     mCurrentPageCanvas = 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(mCurrentPageCanvas); | 
 |  | 
 |     return mNwayCanvas.get(); | 
 | } | 
 |  | 
 | void SkiaCapture::endCapture() NO_THREAD_SAFETY_ANALYSIS { | 
 |     ATRACE_CALL(); | 
 |     // Don't end anything if we are not running. | 
 |     if (CC_LIKELY(!mCaptureRunning)) { | 
 |         return; | 
 |     } | 
 |     // Reset the canvas pointer. | 
 |     mCurrentPageCanvas = nullptr; | 
 |     mNwayCanvas.reset(); | 
 |     // End page. | 
 |     if (mMultiPic) { | 
 |         mMultiPic->endPage(); | 
 |     } | 
 |     mMutex.unlock(); | 
 | } | 
 |  | 
 | SkCanvas* SkiaCapture::tryOffscreenCapture(SkSurface* surface, OffscreenState* state) { | 
 |     ATRACE_CALL(); | 
 |     // Don't start anything if we are not running. | 
 |     if (CC_LIKELY(!mCaptureRunning)) { | 
 |         return surface->getCanvas(); | 
 |     } | 
 |  | 
 |     // Create a canvas pointer, fill it. | 
 |     state->offscreenRecorder = std::make_unique<SkPictureRecorder>(); | 
 |     SkCanvas* pictureCanvas = | 
 |             state->offscreenRecorder->beginRecording(surface->width(), surface->height()); | 
 |  | 
 |     // Setting up an nway canvas is common to any kind of capture. | 
 |     state->offscreenCanvas = std::make_unique<SkNWayCanvas>(surface->width(), surface->height()); | 
 |     state->offscreenCanvas->addCanvas(surface->getCanvas()); | 
 |     state->offscreenCanvas->addCanvas(pictureCanvas); | 
 |  | 
 |     return state->offscreenCanvas.get(); | 
 | } | 
 |  | 
 | uint64_t SkiaCapture::endOffscreenCapture(OffscreenState* state) { | 
 |     ATRACE_CALL(); | 
 |     // Don't end anything if we are not running. | 
 |     if (CC_LIKELY(!mCaptureRunning)) { | 
 |         return 0; | 
 |     } | 
 |  | 
 |     // compute the uniqueID for this capture | 
 |     static std::atomic<uint64_t> nextID{1}; | 
 |     const uint64_t uniqueID = nextID.fetch_add(1, std::memory_order_relaxed); | 
 |  | 
 |     // Reset the canvas pointer as we are no longer drawing into it | 
 |     state->offscreenCanvas.reset(); | 
 |  | 
 |     // Record the offscreen as a picture in the currently active page. | 
 |     SkRect bounds = | 
 |             SkRect::Make(state->offscreenRecorder->getRecordingCanvas()->imageInfo().dimensions()); | 
 |     mCurrentPageCanvas | 
 |             ->drawAnnotation(bounds, | 
 |                              String8::format("OffscreenLayerDraw|%" PRId64, uniqueID).c_str(), | 
 |                              nullptr); | 
 |     mCurrentPageCanvas->drawPicture(state->offscreenRecorder->finishRecordingAsPicture()); | 
 |  | 
 |     // Reset the offscreen picture recorder | 
 |     state->offscreenRecorder.reset(); | 
 |  | 
 |     return uniqueID; | 
 | } | 
 |  | 
 | 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, name = std::move(mCaptureFile)] { | 
 |         ALOGD("Finalizing multi frame SKP"); | 
 |         doc->close(); | 
 |         delete stream; | 
 |         ALOGD("Multi frame SKP saved to %s.", name.c_str()); | 
 |         base::SetProperty(PROPERTY_DEBUG_RENDERENGINE_CAPTURE_FILENAME, name); | 
 |     }); | 
 |     mCaptureRunning = false; | 
 | } | 
 |  | 
 | bool SkiaCapture::setupMultiFrameCapture() { | 
 |     ATRACE_CALL(); | 
 |     ALOGD("Set up multi-frame capture, ms = %llu", mTimerInterval.count()); | 
 |     base::SetProperty(PROPERTY_DEBUG_RENDERENGINE_CAPTURE_FILENAME, ""); | 
 |     const std::scoped_lock lock(mMutex); | 
 |  | 
 |     // Attach a timestamp to the file. | 
 |     mCaptureFile.clear(); | 
 |     base::StringAppendF(&mCaptureFile, "%s_%lld.mskp", CAPTURED_FILENAME_BASE.c_str(), | 
 |                         std::chrono::steady_clock::now().time_since_epoch().count()); | 
 |     auto stream = std::make_unique<SkFILEWStream>(mCaptureFile.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 | 
 |         // The last argument is a callback for the endPage behavior. | 
 |         // See SkSharingProc.h for more explanation of this callback. | 
 |         mMultiPic = SkMakeMultiPictureDocument( | 
 |                 mOpenMultiPicStream.get(), &procs, | 
 |                 [sharingCtx = mSerialContext.get()](const SkPicture* pic) { | 
 |                     SkSharingSerialContext::collectNonTextureImagesFromPicture(pic, sharingCtx); | 
 |                 }); | 
 |         mCaptureRunning = true; | 
 |         return true; | 
 |     } else { | 
 |         ALOGE("Could not open \"%s\" for writing.", mCaptureFile.c_str()); | 
 |         return false; | 
 |     } | 
 | } | 
 |  | 
 | } // namespace skia | 
 | } // namespace renderengine | 
 | } // namespace android |