Threading textures load process for android opengl game
Asked Answered
M

5

11

I have a large amount of textures in JPG format. And I need to preload them in opengl memory before the actual drawing starts. I've asked a question and I've been told that the way to do this is to separate JPEG unpacking from glTexImage2D(...) calls to another thread. The problem is I'm not quite sure how to do this.

OpenGL (handler?), needed to execute glTexImage2D is only available in GLSurfaceView.Renderer's onSurfaceCreated and OnDrawFrame methods.

I can't unpack all my textures and then in onSurfaceCreated(...) load them in opnegl, because they probably won't fit in limited vm's memory (20-40MB?)

That means I have to unpack and load them one-by one, but in that case I can't get an opengl pointer.

Could someone, please, give me and example of threading of textures loading for opengl game?

It must be some some typical procedure, and I can't get any info anywhere.

Marijn answered 11/6, 2011 at 20:15 Comment(1)
Could you solve this? How? The selected answer isn't complete.Olathe
E
2

You just have your main thread with the uploading routine, that has access to OpenGL and calls glTexImage2D. The other thread loads (and decodes) the image from file to memory. While the secondary thread loads the next image, the main thread uploads the previously loaded image into the texture. So you only need memory for two images, the one currently loaded from file and the one currently uploaded into the GL (which is the one loaded previously). Of course you need a bit of synchronization, to prevent the loader thread from overwriting the memory, that the main thread currently sends to GL and to prevent the main thread from sending unfinished data.

Endermic answered 12/6, 2011 at 0:43 Comment(3)
From what you said, I conclude I can simply save OpenGL pointer from GLRenderer's constructor and use it in texture loading function. So the plan is: 1) On start set some splashscreen layout 2) Create GLSurfaceView, but don't show it yet 3) Start AsyncTask that will post ProgressUpdate after unpacking one texture and call loadtexture method that will use saved OpenGL pointer, and maybe, update progress widget on splash screen. 4) when done, switch layout to GLSurfaceView. I'll try to implement that and report on success.Marijn
It doesn't work that way. OnSurfaceCreated() is only called after setting GLSurfaceView to current layout, and even if I try to use gl pointer saved after actually showing GLSurfaceView, the pointer doesn't work (glGenTextures() returns zero). That's the main problem - how to load it back into OpenGL after uncompressing?Marijn
@Marijn I have to tell you, that I don't have any experience with Android, so all these words don't tell me anything. But the proposed method is the method of choice when doing such texture streaming, the Android implementation is up to you. There has to be a way to call GL functions outside of the initialization function. Just be sure to only use OpenGL in the main thread.Endermic
O
15

As explained in 'OpenGLES preloading textures in other thread' there are two separate steps: bitmap creation and bitmap upload. In most cases you should be fine by just doing the bitmap creation on a secondary thread --- which is fairly easy.

If you experience frame drops while uploading the textures, call texImage2D from a background thread. To do so you'll need to create a new OpenGL context which shares it's textures with your rendering thread because each thread needs it's own OpenGL context.

EGLContext textureContext = egl.eglCreateContext(display, eglConfig, renderContext, null);

Getting the parameters for eglCreateContext is a little bit tricky. You need to use setEGLContextFactory on your SurfaceView to hook into the EGLContext creation:

@Override
public EGLContext createContext(final EGL10 egl, final EGLDisplay display, final EGLConfig eglConfig) {
     EGLContext renderContext = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, null);

     // create your texture context here

     return renderContext;
}

Then you are ready to start a texture loading thread:

public void run() {
    int pbufferAttribs[] = { EGL10.EGL_WIDTH, 1, EGL10.EGL_HEIGHT, 1, EGL14.EGL_TEXTURE_TARGET,
            EGL14.EGL_NO_TEXTURE, EGL14.EGL_TEXTURE_FORMAT, EGL14.EGL_NO_TEXTURE,
            EGL10.EGL_NONE };

    EGLSurface localSurface = egl.eglCreatePbufferSurface(display, eglConfig, pbufferAttribs);
    egl.eglMakeCurrent(display, localSurface, localSurface, textureContext);

    int textureId = loadTexture(R.drawable.waterfalls);

    // here you can pass the textureId to your 
    // render thread to be used with glBindTexture
}

