Merge pull request #69 from lvonasek/feature_openxr

OpenXR integration
This commit is contained in:
Simon 2022-05-01 10:20:40 +01:00 committed by GitHub
commit cb152467ba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 1811 additions and 466 deletions

2
.gitignore vendored
View file

@ -1,4 +1,6 @@
build build
android/app/src/main/cpp/code/OpenXR-SDK
android/app/src/main/cpp/code/OpenXR
android/app/src/main/cpp/code/VrApi android/app/src/main/cpp/code/VrApi
*.swp *.swp
*tags *tags

View file

@ -281,7 +281,8 @@ LOKISETUPDIR=misc/setup
NSISDIR=misc/nsis NSISDIR=misc/nsis
SDLHDIR=$(MOUNT_DIR)/SDL2 SDLHDIR=$(MOUNT_DIR)/SDL2
LIBSDIR=$(MOUNT_DIR)/libs LIBSDIR=$(MOUNT_DIR)/libs
VRAPIDIR=$(MOUNT_DIR)/VrApi OPENXRDIR=$(MOUNT_DIR)/OpenXR
OPENXRSDKDIR=$(MOUNT_DIR)/OpenXR-SDK
bin_path=$(shell which $(1) 2> /dev/null) bin_path=$(shell which $(1) 2> /dev/null)
@ -468,11 +469,11 @@ ifeq ($(PLATFORM),android)
RENDERER_LIBS += $(LIBSDIR)/android/arm64-v8a/libSDL2.so RENDERER_LIBS += $(LIBSDIR)/android/arm64-v8a/libSDL2.so
CLIENT_EXTRA_FILES += $(LIBSDIR)/android/arm64-v8a/libSDL2.so CLIENT_EXTRA_FILES += $(LIBSDIR)/android/arm64-v8a/libSDL2.so
# VrApi # OpenXR
BASE_CFLAGS += -I$(VRAPIDIR)/Include BASE_CFLAGS += -I$(OPENXRDIR)/Include -I$(OPENXRSDKDIR)/include
CLIENT_LIBS += $(VRAPIDIR)/Libs/Android/arm64-v8a/Release/libvrapi.so CLIENT_LIBS += $(OPENXRDIR)/Libs/Android/arm64-v8a/Release/libopenxr_loader.so
RENDERER_LIBS += $(VRAPIDIR)/Libs/Android/arm64-v8a/Release/libvrapi.so RENDERER_LIBS += $(OPENXRDIR)/Libs/Android/arm64-v8a/Release/libopenxr_loader.so
CLIENT_EXTRA_FILES += $(VRAPIDIR)/Libs/Android/arm64-v8a/Release/libvrapi.so CLIENT_EXTRA_FILES += $(OPENXRDIR)/Libs/Android/arm64-v8a/Release/libopenxr_loader.so
else # ifeq Android else # ifeq Android
############################################################################# #############################################################################
@ -1819,6 +1820,7 @@ Q3OBJ = \
$(B)/client/vr_base.o \ $(B)/client/vr_base.o \
$(B)/client/vr_input.o \ $(B)/client/vr_input.o \
$(B)/client/vr_renderer.o \ $(B)/client/vr_renderer.o \
$(B)/client/vr_types.o \
\ \
$(B)/client/con_log.o \ $(B)/client/con_log.o \
$(B)/client/sys_autoupdater.o \ $(B)/client/sys_autoupdater.o \

View file

@ -5,7 +5,9 @@
### Prerequisites ### Prerequisites
1. Install your copy of Quake III Arena from Steam. 1. Install your copy of Quake III Arena from Steam.
2. Android Studio with NDK version 21.1.6352462. 2. Android Studio with NDK version 21.1.6352462.
3. Copy the Oculus VR SDK from https://developer.oculus.com/downloads/package/oculus-mobile-sdk/ and extract the VrApi folder to ./android/app/src/main/cpp/code/VrApi/ 3. Download the Oculus OpenXR SDK from https://developer.oculus.com/downloads/package/oculus-openxr-mobile-sdk/
4. Extract the OpenXR folder to ./android/app/src/main/cpp/code/OpenXR/
5. Extract the 3rdParty/khronos/openxr/OpenXR-SDK folder to ./android/app/src/main/cpp/code/OpenXR-SDK/
### Building and running the build ### Building and running the build
The scripts assume that you installed everything in the default locations. In case you want to deviate from that, the paths are in ./android/run.(sh|bat) and in Makefile.local. The scripts assume that you installed everything in the default locations. In case you want to deviate from that, the paths are in ./android/run.(sh|bat) and in Makefile.local.

View file

