From a1aa3c8ae82a7a3a33e68260b1a153e7ac6ab5cc Mon Sep 17 00:00:00 2001 From: bjorn Date: Tue, 9 Jun 2020 18:00:33 -0600 Subject: [PATCH] Finish vrapi driver; --- .gitignore | 1 + Tupfile | 2 +- Tuprules.tup | 7 +- src/api/l_headset.c | 1 + src/core/os.h | 4 +- src/core/os_android.c | 79 ++++- src/modules/graphics/graphics.c | 4 +- src/modules/headset/headset.c | 3 + src/modules/headset/headset.h | 2 + src/modules/headset/headset_vrapi.c | 456 ++++++++++++++++++++++++++++ src/resources/LoadLibraries.java | 2 +- src/resources/boot.lua | 2 +- 12 files changed, 544 insertions(+), 19 deletions(-) create mode 100644 src/modules/headset/headset_vrapi.c diff --git a/.gitignore b/.gitignore index 6e4b9754..3aa12381 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,4 @@ bin .DS_Store .vs /test +deps/Vrapi diff --git a/Tupfile b/Tupfile index f6124543..bfaa8888 100644 --- a/Tupfile +++ b/Tupfile @@ -25,7 +25,7 @@ SRC_@(HEADSET)@(SIMULATOR) += src/modules/headset/headset_desktop.c SRC_@(HEADSET)@(OPENVR) += src/modules/headset/headset_openvr.c SRC_@(HEADSET)@(OPENXR) += src/modules/headset/headset_openxr.c SRC_@(HEADSET)@(OCULUS) += src/modules/headset/headset_oculus.c -SRC_@(HEADSET)@(VRAPI) += src/modules/headset/headset_oculus_mobile.c +SRC_@(HEADSET)@(VRAPI) += src/modules/headset/headset_vrapi.c SRC_@(HEADSET)@(WEBVR) += src/modules/headset/headset_webvr.c SRC_@(HEADSET)@(WEBXR) += src/modules/headset/headset_webxr.c SRC_@(HEADSET)@(LEAP) += src/modules/headset/headset_leap.c diff --git a/Tuprules.tup b/Tuprules.tup index 2f1890ca..960d7b93 100644 --- a/Tuprules.tup +++ b/Tuprules.tup @@ -37,7 +37,8 @@ CFLAGS_@(OPENVR) += -DLOVR_USE_OPENVR CFLAGS_@(OPENXR) += -DLOVR_USE_OPENXR CFLAGS_@(OCULUS) += -DLOVR_USE_OCULUS CFLAGS_@(OCULUS) += -I@(OCULUS_PATH)/LibOVR/Include -CFLAGS_@(VRAPI) += -DLOVR_USE_OCULUS_MOBILE +CFLAGS_@(VRAPI) += -DLOVR_USE_VRAPI +CFLAGS_@(VRAPI) += -I$(ROOT)/deps/VrApi/Include CFLAGS_@(WEBXR) += -DLOVR_USE_WEBXR CFLAGS_@(LEAP) += -DLOVR_USE_LEAP @@ -177,6 +178,7 @@ ifeq ($(PLATFORM),android) TOOLS = @(ANDROID_SDK)/sdk/build-tools/@(ANDROID_BUILD_TOOLS_VERSION) JAR = @(ANDROID_SDK)/sdk/platforms/android-@(ANDROID_API_VERSION)/android.jar GLUE = @(ANDROID_SDK)/sdk/ndk-bundle/sources/android/native_app_glue + VRAPI_LIB_PATH = $(ROOT)/deps/VrApi/Libs/Android/@(ANDROID_ABI)/Release CFLAGS += --target=@(ANDROID_TARGET) CFLAGS += -I$(GLUE) LDFLAGS += --target=@(ANDROID_TARGET) @@ -207,9 +209,12 @@ ifeq ($(PLATFORM),android) LDFLAGS_@(DATA) += -L$(ROOT)/build/lib_msdfgen -lmsdfgen LDFLAGS_@(PHYSICS) += -L$(ROOT)/build/ode -lode LDFLAGS_@(ENET) += -L$(ROOT)/build/enet -lenet + LDFLAGS_@(VRAPI) += -L$(VRAPI_LIB_PATH) + LDFLAGS_@(VRAPI) += -lvrapi LIBS_@(AUDIO) += $(ROOT)/build/openal/libopenal.*so* LIBS_@(PHYSICS) += $(ROOT)/build/ode/libode.so + LIBS_@(VRAPI) += $(VRAPI_LIB_PATH)/libvrapi.so endif endif diff --git a/src/api/l_headset.c b/src/api/l_headset.c index 3a883286..f1be6b15 100644 --- a/src/api/l_headset.c +++ b/src/api/l_headset.c @@ -19,6 +19,7 @@ StringEntry HeadsetDrivers[] = { [DRIVER_OCULUS_MOBILE] = ENTRY("oculusmobile"), [DRIVER_OPENVR] = ENTRY("openvr"), [DRIVER_OPENXR] = ENTRY("openxr"), + [DRIVER_VRAPI] = ENTRY("vrapi"), [DRIVER_WEBVR] = ENTRY("webvr"), [DRIVER_WEBXR] = ENTRY("webxr"), { 0 } diff --git a/src/core/os.h b/src/core/os.h index 4feab93c..b6408e93 100644 --- a/src/core/os.h +++ b/src/core/os.h @@ -48,7 +48,7 @@ typedef enum { BUTTON_RELEASED } ButtonAction; -typedef void (*windowCloseCallback)(void); +typedef void (*quitCallback)(void); typedef void (*windowFocusCallback)(bool focused); typedef void (*windowResizeCallback)(int width, int height); typedef void (*mouseButtonCallback)(MouseButton button, ButtonAction action); @@ -69,7 +69,7 @@ void lovrPlatformGetFramebufferSize(int* width, int* height); void lovrPlatformSetSwapInterval(int interval); void lovrPlatformSwapBuffers(void); void* lovrPlatformGetProcAddress(const char* function); -void lovrPlatformOnWindowClose(windowCloseCallback callback); +void lovrPlatformOnQuitRequest(quitCallback callback); void lovrPlatformOnWindowFocus(windowFocusCallback callback); void lovrPlatformOnWindowResize(windowResizeCallback callback); void lovrPlatformOnMouseButton(mouseButtonCallback callback); diff --git a/src/core/os_android.c b/src/core/os_android.c index 54075c45..ce7db410 100644 --- a/src/core/os_android.c +++ b/src/core/os_android.c @@ -7,28 +7,48 @@ #include #include +// The activity is considered ready if it's resumed and there's an active window. This is just an +// artifact of how Oculus' app model works and could be the wrong abstraction, feel free to change. +typedef void (*activeCallback)(bool active); + #ifndef LOVR_USE_OCULUS_MOBILE static struct { + struct android_app* app; + ANativeWindow* window; + bool resumed; + JNIEnv* jni; EGLDisplay display; EGLContext context; EGLSurface surface; + activeCallback onActive; + quitCallback onQuit; } state; -static JavaVM* lovrJavaVM; -static JNIEnv* lovrJNIEnv; - int main(int argc, char** argv); static void onAppCmd(struct android_app* app, int32_t cmd) { - // pause, resume, events, etc. + bool wasActive = state.window && state.resumed; + + switch (cmd) { + case APP_CMD_RESUME: state.resumed = true; break; + case APP_CMD_PAUSE: state.resumed = false; break; + case APP_CMD_INIT_WINDOW: state.window = app->window; break; + case APP_CMD_TERM_WINDOW: state.window = NULL; break; + default: break; + } + + bool active = state.window && state.resumed; + if (state.onActive && wasActive != active) { + state.onActive(active); + } } void android_main(struct android_app* app) { - lovrJavaVM = app->activity->vm; - (*lovrJavaVM)->AttachCurrentThread(lovrJavaVM, &lovrJNIEnv, NULL); + state.app = app; + (*app->activity->vm)->AttachCurrentThread(app->activity->vm, &state.jni, NULL); app->onAppCmd = onAppCmd; main(0, NULL); - (*lovrJavaVM)->DetachCurrentThread(lovrJavaVM); + (*app->activity->vm)->DetachCurrentThread(app->activity->vm); } #endif @@ -42,8 +62,8 @@ void lovrPlatformDestroy() { if (state.surface) eglDestroySurface(state.display, state.surface); if (state.context) eglDestroyContext(state.display, state.context); if (state.display) eglTerminate(state.display); - memset(&state, 0, sizeof(state)); #endif + memset(&state, 0, sizeof(state)); } const char* lovrPlatformGetName() { @@ -79,7 +99,16 @@ void lovrPlatformSleep(double seconds) { #endif void lovrPlatformPollEvents() { - // TODO +#ifndef LOVR_USE_OCULUS_MOBILE + int events; + struct android_poll_source* source; + bool active = state.window && state.resumed; + while (ALooper_pollAll(active ? 0 : 0, NULL, &events, (void**) &source) >= 0) { + if (source) { + source->process(state.app, source); + } + } +#endif } void lovrPlatformOpenConsole() { @@ -197,8 +226,8 @@ void* lovrPlatformGetProcAddress(const char* function) { return (void*) eglGetProcAddress(function); } -void lovrPlatformOnWindowClose(windowCloseCallback callback) { - // +void lovrPlatformOnQuitRequest(quitCallback callback) { + state.onQuit = callback; } void lovrPlatformOnWindowFocus(windowFocusCallback callback) { @@ -232,3 +261,31 @@ bool lovrPlatformIsMouseDown(MouseButton button) { bool lovrPlatformIsKeyDown(KeyCode key) { return false; } + +void lovrPlatformOnActive(activeCallback callback) { + state.onActive = callback; +} + +struct ANativeActivity* lovrPlatformGetActivity() { + return state.app->activity; +} + +ANativeWindow* lovrPlatformGetNativeWindow() { + return state.window; +} + +JNIEnv* lovrPlatformGetJNI() { + return state.jni; +} + +EGLDisplay lovrPlatformGetEGLDisplay() { + return state.display; +} + +EGLContext lovrPlatformGetEGLContext() { + return state.context; +} + +EGLSurface lovrPlatformGetEGLSurface() { + return state.surface; +} diff --git a/src/modules/graphics/graphics.c b/src/modules/graphics/graphics.c index 984ce43f..0291edac 100644 --- a/src/modules/graphics/graphics.c +++ b/src/modules/graphics/graphics.c @@ -161,7 +161,7 @@ static void gammaCorrect(Color* color) { color->b = lovrMathGammaToLinear(color->b); } -static void onCloseWindow(void) { +static void onQuitRequest(void) { lovrEventPush((Event) { .type = EVENT_QUIT, .data.quit = { .exitCode = 0 } }); } @@ -223,7 +223,7 @@ void lovrGraphicsPresent() { void lovrGraphicsCreateWindow(WindowFlags* flags) { lovrAssert(!state.initialized, "Window is already created"); lovrAssert(lovrPlatformCreateWindow(flags), "Could not create window"); - lovrPlatformOnWindowClose(onCloseWindow); + lovrPlatformOnQuitRequest(onQuitRequest); lovrPlatformOnWindowResize(onResizeWindow); lovrPlatformGetFramebufferSize(&state.width, &state.height); lovrGpuInit(lovrPlatformGetProcAddress); diff --git a/src/modules/headset/headset.c b/src/modules/headset/headset.c index 1b0894b9..0d844f42 100644 --- a/src/modules/headset/headset.c +++ b/src/modules/headset/headset.c @@ -33,6 +33,9 @@ bool lovrHeadsetInit(HeadsetDriver* drivers, size_t count, float offset, uint32_ #ifdef LOVR_USE_OPENXR case DRIVER_OPENXR: interface = &lovrHeadsetOpenXRDriver; break; #endif +#ifdef LOVR_USE_VRAPI + case DRIVER_VRAPI: interface = &lovrHeadsetVrApiDriver; break; +#endif #ifdef LOVR_USE_WEBVR case DRIVER_WEBVR: interface = &lovrHeadsetWebVRDriver; break; #endif diff --git a/src/modules/headset/headset.h b/src/modules/headset/headset.h index 0fb2abf2..e58fccce 100644 --- a/src/modules/headset/headset.h +++ b/src/modules/headset/headset.h @@ -16,6 +16,7 @@ typedef enum { DRIVER_OCULUS_MOBILE, DRIVER_OPENVR, DRIVER_OPENXR, + DRIVER_VRAPI, DRIVER_WEBVR, DRIVER_WEBXR } HeadsetDriver; @@ -116,6 +117,7 @@ typedef struct HeadsetInterface { extern HeadsetInterface lovrHeadsetOculusDriver; extern HeadsetInterface lovrHeadsetOpenVRDriver; extern HeadsetInterface lovrHeadsetOpenXRDriver; +extern HeadsetInterface lovrHeadsetVrApiDriver; extern HeadsetInterface lovrHeadsetWebVRDriver; extern HeadsetInterface lovrHeadsetWebXRDriver; extern HeadsetInterface lovrHeadsetDesktopDriver; diff --git a/src/modules/headset/headset_vrapi.c b/src/modules/headset/headset_vrapi.c new file mode 100644 index 00000000..8de7b400 --- /dev/null +++ b/src/modules/headset/headset_vrapi.c @@ -0,0 +1,456 @@ +#include "headset/headset.h" +#include "graphics/canvas.h" +#include "graphics/graphics.h" +#include "core/maf.h" +#include "core/os.h" +#include "core/ref.h" +#include +#include +#include +#include +#include +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc11-extensions" +#pragma clang diagnostic ignored "-Wgnu-empty-initializer" +#pragma clang diagnostic ignored "-Wpedantic" +#include +#include +#include +#pragma clang diagnostic pop + +#define GL_SRGB8_ALPHA8 0x8C43 + +// Private platform functions +void lovrPlatformOnActive(void (*callback)(bool active)); +JNIEnv* lovrPlatformGetJNI(void); +struct ANativeActivity* lovrPlatformGetActivity(void); +ANativeWindow* lovrPlatformGetNativeWindow(void); +EGLDisplay lovrPlatformGetEGLDisplay(void); +EGLContext lovrPlatformGetEGLContext(void); + +static struct { + ovrJava java; + ovrMobile* session; + ovrDeviceType deviceType; + uint64_t frameIndex; + float offset; + uint32_t msaa; + ovrVector3f* rawBoundaryPoints; + float* boundaryPoints; + uint32_t boundaryPointCount; + ovrTextureSwapChain* swapchain; + Canvas* canvases[3]; + ovrInputTrackedRemoteCapabilities controllerInfo[2]; + ovrInputStateTrackedRemote controllers[2]; + ovrInputStateHand hands[2]; +} state; + +static void onActive(bool active) { + if (!state.session && active) { + ovrModeParms config = vrapi_DefaultModeParms(&state.java); + config.Flags &= ~VRAPI_MODE_FLAG_RESET_WINDOW_FULLSCREEN; + config.Flags |= VRAPI_MODE_FLAG_NATIVE_WINDOW; + config.Flags |= VRAPI_MODE_FLAG_FRONT_BUFFER_SRGB; + config.Display = (size_t) lovrPlatformGetEGLDisplay(); + config.WindowSurface = (size_t) lovrPlatformGetNativeWindow(); + config.ShareContext = (size_t) lovrPlatformGetEGLContext(); + state.session = vrapi_EnterVrMode(&config); + state.frameIndex = 0; + } else if (state.session && !active) { + vrapi_LeaveVrMode(state.session); + state.session = NULL; + } +} + +static bool vrapi_init(float offset, uint32_t msaa) { + ANativeActivity* activity = lovrPlatformGetActivity(); + state.java.Vm = activity->vm; + state.java.ActivityObject = activity->clazz; + state.java.Env = lovrPlatformGetJNI(); + state.offset = offset; + state.msaa = msaa; + const ovrInitParms config = vrapi_DefaultInitParms(&state.java); + if (vrapi_Initialize(&config) != VRAPI_INITIALIZE_SUCCESS) { + return false; + } + state.deviceType = vrapi_GetSystemPropertyInt(&state.java, VRAPI_SYS_PROP_DEVICE_TYPE); + lovrPlatformOnActive(onActive); + return true; +} + +static void vrapi_destroy() { + vrapi_DestroyTextureSwapChain(state.swapchain); + vrapi_Shutdown(); + for (uint32_t i = 0; i < 3; i++) { + lovrRelease(Canvas, state.canvases[i]); + } + memset(&state, 0, sizeof(state)); +} + +static bool vrapi_getName(char* buffer, size_t length) { + switch (state.deviceType) { + case VRAPI_DEVICE_TYPE_OCULUSGO: strncpy(buffer, "Oculus Go", length - 1); break; + case VRAPI_DEVICE_TYPE_OCULUSQUEST: strncpy(buffer, "Oculus Quest", length - 1); break; + default: return false; + } + buffer[length - 1] = '\0'; + return true; +} + +static HeadsetOrigin vrapi_getOriginType(void) { + return vrapi_GetTrackingSpace(state.session) == VRAPI_TRACKING_SPACE_LOCAL_FLOOR ? ORIGIN_FLOOR : ORIGIN_HEAD; +} + +static void vrapi_getDisplayDimensions(uint32_t* width, uint32_t* height) { + *width = (uint32_t) vrapi_GetSystemPropertyInt(&state.java, VRAPI_SYS_PROP_SUGGESTED_EYE_TEXTURE_WIDTH); + *height = (uint32_t) vrapi_GetSystemPropertyInt(&state.java, VRAPI_SYS_PROP_SUGGESTED_EYE_TEXTURE_HEIGHT); +} + +static float vrapi_getDisplayFrequency() { + return vrapi_GetSystemPropertyFloat(&state.java, VRAPI_SYS_PROP_DISPLAY_REFRESH_RATE); +} + +static const float* vrapi_getDisplayMask(uint32_t* count) { + *count = 0; + return NULL; +} + +static double vrapi_getDisplayTime() { + return vrapi_GetPredictedDisplayTime(state.session, state.frameIndex); +} + +static uint32_t vrapi_getViewCount() { + return 2; +} + +static bool vrapi_getViewPose(uint32_t view, float* position, float* orientation) { + if (view >= 2) return false; + ovrTracking2 tracking = vrapi_GetPredictedTracking2(state.session, vrapi_getDisplayTime()); + float transform[16]; + mat4_init(transform, (float*) &tracking.Eye[view].ViewMatrix); + mat4_invert(transform); + mat4_getPosition(transform, position); + mat4_getOrientation(transform, orientation); + uint32_t mask = VRAPI_TRACKING_STATUS_POSITION_VALID | VRAPI_TRACKING_STATUS_ORIENTATION_VALID; + return (tracking.Status & mask) == mask; +} + +static bool vrapi_getViewAngles(uint32_t view, float* left, float* right, float* up, float* down) { + if (view >= 2) return false; + ovrTracking2 tracking = vrapi_GetPredictedTracking2(state.session, vrapi_getDisplayTime()); + ovrMatrix4f_ExtractFov(&tracking.Eye[view].ProjectionMatrix, left, right, up, down); + uint32_t mask = VRAPI_TRACKING_STATUS_POSITION_VALID | VRAPI_TRACKING_STATUS_ORIENTATION_VALID; + return (tracking.Status & mask) == mask; +} + +static void vrapi_getClipDistance(float* clipNear, float* clipFar) { + // Unsupported +} + +static void vrapi_setClipDistance(float clipNear, float clipFar) { + // Unsupported +} + +static void vrapi_getBoundsDimensions(float* width, float* depth) { + ovrPosef pose; + ovrVector3f scale; + if (vrapi_GetBoundaryOrientedBoundingBox(state.session, &pose, &scale) == ovrSuccess) { + *width = scale.x * 2.f; + *depth = scale.z * 2.f; + } else { + *width = 0.f; + *depth = 0.f; + } +} + +static const float* vrapi_getBoundsGeometry(uint32_t* count) { + if (vrapi_GetBoundaryGeometry(state.session, 0, count, NULL) != ovrSuccess) { + return NULL; + } + + if (*count > state.boundaryPointCount) { + state.boundaryPointCount = *count; + state.boundaryPoints = realloc(state.boundaryPoints, 4 * *count * sizeof(float)); + state.rawBoundaryPoints = realloc(state.rawBoundaryPoints, *count * sizeof(ovrVector3f)); + lovrAssert(state.boundaryPoints && state.rawBoundaryPoints, "Out of memory"); + } + + if (vrapi_GetBoundaryGeometry(state.session, state.boundaryPointCount, count, state.rawBoundaryPoints) != ovrSuccess) { + return NULL; + } + + for (uint32_t i = 0; i < *count; i++) { + state.boundaryPoints[4 * i + 0] = state.rawBoundaryPoints[i].x; + state.boundaryPoints[4 * i + 1] = state.rawBoundaryPoints[i].y; + state.boundaryPoints[4 * i + 2] = state.rawBoundaryPoints[i].z; + } + + return state.boundaryPoints; +} + +static bool getTracking(Device device, ovrTracking* tracking) { + if (device == DEVICE_HEAD) { + *tracking = vrapi_GetPredictedTracking(state.session, vrapi_getDisplayTime()); + return true; + } else if (device == DEVICE_HAND_LEFT || device == DEVICE_HAND_RIGHT) { + ovrInputCapabilityHeader* header = &state.controllerInfo[device - DEVICE_HAND_LEFT].Header; + if (header->Type == ovrControllerType_TrackedRemote) { + return vrapi_GetInputTrackingState(state.session, header->DeviceID, vrapi_getDisplayTime(), tracking) == ovrSuccess; + } + } + + return false; +} + +static bool vrapi_getPose(Device device, float* position, float* orientation) { + ovrTracking tracking; + if (!getTracking(device, &tracking)) { + return false; + } + + ovrPosef* pose = &tracking.HeadPose.Pose; + vec3_set(position, pose->Position.x, pose->Position.y + state.offset, pose->Position.z); + quat_init(orientation, &pose->Orientation.x); + uint32_t mask = VRAPI_TRACKING_STATUS_POSITION_VALID | VRAPI_TRACKING_STATUS_ORIENTATION_VALID; + return (tracking.Status & mask) == mask; +} + +static bool vrapi_getVelocity(Device device, float* velocity, float* angularVelocity) { + ovrTracking tracking; + if (!getTracking(device, &tracking)) { + return false; + } + + ovrVector3f* linear = &tracking.HeadPose.LinearVelocity; + ovrVector3f* angular = &tracking.HeadPose.AngularVelocity; + vec3_set(velocity, linear->x, linear->y, linear->z); + vec3_set(angularVelocity, angular->x, angular->y, angular->z); + uint32_t mask = VRAPI_TRACKING_STATUS_POSITION_VALID | VRAPI_TRACKING_STATUS_ORIENTATION_VALID; + return (tracking.Status & mask) == mask; +} + +static bool vrapi_isDown(Device device, DeviceButton button, bool* down, bool* changed) { + if (device == DEVICE_HEAD && button == BUTTON_PROXIMITY) { + *down = vrapi_GetSystemStatusInt(&state.java, VRAPI_SYS_STATUS_MOUNTED); + return true; + } + + if (device != DEVICE_HAND_LEFT && device != DEVICE_HAND_RIGHT) { + return false; + } + + if (state.controllerInfo[device - DEVICE_HAND_LEFT].Header.Type != ovrControllerType_TrackedRemote) { + return false; + } + + ovrInputStateTrackedRemote* input = &state.controllers[device - DEVICE_HAND_LEFT]; + + if (state.deviceType == VRAPI_DEVICE_TYPE_OCULUSGO) { + switch (button) { + case BUTTON_TRIGGER: *down = input->Buttons & ovrButton_Trigger; return true; + case BUTTON_TOUCHPAD: *down = input->Buttons & ovrButton_Enter; return true; + case BUTTON_MENU: *down = input->Buttons & ovrButton_Back; return true; + default: return false; + } + } else if (state.deviceType == VRAPI_DEVICE_TYPE_OCULUSQUEST) { + switch (button) { + case BUTTON_TRIGGER: *down = input->Buttons & ovrButton_Trigger; return true; + case BUTTON_THUMBSTICK: *down = input->Buttons & ovrButton_Joystick; return true; + case BUTTON_GRIP: *down = input->Buttons & ovrButton_GripTrigger; return true; + case BUTTON_MENU: *down = input->Buttons & ovrButton_Enter; return true; + case BUTTON_A: *down = input->Buttons & ovrButton_A; return true; + case BUTTON_B: *down = input->Buttons & ovrButton_B; return true; + case BUTTON_X: *down = input->Buttons & ovrButton_X; return true; + case BUTTON_Y: *down = input->Buttons & ovrButton_Y; return true; + default: return false; + } + } + + return false; +} + +static bool vrapi_isTouched(Device device, DeviceButton button, bool* touched) { + if (device != DEVICE_HAND_LEFT && device != DEVICE_HAND_RIGHT) { + return false; + } + + if (state.controllerInfo[device - DEVICE_HAND_LEFT].Header.Type != ovrControllerType_TrackedRemote) { + return false; + } + + ovrInputStateTrackedRemote* input = &state.controllers[device - DEVICE_HAND_LEFT]; + + if (state.deviceType == VRAPI_DEVICE_TYPE_OCULUSGO) { + switch (button) { + case BUTTON_TOUCHPAD: return input->Touches & ovrTouch_TrackPad; return true; + default: return false; + } + } else if (state.deviceType == VRAPI_DEVICE_TYPE_OCULUSQUEST) { + switch (button) { + case BUTTON_TRIGGER: *touched = input->Touches & ovrTouch_IndexTrigger; return true; + case BUTTON_THUMBSTICK: *touched = input->Touches & ovrTouch_Joystick; return true; + case BUTTON_A: *touched = input->Touches & ovrTouch_A; return true; + case BUTTON_B: *touched = input->Touches & ovrTouch_B; return true; + case BUTTON_X: *touched = input->Touches & ovrTouch_X; return true; + case BUTTON_Y: *touched = input->Touches & ovrTouch_Y; return true; + default: return false; + } + } + + return false; +} + +static bool vrapi_getAxis(Device device, DeviceAxis axis, float* value) { + if (device != DEVICE_HAND_LEFT && device != DEVICE_HAND_RIGHT) { + return false; + } + + ovrInputStateTrackedRemote* input = &state.controllers[device - DEVICE_HAND_LEFT]; + + if (state.deviceType == VRAPI_DEVICE_TYPE_OCULUSGO) { + switch (axis) { + case AXIS_TOUCHPAD: + value[0] = (input->TrackpadPosition.x - 160.f) / 160.f; + value[1] = (input->TrackpadPosition.y - 160.f) / 160.f; + return true; + case AXIS_TRIGGER: value[0] = (input->Buttons & ovrButton_Trigger) ? 1.f : 0.f; return true; + default: return false; + } + } else if (state.deviceType == VRAPI_DEVICE_TYPE_OCULUSQUEST) { + switch (axis) { + case AXIS_THUMBSTICK: + value[0] = input->TrackpadPosition.x; + value[1] = input->TrackpadPosition.y; + break; + case AXIS_TRIGGER: value[0] = input->IndexTrigger; break; + case AXIS_GRIP: value[0] = input->GripTrigger; break; + default: return false; + } + } + + return false; +} + +static bool vrapi_vibrate(Device device, float strength, float duration, float frequency) { + return false; +} + +static struct ModelData* vrapi_newModelData(Device device) { + return NULL; +} + +static void vrapi_renderTo(void (*callback)(void*), void* userdata) { + if (!state.swapchain) { + CanvasFlags flags = { + .depth.enabled = true, + .depth.readable = false, + .depth.format = FORMAT_D24S8, + .msaa = state.msaa, + .stereo = true, + .mipmaps = false + }; + + uint32_t width, height; + vrapi_getDisplayDimensions(&width, &height); + vrapi_CreateTextureSwapChain3(VRAPI_TEXTURE_TYPE_2D_ARRAY, GL_SRGB8_ALPHA8, width, height, 1, 3); + + for (uint32_t i = 0; i < 3; i++) { + state.canvases[i] = lovrCanvasCreate(width, height, flags); + uint32_t handle = vrapi_GetTextureSwapChainHandle(state.swapchain, i); + Texture* texture = lovrTextureCreateFromHandle(handle, TEXTURE_ARRAY, 2); + lovrCanvasSetAttachments(state.canvases[i], &(Attachment) { .texture = texture }, 1); + lovrRelease(Texture, texture); + } + } + + double displayTime = vrapi_getDisplayTime(); + ovrTracking2 tracking = vrapi_GetPredictedTracking2(state.session, displayTime); + + ovrLayerProjection2 layer = vrapi_DefaultLayerProjection2(); + layer.HeadPose = tracking.HeadPose; + + Camera camera; + camera.stereo = true; + camera.canvas = state.canvases[state.frameIndex % 3]; + + for (uint32_t i = 0; i < 2; i++) { + ovrMatrix4f* viewMatrix = &tracking.Eye[i].ViewMatrix; + ovrMatrix4f* projection = &tracking.Eye[i].ProjectionMatrix; + + layer.Textures[i].ColorSwapChain = state.swapchain; + layer.Textures[i].SwapChainIndex = state.frameIndex % 3; + layer.Textures[i].TexCoordsFromTanAngles = ovrMatrix4f_TanAngleMatrixFromProjection(projection); + + mat4_init(camera.viewMatrix[i], &viewMatrix->M[0][0]); + mat4_init(camera.projection[i], &projection->M[0][0]); + mat4_transpose(camera.viewMatrix[i]); + mat4_transpose(camera.projection[i]); + } + + lovrGraphicsSetCamera(&camera, true); + callback(userdata); + lovrGraphicsSetCamera(NULL, false); + + ovrSubmitFrameDescription2 frame = { + .SwapInterval = 1, + .FrameIndex = state.frameIndex, + .DisplayTime = displayTime, + .LayerCount = 1, + .Layers = (const ovrLayerHeader2*[]) { &layer.Header } + }; + + vrapi_SubmitFrame2(state.session, &frame); + state.frameIndex++; +} + +static void vrapi_update(float dt) { + state.controllerInfo[0].Header.Type = ovrControllerType_None; + state.controllerInfo[1].Header.Type = ovrControllerType_None; + + ovrInputCapabilityHeader header; + for (uint32_t i = 0; vrapi_EnumerateInputDevices(state.session, i, &header) == ovrSuccess; i++) { + if (header.Type == ovrControllerType_TrackedRemote) { + ovrInputTrackedRemoteCapabilities info; + info.Header = header; + vrapi_GetInputDeviceCapabilities(state.session, &info.Header); + Device device = info.ControllerCapabilities & ovrControllerCaps_LeftHand ? DEVICE_HAND_LEFT : DEVICE_HAND_RIGHT; + state.controllerInfo[device] = info; + vrapi_GetCurrentInputState(state.session, header.DeviceID, &state.controllers[device].Header); + } else if (header.Type == ovrControllerType_Hand) { + ovrInputHandCapabilities info; + info.Header = header; + vrapi_GetInputDeviceCapabilities(state.session, &info.Header); + Device device = info.HandCapabilities & ovrHandCaps_LeftHand ? DEVICE_HAND_LEFT : DEVICE_HAND_RIGHT; + state.controllerInfo[device].Header.Type = header.Type; + vrapi_GetCurrentInputState(state.session, header.DeviceID, &state.controllers[device].Header); + } + } +} + +HeadsetInterface lovrHeadsetVrApiDriver = { + .driverType = DRIVER_VRAPI, + .init = vrapi_init, + .destroy = vrapi_destroy, + .getName = vrapi_getName, + .getOriginType = vrapi_getOriginType, + .getDisplayDimensions = vrapi_getDisplayDimensions, + .getDisplayFrequency = vrapi_getDisplayFrequency, + .getDisplayMask = vrapi_getDisplayMask, + .getDisplayTime = vrapi_getDisplayTime, + .getViewCount = vrapi_getViewCount, + .getViewPose = vrapi_getViewPose, + .getViewAngles = vrapi_getViewAngles, + .getClipDistance = vrapi_getClipDistance, + .setClipDistance = vrapi_setClipDistance, + .getBoundsDimensions = vrapi_getBoundsDimensions, + .getBoundsGeometry = vrapi_getBoundsGeometry, + .getPose = vrapi_getPose, + .getVelocity = vrapi_getVelocity, + .isDown = vrapi_isDown, + .isTouched = vrapi_isTouched, + .getAxis = vrapi_getAxis, + .vibrate = vrapi_vibrate, + .newModelData = vrapi_newModelData, + .renderTo = vrapi_renderTo, + .update = vrapi_update +}; diff --git a/src/resources/LoadLibraries.java b/src/resources/LoadLibraries.java index ef23055e..89e64b58 100644 --- a/src/resources/LoadLibraries.java +++ b/src/resources/LoadLibraries.java @@ -5,6 +5,6 @@ import android.app.NativeActivity; public class LoadLibraries extends NativeActivity { static { System.loadLibrary("lovr"); - // System.loadLibrary("vrapi"); + System.loadLibrary("vrapi"); } } diff --git a/src/resources/boot.lua b/src/resources/boot.lua index f8eb8478..dbcba0b2 100644 --- a/src/resources/boot.lua +++ b/src/resources/boot.lua @@ -96,7 +96,7 @@ function lovr.boot() timer = true }, headset = { - drivers = { 'leap', 'openxr', 'oculus', 'oculusmobile', 'openvr', 'webxr', 'webvr', 'desktop' }, + drivers = { 'leap', 'openxr', 'oculus', 'vrapi', 'oculusmobile', 'openvr', 'webxr', 'webvr', 'desktop' }, offset = 1.7, msaa = 4 },