/* * Copyright (C) 2016 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 "pipeline/skia/SkiaPipeline.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "LightingInfo.h" #include "VectorDrawable.h" #include "include/gpu/GpuTypes.h" // from Skia #include "thread/CommonPool.h" #include "tools/SkSharingProc.h" #include "utils/Color.h" #include "utils/String8.h" using namespace android::uirenderer::renderthread; namespace android { namespace uirenderer { namespace skiapipeline { SkiaPipeline::SkiaPipeline(RenderThread& thread) : mRenderThread(thread) { setSurfaceColorProperties(mColorMode); } SkiaPipeline::~SkiaPipeline() {} void SkiaPipeline::onDestroyHardwareResources() { unpinImages(); mRenderThread.cacheManager().trimStaleResources(); } void SkiaPipeline::renderLayers(const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, bool opaque, const LightInfo& lightInfo) { LightingInfo::updateLighting(lightGeometry, lightInfo); ATRACE_NAME("draw layers"); renderLayersImpl(*layerUpdateQueue, opaque); layerUpdateQueue->clear(); } bool SkiaPipeline::renderLayerImpl(RenderNode* layerNode, const Rect& layerDamage) { SkASSERT(layerNode->getLayerSurface()); SkiaDisplayList* displayList = layerNode->getDisplayList().asSkiaDl(); if (!displayList || displayList->isEmpty()) { ALOGE("%p drawLayers(%s) : missing drawable", layerNode, layerNode->getName()); return false; } SkCanvas* layerCanvas = layerNode->getLayerSurface()->getCanvas(); int saveCount = layerCanvas->save(); SkASSERT(saveCount == 1); layerCanvas->androidFramework_setDeviceClipRestriction(layerDamage.toSkIRect()); // 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 false; } 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); LightingInfo::setLightCenterRaw(savedLightCenter); return true; } static void savePictureAsync(const sk_sp& data, const std::string& filename) { CommonPool::post([data, filename] { if (0 == access(filename.c_str(), F_OK)) { return; } SkFILEWStream stream(filename.c_str()); if (stream.isValid()) { stream.write(data->data(), data->size()); stream.flush(); ALOGD("SKP Captured Drawing Output (%zu bytes) for frame. %s", stream.bytesWritten(), filename.c_str()); } }); } // 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) { return false; } else if (mCaptureSequence == 1) { mCaptureMode = CaptureMode::SingleFrameSKP; } else { mCaptureMode = CaptureMode::MultiFrameSKP; } 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(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 = SkMultiPictureDocument::Make(mOpenMultiPicStream.get(), &procs, [sharingCtx = mSerialContext.get()](const SkPicture* pic) { SkSharingSerialContext::collectNonTextureImagesFromPicture(pic, sharingCtx); }); 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 = node->getDisplayList().asSkiaDl(); 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(); } } // 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(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(); 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 picture = mRecorder->finishRecordingAsPicture(); if (picture->approximateOpCount() > 0) { 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); }; procs.fImageProc = [](SkImage* img, void* ctx) -> sk_sp { GrDirectContext* dCtx = static_cast(ctx); return SkPngEncoder::Encode(dCtx, img, SkPngEncoder::Options{}); }; procs.fImageCtx = mRenderThread.getGrContext(); auto data = picture->serialize(&procs); savePictureAsync(data, mCapturedFile); mCaptureSequence = 0; mCaptureMode = CaptureMode::None; } } mRecorder.reset(); } } void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& clip, const std::vector>& nodes, bool opaque, const Rect& contentDrawBounds, sk_sp surface, const SkMatrix& preTransform) { bool previousSkpEnabled = Properties::skpCaptureEnabled; if (mPictureCapturedCallback) { Properties::skpCaptureEnabled = true; } // 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); renderFrameImpl(clip, nodes, opaque, contentDrawBounds, canvas, preTransform); endCapture(surface.get()); if (CC_UNLIKELY(Properties::debugOverdraw)) { renderOverdraw(clip, nodes, contentDrawBounds, surface, preTransform); } Properties::skpCaptureEnabled = previousSkpEnabled; } namespace { static Rect nodeBounds(RenderNode& node) { auto& props = node.properties(); return Rect(props.getLeft(), props.getTop(), props.getRight(), props.getBottom()); } } // namespace void SkiaPipeline::renderFrameImpl(const SkRect& clip, const std::vector>& nodes, bool opaque, const Rect& contentDrawBounds, SkCanvas* canvas, const SkMatrix& preTransform) { SkAutoCanvasRestore saver(canvas, true); auto clipRestriction = preTransform.mapRect(clip).roundOut(); if (CC_UNLIKELY(isCapturingSkp())) { 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); if (!opaque) { canvas->clear(SK_ColorTRANSPARENT); } if (1 == nodes.size()) { if (!nodes[0]->nothingToDraw()) { RenderNodeDrawable root(nodes[0].get(), canvas); root.draw(canvas); } } else if (0 == nodes.size()) { // nothing to draw } else { // It there are multiple render nodes, they are laid out as follows: // #0 - backdrop (content + caption) // #1 - content (local bounds are at (0,0), will be translated and clipped to backdrop) // #2 - additional overlay nodes // Usually the backdrop cannot be seen since it will be entirely covered by the content. // While // resizing however it might become partially visible. The following render loop will crop // the // backdrop against the content and draw the remaining part of it. It will then draw the // content // cropped to the backdrop (since that indicates a shrinking of the window). // // Additional nodes will be drawn on top with no particular clipping semantics. // Usually the contents bounds should be mContentDrawBounds - however - we will // move it towards the fixed edge to give it a more stable appearance (for the moment). // If there is no content bounds we ignore the layering as stated above and start with 2. // Backdrop bounds in render target space const Rect backdrop = nodeBounds(*nodes[0]); // Bounds that content will fill in render target space (note content node bounds may be // bigger) Rect content(contentDrawBounds.getWidth(), contentDrawBounds.getHeight()); content.translate(backdrop.left, backdrop.top); if (!content.contains(backdrop) && !nodes[0]->nothingToDraw()) { // Content doesn't entirely overlap backdrop, so fill around content (right/bottom) // Note: in the future, if content doesn't snap to backdrop's left/top, this may need to // also fill left/top. Currently, both 2up and freeform position content at the top/left // of // the backdrop, so this isn't necessary. RenderNodeDrawable backdropNode(nodes[0].get(), canvas); if (content.right < backdrop.right) { // draw backdrop to right side of content SkAutoCanvasRestore acr(canvas, true); canvas->clipRect(SkRect::MakeLTRB(content.right, backdrop.top, backdrop.right, backdrop.bottom)); backdropNode.draw(canvas); } if (content.bottom < backdrop.bottom) { // draw backdrop to bottom of content // Note: bottom fill uses content left/right, to avoid overdrawing left/right fill SkAutoCanvasRestore acr(canvas, true); canvas->clipRect(SkRect::MakeLTRB(content.left, content.bottom, content.right, backdrop.bottom)); backdropNode.draw(canvas); } } RenderNodeDrawable contentNode(nodes[1].get(), canvas); if (!backdrop.isEmpty()) { // content node translation to catch up with backdrop float dx = backdrop.left - contentDrawBounds.left; float dy = backdrop.top - contentDrawBounds.top; SkAutoCanvasRestore acr(canvas, true); canvas->translate(dx, dy); const SkRect contentLocalClip = SkRect::MakeXYWH(contentDrawBounds.left, contentDrawBounds.top, backdrop.getWidth(), backdrop.getHeight()); canvas->clipRect(contentLocalClip); contentNode.draw(canvas); } else { SkAutoCanvasRestore acr(canvas, true); contentNode.draw(canvas); } // remaining overlay nodes, simply defer for (size_t index = 2; index < nodes.size(); index++) { if (!nodes[index]->nothingToDraw()) { SkAutoCanvasRestore acr(canvas, true); RenderNodeDrawable overlayNode(nodes[index].get(), canvas); overlayNode.draw(canvas); } } } } void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) { mColorMode = colorMode; switch (colorMode) { case ColorMode::Default: mSurfaceColorType = SkColorType::kN32_SkColorType; mSurfaceColorSpace = SkColorSpace::MakeSRGB(); break; case ColorMode::WideColorGamut: mSurfaceColorType = DeviceInfo::get()->getWideColorType(); mSurfaceColorSpace = DeviceInfo::get()->getWideColorSpace(); break; case ColorMode::Hdr: if (DeviceInfo::get()->isSupportRgba10101010ForHdr()) { mSurfaceColorType = SkColorType::kRGBA_10x6_SkColorType; mSurfaceColorSpace = SkColorSpace::MakeRGB( GetExtendedTransferFunction(mTargetSdrHdrRatio), SkNamedGamut::kDisplayP3); } else if (DeviceInfo::get()->isSupportFp16ForHdr()) { mSurfaceColorType = SkColorType::kRGBA_F16_SkColorType; mSurfaceColorSpace = SkColorSpace::MakeSRGB(); } else { mSurfaceColorType = SkColorType::kN32_SkColorType; mSurfaceColorSpace = SkColorSpace::MakeRGB( GetExtendedTransferFunction(mTargetSdrHdrRatio), SkNamedGamut::kDisplayP3); } break; case ColorMode::Hdr10: mSurfaceColorType = SkColorType::kRGBA_1010102_SkColorType; mSurfaceColorSpace = SkColorSpace::MakeRGB( GetExtendedTransferFunction(mTargetSdrHdrRatio), SkNamedGamut::kDisplayP3); break; case ColorMode::A8: mSurfaceColorType = SkColorType::kAlpha_8_SkColorType; mSurfaceColorSpace = nullptr; break; } } void SkiaPipeline::setTargetSdrHdrRatio(float ratio) { if (mColorMode == ColorMode::Hdr || mColorMode == ColorMode::Hdr10) { mTargetSdrHdrRatio = ratio; if (mColorMode == ColorMode::Hdr && DeviceInfo::get()->isSupportFp16ForHdr() && !DeviceInfo::get()->isSupportRgba10101010ForHdr()) { mSurfaceColorSpace = SkColorSpace::MakeSRGB(); } else { mSurfaceColorSpace = SkColorSpace::MakeRGB( GetExtendedTransferFunction(mTargetSdrHdrRatio), SkNamedGamut::kDisplayP3); } } else { mTargetSdrHdrRatio = 1.f; } } // Overdraw debugging // These colors should be kept in sync with Caches::getOverdrawColor() with a few differences. // 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 SkRect& clip, const std::vector>& nodes, const Rect& contentDrawBounds, sk_sp surface, const SkMatrix& preTransform) { // Set up the overdraw canvas. SkImageInfo offscreenInfo = SkImageInfo::MakeA8(surface->width(), surface->height()); sk_sp 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(clip, nodes, true, contentDrawBounds, &overdrawCanvas, preTransform); sk_sp counts = offscreen->makeImageSnapshot(); // Draw overdraw colors to the canvas. The color filter will convert counts to colors. SkPaint paint; const SkColor* colors = kOverdrawColors[static_cast(Properties::overdrawColorSet)]; paint.setColorFilter(SkOverdrawColorFilter::MakeWithSkColors(colors)); surface->getCanvas()->drawImage(counts.get(), 0.0f, 0.0f, SkSamplingOptions(), &paint); } } /* namespace skiapipeline */ } /* namespace uirenderer */ } /* namespace android */