Android: OpenGL textures too large, can't figure out why - android

Why do my textures seemingly take up so much space?
My app, which uses opengl heavily, produces the following heap stats* during operation:
Used heap dump 1.8 MB
Number of objects 49,447
Number of classes 2,257
Number of class loaders 4
Number of GC roots 8,551
Format hprof
JVM version
Time 1:08:15 AM GMT+02:00
Date Oct 2, 2011
Identifier size 32-bit
But, when I use the task manager on my phone to look at the ram use of my application it says my app uses 44.42MB. Is there any relationship between heap size use and ram use? I think much of that 42MB must be my open GL textures, but I can't figure out why they take up so much space, because on disk all the files together take only take up 24MB (and they are not all loaded at the same time). And I'm even making many of them smaller by resizing the bitmap prior to texture loading. I also dynamically create some textures, but also destroy those textures after use.
I am using OpenGL 1.0 and Android 2.2, typical code that I use to load a texture looks like this:
static int set_gl_texture(Bitmap bitmap){
bitmap = Bitmap.createScaledBitmap(bitmap, 256, 256, true);
// generate one texture pointer
mGL.glGenTextures(1, mTextures, 0);
mGL.glBindTexture(GL10.GL_TEXTURE_2D, mTextures[0]); // A bound texture is
// an active texture
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0,GL10.GL_RGBA, bitmap, 0);
// create nearest filtered texture
mGL.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER,
GL10.GL_LINEAR); // This is where the scaling algorithms are
mGL.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER,
GL10.GL_LINEAR); // This is where the scaling algorithms are
mGL.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,
GL10.GL_CLAMP_TO_EDGE);
mGL.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,
GL10.GL_CLAMP_TO_EDGE);
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
bitmap.recycle();
Log.v("GLSurfaceView", "Loading Texture Finished, Error Codes:"+mGL.glGetError());
return mTextures[0];
}
Code which I use to load bitmaps looks like the following:
public int load_texture(int res_id){
if(mBitmapOpts==null){
mBitmapOpts = new BitmapFactory.Options();
mBitmapOpts.inScaled = false;
}
mBtoLoad = BitmapFactory.decodeResource(MyApplicationObject.getContext().getResources(),res_id, mBitmapOpts);
assert mBtoLoad != null;
return GraphicsOperations.set_gl_texture(mBtoLoad);
}
*hprof file analyzed using mat, same data is generated by eclipse ddms

PNGs are compressed images. In order for OpenGL to use them, the pngs must be decompressed. This decompression will increase the memory size.
You may want to decrease the size of some of the textures somehow. Maybe instead of using 512x512 images, use 256x256 or 128x128. Some textures that you use may not need to be so large since they are going onto a mobile device with a limited screen size.

Related

Why POT textures work slower than non-pot?

