#include #include #include #include #include #include #include // for prctl( PR_SET_NAME ) #include #include // for native window JNI #include #include "argtable3.h" #include "VrInput.h" #include "VrCvars.h" #include #include #include #include #define GL_GLEXT_PROTOTYPES #include extern "C" { #include "src/gl/loader.h" } #include #include "VrInput.h" #include "VrCommon.h" #include "../SupportLibs/jpeg8d/jinclude.h" #include "../SupportLibs/libpng/pngconf.h" #include "../SupportLibs/gl4es/include/android_debug.h" #include "../SupportLibs/gl4es/src/gl/logs.h" #if !defined( EGL_OPENGL_ES3_BIT_KHR ) #define EGL_OPENGL_ES3_BIT_KHR 0x0040 #endif // EXT_texture_border_clamp #ifndef GL_CLAMP_TO_BORDER #define GL_CLAMP_TO_BORDER 0x812D #endif #ifndef GL_TEXTURE_BORDER_COLOR #define GL_TEXTURE_BORDER_COLOR 0x1004 #endif #ifndef GLAPI #define GLAPI extern #endif //#define ENABLE_GL_DEBUG #define ENABLE_GL_DEBUG_VERBOSE 1 #define EGL_SYNC #if defined EGL_SYNC // EGL_KHR_reusable_sync PFNEGLCREATESYNCKHRPROC eglCreateSyncKHR; PFNEGLDESTROYSYNCKHRPROC eglDestroySyncKHR; PFNEGLCLIENTWAITSYNCKHRPROC eglClientWaitSyncKHR; PFNEGLSIGNALSYNCKHRPROC eglSignalSyncKHR; PFNEGLGETSYNCATTRIBKHRPROC eglGetSyncAttribKHR; #endif //Let's go to the maximum! int NUM_MULTI_SAMPLES = 2; int REFRESH = 0; float SS_MULTIPLIER = 0.0f; GLboolean stageSupported = GL_FALSE; #ifdef META_QUEST const char* const requiredExtensionNames[] = { XR_KHR_OPENGL_ES_ENABLE_EXTENSION_NAME, XR_EXT_PERFORMANCE_SETTINGS_EXTENSION_NAME, XR_KHR_ANDROID_THREAD_SETTINGS_EXTENSION_NAME, XR_FB_DISPLAY_REFRESH_RATE_EXTENSION_NAME, XR_FB_COLOR_SPACE_EXTENSION_NAME}; #endif //META_QUEST #ifdef PICO_XR #define XR_PICO_CONFIGS_EXT_EXTENSION_NAME "XR_PICO_configs_ext" enum ConfigsEXT { RENDER_TEXTURE_WIDTH = 0, RENDER_TEXTURE_HEIGHT, SHOW_FPS, RUNTIME_LOG_LEVEL, PXRPLUGIN_LOG_LEVEL, UNITY_LOG_LEVEL, UNREAL_LOG_LEVEL, NATIVE_LOG_LEVEL, TARGET_FRAME_RATE, NECK_MODEL_X, NECK_MODEL_Y, NECK_MODEL_Z, DISPLAY_REFRESH_RATE, ENABLE_6DOF, CONTROLLER_TYPE, PHYSICAL_IPD, TO_DELTA_SENSOR_Y, GET_DISPLAY_RATE, FOVEATION_SUBSAMPLED_ENABLED = 18, TRACKING_ORIGIN_HEIGHT }; typedef XrResult (XRAPI_PTR *PFN_xrGetConfigPICO)( XrSession session, enum ConfigsEXT configIndex, float * configData); PFN_xrGetConfigPICO pfnXrGetConfigPICO; enum ConfigsSetEXT { UNREAL_VERSION = 0, TRACKING_ORIGIN, OPENGL_NOERROR, ENABLE_SIX_DOF, PRESENTATION_FLAG, ENABLE_CPT, PLATFORM, FOVEATION_LEVEL, SET_DISPLAY_RATE = 8, MRC_TEXTURE_ID = 9, }; typedef XrResult (XRAPI_PTR *PFN_xrSetConfigPICO) ( XrSession session, enum ConfigsSetEXT configIndex, char * configData); PFN_xrSetConfigPICO pfnXrSetConfigPICO; const char* const requiredExtensionNames[] = { XR_KHR_ANDROID_CREATE_INSTANCE_EXTENSION_NAME, XR_EXT_PERFORMANCE_SETTINGS_EXTENSION_NAME, XR_KHR_OPENGL_ES_ENABLE_EXTENSION_NAME, XR_PICO_CONFIGS_EXT_EXTENSION_NAME}; #endif //PICO_XR const uint32_t numRequiredExtensions = sizeof(requiredExtensionNames) / sizeof(requiredExtensionNames[0]); /* ================================================================================ System Clock Time in millis ================================================================================ */ double TBXR_GetTimeInMilliSeconds() { struct timespec now; clock_gettime( CLOCK_MONOTONIC, &now ); return ( now.tv_sec * 1e9 + now.tv_nsec ) * (double)(1e-6); } int runStatus = -1; void TBXR_exit(int exitCode) { runStatus = exitCode; } /* ================================================================================ OpenGL-ES Utility Functions ================================================================================ */ typedef struct { bool multi_view; // GL_OVR_multiview, GL_OVR_multiview2 bool EXT_texture_border_clamp; // GL_EXT_texture_border_clamp, GL_OES_texture_border_clamp } OpenGLExtensions_t; OpenGLExtensions_t glExtensions; static void EglInitExtensions() { #if defined EGL_SYNC eglCreateSyncKHR = (PFNEGLCREATESYNCKHRPROC) eglGetProcAddress( "eglCreateSyncKHR" ); eglDestroySyncKHR = (PFNEGLDESTROYSYNCKHRPROC) eglGetProcAddress( "eglDestroySyncKHR" ); eglClientWaitSyncKHR = (PFNEGLCLIENTWAITSYNCKHRPROC) eglGetProcAddress( "eglClientWaitSyncKHR" ); eglSignalSyncKHR = (PFNEGLSIGNALSYNCKHRPROC) eglGetProcAddress( "eglSignalSyncKHR" ); eglGetSyncAttribKHR = (PFNEGLGETSYNCATTRIBKHRPROC) eglGetProcAddress( "eglGetSyncAttribKHR" ); #endif const char * allExtensions = (const char *)glGetString( GL_EXTENSIONS ); if ( allExtensions != NULL ) { glExtensions.multi_view = strstr( allExtensions, "GL_OVR_multiview2" ) && strstr( allExtensions, "GL_OVR_multiview_multisampled_render_to_texture" ); glExtensions.EXT_texture_border_clamp = false;//strstr( allExtensions, "GL_EXT_texture_border_clamp" ) || //strstr( allExtensions, "GL_OES_texture_border_clamp" ); } } static const char * EglErrorString( const EGLint error ) { switch ( error ) { case EGL_SUCCESS: return "EGL_SUCCESS"; case EGL_NOT_INITIALIZED: return "EGL_NOT_INITIALIZED"; case EGL_BAD_ACCESS: return "EGL_BAD_ACCESS"; case EGL_BAD_ALLOC: return "EGL_BAD_ALLOC"; case EGL_BAD_ATTRIBUTE: return "EGL_BAD_ATTRIBUTE"; case EGL_BAD_CONTEXT: return "EGL_BAD_CONTEXT"; case EGL_BAD_CONFIG: return "EGL_BAD_CONFIG"; case EGL_BAD_CURRENT_SURFACE: return "EGL_BAD_CURRENT_SURFACE"; case EGL_BAD_DISPLAY: return "EGL_BAD_DISPLAY"; case EGL_BAD_SURFACE: return "EGL_BAD_SURFACE"; case EGL_BAD_MATCH: return "EGL_BAD_MATCH"; case EGL_BAD_PARAMETER: return "EGL_BAD_PARAMETER"; case EGL_BAD_NATIVE_PIXMAP: return "EGL_BAD_NATIVE_PIXMAP"; case EGL_BAD_NATIVE_WINDOW: return "EGL_BAD_NATIVE_WINDOW"; case EGL_CONTEXT_LOST: return "EGL_CONTEXT_LOST"; default: return "unknown"; } } static const char * GlFrameBufferStatusString( GLenum status ) { switch ( status ) { case GL_FRAMEBUFFER_UNDEFINED: return "GL_FRAMEBUFFER_UNDEFINED"; case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: return "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"; case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: return "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"; case GL_FRAMEBUFFER_UNSUPPORTED: return "GL_FRAMEBUFFER_UNSUPPORTED"; case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: return "GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE"; default: return "unknown"; } } /* ================================================================================ ovrEgl ================================================================================ */ static void ovrEgl_Clear( ovrEgl * egl ) { egl->MajorVersion = 0; egl->MinorVersion = 0; egl->Display = 0; egl->Config = 0; egl->TinySurface = EGL_NO_SURFACE; egl->MainSurface = EGL_NO_SURFACE; egl->Context = EGL_NO_CONTEXT; } static void ovrEgl_CreateContext( ovrEgl * egl, const ovrEgl * shareEgl ) { if ( egl->Display != 0 ) { return; } egl->Display = eglGetDisplay( EGL_DEFAULT_DISPLAY ); ALOGV( " eglInitialize( Display, &MajorVersion, &MinorVersion )" ); eglInitialize( egl->Display, &egl->MajorVersion, &egl->MinorVersion ); // Do NOT use eglChooseConfig, because the Android EGL code pushes in multisample // flags in eglChooseConfig if the user has selected the "force 4x MSAA" option in // settings, and that is completely wasted for our warp target. const int MAX_CONFIGS = 1024; EGLConfig configs[MAX_CONFIGS]; EGLint numConfigs = 0; if ( eglGetConfigs( egl->Display, configs, MAX_CONFIGS, &numConfigs ) == EGL_FALSE ) { ALOGE( " eglGetConfigs() failed: %s", EglErrorString( eglGetError() ) ); return; } const EGLint configAttribs[] = { EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, // need alpha for the multi-pass timewarp compositor EGL_DEPTH_SIZE, 0, EGL_STENCIL_SIZE, 0, EGL_SAMPLES, 0, EGL_NONE }; egl->Config = 0; for ( int i = 0; i < numConfigs; i++ ) { EGLint value = 0; eglGetConfigAttrib( egl->Display, configs[i], EGL_RENDERABLE_TYPE, &value ); if ( ( value & EGL_OPENGL_ES3_BIT_KHR ) != EGL_OPENGL_ES3_BIT_KHR ) { continue; } // The pbuffer config also needs to be compatible with normal window rendering // so it can share textures with the window context. eglGetConfigAttrib( egl->Display, configs[i], EGL_SURFACE_TYPE, &value ); if ( ( value & ( EGL_WINDOW_BIT | EGL_PBUFFER_BIT ) ) != ( EGL_WINDOW_BIT | EGL_PBUFFER_BIT ) ) { continue; } int j = 0; for ( ; configAttribs[j] != EGL_NONE; j += 2 ) { eglGetConfigAttrib( egl->Display, configs[i], configAttribs[j], &value ); if ( value != configAttribs[j + 1] ) { break; } } if ( configAttribs[j] == EGL_NONE ) { egl->Config = configs[i]; break; } } if ( egl->Config == 0 ) { ALOGE( " eglChooseConfig() failed: %s", EglErrorString( eglGetError() ) ); return; } EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE }; ALOGV( " Context = eglCreateContext( Display, Config, EGL_NO_CONTEXT, contextAttribs )" ); egl->Context = eglCreateContext( egl->Display, egl->Config, ( shareEgl != NULL ) ? shareEgl->Context : EGL_NO_CONTEXT, contextAttribs ); if ( egl->Context == EGL_NO_CONTEXT ) { ALOGE( " eglCreateContext() failed: %s", EglErrorString( eglGetError() ) ); return; } const EGLint surfaceAttribs[] = { EGL_WIDTH, 16, EGL_HEIGHT, 16, EGL_NONE }; ALOGV( " TinySurface = eglCreatePbufferSurface( Display, Config, surfaceAttribs )" ); egl->TinySurface = eglCreatePbufferSurface( egl->Display, egl->Config, surfaceAttribs ); if ( egl->TinySurface == EGL_NO_SURFACE ) { ALOGE( " eglCreatePbufferSurface() failed: %s", EglErrorString( eglGetError() ) ); eglDestroyContext( egl->Display, egl->Context ); egl->Context = EGL_NO_CONTEXT; return; } ALOGV( " eglMakeCurrent( Display, TinySurface, TinySurface, Context )" ); if ( eglMakeCurrent( egl->Display, egl->TinySurface, egl->TinySurface, egl->Context ) == EGL_FALSE ) { ALOGE( " eglMakeCurrent() failed: %s", EglErrorString( eglGetError() ) ); eglDestroySurface( egl->Display, egl->TinySurface ); eglDestroyContext( egl->Display, egl->Context ); egl->Context = EGL_NO_CONTEXT; return; } } static void ovrEgl_DestroyContext( ovrEgl * egl ) { if ( egl->Display != 0 ) { ALOGE( " eglMakeCurrent( Display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT )" ); if ( eglMakeCurrent( egl->Display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT ) == EGL_FALSE ) { ALOGE( " eglMakeCurrent() failed: %s", EglErrorString( eglGetError() ) ); } } if ( egl->Context != EGL_NO_CONTEXT ) { ALOGE( " eglDestroyContext( Display, Context )" ); if ( eglDestroyContext( egl->Display, egl->Context ) == EGL_FALSE ) { ALOGE( " eglDestroyContext() failed: %s", EglErrorString( eglGetError() ) ); } egl->Context = EGL_NO_CONTEXT; } if ( egl->TinySurface != EGL_NO_SURFACE ) { ALOGE( " eglDestroySurface( Display, TinySurface )" ); if ( eglDestroySurface( egl->Display, egl->TinySurface ) == EGL_FALSE ) { ALOGE( " eglDestroySurface() failed: %s", EglErrorString( eglGetError() ) ); } egl->TinySurface = EGL_NO_SURFACE; } if ( egl->Display != 0 ) { ALOGE( " eglTerminate( Display )" ); if ( eglTerminate( egl->Display ) == EGL_FALSE ) { ALOGE( " eglTerminate() failed: %s", EglErrorString( eglGetError() ) ); } egl->Display = 0; } } /* ================================================================================ ovrFramebuffer ================================================================================ */ static void ovrFramebuffer_Clear(ovrFramebuffer* frameBuffer) { frameBuffer->Width = 0; frameBuffer->Height = 0; frameBuffer->Multisamples = 0; frameBuffer->TextureSwapChainLength = 0; frameBuffer->TextureSwapChainIndex = 0; frameBuffer->ColorSwapChain.Handle = XR_NULL_HANDLE; frameBuffer->ColorSwapChain.Width = 0; frameBuffer->ColorSwapChain.Height = 0; frameBuffer->ColorSwapChainImage = NULL; frameBuffer->DepthBuffers = NULL; frameBuffer->FrameBuffers = NULL; } static bool ovrFramebuffer_Create( XrSession session, ovrFramebuffer* frameBuffer, const GLenum colorFormat, const int width, const int height, const int multisamples) { PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC glRenderbufferStorageMultisampleEXT = (PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC)eglGetProcAddress( "glRenderbufferStorageMultisampleEXT"); PFNGLFRAMEBUFFERTEXTURE2DMULTISAMPLEEXTPROC glFramebufferTexture2DMultisampleEXT = (PFNGLFRAMEBUFFERTEXTURE2DMULTISAMPLEEXTPROC)eglGetProcAddress( "glFramebufferTexture2DMultisampleEXT"); frameBuffer->Width = width; frameBuffer->Height = height; frameBuffer->Multisamples = multisamples; XrSwapchainCreateInfo swapChainCreateInfo; memset(&swapChainCreateInfo, 0, sizeof(swapChainCreateInfo)); swapChainCreateInfo.type = XR_TYPE_SWAPCHAIN_CREATE_INFO; swapChainCreateInfo.usageFlags = XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT; swapChainCreateInfo.format = colorFormat; swapChainCreateInfo.sampleCount = 1; swapChainCreateInfo.width = width; swapChainCreateInfo.height = height; swapChainCreateInfo.faceCount = 1; swapChainCreateInfo.arraySize = 1; swapChainCreateInfo.mipCount = 1; frameBuffer->ColorSwapChain.Width = swapChainCreateInfo.width; frameBuffer->ColorSwapChain.Height = swapChainCreateInfo.height; // Create the swapchain. OXR(xrCreateSwapchain(session, &swapChainCreateInfo, &frameBuffer->ColorSwapChain.Handle)); // Get the number of swapchain images. OXR(xrEnumerateSwapchainImages( frameBuffer->ColorSwapChain.Handle, 0, &frameBuffer->TextureSwapChainLength, NULL)); // Allocate the swapchain images array. frameBuffer->ColorSwapChainImage = (XrSwapchainImageOpenGLESKHR*)malloc( frameBuffer->TextureSwapChainLength * sizeof(XrSwapchainImageOpenGLESKHR)); // Populate the swapchain image array. for (uint32_t i = 0; i < frameBuffer->TextureSwapChainLength; i++) { frameBuffer->ColorSwapChainImage[i].type = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_ES_KHR; frameBuffer->ColorSwapChainImage[i].next = NULL; } OXR(xrEnumerateSwapchainImages( frameBuffer->ColorSwapChain.Handle, frameBuffer->TextureSwapChainLength, &frameBuffer->TextureSwapChainLength, (XrSwapchainImageBaseHeader*)frameBuffer->ColorSwapChainImage)); frameBuffer->DepthBuffers = (GLuint*)malloc(frameBuffer->TextureSwapChainLength * sizeof(GLuint)); frameBuffer->FrameBuffers = (GLuint*)malloc(frameBuffer->TextureSwapChainLength * sizeof(GLuint)); for (uint32_t i = 0; i < frameBuffer->TextureSwapChainLength; i++) { // Create the color buffer texture. const GLuint colorTexture = frameBuffer->ColorSwapChainImage[i].image; GLfloat borderColor[] = {0.0f, 0.0f, 0.0f, 0.0f}; GLenum colorTextureTarget = GL_TEXTURE_2D; GL(glTexParameterfv(colorTextureTarget, GL_TEXTURE_BORDER_COLOR, borderColor)); GL(glBindTexture(colorTextureTarget, colorTexture)); GL(glTexParameteri(colorTextureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); GL(glTexParameteri(colorTextureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); GL(glTexParameteri(colorTextureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); GL(glTexParameteri(colorTextureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); GL(glBindTexture(colorTextureTarget, 0)); if (glRenderbufferStorageMultisampleEXT != NULL && glFramebufferTexture2DMultisampleEXT != NULL) { // Create multisampled depth buffer. GL(glGenRenderbuffers(1, &frameBuffer->DepthBuffers[i])); GL(glBindRenderbuffer(GL_RENDERBUFFER, frameBuffer->DepthBuffers[i])); GL(glRenderbufferStorageMultisampleEXT( GL_RENDERBUFFER, multisamples, GL_DEPTH_COMPONENT24, width, height)); GL(glBindRenderbuffer(GL_RENDERBUFFER, 0)); // Create the frame buffer. // NOTE: glFramebufferTexture2DMultisampleEXT only works with GL_FRAMEBUFFER. GL(glGenFramebuffers(1, &frameBuffer->FrameBuffers[i])); GL(glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer->FrameBuffers[i])); GL(glFramebufferTexture2DMultisampleEXT( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexture, 0, multisamples)); GL(glFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, frameBuffer->DepthBuffers[i])); GL(GLenum renderFramebufferStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER)); GL(glBindFramebuffer(GL_FRAMEBUFFER, 0)); if (renderFramebufferStatus != GL_FRAMEBUFFER_COMPLETE) { ALOGE( "Incomplete frame buffer object: %s", GlFrameBufferStatusString(renderFramebufferStatus)); return false; } } else { return false; } } return true; } void ovrFramebuffer_Destroy(ovrFramebuffer* frameBuffer) { GL(glDeleteFramebuffers(frameBuffer->TextureSwapChainLength, frameBuffer->FrameBuffers)); GL(glDeleteRenderbuffers(frameBuffer->TextureSwapChainLength, frameBuffer->DepthBuffers)); OXR(xrDestroySwapchain(frameBuffer->ColorSwapChain.Handle)); free(frameBuffer->ColorSwapChainImage); free(frameBuffer->DepthBuffers); free(frameBuffer->FrameBuffers); ovrFramebuffer_Clear(frameBuffer); } void ovrFramebuffer_SetCurrent(ovrFramebuffer* frameBuffer) { GL(glBindFramebuffer( GL_DRAW_FRAMEBUFFER, frameBuffer->FrameBuffers[frameBuffer->TextureSwapChainIndex])); } void ovrFramebuffer_SetNone() { GL(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0)); } void ovrFramebuffer_Resolve(ovrFramebuffer* frameBuffer) { // Discard the depth buffer, so the tiler won't need to write it back out to memory. const GLenum depthAttachment[1] = {GL_DEPTH_ATTACHMENT}; glInvalidateFramebuffer(GL_DRAW_FRAMEBUFFER, 1, depthAttachment); // We now let the resolve happen implicitly. } void ovrFramebuffer_Acquire(ovrFramebuffer* frameBuffer) { // Acquire the swapchain image XrSwapchainImageAcquireInfo acquireInfo = {XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO, NULL}; OXR(xrAcquireSwapchainImage( frameBuffer->ColorSwapChain.Handle, &acquireInfo, &frameBuffer->TextureSwapChainIndex)); XrSwapchainImageWaitInfo waitInfo; waitInfo.type = XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO; waitInfo.next = NULL; waitInfo.timeout = 1000000000; /* timeout in nanoseconds */ XrResult res = xrWaitSwapchainImage(frameBuffer->ColorSwapChain.Handle, &waitInfo); int i = 0; while (res == XR_TIMEOUT_EXPIRED) { res = xrWaitSwapchainImage(frameBuffer->ColorSwapChain.Handle, &waitInfo); i++; ALOGV( " Retry xrWaitSwapchainImage %d times due to XR_TIMEOUT_EXPIRED (duration %f seconds)", i, waitInfo.timeout * (1E-9)); } } void ovrFramebuffer_Release(ovrFramebuffer* frameBuffer) { XrSwapchainImageReleaseInfo releaseInfo = {XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO, NULL}; OXR(xrReleaseSwapchainImage(frameBuffer->ColorSwapChain.Handle, &releaseInfo)); } /* ================================================================================ ovrRenderer ================================================================================ */ void ovrRenderer_Clear(ovrRenderer* renderer) { for (int eye = 0; eye < ovrMaxNumEyes; eye++) { ovrFramebuffer_Clear(&renderer->FrameBuffer[eye]); } } void ovrRenderer_Create( XrSession session, ovrRenderer* renderer, int suggestedEyeTextureWidth, int suggestedEyeTextureHeight) { // Create the frame buffers. for (int eye = 0; eye < ovrMaxNumEyes; eye++) { ovrFramebuffer_Create( session, &renderer->FrameBuffer[eye], GL_SRGB8_ALPHA8, suggestedEyeTextureWidth, suggestedEyeTextureHeight, NUM_MULTI_SAMPLES); } } void ovrRenderer_Destroy(ovrRenderer* renderer) { for (int eye = 0; eye < ovrMaxNumEyes; eye++) { ovrFramebuffer_Destroy(&renderer->FrameBuffer[eye]); } } /* ================================================================================ ovrMatrix4f ================================================================================ */ ovrMatrix4f ovrMatrix4f_CreateFromQuaternion(const XrQuaternionf* q) { const float ww = q->w * q->w; const float xx = q->x * q->x; const float yy = q->y * q->y; const float zz = q->z * q->z; ovrMatrix4f out; out.M[0][0] = ww + xx - yy - zz; out.M[0][1] = 2 * (q->x * q->y - q->w * q->z); out.M[0][2] = 2 * (q->x * q->z + q->w * q->y); out.M[0][3] = 0; out.M[1][0] = 2 * (q->x * q->y + q->w * q->z); out.M[1][1] = ww - xx + yy - zz; out.M[1][2] = 2 * (q->y * q->z - q->w * q->x); out.M[1][3] = 0; out.M[2][0] = 2 * (q->x * q->z - q->w * q->y); out.M[2][1] = 2 * (q->y * q->z + q->w * q->x); out.M[2][2] = ww - xx - yy + zz; out.M[2][3] = 0; out.M[3][0] = 0; out.M[3][1] = 0; out.M[3][2] = 0; out.M[3][3] = 1; return out; } /// Use left-multiplication to accumulate transformations. ovrMatrix4f ovrMatrix4f_Multiply(const ovrMatrix4f* a, const ovrMatrix4f* b) { ovrMatrix4f out; out.M[0][0] = a->M[0][0] * b->M[0][0] + a->M[0][1] * b->M[1][0] + a->M[0][2] * b->M[2][0] + a->M[0][3] * b->M[3][0]; out.M[1][0] = a->M[1][0] * b->M[0][0] + a->M[1][1] * b->M[1][0] + a->M[1][2] * b->M[2][0] + a->M[1][3] * b->M[3][0]; out.M[2][0] = a->M[2][0] * b->M[0][0] + a->M[2][1] * b->M[1][0] + a->M[2][2] * b->M[2][0] + a->M[2][3] * b->M[3][0]; out.M[3][0] = a->M[3][0] * b->M[0][0] + a->M[3][1] * b->M[1][0] + a->M[3][2] * b->M[2][0] + a->M[3][3] * b->M[3][0]; out.M[0][1] = a->M[0][0] * b->M[0][1] + a->M[0][1] * b->M[1][1] + a->M[0][2] * b->M[2][1] + a->M[0][3] * b->M[3][1]; out.M[1][1] = a->M[1][0] * b->M[0][1] + a->M[1][1] * b->M[1][1] + a->M[1][2] * b->M[2][1] + a->M[1][3] * b->M[3][1]; out.M[2][1] = a->M[2][0] * b->M[0][1] + a->M[2][1] * b->M[1][1] + a->M[2][2] * b->M[2][1] + a->M[2][3] * b->M[3][1]; out.M[3][1] = a->M[3][0] * b->M[0][1] + a->M[3][1] * b->M[1][1] + a->M[3][2] * b->M[2][1] + a->M[3][3] * b->M[3][1]; out.M[0][2] = a->M[0][0] * b->M[0][2] + a->M[0][1] * b->M[1][2] + a->M[0][2] * b->M[2][2] + a->M[0][3] * b->M[3][2]; out.M[1][2] = a->M[1][0] * b->M[0][2] + a->M[1][1] * b->M[1][2] + a->M[1][2] * b->M[2][2] + a->M[1][3] * b->M[3][2]; out.M[2][2] = a->M[2][0] * b->M[0][2] + a->M[2][1] * b->M[1][2] + a->M[2][2] * b->M[2][2] + a->M[2][3] * b->M[3][2]; out.M[3][2] = a->M[3][0] * b->M[0][2] + a->M[3][1] * b->M[1][2] + a->M[3][2] * b->M[2][2] + a->M[3][3] * b->M[3][2]; out.M[0][3] = a->M[0][0] * b->M[0][3] + a->M[0][1] * b->M[1][3] + a->M[0][2] * b->M[2][3] + a->M[0][3] * b->M[3][3]; out.M[1][3] = a->M[1][0] * b->M[0][3] + a->M[1][1] * b->M[1][3] + a->M[1][2] * b->M[2][3] + a->M[1][3] * b->M[3][3]; out.M[2][3] = a->M[2][0] * b->M[0][3] + a->M[2][1] * b->M[1][3] + a->M[2][2] * b->M[2][3] + a->M[2][3] * b->M[3][3]; out.M[3][3] = a->M[3][0] * b->M[0][3] + a->M[3][1] * b->M[1][3] + a->M[3][2] * b->M[2][3] + a->M[3][3] * b->M[3][3]; return out; } ovrMatrix4f ovrMatrix4f_CreateRotation(const float radiansX, const float radiansY, const float radiansZ) { const float sinX = sinf(radiansX); const float cosX = cosf(radiansX); const ovrMatrix4f rotationX = { {{1, 0, 0, 0}, {0, cosX, -sinX, 0}, {0, sinX, cosX, 0}, {0, 0, 0, 1}}}; const float sinY = sinf(radiansY); const float cosY = cosf(radiansY); const ovrMatrix4f rotationY = { {{cosY, 0, sinY, 0}, {0, 1, 0, 0}, {-sinY, 0, cosY, 0}, {0, 0, 0, 1}}}; const float sinZ = sinf(radiansZ); const float cosZ = cosf(radiansZ); const ovrMatrix4f rotationZ = { {{cosZ, -sinZ, 0, 0}, {sinZ, cosZ, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1}}}; const ovrMatrix4f rotationXY = ovrMatrix4f_Multiply(&rotationY, &rotationX); return ovrMatrix4f_Multiply(&rotationZ, &rotationXY); } XrVector4f XrVector4f_MultiplyMatrix4f(const ovrMatrix4f* a, const XrVector4f* v) { XrVector4f out; out.x = a->M[0][0] * v->x + a->M[0][1] * v->y + a->M[0][2] * v->z + a->M[0][3] * v->w; out.y = a->M[1][0] * v->x + a->M[1][1] * v->y + a->M[1][2] * v->z + a->M[1][3] * v->w; out.z = a->M[2][0] * v->x + a->M[2][1] * v->y + a->M[2][2] * v->z + a->M[2][3] * v->w; out.w = a->M[3][0] * v->x + a->M[3][1] * v->y + a->M[3][2] * v->z + a->M[3][3] * v->w; return out; } #ifndef EPSILON #define EPSILON 0.001f #endif static XrVector3f normalizeVec(XrVector3f vec) { //NOTE: leave w-component untouched //@@const float EPSILON = 0.000001f; float xxyyzz = vec.x*vec.x + vec.y*vec.y + vec.z*vec.z; //@@if(xxyyzz < EPSILON) //@@ return *this; // do nothing if it is zero vector //float invLength = invSqrt(xxyyzz); XrVector3f result; float invLength = 1.0f / sqrtf(xxyyzz); result.x = vec.x * invLength; result.y = vec.y * invLength; result.z = vec.z * invLength; return result; } void NormalizeAngles(vec3_t angles) { while (angles[0] >= 90) angles[0] -= 180; while (angles[1] >= 180) angles[1] -= 360; while (angles[2] >= 180) angles[2] -= 360; while (angles[0] < -90) angles[0] += 180; while (angles[1] < -180) angles[1] += 360; while (angles[2] < -180) angles[2] += 360; } void GetAnglesFromVectors(const XrVector3f forward, const XrVector3f right, const XrVector3f up, vec3_t angles) { float sr, sp, sy, cr, cp, cy; sp = -forward.z; float cp_x_cy = forward.x; float cp_x_sy = forward.y; float cp_x_sr = -right.z; float cp_x_cr = up.z; float yaw = atan2(cp_x_sy, cp_x_cy); float roll = atan2(cp_x_sr, cp_x_cr); cy = cos(yaw); sy = sin(yaw); cr = cos(roll); sr = sin(roll); if (fabs(cy) > EPSILON) { cp = cp_x_cy / cy; } else if (fabs(sy) > EPSILON) { cp = cp_x_sy / sy; } else if (fabs(sr) > EPSILON) { cp = cp_x_sr / sr; } else if (fabs(cr) > EPSILON) { cp = cp_x_cr / cr; } else { cp = cos(asin(sp)); } float pitch = atan2(sp, cp); angles[0] = pitch / (M_PI*2.f / 360.f); angles[1] = yaw / (M_PI*2.f / 360.f); angles[2] = roll / (M_PI*2.f / 360.f); NormalizeAngles(angles); } void QuatToYawPitchRoll(XrQuaternionf q, vec3_t rotation, vec3_t out) { ovrMatrix4f mat = ovrMatrix4f_CreateFromQuaternion( &q ); if (rotation[0] != 0.0f || rotation[1] != 0.0f || rotation[2] != 0.0f) { ovrMatrix4f rot = ovrMatrix4f_CreateRotation(DEG2RAD(rotation[0]), DEG2RAD(rotation[1]), DEG2RAD(rotation[2])); mat = ovrMatrix4f_Multiply(&mat, &rot); } XrVector4f v1 = {0, 0, -1, 0}; XrVector4f v2 = {1, 0, 0, 0}; XrVector4f v3 = {0, 1, 0, 0}; XrVector4f forwardInVRSpace = XrVector4f_MultiplyMatrix4f(&mat, &v1); XrVector4f rightInVRSpace = XrVector4f_MultiplyMatrix4f(&mat, &v2); XrVector4f upInVRSpace = XrVector4f_MultiplyMatrix4f(&mat, &v3); XrVector3f forward = {-forwardInVRSpace.z, -forwardInVRSpace.x, forwardInVRSpace.y}; XrVector3f right = {-rightInVRSpace.z, -rightInVRSpace.x, rightInVRSpace.y}; XrVector3f up = {-upInVRSpace.z, -upInVRSpace.x, upInVRSpace.y}; XrVector3f forwardNormal = normalizeVec(forward); XrVector3f rightNormal = normalizeVec(right); XrVector3f upNormal = normalizeVec(up); GetAnglesFromVectors(forwardNormal, rightNormal, upNormal, out); } /* ======================== TBXR_Vibrate ======================== */ void TBXR_Vibrate( int duration, int chan, float intensity ); /* ================================================================================ ovrRenderThread ================================================================================ */ void ovrApp_Clear(ovrApp* app) { app->Focused = false; app->Instance = XR_NULL_HANDLE; app->Session = XR_NULL_HANDLE; memset(&app->ViewportConfig, 0, sizeof(XrViewConfigurationProperties)); memset(&app->ViewConfigurationView, 0, ovrMaxNumEyes * sizeof(XrViewConfigurationView)); app->SystemId = XR_NULL_SYSTEM_ID; app->HeadSpace = XR_NULL_HANDLE; app->StageSpace = XR_NULL_HANDLE; app->FakeStageSpace = XR_NULL_HANDLE; app->CurrentSpace = XR_NULL_HANDLE; app->SessionActive = false; app->SupportedDisplayRefreshRates = NULL; app->RequestedDisplayRefreshRateIndex = 0; app->NumSupportedDisplayRefreshRates = 0; app->pfnGetDisplayRefreshRate = NULL; app->pfnRequestDisplayRefreshRate = NULL; app->SwapInterval = 1; memset(app->Layers, 0, sizeof(xrCompositorLayer_Union) * ovrMaxLayerCount); app->LayerCount = 0; app->MainThreadTid = 0; app->RenderThreadTid = 0; ovrEgl_Clear( &app->Egl ); ovrRenderer_Clear(&app->Renderer); } void ovrApp_HandleSessionStateChanges(ovrApp* app, XrSessionState state) { if (state == XR_SESSION_STATE_READY) { assert(app->SessionActive == false); XrSessionBeginInfo sessionBeginInfo; memset(&sessionBeginInfo, 0, sizeof(sessionBeginInfo)); sessionBeginInfo.type = XR_TYPE_SESSION_BEGIN_INFO; sessionBeginInfo.next = NULL; sessionBeginInfo.primaryViewConfigurationType = app->ViewportConfig.viewConfigurationType; XrResult result; OXR(result = xrBeginSession(app->Session, &sessionBeginInfo)); app->SessionActive = (result == XR_SUCCESS); // Set session state once we have entered VR mode and have a valid session object. if (app->SessionActive) { XrPerfSettingsLevelEXT cpuPerfLevel = XR_PERF_SETTINGS_LEVEL_BOOST_EXT; XrPerfSettingsLevelEXT gpuPerfLevel = XR_PERF_SETTINGS_LEVEL_BOOST_EXT; PFN_xrPerfSettingsSetPerformanceLevelEXT pfnPerfSettingsSetPerformanceLevelEXT = NULL; OXR(xrGetInstanceProcAddr( app->Instance, "xrPerfSettingsSetPerformanceLevelEXT", (PFN_xrVoidFunction * )(&pfnPerfSettingsSetPerformanceLevelEXT))); OXR(pfnPerfSettingsSetPerformanceLevelEXT( app->Session, XR_PERF_SETTINGS_DOMAIN_CPU_EXT, cpuPerfLevel)); OXR(pfnPerfSettingsSetPerformanceLevelEXT( app->Session, XR_PERF_SETTINGS_DOMAIN_GPU_EXT, gpuPerfLevel)); #ifdef META_QUEST PFN_xrSetAndroidApplicationThreadKHR pfnSetAndroidApplicationThreadKHR = NULL; OXR(xrGetInstanceProcAddr( app->Instance, "xrSetAndroidApplicationThreadKHR", (PFN_xrVoidFunction * )(&pfnSetAndroidApplicationThreadKHR))); OXR(pfnSetAndroidApplicationThreadKHR( app->Session, XR_ANDROID_THREAD_TYPE_APPLICATION_MAIN_KHR, app->MainThreadTid)); OXR(pfnSetAndroidApplicationThreadKHR( app->Session, XR_ANDROID_THREAD_TYPE_RENDERER_MAIN_KHR, app->RenderThreadTid)); #endif } } else if (state == XR_SESSION_STATE_STOPPING) { assert(app->SessionActive); OXR(xrEndSession(app->Session)); app->SessionActive = false; } } GLboolean ovrApp_HandleXrEvents(ovrApp* app) { XrEventDataBuffer eventDataBuffer = {}; GLboolean recenter = GL_FALSE; // Poll for events for (;;) { XrEventDataBaseHeader* baseEventHeader = (XrEventDataBaseHeader*)(&eventDataBuffer); baseEventHeader->type = XR_TYPE_EVENT_DATA_BUFFER; baseEventHeader->next = NULL; XrResult r; OXR(r = xrPollEvent(app->Instance, &eventDataBuffer)); if (r != XR_SUCCESS) { break; } switch (baseEventHeader->type) { case XR_TYPE_EVENT_DATA_EVENTS_LOST: ALOGV("xrPollEvent: received XR_TYPE_EVENT_DATA_EVENTS_LOST event"); break; case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING: { const XrEventDataInstanceLossPending* instance_loss_pending_event = (XrEventDataInstanceLossPending*)(baseEventHeader); ALOGV( "xrPollEvent: received XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING event: time %f", FromXrTime(instance_loss_pending_event->lossTime)); } break; case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED: ALOGV("xrPollEvent: received XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED event"); break; case XR_TYPE_EVENT_DATA_PERF_SETTINGS_EXT: { const XrEventDataPerfSettingsEXT* perf_settings_event = (XrEventDataPerfSettingsEXT*)(baseEventHeader); ALOGV( "xrPollEvent: received XR_TYPE_EVENT_DATA_PERF_SETTINGS_EXT event: type %d subdomain %d : level %d -> level %d", perf_settings_event->type, perf_settings_event->subDomain, perf_settings_event->fromLevel, perf_settings_event->toLevel); } break; #ifdef META_QUEST case XR_TYPE_EVENT_DATA_DISPLAY_REFRESH_RATE_CHANGED_FB: { const XrEventDataDisplayRefreshRateChangedFB* refresh_rate_changed_event = (XrEventDataDisplayRefreshRateChangedFB*)(baseEventHeader); ALOGV( "xrPollEvent: received XR_TYPE_EVENT_DATA_DISPLAY_REFRESH_RATE_CHANGED_FB event: fromRate %f -> toRate %f", refresh_rate_changed_event->fromDisplayRefreshRate, refresh_rate_changed_event->toDisplayRefreshRate); } break; #endif case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING: { XrEventDataReferenceSpaceChangePending* ref_space_change_event = (XrEventDataReferenceSpaceChangePending*)(baseEventHeader); ALOGV( "xrPollEvent: received XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING event: changed space: %d for session %p at time %f", ref_space_change_event->referenceSpaceType, (void*)ref_space_change_event->session, FromXrTime(ref_space_change_event->changeTime)); recenter = GL_TRUE; } break; case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: { const XrEventDataSessionStateChanged* session_state_changed_event = (XrEventDataSessionStateChanged*)(baseEventHeader); ALOGV( "xrPollEvent: received XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: %d for session %p at time %f", session_state_changed_event->state, (void*)session_state_changed_event->session, FromXrTime(session_state_changed_event->time)); switch (session_state_changed_event->state) { case XR_SESSION_STATE_FOCUSED: app->Focused = true; break; case XR_SESSION_STATE_VISIBLE: app->Focused = false; break; case XR_SESSION_STATE_READY: case XR_SESSION_STATE_STOPPING: ovrApp_HandleSessionStateChanges(app, session_state_changed_event->state); break; default: break; } } break; default: ALOGV("xrPollEvent: Unknown event"); break; } } return recenter; } void ovrAppThread_Create( ovrAppThread * appThread, JNIEnv * env, jobject activityObject, jclass activityClass ) { env->GetJavaVM( &appThread->JavaVm ); appThread->ActivityObject = env->NewGlobalRef( activityObject ); appThread->ActivityClass = (jclass)env->NewGlobalRef( activityClass ); appThread->Thread = 0; appThread->NativeWindow = NULL; surfaceMessageQueue_Create(&appThread->MessageQueue); const int createErr = pthread_create( &appThread->Thread, NULL, AppThreadFunction, appThread ); if ( createErr != 0 ) { ALOGE( "pthread_create returned %i", createErr ); } } void ovrAppThread_Destroy( ovrAppThread * appThread, JNIEnv * env ) { pthread_join( appThread->Thread, NULL ); env->DeleteGlobalRef( appThread->ActivityObject ); env->DeleteGlobalRef( appThread->ActivityClass ); surfaceMessageQueue_Destroy(&appThread->MessageQueue); } /* ================================================================================ surfaceMessageQueue ================================================================================ */ void surfaceMessage_Init(srufaceMessage * message, const int id, const int wait ) { message->Id = id; message->Wait = (ovrMQWait)wait; memset( message->Parms, 0, sizeof( message->Parms ) ); } void surfaceMessage_SetPointerParm(srufaceMessage * message, int index, void * ptr ) { *(void **)&message->Parms[index] = ptr; } void * surfaceMessage_GetPointerParm(srufaceMessage * message, int index ) { return *(void **)&message->Parms[index]; } void surfaceMessageQueue_Create(surfaceMessageQueue * messageQueue ) { messageQueue->Head = 0; messageQueue->Tail = 0; messageQueue->Wait = MQ_WAIT_NONE; messageQueue->EnabledFlag = false; messageQueue->PostedFlag = false; messageQueue->ReceivedFlag = false; messageQueue->ProcessedFlag = false; pthread_mutexattr_t attr; pthread_mutexattr_init( &attr ); pthread_mutexattr_settype( &attr, PTHREAD_MUTEX_ERRORCHECK ); pthread_mutex_init( &messageQueue->Mutex, &attr ); pthread_mutexattr_destroy( &attr ); pthread_cond_init( &messageQueue->PostedCondition, NULL ); pthread_cond_init( &messageQueue->ReceivedCondition, NULL ); pthread_cond_init( &messageQueue->ProcessedCondition, NULL ); } void surfaceMessageQueue_Destroy(surfaceMessageQueue * messageQueue ) { pthread_mutex_destroy( &messageQueue->Mutex ); pthread_cond_destroy( &messageQueue->PostedCondition ); pthread_cond_destroy( &messageQueue->ReceivedCondition ); pthread_cond_destroy( &messageQueue->ProcessedCondition ); } void surfaceMessageQueue_Enable(surfaceMessageQueue * messageQueue, const bool set ) { messageQueue->EnabledFlag = set; } void surfaceMessageQueue_PostMessage(surfaceMessageQueue * messageQueue, const srufaceMessage * message ) { if ( !messageQueue->EnabledFlag ) { return; } while ( messageQueue->Tail - messageQueue->Head >= MAX_MESSAGES ) { usleep( 1000 ); } pthread_mutex_lock( &messageQueue->Mutex ); messageQueue->Messages[messageQueue->Tail & ( MAX_MESSAGES - 1 )] = *message; messageQueue->Tail++; messageQueue->PostedFlag = true; pthread_cond_broadcast( &messageQueue->PostedCondition ); if ( message->Wait == MQ_WAIT_RECEIVED ) { while ( !messageQueue->ReceivedFlag ) { pthread_cond_wait( &messageQueue->ReceivedCondition, &messageQueue->Mutex ); } messageQueue->ReceivedFlag = false; } else if ( message->Wait == MQ_WAIT_PROCESSED ) { while ( !messageQueue->ProcessedFlag ) { pthread_cond_wait( &messageQueue->ProcessedCondition, &messageQueue->Mutex ); } messageQueue->ProcessedFlag = false; } pthread_mutex_unlock( &messageQueue->Mutex ); } static void ovrMessageQueue_SleepUntilMessage(surfaceMessageQueue * messageQueue ) { if ( messageQueue->Wait == MQ_WAIT_PROCESSED ) { messageQueue->ProcessedFlag = true; pthread_cond_broadcast( &messageQueue->ProcessedCondition ); messageQueue->Wait = MQ_WAIT_NONE; } pthread_mutex_lock( &messageQueue->Mutex ); if ( messageQueue->Tail > messageQueue->Head ) { pthread_mutex_unlock( &messageQueue->Mutex ); return; } while ( !messageQueue->PostedFlag ) { pthread_cond_wait( &messageQueue->PostedCondition, &messageQueue->Mutex ); } messageQueue->PostedFlag = false; pthread_mutex_unlock( &messageQueue->Mutex ); } static bool surfaceMessageQueue_GetNextMessage(surfaceMessageQueue * messageQueue, srufaceMessage * message, bool waitForMessages ) { if ( messageQueue->Wait == MQ_WAIT_PROCESSED ) { messageQueue->ProcessedFlag = true; pthread_cond_broadcast( &messageQueue->ProcessedCondition ); messageQueue->Wait = MQ_WAIT_NONE; } if ( waitForMessages ) { ovrMessageQueue_SleepUntilMessage( messageQueue ); } pthread_mutex_lock( &messageQueue->Mutex ); if ( messageQueue->Tail <= messageQueue->Head ) { pthread_mutex_unlock( &messageQueue->Mutex ); return false; } *message = messageQueue->Messages[messageQueue->Head & ( MAX_MESSAGES - 1 )]; messageQueue->Head++; pthread_mutex_unlock( &messageQueue->Mutex ); if ( message->Wait == MQ_WAIT_RECEIVED ) { messageQueue->ReceivedFlag = true; pthread_cond_broadcast( &messageQueue->ReceivedCondition ); } else if ( message->Wait == MQ_WAIT_PROCESSED ) { messageQueue->Wait = MQ_WAIT_PROCESSED; } return true; } ovrAppThread * gAppThread = NULL; ovrApp gAppState; ovrJava java; bool destroyed = qfalse; void TBXR_GetScreenRes(int *width, int *height) { *width = gAppState.Width; *height = gAppState.Height; } XrInstance TBXR_GetXrInstance() { return gAppState.Instance; } static void TBXR_ProcessMessageQueue() { for ( ; ; ) { srufaceMessage message; if ( !surfaceMessageQueue_GetNextMessage(&gAppThread->MessageQueue, &message, false) ) { break; } switch ( message.Id ) { case MESSAGE_ON_CREATE: { break; } case MESSAGE_ON_START: { break; } case MESSAGE_ON_RESUME: { //If we get here, then user has opted not to quit gAppState.Resumed = true; break; } case MESSAGE_ON_PAUSE: { gAppState.Resumed = false; break; } case MESSAGE_ON_STOP: { break; } case MESSAGE_ON_DESTROY: { gAppState.NativeWindow = NULL; destroyed = true; //shutdown = true; break; } case MESSAGE_ON_SURFACE_CREATED: { gAppState.NativeWindow = (ANativeWindow *) surfaceMessage_GetPointerParm( &message, 0); break; } case MESSAGE_ON_SURFACE_DESTROYED: { gAppState.NativeWindow = NULL; break; } } } } void ovrTrackedController_Clear(ovrTrackedController* controller) { controller->Active = false; controller->Pose = XrPosef_Identity(); } void GL_APIENTRYP VR_GLDebugLog(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,GLvoid *userParam) { if (type == GL_DEBUG_TYPE_ERROR || type == GL_DEBUG_TYPE_PERFORMANCE || ENABLE_GL_DEBUG_VERBOSE) { char typeStr[128]; switch (type) { case GL_DEBUG_TYPE_ERROR: sprintf(typeStr, "ERROR"); break; case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: sprintf(typeStr, "DEPRECATED_BEHAVIOR"); break; case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: sprintf(typeStr, "UNDEFINED_BEHAVIOR"); break; case GL_DEBUG_TYPE_PORTABILITY: sprintf(typeStr, "PORTABILITY"); break; case GL_DEBUG_TYPE_PERFORMANCE: sprintf(typeStr, "PERFORMANCE"); break; case GL_DEBUG_TYPE_MARKER: sprintf(typeStr, "MARKER"); break; case GL_DEBUG_TYPE_PUSH_GROUP: sprintf(typeStr, "PUSH_GROUP"); break; case GL_DEBUG_TYPE_POP_GROUP: sprintf(typeStr, "POP_GROUP"); break; default: sprintf(typeStr, "OTHER"); break; } char severinityStr[128]; switch (severity) { case GL_DEBUG_SEVERITY_HIGH: sprintf(severinityStr, "HIGH"); break; case GL_DEBUG_SEVERITY_MEDIUM: sprintf(severinityStr, "MEDIUM"); break; case GL_DEBUG_SEVERITY_LOW: sprintf(severinityStr, "LOW"); break; default: sprintf(severinityStr, "VERBOSE"); break; } ALOGE("[%s] GL issue - %s: %s\n", severinityStr, typeStr, message); } } void TBXR_InitialiseResolution() { // Enumerate the viewport configurations. uint32_t viewportConfigTypeCount = 0; OXR(xrEnumerateViewConfigurations( gAppState.Instance, gAppState.SystemId, 0, &viewportConfigTypeCount, NULL)); XrViewConfigurationType* viewportConfigurationTypes = (XrViewConfigurationType*)malloc(viewportConfigTypeCount * sizeof(XrViewConfigurationType)); OXR(xrEnumerateViewConfigurations( gAppState.Instance, gAppState.SystemId, viewportConfigTypeCount, &viewportConfigTypeCount, viewportConfigurationTypes)); ALOGV("Available Viewport Configuration Types: %d", viewportConfigTypeCount); for (uint32_t i = 0; i < viewportConfigTypeCount; i++) { const XrViewConfigurationType viewportConfigType = viewportConfigurationTypes[i]; ALOGV( "Viewport configuration type %d : %s", viewportConfigType, viewportConfigType == XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO ? "Selected" : ""); XrViewConfigurationProperties viewportConfig; viewportConfig.type = XR_TYPE_VIEW_CONFIGURATION_PROPERTIES; OXR(xrGetViewConfigurationProperties( gAppState.Instance, gAppState.SystemId, viewportConfigType, &viewportConfig)); ALOGV( "FovMutable=%s ConfigurationType %d", viewportConfig.fovMutable ? "true" : "false", viewportConfig.viewConfigurationType); uint32_t viewCount; OXR(xrEnumerateViewConfigurationViews( gAppState.Instance, gAppState.SystemId, viewportConfigType, 0, &viewCount, NULL)); if (viewCount > 0) { XrViewConfigurationView* elements = (XrViewConfigurationView*)malloc(viewCount * sizeof(XrViewConfigurationView)); for (uint32_t e = 0; e < viewCount; e++) { elements[e].type = XR_TYPE_VIEW_CONFIGURATION_VIEW; elements[e].next = NULL; } OXR(xrEnumerateViewConfigurationViews( gAppState.Instance, gAppState.SystemId, viewportConfigType, viewCount, &viewCount, elements)); // Cache the view config properties for the selected config type. if (viewportConfigType == XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO) { assert(viewCount == ovrMaxNumEyes); for (uint32_t e = 0; e < viewCount; e++) { gAppState.ViewConfigurationView[e] = elements[e]; } } free(elements); } else { ALOGE("Empty viewport configuration type: %d", viewCount); } } free(viewportConfigurationTypes); //Shortcut to width and height gAppState.Width = gAppState.ViewConfigurationView[0].recommendedImageRectWidth; gAppState.Height = gAppState.ViewConfigurationView[0].recommendedImageRectHeight; } void TBXR_EnterVR( ) { if (gAppState.Session) { Com_Printf("TBXR_EnterVR called with existing session"); return; } // Create the OpenXR Session. XrGraphicsBindingOpenGLESAndroidKHR graphicsBindingAndroidGLES = {}; graphicsBindingAndroidGLES.type = XR_TYPE_GRAPHICS_BINDING_OPENGL_ES_ANDROID_KHR; graphicsBindingAndroidGLES.next = NULL; graphicsBindingAndroidGLES.display = eglGetCurrentDisplay(); graphicsBindingAndroidGLES.config = eglGetCurrentSurface(EGL_DRAW); graphicsBindingAndroidGLES.context = eglGetCurrentContext(); XrSessionCreateInfo sessionCreateInfo = {}; memset(&sessionCreateInfo, 0, sizeof(sessionCreateInfo)); sessionCreateInfo.type = XR_TYPE_SESSION_CREATE_INFO; sessionCreateInfo.next = &graphicsBindingAndroidGLES; sessionCreateInfo.createFlags = 0; sessionCreateInfo.systemId = gAppState.SystemId; XrResult initResult; OXR(initResult = xrCreateSession(gAppState.Instance, &sessionCreateInfo, &gAppState.Session)); if (initResult != XR_SUCCESS) { ALOGE("Failed to create XR session: %d.", initResult); exit(1); } // Create a space to the first path XrReferenceSpaceCreateInfo spaceCreateInfo = {}; spaceCreateInfo.type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO; spaceCreateInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_VIEW; spaceCreateInfo.poseInReferenceSpace.orientation.w = 1.0f; OXR(xrCreateReferenceSpace(gAppState.Session, &spaceCreateInfo, &gAppState.HeadSpace)); } void TBXR_LeaveVR( ) { if (gAppState.Session) { OXR(xrDestroySpace(gAppState.HeadSpace)); // StageSpace is optional. if (gAppState.StageSpace != XR_NULL_HANDLE) { OXR(xrDestroySpace(gAppState.StageSpace)); } OXR(xrDestroySpace(gAppState.FakeStageSpace)); gAppState.CurrentSpace = XR_NULL_HANDLE; OXR(xrDestroySession(gAppState.Session)); gAppState.Session = NULL; } ovrRenderer_Destroy( &gAppState.Renderer ); ovrEgl_DestroyContext( &gAppState.Egl ); java.Vm->DetachCurrentThread( ); } void TBXR_InitRenderer( ) { // Get the viewport configuration info for the chosen viewport configuration type. gAppState.ViewportConfig.type = XR_TYPE_VIEW_CONFIGURATION_PROPERTIES; OXR(xrGetViewConfigurationProperties( gAppState.Instance, gAppState.SystemId, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, &gAppState.ViewportConfig)); #ifdef META_QUEST XrSystemColorSpacePropertiesFB colorSpacePropertiesFB = {}; colorSpacePropertiesFB.type = XR_TYPE_SYSTEM_COLOR_SPACE_PROPERTIES_FB; XrSystemProperties systemProperties = {}; systemProperties.type = XR_TYPE_SYSTEM_PROPERTIES; systemProperties.next = &colorSpacePropertiesFB; OXR(xrGetSystemProperties(gAppState.Instance, gAppState.SystemId, &systemProperties)); // Enumerate the supported color space options for the system. { PFN_xrEnumerateColorSpacesFB pfnxrEnumerateColorSpacesFB = NULL; OXR(xrGetInstanceProcAddr( gAppState.Instance, "xrEnumerateColorSpacesFB", (PFN_xrVoidFunction*)(&pfnxrEnumerateColorSpacesFB))); uint32_t colorSpaceCountOutput = 0; OXR(pfnxrEnumerateColorSpacesFB(gAppState.Session, 0, &colorSpaceCountOutput, NULL)); XrColorSpaceFB* colorSpaces = (XrColorSpaceFB*)malloc(colorSpaceCountOutput * sizeof(XrColorSpaceFB)); OXR(pfnxrEnumerateColorSpacesFB( gAppState.Session, colorSpaceCountOutput, &colorSpaceCountOutput, colorSpaces)); ALOGV("Supported ColorSpaces:"); for (uint32_t i = 0; i < colorSpaceCountOutput; i++) { ALOGV("%d:%d", i, colorSpaces[i]); } const XrColorSpaceFB requestColorSpace = XR_COLOR_SPACE_REC2020_FB; PFN_xrSetColorSpaceFB pfnxrSetColorSpaceFB = NULL; OXR(xrGetInstanceProcAddr( gAppState.Instance, "xrSetColorSpaceFB", (PFN_xrVoidFunction*)(&pfnxrSetColorSpaceFB))); OXR(pfnxrSetColorSpaceFB(gAppState.Session, requestColorSpace)); free(colorSpaces); } // Get the supported display refresh rates for the system. { PFN_xrEnumerateDisplayRefreshRatesFB pfnxrEnumerateDisplayRefreshRatesFB = NULL; OXR(xrGetInstanceProcAddr( gAppState.Instance, "xrEnumerateDisplayRefreshRatesFB", (PFN_xrVoidFunction*)(&pfnxrEnumerateDisplayRefreshRatesFB))); OXR(pfnxrEnumerateDisplayRefreshRatesFB( gAppState.Session, 0, &gAppState.NumSupportedDisplayRefreshRates, NULL)); gAppState.SupportedDisplayRefreshRates = (float*)malloc(gAppState.NumSupportedDisplayRefreshRates * sizeof(float)); OXR(pfnxrEnumerateDisplayRefreshRatesFB( gAppState.Session, gAppState.NumSupportedDisplayRefreshRates, &gAppState.NumSupportedDisplayRefreshRates, gAppState.SupportedDisplayRefreshRates)); ALOGV("Supported Refresh Rates:"); for (uint32_t i = 0; i < gAppState.NumSupportedDisplayRefreshRates; i++) { ALOGV("%d:%f", i, gAppState.SupportedDisplayRefreshRates[i]); } OXR(xrGetInstanceProcAddr( gAppState.Instance, "xrGetDisplayRefreshRateFB", (PFN_xrVoidFunction*)(&gAppState.pfnGetDisplayRefreshRate))); OXR(gAppState.pfnGetDisplayRefreshRate(gAppState.Session, &gAppState.currentDisplayRefreshRate)); ALOGV("Current System Display Refresh Rate: %f", gAppState.currentDisplayRefreshRate); OXR(xrGetInstanceProcAddr( gAppState.Instance, "xrRequestDisplayRefreshRateFB", (PFN_xrVoidFunction*)(&gAppState.pfnRequestDisplayRefreshRate))); // Test requesting the system default. OXR(gAppState.pfnRequestDisplayRefreshRate(gAppState.Session, 0.0f)); ALOGV("Requesting system default display refresh rate"); } #endif uint32_t numOutputSpaces = 0; OXR(xrEnumerateReferenceSpaces(gAppState.Session, 0, &numOutputSpaces, NULL)); XrReferenceSpaceType* referenceSpaces = (XrReferenceSpaceType*)malloc(numOutputSpaces * sizeof(XrReferenceSpaceType)); OXR(xrEnumerateReferenceSpaces( gAppState.Session, numOutputSpaces, &numOutputSpaces, referenceSpaces)); for (uint32_t i = 0; i < numOutputSpaces; i++) { if (referenceSpaces[i] == XR_REFERENCE_SPACE_TYPE_STAGE) { stageSupported = GL_TRUE; break; } } free(referenceSpaces); if (gAppState.CurrentSpace == XR_NULL_HANDLE) { TBXR_Recenter(); } gAppState.Projections = (XrView*)(malloc(ovrMaxNumEyes * sizeof(XrView))); for (int eye = 0; eye < ovrMaxNumEyes; eye++) { memset(&gAppState.Projections[eye], 0, sizeof(XrView)); gAppState.Projections[eye].type = XR_TYPE_VIEW; } #ifdef PICO_XR xrGetInstanceProcAddr(gAppState.Instance,"xrSetConfigPICO", (PFN_xrVoidFunction*)(&pfnXrSetConfigPICO)); xrGetInstanceProcAddr(gAppState.Instance,"xrGetConfigPICO", (PFN_xrVoidFunction*)(&pfnXrGetConfigPICO)); pfnXrSetConfigPICO(gAppState.Session,TRACKING_ORIGIN,"0"); pfnXrSetConfigPICO(gAppState.Session,TRACKING_ORIGIN,"1"); pfnXrGetConfigPICO(gAppState.Session, GET_DISPLAY_RATE, &gAppState.currentDisplayRefreshRate); #endif ovrRenderer_Create( gAppState.Session, &gAppState.Renderer, gAppState.ViewConfigurationView[0].recommendedImageRectWidth, gAppState.ViewConfigurationView[0].recommendedImageRectHeight); } void VR_DestroyRenderer( ) { ovrRenderer_Destroy(&gAppState.Renderer); free(gAppState.Projections); } void TBXR_InitialiseOpenXR() { ovrApp_Clear(&gAppState); gAppState.Java = java; ovrEgl_CreateContext(&gAppState.Egl, NULL); EglInitExtensions(); PFN_xrInitializeLoaderKHR xrInitializeLoaderKHR; xrGetInstanceProcAddr( XR_NULL_HANDLE, "xrInitializeLoaderKHR", (PFN_xrVoidFunction*)&xrInitializeLoaderKHR); if (xrInitializeLoaderKHR != NULL) { XrLoaderInitInfoAndroidKHR loaderInitializeInfoAndroid; memset(&loaderInitializeInfoAndroid, 0, sizeof(loaderInitializeInfoAndroid)); loaderInitializeInfoAndroid.type = XR_TYPE_LOADER_INIT_INFO_ANDROID_KHR; loaderInitializeInfoAndroid.next = NULL; loaderInitializeInfoAndroid.applicationVM = java.Vm; loaderInitializeInfoAndroid.applicationContext = java.ActivityObject; xrInitializeLoaderKHR((XrLoaderInitInfoBaseHeaderKHR*)&loaderInitializeInfoAndroid); } // Create the OpenXR instance. XrApplicationInfo appInfo; memset(&appInfo, 0, sizeof(appInfo)); strcpy(appInfo.applicationName, "JKXR"); appInfo.applicationVersion = 0; strcpy(appInfo.engineName, "JKXR"); appInfo.engineVersion = 0; appInfo.apiVersion = XR_CURRENT_API_VERSION; XrInstanceCreateInfo instanceCreateInfo; memset(&instanceCreateInfo, 0, sizeof(instanceCreateInfo)); instanceCreateInfo.type = XR_TYPE_INSTANCE_CREATE_INFO; #ifdef META_QUEST instanceCreateInfo.next = NULL; #endif #ifdef PICO_XR XrInstanceCreateInfoAndroidKHR instanceCreateInfoAndroid = {XR_TYPE_INSTANCE_CREATE_INFO_ANDROID_KHR}; instanceCreateInfoAndroid.applicationVM = java.Vm; instanceCreateInfoAndroid.applicationActivity = java.ActivityObject; instanceCreateInfo.next = (XrBaseInStructure*)&instanceCreateInfoAndroid; #endif instanceCreateInfo.createFlags = 0; instanceCreateInfo.applicationInfo = appInfo; instanceCreateInfo.enabledApiLayerCount = 0; instanceCreateInfo.enabledApiLayerNames = NULL; instanceCreateInfo.enabledExtensionCount = numRequiredExtensions; instanceCreateInfo.enabledExtensionNames = requiredExtensionNames; XrResult initResult; OXR(initResult = xrCreateInstance(&instanceCreateInfo, &gAppState.Instance)); if (initResult != XR_SUCCESS) { ALOGE("Failed to create XR instance: %d.", initResult); exit(1); } XrInstanceProperties instanceInfo; instanceInfo.type = XR_TYPE_INSTANCE_PROPERTIES; instanceInfo.next = NULL; OXR(xrGetInstanceProperties(gAppState.Instance, &instanceInfo)); ALOGV( "Runtime %s: Version : %u.%u.%u", instanceInfo.runtimeName, XR_VERSION_MAJOR(instanceInfo.runtimeVersion), XR_VERSION_MINOR(instanceInfo.runtimeVersion), XR_VERSION_PATCH(instanceInfo.runtimeVersion)); XrSystemGetInfo systemGetInfo; memset(&systemGetInfo, 0, sizeof(systemGetInfo)); systemGetInfo.type = XR_TYPE_SYSTEM_GET_INFO; systemGetInfo.next = NULL; systemGetInfo.formFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY; OXR(initResult = xrGetSystem(gAppState.Instance, &systemGetInfo, &gAppState.SystemId)); if (initResult != XR_SUCCESS) { ALOGE("Failed to get system."); exit(1); } // Get the graphics requirements. PFN_xrGetOpenGLESGraphicsRequirementsKHR pfnGetOpenGLESGraphicsRequirementsKHR = NULL; OXR(xrGetInstanceProcAddr( gAppState.Instance, "xrGetOpenGLESGraphicsRequirementsKHR", (PFN_xrVoidFunction * )(&pfnGetOpenGLESGraphicsRequirementsKHR))); XrGraphicsRequirementsOpenGLESKHR graphicsRequirements = {}; graphicsRequirements.type = XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_ES_KHR; OXR(pfnGetOpenGLESGraphicsRequirementsKHR(gAppState.Instance, gAppState.SystemId, &graphicsRequirements)); #ifdef META_QUEST XrSystemColorSpacePropertiesFB colorSpacePropertiesFB = {}; colorSpacePropertiesFB.type = XR_TYPE_SYSTEM_COLOR_SPACE_PROPERTIES_FB; XrSystemProperties systemProperties = {}; systemProperties.type = XR_TYPE_SYSTEM_PROPERTIES; systemProperties.next = &colorSpacePropertiesFB; OXR(xrGetSystemProperties(gAppState.Instance, gAppState.SystemId, &systemProperties)); ALOGV("System Color Space Properties: colorspace=%d", colorSpacePropertiesFB.colorSpace); #endif TBXR_InitialiseResolution(); } void TBXR_Recenter() { // Calculate recenter reference XrReferenceSpaceCreateInfo spaceCreateInfo = {}; spaceCreateInfo.type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO; spaceCreateInfo.poseInReferenceSpace.orientation.w = 1.0f; if (gAppState.CurrentSpace != XR_NULL_HANDLE) { vec3_t rotation = {0, 0, 0}; XrSpaceLocation loc = {}; loc.type = XR_TYPE_SPACE_LOCATION; OXR(xrLocateSpace(gAppState.HeadSpace, gAppState.CurrentSpace, gAppState.PredictedDisplayTime, &loc)); QuatToYawPitchRoll(loc.pose.orientation, rotation, vr.hmdorientation); spaceCreateInfo.poseInReferenceSpace.orientation.x = 0; spaceCreateInfo.poseInReferenceSpace.orientation.y = 0; spaceCreateInfo.poseInReferenceSpace.orientation.z = 0; spaceCreateInfo.poseInReferenceSpace.orientation.w = 1; } // Delete previous space instances if (gAppState.StageSpace != XR_NULL_HANDLE) { OXR(xrDestroySpace(gAppState.StageSpace)); } if (gAppState.FakeStageSpace != XR_NULL_HANDLE) { OXR(xrDestroySpace(gAppState.FakeStageSpace)); } // Create a default stage space to use if SPACE_TYPE_STAGE is not // supported, or calls to xrGetReferenceSpaceBoundsRect fail. spaceCreateInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL; spaceCreateInfo.poseInReferenceSpace.position.y = -1.6750f; OXR(xrCreateReferenceSpace(gAppState.Session, &spaceCreateInfo, &gAppState.FakeStageSpace)); ALOGV("Created fake stage space from local space with offset"); gAppState.CurrentSpace = gAppState.FakeStageSpace; if (stageSupported) { spaceCreateInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE; spaceCreateInfo.poseInReferenceSpace.position.y = 0.0f; OXR(xrCreateReferenceSpace(gAppState.Session, &spaceCreateInfo, &gAppState.StageSpace)); ALOGV("Created stage space"); gAppState.CurrentSpace = gAppState.StageSpace; } } void TBXR_UpdateStageBounds() { XrExtent2Df stageBounds = {}; XrResult result; OXR(result = xrGetReferenceSpaceBoundsRect( gAppState.Session, XR_REFERENCE_SPACE_TYPE_STAGE, &stageBounds)); if (result != XR_SUCCESS) { ALOGE("Stage bounds query failed: using small defaults"); stageBounds.width = 1.0f; stageBounds.height = 1.0f; gAppState.CurrentSpace = gAppState.FakeStageSpace; } } void TBXR_WaitForSessionActive() {//Now wait for the session to be ready while (!gAppState.SessionActive) { TBXR_ProcessMessageQueue(); if (ovrApp_HandleXrEvents(&gAppState)) { TBXR_Recenter(); } } } static void TBXR_GetHMDOrientation() { if (gAppState.PredictedDisplayTime == 0) { return; } // Get the HMD pose, predicted for the middle of the time period during which // the new eye images will be displayed. The number of frames predicted ahead // depends on the pipeline depth of the engine and the synthesis rate. // The better the prediction, the less black will be pulled in at the edges. XrSpaceLocation loc = {}; loc.type = XR_TYPE_SPACE_LOCATION; OXR(xrLocateSpace(gAppState.HeadSpace, gAppState.CurrentSpace, gAppState.PredictedDisplayTime, &loc)); gAppState.xfStageFromHead = loc.pose; const XrQuaternionf quatHmd = gAppState.xfStageFromHead.orientation; const XrVector3f positionHmd = gAppState.xfStageFromHead.position; vec3_t rotation = {0, 0, 0}; vec3_t hmdorientation = {0, 0, 0}; QuatToYawPitchRoll(quatHmd, rotation, hmdorientation); VR_SetHMDPosition(positionHmd.x, positionHmd.y, positionHmd.z); VR_SetHMDOrientation(hmdorientation[0], hmdorientation[1], hmdorientation[2]); } //All the stuff we want to do each frame void TBXR_FrameSetup() { if (gAppState.FrameSetup) { return; } while (!destroyed) { TBXR_ProcessMessageQueue(); GLboolean stageBoundsDirty = GL_TRUE; if (ovrApp_HandleXrEvents(&gAppState)) { TBXR_Recenter(); } if (gAppState.SessionActive == GL_FALSE) { continue; } if (stageBoundsDirty) { TBXR_UpdateStageBounds(); stageBoundsDirty = GL_FALSE; } break; } if (destroyed) { TBXR_LeaveVR(); //Ask Java to shut down VR_Shutdown(); exit(0); // in case Java doesn't do the job } // NOTE: OpenXR does not use the concept of frame indices. Instead, // XrWaitFrame returns the predicted display time. XrFrameWaitInfo waitFrameInfo = {}; waitFrameInfo.type = XR_TYPE_FRAME_WAIT_INFO; waitFrameInfo.next = NULL; XrFrameState frameState = {}; frameState.type = XR_TYPE_FRAME_STATE; frameState.next = NULL; OXR(xrWaitFrame(gAppState.Session, &waitFrameInfo, &frameState)); gAppState.PredictedDisplayTime = frameState.predictedDisplayTime; if (!frameState.shouldRender) { return; } // Get the HMD pose, predicted for the middle of the time period during which // the new eye images will be displayed. The number of frames predicted ahead // depends on the pipeline depth of the engine and the synthesis rate. // The better the prediction, the less black will be pulled in at the edges. XrFrameBeginInfo beginFrameDesc = {}; beginFrameDesc.type = XR_TYPE_FRAME_BEGIN_INFO; beginFrameDesc.next = NULL; OXR(xrBeginFrame(gAppState.Session, &beginFrameDesc)); //Game specific frame setup stuff called here VR_FrameSetup(); //Get controller state here TBXR_GetHMDOrientation(); VR_HandleControllerInput(); TBXR_ProcessHaptics(); gAppState.FrameSetup = true; } int TBXR_GetRefresh() { return gAppState.currentDisplayRefreshRate; } #define GL_FRAMEBUFFER_SRGB 0x8DB9 void TBXR_ClearFrameBuffer(int width, int height) { glEnable( GL_SCISSOR_TEST ); glViewport( 0, 0, width, height ); //Black glClearColor( 0.0f, 0.0f, 0.0f, 1.0f ); glScissor( 0, 0, width, height ); glClear( GL_COLOR_BUFFER_BIT ); glScissor( 0, 0, 0, 0 ); glDisable( GL_SCISSOR_TEST ); //This is a bit of a hack, but we need to do this to correct for the fact that the engine uses linear RGB colorspace //but openxr uses SRGB (or something, must admit I don't really understand, but adding this works to make it look good again) glDisable( GL_FRAMEBUFFER_SRGB ); } void TBXR_prepareEyeBuffer(int eye ) { ovrFramebuffer* frameBuffer = &(gAppState.Renderer.FrameBuffer[eye]); ovrFramebuffer_Acquire(frameBuffer); ovrFramebuffer_SetCurrent(frameBuffer); TBXR_ClearFrameBuffer(frameBuffer->ColorSwapChain.Width, frameBuffer->ColorSwapChain.Height); } void TBXR_finishEyeBuffer(int eye ) { ovrRenderer *renderer = &gAppState.Renderer; ovrFramebuffer *frameBuffer = &(renderer->FrameBuffer[eye]); // Clear the alpha channel, other way OpenXR would not transfer the framebuffer fully glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE); glClearColor(0.0, 0.0, 0.0, 1.0); glClear(GL_COLOR_BUFFER_BIT); glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); //Clear edge to prevent smearing ovrFramebuffer_Resolve(frameBuffer); ovrFramebuffer_Release(frameBuffer); ovrFramebuffer_SetNone(); } void TBXR_updateProjections() { XrViewLocateInfo projectionInfo = {}; projectionInfo.type = XR_TYPE_VIEW_LOCATE_INFO; projectionInfo.viewConfigurationType = gAppState.ViewportConfig.viewConfigurationType; projectionInfo.displayTime = gAppState.PredictedDisplayTime; projectionInfo.space = gAppState.HeadSpace; XrViewState viewState = {XR_TYPE_VIEW_STATE, NULL}; uint32_t projectionCapacityInput = ovrMaxNumEyes; uint32_t projectionCountOutput = projectionCapacityInput; OXR(xrLocateViews( gAppState.Session, &projectionInfo, &viewState, projectionCapacityInput, &projectionCountOutput, gAppState.Projections)); } void TBXR_submitFrame() { if (gAppState.SessionActive == GL_FALSE) { return; } TBXR_updateProjections(); XrFovf fov = {}; XrPosef viewTransform[2]; for (int eye = 0; eye < ovrMaxNumEyes; eye++) { XrPosef xfHeadFromEye = gAppState.Projections[eye].pose; XrPosef xfStageFromEye = XrPosef_Multiply(gAppState.xfStageFromHead, xfHeadFromEye); viewTransform[eye] = XrPosef_Inverse(xfStageFromEye); fov.angleLeft += gAppState.Projections[eye].fov.angleLeft / 2.0f; fov.angleRight += gAppState.Projections[eye].fov.angleRight / 2.0f; fov.angleUp += gAppState.Projections[eye].fov.angleUp / 2.0f; fov.angleDown += gAppState.Projections[eye].fov.angleDown / 2.0f; } vr.fov_x = (fabs(fov.angleLeft) + fabs(fov.angleRight)) * 180.0f / M_PI; vr.fov_y = (fabs(fov.angleUp) + fabs(fov.angleDown)) * 180.0f / M_PI; if (vr.cgzoommode) { fov.angleLeft /= 1.3f; fov.angleRight /= 1.3f; fov.angleUp /= 1.3f; fov.angleDown /= 1.3f; } gAppState.LayerCount = 0; memset(gAppState.Layers, 0, sizeof(xrCompositorLayer_Union) * ovrMaxLayerCount); XrCompositionLayerProjectionView projection_layer_elements[2] = {}; if (!VR_UseScreenLayer()) { XrCompositionLayerProjection projection_layer = {}; projection_layer.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION; projection_layer.layerFlags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT; projection_layer.layerFlags |= XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT; projection_layer.space = gAppState.CurrentSpace; projection_layer.viewCount = ovrMaxNumEyes; projection_layer.views = projection_layer_elements; for (int eye = 0; eye < ovrMaxNumEyes; eye++) { ovrFramebuffer* frameBuffer = &gAppState.Renderer.FrameBuffer[eye]; memset(&projection_layer_elements[eye], 0, sizeof(XrCompositionLayerProjectionView)); projection_layer_elements[eye].type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW; projection_layer_elements[eye].pose = gAppState.xfStageFromHead; projection_layer_elements[eye].fov = fov; memset(&projection_layer_elements[eye].subImage, 0, sizeof(XrSwapchainSubImage)); projection_layer_elements[eye].subImage.swapchain = frameBuffer->ColorSwapChain.Handle; projection_layer_elements[eye].subImage.imageRect.offset.x = 0; projection_layer_elements[eye].subImage.imageRect.offset.y = 0; projection_layer_elements[eye].subImage.imageRect.extent.width = frameBuffer->ColorSwapChain.Width; projection_layer_elements[eye].subImage.imageRect.extent.height = frameBuffer->ColorSwapChain.Height; projection_layer_elements[eye].subImage.imageArrayIndex = 0; } gAppState.Layers[gAppState.LayerCount++].Projection = projection_layer; } else { // Build the quad layer XrCompositionLayerQuad quad_layer = {}; int width = gAppState.Renderer.FrameBuffer[0].ColorSwapChain.Width; int height = gAppState.Renderer.FrameBuffer[0].ColorSwapChain.Height; quad_layer.type = XR_TYPE_COMPOSITION_LAYER_QUAD; quad_layer.next = NULL; quad_layer.layerFlags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT; quad_layer.space = gAppState.CurrentSpace; quad_layer.eyeVisibility = XR_EYE_VISIBILITY_BOTH; memset(&quad_layer.subImage, 0, sizeof(XrSwapchainSubImage)); quad_layer.subImage.swapchain = gAppState.Renderer.FrameBuffer[0].ColorSwapChain.Handle; quad_layer.subImage.imageRect.offset.x = 0; quad_layer.subImage.imageRect.offset.y = 0; quad_layer.subImage.imageRect.extent.width = width; quad_layer.subImage.imageRect.extent.height = height; quad_layer.subImage.imageArrayIndex = 0; const XrVector3f axis = {0.0f, 1.0f, 0.0f}; XrVector3f pos = { gAppState.xfStageFromHead.position.x - sin(DEG2RAD(vr.hmdorientation_snap[YAW])) * VR_GetScreenLayerDistance(), 1.0f, gAppState.xfStageFromHead.position.z - cos(DEG2RAD(vr.hmdorientation_snap[YAW])) * VR_GetScreenLayerDistance() }; quad_layer.pose.orientation = XrQuaternionf_CreateFromVectorAngle(axis, DEG2RAD(vr.hmdorientation_snap[YAW])); quad_layer.pose.position = pos; XrExtent2Df size = {5.0f, 4.5f}; quad_layer.size = size; gAppState.Layers[gAppState.LayerCount++].Quad = quad_layer; } // Compose the layers for this frame. const XrCompositionLayerBaseHeader* layers[ovrMaxLayerCount] = {}; for (int i = 0; i < gAppState.LayerCount; i++) { layers[i] = (const XrCompositionLayerBaseHeader*)&gAppState.Layers[i]; } XrFrameEndInfo endFrameInfo = {}; endFrameInfo.type = XR_TYPE_FRAME_END_INFO; endFrameInfo.displayTime = gAppState.PredictedDisplayTime; endFrameInfo.environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE; endFrameInfo.layerCount = gAppState.LayerCount; endFrameInfo.layers = layers; OXR(xrEndFrame(gAppState.Session, &endFrameInfo)); gAppState.FrameSetup = false; }