Merge pull request #1594 from js6i/perf2

Reducing redundant state changes
diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandEncoderState.h b/MoltenVK/MoltenVK/Commands/MVKCommandEncoderState.h
index c6e7280..0def2be 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCommandEncoderState.h
+++ b/MoltenVK/MoltenVK/Commands/MVKCommandEncoderState.h
@@ -373,7 +373,7 @@
     // Template function that marks both the vector and all binding elements in the vector as dirty.
     template<class T>
     void markDirty(T& bindings, bool& bindingsDirtyFlag) {
-        for (auto& b : bindings) { b.isDirty = true; }
+        for (auto& b : bindings) { b.markDirty(); }
         bindingsDirtyFlag = true;
     }
 
@@ -384,18 +384,18 @@
 
         if ( !b.mtlResource ) { return; }
 
-        T db = b;   // Copy that can be marked dirty
         MVKCommandEncoderState::markDirty();
         bindingsDirtyFlag = true;
-        db.isDirty = true;
 
         for (auto iter = bindings.begin(), end = bindings.end(); iter != end; ++iter) {
-            if( iter->index == db.index ) {
-                *iter = db;
+            if (iter->index == b.index) {
+                iter->update(b);
                 return;
             }
         }
-        bindings.push_back(db);
+
+        bindings.push_back(b);
+        bindings.back().markDirty();
     }
 
 	// For texture bindings, we also keep track of whether any bindings need a texture swizzle
diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandEncoderState.mm b/MoltenVK/MoltenVK/Commands/MVKCommandEncoderState.mm
index dfb8c1c..1a36927 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCommandEncoderState.mm
+++ b/MoltenVK/MoltenVK/Commands/MVKCommandEncoderState.mm
@@ -37,8 +37,8 @@
 #pragma mark MVKPipelineCommandEncoderState
 
 void MVKPipelineCommandEncoderState::bindPipeline(MVKPipeline* pipeline) {
+    if (pipeline != _pipeline) markDirty();
     _pipeline = pipeline;
-    markDirty();
 }
 
 MVKPipeline* MVKPipelineCommandEncoderState::getPipeline() { return _pipeline; }
@@ -71,14 +71,17 @@
 		usingViewports.resize(firstViewport + vpCnt);
 	}
 
+    bool dirty;
 	bool mustSetDynamically = _cmdEncoder->supportsDynamicState(VK_DYNAMIC_STATE_VIEWPORT);
 	if (isSettingDynamically || (!mustSetDynamically && vpCnt > 0)) {
+        dirty = memcmp(&usingViewports[firstViewport], &viewports[0], vpCnt * sizeof(VkViewport)) != 0;
 		std::copy(viewports.begin(), viewports.end(), usingViewports.begin() + firstViewport);
 	} else {
+        dirty = !usingViewports.empty();
 		usingViewports.clear();
 	}
 
-	markDirty();
+	if (dirty) markDirty();
 }
 
 void MVKViewportCommandEncoderState::encodeImpl(uint32_t stage) {
@@ -121,14 +124,17 @@
 		usingScissors.resize(firstScissor + sCnt);
 	}
 
+    bool dirty;
 	bool mustSetDynamically = _cmdEncoder->supportsDynamicState(VK_DYNAMIC_STATE_SCISSOR);
 	if (isSettingDynamically || (!mustSetDynamically && sCnt > 0)) {
+        dirty = memcmp(&usingScissors[firstScissor], &scissors[0], sCnt * sizeof(VkRect2D)) != 0;
 		std::copy(scissors.begin(), scissors.end(), usingScissors.begin() + firstScissor);
 	} else {
+        dirty = !usingScissors.empty();
 		usingScissors.clear();
 	}
 
