I'm new to OpenGL ES and developing a simple 2D game. However, I'm confused as to how I can go about loading multiple animation frames as textures (for the player character). I've tried to load a different image every time the character is rendered, but that's too slow.
Here is my texture loading code thus far:
public void loadGLTexture(GL10 gl, Context context) {
InputStream[] is=new InputStream[3];
is[0]= context.getResources().openRawResource(R.drawable.r1);
is[1]= context.getResources().openRawResource(R.drawable.r2);
is[2]= context.getResources().openRawResource(R.drawable.r3);
try {
bitmap[0]= BitmapFactory.decodeStream(is[0]);
bitmap[1]= BitmapFactory.decodeStream(is[1]);
bitmap[2]= BitmapFactory.decodeStream(is[2]);
} finally {
try {
is[0].close();
is[1].close();
is[2].close();
is = null;
} catch (IOException e) {
}
}
gl.glGenTextures(3, textures,0);
gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap[0], 0);
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap[1], 0);
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap[2], 0);
bitmap[0].recycle();
bitmap[1].recycle();
bitmap[2].recycle();
}
How can I make all three images accessible through an array?
You need to call glBindTexture before every texImage2D. Currently you are loading all three images into textures[0].
Don't try to load all textures at once. Change your function to load only one texture and just call it three times. You should be able to do:
textures[0]=loadGLTexture(GL10,context,R.drawable.r1);
textures[1]=loadGLTexture(GL10,context,R.drawable.r2);
textures[2]=loadGLTexture(GL10,context,R.drawable.r3);
You can place all the frames of animation on a single texture and use texture coordinates to select which one to use
Related
I've managed to load textures and free rotate a sphere thanks to several tutorials, questions and answers asked here but i stumbled upon the need of texture reloading at runtime (get a bitmap image, process it and then apply it as a new texture to an object). I didnt find any working solutions for my specific problem (i've read all related questions and answers).
Im quite new to OpenGL. This is my second project, my first 3D one and my first question asked here. So here it goes:
Basicaly the texture loading is done in the following function:
public void loadGLTexture(final Context context, final int texture) {
GLES20.glGenTextures(1, mTextures, 0);
if (mTextures[0] != 0)
{
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false;
final Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), texture, options);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextures[0]);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
bitmap.recycle();
}
if (mTextures[0] == 0)
{
throw new RuntimeException("Texture load fail");
}
}
While the draw is done in this function:
public void draw(float[] mvpMatrix) {
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, this.mTextures[0]);
GLES20.glFrontFace(GLES20.GL_CW);
for (int i = 0; i < this.mTotalNumStrips; i++) {
//vertex
this.mVertexBuffer.get(i).position(0);
GLES20.glVertexAttribPointer(mPositionHandle, NUM_FLOATS_PER_VERTEX,
GLES20.GL_FLOAT, false,
vertexStride, this.mVertexBuffer.get(i));
GLES20.glEnableVertexAttribArray(mPositionHandle);
//texture
this.mTextureBuffer.get(i).position(0);
GLES20.glVertexAttribPointer(mTextureCoordinateHandle, NUM_FLOATS_PER_TEXTURE,
GLES20.GL_FLOAT, false,
textureStride, this.mTextureBuffer.get(i));
GLES20.glEnableVertexAttribArray(mTextureCoordinateHandle);
//draw strip
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, this.mVertices.get(i).length / NUM_FLOATS_PER_VERTEX);
}
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);
// Disable the client state before leaving.
GLES20.glDisableVertexAttribArray(mPositionHandle);
GLES20.glDisableVertexAttribArray(mTextureCoordinateHandle);
}
In the above function i use the Strips tehnique to render a sphere. For each strip i have to load the texture and vertex data and finaly draw it.
I also have a function that should delete the textures that does nothing more than:
public void clearGLTextures(){
GLES20.glDeleteTextures(1, mTextures, 0);
}
What i want to achieve here is texture reloading so the plan was:
INITIALISE(works): loadGLTexture(initialtexture) -> loop the draw() function
RELOAD(does not work): clearGLTextures() -> loadGLTexture(newTexture) -> loop the draw() function
So not only that i cant reload the texture but also the call to clearGLTextures() seems not to work because the initialtexture remains on scren.
Any thoughts are welcome,
Thanks!
This is an example of a very common kind of problem when doing OpenGL programming on Android. Unfortunately the symptoms are very variable, so the questions are not really duplicates.
When you use GLSurfaceView, it creates a separate rendering thread. All the methods in your GLSurfaceView.Renderer implementation (onSurfaceCreated, onSurfaceChanged, onDrawFrame) are called in this rendering thread.
The second part of the puzzle is that you need a current OpenGL context to make OpenGL calls. And OpenGL contexts are current per thread. GLSurfaceView takes care of creating a context, and making it current in the rendering thread.
As a consequence, you can't make any OpenGL calls from other threads (unless you do much more work, and add complexity). The most common error is to try making OpenGL calls from the UI thread, typically when handling user input like touch events. Those calls will not work.
There are a number of options if you want to execute OpenGL calls in response to user input. For example, you can set members in the Renderer that describe the necessary state changes, and are checked in the onDraw() method. Another convenient option is using the queueEvent() method on the view, which allows you to pass in a Runnable that will later be executed in the rendering thread.
I just ran into the same problem.
Reto Koradi explained it great but I wanted to shared my solution:
I used queueEvent with a Runnable in the GLSurfaceView.
public void addTexture(final int textureResId) {
queueEvent(new Runnable() {
#Override
public void run() {
mRenderer.loadTexture(textureResId);
// or different GL thread tasks like clearing the texture
}
});
}
i am trying to convert bitmap into a libGDX Texture by converting:
Android Bitmap to byte[]
byte[] to libGDX Pixmap
libGDX Pixmap to libGDX Texture
The problem I am facing is that the bitmap which is converted to texture is drawing the sprite sheet from texture packer that is in assets folder
public void onByteArrayOfCroppedImageReciever(byte[] bytes) {
try {
pmap=new Pixmap(bytes, 0, bytes.length);
tex=new Texture(pmap);
face=new Sprite(tex);
// game.setScreen(new GameScreen(game, batcher, face));
} catch(Exception e) {
Gdx.app.log("KS", e.toString());
e.printStackTrace();
}
}
If the goal is to convert an Android Bitmap to a libgdx Texture, you don't need to use Pixmap at all. You can do it directly with the help of simple OpenGL and Android GLUtils. Try the followings; it is 100x faster than your solution. I assume that you are not in the rendering thread (you should not most likely). If you are, you don't need to call postRunnable().
Gdx.app.postRunnable(new Runnable() {
#Override
public void run() {
Texture tex = new Texture(bitmap.getWidth(), bitmap.getHeight(), Format.RGBA8888);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, tex.getTextureObjectHandle());
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
bitmap.recycle();
// now you have the texture to do whatever you want
}
});
hmm another possibility is that you've got a threading issue. I've noticed this kind of problem when loading my own unmanaged textures on the UI thread while libgdx is doing its thing loading textures concurrently on the render thread.
If this is the problem the simple solution is to synchronize the creation of the texture with the render thread using Gdx.app.postRunnable. i.e.:
public void onByteArrayOfCroppedImageReciever(byte[] bytes) {
try {
pmap=new Pixmap(bytes, 0, bytes.length);
Gdx.app.postRunnable(new Runnable() {
#Override
public void run() {
tex=new Texture(pmap);
face=new Sprite(tex);
}
});
} catch(Exception e) {
Gdx.app.log("KS", e.toString());
e.printStackTrace();
}
}
you have to code on a new thread because pixmap class takes time for byte conversion and sometimes returns a temporary pixmap in case the process hasnt finished so its better to run on a seperate thread and your problem will be solved.
Are you committed to that operational pipeline? Here's an alternate way to do the conversion you asked for:
Implement com.badlogic.gdx.graphics.TextureData to take an android.graphics.Bitmap in the constructor.
In prepare(), allocate and fill an IntBuffer using Bitmap.getPixels.
Use bitmath to swap the data in the buffer from ARGB to RGBA: (arr[i] << 8) | (arr[i]>>24)
In getType() return TextureData.TextureDataType.Compressed
In consumeCompressedData() call glTexImage2D to feed the data into the (prebound) texture.
Your pipeline above includes several copies and recompressions of pixel data; this pipeline allows you to feed your Bitmap data straight into the texture with only one copy (necessary for the byte format conversion anyway). Would that work for you?
I am making a 3D cube, I can assign one image for every face, but I would like to assign different images for each face.I used the NeHe Port for reference, so for one image the function is::
public static Bitmap getTextureFromBitmapResource(Context context, int resourceId)
{
Bitmap bitmap = null;
try
{
bitmap = BitmapFactory.decodeResource(context.getResources(), resourceId);
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), yFlipMatrix, false);
} finally
{
if (bitmap != null)
{
bitmap.recycle();
}
}
}
I am calling this function in my Renderer like this::
Bitmap texture = getTextureFromBitmapResource(context, R.drawable.img1);
But i can set only one image to every face. I want to set different images for each face. I tried making an array of images and then passing it to this function but in vain.
Texture arrays (if it is what you are referring to) should work, but it depends on the way you calculate the layer in your shader.
Otherwise, you must either draw each face individually and bind the right texture each time, or use a cubemap.
I am currently implementing a 3D viewer which basically renders a subset of all the images the user has on his SD Card. The closest matching product I would think of would be CoolIris:
It simply show a scrolling board of N tiles on screen, each showing different images, with new tiles entering the screen and showing new images.
Now for my problem: I have the program working and rendering nicely the quads. When a quad goes out of the screen, it gets recycled/released. And new quads keep on being added to the tile board before they enter the screen.
Because there can be hundreds of images, the textures need to be created and deleted on the fly (so that we don't run out of memory). The problem I have is that after I delete textures, newly created textures seem to get some IDs of other textures currently in use.
My rendering loop looks like this:
void render(GL10 gl) {
0. Move the camera
// Tile board maintenance
1. Remove tiles out of screen
2. Add new tiles which are about to enter screen
// Texture handling
3. glDeleteTextures on all unused textures followed by glFlush
4. For newly used images
- Create corresponding Bitmap
- Create the OpenGL texture, followed by glFlush
- Release the Bitmap
// Rendering
5. Render the tile (using a textured quad)
}
To give a better idea of how the data is organised, here is an overview of the classes:
TileBoard {
Image[] allImages;
Tile[] board;
}
Tile {
Image image;
}
Image {
String path;
int textureId;
int referenceCount;
}
Texture creation code:
protected void loadSingleTexture(GL10 gl, long objectId, Bitmap bmp) {
int[] textures = new int[1];
gl.glGenTextures(1, textures, 0);
gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bmp, 0);
gl.glFlush();
if (bmp != null) bmp.recycle();
if (listener != null) listener.onTextureLoaded(gl, objectId, textures[0]);
}
Texture deletion code:
// pendingTextureUnloads is a Set<Integer>
if (pendingTextureUnloads.size() > 0) {
int[] textureIds = new int[pendingTextureUnloads.size()];
int i = 0;
Iterator<Integer> it = pendingTextureUnloads.iterator();
while (it.hasNext()) {
textureIds[i] = it.next();
}
gl.glDeleteTextures(textureIds.length, textureIds, 0);
gl.glFlush();
}
I have solved the problem: the issue was that you have to keep the texture array passed to glGenTextures and reuse it.
Here is the modified overview for the ones who will have the same problem:
Image {
String path;
int[] textureIds;
int referenceCount;
}
Texture creation code:
// Notice that I don't allocate the int[] at the beginning but use the one of the image
protected void loadSingleTexture(GL10 gl, Image img, Bitmap bmp) {
gl.glGenTextures(1, img.textureIds, 0);
gl.glBindTexture(GL10.GL_TEXTURE_2D, img.textureIds[0]);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bmp, 0);
gl.glFlush();
if (bmp != null) bmp.recycle();
}
Texture deletion code:
foreach Image img {
gl.glDeleteTextures(img.textureIds.length, img.textureIds, 0);
}
gl.glFlush();
I know you said that you solved your issue, but I think I noticed your error in the first bit of code. Look at the while loop in your texture deletion code, you are not increasing the index i for your array, so you will continuously assign the first index until you reach the last entry in pendingTextureUnloads, the rest of the indices will be 0 (null). That might be problematic.
And by the way, I have got texture generation working by not reusing the array, just returning the index that was generated by glGenTextures. My code is to the line equal to yours, except from creating a new int array in the beginning of the method and returning the int at the first index at the end. Your code for texture generation should work, the error was just in the texture deletion.
I have been trying to fix this issue for weeks but I'm at the point where I dont know what to do now.
I think that some Android devices dont have enough memory to load the amount of textures, although it could be something else causing the issue, as I said I really dont know what to do with this.
There are 28 PNG's being loaded all 1024x1024 which come to a total of 4.8megs. Below is the OpenGL method for loading textures
GL10 gl = glGraphics.getGL();
int[] textureIds = new int[1];
gl.glGenTextures(1, textureIds, 0);
textureId = textureIds[0];
InputStream in = null;
try {
in = fileIO.readAsset(fileName);
Bitmap bitmap = BitmapFactory.decodeStream(in);
gl.glBindTexture(GL10.GL_TEXTURE_2D, textureId);
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
setFilters(GL10.GL_LINEAR , GL10.GL_LINEAR);
gl.glBindTexture(GL10.GL_TEXTURE_2D, 0);
width = bitmap.getWidth();
height = bitmap.getHeight();
bitmap.recycle();
} catch(IOException e) {
throw new RuntimeException("Couldn't load texture '" + fileName +"'", e);
} finally {
if(in != null)
try { in.close(); } catch (IOException e) { }
}
There are no issues on my Desire HD, but on a HTC Cha Cha a lot of textures dont appear at all and on a Galaxy S two textures just appear white. The Cha Cha throws this error while loading textures
02-04 15:46:28.907: E/Adreno200-ES20(1501): override1= 0xfffffffe,
override2= 0xfff *
Oddly if the Cha Cha is locked (OpenGL textures are destroyed) and then unlocked (reloaded textures) the particular textures that were not there initally are now, however different textures are now not visable.
Is this a memory issue? If so is there a way around this?
Thanks
The correct solution is texture compression, not PNG compression. PVR-TC would get you most of what you need. At 4-bpp, you'd go down to 12MB instead of 117MB. Even just using lower bitdepth images, like RGB-565 formats, (16-bits per pixel) would cut your needs in half.
Also, you don't have to use 1024x1024 textures for phones; that's kind of overkill. You could probably get away with 512x512 images. Coupled with PVR-TC, you'd only need about 3MB for all of that texture data.