Android Opengl-es loading a non-power of 2 texture
Asked Answered
V

5

10

I have an app that I've been repeatedly playing with in android, it uses opengl-es.

Currently I load textures from a bitmap like so:

//Load up and flip the texture - then dispose the temp
    Bitmap temp = BitmapFactory.decodeResource(Deflecticon.getContext().getResources(), resourceID);
    Bitmap bmp = Bitmap.createBitmap(temp, 0, 0, temp.getWidth(), temp.getHeight(), flip, true);
    temp.recycle();

    //Bind the texture in memory
    gl.glBindTexture(GL10.GL_TEXTURE_2D, id);

    //Set the parameters of the texture.
    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);

    //On to the GPU
    GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bmp, 0);

The obvious issue is that the texture I'm using has to be a power of 2. At the moment I'm pre-editing the textures in photoshop to be a power of 2 and simply have empty borders. However this is a little tedious and I want to be able to load them as they are .. recognise they aren't a power of 2 and load them into a texture that is.

I know I could scale the bitmap to become a power of 2 size and simply stretch the texture but I do not wish to stretch the texture and in some cases may want to put several textures into one "atlas".

I know I can use glTexSubImage2D() to paste into the texture the data I want at the origin I want. This is great!

However I do not know how in Android to initialise a texture with no data?

In this question previously asked the suggestion was to call glTexImage2D() with no data and to then fill it.

However in android when you call "GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bmp, 0);" you do not specify a width / height. It reads this from the bitmap I assume.

What is the best way to do this? Can I create a new bitmap of the right power of 2 size only blank and not filled with any data and use this to initialise the texture then paste into it using subImage? Or should I make a new bitmap somehow copy the pixels I want (not sure if you can do this easily) into this new bitmap (leaving borders) and then just use this?

Edit: clarified that I'm using opengl.

Vitriolic answered 18/4, 2011 at 16:10 Comment(0)
A
4

I think if you tried creating a bitmap with the power of 2 axis sizes and then add your bitmap it should work just fine. maybe something like

Bitmap.createBitmap(notPowerOf2Bitmap, offx, offy, xsize, ysize, bitmapFlag)

other than that, I would say suffer through the photoshop process. How many pictures you got?

Armillda answered 18/4, 2011 at 22:42 Comment(8)
Not too many, just we were hoping to possibly change them at a later date and it makes it easier if the resizing is done nicely! This method says it takes a subset of the nonPOTBitmap does this mean that if the subset bitmap is smaller than the specified xSize and ySize it would have blank edges? Or would it stretch it? (I would experiment but sadly not at my coding pc).Vitriolic
Bitmaps shouldn't stretch unless they are tossed to a createScaledBitmap method. The remaining space will be empty.Armillda
Sorry for the delayed reply, I've been off with other things, however I came around to actually implementing this and it does not work via this method. You cannot sub a bitmap into a bitmap with a larger size than the source it seems... I'm now at a bit of a loss.Vitriolic
Hmmm... The only thing I can think of then is to create a bitmap, assign a canvas to it and draw the unscaled bitmap to the canvas and then use the new bitmap. It's kinda resource heavy but again that is the only other thing I can think of....Armillda
I gave that a go and so far is working beautifully, I don't mind the resource issue as I am doing this all at the start of the app. Any further loading / releasing will be done between any important speed based things so it's all good!Vitriolic
Fantastic! Good luck furthermore.Armillda
Wait, which worked? The written solution above or the one in the comment? Was the written solution above modified? This has been accepted.Impersonal
Wouldn't Bitmap.createScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter) work as well? You should be able to upscale with this.Charcoal
L
2

Non-power-of-two (NPOT) bitmaps are supported on some GLES platforms, but you have to check to see if the appropriate extension exists. Note, however, that at least on PowerVR SGX, even though NPOT is supported, there are still some other fairly arbitrary restrictions; for example, your texture width must be a multiple of 2 (if not a power of 2). Also, NPOT rendering tends to be a bit slower on many GPUs.

One thing you can do is just create a wrapper texture which is a power-of-two size and then use glTexSubimage2D to upload the texture to cover only part of that, and then adjust your texture coordinates accordingly. The obvious drawback to this is that you can't use texture wrapping in that circumstance. If you absolutely must support wrapping, you could just scale your texture to the nearest power-of-two size before you call glTexImage2D, although this usually introduces sampling artifacts and makes things blurry, especially if you're trying to do pixel-precise 2D work.

Another thing you might consider, if you don't need to support wrapping, is to make a "texture atlas," in which you condense all of your textures into a few big textures, and have your polygons map to just some portions of the texture atlas(es). You have to be careful when generating MIPmaps, but other than that it usually provides a pretty nice performance benefit, as well as making more efficient use of texture memory since you're not wasting so much on padded or scaled images.

Liar answered 28/4, 2011 at 19:57 Comment(2)
As my question states though how do you make a wrapper texture in android that you sample into? What's the best method to do this?Vitriolic
Look at the documentation for glTexSubimage2D.Liar
S
2

I have 2 solutions I have employed for this problem. I can be more specific if necessary, but conceptually you can: -

  1. Make the image a power of 2, and the section to crop you fill with 100% alpha channel and load the images with alpha enabled.

  2. Tweak your texture vector / buffer so it doesn't load that section. So instead of using the default

    float texture[] = { 
        0.0f, 1.0f, //
        1.0f, 1.0f, //
        0.0f, 0.0f, //
        1.0f, 0.0f, //
    };
    

as the matrix (obviously this is for loading an image to a 2 triangled square), factor back by ratio the area to crop, eg.

    float texture[] = { 
        0.0f, 0.75f, //
        0.9f, 0.75f, //
        0.0f, 0.0f, //
        0.9f, 0.0f, //
     };

of course, be precise with your math or the unwanted bit may bleed in, or you'll cut out some of the real image. Obviously this array is calculated on the fly and not hard-coded as I have demonstrated here.

Schizoid answered 8/9, 2011 at 22:6 Comment(0)
O
0

Uh why don't you create two bitmaps. Load the first one as you're doing then use createBitmapScaled to turn that bitmap into a power of two. Performance-wise I don't know if it is the fastest method possible but it works.

Oloughlin answered 3/6, 2011 at 12:50 Comment(0)
P
0

YOu can use GLES20.glTexImage2D() to create a empty texture with specified width and height. The example code is

public static int genTexture(int texWidth, int texHeight) {
    int[] textureIds = new int[1];
    GLES20.glGenTextures(1, textureIds, 0);
    assertNoError();
    int textureId = textureIds[0];
    texWidth = Utils.nextPowerOf2(texWidth);
    texHeight = Utils.nextPowerOf2(texHeight);

    GLES20.glBindTexture(GL11.GL_TEXTURE_2D, textureId);
    GLES20.glTexParameteri(
            GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
    GLES20.glTexParameteri(
            GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
    GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
            GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
    GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
            GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);

    GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA,
            texWidth, texHeight, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);

    return textureId;
}
Planoconvex answered 22/12, 2014 at 12:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.