#include "compositor.h" #include "common.h" #include // wlroots is a C library; wrap headers in extern "C" extern "C" { #include #include } #include "wlr/openxr_gl.h" #include #include #include #include #include #include #include #include void Compositor::create_instance() { // Wow, can't believe this compositor uses AI! XrApplicationInfo ai { .applicationName = {}, .applicationVersion = 1, .engineName = {}, .engineVersion = 1, .apiVersion = XR_API_VERSION_1_0, }; std::strncpy( ai.applicationName, "LunarWM", XR_MAX_APPLICATION_NAME_SIZE - 1); std::strncpy(ai.engineName, "LunarWM Engine", XR_MAX_ENGINE_NAME_SIZE - 1); std::vector const instance_extensions { XR_EXT_DEBUG_UTILS_EXTENSION_NAME, XR_KHR_OPENGL_ENABLE_EXTENSION_NAME, }; std::vector extension_props; { // Populate extension_props uint32_t len = 0; OPENXR_CHECK( xrEnumerateInstanceExtensionProperties, nullptr, 0, &len, nullptr); extension_props.resize(len, { XR_TYPE_EXTENSION_PROPERTIES }); OPENXR_CHECK(xrEnumerateInstanceExtensionProperties, nullptr, len, &len, extension_props.data()); } // Check that we have all our requested extensions available for (auto const& requested_instance_extension : instance_extensions) { bool found = false; for (auto const& eprop : extension_props) { if (strcmp(requested_instance_extension, eprop.extensionName) == 0) { found = true; break; } } if (!found) { throw std::runtime_error(std::format( "Failed to find extension: {}", requested_instance_extension)); } } { // Create our instance XrInstanceCreateInfo ci { .type = XR_TYPE_INSTANCE_CREATE_INFO, .next = nullptr, .createFlags = 0, .applicationInfo = ai, .enabledApiLayerCount = 0, .enabledApiLayerNames = nullptr, .enabledExtensionCount = static_cast(instance_extensions.size()), .enabledExtensionNames = instance_extensions.data(), }; OPENXR_CHECK(xrCreateInstance, &ci, &m_xr_instance); } } void Compositor::destroy_instance() { OPENXR_CHECK(xrDestroyInstance, m_xr_instance); } void Compositor::create_session() { XrSystemId system_id; { // Get system ID. XrSystemGetInfo system_gi { XR_TYPE_SYSTEM_GET_INFO }; system_gi.formFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY; OPENXR_CHECK(xrGetSystem, m_xr_instance, &system_gi, &system_id); // NOTE: Might be useful later. XrSystemProperties system_properties = { XR_TYPE_SYSTEM_PROPERTIES }; OPENXR_CHECK(xrGetSystemProperties, m_xr_instance, system_id, &system_properties); } { // Get GLX graphics requirements PFN_xrGetOpenGLGraphicsRequirementsKHR xr_get_gl_req = nullptr; xrGetInstanceProcAddr(m_xr_instance, "xrGetOpenGLGraphicsRequirementsKHR", reinterpret_cast(&xr_get_gl_req)); XrGraphicsRequirementsOpenGLKHR gl_req { XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_KHR }; OPENXR_CHECK(xr_get_gl_req, m_xr_instance, system_id, &gl_req); std::println("OpenGL requirements: min=0x{:X}, max=0x{:X}", gl_req.minApiVersionSupported, gl_req.maxApiVersionSupported); } XrGraphicsBindingOpenGLXlibKHR graphics_binding {}; { // Xlib stuff Display* display = XOpenDisplay(nullptr); if (!display) throw std::runtime_error("Failed to open X display"); int screen = DefaultScreen(display); static int visual_attribs[] = { GLX_X_RENDERABLE, True, GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, GLX_RENDER_TYPE, GLX_RGBA_BIT, GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, GLX_RED_SIZE, 8, GLX_GREEN_SIZE, 8, GLX_BLUE_SIZE, 8, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, True, None, }; int fbcount; GLXFBConfig* fbc = glXChooseFBConfig(display, screen, visual_attribs, &fbcount); if (!fbc) throw std::runtime_error("No GLXFBConfig found"); // just grab first config GLXFBConfig best_fbc = fbc[0]; XFree(fbc); XVisualInfo* vi = glXGetVisualFromFBConfig(display, best_fbc); if (!vi) throw std::runtime_error("Failed to get XVisualInfo"); Window root = RootWindow(display, screen); XSetWindowAttributes swa; swa.colormap = XCreateColormap(display, root, vi->visual, AllocNone); swa.event_mask = ExposureMask | KeyPressMask; Window win = XCreateWindow(display, root, 0, 0, 800, 600, 0, vi->depth, InputOutput, vi->visual, CWColormap | CWEventMask, &swa); XMapWindow(display, win); XStoreName(display, win, "LunarWM OpenXR"); GLXContext glc = glXCreateNewContext( display, best_fbc, GLX_RGBA_TYPE, nullptr, True); if (!glc) throw std::runtime_error("Failed to create GLX context"); if (!glXMakeContextCurrent(display, win, win, glc)) throw std::runtime_error("glXMakeContextCurrent failed"); // fill in OpenXR graphics binding graphics_binding.type = XR_TYPE_GRAPHICS_BINDING_OPENGL_XLIB_KHR; graphics_binding.next = nullptr; graphics_binding.xDisplay = display; graphics_binding.visualid = vi->visualid; graphics_binding.glxFBConfig = best_fbc; graphics_binding.glxDrawable = win; graphics_binding.glxContext = glc; } { // Create session XrSessionCreateInfo ci { XR_TYPE_SESSION_CREATE_INFO }; ci.next = &graphics_binding; ci.systemId = system_id; ci.createFlags = 0; OPENXR_CHECK(xrCreateSession, m_xr_instance, &ci, &m_session); } // --- wlroots integration: create Wayland display and OpenXR wlroots // backend --- initialize wlroots logging wlr_log_init(WLR_DEBUG, nullptr); // create Wayland display m_display = wl_display_create(); if (!m_display) { throw std::runtime_error("Failed to create Wayland display"); } // get event loop m_event_loop = wl_display_get_event_loop(m_display); if (!m_event_loop) { wl_display_destroy(m_display); throw std::runtime_error("Failed to get Wayland event loop"); } // create the OpenXR wlroots backend m_backend = wlr_openxr_backend_create(m_display, m_event_loop); if (!m_backend || !wlr_backend_is_openxr(m_backend)) { wl_display_destroy(m_display); throw std::runtime_error("Failed to create OpenXR wlroots backend"); } } void Compositor::destroy_session() { // destroy wlroots backend and Wayland display if (m_backend) { wlr_backend_destroy(m_backend); m_backend = nullptr; } if (m_display) { wl_display_destroy(m_display); m_display = nullptr; } // destroy OpenXR session OPENXR_CHECK(xrDestroySession, m_session); } void Compositor::poll_events() { XrEventDataBuffer event_data { XR_TYPE_EVENT_DATA_BUFFER }; auto xr_poll_events = [&]() -> bool { event_data = { XR_TYPE_EVENT_DATA_BUFFER }; return xrPollEvent(m_xr_instance, &event_data) == XR_SUCCESS; }; while (xr_poll_events()) { switch (event_data.type) { case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: { XrEventDataSessionStateChanged* session_state_changed = reinterpret_cast( &event_data); if (session_state_changed->session != m_session) { std::println(std::cerr, "XrEventDataSessionStateChanged for unknown Session"); break; } if (session_state_changed->state == XR_SESSION_STATE_READY) { XrSessionBeginInfo session_begin_info { XR_TYPE_SESSION_BEGIN_INFO }; session_begin_info.primaryViewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; OPENXR_CHECK(xrBeginSession, m_session, &session_begin_info); m_session_running = true; } if (session_state_changed->state == XR_SESSION_STATE_STOPPING) { OPENXR_CHECK(xrEndSession, m_session); m_session_running = false; } if (session_state_changed->state == XR_SESSION_STATE_EXITING || session_state_changed->state == XR_SESSION_STATE_LOSS_PENDING) { m_session_running = false; m_running = false; } m_session_state = session_state_changed->state; break; } default: break; } } } void Compositor::run() { // start the wlroots backend (this also begins XrSession) if (!wlr_backend_start(m_backend)) { throw std::runtime_error("Failed to start wlroots backend"); } while (m_running) { this->poll_events(); // dispatch Wayland events wl_event_loop_dispatch(m_event_loop, 0); if (m_session_running) { // TODO: Render frame (per-eye rendering via OpenXR swapchain) } } }