首页 > 代码库 > 安卓下多线程OpenGL共享Context (二)

安卓下多线程OpenGL共享Context (二)

      为了在Java线程进行OpenGL调用,需要为java线程初始化OpenGL环境,initOpenGL函数展示了初始化OpenGL环境的过程。在setupOpenGL方法中,在线程上先执行该调用即可。Java代码示例如下:

 1 package com.thornbirds.unity;
 2 
 3 public class PluginTexture {
 4 
 5     private EGLDisplay mEGLDisplay;
 6     private EGLConfig mEglConfig;
 7     private EGLContext mEglContext;
 8     private EGLSurface mEglSurface;
 9 
10     private void glLogE(String msg) {
11         Log.e(TAG, msg + ", err=" + GLES10.glGetError());
12     }
13 
14     private void initOpenGL() {
15         mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
16         if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
17             glLogE("eglGetDisplay failed");
18             return;
19         }
20 
21         int[] version = new int[2];
22         if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) {
23             mEGLDisplay = null;
24             glLogE("eglInitialize failed");
25             return;
26         }
27 
28         int[] eglConfigAttribList = new int[]{
29                 EGL14.EGL_RED_SIZE, 8,
30                 EGL14.EGL_GREEN_SIZE, 8,
31                 EGL14.EGL_BLUE_SIZE, 8,
32                 EGL14.EGL_ALPHA_SIZE, 8,
33                 EGL14.EGL_NONE
34         };
35         int[] numEglConfigs = new int[1];
36         EGLConfig[] eglConfigs = new EGLConfig[1];
37         if (!EGL14.eglChooseConfig(mEGLDisplay, eglConfigAttribList, 0, eglConfigs, 0,
38                 eglConfigs.length, numEglConfigs, 0)) {
39             glLogE("eglGetConfigs failed");
40             return;
41         }
42         mEglConfig = eglConfigs[0];
43 
44         int[] eglContextAttribList = new int[]{
45                 EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
46                 EGL14.EGL_NONE
47         };
48         mEglContext = EGL14.eglCreateContext(mEGLDisplay, mEglConfig, EGL14.EGL_NO_CONTEXT,
49                 eglContextAttribList, 0);
50         if (mEglContext == EGL14.EGL_NO_CONTEXT) {
51             glLogE("eglCreateContext failed");
52             return;
53         }
54 
55         int[] surfaceAttribList = {
56                 EGL14.EGL_WIDTH, 64,
57                 EGL14.EGL_HEIGHT, 64,
58                 EGL14.EGL_NONE
59         };
60         // Java线程不进行实际绘制,因此创建PbufferSurface而非WindowSurface
61         mEglSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, mEglConfig, surfaceAttribList, 0);
62         if (mEglSurface == EGL14.EGL_NO_SURFACE) {
63             glLogE("eglCreatePbufferSurface failed");
64             return;
65         }
66 
67         if (!EGL14.eglMakeCurrent(mEGLDisplay, mEglSurface, mEglSurface, mEglContext)) {
68             glLogE("eglMakeCurrent failed");
69             return;
70         }
71         GLES20.glFlush();
72     }
73 
74     public void setupOpenGL() {
75         mRenderThread.execute(new Runnable() {
76             @Override
77             public void run() {
78                 // 初始化OpenGL环境
79                 initOpenGL();
80                 // ...
81             }
82         });
83     }
84 }

      初始化完OpenGL环境之后,就可以在Java线程中愉快地进行OpenGL调用了。我们在OpenGL线程中调用glGenTextures生成纹理ID(见上一节),然后将纹理ID传递给C#,并与Unity场景中的GameObject绑定。但是,由于OpenGL执行环境是线程独立的,Java线程生成的纹理ID并不能被应用到Unity的渲染线程。所以需要让两个线程共享上下文。

      首先,需要获取到Unity线程的EGLContext,因为setupOpenGL是从Unity线程调用过来的,因此我们在该调用中获取当前线程的EGLContext即可。然后,在创建Java线程的EGLContext时,将Unity线程的EGLContext作为参数传递给eglCreateContext即可。Java示例如下:

 1 package com.thornbirds.unity;
 2 
 3 import java.util.concurrent.ExecutorService;
 4 import java.util.concurrent.Executors;
 5 
 6 public class PluginTexture {
 7 
 8     private volatile EGLContext mSharedEglContext;
 9     private volatile EGLConfig mSharedEglConfig;
10 
11     private EGLDisplay mEGLDisplay;
12     private EGLContext mEglContext;
13     private EGLSurface mEglSurface;
14 
15     private void initOpenGL() {
16         mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
17         if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
18             glLogE("eglGetDisplay failed");
19             return;
20         }
21 
22         int[] version = new int[2];
23         if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) {
24             mEGLDisplay = null;
25             glLogE("eglInitialize failed");
26             return;
27         }
28 
29         int[] eglContextAttribList = new int[]{
30                 EGL14.EGL_CONTEXT_CLIENT_VERSION, 3, // 注意:该值需与Unity绘制线程使用的一致,否则eglCreateContext调用会失败,EGL_BAD_MATCH
31                 EGL14.EGL_NONE
32         };
33         // 注意:创建Java线程的EGLContext时,将Unity线程的EGLContext和EGLConfig作为参数传递给eglCreateContext,
34         // 从而实现两个线程共享EGLContext
35         mEglContext = EGL14.eglCreateContext(mEGLDisplay, mSharedEglConfig, mSharedEglContext,
36                 eglContextAttribList, 0);
37         if (mEglContext == EGL14.EGL_NO_CONTEXT) {
38             glLogE("eglCreateContext failed");
39             return;
40         }
41 
42         int[] surfaceAttribList = {
43                 EGL14.EGL_WIDTH, 64,
44                 EGL14.EGL_HEIGHT, 64,
45                 EGL14.EGL_NONE
46         };
47         // Java线程不进行实际绘制,因此创建PbufferSurface而非WindowSurface
48         // 注意:创建Java线程的EGLSurface时,将Unity线程的EGLConfig作为参数传递给eglCreatePbufferSurface
49         mEglSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, mSharedEglConfig, surfaceAttribList, 0);
50         if (mEglSurface == EGL14.EGL_NO_SURFACE) {
51             glLogE("eglCreatePbufferSurface failed");
52             return;
53         }
54 
55         // 由于Java线程只初始化了一个OpenGL执行环境,所以此步是非必需的
56         if (!EGL14.eglMakeCurrent(mEGLDisplay, mEglSurface, mEglSurface, mEglContext)) {
57             glLogE("eglMakeCurrent failed");
58             return;
59         }
60         GLES20.glFlush();
61     }
62 
63     public void setupOpenGL() {
64         // 注意:该调用一定是从Unity绘制线程发起
65         // 获取Unity绘制线程的EGLContext
66         mSharedEglContext = EGL14.eglGetCurrentContext();
67         if (mSharedEglContext == EGL14.EGL_NO_CONTEXT) {
68             glLogE("eglGetCurrentContext failed");
69             return;
70         }
71         EGLDisplay sharedEglDisplay = EGL14.eglGetCurrentDisplay();
72         if (sharedEglDisplay == EGL14.EGL_NO_DISPLAY) {
73             glLogE("sharedEglDisplay failed");
74             return;
75         }
76         // 获取Unity绘制线程的EGLConfig
77         int[] numEglConfigs = new int[1];
78         EGLConfig[] eglConfigs = new EGLConfig[1];
79         if (!EGL14.eglGetConfigs(sharedEglDisplay, eglConfigs, 0, eglConfigs.length,
80                 numEglConfigs, 0)) {
81             glLogE("eglGetConfigs failed");
82             return;
83         }
84         mSharedEglConfig = eglConfigs[0];
85         mRenderThread.execute(new Runnable() {
86             @Override
87             public void run() {
88                 // 初始化OpenGL环境
89                 initOpenGL();
90                 // ... 
91             }
92         });
93     }
94 }

      共享上下文之后,两个线程就可以共享纹理了。将Java线程生成的纹理返回给C#线程即可。不过,此方案只适用在Java线程加载纹理,然后给到Unity线程使用。如果需要在Java线程不断修改纹理数据,会由于并发访问导致Unity线程出现访问非法内存而崩溃。所以,如果需要不断更新纹理内容,多线程OpenGL并不可行,至少以笔者目前的OpenGL水平是不可行的。下回继续。

      如果在使用EGL过程中执行调用失败,可以在该网址查看错误码的描述:https://www.khronos.org/registry/EGL/sdk/docs/man/html/