@ -15,7 +15,7 @@ endif()
add_custom_target(copy_libs add_custom_target(copy_libs
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/../../../../../build/${BUILD_FOLDER}/libioquake3_opengl2.so ${CMAKE_SOURCE_DIR}/../jniLibs/arm64-v8a/libioquake3_opengl2.so COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/../../../../../build/${BUILD_FOLDER}/libioquake3_opengl2.so ${CMAKE_SOURCE_DIR}/../jniLibs/arm64-v8a/libioquake3_opengl2.so
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/../../../../../build/${BUILD_FOLDER}/libSDL2.so ${CMAKE_SOURCE_DIR}/../jniLibs/arm64-v8a/libSDL2.so COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/../../../../../build/${BUILD_FOLDER}/libSDL2.so ${CMAKE_SOURCE_DIR}/../jniLibs/arm64-v8a/libSDL2.so
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/../../../../../build/${BUILD_FOLDER}/libvrapi.so ${CMAKE_SOURCE_DIR}/../jniLibs/arm64-v8a/libvrapi.so COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/../../../../../build/${BUILD_FOLDER}/libopenxr_loader.so ${CMAKE_SOURCE_DIR}/../jniLibs/arm64-v8a/libopenxr_loader.so
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/../../../../../build/${BUILD_FOLDER}/baseq3/cgameaarch64.so ${CMAKE_SOURCE_DIR}/../jniLibs/arm64-v8a/libcgameaarch64_baseq3.so COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/../../../../../build/${BUILD_FOLDER}/baseq3/cgameaarch64.so ${CMAKE_SOURCE_DIR}/../jniLibs/arm64-v8a/libcgameaarch64_baseq3.so
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/../../../../../build/${BUILD_FOLDER}/baseq3/qagameaarch64.so ${CMAKE_SOURCE_DIR}/../jniLibs/arm64-v8a/libqagameaarch64_baseq3.so COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/../../../../../build/${BUILD_FOLDER}/baseq3/qagameaarch64.so ${CMAKE_SOURCE_DIR}/../jniLibs/arm64-v8a/libqagameaarch64_baseq3.so
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/../../../../../build/${BUILD_FOLDER}/baseq3/uiaarch64.so ${CMAKE_SOURCE_DIR}/../jniLibs/arm64-v8a/libuiaarch64_baseq3.so COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/../../../../../build/${BUILD_FOLDER}/baseq3/uiaarch64.so ${CMAKE_SOURCE_DIR}/../jniLibs/arm64-v8a/libuiaarch64_baseq3.so
@ -27,7 +27,8 @@ add_custom_target(copy_libs
add_dependencies(main copy_libs) add_dependencies(main copy_libs)
target_include_directories(main PRIVATE target_include_directories(main PRIVATE
${CMAKE_SOURCE_DIR}/code/VrApi/Include ${CMAKE_SOURCE_DIR}/code/OpenXR-SDK/include
${CMAKE_SOURCE_DIR}/code/OpenXR/Include
${CMAKE_SOURCE_DIR}/code/SDL2/include ${CMAKE_SOURCE_DIR}/code/SDL2/include
${CMAKE_SOURCE_DIR}/code) ${CMAKE_SOURCE_DIR}/code)
@ -40,5 +41,5 @@ target_link_libraries(main
android android
ioquake3_opengl2 ioquake3_opengl2
SDL2 SDL2
vrapi openxr_loader
${log-lib}) ${log-lib})

View file

@ -24,13 +24,6 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#include "tr_types.h" #include "tr_types.h"
#if __ANDROID__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wstrict-prototypes"
#include <VrApi.h>
#pragma clang diagnostic pop
#endif
#define REF_API_VERSION 8 #define REF_API_VERSION 8
// //
@ -92,8 +85,9 @@ typedef struct {
void (*EndFrame)( int *frontEndMsec, int *backEndMsec ); void (*EndFrame)( int *frontEndMsec, int *backEndMsec );
#if __ANDROID__ #if __ANDROID__
void (*SetVRHeadsetParms)( const ovrMatrix4f *projectionMatrix, const ovrMatrix4f *nonVRProjectionMatrix, void (*SetVRHeadsetParms)( const float projectionMatrix[4][4],
int renderBuffer ); const float nonVRProjectionMatrix[4][4],
int renderBuffer );
#endif #endif
int (*MarkFragments)( int numPoints, const vec3_t *points, const vec3_t projection, int (*MarkFragments)( int numPoints, const vec3_t *points, const vec3_t projection,

View file

@ -513,11 +513,10 @@ void R_Mat4Transpose( const float in[4][4], float* out ) {
} }
} }
void RE_SetVRHeadsetParms( const float projectionMatrix[4][4], const float nonVRProjectionMatrix[4][4],
void RE_SetVRHeadsetParms( const ovrMatrix4f *projectionMatrix, const ovrMatrix4f *nonVRProjectionMatrix,
int renderBuffer ) { int renderBuffer ) {
R_Mat4Transpose(projectionMatrix->M, tr.vrParms.projection); R_Mat4Transpose(projectionMatrix, tr.vrParms.projection);
R_Mat4Transpose(nonVRProjectionMatrix->M, tr.vrParms.monoVRProjection); R_Mat4Transpose(nonVRProjectionMatrix, tr.vrParms.monoVRProjection);
tr.vrParms.renderBuffer = renderBuffer; tr.vrParms.renderBuffer = renderBuffer;
tr.vrParms.valid = qtrue; tr.vrParms.valid = qtrue;
} }

View file

@ -2535,8 +2535,9 @@ void RE_StretchPic ( float x, float y, float w, float h,
void RE_BeginFrame( stereoFrame_t stereoFrame ); void RE_BeginFrame( stereoFrame_t stereoFrame );
void RE_EndFrame( int *frontEndMsec, int *backEndMsec ); void RE_EndFrame( int *frontEndMsec, int *backEndMsec );
#if __ANDROID__ #if __ANDROID__
void RE_SetVRHeadsetParms( const ovrMatrix4f *projectionMatrix, const ovrMatrix4f *nonVRProjectionMatrix, void RE_SetVRHeadsetParms( const float projectionMatrix[4][4],
int renderBuffer ); const float nonVRProjectionMatrix[4][4],
int renderBuffer );
#endif #endif
void RE_HUDBufferStart( qboolean clear ); void RE_HUDBufferStart( qboolean clear );
void RE_HUDBufferEnd( void ); void RE_HUDBufferEnd( void );

View file

@ -1,22 +1,24 @@
#include "vr_base.h" #include "vr_base.h"
#include "../VrApi/Include/VrApi.h"
#include "../VrApi/Include/VrApi_Helpers.h"
#include "../VrApi/Include/VrApi_Types.h"
#include "../qcommon/q_shared.h" #include "../qcommon/q_shared.h"
#include "../qcommon/qcommon.h" #include "../qcommon/qcommon.h"
#include "../client/client.h" #include "../client/client.h"
//#if __ANDROID__ //#if __ANDROID__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wstrict-prototypes"
#pragma clang diagnostic pop
#include <EGL/egl.h>
#include <assert.h> #include <assert.h>
#include <unistd.h>
static engine_t vr_engine; static engine_t vr_engine;
qboolean vr_initialized = qfalse;
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_KHR_COMPOSITION_LAYER_CYLINDER_EXTENSION_NAME,
XR_FB_DISPLAY_REFRESH_RATE_EXTENSION_NAME};
const uint32_t numRequiredExtensions =
sizeof(requiredExtensionNames) / sizeof(requiredExtensionNames[0]);
cvar_t *vr_worldscale = NULL; cvar_t *vr_worldscale = NULL;
cvar_t *vr_worldscaleScaler = NULL; cvar_t *vr_worldscaleScaler = NULL;
@ -46,18 +48,92 @@ cvar_t *vr_showConsoleMessages = NULL;
engine_t* VR_Init( ovrJava java ) engine_t* VR_Init( ovrJava java )
{ {
ovrInitParms initParams; if (vr_initialized)
ovrResult initResult; return &vr_engine;
memset(&vr_engine, 0, sizeof(vr_engine)); ovrApp_Clear(&vr_engine.appState);
initParams = vrapi_DefaultInitParms(&java); PFN_xrInitializeLoaderKHR xrInitializeLoaderKHR;
initResult = vrapi_Initialize(&initParams); xrGetInstanceProcAddr(
assert(initResult == VRAPI_INITIALIZE_SUCCESS); 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);
}
vr_engine.java = java; // Create the OpenXR instance.
XrApplicationInfo appInfo;
memset(&appInfo, 0, sizeof(appInfo));
strcpy(appInfo.applicationName, "Quake 3 Arena");
appInfo.applicationVersion = 0;
strcpy(appInfo.engineName, "Quake 3 Arena");
appInfo.engineVersion = 0;
appInfo.apiVersion = XR_CURRENT_API_VERSION;
return &vr_engine; XrInstanceCreateInfo instanceCreateInfo;
memset(&instanceCreateInfo, 0, sizeof(instanceCreateInfo));
instanceCreateInfo.type = XR_TYPE_INSTANCE_CREATE_INFO;
instanceCreateInfo.next = NULL;
instanceCreateInfo.createFlags = 0;
instanceCreateInfo.applicationInfo = appInfo;
instanceCreateInfo.enabledApiLayerCount = 0;
instanceCreateInfo.enabledApiLayerNames = NULL;
instanceCreateInfo.enabledExtensionCount = numRequiredExtensions;
instanceCreateInfo.enabledExtensionNames = requiredExtensionNames;
XrResult initResult;
OXR(initResult = xrCreateInstance(&instanceCreateInfo, &vr_engine.appState.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(vr_engine.appState.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;
XrSystemId systemId;
OXR(initResult = xrGetSystem(vr_engine.appState.Instance, &systemGetInfo, &systemId));
if (initResult != XR_SUCCESS) {
ALOGE("Failed to get system.");
exit(1);
}
// Get the graphics requirements.
PFN_xrGetOpenGLESGraphicsRequirementsKHR pfnGetOpenGLESGraphicsRequirementsKHR = NULL;
OXR(xrGetInstanceProcAddr(
vr_engine.appState.Instance,
"xrGetOpenGLESGraphicsRequirementsKHR",
(PFN_xrVoidFunction*)(&pfnGetOpenGLESGraphicsRequirementsKHR)));
XrGraphicsRequirementsOpenGLESKHR graphicsRequirements = {};
graphicsRequirements.type = XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_ES_KHR;
OXR(pfnGetOpenGLESGraphicsRequirementsKHR(vr_engine.appState.Instance, systemId, &graphicsRequirements));
vr_engine.appState.MainThreadTid = gettid();
vr_engine.appState.SystemId = systemId;
vr_engine.java = java;
vr_initialized = qtrue;
return &vr_engine;
} }
void VR_InitCvars( void ) void VR_InitCvars( void )
@ -184,53 +260,77 @@ void VR_InitCvars( void )
void VR_Destroy( engine_t* engine ) void VR_Destroy( engine_t* engine )
{ {
if (engine == &vr_engine) { if (engine == &vr_engine) {
vrapi_Shutdown(); xrDestroyInstance(engine->appState.Instance);
} ovrApp_Destroy(&engine->appState);
}
} }
void VR_EnterVR( engine_t* engine, ovrJava java ) { void VR_EnterVR( engine_t* engine, ovrJava java ) {
if (!engine->ovr) {
ovrModeParms modeParams = vrapi_DefaultModeParms(&java);
modeParams.Display = (size_t)eglGetCurrentDisplay();
modeParams.WindowSurface = (size_t)eglGetCurrentSurface(EGL_DRAW);
modeParams.ShareContext = (size_t)eglGetCurrentContext();
engine->ovr = vrapi_EnterVrMode(&modeParams); if (engine->appState.Session) {
engine->frameIndex = 0; Com_Printf("VR_EnterVR called with existing session");
return;
}
vrapi_SetTrackingSpace(engine->ovr, VRAPI_TRACKING_SPACE_LOCAL_FLOOR); // 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();
vrapi_SetClockLevels(engine->ovr, 4, 4); XrSessionCreateInfo sessionCreateInfo = {};
} memset(&sessionCreateInfo, 0, sizeof(sessionCreateInfo));
sessionCreateInfo.type = XR_TYPE_SESSION_CREATE_INFO;
sessionCreateInfo.next = &graphicsBindingAndroidGLES;
sessionCreateInfo.createFlags = 0;
sessionCreateInfo.systemId = engine->appState.SystemId;
XrResult initResult;
OXR(initResult = xrCreateSession(engine->appState.Instance, &sessionCreateInfo, &engine->appState.Session));
if (initResult != XR_SUCCESS) {
ALOGE("Failed to create XR session: %d.", initResult);
exit(1);
}
} }
void VR_LeaveVR( engine_t* engine ) { void VR_LeaveVR( engine_t* engine ) {
if (engine->ovr) { if (engine->appState.Session) {
vrapi_LeaveVrMode(engine->ovr); OXR(xrDestroySpace(engine->appState.HeadSpace));
engine->ovr = NULL; OXR(xrDestroySpace(engine->appState.LocalSpace));
} // StageSpace is optional.
if (engine->appState.StageSpace != XR_NULL_HANDLE) {
OXR(xrDestroySpace(engine->appState.StageSpace));
}
OXR(xrDestroySpace(engine->appState.FakeStageSpace));
engine->appState.CurrentSpace = XR_NULL_HANDLE;
OXR(xrDestroySession(engine->appState.Session));
OXR(xrDestroyInstance(engine->appState.Instance));
engine->appState.Session = NULL;
}
} }
engine_t* VR_GetEngine( void ) { engine_t* VR_GetEngine( void ) {
return &vr_engine; return &vr_engine;
} }
bool VR_isPauseable( void ) int VR_isPauseable( void )
{ {
return (bool)( ( clc.state == CA_ACTIVE) && !Cvar_VariableValue ("cl_paused") ); return ( clc.state == CA_ACTIVE) && !Cvar_VariableValue ("cl_paused");
} }
bool VR_useScreenLayer( void ) int VR_useScreenLayer( void )
{ {
//intermission is never full screen //intermission is never full screen
if ( cl.snap.ps.pm_type == PM_INTERMISSION ) if ( cl.snap.ps.pm_type == PM_INTERMISSION )
{ {
return qfalse; return 0;
} }
int keyCatcher = Key_GetCatcher( ); int keyCatcher = Key_GetCatcher( );
return (bool)( clc.state == CA_CINEMATIC || return ( clc.state == CA_CINEMATIC ||
( keyCatcher & (KEYCATCH_UI | KEYCATCH_CONSOLE) )); ( keyCatcher & (KEYCATCH_UI | KEYCATCH_CONSOLE) ));
} }
//#endif //#endif

View file

@ -12,8 +12,8 @@ void VR_EnterVR( engine_t* engine, ovrJava java );
void VR_LeaveVR( engine_t* engine ); void VR_LeaveVR( engine_t* engine );
engine_t* VR_GetEngine( void ); engine_t* VR_GetEngine( void );
bool VR_isPauseable( void ); int VR_useScreenLayer( void );
bool VR_useScreenLayer( void ); int VR_isPauseable( void );
float radians(float deg); float radians(float deg);

View file

@ -7,8 +7,6 @@
#include "../client/keycodes.h" #include "../client/keycodes.h"
#include "../client/client.h" #include "../client/client.h"
#include "vr_base.h" #include "vr_base.h"
#include "../VrApi/Include/VrApi_Input.h"
#include "../VrApi/Include/VrApi_Helpers.h"
#include "vr_clientinfo.h" #include "vr_clientinfo.h"
#include <unistd.h> #include <unistd.h>
@ -20,6 +18,39 @@
# include <SDL.h> # include <SDL.h>
#endif #endif
//OpenXR
XrPath leftHandPath;
XrPath rightHandPath;
XrAction aimPoseAction;
XrAction gripPoseAction;
XrAction indexLeftAction;
XrAction indexRightAction;
XrAction menuAction;
XrAction buttonAAction;
XrAction buttonBAction;
XrAction buttonXAction;
XrAction buttonYAction;
XrAction gripLeftAction;
XrAction gripRightAction;
XrAction moveOnLeftJoystickAction;
XrAction moveOnRightJoystickAction;
XrAction thumbstickLeftClickAction;
XrAction thumbstickRightClickAction;
XrAction vibrateLeftFeedback;
XrAction vibrateRightFeedback;
XrActionSet runningActionSet;
XrSpace leftControllerAimSpace = XR_NULL_HANDLE;
XrSpace rightControllerAimSpace = XR_NULL_HANDLE;
XrSpace leftControllerGripSpace = XR_NULL_HANDLE;
XrSpace rightControllerGripSpace = XR_NULL_HANDLE;
qboolean inputInitialized = qfalse;
qboolean useSimpleProfile = qfalse;
typedef struct {
XrSpaceLocation loc;
XrSpaceVelocity vel;
} LocVel;
enum { enum {
VR_TOUCH_AXIS_UP = 1 << 0, VR_TOUCH_AXIS_UP = 1 << 0,
VR_TOUCH_AXIS_UPRIGHT = 1 << 1, VR_TOUCH_AXIS_UPRIGHT = 1 << 1,
@ -92,7 +123,7 @@ void rotateAboutOrigin(float x, float y, float rotation, vec2_t out)
out[1] = cosf(DEG2RAD(-rotation)) * y - sinf(DEG2RAD(-rotation)) * x; out[1] = cosf(DEG2RAD(-rotation)) * y - sinf(DEG2RAD(-rotation)) * x;
} }
static ovrVector3f normalizeVec(ovrVector3f vec) { XrVector3f normalizeVec(XrVector3f vec) {
//NOTE: leave w-component untouched //NOTE: leave w-component untouched
//@@const float EPSILON = 0.000001f; //@@const float EPSILON = 0.000001f;
float xxyyzz = vec.x*vec.x + vec.y*vec.y + vec.z*vec.z; float xxyyzz = vec.x*vec.x + vec.y*vec.y + vec.z*vec.z;
@ -100,7 +131,7 @@ static ovrVector3f normalizeVec(ovrVector3f vec) {
//@@ return *this; // do nothing if it is zero vector //@@ return *this; // do nothing if it is zero vector
//float invLength = invSqrt(xxyyzz); //float invLength = invSqrt(xxyyzz);
ovrVector3f result; XrVector3f result;
float invLength = 1.0f / sqrtf(xxyyzz); float invLength = 1.0f / sqrtf(xxyyzz);
result.x = vec.x * invLength; result.x = vec.x * invLength;
result.y = vec.y * invLength; result.y = vec.y * invLength;
@ -123,7 +154,7 @@ void NormalizeAngles(vec3_t angles)
while (angles[2] < -180) angles[2] += 360; while (angles[2] < -180) angles[2] += 360;
} }
void GetAnglesFromVectors(const ovrVector3f forward, const ovrVector3f right, const ovrVector3f up, vec3_t angles) void GetAnglesFromVectors(const XrVector3f forward, const XrVector3f right, const XrVector3f up, vec3_t angles)
{ {
float sr, sp, sy, cr, cp, cy; float sr, sp, sy, cr, cp, cy;
@ -172,7 +203,7 @@ void GetAnglesFromVectors(const ovrVector3f forward, const ovrVector3f right, co
NormalizeAngles(angles); NormalizeAngles(angles);
} }
void QuatToYawPitchRoll(ovrQuatf q, vec3_t rotation, vec3_t out) { void QuatToYawPitchRoll(XrQuaternionf q, vec3_t rotation, vec3_t out) {
ovrMatrix4f mat = ovrMatrix4f_CreateFromQuaternion( &q ); ovrMatrix4f mat = ovrMatrix4f_CreateFromQuaternion( &q );
@ -182,21 +213,21 @@ void QuatToYawPitchRoll(ovrQuatf q, vec3_t rotation, vec3_t out) {
mat = ovrMatrix4f_Multiply(&mat, &rot); mat = ovrMatrix4f_Multiply(&mat, &rot);
} }
ovrVector4f v1 = {0, 0, -1, 0}; XrVector4f v1 = {0, 0, -1, 0};
ovrVector4f v2 = {1, 0, 0, 0}; XrVector4f v2 = {1, 0, 0, 0};
ovrVector4f v3 = {0, 1, 0, 0}; XrVector4f v3 = {0, 1, 0, 0};
ovrVector4f forwardInVRSpace = ovrVector4f_MultiplyMatrix4f(&mat, &v1); XrVector4f forwardInVRSpace = XrVector4f_MultiplyMatrix4f(&mat, &v1);
ovrVector4f rightInVRSpace = ovrVector4f_MultiplyMatrix4f(&mat, &v2); XrVector4f rightInVRSpace = XrVector4f_MultiplyMatrix4f(&mat, &v2);
ovrVector4f upInVRSpace = ovrVector4f_MultiplyMatrix4f(&mat, &v3); XrVector4f upInVRSpace = XrVector4f_MultiplyMatrix4f(&mat, &v3);
ovrVector3f forward = {-forwardInVRSpace.z, -forwardInVRSpace.x, forwardInVRSpace.y}; XrVector3f forward = {-forwardInVRSpace.z, -forwardInVRSpace.x, forwardInVRSpace.y};
ovrVector3f right = {-rightInVRSpace.z, -rightInVRSpace.x, rightInVRSpace.y}; XrVector3f right = {-rightInVRSpace.z, -rightInVRSpace.x, rightInVRSpace.y};
ovrVector3f up = {-upInVRSpace.z, -upInVRSpace.x, upInVRSpace.y}; XrVector3f up = {-upInVRSpace.z, -upInVRSpace.x, upInVRSpace.y};
ovrVector3f forwardNormal = normalizeVec(forward); XrVector3f forwardNormal = normalizeVec(forward);
ovrVector3f rightNormal = normalizeVec(right); XrVector3f rightNormal = normalizeVec(right);
ovrVector3f upNormal = normalizeVec(up); XrVector3f upNormal = normalizeVec(up);
GetAnglesFromVectors(forwardNormal, rightNormal, upNormal, out); GetAnglesFromVectors(forwardNormal, rightNormal, upNormal, out);
} }
@ -204,7 +235,6 @@ void QuatToYawPitchRoll(ovrQuatf q, vec3_t rotation, vec3_t out) {
//0 = left, 1 = right //0 = left, 1 = right
float vibration_channel_duration[2] = {0.0f, 0.0f}; float vibration_channel_duration[2] = {0.0f, 0.0f};
float vibration_channel_intensity[2] = {0.0f, 0.0f}; float vibration_channel_intensity[2] = {0.0f, 0.0f};
ovrDeviceID controllerIDs[2];
void VR_Vibrate( int duration, int chan, float intensity ) void VR_Vibrate( int duration, int chan, float intensity )
{ {
@ -235,8 +265,19 @@ static void VR_processHaptics() {
for (int i = 0; i < 2; ++i) { for (int i = 0; i < 2; ++i) {
if (vibration_channel_duration[i] > 0.0f || if (vibration_channel_duration[i] > 0.0f ||
vibration_channel_duration[i] == -1.0f) { vibration_channel_duration[i] == -1.0f) {
vrapi_SetHapticVibrationSimple(VR_GetEngine()->ovr, controllerIDs[i],
vibration_channel_intensity[i]); // fire haptics using output action
XrHapticVibration vibration = {};
vibration.type = XR_TYPE_HAPTIC_VIBRATION;
vibration.next = NULL;
vibration.amplitude = vibration_channel_intensity[i];
vibration.duration = ToXrTime(vibration_channel_duration[i]);
vibration.frequency = 3000;
XrHapticActionInfo hapticActionInfo = {};
hapticActionInfo.type = XR_TYPE_HAPTIC_ACTION_INFO;
hapticActionInfo.next = NULL;
hapticActionInfo.action = i == 0 ? vibrateLeftFeedback : vibrateRightFeedback;
OXR(xrApplyHapticFeedback(VR_GetEngine()->appState.Session, &hapticActionInfo, (const XrHapticBaseHeader*)&vibration));
if (vibration_channel_duration[i] != -1.0f) { if (vibration_channel_duration[i] != -1.0f) {
vibration_channel_duration[i] -= frametime; vibration_channel_duration[i] -= frametime;
@ -247,7 +288,12 @@ static void VR_processHaptics() {
} }
} }
} else { } else {
vrapi_SetHapticVibrationSimple(VR_GetEngine()->ovr, controllerIDs[i], 0.0f); // Stop haptics
XrHapticActionInfo hapticActionInfo = {};
hapticActionInfo.type = XR_TYPE_HAPTIC_ACTION_INFO;
hapticActionInfo.next = NULL;
hapticActionInfo.action = i == 0 ? vibrateLeftFeedback : vibrateRightFeedback;
OXR(xrStopHapticFeedback(VR_GetEngine()->appState.Session, &hapticActionInfo));
} }
} }
} }
@ -474,50 +520,370 @@ void VR_HapticEvent(const char* event, int position, int flags, int intensity, f
} }
} }
XrSpace CreateActionSpace(XrAction poseAction, XrPath subactionPath) {
XrActionSpaceCreateInfo asci = {};
asci.type = XR_TYPE_ACTION_SPACE_CREATE_INFO;
asci.action = poseAction;
asci.poseInActionSpace.orientation.w = 1.0f;
asci.subactionPath = subactionPath;
XrSpace actionSpace = XR_NULL_HANDLE;
OXR(xrCreateActionSpace(VR_GetEngine()->appState.Session, &asci, &actionSpace));
return actionSpace;
}
XrActionSuggestedBinding ActionSuggestedBinding(XrAction action, const char* bindingString) {
XrActionSuggestedBinding asb;
asb.action = action;
XrPath bindingPath;
OXR(xrStringToPath(VR_GetEngine()->appState.Instance, bindingString, &bindingPath));
asb.binding = bindingPath;
return asb;
}
XrActionSet CreateActionSet(int priority, const char* name, const char* localizedName) {
XrActionSetCreateInfo asci = {};
asci.type = XR_TYPE_ACTION_SET_CREATE_INFO;
asci.next = NULL;
asci.priority = priority;
strcpy(asci.actionSetName, name);
strcpy(asci.localizedActionSetName, localizedName);
XrActionSet actionSet = XR_NULL_HANDLE;
OXR(xrCreateActionSet(VR_GetEngine()->appState.Instance, &asci, &actionSet));
return actionSet;
}
XrAction CreateAction(
XrActionSet actionSet,
XrActionType type,
const char* actionName,
const char* localizedName,
int countSubactionPaths,
XrPath* subactionPaths) {
ALOGV("CreateAction %s, %" PRIi32, actionName, countSubactionPaths);
XrActionCreateInfo aci = {};
aci.type = XR_TYPE_ACTION_CREATE_INFO;
aci.next = NULL;
aci.actionType = type;
if (countSubactionPaths > 0) {
aci.countSubactionPaths = countSubactionPaths;
aci.subactionPaths = subactionPaths;
}
strcpy(aci.actionName, actionName);
strcpy(aci.localizedActionName, localizedName ? localizedName : actionName);
XrAction action = XR_NULL_HANDLE;
OXR(xrCreateAction(actionSet, &aci, &action));
return action;
}
qboolean ActionPoseIsActive(XrAction action, XrPath subactionPath) {
XrActionStateGetInfo getInfo = {};
getInfo.type = XR_TYPE_ACTION_STATE_GET_INFO;
getInfo.action = action;
getInfo.subactionPath = subactionPath;
XrActionStatePose state = {};
state.type = XR_TYPE_ACTION_STATE_POSE;
OXR(xrGetActionStatePose(VR_GetEngine()->appState.Session, &getInfo, &state));
return state.isActive != XR_FALSE;
}
LocVel GetSpaceLocVel(XrSpace space, XrTime time) {
LocVel lv = {{}};
lv.loc.type = XR_TYPE_SPACE_LOCATION;
lv.loc.next = &lv.vel;
lv.vel.type = XR_TYPE_SPACE_VELOCITY;
OXR(xrLocateSpace(space, VR_GetEngine()->appState.CurrentSpace, time, &lv.loc));
lv.loc.next = NULL; // pointer no longer valid or necessary
return lv;
}
XrActionStateFloat GetActionStateFloat(XrAction action) {
XrActionStateGetInfo getInfo = {};
getInfo.type = XR_TYPE_ACTION_STATE_GET_INFO;
getInfo.action = action;
XrActionStateFloat state = {};
state.type = XR_TYPE_ACTION_STATE_FLOAT;
OXR(xrGetActionStateFloat(VR_GetEngine()->appState.Session, &getInfo, &state));
return state;
}
XrActionStateBoolean GetActionStateBoolean(XrAction action) {
XrActionStateGetInfo getInfo = {};
getInfo.type = XR_TYPE_ACTION_STATE_GET_INFO;
getInfo.action = action;
XrActionStateBoolean state = {};
state.type = XR_TYPE_ACTION_STATE_BOOLEAN;
OXR(xrGetActionStateBoolean(VR_GetEngine()->appState.Session, &getInfo, &state));
return state;
}
XrActionStateVector2f GetActionStateVector2(XrAction action) {
XrActionStateGetInfo getInfo = {};
getInfo.type = XR_TYPE_ACTION_STATE_GET_INFO;
getInfo.action = action;
XrActionStateVector2f state = {};
state.type = XR_TYPE_ACTION_STATE_VECTOR2F;
OXR(xrGetActionStateVector2f(VR_GetEngine()->appState.Session, &getInfo, &state));
return state;
}
void IN_VRInit( void ) void IN_VRInit( void )
{ {
if (inputInitialized)
return;
memset(&vr, 0, sizeof(vr)); memset(&vr, 0, sizeof(vr));
engine_t *engine = VR_GetEngine(); engine_t *engine = VR_GetEngine();
callbackClass = (*engine->java.Env)->GetObjectClass(engine->java.Env, engine->java.ActivityObject); callbackClass = (*engine->java.Env)->GetObjectClass(engine->java.Env, engine->java.ActivityObject);
android_haptic_event = (*engine->java.Env)->GetMethodID(engine->java.Env, callbackClass, "haptic_event","(Ljava/lang/String;IIIFF)V"); android_haptic_event = (*engine->java.Env)->GetMethodID(engine->java.Env, callbackClass, "haptic_event","(Ljava/lang/String;IIIFF)V");
// Actions
runningActionSet = CreateActionSet(1, "running_action_set", "Action Set used on main loop");
indexLeftAction = CreateAction(runningActionSet, XR_ACTION_TYPE_BOOLEAN_INPUT, "index_left", "Index left", 0, NULL);
indexRightAction = CreateAction(runningActionSet, XR_ACTION_TYPE_BOOLEAN_INPUT, "index_right", "Index right", 0, NULL);
menuAction = CreateAction(runningActionSet, XR_ACTION_TYPE_BOOLEAN_INPUT, "menu_action", "Menu", 0, NULL);
buttonAAction = CreateAction(runningActionSet, XR_ACTION_TYPE_BOOLEAN_INPUT, "button_a", "Button A", 0, NULL);
buttonBAction = CreateAction(runningActionSet, XR_ACTION_TYPE_BOOLEAN_INPUT, "button_b", "Button B", 0, NULL);
buttonXAction = CreateAction(runningActionSet, XR_ACTION_TYPE_BOOLEAN_INPUT, "button_x", "Button X", 0, NULL);
buttonYAction = CreateAction(runningActionSet, XR_ACTION_TYPE_BOOLEAN_INPUT, "button_y", "Button Y", 0, NULL);
gripLeftAction = CreateAction(runningActionSet, XR_ACTION_TYPE_FLOAT_INPUT, "grip_left", "Grip left", 0, NULL);
gripRightAction = CreateAction(runningActionSet, XR_ACTION_TYPE_FLOAT_INPUT, "grip_right", "Grip right", 0, NULL);
moveOnLeftJoystickAction = CreateAction(runningActionSet, XR_ACTION_TYPE_VECTOR2F_INPUT, "move_on_left_joy", "Move on left Joy", 0, NULL);
moveOnRightJoystickAction = CreateAction(runningActionSet, XR_ACTION_TYPE_VECTOR2F_INPUT, "move_on_right_joy", "Move on right Joy", 0, NULL);
thumbstickLeftClickAction = CreateAction(runningActionSet, XR_ACTION_TYPE_BOOLEAN_INPUT, "thumbstick_left", "Thumbstick left", 0, NULL);
thumbstickRightClickAction = CreateAction(runningActionSet, XR_ACTION_TYPE_BOOLEAN_INPUT, "thumbstick_right", "Thumbstick right", 0, NULL);
vibrateLeftFeedback = CreateAction(runningActionSet, XR_ACTION_TYPE_VIBRATION_OUTPUT, "vibrate_left_feedback", "Vibrate Left Controller Feedback", 0, NULL);
vibrateRightFeedback = CreateAction(runningActionSet, XR_ACTION_TYPE_VIBRATION_OUTPUT, "vibrate_right_feedback", "Vibrate Right Controller Feedback", 0, NULL);
OXR(xrStringToPath(engine->appState.Instance, "/user/hand/left", &leftHandPath));
OXR(xrStringToPath(engine->appState.Instance, "/user/hand/right", &rightHandPath));
XrPath handSubactionPaths[2] = {leftHandPath, rightHandPath};
aimPoseAction = CreateAction(runningActionSet, XR_ACTION_TYPE_POSE_INPUT, "aim_pose", NULL, 2, &handSubactionPaths[0]);
gripPoseAction = CreateAction(runningActionSet, XR_ACTION_TYPE_POSE_INPUT, "grip_pose", NULL, 2, &handSubactionPaths[0]);
XrPath interactionProfilePath = XR_NULL_PATH;
XrPath interactionProfilePathTouch = XR_NULL_PATH;
XrPath interactionProfilePathKHRSimple = XR_NULL_PATH;
OXR(xrStringToPath(engine->appState.Instance, "/interaction_profiles/oculus/touch_controller", &interactionProfilePathTouch));
OXR(xrStringToPath(engine->appState.Instance, "/interaction_profiles/khr/simple_controller", &interactionProfilePathKHRSimple));
// Toggle this to force simple as a first choice, otherwise use it as a last resort
if (useSimpleProfile) {
ALOGV("xrSuggestInteractionProfileBindings found bindings for Khronos SIMPLE controller");
interactionProfilePath = interactionProfilePathKHRSimple;
} else {
// Query Set
XrActionSet queryActionSet = CreateActionSet(1, "query_action_set", "Action Set used to query device caps");
XrAction dummyAction = CreateAction(queryActionSet, XR_ACTION_TYPE_BOOLEAN_INPUT, "dummy_action", "Dummy Action", 0, NULL);
// Map bindings
XrActionSuggestedBinding bindings[1];
int currBinding = 0;
bindings[currBinding++] = ActionSuggestedBinding(dummyAction, "/user/hand/right/input/system/click");
XrInteractionProfileSuggestedBinding suggestedBindings = {};
suggestedBindings.type = XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING;
suggestedBindings.next = NULL;
suggestedBindings.suggestedBindings = bindings;
suggestedBindings.countSuggestedBindings = currBinding;
// Try all
suggestedBindings.interactionProfile = interactionProfilePathTouch;
XrResult suggestTouchResult = xrSuggestInteractionProfileBindings(engine->appState.Instance, &suggestedBindings);
OXR(suggestTouchResult);
if (XR_SUCCESS == suggestTouchResult) {
ALOGV("xrSuggestInteractionProfileBindings found bindings for QUEST controller");
interactionProfilePath = interactionProfilePathTouch;
}
if (interactionProfilePath == XR_NULL_PATH) {
// Simple as a fallback
bindings[0] = ActionSuggestedBinding(dummyAction, "/user/hand/right/input/select/click");
suggestedBindings.interactionProfile = interactionProfilePathKHRSimple;
XrResult suggestKHRSimpleResult = xrSuggestInteractionProfileBindings(engine->appState.Instance, &suggestedBindings);
OXR(suggestKHRSimpleResult);
if (XR_SUCCESS == suggestKHRSimpleResult) {
ALOGV("xrSuggestInteractionProfileBindings found bindings for Khronos SIMPLE controller");
interactionProfilePath = interactionProfilePathKHRSimple;
} else {
ALOGE("xrSuggestInteractionProfileBindings did NOT find any bindings.");
assert(qfalse);
}
}
}
// Action creation
{
// Map bindings
XrActionSuggestedBinding bindings[32]; // large enough for all profiles
int currBinding = 0;
{
if (interactionProfilePath == interactionProfilePathTouch) {
bindings[currBinding++] = ActionSuggestedBinding(indexLeftAction, "/user/hand/left/input/trigger");
bindings[currBinding++] = ActionSuggestedBinding(indexRightAction, "/user/hand/right/input/trigger");
bindings[currBinding++] = ActionSuggestedBinding(menuAction, "/user/hand/left/input/menu/click");
bindings[currBinding++] = ActionSuggestedBinding(buttonXAction, "/user/hand/left/input/x/click");
bindings[currBinding++] = ActionSuggestedBinding(buttonYAction, "/user/hand/left/input/y/click");
bindings[currBinding++] = ActionSuggestedBinding(buttonAAction, "/user/hand/right/input/a/click");
bindings[currBinding++] = ActionSuggestedBinding(buttonBAction, "/user/hand/right/input/b/click");
bindings[currBinding++] = ActionSuggestedBinding(gripLeftAction, "/user/hand/left/input/squeeze/value");
bindings[currBinding++] = ActionSuggestedBinding(gripRightAction, "/user/hand/right/input/squeeze/value");
bindings[currBinding++] = ActionSuggestedBinding(moveOnLeftJoystickAction, "/user/hand/left/input/thumbstick");
bindings[currBinding++] = ActionSuggestedBinding(moveOnRightJoystickAction, "/user/hand/right/input/thumbstick");
bindings[currBinding++] = ActionSuggestedBinding(thumbstickLeftClickAction, "/user/hand/left/input/thumbstick/click");
bindings[currBinding++] = ActionSuggestedBinding(thumbstickRightClickAction, "/user/hand/right/input/thumbstick/click");
bindings[currBinding++] = ActionSuggestedBinding(vibrateLeftFeedback, "/user/hand/left/output/haptic");
bindings[currBinding++] = ActionSuggestedBinding(vibrateRightFeedback, "/user/hand/right/output/haptic");
bindings[currBinding++] = ActionSuggestedBinding(aimPoseAction, "/user/hand/left/input/aim/pose");
bindings[currBinding++] = ActionSuggestedBinding(aimPoseAction, "/user/hand/right/input/aim/pose");
bindings[currBinding++] = ActionSuggestedBinding(gripPoseAction, "/user/hand/left/input/grip/pose");
bindings[currBinding++] = ActionSuggestedBinding(gripPoseAction, "/user/hand/right/input/grip/pose");
}
if (interactionProfilePath == interactionProfilePathKHRSimple) {
bindings[currBinding++] = ActionSuggestedBinding(indexLeftAction, "/user/hand/left/input/select/click");
bindings[currBinding++] = ActionSuggestedBinding(indexRightAction, "/user/hand/right/input/select/click");
bindings[currBinding++] = ActionSuggestedBinding(buttonAAction, "/user/hand/left/input/menu/click");
bindings[currBinding++] = ActionSuggestedBinding(buttonXAction, "/user/hand/right/input/menu/click");
bindings[currBinding++] = ActionSuggestedBinding(vibrateLeftFeedback, "/user/hand/left/output/haptic");
bindings[currBinding++] = ActionSuggestedBinding(vibrateRightFeedback, "/user/hand/right/output/haptic");
bindings[currBinding++] = ActionSuggestedBinding(aimPoseAction, "/user/hand/left/input/aim/pose");
bindings[currBinding++] = ActionSuggestedBinding(aimPoseAction, "/user/hand/right/input/aim/pose");
bindings[currBinding++] = ActionSuggestedBinding(gripPoseAction, "/user/hand/left/input/grip/pose");
bindings[currBinding++] = ActionSuggestedBinding(gripPoseAction, "/user/hand/right/input/grip/pose");
}
}
XrInteractionProfileSuggestedBinding suggestedBindings = {};
suggestedBindings.type = XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING;
suggestedBindings.next = NULL;
suggestedBindings.interactionProfile = interactionProfilePath;
suggestedBindings.suggestedBindings = bindings;
suggestedBindings.countSuggestedBindings = currBinding;
OXR(xrSuggestInteractionProfileBindings(engine->appState.Instance, &suggestedBindings));
// Enumerate actions
XrPath actionPathsBuffer[32];
char stringBuffer[256];
XrAction actionsToEnumerate[] = {
indexLeftAction,
indexRightAction,
menuAction,
buttonAAction,
buttonBAction,
buttonXAction,
buttonYAction,
gripLeftAction,
gripRightAction,
moveOnLeftJoystickAction,
moveOnRightJoystickAction,
thumbstickLeftClickAction,
thumbstickRightClickAction,
vibrateLeftFeedback,
vibrateRightFeedback,
aimPoseAction,
gripPoseAction,
};
for (size_t i = 0; i < sizeof(actionsToEnumerate) / sizeof(actionsToEnumerate[0]); ++i) {
XrBoundSourcesForActionEnumerateInfo enumerateInfo = {};
enumerateInfo.type = XR_TYPE_BOUND_SOURCES_FOR_ACTION_ENUMERATE_INFO;
enumerateInfo.next = NULL;
enumerateInfo.action = actionsToEnumerate[i];
// Get Count
uint32_t countOutput = 0;
OXR(xrEnumerateBoundSourcesForAction(
engine->appState.Session, &enumerateInfo, 0 /* request size */, &countOutput, NULL));
ALOGV(
"xrEnumerateBoundSourcesForAction action=%lld count=%u",
(long long)enumerateInfo.action,
countOutput);
if (countOutput < 32) {
OXR(xrEnumerateBoundSourcesForAction(
engine->appState.Session, &enumerateInfo, 32, &countOutput, actionPathsBuffer));
for (uint32_t a = 0; a < countOutput; ++a) {
XrInputSourceLocalizedNameGetInfo nameGetInfo = {};
nameGetInfo.type = XR_TYPE_INPUT_SOURCE_LOCALIZED_NAME_GET_INFO;
nameGetInfo.next = NULL;
nameGetInfo.sourcePath = actionPathsBuffer[a];
nameGetInfo.whichComponents = XR_INPUT_SOURCE_LOCALIZED_NAME_USER_PATH_BIT |
XR_INPUT_SOURCE_LOCALIZED_NAME_INTERACTION_PROFILE_BIT |
XR_INPUT_SOURCE_LOCALIZED_NAME_COMPONENT_BIT;
uint32_t stringCount = 0u;
OXR(xrGetInputSourceLocalizedName(
engine->appState.Session, &nameGetInfo, 0, &stringCount, NULL));
if (stringCount < 256) {
OXR(xrGetInputSourceLocalizedName(
engine->appState.Session, &nameGetInfo, 256, &stringCount, stringBuffer));
char pathStr[256];
uint32_t strLen = 0;
OXR(xrPathToString(
engine->appState.Instance,
actionPathsBuffer[a],
(uint32_t)sizeof(pathStr),
&strLen,
pathStr));
ALOGV(
" -> path = %lld `%s` -> `%s`",
(long long)actionPathsBuffer[a],
pathStr,
stringBuffer);
}
}
}
}
}
inputInitialized = qtrue;
} }
static void IN_VRController( qboolean isRightController, ovrTracking remoteTracking ) static void IN_VRController( qboolean isRightController, XrPosef pose )
{ {
//Set gun angles - We need to calculate all those we might need (including adjustments) for the client to then take its pick
vec3_t rotation = {0};
if (isRightController == (vr_righthanded->integer != 0)) if (isRightController == (vr_righthanded->integer != 0))
{ {
//Set gun angles - We need to calculate all those we might need (including adjustments) for the client to then take its pick //Set gun angles - We need to calculate all those we might need (including adjustments) for the client to then take its pick
vec3_t rotation = {0};
rotation[PITCH] = vr_weaponPitch->value;
QuatToYawPitchRoll(remoteTracking.HeadPose.Pose.Orientation, rotation, vr.weaponangles);
VectorSubtract(vr.weaponangles_last, vr.weaponangles, vr.weaponangles_delta);
VectorCopy(vr.weaponangles, vr.weaponangles_last);
///Weapon location relative to view
vr.weaponposition[0] = remoteTracking.HeadPose.Pose.Position.x;
vr.weaponposition[1] = remoteTracking.HeadPose.Pose.Position.y + vr_heightAdjust->value;
vr.weaponposition[2] = remoteTracking.HeadPose.Pose.Position.z;
VectorCopy(vr.weaponoffset_last[1], vr.weaponoffset_last[0]);
VectorCopy(vr.weaponoffset, vr.weaponoffset_last[1]);
VectorSubtract(vr.weaponposition, vr.hmdposition, vr.weaponoffset);
} else {
vec3_t rotation = {0};
QuatToYawPitchRoll(remoteTracking.HeadPose.Pose.Orientation, rotation, vr.offhandangles2); // used for off-hand direction mode
rotation[PITCH] = vr_weaponPitch->value; rotation[PITCH] = vr_weaponPitch->value;
QuatToYawPitchRoll(remoteTracking.HeadPose.Pose.Orientation, rotation, vr.offhandangles); QuatToYawPitchRoll(pose.orientation, rotation, vr.weaponangles);
VectorSubtract(vr.weaponangles_last, vr.weaponangles, vr.weaponangles_delta);
VectorCopy(vr.weaponangles, vr.weaponangles_last);
///Weapon location relative to view
vr.weaponposition[0] = pose.position.x;
vr.weaponposition[1] = pose.position.y + vr_heightAdjust->value;
vr.weaponposition[2] = pose.position.z;
VectorCopy(vr.weaponoffset_last[1], vr.weaponoffset_last[0]);
VectorCopy(vr.weaponoffset, vr.weaponoffset_last[1]);
VectorSubtract(vr.weaponposition, vr.hmdposition, vr.weaponoffset);
} else {
QuatToYawPitchRoll(pose.orientation, rotation, vr.offhandangles2); // used for off-hand direction mode
rotation[PITCH] = vr_weaponPitch->value;
QuatToYawPitchRoll(pose.orientation, rotation, vr.offhandangles);
///location relative to view ///location relative to view
vr.offhandposition[0] = remoteTracking.HeadPose.Pose.Position.x; vr.weaponposition[0] = pose.position.x;
vr.offhandposition[1] = remoteTracking.HeadPose.Pose.Position.y + vr_heightAdjust->value; vr.weaponposition[1] = pose.position.y + vr_heightAdjust->value;
vr.offhandposition[2] = remoteTracking.HeadPose.Pose.Position.z; vr.weaponposition[2] = pose.position.z;
VectorCopy(vr.offhandoffset_last[1], vr.offhandoffset_last[0]); VectorCopy(vr.offhandoffset_last[1], vr.offhandoffset_last[0]);
VectorCopy(vr.offhandoffset, vr.offhandoffset_last[1]); VectorCopy(vr.offhandoffset, vr.offhandoffset_last[1]);
VectorSubtract(vr.offhandposition, vr.hmdposition, vr.offhandoffset); VectorSubtract(vr.offhandposition, vr.hmdposition, vr.offhandoffset);
} }
if (vr.virtual_screen || cl.snap.ps.pm_type == PM_INTERMISSION) if (vr.virtual_screen || cl.snap.ps.pm_type == PM_INTERMISSION)
{ {
@ -737,7 +1103,8 @@ static void IN_VRJoystick( qboolean isRightController, float joystickX, float jo
else else
{ {
//Positional movement speed correction for when we are not hitting target framerate //Positional movement speed correction for when we are not hitting target framerate
int refresh = vrapi_GetSystemPropertyInt(&(VR_GetEngine()->java), VRAPI_SYS_PROP_DISPLAY_REFRESH_RATE); float refresh;
VR_GetEngine()->appState.pfnGetDisplayRefreshRate(VR_GetEngine()->appState.Session, &refresh);
float multiplier = (float)((1000.0 / refresh) / (in_vrEventTime - lastframetime)); float multiplier = (float)((1000.0 / refresh) / (in_vrEventTime - lastframetime));
float factor = (refresh / 72.0F) * 10.0f; // adjust positional factor based on refresh rate float factor = (refresh / 72.0F) * 10.0f; // adjust positional factor based on refresh rate
@ -935,7 +1302,6 @@ static void IN_VRButtons( qboolean isRightController, uint32_t buttons )
} else { } else {
IN_HandleInactiveInput(&controller->buttons, ovrButton_Y, "Y", 0, qfalse); IN_HandleInactiveInput(&controller->buttons, ovrButton_Y, "Y", 0, qfalse);
} }
} }
void IN_VRInputFrame( void ) void IN_VRInputFrame( void )
@ -945,27 +1311,17 @@ void IN_VRInputFrame( void )
memset(&rightController, 0, sizeof(rightController)); memset(&rightController, 0, sizeof(rightController));
controllerInit = qtrue; controllerInit = qtrue;
} }
engine_t* engine = VR_GetEngine();
ovrMobile* ovr = VR_GetEngine()->ovr;
if (!ovr) {
return;
}
ovrResult result;
if (vr_extralatencymode != NULL && if (vr_extralatencymode != NULL &&
vr_extralatencymode->integer) { vr_extralatencymode->integer) {
result = vrapi_SetExtraLatencyMode(VR_GetEngine()->ovr, VRAPI_EXTRA_LATENCY_MODE_ON); //TODO:vrapi_SetExtraLatencyMode(VR_GetEngine()->ovr, VRAPI_EXTRA_LATENCY_MODE_ON);
assert(result == VRAPI_INITIALIZE_SUCCESS);
} }
if (vr_refreshrate != NULL && vr_refreshrate->integer) if (vr_refreshrate != NULL && vr_refreshrate->integer) {
{ OXR(engine->appState.pfnRequestDisplayRefreshRate(engine->appState.Session, (float)vr_refreshrate->integer));
vrapi_SetDisplayRefreshRate(VR_GetEngine()->ovr, (float)vr_refreshrate->integer);
} }
result = vrapi_SetClockLevels(VR_GetEngine()->ovr, 4, 4);
assert(result == VRAPI_INITIALIZE_SUCCESS);
vr.virtual_screen = VR_useScreenLayer(); vr.virtual_screen = VR_useScreenLayer();
VR_processHaptics(); VR_processHaptics();
@ -976,10 +1332,14 @@ void IN_VRInputFrame( void )
{ {
// We extract Yaw, Pitch, Roll instead of directly using the orientation // We extract Yaw, Pitch, Roll instead of directly using the orientation
// to allow "additional" yaw manipulation with mouse/controller. // to allow "additional" yaw manipulation with mouse/controller.
const ovrQuatf quatHmd = VR_GetEngine()->tracking.HeadPose.Pose.Orientation; XrSpaceLocation loc = {};
const ovrVector3f positionHmd = VR_GetEngine()->tracking.HeadPose.Pose.Position; loc.type = XR_TYPE_SPACE_LOCATION;
OXR(xrLocateSpace(VR_GetEngine()->appState.HeadSpace, VR_GetEngine()->appState.CurrentSpace, VR_GetEngine()->predictedDisplayTime, &loc));
XrPosef xfStageFromHead = loc.pose;
const XrQuaternionf quatHmd = xfStageFromHead.orientation;
const XrVector3f positionHmd = xfStageFromHead.position;
vec3_t rotation = {0, 0, 0}; vec3_t rotation = {0, 0, 0};
QuatToYawPitchRoll(quatHmd, rotation, vr.hmdorientation); QuatToYawPitchRoll(quatHmd, rotation, vr.hmdorientation);
VectorSet(vr.hmdposition, positionHmd.x, positionHmd.y + vr_heightAdjust->value, positionHmd.z); VectorSet(vr.hmdposition, positionHmd.x, positionHmd.y + vr_heightAdjust->value, positionHmd.z);
//Position //Position
@ -1000,60 +1360,105 @@ void IN_VRInputFrame( void )
vr.clientview_yaw_last = clientview_yaw; vr.clientview_yaw_last = clientview_yaw;
} }
ovrInputCapabilityHeader capsHeader; if (leftControllerAimSpace == XR_NULL_HANDLE) {
uint32_t index = 0; leftControllerAimSpace = CreateActionSpace(aimPoseAction, leftHandPath);
for (;;) { }
ovrResult enumResult = vrapi_EnumerateInputDevices(ovr, index, &capsHeader); if (rightControllerAimSpace == XR_NULL_HANDLE) {
if (enumResult < 0) { rightControllerAimSpace = CreateActionSpace(aimPoseAction, rightHandPath);
break; }
} if (leftControllerGripSpace == XR_NULL_HANDLE) {
++index; leftControllerGripSpace = CreateActionSpace(gripPoseAction, leftHandPath);
}
if (rightControllerGripSpace == XR_NULL_HANDLE) {
rightControllerGripSpace = CreateActionSpace(gripPoseAction, rightHandPath);
}
if (capsHeader.Type != ovrControllerType_TrackedRemote) { // update input information
continue; XrAction controller[] = {aimPoseAction, gripPoseAction, aimPoseAction, gripPoseAction};
} XrPath subactionPath[] = {leftHandPath, leftHandPath, rightHandPath, rightHandPath};
XrSpace controllerSpace[] = {
leftControllerAimSpace,
leftControllerGripSpace,
rightControllerAimSpace,
rightControllerGripSpace,
};
for (int i = 0; i < 4; i++) {
if (ActionPoseIsActive(controller[i], subactionPath[i])) {
LocVel lv = GetSpaceLocVel(controllerSpace[i], VR_GetEngine()->predictedDisplayTime);
VR_GetEngine()->appState.TrackedController[i].Active = (lv.loc.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) != 0;
VR_GetEngine()->appState.TrackedController[i].Pose = lv.loc.pose;
for (int j = 0; j < 3; j++) {
float dt = 0.01f; // use 0.2f for for testing velocity vectors
(&VR_GetEngine()->appState.TrackedController[i].Pose.position.x)[j] += (&lv.vel.linearVelocity.x)[j] * dt;
}
} else {
ovrTrackedController_Clear(&VR_GetEngine()->appState.TrackedController[i]);
}
}
ovrInputTrackedRemoteCapabilities caps; // OpenXR input
caps.Header = capsHeader; {
ovrResult capsResult = vrapi_GetInputDeviceCapabilities(ovr, &caps.Header); // Attach to session
if (capsResult < 0) { XrSessionActionSetsAttachInfo attachInfo = {};
continue; attachInfo.type = XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO;
} attachInfo.next = NULL;
attachInfo.countActionSets = 1;
attachInfo.actionSets = &runningActionSet;
OXR(xrAttachSessionActionSets(engine->appState.Session, &attachInfo));
ovrInputStateTrackedRemote state; // sync action data
state.Header.ControllerType = ovrControllerType_TrackedRemote; XrActiveActionSet activeActionSet = {};
ovrResult stateResult = vrapi_GetCurrentInputState(ovr, capsHeader.DeviceID, &state.Header); activeActionSet.actionSet = runningActionSet;
if (stateResult < 0) { activeActionSet.subactionPath = XR_NULL_PATH;
continue;
}
ovrTracking remoteTracking; XrActionsSyncInfo syncInfo = {};
stateResult = vrapi_GetInputTrackingState(ovr, capsHeader.DeviceID, VR_GetEngine()->predictedDisplayTime, syncInfo.type = XR_TYPE_ACTIONS_SYNC_INFO;
&remoteTracking); syncInfo.next = NULL;
if (stateResult < 0) { syncInfo.countActiveActionSets = 1;
continue; syncInfo.activeActionSets = &activeActionSet;
} OXR(xrSyncActions(engine->appState.Session, &syncInfo));
qboolean isRight; // query input action states
vrController_t* controller; XrActionStateGetInfo getInfo = {};
if (caps.ControllerCapabilities & ovrControllerCaps_LeftHand) { getInfo.type = XR_TYPE_ACTION_STATE_GET_INFO;
isRight = qfalse; getInfo.next = NULL;
controller = &leftController; getInfo.subactionPath = XR_NULL_PATH;
controllerIDs[0] = capsHeader.DeviceID; }
} else if (caps.ControllerCapabilities & ovrControllerCaps_RightHand) {
isRight = qtrue;
controller = &rightController;
controllerIDs[1] = capsHeader.DeviceID;
}
else {
continue;
}
IN_VRButtons(isRight, state.Buttons); //button mapping
IN_VRController(isRight, remoteTracking); uint32_t lButtons = 0;
IN_VRJoystick(isRight, state.Joystick.x, state.Joystick.y); if (GetActionStateBoolean(menuAction).currentState) lButtons |= ovrButton_Enter;
IN_VRTriggers(isRight, state.IndexTrigger); if (GetActionStateBoolean(buttonXAction).currentState) lButtons |= ovrButton_X;
} if (GetActionStateBoolean(buttonYAction).currentState) lButtons |= ovrButton_Y;
if (GetActionStateFloat(gripLeftAction).currentState > 0.5f) lButtons |= ovrButton_GripTrigger;
if (GetActionStateBoolean(thumbstickLeftClickAction).currentState) lButtons |= ovrButton_LThumb;
IN_VRButtons(qfalse, lButtons);
uint32_t rButtons = 0;
if (GetActionStateBoolean(buttonAAction).currentState) rButtons |= ovrButton_A;
if (GetActionStateBoolean(buttonBAction).currentState) rButtons |= ovrButton_B;
if (GetActionStateFloat(gripRightAction).currentState > 0.5f) rButtons |= ovrButton_GripTrigger;
if (GetActionStateBoolean(thumbstickRightClickAction).currentState) rButtons |= ovrButton_RThumb;
IN_VRButtons(qtrue, rButtons);
//index finger click
XrActionStateBoolean indexState;
indexState = GetActionStateBoolean(indexLeftAction);
IN_VRTriggers(qfalse, indexState.currentState ? 1 : 0);
indexState = GetActionStateBoolean(indexRightAction);
IN_VRTriggers(qtrue, indexState.currentState ? 1 : 0);
//thumbstick
XrActionStateVector2f moveJoystickState;
moveJoystickState = GetActionStateVector2(moveOnLeftJoystickAction);
IN_VRJoystick(qfalse, moveJoystickState.currentState.x, moveJoystickState.currentState.y);
moveJoystickState = GetActionStateVector2(moveOnRightJoystickAction);
IN_VRJoystick(qtrue, moveJoystickState.currentState.x, moveJoystickState.currentState.y);
//controller pose
if (engine->appState.TrackedController[0].Active)
IN_VRController(qfalse, engine->appState.TrackedController[0].Pose);
if (engine->appState.TrackedController[2].Active)
IN_VRController(qtrue, engine->appState.TrackedController[2].Pose);
lastframetime = in_vrEventTime; lastframetime = in_vrEventTime;
in_vrEventTime = Sys_Milliseconds( ); in_vrEventTime = Sys_Milliseconds( );

View file

@ -4,22 +4,10 @@
#include "../qcommon/q_shared.h" #include "../qcommon/q_shared.h"
#include "../qcommon/qcommon.h" #include "../qcommon/qcommon.h"
#include "../client/client.h" #include "../client/client.h"
#include "../VrApi/Include/VrApi_Types.h"
#include "vr_clientinfo.h" #include "vr_clientinfo.h"
#include "vr_types.h" #include "vr_types.h"
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES3/gl3.h>
#include <GLES3/gl3ext.h>
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wstrict-prototypes"
#include <VrApi.h>
#include <VrApi_Helpers.h>
#pragma clang diagnostic pop
#include <assert.h> #include <assert.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -30,10 +18,28 @@
#include <GLES3/gl32.h> #include <GLES3/gl32.h>
#endif #endif
#define SUPER_SAMPLE 1.15f
extern vr_clientinfo_t vr; extern vr_clientinfo_t vr;
XrView* projections;
void VR_UpdateStageBounds(ovrApp* pappState) {
XrExtent2Df stageBounds = {};
XrResult result;
OXR(result = xrGetReferenceSpaceBoundsRect(
pappState->Session, XR_REFERENCE_SPACE_TYPE_STAGE, &stageBounds));
if (result != XR_SUCCESS) {
ALOGV("Stage bounds query failed: using small defaults");
stageBounds.width = 1.0f;
stageBounds.height = 1.0f;
pappState->CurrentSpace = pappState->FakeStageSpace;
}
ALOGV("Stage bounds: width = %f, depth %f", stageBounds.width, stageBounds.height);
}
void APIENTRY VR_GLDebugLog(GLenum source, GLenum type, GLuint id, void APIENTRY VR_GLDebugLog(GLenum source, GLenum type, GLuint id,
GLenum severity, GLsizei length, const GLchar* message, const void* userParam) GLenum severity, GLsizei length, const GLchar* message, const void* userParam)
{ {
@ -71,11 +77,79 @@ void VR_GetResolution(engine_t* engine, int *pWidth, int *pHeight)
if (engine) if (engine)
{ {
*pWidth = width = vrapi_GetSystemPropertyInt(&engine->java, VRAPI_SYS_PROP_SUGGESTED_EYE_TEXTURE_WIDTH) * SUPER_SAMPLE; // Enumerate the viewport configurations.
*pHeight = height = vrapi_GetSystemPropertyInt(&engine->java, VRAPI_SYS_PROP_SUGGESTED_EYE_TEXTURE_HEIGHT) * SUPER_SAMPLE; uint32_t viewportConfigTypeCount = 0;
OXR(xrEnumerateViewConfigurations(
engine->appState.Instance, engine->appState.SystemId, 0, &viewportConfigTypeCount, NULL));
vr.fov_x = vrapi_GetSystemPropertyInt( &engine->java, VRAPI_SYS_PROP_SUGGESTED_EYE_FOV_DEGREES_X); XrViewConfigurationType* viewportConfigurationTypes =
vr.fov_y = vrapi_GetSystemPropertyInt( &engine->java, VRAPI_SYS_PROP_SUGGESTED_EYE_FOV_DEGREES_Y); (XrViewConfigurationType*)malloc(viewportConfigTypeCount * sizeof(XrViewConfigurationType));
OXR(xrEnumerateViewConfigurations(
engine->appState.Instance,
engine->appState.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(
engine->appState.Instance, engine->appState.SystemId, viewportConfigType, &viewportConfig));
ALOGV(
"FovMutable=%s ConfigurationType %d",
viewportConfig.fovMutable ? "true" : "false",
viewportConfig.viewConfigurationType);
uint32_t viewCount;
OXR(xrEnumerateViewConfigurationViews(
engine->appState.Instance, engine->appState.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(
engine->appState.Instance,
engine->appState.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++) {
engine->appState.ViewConfigurationView[e] = elements[e];
}
}
free(elements);
} else {
ALOGE("Empty viewport configuration type: %d", viewCount);
}
}
free(viewportConfigurationTypes);
*pWidth = width = engine->appState.ViewConfigurationView[0].recommendedImageRectWidth;
*pHeight = height = engine->appState.ViewConfigurationView[0].recommendedImageRectHeight;
} }
else else
{ {
@ -85,313 +159,348 @@ void VR_GetResolution(engine_t* engine, int *pWidth, int *pHeight)
} }
} }
typedef void(GL_APIENTRY* PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC)(
GLenum target,
GLenum attachment,
GLuint texture,
GLint level,
GLint baseViewIndex,
GLsizei numViews);
void VR_InitRenderer( engine_t* engine ) { void VR_InitRenderer( engine_t* engine ) {
#if ENABLE_GL_DEBUG #if ENABLE_GL_DEBUG
glEnable(GL_DEBUG_OUTPUT); glEnable(GL_DEBUG_OUTPUT);
glDebugMessageCallback(VR_GLDebugLog, 0); glDebugMessageCallback(VR_GLDebugLog, 0);
#endif #endif
PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC glFramebufferTextureMultiviewOVR =
(PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC)eglGetProcAddress(
"glFramebufferTextureMultiviewOVR");
int eyeW, eyeH; int eyeW, eyeH;
VR_GetResolution(engine, &eyeW, &eyeH); VR_GetResolution(engine, &eyeW, &eyeH);
//for (int eye = 0; eye < VRAPI_FRAME_LAYER_EYE_MAX; ++eye) // Get the viewport configuration info for the chosen viewport configuration type.
{ engine->appState.ViewportConfig.type = XR_TYPE_VIEW_CONFIGURATION_PROPERTIES;
framebuffer_t* framebuffer = &engine->framebuffers;
framebuffer->colorTexture = vrapi_CreateTextureSwapChain3(VRAPI_TEXTURE_TYPE_2D_ARRAY, GL_RGBA8,
eyeW, eyeH, 1, 3);
framebuffer->swapchainLength = vrapi_GetTextureSwapChainLength(framebuffer->colorTexture);
framebuffer->depthBuffers = (GLuint*)malloc(framebuffer->swapchainLength * sizeof(GLuint));
framebuffer->framebuffers = (GLuint*)malloc(framebuffer->swapchainLength * sizeof(GLuint));
for (int index = 0; index < framebuffer->swapchainLength; ++index) { OXR(xrGetViewConfigurationProperties(
GLuint colorTexture; engine->appState.Instance, engine->appState.SystemId, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, &engine->appState.ViewportConfig));
GLenum framebufferStatus;
colorTexture = vrapi_GetTextureSwapChainHandle(framebuffer->colorTexture, index); // Get the supported display refresh rates for the system.
glBindTexture(GL_TEXTURE_2D_ARRAY, colorTexture); {
GLfloat borderColor[] = {0.0f, 0.0f, 0.0f, 0.0f}; PFN_xrEnumerateDisplayRefreshRatesFB pfnxrEnumerateDisplayRefreshRatesFB = NULL;
glTexParameterfv(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_BORDER_COLOR, borderColor); OXR(xrGetInstanceProcAddr(
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR); engine->appState.Instance,
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); "xrEnumerateDisplayRefreshRatesFB",
glBindTexture(GL_TEXTURE_2D_ARRAY, 0); (PFN_xrVoidFunction*)(&pfnxrEnumerateDisplayRefreshRatesFB)));
glGenTextures(1, &framebuffer->depthBuffers[index]); OXR(pfnxrEnumerateDisplayRefreshRatesFB(
glBindTexture(GL_TEXTURE_2D_ARRAY, framebuffer->depthBuffers[index]); engine->appState.Session, 0, &engine->appState.NumSupportedDisplayRefreshRates, NULL));
glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_DEPTH_COMPONENT24, eyeW, eyeH, 2);
glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
glGenFramebuffers(1, &framebuffer->framebuffers[index]); engine->appState.SupportedDisplayRefreshRates =
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer->framebuffers[index]); (float*)malloc(engine->appState.NumSupportedDisplayRefreshRates * sizeof(float));
OXR(pfnxrEnumerateDisplayRefreshRatesFB(
engine->appState.Session,
engine->appState.NumSupportedDisplayRefreshRates,
&engine->appState.NumSupportedDisplayRefreshRates,
engine->appState.SupportedDisplayRefreshRates));
ALOGV("Supported Refresh Rates:");
for (uint32_t i = 0; i < engine->appState.NumSupportedDisplayRefreshRates; i++) {
ALOGV("%d:%f", i, engine->appState.SupportedDisplayRefreshRates[i]);
}
glFramebufferTextureMultiviewOVR(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, OXR(xrGetInstanceProcAddr(
framebuffer->depthBuffers[index], 0, 0, 2); engine->appState.Instance,
glFramebufferTextureMultiviewOVR(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, "xrGetDisplayRefreshRateFB",
colorTexture, 0, 0, 2); (PFN_xrVoidFunction*)(&engine->appState.pfnGetDisplayRefreshRate)));
framebufferStatus = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER); float currentDisplayRefreshRate = 0.0f;
assert(framebufferStatus == GL_FRAMEBUFFER_COMPLETE); OXR(engine->appState.pfnGetDisplayRefreshRate(engine->appState.Session, &currentDisplayRefreshRate));
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); ALOGV("Current System Display Refresh Rate: %f", currentDisplayRefreshRate);
}
} OXR(xrGetInstanceProcAddr(
engine->appState.Instance,
"xrRequestDisplayRefreshRateFB",
(PFN_xrVoidFunction*)(&engine->appState.pfnRequestDisplayRefreshRate)));
// Test requesting the system default.
OXR(engine->appState.pfnRequestDisplayRefreshRate(engine->appState.Session, 0.0f));
ALOGV("Requesting system default display refresh rate");
}
uint32_t numOutputSpaces = 0;
OXR(xrEnumerateReferenceSpaces(engine->appState.Session, 0, &numOutputSpaces, NULL));
XrReferenceSpaceType* referenceSpaces =
(XrReferenceSpaceType*)malloc(numOutputSpaces * sizeof(XrReferenceSpaceType));
OXR(xrEnumerateReferenceSpaces(
engine->appState.Session, numOutputSpaces, &numOutputSpaces, referenceSpaces));
GLboolean stageSupported = GL_FALSE;
for (uint32_t i = 0; i < numOutputSpaces; i++) {
if (referenceSpaces[i] == XR_REFERENCE_SPACE_TYPE_STAGE) {
stageSupported = GL_TRUE;
break;
}
}
free(referenceSpaces);
// 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(engine->appState.Session, &spaceCreateInfo, &engine->appState.HeadSpace));
spaceCreateInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL;
OXR(xrCreateReferenceSpace(engine->appState.Session, &spaceCreateInfo, &engine->appState.LocalSpace));
// 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(engine->appState.Session, &spaceCreateInfo, &engine->appState.FakeStageSpace));
ALOGV("Created fake stage space from local space with offset");
engine->appState.CurrentSpace = engine->appState.FakeStageSpace;
}
if (stageSupported) {
spaceCreateInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE;
spaceCreateInfo.poseInReferenceSpace.position.y = 0.0f;
OXR(xrCreateReferenceSpace(engine->appState.Session, &spaceCreateInfo, &engine->appState.StageSpace));
ALOGV("Created stage space");
engine->appState.CurrentSpace = engine->appState.StageSpace;
}
projections = (XrView*)(malloc(ovrMaxNumEyes * sizeof(XrView)));
ovrRenderer_Create(
engine->appState.Session,
&engine->appState.Renderer,
engine->appState.ViewConfigurationView[0].recommendedImageRectWidth,
engine->appState.ViewConfigurationView[0].recommendedImageRectHeight);
} }
void VR_DestroyRenderer( engine_t* engine ) { void VR_DestroyRenderer( engine_t* engine )
for (int eye = 0; eye < VRAPI_FRAME_LAYER_EYE_MAX; ++eye) {
{ ovrRenderer_Destroy(&engine->appState.Renderer);
if (engine->framebuffers.swapchainLength > 0) { free(projections);
glDeleteFramebuffers(engine->framebuffers.swapchainLength,
engine->framebuffers.depthBuffers);
free(engine->framebuffers.depthBuffers);
free(engine->framebuffers.framebuffers);
vrapi_DestroyTextureSwapChain(engine->framebuffers.colorTexture);
memset(&engine->framebuffers, 0, sizeof(engine->framebuffers));
}
}
} }
void VR_ReInitRenderer() void VR_ReInitRenderer()
{ {
VR_DestroyRenderer( VR_GetEngine() ); VR_DestroyRenderer( VR_GetEngine() );
VR_InitRenderer( VR_GetEngine() ); VR_InitRenderer( VR_GetEngine() );
} }
void VR_ClearFrameBuffer( int width, int height)
// Assumes landscape cylinder shape.
static ovrMatrix4f CylinderModelMatrix( const int texWidth, const int texHeight,
const ovrVector3f translation,
const float rotateYaw,
const float rotatePitch,
const float radius,
const float density )
{ {
const ovrMatrix4f scaleMatrix = ovrMatrix4f_CreateScale( radius, radius * (float)texHeight * VRAPI_PI / density, radius );
const ovrMatrix4f transMatrix = ovrMatrix4f_CreateTranslation( translation.x, translation.y, translation.z );
const ovrMatrix4f rotXMatrix = ovrMatrix4f_CreateRotation( rotateYaw, 0.0f, 0.0f );
const ovrMatrix4f rotYMatrix = ovrMatrix4f_CreateRotation( 0.0f, rotatePitch, 0.0f );
const ovrMatrix4f m0 = ovrMatrix4f_Multiply( &transMatrix, &scaleMatrix );
const ovrMatrix4f m1 = ovrMatrix4f_Multiply( &rotXMatrix, &m0 );
const ovrMatrix4f m2 = ovrMatrix4f_Multiply( &rotYMatrix, &m1 );
return m2;
}
extern cvar_t *vr_screen_dist;
ovrLayerCylinder2 BuildCylinderLayer(engine_t* engine, const int textureWidth, const int textureHeight,
const ovrTracking2 * tracking, float rotatePitch )
{
ovrLayerCylinder2 layer = vrapi_DefaultLayerCylinder2();
const float fadeLevel = 1.0f;
layer.Header.ColorScale.x =
layer.Header.ColorScale.y =
layer.Header.ColorScale.z =
layer.Header.ColorScale.w = fadeLevel;
layer.Header.SrcBlend = VRAPI_FRAME_LAYER_BLEND_SRC_ALPHA;
layer.Header.DstBlend = VRAPI_FRAME_LAYER_BLEND_ONE_MINUS_SRC_ALPHA;
//layer.Header.Flags = VRAPI_FRAME_LAYER_FLAG_CLIP_TO_TEXTURE_RECT;
layer.HeadPose = tracking->HeadPose;
const float density = 4500.0f;
const float rotateYaw = 0.0f;
const float radius = 12.0f;
const float distance = -16.0f;
const ovrVector3f translation = { 0.0f, 1.0f, distance };
ovrMatrix4f cylinderTransform =
CylinderModelMatrix( textureWidth, textureHeight, translation,
rotateYaw, rotatePitch, radius, density );
const float circScale = density * 0.5f / textureWidth;
const float circBias = -circScale * ( 0.5f * ( 1.0f - 1.0f / circScale ) );
for ( int eye = 0; eye < VRAPI_FRAME_LAYER_EYE_MAX; eye++ )
{
ovrMatrix4f modelViewMatrix = ovrMatrix4f_Multiply( &tracking->Eye[eye].ViewMatrix, &cylinderTransform );
layer.Textures[eye].TexCoordsFromTanAngles = ovrMatrix4f_Inverse( &modelViewMatrix );
layer.Textures[eye].ColorSwapChain = engine->framebuffers.colorTexture;
layer.Textures[eye].SwapChainIndex = engine->framebuffers.swapchainIndex;
// Texcoord scale and bias is just a representation of the aspect ratio. The positioning
// of the cylinder is handled entirely by the TexCoordsFromTanAngles matrix.
const float texScaleX = circScale;
const float texBiasX = circBias;
const float texScaleY = -0.5f;
const float texBiasY = texScaleY * ( 0.5f * ( 1.0f - ( 1.0f / texScaleY ) ) );
layer.Textures[eye].TextureMatrix.M[0][0] = texScaleX;
layer.Textures[eye].TextureMatrix.M[0][2] = texBiasX;
layer.Textures[eye].TextureMatrix.M[1][1] = texScaleY;
layer.Textures[eye].TextureMatrix.M[1][2] = -texBiasY;
layer.Textures[eye].TextureRect.width = 1.0f;
layer.Textures[eye].TextureRect.height = 1.0f;
}
return layer;
}
void VR_ClearFrameBuffer( GLuint frameBuffer, int width, int height)
{
glBindFramebuffer( GL_DRAW_FRAMEBUFFER, frameBuffer );
glEnable( GL_SCISSOR_TEST ); glEnable( GL_SCISSOR_TEST );
glViewport( 0, 0, width, height ); glViewport( 0, 0, width, height );
if (Cvar_VariableIntegerValue("vr_thirdPersonSpectator")) if (Cvar_VariableIntegerValue("vr_thirdPersonSpectator"))
{ {
//Blood red.. ish //Blood red.. ish
glClearColor( 0.12f, 0.0f, 0.05f, 1.0f ); glClearColor( 0.12f, 0.0f, 0.05f, 1.0f );
} }
else else
{ {
//Black //Black
glClearColor( 0.0f, 0.0f, 0.0f, 1.0f ); glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
} }
glScissor( 0, 0, width, height ); glScissor( 0, 0, width, height );
glClear( GL_COLOR_BUFFER_BIT ); glClear( GL_COLOR_BUFFER_BIT );
glScissor( 0, 0, 0, 0 ); glScissor( 0, 0, 0, 0 );
glDisable( GL_SCISSOR_TEST ); glDisable( GL_SCISSOR_TEST );
glBindFramebuffer( GL_DRAW_FRAMEBUFFER, 0 );
} }
void VR_DrawFrame( engine_t* engine ) { void VR_DrawFrame( engine_t* engine ) {
if (!engine->ovr)
{
return;
}
++engine->frameIndex;
engine->predictedDisplayTime = vrapi_GetPredictedDisplayTime(engine->ovr, engine->frameIndex);
engine->tracking = vrapi_GetPredictedTracking2(engine->ovr, engine->predictedDisplayTime);
float fov_y = vrapi_GetSystemPropertyInt( engine->ovr, VRAPI_SYS_PROP_SUGGESTED_EYE_FOV_DEGREES_Y);
float fov_x = vrapi_GetSystemPropertyInt( engine->ovr, VRAPI_SYS_PROP_SUGGESTED_EYE_FOV_DEGREES_X);
if (vr.weapon_zoomed) { if (vr.weapon_zoomed) {
vr.weapon_zoomLevel += 0.05; vr.weapon_zoomLevel += 0.05;
if (vr.weapon_zoomLevel > 2.5f) if (vr.weapon_zoomLevel > 2.5f)
vr.weapon_zoomLevel = 2.5f; vr.weapon_zoomLevel = 2.5f;
} }
else { else {
//Zoom back out quicker //Zoom back out quicker
vr.weapon_zoomLevel -= 0.25f; vr.weapon_zoomLevel -= 0.25f;
if (vr.weapon_zoomLevel < 1.0f) if (vr.weapon_zoomLevel < 1.0f)
vr.weapon_zoomLevel = 1.0f; vr.weapon_zoomLevel = 1.0f;
} }
const ovrMatrix4f projectionMatrix = ovrMatrix4f_CreateProjectionFov( GLboolean stageBoundsDirty = GL_TRUE;
fov_x / vr.weapon_zoomLevel, fov_y / vr.weapon_zoomLevel, 0.0f, 0.0f, 1.0f, 0.0f ); ovrApp_HandleXrEvents(&engine->appState);
if (engine->appState.SessionActive == GL_FALSE) {
return;
}
//Projection used for drawing HUD models etc if (stageBoundsDirty) {
const ovrMatrix4f monoVRMatrix = ovrMatrix4f_CreateProjectionFov( VR_UpdateStageBounds(&engine->appState);
30.0f, 30.0f, 0.0f, 0.0f, 1.0f, 0.0f ); stageBoundsDirty = GL_FALSE;
}
int eyeW, eyeH; // NOTE: OpenXR does not use the concept of frame indices. Instead,
VR_GetResolution(engine, &eyeW, &eyeH); // XrWaitFrame returns the predicted display time.
XrFrameWaitInfo waitFrameInfo = {};
waitFrameInfo.type = XR_TYPE_FRAME_WAIT_INFO;
waitFrameInfo.next = NULL;
if (VR_useScreenLayer() || XrFrameState frameState = {};
(cl.snap.ps.pm_flags & PMF_FOLLOW && vr.follow_mode == VRFM_FIRSTPERSON)) frameState.type = XR_TYPE_FRAME_STATE;
{ frameState.next = NULL;
static ovrLayer_Union2 cylinderLayer;
memset( &cylinderLayer, 0, sizeof( ovrLayer_Union2 ) );
// Add a simple cylindrical layer OXR(xrWaitFrame(engine->appState.Session, &waitFrameInfo, &frameState));
cylinderLayer.Cylinder = engine->predictedDisplayTime = frameState.predictedDisplayTime;
BuildCylinderLayer(engine, eyeW, eyeW * 0.75f, &engine->tracking, radians(vr.menuYaw) ); if (!frameState.shouldRender) {
return;
}
const ovrLayerHeader2* layers[] = { // Get the HMD pose, predicted for the middle of the time period during which
&cylinderLayer.Header // 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(engine->appState.Session, &beginFrameDesc));
// Set up the description for this frame. XrSpaceLocation loc = {};
ovrSubmitFrameDescription2 frameDesc = { 0 }; loc.type = XR_TYPE_SPACE_LOCATION;
frameDesc.Flags = 0; OXR(xrLocateSpace(
frameDesc.SwapInterval = 1; engine->appState.HeadSpace, engine->appState.CurrentSpace, frameState.predictedDisplayTime, &loc));
frameDesc.FrameIndex = engine->frameIndex; XrPosef xfStageFromHead = loc.pose;
frameDesc.DisplayTime = engine->predictedDisplayTime; OXR(xrLocateSpace(
frameDesc.LayerCount = 1; engine->appState.HeadSpace, engine->appState.LocalSpace, frameState.predictedDisplayTime, &loc));
frameDesc.Layers = layers;
re.SetVRHeadsetParms(&projectionMatrix, &monoVRMatrix, XrViewLocateInfo projectionInfo = {};
engine->framebuffers.framebuffers[engine->framebuffers.swapchainIndex]); projectionInfo.type = XR_TYPE_VIEW_LOCATE_INFO;
projectionInfo.viewConfigurationType = engine->appState.ViewportConfig.viewConfigurationType;
projectionInfo.displayTime = frameState.predictedDisplayTime;
projectionInfo.space = engine->appState.HeadSpace;
VR_ClearFrameBuffer(engine->framebuffers.framebuffers[engine->framebuffers.swapchainIndex], eyeW, eyeH); XrViewState viewState = {XR_TYPE_VIEW_STATE, NULL};
Com_Frame(); uint32_t projectionCapacityInput = ovrMaxNumEyes;
uint32_t projectionCountOutput = projectionCapacityInput;
engine->framebuffers.swapchainIndex = (engine->framebuffers.swapchainIndex + 1) % OXR(xrLocateViews(
engine->framebuffers.swapchainLength; engine->appState.Session,
&projectionInfo,
&viewState,
projectionCapacityInput,
&projectionCountOutput,
projections));
//
// Hand over the eye images to the time warp. XrFovf fov;
vrapi_SubmitFrame2(engine->ovr, &frameDesc); XrPosef viewTransform[2];
} for (int eye = 0; eye < ovrMaxNumEyes; eye++) {
else XrPosef xfHeadFromEye = projections[eye].pose;
{ XrPosef xfStageFromEye = XrPosef_Multiply(xfStageFromHead, xfHeadFromEye);
vr.menuYaw = vr.hmdorientation[YAW]; viewTransform[eye] = XrPosef_Inverse(xfStageFromEye);
ovrLayerProjection2 layer = vrapi_DefaultLayerProjection2(); fov = projections[eye].fov;
layer.HeadPose = engine->tracking.HeadPose; }
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;
const ovrMatrix4f defaultProjection = ovrMatrix4f_CreateProjectionFov( //Projection used for drawing HUD models etc
fov_x, fov_y, 0.0f, 0.0f, 1.0f, 0.0f ); float hudScale = M_PI * 15.0f / 180.0f;
const ovrMatrix4f monoVRMatrix = ovrMatrix4f_CreateProjectionFov(
-hudScale, hudScale, hudScale, -hudScale, 1.0f, 0.0f );
const ovrMatrix4f projectionMatrix = ovrMatrix4f_CreateProjectionFov(
fov.angleLeft / vr.weapon_zoomLevel,
fov.angleRight / vr.weapon_zoomLevel,
fov.angleUp / vr.weapon_zoomLevel,
fov.angleDown / vr.weapon_zoomLevel,
1.0f, 0.0f );
engine->appState.LayerCount = 0;
memset(engine->appState.Layers, 0, sizeof(ovrCompositorLayer_Union) * ovrMaxLayerCount);
for (int eye = 0; eye < VRAPI_FRAME_LAYER_EYE_MAX; ++eye) { ovrFramebuffer* frameBuffer = &engine->appState.Renderer.FrameBuffer;
layer.Textures[eye].ColorSwapChain = engine->framebuffers.colorTexture; int swapchainIndex = engine->appState.Renderer.FrameBuffer.TextureSwapChainIndex;
layer.Textures[eye].SwapChainIndex = engine->framebuffers.swapchainIndex; int glFramebuffer = engine->appState.Renderer.FrameBuffer.FrameBuffers[swapchainIndex];
layer.Textures[eye].TexCoordsFromTanAngles = ovrMatrix4f_TanAngleMatrixFromProjection(&defaultProjection); re.SetVRHeadsetParms(projectionMatrix.M, monoVRMatrix.M, glFramebuffer);
}
layer.Header.Flags |= VRAPI_FRAME_LAYER_FLAG_CHROMATIC_ABERRATION_CORRECTION;
VR_ClearFrameBuffer(engine->framebuffers.framebuffers[engine->framebuffers.swapchainIndex], eyeW, eyeH); ovrFramebuffer_Acquire(frameBuffer);
ovrFramebuffer_SetCurrent(frameBuffer);
VR_ClearFrameBuffer(frameBuffer->ColorSwapChain.Width, frameBuffer->ColorSwapChain.Height);
Com_Frame();
re.SetVRHeadsetParms(&projectionMatrix, &monoVRMatrix, // Clear the alpha channel, other way OpenXR would not transfer the framebuffer fully
engine->framebuffers.framebuffers[engine->framebuffers.swapchainIndex]); 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);
Com_Frame(); ovrFramebuffer_Resolve(frameBuffer);
ovrFramebuffer_Release(frameBuffer);
ovrFramebuffer_SetNone();
engine->framebuffers.swapchainIndex = (engine->framebuffers.swapchainIndex + 1) % XrCompositionLayerProjectionView projection_layer_elements[2] = {};
engine->framebuffers.swapchainLength; if (!VR_useScreenLayer() && !(cl.snap.ps.pm_flags & PMF_FOLLOW && vr.follow_mode == VRFM_FIRSTPERSON)) {
const ovrLayerHeader2* layers[] = { for (int eye = 0; eye < ovrMaxNumEyes; eye++) {
&layer.Header ovrFramebuffer* frameBuffer = &engine->appState.Renderer.FrameBuffer;
};
ovrSubmitFrameDescription2 frameDesc = { 0 }; memset(&projection_layer_elements[eye], 0, sizeof(XrCompositionLayerProjectionView));
frameDesc.Flags = 0; projection_layer_elements[eye].type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW;
frameDesc.SwapInterval = 1; projection_layer_elements[eye].pose = XrPosef_Inverse(viewTransform[eye]);
frameDesc.FrameIndex = engine->frameIndex; projection_layer_elements[eye].fov = fov;
frameDesc.DisplayTime = engine->predictedDisplayTime;
frameDesc.LayerCount = 1;
frameDesc.Layers = layers;
vrapi_SubmitFrame2(engine->ovr, &frameDesc); 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 = eye;
}
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 = engine->appState.CurrentSpace;
projection_layer.viewCount = ovrMaxNumEyes;
projection_layer.views = projection_layer_elements;
engine->appState.Layers[engine->appState.LayerCount++].Projection = projection_layer;
} else {
// Build the cylinder layer
int width = engine->appState.Renderer.FrameBuffer.ColorSwapChain.Width;
int height = engine->appState.Renderer.FrameBuffer.ColorSwapChain.Height;
XrCompositionLayerCylinderKHR cylinder_layer = {};
cylinder_layer.type = XR_TYPE_COMPOSITION_LAYER_CYLINDER_KHR;
cylinder_layer.layerFlags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
cylinder_layer.space = engine->appState.LocalSpace;
cylinder_layer.eyeVisibility = XR_EYE_VISIBILITY_BOTH;
memset(&cylinder_layer.subImage, 0, sizeof(XrSwapchainSubImage));
cylinder_layer.subImage.swapchain = engine->appState.Renderer.FrameBuffer.ColorSwapChain.Handle;
cylinder_layer.subImage.imageRect.offset.x = 0;
cylinder_layer.subImage.imageRect.offset.y = 0;
cylinder_layer.subImage.imageRect.extent.width = width;
cylinder_layer.subImage.imageRect.extent.height = height;
cylinder_layer.subImage.imageArrayIndex = 0;
const XrVector3f axis = {0.0f, 1.0f, 0.0f};
const XrVector3f pos = {xfStageFromHead.position.x, -0.25f, xfStageFromHead.position.z - 4.0f};
cylinder_layer.pose.orientation = XrQuaternionf_CreateFromVectorAngle(axis, 0);
cylinder_layer.pose.position = pos;
cylinder_layer.radius = 12.0f;
cylinder_layer.centralAngle = MATH_PI * 0.5f;
cylinder_layer.aspectRatio = width / (float)height / 0.75f;
engine->appState.Layers[engine->appState.LayerCount++].Cylinder = cylinder_layer;
}
// Compose the layers for this frame.
const XrCompositionLayerBaseHeader* layers[ovrMaxLayerCount] = {};
for (int i = 0; i < engine->appState.LayerCount; i++) {
layers[i] = (const XrCompositionLayerBaseHeader*)&engine->appState.Layers[i];
}
XrFrameEndInfo endFrameInfo = {};
endFrameInfo.type = XR_TYPE_FRAME_END_INFO;
endFrameInfo.displayTime = frameState.predictedDisplayTime;
endFrameInfo.environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
endFrameInfo.layerCount = engine->appState.LayerCount;
endFrameInfo.layers = layers;
OXR(xrEndFrame(engine->appState.Session, &endFrameInfo));
frameBuffer->TextureSwapChainIndex++;
frameBuffer->TextureSwapChainIndex %= frameBuffer->TextureSwapChainLength;
} }