I use following function to load textures
public static int loadTexture(Bitmap bmp)
{
final int[] textureHandle = new int[1];
GLES20.glGenTextures(1, textureHandle, 0);
if (textureHandle[0] != 0)
{
// Bind to the texture in OpenGL
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle[0]);
// Set filtering
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.glGenerateMipmap(textureHandle[0]);
//adapt texture to POT
int adaptedWidth= (int) Math.pow(2,Math.ceil(Math.log(bmp.getWidth())/Math.log(2d)));
int adaptedHeight= (int) Math.pow(2,Math.ceil(Math.log(bmp.getHeight())/Math.log(2d)));
Log.d("texture",adaptedWidth+","+adaptedHeight);
Bitmap tmp = Bitmap.createScaledBitmap(bmp, adaptedWidth, adaptedHeight, false);
Log.d("asize",tmp.getWidth()+","+tmp.getHeight());
// Load the bitmap into the bound texture.
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, tmp, 0);
//GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bmp, 0);
tmp.recycle();
// Recycle the bitmap, since its data has been loaded into OpenGL.
//bmp.recycle();
}
if (textureHandle[0] == 0)
{
throw new RuntimeException("Error loading texture.");
}
return textureHandle[0];
}
I got 14-17 fps with this code.Hovever if I load my bitmap(which is non POT) directly without adaptation to POT.FPS jumps to 28-30.I thought POT textures should work faster then non-POT.Is there explanation for this?
UPD:Rendering code:
#Override
public void onDrawFrame(GL10 gl) {
//curScale=modelMatrix[SCALE_X];
TimeMeasurer.reset();
long curTS= SystemClock.uptimeMillis();
long frameRenderTime=curTS-ts;
//Log.d("renderer","FPS:"+1000.0/frameRenderTime);
Log.d("renderer","frame render time:"+frameRenderTime);
ts=curTS;
GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
if (piecesMesh!=null) {
Matrix.setIdentityM(MVPMatrix,0);
Matrix.multiplyMM(MVPMatrix,0,projMatrix,0,modelMatrix,0);
drawPassivePieces();
drawActivePieces();
if (helper!=null) {
drawHelper();
}
}
TimeMeasurer.measure("onDrawFrame execution time:");
}
private void drawPassivePieces() {
//shadows
shadowProgram.useProgram();
shadowProgram.setUniforms(MVPMatrix,textureMaskId);
shadowMesh.bindPieceData(shadowProgram,false);
shadowMesh.drawPieces(false);
shadowMesh.disableAttributes(shadowProgram);
//pieces
piecesProgram.useProgram();
piecesProgram.setUniforms(MVPMatrix, textureImageId, textureMaskId);
piecesMesh.bindPieceData(piecesProgram,false);
piecesMesh.drawPieces(false);
piecesMesh.disableAttributes(piecesProgram);
}
private void drawActivePieces() {
//shadows
shadowProgram.useProgram();
shadowProgram.setUniforms(MVPMatrix,textureMaskId);
shadowMesh.bindPieceData(shadowProgram,true);
shadowMesh.drawPieces(true);
shadowMesh.disableAttributes(shadowProgram);
//pieces
piecesProgram.useProgram();
piecesProgram.setUniforms(MVPMatrix, textureImageId, textureMaskId);
piecesMesh.bindPieceData(piecesProgram,true);
piecesMesh.drawPieces(true);
piecesMesh.disableAttributes(piecesProgram);
}
public void drawHelper() {
helperProgram.useProgram();
helper.bindData(helperProgram);
helper.draw();
helper.disableAttributes(helperProgram);
}
Without a detailed performance analysis, it's not really possible to do more than speculate.
One likely cause is that your rendering is limited by memory bandwidth of texture sampling. If you make the texture larger, the total amount of memory accessed is larger, causing the slowdown.
Or, very related to the above, your cache hit rate for texture sampling drops if the sampled texels are spread out farther in memory, which happens when you upscale the texture. Lower cache hit rate means slower performance.
You really shouldn't artificially make the texture larger than necessary, unless it's needed due to the limited NPOT support in ES 2.0, and the hardware you use does not advertise the OES_texture_npot extension. I doubt that anybody has made hardware that prefers POT textures in a long time.
There are big advantages to using POT textures. In OpenGLES 2.0 they allow you to use mipmaps and useful texture addressing modes like repeat. You also can utilize memory more efficiently because lots of implementations allocate memory as if your texture is POT anyway.
However, in this case where you just take a non-POT texture and scale it up, I'd expect performance to be slightly worse as a result. You're missing out on the big potential win because you're not using mipmaps. By using a larger texture you're just asking more of the GPU's texture cache because the useful parts of the image are now more spread out in memory than they were previously.
Another way to look at it is that in the absence of mipmapping, big textures are going to perform worse than little textures, and your rescaling process is just making your texture bigger.
I'm surprised the difference is so noticeable though - are you sure that your rescaling codepath isn't doing anything unexpected like resizing too large, or picking a different texture format or filter mode?

Common reasons for OpenGL Textures not being rendered

