代わりにTextureView
(またはSurfaceView
?)にレンダリングするには、CreateOffscreenBuffer
の代わりにCreateWindowSurface
を使用します。
using System;
using Android.Graphics;
using Android.Runtime;
using Javax.Microedition.Khronos.Egl;
namespace YourAppNameHere
{
// Manage an EGLContext. This allows GL rendering without a GLSurfaceView or TextureView.
// The heart of this class is "MakeCurrent": after calling that, you can make GL calls,
// because you have an active EGLContext.
// The GL calls render to an offscreen buffer, previously created in CreateGLAndSurface via CreateOffscreenBuffer.
// To instead render to a `TextureView` (or `SurfaceView`?), then use `CreateWindowSurface` instead of `CreateOffscreenBuffer`.
public class OurGLRenderer
{
// Your app supplies this class.
public interface IRenderEngine
{
void EnsureInitialized();
// The frame buffer or view size.
void EnsureSize(int width, int height);
// Our client calls our MakeCurrent, then calls this to render.
// "model" should be a class in your app.
// On Android, this could return a Bitmap, which you then place in an ImageView.
object RenderAsPlatformImage(object model);
}
// HACK: ASSUMES Singleton.
public static Action OneTimeAfterCreated;
// Most recent error code.
static int _error = 0;
#region "=== static methods - could be in a utility class ==="
// These are static, so that they can be used independently. Could be "public".
// ----- Based on https://forums.xamarin.com/discussion/3406/xamarin-android-textureview-sample-render-an-opengl-scene-to-a-view -----
static bool InitializeEGL(out IEGL10 _egl10, out EGLDisplay _display, out bool _display_initialized,
out bool _choose_config, out EGLConfig _config)
{
_display_initialized = false;
_choose_config = false;
_config = null;
//FAIL Javax.Microedition.Khronos.Egl.IEGL10 t_egl10 = (Javax.Microedition.Khronos.Egl.IEGL10)Javax.Microedition.Khronos.Egl.EGLContext.EGL;
_egl10 = EGLContext.EGL.JavaCast<IEGL10>();
// _display
_display = _egl10.EglGetDisplay(EGL10.EglDefaultDisplay); // EglGetCurrentDisplay returns NULL !
if (_display == null)
return false;
// EglInitialize
int[] _major_minor = new int[ 2 ];
_display_initialized = _egl10.EglInitialize(_display, _major_minor);
Console.WriteLine(string.Format("EglInitialize -> {0}, version={1}.{2}", _display_initialized, _major_minor[ 0 ], _major_minor[ 1 ]));
if (!CheckEglError(_egl10, "EglInitialize") || !_display_initialized)
return false;
return InitializeEGLConfig(_egl10, _display, out _choose_config, out _config);
}
static bool InitializeEGLConfig(IEGL10 _egl10, EGLDisplay _display,
out bool _choose_config, out EGLConfig _config)
{
_config = null;
// EglChooseConfig -> OpenGL ES 2.0 Config
int EGL_OPENGL_ES2_BIT = 4;
int[] _attribs_config = new int[]{
EGL10.EglRenderableType, EGL_OPENGL_ES2_BIT, // IMPORTANT
EGL10.EglRedSize, 8,
EGL10.EglGreenSize, 8,
EGL10.EglBlueSize, 8,
EGL10.EglAlphaSize, 8,
EGL10.EglDepthSize, 0,
EGL10.EglStencilSize, 0,
EGL10.EglNone
};
EGLConfig[] _configs = null;
_configs = new EGLConfig[ 1 ];
int[] _numconfigs = new int[ 1 ];
_choose_config = _egl10.EglChooseConfig(_display, _attribs_config, _configs, 1, _numconfigs);
if (!CheckEglError(_egl10, "EglChooseConfig") || !_choose_config)
return false;
_config = _configs[ 0 ];
// Why? (I guess so not holding another reference.)
_configs[ 0 ] = null; _configs = null;
return (_config != null);
}
static bool EglCreateContext(IEGL10 _egl10, EGLDisplay _display, EGLConfig _config,
out EGLContext _context)
{
// EglCreateContext -> OpenGL ES 2.0 Context
int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
int _version = EGL10.EglVersion;
int[] _attribs_config = new int[]{
EGL_CONTEXT_CLIENT_VERSION, 2, // IMPORTANT
EGL10.EglNone
};
_context = _egl10.EglCreateContext(_display, _config, EGL10.EglNoContext, _attribs_config);
return CheckEglError(_egl10, "EglCreateContext") && (_context != null);
}
static bool CreateWindowSurface(IEGL10 _egl10, EGLDisplay _display, EGLConfig _config, SurfaceTexture _surfaceTexture,
out EGLSurface _surface)
{
_surface = null;
if (_surfaceTexture == null)
return false;
// EglCreateWindowSurface
int[] _attribs_config = new int[]{
EGL10.EglNone
};
_surface = _egl10.EglCreateWindowSurface(_display, _config, _surfaceTexture, _attribs_config);
return CheckEglError(_egl10, "EglCreateWindowSurface") && (_surface != null);
}
static bool CreateOffscreenBuffer(IEGL10 _egl10, EGLDisplay _display, EGLConfig _config, int width, int height,
out EGLSurface _surface)
{
int[] _attribs_config = new int[]{
EGL10.EglWidth, width,
EGL10.EglHeight, height,
EGL10.EglNone
};
_surface = _egl10.EglCreatePbufferSurface(_display, _config, _attribs_config);
return CheckEglError(_egl10, "EglCreatePbufferSurface") && (_surface != null);
}
static bool EglMakeCurrent(IEGL10 _egl10, EGLDisplay _display, EGLSurface _surface, EGLContext _context,
out bool _make_current)
{
_make_current = _egl10.EglMakeCurrent(_display, _surface, _surface, _context);
return (CheckEglError(_egl10, "EglMakeCurrent") && _make_current);
}
static bool EglSwapBuffers(IEGL10 _egl10, EGLDisplay _display, EGLSurface _surface)
{
bool _ok = _egl10.EglSwapBuffers(_display, _surface);
return (CheckEglError(_egl10, "EglSwapBuffers") && _ok);
}
static bool CheckEglError(IEGL10 _egl10, string tag)
{
_error = _egl10.EglGetError();
if (_error != EGL10.EglSuccess) {
Log.e(tag, string.Format("EGL-Error={0}", _error));
return false;
}
return true;
}
#endregion
#region "=== constructor and Dispose ==="
public OurGLRenderer(int ourWidth, int ourHeight)
{
Width = ourWidth; Height = ourHeight;
EnsureOurRenderEngine(ourWidth, ourHeight);
}
public void Dispose()
{
OurRenderEngine = null;
EndOurGL();
}
#endregion
#region "=== public fields ==="
public int Width { get; protected set; }
public int Height { get; protected set; }
public IRenderEngine OurRenderEngine;
#endregion
#region "=== public methods ==="
// NOTE: call EndOurGL when leave fragment.
public bool BeginOurGL(int width, int height)
{
if (alreadyBeginningOurGL)
// CAUTION: Caller must not call EndOurGLThread - might be another view starting it!
return false;
alreadyBeginningOurGL = true;
try {
if (!EnsureGLAndSurfaceInitialized(width, height)) {
return false;
}
//TEST_TextureView(_egl10, null); // tmstest
return true;
} finally {
alreadyBeginningOurGL = false;
}
}
// Client must call this before any GL calls.
// Before first GL call, and whenever Android may have done drawing in its own EGLContext.
public bool MakeCurrent()
{
return EglMakeCurrent(_egl10, _display, _surface, _context, out _make_current);
}
// ASSUME MakeCurrent already called.
public void EnsureOurSize(int ourWidth, int ourHeight)
{
// In our app, we create an OurGLRenderer, then use it to render multiple images of the same size -
// our IRenderEngine is set up once for that size.
// You might not have this constraint; in which case, comment this out.
if (Width != ourWidth || Height != ourHeight)
throw new InvalidProgramException("OurGLRenderer.EnsureOurSize - all images must be same size.");
OurRenderEngine.EnsureSize(Width, Height);
}
public void EndOurGL()
{
EndAndDispose(_egl10); _egl10 = null;
}
#endregion
#region "=== private fields ==="
IEGL10 _egl10 = null;
EGLDisplay _display = null;
bool _display_initialized = false;
bool _choose_config = false;
EGLConfig _config = null;
EGLSurface _surface = null;
EGLContext _context = null;
bool _make_current = false;
bool alreadyBeginningOurGL = false;
#endregion
#region "=== private methods ==="
bool EnsureGLAndSurfaceInitialized(int width, int height)
{
if (_surface == null) {
if (!CreateGLAndSurface(width, height))
return false;
}
// TODO: Try this, once NOT on PaintingView.
if (false) {
// Make current. We aren't rendering yet, but confirm that this succeeds.
if (!MakeCurrent()) {
// Failed; undo any work that was done.
EndOurGL();
return false;
}
}
return true;
}
bool CreateGLAndSurface(int width, int height)
{
if (!InitializeEGL(out _egl10, out _display, out _display_initialized, out _choose_config, out _config) ||
!EglCreateContext(_egl10, _display, _config, out _context) ||
!CreateOffscreenBuffer(_egl10, _display, _config, width, height, out _surface)) {
// Failed; undo any work that was done.
EndOurGL();
return false;
}
return true;
}
void EndAndDispose(IEGL10 _egl10)
{
// EglMakeCurrent
if (_make_current) {
_egl10.EglMakeCurrent(_display, EGL10.EglNoSurface, EGL10.EglNoSurface, EGL10.EglNoContext);
_make_current = false;
}
// EglDestroyContext
if (_context != null) {
_egl10.EglDestroyContext(_display, _context);
_context = null;
}
// EglDestroySurface
if (_surface != null) {
_egl10.EglDestroySurface(_display, _surface);
_surface = null;
}
//
if (_config != null) {
_config.Dispose();
_config = null;
}
// EglTerminate
if (_display_initialized) {
_egl10.EglTerminate(_display);
_display_initialized = false;
}
//
if (_display != null) {
_display.Dispose();
_display = null;
}
}
#endregion
#region "=== specific to our app ==="
// Shows that OurRenderEngine must be created, and BeginOurGL called.
// Also shows a call to MakeCurrent, and OurRenderEngine.EnsureInitialized.
public void EnsureOurRenderEngine(int ourWidth, int ourHeight)
{
// if ((OurRenderEngine == null) || !ReferenceEquals(AppState.ActiveRenderEngine, OurRenderEngine)) {
// AppState.ReleaseRenderEngine();
// // NOTE: We can't pass a reDrawDelegate because multiple views are sharing this engine.
// //AppState.ActiveRenderEngine = OurRenderEngine = AppState.CreateRenderEngine(MainActivity.OurAppType, ourWidth, ourHeight, null);
// AppState.ActiveRenderEngine = OurRenderEngine = (OffscreenRenderEngine)AppState.CreateRenderEngine(AppState.AppType.Offscreen, ourWidth, ourHeight, null);
//
// if (Width != ourWidth || Height != ourHeight)
// throw new InvalidProgramException("OurGLRenderer.EnsureOurRenderEngine - all images must be same size.");
//
// // BEFORE ActiveRenderEngine.EnsureInitialized.
// // TODO: GL Program fails to compile, when called in OnDraw. Conflict with framework's GL context?
// BeginOurGL(ourWidth, ourHeight);
//
// // Before any GL calls.
// if (!MakeCurrent())
// return;
// // Needed because FragmentMain.OnCreateView runs before this, so its initialization is skipped (no ActiveRenderEngine yet)..
// OurRenderEngine.EnsureInitialized();
//
// if (OneTimeAfterCreated != null) {
// OneTimeAfterCreated();
// OneTimeAfterCreated = null;
// }
// }
}
#endregion
}
}