View file

@ -9,6 +9,7 @@ void VR_GetResolution( engine_t* engine, int *pWidth, int *pHeight );
void VR_InitRenderer( engine_t* engine ); void VR_InitRenderer( engine_t* engine );
void VR_DestroyRenderer( engine_t* engine ); void VR_DestroyRenderer( engine_t* engine );
void VR_DrawFrame( engine_t* engine ); void VR_DrawFrame( engine_t* engine );
void VR_ReInitRenderer();
#endif #endif

View file

@ -0,0 +1,591 @@
#include "vr_types.h"
/************************************************************************************
Original file name : XrCompositor_NativeActivity.c
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
*************************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <pthread.h>
#include <sys/prctl.h>
#include <assert.h>
typedef void(GL_APIENTRY* PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC)(
GLenum target,
GLenum attachment,
GLuint texture,
GLint level,
GLint baseViewIndex,
GLsizei numViews);
/*
================================================================================
ovrFramebuffer
================================================================================
*/
void ovrFramebuffer_Clear(ovrFramebuffer* frameBuffer) {
frameBuffer->Width = 0;
frameBuffer->Height = 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;
}
bool ovrFramebuffer_Create(
XrSession session,
ovrFramebuffer* frameBuffer,
const int width,
const int height) {
frameBuffer->Width = width;
frameBuffer->Height = height;
PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC glFramebufferTextureMultiviewOVR =
(PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC)eglGetProcAddress(
"glFramebufferTextureMultiviewOVR");
XrSwapchainCreateInfo swapChainCreateInfo;
memset(&swapChainCreateInfo, 0, sizeof(swapChainCreateInfo));
swapChainCreateInfo.type = XR_TYPE_SWAPCHAIN_CREATE_INFO;
swapChainCreateInfo.usageFlags = XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT;
swapChainCreateInfo.format = GL_RGBA8;
swapChainCreateInfo.sampleCount = 1;
swapChainCreateInfo.width = width;
swapChainCreateInfo.height = height;
swapChainCreateInfo.faceCount = 1;
swapChainCreateInfo.arraySize = 2;
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 textureTarget = GL_TEXTURE_2D_ARRAY;
GL(glBindTexture(textureTarget, colorTexture));
GL(glTexParameterfv(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_BORDER_COLOR, borderColor));
GL(glTexParameteri(textureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
GL(glTexParameteri(textureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
GL(glTexParameteri(textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
GL(glTexParameteri(textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
GL(glBindTexture(textureTarget, 0));
// Create depth buffer.
GL(glGenTextures(1, &frameBuffer->DepthBuffers[i]));
GL(glBindTexture(textureTarget, frameBuffer->DepthBuffers[i]));
GL(glTexStorage3D(textureTarget, 1, GL_DEPTH_COMPONENT24, width, height, 2));
GL(glBindTexture(textureTarget, 0));
// Create the frame buffer.
GL(glGenFramebuffers(1, &frameBuffer->FrameBuffers[i]));
GL(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, frameBuffer->FrameBuffers[i]));
GL(glFramebufferTextureMultiviewOVR(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, frameBuffer->DepthBuffers[i], 0, 0, 2));
GL(glFramebufferTextureMultiviewOVR(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, colorTexture, 0, 0, 2));
GL(GLenum renderFramebufferStatus = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER));
GL(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0));
if (renderFramebufferStatus != GL_FRAMEBUFFER_COMPLETE) {
ALOGE("Incomplete frame buffer object: %d", renderFramebufferStatus);
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);
}
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 = 1000; /* timeout in nanoseconds */
XrResult res = xrWaitSwapchainImage(frameBuffer->ColorSwapChain.Handle, &waitInfo);
int i = 0;
while (res != XR_SUCCESS) {
res = xrWaitSwapchainImage(frameBuffer->ColorSwapChain.Handle, &waitInfo);
i++;
ALOGV(
" Retry xrWaitSwapchainImage %d times due to XR_TIMEOUT_EXPIRED (duration %f micro 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) {
ovrFramebuffer_Clear(&renderer->FrameBuffer);
}
void ovrRenderer_Create(
XrSession session,
ovrRenderer* renderer,
int suggestedEyeTextureWidth,
int suggestedEyeTextureHeight) {
// Create the frame buffers.
ovrFramebuffer_Create(
session,
&renderer->FrameBuffer,
suggestedEyeTextureWidth,
suggestedEyeTextureHeight);
}
void ovrRenderer_Destroy(ovrRenderer* renderer) {
ovrFramebuffer_Destroy(&renderer->FrameBuffer);
}
/*
================================================================================
ovrApp
================================================================================
*/
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->LocalSpace = 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(ovrCompositorLayer_Union) * ovrMaxLayerCount);
app->LayerCount = 0;
app->MainThreadTid = 0;
app->RenderThreadTid = 0;
app->TouchPadDownLastFrame = false;
ovrRenderer_Clear(&app->Renderer);
}
void ovrApp_Destroy(ovrApp* app) {
if (app->SupportedDisplayRefreshRates != NULL) {
free(app->SupportedDisplayRefreshRates);
}
ovrApp_Clear(app);
}
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));
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));
}
} else if (state == XR_SESSION_STATE_STOPPING) {
assert(app->SessionActive);
OXR(xrEndSession(app->Session));
app->SessionActive = false;
}
}
void ovrApp_HandleXrEvents(ovrApp* app) {
XrEventDataBuffer eventDataBuffer = {};
// 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;
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;
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));
} 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;
}
}
}
/*
================================================================================
ovrMatrix4f
================================================================================
*/
ovrMatrix4f ovrMatrix4f_CreateProjectionFov(
const float angleLeft,
const float angleRight,
const float angleUp,
const float angleDown,
const float nearZ,
const float farZ) {
const float tanAngleLeft = tanf(angleLeft);
const float tanAngleRight = tanf(angleRight);
const float tanAngleDown = tanf(angleDown);
const float tanAngleUp = tanf(angleUp);
const float tanAngleWidth = tanAngleRight - tanAngleLeft;
// Set to tanAngleDown - tanAngleUp for a clip space with positive Y
// down (Vulkan). Set to tanAngleUp - tanAngleDown for a clip space with
// positive Y up (OpenGL / D3D / Metal).
const float tanAngleHeight = tanAngleUp - tanAngleDown;
// Set to nearZ for a [-1,1] Z clip space (OpenGL / OpenGL ES).
// Set to zero for a [0,1] Z clip space (Vulkan / D3D / Metal).
const float offsetZ = nearZ;
ovrMatrix4f result;
if (farZ <= nearZ) {
// place the far plane at infinity
result.M[0][0] = 2 / tanAngleWidth;
result.M[0][1] = 0;
result.M[0][2] = (tanAngleRight + tanAngleLeft) / tanAngleWidth;
result.M[0][3] = 0;
result.M[1][0] = 0;
result.M[1][1] = 2 / tanAngleHeight;
result.M[1][2] = (tanAngleUp + tanAngleDown) / tanAngleHeight;
result.M[1][3] = 0;
result.M[2][0] = 0;
result.M[2][1] = 0;
result.M[2][2] = -1;
result.M[2][3] = -(nearZ + offsetZ);
result.M[3][0] = 0;
result.M[3][1] = 0;
result.M[3][2] = -1;
result.M[3][3] = 0;
} else {
// normal projection
result.M[0][0] = 2 / tanAngleWidth;
result.M[0][1] = 0;
result.M[0][2] = (tanAngleRight + tanAngleLeft) / tanAngleWidth;
result.M[0][3] = 0;
result.M[1][0] = 0;
result.M[1][1] = 2 / tanAngleHeight;
result.M[1][2] = (tanAngleUp + tanAngleDown) / tanAngleHeight;
result.M[1][3] = 0;
result.M[2][0] = 0;
result.M[2][1] = 0;
result.M[2][2] = -(farZ + offsetZ) / (farZ - nearZ);
result.M[2][3] = -(farZ * (nearZ + offsetZ)) / (farZ - nearZ);
result.M[3][0] = 0;
result.M[3][1] = 0;
result.M[3][2] = -1;
result.M[3][3] = 0;
}
return result;
}
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;
}
/*
================================================================================
ovrTrackedController
================================================================================
*/
void ovrTrackedController_Clear(ovrTrackedController* controller) {
controller->Active = false;
controller->Pose = XrPosef_Identity();
}

