diff options
Diffstat (limited to 'libs/hwui/pipeline')
21 files changed, 731 insertions, 809 deletions
diff --git a/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp b/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp new file mode 100644 index 000000000000..234f42d79cb7 --- /dev/null +++ b/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp @@ -0,0 +1,177 @@ +/* + * Copyright (C) 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 "ATraceMemoryDump.h" + +#include <utils/Trace.h> + +#include <cstring> + +namespace android { +namespace uirenderer { +namespace skiapipeline { + +// When purgeable is INVALID_MEMORY_SIZE it won't be logged at all. +#define INVALID_MEMORY_SIZE -1 + +/** + * Skia invokes the following SkTraceMemoryDump functions: + * 1. dumpNumericValue (dumpName, units="bytes", valueName="size") + * 2. dumpStringValue (dumpName, valueName="type") [optional -> for example CPU memory does not + * invoke dumpStringValue] + * 3. dumpNumericValue (dumpName, units="bytes", valueName="purgeable_size") [optional] + * 4. setMemoryBacking(dumpName, backingType) [optional -> for example Vulkan GPU resources do not + * invoke setMemoryBacking] + * + * ATraceMemoryDump calculates memory category first by looking at the "type" string passed to + * dumpStringValue and then by looking at "backingType" passed to setMemoryBacking. + * Only GPU Texture memory is tracked separately and everything else is grouped as one + * "Misc Memory" category. + */ +static std::unordered_map<const char*, const char*> sResourceMap = { + {"malloc", "HWUI CPU Memory"}, // taken from setMemoryBacking(backingType) + {"gl_texture", "HWUI Texture Memory"}, // taken from setMemoryBacking(backingType) + {"Texture", "HWUI Texture Memory"}, // taken from dumpStringValue(value, valueName="type") + // Uncomment categories below to split "Misc Memory" into more brackets for debugging. + /*{"vk_buffer", "vk_buffer"}, + {"gl_renderbuffer", "gl_renderbuffer"}, + {"gl_buffer", "gl_buffer"}, + {"RenderTarget", "RenderTarget"}, + {"Stencil", "Stencil"}, + {"Path Data", "Path Data"}, + {"Buffer Object", "Buffer Object"}, + {"Surface", "Surface"},*/ +}; + +ATraceMemoryDump::ATraceMemoryDump() { + mLastDumpName.reserve(100); + mCategory.reserve(100); +} + +void ATraceMemoryDump::dumpNumericValue(const char* dumpName, const char* valueName, + const char* units, uint64_t value) { + if (!strcmp(units, "bytes")) { + recordAndResetCountersIfNeeded(dumpName); + if (!strcmp(valueName, "size")) { + mLastDumpValue = value; + } else if (!strcmp(valueName, "purgeable_size")) { + mLastPurgeableDumpValue = value; + } + } +} + +void ATraceMemoryDump::dumpStringValue(const char* dumpName, const char* valueName, + const char* value) { + if (!strcmp(valueName, "type")) { + recordAndResetCountersIfNeeded(dumpName); + auto categoryIt = sResourceMap.find(value); + if (categoryIt != sResourceMap.end()) { + mCategory = categoryIt->second; + } + } +} + +void ATraceMemoryDump::setMemoryBacking(const char* dumpName, const char* backingType, + const char* backingObjectId) { + recordAndResetCountersIfNeeded(dumpName); + auto categoryIt = sResourceMap.find(backingType); + if (categoryIt != sResourceMap.end()) { + mCategory = categoryIt->second; + } +} + +/** + * startFrame is invoked before dumping anything. It resets counters from the previous frame. + * This is important, because if there is no new data for a given category trace would assume + * usage has not changed (instead of reporting 0). + */ +void ATraceMemoryDump::startFrame() { + resetCurrentCounter(""); + for (auto& it : mCurrentValues) { + // Once a category is observed in at least one frame, it is always reported in subsequent + // frames (even if it is 0). Not logging a category to ATRACE would mean its value has not + // changed since the previous frame, which is not what we want. + it.second.memory = 0; + // If purgeableMemory is INVALID_MEMORY_SIZE, then logTraces won't log it at all. + if (it.second.purgeableMemory != INVALID_MEMORY_SIZE) { + it.second.purgeableMemory = 0; + } + } +} + +/** + * logTraces reads from mCurrentValues and logs the counters with ATRACE. + */ +void ATraceMemoryDump::logTraces() { + // Accumulate data from last dumpName + recordAndResetCountersIfNeeded(""); + uint64_t hwui_all_frame_memory = 0; + for (auto& it : mCurrentValues) { + hwui_all_frame_memory += it.second.memory; + ATRACE_INT64(it.first.c_str(), it.second.memory); + if (it.second.purgeableMemory != INVALID_MEMORY_SIZE) { + ATRACE_INT64((std::string("Purgeable ") + it.first).c_str(), it.second.purgeableMemory); + } + } + ATRACE_INT64("HWUI All Memory", hwui_all_frame_memory); +} + +/** + * recordAndResetCountersIfNeeded reads memory usage from mLastDumpValue/mLastPurgeableDumpValue and + * accumulates in mCurrentValues[category]. It makes provision to create a new category and track + * purgeable memory only if there is at least one observation. + * recordAndResetCountersIfNeeded won't do anything until all the information for a given dumpName + * is received. + */ +void ATraceMemoryDump::recordAndResetCountersIfNeeded(const char* dumpName) { + if (!mLastDumpName.compare(dumpName)) { + // Still waiting for more data for current dumpName. + return; + } + + // First invocation will have an empty mLastDumpName. + if (!mLastDumpName.empty()) { + // A new dumpName observed -> store the data already collected. + auto memoryCounter = mCurrentValues.find(mCategory); + if (memoryCounter != mCurrentValues.end()) { + memoryCounter->second.memory += mLastDumpValue; + if (mLastPurgeableDumpValue != INVALID_MEMORY_SIZE) { + if (memoryCounter->second.purgeableMemory == INVALID_MEMORY_SIZE) { + memoryCounter->second.purgeableMemory = mLastPurgeableDumpValue; + } else { + memoryCounter->second.purgeableMemory += mLastPurgeableDumpValue; + } + } + } else { + mCurrentValues[mCategory] = {mLastDumpValue, mLastPurgeableDumpValue}; + } + } + + // Reset counters and default category for the newly observed "dumpName". + resetCurrentCounter(dumpName); +} + +void ATraceMemoryDump::resetCurrentCounter(const char* dumpName) { + mLastDumpValue = 0; + mLastPurgeableDumpValue = INVALID_MEMORY_SIZE; + mLastDumpName = dumpName; + // Categories not listed in sResourceMap are reported as "Misc Memory" + mCategory = "HWUI Misc Memory"; +} + +} /* namespace skiapipeline */ +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/pipeline/skia/ATraceMemoryDump.h b/libs/hwui/pipeline/skia/ATraceMemoryDump.h new file mode 100644 index 000000000000..4592711dd5b5 --- /dev/null +++ b/libs/hwui/pipeline/skia/ATraceMemoryDump.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 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. + */ + +#pragma once + +#include <SkString.h> +#include <SkTraceMemoryDump.h> + +#include <string> +#include <unordered_map> +#include <utility> + +namespace android { +namespace uirenderer { +namespace skiapipeline { + +class ATraceMemoryDump : public SkTraceMemoryDump { +public: + ATraceMemoryDump(); + ~ATraceMemoryDump() override {} + + void dumpNumericValue(const char* dumpName, const char* valueName, const char* units, + uint64_t value) override; + + void dumpStringValue(const char* dumpName, const char* valueName, const char* value) override; + + LevelOfDetail getRequestedDetails() const override { + return SkTraceMemoryDump::kLight_LevelOfDetail; + } + + bool shouldDumpWrappedObjects() const override { return false; } + + void setMemoryBacking(const char* dumpName, const char* backingType, + const char* backingObjectId) override; + + void setDiscardableMemoryBacking(const char*, const SkDiscardableMemory&) override {} + + void startFrame(); + + void logTraces(); + +private: + std::string mLastDumpName; + + uint64_t mLastDumpValue; + + uint64_t mLastPurgeableDumpValue; + + std::string mCategory; + + struct TraceValue { + uint64_t memory; + uint64_t purgeableMemory; + }; + + // keys are define in sResourceMap + std::unordered_map<std::string, TraceValue> mCurrentValues; + + void recordAndResetCountersIfNeeded(const char* dumpName); + + void resetCurrentCounter(const char* dumpName); +}; + +} /* namespace skiapipeline */ +} /* namespace uirenderer */ +} /* namespace android */
\ No newline at end of file diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp index a1b2b18195bc..8f67f97fb4bc 100644 --- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp +++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp @@ -26,6 +26,7 @@ #include "SkAndroidFrameworkUtils.h" #include "SkClipStack.h" #include "SkRect.h" +#include "include/private/SkM44.h" namespace android { namespace uirenderer { @@ -47,29 +48,20 @@ static void setScissor(int viewportHeight, const SkIRect& clip) { glScissor(clip.fLeft, y, clip.width(), height); } -static bool GetFboDetails(SkCanvas* canvas, GLuint* outFboID, SkISize* outFboSize) { +static void GetFboDetails(SkCanvas* canvas, GLuint* outFboID, SkISize* outFboSize) { GrRenderTargetContext* renderTargetContext = canvas->internal_private_accessTopLayerRenderTargetContext(); - if (!renderTargetContext) { - ALOGW("Unable to extract renderTarget info from canvas; aborting GLFunctor draw"); - return false; - } + LOG_ALWAYS_FATAL_IF(!renderTargetContext, "Failed to retrieve GrRenderTargetContext"); GrRenderTarget* renderTarget = renderTargetContext->accessRenderTarget(); - if (!renderTarget) { - ALOGW("Unable to extract renderTarget info from canvas; aborting GLFunctor draw"); - return false; - } + LOG_ALWAYS_FATAL_IF(!renderTarget, "accessRenderTarget failed"); GrGLFramebufferInfo fboInfo; - if (!renderTarget->getBackendRenderTarget().getGLFramebufferInfo(&fboInfo)) { - ALOGW("Unable to extract renderTarget info from canvas; aborting GLFunctor draw"); - return false; - } + LOG_ALWAYS_FATAL_IF(!renderTarget->getBackendRenderTarget().getGLFramebufferInfo(&fboInfo), + "getGLFrameBufferInfo failed"); *outFboID = fboInfo.fFBOID; *outFboSize = SkISize::Make(renderTargetContext->width(), renderTargetContext->height()); - return true; } void GLFunctorDrawable::onDraw(SkCanvas* canvas) { @@ -84,15 +76,16 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) { return; } + // flush will create a GrRenderTarget if not already present. + canvas->flush(); + GLuint fboID = 0; SkISize fboSize; - if (!GetFboDetails(canvas, &fboID, &fboSize)) { - return; - } + GetFboDetails(canvas, &fboID, &fboSize); SkIRect surfaceBounds = canvas->internal_private_getTopLayerBounds(); SkIRect clipBounds = canvas->getDeviceClipBounds(); - SkMatrix44 mat4(canvas->getTotalMatrix()); + SkM44 mat4(canvas->experimental_getLocalToDevice()); SkRegion clipRegion; canvas->temporary_internal_getRgnClip(&clipRegion); @@ -118,7 +111,7 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) { // update the matrix and clip that we pass to the WebView to match the coordinates of // the offscreen layer - mat4.preTranslate(-clipBounds.fLeft, -clipBounds.fTop, 0); + mat4.preTranslate(-clipBounds.fLeft, -clipBounds.fTop); clipBounds.offsetTo(0, 0); clipRegion.translate(-surfaceBounds.fLeft, -surfaceBounds.fTop); @@ -126,7 +119,7 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) { // we are drawing into a (clipped) offscreen layer so we must update the clip and matrix // from device coordinates to the layer's coordinates clipBounds.offset(-surfaceBounds.fLeft, -surfaceBounds.fTop); - mat4.preTranslate(-surfaceBounds.fLeft, -surfaceBounds.fTop, 0); + mat4.preTranslate(-surfaceBounds.fLeft, -surfaceBounds.fTop); } DrawGlInfo info; @@ -137,12 +130,11 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) { info.isLayer = fboID != 0; info.width = fboSize.width(); info.height = fboSize.height(); - mat4.asColMajorf(&info.transform[0]); + mat4.getColMajor(&info.transform[0]); info.color_space_ptr = canvas->imageInfo().colorSpace(); // ensure that the framebuffer that the webview will render into is bound before we clear // the stencil and/or draw the functor. - canvas->flush(); glViewport(0, 0, info.width, info.height); glBindFramebuffer(GL_FRAMEBUFFER, fboID); diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp index 1bd30eb5371b..00ceb2d84f9e 100644 --- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp +++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp @@ -18,7 +18,6 @@ #include <SkPaintFilterCanvas.h> #include "RenderNode.h" #include "SkiaDisplayList.h" -#include "SkiaPipeline.h" #include "utils/TraceUtils.h" #include <optional> @@ -42,7 +41,7 @@ RenderNodeDrawable::~RenderNodeDrawable() { void RenderNodeDrawable::drawBackwardsProjectedNodes(SkCanvas* canvas, const SkiaDisplayList& displayList, - int nestLevel) { + int nestLevel) const { LOG_ALWAYS_FATAL_IF(0 == nestLevel && !displayList.mProjectionReceiver); for (auto& child : displayList.mChildNodes) { const RenderProperties& childProperties = child.getNodeProperties(); @@ -133,7 +132,7 @@ private: RenderNode& mNode; }; -void RenderNodeDrawable::forceDraw(SkCanvas* canvas) { +void RenderNodeDrawable::forceDraw(SkCanvas* canvas) const { RenderNode* renderNode = mRenderNode.get(); MarkDraw _marker{*canvas, *renderNode}; @@ -187,17 +186,13 @@ public: AlphaFilterCanvas(SkCanvas* canvas, float alpha) : SkPaintFilterCanvas(canvas), mAlpha(alpha) {} protected: - bool onFilter(SkTCopyOnFirstWrite<SkPaint>* paint, Type t) const override { - std::optional<SkPaint> defaultPaint; - if (!*paint) { - paint->init(defaultPaint.emplace()); - } - paint->writable()->setAlpha((uint8_t)(*paint)->getAlpha() * mAlpha); + bool onFilter(SkPaint& paint) const override { + paint.setAlpha((uint8_t)paint.getAlpha() * mAlpha); return true; } void onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) override { // We unroll the drawable using "this" canvas, so that draw calls contained inside will - // get their alpha applied. THe default SkPaintFilterCanvas::onDrawDrawable does not unroll. + // get their alpha applied. The default SkPaintFilterCanvas::onDrawDrawable does not unroll. drawable->draw(this, matrix); } @@ -235,7 +230,14 @@ void RenderNodeDrawable::drawContent(SkCanvas* canvas) const { // we need to restrict the portion of the surface drawn to the size of the renderNode. SkASSERT(renderNode->getLayerSurface()->width() >= bounds.width()); SkASSERT(renderNode->getLayerSurface()->height() >= bounds.height()); - canvas->drawImageRect(renderNode->getLayerSurface()->makeImageSnapshot().get(), bounds, + + // If SKP recording is active save an annotation that indicates this drawImageRect + // could also be rendered with the commands saved at ID associated with this node. + if (CC_UNLIKELY(Properties::skpCaptureEnabled)) { + canvas->drawAnnotation(bounds, String8::format( + "SurfaceID|%" PRId64, renderNode->uniqueId()).c_str(), nullptr); + } + canvas->drawImageRect(renderNode->getLayerSurface()->makeImageSnapshot(), bounds, bounds, &paint); if (!renderNode->getSkiaLayer()->hasRenderedSinceRepaint) { diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.h b/libs/hwui/pipeline/skia/RenderNodeDrawable.h index 6ba8e599818c..6c390c3fce24 100644 --- a/libs/hwui/pipeline/skia/RenderNodeDrawable.h +++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.h @@ -58,7 +58,7 @@ public: * projection receiver then all projected children (excluding direct children) will be drawn * last. Any projected node not matching those requirements will not be drawn by this function. */ - void forceDraw(SkCanvas* canvas); + void forceDraw(SkCanvas* canvas) const; /** * Returns readonly render properties for this render node. @@ -113,7 +113,7 @@ private: * @param nestLevel should be always 0. Used to track how far we are from the receiver. */ void drawBackwardsProjectedNodes(SkCanvas* canvas, const SkiaDisplayList& displayList, - int nestLevel = 0); + int nestLevel = 0) const; /** * Applies the rendering properties of a view onto a SkCanvas. diff --git a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp index 0a3c8f4347eb..3b8caeb3aab1 100644 --- a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp +++ b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp @@ -17,7 +17,7 @@ #include "ReorderBarrierDrawables.h" #include "RenderNode.h" #include "SkiaDisplayList.h" -#include "SkiaPipeline.h" +#include "LightingInfo.h" #include <SkPathOps.h> #include <SkShadowUtils.h> @@ -27,7 +27,7 @@ namespace uirenderer { namespace skiapipeline { StartReorderBarrierDrawable::StartReorderBarrierDrawable(SkiaDisplayList* data) - : mEndChildIndex(0), mBeginChildIndex(data->mChildNodes.size()), mDisplayList(data) {} + : mEndChildIndex(-1), mBeginChildIndex(data->mChildNodes.size()), mDisplayList(data) {} void StartReorderBarrierDrawable::onDraw(SkCanvas* canvas) { if (mChildren.empty()) { @@ -139,8 +139,8 @@ void EndReorderBarrierDrawable::drawShadow(SkCanvas* canvas, RenderNodeDrawable* return; } - float ambientAlpha = (SkiaPipeline::getAmbientShadowAlpha() / 255.f) * casterAlpha; - float spotAlpha = (SkiaPipeline::getSpotShadowAlpha() / 255.f) * casterAlpha; + float ambientAlpha = (LightingInfo::getAmbientShadowAlpha() / 255.f) * casterAlpha; + float spotAlpha = (LightingInfo::getSpotShadowAlpha() / 255.f) * casterAlpha; const RevealClip& revealClip = casterProperties.getRevealClip(); const SkPath* revealClipPath = revealClip.getPath(); @@ -192,7 +192,7 @@ void EndReorderBarrierDrawable::drawShadow(SkCanvas* canvas, RenderNodeDrawable* casterPath = &tmpPath; } - const Vector3 lightPos = SkiaPipeline::getLightCenter(); + const Vector3 lightPos = LightingInfo::getLightCenter(); SkPoint3 skiaLightPos = SkPoint3::Make(lightPos.x, lightPos.y, lightPos.z); SkPoint3 zParams; if (shadowMatrix.hasPerspective()) { @@ -206,7 +206,7 @@ void EndReorderBarrierDrawable::drawShadow(SkCanvas* canvas, RenderNodeDrawable* SkColor ambientColor = multiplyAlpha(casterProperties.getAmbientShadowColor(), ambientAlpha); SkColor spotColor = multiplyAlpha(casterProperties.getSpotShadowColor(), spotAlpha); SkShadowUtils::DrawShadow( - canvas, *casterPath, zParams, skiaLightPos, SkiaPipeline::getLightRadius(), + canvas, *casterPath, zParams, skiaLightPos, LightingInfo::getLightRadius(), ambientColor, spotColor, casterAlpha < 1.0f ? SkShadowFlags::kTransparentOccluder_ShadowFlag : 0); } diff --git a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.h b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.h index cfc0f9b258da..d669f84c5ee2 100644 --- a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.h +++ b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.h @@ -21,7 +21,7 @@ #include <SkCanvas.h> #include <SkDrawable.h> -#include <utils/FatVector.h> +#include <ui/FatVector.h> namespace android { namespace uirenderer { diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp index 41bcfc25f5c1..158c3493a90c 100644 --- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp +++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp @@ -15,11 +15,18 @@ */ #include "SkiaDisplayList.h" +#include "FunctorDrawable.h" #include "DumpOpsCanvas.h" +#ifdef __ANDROID__ // Layoutlib does not support SkiaPipeline #include "SkiaPipeline.h" +#else +#include "DamageAccumulator.h" +#endif #include "VectorDrawable.h" +#ifdef __ANDROID__ #include "renderthread/CanvasContext.h" +#endif #include <SkImagePriv.h> #include <SkPathOps.h> @@ -81,6 +88,7 @@ bool SkiaDisplayList::prepareListAndChildren( // If the prepare tree is triggered by the UI thread and no previous call to // pinImages has failed then we must pin all mutable images in the GPU cache // until the next UI thread draw. +#ifdef __ANDROID__ // Layoutlib does not support CanvasContext if (info.prepareTextures && !info.canvasContext.pinImages(mMutableImages)) { // In the event that pinning failed we prevent future pinImage calls for the // remainder of this tree traversal and also unpin any currently pinned images @@ -88,6 +96,7 @@ bool SkiaDisplayList::prepareListAndChildren( info.prepareTextures = false; info.canvasContext.unpinImages(); } +#endif bool hasBackwardProjectedNodesHere = false; bool hasBackwardProjectedNodesSubtree = false; @@ -131,20 +140,16 @@ bool SkiaDisplayList::prepareListAndChildren( } } - for (auto& vectorDrawablePair : mVectorDrawables) { + for (auto& [vectorDrawable, cachedMatrix] : mVectorDrawables) { // If any vector drawable in the display list needs update, damage the node. - auto& vectorDrawable = vectorDrawablePair.first; if (vectorDrawable->isDirty()) { Matrix4 totalMatrix; info.damageAccumulator->computeCurrentTransform(&totalMatrix); - Matrix4 canvasMatrix(vectorDrawablePair.second); + Matrix4 canvasMatrix(cachedMatrix); totalMatrix.multiply(canvasMatrix); const SkRect& bounds = vectorDrawable->properties().getBounds(); if (intersects(info.screenSize, totalMatrix, bounds)) { isDirty = true; - static_cast<SkiaPipeline*>(info.canvasContext.getRenderPipeline()) - ->getVectorDrawables() - ->push_back(vectorDrawable); vectorDrawable->setPropertyChangeWillBeConsumed(true); } } diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h index b79103787023..cdd00db9afdc 100644 --- a/libs/hwui/pipeline/skia/SkiaDisplayList.h +++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h @@ -16,7 +16,6 @@ #pragma once -#include "FunctorDrawable.h" #include "RecordingCanvas.h" #include "RenderNodeDrawable.h" #include "TreeInfo.h" @@ -34,6 +33,7 @@ class CanvasContext; } class Outline; +struct WebViewSyncData; namespace VectorDrawable { class Tree; @@ -42,9 +42,12 @@ typedef uirenderer::VectorDrawable::Tree VectorDrawableRoot; namespace skiapipeline { +class FunctorDrawable; + class SkiaDisplayList { public: size_t getUsedSize() { return allocator.usedSize() + mDisplayList.usedSize(); } + size_t getAllocatedSize() { return allocator.allocatedSize() + mDisplayList.allocatedSize(); } ~SkiaDisplayList() { /* Given that we are using a LinearStdAllocator to store some of the diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp index 8092b1d10659..24a6228242a5 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp @@ -18,6 +18,7 @@ #include "DeferredLayerUpdater.h" #include "LayerDrawable.h" +#include "LightingInfo.h" #include "SkiaPipeline.h" #include "SkiaProfileRenderer.h" #include "hwui/Bitmap.h" @@ -99,7 +100,7 @@ bool SkiaOpenGLPipeline::draw(const Frame& frame, const SkRect& screenDirty, con mRenderThread.getGrContext(), backendRT, this->getSurfaceOrigin(), colorType, mSurfaceColorSpace, &props)); - SkiaPipeline::updateLighting(lightGeometry, lightInfo); + LightingInfo::updateLighting(lightGeometry, lightInfo); renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface, SkMatrix::I()); layerUpdateQueue->clear(); @@ -156,31 +157,15 @@ void SkiaOpenGLPipeline::onStop() { } } -static void setBufferCount(ANativeWindow* window, uint32_t extraBuffers) { - int query_value; - int err = window->query(window, NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &query_value); - if (err != 0 || query_value < 0) { - ALOGE("window->query failed: %s (%d) value=%d", strerror(-err), err, query_value); - return; - } - auto min_undequeued_buffers = static_cast<uint32_t>(query_value); - - int bufferCount = min_undequeued_buffers + 2 + extraBuffers; - native_window_set_buffer_count(window, bufferCount); -} - -bool SkiaOpenGLPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBehavior, - ColorMode colorMode, uint32_t extraBuffers) { +bool SkiaOpenGLPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBehavior) { if (mEglSurface != EGL_NO_SURFACE) { mEglManager.destroySurface(mEglSurface); mEglSurface = EGL_NO_SURFACE; } - setSurfaceColorProperties(colorMode); - if (surface) { mRenderThread.requireGlContext(); - auto newSurface = mEglManager.createSurface(surface, colorMode, mSurfaceColorSpace); + auto newSurface = mEglManager.createSurface(surface, mColorMode, mSurfaceColorSpace); if (!newSurface) { return false; } @@ -190,7 +175,6 @@ bool SkiaOpenGLPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBeh if (mEglSurface != EGL_NO_SURFACE) { const bool preserveBuffer = (swapBehavior != SwapBehavior::kSwap_discardBuffer); mBufferPreserved = mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer); - setBufferCount(surface, extraBuffers); return true; } diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h index 3fe0f92b1924..fddd97f1c5b3 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h @@ -16,8 +16,10 @@ #pragma once -#include "SkiaPipeline.h" +#include <EGL/egl.h> +#include <system/window.h> +#include "SkiaPipeline.h" #include "renderstate/RenderState.h" namespace android { @@ -43,8 +45,7 @@ public: bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) override; DeferredLayerUpdater* createTextureLayer() override; - bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior, - renderthread::ColorMode colorMode, uint32_t extraBuffers) override; + bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior) override; void onStop() override; bool isSurfaceReady() override; bool isContextReady() override; 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); } diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h index 41d864653b67..8341164edc19 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaPipeline.h @@ -17,12 +17,15 @@ #pragma once #include <SkSurface.h> +#include <SkDocument.h> +#include <SkMultiPictureDocument.h> #include "Lighting.h" #include "hwui/AnimatedImageDrawable.h" #include "renderthread/CanvasContext.h" #include "renderthread/IRenderPipeline.h" class SkPictureRecorder; +struct SkSharingSerialContext; namespace android { namespace uirenderer { @@ -38,14 +41,16 @@ public: bool pinImages(std::vector<SkImage*>& mutableImages) override; bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override { return false; } void unpinImages() override; - void onPrepareTree() override; void renderLayers(const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, bool opaque, const LightInfo& lightInfo) override; + // If the given node didn't have a layer surface, or had one of the wrong size, this method + // creates a new one and returns true. Otherwise does nothing and returns false. bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator, ErrorHandler* errorHandler) override; + void setSurfaceColorProperties(renderthread::ColorMode colorMode) override; SkColorType getSurfaceColorType() const override { return mSurfaceColorType; } sk_sp<SkColorSpace> getSurfaceColorSpace() override { return mSurfaceColorSpace; } @@ -54,70 +59,29 @@ public: const Rect& contentDrawBounds, sk_sp<SkSurface> surface, const SkMatrix& preTransform); - std::vector<VectorDrawableRoot*>* getVectorDrawables() { return &mVectorDrawables; } - static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap); void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque); - static float getLightRadius() { - if (CC_UNLIKELY(Properties::overrideLightRadius > 0)) { - return Properties::overrideLightRadius; - } - return mLightRadius; - } - - static uint8_t getAmbientShadowAlpha() { - if (CC_UNLIKELY(Properties::overrideAmbientShadowStrength >= 0)) { - return Properties::overrideAmbientShadowStrength; - } - return mAmbientShadowAlpha; - } - - static uint8_t getSpotShadowAlpha() { - if (CC_UNLIKELY(Properties::overrideSpotShadowStrength >= 0)) { - return Properties::overrideSpotShadowStrength; - } - return mSpotShadowAlpha; - } - - static Vector3 getLightCenter() { - if (CC_UNLIKELY(Properties::overrideLightPosY > 0 || Properties::overrideLightPosZ > 0)) { - Vector3 adjustedLightCenter = mLightCenter; - if (CC_UNLIKELY(Properties::overrideLightPosY > 0)) { - // negated since this shifts up - adjustedLightCenter.y = -Properties::overrideLightPosY; - } - if (CC_UNLIKELY(Properties::overrideLightPosZ > 0)) { - adjustedLightCenter.z = Properties::overrideLightPosZ; - } - return adjustedLightCenter; - } - return mLightCenter; - } - - static void updateLighting(const LightGeometry& lightGeometry, const LightInfo& lightInfo) { - mLightRadius = lightGeometry.radius; - mAmbientShadowAlpha = lightInfo.ambientShadowAlpha; - mSpotShadowAlpha = lightInfo.spotShadowAlpha; - mLightCenter = lightGeometry.center; - } - + // Sets the recording callback to the provided function and the recording mode + // to CallbackAPI void setPictureCapturedCallback( const std::function<void(sk_sp<SkPicture>&&)>& callback) override { mPictureCapturedCallback = callback; + mCaptureMode = callback ? CaptureMode::CallbackAPI : CaptureMode::None; } protected: void dumpResourceCacheUsage() const; - void setSurfaceColorProperties(renderthread::ColorMode colorMode); renderthread::RenderThread& mRenderThread; + + renderthread::ColorMode mColorMode = renderthread::ColorMode::SRGB; SkColorType mSurfaceColorType; sk_sp<SkColorSpace> mSurfaceColorSpace; private: - void renderFrameImpl(const LayerUpdateQueue& layers, const SkRect& clip, + void renderFrameImpl(const SkRect& clip, const std::vector<sp<RenderNode>>& nodes, bool opaque, const Rect& contentDrawBounds, SkCanvas* canvas, const SkMatrix& preTransform); @@ -126,48 +90,67 @@ private: * Debugging feature. Draws a semi-transparent overlay on each pixel, indicating * how many times it has been drawn. */ - void renderOverdraw(const LayerUpdateQueue& layers, const SkRect& clip, + void renderOverdraw(const SkRect& clip, const std::vector<sp<RenderNode>>& nodes, const Rect& contentDrawBounds, sk_sp<SkSurface> surface, const SkMatrix& preTransform); - /** - * Render mVectorDrawables into offscreen buffers. - */ - void renderVectorDrawableCache(); - - SkCanvas* tryCapture(SkSurface* surface); + // Called every frame. Normally returns early with screen canvas. + // But when capture is enabled, returns an nwaycanvas where commands are also recorded. + SkCanvas* tryCapture(SkSurface* surface, RenderNode* root, const LayerUpdateQueue& dirtyLayers); + // Called at the end of every frame, closes the recording if necessary. void endCapture(SkSurface* surface); + // Determine if a new file-based capture should be started. + // If so, sets mCapturedFile and mCaptureSequence and returns true. + // Should be called every frame when capture is enabled. + // sets mCaptureMode. + bool shouldStartNewFileCapture(); + // Set up a multi frame capture. + bool setupMultiFrameCapture(); std::vector<sk_sp<SkImage>> mPinnedImages; - /** - * populated by prepareTree with dirty VDs - */ - std::vector<VectorDrawableRoot*> mVectorDrawables; - // Block of properties used only for debugging to record a SkPicture and save it in a file. + // There are three possible ways of recording drawing commands. + enum class CaptureMode { + // return to this mode when capture stops. + None, + // A mode where every frame is recorded into an SkPicture and sent to a provided callback, + // until that callback is cleared + CallbackAPI, + // A mode where a finite number of frames are recorded to a file with + // SkMultiPictureDocument + MultiFrameSKP, + // A mode which records a single frame to a normal SKP file. + SingleFrameSKP, + }; + CaptureMode mCaptureMode = CaptureMode::None; + /** - * mCapturedFile is used to enforce we don't capture more than once for a given name (cause - * permissions don't allow to reset a property from render thread). + * mCapturedFile - the filename to write a recorded SKP to in either MultiFrameSKP or + * SingleFrameSKP mode. */ std::string mCapturedFile; /** - * mCaptureSequence counts how many frames are left to take in the sequence. + * mCaptureSequence counts down how many frames are left to take in the sequence. Applicable + * only to MultiFrameSKP or SingleFrameSKP mode. */ int mCaptureSequence = 0; + // Multi frame serialization stream and writer used when serializing more than one frame. + std::unique_ptr<SkFILEWStream> mOpenMultiPicStream; + sk_sp<SkDocument> mMultiPic; + std::unique_ptr<SkSharingSerialContext> mSerialContext; + /** - * mRecorder holds the current picture recorder. We could store it on the stack to support - * parallel tryCapture calls (not really needed). + * mRecorder holds the current picture recorder when serializing in either SingleFrameSKP or + * CallbackAPI modes. */ std::unique_ptr<SkPictureRecorder> mRecorder; std::unique_ptr<SkNWayCanvas> mNwayCanvas; - std::function<void(sk_sp<SkPicture>&&)> mPictureCapturedCallback; - static float mLightRadius; - static uint8_t mAmbientShadowAlpha; - static uint8_t mSpotShadowAlpha; - static Vector3 mLightCenter; + // Set by setPictureCapturedCallback and when set, CallbackAPI mode recording is ongoing. + // Not used in other recording modes. + std::function<void(sk_sp<SkPicture>&&)> mPictureCapturedCallback; }; } /* namespace skiapipeline */ diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp index 16c8b8923074..d67cf8c9c73f 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp @@ -15,17 +15,21 @@ */ #include "SkiaRecordingCanvas.h" - +#include "hwui/Paint.h" #include <SkImagePriv.h> #include "CanvasTransform.h" +#ifdef __ANDROID__ // Layoutlib does not support Layers #include "Layer.h" #include "LayerDrawable.h" +#endif #include "NinePatchUtils.h" #include "RenderNode.h" #include "pipeline/skia/AnimatedDrawables.h" +#ifdef __ANDROID__ // Layoutlib does not support GL, Vulcan etc. #include "pipeline/skia/GLFunctorDrawable.h" #include "pipeline/skia/VkFunctorDrawable.h" #include "pipeline/skia/VkInteropFunctorDrawable.h" +#endif namespace android { namespace uirenderer { @@ -102,13 +106,16 @@ void SkiaRecordingCanvas::insertReorderBarrier(bool enableReorder) { } void SkiaRecordingCanvas::drawLayer(uirenderer::DeferredLayerUpdater* layerUpdater) { +#ifdef __ANDROID__ // Layoutlib does not support Layers if (layerUpdater != nullptr) { // Create a ref-counted drawable, which is kept alive by sk_sp in SkLiteDL. sk_sp<SkDrawable> drawable(new LayerDrawable(layerUpdater)); drawDrawable(drawable.get()); } +#endif } + void SkiaRecordingCanvas::drawRenderNode(uirenderer::RenderNode* renderNode) { // Record the child node. Drawable dtor will be invoked when mChildNodes deque is cleared. mDisplayList->mChildNodes.emplace_back(renderNode, asSkCanvas(), true, mCurrentBarrier); @@ -125,8 +132,10 @@ void SkiaRecordingCanvas::drawRenderNode(uirenderer::RenderNode* renderNode) { } } + void SkiaRecordingCanvas::callDrawGLFunction(Functor* functor, uirenderer::GlFunctorLifecycleListener* listener) { +#ifdef __ANDROID__ // Layoutlib does not support GL, Vulcan etc. FunctorDrawable* functorDrawable; if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) { functorDrawable = mDisplayList->allocateDrawable<VkInteropFunctorDrawable>( @@ -137,9 +146,11 @@ void SkiaRecordingCanvas::callDrawGLFunction(Functor* functor, } mDisplayList->mChildFunctors.push_back(functorDrawable); drawDrawable(functorDrawable); +#endif } void SkiaRecordingCanvas::drawWebViewFunctor(int functor) { +#ifdef __ANDROID__ // Layoutlib does not support GL, Vulcan etc. FunctorDrawable* functorDrawable; if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) { functorDrawable = mDisplayList->allocateDrawable<VkFunctorDrawable>(functor, asSkCanvas()); @@ -147,7 +158,8 @@ void SkiaRecordingCanvas::drawWebViewFunctor(int functor) { functorDrawable = mDisplayList->allocateDrawable<GLFunctorDrawable>(functor, asSkCanvas()); } mDisplayList->mChildFunctors.push_back(functorDrawable); - drawDrawable(functorDrawable); + mRecorder.drawWebView(functorDrawable); +#endif } void SkiaRecordingCanvas::drawVectorDrawable(VectorDrawableRoot* tree) { @@ -185,9 +197,37 @@ SkiaCanvas::PaintCoW&& SkiaRecordingCanvas::filterBitmap(PaintCoW&& paint) { return filterPaint(std::move(paint)); } -void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, float left, float top, const SkPaint* paint) { +static SkDrawLooper* get_looper(const Paint* paint) { + return paint ? paint->getLooper() : nullptr; +} + +template <typename Proc> +void applyLooper(SkDrawLooper* looper, const SkPaint& paint, Proc proc) { + if (looper) { + SkSTArenaAlloc<256> alloc; + SkDrawLooper::Context* ctx = looper->makeContext(&alloc); + if (ctx) { + SkDrawLooper::Context::Info info; + for (;;) { + SkPaint p = paint; + if (!ctx->next(&info, &p)) { + break; + } + proc(info.fTranslate.fX, info.fTranslate.fY, p); + } + } + } else { + proc(0, 0, paint); + } +} + +void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, float left, float top, const Paint* paint) { sk_sp<SkImage> image = bitmap.makeImage(); - mRecorder.drawImage(image, left, top, filterBitmap(paint), bitmap.palette()); + + applyLooper(get_looper(paint), *filterBitmap(paint), [&](SkScalar x, SkScalar y, const SkPaint& p) { + mRecorder.drawImage(image, left + x, top + y, &p, bitmap.palette()); + }); + // if image->unique() is true, then mRecorder.drawImage failed for some reason. It also means // it is not safe to store a raw SkImage pointer, because the image object will be destroyed // when this function ends. @@ -196,12 +236,16 @@ void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, float left, float top, cons } } -void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, const SkMatrix& matrix, const SkPaint* paint) { +void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, const SkMatrix& matrix, const Paint* paint) { SkAutoCanvasRestore acr(&mRecorder, true); concat(matrix); sk_sp<SkImage> image = bitmap.makeImage(); - mRecorder.drawImage(image, 0, 0, filterBitmap(paint), bitmap.palette()); + + applyLooper(get_looper(paint), *filterBitmap(paint), [&](SkScalar x, SkScalar y, const SkPaint& p) { + mRecorder.drawImage(image, x, y, &p, bitmap.palette()); + }); + if (!bitmap.isImmutable() && image.get() && !image->unique()) { mDisplayList->mMutableImages.push_back(image.get()); } @@ -209,13 +253,17 @@ void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, const SkMatrix& matrix, con void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, float srcLeft, float srcTop, float srcRight, float srcBottom, float dstLeft, float dstTop, float dstRight, - float dstBottom, const SkPaint* paint) { + float dstBottom, const Paint* paint) { SkRect srcRect = SkRect::MakeLTRB(srcLeft, srcTop, srcRight, srcBottom); SkRect dstRect = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom); sk_sp<SkImage> image = bitmap.makeImage(); - mRecorder.drawImageRect(image, srcRect, dstRect, filterBitmap(paint), - SkCanvas::kFast_SrcRectConstraint, bitmap.palette()); + + applyLooper(get_looper(paint), *filterBitmap(paint), [&](SkScalar x, SkScalar y, const SkPaint& p) { + mRecorder.drawImageRect(image, srcRect, dstRect.makeOffset(x, y), &p, + SkCanvas::kFast_SrcRectConstraint, bitmap.palette()); + }); + if (!bitmap.isImmutable() && image.get() && !image->unique() && !srcRect.isEmpty() && !dstRect.isEmpty()) { mDisplayList->mMutableImages.push_back(image.get()); @@ -224,7 +272,7 @@ void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, float srcLeft, float srcTop void SkiaRecordingCanvas::drawNinePatch(Bitmap& bitmap, const Res_png_9patch& chunk, float dstLeft, float dstTop, float dstRight, float dstBottom, - const SkPaint* paint) { + const Paint* paint) { SkCanvas::Lattice lattice; NinePatchUtils::SetLatticeDivs(&lattice, chunk, bitmap.width(), bitmap.height()); @@ -252,8 +300,11 @@ void SkiaRecordingCanvas::drawNinePatch(Bitmap& bitmap, const Res_png_9patch& ch filteredPaint.writeable().setFilterQuality(kLow_SkFilterQuality); } sk_sp<SkImage> image = bitmap.makeImage(); - mRecorder.drawImageLattice(image, lattice, dst, filterBitmap(std::move(filteredPaint)), - bitmap.palette()); + + applyLooper(get_looper(paint), *filterBitmap(paint), [&](SkScalar x, SkScalar y, const SkPaint& p) { + mRecorder.drawImageLattice(image, lattice, dst.makeOffset(x, y), &p, bitmap.palette()); + }); + if (!bitmap.isImmutable() && image.get() && !image->unique() && !dst.isEmpty()) { mDisplayList->mMutableImages.push_back(image.get()); } diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h index c42cea33211e..bd5274c94e75 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h @@ -45,14 +45,14 @@ public: virtual uirenderer::DisplayList* finishRecording() override; - virtual void drawBitmap(Bitmap& bitmap, float left, float top, const SkPaint* paint) override; - virtual void drawBitmap(Bitmap& bitmap, const SkMatrix& matrix, const SkPaint* paint) override; + virtual void drawBitmap(Bitmap& bitmap, float left, float top, const Paint* paint) override; + virtual void drawBitmap(Bitmap& bitmap, const SkMatrix& matrix, const Paint* paint) override; virtual void drawBitmap(Bitmap& bitmap, float srcLeft, float srcTop, float srcRight, float srcBottom, float dstLeft, float dstTop, float dstRight, - float dstBottom, const SkPaint* paint) override; + float dstBottom, const Paint* paint) override; virtual void drawNinePatch(Bitmap& hwuiBitmap, const android::Res_png_9patch& chunk, float dstLeft, float dstTop, float dstRight, float dstBottom, - const SkPaint* paint) override; + const Paint* paint) override; virtual double drawAnimatedImage(AnimatedImageDrawable* animatedImage) override; virtual void drawRoundRect(uirenderer::CanvasPropertyPrimitive* left, diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp index e8cb219db320..212a4284a824 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp @@ -19,6 +19,7 @@ #include "DeferredLayerUpdater.h" #include "Readback.h" #include "ShaderCache.h" +#include "LightingInfo.h" #include "SkiaPipeline.h" #include "SkiaProfileRenderer.h" #include "VkInteropFunctorDrawable.h" @@ -69,7 +70,7 @@ bool SkiaVulkanPipeline::draw(const Frame& frame, const SkRect& screenDirty, con if (backBuffer.get() == nullptr) { return false; } - SkiaPipeline::updateLighting(lightGeometry, lightInfo); + LightingInfo::updateLighting(lightGeometry, lightInfo); renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, backBuffer, mVkSurface->getCurrentPreTransform()); ShaderCache::get().onVkFrameFlushed(mRenderThread.getGrContext()); @@ -115,19 +116,17 @@ DeferredLayerUpdater* SkiaVulkanPipeline::createTextureLayer() { void SkiaVulkanPipeline::onStop() {} -bool SkiaVulkanPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBehavior, - ColorMode colorMode, uint32_t extraBuffers) { +bool SkiaVulkanPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBehavior) { if (mVkSurface) { mVkManager.destroySurface(mVkSurface); mVkSurface = nullptr; } - setSurfaceColorProperties(colorMode); if (surface) { mRenderThread.requireVkContext(); mVkSurface = - mVkManager.createSurface(surface, colorMode, mSurfaceColorSpace, mSurfaceColorType, - mRenderThread.getGrContext(), extraBuffers); + mVkManager.createSurface(surface, mColorMode, mSurfaceColorSpace, mSurfaceColorType, + mRenderThread.getGrContext(), 0); } return mVkSurface != nullptr; diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h index 31734783de7f..6268daa6213f 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h @@ -42,8 +42,7 @@ public: bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) override; DeferredLayerUpdater* createTextureLayer() override; - bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior, - renderthread::ColorMode colorMode, uint32_t extraBuffers) override; + bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior) override; void onStop() override; bool isSurfaceReady() override; bool isContextReady() override; diff --git a/libs/hwui/pipeline/skia/VectorDrawableAtlas.cpp b/libs/hwui/pipeline/skia/VectorDrawableAtlas.cpp deleted file mode 100644 index e783f389feb8..000000000000 --- a/libs/hwui/pipeline/skia/VectorDrawableAtlas.cpp +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright (C) 2017 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 "VectorDrawableAtlas.h" - -#include <GrRectanizer_pow2.h> -#include <SkCanvas.h> -#include <cmath> -#include "renderthread/RenderProxy.h" -#include "renderthread/RenderThread.h" -#include "utils/TraceUtils.h" - -namespace android { -namespace uirenderer { -namespace skiapipeline { - -VectorDrawableAtlas::VectorDrawableAtlas(size_t surfaceArea, StorageMode storageMode) - : mWidth((int)std::sqrt(surfaceArea)) - , mHeight((int)std::sqrt(surfaceArea)) - , mStorageMode(storageMode) {} - -void VectorDrawableAtlas::prepareForDraw(GrContext* context) { - if (StorageMode::allowSharedSurface == mStorageMode) { - if (!mSurface) { - mSurface = createSurface(mWidth, mHeight, context); - mRectanizer = std::make_unique<GrRectanizerPow2>(mWidth, mHeight); - mPixelUsedByVDs = 0; - mPixelAllocated = 0; - mConsecutiveFailures = 0; - mFreeRects.clear(); - } else { - if (isFragmented()) { - // Invoke repack outside renderFrame to avoid jank. - renderthread::RenderProxy::repackVectorDrawableAtlas(); - } - } - } -} - -#define MAX_CONSECUTIVE_FAILURES 5 -#define MAX_UNUSED_RATIO 2.0f - -bool VectorDrawableAtlas::isFragmented() { - return mConsecutiveFailures > MAX_CONSECUTIVE_FAILURES && - mPixelUsedByVDs * MAX_UNUSED_RATIO < mPixelAllocated; -} - -void VectorDrawableAtlas::repackIfNeeded(GrContext* context) { - // We repackage when atlas failed to allocate space MAX_CONSECUTIVE_FAILURES consecutive - // times and the atlas allocated pixels are at least MAX_UNUSED_RATIO times higher than pixels - // used by atlas VDs. - if (isFragmented() && mSurface) { - repack(context); - } -} - -// compare to CacheEntry objects based on VD area. -bool VectorDrawableAtlas::compareCacheEntry(const CacheEntry& first, const CacheEntry& second) { - return first.VDrect.width() * first.VDrect.height() < - second.VDrect.width() * second.VDrect.height(); -} - -void VectorDrawableAtlas::repack(GrContext* context) { - ATRACE_CALL(); - sk_sp<SkSurface> newSurface; - SkCanvas* canvas = nullptr; - if (StorageMode::allowSharedSurface == mStorageMode) { - newSurface = createSurface(mWidth, mHeight, context); - if (!newSurface) { - return; - } - canvas = newSurface->getCanvas(); - canvas->clear(SK_ColorTRANSPARENT); - mRectanizer = std::make_unique<GrRectanizerPow2>(mWidth, mHeight); - } else { - if (!mSurface) { - return; // nothing to repack - } - mRectanizer.reset(); - } - mFreeRects.clear(); - SkImage* sourceImageAtlas = nullptr; - if (mSurface) { - sourceImageAtlas = mSurface->makeImageSnapshot().get(); - } - - // Sort the list by VD size, which allows for the smallest VDs to get first in the atlas. - // Sorting is safe, because it does not affect iterator validity. - if (mRects.size() <= 100) { - mRects.sort(compareCacheEntry); - } - - for (CacheEntry& entry : mRects) { - SkRect currentVDRect = entry.VDrect; - SkImage* sourceImage; // copy either from the atlas or from a standalone surface - if (entry.surface) { - if (!fitInAtlas(currentVDRect.width(), currentVDRect.height())) { - continue; // don't even try to repack huge VD - } - sourceImage = entry.surface->makeImageSnapshot().get(); - } else { - sourceImage = sourceImageAtlas; - } - size_t VDRectArea = currentVDRect.width() * currentVDRect.height(); - SkIPoint16 pos; - if (canvas && mRectanizer->addRect(currentVDRect.width(), currentVDRect.height(), &pos)) { - SkRect newRect = - SkRect::MakeXYWH(pos.fX, pos.fY, currentVDRect.width(), currentVDRect.height()); - canvas->drawImageRect(sourceImage, currentVDRect, newRect, nullptr); - entry.VDrect = newRect; - entry.rect = newRect; - if (entry.surface) { - // A rectangle moved from a standalone surface to the atlas. - entry.surface = nullptr; - mPixelUsedByVDs += VDRectArea; - } - } else { - // Repack failed for this item. If it is not already, store it in a standalone - // surface. - if (!entry.surface) { - // A rectangle moved from an atlas to a standalone surface. - mPixelUsedByVDs -= VDRectArea; - SkRect newRect = SkRect::MakeWH(currentVDRect.width(), currentVDRect.height()); - entry.surface = createSurface(newRect.width(), newRect.height(), context); - auto tempCanvas = entry.surface->getCanvas(); - tempCanvas->clear(SK_ColorTRANSPARENT); - tempCanvas->drawImageRect(sourceImageAtlas, currentVDRect, newRect, nullptr); - entry.VDrect = newRect; - entry.rect = newRect; - } - } - } - mPixelAllocated = mPixelUsedByVDs; - context->flush(); - mSurface = newSurface; - mConsecutiveFailures = 0; -} - -AtlasEntry VectorDrawableAtlas::requestNewEntry(int width, int height, GrContext* context) { - AtlasEntry result; - if (width <= 0 || height <= 0) { - return result; - } - - if (mSurface) { - const size_t area = width * height; - - // Use a rectanizer to allocate unused space from the atlas surface. - bool notTooBig = fitInAtlas(width, height); - SkIPoint16 pos; - if (notTooBig && mRectanizer->addRect(width, height, &pos)) { - mPixelUsedByVDs += area; - mPixelAllocated += area; - result.rect = SkRect::MakeXYWH(pos.fX, pos.fY, width, height); - result.surface = mSurface; - auto eraseIt = mRects.emplace(mRects.end(), result.rect, result.rect, nullptr); - CacheEntry* entry = &(*eraseIt); - entry->eraseIt = eraseIt; - result.key = reinterpret_cast<AtlasKey>(entry); - mConsecutiveFailures = 0; - return result; - } - - // Try to reuse atlas memory from rectangles freed by "releaseEntry". - auto freeRectIt = mFreeRects.lower_bound(area); - while (freeRectIt != mFreeRects.end()) { - SkRect& freeRect = freeRectIt->second; - if (freeRect.width() >= width && freeRect.height() >= height) { - result.rect = SkRect::MakeXYWH(freeRect.fLeft, freeRect.fTop, width, height); - result.surface = mSurface; - auto eraseIt = mRects.emplace(mRects.end(), result.rect, freeRect, nullptr); - CacheEntry* entry = &(*eraseIt); - entry->eraseIt = eraseIt; - result.key = reinterpret_cast<AtlasKey>(entry); - mPixelUsedByVDs += area; - mFreeRects.erase(freeRectIt); - mConsecutiveFailures = 0; - return result; - } - freeRectIt++; - } - - if (notTooBig && mConsecutiveFailures <= MAX_CONSECUTIVE_FAILURES) { - mConsecutiveFailures++; - } - } - - // Allocate a surface for a rectangle that is too big or if atlas is full. - if (nullptr != context) { - result.rect = SkRect::MakeWH(width, height); - result.surface = createSurface(width, height, context); - auto eraseIt = mRects.emplace(mRects.end(), result.rect, result.rect, result.surface); - CacheEntry* entry = &(*eraseIt); - entry->eraseIt = eraseIt; - result.key = reinterpret_cast<AtlasKey>(entry); - } - - return result; -} - -AtlasEntry VectorDrawableAtlas::getEntry(AtlasKey atlasKey) { - AtlasEntry result; - if (INVALID_ATLAS_KEY != atlasKey) { - CacheEntry* entry = reinterpret_cast<CacheEntry*>(atlasKey); - result.rect = entry->VDrect; - result.surface = entry->surface; - if (!result.surface) { - result.surface = mSurface; - } - result.key = atlasKey; - } - return result; -} - -void VectorDrawableAtlas::releaseEntry(AtlasKey atlasKey) { - if (INVALID_ATLAS_KEY != atlasKey) { - if (!renderthread::RenderThread::isCurrent()) { - { - AutoMutex _lock(mReleaseKeyLock); - mKeysForRelease.push_back(atlasKey); - } - // invoke releaseEntry on the renderthread - renderthread::RenderProxy::releaseVDAtlasEntries(); - return; - } - CacheEntry* entry = reinterpret_cast<CacheEntry*>(atlasKey); - if (!entry->surface) { - // Store freed atlas rectangles in "mFreeRects" and try to reuse them later, when atlas - // is full. - SkRect& removedRect = entry->rect; - size_t rectArea = removedRect.width() * removedRect.height(); - mFreeRects.emplace(rectArea, removedRect); - SkRect& removedVDRect = entry->VDrect; - size_t VDRectArea = removedVDRect.width() * removedVDRect.height(); - mPixelUsedByVDs -= VDRectArea; - mConsecutiveFailures = 0; - } - auto eraseIt = entry->eraseIt; - mRects.erase(eraseIt); - } -} - -void VectorDrawableAtlas::delayedReleaseEntries() { - AutoMutex _lock(mReleaseKeyLock); - for (auto key : mKeysForRelease) { - releaseEntry(key); - } - mKeysForRelease.clear(); -} - -sk_sp<SkSurface> VectorDrawableAtlas::createSurface(int width, int height, GrContext* context) { - SkImageInfo info = SkImageInfo::MakeN32(width, height, kPremul_SkAlphaType); - // This must have a top-left origin so that calls to surface->canvas->writePixels - // performs a basic texture upload instead of a more complex drawing operation - return SkSurface::MakeRenderTarget(context, SkBudgeted::kYes, info, 0, kTopLeft_GrSurfaceOrigin, - nullptr); -} - -void VectorDrawableAtlas::setStorageMode(StorageMode mode) { - mStorageMode = mode; - if (StorageMode::disallowSharedSurface == mStorageMode && mSurface) { - mSurface.reset(); - mRectanizer.reset(); - mFreeRects.clear(); - } -} - -} /* namespace skiapipeline */ -} /* namespace uirenderer */ -} /* namespace android */ diff --git a/libs/hwui/pipeline/skia/VectorDrawableAtlas.h b/libs/hwui/pipeline/skia/VectorDrawableAtlas.h deleted file mode 100644 index 5e892aa7e92c..000000000000 --- a/libs/hwui/pipeline/skia/VectorDrawableAtlas.h +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright (C) 2017 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. - */ - -#pragma once - -#include <SkSurface.h> -#include <utils/FatVector.h> -#include <utils/RefBase.h> -#include <utils/Thread.h> -#include <list> -#include <map> - -class GrRectanizer; - -namespace android { -namespace uirenderer { -namespace skiapipeline { - -typedef uintptr_t AtlasKey; - -#define INVALID_ATLAS_KEY 0 - -struct AtlasEntry { - sk_sp<SkSurface> surface; - SkRect rect; - AtlasKey key = INVALID_ATLAS_KEY; -}; - -/** - * VectorDrawableAtlas provides offscreen buffers used to draw VD and AnimatedVD. - * VectorDrawableAtlas can allocate a standalone surface or provide a subrect from a shared surface. - * VectorDrawableAtlas is owned by the CacheManager and weak pointers are kept by each - * VectorDrawable that is using it. VectorDrawableAtlas and its surface can be deleted at any time, - * except during a renderFrame call. VectorDrawable does not contain a pointer to atlas SkSurface - * nor any coordinates into the atlas, but instead holds a rectangle "id", which is resolved only - * when drawing. This design makes VectorDrawableAtlas free to move the data internally. - * At draw time a VectorDrawable may find, that its atlas has been deleted, which will make it - * draw in a standalone cache surface not part of an atlas. In this case VD won't use - * VectorDrawableAtlas until the next frame. - * VectorDrawableAtlas tries to fit VDs in the atlas SkSurface. If there is not enough space in - * the atlas, VectorDrawableAtlas creates a standalone surface for each VD. - * When a VectorDrawable is deleted, it invokes VectorDrawableAtlas::releaseEntry, which is keeping - * track of free spaces and allow to reuse the surface for another VD. - */ -// TODO: Check if not using atlas for AnimatedVD is more efficient. -// TODO: For low memory situations, when there are no paint effects in VD, we may render without an -// TODO: offscreen surface. -class VectorDrawableAtlas : public virtual RefBase { -public: - enum class StorageMode { allowSharedSurface, disallowSharedSurface }; - - explicit VectorDrawableAtlas(size_t surfaceArea, - StorageMode storageMode = StorageMode::allowSharedSurface); - - /** - * "prepareForDraw" may allocate a new surface if needed. It may schedule to repack the - * atlas at a later time. - */ - void prepareForDraw(GrContext* context); - - /** - * Repack the atlas if needed, by moving used rectangles into a new atlas surface. - * The goal of repacking is to fix a fragmented atlas. - */ - void repackIfNeeded(GrContext* context); - - /** - * Returns true if atlas is fragmented and repack is needed. - */ - bool isFragmented(); - - /** - * "requestNewEntry" is called by VectorDrawable to allocate a new rectangle area from the atlas - * or create a standalone surface if atlas is full. - * On success it returns a non-negative unique id, which can be used later with "getEntry" and - * "releaseEntry". - */ - AtlasEntry requestNewEntry(int width, int height, GrContext* context); - - /** - * "getEntry" extracts coordinates and surface of a previously created rectangle. - * "atlasKey" is an unique id created by "requestNewEntry". Passing a non-existing "atlasKey" is - * causing an undefined behaviour. - * On success it returns a rectangle Id -> may be same or different from "atlasKey" if - * implementation decides to move the record internally. - */ - AtlasEntry getEntry(AtlasKey atlasKey); - - /** - * "releaseEntry" is invoked when a VectorDrawable is deleted. Passing a non-existing "atlasKey" - * is causing an undefined behaviour. This is the only function in the class that can be - * invoked from any thread. It will marshal internally to render thread if needed. - */ - void releaseEntry(AtlasKey atlasKey); - - void setStorageMode(StorageMode mode); - - /** - * "delayedReleaseEntries" is indirectly invoked by "releaseEntry", when "releaseEntry" is - * invoked from a non render thread. - */ - void delayedReleaseEntries(); - -private: - struct CacheEntry { - CacheEntry(const SkRect& newVDrect, const SkRect& newRect, - const sk_sp<SkSurface>& newSurface) - : VDrect(newVDrect), rect(newRect), surface(newSurface) {} - - /** - * size and position of VectorDrawable into the atlas or in "this.surface" - */ - SkRect VDrect; - - /** - * rect allocated in atlas surface or "this.surface". It may be bigger than "VDrect" - */ - SkRect rect; - - /** - * this surface is used if atlas is full or VD is too big - */ - sk_sp<SkSurface> surface; - - /** - * iterator is used to delete self with a constant complexity (without traversing the list) - */ - std::list<CacheEntry>::iterator eraseIt; - }; - - /** - * atlas surface shared by all VDs - */ - sk_sp<SkSurface> mSurface; - - std::unique_ptr<GrRectanizer> mRectanizer; - const int mWidth; - const int mHeight; - - /** - * "mRects" keeps records only for rectangles used by VDs. List has nice properties: constant - * complexity to insert and erase and references are not invalidated by insert/erase. - */ - std::list<CacheEntry> mRects; - - /** - * Rectangles freed by "releaseEntry" are removed from "mRects" and added to "mFreeRects". - * "mFreeRects" is using for an index the rectangle area. There could be more than one free - * rectangle with the same area, which is the reason to use "multimap" instead of "map". - */ - std::multimap<size_t, SkRect> mFreeRects; - - /** - * area in atlas used by VectorDrawables (area in standalone surface not counted) - */ - int mPixelUsedByVDs = 0; - - /** - * area allocated in mRectanizer - */ - int mPixelAllocated = 0; - - /** - * Consecutive times we had to allocate standalone surfaces, because atlas was full. - */ - int mConsecutiveFailures = 0; - - /** - * mStorageMode allows using a shared surface to store small vector drawables. - * Using a shared surface can boost the performance by allowing GL ops to be batched, but may - * consume more memory. - */ - StorageMode mStorageMode; - - /** - * mKeysForRelease is used by releaseEntry implementation to pass atlas keys from an arbitrary - * calling thread to the render thread. - */ - std::vector<AtlasKey> mKeysForRelease; - - /** - * A lock used to protect access to mKeysForRelease. - */ - Mutex mReleaseKeyLock; - - sk_sp<SkSurface> createSurface(int width, int height, GrContext* context); - - inline bool fitInAtlas(int width, int height) { - return 2 * width < mWidth && 2 * height < mHeight; - } - - void repack(GrContext* context); - - static bool compareCacheEntry(const CacheEntry& first, const CacheEntry& second); -}; - -} /* namespace skiapipeline */ -} /* namespace uirenderer */ -} /* namespace android */ diff --git a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp index 112792611fc3..68f111752a4c 100644 --- a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp +++ b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp @@ -20,6 +20,7 @@ #include <GrBackendDrawableInfo.h> #include <SkAndroidFrameworkUtils.h> #include <SkImage.h> +#include "include/private/SkM44.h" #include <utils/Color.h> #include <utils/Trace.h> #include <utils/TraceUtils.h> @@ -62,7 +63,7 @@ void VkFunctorDrawHandler::draw(const GrBackendDrawableInfo& info) { renderthread::RenderThread::getInstance().vulkanManager(); mFunctorHandle->initVk(vk_manager.getVkFunctorInitParams()); - SkMatrix44 mat4(mMatrix); + SkM44 mat4(mMatrix); VkFunctorDrawParams params{ .width = mImageInfo.width(), .height = mImageInfo.height(), @@ -72,7 +73,7 @@ void VkFunctorDrawHandler::draw(const GrBackendDrawableInfo& info) { .clip_right = mClip.fRight, .clip_bottom = mClip.fBottom, }; - mat4.asColMajorf(¶ms.transform[0]); + mat4.getColMajor(¶ms.transform[0]); params.secondary_command_buffer = vulkan_info.fSecondaryCommandBuffer; params.color_attachment_index = vulkan_info.fColorAttachmentIndex; params.compatible_render_pass = vulkan_info.fCompatibleRenderPass; diff --git a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp index 706325f00bd2..241d3708def5 100644 --- a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp +++ b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp @@ -121,7 +121,7 @@ void VkInteropFunctorDrawable::onDraw(SkCanvas* canvas) { glBindTexture(GL_TEXTURE_2D, 0); DrawGlInfo info; - SkMatrix44 mat4(canvas->getTotalMatrix()); + SkM44 mat4(canvas->experimental_getLocalToDevice()); SkIRect clipBounds = canvas->getDeviceClipBounds(); info.clipLeft = clipBounds.fLeft; @@ -131,7 +131,7 @@ void VkInteropFunctorDrawable::onDraw(SkCanvas* canvas) { info.isLayer = true; info.width = mFBInfo.width(); info.height = mFBInfo.height(); - mat4.asColMajorf(&info.transform[0]); + mat4.getColMajor(&info.transform[0]); info.color_space_ptr = canvas->imageInfo().colorSpace(); glViewport(0, 0, info.width, info.height); |