安卓下多线程OpenGL共享Context (一)
最近在研究Unity3D开发中使用Java Plugin进行纹理更新,想法很简单,在Java线程更新纹理数据,然后Unity场景中的纹理将自动更新。
首先,创建Java类,定义创建纹理及获取纹理参数的接口,并创建单线程池用于进行加载Bitmap并绑定纹理数据等OpenGL操作。Java代码示例:
1 package com.thornbirds.unity; 2 3 import android.graphics.Bitmap; 4 import android.graphics.BitmapFactory; 5 import android.opengl.GLES11Ext; 6 import android.opengl.GLES20; 7 import android.opengl.GLUtils; 8 import android.util.Log; 9 10 import java.util.concurrent.ExecutorService; 11 import java.util.concurrent.Executors; 12 13 public class PluginTexture { 14 private static final String TAG = "PluginTexture"; 15 private int mTextureID = 0; 16 private int mTextureWidth = 0; 17 private int mTextureHeight = 0; 18 19 // 创建单线程池,用于处理OpenGL纹理 20 private final ExecutorService mRenderThread = Executors.newSingleThreadExecutor(); 21 22 public int getStreamTextureWidth() { 23 return mTextureWidth; 24 } 25 26 public int getStreamTextureHeight() { 27 return mTextureHeight; 28 } 29 30 public int getStreamTextureID() { 31 return mTextureID; 32 } 33 34 public PluginTexture() { 35 } 36 37 public void setupOpenGL() { 38 mRenderThread.execute(new Runnable() { 39 @Override 40 public void run() { 41 // 生成OpenGL纹理ID 42 int textures[] = new int[1]; 43 GLES20.glGenTextures(1, textures, 0); 44 if (textures[0] == 0) { 45 Log.e(TAG, "glGenTextures failed"); 46 return; 47 } 48 mTextureID = textures[0]; 49 mTextureWidth = 640; 50 mTextureHeight = 360; 51 } 52 }); 53 } 54 55 public void updateTexture() { 56 mRenderThread.execute(new Runnable() { 57 @Override 58 public void run() { 59 String imageFilePath = "/sdcard/test/image.png"; 60 final Bitmap bitmap = BitmapFactory.decodeFile(imageFilePath); 61 62 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureID); 63 GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST); 64 GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST); 65 GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0); 66 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); 67 68 bitmap.recycle(); 69 } 70 }); 71 } 72 73 public void destroy() { 74 mRenderThread.shutdownNow(); 75 } 76 }
然后,创建C#脚本,在脚本中示例化Java对象,并调用Java方法初始化OpenGL;为了展示更新纹理,需获取到Java生成的纹理ID,并将其与脚本当前GameObject绑定。C#代码示例:
1 using System; 2 using UnityEngine; 3 4 public class PluginTestureDemo : MonoBehaviour { 5 private AndroidJavaObject mPluginTexture; 6 private int mTextureId; 7 private int mWidth; 8 private int mHeight; 9 10 void Start () { 11 // 实例化com.thornbirds.unity.PluginTexture类的对象 12 mPluginTexture = new AndroidJavaObject ("com.thornbirds.unity.PluginTexture"); 13 // 初始化OpenGL 14 mPluginTexture.Call ("setupOpenGL"); 15 } 16 17 void BindTexture () { 18 // 获取JavaPlugin生成的纹理ID 19 mTextureId = mPluginTexture.Call<int> ("getPluginTextureID"); 20 if (mTextureId == 0) { 21 Debug.LogError ("getPluginTextureID failed"); 22 return; 23 } 24 mWidth = mPluginTexture.Call<int> ("getPluginTextureWidth"); 25 mHeight = mPluginTexture.Call<int> ("getPluginTextureHeight"); 26 // 将纹理ID与当前GameObject绑定 27 GetComponent<MeshRenderer> ().material.mainTexture = Texture2D.CreateExternalTexture 28 (mWidth, mHeight, TextureFormat.ARGB32, false, false, (IntPtr) mTextureId); 29 // 更新纹理数据 30 mPluginTexture.Call ("updateTexture"); 31 } 32 }
注意:C#中使用了方法名调用Java方法,因此需要防止编译Jar包时Java类及其public方法被混淆。在Proguard文件中加入如下代码:
1 -keep class com.thornbirds.unity.PluginTexture { 2 public *; 3 }
该方案存在一个问题,在Java线程中访问进行OpenGL操作,会报Missing GLContext,需要为Java线程初始化OpenGL绘制环境。下回继续。