diff --git a/.gitignore b/.gitignore index 3c29d361..9feb25c2 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,4 @@ bin deps/VrApi deps/pico deps/openxr +deps/OpenXR-Oculus diff --git a/CMakeLists.txt b/CMakeLists.txt index 52748f20..fcc2b67f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -234,10 +234,19 @@ endif() # OpenXR # Currently, to use OpenXR, add the OpenXR SDK to the deps folder: # git submodule add https://github.com/khronosgroup/openxr-sdk deps/openxr +# On Android, download the Oculus OpenXR loader and place the "OpenXR" folder at "deps/OpenXR-Oculus" if(LOVR_ENABLE_HEADSET AND LOVR_USE_OPENXR) include_directories(deps/openxr/include) - add_subdirectory(deps/openxr openxr) - set(LOVR_OPENXR openxr_loader) + if(ANDROID) + set(LOVR_OPENXR_OCULUS_PATH "${CMAKE_CURRENT_SOURCE_DIR}/deps/OpenXR-Oculus" CACHE STRING "The path to the Oculus OpenXR loader") + add_library(openxr_loader SHARED IMPORTED) + include_directories("${LOVR_OPENXR_OCULUS_PATH}/Include") + set_target_properties(openxr_loader PROPERTIES IMPORTED_LOCATION "${LOVR_OPENXR_OCULUS_PATH}/Libs/Android/${ANDROID_ABI}/Release/libopenxr_loader.so") + set(LOVR_OPENXR openxr_loader) + else() + add_subdirectory(deps/openxr openxr) + set(LOVR_OPENXR openxr_loader) + endif() endif() # Oculus SDK -- expects Oculus SDK 1.26.0 or later @@ -614,15 +623,24 @@ elseif(ANDROID) # - Figure out which Java class (Activity) and AndroidManifest.xml to use # - Oculus uses the regular android os layer, pico implements its own in the headset backend # - Some of the Pico SDK is in a jar that has to be added to the classpath and dx invocation - # TODO error (probably way earlier) if both USE_VRAPI and USE_PICO aren't defined - if(LOVR_USE_VRAPI) - set(ANDROID_FLAVOR "vrapi") + # TODO error (probably way earlier) if no headset API is defined, since everything will break + if(LOVR_USE_OPENXR) + set(MANIFEST "oculus") + set(ACTIVITY "openxr") + target_sources(lovr PRIVATE src/core/os_android.c) + get_target_property(OPENXR_LIB ${LOVR_OPENXR} IMPORTED_LOCATION) + file(COPY ${OPENXR_LIB} DESTINATION lib/${ANDROID_ABI}) + set(ANDROID_CLASSPATH "${ANDROID_JAR}") + elseif(LOVR_USE_VRAPI) + set(MANIFEST "oculus") + set(ACTIVITY "vrapi") target_sources(lovr PRIVATE src/core/os_android.c) get_target_property(VRAPI_LIB ${LOVR_VRAPI} IMPORTED_LOCATION) file(COPY ${VRAPI_LIB} DESTINATION lib/${ANDROID_ABI}) set(ANDROID_CLASSPATH "${ANDROID_JAR}") elseif(LOVR_USE_PICO) - set(ANDROID_FLAVOR "pico") + set(MANIFEST "pico") + set(ACTIVITY "pico") get_target_property(PICO_LIB ${LOVR_PICO} IMPORTED_LOCATION) file(COPY ${PICO_LIB} DESTINATION lib/${ANDROID_ABI}) set(EXTRA_JAR "${LOVR_PICO_PATH}/classes.jar") @@ -633,7 +651,7 @@ elseif(ANDROID) endif() endif() - set(ANDROID_MANIFEST "${CMAKE_CURRENT_SOURCE_DIR}/src/resources/AndroidManifest_${ANDROID_FLAVOR}.xml" CACHE STRING "The AndroidManifest.xml file to use") + set(ANDROID_MANIFEST "${CMAKE_CURRENT_SOURCE_DIR}/src/resources/AndroidManifest_${MANIFEST}.xml" CACHE STRING "The AndroidManifest.xml file to use") # Make an apk add_custom_command( @@ -642,7 +660,7 @@ elseif(ANDROID) BYPRODUCTS lovr.apk WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMAND ${CMAKE_COMMAND} -E copy "${ANDROID_MANIFEST}" AndroidManifest.xml - COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/src/resources/Activity_${ANDROID_FLAVOR}.java Activity.java + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/src/resources/Activity_${ACTIVITY}.java Activity.java COMMAND ${Java_JAVAC_EXECUTABLE} -classpath "${ANDROID_CLASSPATH}" -d . Activity.java COMMAND ${ANDROID_TOOLS}/dx --dex --output classes.dex ${EXTRA_JAR} org/lovr/app/Activity.class COMMAND diff --git a/Tuprules.tup b/Tuprules.tup index b44b504a..e9c13361 100644 --- a/Tuprules.tup +++ b/Tuprules.tup @@ -182,6 +182,7 @@ ifeq ($(PLATFORM),android) TOOLS = @(ANDROID_SDK)/sdk/build-tools/@(ANDROID_BUILD_TOOLS_VERSION) ANDROID_JAR = @(ANDROID_SDK)/sdk/platforms/android-@(ANDROID_VERSION)/android.jar GLUE = @(ANDROID_SDK)/sdk/ndk-bundle/sources/android/native_app_glue + OPENXR_LIB_PATH = $(DEPS)/OpenXR-Oculus/Libs/Android/arm64-v8a/Release VRAPI_LIB_PATH = $(DEPS)/VrApi/Libs/Android/arm64-v8a/Release PICO_LIB_PATH = $(DEPS)/pico/jni/arm64-v8a CFLAGS += --target=aarch64-linux-android@(ANDROID_VERSION) @@ -195,11 +196,13 @@ ifeq ($(PLATFORM),android) PREFIX = $(LIB)/lib SUFFIX = .so CFLAGS_@(GRAPHICS) += -DLOVR_GLES + ACTIVITY_@(OPENXR) = Activity_openxr ACTIVITY_@(VRAPI) = Activity_vrapi ACTIVITY_@(PICO) = Activity_pico ifeq (@(ANDROID_MANIFEST),) - ANDROID_MANIFEST_@(VRAPI) = src/resources/AndroidManifest_vrapi.xml + ANDROID_MANIFEST_@(OPENXR) = src/resources/AndroidManifest_oculus.xml + ANDROID_MANIFEST_@(VRAPI) = src/resources/AndroidManifest_oculus.xml ANDROID_MANIFEST_@(PICO) = src/resources/AndroidManifest_pico.xml else ANDROID_MANIFEST_y = @(ANDROID_MANIFEST) @@ -238,17 +241,20 @@ ifeq ($(PLATFORM),android) CFLAGS_@(DATA) += -I$(DEPS)/msdfgen CFLAGS_@(PHYSICS) += -I$(DEPS)/ode/include -I$(BUILD)/ode/include CFLAGS_@(ENET) += -I$(DEPS)/enet/include + CFLAGS_@(OPENXR) += -I$(DEPS)/OpenXR-Oculus/Include CFLAGS_@(VRAPI) += -I$(DEPS)/VrApi/Include LDFLAGS_@(AUDIO) += -L$(BUILD)/$(LIB) -lopenal LDFLAGS_@(DATA) += -L$(BUILD)/lib_msdfgen -lmsdfgen LDFLAGS_@(PHYSICS) += -L$(BUILD)/$(LIB) -lode LDFLAGS_@(ENET) += -L$(BUILD)/enet -lenet + LDFLAGS_@(OPENXR) += -L$(OPENXR_LIB_PATH) -lopenxr_loader LDFLAGS_@(VRAPI) += -L$(VRAPI_LIB_PATH) -lvrapi LDFLAGS_@(PICO) += -L$(PICO_LIB_PATH) -lPvr_NativeSDK LIBS_@(AUDIO) += $(BUILD)/$(LIB)/libopenal.*so* LIBS_@(PHYSICS) += $(BUILD)/$(LIB)/libode.so + LIBS_@(OPENXR) += $(OPENXR_LIB_PATH)/libopenxr_loader.so LIBS_@(VRAPI) += $(VRAPI_LIB_PATH)/libvrapi.so LIBS_@(PICO) += $(PICO_LIB_PATH)/libPvr_NativeSDK.so endif diff --git a/deps/openxr b/deps/openxr index 69bb1508..e3a4e41d 160000 --- a/deps/openxr +++ b/deps/openxr @@ -1 +1 @@ -Subproject commit 69bb1508eae5b3c5be149d3518e772443db6a077 +Subproject commit e3a4e41d61544d8e2eba73f00da99b6818ec472b diff --git a/src/modules/headset/headset_openxr.c b/src/modules/headset/headset_openxr.c index b73dfbbc..411df9fa 100644 --- a/src/modules/headset/headset_openxr.c +++ b/src/modules/headset/headset_openxr.c @@ -13,6 +13,7 @@ #include #elif defined(__ANDROID__) #define XR_USE_PLATFORM_ANDROID + #include #include #include #endif @@ -20,12 +21,15 @@ #define XR_USE_GRAPHICS_API_OPENGL #define GRAPHICS_EXTENSION "XR_KHR_opengl_enable" #elif defined(LOVR_GLES) - #define XR_USE_GRAPHICS_API_OPENGLES + #define XR_USE_GRAPHICS_API_OPENGL_ES #define GRAPHICS_EXTENSION "XR_KHR_opengl_es_enable" #endif #define XR_NO_PROTOTYPES #include #include +#ifdef __ANDROID__ +#include +#endif #include "resources/openxr_actions.h" #define XR(f) handleResult(f, __FILE__, __LINE__) @@ -38,9 +42,10 @@ HANDLE lovrPlatformGetWindow(void); HGLRC lovrPlatformGetContext(void); #elif defined(__ANDROID__) -EGLDisplay lovrPlatformGetEGLDisplay(); -EGLContext lovrPlatformGetEGLContext(); -EGLConfig lovrPlatformGetEGLConfig(); +struct ANativeActivity* lovrPlatformGetActivity(void); +EGLDisplay lovrPlatformGetEGLDisplay(void); +EGLContext lovrPlatformGetEGLContext(void); +EGLConfig lovrPlatformGetEGLConfig(void); #endif #define XR_FOREACH(X)\ @@ -146,6 +151,25 @@ static void openxr_destroy(); static bool openxr_init(float offset, uint32_t msaa) { state.msaa = msaa; +#ifdef __ANDROID__ + static PFN_xrInitializeLoaderKHR xrInitializeLoaderKHR; + XR_LOAD(xrInitializeLoaderKHR); + if (!xrInitializeLoaderKHR) { + return false; + } + + ANativeActivity* activity = lovrPlatformGetActivity(); + XrLoaderInitInfoAndroidKHR loaderInfo = { + .type = XR_TYPE_LOADER_INIT_INFO_ANDROID_KHR, + .applicationVM = activity->vm, + .applicationContext = activity->clazz + }; + + if (XR_FAILED(xrInitializeLoaderKHR((XrLoaderInitInfoBaseHeaderKHR*) &loaderInfo))) { + return false; + } +#endif + { // Instance uint32_t extensionCount; xrEnumerateInstanceExtensionProperties(NULL, 0, &extensionCount, NULL); @@ -157,6 +181,10 @@ static bool openxr_init(float offset, uint32_t msaa) { const char* enabledExtensionNames[4]; uint32_t enabledExtensionCount = 0; +#ifdef __ANDROID__ + enabledExtensionNames[enabledExtensionCount++] = XR_KHR_ANDROID_CREATE_INSTANCE_EXTENSION_NAME; +#endif + enabledExtensionNames[enabledExtensionCount++] = GRAPHICS_EXTENSION; if (hasExtension(extensions, extensionCount, XR_EXT_HAND_TRACKING_EXTENSION_NAME)) { @@ -173,6 +201,13 @@ static bool openxr_init(float offset, uint32_t msaa) { XrInstanceCreateInfo info = { .type = XR_TYPE_INSTANCE_CREATE_INFO, +#ifdef __ANDROID__ + .next = &(XrInstanceCreateInfoAndroidKHR) { + .type = XR_TYPE_INSTANCE_CREATE_INFO_ANDROID_KHR, + .applicationVM = activity->vm, + .applicationActivity = activity->clazz + }, +#endif .applicationInfo.engineName = "LÖVR", .applicationInfo.engineVersion = (LOVR_VERSION_MAJOR << 24) + (LOVR_VERSION_MINOR << 16) + LOVR_VERSION_PATCH, .applicationInfo.applicationName = "LÖVR", @@ -272,13 +307,13 @@ static bool openxr_init(float offset, uint32_t msaa) { { // Session #if defined(LOVR_GL) - XrGraphicsRequirementsOpenGLKHR requirements; + XrGraphicsRequirementsOpenGLKHR requirements = { .type = XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_KHR, NULL }; PFN_xrGetOpenGLGraphicsRequirementsKHR xrGetOpenGLGraphicsRequirementsKHR; XR_LOAD(xrGetOpenGLGraphicsRequirementsKHR); XR_INIT(xrGetOpenGLGraphicsRequirementsKHR(state.instance, state.system, &requirements)); // TODO validate OpenGL versions #elif defined(LOVR_GLES) - XrGraphicsRequirementsOpenGLESKHR requirements; + XrGraphicsRequirementsOpenGLESKHR requirements = { .type = XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_ES_KHR, NULL }; PFN_xrGetOpenGLESGraphicsRequirementsKHR xrGetOpenGLESGraphicsRequirementsKHR; XR_LOAD(xrGetOpenGLESGraphicsRequirementsKHR); XR_INIT(xrGetOpenGLESGraphicsRequirementsKHR(state.instance, state.system, &requirements)); @@ -364,28 +399,43 @@ static bool openxr_init(float offset, uint32_t msaa) { } { // Swapchain +#if defined(XR_USE_GRAPHICS_API_OPENGL) + TextureType textureType = TEXTURE_2D; + uint32_t width = state.width * 2; + uint32_t arraySize = 1; + XrSwapchainImageOpenGLKHR images[MAX_IMAGES]; + for (uint32_t i = 0; i < MAX_IMAGES; i++) { + images[i].type = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_KHR; + images[i].next = NULL; + } +#elif defined(XR_USE_GRAPHICS_API_OPENGL_ES) + TextureType textureType = TEXTURE_ARRAY; + uint32_t width = state.width; + uint32_t arraySize = 2; + XrSwapchainImageOpenGLESKHR images[MAX_IMAGES]; + for (uint32_t i = 0; i < MAX_IMAGES; i++) { + images[i].type = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_ES_KHR; + images[i].next = NULL; + } +#endif + XrSwapchainCreateInfo info = { .type = XR_TYPE_SWAPCHAIN_CREATE_INFO, .usageFlags = XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_SAMPLED_BIT, .format = GL_SRGB8_ALPHA8, - .width = state.width * 2, + .width = width, .height = state.height, .sampleCount = 1, .faceCount = 1, - .arraySize = 1, + .arraySize = arraySize, .mipCount = 1 }; -#if defined(XR_USE_GRAPHICS_API_OPENGL) - XrSwapchainImageOpenGLKHR images[MAX_IMAGES]; -#elif defined(XR_USE_GRAPHICS_API_OPENGLES) - XrSwapchainImageOpenGLESKHR images[MAX_IMAGES]; -#endif XR_INIT(xrCreateSwapchain(state.session, &info, &state.swapchain)); XR_INIT(xrEnumerateSwapchainImages(state.swapchain, MAX_IMAGES, &state.imageCount, (XrSwapchainImageBaseHeader*) images)); for (uint32_t i = 0; i < state.imageCount; i++) { - state.textures[i] = lovrTextureCreateFromHandle(images[i].image, TEXTURE_2D, 1, state.msaa); + state.textures[i] = lovrTextureCreateFromHandle(images[i].image, textureType, arraySize, state.msaa); } // Pre-init composition layer @@ -402,14 +452,20 @@ static bool openxr_init(float offset, uint32_t msaa) { .subImage = { state.swapchain, { { 0, 0 }, { state.width, state.height } }, 0 } }; - // Copy the left view to the right view and offset for side-by-side submission + // Copy the left view to the right view and offset either the viewport or array index state.layerViews[1] = state.layerViews[0]; +#if defined(XR_USE_GRAPHICS_API_OPENGL) state.layerViews[1].subImage.imageRect.offset.x += state.width; +#elif defined(XR_USE_GRAPHICS_API_OPENGL_ES) + state.layerViews[1].subImage.imageArrayIndex = 1; +#endif } state.clipNear = .1f; state.clipFar = 100.f; + state.frameState.type = XR_TYPE_FRAME_STATE; + return true; } @@ -475,7 +531,7 @@ static void getViews(XrView views[2], uint32_t* count) { .space = state.referenceSpace }; - XrViewState viewState; + XrViewState viewState = { .type = XR_TYPE_VIEW_STATE }; XR(xrLocateViews(state.session, &viewLocateInfo, &viewState, 2 * sizeof(XrView), count, views)); } @@ -725,7 +781,8 @@ static void openxr_renderTo(void (*callback)(void*), void* userdata) { XrFrameEndInfo endInfo = { .type = XR_TYPE_FRAME_END_INFO, .displayTime = state.frameState.predictedDisplayTime, - .environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE + .environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE, + .layers = (const XrCompositionLayerBaseHeader*[1]) { (XrCompositionLayerBaseHeader*) &state.layers[0] } }; XR(xrBeginFrame(state.session, &beginInfo)); @@ -769,8 +826,6 @@ static void openxr_renderTo(void (*callback)(void*), void* userdata) { callback(userdata); lovrGraphicsSetCamera(NULL, false); - const XrCompositionLayerBaseHeader* layers[1] = { (XrCompositionLayerBaseHeader*) &state.layers[0] }; - endInfo.layers = layers; endInfo.layerCount = 1; state.layerViews[0].pose = views[0].pose; state.layerViews[0].fov = views[0].fov; @@ -822,7 +877,7 @@ static void openxr_update(float dt) { bool wasFocused = state.sessionState == XR_SESSION_STATE_FOCUSED; bool isFocused = event->state == XR_SESSION_STATE_FOCUSED; if (wasFocused != isFocused) { - lovrEventPush((Event) { .type = EVENT_FOCUS, .data.boolean = isFocused }); + lovrEventPush((Event) { .type = EVENT_FOCUS, .data.boolean.value = isFocused }); } state.sessionState = event->state; diff --git a/src/resources/Activity_openxr.java b/src/resources/Activity_openxr.java new file mode 100644 index 00000000..e5655160 --- /dev/null +++ b/src/resources/Activity_openxr.java @@ -0,0 +1,10 @@ +package org.lovr.app; + +import android.app.NativeActivity; + +public class Activity extends NativeActivity { + static { + System.loadLibrary("openxr_loader"); + System.loadLibrary("lovr"); + } +} diff --git a/src/resources/AndroidManifest_vrapi.xml b/src/resources/AndroidManifest_oculus.xml similarity index 94% rename from src/resources/AndroidManifest_vrapi.xml rename to src/resources/AndroidManifest_oculus.xml index d6918b85..d6cf21f5 100644 --- a/src/resources/AndroidManifest_vrapi.xml +++ b/src/resources/AndroidManifest_oculus.xml @@ -12,6 +12,7 @@ +