Finish shadow support in new reorderer/renderer

Now passes alphas and light radius, and correctly transforms light
center for layers.

Also fixes begin-frame/layer clears to be damage rect aware.

Change-Id: I3b1415cd7bf1518c510145ebebdb745f494a2542
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 4acad67..8565372 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -206,7 +206,9 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_SHARED_LIBRARIES := $(hwui_shared_libraries)
 LOCAL_STATIC_LIBRARIES := libhwui_static_null_gpu
-LOCAL_CFLAGS := $(hwui_cflags)
+LOCAL_CFLAGS := \
+        $(hwui_cflags) \
+        -DHWUI_NULL_GPU
 
 LOCAL_SRC_FILES += \
     unit_tests/CanvasStateTests.cpp \
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index 1aa291f..d2d3285 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -35,11 +35,11 @@
     LOG_ALWAYS_FATAL_IF(mRenderTarget.offscreenBuffer, "already has layer...");
 
     OffscreenBuffer* buffer = mRenderState.layerPool().get(mRenderState, width, height);
-    startRepaintLayer(buffer);
+    startRepaintLayer(buffer, Rect(width, height));
     return buffer;
 }
 
-void BakedOpRenderer::startRepaintLayer(OffscreenBuffer* offscreenBuffer) {
+void BakedOpRenderer::startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) {
     LOG_ALWAYS_FATAL_IF(mRenderTarget.offscreenBuffer, "already has layer...");
 
     mRenderTarget.offscreenBuffer = offscreenBuffer;
@@ -55,12 +55,10 @@
     LOG_ALWAYS_FATAL_IF(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE,
             "framebuffer incomplete!");
 
-    // Clear the FBO
-    mRenderState.scissor().setEnabled(false);
-    glClear(GL_COLOR_BUFFER_BIT);
-
     // Change the viewport & ortho projection
     setViewport(offscreenBuffer->viewportWidth, offscreenBuffer->viewportHeight);
+
+    clearColorBuffer(repaintRect);
 }
 
 void BakedOpRenderer::endLayer() {
@@ -74,16 +72,13 @@
     mRenderTarget.frameBufferId = -1;
 }
 
-void BakedOpRenderer::startFrame(uint32_t width, uint32_t height) {
+void BakedOpRenderer::startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) {
     mRenderState.bindFramebuffer(0);
     setViewport(width, height);
     mCaches.clearGarbage();
 
     if (!mOpaque) {
-        // TODO: partial invalidate!
-        mRenderState.scissor().setEnabled(false);
-        glClear(GL_COLOR_BUFFER_BIT);
-        mHasDrawn = true;
+        clearColorBuffer(repaintRect);
     }
 }
 
@@ -113,6 +108,20 @@
     mRenderState.blend().syncEnabled();
 }
 
+void BakedOpRenderer::clearColorBuffer(const Rect& rect) {
+    if (Rect(mRenderTarget.viewportWidth, mRenderTarget.viewportHeight).contains(rect)) {
+        // Full viewport is being cleared - disable scissor
+        mRenderState.scissor().setEnabled(false);
+    } else {
+        // Requested rect is subset of viewport - scissor to it to avoid over-clearing
+        mRenderState.scissor().setEnabled(true);
+        mRenderState.scissor().set(rect.left, mRenderTarget.viewportHeight - rect.bottom,
+                rect.getWidth(), rect.getHeight());
+    }
+    glClear(GL_COLOR_BUFFER_BIT);
+    if (!mRenderTarget.frameBufferId) mHasDrawn = true;
+}
+
 Texture* BakedOpRenderer::getTexture(const SkBitmap* bitmap) {
     Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap);
     if (!texture) {
@@ -136,7 +145,7 @@
         mRenderTarget.offscreenBuffer->region.orSelf(dirty);
     }
     mRenderState.render(glop, mRenderTarget.orthoMatrix);
-    mHasDrawn = true;
+    if (!mRenderTarget.frameBufferId) mHasDrawn = true;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -217,7 +226,7 @@
     paint.setAntiAlias(true); // want to use AlphaVertex
 
     // The caller has made sure casterAlpha > 0.
-    uint8_t ambientShadowAlpha = 128u; //TODO: mAmbientShadowAlpha;
+    uint8_t ambientShadowAlpha = renderer.getLightInfo().ambientShadowAlpha;
     if (CC_UNLIKELY(Properties::overrideAmbientShadowStrength >= 0)) {
         ambientShadowAlpha = Properties::overrideAmbientShadowStrength;
     }
@@ -227,7 +236,7 @@
                 paint, VertexBufferRenderFlags::ShadowInterp);
     }
 
-    uint8_t spotShadowAlpha = 128u; //TODO: mSpotShadowAlpha;
+    uint8_t spotShadowAlpha = renderer.getLightInfo().spotShadowAlpha;
     if (CC_UNLIKELY(Properties::overrideSpotShadowStrength >= 0)) {
         spotShadowAlpha = Properties::overrideSpotShadowStrength;
     }
@@ -240,12 +249,10 @@
 
 void BakedOpDispatcher::onShadowOp(BakedOpRenderer& renderer, const ShadowOp& op, const BakedOpState& state) {
     TessellationCache::vertexBuffer_pair_t buffers;
-    Vector3 lightCenter = { 300, 300, 300 }; // TODO!
-    float lightRadius = 150; // TODO!
-
     renderer.caches().tessellationCache.getShadowBuffers(&state.computedState.transform,
             op.localClipRect, op.casterAlpha >= 1.0f, op.casterPath,
-            &op.shadowMatrixXY, &op.shadowMatrixZ, lightCenter, lightRadius,
+            &op.shadowMatrixXY, &op.shadowMatrixZ,
+            op.lightCenter, renderer.getLightInfo().lightRadius,
             buffers);
 
     renderShadow(renderer, state, op.casterAlpha, buffers.first, buffers.second);
diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h
index d6d9cb1..29f9a6f 100644
--- a/libs/hwui/BakedOpRenderer.h
+++ b/libs/hwui/BakedOpRenderer.h
@@ -39,27 +39,39 @@
  */
 class BakedOpRenderer {
 public:
-    BakedOpRenderer(Caches& caches, RenderState& renderState, bool opaque)
+    /**
+     * Position agnostic shadow lighting info. Used with all shadow ops in scene.
+     */
+    struct LightInfo {
+        float lightRadius = 0;
+        uint8_t ambientShadowAlpha = 0;
+        uint8_t spotShadowAlpha = 0;
+    };
+
+    BakedOpRenderer(Caches& caches, RenderState& renderState, bool opaque, const LightInfo& lightInfo)
             : mRenderState(renderState)
             , mCaches(caches)
-            , mOpaque(opaque) {
+            , mOpaque(opaque)
+            , mLightInfo(lightInfo) {
     }
 
     RenderState& renderState() { return mRenderState; }
     Caches& caches() { return mCaches; }
 
-    void startFrame(uint32_t width, uint32_t height);
+    void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect);
     void endFrame();
     OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height);
-    void startRepaintLayer(OffscreenBuffer* offscreenBuffer);
+    void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect);
     void endLayer();
 
     Texture* getTexture(const SkBitmap* bitmap);
