Spot the difference

Published June 25, 2007
Advertisement
Original version:
	public void addToBuffer(Buffers dest)	{		float innerRadius = radius - edgeWidth;		innerRadius = Math.max(innerRadius, 0);				final float outerRadius = radius;				final float vTexel = (1f / edgeImage.getTexture().getHeight()) / 2;						for (int i=0; i		{			float angle0 = (((float)Math.PI*2f)/(NUM_SUBDIVISIONS)) * i;			float angle1 = (((float)Math.PI*2f)/(NUM_SUBDIVISIONS)) * (i+1);						angle0 += angleOffset;			angle1 += angleOffset;									final float alpha0 = i / (float)(NUM_SUBDIVISIONS);			final float u0 = getU(alpha0, edgeImage);						final float alpha1 = (i+1) / (float)(NUM_SUBDIVISIONS);			final float u1 = getU(alpha1, edgeImage);						// --- First edge ---			dest.vertex( coord(innerRadius*(float)Math.cos(angle0) * xScale + x,								innerRadius*(float)Math.sin(angle0) * yScale + y),						 textureCoord(u0, edgeImage.getTy1()-vTexel),						 colour(red, green, blue, alpha) );									dest.vertex( coord(outerRadius*(float)Math.cos(angle0) * xScale + x,								outerRadius*(float)Math.sin(angle0) * yScale + y),						 textureCoord(u0, edgeImage.getTy0()+vTexel),						 colour(red, green, blue, alpha) );									// --- Second edge ---			dest.vertex( coord(outerRadius*(float)Math.cos(angle1) * xScale + x,								outerRadius*(float)Math.sin(angle1) * yScale + y),						 textureCoord(u1, edgeImage.getTy0()+vTexel),						 colour(red, green, blue, alpha) );									dest.vertex( coord(innerRadius*(float)Math.cos(angle1) * xScale + x,								innerRadius*(float)Math.sin(angle1) * yScale + y),						 textureCoord(u1, edgeImage.getTy1()-vTexel),						 colour(red, green, blue, alpha) );		}			}		private static float getU(float alpha, SpriteImage image)	{		// Offset the u coord by half a texel so that the left and right texels of the		// sprite map directly next to each other.		// Otherwise we get a seam due to texture filtering				final float halfUTexel = (1f / image.getTexture().getWidth()) / 2f;				final float invAlpha = 1f - alpha;				return ((image.getTx0()+(halfUTexel)) * invAlpha) + ((image.getTx1()-halfUTexel) * alpha);	}


Optimised version:
public void addToBuffer(Buffers dest)	{		// Loop constants		float innerRadius = radius - edgeWidth;		innerRadius = Math.max(innerRadius, 0);				final float outerRadius = radius;				final float vTexel = (1f / edgeImage.getTexture().getHeight()) / 2;		final float halfUTexel = (1f / edgeImage.getTexture().getWidth()) / 2f;				final float ty0 = edgeImage.getTy0() + vTexel;		final float ty1 = edgeImage.getTy1() - vTexel;				// Start at angleOffset and add angleInc each iteration		final float angleInc = ((float)Math.PI*2f)/NUM_SUBDIVISIONS;		float angle0 = angleOffset;		float angle1 = angleOffset + angleInc;				// Calculate the first pair of cos/sin, then reuse the previous iteration's values after that		float cosAngle1 = (float)Math.cos(angle0);		float sinAngle1 = (float)Math.sin(angle0);		float cosAngle0, sinAngle0;				// Start with the initial alpha0, then reuse the previous iteration's values after that		float alpha0;		float alpha1 = 0;				for (int i=0; i		{			// Reuse the texture alpha from the last iteration			alpha0 = alpha1;			alpha1 = (i+1) / (float)NUM_SUBDIVISIONS;						// Calculate texture interpolation texture coords			final float invAlpha0 = 1f - alpha0;			final float u0 = ((edgeImage.getTx0()+(halfUTexel)) * invAlpha0) + ((edgeImage.getTx1()-halfUTexel) * alpha0);						final float invAlpha1 = 1f - alpha1;			final float u1 = ((edgeImage.getTx0()+(halfUTexel)) * invAlpha1) + ((edgeImage.getTx1()-halfUTexel) * alpha1);			// Reuse the cos/sin from last iteration			cosAngle0 = cosAngle1;			sinAngle0 = sinAngle1;			cosAngle1 = (float)Math.cos(angle1);			sinAngle1 = (float)Math.sin(angle1);						// Geometry coord generation			final float cosAngle0Scaled = cosAngle0 * xScale;			final float sinAngle0Scaled = sinAngle0 * yScale;						final float cosAngle1Scaled = cosAngle1 * xScale;			final float sinAngle1Scaled = sinAngle1 * yScale;						// --- First edge ---			dest.vertex( innerRadius * cosAngle0Scaled + x,						 innerRadius * sinAngle0Scaled + y,						 u0, ty1,						 red, green, blue, alpha);						dest.vertex( outerRadius * cosAngle0Scaled + x,						 outerRadius * sinAngle0Scaled + y,						 u0, ty0,						 red, green, blue, alpha);						// --- Second edge ---			dest.vertex( outerRadius * cosAngle1Scaled + x,						 outerRadius * sinAngle1Scaled + y,						 u1, ty0,						 red, green, blue, alpha);						dest.vertex( innerRadius * cosAngle1Scaled + x,						 innerRadius * sinAngle1Scaled + y,						 u1, ty1,						 red, green, blue, alpha);						angle0 = angle1;			angle1 += angleInc;		}			}


I wanted to add some water ripples to Rescue Squad, but adding too many ring geometries really hit the framerate. Cracking out the profiler shows that the geometry generation (above) was taking most of the time, so I started tinkering.

The two biggest gains were inlining the getU call, and reusing the sin and cos calls from the previous iteration instead of recalculating them. Then a whole bunch of lesser optimisations like extracting out duplicate calculations and moving as much work out of the loop as possible gave small but solid improvements.

Overall on my test scene of 500 rings I've managed to go from 6fps to a nice 32fps. [grin] Thats still a little slow for my liking, but thats about all the optimisations I can do here I think. The last one is making the subdivision amount dynamic (either automatic based on size, or manually set at creation time). However I'll leave this for later as I can't figure out which way would be best right now.

Suggestions for improvements are welcome, but I think I've done all that is possible without actually changing the external behaviour of the code.
Previous Entry Heh.
Next Entry Water Ripples
0 likes 0 comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement
Advertisement