I just heard from a user who says that my (Android OpenGL ES 2.0) app (a game) won't run on his HTC 1X+ handset. All he gets is the music and the banner ad at the top of the screen and nothing else.
Unfortunately I don't have an HTC 1X+ to test this on.
Some notes:
Although my textures are not power of 2, I'm only using GLES20.GL_CLAMP_TO_EDGE
From what I've read, the HTC 1X+ has a max Texture Size of 2048 x 2048 and it gets it's resources from the XHDPI folder (annoyingly), even so, I have only 1 texure that exceeds that size, all other objects displayed on my app's opening page use textures much smaller than this max amount, so something should be displayed.
I'm not using texture compression of any kind
My app runs quite happily on the 15 (aprox) other devices I, and others have tested it on - just the 1x (so far) is giving problems.
Can anyone point out some common issues with OpenGL ES 2.0 that could be causing these textures not to be rendered? Are there any quirks with certain Android versions or devices?
I haven't yet posted any code simply because the app works on most devices, and I'm not sure which parts of the code would be helpful, but if any code is required, please just ask.
Edit - including texture loading code
public static int LoadTexture(GLSurfaceView view, Bitmap imgTex){
//Array for texture
int textures[] = new int[1];
try {
//texture name
GLES20.glGenTextures(1, textures, 0);
//Bind textures
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);
//Set parameters for texture
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
//Apply texture
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, imgTex, 0);
//clamp texture
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,GLES20.GL_CLAMP_TO_EDGE);
} catch (Exception e){
}
//Increase texture count
textureCount++;
//Return texture
return textures[0];
}
Have you checked the sampler properties?
The settings look correct, but why not also specify the GL_TEXTURE_WRAP_S setting? Use GLES20.glTexParameteri for integer values.
What type of bitmap are you using?
Try to force the internal format to GL_RGBA, GL_BGR, GL_RGB
Do you properly unbind or bind to a different texture?
Other texture settings may be causing havoc in other parts of the code..
Do you specify the correct texture unit in your shader?
Print out the shader's sampler attribute position so you know this is correct, and make sure to bind the texture to it explicitly during rendering.

Android OpenGL ES texture TextImage2D error

Due to some unfathomable reason, the textures won't load.
Here's the method
public static void loadGLTexture(GL10 gl, Context context){
BitmapFactory.Options options = new BitmapFactory.Options();
options.inDensity = 240;// needed so that the image will be 512x512
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.glasstexture, options);
int width = bitmap.getWidth();
int height = bitmap.getHeight();
Log.i("GridLoginSquare.loadGLTexture 96", "Bitmap:{w:" + width + " h:" + height + "}");
gl.glGenTextures(1, textures, 0);
gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
// create nearest filtered texture
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
// Use Android GLUtils to specify a two-dimensional texture image from our bitmap
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0); //error is on this line
Log.e("GridLoginSquare.loadGLTexture 102", " ERR "+gl.glGetError());
// Clean up
bitmap.recycle();
}
The size of my texture image is 405 x 512
The line Log.e("GridLoginSquare.loadGLTexture 102", " ERR "+gl.glGetError()); gives
me a 1281 error which (after investigating others who had similar problems) means
INVALID_VALUE
The above code is a part of the overall GridLoginSquare class which is here
NOTE This problem had already persisted before BitmapFactory.Options density was included to scale the image. Similarly the texture uv was divided by 405 and 512 when this was included in the gist code.
I have had this problem when I did not put my textures in the drawable-nodpi folder. Try moving your textures into that folder.
Also, OpenGL prefers your textures to have dimensions that are powers of two. I am not sure if this would cause the textures to not appear at all, but you may as well try making your texture images 512x512 or 256x256 just to see.
I had exactly the same error at textImage2D with glGetError returning 0, and WindyB's solution of using images whose width & length are powers of two... DID WORK form me! Thank you very much! :)
This error took place only in Android 2 (exactly Android 2.3.3). In Android 4 it always worked fine (I like to test my Apps in different Android versions).
By the way, I had no need of creating folder 'drawable-nodpi', I have it all stacked in just 'drawable'
In case it helps: At http://developer.android.com/guide/topics/graphics/opengl.html#manifest under 'Declaring OpenGL Requirements' it says that some texture compression formats are not compatible with some devices.. Maybe your problem has something to do with this?
I have my textures on drawable-nodpi folder (which we have to create) and was getting the same error 1281 after call this function: GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bmp, 0);
And it was being caused because my textures wasn't at power of 2, so after I fix it (leave it at power of 2), it works fine, only leaving a texture bleeding (when a tiny undesired part of the texture appears on the borders), but this is solved adjusting the values of the uvs coordinates.

Handling large bitmaps on Android - int[] larger than max heap size

