为了在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_NONE34 };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_NONE47 };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_NONE59 };60 // Java线程不进行实际绘制,因此创建PbufferSurface而非WindowSurface61 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 @Override77 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_MATCH31 EGL14.EGL_NONE32 };33 // 注意:创建Java线程的EGLContext时,将Unity线程的EGLContext和EGLConfig作为参数传递给eglCreateContext,34 // 从而实现两个线程共享EGLContext35 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_NONE46 };47 // Java线程不进行实际绘制,因此创建PbufferSurface而非WindowSurface48 // 注意:创建Java线程的EGLSurface时,将Unity线程的EGLConfig作为参数传递给eglCreatePbufferSurface49 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绘制线程的EGLContext66 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绘制线程的EGLConfig77 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 @Override87 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绘制线程的EGLContext105 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绘制线程的EGLConfig116 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 @Override126 public void run() {127 // 初始化OpenGL环境128 initOpenGL();129 130 // 生成OpenGL纹理ID131 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 @Override147 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