Explorar o código

[unity] Fix bugs. Use faster/arrays generation if not clipping.

pharan %!s(int64=8) %!d(string=hai) anos
pai
achega
7409c8a092

+ 9 - 4
spine-unity/Assets/spine-unity/Editor/SkeletonRendererInspector.cs

@@ -43,12 +43,14 @@ namespace Spine.Unity.Editor {
 		protected static bool advancedFoldout;
 		protected static bool showBoneNames, showPaths, showShapes, showConstraints = true;
 
-		protected SerializedProperty skeletonDataAsset, initialSkinName, normals, tangents, meshes, immutableTriangles, separatorSlotNames, frontFacing, zSpacing, pmaVertexColors, clearStateOnDisable, tintBlack;
+		protected SerializedProperty skeletonDataAsset, initialSkinName;
+		protected SerializedProperty singleSubmesh, separatorSlotNames, clearStateOnDisable, immutableTriangles;
+		protected SerializedProperty normals, tangents, meshes, zSpacing, pmaVertexColors, tintBlack; // MeshGenerator settings
 		protected SpineInspectorUtility.SerializedSortingProperties sortingProperties;
 		protected bool isInspectingPrefab;
 
 		protected GUIContent SkeletonDataAssetLabel, SkeletonUtilityButtonContent;
-		protected GUIContent PMAVertexColorsLabel, ClearStateOnDisableLabel, ZSpacingLabel, MeshesLabel, ImmubleTrianglesLabel, TintBlackLabel;
+		protected GUIContent PMAVertexColorsLabel, ClearStateOnDisableLabel, ZSpacingLabel, MeshesLabel, ImmubleTrianglesLabel, TintBlackLabel, SingleSubmeshLabel;
 		protected GUIContent NormalsLabel, TangentsLabel;
 		const string ReloadButtonLabel = "Reload";
 
@@ -84,6 +86,7 @@ namespace Spine.Unity.Editor {
 			NormalsLabel = new GUIContent("Add Normals", "Use this if your shader requires vertex normals. A more efficient solution for 2D setups is to modify the shader to assume a single normal value for the whole mesh.");
 			TangentsLabel = new GUIContent("Solve Tangents", "Calculates the tangents per frame. Use this if you are using lit shaders (usually with normal maps) that require vertex tangents.");
 			TintBlackLabel = new GUIContent("Tint Black", "Adds black tint vertex data to the mesh as UV2 and UV3. Black tinting requires that the shader interpret UV2 and UV3 as black tint colors for this effect to work. You may also use the default [Spine/Skeleton Tint Black] shader.\n\nIf you only need to tint the whole skeleton and not individual parts, the [Spine/Skeleton Tint] shader is recommended for better efficiency and changing/animating the _Black material property via MaterialPropertyBlock.");
+			SingleSubmeshLabel = new GUIContent("Use Single Submesh", "Simplifies submesh determination by assuming you are only using one Material and need only one submesh. This is will disable render separation and custom slot materials.");
 
 			var so = this.serializedObject;
 			skeletonDataAsset = so.FindProperty("skeletonDataAsset");
@@ -95,11 +98,12 @@ namespace Spine.Unity.Editor {
 			pmaVertexColors = so.FindProperty("pmaVertexColors");
 			clearStateOnDisable = so.FindProperty("clearStateOnDisable");
 			tintBlack = so.FindProperty("tintBlack");
+			singleSubmesh = so.FindProperty("singleSubmesh");
 
 			separatorSlotNames = so.FindProperty("separatorSlotNames");
 			separatorSlotNames.isExpanded = true;
 
-			frontFacing = so.FindProperty("frontFacing");
+			//frontFacing = so.FindProperty("frontFacing");
 			zSpacing = so.FindProperty("zSpacing");
 
 			SerializedObject rso = SpineInspectorUtility.GetRenderersSerializedObject(serializedObject);
@@ -237,6 +241,7 @@ namespace Spine.Unity.Editor {
 
 						using (new SpineInspectorUtility.LabelWidthScope()) {
 							// Optimization options
+							if (singleSubmesh != null) EditorGUILayout.PropertyField(singleSubmesh, SingleSubmeshLabel);
 							if (meshes != null) EditorGUILayout.PropertyField(meshes, MeshesLabel);
 							if (immutableTriangles != null) EditorGUILayout.PropertyField(immutableTriangles, ImmubleTrianglesLabel);
 							EditorGUILayout.PropertyField(tintBlack, TintBlackLabel);
@@ -260,7 +265,7 @@ namespace Spine.Unity.Editor {
 							// Optional fields. May be disabled in SkeletonRenderer.
 							if (normals != null) EditorGUILayout.PropertyField(normals, NormalsLabel);
 							if (tangents != null) EditorGUILayout.PropertyField(tangents, TangentsLabel);
-							if (frontFacing != null) EditorGUILayout.PropertyField(frontFacing);
+							//if (frontFacing != null) EditorGUILayout.PropertyField(frontFacing);
 
 							EditorGUILayout.Space();
 

+ 355 - 207
spine-unity/Assets/spine-unity/Mesh Generation/SpineMesh.cs

@@ -28,7 +28,9 @@
  * POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
 
-//#define SPINE_TRIANGLECHECK // Avoid calling SetTriangles at the cost of checking for mesh differences (vertex counts, memberwise attachment list compare) every frame.
+// Not for optimization. Do  not disable.
+#define SPINE_TRIANGLECHECK // Avoid calling SetTriangles at the cost of checking for mesh differences (vertex counts, memberwise attachment list compare) every frame.
+//#define SPINE_DEBUG
 
 using UnityEngine;
 using System;
@@ -77,7 +79,7 @@ namespace Spine.Unity {
 
 		[System.Serializable]
 		public struct Settings {
-			public bool renderMeshes;
+			//public bool renderMeshes;
 			public bool useClipping;
 			[Space]
 			[Range(-0.1f, 0f)] public float zSpacing;
@@ -97,7 +99,7 @@ namespace Spine.Unity {
 						useClipping = true,
 						tintBlack = false,
 						calculateTangents = false,
-						renderMeshes = true,
+						//renderMeshes = true,
 						addNormals = false,
 						immutableTriangles = false
 					};
@@ -130,7 +132,7 @@ namespace Spine.Unity {
 		#endregion
 
 		#region Step 1 : Generate Instructions
-		public static void GenerateSingleSubmeshInstruction (SkeletonRendererInstruction instructionOutput, Skeleton skeleton, bool renderMeshes, Material material) {
+		public static void GenerateSingleSubmeshInstruction (SkeletonRendererInstruction instructionOutput, Skeleton skeleton, Material material) {
 			ExposedList<Slot> drawOrder = skeleton.drawOrder;
 			int drawOrderCount = drawOrder.Count;
 
@@ -165,30 +167,26 @@ namespace Spine.Unity {
 				Attachment attachment = slot.attachment;
 
 				workingAttachmentsItems[i] = attachment;
-				int attachmentTriangleCount = 0;
-				int attachmentVertexCount = 0;
-				
+				int attachmentTriangleCount;
+				int attachmentVertexCount;
 
 				var regionAttachment = attachment as RegionAttachment;
 				if (regionAttachment != null) {
 					attachmentVertexCount = 4;
 					attachmentTriangleCount = 6;
 				} else {
-					if (renderMeshes) {
-						var meshAttachment = attachment as MeshAttachment;
-						if (meshAttachment != null) {
-							attachmentVertexCount = meshAttachment.worldVerticesLength >> 1;
-							attachmentTriangleCount = meshAttachment.triangles.Length;						
-						} else {
-							var clippingAttachment = attachment as ClippingAttachment;
-							if (clippingAttachment != null) {
-								current.hasClipping = true;
-								skeletonHasClipping = true;
-							}
-							attachmentVertexCount = 0;
-							attachmentTriangleCount = 0;
-							//continue;
+					var meshAttachment = attachment as MeshAttachment;
+					if (meshAttachment != null) {
+						attachmentVertexCount = meshAttachment.worldVerticesLength >> 1;
+						attachmentTriangleCount = meshAttachment.triangles.Length;						
+					} else {
+						var clippingAttachment = attachment as ClippingAttachment;
+						if (clippingAttachment != null) {
+							current.hasClipping = true;
+							skeletonHasClipping = true;
 						}
+						attachmentVertexCount = 0;
+						attachmentTriangleCount = 0;
 					}
 				}
 				current.rawTriangleCount += attachmentTriangleCount;
@@ -204,7 +202,7 @@ namespace Spine.Unity {
 			workingSubmeshInstructions.Items[0] = current;
 		}
 			
-		public static void GenerateSkeletonRendererInstruction (SkeletonRendererInstruction instructionOutput, Skeleton skeleton, Dictionary<Slot, Material> customSlotMaterials, List<Slot> separatorSlots, bool generateMeshOverride, bool immutableTriangles = false, bool renderMeshes = true) {
+		public static void GenerateSkeletonRendererInstruction (SkeletonRendererInstruction instructionOutput, Skeleton skeleton, Dictionary<Slot, Material> customSlotMaterials, List<Slot> separatorSlots, bool generateMeshOverride, bool immutableTriangles = false) {
 //			if (skeleton == null) throw new ArgumentNullException("skeleton");
 //			if (instructionOutput == null) throw new ArgumentNullException("instructionOutput");
 
@@ -238,7 +236,6 @@ namespace Spine.Unity {
 			SlotData clippingEndSlot = null;
 			int submeshIndex = 0;
 			var drawOrderItems = drawOrder.Items;
-			bool currentHasRenderable = false;
 			for (int i = 0; i < drawOrderCount; i++) {
 				Slot slot = drawOrderItems[i];
 				Attachment attachment = slot.attachment;
@@ -257,31 +254,24 @@ namespace Spine.Unity {
 					attachmentVertexCount = 4;
 					attachmentTriangleCount = 6;
 					#endif
-					currentHasRenderable = true;
 				} else {
-					if (renderMeshes) {
-						var meshAttachment = attachment as MeshAttachment;
-						if (meshAttachment != null) {
-							rendererObject = meshAttachment.RendererObject;
-							#if SPINE_TRIANGLECHECK
-							attachmentVertexCount = meshAttachment.worldVerticesLength >> 1;
-							attachmentTriangleCount = meshAttachment.triangles.Length;
-							#endif
-							currentHasRenderable = true;
-						} else {
-							#if SPINE_TRIANGLECHECK
-							var clippingAttachment = attachment as ClippingAttachment;
-							if (clippingAttachment != null) {
-								clippingEndSlot = clippingAttachment.endSlot;
-								clippingAttachmentSource = i;
-								current.hasClipping = true;
-								skeletonHasClipping = true;
-							}
-							#endif
-							noRender = true;
-							//continue;
-						}
+					var meshAttachment = attachment as MeshAttachment;
+					if (meshAttachment != null) {
+						rendererObject = meshAttachment.RendererObject;
+						#if SPINE_TRIANGLECHECK
+						attachmentVertexCount = meshAttachment.worldVerticesLength >> 1;
+						attachmentTriangleCount = meshAttachment.triangles.Length;
+						#endif
 					} else {
+						#if SPINE_TRIANGLECHECK
+						var clippingAttachment = attachment as ClippingAttachment;
+						if (clippingAttachment != null) {
+							clippingEndSlot = clippingAttachment.endSlot;
+							clippingAttachmentSource = i;
+							current.hasClipping = true;
+							skeletonHasClipping = true;
+						}
+						#endif
 						noRender = true;
 					}
 				}
@@ -304,7 +294,7 @@ namespace Spine.Unity {
 				}
 
 				if (noRender) {
-					if (current.forceSeparate && currentHasRenderable && generateMeshOverride) {
+					if (current.forceSeparate && current.rawVertexCount > 0 && generateMeshOverride) {
 						{ // Add
 							current.endSlot = i;
 							current.preActiveClippingSlotSource = lastPreActiveClipping;
@@ -314,7 +304,6 @@ namespace Spine.Unity {
 
 							submeshIndex++;
 						}
-						currentHasRenderable = false;
 						current.startSlot = i;
 						lastPreActiveClipping = clippingAttachmentSource;
 						#if SPINE_TRIANGLECHECK
@@ -337,7 +326,7 @@ namespace Spine.Unity {
 					Material material = (rendererObject.GetType() == typeof(Material)) ? (Material)rendererObject : (Material)((AtlasRegion)rendererObject).page.rendererObject;
 					#endif
 
-					if (currentHasRenderable && (current.forceSeparate || !System.Object.ReferenceEquals(current.material, material))) { // Material changed. Add the previous submesh.
+					if (current.rawVertexCount > 0 && (current.forceSeparate || !System.Object.ReferenceEquals(current.material, material))) { // Material changed. Add the previous submesh.
 						{ // Add
 							current.endSlot = i;
 							current.preActiveClippingSlotSource = lastPreActiveClipping;
@@ -346,7 +335,6 @@ namespace Spine.Unity {
 							workingSubmeshInstructions.Items[submeshIndex] = current;
 							submeshIndex++;
 						}
-						currentHasRenderable = false;
 						current.startSlot = i;
 						lastPreActiveClipping = clippingAttachmentSource;
 						#if SPINE_TRIANGLECHECK
@@ -368,7 +356,7 @@ namespace Spine.Unity {
 				}
 			}
 				
-			if (currentHasRenderable) {
+			if (current.rawVertexCount > 0) {
 				{ // Add last or only submesh.
 					current.endSlot = drawOrderCount;
 					current.preActiveClippingSlotSource = lastPreActiveClipping;
@@ -391,18 +379,18 @@ namespace Spine.Unity {
 			// Material overrides are done here so they can be applied per submesh instead of per slot
 			// but they will still be passed through the GenerateMeshOverride delegate,
 			// and will still go through the normal material match check step in STEP 3.
-			var workingSubmeshInstructionsItems = workingSubmeshInstructions.Items;
+			var wsii = workingSubmeshInstructions.Items;
 			for (int i = 0; i < workingSubmeshInstructions.Count; i++) {
-				var m = workingSubmeshInstructionsItems[i].material;
+				var m = wsii[i].material;
 				Material mo;
 				if (customMaterialOverride.TryGetValue(m, out mo))
-					workingSubmeshInstructionsItems[i].material = mo;
+					wsii[i].material = mo;
 			}
 		}
 		#endregion
 
 		#region Step 2 : Populate vertex data and triangle index buffers.
-		public void BeginNewMesh () {
+		public void Begin () {
 			vertexBuffer.Clear(false);
 			colorBuffer.Clear(false);
 			uvBuffer.Clear(false);
@@ -421,12 +409,8 @@ namespace Spine.Unity {
 			submeshIndex = 0;
 		}
 
-		public void AddSubmesh (SubmeshInstruction instruction) {
+		public void AddSubmesh (SubmeshInstruction instruction, bool updateTriangles = true) {
 			var settings = this.settings;
-			if (!settings.renderMeshes) {
-				AddSubmeshQuadsOnly(instruction);
-				return;
-			}
 
 			if (submeshes.Count - 1 < submeshIndex) {
 				submeshes.Resize(submeshIndex + 1);
@@ -445,6 +429,8 @@ namespace Spine.Unity {
 
 			// Settings
 			float zSpacing = settings.zSpacing;
+			bool pmaVertexColors = settings.pmaVertexColors;
+			bool tintBlack = settings.tintBlack;
 			#if SPINE_TRIANGLECHECK
 			bool useClipping = settings.useClipping && instruction.hasClipping;
 			#else
@@ -453,13 +439,11 @@ namespace Spine.Unity {
 
 			if (useClipping) {
 				if (instruction.preActiveClippingSlotSource >= 0) {
-					Debug.Log("PreActiveClipping");
 					var slot = drawOrderItems[instruction.preActiveClippingSlotSource];
 					clipper.ClipStart(slot, slot.attachment as ClippingAttachment);
 				}	
 			}
-
-			bool pmaVertexColors = settings.pmaVertexColors;
+				
 			for (int slotIndex = instruction.startSlot; slotIndex < instruction.endSlot; slotIndex++) {
 				var slot = drawOrderItems[slotIndex];
 				var attachment = slot.attachment;
@@ -503,7 +487,6 @@ namespace Spine.Unity {
 								continue;
 							}
 						}
-
 						continue;
 					}
 				}
@@ -531,7 +514,7 @@ namespace Spine.Unity {
 				}
 
 				if (attachmentVertexCount != 0 && attachmentIndexCount != 0) {
-					if (settings.tintBlack)
+					if (tintBlack)
 						AddAttachmentTintBlack(slot.r2, slot.g2, slot.b2, attachmentVertexCount);
 
 					//AddAttachment(workingVerts, uvs, color, attachmentTriangleIndices, attachmentVertexCount, attachmentIndexCount, ref meshBoundsMin, ref meshBoundsMax, z);
@@ -550,38 +533,62 @@ namespace Spine.Unity {
 					var vbi = vertexBuffer.Items;
 					var ubi = uvBuffer.Items;
 					var cbi = colorBuffer.Items;
-					for (int i = 0; i < attachmentVertexCount; i++) {
-						int vi = ovc + i;
-						int i2 = i << 1; // i * 2
-						float x = workingVerts[i2];
-						float y = workingVerts[i2 + 1];
-
-						vbi[vi].x = x;
-						vbi[vi].y = y;
-						vbi[vi].z = z;
-						ubi[vi].x = uvs[i2];
-						ubi[vi].y = uvs[i2 + 1];
-						cbi[vi] = color;
-
-						// Calculate bounds.
-						if (x < meshBoundsMin.x) meshBoundsMin.x = x;
-						else if (x > meshBoundsMax.x) meshBoundsMax.x = x;
-						if (y < meshBoundsMin.y) meshBoundsMin.y = y;
-						else if (y > meshBoundsMax.y) meshBoundsMax.y = y;
+					if (ovc == 0) {
+						for (int i = 0; i < attachmentVertexCount; i++) {
+							int vi = ovc + i;
+							int i2 = i << 1; // i * 2
+							float x = workingVerts[i2];
+							float y = workingVerts[i2 + 1];
+
+							vbi[vi].x = x;
+							vbi[vi].y = y;
+							vbi[vi].z = z;
+							ubi[vi].x = uvs[i2];
+							ubi[vi].y = uvs[i2 + 1];
+							cbi[vi] = color;
+
+							// Calculate bounds.
+							if (x < meshBoundsMin.x) meshBoundsMin.x = x;
+							if (x > meshBoundsMax.x) meshBoundsMax.x = x;
+							if (y < meshBoundsMin.y) meshBoundsMin.y = y;
+							if (y > meshBoundsMax.y) meshBoundsMax.y = y;
+						}
+					} else {
+						for (int i = 0; i < attachmentVertexCount; i++) {
+							int vi = ovc + i;
+							int i2 = i << 1; // i * 2
+							float x = workingVerts[i2];
+							float y = workingVerts[i2 + 1];
+
+							vbi[vi].x = x;
+							vbi[vi].y = y;
+							vbi[vi].z = z;
+							ubi[vi].x = uvs[i2];
+							ubi[vi].y = uvs[i2 + 1];
+							cbi[vi] = color;
+
+							// Calculate bounds.
+							if (x < meshBoundsMin.x) meshBoundsMin.x = x;
+							else if (x > meshBoundsMax.x) meshBoundsMax.x = x;
+							if (y < meshBoundsMin.y) meshBoundsMin.y = y;
+							else if (y > meshBoundsMax.y) meshBoundsMax.y = y;
+						}
 					}
 
+
 					// Add data to triangle buffer
-					int oldTriangleCount = submesh.Count;
-					{ //submesh.Resize(oldTriangleCount + attachmentIndexCount);
-						int newTriangleCount = oldTriangleCount + attachmentIndexCount;
-						if (newTriangleCount > submesh.Items.Length) Array.Resize(ref submesh.Items, newTriangleCount); 
-						submesh.Count = newTriangleCount;
+					if (updateTriangles) {
+						int oldTriangleCount = submesh.Count;
+						{ //submesh.Resize(oldTriangleCount + attachmentIndexCount);
+							int newTriangleCount = oldTriangleCount + attachmentIndexCount;
+							if (newTriangleCount > submesh.Items.Length) Array.Resize(ref submesh.Items, newTriangleCount); 
+							submesh.Count = newTriangleCount;
+						}
+						var submeshItems = submesh.Items;
+						for (int i = 0; i < attachmentIndexCount; i++)
+							submeshItems[oldTriangleCount + i] = attachmentTriangleIndices[i] + ovc;
 					}
-					var submeshItems = submesh.Items;
-					for (int i = 0; i < attachmentIndexCount; i++)
-						submeshItems[oldTriangleCount + i] = attachmentTriangleIndices[i] + ovc;
 				}
-
 				clipper.ClipEnd(slot);
 			}
 			clipper.ClipEnd();
@@ -595,146 +602,289 @@ namespace Spine.Unity {
 			for (int i = submesh.Count, n = currentSubmeshItems.Length; i < n; i++)
 				currentSubmeshItems[i] = 0;
 
-			// Next AddSubmesh will use a new submeshIndex value.
-			submeshIndex++;
+			submeshIndex++; // Next AddSubmesh will use a new submeshIndex value.
 		}
 
-		public void ScaleVertexData (float scale) {
-			var vbi = vertexBuffer.Items;
-			for (int i = 0, n = vertexBuffer.Count; i < n; i++) {
-//				vbi[i].x *= scale;
-//				vbi[i].y *= scale;
-				vbi[i] *= scale;
-			}
-
-			meshBoundsMin *= scale;
-			meshBoundsMax *= scale;
-			meshBoundsThickness *= scale;
+		public void BuildMesh (SkeletonRendererInstruction instruction, bool updateTriangles) {
+			var wsii = instruction.submeshInstructions.Items;
+			for (int i = 0, n = instruction.submeshInstructions.Count; i < n; i++)
+				this.AddSubmesh(wsii[i], updateTriangles);
 		}
 
-		void AddSubmeshQuadsOnly (SubmeshInstruction instruction) {
-			const int attachmentVertexCount = 4;
-			const int attachmentIndexCount = 6;
-			int[] attachmentTriangleIndices = regionTriangles;
-
+		// Use this faster method when no clipping is involved.
+		public void BuildMeshWithArrays (SkeletonRendererInstruction instruction, bool updateTriangles) {
 			var settings = this.settings;
+			int totalVertexCount = instruction.rawVertexCount;
 
-			if (submeshes.Count - 1 < submeshIndex) {
-				submeshes.Resize(submeshIndex + 1);
-				if (submeshes.Items[submeshIndex] == null)
-					submeshes.Items[submeshIndex] = new ExposedList<int>();
+			// Add data to vertex buffers
+			{
+				if (totalVertexCount > vertexBuffer.Items.Length) { // Manual ExposedList.Resize()
+					Array.Resize(ref vertexBuffer.Items, totalVertexCount);
+					Array.Resize(ref uvBuffer.Items, totalVertexCount);
+					Array.Resize(ref colorBuffer.Items, totalVertexCount);
+				}
+				vertexBuffer.Count = uvBuffer.Count = colorBuffer.Count = totalVertexCount;
 			}
-			var submesh = submeshes.Items[submeshIndex];
-			submesh.Clear(false);
-
-			var skeleton = instruction.skeleton;
-			var drawOrderItems = skeleton.drawOrder.Items;
 
+			// Populate Verts
 			Color32 color;
-			float skeletonA = skeleton.a * 255, skeletonR = skeleton.r, skeletonG = skeleton.g, skeletonB = skeleton.b;
-			Vector2 meshBoundsMin = this.meshBoundsMin, meshBoundsMax = this.meshBoundsMax;
 
-			// Settings
-			float zSpacing = settings.zSpacing;
+			int vertexIndex = 0;
+			var tempVerts = this.tempVerts;
+			Vector3 bmin = this.meshBoundsMin;
+			Vector3 bmax = this.meshBoundsMax;
 
-			bool pmaVertexColors = settings.pmaVertexColors;
-			for (int slotIndex = instruction.startSlot; slotIndex < instruction.endSlot; slotIndex++) {
-				var slot = drawOrderItems[slotIndex];
-				var attachment = slot.attachment;
-				float z = zSpacing * slotIndex;
+			var vbi = vertexBuffer.Items;
+			var ubi = uvBuffer.Items;
+			var cbi = colorBuffer.Items;
+			int lastSlotIndex = 0;
+
+			// drawOrder[endSlot] is excluded
+			for (int si = 0, n = instruction.submeshInstructions.Count; si < n; si++) {
+				var submesh = instruction.submeshInstructions.Items[si];
+				var skeleton = submesh.skeleton;
+				var skeletonDrawOrderItems = skeleton.drawOrder.Items;
+				float a = skeleton.a * 255, r = skeleton.r, g = skeleton.g, b = skeleton.b;
+
+				int endSlot = submesh.endSlot;
+				int startSlot = submesh.startSlot;
+				lastSlotIndex = endSlot;
+
+				for (int slotIndex = startSlot; slotIndex < endSlot; slotIndex++) {
+					var slot = skeletonDrawOrderItems[slotIndex];
+					var attachment = slot.attachment;
+					float z = slotIndex * settings.zSpacing;
+
+					var regionAttachment = attachment as RegionAttachment;
+					if (regionAttachment != null) {
+						regionAttachment.ComputeWorldVertices(slot.bone, tempVerts, 0);
+
+						float x1 = tempVerts[RegionAttachment.BLX], y1 = tempVerts[RegionAttachment.BLY];
+						float x2 = tempVerts[RegionAttachment.ULX], y2 = tempVerts[RegionAttachment.ULY];
+						float x3 = tempVerts[RegionAttachment.URX], y3 = tempVerts[RegionAttachment.URY];
+						float x4 = tempVerts[RegionAttachment.BRX], y4 = tempVerts[RegionAttachment.BRY];
+						vbi[vertexIndex].x = x1; vbi[vertexIndex].y = y1; vbi[vertexIndex].z = z;
+						vbi[vertexIndex + 1].x = x4; vbi[vertexIndex + 1].y = y4; vbi[vertexIndex + 1].z = z;
+						vbi[vertexIndex + 2].x = x2; vbi[vertexIndex + 2].y = y2; vbi[vertexIndex + 2].z = z;
+						vbi[vertexIndex + 3].x = x3; vbi[vertexIndex + 3].y = y3;	vbi[vertexIndex + 3].z = z;
+
+						if (settings.pmaVertexColors) {
+							color.a = (byte)(a * slot.a * regionAttachment.a);
+							color.r = (byte)(r * slot.r * regionAttachment.r * color.a);
+							color.g = (byte)(g * slot.g * regionAttachment.g * color.a);
+							color.b = (byte)(b * slot.b * regionAttachment.b * color.a);
+							if (slot.data.blendMode == BlendMode.Additive) color.a = 0;
+						} else {
+							color.a = (byte)(a * slot.a * regionAttachment.a);
+							color.r = (byte)(r * slot.r * regionAttachment.r * 255);
+							color.g = (byte)(g * slot.g * regionAttachment.g * 255);
+							color.b = (byte)(b * slot.b * regionAttachment.b * 255);
+						}
 
-				var workingVerts = this.tempVerts;
-				float[] uvs;
+						cbi[vertexIndex] = color; cbi[vertexIndex + 1] = color; cbi[vertexIndex + 2] = color; cbi[vertexIndex + 3] = color;
+
+						float[] regionUVs = regionAttachment.uvs;
+						ubi[vertexIndex].x = regionUVs[RegionAttachment.BLX]; ubi[vertexIndex].y = regionUVs[RegionAttachment.BLY];
+						ubi[vertexIndex + 1].x = regionUVs[RegionAttachment.BRX]; ubi[vertexIndex + 1].y = regionUVs[RegionAttachment.BRY];
+						ubi[vertexIndex + 2].x = regionUVs[RegionAttachment.ULX]; ubi[vertexIndex + 2].y = regionUVs[RegionAttachment.ULY];
+						ubi[vertexIndex + 3].x = regionUVs[RegionAttachment.URX]; ubi[vertexIndex + 3].y = regionUVs[RegionAttachment.URY];
+
+						if (x1 < bmin.x) bmin.x = x1; // Potential first attachment bounds initialization. Initial min should not block initial max. Same for Y below.
+						if (x1 > bmax.x) bmax.x = x1;
+						if (x2 < bmin.x) bmin.x = x2;
+						else if (x2 > bmax.x) bmax.x = x2;
+						if (x3 < bmin.x) bmin.x = x3;
+						else if (x3 > bmax.x) bmax.x = x3;
+						if (x4 < bmin.x) bmin.x = x4;
+						else if (x4 > bmax.x) bmax.x = x4;
+
+						if (y1 < bmin.y) bmin.y = y1;
+						if (y1 > bmax.y) bmax.y = y1;
+						if (y2 < bmin.y) bmin.y = y2;
+						else if (y2 > bmax.y) bmax.y = y2;
+						if (y3 < bmin.y) bmin.y = y3;
+						else if (y3 > bmax.y) bmax.y = y3;
+						if (y4 < bmin.y) bmin.y = y4;
+						else if (y4 > bmax.y) bmax.y = y4;
+
+						vertexIndex += 4;
+					} else { //if (settings.renderMeshes) {
+						var meshAttachment = attachment as MeshAttachment;
+						if (meshAttachment != null) {
+							int meshVertexCount = meshAttachment.worldVerticesLength;
+							if (tempVerts.Length < meshVertexCount) this.tempVerts = tempVerts = new float[meshVertexCount];
+							meshAttachment.ComputeWorldVertices(slot, tempVerts);
+
+							if (settings.pmaVertexColors) {
+								color.a = (byte)(a * slot.a * meshAttachment.a);
+								color.r = (byte)(r * slot.r * meshAttachment.r * color.a);
+								color.g = (byte)(g * slot.g * meshAttachment.g * color.a);
+								color.b = (byte)(b * slot.b * meshAttachment.b * color.a);
+								if (slot.data.blendMode == BlendMode.Additive) color.a = 0;
+							} else {
+								color.a = (byte)(a * slot.a * meshAttachment.a);
+								color.r = (byte)(r * slot.r * meshAttachment.r * 255);
+								color.g = (byte)(g * slot.g * meshAttachment.g * 255);
+								color.b = (byte)(b * slot.b * meshAttachment.b * 255);
+							}
 
-				Color c = default(Color);
+							float[] attachmentUVs = meshAttachment.uvs;
+
+							// Potential first attachment bounds initialization. See conditions in RegionAttachment logic.
+							if (vertexIndex == 0) {
+								// Initial min should not block initial max.
+								// vi == vertexIndex does not always mean the bounds are fresh. It could be a submesh. Do not nuke old values by omitting the check.
+								// Should know that this is the first attachment in the submesh. slotIndex == startSlot could be an empty slot.
+								float fx = tempVerts[0], fy = tempVerts[1];
+								if (fx < bmin.x) bmin.x = fx;
+								if (fx > bmax.x) bmax.x = fx;
+								if (fy < bmin.y) bmin.y = fy;
+								if (fy > bmax.y) bmax.y = fy;
+							}
 
-				var region = attachment as RegionAttachment;
-				if (region != null) {
-					region.ComputeWorldVertices(slot.bone, workingVerts, 0);
-					uvs = region.uvs;
-					c.r = region.r; c.g = region.g; c.b = region.b; c.a = region.a;
-				} else {
-					continue;
-				}
+							for (int iii = 0; iii < meshVertexCount; iii += 2) {
+								float x = tempVerts[iii], y = tempVerts[iii + 1];
+								vbi[vertexIndex].x = x; vbi[vertexIndex].y = y; vbi[vertexIndex].z = z;
+								cbi[vertexIndex] = color; ubi[vertexIndex].x = attachmentUVs[iii]; ubi[vertexIndex].y = attachmentUVs[iii + 1];
 
-				if (pmaVertexColors) {
-					color.a = (byte)(skeletonA * slot.a * c.a);
-					color.r = (byte)(skeletonR * slot.r * c.r * color.a);
-					color.g = (byte)(skeletonG * slot.g * c.g * color.a);
-					color.b = (byte)(skeletonB * slot.b * c.b * color.a);
-					if (slot.data.blendMode == BlendMode.Additive) color.a = 0;
-				} else {
-					color.a = (byte)(skeletonA * slot.a * c.a);
-					color.r = (byte)(skeletonR * slot.r * c.r * 255);
-					color.g = (byte)(skeletonG * slot.g * c.g * 255);
-					color.b = (byte)(skeletonB * slot.b * c.b * 255);
-				}
+								if (x < bmin.x) bmin.x = x;
+								else if (x > bmax.x) bmax.x = x;
 
-				{
-					if (settings.tintBlack)
-						AddAttachmentTintBlack(slot.r2, slot.g2, slot.b2, attachmentVertexCount);
+								if (y < bmin.y) bmin.y = y;
+								else if (y > bmax.y) bmax.y = y;
 
-					//AddAttachment(workingVerts, uvs, color, attachmentTriangleIndices, attachmentVertexCount, attachmentIndexCount, ref meshBoundsMin, ref meshBoundsMax, z);
-					int ovc = vertexBuffer.Count;
-					// Add data to vertex buffers
-					{
-						int newVertexCount = ovc + attachmentVertexCount;
-						if (newVertexCount > vertexBuffer.Items.Length) { // Manual ExposedList.Resize()
-							Array.Resize(ref vertexBuffer.Items, newVertexCount);
-							Array.Resize(ref uvBuffer.Items, newVertexCount);
-							Array.Resize(ref colorBuffer.Items, newVertexCount);
+								vertexIndex++;
+							}
 						}
-						vertexBuffer.Count = uvBuffer.Count = colorBuffer.Count = newVertexCount;
 					}
+				}
 
-					var vbi = vertexBuffer.Items;
-					var ubi = uvBuffer.Items;
-					var cbi = colorBuffer.Items;
-					for (int i = 0; i < attachmentVertexCount; i++) {
-						int vi = ovc + i;
-						int i2 = i << 1; // i * 2
-						float x = workingVerts[i2];
-						float y = workingVerts[i2 + 1];
-
-						vbi[vi].x = x;
-						vbi[vi].y = y;
-						vbi[vi].z = z;
-						ubi[vi].x = uvs[i2];
-						ubi[vi].y = uvs[i2 + 1];
-						cbi[vi] = color;
-
-						// Calculate bounds.
-						if (x < meshBoundsMin.x) meshBoundsMin.x = x;
-						else if (x > meshBoundsMax.x) meshBoundsMax.x = x;
-						if (y < meshBoundsMin.y) meshBoundsMin.y = y;
-						else if (y > meshBoundsMax.y) meshBoundsMax.y = y;
+				if (settings.tintBlack) {
+					Vector2 rg, b2;
+					int vi = vertexIndex;
+					b2.y = 1f;
+
+					{ 
+						if (uv2 == null) {
+							uv2 = new ExposedList<Vector2>();
+							uv3 = new ExposedList<Vector2>();
+						}
+						if (totalVertexCount > uv2.Items.Length) { // Manual ExposedList.Resize()
+							Array.Resize(ref uv2.Items, totalVertexCount);
+							Array.Resize(ref uv3.Items, totalVertexCount);
+						}
+						uv2.Count = uv3.Count = totalVertexCount;
 					}
 
-					// TODO: Simplify triangle buffer handling.
-					// Add data to triangle buffer
-					int oldTriangleCount = submesh.Count;
-					{ //submesh.Resize(oldTriangleCount + attachmentIndexCount);
-						int newTriangleCount = oldTriangleCount + attachmentIndexCount;
-						if (newTriangleCount > submesh.Items.Length) Array.Resize(ref submesh.Items, newTriangleCount); 
-						submesh.Count = newTriangleCount;
+					var uv2i = uv2.Items;
+					var uv3i = uv3.Items;
+
+					for (int slotIndex = startSlot; slotIndex < endSlot; slotIndex++) {
+						var slot = skeletonDrawOrderItems[slotIndex];
+						var attachment = slot.attachment;
+
+						rg.x = slot.r2; //r
+						rg.y = slot.g2; //g
+						b2.x = slot.b2; //b
+
+						var regionAttachment = attachment as RegionAttachment;
+						if (regionAttachment != null) {
+							uv2i[vi] = rg; uv2i[vi + 1] = rg; uv2i[vi + 2] = rg; uv2i[vi + 3] = rg;
+							uv3i[vi] = b2; uv3i[vi + 1] = b2; uv3i[vi + 2] = b2; uv3i[vi + 3] = b2;
+							vi += 4;
+						} else { //} if (settings.renderMeshes) {
+							var meshAttachment = attachment as MeshAttachment;
+							if (meshAttachment != null) {
+								int meshVertexCount = meshAttachment.worldVerticesLength;
+								for (int iii = 0; iii < meshVertexCount; iii += 2) {
+									uv2i[vi] = rg;
+									uv3i[vi] = b2;
+									vi++;
+								}
+							}
+						}
 					}
-					var submeshItems = submesh.Items;
-					for (int i = 0; i < attachmentIndexCount; i++)
-						submeshItems[oldTriangleCount + i] = attachmentTriangleIndices[i] + ovc;
 				}
 			}
 
-			this.meshBoundsMin = meshBoundsMin;
-			this.meshBoundsMax = meshBoundsMax;
-			meshBoundsThickness = instruction.endSlot * zSpacing;
+			this.meshBoundsMin = bmin;
+			this.meshBoundsMax = bmax;
+			this.meshBoundsThickness = lastSlotIndex * settings.zSpacing;
+
+			// Add triangles
+			if (updateTriangles) {
+				int submeshInstructionCount = instruction.submeshInstructions.Count;
+
+				// Match submesh buffers count with submeshInstruction count.
+				if (this.submeshes.Count < submeshInstructionCount) {
+					this.submeshes.Resize(submeshInstructionCount);
+					for (int i = 0, n = submeshInstructionCount; i < n; i++) {
+						var submeshBuffer = this.submeshes.Items[i];
+						if (submeshBuffer == null)
+							this.submeshes.Items[i] = new ExposedList<int>();
+						else
+							submeshBuffer.Clear(false);
+					}
+				}
 
-			// Trim or zero submesh triangles.
-			var currentSubmeshItems = submesh.Items;
-			for (int i = submesh.Count, n = currentSubmeshItems.Length; i < n; i++)
-				currentSubmeshItems[i] = 0;
+				var submeshInstructionsItems = instruction.submeshInstructions.Items; // This relies on the resize above.
+
+				// Fill the buffers.
+				int attachmentFirstVertex = 0;
+				for (int smbi = 0; smbi < submeshInstructionCount; smbi++) {
+					var submeshInstruction = submeshInstructionsItems[smbi];
+					var currentSubmeshBuffer = this.submeshes.Items[smbi];
+					{ //submesh.Resize(submesh.rawTriangleCount);
+						int newTriangleCount = submeshInstruction.rawTriangleCount;
+						if (newTriangleCount > currentSubmeshBuffer.Items.Length)
+							Array.Resize(ref currentSubmeshBuffer.Items, newTriangleCount); 
+						else if (newTriangleCount < currentSubmeshBuffer.Items.Length) {
+							// Zero the extra.
+							var sbi = currentSubmeshBuffer.Items;
+							for (int ei = newTriangleCount, nn = sbi.Length; ei < nn; ei++)
+								sbi[ei] = 0;
+						}
+						currentSubmeshBuffer.Count = newTriangleCount;
+					}
+
+					var tris = currentSubmeshBuffer.Items;
+					int triangleIndex = 0;
+					var skeleton = submeshInstruction.skeleton;
+					var skeletonDrawOrderItems = skeleton.drawOrder.Items;
+					for (int a = submeshInstruction.startSlot, endSlot = submeshInstruction.endSlot; a < endSlot; a++) {			
+						var attachment = skeletonDrawOrderItems[a].attachment;
+						if (attachment is RegionAttachment) {
+							tris[triangleIndex] = attachmentFirstVertex;
+							tris[triangleIndex + 1] = attachmentFirstVertex + 2;
+							tris[triangleIndex + 2] = attachmentFirstVertex + 1;
+							tris[triangleIndex + 3] = attachmentFirstVertex + 2;
+							tris[triangleIndex + 4] = attachmentFirstVertex + 3;
+							tris[triangleIndex + 5] = attachmentFirstVertex + 1;
+							triangleIndex += 6;
+							attachmentFirstVertex += 4;
+							continue;
+						}
+						var meshAttachment = attachment as MeshAttachment;
+						if (meshAttachment != null) {
+							int[] attachmentTriangles = meshAttachment.triangles;
+							for (int ii = 0, nn = attachmentTriangles.Length; ii < nn; ii++, triangleIndex++)
+								tris[triangleIndex] = attachmentFirstVertex + attachmentTriangles[ii];
+							attachmentFirstVertex += meshAttachment.worldVerticesLength >> 1; // length/2;
+						}
+					}
+				}
+			}
+		}
+
+		public void ScaleVertexData (float scale) {
+			var vbi = vertexBuffer.Items;
+			for (int i = 0, n = vertexBuffer.Count; i < n; i++) {
+				vbi[i] *= scale; // vbi[i].x *= scale; vbi[i].y *= scale;
+			}
 
-			// Next AddSubmesh will use a new submeshIndex value.
-			submeshIndex++;
+			meshBoundsMin *= scale;
+			meshBoundsMax *= scale;
+			meshBoundsThickness *= scale;
 		}
 
 		void AddAttachmentTintBlack (float r2, float g2, float b2, int vertexCount) {
@@ -790,12 +940,12 @@ namespace Spine.Unity {
 				if (meshBoundsMin.x == BoundsMinDefault) {
 					mesh.bounds = new Bounds();
 				} else {
+					//mesh.bounds = ArraysMeshGenerator.ToBounds(meshBoundsMin, meshBoundsMax);
 					Vector2 halfSize = (meshBoundsMax - meshBoundsMin) * 0.5f;
 					mesh.bounds = new Bounds {
 						center = (Vector3)(meshBoundsMin + halfSize),
 						extents = new Vector3(halfSize.x, halfSize.y, meshBoundsThickness * 0.5f)
 					};
-					//mesh.bounds = ArraysMeshGenerator.ToBounds(meshBoundsMin, meshBoundsMax);
 				}
 			}
 
@@ -928,7 +1078,6 @@ namespace Spine.Unity {
 		/// <param name="tempTanBuffer">A temporary Vector3[] for calculating tangents.</param>
 		/// <param name="vertexCount">Number of vertices that require tangents (or the size of the vertex array)</param>
 		internal static void SolveTangents2DBuffer (Vector4[] tangents, Vector2[] tempTanBuffer, int vertexCount) {
-
 			Vector4 tangent;
 			tangent.z = 0;
 			for (int i = 0; i < vertexCount; ++i) {
@@ -949,7 +1098,6 @@ namespace Spine.Unity {
 				tangent.w = (t.y * t2.x > t.x * t2.y) ? 1 : -1; // 2D direction calculation. Used for binormals.
 				tangents[i] = tangent;
 			}
-
 		}
 		#endregion
 	}

+ 14 - 10
spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/SkeletonGraphic.cs

@@ -195,20 +195,24 @@ namespace Spine.Unity {
 			if (!this.IsValid) return;
 
 			skeleton.SetColor(this.color);
-
+			var smartMesh = meshBuffers.GetNext();
 			var currentInstructions = this.currentInstructions;
-			MeshGenerator.GenerateSingleSubmeshInstruction(currentInstructions, skeleton, true, this.material);
-			meshGenerator.BeginNewMesh();
-			meshGenerator.AddSubmesh(currentInstructions.submeshInstructions.Items[0]);
-			if (canvas != null)
-				meshGenerator.ScaleVertexData(canvas.referencePixelsPerUnit);
 
-			var smartMesh = meshBuffers.GetNext();
-			var mesh = smartMesh.mesh;
+			MeshGenerator.GenerateSingleSubmeshInstruction(currentInstructions, skeleton, this.material);
+			bool updateTriangles = SkeletonRendererInstruction.GeometryNotEqual(currentInstructions, smartMesh.instructionUsed);
 
+			meshGenerator.Begin();
+			if (currentInstructions.hasActiveClipping) {
+				meshGenerator.AddSubmesh(currentInstructions.submeshInstructions.Items[0], updateTriangles);
+			} else {
+				meshGenerator.BuildMeshWithArrays(currentInstructions, updateTriangles);
+			}
+
+			if (canvas != null) meshGenerator.ScaleVertexData(canvas.referencePixelsPerUnit);
+
+			var mesh = smartMesh.mesh;
 			meshGenerator.FillVertexData(mesh);
-			if (SkeletonRendererInstruction.GeometryNotEqual(currentInstructions, smartMesh.instructionUsed))
-				meshGenerator.FillTrianglesSingle(mesh);
+			if (updateTriangles) meshGenerator.FillTrianglesSingle(mesh);
 			
 			canvasRenderer.SetMesh(mesh);
 			smartMesh.instructionUsed.Set(currentInstructions);

+ 10 - 5
spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonPartsRenderer.cs

@@ -86,21 +86,26 @@ namespace Spine.Unity.Modules {
 			LazyIntialize();
 
 			// STEP 1: Create instruction
+			var smartMesh = buffers.GetNextMesh();
 			currentInstructions.SetWithSubset(instructions, startSubmesh, endSubmesh);
+			bool updateTriangles = SkeletonRendererInstruction.GeometryNotEqual(currentInstructions, smartMesh.instructionUsed);
 
 			// STEP 2: Generate mesh buffers.
 			var currentInstructionsSubmeshesItems = currentInstructions.submeshInstructions.Items;
-			meshGenerator.BeginNewMesh();
-			for (int i = 0; i < currentInstructions.submeshInstructions.Count; i++)
-				meshGenerator.AddSubmesh(currentInstructionsSubmeshesItems[i]);
+			meshGenerator.Begin();
+			if (currentInstructions.hasActiveClipping) {
+				for (int i = 0; i < currentInstructions.submeshInstructions.Count; i++)
+					meshGenerator.AddSubmesh(currentInstructionsSubmeshesItems[i], updateTriangles);
+			} else {
+				meshGenerator.BuildMeshWithArrays(currentInstructions, updateTriangles);
+			}
 
 			buffers.UpdateSharedMaterials(currentInstructions.submeshInstructions);
 
 			// STEP 3: modify mesh.
-			var smartMesh = buffers.GetNextMesh();
 			var mesh = smartMesh.mesh;
 			meshGenerator.FillVertexData(mesh);
-			if (SkeletonRendererInstruction.GeometryNotEqual(currentInstructions, smartMesh.instructionUsed)) {
+			if (updateTriangles) {
 				meshGenerator.FillTriangles(mesh);
 				meshRenderer.sharedMaterials = buffers.GetUpdatedShaderdMaterialsArray();
 			} else if (buffers.MaterialsChangedInLastUpdate()) {

+ 1 - 1
spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonRenderSeparator.cs

@@ -142,7 +142,7 @@ namespace Spine.Unity.Modules {
 				calculateTangents = skeletonRenderer.calculateTangents,
 				immutableTriangles = false, // parts cannot do immutable triangles.
 				pmaVertexColors = skeletonRenderer.pmaVertexColors,
-				renderMeshes = skeletonRenderer.renderMeshes,
+				//renderMeshes = skeletonRenderer.renderMeshes,
 				tintBlack = skeletonRenderer.tintBlack,
 				useClipping = true,
 				zSpacing = skeletonRenderer.zSpacing

+ 75 - 49
spine-unity/Assets/spine-unity/SkeletonRenderer.cs

@@ -53,11 +53,13 @@ namespace Spine.Unity {
 		[System.NonSerialized] public readonly List<Slot> separatorSlots = new List<Slot>();
 
 		[Range(-0.1f, 0f)] public float zSpacing;
-		public bool renderMeshes = true;
+		//public bool renderMeshes = true;
+		public bool useClipping = true;
 		public bool immutableTriangles = false;
 		public bool pmaVertexColors = true;
 		public bool clearStateOnDisable = false;
 		public bool tintBlack = false;
+		public bool singleSubmesh = false;
 
 		[UnityEngine.Serialization.FormerlySerializedAs("calculateNormals")]
 		public bool addNormals;
@@ -110,7 +112,7 @@ namespace Spine.Unity {
 		}
 			
 		[System.NonSerialized] readonly SkeletonRendererInstruction currentInstructions = new SkeletonRendererInstruction();
-		[System.NonSerialized] readonly MeshGenerator meshGenerator = new MeshGenerator();
+		readonly MeshGenerator meshGenerator = new MeshGenerator();
 		[System.NonSerialized] readonly MeshRendererBuffers rendererBuffers = new MeshRendererBuffers();
 
 		#region Runtime Instantiation
@@ -163,20 +165,18 @@ namespace Spine.Unity {
 
 				currentInstructions.Clear();
 				rendererBuffers.Clear();
-				meshGenerator.BeginNewMesh();
+				meshGenerator.Begin();
 				skeleton = null;
 				valid = false;
 			}
 
 			if (!skeletonDataAsset) {
-				if (logErrors)
-					Debug.LogError("Missing SkeletonData asset.", this);
-
+				if (logErrors) Debug.LogError("Missing SkeletonData asset.", this);
 				return;
 			}
+
 			SkeletonData skeletonData = skeletonDataAsset.GetSkeletonData(false);
-			if (skeletonData == null)
-				return;
+			if (skeletonData == null) return;
 			valid = true;
 
 			meshFilter = GetComponent<MeshFilter>();
@@ -184,7 +184,7 @@ namespace Spine.Unity {
 			rendererBuffers.Initialize();
 
 			skeleton = new Skeleton(skeletonData);
-			if (!string.IsNullOrEmpty(initialSkinName) && initialSkinName != "default")
+			if (!string.IsNullOrEmpty(initialSkinName) && !string.Equals(initialSkinName, "default", System.StringComparison.Ordinal))
 				skeleton.SetSkin(initialSkinName);
 
 			separatorSlots.Clear();
@@ -207,61 +207,87 @@ namespace Spine.Unity {
 			const bool doMeshOverride = false;
 			if (!meshRenderer.enabled) return;
 			#endif
-
 			var currentInstructions = this.currentInstructions;
+			var workingSubmeshInstructions = currentInstructions.submeshInstructions;
+			var currentSmartMesh = rendererBuffers.GetNextMesh(); // Double-buffer for performance.
+
+			bool updateTriangles;
+
+			if (this.singleSubmesh) {
+				// STEP 1. Determine a SmartMesh.Instruction. Split up instructions into submeshes. =============================================
+				MeshGenerator.GenerateSingleSubmeshInstruction(currentInstructions, skeleton, skeletonDataAsset.atlasAssets[0].materials[0]);
+
+				// STEP 1.9. Post-process workingInstructions. ==================================================================================
+				#if SPINE_OPTIONAL_MATERIALOVERRIDE
+				if (customMaterialOverride.Count > 0) // isCustomMaterialOverridePopulated 
+					MeshGenerator.TryReplaceMaterials(workingSubmeshInstructions, customMaterialOverride);
+				#endif
+
+				// STEP 2. Update vertex buffer based on verts from the attachments.  ===========================================================
+				meshGenerator.settings = new MeshGenerator.Settings {
+					pmaVertexColors = this.pmaVertexColors,
+					zSpacing = this.zSpacing,
+					useClipping = this.useClipping,
+					tintBlack = this.tintBlack,
+					calculateTangents = this.calculateTangents,
+					addNormals = this.addNormals
+				};
+				meshGenerator.Begin();
+				updateTriangles = SkeletonRendererInstruction.GeometryNotEqual(currentInstructions, currentSmartMesh.instructionUsed);
+				if (currentInstructions.hasActiveClipping) {
+					meshGenerator.AddSubmesh(workingSubmeshInstructions.Items[0], updateTriangles);
+				} else {
+					meshGenerator.BuildMeshWithArrays(currentInstructions, updateTriangles);
+				}
 
-			// STEP 1. Determine a SmartMesh.Instruction. Split up instructions into submeshes. ============================================================
-			MeshGenerator.GenerateSkeletonRendererInstruction(currentInstructions, skeleton, customSlotMaterials, separatorSlots, doMeshOverride, this.immutableTriangles, this.renderMeshes);
-
-
-			// STEP 1.9. Post-process workingInstructions. ============================================================
-			#if SPINE_OPTIONAL_MATERIALOVERRIDE
-			if (customMaterialOverride.Count > 0) // isCustomMaterialOverridePopulated 
-				MeshGenerator.TryReplaceMaterials(currentInstructions.submeshInstructions, customMaterialOverride);
-			#endif
-
-			#if SPINE_OPTIONAL_RENDEROVERRIDE
-			if (doMeshOverride) {
-				this.generateMeshOverride(currentInstructions);
-				if (disableRenderingOnOverride) return;
-			}
-			#endif
-
+			} else {
+				// STEP 1. Determine a SmartMesh.Instruction. Split up instructions into submeshes. =============================================
+				MeshGenerator.GenerateSkeletonRendererInstruction(currentInstructions, skeleton, customSlotMaterials, separatorSlots, doMeshOverride, this.immutableTriangles);
 
-			// STEP 2. Update vertex buffer based on verts from the attachments.  ============================================================
-			meshGenerator.BeginNewMesh();
-			meshGenerator.settings = new MeshGenerator.Settings {
-				pmaVertexColors = this.pmaVertexColors,
-				zSpacing = this.zSpacing,
-				useClipping = true,
-				tintBlack = this.tintBlack,
-				calculateTangents = this.calculateTangents,
-				renderMeshes = this.renderMeshes,
-				addNormals = this.addNormals
-			};
+				// STEP 1.9. Post-process workingInstructions. ==================================================================================
+				#if SPINE_OPTIONAL_MATERIALOVERRIDE
+				if (customMaterialOverride.Count > 0) // isCustomMaterialOverridePopulated 
+					MeshGenerator.TryReplaceMaterials(workingSubmeshInstructions, customMaterialOverride);
+				#endif
 
-			var workingSubmeshInstructions = currentInstructions.submeshInstructions;
-			foreach (var submeshInstruction in workingSubmeshInstructions) {
-				meshGenerator.AddSubmesh(submeshInstruction);
+				#if SPINE_OPTIONAL_RENDEROVERRIDE
+				if (doMeshOverride) {
+					this.generateMeshOverride(currentInstructions);
+					if (disableRenderingOnOverride) return;
+				}
+				#endif
+
+				updateTriangles = SkeletonRendererInstruction.GeometryNotEqual(currentInstructions, currentSmartMesh.instructionUsed);
+
+				// STEP 2. Update vertex buffer based on verts from the attachments.  ===========================================================
+				meshGenerator.settings = new MeshGenerator.Settings {
+					pmaVertexColors = this.pmaVertexColors,
+					zSpacing = this.zSpacing,
+					useClipping = this.useClipping,
+					tintBlack = this.tintBlack,
+					calculateTangents = this.calculateTangents,
+					addNormals = this.addNormals
+				};
+				meshGenerator.Begin();
+				if (currentInstructions.hasActiveClipping)
+					meshGenerator.BuildMesh(currentInstructions, updateTriangles);
+				else
+					meshGenerator.BuildMeshWithArrays(currentInstructions, updateTriangles);
 			}
-				
 
-			// Step 3. Move the mesh data into a UnityEngine.Mesh ============================================================
-			var currentSmartMesh = rendererBuffers.GetNextMesh();	// Double-buffer for performance.
+			// STEP 3. Move the mesh data into a UnityEngine.Mesh ===========================================================================
 			var currentMesh = currentSmartMesh.mesh;
 			meshGenerator.FillVertexData(currentMesh);
-
 			rendererBuffers.UpdateSharedMaterials(workingSubmeshInstructions);
-
-			// Check if the triangles should also be updated.
-			if (SkeletonRendererInstruction.GeometryNotEqual(currentInstructions, currentSmartMesh.instructionUsed)) { // This thorough structure check is cheaper than updating triangles every frame.
+			if (updateTriangles) { // Check if the triangles should also be updated.
 				meshGenerator.FillTriangles(currentMesh);
 				meshRenderer.sharedMaterials = rendererBuffers.GetUpdatedShaderdMaterialsArray();
 			} else if (rendererBuffers.MaterialsChangedInLastUpdate()) {
 				meshRenderer.sharedMaterials = rendererBuffers.GetUpdatedShaderdMaterialsArray();
 			}
 
-			// Step 4. The UnityEngine.Mesh is ready. Set it as the MeshFilter's mesh. Store the instructions used for that mesh. ============================================================
+
+			// STEP 4. The UnityEngine.Mesh is ready. Set it as the MeshFilter's mesh. Store the instructions used for that mesh. ===========
 			meshFilter.sharedMesh = currentMesh;
 			currentSmartMesh.instructionUsed.Set(currentInstructions);
 		}