xash3d-fwgs/engine/platform/android/eglutil.c

454 lines
11 KiB
C

/*
eglutil.c - EGL context utility
Copyright (C) 2023 mittorn
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*/
#include "platform/platform.h"
#if !defined(XASH_DEDICATED) && !XASH_SDL
#include "eglutil.h"
#include "client.h"
#include "vid_common.h"
#include <EGL/egl.h>
#include <EGL/eglext.h>
struct eglapi_s egl;
#undef GetProcAddress // windows.h?
#define EGL_FF(x) {"egl"#x, (void*)&egl.x}
static dllfunc_t egl_funcs[] =
{
EGL_FF(SwapInterval),
EGL_FF(SwapBuffers),
EGL_FF(GetError),
EGL_FF(GetCurrentDisplay),
EGL_FF(GetCurrentSurface),
EGL_FF(GetProcAddress),
EGL_FF(GetConfigAttrib),
EGL_FF(GetDisplay),
EGL_FF(Initialize),
EGL_FF(Terminate),
EGL_FF(QueryString),
EGL_FF(ChooseConfig),
EGL_FF(CreateWindowSurface),
EGL_FF(CreateContext),
EGL_FF(DestroyContext),
EGL_FF(MakeCurrent),
EGL_FF(BindAPI),
EGL_FF(DestroySurface),
{ NULL, NULL }
};
#undef EGL_FF
dll_info_t egl_info = { "libEGL.so", egl_funcs, false };
struct eglstate_s eglstate;
/*
===================
refapi/egl wrappers
===================
*/
int EGL_SetAttribute( int attr, int val )
{
if( attr < 0 || attr >= REF_GL_ATTRIBUTES_COUNT )
return -1;
eglstate.gl_attribs[attr] = val;
eglstate.gl_attribs_set[attr] = true;
return 0;
}
#define COPY_ATTR_IF_SET( refattr, attr ) \
if( eglstate.gl_attribs_set[refattr] ) \
{ \
attribs[i++] = attr; \
attribs[i++] = eglstate.gl_attribs[refattr]; \
}
size_t EGL_GenerateConfig( EGLint *attribs, size_t size )
{
size_t i = 0;
memset( attribs, 0, size * sizeof( EGLint ) );
eglstate.gles1 = false;
memset( eglstate.gl_attribs, 0, sizeof( eglstate.gl_attribs ));
memset( eglstate.gl_attribs_set, 0, sizeof( eglstate.gl_attribs_set ));
// refdll can request some attributes
ref.dllFuncs.GL_SetupAttributes( glw_state.safe );
COPY_ATTR_IF_SET( REF_GL_RED_SIZE, EGL_RED_SIZE );
COPY_ATTR_IF_SET( REF_GL_GREEN_SIZE, EGL_GREEN_SIZE );
COPY_ATTR_IF_SET( REF_GL_BLUE_SIZE, EGL_BLUE_SIZE );
COPY_ATTR_IF_SET( REF_GL_ALPHA_SIZE, EGL_ALPHA_SIZE );
COPY_ATTR_IF_SET( REF_GL_DEPTH_SIZE, EGL_DEPTH_SIZE );
COPY_ATTR_IF_SET( REF_GL_STENCIL_SIZE, EGL_STENCIL_SIZE );
COPY_ATTR_IF_SET( REF_GL_MULTISAMPLEBUFFERS, EGL_SAMPLE_BUFFERS );
COPY_ATTR_IF_SET( REF_GL_MULTISAMPLESAMPLES, EGL_SAMPLES );
if( eglstate.gl_attribs_set[REF_GL_ACCELERATED_VISUAL] )
{
attribs[i++] = EGL_CONFIG_CAVEAT;
attribs[i++] = eglstate.gl_attribs[REF_GL_ACCELERATED_VISUAL] ? EGL_NONE : EGL_DONT_CARE;
}
// BigGL support
attribs[i++] = EGL_RENDERABLE_TYPE;
eglstate.gl_api = EGL_OPENGL_ES_API;
if( eglstate.gl_attribs_set[REF_GL_CONTEXT_PROFILE_MASK] &&
!( eglstate.gl_attribs[REF_GL_CONTEXT_PROFILE_MASK] & REF_GL_CONTEXT_PROFILE_ES ))
{
attribs[i++] = EGL_OPENGL_BIT;
eglstate.gl_api = EGL_OPENGL_API;
}
else if( eglstate.gl_attribs_set[REF_GL_CONTEXT_MAJOR_VERSION] &&
eglstate.gl_attribs[REF_GL_CONTEXT_MAJOR_VERSION] >= 2 )
{
attribs[i++] = EGL_OPENGL_ES2_BIT;
}
else
{
i--; // erase EGL_RENDERABLE_TYPE
eglstate.gles1 = true;
}
attribs[i++] = EGL_NONE;
return i;
}
size_t EGL_GenerateContextConfig( EGLint *attribs, size_t size )
{
size_t i = 0;
memset( attribs, 0, size * sizeof( EGLint ));
if( Q_strstr( eglstate.extensions, "EGL_KHR_create_context") )
{
Con_DPrintf( S_NOTE "EGL_KHR_create_context found, setting additional context flags\n");
if( eglstate.gl_attribs_set[REF_GL_CONTEXT_FLAGS] )
{
attribs[i++] = 0x30FC; // EGL_CONTEXT_FLAGS_KHR
attribs[i++] = eglstate.gl_attribs[REF_GL_CONTEXT_FLAGS] & ((REF_GL_CONTEXT_ROBUST_ACCESS_FLAG << 1) - 1);
}
if( eglstate.gl_attribs_set[REF_GL_CONTEXT_PROFILE_MASK] )
{
int val = eglstate.gl_attribs[REF_GL_CONTEXT_PROFILE_MASK];
if( val & ( (REF_GL_CONTEXT_PROFILE_COMPATIBILITY << 1) - 1 ) )
{
attribs[i++] = 0x30FD; // EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR;
attribs[i++] = val;
}
}
COPY_ATTR_IF_SET( REF_GL_CONTEXT_MAJOR_VERSION, EGL_CONTEXT_CLIENT_VERSION );
COPY_ATTR_IF_SET( REF_GL_CONTEXT_MINOR_VERSION, 0x30FB );
}
else
{
// without extension we can set only major version
COPY_ATTR_IF_SET( REF_GL_CONTEXT_MAJOR_VERSION, EGL_CONTEXT_CLIENT_VERSION );
if( eglstate.gl_attribs_set[REF_GL_CONTEXT_FLAGS] && (eglstate.gl_attribs[REF_GL_CONTEXT_FLAGS] & REF_GL_CONTEXT_DEBUG_FLAG ))
{
attribs[i++] = 0x31B0; // EGL_CONTEXT_OPENGL_DEBUG;
attribs[i++] = EGL_TRUE;
}
}
attribs[i++] = EGL_NONE;
return i;
}
/*
=========================
EGL_GetAttribute
query
=========================
*/
int EGL_GetAttribute( int attr, int *val )
{
EGLBoolean ret;
if( attr < 0 || attr >= REF_GL_ATTRIBUTES_COUNT )
return -1;
if( !val )
return -1;
switch( attr )
{
case REF_GL_RED_SIZE:
ret = egl.GetConfigAttrib( eglstate.dpy, eglstate.cfg, EGL_RED_SIZE, val );
return 0;
case REF_GL_GREEN_SIZE:
ret = egl.GetConfigAttrib( eglstate.dpy, eglstate.cfg, EGL_GREEN_SIZE, val );
return 0;
case REF_GL_BLUE_SIZE:
ret = egl.GetConfigAttrib( eglstate.dpy, eglstate.cfg, EGL_BLUE_SIZE, val );
return 0;
case REF_GL_ALPHA_SIZE:
ret = egl.GetConfigAttrib( eglstate.dpy, eglstate.cfg, EGL_ALPHA_SIZE, val );
return 0;
case REF_GL_DEPTH_SIZE:
ret = egl.GetConfigAttrib( eglstate.dpy, eglstate.cfg, EGL_DEPTH_SIZE, val );
return 0;
case REF_GL_STENCIL_SIZE:
ret = egl.GetConfigAttrib( eglstate.dpy, eglstate.cfg, EGL_STENCIL_SIZE, val );
return 0;
case REF_GL_MULTISAMPLESAMPLES:
ret = egl.GetConfigAttrib( eglstate.dpy, eglstate.cfg, EGL_SAMPLES, val );
return 0;
}
return -1;
}
/*
=========================
EGL_UpdateSurface
destroy old surface, recreate and make context current
must be called with valid context
=========================
*/
qboolean EGL_UpdateSurface( void *window, qboolean dummy )
{
if( !eglstate.valid )
return false;
egl.MakeCurrent( eglstate.dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT );
host.status = HOST_SLEEP;
if( eglstate.surface )
{
egl.DestroySurface( eglstate.dpy, eglstate.surface );
eglstate.surface = EGL_NO_SURFACE;
}
if( !window )
{
if( dummy && eglstate.support_surfaceless_context )
{
if( egl.MakeCurrent( eglstate.dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, eglstate.context ))
{
Con_Reportf( S_NOTE "EGL_UpdateSurface: using surfaceless mode\n" );
return true;
}
else
{
egl.MakeCurrent( eglstate.dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT );
eglstate.support_surfaceless_context = false;
}
Con_Reportf( S_NOTE "EGL_UpdateSurface: missing native window, detaching context\n" );
}
return false; // let platform fallback to dummy surface or pause engine
}
if(( eglstate.surface = egl.CreateWindowSurface( eglstate.dpy, eglstate.cfg, window, NULL )) == EGL_NO_SURFACE )
{
Con_Reportf( S_ERROR "eglCreateWindowSurface returned error: 0x%x\n", egl.GetError() );
return false;
}
if( !egl.MakeCurrent( eglstate.dpy, eglstate.surface, eglstate.surface, eglstate.context ))
{
Con_Reportf( S_ERROR "eglMakeCurrent returned error: 0x%x\n", egl.GetError() );
return false;
}
Con_DPrintf( S_NOTE "restored current context\n" );
if( !dummy)
host.status = HOST_FRAME;
return true;
}
/*
=========================
EGL_CreateContext
query attributes for ref and create context
=========================
*/
qboolean EGL_CreateContext( void )
{
EGLint attribs[32+1], contextAttribs[32+1];
const size_t attribsSize = ARRAYSIZE( attribs );
size_t s1, s2;
if( !eglstate.dpy && ( eglstate.dpy = egl.GetDisplay( EGL_DEFAULT_DISPLAY )) == EGL_NO_DISPLAY )
{
Con_Reportf( S_ERROR "eglGetDisplay returned error: 0x%x\n", egl.GetError() );
return false;
}
eglstate.extensions = egl.QueryString( eglstate.dpy, EGL_EXTENSIONS );
s1 = EGL_GenerateConfig(attribs, attribsSize);
s2 = EGL_GenerateContextConfig(contextAttribs, attribsSize);
if( !egl.BindAPI( eglstate.gl_api ))
{
Con_Reportf( S_ERROR "eglBindAPI returned error: 0x%x\n", egl.GetError() );
return false;
}
if( !egl.Initialize( eglstate.dpy, NULL, NULL ))
{
Con_Reportf( S_ERROR "eglInitialize returned error: 0x%x\n", egl.GetError() );
return false;
}
if( !egl.ChooseConfig( eglstate.dpy, attribs, &eglstate.cfg, 1, &eglstate.numCfg ))
{
Con_Reportf( S_ERROR "eglChooseConfig returned error: 0x%x\n", egl.GetError() );
return false;
}
if(( eglstate.context = egl.CreateContext( eglstate.dpy, eglstate.cfg, NULL, contextAttribs )) == EGL_NO_CONTEXT )
{
Con_Reportf( S_ERROR "eglCreateContext returned error: 0x%x\n", egl.GetError() );
return false;
}
eglstate.valid = true;
eglstate.imported = false;
// now check if it's safe to use surfaceless context here without surface fallback
if( eglstate.extensions && Q_strstr( eglstate.extensions, "EGL_KHR_surfaceless_context" ))
eglstate.support_surfaceless_context = true;
return true;
}
/*
===========================
EGL_ImportContext
import current egl context to use EGL functions
===========================
*/
qboolean EGL_ImportContext( void )
{
if( !egl.GetCurrentDisplay )
return false;
eglstate.dpy = egl.GetCurrentDisplay();
if( eglstate.dpy == EGL_NO_DISPLAY )
return false;
eglstate.surface = egl.GetCurrentSurface( EGL_DRAW );
if( eglstate.surface == EGL_NO_SURFACE )
return false;
// now check if swapBuffers does not give error
if( egl.SwapBuffers( eglstate.dpy, eglstate.surface ) == EGL_FALSE )
return false;
// double check
if( egl.GetError() != EGL_SUCCESS )
return false;
eglstate.extensions = egl.QueryString( eglstate.dpy, EGL_EXTENSIONS );
eglstate.valid = eglstate.imported = true;
return true;
}
/*
=========================
EGL_Terminate
=========================
*/
void EGL_Terminate( void )
{
egl.MakeCurrent( eglstate.dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT );
egl.DestroyContext( eglstate.dpy, eglstate.context );
egl.DestroySurface( eglstate.dpy, eglstate.surface );
egl.Terminate( eglstate.dpy );
Sys_FreeLibrary( &egl_info );
}
/*
=========================
EGL_GetProcAddress
eglGetProcAddress/dlsym wrapper
=========================
*/
void *EGL_GetProcAddress( const char *name )
{
void *gles;
void *addr;
// TODO: cross-platform loading
if( eglstate.gles1 )
{
if( !eglstate.libgles1 )
eglstate.libgles1 = dlopen( "libGLESv1_CM.so", RTLD_NOW );
gles = eglstate.libgles1;
}
else
{
if( !eglstate.libgles2 )
eglstate.libgles2 = dlopen( "libGLESv2.so", RTLD_NOW );
gles = eglstate.libgles2;
}
if( gles && ( addr = dlsym( gles, name )))
return addr;
if( !egl.GetProcAddress )
return NULL;
return egl.GetProcAddress( name );
}
/*
=========================
EGL_LoadLibrary
=========================
*/
void EGL_LoadLibrary( void )
{
Sys_LoadLibrary( &egl_info );
}
#endif