Organization, render cursor above all else

Signed-off-by: Slendi <slendi@socopon.com>
This commit is contained in:
2025-09-29 18:51:43 +03:00
parent a6949eeda0
commit 28a9f54227
18 changed files with 3499 additions and 3408 deletions

1
.direnv/flake-profile Symbolic link
View File

@@ -0,0 +1 @@
flake-profile-1-link

View File

@@ -0,0 +1 @@
/nix/store/czy1f5v0c5i86rm2msxlbq9n8s5lhiv0-nix-shell-env

2
.envrc Normal file
View File

@@ -0,0 +1,2 @@
use flake

View File

@@ -53,7 +53,10 @@ target_sources(${PROJECT_NAME} PUBLIC
src/RayExt.c
src/Config.c
src/LunarWM.c
src/LunarWM_core.c
src/LunarWM_wayland.c
src/LunarWM_xr.c
src/LunarWM_render.c
src/main.c
)
target_link_libraries(${PROJECT_NAME} PUBLIC
@@ -120,4 +123,3 @@ target_link_libraries(${PROJECT_NAME} PRIVATE xdg_shell)
target_include_directories(${PROJECT_NAME} PRIVATE
${CMAKE_BINARY_DIR}
)

View File

@@ -7,6 +7,9 @@ return {
keyboard = {
xkb_options = { "altwin:swap_lalt_lwin" },
},
mouse = {
invert_y = false,
},
},
keybindings = {
{ bind = main("Shift-Q"), action = lunar.quit_compositor },

View File

@@ -166,6 +166,8 @@ int config_load_ref(lua_State *L, int idx, Config *out)
out->displays.virtual.resolution = (Vector2) { 2560, 1440 };
out->xwayland.enabled = true;
out->xwayland.lazy = false;
out->input.mouse.invert_x = false;
out->input.mouse.invert_y = false;
// ====== END DEFAULTS ======
int cfg_abs = lua_absindex(L, idx);
@@ -250,6 +252,20 @@ int config_load_ref(lua_State *L, int idx, Config *out)
lua_pop(L, 1);
}
lua_pop(L, 1);
lua_getfield(L, -1, "mouse");
if (lua_istable(L, -1)) {
lua_getfield(L, -1, "invert_x");
if (lua_isboolean(L, -1))
out->input.mouse.invert_x = lua_toboolean(L, -1);
lua_pop(L, 1);
lua_getfield(L, -1, "invert_y");
if (lua_isboolean(L, -1))
out->input.mouse.invert_y = lua_toboolean(L, -1);
lua_pop(L, 1);
}
lua_pop(L, 1);
}
lua_pop(L, 1);

View File

