元ネタ
https://github.com/Unity-Technologies/NativeRenderingPlugin
https://qiita.com/fukaken5050/items/63940b65ec79a59161eb
環境
OS : win10pro
IDE : AndroidStudio 4.0.1
Unity : 2019.4.5f1
Unity/VS : VS2019
確認した実機: Galaxys8
要点の箇条書き
低レベルネイティブプラグインインターフェース(Low-Level Native Plugin Interface)を使ってUnityAndroid/Pluginで動画を再生する。
・AndroidStudio/C++で次を#includeする。Unityインストールしたフォルダからコピペする。
# include "IUnityInterface.h"
# include "IUnityGraphics.h" // UnityRenderingEvent
・Unityプラグインの更新はUnity再起動が必要。
→ ホットリロード検索、名前を変えて追加するなど
・今回はOpenGLESを使う。多分Vulkanでもできると思う。
基本知識
◆ 低レベルネイティブプラグインインターフェース
Unityのレンダリングは、マルチスレッドです。
レンダリング APIは、MonoBehaviourスレッドとは別のスレッド上で行われます。プラグインはUnityレンダースレッドと干渉する可能性があります。そのため、GL.IssuePluginEvent でメインスレッドからプラグインを呼びます。
Unityカメラの MonoBehaviour.OnPostRender から GL.IssuePluginEvent を呼び出す場合、プラグインはカメラのレンダリング終了後すぐにコールバックを取得します。UnityRenderingEventを使うには IUnityGraphics.h をincludeします。
◆OpenGL グラフィックス API を使用したプラグイン
2種類の OpenGL オブジェクトがあります。
1.OpenGL コンテキストをまたいで共有されるオブジェクト (texture, renderBuffer,shader等)
2.OpenGL コンテキストごとのオブジェクト(頂点配列,program pipeline等)
Unity は複数の OpenGL コンテキストを使用します。
エディターとプレイヤーの初期化と終了のときは
マスターコンテキストに依存しますが、レンダリングには専門のコンテキストを使用します。
したがって、kUnityGfxDeviceEventInitialize と kUnityGfxDeviceEventShutdown イベントの間、
コンテキストごとのオブジェクトを作成することはできません。
例えば、ネイティブのプラグインは kUnityGfxDeviceEventInitialize イベント中に頂点配列オブジェクトを作成できず、
UnityRenderingEvent コールバックでそれを使用することはできません。
これは、アクティブなコンテキストが頂点配列オブジェクトの作成時に使用されるものでないからです。
◆ Unity c# からnative plugin(C++)を呼ぶ方法(1)
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;
[DllImport("nativerender")]
private static extern bool SetupNativeTextureRender(IntPtr textureId, int width, int height);
Javaのように"com.example.hoge"は意識しなくても良いです。
プラグイン配置場所はUnityApp\Assets\Plugins\Androidです(Androidの場合)
プラグインの名前が重要です。”libnativerender.so”の場合はlib/.soを除去して[DllImport("nativerender")]です。
プラグインの更新後はUnity再起動が必要です。
void*の代わりにIntPtrを使います。
メモ
◆初期化
Unityのprivate void Start() でプラグインの初期化をします。
UnityのTexture2Dを生成して、テクスチャアドレスtexture.GetNativeTexturePtr()をプラグインに渡します。
_rawImage.textureをUnity-RawImage等にセットして描画結果を確認します。
var texture = new Texture2D(_width, _height, TextureFormat.ARGB32, false);
_rawImage.texture = texture;
SetupNativeTextureRender(texture.GetNativeTexturePtr(), texture.width,texture.height) ;
StartCoroutine(NativeTextureRenderLoop()); //コルーチン起床
◆コルーチン/描画
private IEnumerator NativeTextureRenderLoop() {
while (true) {
yield return new WaitForEndOfFrame();
GL.IssuePluginEvent(GetRenderEventFunc(), 1);
}
}
メモ
SetupNativeTextureRender( unityで生成したテクスチャメモリ,w,h)をC++へ渡す。->g_pBytes,g_textureId
g_pBytesを更新する。
UnityRenderingEvent():
glBindTexture( GL_TEXTURE_2D, g_textureId );でg_textureId を有効にする。
glTexSubImage2D( GL_TEXTURE_2D...) にg_pBytesを渡してOpenGLESテクスチャを更新する
コード C++ (拝借してます)
# include "IUnityInterface.h"
# include "IUnityGraphics.h" // UnityRenderingEvent
# include <math.h>
# include <stdio.h>
# include <string>
# include <assert.h>
# include <GLES2/gl2.h>
# include <jni.h>
static GLuint g_textureId = NULL;
static int g_texWidth;
static int g_texHeight;
static u_char* g_pBytes = NULL;
# define LOG_PRINTF printf
# if 1
extern "C" bool SetupNativeTextureRender( void* textureId, int width, int height )
{
g_textureId = (GLuint)(size_t)textureId;
g_texWidth = width;
g_texHeight = height;
LOG_PRINTF( "SetupNativeTextureRender:%d, %d, %d", g_textureId, g_texWidth, g_texHeight );
g_pBytes = new u_char[ g_texWidth * g_texHeight * 4 ];
return true;
}
extern "C" void FinishNativeTextureRender()
{
if( g_pBytes != NULL )
delete[] g_pBytes;
g_pBytes = NULL;
}
static void UNITY_INTERFACE_API
OnRenderEvent( int eventID )
{
glBindTexture( GL_TEXTURE_2D, g_textureId );
static u_char s_r = 0;
u_char* bytes = g_pBytes;
for( int y = 0; y < g_texHeight; y++ )
{
for( int x = 0; x < g_texWidth; x++ )
{
int offset = ( ( y * g_texWidth ) + x ) * 4;
bytes[ offset + 0 ] = s_r;
bytes[ offset + 1 ] = 0;
bytes[ offset + 2 ] = 0;
bytes[ offset + 3 ] = 255;
}
}
glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, g_texWidth, g_texHeight, GL_RGBA, GL_UNSIGNED_BYTE, bytes );
s_r ++;
s_r %= 255;
}
extern "C" UnityRenderingEvent UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API
GetRenderEventFunc()
{
return OnRenderEvent;
}
//com.ore.unityplugin
extern "C" JNIEXPORT jstring JNICALL
Java_com_ore_unityplugin_Calc_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "hoge from C++";
return env->NewStringUTF(hello.c_str());
}
# endif
Unity
# define USE_ANDROID_PLUGIN
using System;
using System.Collections;
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.UI;
public class TestNativeCppRender : MonoBehaviour {
[SerializeField] private RawImage _rawImage = null;
[SerializeField] private int _width = 512;
[SerializeField] private int _height = 512;
public Text m_text;
//PluginFunction
//nativerender
[DllImport("nativerender")]
private static extern bool SetupNativeTextureRender(IntPtr textureId, int width, int height);
[DllImport("nativerender")]
private static extern void FinishNativeTextureRender();
[DllImport("nativerender")]
private static extern IntPtr GetRenderEventFunc();
private void Start() {
var texture = new Texture2D(_width, _height, TextureFormat.ARGB32, false);
_rawImage.texture = texture;
# if USE_ANDROID_PLUGIN
if (SetupNativeTextureRender(texture.GetNativeTexturePtr(), texture.width, texture.height) == false) {
m_text.text = "fail SetupNativeTextureRender";
return;
}
StartCoroutine(NativeTextureRenderLoop());
m_text.text = "after StartCoroutine";
# else
m_text.text = " editor";
StartCoroutine(NativeTextureRenderLoop());
# endif
}
private void OnDestroy() {
FinishNativeTextureRender();
}
private IEnumerator NativeTextureRenderLoop() {
int cnt = 0;
while (true) {
yield return new WaitForEndOfFrame();
GL.IssuePluginEvent(GetRenderEventFunc(), 1);
m_text.text = String.Format("cnt = {0}", cnt);
cnt++;
}
}
}
CMakelists.txt更新
makeを更新したら「LinkC++ProjectWithGradle」する