安卓下多線程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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM