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
}
}