2
\$\begingroup\$

I have a 2d Android game that is currently causing certain devices to run out of memory. I have a number of PNGs (about 10 MBs in total) that I use in the game at various times. At some points in the game, the majority of these need to be displayed at the same time.

I have tried just decreasing the resolution of my images but I am not happy with the quality after doing so.

I have read a number of posts about how to solve these memory issues and as far as I can see, texture compression is the best approach (feel free to correct me if I am wrong). I have also seen this post that covers how to determine which texture compression formats are supported on a device and I understand this part of things: https://stackoverflow.com/questions/9148795/android-opengl-texture-compression

My question is two-fold:

  1. Most of my textures require alphas. I know that by default ETC1 does not support alpha, but I also know that when using ETC1 you can create a separate alpha compression as described here: http://sbcgamesdev.blogspot.com/2013/06/etc1-textures-loading-and-alpha.html. Shown in that link is how to apply the alphas using the NDK. I am battling to understand how to do this using the standard OpenGL ES Java wrappers though. Below is how I currently handle textures (i.e. no texture compression). How would I convert this to handle compressed textures where I need to load the alphas separately?

    GLGraphics glGraphics;
    FileIO fileIO;
    String fileName;
    int textureId;
    int minFilter;
    int magFilter;
    
    public int width;
    public int height;
    
    private boolean loaded = false;
    
    public Texture(GLGame glGame, String fileName) {
        this.glGraphics = glGame.getGLGraphics();
        this.fileIO = glGame.getFileIO();
        this.fileName = fileName;
        load();
    }
    
    public void load() {
        GL10 gl = glGraphics.getGL();
        int[] textureIds = new int[1];
        gl.glGenTextures(1, textureIds, 0);
        textureId = textureIds[0];
    
        InputStream inputStream = null;
    
        try {
            inputStream = fileIO.readAsset(fileName);
            Bitmap bitmap = BitmapFactory.decodeStream(inputStream);            
            gl.glBindTexture(GL10.GL_TEXTURE_2D, textureId);
            GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
            setFilters(GL10.GL_NEAREST, GL10.GL_NEAREST);
            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 (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    // do nothing
                }
            }
        }
    
        loaded = true;
    }
    
    public void reload() {
        load();
        bind();
        setFilters(minFilter, magFilter);
        glGraphics.getGL().glBindTexture(GL10.GL_TEXTURE_2D, 0);
    }
    
    public void setFilters(int minFilter, int magFilter) {
        this.minFilter = minFilter;
        this.magFilter = magFilter;
    
        GL10 gl = glGraphics.getGL();
        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, minFilter);
        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, magFilter);
    }
    
    public void bind() {
        GL10 gl = glGraphics.getGL();
        gl.glBindTexture(GL10.GL_TEXTURE_2D, textureId);
    }
    
    public void dispose() {
        loaded = false;
    
        GL10 gl = glGraphics.getGL();
        gl.glBindTexture(GL10.GL_TEXTURE_2D, textureId);
        int[] textureIds = { textureId };
        gl.glDeleteTextures(1, textureIds, 0);
        gl.glBindTexture(GL10.GL_TEXTURE_2D, 0);
    }
    
    public boolean isLoaded() {
        return loaded;
    }
    
    public void setLoaded(boolean loaded) {
        this.loaded = loaded;
    }
    
  2. My understanding is that I would have to provide 4 compressed textures (one for each format) and a fall back uncompressed PNG for each of my images to support a wide range of devices. My concern is the required disk size increase that this will cause. Is there any solution to this? i.e. use compressed textures in order to lower the memory usage of my game without causing the size of the game on disk to explode?

\$\endgroup\$
  • 1
    \$\begingroup\$ What version of ES are you using, by the way? If you are using ES 2.0, you can actually pack this into a very primitive texture atlas. Effectively, if you need ETC1+Alpha, you can double the width or height of your individual textures. The top/left would be the color image and the bottom/right would be an alpha mask. You can extract both images using two texture fetches in a shader, but you may need 2 sets of output texture coordinates in the vertex shader to avoid paying a penalty for a "dependent" texture fetch in the fragment shader on lower-end hardware. \$\endgroup\$ – Andon M. Coleman May 16 '14 at 1:36
  • \$\begingroup\$ @AndonM.Coleman up until now I have been using ES 1. I am starting to get the feeling that I may need to shift over to 2 though. \$\endgroup\$ – brent777 May 16 '14 at 23:00
  • \$\begingroup\$ Ah, ES 1.0 has texture combiners, which you could probably use to do essentially the same thing. They are a pain in the butt to work with though, and learning to use shaders would be a better use of time ultimately. \$\endgroup\$ – Andon M. Coleman May 16 '14 at 23:01
0
\$\begingroup\$

So after looking into this quite a lot I have found that you can only really do it using shaders as @Andon M. Coleman mentioned. This, however, only works in OpenGL ES 2.0 and up.

I almost got this working in OpenGL ES 1.1 using multitexturing but eventually it became clear to me (through my own experimentation and other questions that I asked) that this was not going to work since it requires you to combine RGB channel values to get the alpha channel values.

If you want to stick to OpenGL ES 1.1, then I found that the best approach is to just compress your images that do not require alphas using ETC1. I then used quantization (via pngquant) to dramatically reduce the size and memory footprint of my images that do require alphas to be preserved. This approach has dramatically decreased my game's memory usage while still keeping the quality of the textures at a more-than-acceptable level.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.