Files
lunarwm/src/compositor.cpp
2025-06-29 17:34:45 +03:00

303 lines
8.3 KiB
C++

#include "compositor.h"
#include "common.h"
#include <wayland-server.h>
// wlroots is a C library; wrap headers in extern "C"
extern "C" {
#include <wlr/backend.h>
#include <wlr/util/log.h>
}
#include "wlr/openxr_gl.h"
#include <cstring>
#include <iostream>
#include <print>
#include <vector>
#include <GL/glx.h>
#include <X11/Xlib.h>
#include <openxr/openxr.h>
#include <openxr/openxr_platform.h>
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<char const*> const instance_extensions {
XR_EXT_DEBUG_UTILS_EXTENSION_NAME,
XR_KHR_OPENGL_ENABLE_EXTENSION_NAME,
};
std::vector<XrExtensionProperties> 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<uint32_t>(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<PFN_xrVoidFunction*>(&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<XrEventDataSessionStateChanged*>(
&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)
}
}
}