+    const LightInfo& getLightInfo() { return mLightInfo; }
 
     void renderGlop(const BakedOpState& state, const Glop& glop);
     bool didDraw() { return mHasDrawn; }
 private:
     void setViewport(uint32_t width, uint32_t height);
+    void clearColorBuffer(const Rect& clearRect);
 
     RenderState& mRenderState;
     Caches& mCaches;
@@ -75,6 +87,8 @@
         uint32_t viewportHeight = 0;
         Matrix4 orthoMatrix;
     } mRenderTarget;
+
+    const LightInfo mLightInfo;
 };
 
 /**
diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp
index 80efaed..b04f16f 100644
--- a/libs/hwui/OpReorderer.cpp
+++ b/libs/hwui/OpReorderer.cpp
@@ -18,6 +18,7 @@
 
 #include "LayerUpdateQueue.h"
 #include "RenderNode.h"
+#include "renderstate/OffscreenBufferPool.h"
 #include "utils/FatVector.h"
 #include "utils/PaintUtils.h"
 
@@ -33,8 +34,8 @@
 
 public:
     BatchBase(batchid_t batchId, BakedOpState* op, bool merging)
-        : mBatchId(batchId)
-        , mMerging(merging) {
+            : mBatchId(batchId)
+            , mMerging(merging) {
         mBounds = op->computedState.clippedBounds;
         mOps.push_back(op);
     }
@@ -207,9 +208,10 @@
 };
 
 OpReorderer::LayerReorderer::LayerReorderer(uint32_t width, uint32_t height,
-        const BeginLayerOp* beginLayerOp, RenderNode* renderNode)
+        const Rect& repaintRect, const BeginLayerOp* beginLayerOp, RenderNode* renderNode)
         : width(width)
         , height(height)
+        , repaintRect(repaintRect)
         , offscreenBuffer(renderNode ? renderNode->getLayer() : nullptr)
         , beginLayerOp(beginLayerOp)
         , renderNode(renderNode) {}
@@ -309,15 +311,19 @@
 
 OpReorderer::OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip,
         uint32_t viewportWidth, uint32_t viewportHeight,
-        const std::vector< sp<RenderNode> >& nodes)
+        const std::vector< sp<RenderNode> >& nodes, const Vector3& lightCenter)
         : mCanvasState(*this) {
     ATRACE_NAME("prepare drawing commands");
-    mLayerReorderers.emplace_back(viewportWidth, viewportHeight);
-    mLayerStack.push_back(0);
 
+    mLayerReorderers.reserve(layers.entries().size());
+    mLayerStack.reserve(layers.entries().size());
+
+    // Prepare to defer Fbo0
+    mLayerReorderers.emplace_back(viewportWidth, viewportHeight, Rect(clip));
+    mLayerStack.push_back(0);
     mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
             clip.fLeft, clip.fTop, clip.fRight, clip.fBottom,
-            Vector3());
+            lightCenter);
 
     // Render all layers to be updated, in order. Defer in reverse order, so that they'll be
     // updated in the order they're passed in (mLayerReorderers are issued to Renderer in reverse)
@@ -325,7 +331,8 @@
         RenderNode* layerNode = layers.entries()[i].renderNode;
         const Rect& layerDamage = layers.entries()[i].damage;
 
-        saveForLayer(layerNode->getWidth(), layerNode->getHeight(), nullptr, layerNode);
+        saveForLayer(layerNode->getWidth(), layerNode->getHeight(),
+                layerDamage, nullptr, layerNode);
         mCanvasState.writableSnapshot()->setClip(
                 layerDamage.left, layerDamage.top, layerDamage.right, layerDamage.bottom);
 
@@ -345,14 +352,17 @@
     }
 }
 
-OpReorderer::OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList)
+OpReorderer::OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList,
+        const Vector3& lightCenter)
         : mCanvasState(*this) {
     ATRACE_NAME("prepare drawing commands");
-    mLayerReorderers.emplace_back(viewportWidth, viewportHeight);
+    // Prepare to defer Fbo0
+    mLayerReorderers.emplace_back(viewportWidth, viewportHeight,
+            Rect(viewportWidth, viewportHeight));
     mLayerStack.push_back(0);
-
     mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
-            0, 0, viewportWidth, viewportHeight, Vector3());
+            0, 0, viewportWidth, viewportHeight, lightCenter);
+
     deferImpl(displayList);
 }
 
@@ -508,7 +518,8 @@
     }
 
     ShadowOp* shadowOp = new (mAllocator) ShadowOp(casterNodeOp, casterAlpha, casterPath,
-            mCanvasState.getLocalClipBounds());
+            mCanvasState.getLocalClipBounds(),
+            mCanvasState.currentSnapshot()->getRelativeLightCenter());
     BakedOpState* bakedOpState = BakedOpState::tryShadowOpConstruct(
             mAllocator, *mCanvasState.currentSnapshot(), shadowOp);
     if (CC_LIKELY(bakedOpState)) {
@@ -589,17 +600,36 @@
     currentLayer().deferUnmergeableOp(mAllocator, bakedStateOp, OpBatchType::Vertices);
 }
 
-void OpReorderer::saveForLayer(uint32_t layerWidth, uint32_t layerHeight,
+void OpReorderer::saveForLayer(uint32_t layerWidth, uint32_t layerHeight, const Rect& repaintRect,
         const BeginLayerOp* beginLayerOp, RenderNode* renderNode) {
 
+    auto previous = mCanvasState.currentSnapshot();
     mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
     mCanvasState.writableSnapshot()->transform->loadIdentity();
     mCanvasState.writableSnapshot()->initializeViewport(layerWidth, layerHeight);
     mCanvasState.writableSnapshot()->roundRectClipState = nullptr;
 
+    Vector3 lightCenter = previous->getRelativeLightCenter();
+    if (renderNode) {
+        Matrix4& inverse = renderNode->getLayer()->inverseTransformInWindow;
+        inverse.mapPoint3d(lightCenter);
+    } else {
+        // Combine all transforms used to present saveLayer content:
+        // parent content transform * canvas transform * bounds offset
+        Matrix4 contentTransform(*previous->transform);
+        contentTransform.multiply(beginLayerOp->localMatrix);
+        contentTransform.translate(beginLayerOp->unmappedBounds.left, beginLayerOp->unmappedBounds.top);
+
+        // inverse the total transform, to map light center into layer-relative space
+        Matrix4 inverse;
+        inverse.loadInverse(contentTransform);
+        inverse.mapPoint3d(lightCenter);
+    }
+    mCanvasState.writableSnapshot()->setRelativeLightCenter(lightCenter);
+
     // create a new layer, and push its index on the stack
     mLayerStack.push_back(mLayerReorderers.size());
-    mLayerReorderers.emplace_back(layerWidth, layerHeight, beginLayerOp, renderNode);
+    mLayerReorderers.emplace_back(layerWidth, layerHeight, repaintRect, beginLayerOp, renderNode);
 }
 
 void OpReorderer::restoreForLayer() {
@@ -612,7 +642,7 @@
 void OpReorderer::onBeginLayerOp(const BeginLayerOp& op) {
     const uint32_t layerWidth = (uint32_t) op.unmappedBounds.getWidth();
     const uint32_t layerHeight = (uint32_t) op.unmappedBounds.getHeight();
-    saveForLayer(layerWidth, layerHeight, &op, nullptr);
+    saveForLayer(layerWidth, layerHeight, Rect(layerWidth, layerHeight), &op, nullptr);
 }
 
 void OpReorderer::onEndLayerOp(const EndLayerOp& /* ignored */) {
diff --git a/libs/hwui/OpReorderer.h b/libs/hwui/OpReorderer.h
index 2c30f0d..09d5cbc 100644
--- a/libs/hwui/OpReorderer.h
+++ b/libs/hwui/OpReorderer.h
@@ -67,13 +67,13 @@
     class LayerReorderer {
     public:
         // Create LayerReorderer for Fbo0
-        LayerReorderer(uint32_t width, uint32_t height)
-                : LayerReorderer(width, height, nullptr, nullptr) {};
+        LayerReorderer(uint32_t width, uint32_t height, const Rect& repaintRect)
+                : LayerReorderer(width, height, repaintRect, nullptr, nullptr) {};
 
         // Create LayerReorderer for an offscreen layer, where beginLayerOp is present for a
         // saveLayer, renderNode is present for a HW layer.
         LayerReorderer(uint32_t width, uint32_t height,
-                const BeginLayerOp* beginLayerOp, RenderNode* renderNode);
+                const Rect& repaintRect, const BeginLayerOp* beginLayerOp, RenderNode* renderNode);
 
         // iterate back toward target to see if anything drawn since should overlap the new op
         // if no target, merging ops still iterate to find similar batch to insert after
@@ -101,6 +101,7 @@
 
         const uint32_t width;
         const uint32_t height;
+        const Rect repaintRect;
         OffscreenBuffer* offscreenBuffer;
         const BeginLayerOp* beginLayerOp;
         const RenderNode* renderNode;
@@ -116,14 +117,15 @@
 
         // Maps batch ids to the most recent *non-merging* batch of that id
         OpBatch* mBatchLookup[OpBatchType::Count] = { nullptr };
-
     };