-	markDirty();
+	if (dirty) markDirty();
 }
 
 void MVKScissorCommandEncoderState::encodeImpl(uint32_t stage) {
@@ -248,6 +254,7 @@
 #pragma mark MVKDepthStencilCommandEncoderState
 
 void MVKDepthStencilCommandEncoderState:: setDepthStencilState(const VkPipelineDepthStencilStateCreateInfo& vkDepthStencilInfo) {
+    auto oldData = _depthStencilData;
 
     if (vkDepthStencilInfo.depthTestEnable) {
         _depthStencilData.depthCompareFunction = mvkMTLCompareFunctionFromVkCompareOp(vkDepthStencilInfo.depthCompareOp);
@@ -260,7 +267,7 @@
     setStencilState(_depthStencilData.frontFaceStencilData, vkDepthStencilInfo.front, vkDepthStencilInfo.stencilTestEnable);
     setStencilState(_depthStencilData.backFaceStencilData, vkDepthStencilInfo.back, vkDepthStencilInfo.stencilTestEnable);
 
-    markDirty();
+    if (!(oldData == _depthStencilData)) markDirty();
 }
 
 void MVKDepthStencilCommandEncoderState::setStencilState(MVKMTLStencilDescriptorData& stencilInfo,
@@ -289,6 +296,8 @@
 // it may not be accurate, and if not dynamic, pipeline will override when it is encoded anyway.
 void MVKDepthStencilCommandEncoderState::setStencilCompareMask(VkStencilFaceFlags faceMask,
                                                                uint32_t stencilCompareMask) {
+    auto oldData = _depthStencilData;
+
     if (mvkAreAllFlagsEnabled(faceMask, VK_STENCIL_FACE_FRONT_BIT)) {
         _depthStencilData.frontFaceStencilData.readMask = stencilCompareMask;
     }
@@ -296,13 +305,15 @@
         _depthStencilData.backFaceStencilData.readMask = stencilCompareMask;
     }
 
-    markDirty();
+    if (!(oldData == _depthStencilData)) markDirty();
 }
 
 // We don't check for dynamic state here, because if this is called before pipeline is set,
 // it may not be accurate, and if not dynamic, pipeline will override when it is encoded anyway.
 void MVKDepthStencilCommandEncoderState::setStencilWriteMask(VkStencilFaceFlags faceMask,
                                                              uint32_t stencilWriteMask) {
+    auto oldData = _depthStencilData;
+
     if (mvkAreAllFlagsEnabled(faceMask, VK_STENCIL_FACE_FRONT_BIT)) {
         _depthStencilData.frontFaceStencilData.writeMask = stencilWriteMask;
     }
@@ -310,10 +321,12 @@
         _depthStencilData.backFaceStencilData.writeMask = stencilWriteMask;
     }
 
-    markDirty();
+    if (!(oldData == _depthStencilData)) markDirty();
 }
 
 void MVKDepthStencilCommandEncoderState::beginMetalRenderPass() {
+    MVKCommandEncoderState::beginMetalRenderPass();
+
 	MVKRenderSubpass* mvkSubpass = _cmdEncoder->getSubpass();
 	MVKPixelFormats* pixFmts = _cmdEncoder->getPixelFormats();
 	MTLPixelFormat mtlDSFormat = pixFmts->getMTLPixelFormat(mvkSubpass->getDepthStencilFormat());
@@ -351,22 +364,27 @@
     // If ref values are to be set dynamically, don't set them here.
     if (_cmdEncoder->supportsDynamicState(VK_DYNAMIC_STATE_STENCIL_REFERENCE)) { return; }
 
+    if (_frontFaceValue != vkDepthStencilInfo.front.reference || _backFaceValue != vkDepthStencilInfo.back.reference)
+        markDirty();
+
     _frontFaceValue = vkDepthStencilInfo.front.reference;
     _backFaceValue = vkDepthStencilInfo.back.reference;
-    markDirty();
 }
 
 // We don't check for dynamic state here, because if this is called before pipeline is set,
 // it may not be accurate, and if not dynamic, pipeline will override when it is encoded anyway.
 void MVKStencilReferenceValueCommandEncoderState::setReferenceValues(VkStencilFaceFlags faceMask,
                                                                      uint32_t stencilReference) {
+    bool dirty = false;
     if (mvkAreAllFlagsEnabled(faceMask, VK_STENCIL_FACE_FRONT_BIT)) {
+        dirty |= (_frontFaceValue != stencilReference);
         _frontFaceValue = stencilReference;
     }
     if (mvkAreAllFlagsEnabled(faceMask, VK_STENCIL_FACE_BACK_BIT)) {
+        dirty |= (_backFaceValue != stencilReference);
         _backFaceValue = stencilReference;
     }
-    markDirty();
+    if (dirty) markDirty();
 }
 
 void MVKStencilReferenceValueCommandEncoderState::encodeImpl(uint32_t stage) {
@@ -381,16 +399,20 @@
 
 void MVKDepthBiasCommandEncoderState::setDepthBias(const VkPipelineRasterizationStateCreateInfo& vkRasterInfo) {
 
+    auto wasEnabled = _isEnabled;
     _isEnabled = vkRasterInfo.depthBiasEnable;
 
     // If ref values are to be set dynamically, don't set them here.
     if (_cmdEncoder->supportsDynamicState(VK_DYNAMIC_STATE_DEPTH_BIAS)) { return; }
 
-    _depthBiasConstantFactor = vkRasterInfo.depthBiasConstantFactor;
-    _depthBiasSlopeFactor = vkRasterInfo.depthBiasSlopeFactor;
-    _depthBiasClamp = vkRasterInfo.depthBiasClamp;
+    if (_isEnabled != wasEnabled || _depthBiasConstantFactor != vkRasterInfo.depthBiasConstantFactor
+        || _depthBiasSlopeFactor != vkRasterInfo.depthBiasSlopeFactor || _depthBiasClamp != vkRasterInfo.depthBiasClamp) {
 
-    markDirty();
+        markDirty();
+        _depthBiasConstantFactor = vkRasterInfo.depthBiasConstantFactor;
+        _depthBiasSlopeFactor = vkRasterInfo.depthBiasSlopeFactor;
+        _depthBiasClamp = vkRasterInfo.depthBiasClamp;
+    }
 }
 
 // We don't check for dynamic state here, because if this is called before pipeline is set,
@@ -398,11 +420,15 @@
 void MVKDepthBiasCommandEncoderState::setDepthBias(float depthBiasConstantFactor,
                                                    float depthBiasSlopeFactor,
                                                    float depthBiasClamp) {
-    _depthBiasConstantFactor = depthBiasConstantFactor;
-    _depthBiasSlopeFactor = depthBiasSlopeFactor;
-    _depthBiasClamp = depthBiasClamp;
 
-    markDirty();
+    if (_depthBiasConstantFactor != depthBiasConstantFactor || _depthBiasSlopeFactor != depthBiasSlopeFactor
+        || _depthBiasClamp != depthBiasClamp) {
+
+        markDirty();
+        _depthBiasConstantFactor = depthBiasConstantFactor;
+        _depthBiasSlopeFactor = depthBiasSlopeFactor;
+        _depthBiasClamp = depthBiasClamp;
+    }
 }
 
 void MVKDepthBiasCommandEncoderState::encodeImpl(uint32_t stage) {
@@ -426,12 +452,13 @@
     // Abort if we are using dynamic, but call is not dynamic.
 	if ( !isDynamic && _cmdEncoder->supportsDynamicState(VK_DYNAMIC_STATE_BLEND_CONSTANTS) ) { return; }
 
-    _red = red;
-    _green = green;
-    _blue = blue;
-    _alpha = alpha;
-
-    markDirty();
+    if (_red != red || _green != green || _blue != blue || _alpha != alpha) {
+        markDirty();
+        _red = red;
+        _green = green;
+        _blue = blue;
+        _alpha = alpha;
+    }
 }
 
 void MVKBlendColorCommandEncoderState::encodeImpl(uint32_t stage) {
@@ -752,6 +779,10 @@
                                                            b.mtlBytes,
                                                            b.size,
                                                            b.index);
+                           else if (b.justOffset)
+                               [cmdEncoder->getMTLComputeEncoder(kMVKCommandUseTessellationVertexTessCtl)
+                                            setBufferOffset: b.offset
+                                            atIndex: b.index];
                            else
                                [cmdEncoder->getMTLComputeEncoder(kMVKCommandUseTessellationVertexTessCtl) setBuffer: b.mtlBuffer
                                                                                                              offset: b.offset
@@ -785,9 +816,14 @@
                                                               b.size,
                                                               b.index);
                                } else {
-                                   [cmdEncoder->_mtlRenderEncoder setVertexBuffer: b.mtlBuffer
-                                                                           offset: b.offset
-                                                                          atIndex: b.index];
+                                   if (b.justOffset) {
+                                       [cmdEncoder->_mtlRenderEncoder setVertexBufferOffset: b.offset
+                                                                                    atIndex: b.index];
+                                   } else {
+                                       [cmdEncoder->_mtlRenderEncoder setVertexBuffer: b.mtlBuffer
+                                                                               offset: b.offset
+                                                                              atIndex: b.index];
+                                   }
 
                                    // Add any translated vertex bindings for this binding
                                    auto xltdVtxBindings = pipeline->getTranslatedVertexBindings();
@@ -828,6 +864,9 @@
                                                            b.mtlBytes,
                                                            b.size,
                                                            b.index);
+                           else if (b.justOffset)
+                               [cmdEncoder->getMTLComputeEncoder(kMVKCommandUseTessellationVertexTessCtl) setBufferOffset: b.offset
+                                                                                                                  atIndex: b.index];
                            else
                                [cmdEncoder->getMTLComputeEncoder(kMVKCommandUseTessellationVertexTessCtl) setBuffer: b.mtlBuffer
                                                                                                              offset: b.offset
@@ -858,6 +897,9 @@
                                                           b.mtlBytes,
                                                           b.size,
                                                           b.index);
+                           else if (b.justOffset)
+                               [cmdEncoder->_mtlRenderEncoder setVertexBufferOffset: b.offset
+                                                                            atIndex: b.index];
                            else
                                [cmdEncoder->_mtlRenderEncoder setVertexBuffer: b.mtlBuffer
                                                                        offset: b.offset
@@ -888,6 +930,9 @@
                                                             b.mtlBytes,
                                                             b.size,
                                                             b.index);
+                           else if (b.justOffset)
+                               [cmdEncoder->_mtlRenderEncoder setFragmentBufferOffset: b.offset
+                                                                              atIndex: b.index];
                            else
                                [cmdEncoder->_mtlRenderEncoder setFragmentBuffer: b.mtlBuffer
                                                                          offset: b.offset
@@ -1027,7 +1072,12 @@
 										b.mtlBytes,
 										b.size,
 										b.index);
-		} else {
+        } else if (b.justOffset) {
+            [cmdEncoder->getMTLComputeEncoder(kMVKCommandUseDispatch)
+                        setBufferOffset: b.offset
+                                atIndex: b.index];
+
+        } else {
 			[cmdEncoder->getMTLComputeEncoder(kMVKCommandUseDispatch) setBuffer: b.mtlBuffer
 																		 offset: b.offset
 																		atIndex: b.index];
diff --git a/MoltenVK/MoltenVK/Commands/MVKMTLResourceBindings.h b/MoltenVK/MoltenVK/Commands/MVKMTLResourceBindings.h
index 012600a..a0f71d0 100644
--- a/MoltenVK/MoltenVK/Commands/MVKMTLResourceBindings.h
+++ b/MoltenVK/MoltenVK/Commands/MVKMTLResourceBindings.h
@@ -34,6 +34,16 @@
     uint32_t swizzle = 0;
 	uint16_t index = 0;
     bool isDirty = true;
+
+    inline void markDirty() { isDirty = true; }
+
+    inline void update(const MVKMTLTextureBinding &other) {
+        if (mtlTexture != other.mtlTexture || swizzle != other.swizzle) {
+            mtlTexture = other.mtlTexture;
+            swizzle = other.swizzle;
+            markDirty();
+        }
+    }
 } MVKMTLTextureBinding;
 
 /** Describes a MTLSamplerState resource binding. */
