android - 异步或在 Android 上的不同线程中编译 GLSL 着色器

标签 android asynchronous opengl-es glsl shader

我正在为 Android 设备编写效果过滤器,它在 fragment 着色器中有二维循环。对于大多数设备,着色器可以在合理的时间内编译和运行,但有些设备第一次编译着色器需要几分钟。

我的 fragment 着色器有一个很重的二维核卷积:

const lowp int KERNEL_RADIUS = 19;

....

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

实际上是一个39x39的循环,由于内核设计的原因,不能分成两遍一维filter。内核权重存储为着色器的另一个输入纹理以供查找。 显然这个着色器直接应用于正常尺寸(800x600 ~ 1600x1200)的图像时不能有合理的性能,所以我将图像调整到 200x200 ~ 400x400 然后我可以在大多数设备上有实时响应。

我知道有些着色器编译器不能接受这么大的循环,会编译失败。我发现了一些具有这种行为的设备。编译时间在设备上仍然是合理的。它只是报告失败,让我禁用效果过滤器。但是在其他一些设备上,编译成功,程序可以正常使用,但是第一次编译大约需要2~3分钟。之后,编译器缓存了程序,并在我再次创建效果器时给出了 50~100 ms 的编译时间。

目前我无法修改我的算法来删除或缩小二维循环,但如果我让用户在第一次启动时等待几分钟也很有趣。我想禁用这些设备上的效果过滤器。问题是我使用 GLES20.glCompileShader() 来编译着色器:

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;
}

这是一个阻塞调用。我需要等待几分钟才能决定在此类设备上禁用过滤器。

有没有办法异步或限时编译着色器代码? (例如编译还没有完成,5秒后返回fail。)

如果只能同步调用 glCompileShader(),我想强制终止线程,这样它就不会阻塞 AP。但是会造成严重的问题。编译着色器代码的线程与创建 OpenGL 上下文的线程相同。如果我在线程阻塞时终止它,我就无法在同一线程中适本地销毁 OpenGL 上下文。

在与初始化 OpenGL 上下文的线程不同的线程中编译着色器代码是否可能或安全?有人告诉我它们应该是同一个线程,我想知道当我真的需要这样做时它们是否能够不同。

最佳答案

我不得不说我从未见过在 Android 设备上编译需要 MINUTES 的着色器,所以我怀疑那里有问题,但是可以在单独的线程上创建着色器。为此,您必须创建第二个 EGLContext,将其设置为与主上下文共享资源。

大多数现代设备都支持这一点,但我遇到过一些不支持的旧版 Android 设备。

像这样创建第二个 EGLContext:

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 ); 
}

然后创建您的线程,并从线程调用它来设置上下文:

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

在此之后,您可以从这个辅助线程创建任何 GL 资源,包括着色器,然后从您的主线程使用着色器。 (确保在创建着色器后在第二个线程上调用 glFlush())。当您的线程退出时,您必须清理辅助 EGLcontext:

egl.eglDestroyContext( EGLDisplay, EGLContext2 );

关于android - 异步或在 Android 上的不同线程中编译 GLSL 着色器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26732228/

相关文章:

安卓|主屏幕上的书签图标

linux - 在 Linux 上等待 infiniband 接收完成的最佳方法?

javascript - 如何在 JavaScript 中使用异步循环?

iphone - 在 CVImageBuffer 上运行多个 OpenGL 着色器

java - 如何检测从一项 Activity 到另一项 Activity 的转变?

java - 即使使用 -keep,kotlin 类的构造函数中的参数名称也会被 proguard 删除

android - Android Spinner 上的多个 AsyncTasks 问题

javascript - Node.js 中的嵌套异步操作

c++ - 直接从 C++ 应用程序绘制到 WebGL Canvas

ios - OpenGL 层在 presentRenderBuffer 上变黑