Merge pull request #273 from bjornbytes/android-tup

Android Support
This commit is contained in:
Bjorn 2020-07-02 09:26:14 -07:00 committed by GitHub
commit 26b4b58479
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 1220 additions and 251 deletions

7
.gitignore vendored
View File

@ -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

View File

@ -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
View File

@ -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

View File

@ -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 |>

View File

@ -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=

View File

@ -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 },

View File

@ -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 }

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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() {

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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;

View File

@ -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
};

View File

@ -0,0 +1,10 @@
package org.lovr.app;
import android.app.NativeActivity;
public class Activity extends NativeActivity {
static {
System.loadLibrary("lovr");
System.loadLibrary("vrapi");
}
}

View File

@ -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>

View File

@ -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
},