3

I'm writing an effect filter for Android devices, which has two-dimension loops in the fragment shader. For most of the devices, the shader can be compiled and run in reasonable time, but some of the devices takes several minutes to compile the shader at the first time.

My fragment shader has a heavy two-dimension kernel convolution:

const lowp int KERNEL_RADIUS = 19;

....

for (int y = -KERNEL_RADIUS; y <= KERNEL_RADIUS; y++)
{
    for (int x = -KERNEL_RADIUS; x <= KERNEL_RADIUS; x++)
    {
        ....
    }
}

In fact it is a 39x39 loop, and it cannot be split into two passes of one-dimension filter due to the kernel design. The kernel weights are stored as another input texture of the shader for lookup. Obviously this shader cannot have reasonable performance when directly applied to an image with normal size (800x600 ~ 1600x1200), so I resize the image to 200x200 ~ 400x400 and then I can have real-time response on most devices.

I know that some shader compiler cannot accept such a large loop and will fail to compile the program. I have found some devices with this behavior. The compile time is still reasonable on the device. It just reports a failure and let me disable the effect filter. However, on some other devices, the compilation is successful and the program can be used normally, but the first time of compilation is about 2~3 minutes. After that, the compiler caches the program and give a compile time of 50~100 ms when I create the effect filter again.

Currently I cannot modify my algorithm to remove or shrink the two-dimension loops, but it is also hilarious if I let the user to wait minutes for the first launch. I want to disable the effect filter on those devices. The problem is that I use GLES20.glCompileShader() to compile the shader:

public static int loadShader(final String strSource, final int iType)
{
    int[] compiled = new int[1];
    int iShader = GLES20.glCreateShader(iType);
    GLES20.glShaderSource(iShader, strSource);
    GLES20.glCompileShader(iShader);
    GLES20.glGetShaderiv(iShader, GLES20.GL_COMPILE_STATUS, compiled, 0);
    if (compiled[0] == 0) {
        Log.d("Load Shader Failed", "Compilation\n" + GLES20.glGetShaderInfoLog(iShader));
        return 0;
    }
    return iShader;
}

It is a blocking call. I need to wait several minutes before making the decision of disabling filter on such devices.

Is there a way to compile the shader codes asynchronously or in a limited duration? (For example, return fail in 5 seconds if the compliation does not complete yet.)

If glCompileShader() can only be called synchronously, I want to force terminate the thread so it does not block the AP. But it will cause a serious problem. The thread which compiles the shader code is the same as the thread creating the OpenGL context. If I kill the thread while it is blocking, I cannot destroy the OpenGL context appropriately in the same thread.

Is it possible, or safe, to compile the shader codes in a different thread than the one which initializes the OpenGL context? I was told that they should be the same thread, and I want to know if they are able to be different when I really need to do this.

Mark
  • 305
  • 5
  • 17
  • maybe there are issues with your shader code that causes the long compilation time, (also you should destroy the shader if you will return 0) – ratchet freak Nov 04 '14 at 10:02

1 Answers1

3

I have to say I have never seen a shader that takes MINUTES to compile on an Android device, so I suspect there is something wrong there, but yes it is possible to create shaders on a separate thread. To do so you have to create a 2nd EGLContext that is set to share resource with the main context.

Most modern devices support this fine, but I have come across some older Android devices that do not.

Create a 2nd EGLContext like this:

int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
EGLContext2 = egl.eglCreateContext( display, eglConfig, EGLContext1, attrib_list);
if( EGLContext2!=null )
{
    int pbufferAttribs[] = { EGL10.EGL_WIDTH, 1, EGL10.EGL_HEIGHT, 1, EGL10.EGL_NONE }; 
    EGLSurface2 = egl.eglCreatePbufferSurface( display, eglConfig, pbufferAttribs ); 
}

Then create your thread, and call this from the thread to set the context:

egl.eglMakeCurrent( EGLDisplay, EGLSurface2, EGLSurface2, EGLContext2 );

After this you can create any GL resources including shaders from this secondary thread, and then use the shader from your primary thread. (Be sure to call glFlush() on the 2nd thread after the shader is created). When your thread exits, you'll have to clean up the secondary EGLcontext:

egl.eglDestroyContext( EGLDisplay, EGLContext2 );
Muzza
  • 1,236
  • 9
  • 13
  • Thank you. I will try this. One more thing I want to ask is that if I need to KILL the second thread, can I clean up the resources just from the main thread? For example, I cannot call EGL.eglMakeCurrent(mEGLDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); on the second thread if I have to kill it. – Mark Nov 05 '14 at 07:14
  • eglDestroyContext can be called from the main thread after you kill the secondary thread. You're right that eglMakeCurrent cannot be called though. I'm not sure whether this will cause problems inside the driver or not. – Muzza Nov 05 '14 at 07:32