mirror of https://github.com/bjornbytes/lovr.git
commit
26b4b58479
|
@ -5,11 +5,13 @@ config/*
|
|||
*.ttf.h
|
||||
*.json.h
|
||||
*.lua.h
|
||||
AndroidManifest*.xml
|
||||
/build.sh
|
||||
/watch.sh
|
||||
/debug.sh
|
||||
/db
|
||||
/lovr
|
||||
/lovr.apk
|
||||
/lovr.exe
|
||||
/lovr.exp
|
||||
/lovr.lib
|
||||
|
@ -17,12 +19,17 @@ config/*
|
|||
/lovr.worker.js
|
||||
/lovr.html
|
||||
/lovr.wasm
|
||||
/classes.dex
|
||||
build
|
||||
build-*
|
||||
/lib
|
||||
/libs
|
||||
/org
|
||||
/tmp
|
||||
bin
|
||||
/data
|
||||
/*.lua
|
||||
.DS_Store
|
||||
.vs
|
||||
/test
|
||||
deps/Vrapi
|
||||
|
|
121
CMakeLists.txt
121
CMakeLists.txt
|
@ -24,6 +24,7 @@ option(LOVR_USE_WEBVR "Enable the WebVR backend for the headset module" OFF)
|
|||
option(LOVR_USE_WEBXR "Enable the WebXR backend for the headset module" OFF)
|
||||
option(LOVR_USE_OCULUS "Enable the LibOVR backend for the headset module (be sure to also set LOVR_OCULUS_PATH to point to the Oculus SDK)" OFF)
|
||||
option(LOVR_USE_OCULUS_MOBILE "Enable the Oculus Mobile (Android) backend for the headset module" OFF)
|
||||
option(LOVR_USE_VRAPI "Enable the VrApi backend for the headset module" OFF)
|
||||
option(LOVR_USE_DESKTOP_HEADSET "Enable the keyboard/mouse backend for the headset module" ON)
|
||||
option(LOVR_USE_LEAP "Enable the Leap Motion backend for the headset module" OFF)
|
||||
|
||||
|
@ -33,7 +34,7 @@ option(LOVR_SYSTEM_LUA "Use the system-provided Lua" OFF)
|
|||
option(LOVR_SYSTEM_ODE "Use the system-provided ODE" OFF)
|
||||
option(LOVR_SYSTEM_OPENAL "Use the system-provided OpenAL" OFF)
|
||||
|
||||
option(LOVR_BUILD_EXE "Build an executable" ON)
|
||||
option(LOVR_BUILD_EXE "Build an executable (or an apk on Android)" ON)
|
||||
option(LOVR_BUILD_SHARED "Build a shared library (takes precedence over LOVR_BUILD_EXE)" OFF)
|
||||
option(LOVR_BUILD_BUNDLE "On macOS, build a .app bundle instead of a raw program" OFF)
|
||||
|
||||
|
@ -71,13 +72,14 @@ if(EMSCRIPTEN)
|
|||
set(LOVR_USE_WEBVR ON)
|
||||
set(LOVR_USE_WEBXR ON)
|
||||
set(LOVR_USE_OPENVR OFF)
|
||||
set(LOVR_USE_OCULUS OFF)
|
||||
elseif(ANDROID)
|
||||
find_package(Java REQUIRED)
|
||||
set(LOVR_USE_OPENVR OFF)
|
||||
set(LOVR_USE_OCULUS OFF)
|
||||
set(LOVR_USE_DESKTOP_HEADSET OFF)
|
||||
set(LOVR_USE_OCULUS_MOBILE ON)
|
||||
set(LOVR_BUILD_SHARED ON) # Android has only "activities"
|
||||
set(ANDROID_MANIFEST "${CMAKE_CURRENT_SOURCE_DIR}/src/resources/AndroidManifest.xml" CACHE STRING "An optional path to a custom AndroidManifest.xml")
|
||||
if(LOVR_BUILD_EXE)
|
||||
set(LOVR_BUILD_SHARED ON)
|
||||
endif()
|
||||
elseif(UNIX)
|
||||
find_package(PkgConfig)
|
||||
if(NOT APPLE)
|
||||
|
@ -287,6 +289,12 @@ if(LOVR_ENABLE_HEADSET AND LOVR_USE_OCULUS)
|
|||
set(LOVR_OCULUS LibOVR)
|
||||
endif()
|
||||
|
||||
# VrApi (Oculus Mobile SDK) -- tested on 1.34.0
|
||||
if(LOVR_ENABLE_HEADSET AND LOVR_USE_VRAPI)
|
||||
include_directories(deps/VrApi/include)
|
||||
set(LOVR_VRAPI ${CMAKE_CURRENT_SOURCE_DIR}/deps/VrApi/Libs/Android/${ANDROID_ABI}/Release/libvrapi.so)
|
||||
endif()
|
||||
|
||||
# pthreads
|
||||
if(LOVR_ENABLE_THREAD)
|
||||
if(NOT WIN32 AND NOT EMSCRIPTEN)
|
||||
|
@ -357,6 +365,7 @@ target_link_libraries(lovr
|
|||
${LOVR_OPENVR}
|
||||
${LOVR_OPENXR}
|
||||
${LOVR_OCULUS}
|
||||
${LOVR_VRAPI}
|
||||
${LOVR_LEAP}
|
||||
${LOVR_PTHREADS}
|
||||
${LOVR_EMSCRIPTEN_FLAGS}
|
||||
|
@ -462,6 +471,10 @@ if(LOVR_ENABLE_HEADSET)
|
|||
add_definitions(-DLOVR_USE_OCULUS_MOBILE)
|
||||
target_sources(lovr PRIVATE src/modules/headset/headset_oculus_mobile.c)
|
||||
endif()
|
||||
if(LOVR_USE_VRAPI)
|
||||
add_definitions(-DLOVR_USE_VRAPI)
|
||||
target_sources(lovr PRIVATE src/modules/headset/headset_vrapi.c)
|
||||
endif()
|
||||
if(LOVR_USE_WEBVR)
|
||||
add_definitions(-DLOVR_USE_WEBVR)
|
||||
target_sources(lovr PRIVATE src/modules/headset/headset_webvr.c)
|
||||
|
@ -606,8 +619,104 @@ elseif(EMSCRIPTEN)
|
|||
target_compile_definitions(lovr PRIVATE -DLOVR_WEBGL)
|
||||
elseif(ANDROID)
|
||||
target_sources(lovr PRIVATE src/core/os_android.c)
|
||||
target_link_libraries(lovr log EGL GLESv3)
|
||||
target_include_directories(lovr PRIVATE "${ANDROID_NDK}/sources/android/native_app_glue")
|
||||
target_link_libraries(lovr log EGL GLESv3 android)
|
||||
target_compile_definitions(lovr PRIVATE -DLOVR_GLES)
|
||||
|
||||
# Variables
|
||||
get_filename_component(ANDROID_SDK "${ANDROID_NDK}/.." ABSOLUTE)
|
||||
file(TO_CMAKE_PATH "${ANDROID_SDK}" ANDROID_SDK)
|
||||
set(ANDROID_JAR "${ANDROID_SDK}/platforms/${ANDROID_PLATFORM}/android.jar")
|
||||
set(ANDROID_BUILD_TOOLS "${ANDROID_SDK}/build-tools/${ANDROID_BUILD_TOOLS_VERSION}")
|
||||
set(ACTIVITY_JAVA "${CMAKE_CURRENT_SOURCE_DIR}/src/resources/Activity.java")
|
||||
set(ACTIVITY_CLASS "org/lovr/app/Activity.class")
|
||||
set(ANDROID_LIBS_SRC "liblovr.so")
|
||||
set(ANDROID_LIBS_DST "lib/${ANDROID_ABI}/liblovr.so")
|
||||
|
||||
if(LOVR_ENABLE_AUDIO)
|
||||
set(ANDROID_LIBS_SRC ${ANDROID_LIBS_SRC} "openal/libopenal.so")
|
||||
set(ANDROID_LIBS_DST ${ANDROID_LIBS_DST} "lib/${ANDROID_ABI}/libopenal.so")
|
||||
endif()
|
||||
|
||||
if(LOVR_ENABLE_PHYSICS)
|
||||
set(ANDROID_LIBS_SRC ${ANDROID_LIBS_SRC} "ode/libode.so")
|
||||
set(ANDROID_LIBS_DST ${ANDROID_LIBS_DST} "lib/${ANDROID_ABI}/libode.so")
|
||||
endif()
|
||||
|
||||
if(LOVR_USE_VRAPI)
|
||||
set(ANDROID_LIBS_SRC ${ANDROID_LIBS_SRC} "${LOVR_VRAPI}")
|
||||
set(ANDROID_LIBS_DST ${ANDROID_LIBS_DST} "lib/${ANDROID_ABI}/libvrapi.so")
|
||||
endif()
|
||||
|
||||
if(ANDROID_ASSETS)
|
||||
set(ANDROID_ASSETS_FLAG -A ${ANDROID_ASSETS})
|
||||
endif()
|
||||
|
||||
# Compile java file to class file
|
||||
add_custom_command(
|
||||
OUTPUT ${ACTIVITY_CLASS}
|
||||
COMMAND ${Java_JAVAC_EXECUTABLE}
|
||||
-classpath "${ANDROID_JAR}"
|
||||
-d .
|
||||
${ACTIVITY_JAVA}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
|
||||
# Compile class file to dex file
|
||||
add_custom_command(
|
||||
OUTPUT classes.dex
|
||||
COMMAND ${ANDROID_BUILD_TOOLS}/dx
|
||||
--dex
|
||||
--output=classes.dex
|
||||
${ACTIVITY_CLASS}
|
||||
DEPENDS ${ACTIVITY_CLASS}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
|
||||
# Make an apk
|
||||
add_custom_command(
|
||||
OUTPUT lovr.unaligned.apk
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory lib/${ANDROID_ABI}
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${ANDROID_LIBS_SRC} lib/${ANDROID_ABI}
|
||||
COMMAND
|
||||
${ANDROID_BUILD_TOOLS}/aapt
|
||||
package -f
|
||||
-M ${ANDROID_MANIFEST}
|
||||
-I ${ANDROID_JAR}
|
||||
-F lovr.unaligned.apk
|
||||
${ANDROID_ASSETS_FLAG}
|
||||
COMMAND
|
||||
${ANDROID_BUILD_TOOLS}/aapt
|
||||
add -f
|
||||
lovr.unaligned.apk
|
||||
classes.dex
|
||||
${ANDROID_LIBS_DST}
|
||||
DEPENDS classes.dex
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
|
||||
# Align apk
|
||||
add_custom_command(
|
||||
OUTPUT lovr.unsigned.apk
|
||||
COMMAND ${ANDROID_BUILD_TOOLS}/zipalign -f 4 lovr.unaligned.apk lovr.unsigned.apk
|
||||
DEPENDS lovr.unaligned.apk
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
|
||||
# Sign apk
|
||||
add_custom_command(
|
||||
OUTPUT lovr.apk
|
||||
COMMAND ${ANDROID_BUILD_TOOLS}/apksigner
|
||||
sign
|
||||
--ks ${ANDROID_KEYSTORE}
|
||||
--ks-pass ${ANDROID_KEYSTORE_PASS}
|
||||
--out=lovr.apk
|
||||
lovr.unsigned.apk
|
||||
DEPENDS lovr.unsigned.apk
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
|
||||
add_custom_target(lovr-apk ALL DEPENDS "lovr.apk")
|
||||
elseif(UNIX)
|
||||
target_sources(lovr PRIVATE src/core/os_linux.c)
|
||||
target_compile_definitions(lovr PRIVATE -DLOVR_GL)
|
||||
|
|
25
Tupfile
25
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
|
||||
|
@ -68,8 +68,25 @@ SRC_@(GRAPHICS) += src/resources/shaders.c
|
|||
# 2 [CC] compile .c -> .o
|
||||
# 3 [LD] link .o -> exe
|
||||
# 4 [CP] copy external libs -> libs folder
|
||||
|
||||
: foreach $(RES) $(RES_y) |> !xd |> %f.h
|
||||
: foreach $(SRC) $(SRC_y) $(SRC_yy) | src/resources/*.h |> !cc |> .obj/%B.o
|
||||
: .obj/*.o $(STATIC_LIBS) $(STATIC_LIBS_y) |> !ld |> lovr$(SUFFIX) | $(EXTRAS) $(EXTRAS_y)
|
||||
: foreach $(LIBS) |> !cp |> libs/%b
|
||||
: .obj/*.o $(STATIC_LIBS) $(STATIC_LIBS_y) |> !ld |> $(PREFIX)lovr$(SUFFIX) | $(EXTRAS) $(EXTRAS_y)
|
||||
: foreach $(LIBS) |> !cp |> $(LIB)/%b
|
||||
|
||||
# Android
|
||||
ifeq ($(PLATFORM),android)
|
||||
# There needs to be a Java class for the activity with 2 lines of code to load native libraries.
|
||||
# It gets compiled to a class file with javac and then into bytecode using dx
|
||||
CLASS = org/lovr/app/Activity.class
|
||||
: src/resources/Activity.java |> ^ JAVAC %b^ javac -classpath $(ANDROID_JAR) -d . %f |> $(CLASS)
|
||||
: $(CLASS) |> !dx |> classes.dex
|
||||
|
||||
# Create an apk from the Android manifest. The zip command is used afterwards to add raw files
|
||||
# because it is way faster than calling aapt again and apks are just zips (TODO windows).
|
||||
: @(ANDROID_MANIFEST) | $(LIB)/*.so classes.dex |> ^ AAPT %b^ aapt package -F %o -M %f -I $(ANDROID_JAR) $(ANDROID_ASSETS) && zip -qu0 %o $(LIB)/*.so classes.dex |> tmp/lovr.unaligned.apk
|
||||
|
||||
# Even though we have an apk, it isn't actually valid yet. It has to be aligned using the special
|
||||
# zipalign tool, and then signed using apksigner.
|
||||
: tmp/lovr.unaligned.apk |> !zipalign |> tmp/lovr.unsigned.apk
|
||||
: tmp/lovr.unsigned.apk |> !apksigner |> lovr.apk
|
||||
endif
|
||||
|
|
66
Tuprules.tup
66
Tuprules.tup
|
@ -1,6 +1,7 @@
|
|||
ROOT = $(TUP_CWD)
|
||||
|
||||
## Base
|
||||
CC = @(CC)
|
||||
CFLAGS += -std=c99 -pedantic
|
||||
CFLAGS += -D_POSIX_C_SOURCE=200809L
|
||||
CFLAGS += -I$(ROOT)/src
|
||||
|
@ -36,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
|
||||
|
||||
|
@ -47,6 +49,8 @@ else
|
|||
PLATFORM = @(PLATFORM)
|
||||
endif
|
||||
|
||||
LIB = libs
|
||||
|
||||
## Windows
|
||||
ifeq ($(PLATFORM),win32)
|
||||
CFLAGS += -D_CRT_SECURE_NO_WARNINGS
|
||||
|
@ -97,7 +101,7 @@ endif
|
|||
ifeq ($(PLATFORM),macosx)
|
||||
PLATFORM = macos
|
||||
LDFLAGS += -lobjc
|
||||
LDFLAGS += -Wl,-rpath,@executable_path/libs
|
||||
LDFLAGS += -Wl,-rpath,@executable_path/$(LIB)
|
||||
|
||||
ifeq (@(CMAKE_DEPS),y)
|
||||
ifeq (@(LUAJIT),y)
|
||||
|
@ -134,7 +138,7 @@ endif
|
|||
## Linux
|
||||
ifeq ($(PLATFORM),linux)
|
||||
LDFLAGS += -lm -lpthread
|
||||
LDFLAGS += -Wl,-rpath,\$ORIGIN/libs
|
||||
LDFLAGS += -Wl,-rpath,\$ORIGIN/$(LIB)
|
||||
|
||||
ifeq (@(CMAKE_DEPS),y)
|
||||
ifeq (@(LUAJIT),y)
|
||||
|
@ -168,6 +172,58 @@ ifeq ($(PLATFORM),linux)
|
|||
endif
|
||||
endif
|
||||
|
||||
## Android
|
||||
ifeq ($(PLATFORM),android)
|
||||
CC = @(ANDROID_SDK)/sdk/ndk-bundle/toolchains/llvm/prebuilt/@(ANDROID_HOST_TAG)/bin/clang
|
||||
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
|
||||
VRAPI_LIB_PATH = $(ROOT)/deps/VrApi/Libs/Android/arm64-v8a/Release
|
||||
CFLAGS += --target=aarch64-linux-android@(ANDROID_VERSION)
|
||||
CFLAGS += -I$(GLUE)
|
||||
LDFLAGS += --target=aarch64-linux-android@(ANDROID_VERSION)
|
||||
LDFLAGS += -shared
|
||||
LDFLAGS += -landroid
|
||||
LIB = lib/arm64-v8a
|
||||
PREFIX = $(LIB)/lib
|
||||
SUFFIX = .so
|
||||
|
||||
ifneq (@(ANDROID_ASSETS),)
|
||||
ANDROID_ASSETS = -A @(ANDROID_ASSETS)
|
||||
endif
|
||||
|
||||
# Macros
|
||||
!dx = |> ^ DX %b^ dx --dex --output=%o %f |>
|
||||
!zipalign = |> ^ ZIPALIGN %f^ $(TOOLS)/zipalign -f 4 %f %o |>
|
||||
!apksigner = |> ^ APKSIGNER %o^ $(TOOLS)/apksigner sign --ks @(ANDROID_KEYSTORE) --ks-pass @(ANDROID_KEYSTORE_PASS) --out %o %f |>
|
||||
|
||||
ifeq (@(CMAKE_DEPS),y)
|
||||
ifeq (@(LUAJIT),y)
|
||||
CFLAGS += -I$(ROOT)/deps/luajit/src
|
||||
LDFLAGS += -L$(ROOT)/build/luajit/src -lluajit
|
||||
STATIC_LIBS += $(ROOT)/build/luajit/src/libluajit.a
|
||||
else
|
||||
CFLAGS += -I$(ROOT)/deps/lua/src -I$(ROOT)/build/lua
|
||||
LDFLAGS += -L$(ROOT)/build/lua -llua
|
||||
endif
|
||||
|
||||
CFLAGS_@(AUDIO) += -I$(ROOT)/deps/openal-soft/include
|
||||
CFLAGS_@(DATA) += -I$(ROOT)/deps/msdfgen
|
||||
CFLAGS_@(PHYSICS) += -I$(ROOT)/deps/ode/include -I$(ROOT)/build/ode/include
|
||||
CFLAGS_@(ENET) += -I$(ROOT)/deps/enet/include
|
||||
|
||||
LDFLAGS_@(AUDIO) += -L$(ROOT)/build/openal -lopenal
|
||||
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) -lvrapi
|
||||
|
||||
LIBS_@(AUDIO) += $(ROOT)/build/openal/libopenal.*so*
|
||||
LIBS_@(PHYSICS) += $(ROOT)/build/ode/libode.so
|
||||
LIBS_@(VRAPI) += $(VRAPI_LIB_PATH)/libvrapi.so
|
||||
endif
|
||||
endif
|
||||
|
||||
## emscripten
|
||||
ifeq ($(PLATFORM),web)
|
||||
SUFFIX = .html
|
||||
|
@ -204,7 +260,7 @@ CFLAGS += @(EXTRA_CFLAGS)
|
|||
LDFLAGS += @(EXTRA_LDFLAGS)
|
||||
|
||||
## Macros
|
||||
!cc = |> ^ CC %b^ @(CC) $(CFLAGS_y) $(CFLAGS) -c %f -o %o |>
|
||||
!ld = |> ^ LD %o^ @(CC) -o %o %f $(LDFLAGS_y) $(LDFLAGS) |>
|
||||
!cc = |> ^ CC %b^ $(CC) $(CFLAGS_y) $(CFLAGS) -o %o -c %f |>
|
||||
!ld = |> ^ LD %o^ $(CC) -o %o %f $(LDFLAGS_y) $(LDFLAGS) |>
|
||||
!xd = |> ^ XD %f^ xxd -i %f > %o |>
|
||||
!cp = |> ^ CP %b^ cp %f %o |>
|
||||
|
|
|
@ -64,3 +64,13 @@ CONFIG_GL=GL
|
|||
# GPU_BACKEND is used to select the backend (gl, vk, wg)
|
||||
CONFIG_GPU=n
|
||||
CONFIG_GPU_BACKEND=gl
|
||||
|
||||
## Android settings
|
||||
CONFIG_ANDROID_SDK=
|
||||
CONFIG_ANDROID_VERSION=
|
||||
CONFIG_ANDROID_BUILD_TOOLS_VERSION=
|
||||
CONFIG_ANDROID_HOST_TAG=
|
||||
CONFIG_ANDROID_KEYSTORE=
|
||||
CONFIG_ANDROID_KEYSTORE_PASS=
|
||||
CONFIG_ANDROID_MANIFEST=
|
||||
CONFIG_ANDROID_ASSETS=
|
||||
|
|
|
@ -100,18 +100,6 @@ static int l_lovrFilesystemGetAppdataDirectory(lua_State* L) {
|
|||
}
|
||||
|
||||
|
||||
static int l_lovrFilesystemGetApplicationId(lua_State *L) {
|
||||
char buffer[LOVR_PATH_MAX];
|
||||
|
||||
if (lovrFilesystemGetApplicationId(buffer, sizeof(buffer))) {
|
||||
lua_pushstring(L, buffer);
|
||||
} else {
|
||||
lua_pushnil(L);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int l_lovrFilesystemGetDirectoryItems(lua_State* L) {
|
||||
const char* path = luaL_checkstring(L, 1);
|
||||
lua_settop(L, 1);
|
||||
|
@ -331,7 +319,6 @@ static const luaL_Reg lovrFilesystem[] = {
|
|||
{ "append", l_lovrFilesystemAppend },
|
||||
{ "createDirectory", l_lovrFilesystemCreateDirectory },
|
||||
{ "getAppdataDirectory", l_lovrFilesystemGetAppdataDirectory },
|
||||
{ "getApplicationId", l_lovrFilesystemGetApplicationId },
|
||||
{ "getDirectoryItems", l_lovrFilesystemGetDirectoryItems },
|
||||
{ "getExecutablePath", l_lovrFilesystemGetExecutablePath },
|
||||
{ "getIdentity", l_lovrFilesystemGetIdentity },
|
||||
|
|
|
@ -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 }
|
||||
|
|
170
src/core/fs.c
170
src/core/fs.c
|
@ -2,8 +2,6 @@
|
|||
|
||||
#include "fs.h"
|
||||
#include <windows.h>
|
||||
#include <KnownFolders.h>
|
||||
#include <ShlObj.h>
|
||||
|
||||
#define FS_PATH_MAX 1024
|
||||
|
||||
|
@ -166,52 +164,6 @@ bool fs_list(const char* path, fs_list_cb* callback, void* context) {
|
|||
return true;
|
||||
}
|
||||
|
||||
size_t fs_getHomeDir(char* buffer, size_t size) {
|
||||
PWSTR wpath = NULL;
|
||||
if (SHGetKnownFolderPath(&FOLDERID_Profile, 0, NULL, &wpath) == S_OK) {
|
||||
size_t bytes = WideCharToMultiByte(CP_UTF8, 0, wpath, -1, buffer, (int) size, NULL, NULL) - 1;
|
||||
CoTaskMemFree(wpath);
|
||||
return bytes;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t fs_getDataDir(char* buffer, size_t size) {
|
||||
PWSTR wpath = NULL;
|
||||
if (SHGetKnownFolderPath(&FOLDERID_RoamingAppData, 0, NULL, &wpath) == S_OK) {
|
||||
size_t bytes = WideCharToMultiByte(CP_UTF8, 0, wpath, -1, buffer, (int) size, NULL, NULL) - 1;
|
||||
CoTaskMemFree(wpath);
|
||||
return bytes;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t fs_getWorkDir(char* buffer, size_t size) {
|
||||
WCHAR wpath[FS_PATH_MAX];
|
||||
int length = GetCurrentDirectoryW((int) size, wpath);
|
||||
if (length) {
|
||||
return WideCharToMultiByte(CP_UTF8, 0, wpath, length + 1, buffer, (int) size, NULL, NULL) - 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t fs_getExecutablePath(char* buffer, size_t size) {
|
||||
WCHAR wpath[FS_PATH_MAX];
|
||||
DWORD length = GetModuleFileNameW(NULL, wpath, FS_PATH_MAX);
|
||||
if (length < FS_PATH_MAX) {
|
||||
return WideCharToMultiByte(CP_UTF8, 0, wpath, length + 1, buffer, (int) size, NULL, NULL) - 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t fs_getBundlePath(char* buffer, size_t size) {
|
||||
return fs_getExecutablePath(buffer, size);
|
||||
}
|
||||
|
||||
size_t fs_getBundleId(char* buffer, size_t size) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else // !_WIN32
|
||||
|
||||
#include "fs.h"
|
||||
|
@ -317,126 +269,4 @@ bool fs_list(const char* path, fs_list_cb* callback, void* context) {
|
|||
return true;
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <objc/objc-runtime.h>
|
||||
#include <mach-o/dyld.h>
|
||||
#elif __ANDROID__
|
||||
extern const char* lovrOculusMobileWritablePath; // TODO
|
||||
#endif
|
||||
|
||||
static size_t copy(char* buffer, size_t size, const char* string, size_t length) {
|
||||
if (length >= size) { return 0; }
|
||||
memcpy(buffer, string, length);
|
||||
buffer[length + 1] = '\0';
|
||||
return length;
|
||||
}
|
||||
|
||||
size_t fs_getHomeDir(char* buffer, size_t size) {
|
||||
const char* home = getenv("HOME");
|
||||
|
||||
if (!home) {
|
||||
struct passwd* entry = getpwuid(getuid());
|
||||
if (!entry) {
|
||||
return 0;
|
||||
}
|
||||
home = entry->pw_dir;
|
||||
}
|
||||
|
||||
return copy(buffer, size, home, strlen(home));
|
||||
}
|
||||
|
||||
size_t fs_getDataDir(char* buffer, size_t size) {
|
||||
#if __APPLE__
|
||||
size_t cursor = fs_getHomeDir(buffer, size);
|
||||
|
||||
if (cursor > 0) {
|
||||
const char* suffix = "/Library/Application Support";
|
||||
return cursor + copy(buffer + cursor, size - cursor, suffix, strlen(suffix));
|
||||
}
|
||||
|
||||
return 0;
|
||||
#elif EMSCRIPTEN
|
||||
const char* path = "/home/web_user";
|
||||
return copy(buffer, size, path, strlen(path));
|
||||
#elif __ANDROID__
|
||||
return copy(buffer, size, lovrOculusMobileWritablePath, strlen(lovrOculusMobileWritablePath));
|
||||
#else
|
||||
const char* xdg = getenv("XDG_DATA_HOME");
|
||||
|
||||
if (xdg) {
|
||||
return copy(buffer, size, xdg, strlen(xdg));
|
||||
} else {
|
||||
size_t cursor = fs_getHomeDir(buffer, size);
|
||||
if (cursor > 0) {
|
||||
const char* suffix = "/.local/share";
|
||||
return cursor + copy(buffer + cursor, size - cursor, suffix, strlen(suffix));
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
size_t fs_getWorkDir(char* buffer, size_t size) {
|
||||
return getcwd(buffer, size) ? strlen(buffer) : 0;
|
||||
}
|
||||
|
||||
size_t fs_getExecutablePath(char* buffer, size_t size) {
|
||||
#if __APPLE__
|
||||
uint32_t size32 = size;
|
||||
return _NSGetExecutablePath(buffer, &size32) ? 0 : size32;
|
||||
#elif EMSCRIPTEN
|
||||
return 0;
|
||||
#else
|
||||
ssize_t length = readlink("/proc/self/exe", buffer, size);
|
||||
return (length < 0) ? 0 : (size_t) length;
|
||||
#endif
|
||||
}
|
||||
|
||||
size_t fs_getBundlePath(char* buffer, size_t size) {
|
||||
#ifdef __APPLE__
|
||||
id extension = ((id(*)(Class, SEL, char*)) objc_msgSend)(objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), "lovr");
|
||||
id bundle = ((id(*)(Class, SEL)) objc_msgSend)(objc_getClass("NSBundle"), sel_registerName("mainBundle"));
|
||||
id path = ((id(*)(id, SEL, char*, id)) objc_msgSend)(bundle, sel_registerName("pathForResource:ofType:"), nil, extension);
|
||||
if (path == nil) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char* cpath = ((const char*(*)(id, SEL)) objc_msgSend)(path, sel_registerName("UTF8String"));
|
||||
if (!cpath) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t length = strlen(cpath);
|
||||
if (length >= size) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
memcpy(buffer, cpath, length);
|
||||
buffer[length] = '\0';
|
||||
return length;
|
||||
#else
|
||||
return fs_getExecutablePath(buffer, size);
|
||||
#endif
|
||||
}
|
||||
|
||||
size_t fs_getBundleId(char* buffer, size_t size) {
|
||||
#ifdef __ANDROID__
|
||||
// TODO
|
||||
pid_t pid = getpid();
|
||||
char path[32];
|
||||
snprintf(path, 32, "/proc/%i/cmdline", (int) pid);
|
||||
FILE* file = fopen(path, "r");
|
||||
if (file) {
|
||||
size_t read = fread(buffer, 1, size, file);
|
||||
fclose(file);
|
||||
return true;
|
||||
}
|
||||
return 0;
|
||||
#else
|
||||
buffer[0] = '\0';
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -34,11 +34,3 @@ bool fs_stat(const char* path, FileInfo* info);
|
|||
bool fs_remove(const char* path);
|
||||
bool fs_mkdir(const char* path);
|
||||
bool fs_list(const char* path, fs_list_cb* callback, void* context);
|
||||
|
||||
// Returns length written to buffer
|
||||
size_t fs_getHomeDir(char* buffer, size_t size);
|
||||
size_t fs_getDataDir(char* buffer, size_t size);
|
||||
size_t fs_getWorkDir(char* buffer, size_t size);
|
||||
size_t fs_getExecutablePath(char* buffer, size_t size);
|
||||
size_t fs_getBundlePath(char* buffer, size_t size);
|
||||
size_t fs_getBundleId(char* buffer, size_t size);
|
||||
|
|
|
@ -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);
|
||||
|
@ -62,6 +62,11 @@ void lovrPlatformSetTime(double t);
|
|||
void lovrPlatformSleep(double seconds);
|
||||
void lovrPlatformOpenConsole(void);
|
||||
void lovrPlatformPollEvents(void);
|
||||
size_t lovrPlatformGetHomeDirectory(char* buffer, size_t size);
|
||||
size_t lovrPlatformGetDataDirectory(char* buffer, size_t size);
|
||||
size_t lovrPlatformGetWorkingDirectory(char* buffer, size_t size);
|
||||
size_t lovrPlatformGetExecutablePath(char* buffer, size_t size);
|
||||
size_t lovrPlatformGetBundlePath(char* buffer, size_t size);
|
||||
bool lovrPlatformCreateWindow(WindowFlags* flags);
|
||||
bool lovrPlatformHasWindow(void);
|
||||
void lovrPlatformGetWindowSize(int* width, int* height);
|
||||
|
@ -69,7 +74,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);
|
||||
|
|
|
@ -1,35 +1,89 @@
|
|||
#include "os.h"
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <pthread.h>
|
||||
#include <EGL/egl.h>
|
||||
#include <EGL/eglext.h>
|
||||
|
||||
#ifndef LOVR_USE_OCULUS_MOBILE
|
||||
// This is probably bad, but makes things easier to build
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wformat-pedantic"
|
||||
#include <android_native_app_glue.c>
|
||||
#pragma clang diagnostic pop
|
||||
#else
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wformat-pedantic"
|
||||
#include <android_native_app_glue.h>
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
// 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);
|
||||
|
||||
static struct {
|
||||
struct android_app* app;
|
||||
ANativeWindow* window;
|
||||
bool resumed;
|
||||
JNIEnv* jni;
|
||||
EGLDisplay display;
|
||||
EGLContext context;
|
||||
EGLSurface surface;
|
||||
activeCallback onActive;
|
||||
quitCallback onQuit;
|
||||
pthread_t log;
|
||||
} state;
|
||||
|
||||
// Currently, the Android entrypoint is in lovr-oculus-mobile, so this one is not enabled.
|
||||
#if 0
|
||||
#include <android_native_app_glue.h>
|
||||
#include <android/log.h>
|
||||
#ifndef LOVR_USE_OCULUS_MOBILE
|
||||
|
||||
static JavaVM* lovrJavaVM;
|
||||
static JNIEnv* lovrJNIEnv;
|
||||
// To make regular printing work, a thread makes a pipe and redirects stdout and stderr to the write
|
||||
// end of the pipe. The read end of the pipe is forwarded to __android_log_write.
|
||||
static void* log_main(void* data) {
|
||||
int* fd = data;
|
||||
pipe(fd);
|
||||
dup2(fd[1], STDOUT_FILENO);
|
||||
dup2(fd[1], STDERR_FILENO);
|
||||
setvbuf(stdout, 0, _IOLBF, 0);
|
||||
setvbuf(stderr, 0, _IONBF, 0);
|
||||
ssize_t length;
|
||||
char buffer[1024];
|
||||
while ((length = read(fd[0], buffer, sizeof(buffer) - 1)) > 0) {
|
||||
buffer[length] = '\0';
|
||||
__android_log_write(ANDROID_LOG_DEBUG, "LOVR", buffer);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void onAppCmd(struct android_app* app, int32_t cmd) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv);
|
||||
|
||||
static void onAppCmd(struct android_app* app, int32_t cmd) {
|
||||
// pause, resume, events, etc.
|
||||
}
|
||||
|
||||
void android_main(struct android_app* app) {
|
||||
lovrJavaVM = app->activity->vm;
|
||||
lovrJavaVM->AttachCurrentThread(&lovrJNIEnv, NULL);
|
||||
state.app = app;
|
||||
(*app->activity->vm)->AttachCurrentThread(app->activity->vm, &state.jni, NULL);
|
||||
int fd[2];
|
||||
pthread_create(&state.log, NULL, log_main, fd);
|
||||
pthread_detach(state.log);
|
||||
app->onAppCmd = onAppCmd;
|
||||
main(0, NULL);
|
||||
lovrJavaVM->DetachCurrentThread();
|
||||
(*app->activity->vm)->DetachCurrentThread(app->activity->vm);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -38,29 +92,130 @@ bool lovrPlatformInit() {
|
|||
}
|
||||
|
||||
void lovrPlatformDestroy() {
|
||||
#if 0
|
||||
#ifndef LOVR_USE_OCULUS_MOBILE
|
||||
if (state.display) eglMakeCurrent(state.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
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() {
|
||||
return "Android";
|
||||
}
|
||||
|
||||
// lovr-oculus-mobile provides its own implementation of the timing functions
|
||||
#define NS_PER_SEC 1000000000ULL
|
||||
#ifndef LOVR_USE_OCULUS_MOBILE
|
||||
static uint64_t epoch;
|
||||
|
||||
static uint64_t getTime() {
|
||||
struct timespec t;
|
||||
clock_gettime(CLOCK_MONOTONIC, &t);
|
||||
return (uint64_t) t.tv_sec * NS_PER_SEC + (uint64_t) t.tv_nsec;
|
||||
}
|
||||
|
||||
double lovrPlatformGetTime() {
|
||||
return (getTime() - epoch) / (double) NS_PER_SEC;
|
||||
}
|
||||
|
||||
void lovrPlatformSetTime(double time) {
|
||||
epoch = getTime() - (uint64_t) (time * NS_PER_SEC + .5);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void lovrPlatformSleep(double seconds) {
|
||||
seconds += .5e-9;
|
||||
struct timespec t;
|
||||
t.tv_sec = seconds;
|
||||
t.tv_nsec = (seconds - t.tv_sec) * NS_PER_SEC;
|
||||
while (nanosleep(&t, &t));
|
||||
}
|
||||
|
||||
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() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
size_t lovrPlatformGetHomeDirectory(char* buffer, size_t size) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern const char* lovrOculusMobileWritablePath;
|
||||
size_t lovrPlatformGetDataDirectory(char* buffer, size_t size) {
|
||||
#ifdef LOVR_USE_OCULUS_MOBILE
|
||||
size_t length = strlen(lovrOculusMobileWritablePath);
|
||||
if (length >= size) return 0;
|
||||
memcpy(buffer, lovrOculusMobileWritablePath, length);
|
||||
buffer[length] = '\0';
|
||||
return length;
|
||||
#else
|
||||
const char* path = state.app->activity->externalDataPath;
|
||||
size_t length = strlen(path);
|
||||
if (length >= size) return 0;
|
||||
memcpy(buffer, path, length);
|
||||
buffer[length] = '\0';
|
||||
return length;
|
||||
#endif
|
||||
}
|
||||
|
||||
size_t lovrPlatformGetWorkingDirectory(char* buffer, size_t size) {
|
||||
return getcwd(buffer, size) ? strlen(buffer) : 0;
|
||||
}
|
||||
|
||||
size_t lovrPlatformGetExecutablePath(char* buffer, size_t size) {
|
||||
ssize_t length = readlink("/proc/self/exe", buffer, size - 1);
|
||||
if (length >= 0) {
|
||||
buffer[length] = '\0';
|
||||
return length;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
size_t lovrPlatformGetBundlePath(char* buffer, size_t size) {
|
||||
#ifdef LOVR_USE_OCULUS_MOBILE
|
||||
buffer[0] = '\0';
|
||||
return 0;
|
||||
#else
|
||||
jobject activity = state.app->activity->clazz;
|
||||
jclass class = (*state.jni)->GetObjectClass(state.jni, activity);
|
||||
jmethodID getPackageCodePath = (*state.jni)->GetMethodID(state.jni, class, "getPackageCodePath", "()Ljava/lang/String;");
|
||||
if (!getPackageCodePath) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
jstring jpath = (*state.jni)->CallObjectMethod(state.jni, activity, getPackageCodePath);
|
||||
if ((*state.jni)->ExceptionOccurred(state.jni)) {
|
||||
(*state.jni)->ExceptionClear(state.jni);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char* path = (*state.jni)->GetStringUTFChars(state.jni, jpath, NULL);
|
||||
size_t length = strlen(path);
|
||||
if (length >= size) return 0;
|
||||
memcpy(buffer, path, length);
|
||||
buffer[length] = '\0';
|
||||
return length;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool lovrPlatformCreateWindow(WindowFlags* flags) {
|
||||
#if 0 // lovr-oculus-mobile creates the EGL context right now
|
||||
#ifndef LOVR_USE_OCULUS_MOBILE // lovr-oculus-mobile creates its own EGL context
|
||||
if (state.display) {
|
||||
return true;
|
||||
}
|
||||
|
@ -104,7 +259,7 @@ bool lovrPlatformCreateWindow(WindowFlags* flags) {
|
|||
continue;
|
||||
}
|
||||
|
||||
for (int a = 0; a < sizeof(attributes) / sizeof(attributes[0]); a += 2) {
|
||||
for (size_t a = 0; a < sizeof(attributes) / sizeof(attributes[0]); a += 2) {
|
||||
if (attributes[a] == EGL_NONE) {
|
||||
config = configs[i];
|
||||
break;
|
||||
|
@ -144,11 +299,24 @@ bool lovrPlatformCreateWindow(WindowFlags* flags) {
|
|||
return true;
|
||||
}
|
||||
|
||||
#ifndef LOVR_USE_OCULUS_MOBILE
|
||||
bool lovrPlatformHasWindow() {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
void lovrPlatformGetWindowSize(int* width, int* height) {
|
||||
if (width) *width = 0;
|
||||
if (height) *height = 0;
|
||||
}
|
||||
|
||||
#ifndef LOVR_USE_OCULUS_MOBILE
|
||||
void lovrPlatformGetFramebufferSize(int* width, int* height) {
|
||||
*width = 0;
|
||||
*height = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
void lovrPlatformSwapBuffers() {
|
||||
//
|
||||
}
|
||||
|
@ -157,8 +325,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) {
|
||||
|
@ -193,6 +361,32 @@ bool lovrPlatformIsKeyDown(KeyCode key) {
|
|||
return false;
|
||||
}
|
||||
|
||||
void lovrPlatformSleep(double seconds) {
|
||||
usleep((unsigned int) (seconds * 1000000));
|
||||
// Private, must be declared manually to use
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -45,3 +45,67 @@ void lovrPlatformSleep(double seconds) {
|
|||
void lovrPlatformOpenConsole() {
|
||||
//
|
||||
}
|
||||
|
||||
size_t lovrPlatformGetHomeDirectory(char* buffer, size_t size) {
|
||||
const char* path = getenv("HOME");
|
||||
|
||||
if (!path) {
|
||||
struct passwd* entry = getpwuid(getuid());
|
||||
if (!entry) {
|
||||
return 0;
|
||||
}
|
||||
path = entry->pw_dir;
|
||||
}
|
||||
|
||||
size_t length = strlen(path);
|
||||
if (length >= size) { return 0; }
|
||||
memcpy(buffer, path, length);
|
||||
buffer[length] = '\0';
|
||||
return length;
|
||||
}
|
||||
|
||||
size_t lovrPlatformGetDataDirectory(char* buffer, size_t size) {
|
||||
const char* xdg = getenv("XDG_DATA_HOME");
|
||||
|
||||
if (xdg) {
|
||||
size_t length = strlen(xdg);
|
||||
if (length < size) {
|
||||
memcpy(buffer, xdg, length);
|
||||
buffer[length] = '\0';
|
||||
return length;
|
||||
}
|
||||
} else {
|
||||
size_t cursor = lovrPlatformGetHomeDirectory(buffer, size);
|
||||
if (cursor > 0) {
|
||||
buffer += cursor;
|
||||
size -= cursor;
|
||||
const char* suffix = "/.local/share";
|
||||
size_t length = strlen(suffix);
|
||||
if (length < size) {
|
||||
memcpy(buffer, suffix, length);
|
||||
buffer[length] = '\0';
|
||||
return cursor + length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t lovrPlatformGetWorkingDirectory(char* buffer, size_t size) {
|
||||
return getcwd(buffer, size) ? strlen(buffer) : 0;
|
||||
}
|
||||
|
||||
size_t lovrPlatformGetExecutablePath(char* buffer, size_t size) {
|
||||
ssize_t length = readlink("/proc/self/exe", buffer, size - 1);
|
||||
if (length >= 0) {
|
||||
buffer[length] = '\0';
|
||||
return length;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
size_t lovrPlatformGetBundlePath(char* buffer, size_t size) {
|
||||
return lovrPlatformGetExecutablePath(buffer, size);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "os.h"
|
||||
#include <objc/objc-runtime.h>
|
||||
#include <mach-o/dyld.h>
|
||||
#include <mach/mach_time.h>
|
||||
#include <time.h>
|
||||
|
@ -47,3 +48,71 @@ void lovrPlatformSleep(double seconds) {
|
|||
void lovrPlatformOpenConsole() {
|
||||
//
|
||||
}
|
||||
|
||||
size_t lovrPlatformGetHomeDirectory(char* buffer, size_t size) {
|
||||
const char* path = getenv("HOME");
|
||||
|
||||
if (!path) {
|
||||
struct passwd* entry = getpwuid(getuid());
|
||||
if (!entry) {
|
||||
return 0;
|
||||
}
|
||||
path = entry->pw_dir;
|
||||
}
|
||||
|
||||
size_t length = strlen(path);
|
||||
if (length >= size) { return 0; }
|
||||
memcpy(buffer, path, length);
|
||||
buffer[length] = '\0';
|
||||
return length;
|
||||
}
|
||||
|
||||
size_t lovrPlatformGetDataDirectory(char* buffer, size_t size) {
|
||||
size_t cursor = lovrPlatformGetHomeDirectory(buffer, size);
|
||||
|
||||
if (cursor > 0) {
|
||||
buffer += cursor;
|
||||
size -= cursor;
|
||||
const char* suffix = "/Library/Application Support";
|
||||
size_t length = strlen(suffix);
|
||||
if (length < size) {
|
||||
memcpy(buffer, suffix, length);
|
||||
buffer[length] = '\0';
|
||||
return cursor + length;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t lovrPlatformGetWorkingDirectory(char* buffer, size_t size) {
|
||||
return getcwd(buffer, size) ? strlen(buffer) : 0;
|
||||
}
|
||||
|
||||
size_t lovrPlatformGetExecutablePath(char* buffer, size_t size) {
|
||||
uint32_t size32 = size;
|
||||
return _NSGetExecutablePath(buffer, &size32) ? 0 : size32;
|
||||
}
|
||||
|
||||
size_t lovrPlatformGetBundlePath(char* buffer, size_t size) {
|
||||
id extension = ((id(*)(Class, SEL, char*)) objc_msgSend)(objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), "lovr");
|
||||
id bundle = ((id(*)(Class, SEL)) objc_msgSend)(objc_getClass("NSBundle"), sel_registerName("mainBundle"));
|
||||
id path = ((id(*)(id, SEL, char*, id)) objc_msgSend)(bundle, sel_registerName("pathForResource:ofType:"), nil, extension);
|
||||
if (path == nil) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char* cpath = ((const char*(*)(id, SEL)) objc_msgSend)(path, sel_registerName("UTF8String"));
|
||||
if (!cpath) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t length = strlen(cpath);
|
||||
if (length >= size) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
memcpy(buffer, cpath, length);
|
||||
buffer[length] = '\0';
|
||||
return length;
|
||||
}
|
||||
|
|
|
@ -34,3 +34,33 @@ void lovrPlatformSleep(double seconds) {
|
|||
void lovrPlatformOpenConsole() {
|
||||
//
|
||||
}
|
||||
|
||||
size_t lovrPlatformGetHomeDirectory(char* buffer, size_t size) {
|
||||
const char* path = getenv("HOME");
|
||||
size_t length = strlen(path);
|
||||
if (length >= size) { return 0; }
|
||||
memcpy(buffer, path, length);
|
||||
buffer[length] = '\0';
|
||||
return length;
|
||||
}
|
||||
|
||||
size_t lovrPlatformGetDataDirectory(char* buffer, size_t size) {
|
||||
const char* path = "/home/web_user";
|
||||
size_t length = strlen(path);
|
||||
if (length >= size) { return 0; }
|
||||
memcpy(buffer, path, length);
|
||||
buffer[length] = '\0';
|
||||
return length;
|
||||
}
|
||||
|
||||
size_t lovrPlatformGetWorkingDirectory(char* buffer, size_t size) {
|
||||
return getcwd(buffer, size) ? strlen(buffer) : 0;
|
||||
}
|
||||
|
||||
size_t lovrPlatformGetExecutablePath(char* buffer, size_t size) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t lovrPlatformGetBundlePath(char* buffer, size_t size) {
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#include "os.h"
|
||||
#include <Windows.h>
|
||||
#include <KnownFolders.h>
|
||||
#include <ShlObj.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "os_glfw.h"
|
||||
|
@ -54,3 +56,45 @@ void lovrPlatformOpenConsole() {
|
|||
freopen("CONIN$", "r", stdin);
|
||||
freopen("CONOUT$", "w", stderr);
|
||||
}
|
||||
|
||||
size_t lovrPlatformGetHomeDirectory(char* buffer, size_t size) {
|
||||
PWSTR wpath = NULL;
|
||||
if (SHGetKnownFolderPath(&FOLDERID_Profile, 0, NULL, &wpath) == S_OK) {
|
||||
size_t bytes = WideCharToMultiByte(CP_UTF8, 0, wpath, -1, buffer, (int) size, NULL, NULL) - 1;
|
||||
CoTaskMemFree(wpath);
|
||||
return bytes;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t lovrPlatformGetDataDirectory(char* buffer, size_t size) {
|
||||
PWSTR wpath = NULL;
|
||||
if (SHGetKnownFolderPath(&FOLDERID_RoamingAppData, 0, NULL, &wpath) == S_OK) {
|
||||
size_t bytes = WideCharToMultiByte(CP_UTF8, 0, wpath, -1, buffer, (int) size, NULL, NULL) - 1;
|
||||
CoTaskMemFree(wpath);
|
||||
return bytes;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t lovrPlatformGetWorkingDirectory(char* buffer, size_t size) {
|
||||
WCHAR wpath[FS_PATH_MAX];
|
||||
int length = GetCurrentDirectoryW((int) size, wpath);
|
||||
if (length) {
|
||||
return WideCharToMultiByte(CP_UTF8, 0, wpath, length + 1, buffer, (int) size, NULL, NULL) - 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t lovrPlatformGetExecutablePath(char* buffer, size_t size) {
|
||||
WCHAR wpath[FS_PATH_MAX];
|
||||
DWORD length = GetModuleFileNameW(NULL, wpath, FS_PATH_MAX);
|
||||
if (length < FS_PATH_MAX) {
|
||||
return WideCharToMultiByte(CP_UTF8, 0, wpath, length + 1, buffer, (int) size, NULL, NULL) - 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t lovrPlatformGetBundlePath(char* buffer, size_t size) {
|
||||
return lovrPlatformGetExecutablePath(buffer, size);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "core/arr.h"
|
||||
#include "core/fs.h"
|
||||
#include "core/map.h"
|
||||
#include "core/os.h"
|
||||
#include "core/util.h"
|
||||
#include "core/zip.h"
|
||||
#include "lib/stb/stb_image.h"
|
||||
|
@ -59,7 +60,7 @@ static struct {
|
|||
char savePath[1024];
|
||||
char source[1024];
|
||||
char requirePath[2][1024];
|
||||
char* identity;
|
||||
char identity[64];
|
||||
bool fused;
|
||||
} state;
|
||||
|
||||
|
@ -118,7 +119,7 @@ bool lovrFilesystemInit(const char* argExe, const char* argGame, const char* arg
|
|||
lovrFilesystemSetCRequirePath("??;lua_modules/??;deps/??");
|
||||
|
||||
// First, try to mount a bundled archive
|
||||
if (fs_getBundlePath(state.source, LOVR_PATH_MAX) && lovrFilesystemMount(state.source, NULL, true, argRoot)) {
|
||||
if (lovrPlatformGetBundlePath(state.source, LOVR_PATH_MAX) && lovrFilesystemMount(state.source, NULL, true, "/assets")) {
|
||||
state.fused = true;
|
||||
return true;
|
||||
}
|
||||
|
@ -273,25 +274,39 @@ void lovrFilesystemGetDirectoryItems(const char* path, void (*callback)(void* co
|
|||
// Writing
|
||||
|
||||
const char* lovrFilesystemGetIdentity() {
|
||||
return state.identity;
|
||||
return state.identity[0] == '\0' ? NULL : state.identity;
|
||||
}
|
||||
|
||||
bool lovrFilesystemSetIdentity(const char* identity) {
|
||||
size_t length = strlen(identity);
|
||||
|
||||
// Identity can only be set once
|
||||
if (state.identity) {
|
||||
// Identity can only be set once, and can't be empty
|
||||
if (state.identity[0] != '\0' || length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Initialize the save path to the data path
|
||||
size_t cursor = fs_getDataDir(state.savePath, sizeof(state.savePath));
|
||||
size_t cursor = lovrPlatformGetDataDirectory(state.savePath, sizeof(state.savePath));
|
||||
|
||||
// If the data path was too long or unavailable, fail
|
||||
if (cursor == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef __ANDROID__
|
||||
// On Android the data path is the save path, and the identity is always the package id.
|
||||
// The data path ends in /package.id/files, so to extract the identity the '/files' is temporarily
|
||||
// chopped off and everything from the last slash is copied to the identity buffer
|
||||
// FIXME brittle? could read package id from /proc/self/cmdline instead
|
||||
state.savePath[cursor - 6] = '\0';
|
||||
char* id = strrchr(state.savePath, '/') + 1;
|
||||
length = strlen(id);
|
||||
memcpy(state.identity, id, length);
|
||||
state.identity[length] = '\0';
|
||||
state.savePath[cursor - 6] = '/';
|
||||
state.savePathLength = cursor;
|
||||
#else
|
||||
|
||||
// Make sure there is enough room to tack on /LOVR/<identity>
|
||||
if (cursor + strlen("/LOVR") + 1 + length >= sizeof(state.savePath)) {
|
||||
return false;
|
||||
|
@ -311,13 +326,16 @@ bool lovrFilesystemSetIdentity(const char* identity) {
|
|||
state.savePathLength = cursor;
|
||||
fs_mkdir(state.savePath);
|
||||
|
||||
// Set the identity string
|
||||
memcpy(state.identity, identity, length + 1);
|
||||
#endif
|
||||
|
||||
// Mount the fully resolved save path
|
||||
if (!lovrFilesystemMount(state.savePath, NULL, false, NULL)) {
|
||||
state.identity[0] = '\0';
|
||||
return false;
|
||||
}
|
||||
|
||||
// Stash a pointer for the identity string (leaf of save path)
|
||||
state.identity = state.savePath + cursor - length;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -370,24 +388,20 @@ size_t lovrFilesystemWrite(const char* path, const char* content, size_t size, b
|
|||
|
||||
// Paths
|
||||
|
||||
size_t lovrFilesystemGetApplicationId(char* buffer, size_t size) {
|
||||
return fs_getBundleId(buffer, size);
|
||||
}
|
||||
|
||||
size_t lovrFilesystemGetAppdataDirectory(char* buffer, size_t size) {
|
||||
return fs_getDataDir(buffer, size);
|
||||
return lovrPlatformGetDataDirectory(buffer, size);
|
||||
}
|
||||
|
||||
size_t lovrFilesystemGetExecutablePath(char* buffer, size_t size) {
|
||||
return fs_getExecutablePath(buffer, size);
|
||||
return lovrPlatformGetExecutablePath(buffer, size);
|
||||
}
|
||||
|
||||
size_t lovrFilesystemGetUserDirectory(char* buffer, size_t size) {
|
||||
return fs_getHomeDir(buffer, size);
|
||||
return lovrPlatformGetHomeDirectory(buffer, size);
|
||||
}
|
||||
|
||||
size_t lovrFilesystemGetWorkingDirectory(char* buffer, size_t size) {
|
||||
return fs_getWorkDir(buffer, size);
|
||||
return lovrPlatformGetWorkingDirectory(buffer, size);
|
||||
}
|
||||
|
||||
const char* lovrFilesystemGetRequirePath() {
|
||||
|
|
|
@ -31,7 +31,6 @@ const char* lovrFilesystemGetSaveDirectory(void);
|
|||
bool lovrFilesystemCreateDirectory(const char* path);
|
||||
bool lovrFilesystemRemove(const char* path);
|
||||
size_t lovrFilesystemWrite(const char* path, const char* content, size_t size, bool append);
|
||||
size_t lovrFilesystemGetApplicationId(char* buffer, size_t size);
|
||||
size_t lovrFilesystemGetAppdataDirectory(char* buffer, size_t size);
|
||||
size_t lovrFilesystemGetExecutablePath(char* buffer, size_t size);
|
||||
size_t lovrFilesystemGetUserDirectory(char* buffer, size_t size);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -0,0 +1,509 @@
|
|||
#include "headset/headset.h"
|
||||
#include "event/event.h"
|
||||
#include "graphics/canvas.h"
|
||||
#include "graphics/graphics.h"
|
||||
#include "core/maf.h"
|
||||
#include "core/os.h"
|
||||
#include "core/ref.h"
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <EGL/egl.h>
|
||||
#include <android_native_app_glue.h>
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wc11-extensions"
|
||||
#pragma clang diagnostic ignored "-Wgnu-empty-initializer"
|
||||
#pragma clang diagnostic ignored "-Wpedantic"
|
||||
#include <VrApi.h>
|
||||
#include <VrApi_Helpers.h>
|
||||
#include <VrApi_Input.h>
|
||||
#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;
|
||||
double displayTime;
|
||||
float offset;
|
||||
uint32_t msaa;
|
||||
ovrVector3f* rawBoundaryPoints;
|
||||
float* boundaryPoints;
|
||||
uint32_t boundaryPointCount;
|
||||
ovrTextureSwapChain* swapchain;
|
||||
uint32_t swapchainLength;
|
||||
uint32_t swapchainIndex;
|
||||
Canvas* canvases[4];
|
||||
ovrInputTrackedRemoteCapabilities controllerInfo[2];
|
||||
ovrInputStateTrackedRemote input[2];
|
||||
ovrInputStateHand handInput[2];
|
||||
float hapticStrength[2];
|
||||
float hapticDuration[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;
|
||||
if (state.deviceType == VRAPI_DEVICE_TYPE_OCULUSQUEST) {
|
||||
vrapi_SetTrackingSpace(state.session, VRAPI_TRACKING_SPACE_STAGE);
|
||||
state.offset = 0.f;
|
||||
}
|
||||
} 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_STAGE ? 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 state.displayTime;
|
||||
}
|
||||
|
||||
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, state.displayTime);
|
||||
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, state.displayTime);
|
||||
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) {
|
||||
*clipNear = *clipFar = 0.f; // 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;
|
||||
}
|
||||
|
||||
*count *= 4;
|
||||
return state.boundaryPoints;
|
||||
}
|
||||
|
||||
static bool getTracking(Device device, ovrTracking* tracking) {
|
||||
if (device == DEVICE_HEAD) {
|
||||
*tracking = vrapi_GetPredictedTracking(state.session, state.displayTime);
|
||||
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, state.displayTime, 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);
|
||||
return tracking.Status & (VRAPI_TRACKING_STATUS_POSITION_VALID | VRAPI_TRACKING_STATUS_ORIENTATION_VALID);
|
||||
}
|
||||
|
||||
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);
|
||||
return tracking.Status & (VRAPI_TRACKING_STATUS_POSITION_VALID | VRAPI_TRACKING_STATUS_ORIENTATION_VALID);
|
||||
}
|
||||
|
||||
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.input[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.input[device - DEVICE_HAND_LEFT];
|
||||
|
||||
if (state.deviceType == VRAPI_DEVICE_TYPE_OCULUSGO) {
|
||||
switch (button) {
|
||||
case BUTTON_TOUCHPAD: *touched = 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.input[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->Joystick.x;
|
||||
value[1] = input->Joystick.y;
|
||||
return true;
|
||||
case AXIS_TRIGGER: value[0] = input->IndexTrigger; return true;
|
||||
case AXIS_GRIP: value[0] = input->GripTrigger; return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool vrapi_vibrate(Device device, float strength, float duration, float frequency) {
|
||||
if (device != DEVICE_HAND_LEFT && device != DEVICE_HAND_RIGHT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t index = device - DEVICE_HAND_LEFT;
|
||||
state.hapticStrength[index] = CLAMP(strength, 0.0f, 1.0f);
|
||||
state.hapticDuration[index] = MAX(duration, 0.0f);
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct ModelData* vrapi_newModelData(Device device) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void vrapi_renderTo(void (*callback)(void*), void* userdata) {
|
||||
if (!state.session) return;
|
||||
|
||||
// Lazily create swapchain and canvases
|
||||
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);
|
||||
state.swapchain = vrapi_CreateTextureSwapChain3(VRAPI_TEXTURE_TYPE_2D_ARRAY, GL_SRGB8_ALPHA8, width, height, 1, 3);
|
||||
state.swapchainLength = vrapi_GetTextureSwapChainLength(state.swapchain);
|
||||
lovrAssert(state.swapchainLength <= sizeof(state.canvases) / sizeof(state.canvases[0]), "VrApi: The swapchain is too long");
|
||||
|
||||
for (uint32_t i = 0; i < state.swapchainLength; 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);
|
||||
}
|
||||
}
|
||||
|
||||
ovrTracking2 tracking = vrapi_GetPredictedTracking2(state.session, state.displayTime);
|
||||
|
||||
// Set up camera
|
||||
Camera camera;
|
||||
camera.canvas = state.canvases[state.swapchainIndex];
|
||||
for (uint32_t i = 0; i < 2; i++) {
|
||||
mat4_init(camera.viewMatrix[i], &tracking.Eye[i].ViewMatrix.M[0][0]);
|
||||
mat4_init(camera.projection[i], &tracking.Eye[i].ProjectionMatrix.M[0][0]);
|
||||
mat4_transpose(camera.projection[i]);
|
||||
mat4_transpose(camera.viewMatrix[i]);
|
||||
mat4_translate(camera.viewMatrix[i], 0.f, -state.offset, 0.f);
|
||||
}
|
||||
|
||||
// Render
|
||||
lovrGraphicsSetCamera(&camera, true);
|
||||
callback(userdata);
|
||||
lovrGraphicsDiscard(false, true, true);
|
||||
lovrGraphicsSetCamera(NULL, false);
|
||||
|
||||
// Submit a layer to VrApi
|
||||
ovrLayerProjection2 layer = vrapi_DefaultLayerProjection2();
|
||||
layer.HeadPose = tracking.HeadPose;
|
||||
layer.Textures[0].ColorSwapChain = state.swapchain;
|
||||
layer.Textures[1].ColorSwapChain = state.swapchain;
|
||||
layer.Textures[0].SwapChainIndex = state.swapchainIndex;
|
||||
layer.Textures[1].SwapChainIndex = state.swapchainIndex;
|
||||
layer.Textures[0].TexCoordsFromTanAngles = ovrMatrix4f_TanAngleMatrixFromProjection(&tracking.Eye[0].ProjectionMatrix);
|
||||
layer.Textures[1].TexCoordsFromTanAngles = ovrMatrix4f_TanAngleMatrixFromProjection(&tracking.Eye[1].ProjectionMatrix);
|
||||
|
||||
ovrSubmitFrameDescription2 frame = {
|
||||
.SwapInterval = 1,
|
||||
.FrameIndex = state.frameIndex,
|
||||
.DisplayTime = state.displayTime,
|
||||
.LayerCount = 1,
|
||||
.Layers = (const ovrLayerHeader2*[]) { &layer.Header }
|
||||
};
|
||||
|
||||
vrapi_SubmitFrame2(state.session, &frame);
|
||||
state.swapchainIndex = (state.swapchainIndex + 1) % state.swapchainLength;
|
||||
}
|
||||
|
||||
static void vrapi_update(float dt) {
|
||||
if (!state.session) return;
|
||||
|
||||
VRAPI_LARGEST_EVENT_TYPE event;
|
||||
while (vrapi_PollEvent(&event.EventHeader) == ovrSuccess) {
|
||||
switch (event.EventHeader.EventType) {
|
||||
case VRAPI_EVENT_FOCUS_GAINED:
|
||||
lovrEventPush((Event) { .type = EVENT_FOCUS, .data.boolean = { true } });
|
||||
break;
|
||||
|
||||
case VRAPI_EVENT_FOCUS_LOST:
|
||||
lovrEventPush((Event) { .type = EVENT_FOCUS, .data.boolean = { false } });
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
state.frameIndex++;
|
||||
state.displayTime = vrapi_GetPredictedDisplayTime(state.session, state.frameIndex);
|
||||
|
||||
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);
|
||||
uint32_t index = (info.ControllerCapabilities & ovrControllerCaps_LeftHand) ? 0 : 1;
|
||||
state.controllerInfo[index] = info;
|
||||
state.input[index].Header.ControllerType = header.Type;
|
||||
vrapi_GetCurrentInputState(state.session, header.DeviceID, &state.input[index].Header);
|
||||
} else if (header.Type == ovrControllerType_Hand) {
|
||||
ovrInputHandCapabilities info;
|
||||
info.Header = header;
|
||||
vrapi_GetInputDeviceCapabilities(state.session, &info.Header);
|
||||
uint32_t index = (info.HandCapabilities & ovrHandCaps_LeftHand) ? 0 : 1;
|
||||
state.controllerInfo[index].Header.Type = header.Type;
|
||||
state.handInput[index].Header.ControllerType = header.Type;
|
||||
vrapi_GetCurrentInputState(state.session, header.DeviceID, &state.handInput[index].Header);
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < 2; i++) {
|
||||
ovrInputCapabilityHeader* header = &state.controllerInfo[i].Header;
|
||||
if (header->Type == ovrControllerType_TrackedRemote) {
|
||||
state.hapticDuration[i] -= dt;
|
||||
float strength = state.hapticDuration[i] > 0.f ? state.hapticStrength[i] : 0.f;
|
||||
vrapi_SetHapticVibrationSimple(state.session, header->DeviceID, strength);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
};
|
|
@ -0,0 +1,10 @@
|
|||
package org.lovr.app;
|
||||
|
||||
import android.app.NativeActivity;
|
||||
|
||||
public class Activity extends NativeActivity {
|
||||
static {
|
||||
System.loadLibrary("lovr");
|
||||
System.loadLibrary("vrapi");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.lovr.app">
|
||||
<uses-sdk android:minSdkVersion="23" android:targetSdkVersion="25"/>
|
||||
<uses-feature android:glEsVersion="0x00030001" android:required="true"/>
|
||||
<uses-feature android:name="android.hardware.vr.headtracking" android:required="false"/>
|
||||
<application android:allowBackup="false" android:label="LÖVR">
|
||||
<meta-data android:name="com.samsung.android.vr.application.mode" android:value="vr_only"/>
|
||||
<activity android:name="Activity">
|
||||
<meta-data android:name="android.app.lib_name" android:value="lovr"/>
|
||||
<meta-data android:name="com.oculus.vr.focusaware" android:value="true"/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
|
@ -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
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue