#include "platform/platform.h" #if !XASH_SDL #include "input.h" #include "client.h" #include "filesystem.h" #include "platform/android/android_priv.h" #include "vid_common.h" #include #include #include "eglutil.h" static struct vid_android_s { qboolean has_context; ANativeWindow* window; qboolean nativeegl; } vid_android; static struct nw_s { void (*release)(ANativeWindow* window); int32_t (*getWidth)(ANativeWindow* window); int32_t (*getHeight)(ANativeWindow* window); int32_t (*getFormat)(ANativeWindow* window); int32_t (*setBuffersGeometry)(ANativeWindow* window, int32_t width, int32_t height, int32_t format); int32_t (*lock)(ANativeWindow* window, ANativeWindow_Buffer* outBuffer, ARect* inOutDirtyBounds); int32_t (*unlockAndPost)(ANativeWindow* window); ANativeWindow* (*fromSurface)(JNIEnv* env, jobject surface); } nw; #define NW_FF(x) {"ANativeWindow_"#x, (void*)&nw.x} static dllfunc_t android_funcs[] = { NW_FF(release), NW_FF(getWidth), NW_FF(getHeight), NW_FF(getFormat), NW_FF(setBuffersGeometry), NW_FF(lock), NW_FF(unlockAndPost), NW_FF(fromSurface), { NULL, NULL } }; #undef NW_FF dll_info_t android_info = { "libandroid.so", android_funcs, false }; convar_t *cv_vid_scale; convar_t *cv_vid_rotate; /* ======================== Android_SwapInterval ======================== */ static void Android_SwapInterval( int interval ) { if( vid_android.nativeegl && eglstate.valid ) egl.SwapInterval( eglstate.dpy, interval ); } /* ======================== Android_SetTitle ======================== */ static void Android_SetTitle( const char *title ) { (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.setTitle, (*jni.env)->NewStringUTF( jni.env, title ) ); } /* ======================== Android_SetIcon ======================== */ static void Android_SetIcon( const char *path ) { (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.setIcon, (*jni.env)->NewStringUTF( jni.env, path ) ); } /* ======================== Android_GetScreenRes Resolution got from last resize event ======================== */ static void Android_GetScreenRes( int *width, int *height ) { *width=jni.width, *height=jni.height; } /* ======================== Android_SwapBuffers Update screen. Use native EGL if possible ======================== */ void GL_SwapBuffers( void ) { if( vid_android.nativeegl && eglstate.valid ) { egl.SwapBuffers( eglstate.dpy, eglstate.surface ); } else { (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.swapBuffers ); } } /* ======================== Android_UpdateSurface Check if we may use native EGL without jni calls ======================== */ void Android_UpdateSurface( qboolean active ) { vid_android.nativeegl = false; if( glw_state.software || eglstate.valid ) { if( vid_android.window && !active ) { nw.release( vid_android.window ); vid_android.window = NULL; } if( active ) { EGLint format = WINDOW_FORMAT_RGB_565; jobject surf; if( vid_android.window ) nw.release( vid_android.window ); surf = (*jni.env)->CallStaticObjectMethod(jni.env, jni.actcls, jni.getSurface); Con_DPrintf("Surface handle %p\n", surf); vid_android.window = nw.fromSurface(jni.env, surf); Con_DPrintf("NativeWindow %p\n", vid_android.window); if( eglstate.valid ) egl.GetConfigAttrib( eglstate.dpy, eglstate.cfg, EGL_NATIVE_VISUAL_ID, &format ); nw.setBuffersGeometry(vid_android.window, 0, 0, format ); (*jni.env)->DeleteLocalRef( jni.env, surf ); } if( Sys_CheckParm( "-egl" )) { EGL_UpdateSurface( vid_android.window ); vid_android.nativeegl = true; } return; } if( !vid_android.has_context ) return; if( ( active && host.status == HOST_FRAME ) || !active ) (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.toggleEGL, 0 ); if( active ) (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.toggleEGL, 1 ); if( !Sys_CheckParm("-nativeegl") || !active ) return; // enabled by user vid_android.nativeegl = EGL_ImportContext(); } /* ======================== Android_GetGLAttribute ======================== */ static int Android_GetGLAttribute( int eglAttr ) { int ret = (*jni.env)->CallStaticIntMethod( jni.env, jni.actcls, jni.getGLAttribute, eglAttr ); // Con_Reportf( "Android_GetGLAttribute( %i ) => %i\n", eglAttr, ret ); return ret; } int Android_GetSelectedPixelFormat( void ) { return (*jni.env)->CallStaticIntMethod( jni.env, jni.actcls, jni.getSelectedPixelFormat ); } qboolean R_Init_Video( const int type ) { char buf[MAX_VA_STRING]; qboolean retval; cv_vid_scale = Cvar_FindVar( "vid_scale" ); cv_vid_rotate = Cvar_FindVar( "vid_rotate" ); switch( Android_GetSelectedPixelFormat() ) { case 1: refState.desktopBitsPixel = 16; break; case 2: refState.desktopBitsPixel = 8; break; default: refState.desktopBitsPixel = 32; break; } if( FS_FileExists( GI->iconpath, true ) ) { Q_snprintf( buf, sizeof( buf ), "%s/%s/%s", COM_CheckStringEmpty( host.rodir ) ? host.rodir : host.rootdir, GI->gamefolder, GI->iconpath ); Android_SetIcon( buf ); } Android_SetTitle( GI->title ); VID_StartupGamma(); Sys_LoadLibrary( &android_info ); switch( type ) { case REF_SOFTWARE: glw_state.software = true; break; case REF_GL: glw_state.software = false; EGL_LoadLibrary(); if( !glw_state.safe && Sys_GetParmFromCmdLine( "-safegl", buf ) ) glw_state.safe = bound( SAFE_NO, Q_atoi( buf ), SAFE_DONTCARE ); break; default: Host_Error( "Can't initialize unknown context type %d!\n", type ); break; } if( glw_state.software ) { uint arg; // Con_Reportf( S_ERROR "Native software mode isn't supported on Android yet! :(\n" ); // return false; Android_UpdateSurface( true ); if( !SW_CreateBuffer( jni.width, jni.height, &arg, &arg, &arg, &arg, &arg ) ) return false; } while( !(retval = VID_SetMode()) ) { glw_state.safe++; if( glw_state.safe > SAFE_LAST ) return false; } switch( type ) { case REF_GL: // refdll also can check extensions ref.dllFuncs.GL_InitExtensions(); break; case REF_SOFTWARE: default: break; } host.renderinfo_changed = false; return true; } void R_Free_Video( void ) { // (*jni.env)->CallStaticBooleanMethod( jni.env, jni.actcls, jni.deleteGLContext ); // VID_DestroyWindow (); // R_FreeVideoModes(); Sys_FreeLibrary( &android_info ); vid_android.has_context = false; ref.dllFuncs.GL_ClearExtensions(); } void *Android_GetWindow( void ) { EGLint format; if( !vid_android.window ) { return NULL; } if( !egl.GetConfigAttrib( eglstate.dpy, eglstate.cfg, EGL_NATIVE_VISUAL_ID, &format) ) { Con_Reportf( S_ERROR "eglGetConfigAttrib(VISUAL_ID) returned error: 0x%x\n", egl.GetError() ); return NULL; } if( nw.setBuffersGeometry( vid_android.window, 0, 0, format ) ) { Con_Reportf( S_ERROR "ANativeWindow_setBuffersGeometry returned error\n" ); return NULL; } return vid_android.window; } qboolean VID_SetMode( void ) { EGLint format; jintArray attribs, contextAttribs; static EGLint nAttribs[32+1], nContextAttribs[32+1]; const size_t attribsSize = ARRAYSIZE( nAttribs ); size_t s1, s2; // create context on egl side by user request if( !vid_android.has_context && Sys_CheckParm("-egl") ) { vid_android.has_context = vid_android.nativeegl = EGL_CreateContext(); if( vid_android.has_context ) Android_UpdateSurface( true ); else return false; } if( vid_android.has_context || glw_state.software ) { R_ChangeDisplaySettings( 0, 0, WINDOW_MODE_WINDOWED ); // width and height are ignored anyway return true; } s1 = EGL_GenerateConfig(nAttribs, attribsSize); s2 = EGL_GenerateContextConfig(nContextAttribs, attribsSize); attribs = (*jni.env)->NewIntArray( jni.env, s1 ); contextAttribs = (*jni.env)->NewIntArray( jni.env, s2 ); (*jni.env)->SetIntArrayRegion( jni.env, attribs, 0, s1, nAttribs ); (*jni.env)->SetIntArrayRegion( jni.env, contextAttribs, 0, s2, nContextAttribs ); R_ChangeDisplaySettings( 0, 0, WINDOW_MODE_WINDOWED ); // width and height are ignored anyway if( (*jni.env)->CallStaticBooleanMethod( jni.env, jni.actcls, jni.createGLContext, attribs, contextAttribs ) ) { vid_android.has_context = true; return true; } return false; } rserr_t R_ChangeDisplaySettings( int width, int height, window_mode_t window_mode ) { int render_w, render_h; uint rotate = cv_vid_rotate->value; Android_GetScreenRes(&width, &height); render_w = width; render_h = height; Con_Reportf( "R_ChangeDisplaySettings: forced resolution to %dx%d)\n", width, height); if( ref.dllFuncs.R_SetDisplayTransform( rotate, 0, 0, cv_vid_scale->value, cv_vid_scale->value ) ) { if( rotate & 1 ) { int swap = render_w; render_w = render_h; render_h = swap; } render_h /= cv_vid_scale->value; render_w /= cv_vid_scale->value; } else { Con_Printf( S_WARN "failed to setup screen transform\n" ); } R_SaveVideoMode( width, height, render_w, render_h, true ); refState.wideScreen = true; // V_AdjustFov will check for widescreen return rserr_ok; } int GL_SetAttribute( int attr, int val ) { return EGL_SetAttribute( attr, val ); } int GL_GetAttribute( int attr, int *val ) { EGLBoolean ret; if( eglstate.valid ) return EGL_GetAttribute( attr, val ); if( attr < 0 || attr >= REF_GL_ATTRIBUTES_COUNT ) return -1; if( !val ) return -1; switch( attr ) { case REF_GL_RED_SIZE: *val = Android_GetGLAttribute( EGL_RED_SIZE ); return 0; case REF_GL_GREEN_SIZE: *val = Android_GetGLAttribute( EGL_GREEN_SIZE ); return 0; case REF_GL_BLUE_SIZE: *val = Android_GetGLAttribute( EGL_BLUE_SIZE ); return 0; case REF_GL_ALPHA_SIZE: *val = Android_GetGLAttribute( EGL_ALPHA_SIZE ); return 0; case REF_GL_DEPTH_SIZE: *val = Android_GetGLAttribute( EGL_DEPTH_SIZE ); return 0; case REF_GL_STENCIL_SIZE: *val = Android_GetGLAttribute( EGL_STENCIL_SIZE ); return 0; case REF_GL_MULTISAMPLESAMPLES: *val = Android_GetGLAttribute( EGL_SAMPLES ); return 0; } return -1; } int R_MaxVideoModes( void ) { return 0; } vidmode_t* R_GetVideoMode( int num ) { return NULL; } void* GL_GetProcAddress( const char *name ) // RenderAPI requirement { return EGL_GetProcAddress( name ); } void GL_UpdateSwapInterval( void ) { // disable VSync while level is loading if( cls.state < ca_active ) { Android_SwapInterval( 0 ); SetBits( gl_vsync.flags, FCVAR_CHANGED ); } else if( FBitSet( gl_vsync.flags, FCVAR_CHANGED )) { ClearBits( gl_vsync.flags, FCVAR_CHANGED ); Android_SwapInterval( gl_vsync.value ); } } void *SW_LockBuffer( void ) { ANativeWindow_Buffer buffer; if( !nw.lock || !vid_android.window ) return NULL; if( nw.lock( vid_android.window, &buffer, NULL ) ) return NULL; if( buffer.width < refState.width || buffer.height < refState.height ) return NULL; return buffer.bits; } void SW_UnlockBuffer( void ) { if( nw.unlockAndPost ) nw.unlockAndPost( vid_android.window ); } qboolean SW_CreateBuffer( int width, int height, uint *stride, uint *bpp, uint *r, uint *g, uint *b ) { ANativeWindow_Buffer buffer; int lock; if( !nw.lock || !vid_android.window ) return false; nw.unlockAndPost( vid_android.window ); if( ( lock = nw.lock( vid_android.window, &buffer, NULL ) ) ) { Con_Printf( "SW_CreateBuffer: lock %d\n", lock ); return false; } nw.unlockAndPost( vid_android.window ); Con_Printf( "SW_CreateBuffer: buffer %d %d %x %d %p\n", buffer.width, buffer.height, buffer.format, buffer.stride, buffer.bits ); if( width > buffer.width || height > buffer.height ) { Con_Printf( "SW_CreateBuffer: buffer too small %d %d\n", width, height ); // resize event missed? if( jni.width < buffer.width ) jni.width = buffer.width; if( jni.height < buffer.height ) jni.width = buffer.height; VID_SetMode(); Android_UpdateSurface( true ); return false; } if( buffer.format != WINDOW_FORMAT_RGB_565 ) { Con_Printf( "SW_CreateBuffer: wrong format %d\n", buffer.format ); return false; } Con_Printf( "SW_CreateBuffer: ok\n" ); *stride = buffer.stride; *bpp = 2; *r = (((1 << 5) - 1) << (5+6)); *g = (((1 << 6) - 1) << (5)); *b = (((1 << 5) - 1) << (0)); return true; } #endif