Enable offscreen buffers in mskp captures for RenderEngine
This CL also fixes a threading bug in mskp captures. Previously,
if the mskp timer fired in the middle of RE drawing a frame it would
crash because the timer thread would delete the canvas. The mutex
that was introduced will prevent the canvas from being deleted until
after the frame has completed drawing.
Test: capture mskp with background blur
Bug: 176903027
Change-Id: I59442d729b17a7edfd6df7ff9295864d1d67f0d7
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index b4a2d79..1c7a4a6 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -518,7 +518,7 @@
}
void SkiaGLRenderEngine::initCanvas(SkCanvas* canvas, const DisplaySettings& display) {
- if (mCapture->isCaptureRunning()) {
+ if (CC_UNLIKELY(mCapture->isCaptureRunning())) {
// Record display settings when capture is running.
std::stringstream displaySettings;
PrintTo(display, &displaySettings);
@@ -626,6 +626,7 @@
// offscreen buffer and when to render to the native buffer.
sk_sp<SkSurface> activeSurface(dstSurface);
SkCanvas* canvas = dstCanvas;
+ SkiaCapture::OffscreenState offscreenCaptureState;
const LayerSettings* blurCompositionLayer = nullptr;
if (mBlurFilter) {
bool requiresCompositionLayer = false;
@@ -642,7 +643,7 @@
}
if (requiresCompositionLayer) {
activeSurface = dstSurface->makeSurface(dstSurface->imageInfo());
- canvas = activeSurface->getCanvas();
+ canvas = mCapture->tryOffscreenCapture(activeSurface.get(), &offscreenCaptureState);
blurCompositionLayer = layer;
break;
}
@@ -692,7 +693,15 @@
// blit the offscreen framebuffer into the destination AHB
SkPaint paint;
paint.setBlendMode(SkBlendMode::kSrc);
- activeSurface->draw(dstCanvas, 0, 0, SkSamplingOptions(), &paint);
+ if (CC_UNLIKELY(mCapture->isCaptureRunning())) {
+ uint64_t id = mCapture->endOffscreenCapture(&offscreenCaptureState);
+ dstCanvas->drawAnnotation(SkRect::Make(dstCanvas->imageInfo().dimensions()),
+ String8::format("SurfaceID|%" PRId64, id).c_str(),
+ nullptr);
+ dstCanvas->drawImage(blurInput, 0, 0, SkSamplingOptions(), &paint);
+ } else {
+ activeSurface->draw(dstCanvas, 0, 0, SkSamplingOptions(), &paint);
+ }
// assign dstCanvas to canvas and ensure that the canvas state is up to date
canvas = dstCanvas;
@@ -709,7 +718,7 @@
}
canvas->save();
- if (mCapture->isCaptureRunning()) {
+ if (CC_UNLIKELY(mCapture->isCaptureRunning())) {
// Record the name of the layer if the capture is running.
std::stringstream layerSettings;
PrintTo(*layer, &layerSettings);
diff --git a/libs/renderengine/skia/debug/SkiaCapture.cpp b/libs/renderengine/skia/debug/SkiaCapture.cpp
index e9cfead..40f5cf2 100644
--- a/libs/renderengine/skia/debug/SkiaCapture.cpp
+++ b/libs/renderengine/skia/debug/SkiaCapture.cpp
@@ -41,11 +41,11 @@
mTimer.stop();
}
-SkCanvas* SkiaCapture::tryCapture(SkSurface* surface) {
+SkCanvas* SkiaCapture::tryCapture(SkSurface* surface) NO_THREAD_SAFETY_ANALYSIS {
ATRACE_CALL();
// If we are not running yet, set up.
- if (!mCaptureRunning) {
+ 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.
@@ -56,7 +56,8 @@
// Start the new timer. When timer expires, write to file.
mTimer.setTimeout(
[this] {
- endCapture();
+ 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.
@@ -65,29 +66,82 @@
mTimerInterval);
}
+ mMutex.lock();
+
// Create a canvas pointer, fill it.
- SkCanvas* pictureCanvas = mMultiPic->beginPage(surface->width(), surface->height());
+ 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(pictureCanvas);
+ mNwayCanvas->addCanvas(mCurrentPageCanvas);
return mNwayCanvas.get();
}
-void SkiaCapture::endCapture() {
+void SkiaCapture::endCapture() NO_THREAD_SAFETY_ANALYSIS {
ATRACE_CALL();
// Don't end anything if we are not running.
- if (!mCaptureRunning) {
+ 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() {
diff --git a/libs/renderengine/skia/debug/SkiaCapture.h b/libs/renderengine/skia/debug/SkiaCapture.h
index eaaf598..08a1359 100644
--- a/libs/renderengine/skia/debug/SkiaCapture.h
+++ b/libs/renderengine/skia/debug/SkiaCapture.h
@@ -18,8 +18,12 @@
#include <SkDocument.h>
#include <SkNWayCanvas.h>
+#include <SkPictureRecorder.h>
#include <SkSurface.h>
+
#include <chrono>
+#include <mutex>
+
#include "CaptureTimer.h"
#include "tools/SkSharingProc.h"
@@ -48,6 +52,16 @@
// Returns whether the capture is running.
bool isCaptureRunning() { return mCaptureRunning; }
+ // Offscreen state member variables are private to SkiaCapture, but the allocation
+ // and lifetime is managed by the caller. This enables nested offscreen
+ // captures to occur.
+ struct OffscreenState {
+ std::unique_ptr<SkPictureRecorder> offscreenRecorder;
+ std::unique_ptr<SkNWayCanvas> offscreenCanvas;
+ };
+ SkCanvas* tryOffscreenCapture(SkSurface* surface, OffscreenState* state);
+ uint64_t endOffscreenCapture(OffscreenState* state);
+
private:
// Performs the first-frame work of a multi frame SKP capture. Returns true if successful.
bool setupMultiFrameCapture();
@@ -61,10 +75,16 @@
std::unique_ptr<SkSharingSerialContext> mSerialContext;
std::unique_ptr<SkNWayCanvas> mNwayCanvas;
+ SkCanvas* mCurrentPageCanvas;
+
// Capturing and interval control.
bool mCaptureRunning = false;
CaptureTimer mTimer;
Interval mTimerInterval = 0ms;
+
+ // Mutex to ensure that a frame in progress when the timer fires is allowed to run to
+ // completion before we write the file to disk.
+ std::mutex mMutex;
};
} // namespace skia