安卓和Unity线程共享context
Posted zhxmdefj
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了安卓和Unity线程共享context相关的知识,希望对你有一定的参考价值。
Unity项目作为library集成到安卓内请看安卓集成Unity开发示例
本项目的目的是实现以下的流程
一开始我尝试了最直接的方法,通过
@Override public void onPreviewFrame(byte[] data, Camera camera){
// function trans data[] to Unity
}
但是涉及到格式转换问题此流程非常低效,而且onPreviewFrame()
方法的回传也涉及到从GPU拷贝到CPU的操作,性能依然不高。
既然我们的最终目的都是传到GPU上让Unity渲染线程渲染,那能不能直接在GPU层传递纹理数据到Unity呢?
完全没有问题,只要我们在Unity线程中拿到EGLContext和EGLConfig,将其作为参数传递给Java线程的eglCreateContext()
创建Java线程的EGLContext,两个线程就可以共享EGLContext了
先在安卓端写好获取eglcontext的方法,供Unity调用
// 创建单线程池,用于处理OpenGL纹理
private final ExecutorService mRenderThread = Executors.newSingleThreadExecutor();
public int getStreamTextureWidth() {return mTextureWidth;}
public int getStreamTextureHeight() {return mTextureHeight;}
public int getStreamTextureID() {return mTextureID;}
private void glLogE(String msg) {
Log.e(TAG, msg + ", err=" + GLES20.glGetError());
}
// 被unity调用获取EGLContext
public void setupOpenGL() {
Log.d(TAG, "setupOpenGL called by Unity ");
// 获取Unity线程的EGLContext,EGLDisplay
mSharedEglContext = EGL14.eglGetCurrentContext();
if (mSharedEglContext == EGL14.EGL_NO_CONTEXT) {
glLogE("eglGetCurrentContext failed");
return;
}glLogE("eglGetCurrentContext success");
EGLDisplay sharedEglDisplay = EGL14.eglGetCurrentDisplay();
if (sharedEglDisplay == EGL14.EGL_NO_DISPLAY) {
glLogE("sharedEglDisplay failed");
return;
}glLogE("sharedEglDisplay success");
// 获取Unity绘制线程的EGLConfig
int[] numEglConfigs = new int[1];
EGLConfig[] eglConfigs = new EGLConfig[1];
if (!EGL14.eglGetConfigs(sharedEglDisplay, eglConfigs, 0, eglConfigs.length,
numEglConfigs, 0)) {
glLogE("eglGetConfigs failed");
return;
}glLogE("eglGetConfigs success");
mSharedEglConfig = eglConfigs[0];
mRenderThread.execute(new Runnable() {
@Override
public void run() {
// Java线程初始化OpenGL环境
initOpenGL();
// 生成OpenGL纹理ID
int textures[] = new int[1];
GLES20.glGenTextures(1, textures, 0);
if (textures[0] == 0) {
glLogE("glGenTextures failed");
return;
}glLogE("glGenTextures success");
mTextureID = textures[0];
mTextureWidth = 670;
mTextureHeight = 670;
}
});
}
Java线程在此之后初始化OpenGL环境
private void initOpenGL() {
mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
glLogE("eglGetDisplay failed");
return;
}glLogE("eglGetDisplay success");
int[] version = new int[2];
if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) {
mEGLDisplay = null;
glLogE("eglInitialize failed");
return;
}glLogE("eglInitialize success");
int[] eglContextAttribList = new int[]{
EGL14.EGL_CONTEXT_CLIENT_VERSION, 3, // 版本需要与Unity使用的一致
EGL14.EGL_NONE
};
// 将Unity线程的EGLContext和EGLConfig作为参数,传递给eglCreateContext,
// 创建Java线程的EGLContext,从而实现两个线程共享EGLContext
mEglContext = EGL14.eglCreateContext(
mEGLDisplay, mSharedEglConfig, mSharedEglContext,
eglContextAttribList, 0);
if (mEglContext == EGL14.EGL_NO_CONTEXT) {
glLogE("eglCreateContext failed");
return;
}glLogE("eglCreateContext success");
int[] surfaceAttribList = {
EGL14.EGL_WIDTH, 64,
EGL14.EGL_HEIGHT, 64,
EGL14.EGL_NONE
};
// Java线程不进行实际绘制,因此创建PbufferSurface而非WindowSurface
// 将Unity线程的EGLConfig作为参数传递给eglCreatePbufferSurface
// 创建Java线程的EGLSurface
mEglSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, mSharedEglConfig, surfaceAttribList, 0);
if (mEglSurface == EGL14.EGL_NO_SURFACE) {
glLogE("eglCreatePbufferSurface failed");
return;
}glLogE("eglCreatePbufferSurface success");
if (!EGL14.eglMakeCurrent(mEGLDisplay, mEglSurface, mEglSurface, mEglContext)) {
glLogE("eglMakeCurrent failed");
return;
}glLogE("eglMakeCurrent success");
GLES20.glFlush();
}
共享context后,两个线程就可以共享纹理了。将Java线程生成的纹理id返回给Unity线程即可,C#代码如下:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
public class GLTexture : MonoBehaviour
{
private AndroidJavaObject mGLTexCtrl;
private int mTextureId;
private int mWidth;
private int mHeight;
private void Awake(){
// 实例化com.xxx.nativeandroidapp.GLTexture类的对象
mGLTexCtrl = new AndroidJavaObject("com.xxx.nativeandroidapp.GLTexture");
// 初始化OpenGL
mGLTexCtrl.Call("setupOpenGL");
}
void Start(){
BindTexture();
}
void BindTexture(){
// 获取JavaPlugin生成的纹理ID
mTextureId = mGLTexCtrl.Call<int>("getStreamTextureID");
if (mTextureId == 0){
Debug.LogError("getStreamTextureID failed");
return;
} Debug.Log("getStreamTextureID success");
mWidth = mGLTexCtrl.Call<int>("getStreamTextureWidth");
mHeight = mGLTexCtrl.Call<int>("getStreamTextureHeight");
// 将纹理ID与当前GameObject绑定
material.mainTexture = Texture2D.CreateExternalTexture(mWidth, mHeight, TextureFormat.ARGB32, false, false, (IntPtr)mTextureId);
// 更新纹理数据
mGLTexCtrl.Call("updateTexture");
}
}
unity需要调用updateTexture方法更新纹理
public void updateTexture() {
//Log.d(TAG,"updateTexture called by unity");
mRenderThread.execute(new Runnable() {
@Override
public void run() {
String imageFilePath = "your own picture path";//图片路径
final Bitmap bitmap = BitmapFactory.decodeFile(imageFilePath);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureID);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
bitmap.recycle();//回收内存
}
});
}
同时注意必须关闭unity的多线程渲染,否则无法获得Unity渲染线程的EGLContext:
然后就可以将Unity工程打包到安卓项目,如果没意外是可以显示纹理到cube等3Dobject上的
上述的方案假定Java层更新纹理时使用的是RGB或RBGA格式的数据,但是播放视频或者camera预览这种应用场景下,解码器解码出来的数据如果是YUV格式,渲染起来就稍微麻烦点,考虑到性能需要使用GPU进行转换,可以编写Shader来实现。
如前文所述,Unity只需要Java层的纹理ID,当使用Shader进行YUV转RGB时,怎么实现更新该纹理的数据呢?答案是Render to Texture。具体做法是,创建一个FrameBuffer,调用glFramebufferTexture2D将纹理与FrameBuffer关联起来,这样在FrameBuffer上进行的绘制,就会被写入到该纹理中。
我们先新建TextureSurface用于接收摄像头的数据
mCameraInputSurface = new SurfaceTexture(0);
mCameraInputSurface.setOnFrameAvailableListener(this);
mCameraInputSurface.setDefaultBufferSize(mFrameWidth, mFrameHeight);
mOutputSurfaceTexture.setOnFrameAvailableListener(this);
设置摄像机的预览到这个Surface,并开始预览
mCamera.setPreviewTexture(mCameraInputSurface);
mCamera.startPreview();
然后这个SurfaceTexture就可以跨线程共享硬件纹理数据了。
和之前一样,在u3d的主线程中获取OpenGL的共享Context,用这个context创建共享线程,然后这个线程对mOutputTex
进行绘制。先用这个mOutputTex
绑定好FBO,然后把上面mCameraInputSurface
的图像渲染到FBO中,最后通知u3d的主线程,可以使用这张纹理进行渲染了。这里的坑之后再填。
以上是关于安卓和Unity线程共享context的主要内容,如果未能解决你的问题,请参考以下文章
JUC并发编程 共享模式之工具 JUC CountdownLatch(倒计时锁) -- CountdownLatch应用(等待多个线程准备完毕( 可以覆盖上次的打印内)等待多个远程调用结束)(代码片段