I've created a working demonstration of the above code snippets at https://github.com/perpetual-mobile/SharedGLContextsTest.

This solution is based on many sources around the internet. The most influencing ones where these three:

Orelie answered 2/11, 2013 at 10:23 Comment(1)
This usually worked while testing on multiple devices, however there were a few cases where this method made no difference. For example on a Huawei P8 Lite a noticeable drop of frames occured during upload. I suspect this is due to the limited hardware/driver which does not support multithreading. For large textures where the upload is slow, an alternative could be preloading in chunks with texSubImage2D.Zomba
E
2

You just have your main thread with the uploading routine, that has access to OpenGL and calls glTexImage2D. The other thread loads (and decodes) the image from file to memory. While the secondary thread loads the next image, the main thread uploads the previously loaded image into the texture. So you only need memory for two images, the one currently loaded from file and the one currently uploaded into the GL (which is the one loaded previously). Of course you need a bit of synchronization, to prevent the loader thread from overwriting the memory, that the main thread currently sends to GL and to prevent the main thread from sending unfinished data.

Endermic answered 12/6, 2011 at 0:43 Comment(3)
From what you said, I conclude I can simply save OpenGL pointer from GLRenderer's constructor and use it in texture loading function. So the plan is: 1) On start set some splashscreen layout 2) Create GLSurfaceView, but don't show it yet 3) Start AsyncTask that will post ProgressUpdate after unpacking one texture and call loadtexture method that will use saved OpenGL pointer, and maybe, update progress widget on splash screen. 4) when done, switch layout to GLSurfaceView. I'll try to implement that and report on success.Marijn
It doesn't work that way. OnSurfaceCreated() is only called after setting GLSurfaceView to current layout, and even if I try to use gl pointer saved after actually showing GLSurfaceView, the pointer doesn't work (glGenTextures() returns zero). That's the main problem - how to load it back into OpenGL after uncompressing?Marijn
@Marijn I have to tell you, that I don't have any experience with Android, so all these words don't tell me anything. But the proposed method is the method of choice when doing such texture streaming, the Android implementation is up to you. There has to be a way to call GL functions outside of the initialization function. Just be sure to only use OpenGL in the main thread.Endermic
R
2

"There has to be a way to call GL functions outside of the initialization function." - Yes. Just copy the pointer to gl and use it anywhere.

"Just be sure to only use OpenGL in the main thread." Very important. You cannot call in your Game Engine (which may be in another thread) a texture-loading function which is not synchronized with the gl-thread. Set there a flag to signal your gl-thread to load a new texture (for example, you can place a function in OnDrawFrame(GL gl) which checks if there must be a new texture loaded.

Raseda answered 7/10, 2011 at 12:47 Comment(2)
Yes, I did it exactly as you said. I also think Christian Rau said the same thing. I have a decompressing routine in a separate thread, a flag and a shared buffer for decompressed pixels. Inside OnDrawFrame(GL gl) there's a code that checks the flag and calls TexImage2D when needed.Marijn
Holding the reference to gl object and using it later (when the thread returns) results in this error: call to OpenGL ES API with no current context (logged once per thread)Olathe
P
1

To add to Rodja's answer, if you want an OpenGL ES 2.0 context, then use the following to create the context:

final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
int[] contextAttributes = 
    { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
EGLContext renderContext = egl.eglCreateContext(
    display, config, EGL10.EGL_NO_CONTEXT, contextAttributes);

You still need to call setEGLContextClientVersion(2) as well, as that is also used by the default config chooser.

This is based on Attribute list in eglCreateContext

Photoactinic answered 3/4, 2014 at 0:17 Comment(0)
O
0

Found a solution for this, which is actually very easy: After you load the bitmap (in a separate thread), store it in an instance variable, and in draw method, you check if it's initialized, if yes, load the texture. Something like this:

if (bitmap != null && textureId == -1) {
    initTexture(gl, bitmap);
}
Olathe answered 23/2, 2013 at 22:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.