Merge pull request #907 from js6i/master

Handle vertex attributes with zero stride
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm
index e2d61ec..2afcc78 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm
@@ -970,9 +970,17 @@
 			maxBinding = max(pVKVB->binding, maxBinding);
 			uint32_t vbIdx = getMetalBufferIndexForVertexAttributeBinding(pVKVB->binding);
 			MTLVertexBufferLayoutDescriptor* vbDesc = plDesc.vertexDescriptor.layouts[vbIdx];
-			vbDesc.stride = (pVKVB->stride == 0) ? sizeof(simd::float4) : pVKVB->stride;      // Vulkan allows zero stride but Metal doesn't. Default to float4
-            vbDesc.stepFunction = mvkMTLVertexStepFunctionFromVkVertexInputRate(pVKVB->inputRate);
-            vbDesc.stepRate = 1;
+			if (pVKVB->stride == 0) {
+				// Stride can't be 0, it will be set later to attributes' maximum offset + size
+				// to prevent it from being larger than the underlying buffer permits.
+				vbDesc.stride = 0;
+				vbDesc.stepFunction = MTLVertexStepFunctionConstant;
+				vbDesc.stepRate = 0;
+			} else {
+				vbDesc.stride = pVKVB->stride;
+				vbDesc.stepFunction = mvkMTLVertexStepFunctionFromVkVertexInputRate(pVKVB->inputRate);
+				vbDesc.stepRate = 1;
+			}
         }
     }
 
@@ -1004,26 +1012,31 @@
 			// Vulkan allows offsets to exceed the buffer stride, but Metal doesn't.
 			// If this is the case, fetch an a translated artificial buffer binding, using the same MTLBuffer,
 			// but that is translated so that the reduced VA offset fits into the binding stride.
-			// Only check non-zero offsets, as it's common for both to be zero when step rate is instance.
-			if (vaOffset > 0) {
-				const VkVertexInputBindingDescription* pVKVB = pVI->pVertexBindingDescriptions;
-				for (uint32_t j = 0; j < vbCnt; j++, pVKVB++) {
-					if (pVKVB->binding == pVKVA->binding) {
-						if (vaOffset >= pVKVB->stride) {
-							// Move vertex attribute offset into the stride. This vertex attribute may be
-							// combined with other vertex attributes into the same translated buffer binding.
-							// But if the reduced offset combined with the vertex attribute size still won't
-							// fit into the buffer binding stride, force the vertex attribute offset to zero,
-							// effectively dedicating this vertex attribute to its own buffer binding.
-							uint32_t origOffset = vaOffset;
-							vaOffset %= pVKVB->stride;
-							if (vaOffset + getPixelFormats()->getBytesPerBlock(pVKVA->format) > pVKVB->stride) {
-								vaOffset = 0;
-							}
-							vaBinding = getTranslatedVertexBinding(vaBinding, origOffset - vaOffset, maxBinding);
+			const VkVertexInputBindingDescription* pVKVB = pVI->pVertexBindingDescriptions;
+			for (uint32_t j = 0; j < vbCnt; j++, pVKVB++) {
+				if (pVKVB->binding == pVKVA->binding) {
+					uint32_t attrSize = getPixelFormats()->getBytesPerBlock(pVKVA->format);
+					if (pVKVB->stride == 0) {
+						// The step is set to constant, but we need to change stride to be non-zero for metal.
+						// Look for the maximum offset + size to set as the stride.
+						uint32_t vbIdx = getMetalBufferIndexForVertexAttributeBinding(pVKVB->binding);
+						MTLVertexBufferLayoutDescriptor* vbDesc = plDesc.vertexDescriptor.layouts[vbIdx];
+						uint32_t strideLowBound = vaOffset + attrSize;
+						if (vbDesc.stride < strideLowBound) vbDesc.stride = strideLowBound;
+					} else if (vaOffset >= pVKVB->stride) {
+						// Move vertex attribute offset into the stride. This vertex attribute may be
+						// combined with other vertex attributes into the same translated buffer binding.
+						// But if the reduced offset combined with the vertex attribute size still won't
+						// fit into the buffer binding stride, force the vertex attribute offset to zero,
+						// effectively dedicating this vertex attribute to its own buffer binding.
+						uint32_t origOffset = vaOffset;
+						vaOffset %= pVKVB->stride;
+						if (vaOffset + attrSize > pVKVB->stride) {
+							vaOffset = 0;
 						}
-						break;
+						vaBinding = getTranslatedVertexBinding(vaBinding, origOffset - vaOffset, maxBinding);
 					}
+					break;
 				}
 			}