瀏覽代碼

[libgdx] Many micro optimizations, shaved off 40% of software clipping runtime.

badlogic 8 年之前
父節點
當前提交
8866925570

+ 2 - 1
spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/ClippingTest.java

@@ -116,7 +116,8 @@ public class ClippingTest extends ApplicationAdapter {
 	}
 
 	public void render () {
-		state.update(Gdx.graphics.getDeltaTime() * 0.3f);
+		// state.update(Gdx.graphics.getDeltaTime() * 0.3f);
+		state.update(0);
 
 		Gdx.gl.glClearColor(0.3f, 0.3f, 0.3f, 1);
 		Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

+ 15 - 8
spine-libgdx/spine-libgdx-tests/src/com/esotericsoftware/spine/SoftwareClippingTest.java

@@ -174,19 +174,24 @@ public class SoftwareClippingTest extends ApplicationAdapter {
 	}
 
 	private void clip () {
-		boolean clipped = clipper.clip(triangle, 0, triangle.length, 5, clippingPolygon, clippedPolygon);
+		float x1 = triangle[0];
+		float y1 = triangle[1];
+		float x2 = triangle[5];
+		float y2 = triangle[6];
+		float x3 = triangle[10];
+		float y3 = triangle[11];
+		
+		// must duplicate first vertex at end of polygon
+		// so we can avoid module/branch in clipping code
+		clippingPolygon.add(clippingPolygon.get(0));
+		clippingPolygon.add(clippingPolygon.get(1));
+		
+		boolean clipped = clipper.clip(x1, y1, x2, y2, x3, y3, clippingPolygon, clippedPolygon);
 		System.out.println("Clipped: " + clipped);
 		if (clipped) {
 			clippedPolygonVertices.clear();
 			clippedPolygonIndices.clear();
 			
-			float x1 = triangle[0];
-			float y1 = triangle[1];
-			float x2 = triangle[5];
-			float y2 = triangle[6];
-			float x3 = triangle[10];
-			float y3 = triangle[11];
-			
 			float d0 = y2 - y3;
 			float d1 = x3 - x2;
 			float d2 = x1 - x3;
@@ -222,6 +227,8 @@ public class SoftwareClippingTest extends ApplicationAdapter {
 		} else {
 			clippedPolygon.clear();
 		}
+		
+		clippingPolygon.setSize(clippingPolygon.size - 2);
 	}
 
 	public static void main (String[] args) {

+ 28 - 30
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java

@@ -62,7 +62,7 @@ public class SkeletonRenderer {
 	private ClippingAttachment clipAttachment;
 	private Slot clipEnd;
 	private FloatArray clippingArea = new FloatArray();
-	private float[] clipInput = new float[6];
+	private boolean clippingAreaClockwise;
 	private FloatArray clipOutput = new FloatArray(400);
 	private FloatArray clippedVertices = new FloatArray(400);	
 	private ShortArray clippedTriangles = new ShortArray(400);
@@ -100,9 +100,9 @@ public class SkeletonRenderer {
 
 			} else if (attachment instanceof ClippingAttachment) {
 				ClippingAttachment clip = (ClippingAttachment) attachment;
-				batch.end();
+				if (!softwareClipping) batch.end();
 				clipStart(batch.getProjectionMatrix(), batch.getTransformMatrix(), slot, clip);
-				batch.begin();
+				if (!softwareClipping) batch.begin();
 				continue;
 
 			} else if (attachment instanceof MeshAttachment) {
@@ -179,9 +179,9 @@ public class SkeletonRenderer {
 
 			} else if (attachment instanceof ClippingAttachment) {
 				ClippingAttachment clip = (ClippingAttachment) attachment;
-				batch.end();
+				if (!softwareClipping) batch.end();
 				clipStart(batch.getProjectionMatrix(), batch.getTransformMatrix(), slot, clip);
-				batch.begin();
+				if (!softwareClipping) batch.begin();
 				continue;
 
 			} else if (attachment instanceof SkeletonAttachment) {
@@ -284,9 +284,9 @@ public class SkeletonRenderer {
 
 			} else if (attachment instanceof ClippingAttachment) {
 				ClippingAttachment clip = (ClippingAttachment) attachment;
-				batch.end();
+				if (!softwareClipping) batch.end();
 				clipStart(batch.getProjectionMatrix(), batch.getTransformMatrix(), slot, clip);
-				batch.begin();
+				if (!softwareClipping) batch.begin();
 				continue;
 
 			} else if (attachment instanceof SkeletonAttachment) {
@@ -393,6 +393,9 @@ public class SkeletonRenderer {
 			int n = clip.getWorldVerticesLength();
 			float[] vertices = this.clippingArea.setSize(n);
 			clip.computeWorldVertices(slot, 0, n, vertices, 0, 2);
+			clippingAreaClockwise = SutherlandHodgmanClipper.clockwise(this.clippingArea);
+			clippingArea.add(clippingArea.items[0]);
+			clippingArea.add(clippingArea.items[1]);
 		}
 	}
 
@@ -402,7 +405,7 @@ public class SkeletonRenderer {
 		if (!softwareClipping) Gdx.gl.glDisable(GL20.GL_STENCIL_TEST);
 	}
 	
-	private void clipSoftware(float[] vertices, int offset, int verticesLength, short[] triangles, int triangleOffset, int trianglesLength, FloatArray clippedVertices, ShortArray clippedTriangles, float dark, float light, boolean twoColor) {
+	private void clipSoftware(final float[] vertices, final int offset, final int verticesLength, final short[] triangles, final int triangleOffset, final int trianglesLength, final FloatArray clippedVertices, final ShortArray clippedTriangles, final float dark, final float light, final boolean twoColor) {
 		int idx = 0;
 		clippedVertices.clear();
 		clippedTriangles.clear();
@@ -410,32 +413,25 @@ public class SkeletonRenderer {
 		final int uvOffset = twoColor ? 4 : 3;
 		for (int i = 0; i < trianglesLength; i += 3) {
 			int triOffset = triangles[i] * vertexSize;
-			clipInput[0] = vertices[triOffset];
-			clipInput[1] = vertices[triOffset + 1];
+			float x1 = vertices[triOffset];
+			float y1= vertices[triOffset + 1];
 			float u1 = vertices[triOffset + uvOffset];
 			float v1 = vertices[triOffset + uvOffset + 1];
 			
 			triOffset = triangles[i + 1] * vertexSize;
-			clipInput[2] = vertices[triOffset];
-			clipInput[3] = vertices[triOffset + 1];
+			float x2 = vertices[triOffset];
+			float y2 = vertices[triOffset + 1];
 			float u2 = vertices[triOffset + uvOffset];
 			float v2 = vertices[triOffset + uvOffset + 1];
 			
 			triOffset = triangles[i + 2] * vertexSize;
-			clipInput[4] = vertices[triOffset];
-			clipInput[5] = vertices[triOffset + 1];
+			float x3 = vertices[triOffset];
+			float y3 = vertices[triOffset + 1];
 			float u3 = vertices[triOffset + uvOffset];
 			float v3 = vertices[triOffset + uvOffset + 1];
 			
-			boolean clipped = clipper.clip(clipInput, 0, 6, 2, clippingArea, clipOutput);
+			boolean clipped = clipper.clip(x1, y1, x2, y2, x3, y3, clippingArea, clipOutput, clippingAreaClockwise);
 			if (clipped) {				
-				float x1 = clipInput[0];
-				float y1 = clipInput[1];
-				float x2 = clipInput[2];
-				float y2 = clipInput[3];
-				float x3 = clipInput[4];
-				float y3 = clipInput[5];
-				
 				float d0 = y2 - y3;
 				float d1 = x3 - x2;
 				float d2 = x1 - x3;
@@ -450,8 +446,10 @@ public class SkeletonRenderer {
 					float x = clipVertices[j];
 					float y = clipVertices[j + 1];
 						
-					float a = (d0 * (x - x3) + d1 * (y - y3)) * denom;
-					float b = (d4 * (x - x3) + d2 * (y - y3)) * denom;
+					float c0 = x - x3;
+					float c1 = y - y3;
+					float a = (d0 * c0 + d1 * c1) * denom;
+					float b = (d4 * c0 + d2 * c1) * denom;
 					float c = 1.0f - a - b;
 					
 					float u = u1 * a + u2 * b + u3 * c;
@@ -472,22 +470,22 @@ public class SkeletonRenderer {
 				
 				idx += clipOutput.size >> 1;
 			} else {
-				clippedVertices.add(clipInput[0]);
-				clippedVertices.add(clipInput[1]);
+				clippedVertices.add(x1);
+				clippedVertices.add(y1);
 				clippedVertices.add(light);
 				if (twoColor) clippedVertices.add(dark);
 				clippedVertices.add(u1);
 				clippedVertices.add(v1);
 				
-				clippedVertices.add(clipInput[2]);
-				clippedVertices.add(clipInput[3]);
+				clippedVertices.add(x2);
+				clippedVertices.add(y2);
 				clippedVertices.add(light);
 				if (twoColor) clippedVertices.add(dark);
 				clippedVertices.add(u2);
 				clippedVertices.add(v2);
 				
-				clippedVertices.add(clipInput[4]);
-				clippedVertices.add(clipInput[5]);
+				clippedVertices.add(x3);
+				clippedVertices.add(y3);
 				clippedVertices.add(light);
 				if (twoColor) clippedVertices.add(dark);
 				clippedVertices.add(u3);

+ 89 - 60
spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/utils/SutherlandHodgmanClipper.java

@@ -1,67 +1,91 @@
 
 package com.esotericsoftware.spine.utils;
 
-import com.badlogic.gdx.math.Intersector;
 import com.badlogic.gdx.math.Vector2;
 import com.badlogic.gdx.utils.FloatArray;
 
 public class SutherlandHodgmanClipper {
 	Vector2 tmp = new Vector2();
 	final FloatArray scratch = new FloatArray();
-	
+
+	public boolean clip(float x1, float y1, float x2, float y2, float x3, float y3, FloatArray clippingArea,
+			FloatArray output) {
+		boolean isClockwise = clockwise(clippingArea);
+		return clip(x1, y1, x2, y2, x3, y3, clippingArea, output, isClockwise);
+	}
+
 	/**
-	 * Clips the input triangle against the convex clipping area. If the triangle lies entirely
-	 * within the clipping area, false is returned.
+	 * Clips the input triangle against the convex clipping area. If the
+	 * triangle lies entirely within the clipping area, false is returned. The
+	 * clipping area must duplicate the first vertex at the end of the vertices
+	 * list!
 	 */
-	public boolean clip (float[] triangle, int offset, int length, int stride, FloatArray clippingArea, FloatArray output) {
-		boolean isClockwise = clockwise(clippingArea);
-		
-		FloatArray originalOutput = output;
+	public boolean clip(float x1, float y1, float x2, float y2, float x3, float y3, FloatArray clippingArea,
+			FloatArray output, boolean isClockwise) {
+		final FloatArray originalOutput = output;
 		boolean clipped = false;
 		
-		FloatArray input = scratch;
-		input.clear();
-		for (int i = offset; i < offset + length; i+= stride) {
-			input.add(triangle[i]);
-			input.add(triangle[i + 1]);
+		FloatArray input = null;
+		// avoid copy at the end
+		if ((clippingArea.size / 2) % 2 != 0) {
+			input = output;
+			output = scratch;
+		} else {
+			input = scratch;
 		}
+		 
+		input.clear();
+		input.add(x1);
+		input.add(y1);
+		input.add(x2);
+		input.add(y2);
+		input.add(x3);
+		input.add(y3);
+		input.add(x1);
+		input.add(y1);
 		output.clear();
-		
-		for (int i = 0; i < clippingArea.size; i += 2) {
-			float edgeX = clippingArea.items[i];
-			float edgeY = clippingArea.items[i + 1];
-			float edgeX2 = clippingArea.items[(i + 2) % clippingArea.size];
-			float edgeY2 = clippingArea.items[(i + 3) % clippingArea.size];
-			
+
+		final float[] clippingVertices = clippingArea.items;
+		final int clippingVerticesLength = clippingArea.size - 2;
+		for (int i = 0; i < clippingVerticesLength; i += 2) {
+			float edgeX = clippingVertices[i];
+			float edgeY = clippingVertices[i + 1];
+			float edgeX2 = clippingVertices[i + 2];
+			float edgeY2 = clippingVertices[i + 3];
+
 			if (!isClockwise) {
 				float tmp = edgeX;
 				edgeX = edgeX2;
 				edgeX2 = tmp;
-				
+
 				tmp = edgeY;
 				edgeY = edgeY2;
 				edgeY2 = tmp;
 			}
 
-			for (int j = 0; j < input.size; j += 2) {
-				float inputX = input.items[j % input.size];
-				float inputY = input.items[(j + 1) % input.size];
-				float inputX2 = input.items[(j + 2) % input.size];
-				float inputY2 = input.items[(j + 3) % input.size];
-				
-				int side = pointLineSide(edgeX2, edgeY2, edgeX, edgeY, inputX, inputY);
-				int side2 = pointLineSide(edgeX2, edgeY2, edgeX, edgeY, inputX2, inputY2);
-				
+			final float deltaX = edgeX - edgeX2;
+			final float deltaY = edgeY - edgeY2;
+
+			final float[] inputVertices = input.items;
+			final int inputVerticesLength = input.size - 2;
+
+			for (int j = 0; j < inputVerticesLength; j += 2) {
+				final float inputX = inputVertices[j];
+				final float inputY = inputVertices[j + 1];
+				final float inputX2 = inputVertices[j + 2];
+				final float inputY2 = inputVertices[j + 3];
+
+				final int side = pointLineSide(deltaX, deltaY, edgeX2, edgeY2, inputX, inputY);
+				final int side2 = pointLineSide(deltaX, deltaY, edgeX2, edgeY2, inputX2, inputY2);
+
 				// v1 inside, v2 inside
 				if (side >= 0 && side2 >= 0) {
 					output.add(inputX2);
 					output.add(inputY2);
-				} 
+				}
 				// v1 inside, v2 outside
 				else if (side >= 0 && side2 < 0) {
-					if (!Intersector.intersectLines(edgeX, edgeY, edgeX2, edgeY2, inputX, inputY, inputX2, inputY2, tmp)) {
-						throw new RuntimeException("Lines should intersect, but didn't");
-					}
+					intersectLines(edgeX, edgeY, edgeX2, edgeY2, inputX, inputY, inputX2, inputY2, tmp);
 					output.add(tmp.x);
 					output.add(tmp.y);
 					clipped = true;
@@ -73,9 +97,7 @@ public class SutherlandHodgmanClipper {
 				}
 				// v1 outside, v2 inside
 				else if (side < 0 && side2 >= 0) {
-					if (!Intersector.intersectLines(edgeX, edgeY, edgeX2, edgeY2, inputX, inputY, inputX2, inputY2, tmp)) {
-						throw new RuntimeException("Lines should intersect, but didn't");
-					}
+					intersectLines(edgeX, edgeY, edgeX2, edgeY2, inputX, inputY, inputX2, inputY2, tmp);
 					output.add(tmp.x);
 					output.add(tmp.y);
 					output.add(inputX2);
@@ -84,50 +106,57 @@ public class SutherlandHodgmanClipper {
 				}
 			}
 			
-			if (i < clippingArea.size - 2) {
+			output.add(output.items[0]);
+			output.add(output.items[1]);
+
+			if (i < clippingVerticesLength - 2) {
 				FloatArray tmp = output;
 				output = input;
 				output.clear();
 				input = tmp;
 			}
 		}
-		
+
 		if (originalOutput != output) {
 			originalOutput.clear();
 			originalOutput.addAll(output.items, 0, output.size);
 		}
 		
+		originalOutput.setSize(originalOutput.size - 2);
+
 		return clipped;
 	}
-	
-	private int pointLineSide(float lineX, float lineY, float lineX2, float lineY2, float pointX, float pointY) {
-		return (int)Math.signum((lineX2 - lineX) * (pointY - lineY) - (lineY2 - lineY) * (pointX - lineX));
+
+	private int pointLineSide(float deltaX, float deltaY, float lineX, float lineY, float pointX, float pointY) {
+		return (int) Math.signum(deltaX * (pointY - lineY) - deltaY * (pointX - lineX));
 	}
-	
-	public static boolean intersectLines (float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4,
-		Vector2 intersection) {
-		float d = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
-		if (d == 0) return false;
-
-		if (intersection != null) {
-			float ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / d;
-			intersection.set(x1 + (x2 - x1) * ua, y1 + (y2 - y1) * ua);
-		}
-		return true;
+
+	public static void intersectLines(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4,
+			Vector2 intersection) {
+		float c0 = y4 - y3;
+		float c1 = x2 - x1;
+		float c2 = x4 - x3;
+		float c3 = y2 - y1;
+		float d = c0 * c1 - c2 * c3;
+
+		float ua = (c2 * (y1 - y3) - c0 * (x1 - x3)) / d;
+		intersection.set(x1 + (x2 - x1) * ua, y1 + (y2 - y1) * ua);
 	}
 
-	public static boolean clockwise (FloatArray poly) {
+	public static boolean clockwise(FloatArray poly) {
 		return area(poly) < 0;
 	}
 
-	public static float area (FloatArray poly) {
+	public static float area(FloatArray poly) {
 		float area = 0;
 
-		for (int i = 0; i < poly.size; i += 2) {
-			float x = poly.items[i];
-			float y = poly.items[i + 1];
-			float x2 = poly.items[(i + 2) % poly.size];
-			float y2 = poly.items[(i + 3) % poly.size];
+		final float[] polyVertices = poly.items;
+		final int polySize = poly.size;
+		for (int i = 0; i < polySize; i += 2) {
+			float x = polyVertices[i];
+			float y = polyVertices[i + 1];
+			float x2 = polyVertices[(i + 2) % poly.size];
+			float y2 = polyVertices[(i + 3) % poly.size];
 
 			area += x * y2 - y * x2;
 		}