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>());
+}