OpenGL Texture Size and performance? Texture Cache? (Android\iOS GLES 2)

I am working on my dragon simulation mobile game and I am at the stage of adding a terrain to the game.

I optimized the terrain render for my iOS devices but on my Android device I was getting bad performance.

Understanding texture size and performance is kind of elusive. I was being told many times that big textures are bad but I was never able to find the correlation between texture size and performance.

I am still not sure what is the correlation between texture size, bandwidth and performance on mobile devices and I am sure it’s a complex one.

However, I did find out something else.

As I mentioned, my first attempt to copy the OpenGLES shaders from my iOS code to my Android code gave me poor results. The same scene that ran at 60 FPS on my iPod was running on 25 FPS on my Android phone.

This is how the scene looked like on my Android phone:

Slow Terrain Render

Scene rendered at 25 FPS(40 ms per frame)

For the terrain I am using two 2048×2048 ETC1 compressed textures. One for the grass and one for the rocky mountain.

Maybe my phone’s performance is really not as good as my iPod? But then, something was missing.

On my iPod I was already using mipmapped textures while on the first attempt of the Android version I didn’t use mipmapped textures.

Mipmapped textures are texture which not only contain the texture itself but also all (or some) of the smaller versions of the same texture image.

If you have a texture of size 16×16 pixels then a mipmapped texture will contain both the 16×16 image but also the 8×8, 4×4, 2×2 and 1×1 resolutions of the same image.

This is useful because it’s hard to scale down a texture on the GPU without losing details. The mipmapped images are precalculated offline and may use the best algorithms to reduce the image.

When rendering with mipmapped textures the GPU selects the mipmapped image that is the most suitable for the current scaling in the scene.

But apart from looking better, there is another advantage. Performance.

The same scene using mipmapped version of the 2048×2048 textures runs a lot faster than before. I could get a scene render at about 50 to 60 FPS.

The reason for that is that textures have a 2D spatial cache.

In this scene the mountain and grass textures are scaled down considerabley. This in turn makes the GPU sample the textures in texel(texture pixels) that are far from each other making no use of the cache.

In order to make use of the cache the sampling of the texture must have spatial proximity.

When using the mipmapped version of the texture a much smaller layer of the 2048×2048 texture was sampled and thus it was possible to make use of the cache for this specific image.

For the sake of completion, here is the scene with the mipmapped textures:

Runs at about 50-60 FPS(17-20 ms)

Runs at about 50-60 FPS(17-20 ms)

Location 0? location -1? glGetUniformLocation, C++ and bugs. (Android\iOS GLES 2)

In OpenGLES 2 glGetUniformLocation receives the program id and a string as parameters. It then attempts to return a location int that can be used to set uniform GLSL shader variables.

If the variable is found it will return a 0 or positive value. If it fails to find the uniform variable it will return -1.

In C++ we should initialize the location ints in the ctr. If we don’t initialize the locations we might have garbage values when in Release mode.

Using the locations with garbage values might overwrite uniform variables with values we did not intend them to have.

So what we should initialize the locations with? One might think that 0 is a good value to initialize but it is not.

Remember! 0 is a valid shader uniform variable location. If we set all the locations to 0 we might overwrite the uniform variable at location 0.

We should initialize the location ints with -1.

We should do this because -1  is the value that is returned in case the uniform variable was not found and setting a value at location -1 will be ignored.

glDepthFunc and GL_LEQUAL for background drawing in an OpenGL ES 2 Android\iOS game.