Java完整代码如下:

技术分享
  1 package com.thornbirds.unity;
  2 
  3 import android.graphics.Bitmap;
  4 import android.graphics.BitmapFactory;
  5 import android.opengl.EGL14;
  6 import android.opengl.EGLConfig;
  7 import android.opengl.EGLContext;
  8 import android.opengl.EGLDisplay;
  9 import android.opengl.EGLSurface;
 10 import android.opengl.GLES10;
 11 import android.opengl.GLES11Ext;
 12 import android.opengl.GLES20;
 13 import android.opengl.GLUtils;
 14 import android.util.Log;
 15 
 16 import java.util.concurrent.ExecutorService;
 17 import java.util.concurrent.Executors;
 18 
 19 public class PluginTexture {
 20     private static final String TAG = "PluginTexture";
 21     private int mTextureID = 0;
 22     private int mTextureWidth = 0;
 23     private int mTextureHeight = 0;
 24 
 25     // 创建单线程池,用于处理OpenGL纹理
 26     private final ExecutorService mRenderThread = Executors.newSingleThreadExecutor();
 27 
 28     public int getStreamTextureWidth() {
 29         return mTextureWidth;
 30     }
 31 
 32     public int getStreamTextureHeight() {
 33         return mTextureHeight;
 34     }
 35 
 36     public int getStreamTextureID() {
 37         return mTextureID;
 38     }
 39 
 40     public PluginTexture() {
 41     }
 42 
 43     private volatile EGLContext mSharedEglContext;
 44     private volatile EGLConfig mSharedEglConfig;
 45 
 46     private EGLDisplay mEGLDisplay;
 47     private EGLContext mEglContext;
 48     private EGLSurface mEglSurface;
 49 
 50     private void glLogE(String msg) {
 51         Log.e(TAG, msg + ", err=" + GLES10.glGetError());
 52     }
 53 
 54     private void initOpenGL() {
 55         mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
 56         if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
 57             glLogE("eglGetDisplay failed");
 58             return;
 59         }
 60 
 61         int[] version = new int[2];
 62         if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) {
 63             mEGLDisplay = null;
 64             glLogE("eglInitialize failed");
 65             return;
 66         }
 67 
 68         int[] eglContextAttribList = new int[]{
 69                 EGL14.EGL_CONTEXT_CLIENT_VERSION, 3, // 注意:该值需与Unity绘制线程使用的一致,否则eglCreateContext调用会失败,EGL_BAD_MATCH
 70                 EGL14.EGL_NONE
 71         };
 72         // 注意:创建Java线程的EGLContext时,将Unity线程的EGLContext和EGLConfig作为参数传递给eglCreateContext,
 73         // 从而实现两个线程共享EGLContext
 74         mEglContext = EGL14.eglCreateContext(mEGLDisplay, mSharedEglConfig, mSharedEglContext,
 75                 eglContextAttribList, 0);
 76         if (mEglContext == EGL14.EGL_NO_CONTEXT) {
 77             glLogE("eglCreateContext failed");
 78             return;
 79         }
 80 
 81         int[] surfaceAttribList = {
 82                 EGL14.EGL_WIDTH, 64,
 83                 EGL14.EGL_HEIGHT, 64,
 84                 EGL14.EGL_NONE
 85         };
 86         // Java线程不进行实际绘制,因此创建PbufferSurface而非WindowSurface
 87         // 注意:创建Java线程的EGLSurface时,将Unity线程的EGLConfig作为参数传递给eglCreatePbufferSurface
 88         mEglSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, mSharedEglConfig, surfaceAttribList, 0);
 89         if (mEglSurface == EGL14.EGL_NO_SURFACE) {
 90             glLogE("eglCreatePbufferSurface failed");
 91             return;
 92         }
 93 
 94         // 由于Java线程只初始化了一个OpenGL执行环境,所以此步是非必需的
 95         if (!EGL14.eglMakeCurrent(mEGLDisplay, mEglSurface, mEglSurface, mEglContext)) {
 96             glLogE("eglMakeCurrent failed");
 97             return;
 98         }
 99         GLES20.glFlush();