+
 public:
     OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip,
             uint32_t viewportWidth, uint32_t viewportHeight,
-            const std::vector< sp<RenderNode> >& nodes);
+            const std::vector< sp<RenderNode> >& nodes, const Vector3& lightCenter);
 
-    OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList);
+    OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList,
+            const Vector3& lightCenter);
 
     virtual ~OpReorderer() {}
 
@@ -153,7 +155,7 @@
             LayerReorderer& layer = mLayerReorderers[i];
             if (layer.renderNode) {
                 // cached HW layer - can't skip layer if empty
-                renderer.startRepaintLayer(layer.offscreenBuffer);
+                renderer.startRepaintLayer(layer.offscreenBuffer, layer.repaintRect);
                 layer.replayBakedOpsImpl((void*)&renderer, receivers);
                 renderer.endLayer();
             } else if (!layer.empty()) { // save layer - skip entire layer if empty
@@ -164,7 +166,7 @@
         }
 
         const LayerReorderer& fbo0 = mLayerReorderers[0];
-        renderer.startFrame(fbo0.width, fbo0.height);
+        renderer.startFrame(fbo0.width, fbo0.height, fbo0.repaintRect);
         fbo0.replayBakedOpsImpl((void*)&renderer, receivers);
         renderer.endFrame();
     }
@@ -188,7 +190,7 @@
         Positive
     };
     void saveForLayer(uint32_t layerWidth, uint32_t layerHeight,
-            const BeginLayerOp* beginLayerOp, RenderNode* renderNode);
+            const Rect& repaintRect, const BeginLayerOp* beginLayerOp, RenderNode* renderNode);
     void restoreForLayer();
 
     LayerReorderer& currentLayer() { return mLayerReorderers[mLayerStack.back()]; }
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
index bb7a0a7c..ef05367 100644
--- a/libs/hwui/RecordedOp.h
+++ b/libs/hwui/RecordedOp.h
@@ -17,10 +17,11 @@
 #ifndef ANDROID_HWUI_RECORDED_OP_H
 #define ANDROID_HWUI_RECORDED_OP_H
 
-#include "utils/LinearAllocator.h"
-#include "Rect.h"
 #include "Matrix.h"
