|
|
|
|
@@ -1,230 +1,109 @@
|
|
|
|
|
#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/types/wlr_xdg_shell.h>
|
|
|
|
|
#include <wlr/util/log.h>
|
|
|
|
|
|
|
|
|
|
wlr_scene* wlr_scene_create();
|
|
|
|
|
}
|
|
|
|
|
#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()
|
|
|
|
|
void handle_new_output(struct wl_listener* l, void* d)
|
|
|
|
|
{
|
|
|
|
|
// 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)l;
|
|
|
|
|
(void)d;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Compositor::destroy_instance()
|
|
|
|
|
void handle_new_input(struct wl_listener* l, void* d)
|
|
|
|
|
{
|
|
|
|
|
OPENXR_CHECK(xrDestroyInstance, m_xr_instance);
|
|
|
|
|
(void)l;
|
|
|
|
|
(void)d;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Compositor::create_session()
|
|
|
|
|
void handle_xdg_toplevel(struct wl_listener* l, void* d)
|
|
|
|
|
{
|
|
|
|
|
XrSystemId system_id;
|
|
|
|
|
(void)l;
|
|
|
|
|
(void)d;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{ // 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);
|
|
|
|
|
void handle_xdg_popup(struct wl_listener* l, void* d)
|
|
|
|
|
{
|
|
|
|
|
(void)l;
|
|
|
|
|
(void)d;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
Compositor::Compositor()
|
|
|
|
|
{
|
|
|
|
|
// init logging + display + event loop
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
// openxr 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");
|
|
|
|
|
}
|
|
|
|
|
if (!m_backend)
|
|
|
|
|
throw std::runtime_error("backend fail");
|
|
|
|
|
|
|
|
|
|
// renderer + allocator
|
|
|
|
|
m_renderer = wlr_renderer_autocreate(m_backend);
|
|
|
|
|
if (!m_renderer)
|
|
|
|
|
throw std::runtime_error("Failed to create wlr_renderer.");
|
|
|
|
|
wlr_renderer_init_wl_display(m_renderer, m_display);
|
|
|
|
|
m_allocator = wlr_allocator_autocreate(m_backend, m_renderer);
|
|
|
|
|
if (!m_allocator)
|
|
|
|
|
throw std::runtime_error("Failed to create wlr_allocator.");
|
|
|
|
|
|
|
|
|
|
// compositor/subcompositor/data-device
|
|
|
|
|
m_compositor = wlr_compositor_create(m_display, 4, m_renderer);
|
|
|
|
|
if (!m_compositor)
|
|
|
|
|
throw std::runtime_error("Failed to create wlr_compositor.");
|
|
|
|
|
m_subcompositor = wlr_subcompositor_create(m_display);
|
|
|
|
|
if (!m_subcompositor)
|
|
|
|
|
throw std::runtime_error("Failed to create wlr_subcompositor.");
|
|
|
|
|
m_data_device = wlr_data_device_manager_create(m_display);
|
|
|
|
|
if (!m_data_device)
|
|
|
|
|
throw std::runtime_error("Failed to create wlr_data_device_manager.");
|
|
|
|
|
|
|
|
|
|
// scene + output layout
|
|
|
|
|
m_scene = wlr_scene_create();
|
|
|
|
|
if (!m_scene)
|
|
|
|
|
throw std::runtime_error("Failed to create wlr_scene.");
|
|
|
|
|
m_output_layout = wlr_output_layout_create(m_display);
|
|
|
|
|
if (!m_output_layout)
|
|
|
|
|
throw std::runtime_error("Failed to create wlr_output_layout.");
|
|
|
|
|
|
|
|
|
|
// seat for input
|
|
|
|
|
m_seat = wlr_seat_create(m_display, "seat0");
|
|
|
|
|
if (!m_seat)
|
|
|
|
|
throw std::runtime_error("Failed to create wlr_seat.");
|
|
|
|
|
|
|
|
|
|
// xdg shell
|
|
|
|
|
m_xdg_shell = wlr_xdg_shell_create(m_display, 3);
|
|
|
|
|
if (!m_xdg_shell)
|
|
|
|
|
throw std::runtime_error("Failed to create wlr_xdg_shell.");
|
|
|
|
|
m_new_xdg_toplevel.notify = handle_xdg_toplevel;
|
|
|
|
|
wl_signal_add(&m_xdg_shell->events.new_toplevel, &m_new_xdg_toplevel);
|
|
|
|
|
m_new_xdg_popup.notify = handle_xdg_popup;
|
|
|
|
|
wl_signal_add(&m_xdg_shell->events.new_popup, &m_new_xdg_popup);
|
|
|
|
|
|
|
|
|
|
// hook up backend signals
|
|
|
|
|
m_new_output.notify = handle_new_output;
|
|
|
|
|
wl_signal_add(&m_backend->events.new_output, &m_new_output);
|
|
|
|
|
m_new_input.notify = handle_new_input;
|
|
|
|
|
wl_signal_add(&m_backend->events.new_input, &m_new_input);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Compositor::destroy_session()
|
|
|
|
|
Compositor::~Compositor()
|
|
|
|
|
{
|
|
|
|
|
// destroy wlroots backend and Wayland display
|
|
|
|
|
if (m_backend) {
|
|
|
|
|
wlr_backend_destroy(m_backend);
|
|
|
|
|
m_backend = nullptr;
|
|
|
|
|
@@ -233,70 +112,23 @@ void Compositor::destroy_session()
|
|
|
|
|
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)
|
|
|
|
|
char const* socket = wl_display_add_socket_auto(m_display);
|
|
|
|
|
if (!socket) {
|
|
|
|
|
throw std::runtime_error("Failed to create socket");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
setenv("WAYLAND_DISPLAY", socket, true);
|
|
|
|
|
|
|
|
|
|
wlr_log(
|
|
|
|
|
WLR_INFO, "Running Wayland compositor on WAYLAND_DISPLAY=%s", socket);
|
|
|
|
|
|
|
|
|
|
wl_display_run(m_display);
|
|
|
|
|
}
|
|
|
|
|
|