diff --git a/src/api/l_headset.c b/src/api/l_headset.c index 1956e58e..db0f0be6 100644 --- a/src/api/l_headset.c +++ b/src/api/l_headset.c @@ -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; } diff --git a/src/modules/headset/headset.h b/src/modules/headset/headset.h index 6a3b7a0b..4327083d 100644 --- a/src/modules/headset/headset.h +++ b/src/modules/headset/headset.h @@ -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); diff --git a/src/modules/headset/headset_desktop.c b/src/modules/headset/headset_desktop.c index 8805052d..bfbc940c 100644 --- a/src/modules/headset/headset_desktop.c +++ b/src/modules/headset/headset_desktop.c @@ -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, diff --git a/src/modules/headset/headset_oculus.c b/src/modules/headset/headset_oculus.c index a3456005..900f7535 100644 --- a/src/modules/headset/headset_oculus.c +++ b/src/modules/headset/headset_oculus.c @@ -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, diff --git a/src/modules/headset/headset_openvr.c b/src/modules/headset/headset_openvr.c index 700b98c4..55f0e39d 100644 --- a/src/modules/headset/headset_openvr.c +++ b/src/modules/headset/headset_openvr.c @@ -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, diff --git a/src/modules/headset/headset_openxr.c b/src/modules/headset/headset_openxr.c index f096bf6e..dbdf55bb 100644 --- a/src/modules/headset/headset_openxr.c +++ b/src/modules/headset/headset_openxr.c @@ -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, diff --git a/src/modules/headset/headset_pico.c b/src/modules/headset/headset_pico.c index a29aa816..f9c612d2 100644 --- a/src/modules/headset/headset_pico.c +++ b/src/modules/headset/headset_pico.c @@ -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, diff --git a/src/modules/headset/headset_vrapi.c b/src/modules/headset/headset_vrapi.c index ae26416f..91d34549 100644 --- a/src/modules/headset/headset_vrapi.c +++ b/src/modules/headset/headset_vrapi.c @@ -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, diff --git a/src/modules/headset/headset_webxr.c b/src/modules/headset/headset_webxr.c index 9111173c..45f80c50 100644 --- a/src/modules/headset/headset_webxr.c +++ b/src/modules/headset/headset_webxr.c @@ -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, diff --git a/src/resources/boot.lua b/src/resources/boot.lua index 663eee4e..014d2535 100644 --- a/src/resources/boot.lua +++ b/src/resources/boot.lua @@ -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 }) diff --git a/src/resources/webxr.js b/src/resources/webxr.js index df2b79e4..8ab24da8 100644 --- a/src/resources/webxr.js +++ b/src/resources/webxr.js @@ -163,6 +163,10 @@ var webxr = { return false; }, + webxr_start: function() { + // Session is handled asynchronously + }, + webxr_destroy: function() { if (state.session) { state.session.end();