首页 > 代码库 > 安卓下多线程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 }
安卓下多线程OpenGL共享Context (二)