I'm using very large bitmaps and I store data in a big int[]. The images can be really large and I can't downsample them (I'm getting the bitmaps over the wire and rendering them).
The problem I'm hitting is on very large bitmaps (bitmap size = 64MB), where I try to allocate int array with size 16384000. I'm testing this on Samsung Galaxy SII, which should have enough memory to handle this, but it seems there is a "cap" on heap size. The method Runtime.getRuntime().maxMemory() returns 64MB, so this is the max heap size for this particular device.
The API level is set to 10, so I can't use android:largeHeap attribute suggested elsewhere (and I don't even know if that would help).
Is there any way to allocate more than 64MB? I tried allocating the array in native (using JNI NewIntArray function), but that fails as well. It seems as though it is bound by the same limit as jvm.
I could however allocate memory on the native side using NewDirectByteBuffer, but since this byte buffer is not backed by an array, I can not get access to int[] (using asIntBuffer().array() in java which I need in order to display the image using setPixels or createBitmap. I guess OpenGL would be a way to go, but I have (so far) 0 experience with OpenGL.
Is there a way to somehow access allocated memory as int[] that I am missing?
So, the only way I've found so far is to allocate image using NDK. Furthermore, since Bitmap does not use existing Buffer as pixel "storage" (the method copyPixelsFromBuffer is also bound to memory limits; and judging by the method name, it copies the data anyway).
The solution (I've only prototyped it roughly) is to malloc whatever the size of the image is, and fill it using c/c++ and then use ByteBuffer in Java with OpenGLES.
The current prototype creates a simple plane and applies this image as a texture to it (luckily, OpenGLES methods take Buffer as input, which seems to work as expected). I'm using glTexImage2D to apply this buffer as a texture to a plane. Here is a sample, where mImageData is ByteBuffer allocated (and filled) on the native side.
int[] textures = new int[1];
gl.glGenTextures(1, textures, 0);
mTextureId = textures[0];
gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureId);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_REPEAT);
gl.glTexImage2D(GL10.GL_TEXTURE_2D, 0, GL10.GL_RGBA, 4000, 4096, 0, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, mImageData);
I assume OP already solved this, but if you have a Stream, you can stream the bitmap into a file and read that file using inSampleSize

Android: For how long does OpenGL store a texture?

How long does openGL store a texture for?
Does the texture memory get recycled when you leave an activity?
For example if I have the following code:
mGL.glGenTextures(1, mTextures, 0);
mGL.glBindTexture(GL10.GL_TEXTURE_2D, mTextures[0]); // A bound texture is
// an active texture
//mGL.glTexImage2D(GL10.GL_TEXTURE_2D, 0, GL10.GL_RGBA, bitmap.getWidth(),bitmap.getHeight(), 0, GL10.GL_RGBA, GL10.GL_FLOAT, textures);
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0,GL10.GL_RGBA, bitmap, 0);
// create nearest filtered texture
mGL.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER,
GL10.GL_LINEAR); // This is where the scaling algorithms are
mGL.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER,
GL10.GL_LINEAR); // This is where the scaling algorithms are
mGL.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,
GL10.GL_CLAMP_TO_EDGE);
mGL.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,
GL10.GL_CLAMP_TO_EDGE);
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
//bitmap.recycle();
Log.v("GLSurfaceView", "Loading Texture Finished");
return mTextures[0];
What changes will invalidate the return value?
I don't want to reload all my textures when I come back into an activity (say the person had a phone call) because it really slows things down.
Update:
Found this info in the Renderer documentation, which confirms the answer given below by #svdree:
EGL Context Lost
There are situations where the EGL rendering context will be lost. This typically happens when device wakes up after going to sleep. When the EGL context is lost, all OpenGL resources (such as textures) that are associated with that context will be automatically deleted. In order to keep rendering correctly, a renderer must recreate any lost resources that it still needs. The onSurfaceCreated(GL10, EGLConfig) method is a convenient place to do this.
That means that the textures are related to the EGL Context
Since posting this question I have attempted to solve the problem by having my activities inherit from a base activity that has a reference to a custom GLRenderer. Basically, I can pass the OpenGLSurface View forward (i.e. make it instantiate it in one activity and use it in the next), but once it goes through its shutdown procedure it doesn't start up again.
I have also found that making your activities transparent preserves the openGL context below the transparent activity (which makes sense, but helps only for menus and such). However I suppose it would be possible to always have every activity after the openGL activity be just a little bit transparent, thereby preserving the textures in the background across all your activities (this is probably what I will do)
When you leave an activity, your OpenGL context will be lost, which means all your textures, vertex buffer objects etc. will need to be recreated. This is typically done in the onSurfaceCreated() method of the GLSurfaceView.Renderer class.
onPause() tosses most openGL handles etc! Good luck in openGL land!

Categories

Resources