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
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 06eb829..8d56d02 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -23,7 +23,10 @@
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 @@
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 4cfbb2a..700642e 100644
--- a/libs/hwui/DeviceInfo.cpp
+++ b/libs/hwui/DeviceInfo.cpp
@@ -41,6 +41,13 @@
});
}
+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 e551eb9..aff84b0 100644
--- a/libs/hwui/DeviceInfo.h
+++ b/libs/hwui/DeviceInfo.h
@@ -32,6 +32,7 @@
// 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 3819c5e..b8964f0 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 @@
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 @@
* 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 @@
* 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 @@
* 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 1ea8bd2..2dc8ce8 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 cefa893..f263c49 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::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 8d77938..875b8ef 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 @@
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 @@
zRatio = 0.95f;
}
- float blurRadius = SkiaFrameRenderer::getLightRadius()*zRatio;
+ float blurRadius = SkiaPipeline::getLightRadius()*zRatio;
SkAutoCanvasRestore acr(canvas, true);
@@ -276,7 +276,7 @@
}
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 @@
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 @@
}
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 @@
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 @@
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/SkiaFrameRenderer.h
deleted file mode 100644
index 70207c1..0000000
--- a/libs/hwui/pipeline/skia/SkiaFrameRenderer.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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
-
-namespace android {
-namespace uirenderer {
-namespace skiapipeline {
-
-/**
- * TODO: this is a stub that will be added in a subsquent CL
- */
-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;
- }
-
-};
-
-}; // namespace skiapipeline
-}; // namespace uirenderer
-}; // namespace android
diff --git a/libs/hwui/pipeline/skia/SkiaLayer.h b/libs/hwui/pipeline/skia/SkiaLayer.h
new file mode 100644
index 0000000..0988d7e
--- /dev/null
+++ b/libs/hwui/pipeline/skia/SkiaLayer.h
@@ -0,0 +1,38 @@
+/*
+ * 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 <SkSurface.h>
+#include "Matrix.h"
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+/**
+ * An offscreen rendering target used to contain the contents a RenderNode.
+ */
+struct SkiaLayer
+{
+ sk_sp<SkSurface> layerSurface;
+ Matrix4 inverseTransformInWindow;
+};
+
+
+} /* 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 0000000..519de96
--- /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 0000000..36685dd
--- /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 0000000..03fa266
--- /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 0000000..160046a
--- /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 0000000..520309a
--- /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 0000000..cdc8692
--- /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 03deb0a..c561c86 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 @@
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 @@
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 @@
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 @@
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 7349dcc..b12522e 100644
--- a/libs/hwui/renderthread/EglManager.h
+++ b/libs/hwui/renderthread/EglManager.h
@@ -31,6 +31,11 @@
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 @@
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 0be5a3b..0ce598d 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 @@
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 19c311c..c68ca4e 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -34,34 +34,6 @@
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 @@
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 @@
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 @@
}, "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 @@
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 @@
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 @@
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 @@
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 @@
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 0000000..7a2fa57
--- /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>());
+}