@@ -19,6 +19,10 @@ typedef struct {
struct {
char *xkb_options;
} keyboard;
struct {
bool invert_x;
bool invert_y;
} mouse;
} input;
struct {

File diff suppressed because it is too large Load Diff

View File

@@ -1,283 +1,7 @@
#ifndef LUNAR_WM_H
#define LUNAR_WM_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 "LunarWM_types.h"
#include "LunarWM_core.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 <wlr/xwayland/xwayland.h>
#include <raylib.h>
#include <raymath.h>
#include <rlgl.h>
#include "Config.h"
#include "RayExt.h"
struct LunarWM;
typedef struct {
float r, theta, phi;
} SphericalCoord;
static inline SphericalCoord Vector3ToSpherical(Vector3 v)
{
SphericalCoord s;
s.r = sqrtf(v.x * v.x + v.y * v.y + v.z * v.z);
if (s.r > 0.0f) {
s.theta = atan2f(v.z, v.x); // azimuth around Y axis
s.phi = acosf(v.y / s.r); // polar angle from Y+
} else {
s.theta = 0.0f;
s.phi = 0.0f;
}
return s;
}
static inline Vector3 SphericalToVector3(SphericalCoord s)
{
Vector3 v;
float sin_phi = sinf(s.phi);
v.x = s.r * cosf(s.theta) * sin_phi;
v.y = s.r * cosf(s.phi);
v.z = s.r * sinf(s.theta) * sin_phi;
return v;
}
typedef struct virtual_output {
struct wl_global *global;
struct wl_display *display;
struct wl_list clients; // vo_client_res.link
// state we advertise
int32_t x, y; // compositor space; keep 0,0 if not relevant
int32_t phys_w_mm, phys_h_mm;
int32_t width, height; // current mode
int32_t refresh_mhz; // mHz
int32_t scale;
enum wl_output_subpixel subpixel;
enum wl_output_transform transform;
char const *make, *model;
char const *name, *desc;
} LunarWM_VirtualOutput;
typedef struct {
struct LunarWM *server;
struct wl_list link;
struct wlr_keyboard *wlr_keyboard;
struct wl_listener modifiers;
struct wl_listener key;
struct wl_listener destroy;
} LunarWM_Keyboard;
typedef struct {
struct LunarWM *server;
struct wl_list link;
struct wlr_pointer *wlr_pointer;
struct wl_listener motion;
struct wl_listener destroy;
} LunarWM_Pointer;
typedef struct {
uint32_t id;
bool is_xwayland;
struct LunarWM *server;
struct wl_listener commit;
struct wl_listener destroy;
struct wl_listener map, unmap;
union {
struct wlr_xdg_toplevel *xdg;
struct wlr_xwayland_surface *xwl;
} u;
struct wlr_surface *surface;
struct wlr_texture *texture;
struct wlr_buffer *locked_buffer;
struct wlr_gles2_texture_attribs attribs;
struct wlr_gles2_texture *gles_texture;
Texture2D rl_texture;
} LunarWM_Toplevel;
typedef struct {
LunarWM_Toplevel *tl;
SphericalCoord coord;
} LunarWM_Window;
bool LunarWM_Toplevel_init_xdg(
LunarWM_Toplevel *tl, struct LunarWM *wm, struct wlr_xdg_toplevel *xdg);
bool LunarWM_Toplevel_init_xwayland(
LunarWM_Toplevel *tl, struct LunarWM *wm, struct wlr_xwayland_surface *xwl);
bool LunarWM_Toplevel_destroy(LunarWM_Toplevel *this);
bool LunarWM_Toplevel_update(LunarWM_Toplevel *this);
void LunarWM_Toplevel_focus(LunarWM_Toplevel *this);
typedef struct {
XrSwapchain swapchain;
int64_t swapchain_format;
GLuint *v_image_views;
} LunarWM_SwapchainInfo;
typedef struct {
XrSwapchain handle;
XrSwapchainImageOpenGLESKHR *a_imgs;
uint32_t a_imgs_count;
} LunarWM_SwapchainImagesEntry;
typedef struct {
XrHandJointLocationEXT joint_locations[XR_HAND_JOINT_COUNT_EXT];
XrHandTrackerEXT hand_tracker;
} LunarWM_Hand;
typedef struct {
XrTime predicted_display_time;
XrCompositionLayerProjection layer_projection;
XrCompositionLayerBaseHeader *layers[10];
uint32_t layers_count;
XrCompositionLayerProjectionView
layer_projection_views[10]; // Hopefully we dont have more than 10.
uint32_t layer_projection_views_count;
} LunarWM_RenderLayerInfo;
typedef struct LunarWM {
struct {
struct wl_display *display;
struct wl_event_loop *event_loop;
struct wlr_backend *backend;
struct wlr_renderer *renderer;
struct wlr_session *session;
struct wlr_egl *egl;
EGLDisplay egl_display;
EGLContext egl_context;
EGLConfig egl_config;
struct wlr_allocator *allocator;
struct wlr_compositor *compositor;
struct wlr_subcompositor *subcompositor;
struct wlr_data_device_manager *data_device_manager;
struct wlr_seat *seat;
struct wl_list keyboards;
struct wl_list pointers;
struct wl_listener new_input_listener;
struct wlr_xdg_shell *xdg_shell;
struct wl_listener new_xdg_toplevel_listener;
struct wl_listener new_xdg_popup_listener;
struct wlr_xwayland *xwayland;
struct wl_listener xwayland_ready;
struct wl_listener xwayland_new_surface;
struct wl_listener xwayland_associate_tmp; // per-surface temp
struct wl_listener xwayland_dissociate_tmp; // per-surface temp
LunarWM_VirtualOutput *custom_out_virtual;
LunarWM_VirtualOutput *custom_out_hud;
LunarWM_Toplevel **v_toplevels;
int current_focus;
} wayland;
struct {
XrInstance instance;
XrSystemId system_id;
XrSession session;
XrSessionState session_state;
struct {
LunarWM_SwapchainInfo *v_color;
LunarWM_SwapchainInfo *v_depth;
} swapchains;
LunarWM_SwapchainImagesEntry
swapchain_images[2]; // 0 is color, 1 is depth
XrViewConfigurationView *a_view_configuration_views;
uint32_t view_configuration_views_count;
XrEnvironmentBlendMode environment_blend_mode;
XrSpace local_space, view_space;
LunarWM_Hand hands[2];
XrSystemHandTrackingPropertiesEXT hand_tracking_system_properties;
PFN_xrCreateHandTrackerEXT CreateHandTrackerEXT;
PFN_xrDestroyHandTrackerEXT DestroyHandTrackerEXT;
PFN_xrLocateHandJointsEXT LocateHandJointsEXT;
Quaternion recenter_rot;
Vector3 recenter_trans;
bool recenter_active;
bool session_running;
} xr;
struct {
GLuint fbo;
RenderTexture2D tmp_rt;
RenderTexture2D main_rt;
RenderTexture2D hud_rt;
Camera3D camera;
Shader linear_srgb;
Skybox skybox;
bool first_frame;
} renderer;
struct {
SphericalCoord pointer;
int active_workspace;
struct {
LunarWM_Window *v_windows;
} workspaces[10];
} wm;
ConfigManager *cman;
_Atomic(int) counter;
bool initialized;
bool running;
} LunarWM;
bool LunarWM_init(LunarWM *wm);
void LunarWM_destroy(LunarWM *this);
void LunarWM_terminate(LunarWM *this);
void LunarWM_run(LunarWM *this);
static inline bool LunarWM_get_new_id(LunarWM *this) { return ++this->counter; }
extern LunarWM g_wm;
#endif // LUNAR_WM_H
#endif

408
src/LunarWM_core.c Normal file
View File

@@ -0,0 +1,408 @@
#include "LunarWM_core.h"
#include "LunarWM_render.h"
#include "LunarWM_wayland.h"
#include "LunarWM_xr.h"
#include "RayExt.h"
#include "common.h"
#include "vec.h"
#include <assert.h>
#include <errno.h>
#include <math.h>
#include <spawn.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <lauxlib.h>
#include <lua.h>
#include <lualib.h>
extern char **environ;
static void cleanup_raylib_egl(LunarWM *wm)
{
if (IsWindowReady()) {
CloseWindow();
}
}
static void cleanup_lua_cfg(LunarWM *wm)
{
if (wm->cman) {
config_manager_destroy(wm->cman);
wm->cman = NULL;
}
}
void LunarWM_set_recenter_from_camera(LunarWM *wm)
{
Vector3 pos = wm->renderer.camera.position;
Vector3 fwd
= Vector3Normalize(Vector3Subtract(wm->renderer.camera.target, pos));
float len_xz = sqrtf(fwd.x * fwd.x + fwd.z * fwd.z);
float yaw = (len_xz > 1e-6f) ? atan2f(fwd.x, fwd.z) : 0.0f;
Quaternion q_step = QuaternionFromAxisAngle((Vector3) { 0, 1, 0 }, -yaw);
Vector3 t_step = Vector3Negate(Vector3RotateByQuaternion(pos, q_step));
Quaternion q_total = QuaternionMultiply(q_step, wm->xr.recenter_rot);
Vector3 t_total = Vector3Add(
Vector3RotateByQuaternion(wm->xr.recenter_trans, q_step), t_step);
wm->xr.recenter_rot = q_total;
wm->xr.recenter_trans = t_total;
wm->xr.recenter_active = true;
}
static void sync_config(LunarWM *wm)
{
if (wm->cman->cfg.cubemap) {
Skybox_init(&wm->renderer.skybox, wm->cman->cfg.cubemap);
} else {
Skybox_destroy(&wm->renderer.skybox);
}
if (IsTextureValid(wm->renderer.hud_rt.texture)) {
UnloadTexture(wm->renderer.hud_rt.texture);
wm->renderer.hud_rt.texture.id = 0;
wm->renderer.hud_rt.texture.width = 0;
wm->renderer.hud_rt.texture.height = 0;
}
int vw = (int)wm->cman->cfg.displays.virtual.resolution.x;
int vh = (int)wm->cman->cfg.displays.virtual.resolution.y;
int hud = wm->cman->cfg.displays.hud.size;
LunarWM_wayland_update_virtual_outputs(wm, vw, vh, hud);
}
static int l_exec(lua_State *L)
{
char const *cmd = luaL_checkstring(L, 1);
pid_t pid;
char *argv[] = { (char *)"sh", (char *)"-c", (char *)cmd, NULL };
int rc = posix_spawnp(&pid, "sh", NULL, NULL, argv, environ);
if (rc != 0) {
lua_pushnil(L);
lua_pushfstring(L, "posix_spawnp failed: %s", strerror(rc));
return 2;
}
lua_pushinteger(L, (lua_Integer)pid);
return 1;
}
static int l_cycle_next(lua_State *L)
{
LunarWM *wm = &g_wm;
if (vector_size(wm->wayland.v_toplevels) == 0) {
lua_pushnil(L);
return 1;
}
wm->wayland.current_focus++;
if (wm->wayland.current_focus >= vector_size(wm->wayland.v_toplevels)) {
wm->wayland.current_focus = 0;
}
LunarWM_Toplevel *tl = wm->wayland.v_toplevels[wm->wayland.current_focus];
LunarWM_Toplevel_focus(tl);
lua_pushnil(L);
return 1;
}
static int l_recenter(lua_State *L)
{
(void)L;
LunarWM_set_recenter_from_camera(&g_wm);
lua_pushnil(L);
return 1;
}
static int l_reload_config(lua_State *L)
{
LunarWM *wm = &g_wm;
ConfigManager *cm = wm->cman;
if (!cm) {
lua_pushnil(L);
return 1;
}
config_manager_reload(cm);
sync_config(wm);
lua_pushnil(L);
return 1;
}
static int l_quit_compositor(lua_State *L)
{
(void)L;
LunarWM_terminate(&g_wm);
lua_pushnil(L);
return 1;
}
bool LunarWM_init(LunarWM *wm)
{
memset(wm, 0, sizeof(*wm));
wm->xr.session = XR_NULL_HANDLE;
wm->xr.session_state = XR_SESSION_STATE_UNKNOWN;
wm->xr.environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_MAX_ENUM;
wm->xr.local_space = wm->xr.view_space = XR_NULL_HANDLE;
wm->xr.hand_tracking_system_properties.type
= XR_TYPE_SYSTEM_HAND_TRACKING_PROPERTIES_EXT;
wm->renderer.camera.position = (Vector3) { 0, 0, 0 };
wm->renderer.camera.target = (Vector3) { 0, 0, 1 };
wm->renderer.camera.up = (Vector3) { 0, 1, 0 };
wm->renderer.camera.fovy = 45;
wm->renderer.camera.projection = CAMERA_PERSPECTIVE;
wm->xr.recenter_rot = (Quaternion) { 0, 0, 0, 1 };
wm->xr.recenter_trans = (Vector3) { 0, 0, 0 };
wm->xr.recenter_active = false;
wm->counter = 0;
wm->wm.active_workspace = 0;
for (size_t i = 0; i < ARRAY_SZ(wm->wm.workspaces); i++) {
wm->wm.workspaces[i].v_windows = vector_create();
}
wm->cman = config_manager_create(get_config_path());
assert(wm->cman);
{
lua_State *L = wm->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_pushcfunction(L, l_recenter);
lua_setfield(L, -2, "recenter");
lua_pushcfunction(L, l_cycle_next);
lua_setfield(L, -2, "cycle_next");
lua_pushcfunction(L, l_exec);
lua_setfield(L, -2, "exec");
lua_setglobal(L, "lunar");
config_manager_reload(wm->cman);
}
if (getenv("DISPLAY") != NULL || getenv("WAYLAND_DISPLAY") != NULL) {
wlr_log(WLR_ERROR, "This compositor can only be ran in DRM mode.");
return false;
}
if (!LunarWM_wayland_init(wm)) {
wlr_log(WLR_ERROR, "Failed to initialize wlroots");
return false;
}
EGLSurface draw = eglGetCurrentSurface(EGL_DRAW);
EGLSurface read = eglGetCurrentSurface(EGL_READ);
if (eglMakeCurrent(wm->wayland.egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
wm->wayland.egl_context)
== EGL_FALSE) {
wlr_log(WLR_ERROR, "Failed to eglMakeCurrent");
return false;
}
if (!LunarWM_xr_init(wm)) {
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(
wm->wayland.egl_display, draw, read, wm->wayland.egl_context)
== EGL_FALSE) {
wlr_log(WLR_ERROR, "Failed to eglMakeCurrent");
return false;
}
sync_config(wm);
wm->initialized = true;
return true;
}
void LunarWM_destroy(LunarWM *wm)
{
if (!wm)
return;
eglMakeCurrent(wm->wayland.egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
wm->wayland.egl_context);
LunarWM_xr_cleanup(wm);
cleanup_raylib_egl(wm);
LunarWM_wayland_cleanup(wm);
cleanup_lua_cfg(wm);
memset(wm, 0, sizeof(*wm));
}
void LunarWM_terminate(LunarWM *wm)
{
wlr_log(WLR_INFO, "Stopping compositor");
wm->running = false;
}
void LunarWM_run(LunarWM *wm)
{
assert(wm);
assert(wm->initialized);
wm->renderer.first_frame = true;
if (!wlr_backend_start(wm->wayland.backend)) {
wlr_log(WLR_ERROR, "Failed to start backend");
return;
}
char const *socket = wl_display_add_socket_auto(wm->wayland.display);
if (socket == NULL) {
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);
wm->running = true;
struct timespec last, now;
clock_gettime(CLOCK_MONOTONIC, &last);
while (wm->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(wm->wayland.v_toplevels); i++) {
LunarWM_Toplevel *tl = wm->wayland.v_toplevels[i];
if (tl->surface) {
wlr_surface_send_frame_done(tl->surface, &now);
}
}
wl_display_flush_clients(wm->wayland.display);
wl_event_loop_dispatch(wm->wayland.event_loop, 0);
EGLSurface draw = eglGetCurrentSurface(EGL_DRAW);
EGLSurface read = eglGetCurrentSurface(EGL_READ);
if (eglMakeCurrent(wm->wayland.egl_display, EGL_NO_SURFACE,
EGL_NO_SURFACE, wm->wayland.egl_context)
== EGL_FALSE) {
wlr_log(WLR_ERROR, "Failed to eglMakeCurrent");
return;
}
WindowShouldClose();
BeginDrawing();
LunarWM_xr_poll_events(wm);
if (!wm->xr.session_running) {
EndDrawing();
continue;
}
XrFrameState frame_state = {
.type = XR_TYPE_FRAME_STATE,
};
XrFrameWaitInfo frame_wait_info = {
.type = XR_TYPE_FRAME_WAIT_INFO,
};
if (xrWaitFrame(wm->xr.session, &frame_wait_info, &frame_state)
!= XR_SUCCESS) {
wlr_log(WLR_ERROR, "Failed to wait for OpenXR frame");
return;
}
XrFrameBeginInfo frame_begin_info = {
.type = XR_TYPE_FRAME_BEGIN_INFO,
};
XrResult res = xrBeginFrame(wm->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 (wm->xr.hand_tracking_system_properties.supportsHandTracking) {
for (size_t i = 0; i < 2; i++) {
LunarWM_Hand *hand = &wm->xr.hands[i];
bool const unobstructed = true;
XrHandJointsMotionRangeInfoEXT mri = {
.type = XR_TYPE_HAND_JOINTS_MOTION_RANGE_INFO_EXT,
};
mri.handJointsMotionRange = unobstructed
? XR_HAND_JOINTS_MOTION_RANGE_UNOBSTRUCTED_EXT
: XR_HAND_JOINTS_MOTION_RANGE_CONFORMING_TO_CONTROLLER_EXT;
XrHandJointsLocateInfoEXT li = {
.type = XR_TYPE_HAND_JOINTS_LOCATE_INFO_EXT,
.next = &mri,
.baseSpace = wm->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 (wm->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,
};
bool const session_active
= (wm->xr.session_state == XR_SESSION_STATE_SYNCHRONIZED)
|| (wm->xr.session_state == XR_SESSION_STATE_VISIBLE)
|| (wm->xr.session_state == XR_SESSION_STATE_FOCUSED);
if (session_active && (frame_state.shouldRender != 0u)) {
bool rendered = LunarWM_render_layer(wm, &render_layer_info, dt);
if (rendered) {
render_layer_info.layers[render_layer_info.layers_count]
= (XrCompositionLayerBaseHeader *)&render_layer_info
.layer_projection;
}
}
XrFrameEndInfo frame_end_info = {
.type = XR_TYPE_FRAME_END_INFO,
.displayTime = frame_state.predictedDisplayTime,
.environmentBlendMode = wm->xr.environment_blend_mode,
.layerCount = render_layer_info.layers_count,
.layers
= (XrCompositionLayerBaseHeader const **)render_layer_info.layers,
};
if (xrEndFrame(wm->xr.session, &frame_end_info) != XR_SUCCESS) {
wlr_log(WLR_ERROR, "Failed to end OpenXR frame");
return;
}
EndDrawing();
}
}

14
src/LunarWM_core.h Normal file
View File

@@ -0,0 +1,14 @@
#ifndef LUNAR_WM_CORE_H
#define LUNAR_WM_CORE_H
#include "LunarWM_types.h"
bool LunarWM_init(LunarWM *wm);
void LunarWM_destroy(LunarWM *wm);
void LunarWM_terminate(LunarWM *wm);
void LunarWM_run(LunarWM *wm);
void LunarWM_set_recenter_from_camera(LunarWM *wm);
extern LunarWM g_wm;
#endif

662
src/LunarWM_render.c Normal file
View File

@@ -0,0 +1,662 @@
#include "LunarWM_render.h"
#include "LunarWM_core.h"
#include "LunarWM_xr.h"
#include "common.h"
#include "vec.h"
#include <assert.h>
#include <math.h>
#include <stdlib.h>
#include <time.h>
static inline SphericalCoord get_forward_spherical_with_nearest(
Vector3 fwd, float r)
{
if (fabs(fwd.y) < 0.2f) {
fwd.y = 0;
}
Vector3 vec = Vector3Scale(Vector3Normalize(fwd), r);
return Vector3ToSpherical(vec);
}
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 DrawTextureCyl(
Texture2D tex, Vector3 center, float radius, float scale, bool y_flip)
{
if (!tex.id || scale <= 0.0f || radius == 0.0f)
return;
float r = fabsf(radius);
float arc_len = (float)tex.width * scale; // arc length in world units
float theta = arc_len / r; // radians across the panel
float half_t = 0.5f * theta;
float half_h = 0.5f * (float)tex.height * scale;
// mid-angle around Y so the segment's middle sits at 'center'
float a0 = atan2f(center.x, center.z);
// shift so the cylinder surface midpoint matches 'center'
Vector3 mid_ref = (Vector3) { sinf(a0) * r, center.y, cosf(a0) * r };
Vector3 delta = Vector3Subtract(center, mid_ref);
// tessellation: about 3° per slice (min 8)
int slices = (int)ceilf(fmaxf(theta * (180.0f / PI) / 3.0f, 8.0f));
if (slices > 1024)
slices = 1024;
float vt = y_flip ? 1.0f : 0.0f;
float vb = y_flip ? 0.0f : 1.0f;
rlDrawRenderBatchActive(); // flush any prior state
rlSetTexture(tex.id);
rlDisableBackfaceCulling();
rlColor4ub(255, 255, 255, 255);
rlBegin(RL_QUADS);
for (int i = 0; i < slices; ++i) {
float u0 = (float)i / (float)slices;
float u1 = (float)(i + 1) / (float)slices;
float aL = a0 - half_t + theta * u0;
float aR = a0 - half_t + theta * u1;
Vector3 nL = (Vector3) { sinf(aL), 0.0f, cosf(aL) };
Vector3 nR = (Vector3) { sinf(aR), 0.0f, cosf(aR) };
if (radius < 0.0f) {
nL = Vector3Negate(nL);
nR = Vector3Negate(nR);
}
Vector3 pLT = Vector3Add(
(Vector3) { nL.x * r, center.y + half_h, nL.z * r }, delta);
Vector3 pLB = Vector3Add(
(Vector3) { nL.x * r, center.y - half_h, nL.z * r }, delta);
Vector3 pRT = Vector3Add(
(Vector3) { nR.x * r, center.y + half_h, nR.z * r }, delta);
Vector3 pRB = Vector3Add(
(Vector3) { nR.x * r, center.y - half_h, nR.z * r }, delta);
// match your flat-quad U flip (so WL textures look correct)
float U0 = 1.0f - u0;
float U1 = 1.0f - u1;
// one normal per-vertex (simple cylindrical)
rlNormal3f(nL.x, nL.y, nL.z);
rlTexCoord2f(U0, vt);
rlVertex3f(pLT.x, pLT.y, pLT.z);
rlNormal3f(nR.x, nR.y, nR.z);
rlTexCoord2f(U1, vt);
rlVertex3f(pRT.x, pRT.y, pRT.z);
rlNormal3f(nR.x, nR.y, nR.z);
rlTexCoord2f(U1, vb);
rlVertex3f(pRB.x, pRB.y, pRB.z);
rlNormal3f(nL.x, nL.y, nL.z);
rlTexCoord2f(U0, vb);
rlVertex3f(pLB.x, pLB.y, pLB.z);
}
rlEnd();
rlSetTexture(0);
rlEnableBackfaceCulling();
}
static void DrawTextureCyl2(Texture2D tex, Vector3 sphere_center,
SphericalCoord coord, float rad, float scale, bool y_flip)
{
if (!tex.id || scale <= 0.0f || rad == 0.0f)
return;
// midpoint on the sphere where the panel should sit (its center)
Vector3 fwd = SphericalToVector3(coord);
if (Vector3Length(fwd) < 1e-6f)
fwd = (Vector3) { 0, 0, 1 };
fwd = Vector3Normalize(fwd);
// build a local tangent frame at that point (right, up, forward)
Vector3 worldUp = (Vector3) { 0, 1, 0 };
if (fabsf(Vector3DotProduct(worldUp, fwd)) > 0.99f)
worldUp = (Vector3) { 1, 0, 0 };
Vector3 right = Vector3Normalize(Vector3CrossProduct(worldUp, fwd));
Vector3 up = Vector3Normalize(Vector3CrossProduct(fwd, right));
float r = fabsf(rad);
float arc_len = (float)tex.width * scale; // world units across
float theta = arc_len / r; // total horizontal FOV in radians
float half_t = 0.5f * theta;
float half_h = 0.5f * (float)tex.height * scale;
// shift so cylinder's surface midpoint lands exactly at coord.r from
// sphere_center
Vector3 delta = Vector3Add(sphere_center,
Vector3Add(Vector3Scale(fwd, coord.r - r), (Vector3) { 0, 0, 0 }));
// tessellation: about 3° per slice (min 8, max 1024)
int slices = (int)ceilf(fmaxf(theta * (180.0f / PI) / 3.0f, 8.0f));
if (slices > 1024)
slices = 1024;
float vt = y_flip ? 1.0f : 0.0f;
float vb = y_flip ? 0.0f : 1.0f;
rlDrawRenderBatchActive();
rlSetTexture(tex.id);
rlDisableBackfaceCulling();
rlColor4ub(255, 255, 255, 255);
rlBegin(RL_QUADS);
for (int i = 0; i < slices; ++i) {
float u0 = (float)i / (float)slices;
float u1 = (float)(i + 1) / (float)slices;
float aL = -half_t + theta * u0;
float aR = -half_t + theta * u1;
// local outward directions on the cylindrical surface
Vector3 nL = Vector3Add(
Vector3Scale(right, sinf(aL)), Vector3Scale(fwd, cosf(aL)));
Vector3 nR = Vector3Add(
Vector3Scale(right, sinf(aR)), Vector3Scale(fwd, cosf(aR)));
if (rad < 0.0f) {
nL = Vector3Negate(nL);
nR = Vector3Negate(nR);
}
// surface points (center band), then top/bottom by +/- up*half_h
Vector3 cL = Vector3Add(delta, Vector3Scale(nL, r));
Vector3 cR = Vector3Add(delta, Vector3Scale(nR, r));
Vector3 pLT = Vector3Add(cL, Vector3Scale(up, half_h));
Vector3 pLB = Vector3Add(cL, Vector3Scale(up, -half_h));
Vector3 pRT = Vector3Add(cR, Vector3Scale(up, half_h));
Vector3 pRB = Vector3Add(cR, Vector3Scale(up, -half_h));
// match the original horizontal flip so Wayland textures look correct
float U0 = 1.0f - u0;
float U1 = 1.0f - u1;
rlNormal3f(nL.x, nL.y, nL.z);
rlTexCoord2f(U0, vt);
rlVertex3f(pLT.x, pLT.y, pLT.z);
rlNormal3f(nR.x, nR.y, nR.z);
rlTexCoord2f(U1, vt);
rlVertex3f(pRT.x, pRT.y, pRT.z);
rlNormal3f(nR.x, nR.y, nR.z);
rlTexCoord2f(U1, vb);
rlVertex3f(pRB.x, pRB.y, pRB.z);
rlNormal3f(nL.x, nL.y, nL.z);
rlTexCoord2f(U0, vb);
rlVertex3f(pLB.x, pLB.y, pLB.z);
}
rlEnd();
rlSetTexture(0);
rlEnableBackfaceCulling();
}
static inline Vector3 RecenterPoint(LunarWM *wm, Vector3 p)
{
if (!wm->xr.recenter_active)
return p;
return Vector3Add(Vector3RotateByQuaternion(p, wm->xr.recenter_rot),
wm->xr.recenter_trans);
}
static inline Quaternion RecenterOrient(LunarWM *wm, Quaternion q)
{
if (!wm->xr.recenter_active)
return q;
return QuaternionMultiply(wm->xr.recenter_rot, q);
}
static LunarWM_Toplevel *find_toplevel(LunarWM *this, int id)
{
for (size_t i = 0; i < vector_size(this->wayland.v_toplevels); i++) {
auto *tl = this->wayland.v_toplevels[i];
if (tl->id == id)
return tl;
}
return NULL;
}
void LunarWM_render_hud(LunarWM *this, float /*dt*/, int hud_size)
{
ClearBackground((Color) { 0, 0, 0, 0 });
float const text_size = this->cman->cfg.displays.hud.font_size;
char const *txt
= TextFormat("WAYLAND_DISPLAY=%s", getenv("WAYLAND_DISPLAY"));
auto txt_w = MeasureText(txt, 24);
DrawText(
txt, hud_size / 2 - txt_w / 2, hud_size - text_size, text_size, WHITE);
txt = TextFormat("DISPLAY=%s", getenv("DISPLAY"));
txt_w = MeasureText(txt, text_size);
DrawText(txt, hud_size / 2 - txt_w / 2, hud_size - text_size * 2, text_size,
WHITE);
{
time_t t = time(NULL);
struct tm *tm_info = localtime(&t);
int hours = tm_info->tm_hour;
int minutes = tm_info->tm_min;
txt = TextFormat("%02d:%02d", hours, minutes);
txt_w = MeasureText(txt, 32);
DrawText(txt, hud_size / 2 - txt_w / 2, 0, text_size, WHITE);
}
}
void LunarWM_render_windows(LunarWM *this, bool alpha_check)
{
for (size_t i = 0; i
< vector_size(this->wm.workspaces[this->wm.active_workspace].v_windows);
i++) {
auto *window
= &this->wm.workspaces[this->wm.active_workspace].v_windows[i];
auto *tl = window->tl;
if (!tl || !tl->surface) {
continue;
}
if (tl->gles_texture) {
if (alpha_check && tl->gles_texture->has_alpha) {
continue;
}
float rad = window->coord.r - 0.01f * (float)i;
DrawTextureCyl2(tl->rl_texture, Vector3Zero(), window->coord, rad,
this->cman->cfg.space.window_scale, false);
}
}
}
static void render_3d(LunarWM *this, float /*dt*/)
{
LunarWM_render_windows(this, true);
for (int h = 0; h < 2; ++h) {
auto *hand_info = &this->xr.hands[h];
for (size_t k = 0; k < XR_HAND_JOINT_COUNT_EXT; ++k) {
auto const *jl = &hand_info->joint_locations[k];
Vector3 pos = {
jl->pose.position.x,
jl->pose.position.y,
jl->pose.position.z,
};
pos = RecenterPoint(this, pos);
DrawSphere(pos, jl->radius, (Color) { 255, 0, 0, 255 });
}
}
Skybox_draw(this->renderer.skybox, this->renderer.camera.position);
rlEnableColorBlend();
rlDisableDepthMask(); // don't write depth
LunarWM_render_windows(this, false);
// TODO: Replace with actual cursor texture.
{ // Cursor
rlDrawRenderBatchActive();
rlDisableDepthTest();
Vector3 tip = SphericalToVector3(this->wm.pointer);
Vector3 n = Vector3Normalize(
Vector3Subtract(this->renderer.camera.position, tip));
Vector3 up_hint = (Vector3) { 0, 1, 0 };
if (fabsf(Vector3DotProduct(up_hint, n)) > 0.98f)
up_hint = (Vector3) { 1, 0, 0 };
Vector3 right = Vector3Normalize(Vector3CrossProduct(up_hint, n));
Vector3 up = Vector3Normalize(Vector3CrossProduct(n, right));
Vector3 down = Vector3Negate(up);
float const s = 0.03f;
Vector3 v1 = tip;
Vector3 v2 = Vector3Add(tip, Vector3Scale(down, s));
Vector3 v3 = Vector3Add(tip, Vector3Scale(right, s));
Vector3 normal = Vector3CrossProduct(
Vector3Subtract(v2, v1), Vector3Subtract(v3, v1));
if (Vector3DotProduct(normal, n) < 0.0f) {
Vector3 tmp = v2;
v2 = v3;
v3 = tmp;
}
DrawTriangle3D(v1, v2, v3, RED);
rlDrawRenderBatchActive();
rlEnableDepthTest();
}
rlEnableDepthMask();
if (IsTextureValid(this->renderer.hud_rt.texture)) {
rlDrawRenderBatchActive();
rlDisableDepthTest();
Vector3 camPos = this->renderer.camera.position;
Vector3 camDir = Vector3Normalize(
Vector3Subtract(this->renderer.camera.target, camPos));
Vector3 up = this->renderer.camera.up;
Vector3 right = Vector3Normalize(Vector3CrossProduct(camDir, up));
up = Vector3CrossProduct(right, camDir);
Vector3 center = Vector3Add(camPos, Vector3Scale(camDir, 0.6f));
float heightMeters = 0.10f;
DrawBillboardNoShear(this->renderer.camera,
this->renderer.hud_rt.texture, center, heightMeters * 5, WHITE);
rlDrawRenderBatchActive();
rlEnableDepthTest();
}
}
bool LunarWM_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 (!LunarWM_xr_acquire_wait(color_sc->swapchain, &col_idx)
|| !LunarWM_xr_acquire_wait(depth_sc->swapchain, &dep_idx)) {
wlr_log(WLR_ERROR, "Swap-chain acquire failed");
return false;
}
GLuint color_tex = LunarWM_xr_get_swapchain_image(this, 0, col_idx);
GLuint depth_tex = LunarWM_xr_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 eye_w
= this->xr.a_view_configuration_views[0].recommendedImageRectWidth;
uint32_t const eye_h
= this->xr.a_view_configuration_views[0].recommendedImageRectHeight;
this->renderer.tmp_rt = (RenderTexture2D) {
.id = this->renderer.fbo,
.texture = { color_tex, (int)eye_w * view_count, (int)eye_h, 1, -1 },
.depth = { depth_tex, (int)eye_w * view_count, (int)eye_h, 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 head_view = MatrixInvert(xr_matrix(headLoc.pose));
// per-eye projection + view-offset
Matrix const view_off_l
= MatrixMultiply(xr_matrix(views[0].pose), head_view);
Matrix const view_off_r
= MatrixMultiply(xr_matrix(views[1].pose), head_view);
Matrix const proj_r = xr_projection_matrix(views[0].fov);
Matrix const proj_l = xr_projection_matrix(views[1].fov);
int const hud_size = this->cman->cfg.displays.hud.size;
if (!IsTextureValid(this->renderer.hud_rt.texture)) {
this->renderer.hud_rt = LoadRenderTexture(hud_size, hud_size);
}
if (IsTextureValid(this->renderer.hud_rt.texture)) {
BeginTextureMode(this->renderer.hud_rt);
{
LunarWM_render_hud(this, dt, hud_size);
}
EndTextureMode();
}
// draw
if (!IsTextureValid(this->renderer.main_rt.texture)) {
this->renderer.main_rt = LoadRenderTexture(eye_w * view_count, eye_h);
}
BeginTextureMode(this->renderer.main_rt);
rlEnableStereoRender();
rlSetMatrixProjectionStereo(proj_r, proj_l);
rlSetMatrixViewOffsetStereo(view_off_r, view_off_l);
glViewport(0, 0, (GLsizei)eye_w * view_count, (GLsizei)eye_h);
rlClearColor(0, 0, 10, 255);
rlClearScreenBuffers();
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;
}
if (this->xr.recenter_active) {
Vector3 pos = this->renderer.camera.position;
Vector3 fwd = Vector3Normalize(
Vector3Subtract(this->renderer.camera.target, pos));
Vector3 up = this->renderer.camera.up;
pos = Vector3Add(
Vector3RotateByQuaternion(pos, this->xr.recenter_rot),
this->xr.recenter_trans);
fwd = Vector3RotateByQuaternion(fwd, this->xr.recenter_rot);
up = Vector3RotateByQuaternion(up, this->xr.recenter_rot);
this->renderer.camera.position = pos;
this->renderer.camera.target = Vector3Add(pos, fwd);
this->renderer.camera.up = up;
}
}
BeginMode3D(this->renderer.camera);
{
ClearBackground(RED);
render_3d(this, dt);
}
EndMode3D();
rlDisableStereoRender();
EndTextureMode();
if (!IsShaderValid(this->renderer.linear_srgb)) {
static char const linear_srgb[] = {
#embed "../assets/linear_srgb.fs"
, 0
};
this->renderer.linear_srgb = LoadShaderFromMemory(NULL, linear_srgb);
}
BeginTextureMode(this->renderer.tmp_rt);
rlDisableColorBlend();
ClearBackground(BLACK);
BeginShaderMode(this->renderer.linear_srgb);
DrawTexturePro(this->renderer.main_rt.texture,
(Rectangle) {
0,
0,
this->renderer.main_rt.texture.width,
-this->renderer.main_rt.texture.height,
},
(Rectangle) {
0,
0,
this->renderer.tmp_rt.texture.width,
this->renderer.tmp_rt.texture.height,
},
(Vector2) { 0, 0 }, 0, WHITE);
EndShaderMode();
EndTextureMode();
rlEnableColorBlend();
// 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 * eye_w;
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 = eye_w, .height = eye_h };
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;
if (this->renderer.first_frame) {
LunarWM_set_recenter_from_camera(this);
this->renderer.first_frame = false;
this->wm.pointer = get_forward_spherical_with_nearest(
this->renderer.camera.target, this->cman->cfg.space.radius);
}
return true;
}

11
src/LunarWM_render.h Normal file
View File

@@ -0,0 +1,11 @@
#ifndef LUNAR_WM_RENDER_H
#define LUNAR_WM_RENDER_H
#include "LunarWM_types.h"
void LunarWM_render_hud(LunarWM *wm, float dt, int hud_size);
void LunarWM_render_windows(LunarWM *wm, bool alpha_check);
bool LunarWM_render_layer(
LunarWM *wm, LunarWM_RenderLayerInfo *info, float dt);
#endif

272
src/LunarWM_types.h Normal file
View File

@@ -0,0 +1,272 @@
#ifndef LUNAR_WM_TYPES_H
#define LUNAR_WM_TYPES_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 <wlr/xwayland/xwayland.h>
#include <raylib.h>
#include <raymath.h>
#include <rlgl.h>
#include "Config.h"
#include "RayExt.h"
struct LunarWM;
typedef struct {
float r, theta, phi;
} SphericalCoord;
static inline SphericalCoord Vector3ToSpherical(Vector3 v)
{
SphericalCoord s;
s.r = sqrtf(v.x * v.x + v.y * v.y + v.z * v.z);
if (s.r > 0.0f) {
s.theta = atan2f(v.z, v.x);
s.phi = acosf(v.y / s.r);
} else {
s.theta = 0.0f;
s.phi = 0.0f;
}
return s;
}
static inline Vector3 SphericalToVector3(SphericalCoord s)
{
Vector3 v;
float sin_phi = sinf(s.phi);
v.x = s.r * cosf(s.theta) * sin_phi;
v.y = s.r * cosf(s.phi);
v.z = s.r * sinf(s.theta) * sin_phi;
return v;
}
typedef struct virtual_output {
struct wl_global *global;
struct wl_display *display;
struct wl_list clients;
int32_t x, y;
int32_t phys_w_mm, phys_h_mm;
int32_t width, height;
int32_t refresh_mhz;
int32_t scale;
enum wl_output_subpixel subpixel;
enum wl_output_transform transform;
char const *make, *model;
char const *name, *desc;
} LunarWM_VirtualOutput;
typedef struct {
struct LunarWM *server;
struct wl_list link;
struct wlr_keyboard *wlr_keyboard;
struct wl_listener modifiers;
struct wl_listener key;
struct wl_listener destroy;
} LunarWM_Keyboard;
typedef struct {
struct LunarWM *server;
struct wl_list link;
struct wlr_pointer *wlr_pointer;
struct wl_listener motion;
struct wl_listener destroy;
} LunarWM_Pointer;
typedef struct {
uint32_t id;
bool is_xwayland;
struct LunarWM *server;
struct wl_listener commit;
struct wl_listener destroy;
struct wl_listener map, unmap;
union {
struct wlr_xdg_toplevel *xdg;
struct wlr_xwayland_surface *xwl;
} u;
struct wlr_surface *surface;
struct wlr_texture *texture;
struct wlr_buffer *locked_buffer;
struct wlr_gles2_texture_attribs attribs;
struct wlr_gles2_texture *gles_texture;
Texture2D rl_texture;
} LunarWM_Toplevel;
typedef struct {
LunarWM_Toplevel *tl;
SphericalCoord coord;
} LunarWM_Window;
bool LunarWM_Toplevel_init_xdg(
LunarWM_Toplevel *tl, struct LunarWM *wm, struct wlr_xdg_toplevel *xdg);
bool LunarWM_Toplevel_init_xwayland(
LunarWM_Toplevel *tl, struct LunarWM *wm, struct wlr_xwayland_surface *xwl);
bool LunarWM_Toplevel_destroy(LunarWM_Toplevel *this);
bool LunarWM_Toplevel_update(LunarWM_Toplevel *this);
void LunarWM_Toplevel_focus(LunarWM_Toplevel *this);
typedef struct {
XrSwapchain swapchain;
int64_t swapchain_format;
GLuint *v_image_views;
} LunarWM_SwapchainInfo;
typedef struct {
XrSwapchain handle;
XrSwapchainImageOpenGLESKHR *a_imgs;
uint32_t a_imgs_count;
} LunarWM_SwapchainImagesEntry;
typedef struct {
XrHandJointLocationEXT joint_locations[XR_HAND_JOINT_COUNT_EXT];
XrHandTrackerEXT hand_tracker;
} LunarWM_Hand;
typedef struct {
XrTime predicted_display_time;
XrCompositionLayerProjection layer_projection;
XrCompositionLayerBaseHeader *layers[10];
uint32_t layers_count;
XrCompositionLayerProjectionView layer_projection_views[10];
uint32_t layer_projection_views_count;
} LunarWM_RenderLayerInfo;
typedef struct LunarWM {
struct {
struct wl_display *display;
struct wl_event_loop *event_loop;
struct wlr_backend *backend;
struct wlr_renderer *renderer;
struct wlr_session *session;
struct wlr_egl *egl;
EGLDisplay egl_display;
EGLContext egl_context;
EGLConfig egl_config;
struct wlr_allocator *allocator;
struct wlr_compositor *compositor;
struct wlr_subcompositor *subcompositor;
struct wlr_data_device_manager *data_device_manager;
struct wlr_seat *seat;
struct wl_list keyboards;
struct wl_list pointers;
struct wl_listener new_input_listener;
struct wlr_xdg_shell *xdg_shell;
struct wl_listener new_xdg_toplevel_listener;
struct wl_listener new_xdg_popup_listener;
struct wlr_xwayland *xwayland;
struct wl_listener xwayland_ready;
struct wl_listener xwayland_new_surface;
struct wl_listener xwayland_associate_tmp;
struct wl_listener xwayland_dissociate_tmp;
LunarWM_VirtualOutput *custom_out_virtual;
LunarWM_VirtualOutput *custom_out_hud;
LunarWM_Toplevel **v_toplevels;
int current_focus;
} wayland;
struct {
XrInstance instance;
XrSystemId system_id;
XrSession session;
XrSessionState session_state;
struct {
LunarWM_SwapchainInfo *v_color;
LunarWM_SwapchainInfo *v_depth;
} swapchains;
LunarWM_SwapchainImagesEntry swapchain_images[2];
XrViewConfigurationView *a_view_configuration_views;
uint32_t view_configuration_views_count;
XrEnvironmentBlendMode environment_blend_mode;
XrSpace local_space, view_space;
LunarWM_Hand hands[2];
XrSystemHandTrackingPropertiesEXT hand_tracking_system_properties;
PFN_xrCreateHandTrackerEXT CreateHandTrackerEXT;
PFN_xrDestroyHandTrackerEXT DestroyHandTrackerEXT;
PFN_xrLocateHandJointsEXT LocateHandJointsEXT;
Quaternion recenter_rot;
Vector3 recenter_trans;
bool recenter_active;
bool session_running;
} xr;
struct {
GLuint fbo;
RenderTexture2D tmp_rt;
RenderTexture2D main_rt;
RenderTexture2D hud_rt;
Camera3D camera;
Shader linear_srgb;
Skybox skybox;
bool first_frame;
} renderer;
struct {
SphericalCoord pointer;
int active_workspace;
struct {
LunarWM_Window *v_windows;
} workspaces[10];
} wm;
ConfigManager *cman;
_Atomic(int) counter;
bool initialized;
bool running;
} LunarWM;
static inline bool LunarWM_get_new_id(LunarWM *this) { return ++this->counter; }
#endif

1250
src/LunarWM_wayland.c Normal file

File diff suppressed because it is too large Load Diff

11
src/LunarWM_wayland.h Normal file
View File

@@ -0,0 +1,11 @@
#ifndef LUNAR_WM_WAYLAND_H
#define LUNAR_WM_WAYLAND_H
#include "LunarWM_types.h"
bool LunarWM_wayland_init(LunarWM *wm);
void LunarWM_wayland_cleanup(LunarWM *wm);
void LunarWM_wayland_update_virtual_outputs(
LunarWM *wm, int virtual_width, int virtual_height, int hud_size);
#endif

824
src/LunarWM_xr.c Normal file
View File

@@ -0,0 +1,824 @@
#include "LunarWM_xr.h"
#include "common.h"
#include "vec.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
GLuint LunarWM_xr_get_swapchain_image(
LunarWM *wm, int swapchain_images_i, uint32_t index)
{
return wm->xr.swapchain_images[swapchain_images_i].a_imgs[index].image;
}
bool LunarWM_xr_init(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 eye_w
= this->xr.a_view_configuration_views[0].recommendedImageRectWidth;
uint32_t const eye_h
= this->xr.a_view_configuration_views[0].recommendedImageRectHeight;
uint32_t const buf_w = eye_w * 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 = buf_w,
.height = eye_h,
.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 = buf_w,
.height = eye_h,
.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, LunarWM_xr_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, LunarWM_xr_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_LOCAL,
.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;
}
}
}
free(extension_properties);
free(a_view_config_types);
free(a_swapchain_formats);
free(a_environment_blend_modes);
vector_free(v_active_instance_extensions);
return true;
}
static void free_swapchain_info(LunarWM_SwapchainInfo *sc)
{
if (!sc)
return;
if (sc->v_image_views) {
for (size_t i = 0; i < vector_size(sc->v_image_views); ++i) {
GLuint fb = sc->v_image_views[i];
if (fb)
glDeleteFramebuffers(1, &fb);
}
vector_free(sc->v_image_views);
sc->v_image_views = NULL;
}
if (sc->swapchain) {
xrDestroySwapchain(sc->swapchain);
sc->swapchain = XR_NULL_HANDLE;
}
}
void LunarWM_xr_cleanup(LunarWM *this)
{
for (int i = 0; i < 2; ++i) {
if (this->xr.swapchain_images[i].a_imgs) {
free(this->xr.swapchain_images[i].a_imgs);
this->xr.swapchain_images[i].a_imgs = NULL;
this->xr.swapchain_images[i].a_imgs_count = 0;
}
}
if (this->xr.swapchains.v_color) {
for (size_t i = 0; i < vector_size(this->xr.swapchains.v_color); ++i)
free_swapchain_info(&this->xr.swapchains.v_color[i]);
vector_free(this->xr.swapchains.v_color);
this->xr.swapchains.v_color = NULL;
}
if (this->xr.swapchains.v_depth) {
for (size_t i = 0; i < vector_size(this->xr.swapchains.v_depth); ++i)
free_swapchain_info(&this->xr.swapchains.v_depth[i]);
vector_free(this->xr.swapchains.v_depth);
this->xr.swapchains.v_depth = NULL;
}
if (this->renderer.fbo) {
glDeleteFramebuffers(1, &this->renderer.fbo);
this->renderer.fbo = 0;
}
this->renderer.tmp_rt = (RenderTexture2D) { 0 };
if (this->xr.view_space)
xrDestroySpace(this->xr.view_space),
this->xr.view_space = XR_NULL_HANDLE;
if (this->xr.local_space)
xrDestroySpace(this->xr.local_space),
this->xr.local_space = XR_NULL_HANDLE;
for (size_t i = 0; i < 2; ++i) {
if (this->xr.hands[i].hand_tracker && this->xr.DestroyHandTrackerEXT)
this->xr.DestroyHandTrackerEXT(this->xr.hands[i].hand_tracker);
this->xr.hands[i].hand_tracker = XR_NULL_HANDLE;
}
if (this->xr.a_view_configuration_views) {
free(this->xr.a_view_configuration_views);
this->xr.a_view_configuration_views = NULL;
this->xr.view_configuration_views_count = 0;
}
if (this->xr.session != XR_NULL_HANDLE) {
xrDestroySession(this->xr.session);
this->xr.session = XR_NULL_HANDLE;
this->xr.session_running = false;
}
if (this->xr.instance)
xrDestroyInstance(this->xr.instance),
this->xr.instance = XR_NULL_HANDLE;
}
void LunarWM_xr_poll_events(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 LunarWM_xr_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;
}

13
src/LunarWM_xr.h Normal file
View File

@@ -0,0 +1,13 @@
#ifndef LUNAR_WM_XR_H
#define LUNAR_WM_XR_H
#include "LunarWM_types.h"
bool LunarWM_xr_init(LunarWM *wm);
void LunarWM_xr_cleanup(LunarWM *wm);
void LunarWM_xr_poll_events(LunarWM *wm);
bool LunarWM_xr_acquire_wait(XrSwapchain sc, uint32_t *idx);
GLuint LunarWM_xr_get_swapchain_image(
LunarWM *wm, int swapchain_images_i, uint32_t index);
#endif