You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
208 lines
7.8 KiB
208 lines
7.8 KiB
/*
|
|
* 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
|