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

相关文章