View file

@ -9,26 +9,136 @@
# include <SDL_opengles2.h> # include <SDL_opengles2.h>
#endif #endif
#pragma clang diagnostic push //OpenXR
#pragma clang diagnostic ignored "-Wstrict-prototypes" #define XR_USE_GRAPHICS_API_OPENGL_ES 1
#include <VrApi.h> #define XR_USE_PLATFORM_ANDROID 1
#pragma clang diagnostic pop #include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES3/gl3.h>
#include <GLES3/gl3ext.h>
#include <jni.h>
#include <openxr/openxr.h>
#include <openxr/openxr_platform.h>
#include <openxr/openxr_oculus.h>
#include <openxr/openxr_oculus_helpers.h>
#define MATH_PI 3.14159265358979323846f
#define ALOGE(...) printf(__VA_ARGS__)
#define ALOGV(...) printf(__VA_ARGS__)
typedef union {
XrCompositionLayerProjection Projection;
XrCompositionLayerCylinderKHR Cylinder;
} ovrCompositorLayer_Union;
enum { ovrMaxLayerCount = 1 };
enum { ovrMaxNumEyes = 2 };
#define GL(func) func;
#define OXR(func) func;
typedef struct { typedef struct {
int swapchainLength; JavaVM* Vm;
int swapchainIndex; jobject ActivityObject;
ovrTextureSwapChain* colorTexture; JNIEnv* Env;
GLuint* depthBuffers; } ovrJava;
GLuint* framebuffers;
} framebuffer_t; typedef struct {
XrSwapchain Handle;
uint32_t Width;
uint32_t Height;
} ovrSwapChain;
typedef struct {
int Width;
int Height;
uint32_t TextureSwapChainLength;
uint32_t TextureSwapChainIndex;
ovrSwapChain ColorSwapChain;
XrSwapchainImageOpenGLESKHR* ColorSwapChainImage;
GLuint* DepthBuffers;
GLuint* FrameBuffers;
} ovrFramebuffer;
typedef struct {
ovrFramebuffer FrameBuffer;
} ovrRenderer;
typedef struct {
GLboolean Active;
XrPosef Pose;
} ovrTrackedController;
typedef struct {
GLboolean Focused;
XrInstance Instance;
XrSession Session;
XrViewConfigurationProperties ViewportConfig;
XrViewConfigurationView ViewConfigurationView[ovrMaxNumEyes];
XrSystemId SystemId;
XrSpace HeadSpace;
XrSpace LocalSpace;
XrSpace StageSpace;
XrSpace FakeStageSpace;
XrSpace CurrentSpace;
GLboolean SessionActive;
float* SupportedDisplayRefreshRates;
uint32_t RequestedDisplayRefreshRateIndex;
uint32_t NumSupportedDisplayRefreshRates;
PFN_xrGetDisplayRefreshRateFB pfnGetDisplayRefreshRate;
PFN_xrRequestDisplayRefreshRateFB pfnRequestDisplayRefreshRate;
int SwapInterval;
// These threads will be marked as performance threads.
int MainThreadTid;
int RenderThreadTid;
ovrCompositorLayer_Union Layers[ovrMaxLayerCount];
int LayerCount;
GLboolean TouchPadDownLastFrame;
ovrRenderer Renderer;
ovrTrackedController TrackedController[4]; // left aim, left grip, right aim, right grip
} ovrApp;
typedef struct {
float M[4][4];
} ovrMatrix4f;
typedef enum ovrButton_ {
ovrButton_A = 0x00000001, // Set for trigger pulled on the Gear VR and Go Controllers
ovrButton_B = 0x00000002,
ovrButton_RThumb = 0x00000004,
ovrButton_RShoulder = 0x00000008,
ovrButton_X = 0x00000100,
ovrButton_Y = 0x00000200,
ovrButton_LThumb = 0x00000400,
ovrButton_LShoulder = 0x00000800,
ovrButton_Up = 0x00010000,
ovrButton_Down = 0x00020000,
ovrButton_Left = 0x00040000,
ovrButton_Right = 0x00080000,
ovrButton_Enter = 0x00100000, //< Set for touchpad click on the Go Controller, menu
// button on Left Quest Controller
ovrButton_Back = 0x00200000, //< Back button on the Go Controller (only set when
// a short press comes up)
ovrButton_GripTrigger = 0x04000000, //< grip trigger engaged
ovrButton_Trigger = 0x20000000, //< Index Trigger engaged
ovrButton_Joystick = 0x80000000, //< Click of the Joystick
ovrButton_EnumSize = 0x7fffffff
} ovrButton;
typedef struct { typedef struct {
uint64_t frameIndex; uint64_t frameIndex;
ovrMobile* ovr; ovrApp appState;
ovrJava java; ovrJava java;
double predictedDisplayTime; float predictedDisplayTime;
ovrTracking2 tracking;
framebuffer_t framebuffers;//[VRAPI_FRAME_LAYER_EYE_MAX];
} engine_t; } engine_t;
typedef enum { typedef enum {
@ -47,4 +157,36 @@ typedef enum {
VRFM_QUERY = 99 //Used to query which mode is active VRFM_QUERY = 99 //Used to query which mode is active
} vrFollowMode_t; } vrFollowMode_t;
void ovrApp_Clear(ovrApp* app);
void ovrApp_Destroy(ovrApp* app);
void ovrApp_HandleXrEvents(ovrApp* app);
void ovrFramebuffer_Acquire(ovrFramebuffer* frameBuffer);
void ovrFramebuffer_Resolve(ovrFramebuffer* frameBuffer);
void ovrFramebuffer_Release(ovrFramebuffer* frameBuffer);
void ovrFramebuffer_SetCurrent(ovrFramebuffer* frameBuffer);
void ovrFramebuffer_SetNone();
void ovrRenderer_Create(
XrSession session,
ovrRenderer* renderer,
int suggestedEyeTextureWidth,
int suggestedEyeTextureHeight);
void ovrRenderer_Destroy(ovrRenderer* renderer);
void ovrTrackedController_Clear(ovrTrackedController* controller);
ovrMatrix4f ovrMatrix4f_Multiply(const ovrMatrix4f* a, const ovrMatrix4f* b);
ovrMatrix4f ovrMatrix4f_CreateRotation(const float radiansX, const float radiansY, const float radiansZ);
ovrMatrix4f ovrMatrix4f_CreateFromQuaternion(const XrQuaternionf* q);
ovrMatrix4f ovrMatrix4f_CreateProjectionFov(
const float fovDegreesX,
const float fovDegreesY,
const float offsetX,
const float offsetY,
const float nearZ,
const float farZ);
XrVector4f XrVector4f_MultiplyMatrix4f(const ovrMatrix4f* a, const XrVector4f* v);
#endif #endif

View file

@ -4,9 +4,6 @@
#include <android/log.h> #include <android/log.h>
#include <VrApi.h>
#include <VrApi_Helpers.h>
#include <client/keycodes.h> #include <client/keycodes.h>
#include <qcommon/q_shared.h> #include <qcommon/q_shared.h>
#include <qcommon/qcommon.h> #include <qcommon/qcommon.h>
@ -27,7 +24,7 @@ extern void CON_LogcatFn( void (*LogcatFn)( const char* message ) );
static JNIEnv* g_Env = NULL; static JNIEnv* g_Env = NULL;
static JavaVM* g_JavaVM = NULL; static JavaVM* g_JavaVM = NULL;
static jobject g_ActivityObject = NULL; static jobject g_ActivityObject = NULL;
static bool g_HasFocus = true; static qboolean g_HasFocus = qtrue;
JNIEXPORT void JNICALL Java_com_drbeef_ioq3quest_MainActivity_nativeCreate(JNIEnv* env, jclass cls, jobject thisObject) JNIEXPORT void JNICALL Java_com_drbeef_ioq3quest_MainActivity_nativeCreate(JNIEnv* env, jclass cls, jobject thisObject)
{ {
@ -80,23 +77,22 @@ int main(int argc, char* argv[]) {
Com_Init(args); Com_Init(args);
NET_Init( ); NET_Init( );
VR_EnterVR(engine, java);
VR_InitRenderer(engine); VR_InitRenderer(engine);
VR_EnterVR(engine, java); qboolean hasFocus = qtrue;
qboolean paused = qfalse;
bool hasFocus = true;
bool paused = false;
while (1) { while (1) {
if (hasFocus != g_HasFocus) { if (hasFocus != g_HasFocus) {
hasFocus = g_HasFocus; hasFocus = g_HasFocus;
if (!hasFocus && VR_isPauseable()) { if (!hasFocus && VR_isPauseable()) {
Com_QueueEvent( Sys_Milliseconds(), SE_KEY, K_ESCAPE, qtrue, 0, NULL ); Com_QueueEvent( Sys_Milliseconds(), SE_KEY, K_ESCAPE, qtrue, 0, NULL );
//Com_QueueEvent( Sys_Milliseconds(), SE_KEY, K_CONSOLE, qtrue, 0, NULL ); //Com_QueueEvent( Sys_Milliseconds(), SE_KEY, K_CONSOLE, qtrue, 0, NULL );
paused = true; paused = qtrue;
} else if (hasFocus && paused) { } else if (hasFocus && paused) {
//Com_QueueEvent( Sys_Milliseconds(), SE_KEY, K_CONSOLE, qtrue, 0, NULL ); //Com_QueueEvent( Sys_Milliseconds(), SE_KEY, K_CONSOLE, qtrue, 0, NULL );
Com_QueueEvent( Sys_Milliseconds(), SE_KEY, K_ESCAPE, qtrue, 0, NULL ); Com_QueueEvent( Sys_Milliseconds(), SE_KEY, K_ESCAPE, qtrue, 0, NULL );
paused = false; paused = qfalse;
} }
} }