100     }
101 
102     public void setupOpenGL() {
103         // 注意:该调用一定是从Unity绘制线程发起
104         // 获取Unity绘制线程的EGLContext
105         mSharedEglContext = EGL14.eglGetCurrentContext();
106         if (mSharedEglContext == EGL14.EGL_NO_CONTEXT) {
107             glLogE("eglGetCurrentContext failed");
108             return;
109         }
110         EGLDisplay sharedEglDisplay = EGL14.eglGetCurrentDisplay();
111         if (sharedEglDisplay == EGL14.EGL_NO_DISPLAY) {
112             glLogE("sharedEglDisplay failed");
113             return;
114         }
115         // 获取Unity绘制线程的EGLConfig
116         int[] numEglConfigs = new int[1];
117         EGLConfig[] eglConfigs = new EGLConfig[1];
118         if (!EGL14.eglGetConfigs(sharedEglDisplay, eglConfigs, 0, eglConfigs.length,
119                 numEglConfigs, 0)) {
120             glLogE("eglGetConfigs failed");
121             return;
122         }
123         mSharedEglConfig = eglConfigs[0];
124         mRenderThread.execute(new Runnable() {
125             @Override
126             public void run() {
127                 // 初始化OpenGL环境
128                 initOpenGL();
129 
130                 // 生成OpenGL纹理ID
131                 int textures[] = new int[1];
132                 GLES20.glGenTextures(1, textures, 0);
133                 if (textures[0] == 0) {
134                     glLogE("glGenTextures failed");
135                     return;
136                 }
137                 mTextureID = textures[0];
138                 mTextureWidth = 640;
139                 mTextureHeight = 360;
140             }
141         });
142     }
143 
144     public void updateTexture() {
145         mRenderThread.execute(new Runnable() {
146             @Override
147             public void run() {
148                 String imageFilePath = "/sdcard/test/image.png";
149                 final Bitmap bitmap = BitmapFactory.decodeFile(imageFilePath);
150 
151                 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureID);
152                 GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
153                 GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
154                 GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
155                 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
156 
157                 bitmap.recycle();
158             }
159         });
160     }
161 
162     public void destroy() {
163         mRenderThread.shutdownNow();
164     }
165 }
View Code

安卓下多线程OpenGL共享Context (二)