【问题标题】:Compile GLSL shader asynchronously or in a different thread on Android在 Android 上异步或在不同线程中编译 GLSL 着色器
【发布时间】:2014-11-05 15:45:53
【问题描述】:

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

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

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 的循环,由于内核设计的原因,它不能分成两遍一维滤波器。内核权重存储为着色器的另一个输入纹理以供查找。 显然这个shader直接应用到正常尺寸(800x600~1600x1200)的图片上是不能有合理的性能的,所以我把图片调整到200x200~400x400,然后在大多数设备上都可以实时响应。

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

目前我无法修改我的算法来删除或缩小二维循环,但如果我让用户等待几分钟等待第一次启动也很有趣。我想在这些设备上禁用效果过滤器。问题是我使用 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 秒内返回失败。)

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

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

【问题讨论】:

  • 可能你的着色器代码有问题导致编译时间过长,(如果你返回 0,你也应该销毁着色器)

标签: android asynchronous opengl-es glsl shader


【解决方案1】:

我不得不说我从未见过需要 MINUTES 才能在 Android 设备上编译的着色器,所以我怀疑那里有问题,但是可以在单独的线程上创建着色器。为此,您必须创建一个设置为与主上下文共享资源的第二个 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())。当您的线程退出时,您必须清理辅助 EGL 上下文:

egl.eglDestroyContext( EGLDisplay, EGLContext2 );

【讨论】:

  • 谢谢。我会试试这个。我还想问的一件事是,如果我需要 KILL 第二个线程,我可以只从主线程中清理资源吗?例如,我不能调用 EGL.eglMakeCurrent(mEGLDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);如果我必须杀死它,请在第二个线程上。
  • eglDestroyContext 可以在您杀死辅助线程后从主线程调用。你是对的,虽然 eglMakeCurrent 不能被调用。我不确定这是否会导致驱动程序内部出现问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-08
  • 1970-01-01
  • 2023-04-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多