Android ShareContext Demo

了解了,你的需求是:只有一个 GLSurfaceView(拥有一个渲染线程),但你需要开启另一个后台线程(子线程)并在该线程中操作 OpenGL 资源(如异步加载纹理),这两个线程需要共享同一个 Context。

这是 Android 高性能渲染(如异步上传纹理、后台滤镜处理)的经典场景。

核心逻辑

  1. 捕获主线程 Context:通过自定义 GLSurfaceView.EGLContextFactory 拿到 GLSurfaceView 创建好的 EGLContext
  2. 后台线程初始化 EGL:在子线程中手动初始化 EGL 环境,并调用 eglCreateContext 时,将主线程的 EGLContext 作为共享参数传入。
  3. 使用离屏 Surface:子线程通常不需要显示,所以使用 PbufferSurface(像素缓冲表面)。

代码实现

1. 自定义 ContextFactory (用于获取主线程 Context)

我们需要拿到 GLSurfaceView 内部创建的那个 EGLContext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MyContextFactory implements GLSurfaceView.EGLContextFactory {
private EGLContext mContext;

@Override
public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig config) {
int[] attrib_list = {0x3098, 2, EGL10.EGL_NONE}; // GLES 2.0
// 创建主 Context
mContext = egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, attrib_list);
return mContext;
}

@Override
public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) {
egl.eglDestroyContext(display, context);
}

// 提供给外部获取
public EGLContext getContext() {
return mContext;
}
}

2. 后台线程的 EGL 环境 (使用 EGL14)

Android 现代开发推荐使用 android.opengl.EGL14

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public class BackgroundGLThread extends Thread {
private android.opengl.EGLContext sharedContext; // 从 GLSurfaceView 传来的

public BackgroundGLThread(android.opengl.EGLContext sharedContext) {
this.sharedContext = sharedContext;
}

@Override
public void run() {
// 1. 初始化 EGL
EGLDisplay display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
int[] version = new int[2];
EGL14.eglInitialize(display, version, 0, version, 1);

// 2. 配置 Config
int[] confAttr = {
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
EGL14.EGL_RED_SIZE, 8,
EGL14.EGL_GREEN_SIZE, 8,
EGL14.EGL_BLUE_SIZE, 8,
EGL14.EGL_ALPHA_SIZE, 8,
EGL14.EGL_NONE
};
EGLConfig[] configs = new EGLConfig[1];
int[] numConfigs = new int[1];
EGL14.eglChooseConfig(display, confAttr, 0, configs, 0, 1, numConfigs, 0);

// 3. 创建 Context 并设置 ShareContext
int[] ctxAttr = { EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE };
android.opengl.EGLContext myContext = EGL14.eglCreateContext(display, configs[0], sharedContext, ctxAttr, 0);

// 4. 创建离屏 Surface (Pbuffer)
int[] surfAttr = { EGL14.EGL_WIDTH, 1, EGL14.EGL_HEIGHT, 1, EGL14.EGL_NONE };
EGLSurface surface = EGL14.eglCreatePbufferSurface(display, configs[0], surfAttr, 0);

// 5. 绑定到当前线程
EGL14.eglMakeCurrent(display, surface, surface, myContext);

// --- 此时可以进行 OpenGL 操作了 ---

// 例如:生成一个纹理
int[] texIds = new int[1];
GLES20.glGenTextures(1, texIds, 0);
int textureId = texIds[0];

// 绑定并操作纹理(比如异步加载图片数据)
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
// ... glTexImage2D ...
GLES20.glFinish(); // 确保操作完成

Log.d("GL", "子线程创建了纹理: " + textureId + ",主线程也可以用它了");

// 释放环境
EGL14.eglMakeCurrent(display, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
EGL14.eglDestroySurface(display, surface);
EGL14.eglDestroyContext(display, myContext);
}
}

3. Activity 中的整合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class MainActivity extends AppCompatActivity {
private GLSurfaceView glSurfaceView;
private MyContextFactory contextFactory;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
glSurfaceView = new GLSurfaceView(this);

// 1. 设置 Factory
contextFactory = new MyContextFactory();
glSurfaceView.setEGLContextFactory(contextFactory);
glSurfaceView.setEGLContextClientVersion(2);

glSurfaceView.setRenderer(new GLSurfaceView.Renderer() {
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// 2. 获取主线程的 Context (注意:GLSurfaceView 内部使用 javax.microedition.khronos.egl)
// 我们需要将其转为 android.opengl.EGLContext (EGL14)
android.opengl.EGLContext mainContext = EGL14.eglGetCurrentContext();

// 3. 开启子线程
new BackgroundGLThread(mainContext).start();
}

@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {}

@Override
public void onDrawFrame(GL10 gl) {
// 这里可以使用子线程生成的 textureId
}
});

setContentView(glSurfaceView);
}
}

关键点详解

  1. EGL10 vs EGL14

    • GLSurfaceView 内部默认使用 javax.microedition.khronos.egl (EGL10)。
    • 现代子线程操作建议使用 android.opengl.EGL14
    • 转换技巧:在 onSurfaceCreated 里直接调用 EGL14.eglGetCurrentContext(),由于该方法是在渲染线程调用的,它能直接拿到当前激活的 EGL14 句柄,哪怕 GLSurfaceView 内部是用 EGL10 创建的。
  2. glFinish() 的必要性

    • 在子线程完成资源创建(如 glTexImage2D)后,必须调用 GLES20.glFinish()。这能确保 GPU 执行完了所有指令,主线程再去访问该纹理时数据才是完整的。
  3. PbufferSurface

    • OpenGL 必须绑定一个 Surface 才能工作。子线程没有 UI,所以用 eglCreatePbufferSurface 创建一个 1x1 的离屏缓冲区即可。
  4. 纹理 ID 的共享

    • 在子线程生成的 textureId (int 类型) 是可以在主线程直接使用的。你只需要通过 Handler 或线程通信手段把这个 int 值传回给 Renderer 即可。

适用场景

  • 异步加载大纹理:避免 glTexImage2D 阻塞 UI 渲染导致掉帧。
  • 多线程并行处理:一个线程负责读取摄像头并做初步美颜(写入 FBO/Texture),主线程负责显示和 UI 覆盖。