#include "LunarWM.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "vec.h" void toplevel_commit_notify(struct wl_listener *l, void *) { auto *tl = wl_container_of(l, (LunarWM_Toplevel *)(NULL), commit); if (tl->xdg_toplevel->base->initial_commit) { wlr_xdg_toplevel_set_size(tl->xdg_toplevel, 0, 0); return; } LunarWM_Toplevel_update(tl); } void toplevel_destroy_notify(struct wl_listener *l, void *) { auto *tl = wl_container_of(l, (LunarWM_Toplevel *)(NULL), destroy); for (size_t i = 0; i < vector_size(tl->server->wayland.v_toplevels); i++) { if (tl == tl->server->wayland.v_toplevels[i]) { vector_remove(tl->server->wayland.v_toplevels, i); break; } } LunarWM_Toplevel_destroy(tl); free(tl); } bool LunarWM_Toplevel_init( LunarWM_Toplevel *tl, struct LunarWM *wm, struct wlr_xdg_toplevel *xdg) { tl->server = wm; tl->xdg_toplevel = xdg; tl->surface = xdg->base->surface; assert(tl->surface); tl->commit.notify = toplevel_commit_notify; wl_signal_add(&tl->surface->events.commit, &tl->commit); tl->destroy.notify = toplevel_destroy_notify; wl_signal_add(&tl->surface->events.destroy, &tl->destroy); return true; } bool LunarWM_Toplevel_destroy(LunarWM_Toplevel *this) { wl_list_remove(&this->commit.link); wl_list_remove(&this->destroy.link); if (this->locked_buffer != nullptr) { wlr_buffer_unlock(this->locked_buffer); } return true; } bool LunarWM_Toplevel_update(LunarWM_Toplevel *this) { this->texture = wlr_surface_get_texture(this->surface); struct wlr_client_buffer *cl_buf = this->surface->buffer; if ((this->texture == nullptr) || (cl_buf == nullptr)) { return false; } if ((this->locked_buffer != nullptr) && this->locked_buffer != &cl_buf->base) { wlr_buffer_unlock(this->locked_buffer); this->locked_buffer = nullptr; } if (this->locked_buffer == nullptr) { this->locked_buffer = wlr_buffer_lock(&cl_buf->base); } wlr_gles2_texture_get_attribs(this->texture, &this->attribs); this->gles_texture = gles2_get_texture(this->texture); if (this->gles_texture == nullptr) { return false; } this->rl_texture.id = (unsigned int)this->attribs.tex; this->rl_texture.width = (int)this->texture->width; this->rl_texture.height = (int)this->texture->height; this->rl_texture.mipmaps = 1; this->rl_texture.format = this->gles_texture->has_alpha ? PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 : PIXELFORMAT_UNCOMPRESSED_R8G8B8; SetTextureFilter(this->rl_texture, TEXTURE_FILTER_BILINEAR); SetTextureWrap(this->rl_texture, TEXTURE_WRAP_CLAMP); return true; } static void keysym_name(xkb_keysym_t sym, char *buf, size_t bufsz) { if (bufsz == 0) return; buf[0] = 0; char tmp[128] = { 0 }; int n = xkb_keysym_get_name(sym, tmp, sizeof tmp); if (n <= 0) { snprintf(buf, bufsz, "Unknown"); return; } if (strlen(tmp) == 1) { buf[0] = (char)toupper((unsigned char)tmp[0]); buf[1] = 0; } else { snprintf(buf, bufsz, "%s", tmp); } } static size_t fill_mod_list(uint32_t mods, char const *outv[4]) { size_t k = 0; if (mods & WLR_MODIFIER_LOGO) outv[k++] = "Super"; if (mods & WLR_MODIFIER_SHIFT) outv[k++] = "Shift"; if (mods & WLR_MODIFIER_CTRL) outv[k++] = "Ctrl"; if (mods & WLR_MODIFIER_ALT) outv[k++] = "Alt"; return k; } static char *make_hotkey_string(uint32_t mods, xkb_keysym_t sym) { char keyname[128]; keysym_name(sym, keyname, sizeof keyname); char const *mods_list[4] = { 0 }; size_t mcount = fill_mod_list(mods, mods_list); size_t need = strlen(keyname) + 1; for (size_t i = 0; i < mcount; ++i) need += strlen(mods_list[i]) + 1; char *s = (char *)malloc(need); if (!s) return NULL; s[0] = 0; for (size_t i = 0; i < mcount; ++i) { strcat(s, mods_list[i]); strcat(s, "-"); } strcat(s, keyname); return s; } static void Keyboard_modifiers_notify(struct wl_listener *listener, void *) { auto *kbd = wl_container_of(listener, (LunarWM_Keyboard *)(NULL), modifiers); wlr_seat_set_keyboard(kbd->server->wayland.seat, kbd->wlr_keyboard); wlr_seat_keyboard_notify_modifiers( kbd->server->wayland.seat, &kbd->wlr_keyboard->modifiers); } static void Keyboard_key_notify(struct wl_listener *listener, void *data) { auto *kbd = wl_container_of(listener, (LunarWM_Keyboard *)(NULL), key); auto *server = kbd->server; auto *event = (struct wlr_keyboard_key_event *)data; struct wlr_seat *seat = server->wayland.seat; uint32_t const keycode = event->keycode + 8; xkb_keysym_t const *syms = nullptr; int const nsyms = xkb_state_key_get_syms(kbd->wlr_keyboard->xkb_state, keycode, &syms); xkb_keysym_t const keysym = xkb_state_key_get_one_sym(kbd->wlr_keyboard->xkb_state, keycode); bool handled = false; uint32_t const modifiers = wlr_keyboard_get_modifiers(kbd->wlr_keyboard); if (server->wayland.session && event->state == WL_KEYBOARD_KEY_STATE_PRESSED && keysym >= XKB_KEY_XF86Switch_VT_1 && keysym <= XKB_KEY_XF86Switch_VT_12) { unsigned const vt = keysym - XKB_KEY_XF86Switch_VT_1 + 1; wlr_session_change_vt(server->wayland.session, vt); return; } if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { for (int i = 0; i < nsyms; i++) { if (trigger_ref_modsym( server->cman->L, &server->cman->cfg, modifiers, syms[i]) == 0) return; // handled } } if (!handled) { wlr_seat_set_keyboard(seat, kbd->wlr_keyboard); wlr_seat_keyboard_notify_key( seat, event->time_msec, event->keycode, event->state); } } static void Keyboard_destroy_notify(struct wl_listener *listener, void *) { auto *kbd = wl_container_of(listener, (LunarWM_Keyboard *)(NULL), destroy); wl_list_remove(&kbd->modifiers.link); wl_list_remove(&kbd->key.link); wl_list_remove(&kbd->destroy.link); wl_list_remove(&kbd->link); free(kbd); } static void new_input_listener_notify(struct wl_listener *listener, void *data) { LunarWM *wm = wl_container_of(listener, wm, wayland.new_input_listener); auto *dev = (struct wlr_input_device *)data; if (dev->type == WLR_INPUT_DEVICE_KEYBOARD) { struct wlr_keyboard *wlr_keyboard = wlr_keyboard_from_input_device(dev); LunarWM_Keyboard *keyboard = calloc(1, sizeof(*keyboard)); keyboard->server = wm; keyboard->wlr_keyboard = wlr_keyboard; struct xkb_rule_names const rule_names = { .options = wm->cman->cfg.input.keyboard.xkb_options, }; wlr_log(LOG_INFO, "xkb_options=%s", wm->cman->cfg.input.keyboard.xkb_options); struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); struct xkb_keymap *keymap = xkb_keymap_new_from_names( context, &rule_names, XKB_KEYMAP_COMPILE_NO_FLAGS); wlr_keyboard_set_keymap(wlr_keyboard, keymap); xkb_keymap_unref(keymap); xkb_context_unref(context); wlr_keyboard_set_repeat_info(wlr_keyboard, 25, 600); keyboard->modifiers.notify = Keyboard_modifiers_notify; wl_signal_add(&wlr_keyboard->events.modifiers, &keyboard->modifiers); keyboard->key.notify = Keyboard_key_notify; wl_signal_add(&wlr_keyboard->events.key, &keyboard->key); keyboard->destroy.notify = Keyboard_destroy_notify; wl_signal_add(&dev->events.destroy, &keyboard->destroy); wlr_seat_set_keyboard(wm->wayland.seat, keyboard->wlr_keyboard); wl_list_insert(&wm->wayland.keyboards, &keyboard->link); } else if (dev->type == WLR_INPUT_DEVICE_POINTER) { wlr_cursor_attach_input_device(wm->wayland.cursor, dev); } uint32_t caps = WL_SEAT_CAPABILITY_POINTER; if (!wl_list_empty(&wm->wayland.keyboards)) { caps |= WL_SEAT_CAPABILITY_KEYBOARD; } assert(wm->wayland.seat); wlr_seat_set_capabilities(wm->wayland.seat, caps); } static void new_xdg_popup_listener_notify(struct wl_listener *, void *) { // FIXME: Add support for popups } static void new_xdg_toplevel_listener_notify( struct wl_listener *listener, void *data) { LunarWM *wm = wl_container_of(listener, wm, wayland.new_xdg_toplevel_listener); auto *xdg_tl = (struct wlr_xdg_toplevel *)data; LunarWM_Toplevel *tl = (LunarWM_Toplevel *)calloc(1, sizeof(*tl)); if (!tl) { wlr_log(WLR_ERROR, "oom"); return; } if (LunarWM_Toplevel_init(tl, wm, xdg_tl)) { vector_add(&wm->wayland.v_toplevels, tl); } else { wlr_log(WLR_ERROR, "Failed to initialize Toplevel."); free(tl); } } static bool init_wayland(LunarWM *this) { wlr_log_init(WLR_DEBUG, nullptr); this->wayland.v_toplevels = vector_create(); this->wayland.display = wl_display_create(); if (this->wayland.display == nullptr) { return false; wlr_log(WLR_ERROR, "Failed to create wayland display"); } this->wayland.event_loop = wl_display_get_event_loop(this->wayland.display); if (this->wayland.event_loop == nullptr) { wlr_log(WLR_ERROR, "Failed to get wayland event loop"); return false; } this->wayland.backend = wlr_backend_autocreate(this->wayland.event_loop, nullptr); if (this->wayland.backend == nullptr) { wlr_log(WLR_ERROR, "Failed to create wlroots backend"); return false; } setenv("WLR_RENDERER", "gles2", 1); this->wayland.renderer = wlr_renderer_autocreate(this->wayland.backend); if (this->wayland.renderer == nullptr) { wlr_log(WLR_ERROR, "Failed to create wlroots renderer"); return false; } this->wayland.session = wlr_session_create(this->wayland.event_loop); if (this->wayland.session == nullptr) { wlr_log(WLR_ERROR, "Failed to create session"); return false; } this->wayland.egl = wlr_gles2_renderer_get_egl(this->wayland.renderer); if (this->wayland.egl == nullptr) { wlr_log(WLR_ERROR, "Failed to get egl information from renderer"); return false; } this->wayland.egl_display = wlr_egl_get_display(this->wayland.egl); this->wayland.egl_context = wlr_egl_get_context(this->wayland.egl); this->wayland.egl_config = EGL_NO_CONFIG_KHR; if (!wlr_renderer_init_wl_display( this->wayland.renderer, this->wayland.display)) { wlr_log( WLR_ERROR, "Failed to initialize renderer with wayland display"); return false; } this->wayland.allocator = wlr_allocator_autocreate( this->wayland.backend, this->wayland.renderer); if (this->wayland.allocator == nullptr) { wlr_log(WLR_ERROR, "Failed to create wlroots allocator"); } this->wayland.compositor = wlr_compositor_create( this->wayland.display, 5, this->wayland.renderer); if (this->wayland.compositor == nullptr) { wlr_log(WLR_ERROR, "Failed to create wlroots compositor"); } this->wayland.subcompositor = wlr_subcompositor_create(this->wayland.display); if (this->wayland.subcompositor == nullptr) { wlr_log(WLR_ERROR, "Failed to create wlroots subcompositor"); } this->wayland.data_device_manager = wlr_data_device_manager_create(this->wayland.display); if (this->wayland.data_device_manager == nullptr) { wlr_log(WLR_ERROR, "Failed to create wlroots data device manager"); } this->wayland.cursor = wlr_cursor_create(); wl_list_init(&this->wayland.keyboards); this->wayland.new_input_listener.notify = new_input_listener_notify; wl_signal_add(&this->wayland.backend->events.new_input, &this->wayland.new_input_listener); this->wayland.seat = wlr_seat_create(this->wayland.display, "seat0"); if (this->wayland.seat == nullptr) { wlr_log(WLR_ERROR, "Failed to create wlroots seat"); return false; } this->wayland.xdg_shell = wlr_xdg_shell_create(this->wayland.display, 3); this->wayland.new_xdg_toplevel_listener.notify = new_xdg_toplevel_listener_notify; wl_signal_add(&this->wayland.xdg_shell->events.new_toplevel, &this->wayland.new_xdg_toplevel_listener); this->wayland.new_xdg_popup_listener.notify = new_xdg_popup_listener_notify; wl_signal_add(&this->wayland.xdg_shell->events.new_popup, &this->wayland.new_xdg_popup_listener); return true; } static uint32_t get_swapchain_image( LunarWM *this, int swapchain_images_i, uint32_t index) { return this->xr.swapchain_images[swapchain_images_i].a_imgs[index].image; } static bool init_xr(LunarWM *this) { XrResult res = XR_SUCCESS; XrApplicationInfo app_info = { .applicationVersion = 1, .engineVersion = 1, .apiVersion = XR_CURRENT_API_VERSION, }; strncpy((char *)app_info.applicationName, "LunarWM", XR_MAX_APPLICATION_NAME_SIZE); strncpy( (char *)app_info.engineName, "LunarWM Engine", XR_MAX_ENGINE_NAME_SIZE); char const *instance_extensions[] = { XR_EXT_DEBUG_UTILS_EXTENSION_NAME, XR_MNDX_EGL_ENABLE_EXTENSION_NAME, XR_KHR_OPENGL_ES_ENABLE_EXTENSION_NAME, XR_EXT_HAND_TRACKING_EXTENSION_NAME, }; char const **v_active_instance_extensions = vector_create(); uint32_t extension_properties_count = 0; XrExtensionProperties *extension_properties; if (xrEnumerateInstanceExtensionProperties( nullptr, 0, &extension_properties_count, nullptr) != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to enumerate OpenXR instance extension properties (count)"); return false; } extension_properties = malloc(sizeof(*extension_properties) * extension_properties_count); for (uint32_t i = 0; i < extension_properties_count; ++i) { extension_properties[i].type = XR_TYPE_EXTENSION_PROPERTIES; extension_properties[i].next = NULL; } if (xrEnumerateInstanceExtensionProperties(nullptr, extension_properties_count, &extension_properties_count, extension_properties) != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to enumerate OpenXR instance extension properties"); return false; } for (size_t i = 0; i < ARRAY_SZ(instance_extensions); i++) { char const *requested_instance_extension = instance_extensions[i]; bool found = false; for (int j = 0; j < extension_properties_count; j++) { if (strcmp(requested_instance_extension, extension_properties[j].extensionName) != 0) { continue; } vector_add( &v_active_instance_extensions, requested_instance_extension); found = true; break; } if (!found) { wlr_log(WLR_ERROR, "Failed to find OpenXR instance extension: %s", requested_instance_extension); return false; } } { XrInstanceCreateInfo const ci = { .type = XR_TYPE_INSTANCE_CREATE_INFO, .next = nullptr, .createFlags = 0, .applicationInfo = app_info, .enabledApiLayerCount = 0, .enabledApiLayerNames = nullptr, .enabledExtensionCount = (uint32_t)vector_size(v_active_instance_extensions), .enabledExtensionNames = v_active_instance_extensions, }; XrInstance instance = nullptr; if (xrCreateInstance(&ci, &instance) != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to create OpenXR instance"); return false; } this->xr.instance = instance; res = xrGetInstanceProcAddr(this->xr.instance, "xrCreateHandTrackerEXT", (PFN_xrVoidFunction *)&this->xr.CreateHandTrackerEXT); if (res != XR_SUCCESS) { wlr_log( WLR_ERROR, "Failed to get proc addr xrCreateHandTrackerEXT"); return false; } res = xrGetInstanceProcAddr(this->xr.instance, "xrDestroyHandTrackerEXT", (PFN_xrVoidFunction *)&this->xr.DestroyHandTrackerEXT); if (res != XR_SUCCESS) { wlr_log( WLR_ERROR, "Failed to get proc addr xrDestroyHandTrackerEXT"); return false; } res = xrGetInstanceProcAddr(this->xr.instance, "xrLocateHandJointsEXT", (PFN_xrVoidFunction *)&this->xr.LocateHandJointsEXT); if (res != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to get proc addr xrLocateHandJointsEXT"); return false; } } { XrSystemGetInfo gi = { .type = XR_TYPE_SYSTEM_GET_INFO, .next = nullptr, }; XrFormFactor const factors[2] = { XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY, XR_FORM_FACTOR_HANDHELD_DISPLAY, }; for (size_t i = 0; i < ARRAY_SZ(factors); i++) { auto factor = factors[i]; gi.formFactor = factor; XrSystemId system_id = 0; if (xrGetSystem(this->xr.instance, &gi, &system_id) == XR_SUCCESS) { this->xr.system_id = system_id; break; } } if (!this->xr.system_id) { wlr_log(WLR_ERROR, "Failed to find valid form factor"); return false; } } { XrSystemProperties system_props = { .type = XR_TYPE_SYSTEM_PROPERTIES, .next = &this->xr.hand_tracking_system_properties, }; res = xrGetSystemProperties( this->xr.instance, this->xr.system_id, &system_props); if (res != XR_SUCCESS) { wlr_log(WLR_ERROR, "xrGetSystemProperties failed: %d", res); return false; } } XrGraphicsRequirementsOpenGLESKHR reqs = { .type = XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_ES_KHR, .next = nullptr, }; PFN_xrGetOpenGLESGraphicsRequirementsKHR xrGetOpenGLESGraphicsRequirementsKHR = nullptr; xrGetInstanceProcAddr(this->xr.instance, "xrGetOpenGLESGraphicsRequirementsKHR", (PFN_xrVoidFunction *)&xrGetOpenGLESGraphicsRequirementsKHR); if (xrGetOpenGLESGraphicsRequirementsKHR( this->xr.instance, this->xr.system_id, &reqs) != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to get GLES graphics requirements"); return false; } wlr_log(WLR_INFO, "OpenGL ES range: %d.%d.%d - %d.%d.%d\n", XR_VERSION_MAJOR(reqs.minApiVersionSupported), XR_VERSION_MINOR(reqs.minApiVersionSupported), XR_VERSION_PATCH(reqs.minApiVersionSupported), XR_VERSION_MAJOR(reqs.maxApiVersionSupported), XR_VERSION_MINOR(reqs.maxApiVersionSupported), XR_VERSION_PATCH(reqs.maxApiVersionSupported)); glEnable(GL_DEBUG_OUTPUT_KHR); glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR); { XrGraphicsBindingEGLMNDX gbind = { .type = XR_TYPE_GRAPHICS_BINDING_EGL_MNDX, .next = nullptr, .getProcAddress = eglGetProcAddress, .display = this->wayland.egl_display, .config = this->wayland.egl_config, .context = this->wayland.egl_context, }; XrSessionCreateInfo const ci = { .type = XR_TYPE_SESSION_CREATE_INFO, .next = &gbind, .createFlags = 0, .systemId = this->xr.system_id, }; if (xrCreateSession(this->xr.instance, &ci, &this->xr.session) != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to create OpenXR session"); return false; } } // Swapchain time! XrViewConfigurationType *a_view_config_types; uint32_t view_config_types_count = 0; { if (xrEnumerateViewConfigurations(this->xr.instance, this->xr.system_id, 0, &view_config_types_count, nullptr) != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to get amount of OpenXR view configurations"); return false; } a_view_config_types = malloc(sizeof(*a_view_config_types) * view_config_types_count); for (uint32_t i = 0; i < this->xr.view_configuration_views_count; ++i) { this->xr.a_view_configuration_views[i].type = XR_TYPE_VIEW_CONFIGURATION_VIEW; this->xr.a_view_configuration_views[i].next = NULL; } if (xrEnumerateViewConfigurations(this->xr.instance, this->xr.system_id, view_config_types_count, &view_config_types_count, a_view_config_types) != XR_SUCCESS) { wlr_log( WLR_ERROR, "Failed to enumerate OpenXR view configurations"); return false; } } { bool found = false; for (size_t i = 0; i < view_config_types_count; i++) { if (a_view_config_types[i] == XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO) { found = true; break; } } if (!found) { wlr_log(WLR_ERROR, "XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO not present"); return false; } } { if (xrEnumerateViewConfigurationViews(this->xr.instance, this->xr.system_id, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, 0, &this->xr.view_configuration_views_count, nullptr) != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to get amount of OpenXR view configuration views"); return false; } this->xr.a_view_configuration_views = malloc(sizeof(*this->xr.a_view_configuration_views) * this->xr.view_configuration_views_count); for (uint32_t i = 0; i < this->xr.view_configuration_views_count; ++i) { this->xr.a_view_configuration_views[i].type = XR_TYPE_VIEW_CONFIGURATION_VIEW; this->xr.a_view_configuration_views[i].next = NULL; } if (xrEnumerateViewConfigurationViews(this->xr.instance, this->xr.system_id, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, this->xr.view_configuration_views_count, &this->xr.view_configuration_views_count, this->xr.a_view_configuration_views) != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to enumerate OpenXR view configuration views"); return false; } } int64_t *a_swapchain_formats; uint32_t a_swapchain_formats_count = 0; { if (xrEnumerateSwapchainFormats( this->xr.session, 0, &a_swapchain_formats_count, nullptr) != XR_SUCCESS) { wlr_log( WLR_ERROR, "Failed to get amount of OpenXR swapchain formats"); return false; } a_swapchain_formats = malloc(sizeof(*a_swapchain_formats) * a_swapchain_formats_count); if (xrEnumerateSwapchainFormats(this->xr.session, a_swapchain_formats_count, &a_swapchain_formats_count, a_swapchain_formats) != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to enumerate OpenXR swapchain formats"); return false; } } { bool found = false; for (size_t i = 0; i < a_swapchain_formats_count; i++) { if (a_swapchain_formats[i] == GL_DEPTH_COMPONENT16) { found = true; break; } } if (!found) { wlr_log( WLR_ERROR, "Failed to find satisfying depth swapchain format"); return false; } } auto const swapchain_format_depth = GL_DEPTH_COMPONENT16; auto const swapchain_format_color = GL_SRGB8_ALPHA8; { bool found = false; for (size_t i = 0; i < a_swapchain_formats_count; i++) { if (a_swapchain_formats[i] == GL_SRGB8_ALPHA8) { found = true; break; } } if (!found) { wlr_log( WLR_ERROR, "Failed to find satisfying color swapchain format"); return false; } } uint32_t const view_count = this->xr.view_configuration_views_count; uint32_t const eyeW = this->xr.a_view_configuration_views[0].recommendedImageRectWidth; uint32_t const eyeH = this->xr.a_view_configuration_views[0].recommendedImageRectHeight; uint32_t const bufW = eyeW * view_count; if (!this->xr.swapchains.v_color) { this->xr.swapchains.v_color = vector_create(); } if (!this->xr.swapchains.v_depth) { this->xr.swapchains.v_depth = vector_create(); } vector_add(&this->xr.swapchains.v_color, (LunarWM_SwapchainInfo) {}); vector_add(&this->xr.swapchains.v_depth, (LunarWM_SwapchainInfo) {}); { auto *color_sc = &this->xr.swapchains.v_color[0]; auto *depth_sc = &this->xr.swapchains.v_depth[0]; auto *vcv = &this->xr.a_view_configuration_views[0]; { // Create color swapchain XrSwapchainCreateInfo const ci = { .type = XR_TYPE_SWAPCHAIN_CREATE_INFO, .next = nullptr, .createFlags = 0, .usageFlags = XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT, .format = swapchain_format_color, .sampleCount = vcv->recommendedSwapchainSampleCount, .width = bufW, .height = eyeH, .faceCount = 1, .arraySize = 1, .mipCount = 1, }; color_sc->swapchain_format = ci.format; if (xrCreateSwapchain(this->xr.session, &ci, &color_sc->swapchain) != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to create OpenXR color swapchain"); return false; } } { // Create depth swapchain XrSwapchainCreateInfo const ci = { .type = XR_TYPE_SWAPCHAIN_CREATE_INFO, .next = nullptr, .createFlags = 0, .usageFlags = XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, .format = swapchain_format_depth, .sampleCount = vcv->recommendedSwapchainSampleCount, .width = bufW, .height = eyeH, .faceCount = 1, .arraySize = 1, .mipCount = 1, }; depth_sc->swapchain_format = ci.format; if (xrCreateSwapchain(this->xr.session, &ci, &depth_sc->swapchain) != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to create OpenXR depth swapchain"); return false; } } // Enumerate swapchain images { // Color this->xr.swapchain_images[0].handle = color_sc->swapchain; if (xrEnumerateSwapchainImages(color_sc->swapchain, 0, &this->xr.swapchain_images[0].a_imgs_count, nullptr) != XR_SUCCESS) { wlr_log( WLR_ERROR, "Failed to get Color Swapchain Images count."); return false; } this->xr.swapchain_images[0].a_imgs = malloc(sizeof(*this->xr.swapchain_images[0].a_imgs) * this->xr.swapchain_images[0].a_imgs_count); for (uint32_t i = 0; i < this->xr.swapchain_images[0].a_imgs_count; ++i) { this->xr.swapchain_images[0].a_imgs[i].type = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_ES_KHR; this->xr.swapchain_images[0].a_imgs[i].next = NULL; } if (xrEnumerateSwapchainImages(color_sc->swapchain, this->xr.swapchain_images[0].a_imgs_count, &this->xr.swapchain_images[0].a_imgs_count, (XrSwapchainImageBaseHeader *)this->xr.swapchain_images[0] .a_imgs) != XR_SUCCESS) { wlr_log( WLR_ERROR, "Failed to enumerate color swapchain images."); return false; } } { // Depth this->xr.swapchain_images[1].handle = depth_sc->swapchain; if (xrEnumerateSwapchainImages(depth_sc->swapchain, 0, &this->xr.swapchain_images[1].a_imgs_count, nullptr) != XR_SUCCESS) { wlr_log( WLR_ERROR, "Failed to get depth Swapchain Images count."); return false; } this->xr.swapchain_images[1].a_imgs = malloc(sizeof(*this->xr.swapchain_images[1].a_imgs) * this->xr.swapchain_images[1].a_imgs_count); for (uint32_t i = 0; i < this->xr.swapchain_images[1].a_imgs_count; ++i) { this->xr.swapchain_images[1].a_imgs[i].type = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_ES_KHR; this->xr.swapchain_images[1].a_imgs[i].next = NULL; } if (xrEnumerateSwapchainImages(depth_sc->swapchain, this->xr.swapchain_images[1].a_imgs_count, &this->xr.swapchain_images[1].a_imgs_count, (XrSwapchainImageBaseHeader *)this->xr.swapchain_images[1] .a_imgs) != XR_SUCCESS) { wlr_log( WLR_ERROR, "Failed to enumerate depth swapchain images."); return false; } } // Get image views for (uint32_t j = 0; j < this->xr.swapchain_images[0].a_imgs_count; j++) { GLuint framebuffer = 0; glGenFramebuffers(1, &framebuffer); GLenum const attachment = GL_COLOR_ATTACHMENT0; glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, attachment, GL_TEXTURE_2D, get_swapchain_image(this, 0, j), 0); GLenum const result = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER); if (result != GL_FRAMEBUFFER_COMPLETE) { wlr_log(WLR_ERROR, "Failed to create color framebuffer"); return false; } glBindFramebuffer(GL_FRAMEBUFFER, 0); if (!color_sc->v_image_views) { color_sc->v_image_views = vector_create(); } vector_add(&color_sc->v_image_views, framebuffer); } for (uint32_t j = 0; j < this->xr.swapchain_images[1].a_imgs_count; j++) { GLuint framebuffer = 0; glGenFramebuffers(1, &framebuffer); GLenum const attachment = GL_DEPTH_ATTACHMENT; glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, attachment, GL_TEXTURE_2D, get_swapchain_image(this, 1, j), 0); GLenum const result = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER); if (result != GL_FRAMEBUFFER_COMPLETE) { wlr_log(WLR_ERROR, "Failed to create depth framebuffer"); return false; } glBindFramebuffer(GL_FRAMEBUFFER, 0); if (!depth_sc->v_image_views) { depth_sc->v_image_views = vector_create(); } vector_add(&depth_sc->v_image_views, framebuffer); } } assert(this->xr.instance); assert(this->xr.system_id); XrEnvironmentBlendMode *a_environment_blend_modes; uint32_t a_environment_blend_modes_count = 0; { // Get available blend modes if (xrEnumerateEnvironmentBlendModes(this->xr.instance, this->xr.system_id, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, 0, &a_environment_blend_modes_count, nullptr) != XR_SUCCESS) { wlr_log( WLR_ERROR, "Failed to get OpenXR environment blend mode count"); return false; } a_environment_blend_modes = malloc(sizeof(*a_environment_blend_modes) * a_environment_blend_modes_count); if (xrEnumerateEnvironmentBlendModes(this->xr.instance, this->xr.system_id, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, a_environment_blend_modes_count, &a_environment_blend_modes_count, a_environment_blend_modes) != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to get XR environment blend modes"); return false; } } XrEnvironmentBlendMode const requested_environment_blend_modes[] = { XR_ENVIRONMENT_BLEND_MODE_OPAQUE, XR_ENVIRONMENT_BLEND_MODE_ADDITIVE, XR_ENVIRONMENT_BLEND_MODE_MAX_ENUM, }; for (size_t i = 0; i < ARRAY_SZ(requested_environment_blend_modes); i++) { this->xr.environment_blend_mode = requested_environment_blend_modes[i]; bool found = false; for (size_t j = 0; j < a_environment_blend_modes_count; j++) { if (requested_environment_blend_modes[i] == a_environment_blend_modes[j]) { found = true; break; } } if (found) { break; } } if (this->xr.environment_blend_mode == XR_ENVIRONMENT_BLEND_MODE_MAX_ENUM) { wlr_log(WLR_INFO, "Failed to find a compatible blend mode. Defaulting to " "XR_ENVIRONMENT_BLEND_MODE_OPAQUE."); this->xr.environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE; } { // Reference space XrReferenceSpaceCreateInfo const ci = { .type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO, .next = nullptr, .referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE, .poseInReferenceSpace = { .orientation = { 0.0f, 0.0f, 0.0f, 1.0f }, .position = { 0.0f, 0.0f, 0.0f } }, }; if (xrCreateReferenceSpace(this->xr.session, &ci, &this->xr.local_space) != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to create OpenXR reference space"); return false; } } { // View reference space XrReferenceSpaceCreateInfo const ci = { .type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO, .next = nullptr, .referenceSpaceType = XR_REFERENCE_SPACE_TYPE_VIEW, .poseInReferenceSpace = { .orientation = { 0.0f, 0.0f, 0.0f, 1.0f }, .position = { 0.0f, 0.0f, 0.0f } }, }; if (xrCreateReferenceSpace(this->xr.session, &ci, &this->xr.view_space) != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to create OpenXR reference space"); return false; } } { // Create hand trackers for (size_t i = 0; i < 2; i++) { auto *hand = &this->xr.hands[i]; XrHandTrackerCreateInfoEXT const ci = { .type = XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT, .hand = i == 0 ? XR_HAND_LEFT_EXT : XR_HAND_RIGHT_EXT, .handJointSet = XR_HAND_JOINT_SET_DEFAULT_EXT, }; res = this->xr.CreateHandTrackerEXT( this->xr.session, &ci, &hand->hand_tracker); if (res != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to create hand tracker: %d", res); return false; } } } return true; } static int l_reload_config(lua_State *L) { ConfigManager *cm = g_wm.cman; if (!cm) { lua_pushnil(L); return 1; } config_manager_reload(cm); lua_pushnil(L); return 1; } static int l_quit_compositor(lua_State *L) { LunarWM_terminate(&g_wm); return 0; } bool LunarWM_init(LunarWM *this) { { // Init defaults memset(this, 0, sizeof(*this)); this->xr.session = XR_NULL_HANDLE; this->xr.session_state = XR_SESSION_STATE_UNKNOWN; this->xr.environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_MAX_ENUM; this->xr.local_space = this->xr.view_space = XR_NULL_HANDLE; this->xr.hand_tracking_system_properties.type = XR_TYPE_SYSTEM_HAND_TRACKING_PROPERTIES_EXT; this->renderer.camera.position = (Vector3) { 0, 0, 0 }; this->renderer.camera.target = (Vector3) { 0, 0, 1 }; this->renderer.camera.up = (Vector3) { 0, 1, 0 }; this->renderer.camera.fovy = 45; this->renderer.camera.projection = CAMERA_PERSPECTIVE; } this->cman = config_manager_create(get_config_path()); assert(this->cman); { lua_State *L = this->cman->L; lua_newtable(L); lua_pushcfunction(L, l_quit_compositor); lua_setfield(L, -2, "quit_compositor"); lua_pushcfunction(L, l_reload_config); lua_setfield(L, -2, "reload_config"); lua_setglobal(L, "lunar"); config_manager_reload(this->cman); } if (getenv("DISPLAY") != nullptr || getenv("WAYLAND_DISPLAY") != nullptr) { wlr_log(WLR_ERROR, "This compositor can only be ran in DRM mode."); return false; } if (!init_wayland(this)) { wlr_log(WLR_ERROR, "Failed to initialize wlroots"); return false; } auto *draw = eglGetCurrentSurface(EGL_DRAW); auto *read = eglGetCurrentSurface(EGL_READ); if (eglMakeCurrent(this->wayland.egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, this->wayland.egl_context) == EGL_FALSE) { wlr_log(WLR_ERROR, "Failed to eglMakeCurrent"); return false; } if (!init_xr(this)) { wlr_log(WLR_ERROR, "Failed to initialize OpenXR"); return false; } wlr_log(WLR_INFO, "OpenGL ES version: %s", glGetString(GL_VERSION)); InitWindow(0, 0, ""); if (eglMakeCurrent( this->wayland.egl_display, draw, read, this->wayland.egl_context) == EGL_FALSE) { wlr_log(WLR_ERROR, "Failed to eglMakeCurrent"); return false; } this->initialized = true; // FIXME: Cleanup. return true; } void LunarWM_destroy(LunarWM *this) { // FIXME: Cleanup. } void LunarWM_terminate(LunarWM *this) { wlr_log(WLR_INFO, "Stopping compositor"); this->running = false; } static void poll_events_xr(LunarWM *this) { XrEventDataBuffer event_data = { .type = XR_TYPE_EVENT_DATA_BUFFER, }; while (xrPollEvent(this->xr.instance, &event_data) == XR_SUCCESS) { switch (event_data.type) { case XR_TYPE_EVENT_DATA_EVENTS_LOST: { auto *el = (XrEventDataEventsLost *)&event_data; wlr_log(WLR_INFO, "OPENXR: Events Lost: %d", el->lostEventCount); break; } case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING: { auto *ilp = (XrEventDataInstanceLossPending *)&event_data; wlr_log(WLR_INFO, "OPENXR: Instance Loss Pending at: %ld", ilp->lossTime); this->xr.session_running = false; this->running = false; break; } case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED: { auto *ipc = (XrEventDataInteractionProfileChanged *)&event_data; wlr_log(WLR_INFO, "OPENXR: Interaction Profile changed for Session: " "%p", ipc->session); if (ipc->session != this->xr.session) { wlr_log(WLR_ERROR, "XrEventDataInteractionProfileChanged for " "unknown Session"); break; } break; } case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING: { auto *scp = (XrEventDataReferenceSpaceChangePending *)&event_data; wlr_log(WLR_INFO, "OPENXR: Reference Space Change pending for " "Session: %p", scp->session); if (scp->session != this->xr.session) { wlr_log(WLR_ERROR, "XrEventDataReferenceSpaceChangePending for " "unknown " "Session"); break; } break; } case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: { auto *sc = (XrEventDataSessionStateChanged *)&event_data; if (sc->session != this->xr.session) { wlr_log(WLR_ERROR, "XrEventDataSessionStateChanged for unknown " "Session"); break; } if (sc->state == XR_SESSION_STATE_READY) { XrSessionBeginInfo bi = { .type = XR_TYPE_SESSION_BEGIN_INFO }; bi.primaryViewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; bi.primaryViewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; if (xrBeginSession(this->xr.session, &bi) != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to begin session"); } else { this->xr.session_running = true; } } if (sc->state == XR_SESSION_STATE_STOPPING) { if (xrEndSession(this->xr.session) != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to end session"); } this->xr.session_running = false; } if (sc->state == XR_SESSION_STATE_EXITING) { this->xr.session_running = false; this->running = false; } if (sc->state == XR_SESSION_STATE_LOSS_PENDING) { this->xr.session_running = false; this->running = false; } this->xr.session_state = sc->state; break; } default: { break; } } } } bool acquire_wait(XrSwapchain sc, uint32_t *idx) { XrSwapchainImageAcquireInfo ai = { .type = XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO, .next = NULL }; if (xrAcquireSwapchainImage(sc, &ai, idx) != XR_SUCCESS) { return false; } XrSwapchainImageWaitInfo wi = { .type = XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO, .next = NULL, .timeout = XR_INFINITE_DURATION }; return xrWaitSwapchainImage(sc, &wi) == XR_SUCCESS; } static inline Matrix xr_matrix(XrPosef const pose) { Matrix const translation = MatrixTranslate(pose.position.x, pose.position.y, pose.position.z); Matrix const rotation = QuaternionToMatrix((Quaternion) { pose.orientation.x, pose.orientation.y, pose.orientation.z, pose.orientation.w, }); return MatrixMultiply(rotation, translation); } static inline Matrix xr_projection_matrix(XrFovf const fov) { static_assert(RL_CULL_DISTANCE_FAR > RL_CULL_DISTANCE_NEAR); Matrix matrix = {}; auto const near = (float)RL_CULL_DISTANCE_NEAR; auto const far = (float)RL_CULL_DISTANCE_FAR; float const tan_angle_left = tanf(fov.angleLeft); float const tan_angle_right = tanf(fov.angleRight); float const tan_angle_down = tanf(fov.angleDown); float const tan_angle_up = tanf(fov.angleUp); float const tan_angle_width = tan_angle_right - tan_angle_left; float const tan_angle_height = tan_angle_up - tan_angle_down; matrix.m0 = 2 / tan_angle_width; matrix.m4 = 0; matrix.m8 = (tan_angle_right + tan_angle_left) / tan_angle_width; matrix.m12 = 0; matrix.m1 = 0; matrix.m5 = 2 / tan_angle_height; matrix.m9 = (tan_angle_up + tan_angle_down) / tan_angle_height; matrix.m13 = 0; matrix.m2 = 0; matrix.m6 = 0; matrix.m10 = -(far + near) / (far - near); matrix.m14 = -(far * (near + near)) / (far - near); matrix.m3 = 0; matrix.m7 = 0; matrix.m11 = -1; matrix.m15 = 0; return matrix; } static void DrawBillboardNoShear( Camera3D const cam, Texture2D tex, Vector3 pos, float scale, Color tint) { Rectangle const src = { 0, 0, tex.width, tex.height }; Vector2 const size = { scale * fabsf(src.width / src.height), scale }; Vector2 const origin = { size.x * 0.5f, size.y * 0.5f }; DrawBillboardPro(cam, tex, src, pos, cam.up, size, origin, 0.0f, tint); } void render_3d(LunarWM *this, float /*dt*/) { DrawGrid(10, 1); for (size_t i = 0; i < vector_size(this->wayland.v_toplevels); i++) { auto *tl = this->wayland.v_toplevels[i]; LunarWM_Toplevel_update(tl); DrawBillboardNoShear(this->renderer.camera, tl->rl_texture, (Vector3) { 0, 1, -1.4f }, 1.0f, WHITE); } for (int h = 0; h < 2; ++h) { auto *handInfo = &this->xr.hands[h]; for (size_t k = 0; k < XR_HAND_JOINT_COUNT_EXT; ++k) { auto const *jl = &handInfo->joint_locations[k]; // NOLINT Vector3 const pos = { jl->pose.position.x, jl->pose.position.y, jl->pose.position.z, }; DrawSphere(pos, jl->radius, RED); } } } static bool render_layer(LunarWM *this, LunarWM_RenderLayerInfo *info, float dt) { auto const view_count = (uint32_t)this->xr.view_configuration_views_count; assert(view_count == 2); XrView views[2] = {}; for (int i = 0; i < ARRAY_SZ(views); i++) { views[i] = (XrView) { .type = XR_TYPE_VIEW, }; } XrViewLocateInfo locInfo = { .type = XR_TYPE_VIEW_LOCATE_INFO, .viewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, .displayTime = info->predicted_display_time, .space = this->xr.local_space, }; XrViewState viewState = { .type = XR_TYPE_VIEW_STATE }; uint32_t located = 0; if (xrLocateViews( this->xr.session, &locInfo, &viewState, view_count, &located, views) != XR_SUCCESS || located != view_count) { wlr_log(WLR_ERROR, "Failed to locate views"); return false; } // acquire swapchain images auto *color_sc = &this->xr.swapchains.v_color[0]; auto *depth_sc = &this->xr.swapchains.v_depth[0]; uint32_t col_idx = 0; uint32_t dep_idx = 0; if (!acquire_wait(color_sc->swapchain, &col_idx) || !acquire_wait(depth_sc->swapchain, &dep_idx)) { wlr_log(WLR_ERROR, "Swap-chain acquire failed"); return false; } GLuint color_tex = get_swapchain_image(this, 0, col_idx); GLuint depth_tex = get_swapchain_image(this, 1, dep_idx); // build FBO if (this->renderer.fbo == 0u) { glGenFramebuffers(1, &this->renderer.fbo); } glBindFramebuffer(GL_FRAMEBUFFER, this->renderer.fbo); rlFramebufferAttach(this->renderer.fbo, color_tex, RL_ATTACHMENT_COLOR_CHANNEL0, RL_ATTACHMENT_TEXTURE2D, 0); rlFramebufferAttach(this->renderer.fbo, depth_tex, RL_ATTACHMENT_DEPTH, RL_ATTACHMENT_TEXTURE2D, 0); assert(rlFramebufferComplete(this->renderer.fbo)); uint32_t const eyeW = this->xr.a_view_configuration_views[0].recommendedImageRectWidth; uint32_t const eyeH = this->xr.a_view_configuration_views[0].recommendedImageRectHeight; this->renderer.tmp_rt = (RenderTexture2D) { .id = this->renderer.fbo, .texture = { color_tex, (int)eyeW * view_count, (int)eyeH, 1, -1 }, .depth = { depth_tex, (int)eyeW * view_count, (int)eyeH, 1, -1 }, }; // head-space view matrix (matches rlOpenXR) XrSpaceLocation headLoc = { .type = XR_TYPE_SPACE_LOCATION }; xrLocateSpace(this->xr.view_space, this->xr.local_space, info->predicted_display_time, &headLoc); auto const headView = MatrixInvert(xr_matrix(headLoc.pose)); // per-eye projection + view-offset Matrix const projR = xr_projection_matrix(views[0].fov); Matrix const projL = xr_projection_matrix(views[1].fov); Matrix const viewOffL = MatrixMultiply(xr_matrix(views[0].pose), headView); Matrix const viewOffR = MatrixMultiply(xr_matrix(views[1].pose), headView); // draw BeginTextureMode(this->renderer.tmp_rt); rlEnableStereoRender(); rlSetMatrixProjectionStereo(projR, projL); // right, left (yes) rlSetMatrixViewOffsetStereo(viewOffR, viewOffL); glViewport(0, 0, (GLsizei)eyeW * view_count, (GLsizei)eyeH); ClearBackground((Color) { 0, 0, 10, 255 }); for (int i = 0; i < 1; i++) { XrTime const time = info->predicted_display_time; XrSpaceLocation view_location = { .type = XR_TYPE_SPACE_LOCATION }; XrResult const result = xrLocateSpace( this->xr.view_space, this->xr.local_space, time, &view_location); if (result != XR_SUCCESS) { break; } if ((view_location.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) != 0u) { auto const pos = view_location.pose.position; this->renderer.camera.position = (Vector3) { pos.x, pos.y, pos.z }; } if ((view_location.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) != 0u) { auto const rot = view_location.pose.orientation; auto const forward = Vector3RotateByQuaternion((Vector3) { 0, 0, -1 }, (Quaternion) { rot.x, rot.y, rot.z, rot.w }); auto const up = Vector3RotateByQuaternion((Vector3) { 0, 1, 0 }, (Quaternion) { rot.x, rot.y, rot.z, rot.w }); this->renderer.camera.target = Vector3Add(this->renderer.camera.position, forward); this->renderer.camera.up = up; } } BeginMode3D(this->renderer.camera); { render_3d(this, dt); } EndMode3D(); rlDisableStereoRender(); EndTextureMode(); // release swapchain images XrSwapchainImageReleaseInfo const ri = { .type = XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO }; xrReleaseSwapchainImage(color_sc->swapchain, &ri); xrReleaseSwapchainImage(depth_sc->swapchain, &ri); // fill projection layer info->layer_projection_views_count = view_count; for (uint32_t i = 0; i < view_count; ++i) { int32_t const xOff = i * eyeW; auto *pv = &info->layer_projection_views[i]; pv->pose = views[i].pose; pv->fov = views[i].fov; pv->subImage.swapchain = color_sc->swapchain; pv->subImage.imageRect.offset = (XrOffset2Di) { .x = xOff, .y = 0 }; pv->subImage.imageRect.extent = (XrExtent2Di) { .width = eyeW, .height = eyeH }; pv->subImage.imageArrayIndex = 0; } info->layer_projection = (XrCompositionLayerProjection) { .type = XR_TYPE_COMPOSITION_LAYER_PROJECTION, .layerFlags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT | XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT, .space = this->xr.local_space, .viewCount = view_count, .views = info->layer_projection_views, }; info->layers_count = 0; info->layers[info->layers_count++] = (XrCompositionLayerBaseHeader *)&info->layer_projection; return true; } void LunarWM_run(LunarWM *this) { assert(this); assert(this->initialized); if (!wlr_backend_start(this->wayland.backend)) { wlr_log(WLR_ERROR, "Failed to start backend"); return; } auto const *socket = wl_display_add_socket_auto(this->wayland.display); if (socket == nullptr) { wlr_log(WLR_ERROR, "Failed to add wayland socket to display"); return; } setenv("WAYLAND_DISPLAY", socket, 1); wlr_log(LOG_INFO, "Running on WAYLAND_DISPLAY=%s", socket); this->running = true; struct timespec last, now; clock_gettime(CLOCK_MONOTONIC, &last); while (this->running) { clock_gettime(CLOCK_MONOTONIC, &now); float dt = (now.tv_sec - last.tv_sec) + (now.tv_nsec - last.tv_nsec) / 1000000000.0f; last = now; for (size_t i = 0; i < vector_size(this->wayland.v_toplevels); i++) { LunarWM_Toplevel *tl = this->wayland.v_toplevels[i]; if (tl->surface) { wlr_surface_send_frame_done(tl->surface, &now); } } wl_display_flush_clients(this->wayland.display); wl_event_loop_dispatch(this->wayland.event_loop, 0); auto *draw = eglGetCurrentSurface(EGL_DRAW); auto *read = eglGetCurrentSurface(EGL_READ); if (eglMakeCurrent(this->wayland.egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, this->wayland.egl_context) == EGL_FALSE) { wlr_log(WLR_ERROR, "Failed to eglMakeCurrent"); return; } WindowShouldClose(); BeginDrawing(); poll_events_xr(this); if (!this->xr.session_running) { EndDrawing(); continue; } XrFrameState frame_state = { .type = XR_TYPE_FRAME_STATE, }; XrFrameWaitInfo const frame_wait_info = { .type = XR_TYPE_FRAME_WAIT_INFO, .next = nullptr, }; if (xrWaitFrame(this->xr.session, &frame_wait_info, &frame_state) != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to wait for OpenXR frame"); return; } XrFrameBeginInfo const frame_begin_info = { .type = XR_TYPE_FRAME_BEGIN_INFO, .next = nullptr, }; XrResult res = xrBeginFrame(this->xr.session, &frame_begin_info); if (res != XR_FRAME_DISCARDED && res != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to begin the OpenXR Frame: %d", res); return; } if (this->xr.hand_tracking_system_properties.supportsHandTracking) { XrActionStateGetInfo const si = { .type = XR_TYPE_ACTION_STATE_GET_INFO, }; for (size_t i = 0; i < 2; i++) { LunarWM_Hand *hand = &this->xr.hands[i]; bool const unobstructed = true; XrHandJointsMotionRangeInfoEXT mri = { .type = XR_TYPE_HAND_JOINTS_MOTION_RANGE_INFO_EXT, }; if (unobstructed) { mri.handJointsMotionRange = XR_HAND_JOINTS_MOTION_RANGE_UNOBSTRUCTED_EXT; } else { mri.handJointsMotionRange = XR_HAND_JOINTS_MOTION_RANGE_CONFORMING_TO_CONTROLLER_EXT; } XrHandJointsLocateInfoEXT const li = { .type = XR_TYPE_HAND_JOINTS_LOCATE_INFO_EXT, .next = &mri, .baseSpace = this->xr.local_space, .time = frame_state.predictedDisplayTime, }; XrHandJointLocationsEXT hji = { .type = XR_TYPE_HAND_JOINT_LOCATIONS_EXT, .jointCount = XR_HAND_JOINT_COUNT_EXT, .jointLocations = hand->joint_locations, }; if (this->xr.LocateHandJointsEXT(hand->hand_tracker, &li, &hji) != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to locate hand joints"); return; } } } LunarWM_RenderLayerInfo render_layer_info = { .predicted_display_time = frame_state.predictedDisplayTime, .layer_projection.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION, .layer_projection.next = nullptr, }; bool const session_active = (this->xr.session_state == XR_SESSION_STATE_SYNCHRONIZED || this->xr.session_state == XR_SESSION_STATE_VISIBLE || this->xr.session_state == XR_SESSION_STATE_FOCUSED); if (session_active && (frame_state.shouldRender != 0u)) { auto rendered = render_layer(this, &render_layer_info, dt); if (rendered) { render_layer_info.layers[render_layer_info.layers_count] = (XrCompositionLayerBaseHeader *)&render_layer_info .layer_projection; } } XrFrameEndInfo const frame_end_info = { .type = XR_TYPE_FRAME_END_INFO, .displayTime = frame_state.predictedDisplayTime, .environmentBlendMode = this->xr.environment_blend_mode, .layerCount = render_layer_info.layers_count, .layers = (XrCompositionLayerBaseHeader const **)render_layer_info.layers, }; if (xrEndFrame(this->xr.session, &frame_end_info) != XR_SUCCESS) { wlr_log(WLR_ERROR, "Failed to end OpenXR frame"); return; } EndDrawing(); } }