diff --git a/libraries/gpu/src/gpu/Batch.cpp b/libraries/gpu/src/gpu/Batch.cpp
index a08fcfc054..0e1da3c4f0 100644
--- a/libraries/gpu/src/gpu/Batch.cpp
+++ b/libraries/gpu/src/gpu/Batch.cpp
@@ -302,7 +302,7 @@ void Batch::setDepthRangeTransform(float nearDepth, float farDepth) {
     _params.emplace_back(nearDepth);
 }
 
-void Batch::saveViewProjectionTransform(uint32 saveSlot) {
+void Batch::saveViewProjectionTransform(uint saveSlot) {
     ADD_COMMAND(saveViewProjectionTransform);
     if (saveSlot >= MAX_TRANSFORM_SAVE_SLOT_COUNT) {
         qCWarning(gpulogging) << "Transform save slot" << saveSlot << "exceeds max save slot count of" << MAX_TRANSFORM_SAVE_SLOT_COUNT;
@@ -310,7 +310,7 @@ void Batch::saveViewProjectionTransform(uint32 saveSlot) {
     _params.emplace_back(saveSlot);
 }
 
-void Batch::setSavedViewProjectionTransform(uint32 saveSlot) {
+void Batch::setSavedViewProjectionTransform(uint saveSlot) {
     ADD_COMMAND(setSavedViewProjectionTransform);
     if (saveSlot >= MAX_TRANSFORM_SAVE_SLOT_COUNT) {
         qCWarning(gpulogging) << "Transform save slot" << saveSlot << "exceeds max save slot count of"
@@ -319,7 +319,7 @@ void Batch::setSavedViewProjectionTransform(uint32 saveSlot) {
     _params.emplace_back(saveSlot);
 }
 
-void Batch::copySavedViewProjectionTransformToBuffer(uint32 saveSlot, const BufferPointer& buffer, Offset offset) {
+void Batch::copySavedViewProjectionTransformToBuffer(uint saveSlot, const BufferPointer& buffer, Offset offset) {
     ADD_COMMAND(copySavedViewProjectionTransformToBuffer);
     if (saveSlot >= MAX_TRANSFORM_SAVE_SLOT_COUNT) {
         qCWarning(gpulogging) << "Transform save slot" << saveSlot << "exceeds max save slot count of"
diff --git a/libraries/gpu/src/gpu/Batch.h b/libraries/gpu/src/gpu/Batch.h
index 8a27f86ab5..1e8a365b7a 100644
--- a/libraries/gpu/src/gpu/Batch.h
+++ b/libraries/gpu/src/gpu/Batch.h
@@ -44,7 +44,11 @@ public:
     typedef Stream::Slot Slot;
 
     enum {
-        MAX_TRANSFORM_SAVE_SLOT_COUNT = 6
+        // This is tied to RenderMirrorTask::MAX_MIRROR_DEPTH and RenderMirrorTask::MAX_MIRRORS_PER_LEVEL
+        // We have 1 view at mirror depth 0, 3 more at mirror depth 1, 9 more at mirror depth 2, and 27 more at mirror depth 3
+        // For each view, we have one slot for the background and one for the primary view, and that's all repeated for the secondary camera
+        // So this is 2 slots/view/camera * 2 cameras * (1 + 3 + 9 + 27) views
+        MAX_TRANSFORM_SAVE_SLOT_COUNT = 160
     };
 
     class DrawCallInfo {
@@ -192,9 +196,9 @@ public:
     void setViewportTransform(const Vec4i& viewport);
     void setDepthRangeTransform(float nearDepth, float farDepth);
 
-    void saveViewProjectionTransform(uint32 saveSlot);
-    void setSavedViewProjectionTransform(uint32 saveSlot);
-    void copySavedViewProjectionTransformToBuffer(uint32 saveSlot, const BufferPointer& buffer, Offset offset);
+    void saveViewProjectionTransform(uint saveSlot);
+    void setSavedViewProjectionTransform(uint saveSlot);
+    void copySavedViewProjectionTransformToBuffer(uint saveSlot, const BufferPointer& buffer, Offset offset);
 
     // Pipeline Stage
     void setPipeline(const PipelinePointer& pipeline);
diff --git a/libraries/render-utils/src/DeferredFrameTransform.h b/libraries/render-utils/src/DeferredFrameTransform.h
index 67179c36d0..8b8ce376f9 100644
--- a/libraries/render-utils/src/DeferredFrameTransform.h
+++ b/libraries/render-utils/src/DeferredFrameTransform.h
@@ -53,12 +53,12 @@ public:
     using Output = DeferredFrameTransformPointer;
     using JobModel = render::Job::ModelO<GenerateDeferredFrameTransform, Output>;
 
-    GenerateDeferredFrameTransform(unsigned int transformSlot) : _transformSlot{ transformSlot } {}
+    GenerateDeferredFrameTransform(uint transformSlot) : _transformSlot(transformSlot) {}
 
     void run(const render::RenderContextPointer& renderContext, Output& frameTransform);
 
 private:
-    unsigned int _transformSlot;
+    uint _transformSlot;
 };
 
 #endif // hifi_DeferredFrameTransform_h
diff --git a/libraries/render-utils/src/RenderCommonTask.cpp b/libraries/render-utils/src/RenderCommonTask.cpp
index 2eeb7d3b9e..4406fca4ca 100644
--- a/libraries/render-utils/src/RenderCommonTask.cpp
+++ b/libraries/render-utils/src/RenderCommonTask.cpp
@@ -52,7 +52,7 @@ void EndGPURangeTimer::run(const render::RenderContextPointer& renderContext, co
     config->setGPUBatchRunTime(timer->getGPUAverage(), timer->getBatchAverage());
 }
 
-DrawLayered3D::DrawLayered3D(const render::ShapePlumberPointer& shapePlumber, bool opaque, bool jitter, unsigned int transformSlot) :
+DrawLayered3D::DrawLayered3D(const render::ShapePlumberPointer& shapePlumber, bool opaque, bool jitter, uint transformSlot) :
     _shapePlumber(shapePlumber),
     _transformSlot(transformSlot),
     _opaquePass(opaque),
@@ -343,7 +343,7 @@ public:
     using Inputs = SetupMirrorTask::Outputs;
     using JobModel = render::Job::ModelI<DrawMirrorTask, Inputs>;
 
-    DrawMirrorTask() {
+    DrawMirrorTask(uint transformSlot) : _transformSlot(transformSlot) {
         static std::once_flag once;
         std::call_once(once, [this] {
             auto state = std::make_shared<gpu::State>();
@@ -379,13 +379,7 @@ public:
             batch.setViewportTransform(args->_viewport);
             batch.setStateScissorRect(args->_viewport);
 
-            glm::mat4 projMat;
-            Transform viewMat;
-            args->getViewFrustum().evalProjectionMatrix(projMat);
-            args->getViewFrustum().evalViewTransform(viewMat);
-
-            batch.setProjectionTransform(projMat);
-            batch.setViewTransform(viewMat);
+            batch.setSavedViewProjectionTransform(_transformSlot);
 
             batch.setResourceTexture(gr::Texture::MaterialMirror, args->_blitFramebuffer->getRenderBuffer(0));
 
@@ -406,20 +400,54 @@ public:
 private:
     static ShapePlumberPointer _forwardPipelines;
     static ShapePlumberPointer _deferredPipelines;
+
+    uint _transformSlot;
 };
 
 ShapePlumberPointer DrawMirrorTask::_forwardPipelines = std::make_shared<ShapePlumber>();
 ShapePlumberPointer DrawMirrorTask::_deferredPipelines = std::make_shared<ShapePlumber>();
 
 void RenderMirrorTask::build(JobModel& task, const render::Varying& inputs, render::Varying& output, size_t mirrorIndex, render::CullFunctor cullFunctor,
-        uint8_t transformOffset, size_t depth) {
+        uint transformOffset,size_t depth) {
     size_t nextDepth = depth + 1;
     const auto setupOutput = task.addJob<SetupMirrorTask>("SetupMirror" + std::to_string(mirrorIndex) + "Depth" + std::to_string(depth), inputs, mirrorIndex, nextDepth);
 
-    task.addJob<RenderViewTask>("RenderMirrorView" + std::to_string(mirrorIndex) + "Depth" + std::to_string(depth), cullFunctor, render::ItemKey::TAG_BITS_1,
-        render::ItemKey::TAG_BITS_1, (RenderViewTask::TransformOffset) transformOffset, nextDepth);
+    // Our primary view starts at transformOffset 0, and the secondary camera starts at transformOffset 2
+    // Our primary mirror views thus start after the secondary camera, at transformOffset 4, and the secondary
+    // camera mirror views start after all of the primary camera mirror views, at 4 + NUM_MAIN_MIRROR_SLOTS
+    static uint NUM_MAIN_MIRROR_SLOTS = 0;
+    static std::once_flag once;
+    std::call_once(once, [] {
+        for (size_t mirrorDepth = 0; mirrorDepth < MAX_MIRROR_DEPTH; mirrorDepth++) {
+            NUM_MAIN_MIRROR_SLOTS += pow(MAX_MIRRORS_PER_LEVEL, mirrorDepth + 1);
+        }
+        NUM_MAIN_MIRROR_SLOTS *= 2;
+    });
 
-    task.addJob<DrawMirrorTask>("DrawMirrorTask" + std::to_string(mirrorIndex) + "Depth" + std::to_string(depth), setupOutput);
+    uint mirrorOffset;
+    if (transformOffset == RenderViewTask::TransformOffset::MAIN_VIEW) {
+        mirrorOffset = RenderViewTask::TransformOffset::FIRST_MIRROR_VIEW - 2;
+    } else if (transformOffset == RenderViewTask::TransformOffset::SECONDARY_VIEW) {
+        mirrorOffset = RenderViewTask::TransformOffset::FIRST_MIRROR_VIEW + NUM_MAIN_MIRROR_SLOTS - 2;
+    } else {
+        mirrorOffset = transformOffset;
+    }
+
+    // To calculate our transformSlot, we take the transformSlot of our parent and add numSubSlots (the number of slots
+    // taken up by a sub-tree starting at this depth) per preceding mirrorIndex
+    uint numSubSlots = 0;
+    for (size_t mirrorDepth = depth; mirrorDepth < MAX_MIRROR_DEPTH; mirrorDepth++) {
+        numSubSlots += pow(MAX_MIRRORS_PER_LEVEL, mirrorDepth + 1 - nextDepth);
+    }
+    numSubSlots *= 2;
+
+    mirrorOffset += 2 + numSubSlots * (uint)mirrorIndex;
+
+    task.addJob<RenderViewTask>("RenderMirrorView" + std::to_string(mirrorIndex) + "Depth" + std::to_string(depth), cullFunctor, render::ItemKey::TAG_BITS_1,
+        render::ItemKey::TAG_BITS_1, (RenderViewTask::TransformOffset) mirrorOffset, nextDepth);
+
+    task.addJob<DrawMirrorTask>("DrawMirrorTask" + std::to_string(mirrorIndex) + "Depth" + std::to_string(depth), setupOutput,
+        render::RenderEngine::TS_MAIN_VIEW + transformOffset);
 }
 
 void RenderSimulateTask::run(const render::RenderContextPointer& renderContext, const Inputs& inputs) {
diff --git a/libraries/render-utils/src/RenderCommonTask.h b/libraries/render-utils/src/RenderCommonTask.h
index 57b65f882c..452c967cdb 100644
--- a/libraries/render-utils/src/RenderCommonTask.h
+++ b/libraries/render-utils/src/RenderCommonTask.h
@@ -69,7 +69,7 @@ public:
     using Config = DrawLayered3DConfig;
     using JobModel = render::Job::ModelI<DrawLayered3D, Inputs, Config>;
 
-    DrawLayered3D(const render::ShapePlumberPointer& shapePlumber, bool opaque, bool jitter, unsigned int transformSlot);
+    DrawLayered3D(const render::ShapePlumberPointer& shapePlumber, bool opaque, bool jitter, uint transformSlot);
 
     void configure(const Config& config) { _maxDrawn = config.maxDrawn; }
     void run(const render::RenderContextPointer& renderContext, const Inputs& inputs);
@@ -165,9 +165,9 @@ public:
 
     RenderMirrorTask() {}
 
-    void build(JobModel& task, const render::Varying& inputs, render::Varying& output, size_t mirrorIndex, render::CullFunctor cullFunctor,
-        uint8_t transformOffset, size_t depth);
+    void build(JobModel& task, const render::Varying& inputs, render::Varying& output, size_t mirrorIndex, render::CullFunctor cullFunctor, uint transformOffset, size_t depth);
 
+    // NOTE: if these change, must also change Batch::MAX_TRANSFORM_SAVE_SLOT_COUNT
     static const size_t MAX_MIRROR_DEPTH { 3 };
     static const size_t MAX_MIRRORS_PER_LEVEL { 3 };
 };
diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp
index b758a74d6d..001ff8106d 100644
--- a/libraries/render-utils/src/RenderDeferredTask.cpp
+++ b/libraries/render-utils/src/RenderDeferredTask.cpp
@@ -94,7 +94,7 @@ void RenderDeferredTask::configure(const Config& config) {
     preparePrimaryBufferConfig->setResolutionScale(config.resolutionScale);
 }
 
-void RenderDeferredTask::build(JobModel& task, const render::Varying& input, render::Varying& output, uint8_t transformOffset, render::CullFunctor cullFunctor, size_t depth) {
+void RenderDeferredTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, uint transformOffset, size_t depth) {
     // Prepare the ShapePipelines
     ShapePlumberPointer shapePlumberDeferred = std::make_shared<ShapePlumber>();
     initDeferredPipelines(*shapePlumberDeferred, FadeEffect::getBatchSetter(), FadeEffect::getItemUniformSetter());
diff --git a/libraries/render-utils/src/RenderDeferredTask.h b/libraries/render-utils/src/RenderDeferredTask.h
index 2b52f164a9..5ecca97306 100644
--- a/libraries/render-utils/src/RenderDeferredTask.h
+++ b/libraries/render-utils/src/RenderDeferredTask.h
@@ -145,7 +145,7 @@ public:
     using JobModel = render::Task::ModelI<RenderDeferredTask, Input, Config>;
 
     void configure(const Config& config);
-    void build(JobModel& task, const render::Varying& input, render::Varying& output, uint8_t transformOffset, render::CullFunctor cullFunctor, size_t depth);
+    void build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, uint transformOffset, size_t depth);
 };
 
 
diff --git a/libraries/render-utils/src/RenderForwardTask.cpp b/libraries/render-utils/src/RenderForwardTask.cpp
index 745b0148b6..ab4edbd765 100644
--- a/libraries/render-utils/src/RenderForwardTask.cpp
+++ b/libraries/render-utils/src/RenderForwardTask.cpp
@@ -68,7 +68,7 @@ void RenderForwardTask::configure(const Config& config) {
     preparePrimaryBufferConfig->setResolutionScale(config.resolutionScale);
 }
 
-void RenderForwardTask::build(JobModel& task, const render::Varying& input, render::Varying& output, uint8_t transformOffset, render::CullFunctor cullFunctor, size_t depth) {
+void RenderForwardTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, uint transformOffset, size_t depth) {
     task.addJob<SetRenderMethod>("SetRenderMethodTask", render::Args::FORWARD);
 
     // Prepare the ShapePipelines
diff --git a/libraries/render-utils/src/RenderForwardTask.h b/libraries/render-utils/src/RenderForwardTask.h
index 7cd7f6cd5f..ef7b6f5a92 100644
--- a/libraries/render-utils/src/RenderForwardTask.h
+++ b/libraries/render-utils/src/RenderForwardTask.h
@@ -37,7 +37,7 @@ public:
     RenderForwardTask() {}
 
     void configure(const Config& config);
-    void build(JobModel& task, const render::Varying& input, render::Varying& output, uint8_t transformOffset, render::CullFunctor cullFunctor, size_t depth);
+    void build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, uint transformOffset, size_t depth);
 };
 
 
diff --git a/libraries/render-utils/src/RenderViewTask.cpp b/libraries/render-utils/src/RenderViewTask.cpp
index ca0bba93e0..5c625df9fc 100644
--- a/libraries/render-utils/src/RenderViewTask.cpp
+++ b/libraries/render-utils/src/RenderViewTask.cpp
@@ -30,14 +30,14 @@ void RenderShadowsAndDeferredTask::build(JobModel& task, const render::Varying&
     const auto shadowTaskOut = task.addJob<RenderShadowTask>("RenderShadowTask", shadowTaskIn, cullFunctor, tagBits, tagMask);
 
     const auto renderDeferredInput = RenderDeferredTask::Input(items, lightingModel, lightingStageFramesAndZones, shadowTaskOut).asVarying();
-    task.addJob<RenderDeferredTask>("RenderDeferredTask", renderDeferredInput, transformOffset, cullFunctor, depth);
+    task.addJob<RenderDeferredTask>("RenderDeferredTask", renderDeferredInput, cullFunctor, transformOffset, depth);
 }
 
 void DeferredForwardSwitchJob::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, uint8_t tagBits,
         uint8_t tagMask, uint8_t transformOffset, size_t depth) {
     task.addBranch<RenderShadowsAndDeferredTask>("RenderShadowsAndDeferredTask", 0, input, cullFunctor, tagBits, tagMask, transformOffset, depth);
 
-    task.addBranch<RenderForwardTask>("RenderForwardTask", 1, input, transformOffset, cullFunctor, depth);
+    task.addBranch<RenderForwardTask>("RenderForwardTask", 1, input, cullFunctor, transformOffset, depth);
 }
 
 void RenderViewTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, uint8_t tagBits, uint8_t tagMask,
@@ -55,6 +55,6 @@ void RenderViewTask::build(JobModel& task, const render::Varying& input, render:
         task.addJob<DeferredForwardSwitchJob>("DeferredForwardSwitch", deferredForwardIn, cullFunctor, tagBits, tagMask, transformOffset, depth);
 #else
         const auto renderInput = RenderForwardTask::Input(items, lightingModel, lightingStageFramesAndZones).asVarying();
-        task.addJob<RenderForwardTask>("RenderForwardTask", renderInput, transformOffset);
+        task.addJob<RenderForwardTask>("RenderForwardTask", renderInput, cullFunctor, transformOffset, depth);
 #endif
 }
diff --git a/libraries/render-utils/src/RenderViewTask.h b/libraries/render-utils/src/RenderViewTask.h
index 539d9be8fb..956af8d3df 100644
--- a/libraries/render-utils/src/RenderViewTask.h
+++ b/libraries/render-utils/src/RenderViewTask.h
@@ -48,9 +48,11 @@ public:
 
     RenderViewTask() {}
 
+    // each view uses 1 transform for the main view, and one for the background, so these need to be increments of 2
     enum TransformOffset: uint8_t {
         MAIN_VIEW = 0,
-        SECONDARY_VIEW = 2 // each view uses 1 transform for the main view, and one for the background, so these need to be increments of 2
+        SECONDARY_VIEW = 2,
+        FIRST_MIRROR_VIEW = 4
     };
 
     void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor,
diff --git a/libraries/render/src/render/Engine.h b/libraries/render/src/render/Engine.h
index c9544a3fa3..e769297bbf 100644
--- a/libraries/render/src/render/Engine.h
+++ b/libraries/render/src/render/Engine.h
@@ -86,7 +86,7 @@ namespace render {
     class RenderEngine : public Engine {
     public:
 
-        enum TransformSlot {
+        enum TransformSlot : uint8_t {
             TS_MAIN_VIEW = 0,
             TS_BACKGROUND_VIEW
         };