mirror of https://github.com/bjornbytes/lovr.git
lovr.headset.init -> lovr.headset.start;
- We need some headset initialization to happen upfront - But we still want some delayed initialization for when graphics is ready - Go back to headset initialization happening when module is required - Add lovr.headset.start that can be used for post-graphics init
This commit is contained in:
parent
7dc54cc264
commit
92400df89d
|
@ -113,59 +113,11 @@ static Device luax_optdevice(lua_State* L, int index) {
|
|||
return luax_checkenum(L, 1, Device, "head");
|
||||
}
|
||||
|
||||
static int l_lovrHeadsetInit(lua_State* L) {
|
||||
luax_pushconf(L);
|
||||
lua_getfield(L, -1, "headset");
|
||||
|
||||
size_t driverCount = 0;
|
||||
HeadsetDriver drivers[8];
|
||||
float supersample = 1.f;
|
||||
float offset = 1.7f;
|
||||
int msaa = 4;
|
||||
bool overlay = false;
|
||||
|
||||
if (lua_istable(L, -1)) {
|
||||
|
||||
// Drivers
|
||||
lua_getfield(L, -1, "drivers");
|
||||
int n = luax_len(L, -1);
|
||||
for (int i = 0; i < n; i++) {
|
||||
lua_rawgeti(L, -1, i + 1);
|
||||
drivers[driverCount++] = luax_checkenum(L, -1, HeadsetDriver, NULL);
|
||||
lovrAssert(driverCount < sizeof(drivers) / sizeof(drivers[0]), "Too many headset drivers specified in conf.lua");
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Supersample
|
||||
lua_getfield(L, -1, "supersample");
|
||||
if (lua_type(L, -1) == LUA_TBOOLEAN) {
|
||||
supersample = lua_toboolean(L, -1) ? 2.f : 1.f;
|
||||
} else {
|
||||
supersample = luax_optfloat(L, -1, 1.f);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Offset
|
||||
lua_getfield(L, -1, "offset");
|
||||
offset = luax_optfloat(L, -1, 1.7f);
|
||||
lua_pop(L, 1);
|
||||
|
||||
// MSAA
|
||||
lua_getfield(L, -1, "msaa");
|
||||
msaa = luaL_optinteger(L, -1, 4);
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Overlay
|
||||
lua_getfield(L, -1, "overlay");
|
||||
overlay = lua_toboolean(L, -1);
|
||||
lua_pop(L, 1);
|
||||
static int l_lovrHeadsetStart(lua_State* L) {
|
||||
lovrHeadsetDisplayDriver->start();
|
||||
FOREACH_TRACKING_DRIVER(driver) {
|
||||
driver->start();
|
||||
}
|
||||
|
||||
luax_atexit(L, lovrHeadsetDestroy); // Always make sure the headset module gets cleaned up
|
||||
lovrHeadsetInit(drivers, driverCount, supersample, offset, msaa, overlay);
|
||||
|
||||
lua_pop(L, 2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -725,7 +677,7 @@ static int l_lovrHeadsetGetHands(lua_State* L) {
|
|||
}
|
||||
|
||||
static const luaL_Reg lovrHeadset[] = {
|
||||
{ "init", l_lovrHeadsetInit },
|
||||
{ "start", l_lovrHeadsetStart },
|
||||
{ "getDriver", l_lovrHeadsetGetDriver },
|
||||
{ "getName", l_lovrHeadsetGetName },
|
||||
{ "getOriginType", l_lovrHeadsetGetOriginType },
|
||||
|
@ -769,6 +721,61 @@ static const luaL_Reg lovrHeadset[] = {
|
|||
int luaopen_lovr_headset(lua_State* L) {
|
||||
lua_newtable(L);
|
||||
luax_register(L, lovrHeadset);
|
||||
|
||||
luax_pushconf(L);
|
||||
lua_getfield(L, -1, "headset");
|
||||
|
||||
size_t driverCount = 0;
|
||||
HeadsetDriver drivers[8];
|
||||
float supersample = 1.f;
|
||||
float offset = 1.7f;
|
||||
int msaa = 4;
|
||||
bool overlay = false;
|
||||
|
||||
if (lua_istable(L, -1)) {
|
||||
|
||||
// Drivers
|
||||
lua_getfield(L, -1, "drivers");
|
||||
int n = luax_len(L, -1);
|
||||
for (int i = 0; i < n; i++) {
|
||||
lua_rawgeti(L, -1, i + 1);
|
||||
drivers[driverCount++] = luax_checkenum(L, -1, HeadsetDriver, NULL);
|
||||
lovrAssert(driverCount < sizeof(drivers) / sizeof(drivers[0]), "Too many headset drivers specified in conf.lua");
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Supersample
|
||||
lua_getfield(L, -1, "supersample");
|
||||
if (lua_type(L, -1) == LUA_TBOOLEAN) {
|
||||
supersample = lua_toboolean(L, -1) ? 2.f : 1.f;
|
||||
} else {
|
||||
supersample = luax_optfloat(L, -1, 1.f);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Offset
|
||||
lua_getfield(L, -1, "offset");
|
||||
offset = luax_optfloat(L, -1, 1.7f);
|
||||
lua_pop(L, 1);
|
||||
|
||||
// MSAA
|
||||
lua_getfield(L, -1, "msaa");
|
||||
msaa = luaL_optinteger(L, -1, 4);
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Overlay
|
||||
lua_getfield(L, -1, "overlay");
|
||||
overlay = lua_toboolean(L, -1);
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
if (lovrHeadsetInit(drivers, driverCount, supersample, offset, msaa, overlay)) {
|
||||
luax_atexit(L, lovrHeadsetDestroy);
|
||||
}
|
||||
|
||||
lua_pop(L, 2);
|
||||
|
||||
headsetRenderData.ref = LUA_NOREF;
|
||||
return 1;
|
||||
}
|
||||
|
|
|
@ -104,6 +104,8 @@ typedef enum {
|
|||
} HandJoint;
|
||||
|
||||
// Notes:
|
||||
// - init is called immediately, the graphics module may not exist yet
|
||||
// - start is called after the graphics module is initialized, can be used to set up graphics objects
|
||||
// - getDisplayFrequency may return 0.f if the information is unavailable.
|
||||
// - For isDown, changed can be set to false if change information is unavailable or inconvenient.
|
||||
// - getAxis may write 4 floats to the output value. The expected number is a constant (see axisCounts in l_headset).
|
||||
|
@ -113,6 +115,7 @@ typedef struct HeadsetInterface {
|
|||
struct HeadsetInterface* next;
|
||||
HeadsetDriver driverType;
|
||||
bool (*init)(float supersample, float offset, uint32_t msaa, bool overlay);
|
||||
void (*start)(void);
|
||||
void (*destroy)(void);
|
||||
bool (*getName)(char* name, size_t length);
|
||||
HeadsetOrigin (*getOriginType)(void);
|
||||
|
|
|
@ -47,6 +47,10 @@ static bool desktop_init(float supersample, float offset, uint32_t msaa, bool ov
|
|||
return true;
|
||||
}
|
||||
|
||||
static void desktop_start(void) {
|
||||
//
|
||||
}
|
||||
|
||||
static void desktop_destroy(void) {
|
||||
//
|
||||
}
|
||||
|
@ -285,6 +289,7 @@ static void desktop_update(float dt) {
|
|||
HeadsetInterface lovrHeadsetDesktopDriver = {
|
||||
.driverType = DRIVER_DESKTOP,
|
||||
.init = desktop_init,
|
||||
.start = desktop_start,
|
||||
.destroy = desktop_destroy,
|
||||
.getName = desktop_getName,
|
||||
.getOriginType = desktop_getOriginType,
|
||||
|
|
|
@ -112,6 +112,37 @@ static bool oculus_init(float supersample, float offset, uint32_t msaa, bool ove
|
|||
return true;
|
||||
}
|
||||
|
||||
static void oculus_start(void) {
|
||||
state.size = ovr_GetFovTextureSize(state.session, ovrEye_Left, state.desc.DefaultEyeFov[ovrEye_Left], 1.0f);
|
||||
state.size.w *= state.supersample;
|
||||
state.size.h *= state.supersample;
|
||||
|
||||
ovrTextureSwapChainDesc swdesc = {
|
||||
.Type = ovrTexture_2D,
|
||||
.ArraySize = 1,
|
||||
.Format = OVR_FORMAT_R8G8B8A8_UNORM_SRGB,
|
||||
.Width = 2 * state.size.w,
|
||||
.Height = state.size.h,
|
||||
.MipLevels = 1,
|
||||
.SampleCount = 1,
|
||||
.StaticImage = ovrFalse
|
||||
};
|
||||
lovrAssert(OVR_SUCCESS(ovr_CreateTextureSwapChainGL(state.session, &swdesc, &state.chain)), "Unable to create swapchain");
|
||||
|
||||
ovrMirrorTextureDesc mdesc = {
|
||||
.Width = lovrGraphicsGetWidth(),
|
||||
.Height = lovrGraphicsGetHeight(),
|
||||
.Format = OVR_FORMAT_R8G8B8A8_UNORM_SRGB,
|
||||
.MirrorOptions = ovrMirrorOption_LeftEyeOnly
|
||||
};
|
||||
lovrAssert(OVR_SUCCESS(ovr_CreateMirrorTextureWithOptionsGL(state.session, &mdesc, &state.mirror)), "Unable to create mirror texture");
|
||||
|
||||
CanvasFlags flags = { .depth = { .enabled = true, .format = FORMAT_D24S8 }, .stereo = true };
|
||||
state.canvas = lovrCanvasCreate(state.size.w, state.size.h, flags);
|
||||
|
||||
os_window_set_vsync(0);
|
||||
}
|
||||
|
||||
static void oculus_destroy(void) {
|
||||
for (size_t i = 0; i < state.textures.length; i++) {
|
||||
lovrRelease(state.textures.data[i], lovrTextureDestroy);
|
||||
|
@ -328,37 +359,6 @@ static ModelData* oculus_newModelData(Device device, bool animated) {
|
|||
}
|
||||
|
||||
static void oculus_renderTo(void (*callback)(void*), void* userdata) {
|
||||
if (!state.canvas) {
|
||||
state.size = ovr_GetFovTextureSize(state.session, ovrEye_Left, state.desc.DefaultEyeFov[ovrEye_Left], 1.0f);
|
||||
state.size.w *= state.supersample;
|
||||
state.size.h *= state.supersample;
|
||||
|
||||
ovrTextureSwapChainDesc swdesc = {
|
||||
.Type = ovrTexture_2D,
|
||||
.ArraySize = 1,
|
||||
.Format = OVR_FORMAT_R8G8B8A8_UNORM_SRGB,
|
||||
.Width = 2 * state.size.w,
|
||||
.Height = state.size.h,
|
||||
.MipLevels = 1,
|
||||
.SampleCount = 1,
|
||||
.StaticImage = ovrFalse
|
||||
};
|
||||
lovrAssert(OVR_SUCCESS(ovr_CreateTextureSwapChainGL(state.session, &swdesc, &state.chain)), "Unable to create swapchain");
|
||||
|
||||
ovrMirrorTextureDesc mdesc = {
|
||||
.Width = lovrGraphicsGetWidth(),
|
||||
.Height = lovrGraphicsGetHeight(),
|
||||
.Format = OVR_FORMAT_R8G8B8A8_UNORM_SRGB,
|
||||
.MirrorOptions = ovrMirrorOption_LeftEyeOnly
|
||||
};
|
||||
lovrAssert(OVR_SUCCESS(ovr_CreateMirrorTextureWithOptionsGL(state.session, &mdesc, &state.mirror)), "Unable to create mirror texture");
|
||||
|
||||
CanvasFlags flags = { .depth = { .enabled = true, .format = FORMAT_D24S8 }, .stereo = true };
|
||||
state.canvas = lovrCanvasCreate(state.size.w, state.size.h, flags);
|
||||
|
||||
os_window_set_vsync(0);
|
||||
}
|
||||
|
||||
ovrPosef EyeRenderPose[2];
|
||||
double sensorSampleTime;
|
||||
getEyePoses(EyeRenderPose, &sensorSampleTime);
|
||||
|
@ -460,6 +460,7 @@ static void oculus_update(float dt) {
|
|||
HeadsetInterface lovrHeadsetOculusDriver = {
|
||||
.driverType = DRIVER_OCULUS,
|
||||
.init = oculus_init,
|
||||
.start = oculus_start,
|
||||
.destroy = oculus_destroy,
|
||||
.getName = oculus_getName,
|
||||
.getOriginType = oculus_getOriginType,
|
||||
|
|
|
@ -254,6 +254,21 @@ static bool openvr_init(float supersample, float offset, uint32_t msaa, bool ove
|
|||
return true;
|
||||
}
|
||||
|
||||
static void openvr_start(void) {
|
||||
uint32_t width, height;
|
||||
state.system->GetRecommendedRenderTargetSize(&width, &height);
|
||||
width *= state.supersample;
|
||||
height *= state.supersample;
|
||||
CanvasFlags flags = { .depth = { true, false, FORMAT_D24S8 }, .stereo = true, .mipmaps = true, .msaa = state.msaa };
|
||||
state.canvas = lovrCanvasCreate(width, height, flags);
|
||||
Texture* texture = lovrTextureCreate(TEXTURE_2D, NULL, 0, true, true, state.msaa);
|
||||
lovrTextureAllocate(texture, width * 2, height, 1, FORMAT_RGBA);
|
||||
lovrTextureSetFilter(texture, lovrGraphicsGetDefaultFilter());
|
||||
lovrCanvasSetAttachments(state.canvas, &(Attachment) { texture, 0, 0 }, 1);
|
||||
lovrRelease(texture, lovrTextureDestroy);
|
||||
os_window_set_vsync(0);
|
||||
}
|
||||
|
||||
static void openvr_destroy(void) {
|
||||
lovrRelease(state.canvas, lovrCanvasDestroy);
|
||||
VR_ShutdownInternal();
|
||||
|
@ -804,21 +819,6 @@ static bool openvr_animate(Device device, Model* model) {
|
|||
}
|
||||
|
||||
static void openvr_renderTo(void (*callback)(void*), void* userdata) {
|
||||
if (!state.canvas) {
|
||||
uint32_t width, height;
|
||||
openvr_getDisplayDimensions(&width, &height);
|
||||
width *= state.supersample;
|
||||
height *= state.supersample;
|
||||
CanvasFlags flags = { .depth = { true, false, FORMAT_D24S8 }, .stereo = true, .mipmaps = true, .msaa = state.msaa };
|
||||
state.canvas = lovrCanvasCreate(width, height, flags);
|
||||
Texture* texture = lovrTextureCreate(TEXTURE_2D, NULL, 0, true, true, state.msaa);
|
||||
lovrTextureAllocate(texture, width * 2, height, 1, FORMAT_RGBA);
|
||||
lovrTextureSetFilter(texture, lovrGraphicsGetDefaultFilter());
|
||||
lovrCanvasSetAttachments(state.canvas, &(Attachment) { texture, 0, 0 }, 1);
|
||||
lovrRelease(texture, lovrTextureDestroy);
|
||||
os_window_set_vsync(0);
|
||||
}
|
||||
|
||||
float head[16];
|
||||
mat4_fromMat34(head, state.renderPoses[k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking.m);
|
||||
|
||||
|
@ -879,6 +879,7 @@ static void openvr_update(float dt) {
|
|||
HeadsetInterface lovrHeadsetOpenVRDriver = {
|
||||
.driverType = DRIVER_OPENVR,
|
||||
.init = openvr_init,
|
||||
.start = openvr_start,
|
||||
.destroy = openvr_destroy,
|
||||
.getName = openvr_getName,
|
||||
.getOriginType = openvr_getOriginType,
|
||||
|
|
|
@ -333,6 +333,13 @@ static bool openxr_init(float supersample, float offset, uint32_t msaa, bool ove
|
|||
}
|
||||
}
|
||||
|
||||
state.clipNear = .1f;
|
||||
state.clipFar = 100.f;
|
||||
state.frameState.type = XR_TYPE_FRAME_STATE;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void openxr_start(void) {
|
||||
{ // Session
|
||||
#if defined(LOVR_GL)
|
||||
XrGraphicsRequirementsOpenGLKHR requirements = { .type = XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_KHR, NULL };
|
||||
|
@ -413,7 +420,7 @@ static bool openxr_init(float supersample, float offset, uint32_t msaa, bool ove
|
|||
XR_INIT(xrAttachSessionActionSets(state.session, &attachInfo));
|
||||
}
|
||||
|
||||
{ // Spaaaaace
|
||||
{ // Spaaace
|
||||
|
||||
// Main reference space (can be stage or local)
|
||||
XrReferenceSpaceCreateInfo info = {
|
||||
|
@ -535,13 +542,7 @@ static bool openxr_init(float supersample, float offset, uint32_t msaa, bool ove
|
|||
#endif
|
||||
}
|
||||
|
||||
state.clipNear = .1f;
|
||||
state.clipFar = 100.f;
|
||||
|
||||
state.frameState.type = XR_TYPE_FRAME_STATE;
|
||||
os_window_set_vsync(0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void openxr_destroy(void) {
|
||||
|
@ -976,6 +977,7 @@ static void openxr_update(float dt) {
|
|||
HeadsetInterface lovrHeadsetOpenXRDriver = {
|
||||
.driverType = DRIVER_OPENXR,
|
||||
.init = openxr_init,
|
||||
.start = openxr_start,
|
||||
.destroy = openxr_destroy,
|
||||
.getName = openxr_getName,
|
||||
.getOriginType = openxr_getOriginType,
|
||||
|
|
|
@ -241,6 +241,10 @@ static bool pico_init(float supersample, float offset, uint32_t msaa, bool overl
|
|||
return true;
|
||||
}
|
||||
|
||||
static void pico_start(void) {
|
||||
//
|
||||
}
|
||||
|
||||
static void pico_destroy(void) {
|
||||
arr_free(&state.canvases);
|
||||
memset(&state, 0, sizeof(state));
|
||||
|
@ -425,6 +429,7 @@ static void pico_update(float dt) {
|
|||
HeadsetInterface lovrHeadsetPicoDriver = {
|
||||
.driverType = DRIVER_PICO,
|
||||
.init = pico_init,
|
||||
.start = pico_start,
|
||||
.destroy = pico_destroy,
|
||||
.getName = pico_getName,
|
||||
.getOriginType = pico_getOriginType,
|
||||
|
|
|
@ -70,6 +70,33 @@ static bool vrapi_init(float supersample, float offset, uint32_t msaa, bool over
|
|||
return true;
|
||||
}
|
||||
|
||||
static void vrapi_start(void) {
|
||||
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);
|
||||
width *= state.supersample;
|
||||
height *= state.supersample;
|
||||
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, 1);
|
||||
lovrCanvasSetAttachments(state.canvases[i], &(Attachment) { .texture = texture }, 1);
|
||||
lovrRelease(texture, lovrTextureDestroy);
|
||||
}
|
||||
}
|
||||
|
||||
static void vrapi_destroy() {
|
||||
if (state.session) {
|
||||
vrapi_LeaveVrMode(state.session);
|
||||
|
@ -628,34 +655,6 @@ static bool vrapi_animate(Device device, struct Model* model) {
|
|||
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);
|
||||
width *= state.supersample;
|
||||
height *= state.supersample;
|
||||
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, 1);
|
||||
lovrCanvasSetAttachments(state.canvases[i], &(Attachment) { .texture = texture }, 1);
|
||||
lovrRelease(texture, lovrTextureDestroy);
|
||||
}
|
||||
}
|
||||
|
||||
ovrTracking2 tracking = vrapi_GetPredictedTracking2(state.session, state.displayTime);
|
||||
|
||||
// Camera
|
||||
|
@ -806,6 +805,7 @@ static void vrapi_update(float dt) {
|
|||
HeadsetInterface lovrHeadsetVrApiDriver = {
|
||||
.driverType = DRIVER_VRAPI,
|
||||
.init = vrapi_init,
|
||||
.start = vrapi_start,
|
||||
.destroy = vrapi_destroy,
|
||||
.getName = vrapi_getName,
|
||||
.getOriginType = vrapi_getOriginType,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "headset/headset.h"
|
||||
|
||||
extern bool webxr_init(float supersample, float offset, uint32_t msaa, bool overlay);
|
||||
extern void webxr_start(void);
|
||||
extern void webxr_destroy(void);
|
||||
extern bool webxr_getName(char* name, size_t length);
|
||||
extern HeadsetOrigin webxr_getOriginType(void);
|
||||
|
@ -69,6 +70,7 @@ void webxr_detach() {
|
|||
HeadsetInterface lovrHeadsetWebXRDriver = {
|
||||
.driverType = DRIVER_WEBXR,
|
||||
.init = webxr_init,
|
||||
.start = webxr_start,
|
||||
.destroy = webxr_destroy,
|
||||
.getName = webxr_getName,
|
||||
.getOriginType = webxr_getOriginType,
|
||||
|
|
|
@ -165,11 +165,7 @@ function lovr.boot()
|
|||
end
|
||||
|
||||
if lovr.headset and lovr.graphics and conf.window then
|
||||
local ok, result = pcall(lovr.headset.init)
|
||||
if not ok then
|
||||
print(string.format('Warning: Could not load module %q: %s', 'headset', result))
|
||||
lovr.headset = nil
|
||||
end
|
||||
lovr.headset.start()
|
||||
end
|
||||
|
||||
lovr.handlers = setmetatable({}, { __index = lovr })
|
||||
|
|
|
@ -163,6 +163,10 @@ var webxr = {
|
|||
return false;
|
||||
},
|
||||
|
||||
webxr_start: function() {
|
||||
// Session is handled asynchronously
|
||||
},
|
||||
|
||||
webxr_destroy: function() {
|
||||
if (state.session) {
|
||||
state.session.end();
|
||||
|
|
Loading…
Reference in New Issue