diff options
Diffstat (limited to 'libs/hwui/pipeline/skia/SkiaPipeline.cpp')
-rw-r--r-- | libs/hwui/pipeline/skia/SkiaPipeline.cpp | 429 |
1 files changed, 285 insertions, 144 deletions
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index 1f9ab5a242b4..5088494d6a07 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -19,31 +19,33 @@ #include <SkImageEncoder.h> #include <SkImageInfo.h> #include <SkImagePriv.h> +#include <SkMultiPictureDocument.h> #include <SkOverdrawCanvas.h> #include <SkOverdrawColorFilter.h> #include <SkPicture.h> #include <SkPictureRecorder.h> -#include "TreeInfo.h" +#include <SkSerialProcs.h> +#include <SkTypeface.h> +#include <android-base/properties.h> +#include <unistd.h> + +#include <sstream> + +#include "LightingInfo.h" #include "VectorDrawable.h" #include "thread/CommonPool.h" +#include "tools/SkSharingProc.h" +#include "utils/String8.h" #include "utils/TraceUtils.h" -#include <unistd.h> - using namespace android::uirenderer::renderthread; namespace android { namespace uirenderer { namespace skiapipeline { -float SkiaPipeline::mLightRadius = 0; -uint8_t SkiaPipeline::mAmbientShadowAlpha = 0; -uint8_t SkiaPipeline::mSpotShadowAlpha = 0; - -Vector3 SkiaPipeline::mLightCenter = {FLT_MIN, FLT_MIN, FLT_MIN}; - SkiaPipeline::SkiaPipeline(RenderThread& thread) : mRenderThread(thread) { - mVectorDrawables.reserve(30); + setSurfaceColorProperties(mColorMode); } SkiaPipeline::~SkiaPipeline() { @@ -73,18 +75,11 @@ void SkiaPipeline::unpinImages() { mPinnedImages.clear(); } -void SkiaPipeline::onPrepareTree() { - // The only time mVectorDrawables is not empty is if prepare tree was called 2 times without - // a renderFrame in the middle. - mVectorDrawables.clear(); -} - void SkiaPipeline::renderLayers(const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, bool opaque, const LightInfo& lightInfo) { - updateLighting(lightGeometry, lightInfo); + LightingInfo::updateLighting(lightGeometry, lightInfo); ATRACE_NAME("draw layers"); - renderVectorDrawableCache(); renderLayersImpl(*layerUpdateQueue, opaque); layerUpdateQueue->clear(); } @@ -98,54 +93,61 @@ void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) // only schedule repaint if node still on layer - possible it may have been // removed during a dropped frame, but layers may still remain scheduled so // as not to lose info on what portion is damaged - if (CC_LIKELY(layerNode->getLayerSurface() != nullptr)) { - SkASSERT(layerNode->getLayerSurface()); - SkiaDisplayList* displayList = (SkiaDisplayList*)layerNode->getDisplayList(); - if (!displayList || displayList->isEmpty()) { - SkDEBUGF(("%p drawLayers(%s) : missing drawable", layerNode, layerNode->getName())); - return; - } + if (CC_UNLIKELY(layerNode->getLayerSurface() == nullptr)) { + continue; + } + SkASSERT(layerNode->getLayerSurface()); + SkiaDisplayList* displayList = (SkiaDisplayList*)layerNode->getDisplayList(); + if (!displayList || displayList->isEmpty()) { + ALOGE("%p drawLayers(%s) : missing drawable", layerNode, layerNode->getName()); + return; + } - const Rect& layerDamage = layers.entries()[i].damage; + const Rect& layerDamage = layers.entries()[i].damage; - SkCanvas* layerCanvas = layerNode->getLayerSurface()->getCanvas(); + SkCanvas* layerCanvas = layerNode->getLayerSurface()->getCanvas(); - int saveCount = layerCanvas->save(); - SkASSERT(saveCount == 1); + int saveCount = layerCanvas->save(); + SkASSERT(saveCount == 1); - layerCanvas->androidFramework_setDeviceClipRestriction(layerDamage.toSkIRect()); + layerCanvas->androidFramework_setDeviceClipRestriction(layerDamage.toSkIRect()); - auto savedLightCenter = mLightCenter; - // map current light center into RenderNode's coordinate space - layerNode->getSkiaLayer()->inverseTransformInWindow.mapPoint3d(mLightCenter); + // TODO: put localized light center calculation and storage to a drawable related code. + // It does not seem right to store something localized in a global state + // fix here and in recordLayers + const Vector3 savedLightCenter(LightingInfo::getLightCenterRaw()); + Vector3 transformedLightCenter(savedLightCenter); + // map current light center into RenderNode's coordinate space + layerNode->getSkiaLayer()->inverseTransformInWindow.mapPoint3d(transformedLightCenter); + LightingInfo::setLightCenterRaw(transformedLightCenter); - const RenderProperties& properties = layerNode->properties(); - const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight()); - if (properties.getClipToBounds() && layerCanvas->quickReject(bounds)) { - return; - } + const RenderProperties& properties = layerNode->properties(); + const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight()); + if (properties.getClipToBounds() && layerCanvas->quickReject(bounds)) { + return; + } + + ATRACE_FORMAT("drawLayer [%s] %.1f x %.1f", layerNode->getName(), bounds.width(), + bounds.height()); - ATRACE_FORMAT("drawLayer [%s] %.1f x %.1f", layerNode->getName(), bounds.width(), - bounds.height()); - - layerNode->getSkiaLayer()->hasRenderedSinceRepaint = false; - layerCanvas->clear(SK_ColorTRANSPARENT); - - RenderNodeDrawable root(layerNode, layerCanvas, false); - root.forceDraw(layerCanvas); - layerCanvas->restoreToCount(saveCount); - mLightCenter = savedLightCenter; - - // cache the current context so that we can defer flushing it until - // either all the layers have been rendered or the context changes - GrContext* currentContext = layerNode->getLayerSurface()->getCanvas()->getGrContext(); - if (cachedContext.get() != currentContext) { - if (cachedContext.get()) { - ATRACE_NAME("flush layers (context changed)"); - cachedContext->flush(); - } - cachedContext.reset(SkSafeRef(currentContext)); + layerNode->getSkiaLayer()->hasRenderedSinceRepaint = false; + layerCanvas->clear(SK_ColorTRANSPARENT); + + RenderNodeDrawable root(layerNode, layerCanvas, false); + root.forceDraw(layerCanvas); + layerCanvas->restoreToCount(saveCount); + + LightingInfo::setLightCenterRaw(savedLightCenter); + + // cache the current context so that we can defer flushing it until + // either all the layers have been rendered or the context changes + GrContext* currentContext = layerNode->getLayerSurface()->getCanvas()->getGrContext(); + if (cachedContext.get() != currentContext) { + if (cachedContext.get()) { + ATRACE_NAME("flush layers (context changed)"); + cachedContext->flush(); } + cachedContext.reset(SkSafeRef(currentContext)); } } @@ -209,19 +211,6 @@ void SkiaPipeline::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) { } } -void SkiaPipeline::renderVectorDrawableCache() { - if (!mVectorDrawables.empty()) { - sp<VectorDrawableAtlas> atlas = mRenderThread.cacheManager().acquireVectorDrawableAtlas(); - auto grContext = mRenderThread.getGrContext(); - atlas->prepareForDraw(grContext); - ATRACE_NAME("Update VectorDrawables"); - for (auto vd : mVectorDrawables) { - vd->updateCache(atlas, grContext); - } - mVectorDrawables.clear(); - } -} - static void savePictureAsync(const sk_sp<SkData>& data, const std::string& filename) { CommonPool::post([data, filename] { if (0 == access(filename.c_str(), F_OK)) { @@ -232,57 +221,204 @@ static void savePictureAsync(const sk_sp<SkData>& data, const std::string& filen if (stream.isValid()) { stream.write(data->data(), data->size()); stream.flush(); - SkDebugf("SKP Captured Drawing Output (%d bytes) for frame. %s", stream.bytesWritten(), + ALOGD("SKP Captured Drawing Output (%zu bytes) for frame. %s", stream.bytesWritten(), filename.c_str()); } }); } -SkCanvas* SkiaPipeline::tryCapture(SkSurface* surface) { - if (CC_UNLIKELY(Properties::skpCaptureEnabled)) { - char prop[PROPERTY_VALUE_MAX] = {'\0'}; +// Note multiple SkiaPipeline instances may be loaded if more than one app is visible. +// Each instance may observe the filename changing and try to record to a file of the same name. +// Only the first one will succeed. There is no scope available here where we could coordinate +// to cause this function to return true for only one of the instances. +bool SkiaPipeline::shouldStartNewFileCapture() { + // Don't start a new file based capture if one is currently ongoing. + if (mCaptureMode != CaptureMode::None) { return false; } + + // A new capture is started when the filename property changes. + // Read the filename property. + std::string prop = base::GetProperty(PROPERTY_CAPTURE_SKP_FILENAME, "0"); + // if the filename property changed to a valid value + if (prop[0] != '0' && mCapturedFile != prop) { + // remember this new filename + mCapturedFile = prop; + // and get a property indicating how many frames to capture. + mCaptureSequence = base::GetIntProperty(PROPERTY_CAPTURE_SKP_FRAMES, 1); if (mCaptureSequence <= 0) { - property_get(PROPERTY_CAPTURE_SKP_FILENAME, prop, "0"); - if (prop[0] != '0' && mCapturedFile != prop) { - mCapturedFile = prop; - mCaptureSequence = property_get_int32(PROPERTY_CAPTURE_SKP_FRAMES, 1); - } + return false; + } else if (mCaptureSequence == 1) { + mCaptureMode = CaptureMode::SingleFrameSKP; + } else { + mCaptureMode = CaptureMode::MultiFrameSKP; } - if (mCaptureSequence > 0 || mPictureCapturedCallback) { - mRecorder.reset(new SkPictureRecorder()); - SkCanvas* pictureCanvas = - mRecorder->beginRecording(surface->width(), surface->height(), nullptr, - SkPictureRecorder::kPlaybackDrawPicture_RecordFlag); - mNwayCanvas = std::make_unique<SkNWayCanvas>(surface->width(), surface->height()); - mNwayCanvas->addCanvas(surface->getCanvas()); - mNwayCanvas->addCanvas(pictureCanvas); - return mNwayCanvas.get(); + return true; + } + return false; +} + +// performs the first-frame work of a multi frame SKP capture. Returns true if successful. +bool SkiaPipeline::setupMultiFrameCapture() { + ALOGD("Set up multi-frame capture, frames = %d", mCaptureSequence); + // We own this stream and need to hold it until close() finishes. + auto stream = std::make_unique<SkFILEWStream>(mCapturedFile.c_str()); + 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 owership 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. + mMultiPic = SkMakeMultiPictureDocument(mOpenMultiPicStream.get(), &procs); + return true; + } else { + ALOGE("Could not open \"%s\" for writing.", mCapturedFile.c_str()); + mCaptureSequence = 0; + mCaptureMode = CaptureMode::None; + return false; + } +} + +// recurse through the rendernode's children, add any nodes which are layers to the queue. +static void collectLayers(RenderNode* node, LayerUpdateQueue* layers) { + SkiaDisplayList* dl = (SkiaDisplayList*)node->getDisplayList(); + if (dl) { + const auto& prop = node->properties(); + if (node->hasLayer()) { + layers->enqueueLayerWithDamage(node, Rect(prop.getWidth(), prop.getHeight())); + } + // The way to recurse through rendernodes is to call this with a lambda. + dl->updateChildren([&](RenderNode* child) { collectLayers(child, layers); }); + } +} + +// record the provided layers to the provided canvas as self-contained skpictures. +static void recordLayers(const LayerUpdateQueue& layers, + SkCanvas* mskpCanvas) { + const Vector3 savedLightCenter(LightingInfo::getLightCenterRaw()); + // Record the commands to re-draw each dirty layer into an SkPicture + for (size_t i = 0; i < layers.entries().size(); i++) { + RenderNode* layerNode = layers.entries()[i].renderNode.get(); + const Rect& layerDamage = layers.entries()[i].damage; + const RenderProperties& properties = layerNode->properties(); + + // Temporarily map current light center into RenderNode's coordinate space + Vector3 transformedLightCenter(savedLightCenter); + layerNode->getSkiaLayer()->inverseTransformInWindow.mapPoint3d(transformedLightCenter); + LightingInfo::setLightCenterRaw(transformedLightCenter); + + SkPictureRecorder layerRec; + auto* recCanvas = layerRec.beginRecording(properties.getWidth(), + properties.getHeight()); + // This is not recorded but still causes clipping. + recCanvas->androidFramework_setDeviceClipRestriction(layerDamage.toSkIRect()); + RenderNodeDrawable root(layerNode, recCanvas, false); + root.forceDraw(recCanvas); + // Now write this picture into the SKP canvas with an annotation indicating what it is + mskpCanvas->drawAnnotation(layerDamage.toSkRect(), String8::format( + "OffscreenLayerDraw|%" PRId64, layerNode->uniqueId()).c_str(), nullptr); + mskpCanvas->drawPicture(layerRec.finishRecordingAsPicture()); + } + LightingInfo::setLightCenterRaw(savedLightCenter); +} + +SkCanvas* SkiaPipeline::tryCapture(SkSurface* surface, RenderNode* root, + const LayerUpdateQueue& dirtyLayers) { + if (CC_LIKELY(!Properties::skpCaptureEnabled)) { + return surface->getCanvas(); // Bail out early when capture is not turned on. + } + // Note that shouldStartNewFileCapture tells us if this is the *first* frame of a capture. + bool firstFrameOfAnim = false; + if (shouldStartNewFileCapture() && mCaptureMode == CaptureMode::MultiFrameSKP) { + // set a reminder to record every layer near the end of this method, after we have set up + // the nway canvas. + firstFrameOfAnim = true; + if (!setupMultiFrameCapture()) { + return surface->getCanvas(); } } - return surface->getCanvas(); + + // Create a canvas pointer, fill it depending on what kind of capture is requested (if any) + SkCanvas* pictureCanvas = nullptr; + switch (mCaptureMode) { + case CaptureMode::CallbackAPI: + case CaptureMode::SingleFrameSKP: + mRecorder.reset(new SkPictureRecorder()); + pictureCanvas = mRecorder->beginRecording(surface->width(), surface->height()); + break; + case CaptureMode::MultiFrameSKP: + // If a multi frame recording is active, initialize recording for a single frame of a + // multi frame file. + pictureCanvas = mMultiPic->beginPage(surface->width(), surface->height()); + break; + case CaptureMode::None: + // Returning here in the non-capture case means we can count on pictureCanvas being + // non-null below. + return surface->getCanvas(); + } + + // 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); + + if (firstFrameOfAnim) { + // On the first frame of any mskp capture we want to record any layers that are needed in + // frame but may have been rendered offscreen before recording began. + // We do not maintain a list of all layers, since it isn't needed outside this rare, + // recording use case. Traverse the tree to find them and put them in this LayerUpdateQueue. + LayerUpdateQueue luq; + collectLayers(root, &luq); + recordLayers(luq, mNwayCanvas.get()); + } else { + // on non-first frames, we record any normal layer draws (dirty regions) + recordLayers(dirtyLayers, mNwayCanvas.get()); + } + + return mNwayCanvas.get(); } void SkiaPipeline::endCapture(SkSurface* surface) { + if (CC_LIKELY(mCaptureMode == CaptureMode::None)) { return; } mNwayCanvas.reset(); - if (CC_UNLIKELY(mRecorder.get())) { - ATRACE_CALL(); + ATRACE_CALL(); + if (mCaptureSequence > 0 && mCaptureMode == CaptureMode::MultiFrameSKP) { + mMultiPic->endPage(); + mCaptureSequence--; + if (mCaptureSequence == 0) { + mCaptureMode = CaptureMode::None; + // 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]{ + ALOGD("Finalizing multi frame SKP"); + doc->close(); + delete stream; + ALOGD("Multi frame SKP complete."); + }); + } + } else { sk_sp<SkPicture> picture = mRecorder->finishRecordingAsPicture(); if (picture->approximateOpCount() > 0) { - if (mCaptureSequence > 0) { - ATRACE_BEGIN("picture->serialize"); - auto data = picture->serialize(); - ATRACE_END(); - - // offload saving to file in a different thread - if (1 == mCaptureSequence) { - savePictureAsync(data, mCapturedFile); - } else { - savePictureAsync(data, mCapturedFile + "_" + std::to_string(mCaptureSequence)); - } - mCaptureSequence--; - } if (mPictureCapturedCallback) { std::invoke(mPictureCapturedCallback, std::move(picture)); + } else { + // single frame skp to file + SkSerialProcs procs; + procs.fTypefaceProc = [](SkTypeface* tf, void* ctx){ + return tf->serialize(SkTypeface::SerializeBehavior::kDoIncludeData); + }; + auto data = picture->serialize(); + savePictureAsync(data, mCapturedFile); + mCaptureSequence = 0; + mCaptureMode = CaptureMode::None; } } mRecorder.reset(); @@ -298,22 +434,19 @@ void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& cli Properties::skpCaptureEnabled = true; } - renderVectorDrawableCache(); + // Initialize the canvas for the current frame, that might be a recording canvas if SKP + // capture is enabled. + SkCanvas* canvas = tryCapture(surface.get(), nodes[0].get(), layers); // draw all layers up front renderLayersImpl(layers, opaque); - // initialize the canvas for the current frame, that might be a recording canvas if SKP - // capture is enabled. - std::unique_ptr<SkPictureRecorder> recorder; - SkCanvas* canvas = tryCapture(surface.get()); - - renderFrameImpl(layers, clip, nodes, opaque, contentDrawBounds, canvas, preTransform); + renderFrameImpl(clip, nodes, opaque, contentDrawBounds, canvas, preTransform); endCapture(surface.get()); if (CC_UNLIKELY(Properties::debugOverdraw)) { - renderOverdraw(layers, clip, nodes, contentDrawBounds, surface, preTransform); + renderOverdraw(clip, nodes, contentDrawBounds, surface, preTransform); } ATRACE_NAME("flush commands"); @@ -329,12 +462,21 @@ static Rect nodeBounds(RenderNode& node) { } } // namespace -void SkiaPipeline::renderFrameImpl(const LayerUpdateQueue& layers, const SkRect& clip, +void SkiaPipeline::renderFrameImpl(const SkRect& clip, const std::vector<sp<RenderNode>>& nodes, bool opaque, const Rect& contentDrawBounds, SkCanvas* canvas, const SkMatrix& preTransform) { SkAutoCanvasRestore saver(canvas, true); - canvas->androidFramework_setDeviceClipRestriction(preTransform.mapRect(clip).roundOut()); + auto clipRestriction = preTransform.mapRect(clip).roundOut(); + if (CC_UNLIKELY(mCaptureMode == CaptureMode::SingleFrameSKP + || mCaptureMode == CaptureMode::MultiFrameSKP)) { + canvas->drawAnnotation(SkRect::Make(clipRestriction), "AndroidDeviceClipRestriction", + nullptr); + } else { + // clip drawing to dirty region only when not recording SKP files (which should contain all + // draw ops on every frame) + canvas->androidFramework_setDeviceClipRestriction(clipRestriction); + } canvas->concat(preTransform); // STOPSHIP: Revert, temporary workaround to clear always F16 frame buffer for b/74976293 @@ -430,13 +572,13 @@ void SkiaPipeline::renderFrameImpl(const LayerUpdateQueue& layers, const SkRect& } void SkiaPipeline::dumpResourceCacheUsage() const { - int resources, maxResources; - size_t bytes, maxBytes; + int resources; + size_t bytes; mRenderThread.getGrContext()->getResourceCacheUsage(&resources, &bytes); - mRenderThread.getGrContext()->getResourceCacheLimits(&maxResources, &maxBytes); + size_t maxBytes = mRenderThread.getGrContext()->getResourceCacheLimit(); SkString log("Resource Cache Usage:\n"); - log.appendf("%8d items out of %d maximum items\n", resources, maxResources); + log.appendf("%8d items\n", resources); log.appendf("%8zu bytes (%.2f MB) out of %.2f MB maximum\n", bytes, bytes * (1.0f / (1024.0f * 1024.0f)), maxBytes * (1.0f / (1024.0f * 1024.0f))); @@ -444,6 +586,7 @@ void SkiaPipeline::dumpResourceCacheUsage() const { } void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) { + mColorMode = colorMode; if (colorMode == ColorMode::SRGB) { mSurfaceColorType = SkColorType::kN32_SkColorType; mSurfaceColorSpace = SkColorSpace::MakeSRGB(); @@ -458,49 +601,47 @@ void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) { // Overdraw debugging // These colors should be kept in sync with Caches::getOverdrawColor() with a few differences. -// This implementation: -// (1) Requires transparent entries for "no overdraw" and "single draws". -// (2) Requires premul colors (instead of unpremul). -// (3) Requires RGBA colors (instead of BGRA). -static const uint32_t kOverdrawColors[2][6] = { - { - 0x00000000, - 0x00000000, - 0x2f2f0000, - 0x2f002f00, - 0x3f00003f, - 0x7f00007f, - }, - { - 0x00000000, - 0x00000000, - 0x2f2f0000, - 0x4f004f4f, - 0x5f50335f, - 0x7f00007f, - }, +// This implementation requires transparent entries for "no overdraw" and "single draws". +static const SkColor kOverdrawColors[2][6] = { + { + 0x00000000, + 0x00000000, + 0x2f0000ff, + 0x2f00ff00, + 0x3fff0000, + 0x7fff0000, + }, + { + 0x00000000, + 0x00000000, + 0x2f0000ff, + 0x4fffff00, + 0x5fff89d7, + 0x7fff0000, + }, }; -void SkiaPipeline::renderOverdraw(const LayerUpdateQueue& layers, const SkRect& clip, +void SkiaPipeline::renderOverdraw(const SkRect& clip, const std::vector<sp<RenderNode>>& nodes, const Rect& contentDrawBounds, sk_sp<SkSurface> surface, const SkMatrix& preTransform) { // Set up the overdraw canvas. SkImageInfo offscreenInfo = SkImageInfo::MakeA8(surface->width(), surface->height()); sk_sp<SkSurface> offscreen = surface->makeSurface(offscreenInfo); + LOG_ALWAYS_FATAL_IF(!offscreen, "Failed to create offscreen SkSurface for overdraw viz."); SkOverdrawCanvas overdrawCanvas(offscreen->getCanvas()); // Fake a redraw to replay the draw commands. This will increment the alpha channel // each time a pixel would have been drawn. // Pass true for opaque so we skip the clear - the overdrawCanvas is already zero // initialized. - renderFrameImpl(layers, clip, nodes, true, contentDrawBounds, &overdrawCanvas, preTransform); + renderFrameImpl(clip, nodes, true, contentDrawBounds, &overdrawCanvas, preTransform); sk_sp<SkImage> counts = offscreen->makeImageSnapshot(); // Draw overdraw colors to the canvas. The color filter will convert counts to colors. SkPaint paint; - const SkPMColor* colors = kOverdrawColors[static_cast<int>(Properties::overdrawColorSet)]; - paint.setColorFilter(SkOverdrawColorFilter::Make(colors)); + const SkColor* colors = kOverdrawColors[static_cast<int>(Properties::overdrawColorSet)]; + paint.setColorFilter(SkOverdrawColorFilter::MakeWithSkColors(colors)); surface->getCanvas()->drawImage(counts.get(), 0.0f, 0.0f, &paint); } |