+#include "Rect.h"
 #include "RenderNode.h"
+#include "utils/LinearAllocator.h"
+#include "Vector.h"
 
 #include "SkXfermode.h"
 
@@ -116,15 +117,17 @@
  * Uses invalid/empty bounds and matrix since ShadowOp bounds aren't known at defer time,
  * and are resolved dynamically, and transform isn't needed.
  *
- * State construction handles these properties specially.
+ * State construction handles these properties specially, ignoring matrix/bounds.
  */
 struct ShadowOp : RecordedOp {
-    ShadowOp(const RenderNodeOp& casterOp, float casterAlpha, const SkPath* casterPath, const Rect& clipRect)
+    ShadowOp(const RenderNodeOp& casterOp, float casterAlpha, const SkPath* casterPath,
+            const Rect& clipRect, const Vector3& lightCenter)
             : RecordedOp(RecordedOpId::ShadowOp, Rect(), Matrix4::identity(), clipRect, nullptr)
             , shadowMatrixXY(casterOp.localMatrix)
             , shadowMatrixZ(casterOp.localMatrix)
             , casterAlpha(casterAlpha)
-            , casterPath(casterPath) {
+            , casterPath(casterPath)
+            , lightCenter(lightCenter) {
         const RenderNode& node = *casterOp.renderNode;
         node.applyViewPropertyTransforms(shadowMatrixXY, false);
         node.applyViewPropertyTransforms(shadowMatrixZ, true);
@@ -133,6 +136,7 @@
     Matrix4 shadowMatrixZ;
     const float casterAlpha;
     const SkPath* casterPath;
+    const Vector3 lightCenter;
 };
 
 struct SimpleRectsOp : RecordedOp { // Filled, no AA (TODO: better name?)
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index e988555..6ab253c 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -156,7 +156,7 @@
 
     snapshot.flags |= Snapshot::kFlagFboTarget | Snapshot::kFlagIsFboLayer;
     snapshot.initializeViewport(untransformedBounds.getWidth(), untransformedBounds.getHeight());
-    snapshot.resetTransform(-untransformedBounds.left, -untransformedBounds.top, 0.0f);
+    snapshot.transform->loadTranslate(-untransformedBounds.left, -untransformedBounds.top, 0.0f);
 
     Rect clip = layerBounds;
     clip.translate(-untransformedBounds.left, -untransformedBounds.top);
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index e177f9a..2713f46 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -326,15 +326,11 @@
         return;
     }
 
-    if (transformUpdateNeeded) {
+    if (transformUpdateNeeded && mLayer) {
         // update the transform in window of the layer to reset its origin wrt light source position
         Matrix4 windowTransform;
         info.damageAccumulator->computeCurrentTransform(&windowTransform);
-#if HWUI_NEW_OPS
-        // TODO: update layer transform (perhaps as part of enqueueLayerWithDamage)
-#else
         mLayer->setWindowTransform(windowTransform);
-#endif
     }
 
 #if HWUI_NEW_OPS
diff --git a/libs/hwui/Snapshot.cpp b/libs/hwui/Snapshot.cpp
index 0a58f4b..2f535bb 100644
--- a/libs/hwui/Snapshot.cpp
+++ b/libs/hwui/Snapshot.cpp
@@ -130,6 +130,9 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 void Snapshot::resetTransform(float x, float y, float z) {
+#if HWUI_NEW_OPS
+    LOG_ALWAYS_FATAL("not supported - light center managed differently");
+#else
     // before resetting, map current light pos with inverse of current transform
     Vector3 center = mRelativeLightCenter;
     mat4 inverse;
@@ -139,6 +142,7 @@
 
     transform = &mTransformRoot;
     transform->loadTranslate(x, y, z);
+#endif
 }
 
 void Snapshot::buildScreenSpaceTransform(Matrix4* outTransform) const {
diff --git a/libs/hwui/microbench/OpReordererBench.cpp b/libs/hwui/microbench/OpReordererBench.cpp
index 7b8d0e5..b24858e 100644
--- a/libs/hwui/microbench/OpReordererBench.cpp
+++ b/libs/hwui/microbench/OpReordererBench.cpp
@@ -48,7 +48,7 @@
 void BM_OpReorderer_defer::Run(int iters) {
     StartBenchmarkTiming();
     for (int i = 0; i < iters; i++) {
-        OpReorderer reorderer(200, 200, *sReorderingDisplayList);
+        OpReorderer reorderer(200, 200, *sReorderingDisplayList, (Vector3) { 100, 100, 100 });
         MicroBench::DoNotOptimize(&reorderer);
     }
     StopBenchmarkTiming();
@@ -59,11 +59,13 @@
     TestUtils::runOnRenderThread([this, iters](renderthread::RenderThread& thread) {
         RenderState& renderState = thread.renderState();
         Caches& caches = Caches::getInstance();
+        BakedOpRenderer::LightInfo lightInfo = { 50.0f, 128, 128 };
+
         StartBenchmarkTiming();
         for (int i = 0; i < iters; i++) {
-            OpReorderer reorderer(200, 200, *sReorderingDisplayList);
+            OpReorderer reorderer(200, 200, *sReorderingDisplayList, (Vector3) { 100, 100, 100 });
 
-            BakedOpRenderer renderer(caches, renderState, true);
+            BakedOpRenderer renderer(caches, renderState, true, lightInfo);
             reorderer.replayBakedOps<BakedOpDispatcher>(renderer);
             MicroBench::DoNotOptimize(&renderer);
         }
diff --git a/libs/hwui/renderstate/OffscreenBufferPool.h b/libs/hwui/renderstate/OffscreenBufferPool.h
index f0fd82d..fac6c35 100644
--- a/libs/hwui/renderstate/OffscreenBufferPool.h
+++ b/libs/hwui/renderstate/OffscreenBufferPool.h
@@ -34,6 +34,11 @@
  * Lightweight alternative to Layer. Owns the persistent state of an offscreen render target, and
  * encompasses enough information to draw it back on screen (minus paint properties, which are held
  * by LayerOp).
+ *
+ * Has two distinct sizes - viewportWidth/viewportHeight describe content area,
+ * texture.width/.height are actual allocated texture size. Texture will tend to be larger than the
+ * viewport bounds, since textures are always allocated with width / height as a multiple of 64, for
+ * the purpose of improving reuse.
  */
 class OffscreenBuffer {
 public:
@@ -44,11 +49,17 @@
     // must be called prior to rendering, to construct/update vertex buffer
     void updateMeshFromRegion();
 
+    // Set by RenderNode for HW layers, TODO for clipped saveLayers
+    void setWindowTransform(const Matrix4& transform) {
+        inverseTransformInWindow.loadInverse(transform);
+    }
+
     static uint32_t computeIdealDimension(uint32_t dimension);
 
     uint32_t getSizeInBytes() { return texture.width * texture.height * 4; }
 
     RenderState& renderState;
+
     uint32_t viewportWidth;
     uint32_t viewportHeight;
     Texture texture;
@@ -56,6 +67,10 @@
     // Portion of layer that has been drawn to. Used to minimize drawing area when
     // drawing back to screen / parent FBO.
     Region region;
+
+    Matrix4 inverseTransformInWindow;
+
+    // vbo / size of mesh
     GLsizei elementCount = 0;
     GLuint vbo = 0;
 };
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index f094b2d..89cadea 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -31,7 +31,6 @@
 #include "utils/TimeUtils.h"
 
 #if HWUI_NEW_OPS
-#include "BakedOpRenderer.h"
 #include "OpReorderer.h"
 #endif
 
@@ -150,13 +149,23 @@
 // TODO: don't pass viewport size, it's automatic via EGL
 void CanvasContext::setup(int width, int height, float lightRadius,
         uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) {
+#if HWUI_NEW_OPS
+    mLightInfo.lightRadius = lightRadius;
+    mLightInfo.ambientShadowAlpha = ambientShadowAlpha;
+    mLightInfo.spotShadowAlpha = spotShadowAlpha;
+#else
     if (!mCanvas) return;
     mCanvas->initLight(lightRadius, ambientShadowAlpha, spotShadowAlpha);
+#endif
 }
 
 void CanvasContext::setLightCenter(const Vector3& lightCenter) {
+#if HWUI_NEW_OPS
+    mLightCenter = lightCenter;
+#else
     if (!mCanvas) return;
     mCanvas->setLightCenter(lightCenter);
+#endif
 }
 
 void CanvasContext::setOpaque(bool opaque) {
@@ -340,9 +349,11 @@
     mEglManager.damageFrame(frame, dirty);
 
 #if HWUI_NEW_OPS
-    OpReorderer reorderer(mLayerUpdateQueue, dirty, frame.width(), frame.height(), mRenderNodes);
+    OpReorderer reorderer(mLayerUpdateQueue, dirty, frame.width(), frame.height(),
+            mRenderNodes, mLightCenter);
     mLayerUpdateQueue.clear();
-    BakedOpRenderer renderer(Caches::getInstance(), mRenderThread.renderState(), mOpaque);
+    BakedOpRenderer renderer(Caches::getInstance(), mRenderThread.renderState(),
+            mOpaque, mLightInfo);
     // TODO: profiler().draw(mCanvas);
     reorderer.replayBakedOps<BakedOpDispatcher>(renderer);
 
@@ -576,8 +587,12 @@
     // purposes when the frame is actually drawn
     node->setPropertyFieldsDirty(RenderNode::GENERIC);
 
+#if HWUI_NEW_OPS
+    LOG_ALWAYS_FATAL("unsupported");
+#else
     mCanvas->markLayersAsBuildLayers();
     mCanvas->flushLayerUpdates();
+#endif
 
     node->incStrong(nullptr);
     mPrefetechedLayers.insert(node);
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index d656014..c3cfc94 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -27,6 +27,10 @@
 #include "renderthread/RenderTask.h"
 #include "renderthread/RenderThread.h"
 
+#if HWUI_NEW_OPS
+#include "BakedOpRenderer.h"
+#endif
+
 #include <cutils/compiler.h>
 #include <EGL/egl.h>
 #include <SkBitmap.h>
@@ -165,6 +169,11 @@
 
     bool mOpaque;
     OpenGLRenderer* mCanvas = nullptr;
+#if HWUI_NEW_OPS
+    BakedOpRenderer::LightInfo mLightInfo;
+    Vector3 mLightCenter = { 0, 0, 0 };
+#endif
+
     bool mHaveNewSurface = false;
     DamageAccumulator mDamageAccumulator;
     LayerUpdateQueue mLayerUpdateQueue;
diff --git a/libs/hwui/unit_tests/OpReordererTests.cpp b/libs/hwui/unit_tests/OpReordererTests.cpp
index 07080a2..a8c9bba 100644
--- a/libs/hwui/unit_tests/OpReordererTests.cpp
+++ b/libs/hwui/unit_tests/OpReordererTests.cpp
@@ -29,6 +29,14 @@
 namespace uirenderer {
 
 LayerUpdateQueue sEmptyLayerUpdateQueue;
+Vector3 sLightCenter = {100, 100, 100};
+
+static std::vector<sp<RenderNode>> createSyncedNodeList(sp<RenderNode>& node) {
+    TestUtils::syncHierarchyPropertiesAndDisplayList(node);
+    std::vector<sp<RenderNode>> vec;
+    vec.emplace_back(node);
+    return vec;
+}
 
 /**
  * Virtual class implemented by each test to redirect static operation / state transitions to
@@ -48,13 +56,13 @@
         ADD_FAILURE() << "Layer creation not expected in this test";
         return nullptr;
     }
-    virtual void startRepaintLayer(OffscreenBuffer*) {
+    virtual void startRepaintLayer(OffscreenBuffer*, const Rect& repaintRect) {
         ADD_FAILURE() << "Layer repaint not expected in this test";
     }
     virtual void endLayer() {
         ADD_FAILURE() << "Layer updates not expected in this test";
     }
-    virtual void startFrame(uint32_t width, uint32_t height) {}
+    virtual void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) {}
     virtual void endFrame() {}
 
     // define virtual defaults for direct
@@ -87,7 +95,7 @@
 TEST(OpReorderer, simple) {
     class SimpleTestRenderer : public TestRendererBase {
     public:
-        void startFrame(uint32_t width, uint32_t height) override {
+        void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
             EXPECT_EQ(0, mIndex++);
             EXPECT_EQ(100u, width);
             EXPECT_EQ(200u, height);
@@ -108,7 +116,7 @@
         canvas.drawRect(0, 0, 100, 200, SkPaint());
         canvas.drawBitmap(bitmap, 10, 10, nullptr);
     });
-    OpReorderer reorderer(100, 200, *dl);
+    OpReorderer reorderer(100, 200, *dl, sLightCenter);
 
     SimpleTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -122,7 +130,7 @@
         canvas.drawRect(0, 0, 400, 400, SkPaint());
         canvas.restore();
     });
-    OpReorderer reorderer(200, 200, *dl);
+    OpReorderer reorderer(200, 200, *dl, sLightCenter);
 
     FailRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -154,7 +162,7 @@
         canvas.restore();
     });
 
-    OpReorderer reorderer(200, 200, *dl);
+    OpReorderer reorderer(200, 200, *dl, sLightCenter);
 
     SimpleBatchingTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -198,13 +206,8 @@
         canvas.restore();
     });
 
-    TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
-
-    std::vector< sp<RenderNode> > nodes;
-    nodes.push_back(parent.get());
-
-    OpReorderer reorderer(sEmptyLayerUpdateQueue,
-            SkRect::MakeWH(200, 200), 200, 200, nodes);
+    OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+            createSyncedNodeList(parent), sLightCenter);
 
     RenderNodeTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -225,13 +228,10 @@
         SkBitmap bitmap = TestUtils::createSkBitmap(200, 200);
         canvas.drawBitmap(bitmap, 0, 0, nullptr);
     });
-    TestUtils::syncHierarchyPropertiesAndDisplayList(node);
-    std::vector< sp<RenderNode> > nodes;
-    nodes.push_back(node.get());
 
     OpReorderer reorderer(sEmptyLayerUpdateQueue,
             SkRect::MakeLTRB(10, 20, 30, 40), // clip to small area, should see in receiver
-            200, 200, nodes);
+            200, 200, createSyncedNodeList(node), sLightCenter);
 
     ClippedTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -273,7 +273,7 @@
         canvas.restore();
     });
 
-    OpReorderer reorderer(200, 200, *dl);
+    OpReorderer reorderer(200, 200, *dl, sLightCenter);
 
     SaveLayerSimpleTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -305,7 +305,7 @@
             int index = mIndex++;
             EXPECT_TRUE(index == 2 || index == 6);
         }
-        void startFrame(uint32_t width, uint32_t height) override {
+        void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
             EXPECT_EQ(7, mIndex++);
         }
         void endFrame() override {
@@ -344,7 +344,7 @@
         canvas.restore();
     });
 
-    OpReorderer reorderer(800, 800, *dl);
+    OpReorderer reorderer(800, 800, *dl, sLightCenter);
 
     SaveLayerNestedTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -363,19 +363,21 @@
         canvas.restore();
         canvas.restore();
     });
-    OpReorderer reorderer(200, 200, *dl);
+    OpReorderer reorderer(200, 200, *dl, sLightCenter);
 
     FailRenderer renderer;
     // should see no ops, even within the layer, since the layer should be rejected
     reorderer.replayBakedOps<TestDispatcher>(renderer);
 }
 
-TEST(OpReorderer, hwLayerSimple) {
+RENDERTHREAD_TEST(OpReorderer, hwLayerSimple) {
     class HwLayerSimpleTestRenderer : public TestRendererBase {
     public:
-        void startRepaintLayer(OffscreenBuffer* offscreenBuffer) override {
+        void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override {
             EXPECT_EQ(0, mIndex++);
-            EXPECT_EQ(offscreenBuffer, (OffscreenBuffer*) 0x0124);
+            EXPECT_EQ(100u, offscreenBuffer->viewportWidth);
+            EXPECT_EQ(100u, offscreenBuffer->viewportHeight);
+            EXPECT_EQ(Rect(25, 25, 75, 75), repaintRect);
         }
         void onRectOp(const RectOp& op, const BakedOpState& state) override {
             EXPECT_EQ(1, mIndex++);
@@ -389,7 +391,7 @@
         void endLayer() override {
             EXPECT_EQ(2, mIndex++);
         }
-        void startFrame(uint32_t width, uint32_t height) override {
+        void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
             EXPECT_EQ(3, mIndex++);
         }
         void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
@@ -405,29 +407,29 @@
         paint.setColor(SK_ColorWHITE);
         canvas.drawRect(0, 0, 100, 100, paint);
     }, TestUtils::getHwLayerSetupCallback());
-    OffscreenBuffer** bufferHandle = node->getLayerHandle();
-    *bufferHandle = (OffscreenBuffer*) 0x0124;
+    OffscreenBuffer** layerHandle = node->getLayerHandle();
 
-    TestUtils::syncHierarchyPropertiesAndDisplayList(node);
+    // create RenderNode's layer here in same way prepareTree would
+    OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 100, 100);
+    *layerHandle = &layer;
 
-    std::vector< sp<RenderNode> > nodes;
-    nodes.push_back(node.get());
+    auto syncedNodeList = createSyncedNodeList(node);
 
     // only enqueue partial damage
-    LayerUpdateQueue layerUpdateQueue;
+    LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
     layerUpdateQueue.enqueueLayerWithDamage(node.get(), Rect(25, 25, 75, 75));
 
-    OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, nodes);
-
+    OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+            syncedNodeList, sLightCenter);
     HwLayerSimpleTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(6, renderer.getIndex());
 
     // clean up layer pointer, so we can safely destruct RenderNode
-    *bufferHandle = nullptr;
+    *layerHandle = nullptr;
 }
 
-TEST(OpReorderer, hwLayerComplex) {
+RENDERTHREAD_TEST(OpReorderer, hwLayerComplex) {
     /* parentLayer { greyRect, saveLayer { childLayer { whiteRect } } } will play back as:
      * - startRepaintLayer(child), rect(grey), endLayer
      * - startTemporaryLayer, drawLayer(child), endLayer
@@ -440,14 +442,16 @@
             EXPECT_EQ(3, mIndex++); // savelayer first
             return (OffscreenBuffer*)0xabcd;
         }
-        void startRepaintLayer(OffscreenBuffer* offscreenBuffer) override {
+        void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override {
             int index = mIndex++;
             if (index == 0) {
                 // starting inner layer
-                EXPECT_EQ((OffscreenBuffer*)0x4567, offscreenBuffer);
+                EXPECT_EQ(100u, offscreenBuffer->viewportWidth);
+                EXPECT_EQ(100u, offscreenBuffer->viewportHeight);
             } else if (index == 6) {
                 // starting outer layer
-                EXPECT_EQ((OffscreenBuffer*)0x0123, offscreenBuffer);
+                EXPECT_EQ(200u, offscreenBuffer->viewportWidth);
+                EXPECT_EQ(200u, offscreenBuffer->viewportHeight);
             } else { ADD_FAILURE(); }
         }
         void onRectOp(const RectOp& op, const BakedOpState& state) override {
@@ -464,17 +468,20 @@
             int index = mIndex++;
             EXPECT_TRUE(index == 2 || index == 5 || index == 9);
         }
-        void startFrame(uint32_t width, uint32_t height) override {
+        void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
             EXPECT_EQ(10, mIndex++);
         }
         void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+            OffscreenBuffer* layer = *op.layerHandle;
             int index = mIndex++;
             if (index == 4) {
-                EXPECT_EQ((OffscreenBuffer*)0x4567, *op.layerHandle);
+                EXPECT_EQ(100u, layer->viewportWidth);
+                EXPECT_EQ(100u, layer->viewportHeight);
             } else if (index == 8) {
                 EXPECT_EQ((OffscreenBuffer*)0xabcd, *op.layerHandle);
             } else if (index == 11) {
-                EXPECT_EQ((OffscreenBuffer*)0x0123, *op.layerHandle);
+                EXPECT_EQ(200u, layer->viewportWidth);
+                EXPECT_EQ(200u, layer->viewportHeight);
             } else { ADD_FAILURE(); }
         }
         void endFrame() override {
@@ -488,7 +495,8 @@
         paint.setColor(SK_ColorWHITE);
         canvas.drawRect(0, 0, 100, 100, paint);
     }, TestUtils::getHwLayerSetupCallback());
-    *(child->getLayerHandle()) = (OffscreenBuffer*) 0x4567;
+    OffscreenBuffer childLayer(renderThread.renderState(), Caches::getInstance(), 100, 100);
+    *(child->getLayerHandle()) = &childLayer;
 
     RenderNode* childPtr = child.get();
     auto parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200,
@@ -501,18 +509,17 @@
         canvas.drawRenderNode(childPtr);
         canvas.restore();
     }, TestUtils::getHwLayerSetupCallback());
-    *(parent->getLayerHandle()) = (OffscreenBuffer*) 0x0123;
+    OffscreenBuffer parentLayer(renderThread.renderState(), Caches::getInstance(), 200, 200);
+    *(parent->getLayerHandle()) = &parentLayer;
 
-    TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
+    auto syncedList = createSyncedNodeList(parent);
 
-    std::vector< sp<RenderNode> > nodes;
-    nodes.push_back(parent.get());
-
-    LayerUpdateQueue layerUpdateQueue;
+    LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
     layerUpdateQueue.enqueueLayerWithDamage(child.get(), Rect(100, 100));
     layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(200, 200));
 
-    OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, nodes);
+    OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+            syncedList, sLightCenter);
 
     HwLayerComplexTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -561,58 +568,184 @@
         drawOrderedRect(&canvas, 8);
         drawOrderedNode(&canvas, 9, -10.0f); // in reorder=false at this point, so played inorder
     });
-    TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
-
-    std::vector< sp<RenderNode> > nodes;
-    nodes.push_back(parent.get());
-    OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100, nodes);
+    OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100,
+            createSyncedNodeList(parent), sLightCenter);
 
     ZReorderTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(10, renderer.getIndex());
 };
 
+// creates a 100x100 shadow casting node with provided translationZ
+static sp<RenderNode> createWhiteRectShadowCaster(float translationZ) {
+    return TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+            [](RecordingCanvas& canvas) {
+        SkPaint paint;
+        paint.setColor(SK_ColorWHITE);
+        canvas.drawRect(0, 0, 100, 100, paint);
+    }, [translationZ] (RenderProperties& properties) {
+        properties.setTranslationZ(translationZ);
+        properties.mutableOutline().setRoundRect(0, 0, 100, 100, 0.0f, 1.0f);
+        return RenderNode::GENERIC | RenderNode::TRANSLATION_Z;
+    });
+}
+
 TEST(OpReorderer, shadow) {
     class ShadowTestRenderer : public TestRendererBase {
     public:
         void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
             EXPECT_EQ(0, mIndex++);
+            EXPECT_FLOAT_EQ(1.0f, op.casterAlpha);
+            EXPECT_TRUE(op.casterPath->isRect(nullptr));
+            EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), op.shadowMatrixXY);
+
+            Matrix4 expectedZ;
+            expectedZ.loadTranslate(0, 0, 5);
+            EXPECT_MATRIX_APPROX_EQ(expectedZ, op.shadowMatrixZ);
         }
         void onRectOp(const RectOp& op, const BakedOpState& state) override {
             EXPECT_EQ(1, mIndex++);
         }
     };
 
-    sp<RenderNode> caster = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
-            [](RecordingCanvas& canvas) {
-        SkPaint paint;
-        paint.setColor(SK_ColorWHITE);
-        canvas.drawRect(0, 0, 100, 100, paint);
-    }, [] (RenderProperties& properties) {
-        properties.setTranslationZ(5.0f);
-        properties.mutableOutline().setRoundRect(0, 0, 100, 100, 5, 1.0f);
-        return RenderNode::GENERIC | RenderNode::TRANSLATION_Z;
-    });
     sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200,
-            [&caster] (RecordingCanvas& canvas) {
+            [] (RecordingCanvas& canvas) {
         canvas.insertReorderBarrier(true);
-        canvas.drawRenderNode(caster.get());
+        canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
     });
 
-    TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
-
-    std::vector< sp<RenderNode> > nodes;
-    nodes.push_back(parent.get());
-
-    OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, nodes);
+    OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+            createSyncedNodeList(parent), sLightCenter);
 
     ShadowTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
     EXPECT_EQ(2, renderer.getIndex());
 }
 
-static void testProperty(
-        TestUtils::PropSetupCallback propSetupCallback,
+TEST(OpReorderer, shadowSaveLayer) {
+    class ShadowSaveLayerTestRenderer : public TestRendererBase {
+    public:
+        OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override {
+            EXPECT_EQ(0, mIndex++);
+            return nullptr;
+        }
+        void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(1, mIndex++);
+            EXPECT_FLOAT_EQ(50, op.lightCenter.x);
+            EXPECT_FLOAT_EQ(40, op.lightCenter.y);
+        }
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(2, mIndex++);
+        }
+        void endLayer() override {
+            EXPECT_EQ(3, mIndex++);
+        }
+        void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(4, mIndex++);
+        }
+    };
+
+    sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200,
+            [] (RecordingCanvas& canvas) {
+        // save/restore outside of reorderBarrier, so they don't get moved out of place
+        canvas.translate(20, 10);
+        int count = canvas.saveLayerAlpha(30, 50, 130, 150, 128, SkCanvas::kClipToLayer_SaveFlag);
+        canvas.insertReorderBarrier(true);
+        canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
+        canvas.insertReorderBarrier(false);
+        canvas.restoreToCount(count);
+    });
+
+    OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+            createSyncedNodeList(parent), (Vector3) { 100, 100, 100 });
+
+    ShadowSaveLayerTestRenderer renderer;
+    reorderer.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(5, renderer.getIndex());
+}
+
+RENDERTHREAD_TEST(OpReorderer, shadowHwLayer) {
+    class ShadowHwLayerTestRenderer : public TestRendererBase {
+    public:
+        void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override {
+            EXPECT_EQ(0, mIndex++);
+        }
+        void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(1, mIndex++);
+            EXPECT_FLOAT_EQ(50, op.lightCenter.x);
+            EXPECT_FLOAT_EQ(40, op.lightCenter.y);
+        }
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(2, mIndex++);
+        }
+        void endLayer() override {
+            EXPECT_EQ(3, mIndex++);
+        }
+        void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(4, mIndex++);
+        }
+    };
+
+    sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(50, 60, 150, 160,
+            [] (RecordingCanvas& canvas) {
+        canvas.insertReorderBarrier(true);
+        canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+        canvas.translate(20, 10);
+        canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
+        canvas.restore();
+    }, TestUtils::getHwLayerSetupCallback());
+    OffscreenBuffer** layerHandle = parent->getLayerHandle();
+
+    // create RenderNode's layer here in same way prepareTree would, setting windowTransform
+    OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 100, 100);
+    Matrix4 windowTransform;
+    windowTransform.loadTranslate(50, 60, 0); // total transform of layer's origin
+    layer.setWindowTransform(windowTransform);
+    *layerHandle = &layer;
+
+    auto syncedList = createSyncedNodeList(parent);
+    LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
+    layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(100, 100));
+    OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+            syncedList, (Vector3) { 100, 100, 100 });
+
+    ShadowHwLayerTestRenderer renderer;
+    reorderer.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(5, renderer.getIndex());
+
+    // clean up layer pointer, so we can safely destruct RenderNode
+    *layerHandle = nullptr;
+}
+
+TEST(OpReorderer, shadowLayering) {
+    class ShadowLayeringTestRenderer : public TestRendererBase {
+    public:
+        void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
+            int index = mIndex++;
+            EXPECT_TRUE(index == 0 || index == 1);
+        }
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            int index = mIndex++;
+            EXPECT_TRUE(index == 2 || index == 3);
+        }
+    };
+    sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200,
+            [] (RecordingCanvas& canvas) {
+        canvas.insertReorderBarrier(true);
+        canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
+        canvas.drawRenderNode(createWhiteRectShadowCaster(5.0001f).get());
+    });
+
+    OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+            createSyncedNodeList(parent), sLightCenter);
+
+    ShadowLayeringTestRenderer renderer;
+    reorderer.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(4, renderer.getIndex());
+}
+
+
+static void testProperty(TestUtils::PropSetupCallback propSetupCallback,
         std::function<void(const RectOp&, const BakedOpState&)> opValidateCallback) {
     class PropertyTestRenderer : public TestRendererBase {
     public:
@@ -630,13 +763,9 @@
         paint.setColor(SK_ColorWHITE);
         canvas.drawRect(0, 0, 100, 100, paint);
     }, propSetupCallback);
-    TestUtils::syncHierarchyPropertiesAndDisplayList(node);
 
-    std::vector< sp<RenderNode> > nodes;
-    nodes.push_back(node.get());
-
-    OpReorderer reorderer(sEmptyLayerUpdateQueue,
-            SkRect::MakeWH(100, 100), 200, 200, nodes);
+    OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 200, 200,
+            createSyncedNodeList(node), sLightCenter);
 
     PropertyTestRenderer renderer(opValidateCallback);
     reorderer.replayBakedOps<TestDispatcher>(renderer);
diff --git a/libs/hwui/unit_tests/TestUtils.h b/libs/hwui/unit_tests/TestUtils.h
index efa28ae..38bafd5 100644
--- a/libs/hwui/unit_tests/TestUtils.h
+++ b/libs/hwui/unit_tests/TestUtils.h
@@ -45,6 +45,20 @@
             && MathUtils::areEqual(a.right, b.right) \
             && MathUtils::areEqual(a.bottom, b.bottom));
 
+/**
+ * Like gtest's TEST, but runs on the RenderThread, and 'renderThread' is passed, in top level scope
+ * (for e.g. accessing its RenderState)
+ */
+#define RENDERTHREAD_TEST(test_case_name, test_name) \
+    class test_case_name##_##test_name##_RenderThreadTest { \
+    public: \
+        static void doTheThing(renderthread::RenderThread& renderThread); \
+    }; \
+    TEST(test_case_name, test_name) { \
+        TestUtils::runOnRenderThread(test_case_name##_##test_name##_RenderThreadTest::doTheThing); \
+    }; \
+    void test_case_name##_##test_name##_RenderThreadTest::doTheThing(renderthread::RenderThread& renderThread)
+
 class TestUtils {
 public:
     class SignalingDtor {