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

This commit is contained in:
badlogic 2017-03-27 20:39:20 +02:00
parent 019a8e6587
commit 8866925570
4 changed files with 135 additions and 100 deletions

View File

@ -116,7 +116,8 @@ public class ClippingTest extends ApplicationAdapter {
} }
public void render () { 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.glClearColor(0.3f, 0.3f, 0.3f, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

View File

@ -174,12 +174,6 @@ public class SoftwareClippingTest extends ApplicationAdapter {
} }
private void clip () { private void clip () {
boolean clipped = clipper.clip(triangle, 0, triangle.length, 5, clippingPolygon, clippedPolygon);
System.out.println("Clipped: " + clipped);
if (clipped) {
clippedPolygonVertices.clear();
clippedPolygonIndices.clear();
float x1 = triangle[0]; float x1 = triangle[0];
float y1 = triangle[1]; float y1 = triangle[1];
float x2 = triangle[5]; float x2 = triangle[5];
@ -187,6 +181,17 @@ public class SoftwareClippingTest extends ApplicationAdapter {
float x3 = triangle[10]; float x3 = triangle[10];
float y3 = triangle[11]; 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 d0 = y2 - y3; float d0 = y2 - y3;
float d1 = x3 - x2; float d1 = x3 - x2;
float d2 = x1 - x3; float d2 = x1 - x3;
@ -222,6 +227,8 @@ public class SoftwareClippingTest extends ApplicationAdapter {
} else { } else {
clippedPolygon.clear(); clippedPolygon.clear();
} }
clippingPolygon.setSize(clippingPolygon.size - 2);
} }
public static void main (String[] args) { public static void main (String[] args) {

View File

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

View File

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