為了在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 }