NOTE: This is Xamarin C# code. That is why it has different capitalization and other naming differences (java get/set methods replaced by C# properties). It uses Xamarin wrappers for Android java, a java version would be a 1:1 translation of each line of code.
OurGLRenderer is a custom class to 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.
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
}
}