For the (semi secret)game I am currently working on(http://www.PompiPompi.net/) I had to implement the background or backdrop of the 3D world.

A simple method to draw the background is having a textured sphere mesh surround the camera.

The issue with this method is that the sphere is not perfect so there are distortions and it doesn’t completely fit the viewing frustum. Which means some geometry that is inside our frustum will be occluded by the sphere.

A different method is to draw a screen space quad at the exact back end of the frustum. This quad will have coordinates in screen space and won’t require transformation.

The back of the plane in screen space coordinates is 1.

You could disable writing into the ZBuffer with glDepthMask(false) and draw the background first. All the geometry that renders afterwards will overwrite the background.

However, what if we want to draw the background last and save on fill rate by using the ZBuffer?

Just drawing the background last should have done that but instead it might not render at all.

We usually clear the depth buffer part of the render buffer into 1.0 which is the highest value for the depth buffer. But our background screen mesh is also rendered into 1!

It turns out the default depth test function in OpenGLES is GL_LESS. Since the ZBuffer is already 1.0 our background screen mesh won’t render.

What we can do is before rendering the screen mesh, we can set the depth test function  into less or equal by calling: glDepthFunc(GL_LEQUAL);

This way our background buffer will not draw at pixels that we have drawn geometry before but will draw on the rest of the “blank” pixels.

Calling a multiple parameters Java method with JNI (Loading ETC1 for Android)

Calling a Java method from C++ with JNI is kind of difficult since you don’t have static type compilation errors.

I was calling a Java method from my Android OpenGL graphics module in order to load an ETC1 texture with this command:

jmethodID mid = env->GetMethodID(cls, "LoadETC1", "(Ljava/lang/String; [I [I)I");

The Java method I was trying to call was the following:

		public int LoadETC1(String s, int[] w1, int[] h1)
		{
			if (!ETC1Util.isETC1Supported())
				return 0;
			try {
				InputStream s2 = assetManager.open(s);
				ETC1Util.ETC1Texture t = ETC1Util.createTexture(s2);
				int textureName = -1;
				int[] texture = new int[1];
				GLES20.glGenTextures(1, texture, 0);
				textureName = texture[0];

		        GLES20.glBindTexture ( GLES20.GL_TEXTURE_2D, textureName );

//				GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);

		        int w = t.getWidth();
		        int h = t.getHeight();
		        int l = t.getData().capacity();
		        GLES20.glCompressedTexImage2D(GLES20.GL_TEXTURE_2D, 0, ETC1.ETC1_RGB8_OES, t.getWidth(), t.getHeight(), 0, t.getData().capacity(), t.getData());
//		        GLES20.glTexImage2D ( GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, w, h, 0,
//		                              GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, ib );

		        GLES20.glTexParameteri ( GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR );
		        GLES20.glTexParameteri ( GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR );
		        GLES20.glTexParameteri ( GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT );
		        GLES20.glTexParameteri ( GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT );
		        TextureNode n = new TextureNode();
		        w1[0] = t.getWidth();
		        h1[0] = t.getHeight();
		        return texture[0];
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			return 0;
		}

The issue was that  GetMethodID was returning 0. In LogCat it mentioned that my method signature is bogus “(Ljava/lang/String; [I [I)I“.

The issue actually was that I had spaces between the 3 parameters of the method. There should be no spaces and there should be ‘;’ only after a non primitive Java class like “Ljava/lang/String;”.

The following method gave a valid jmethodID:

jmethodID mid = env->GetMethodID(cls, "LoadETC1", "(Ljava/lang/String;[I[I)I");

GLSL(OpenGL Shader Language) compilation bug with for loop on Adreno 205, Android.

One of the biggest advantages of the OpenGL API specification is that OpenGL is language agnostic.

That means it can be implemented on almost any programming language which makes it a very portable library.

However, there is a serious issue with OpenGL. It’s shader language(GLSL) has no specification for compilation. You can’t rely on binary files of compiled shaders to work on different devices.

Not only that but compiling the GLSL source code while running the app on different devices might produce different results or even silent bugs(Depending on driver implementation).

My game Shotgun Practice was running perfectly on my device(Galaxy Note N7000) but didn’t work on my friend’s device(HTC Desire Z).

On my friend’s ‘HTC Desire Z‘ Android device with the ‘Adreno 205‘ GPU it had graphics artifacts.

After quite some tests I found that a specific shader was the culprit. That shader was the vertex shader of skinned objects.

It took me a lot of tests because the driver for HTC Desire Z didn’t report any error or warning upon compiling and validating the skinning shader.

Eventually it boiled down to the part of code that transforms the vertices with the relevant bones.

Doesn’t work on HTC Desire Z

for(int i = 0; i < 4; ++i)
{
	mat4 m = BoneTransform[Index[i]];
	posOut += (w[i]*m*vec4(position, 1.0)).xyz;
	normalOut += (w[i]*m*vec4(normal, 0.0)).xyz;
}

Works on HTC Desire Z

mat4 m = BoneTransform[Index[0]];
posOut += (w[0]*m*vec4(position, 1.0)).xyz;
normalOut += (w[0]*m*vec4(normal, 0.0)).xyz;
m = BoneTransform[Index[1]];
posOut += (w[1]*m*vec4(position, 1.0)).xyz;
normalOut += (w[1]*m*vec4(normal, 0.0)).xyz;
m = BoneTransform[Index[2]];
posOut += (w[2]*m*vec4(position, 1.0)).xyz;
normalOut += (w[2]*m*vec4(normal, 0.0)).xyz;
m = BoneTransform[Index[3]];
posOut += (w[3]*m*vec4(position, 1.0)).xyz;
normalOut += (w[3]*m*vec4(normal, 0.0)).xyz;

As you can see the code that doesn’t work has a ‘for loop’ and in the code that works I manually unrolled the ‘for loop’.

I also tested if the issue was that ‘mat4 m’ was inside the ‘for loop’ block or that using a hard coded number of iterations would cause a faulty loop unrolling.

Neither attempts worked. I don’t know exactly what is the driver issue with this but I was told you should use ‘for loops’ very cautiously in GLSL meant for mobile devices.

Conclusion

Beware of ‘for loops’ and generally branching in GLSL meant for mobile devices.

But even worse, some drivers(hopefully only old devices) might not warn you that the shader isn’t going to work on the device even though it passed all the validation.

SoundPool doesn’t loop? Android

In my new small Android racing game called ‘Diesel Racer’ I have the need to play an engine sound in a loop.

I play my sounds in Java using SoundPool.

My first attempt to play a sound in a loop was calling the method setLoop  just before playing the sound like so:

Pool.setLoop(sampleId, -1);

That did not have any affect and the sound was playing just once.

I tried to put the same function after I called the play method but it didn’t help either.


I have later found that the play method has a ‘loop’ parameter as well. Since in the past I only needed to play the sound once, I passed 0 to the ‘loop’ parameter which would set the sound to play once.

Setting the ‘loop’ value before calling play will have no effect.

However, I am not sure why setting the ‘loop’ parameter after using play didn’t have any effect either.

In order to play the sound looping forever I just passed -1 to the ‘loop’ parameter when using play.

(I am still not sure how am I suppose to use setLoop).

For the sake of completion here is the code I use to play a SoundPool sound looped:

					try {
						n.SoundID = Pool.load(a.assetManager.openFd(n.Path), Thread.NORM_PRIORITY);
						if (n.IsLoop)
						{
							Pool.setOnLoadCompleteListener(new SoundPool.OnLoadCompleteListener() {
							    public void onLoadComplete(SoundPool Pool, int sampleId,int status) {
									Pool.play(sampleId, 1, 1, 0, -1, 1);
							    }
							});
						}
					} catch (IOException e) {
						e.printStackTrace();
					}

Do notice that this code will loop both ‘.ogg’ and ‘.wav’. I saw some post somewhere that said ‘.ogg’ might not be looped with a SoundPool, but ‘.ogg’ does loop for me with this code.

Shotgun Practice! Quick game dev.

It took me only a few days to create this small Android game, and to my surprise it is actually a fun game.

I figured I won’t learn how to design games if it takes me 2 years to finish each game. In addition it’s a lot more exciting and motivating to release a game to the Android app store.

So without further ado, please download and try Shotgun Practice(it’s free!):

ShotgunIcon144