1679 lines
49 KiB
C
1679 lines
49 KiB
C
#include "LunarWM.h"
|
|
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
#include <math.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <EGL/egl.h>
|
|
#include <EGL/eglext.h>
|
|
#include <GLES2/gl2.h>
|
|
#include <GLES2/gl2ext.h>
|
|
#include <GLES3/gl3.h>
|
|
#include <GLES3/gl31.h>
|
|
#include <openxr/openxr.h>
|
|
#include <openxr/openxr_platform.h>
|
|
|
|
#include <wayland-client-protocol.h>
|
|
#include <wayland-egl.h>
|
|
#include <wayland-server-core.h>
|
|
|
|
#include <wlr/backend/session.h>
|
|
#include <wlr/backend/wayland.h>
|
|
#include <wlr/render/allocator.h>
|
|
#include <wlr/render/egl.h>
|
|
#include <wlr/render/gles2.h>
|
|
#include <wlr/types/wlr_compositor.h>
|
|
#include <wlr/types/wlr_cursor.h>
|
|
#include <wlr/types/wlr_data_device.h>
|
|
#include <wlr/types/wlr_pointer.h>
|
|
#include <wlr/types/wlr_subcompositor.h>
|
|
#include <wlr/types/wlr_xdg_shell.h>
|
|
#include <wlr/util/log.h>
|
|
|
|
#include <raylib.h>
|
|
#include <raymath.h>
|
|
#include <rlgl.h>
|
|
|
|
#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();
|
|
}
|
|
}
|