diff options
author | 2016-10-26 10:30:09 -0400 | |
---|---|---|
committer | 2016-11-03 13:28:49 -0400 | |
commit | 500a0c30d4dcd012218c3e44a62926a1c34a259f (patch) | |
tree | 674811f3d2d545ac306a5aaf3b3132b9734f044a | |
parent | 253f81b36747f54b4ba040f523df02d4b33163b7 (diff) |
Implement Skia pipelines for OpenGL and Vulkan.
Implement Skia pipelines for OpenGL and Vulkan:
base SkiaPipeline, SkiaOpenGLPipeline and SkiaVulkanPipeline.
Write unit tests for SkiaPipeline.
Test: Built and run manually on angler-eng.
Change-Id: Ie02583426cb3547541ad9bf91700602a6163ff58
19 files changed, 1114 insertions, 93 deletions
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index 06eb829e22e3..8d56d02df7bf 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -23,7 +23,10 @@ hwui_src_files := \ pipeline/skia/RenderNodeDrawable.cpp \ pipeline/skia/ReorderBarrierDrawables.cpp \ pipeline/skia/SkiaDisplayList.cpp \ + pipeline/skia/SkiaOpenGLPipeline.cpp \ + pipeline/skia/SkiaPipeline.cpp \ pipeline/skia/SkiaRecordingCanvas.cpp \ + pipeline/skia/SkiaVulkanPipeline.cpp \ renderstate/Blend.cpp \ renderstate/MeshState.cpp \ renderstate/OffscreenBufferPool.cpp \ @@ -296,6 +299,7 @@ LOCAL_SRC_FILES += \ tests/unit/RenderPropertiesTests.cpp \ tests/unit/SkiaBehaviorTests.cpp \ tests/unit/SkiaDisplayListTests.cpp \ + tests/unit/SkiaPipelineTests.cpp \ tests/unit/SkiaCanvasTests.cpp \ tests/unit/SnapshotTests.cpp \ tests/unit/StringUtilsTests.cpp \ diff --git a/libs/hwui/DeviceInfo.cpp b/libs/hwui/DeviceInfo.cpp index 4cfbb2a43198..700642ed7334 100644 --- a/libs/hwui/DeviceInfo.cpp +++ b/libs/hwui/DeviceInfo.cpp @@ -41,6 +41,13 @@ void DeviceInfo::initialize() { }); } +void DeviceInfo::initialize(int maxTextureSize) { + std::call_once(sInitializedFlag, [maxTextureSize]() { + sDeviceInfo = new DeviceInfo(); + sDeviceInfo->mMaxTextureSize = maxTextureSize; + }); +} + void DeviceInfo::load() { glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize); } diff --git a/libs/hwui/DeviceInfo.h b/libs/hwui/DeviceInfo.h index e551eb9bb8c0..aff84b02d85a 100644 --- a/libs/hwui/DeviceInfo.h +++ b/libs/hwui/DeviceInfo.h @@ -32,6 +32,7 @@ public: // only call this after GL has been initialized, or at any point if compiled // with HWUI_NULL_GPU static void initialize(); + static void initialize(int maxTextureSize); int maxTextureSize() const { return mMaxTextureSize; } diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h index 3819c5e9187d..b8964f0f16a0 100644 --- a/libs/hwui/RenderNode.h +++ b/libs/hwui/RenderNode.h @@ -33,6 +33,7 @@ #include "Matrix.h" #include "RenderProperties.h" #include "pipeline/skia/SkiaDisplayList.h" +#include "pipeline/skia/SkiaLayer.h" #include <vector> @@ -60,10 +61,6 @@ namespace proto { class RenderNode; } -namespace skiapipeline { - class SkiaDisplayList; -} - /** * Primary class for storing recorded canvas commands, as well as per-View/ViewGroup display properties. * @@ -312,14 +309,23 @@ public: * Returns true if an offscreen layer from any renderPipeline is attached * to this node. */ - bool hasLayer() const { return mLayer || mLayerSurface.get(); } + bool hasLayer() const { return mLayer || mSkiaLayer.get(); } /** * Used by the RenderPipeline to attach an offscreen surface to the RenderNode. * The surface is then will be used to store the contents of a layer. */ - void setLayerSurface(sk_sp<SkSurface> layer) { mLayerSurface = layer; } - + void setLayerSurface(sk_sp<SkSurface> layer) { + if (layer.get()) { + if (!mSkiaLayer.get()) { + mSkiaLayer = std::make_unique<skiapipeline::SkiaLayer>(); + } + mSkiaLayer->layerSurface = std::move(layer); + mSkiaLayer->inverseTransformInWindow.loadIdentity(); + } else { + mSkiaLayer.reset(); + } + } /** * If the RenderNode is of type LayerType::RenderLayer then this method will @@ -330,7 +336,13 @@ public: * NOTE: this function is only guaranteed to return accurate results after * prepareTree has been run for this RenderNode */ - SkSurface* getLayerSurface() const { return mLayerSurface.get(); } + SkSurface* getLayerSurface() const { + return mSkiaLayer.get() ? mSkiaLayer->layerSurface.get() : nullptr; + } + + skiapipeline::SkiaLayer* getSkiaLayer() const { + return mSkiaLayer.get(); + } private: /** @@ -346,7 +358,7 @@ private: * An offscreen rendering target used to contain the contents this RenderNode * when it has been set to draw as a LayerType::RenderLayer. */ - sk_sp<SkSurface> mLayerSurface; + std::unique_ptr<skiapipeline::SkiaLayer> mSkiaLayer; }; // class RenderNode } /* namespace uirenderer */ diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp index 1ea8bd2fcdbf..2dc8ce8fe774 100644 --- a/libs/hwui/hwui/Canvas.cpp +++ b/libs/hwui/hwui/Canvas.cpp @@ -20,6 +20,8 @@ #include "RenderNode.h" #include "MinikinUtils.h" #include "Paint.h" +#include "Properties.h" +#include "pipeline/skia/SkiaRecordingCanvas.h" #include "Typeface.h" #include <SkDrawFilter.h> @@ -27,6 +29,9 @@ namespace android { Canvas* Canvas::create_recording_canvas(int width, int height, uirenderer::RenderNode* renderNode) { + if (uirenderer::Properties::isSkiaEnabled()) { + return new uirenderer::skiapipeline::SkiaRecordingCanvas(renderNode, width, height); + } return new uirenderer::RecordingCanvas(width, height); } diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp index cefa893f2864..f263c490668a 100644 --- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp +++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp @@ -17,7 +17,7 @@ #include "RenderNodeDrawable.h" #include "RenderNode.h" #include "SkiaDisplayList.h" -#include "SkiaFrameRenderer.h" +#include "SkiaPipeline.h" #include "utils/TraceUtils.h" namespace android { @@ -57,7 +57,7 @@ void RenderNodeDrawable::onDraw(SkCanvas* canvas) { void RenderNodeDrawable::forceDraw(SkCanvas* canvas) { RenderNode* renderNode = mRenderNode.get(); - if (SkiaFrameRenderer::skpCaptureEnabled()) { + if (SkiaPipeline::skpCaptureEnabled()) { SkRect dimensions = SkRect::MakeWH(renderNode->getWidth(), renderNode->getHeight()); canvas->drawAnnotation(dimensions, renderNode->getName(), nullptr); } diff --git a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp index 8d77938ad1b9..875b8ef367c9 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 "SkiaFrameRenderer.h" +#include "SkiaPipeline.h" #include <SkBlurMask.h> #include <SkBlurMaskFilter.h> @@ -161,7 +161,7 @@ static void DrawSpotShadowGeneral(SkCanvas* canvas, const Shape& shape, float ca return; } - const Vector3 lightPos = SkiaFrameRenderer::getLightCenter(); + const Vector3 lightPos = SkiaPipeline::getLightCenter(); float zRatio = casterZValue / (lightPos.z - casterZValue); // clamp if (zRatio < 0.0f) { @@ -170,7 +170,7 @@ static void DrawSpotShadowGeneral(SkCanvas* canvas, const Shape& shape, float ca zRatio = 0.95f; } - float blurRadius = SkiaFrameRenderer::getLightRadius()*zRatio; + float blurRadius = SkiaPipeline::getLightRadius()*zRatio; SkAutoCanvasRestore acr(canvas, true); @@ -276,7 +276,7 @@ static void DrawRRectShadows(const SkRect& casterRect, SkScalar casterCornerRadi } if (spotAlpha > 0.0f) { - const Vector3 lightPos = SkiaFrameRenderer::getLightCenter(); + const Vector3 lightPos = SkiaPipeline::getLightCenter(); float zRatio = casterZValue / (lightPos.z - casterZValue); // clamp if (zRatio < 0.0f) { @@ -285,7 +285,7 @@ static void DrawRRectShadows(const SkRect& casterRect, SkScalar casterCornerRadi zRatio = 0.95f; } - const SkScalar lightWidth = SkiaFrameRenderer::getLightRadius(); + const SkScalar lightWidth = SkiaPipeline::getLightRadius(); SkScalar srcSpaceSpotRadius = 2.0f * lightWidth * zRatio; // the device-space radius sent to the blur shader must fit in 14.2 fixed point if (srcSpaceSpotRadius*scaleFactor > MAX_BLUR_RADIUS) { @@ -439,7 +439,7 @@ static void DrawRRectShadowsWithClip(const SkRect& casterRect, SkScalar casterCo } if (spotAlpha > 0.0f) { - const Vector3 lightPos = SkiaFrameRenderer::getLightCenter(); + const Vector3 lightPos = SkiaPipeline::getLightCenter(); float zRatio = casterZValue / (lightPos.z - casterZValue); // clamp if (zRatio < 0.0f) { @@ -448,7 +448,7 @@ static void DrawRRectShadowsWithClip(const SkRect& casterRect, SkScalar casterCo zRatio = 0.95f; } - const SkScalar lightWidth = SkiaFrameRenderer::getLightRadius(); + const SkScalar lightWidth = SkiaPipeline::getLightRadius(); const SkScalar srcSpaceSpotRadius = 2.0f * lightWidth * zRatio; const SkScalar devSpaceSpotRadius = srcSpaceSpotRadius * scaleFactor; @@ -626,8 +626,8 @@ void EndReorderBarrierDrawable::drawShadow(SkCanvas* canvas, RenderNodeDrawable* return; } - float ambientAlpha = SkiaFrameRenderer::getAmbientShadowAlpha()*casterAlpha; - float spotAlpha = SkiaFrameRenderer::getSpotShadowAlpha()*casterAlpha; + float ambientAlpha = SkiaPipeline::getAmbientShadowAlpha()*casterAlpha; + float spotAlpha = SkiaPipeline::getSpotShadowAlpha()*casterAlpha; const float casterZValue = casterProperties.getZ(); const RevealClip& revealClip = casterProperties.getRevealClip(); diff --git a/libs/hwui/pipeline/skia/SkiaFrameRenderer.h b/libs/hwui/pipeline/skia/SkiaLayer.h index 70207c1280ab..0988d7eda89d 100644 --- a/libs/hwui/pipeline/skia/SkiaFrameRenderer.h +++ b/libs/hwui/pipeline/skia/SkiaLayer.h @@ -16,39 +16,23 @@ #pragma once +#include <SkSurface.h> +#include "Matrix.h" + namespace android { namespace uirenderer { namespace skiapipeline { /** - * TODO: this is a stub that will be added in a subsquent CL + * An offscreen rendering target used to contain the contents a RenderNode. */ -class SkiaFrameRenderer { -public: - - static bool skpCaptureEnabled() { return false; } - - // TODO avoids unused compile error but we need to pass this to the reorder drawables! - static float getLightRadius() { - return 1.0f; - } - - static uint8_t getAmbientShadowAlpha() { - return 1; - } - - static uint8_t getSpotShadowAlpha() { - return 1; - } - - static Vector3 getLightCenter() { - Vector3 result; - result.x = result.y = result.z = 1.0f; - return result; - } - +struct SkiaLayer +{ + sk_sp<SkSurface> layerSurface; + Matrix4 inverseTransformInWindow; }; -}; // namespace skiapipeline -}; // namespace uirenderer -}; // namespace android + +} /* namespace skiapipeline */ +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp new file mode 100644 index 000000000000..519de967794d --- /dev/null +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp @@ -0,0 +1,170 @@ +/* + * 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 "SkiaOpenGLPipeline.h" + +#include "DeferredLayerUpdater.h" +#include "renderthread/EglManager.h" +#include "renderstate/RenderState.h" +#include "Readback.h" +#include "utils/TraceUtils.h" + +#include <android/native_window.h> +#include <cutils/properties.h> +#include <strings.h> + +using namespace android::uirenderer::renderthread; + +namespace android { +namespace uirenderer { +namespace skiapipeline { + +SkiaOpenGLPipeline::SkiaOpenGLPipeline(RenderThread& thread) + : SkiaPipeline(thread) + , mEglManager(thread.eglManager()) { +} + +MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() { + // TODO: Figure out why this workaround is needed, see b/13913604 + // In the meantime this matches the behavior of GLRenderer, so it is not a regression + EGLint error = 0; + if (!mEglManager.makeCurrent(mEglSurface, &error)) { + return MakeCurrentResult::AlreadyCurrent; + } + return error ? MakeCurrentResult::Failed : MakeCurrentResult::Succeeded; +} + +Frame SkiaOpenGLPipeline::getFrame() { + LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE, + "drawRenderNode called on a context with no surface!"); + return mEglManager.beginFrame(mEglSurface); +} + +bool SkiaOpenGLPipeline::draw(const Frame& frame, const SkRect& screenDirty, + const SkRect& dirty, + const FrameBuilder::LightGeometry& lightGeometry, + LayerUpdateQueue* layerUpdateQueue, + const Rect& contentDrawBounds, bool opaque, + const BakedOpRenderer::LightInfo& lightInfo, + const std::vector<sp<RenderNode>>& renderNodes, + FrameInfoVisualizer* profiler) { + + mEglManager.damageFrame(frame, dirty); + + // setup surface for fbo0 + GrBackendRenderTargetDesc renderTargetDesc; + renderTargetDesc.fWidth = frame.width(); + renderTargetDesc.fHeight = frame.height(); + renderTargetDesc.fConfig = kRGBA_8888_GrPixelConfig; + renderTargetDesc.fOrigin = kBottomLeft_GrSurfaceOrigin; + renderTargetDesc.fSampleCnt = 0; + renderTargetDesc.fStencilBits = STENCIL_BUFFER_SIZE; + renderTargetDesc.fRenderTargetHandle = 0; + + SkSurfaceProps props(0, kUnknown_SkPixelGeometry); + + SkASSERT(mRenderThread.getGrContext() != nullptr); + sk_sp<SkSurface> surface(SkSurface::MakeFromBackendRenderTarget( + mRenderThread.getGrContext(), renderTargetDesc, &props)); + + SkiaPipeline::updateLighting(lightGeometry, lightInfo); + renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface); + layerUpdateQueue->clear(); + return true; +} + +bool SkiaOpenGLPipeline::swapBuffers(const Frame& frame, bool drew, + const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) { + + GL_CHECKPOINT(LOW); + + // Even if we decided to cancel the frame, from the perspective of jank + // metrics the frame was swapped at this point + currentFrameInfo->markSwapBuffers(); + + *requireSwap = drew || mEglManager.damageRequiresSwap(); + + if (*requireSwap && (CC_UNLIKELY(!mEglManager.swapBuffers(frame, screenDirty)))) { + return false; + } + + return *requireSwap; +} + +bool SkiaOpenGLPipeline::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) { + layer->apply(); + return Readback::copyTextureLayerInto(mRenderThread, *(layer->backingLayer()), bitmap) + == CopyResult::Success; +} + +DeferredLayerUpdater* SkiaOpenGLPipeline::createTextureLayer() { + mEglManager.initialize(); + Layer* layer = new Layer(mRenderThread.renderState(), 0, 0); + layer->generateTexture(); + return new DeferredLayerUpdater(layer); +} + +void SkiaOpenGLPipeline::onStop() { + if (mEglManager.isCurrent(mEglSurface)) { + mEglManager.makeCurrent(EGL_NO_SURFACE); + } +} + +bool SkiaOpenGLPipeline::setSurface(Surface* surface, SwapBehavior swapBehavior) { + + if (mEglSurface != EGL_NO_SURFACE) { + mEglManager.destroySurface(mEglSurface); + mEglSurface = EGL_NO_SURFACE; + } + + if (surface) { + mEglSurface = mEglManager.createSurface(surface); + } + + if (mEglSurface != EGL_NO_SURFACE) { + const bool preserveBuffer = (swapBehavior != SwapBehavior::kSwap_discardBuffer); + mBufferPreserved = mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer); + return true; + } + + return false; +} + +bool SkiaOpenGLPipeline::isSurfaceReady() { + return CC_UNLIKELY(mEglSurface != EGL_NO_SURFACE); +} + +bool SkiaOpenGLPipeline::isContextReady() { + return CC_LIKELY(mEglManager.hasEglContext()); +} + +void SkiaOpenGLPipeline::invokeFunctor(const RenderThread& thread, Functor* functor) { + DrawGlInfo::Mode mode = DrawGlInfo::kModeProcessNoContext; + if (thread.eglManager().hasEglContext()) { + mode = DrawGlInfo::kModeProcess; + } + + (*functor)(mode, nullptr); + + // If there's no context we don't need to reset as there's no gl state to save/restore + if (mode != DrawGlInfo::kModeProcessNoContext) { + thread.getGrContext()->resetContext(); + } +} + +} /* namespace skiapipeline */ +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h new file mode 100644 index 000000000000..36685ddb17a7 --- /dev/null +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h @@ -0,0 +1,58 @@ +/* + * 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. + */ + +#pragma once + +#include "SkiaPipeline.h" + +namespace android { +namespace uirenderer { +namespace skiapipeline { + +class SkiaOpenGLPipeline : public SkiaPipeline { +public: + SkiaOpenGLPipeline(renderthread::RenderThread& thread); + virtual ~SkiaOpenGLPipeline() {} + + renderthread::MakeCurrentResult makeCurrent() override; + renderthread::Frame getFrame() override; + bool draw(const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty, + const FrameBuilder::LightGeometry& lightGeometry, + LayerUpdateQueue* layerUpdateQueue, + const Rect& contentDrawBounds, bool opaque, + const BakedOpRenderer::LightInfo& lightInfo, + const std::vector< sp<RenderNode> >& renderNodes, + FrameInfoVisualizer* profiler) override; + bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty, + FrameInfo* currentFrameInfo, bool* requireSwap) override; + bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) override; + DeferredLayerUpdater* createTextureLayer() override; + bool setSurface(Surface* window, renderthread::SwapBehavior swapBehavior) override; + void onStop() override; + bool isSurfaceReady() override; + bool isContextReady() override; + + static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor); + +private: + renderthread::EglManager& mEglManager; + EGLSurface mEglSurface = EGL_NO_SURFACE; + bool mBufferPreserved = false; +}; + +} /* namespace skiapipeline */ +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp new file mode 100644 index 000000000000..03fa266a5c20 --- /dev/null +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -0,0 +1,258 @@ +/* + * 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 "SkiaPipeline.h" + +#include "utils/TraceUtils.h" +#include <SkOSFile.h> +#include <SkPicture.h> +#include <SkPictureRecorder.h> +#include <SkPixelSerializer.h> +#include <SkStream.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) { } + +TaskManager* SkiaPipeline::getTaskManager() { + return &mTaskManager; +} + +void SkiaPipeline::onDestroyHardwareResources() { + // No need to flush the caches here. There is a timer + // which will flush temporary resources over time. +} + +void SkiaPipeline::renderLayers(const FrameBuilder::LightGeometry& lightGeometry, + LayerUpdateQueue* layerUpdateQueue, bool opaque, + const BakedOpRenderer::LightInfo& lightInfo) { + updateLighting(lightGeometry, lightInfo); + ATRACE_NAME("draw layers"); + renderLayersImpl(*layerUpdateQueue, opaque); + layerUpdateQueue->clear(); +} + +void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) { + // Render all layers that need to be updated, in order. + for (size_t i = 0; i < layers.entries().size(); i++) { + RenderNode* layerNode = layers.entries()[i].renderNode; + // 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()); + SkASSERT(layerNode->getDisplayList()->isSkiaDL()); + SkiaDisplayList* displayList = (SkiaDisplayList*)layerNode->getDisplayList(); + if (!displayList || displayList->isEmpty()) { + SkDEBUGF(("%p drawLayers(%s) : missing drawable", this, layerNode->getName())); + return; + } + + const Rect& layerDamage = layers.entries()[i].damage; + + SkCanvas* layerCanvas = layerNode->getLayerSurface()->getCanvas(); + + int saveCount = layerCanvas->save(); + SkASSERT(saveCount == 1); + + layerCanvas->clipRect(layerDamage.toSkRect(), SkRegion::kReplace_Op); + + auto savedLightCenter = mLightCenter; + // map current light center into RenderNode's coordinate space + layerNode->getSkiaLayer()->inverseTransformInWindow.mapPoint3d(mLightCenter); + + const RenderProperties& properties = layerNode->properties(); + const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight()); + if (properties.getClipToBounds() && layerCanvas->quickReject(bounds)) { + return; + } + + layerCanvas->clear(SK_ColorTRANSPARENT); + + RenderNodeDrawable root(layerNode, layerCanvas, false); + root.forceDraw(layerCanvas); + layerCanvas->restoreToCount(saveCount); + layerCanvas->flush(); + mLightCenter = savedLightCenter; + } + } +} + +bool SkiaPipeline::createOrUpdateLayer(RenderNode* node, + const DamageAccumulator& damageAccumulator) { + SkSurface* layer = node->getLayerSurface(); + if (!layer || layer->width() != node->getWidth() || layer->height() != node->getHeight()) { + SkImageInfo info = SkImageInfo::MakeN32Premul(node->getWidth(), node->getHeight()); + SkSurfaceProps props(0, kUnknown_SkPixelGeometry); + SkASSERT(mRenderThread.getGrContext() != nullptr); + node->setLayerSurface( + SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), SkBudgeted::kYes, + info, 0, &props)); + if (node->getLayerSurface()) { + // update the transform in window of the layer to reset its origin wrt light source + // position + Matrix4 windowTransform; + damageAccumulator.computeCurrentTransform(&windowTransform); + node->getSkiaLayer()->inverseTransformInWindow = windowTransform; + } + return true; + } + return false; +} + +void SkiaPipeline::destroyLayer(RenderNode* node) { + node->setLayerSurface(nullptr); +} + +void SkiaPipeline::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) { + GrContext* context = thread.getGrContext(); + if (context) { + ATRACE_FORMAT("Bitmap#prepareToDraw %dx%d", bitmap->width(), bitmap->height()); + SkBitmap skiaBitmap; + bitmap->getSkBitmap(&skiaBitmap); + sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(skiaBitmap, kNever_SkCopyPixelsMode); + SkImage_pinAsTexture(image.get(), context); + SkImage_unpinAsTexture(image.get(), context); + } +} + +// Encodes to PNG, unless there is already encoded data, in which case that gets +// used. +class PngPixelSerializer : public SkPixelSerializer { +public: + bool onUseEncodedData(const void*, size_t) override { return true; } + SkData* onEncode(const SkPixmap& pixmap) override { + return SkImageEncoder::EncodeData(pixmap.info(), pixmap.addr(), pixmap.rowBytes(), + SkImageEncoder::kPNG_Type, 100); + } +}; + +void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& clip, + const std::vector<sp<RenderNode>>& nodes, bool opaque, const Rect &contentDrawBounds, + sk_sp<SkSurface> surface) { + + // unpin all mutable images that were attached to nodes deleted while on the UI thread + SkiaDisplayList::cleanupImages(surface->getCanvas()->getGrContext()); + + // draw all layers up front + renderLayersImpl(layers, opaque); + + // initialize the canvas for the current frame + SkCanvas* canvas = surface->getCanvas(); + + std::unique_ptr<SkPictureRecorder> recorder; + bool recordingPicture = false; + char prop[PROPERTY_VALUE_MAX]; + if (skpCaptureEnabled()) { + property_get("debug.hwui.capture_frame_as_skp", prop, "0"); + recordingPicture = prop[0] != '0' && !sk_exists(prop); + if (recordingPicture) { + recorder.reset(new SkPictureRecorder()); + canvas = recorder->beginRecording(surface->width(), surface->height(), + nullptr, SkPictureRecorder::kPlaybackDrawPicture_RecordFlag); + } + } + + canvas->clipRect(clip, SkRegion::kReplace_Op); + + if (!opaque) { + canvas->clear(SK_ColorTRANSPARENT); + } + + // If there are multiple render nodes, they are laid out as follows: + // #0 - backdrop (content + caption) + // #1 - content (positioned at (0,0) and clipped to - its bounds mContentDrawBounds) + // #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. + + // The bounds of the backdrop against which the content should be clipped. + Rect backdropBounds = contentDrawBounds; + // 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. + int layer = (contentDrawBounds.isEmpty() || nodes.size() == 1) ? 2 : 0; + + for (const sp<RenderNode>& node : nodes) { + if (node->nothingToDraw()) continue; + + SkASSERT(node->getDisplayList()->isSkiaDL()); + + int count = canvas->save(); + + if (layer == 0) { + const RenderProperties& properties = node->properties(); + Rect targetBounds(properties.getLeft(), properties.getTop(), + properties.getRight(), properties.getBottom()); + // Move the content bounds towards the fixed corner of the backdrop. + const int x = targetBounds.left; + const int y = targetBounds.top; + // Remember the intersection of the target bounds and the intersection bounds against + // which we have to crop the content. + backdropBounds.set(x, y, x + backdropBounds.getWidth(), y + backdropBounds.getHeight()); + backdropBounds.doIntersect(targetBounds); + } else if (layer == 1) { + // We shift and clip the content to match its final location in the window. + const SkRect clip = SkRect::MakeXYWH(contentDrawBounds.left, contentDrawBounds.top, + backdropBounds.getWidth(), backdropBounds.getHeight()); + const float dx = backdropBounds.left - contentDrawBounds.left; + const float dy = backdropBounds.top - contentDrawBounds.top; + canvas->translate(dx, dy); + // It gets cropped against the bounds of the backdrop to stay inside. + canvas->clipRect(clip, SkRegion::kIntersect_Op); + } + + RenderNodeDrawable root(node.get(), canvas); + root.draw(canvas); + canvas->restoreToCount(count); + layer++; + } + + if (skpCaptureEnabled() && recordingPicture) { + sk_sp<SkPicture> picture = recorder->finishRecordingAsPicture(); + if (picture->approximateOpCount() > 0) { + SkFILEWStream stream(prop); + if (stream.isValid()) { + PngPixelSerializer serializer; + picture->serialize(&stream, &serializer); + stream.flush(); + SkDebugf("Captured Drawing Output (%d bytes) for frame. %s", stream.bytesWritten(), prop); + } + } + surface->getCanvas()->drawPicture(picture); + } + + ATRACE_NAME("flush commands"); + canvas->flush(); +} + +} /* namespace skiapipeline */ +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h new file mode 100644 index 000000000000..160046af63a6 --- /dev/null +++ b/libs/hwui/pipeline/skia/SkiaPipeline.h @@ -0,0 +1,112 @@ +/* + * 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. + */ + +#pragma once + +#include "renderthread/CanvasContext.h" +#include "FrameBuilder.h" +#include "renderthread/IRenderPipeline.h" +#include <SkSurface.h> + +namespace android { +namespace uirenderer { +namespace skiapipeline { + +class SkiaPipeline : public renderthread::IRenderPipeline { +public: + SkiaPipeline(renderthread::RenderThread& thread); + virtual ~SkiaPipeline() {} + + TaskManager* getTaskManager() override; + + void onDestroyHardwareResources() override; + + void renderLayers(const FrameBuilder::LightGeometry& lightGeometry, + LayerUpdateQueue* layerUpdateQueue, bool opaque, + const BakedOpRenderer::LightInfo& lightInfo) override; + + bool createOrUpdateLayer(RenderNode* node, + const DamageAccumulator& damageAccumulator) override; + + void renderFrame(const LayerUpdateQueue& layers, const SkRect& clip, + const std::vector< sp<RenderNode> >& nodes, bool opaque, const Rect &contentDrawBounds, + sk_sp<SkSurface> surface); + + static void destroyLayer(RenderNode* node); + + static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap); + + static void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque); + + static bool skpCaptureEnabled() { return false; } + + 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 FrameBuilder::LightGeometry& lightGeometry, + const BakedOpRenderer::LightInfo& lightInfo) { + mLightRadius = lightGeometry.radius; + mAmbientShadowAlpha = lightInfo.ambientShadowAlpha; + mSpotShadowAlpha = lightInfo.spotShadowAlpha; + mLightCenter = lightGeometry.center; + } +protected: + renderthread::RenderThread& mRenderThread; + +private: + TaskManager mTaskManager; + static float mLightRadius; + static uint8_t mAmbientShadowAlpha; + static uint8_t mSpotShadowAlpha; + static Vector3 mLightCenter; +}; + +} /* namespace skiapipeline */ +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp new file mode 100644 index 000000000000..520309a50f53 --- /dev/null +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp @@ -0,0 +1,144 @@ +/* + * 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 "SkiaVulkanPipeline.h" + +#include "DeferredLayerUpdater.h" +#include "renderthread/EglManager.h" // needed for Frame +#include "Readback.h" +#include "renderstate/RenderState.h" + +#include <SkTypes.h> +#include <WindowContextFactory_android.h> +#include <VulkanWindowContext.h> + +#include <android/native_window.h> +#include <cutils/properties.h> +#include <strings.h> + +using namespace android::uirenderer::renderthread; +using namespace sk_app; + +namespace android { +namespace uirenderer { +namespace skiapipeline { + +MakeCurrentResult SkiaVulkanPipeline::makeCurrent() { + return (mWindowContext != nullptr) ? + MakeCurrentResult::AlreadyCurrent : MakeCurrentResult::Failed; +} + +Frame SkiaVulkanPipeline::getFrame() { + LOG_ALWAYS_FATAL_IF(mWindowContext == nullptr, "Tried to draw into null vulkan context!"); + mBackbuffer = mWindowContext->getBackbufferSurface(); + if (mBackbuffer.get() == nullptr) { + // try recreating the context? + SkDebugf("failed to get backbuffer"); + return Frame(-1, -1, 0); + } + + // TODO: support buffer age if Vulkan API can do it + Frame frame(mBackbuffer->width(), mBackbuffer->height(), 0); + return frame; +} + +bool SkiaVulkanPipeline::draw(const Frame& frame, const SkRect& screenDirty, + const SkRect& dirty, + const FrameBuilder::LightGeometry& lightGeometry, + LayerUpdateQueue* layerUpdateQueue, + const Rect& contentDrawBounds, bool opaque, + const BakedOpRenderer::LightInfo& lightInfo, + const std::vector<sp<RenderNode>>& renderNodes, + FrameInfoVisualizer* profiler) { + + if (mBackbuffer.get() == nullptr) { + return false; + } + renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, mBackbuffer); + layerUpdateQueue->clear(); + return true; +} + +bool SkiaVulkanPipeline::swapBuffers(const Frame& frame, bool drew, + const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) { + + *requireSwap = drew; + + // Even if we decided to cancel the frame, from the perspective of jank + // metrics the frame was swapped at this point + currentFrameInfo->markSwapBuffers(); + + if (*requireSwap) { + mWindowContext->swapBuffers(); + } + + mBackbuffer.reset(); + + return *requireSwap; +} + +bool SkiaVulkanPipeline::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) { + // TODO: implement copyLayerInto for vulkan. + return false; +} + +DeferredLayerUpdater* SkiaVulkanPipeline::createTextureLayer() { + Layer* layer = new Layer(mRenderThread.renderState(), 0, 0); + return new DeferredLayerUpdater(layer); +} + +void SkiaVulkanPipeline::onStop() { +} + +bool SkiaVulkanPipeline::setSurface(Surface* surface, SwapBehavior swapBehavior) { + + if (mWindowContext) { + delete mWindowContext; + mWindowContext = nullptr; + } + + if (surface) { + DisplayParams displayParams; + mWindowContext = window_context_factory::NewVulkanForAndroid(surface, displayParams); + if (mWindowContext) { + DeviceInfo::initialize(mWindowContext->getGrContext()->caps()->maxRenderTargetSize()); + } + } + + + // this doesn't work for if there is more than one CanvasContext available at one time! + mRenderThread.setGrContext(mWindowContext ? mWindowContext->getGrContext() : nullptr); + + return mWindowContext != nullptr; +} + +bool SkiaVulkanPipeline::isSurfaceReady() { + return CC_LIKELY(mWindowContext != nullptr) && mWindowContext->isValid(); +} + +bool SkiaVulkanPipeline::isContextReady() { + return CC_LIKELY(mWindowContext != nullptr); +} + +void SkiaVulkanPipeline::invokeFunctor(const RenderThread& thread, Functor* functor) { + // TODO: we currently don't support OpenGL WebView's + DrawGlInfo::Mode mode = DrawGlInfo::kModeProcessNoContext; + (*functor)(mode, nullptr); +} + +} /* namespace skiapipeline */ +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h new file mode 100644 index 000000000000..cdc869245be3 --- /dev/null +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h @@ -0,0 +1,62 @@ +/* + * 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. + */ + +#pragma once + +#include "SkiaPipeline.h" +#include <SkSurface.h> + +namespace sk_app { +class WindowContext; +} + +namespace android { +namespace uirenderer { +namespace skiapipeline { + +class SkiaVulkanPipeline : public SkiaPipeline { +public: + SkiaVulkanPipeline(renderthread::RenderThread& thread) : SkiaPipeline(thread) {} + virtual ~SkiaVulkanPipeline() {} + + renderthread::MakeCurrentResult makeCurrent() override; + renderthread::Frame getFrame() override; + bool draw(const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty, + const FrameBuilder::LightGeometry& lightGeometry, + LayerUpdateQueue* layerUpdateQueue, + const Rect& contentDrawBounds, bool opaque, + const BakedOpRenderer::LightInfo& lightInfo, + const std::vector< sp<RenderNode> >& renderNodes, + FrameInfoVisualizer* profiler) override; + bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty, + FrameInfo* currentFrameInfo, bool* requireSwap) override; + bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) override; + DeferredLayerUpdater* createTextureLayer() override; + bool setSurface(Surface* window, renderthread::SwapBehavior swapBehavior) override; + void onStop() override; + bool isSurfaceReady() override; + bool isContextReady() override; + + static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor); + +private: + sk_app::WindowContext* mWindowContext = nullptr; + sk_sp<SkSurface> mBackbuffer; +}; + +} /* namespace skiapipeline */ +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 03deb0ac213b..c561c86460c6 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -28,6 +28,9 @@ #include "renderstate/Stencil.h" #include "protos/hwui.pb.h" #include "OpenGLPipeline.h" +#include "pipeline/skia/SkiaOpenGLPipeline.h" +#include "pipeline/skia/SkiaPipeline.h" +#include "pipeline/skia/SkiaVulkanPipeline.h" #include "utils/GLUtils.h" #include "utils/TimeUtils.h" @@ -69,13 +72,11 @@ CanvasContext* CanvasContext::create(RenderThread& thread, return new CanvasContext(thread, translucent, rootRenderNode, contextFactory, std::make_unique<OpenGLPipeline>(thread)); case RenderPipelineType::SkiaGL: - //TODO: implement SKIA GL - LOG_ALWAYS_FATAL("skiaGL canvas type not implemented."); - break; + return new CanvasContext(thread, translucent, rootRenderNode, contextFactory, + std::make_unique<skiapipeline::SkiaOpenGLPipeline>(thread)); case RenderPipelineType::SkiaVulkan: - //TODO: implement Vulkan - LOG_ALWAYS_FATAL("Vulkan canvas type not implemented."); - break; + return new CanvasContext(thread, translucent, rootRenderNode, contextFactory, + std::make_unique<skiapipeline::SkiaVulkanPipeline>(thread)); default: LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t) renderType); break; @@ -89,6 +90,10 @@ void CanvasContext::destroyLayer(RenderNode* node) { case RenderPipelineType::OpenGL: OpenGLPipeline::destroyLayer(node); break; + case RenderPipelineType::SkiaGL: + case RenderPipelineType::SkiaVulkan: + skiapipeline::SkiaPipeline::destroyLayer(node); + break; default: LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t) renderType); break; @@ -102,6 +107,12 @@ void CanvasContext::invokeFunctor(const RenderThread& thread, Functor* functor) case RenderPipelineType::OpenGL: OpenGLPipeline::invokeFunctor(thread, functor); break; + case RenderPipelineType::SkiaGL: + skiapipeline::SkiaOpenGLPipeline::invokeFunctor(thread, functor); + break; + case RenderPipelineType::SkiaVulkan: + skiapipeline::SkiaVulkanPipeline::invokeFunctor(thread, functor); + break; default: LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t) renderType); break; @@ -114,6 +125,10 @@ void CanvasContext::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) { case RenderPipelineType::OpenGL: OpenGLPipeline::prepareToDraw(thread, bitmap); break; + case RenderPipelineType::SkiaGL: + case RenderPipelineType::SkiaVulkan: + skiapipeline::SkiaPipeline::prepareToDraw(thread, bitmap); + break; default: LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t) renderType); break; diff --git a/libs/hwui/renderthread/EglManager.h b/libs/hwui/renderthread/EglManager.h index 7349dcc9f041..b12522e6bd41 100644 --- a/libs/hwui/renderthread/EglManager.h +++ b/libs/hwui/renderthread/EglManager.h @@ -31,6 +31,11 @@ class EglManager; class Frame { public: + Frame(EGLint width, EGLint height, EGLint bufferAge) + : mWidth(width) + , mHeight(height) + , mBufferAge(bufferAge) { } + EGLint width() const { return mWidth; } EGLint height() const { return mHeight; } @@ -39,6 +44,7 @@ public: EGLint bufferAge() const { return mBufferAge; } private: + Frame() {} friend class EglManager; EGLSurface mSurface; diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h index 0be5a3b9d439..0ce598d4dba4 100644 --- a/libs/hwui/tests/common/TestUtils.h +++ b/libs/hwui/tests/common/TestUtils.h @@ -22,6 +22,7 @@ #include <Rect.h> #include <RenderNode.h> #include <hwui/Bitmap.h> +#include <pipeline/skia/SkiaRecordingCanvas.h> #include <renderstate/RenderState.h> #include <renderthread/RenderThread.h> #include <Snapshot.h> @@ -196,6 +197,35 @@ public: node.setStagingDisplayList(canvas->finishRecording(), nullptr); } + static sp<RenderNode> createSkiaNode(int left, int top, int right, int bottom, + std::function<void(RenderProperties& props, skiapipeline::SkiaRecordingCanvas& canvas)> setup, + const char* name = nullptr, skiapipeline::SkiaDisplayList* displayList = nullptr) { + #if HWUI_NULL_GPU + // if RenderNodes are being sync'd/used, device info will be needed, since + // DeviceInfo::maxTextureSize() affects layer property + DeviceInfo::initialize(); + #endif + sp<RenderNode> node = new RenderNode(); + if (name) { + node->setName(name); + } + RenderProperties& props = node->mutateStagingProperties(); + props.setLeftTopRightBottom(left, top, right, bottom); + if (displayList) { + node->setStagingDisplayList(displayList, nullptr); + } + if (setup) { + std::unique_ptr<skiapipeline::SkiaRecordingCanvas> canvas( + new skiapipeline::SkiaRecordingCanvas(nullptr, + props.getWidth(), props.getHeight())); + setup(props, *canvas.get()); + node->setStagingDisplayList(canvas->finishRecording(), nullptr); + } + node->setPropertyFieldsDirty(0xFFFFFFFF); + TestUtils::syncHierarchyPropertiesAndDisplayList(node); + return node; + } + /** * Forces a sync of a tree of RenderNode, such that every descendant will have its staging * properties and DisplayList moved to the render copies. diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp index 19c311c7e182..c68ca4e74368 100644 --- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp +++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp @@ -34,34 +34,6 @@ using namespace android::uirenderer; using namespace android::uirenderer::renderthread; using namespace android::uirenderer::skiapipeline; -static sp<RenderNode> createSkiaNode(int left, int top, int right, int bottom, - std::function<void(RenderProperties& props, SkiaRecordingCanvas& canvas)> setup, - const char* name = nullptr, SkiaDisplayList* displayList = nullptr) { -#if HWUI_NULL_GPU - // if RenderNodes are being sync'd/used, device info will be needed, since - // DeviceInfo::maxTextureSize() affects layer property - DeviceInfo::initialize(); -#endif - sp<RenderNode> node = new RenderNode(); - if (name) { - node->setName(name); - } - RenderProperties& props = node->mutateStagingProperties(); - props.setLeftTopRightBottom(left, top, right, bottom); - if (displayList) { - node->setStagingDisplayList(displayList, nullptr); - } - if (setup) { - std::unique_ptr<SkiaRecordingCanvas> canvas(new SkiaRecordingCanvas(nullptr, - props.getWidth(), props.getHeight())); - setup(props, *canvas.get()); - node->setStagingDisplayList(canvas->finishRecording(), nullptr); - } - node->setPropertyFieldsDirty(0xFFFFFFFF); - TestUtils::syncHierarchyPropertiesAndDisplayList(node); - return node; -} - TEST(RenderNodeDrawable, create) { auto rootNode = TestUtils::createNode(0, 0, 200, 400, [](RenderProperties& props, Canvas& canvas) { @@ -86,7 +58,7 @@ TEST(RenderNodeDrawable, drawContent) { ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); //create a RenderNodeDrawable backed by a RenderNode backed by a SkLiteRecorder - auto rootNode = createSkiaNode(0, 0, 1, 1, + auto rootNode = TestUtils::createSkiaNode(0, 0, 1, 1, [](RenderProperties& props, SkiaRecordingCanvas& recorder) { recorder.drawColor(SK_ColorRED, SkBlendMode::kSrcOver); }); @@ -118,14 +90,14 @@ TEST(RenderNodeDrawable, drawAndReorder) { ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorWHITE); //-z draws to all 4 pixels (RED) - auto redNode = createSkiaNode(0, 0, 4, 4, + auto redNode = TestUtils::createSkiaNode(0, 0, 4, 4, [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) { redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver); props.setElevation(-10.0f); }, "redNode"); //0z draws to bottom 2 pixels (GREEN) - auto bottomHalfGreenNode = createSkiaNode(0, 0, 4, 4, + auto bottomHalfGreenNode = TestUtils::createSkiaNode(0, 0, 4, 4, [](RenderProperties& props, SkiaRecordingCanvas& bottomHalfGreenCanvas) { SkPaint greenPaint; greenPaint.setColor(SK_ColorGREEN); @@ -135,7 +107,7 @@ TEST(RenderNodeDrawable, drawAndReorder) { }, "bottomHalfGreenNode"); //+z draws to right 2 pixels (BLUE) - auto rightHalfBlueNode = createSkiaNode(0, 0, 4, 4, + auto rightHalfBlueNode = TestUtils::createSkiaNode(0, 0, 4, 4, [](RenderProperties& props, SkiaRecordingCanvas& rightHalfBlueCanvas) { SkPaint bluePaint; bluePaint.setColor(SK_ColorBLUE); @@ -144,7 +116,7 @@ TEST(RenderNodeDrawable, drawAndReorder) { props.setElevation(10.0f); }, "rightHalfBlueNode"); - auto rootNode = createSkiaNode(0, 0, 4, 4, + auto rootNode = TestUtils::createSkiaNode(0, 0, 4, 4, [&](RenderProperties& props, SkiaRecordingCanvas& rootRecorder) { rootRecorder.insertReorderBarrier(true); //draw in reverse Z order, so Z alters draw order @@ -167,7 +139,7 @@ TEST(RenderNodeDrawable, composeOnLayer) canvas.drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); - auto rootNode = createSkiaNode(0, 0, 1, 1, + auto rootNode = TestUtils::createSkiaNode(0, 0, 1, 1, [](RenderProperties& props, SkiaRecordingCanvas& recorder) { recorder.drawColor(SK_ColorRED, SkBlendMode::kSrcOver); }); @@ -176,7 +148,7 @@ TEST(RenderNodeDrawable, composeOnLayer) auto surfaceLayer = SkSurface::MakeRasterN32Premul(1, 1); auto canvas2 = surfaceLayer->getCanvas(); canvas2->drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver); - rootNode->setLayerSurface( surfaceLayer ); + rootNode->setLayerSurface(surfaceLayer); RenderNodeDrawable drawable1(rootNode.get(), &canvas, false); canvas.drawDrawable(&drawable1); @@ -190,7 +162,7 @@ TEST(RenderNodeDrawable, composeOnLayer) canvas.drawDrawable(&drawable3); ASSERT_EQ(SK_ColorRED, TestUtils::getColor(surface, 0, 0)); - rootNode->setLayerSurface( sk_sp<SkSurface>() ); + rootNode->setLayerSurface(sk_sp<SkSurface>()); } //TODO: refactor to cover test cases from FrameBuilderTests_projectionReorder @@ -203,18 +175,18 @@ TEST(RenderNodeDrawable, projectDraw) { canvas.drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); - auto redNode = createSkiaNode(0, 0, 1, 1, + auto redNode = TestUtils::createSkiaNode(0, 0, 1, 1, [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) { redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver); }, "redNode"); - auto greenNodeWithRedChild = createSkiaNode(0, 0, 1, 1, + auto greenNodeWithRedChild = TestUtils::createSkiaNode(0, 0, 1, 1, [&](RenderProperties& props, SkiaRecordingCanvas& greenCanvasWithRedChild) { greenCanvasWithRedChild.drawRenderNode(redNode.get()); greenCanvasWithRedChild.drawColor(SK_ColorGREEN, SkBlendMode::kSrcOver); }, "greenNodeWithRedChild"); - auto rootNode = createSkiaNode(0, 0, 1, 1, + auto rootNode = TestUtils::createSkiaNode(0, 0, 1, 1, [&](RenderProperties& props, SkiaRecordingCanvas& rootCanvas) { rootCanvas.drawRenderNode(greenNodeWithRedChild.get()); }, "rootNode"); diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp new file mode 100644 index 000000000000..7a2fa5792f67 --- /dev/null +++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp @@ -0,0 +1,181 @@ +/* + * 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 <gtest/gtest.h> +#include <VectorDrawable.h> + +#include "AnimationContext.h" +#include "DamageAccumulator.h" +#include "IContextFactory.h" +#include "pipeline/skia/SkiaDisplayList.h" +#include "pipeline/skia/SkiaRecordingCanvas.h" +#include "pipeline/skia/SkiaOpenGLPipeline.h" +#include "renderthread/CanvasContext.h" +#include "tests/common/TestUtils.h" +#include "SkiaCanvas.h" +#include <SkLiteRecorder.h> +#include <string.h> + +using namespace android; +using namespace android::uirenderer; +using namespace android::uirenderer::renderthread; +using namespace android::uirenderer::skiapipeline; + +RENDERTHREAD_TEST(SkiaPipeline, renderFrame) { + auto redNode = TestUtils::createSkiaNode(0, 0, 1, 1, + [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) { + redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver); + }); + LayerUpdateQueue layerUpdateQueue; + SkRect dirty = SkRect::MakeLargest(); + std::vector<sp<RenderNode>> renderNodes; + renderNodes.push_back(redNode); + bool opaque = true; + android::uirenderer::Rect contentDrawBounds(0, 0, 1, 1); + auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread); + auto surface = SkSurface::MakeRasterN32Premul(1, 1); + surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver); + ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface); + ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED); +} + +RENDERTHREAD_TEST(SkiaPipeline, renderFrameCheckBounds) { + auto backdropRedNode = TestUtils::createSkiaNode(1, 1, 4, 4, + [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) { + redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver); + }); + auto contentGreenNode = TestUtils::createSkiaNode(2, 2, 5, 5, + [](RenderProperties& props, SkiaRecordingCanvas& blueCanvas) { + blueCanvas.drawColor(SK_ColorGREEN, SkBlendMode::kSrcOver); + }); + LayerUpdateQueue layerUpdateQueue; + SkRect dirty = SkRect::MakeLargest(); + std::vector<sp<RenderNode>> renderNodes; + renderNodes.push_back(backdropRedNode); //first node is backdrop + renderNodes.push_back(contentGreenNode); //second node is content drawn on top of the backdrop + android::uirenderer::Rect contentDrawBounds(1, 1, 3, 3); + auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread); + auto surface = SkSurface::MakeRasterN32Premul(5, 5); + surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver); + ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); + //backdropBounds is (1, 1, 3, 3), content clip is (1, 1, 3, 3), content translate is (0, 0) + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface); + ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); + ASSERT_EQ(TestUtils::getColor(surface, 1, 1), SK_ColorRED); + ASSERT_EQ(TestUtils::getColor(surface, 2, 2), SK_ColorGREEN); + ASSERT_EQ(TestUtils::getColor(surface, 3, 3), SK_ColorRED); + ASSERT_EQ(TestUtils::getColor(surface, 4, 4), SK_ColorBLUE); + + surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver); + contentDrawBounds.set(0, 0, 5, 5); + //backdropBounds is (1, 1, 4, 4), content clip is (0, 0, 3, 3), content translate is (1, 1) + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface); + ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); + ASSERT_EQ(TestUtils::getColor(surface, 1, 1), SK_ColorRED); + ASSERT_EQ(TestUtils::getColor(surface, 2, 2), SK_ColorRED); + ASSERT_EQ(TestUtils::getColor(surface, 3, 3), SK_ColorGREEN); + ASSERT_EQ(TestUtils::getColor(surface, 4, 4), SK_ColorBLUE); +} + +RENDERTHREAD_TEST(SkiaPipeline, renderFrameCheckOpaque) { + auto halfGreenNode = TestUtils::createSkiaNode(0, 0, 2, 2, + [](RenderProperties& props, SkiaRecordingCanvas& bottomHalfGreenCanvas) { + SkPaint greenPaint; + greenPaint.setColor(SK_ColorGREEN); + greenPaint.setStyle(SkPaint::kFill_Style); + bottomHalfGreenCanvas.drawRect(0, 1, 2, 2, greenPaint); + }); + LayerUpdateQueue layerUpdateQueue; + SkRect dirty = SkRect::MakeLargest(); + std::vector<sp<RenderNode>> renderNodes; + renderNodes.push_back(halfGreenNode); + android::uirenderer::Rect contentDrawBounds(0, 0, 2, 2); + auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread); + auto surface = SkSurface::MakeRasterN32Premul(2, 2); + surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver); + ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface); + ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); + ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorGREEN); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, false, contentDrawBounds, surface); + ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned int)SK_ColorTRANSPARENT); + ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorGREEN); +} + +RENDERTHREAD_TEST(SkiaPipeline, renderFrameCheckDirtyRect) { + auto redNode = TestUtils::createSkiaNode(0, 0, 2, 2, + [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) { + redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver); + }); + LayerUpdateQueue layerUpdateQueue; + SkRect dirty = SkRect::MakeXYWH(0, 1, 2, 1); + std::vector<sp<RenderNode>> renderNodes; + renderNodes.push_back(redNode); + android::uirenderer::Rect contentDrawBounds(0, 0, 2, 2); + auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread); + auto surface = SkSurface::MakeRasterN32Premul(2, 2); + surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver); + ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface); + ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); + ASSERT_EQ(TestUtils::getColor(surface, 1, 0), SK_ColorBLUE); + ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorRED); + ASSERT_EQ(TestUtils::getColor(surface, 1, 1), SK_ColorRED); +} + +RENDERTHREAD_TEST(SkiaPipeline, renderLayer) { + auto redNode = TestUtils::createSkiaNode(0, 0, 1, 1, + [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) { + redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver); + }); + auto surfaceLayer1 = SkSurface::MakeRasterN32Premul(1, 1); + surfaceLayer1->getCanvas()->drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver); + ASSERT_EQ(TestUtils::getColor(surfaceLayer1, 0, 0), SK_ColorWHITE); + redNode->setLayerSurface(surfaceLayer1); + + //create a 2nd 2x2 layer and add it to the queue as well. + //make the layer's dirty area one half of the layer and verify only the dirty half is updated. + auto blueNode = TestUtils::createSkiaNode(0, 0, 2, 2, + [](RenderProperties& props, SkiaRecordingCanvas& blueCanvas) { + blueCanvas.drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver); + }); + auto surfaceLayer2 = SkSurface::MakeRasterN32Premul(2, 2); + surfaceLayer2->getCanvas()->drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver); + ASSERT_EQ(TestUtils::getColor(surfaceLayer2, 0, 0), SK_ColorWHITE); + blueNode->setLayerSurface(surfaceLayer2); + + //attach both layers to the update queue + LayerUpdateQueue layerUpdateQueue; + SkRect dirty = SkRect::MakeLargest(); + layerUpdateQueue.enqueueLayerWithDamage(redNode.get(), dirty); + layerUpdateQueue.enqueueLayerWithDamage(blueNode.get(), SkRect::MakeWH(2, 1)); + ASSERT_EQ(layerUpdateQueue.entries().size(), 2UL); + + bool opaque = true; + FrameBuilder::LightGeometry lightGeometry; + lightGeometry.radius = 1.0f; + lightGeometry.center = { 0.0f, 0.0f, 0.0f }; + BakedOpRenderer::LightInfo lightInfo; + auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread); + pipeline->renderLayers(lightGeometry, &layerUpdateQueue, opaque, lightInfo); + ASSERT_EQ(TestUtils::getColor(surfaceLayer1, 0, 0), SK_ColorRED); + ASSERT_EQ(TestUtils::getColor(surfaceLayer2, 0, 0), SK_ColorBLUE); + ASSERT_EQ(TestUtils::getColor(surfaceLayer2, 0, 1), SK_ColorWHITE); + ASSERT_TRUE(layerUpdateQueue.entries().empty()); + redNode->setLayerSurface(sk_sp<SkSurface>()); + blueNode->setLayerSurface(sk_sp<SkSurface>()); +} |