@@ -41,6 +51,15 @@
     union { id<MTLSamplerState> mtlSamplerState = nil; id<MTLSamplerState> mtlResource; }; // aliases
     uint16_t index = 0;
     bool isDirty = true;
+
+    inline void markDirty() { isDirty = true; }
+
+    inline void update(const MVKMTLSamplerStateBinding &other) {
+        if (mtlSamplerState != other.mtlSamplerState) {
+            mtlSamplerState = other.mtlSamplerState;
+            markDirty();
+        }
+    }
 } MVKMTLSamplerStateBinding;
 
 /** Describes a MTLBuffer resource binding. */
@@ -49,8 +68,27 @@
     VkDeviceSize offset = 0;
     uint32_t size = 0;
 	uint16_t index = 0;
+    bool justOffset = false;
     bool isDirty = true;
     bool isInline = false;
+
+    inline void markDirty() { justOffset = false; isDirty = true; }
+
+    inline void update(const MVKMTLBufferBinding &other) {
+        if (mtlBuffer != other.mtlBuffer || size != other.size || isInline != other.isInline) {
+            mtlBuffer = other.mtlBuffer;
+            size = other.size;
+            isInline = other.isInline;
+            offset = other.offset;
+
+            justOffset = false;
+            isDirty = true;
+        } else if (offset != other.offset) {
+            offset = other.offset;
+            justOffset = !isDirty || justOffset;
+            isDirty = true;
+        }
+    }
 } MVKMTLBufferBinding;
 
 /** Describes a MTLBuffer resource binding as used for an index buffer. */