/* * 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 #include #include #include #include #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(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(); 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(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 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(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