26
.clang-format
Normal file
26
.clang-format
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
UseTab: ForIndentation
|
||||||
|
TabWidth: 4
|
||||||
|
IndentWidth: 4
|
||||||
|
ColumnLimit: 80
|
||||||
|
|
||||||
|
AlignEscapedNewlines: DontAlign
|
||||||
|
AlignTrailingComments:
|
||||||
|
Kind: Always
|
||||||
|
OverEmptyLines: 0
|
||||||
|
BasedOnStyle: WebKit
|
||||||
|
BraceWrapping:
|
||||||
|
AfterFunction: true
|
||||||
|
BreakBeforeBraces: Custom
|
||||||
|
BreakBeforeInheritanceComma: true
|
||||||
|
BreakConstructorInitializers: BeforeComma
|
||||||
|
IndentPPDirectives: AfterHash
|
||||||
|
IndentRequiresClause: false
|
||||||
|
InsertNewlineAtEOF: true
|
||||||
|
LineEnding: LF
|
||||||
|
NamespaceIndentation: None
|
||||||
|
PointerAlignment: Right # east pointer
|
||||||
|
QualifierAlignment: Right # east const
|
||||||
|
RemoveSemicolon: true
|
||||||
|
RequiresClausePosition: WithFollowing
|
||||||
|
RequiresExpressionIndentation: OuterScope
|
||||||
|
SpaceAfterTemplateKeyword: false
|
||||||
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
.cache
|
||||||
|
[Bb]uild*
|
||||||
|
/target
|
||||||
|
.direnv
|
||||||
|
log
|
||||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "wlroots-lunar"]
|
||||||
|
path = wlroots-lunar
|
||||||
|
url = git@github.com:slendidev/wlroots-lunar
|
||||||
127
CMakeLists.txt
Normal file
127
CMakeLists.txt
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.31)
|
||||||
|
|
||||||
|
project(LunarWM LANGUAGES C)
|
||||||
|
|
||||||
|
set(CMAKE_C_STANDARD 23)
|
||||||
|
set(CMAKE_C_STANDARD_REQUIRED 23)
|
||||||
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
|
|
||||||
|
add_compile_options(
|
||||||
|
-fstack-protector-strong
|
||||||
|
-fwrapv
|
||||||
|
)
|
||||||
|
add_compile_definitions(
|
||||||
|
WLR_USE_UNSTABLE
|
||||||
|
XR_USE_PLATFORM_EGL
|
||||||
|
XR_USE_GRAPHICS_API_OPENGL_ES
|
||||||
|
)
|
||||||
|
|
||||||
|
find_package(PkgConfig REQUIRED)
|
||||||
|
|
||||||
|
pkg_check_modules(WAYLAND REQUIRED IMPORTED_TARGET GLOBAL wayland-server)
|
||||||
|
pkg_check_modules(EGL REQUIRED IMPORTED_TARGET egl)
|
||||||
|
pkg_check_modules(GLES2 REQUIRED IMPORTED_TARGET glesv2)
|
||||||
|
pkg_check_modules(WLROOTS REQUIRED IMPORTED_TARGET wlroots-0.20)
|
||||||
|
pkg_check_modules(XKBCOMMON REQUIRED IMPORTED_TARGET xkbcommon)
|
||||||
|
pkg_check_modules(OPENXR REQUIRED IMPORTED_TARGET openxr)
|
||||||
|
pkg_check_modules(LUA REQUIRED IMPORTED_TARGET lua)
|
||||||
|
pkg_check_modules(PIXMAN REQUIRED IMPORTED_TARGET pixman-1)
|
||||||
|
|
||||||
|
find_program(WAYLAND_SCANNER_EXECUTABLE wayland-scanner REQUIRED)
|
||||||
|
message(STATUS "Found wayland-scanner at ${WAYLAND_SCANNER_EXECUTABLE}")
|
||||||
|
pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir)
|
||||||
|
message(STATUS "Found wayland-protocols at ${WAYLAND_PROTOCOLS_DIR}")
|
||||||
|
pkg_get_variable(WAYLAND_SCANNER_PKGDATA_DIR wayland-scanner pkgdatadir)
|
||||||
|
message(
|
||||||
|
STATUS "Found wayland-scanner pkgdatadir at ${WAYLAND_SCANNER_PKGDATA_DIR}")
|
||||||
|
|
||||||
|
include(FetchContent)
|
||||||
|
|
||||||
|
FetchContent_Declare(
|
||||||
|
raylib
|
||||||
|
GIT_REPOSITORY https://github.com/slendidev/raylib.git
|
||||||
|
GIT_TAG "lunar"
|
||||||
|
GIT_SHALLOW 1
|
||||||
|
)
|
||||||
|
set(OPENGL_VERSION "ES 3.0")
|
||||||
|
set(PLATFORM DRM)
|
||||||
|
set(BUILD_EXAMPLES OFF)
|
||||||
|
FetchContent_MakeAvailable(raylib)
|
||||||
|
|
||||||
|
add_executable(${PROJECT_NAME})
|
||||||
|
target_sources(${PROJECT_NAME} PUBLIC
|
||||||
|
src/vec.c
|
||||||
|
|
||||||
|
src/RayExt.c
|
||||||
|
src/Config.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
|
||||||
|
PkgConfig::XKBCOMMON
|
||||||
|
PkgConfig::WAYLAND
|
||||||
|
PkgConfig::EGL
|
||||||
|
PkgConfig::GLES2
|
||||||
|
PkgConfig::WLROOTS
|
||||||
|
PkgConfig::OPENXR
|
||||||
|
PkgConfig::LUA
|
||||||
|
PkgConfig::PIXMAN
|
||||||
|
|
||||||
|
raylib
|
||||||
|
)
|
||||||
|
|
||||||
|
# Wayland protocol codegen
|
||||||
|
set(XDG_SHELL_XML
|
||||||
|
${WAYLAND_PROTOCOLS_DIR}/stable/xdg-shell/xdg-shell.xml
|
||||||
|
)
|
||||||
|
|
||||||
|
set(XDG_SHELL_HEADER
|
||||||
|
${CMAKE_BINARY_DIR}/xdg-shell-protocol.h
|
||||||
|
)
|
||||||
|
|
||||||
|
set(XDG_SHELL_C
|
||||||
|
${CMAKE_BINARY_DIR}/xdg-shell-protocol.c
|
||||||
|
)
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${XDG_SHELL_HEADER}
|
||||||
|
COMMAND ${WAYLAND_SCANNER_EXECUTABLE} server-header
|
||||||
|
${XDG_SHELL_XML} ${XDG_SHELL_HEADER}
|
||||||
|
DEPENDS ${XDG_SHELL_XML}
|
||||||
|
COMMENT "Generating xdg-shell-protocol.h (server header)"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${XDG_SHELL_C}
|
||||||
|
COMMAND ${WAYLAND_SCANNER_EXECUTABLE} private-code
|
||||||
|
${XDG_SHELL_XML} ${XDG_SHELL_C}
|
||||||
|
DEPENDS ${XDG_SHELL_XML}
|
||||||
|
COMMENT "Generating xdg-shell-protocol.c"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
|
||||||
|
add_custom_target(xdg_shell_protocol
|
||||||
|
DEPENDS ${XDG_SHELL_HEADER} ${XDG_SHELL_C}
|
||||||
|
)
|
||||||
|
|
||||||
|
add_library(xdg_shell STATIC
|
||||||
|
${XDG_SHELL_C}
|
||||||
|
)
|
||||||
|
|
||||||
|
add_dependencies(xdg_shell xdg_shell_protocol)
|
||||||
|
|
||||||
|
target_include_directories(xdg_shell PUBLIC
|
||||||
|
${CMAKE_BINARY_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
add_dependencies(${PROJECT_NAME} xdg_shell_protocol)
|
||||||
|
|
||||||
|
target_link_libraries(${PROJECT_NAME} PRIVATE xdg_shell)
|
||||||
|
|
||||||
|
target_include_directories(${PROJECT_NAME} PRIVATE
|
||||||
|
${CMAKE_BINARY_DIR}
|
||||||
|
)
|
||||||
722
assets/hand_l.gltf
Normal file
722
assets/hand_l.gltf
Normal file
File diff suppressed because one or more lines are too long
722
assets/hand_r.gltf
Normal file
722
assets/hand_r.gltf
Normal file
File diff suppressed because one or more lines are too long
18
assets/linear_srgb.fs
Normal file
18
assets/linear_srgb.fs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
precision mediump float;
|
||||||
|
|
||||||
|
varying vec2 fragTexCoord;
|
||||||
|
varying vec4 fragColor;
|
||||||
|
uniform sampler2D texture0;
|
||||||
|
|
||||||
|
vec3 srgb_to_linear(vec3 c) {
|
||||||
|
bvec3 cutoff = lessThanEqual(c, vec3(0.04045));
|
||||||
|
vec3 low = c / 12.92;
|
||||||
|
vec3 high = pow((c + 0.055) / 1.055, vec3(2.4));
|
||||||
|
return mix(high, low, vec3(cutoff));
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 c = texture2D(texture0, fragTexCoord) * fragColor;
|
||||||
|
c.rgb = srgb_to_linear(c.rgb); // decode to linear
|
||||||
|
gl_FragColor = c;
|
||||||
|
}
|
||||||
116
flake.lock
generated
Normal file
116
flake.lock
generated
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1731533236,
|
||||||
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils_2": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems_2"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1731533236,
|
||||||
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1759036355,
|
||||||
|
"narHash": "sha256-0m27AKv6ka+q270dw48KflE0LwQYrO7Fm4/2//KCVWg=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "e9f00bd893984bc8ce46c895c3bf7cac95331127",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs",
|
||||||
|
"wlroots-lunar": "wlroots-lunar"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"wlroots-lunar": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils_2",
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1759266345,
|
||||||
|
"narHash": "sha256-BJ+CTRXaFArVFgJfL19QpoR7Ebk8HU63Lz0+jQvhV3Y=",
|
||||||
|
"owner": "slendidev",
|
||||||
|
"repo": "wlroots-lunar",
|
||||||
|
"rev": "1179ca07821decbff320eafd7ffb3caaadcefbf4",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "slendidev",
|
||||||
|
"repo": "wlroots-lunar",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
88
flake.nix
Normal file
88
flake.nix
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
{
|
||||||
|
description = "LunarWM is a VR-based Wayland compositor";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
wlroots-lunar = {
|
||||||
|
url = "github:slendidev/wlroots-lunar";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs =
|
||||||
|
{
|
||||||
|
self,
|
||||||
|
nixpkgs,
|
||||||
|
flake-utils,
|
||||||
|
wlroots-lunar,
|
||||||
|
}:
|
||||||
|
flake-utils.lib.eachDefaultSystem (
|
||||||
|
system:
|
||||||
|
let
|
||||||
|
pkgs = import nixpkgs {
|
||||||
|
system = system;
|
||||||
|
config.cudaSupport = true;
|
||||||
|
config.allowUnfree = true;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
devShells.default = pkgs.mkShell.override { stdenv = pkgs.llvmPackages_20.libcxxStdenv; } {
|
||||||
|
hardeningDisable = [ "fortify" ];
|
||||||
|
|
||||||
|
packages = with pkgs; [
|
||||||
|
pkg-config
|
||||||
|
cmake
|
||||||
|
ninja
|
||||||
|
(pkgs.llvmPackages_20.clang-tools.override { enableLibcxx = true; })
|
||||||
|
lldb
|
||||||
|
|
||||||
|
lua
|
||||||
|
|
||||||
|
# For wlroots
|
||||||
|
libxkbcommon
|
||||||
|
libxkbcommon.dev
|
||||||
|
libdrm
|
||||||
|
xorg.libxcb
|
||||||
|
pixman
|
||||||
|
libgbm
|
||||||
|
lcms2
|
||||||
|
seatd
|
||||||
|
libdisplay-info
|
||||||
|
libliftoff
|
||||||
|
libinput
|
||||||
|
xorg.xcbutilrenderutil
|
||||||
|
xorg.xcbutilwm
|
||||||
|
xorg.xcbutilerrors
|
||||||
|
vulkan-loader
|
||||||
|
|
||||||
|
wlroots-lunar.packages."${system}".default
|
||||||
|
|
||||||
|
# For raylib
|
||||||
|
xorg.libXrandr
|
||||||
|
xorg.libXinerama
|
||||||
|
xorg.libXcursor
|
||||||
|
xorg.libXi
|
||||||
|
glfw
|
||||||
|
|
||||||
|
boost
|
||||||
|
libffi
|
||||||
|
wayland
|
||||||
|
wayland-scanner
|
||||||
|
wayland-protocols
|
||||||
|
|
||||||
|
openxr-loader
|
||||||
|
libGL
|
||||||
|
glm
|
||||||
|
xorg.libX11
|
||||||
|
xorg.libXau
|
||||||
|
xorg.libXdmcp
|
||||||
|
];
|
||||||
|
|
||||||
|
shellHook = ''
|
||||||
|
export NIX_CFLAGS_COMPILE="$NIX_CFLAGS_COMPILE -B${pkgs.llvmPackages_20.libcxx}/lib -I${pkgs.llvmPackages_20.libcxx.dev}/include/c++/v1"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
34
launch_settings.cap
Normal file
34
launch_settings.cap
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"rdocCaptureSettings": 1,
|
||||||
|
"settings": {
|
||||||
|
"autoStart": false,
|
||||||
|
"commandLine": "",
|
||||||
|
"environment": [
|
||||||
|
{
|
||||||
|
"separator": "Platform style",
|
||||||
|
"type": "Set",
|
||||||
|
"value": "1",
|
||||||
|
"variable": "LWM_NO_XR"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"executable": "/home/lain/Documents/projs/lunarwm/build/LunarWM",
|
||||||
|
"inject": false,
|
||||||
|
"numQueuedFrames": 0,
|
||||||
|
"options": {
|
||||||
|
"allowFullscreen": true,
|
||||||
|
"allowVSync": true,
|
||||||
|
"apiValidation": false,
|
||||||
|
"captureAllCmdLists": false,
|
||||||
|
"captureCallstacks": false,
|
||||||
|
"captureCallstacksOnlyDraws": false,
|
||||||
|
"debugOutputMute": true,
|
||||||
|
"delayForDebugger": 0,
|
||||||
|
"hookIntoChildren": false,
|
||||||
|
"refAllResources": false,
|
||||||
|
"softMemoryLimit": 0,
|
||||||
|
"verifyBufferAccess": false
|
||||||
|
},
|
||||||
|
"queuedFrameCap": 0,
|
||||||
|
"workingDir": "/home/lain/Documents/projs/lunarwm"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
lunarwm/citrus_orchard_road_puresky_8k.hdr
Normal file
BIN
lunarwm/citrus_orchard_road_puresky_8k.hdr
Normal file
Binary file not shown.
BIN
lunarwm/citrus_orchard_road_puresky_8k.png
Normal file
BIN
lunarwm/citrus_orchard_road_puresky_8k.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 MiB |
BIN
lunarwm/cubemap.png
Normal file
BIN
lunarwm/cubemap.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 MiB |
34
lunarwm/init.lua
Normal file
34
lunarwm/init.lua
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
function main(kbd)
|
||||||
|
return "Super-" .. kbd
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
input = {
|
||||||
|
keyboard = {
|
||||||
|
xkb_options = { "altwin:swap_lalt_lwin" },
|
||||||
|
},
|
||||||
|
mouse = {
|
||||||
|
invert_y = false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
keybindings = {
|
||||||
|
{ bind = main("Shift-Q"), action = lunar.quit_compositor },
|
||||||
|
{ bind = main("Shift-R"), action = lunar.reload_config },
|
||||||
|
{ bind = main("R"), action = lunar.recenter },
|
||||||
|
{ bind = main("Tab"), action = lunar.cycle_next },
|
||||||
|
{ bind = main("Space"), action = function() lunar.exec("kitty") end },
|
||||||
|
},
|
||||||
|
space = {
|
||||||
|
radius = 1.0,
|
||||||
|
},
|
||||||
|
displays = {
|
||||||
|
hud = {
|
||||||
|
size = 720,
|
||||||
|
font_size = 24,
|
||||||
|
},
|
||||||
|
virtual = {
|
||||||
|
resolution = { 2560, 1440 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cubemap = 'cubemap.png',
|
||||||
|
}
|
||||||
493
src/Config.c
Normal file
493
src/Config.c
Normal file
@@ -0,0 +1,493 @@
|
|||||||
|
#include "Config.h"
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "lua_helpers.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#include <lauxlib.h>
|
||||||
|
#include <lualib.h>
|
||||||
|
|
||||||
|
#include <wlr/types/wlr_keyboard.h>
|
||||||
|
#include <xkbcommon/xkbcommon.h>
|
||||||
|
|
||||||
|
char const *get_config_path(void)
|
||||||
|
{
|
||||||
|
static char const *paths[] = {
|
||||||
|
"lunarwm/init.lua",
|
||||||
|
};
|
||||||
|
for (size_t i = 0; i < ARRAY_SZ(paths); ++i) {
|
||||||
|
char const *p = paths[i];
|
||||||
|
struct stat s;
|
||||||
|
if (stat(p, &s) == 0)
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int dupstr(char const *s, char **out)
|
||||||
|
{
|
||||||
|
if (!s) {
|
||||||
|
*out = NULL;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
size_t n = strlen(s) + 1;
|
||||||
|
char *m = (char *)malloc(n);
|
||||||
|
if (!m)
|
||||||
|
return -1;
|
||||||
|
memcpy(m, s, n);
|
||||||
|
*out = m;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t mod_from_token(char const *t)
|
||||||
|
{
|
||||||
|
if (!t)
|
||||||
|
return 0;
|
||||||
|
if (strcasecmp(t, "Super") == 0)
|
||||||
|
return WLR_MODIFIER_LOGO;
|
||||||
|
if (strcasecmp(t, "Shift") == 0)
|
||||||
|
return WLR_MODIFIER_SHIFT;
|
||||||
|
if (strcasecmp(t, "Ctrl") == 0 || strcasecmp(t, "Control") == 0)
|
||||||
|
return WLR_MODIFIER_CTRL;
|
||||||
|
if (strcasecmp(t, "Alt") == 0)
|
||||||
|
return WLR_MODIFIER_ALT;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parse_bind(
|
||||||
|
char const *bind, uint32_t *mods_out, xkb_keysym_t *sym_out)
|
||||||
|
{
|
||||||
|
if (!bind || !mods_out || !sym_out)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
char buf[256];
|
||||||
|
strncpy(buf, bind, sizeof buf - 1);
|
||||||
|
buf[sizeof buf - 1] = 0;
|
||||||
|
|
||||||
|
char *save = NULL;
|
||||||
|
char *tok = strtok_r(buf, "-", &save);
|
||||||
|
char *last = tok;
|
||||||
|
while (tok) {
|
||||||
|
last = tok;
|
||||||
|
tok = strtok_r(NULL, "-", &save);
|
||||||
|
}
|
||||||
|
|
||||||
|
strncpy(buf, bind, sizeof buf - 1);
|
||||||
|
buf[sizeof buf - 1] = 0;
|
||||||
|
save = NULL;
|
||||||
|
|
||||||
|
uint32_t mods = 0;
|
||||||
|
for (char *t = strtok_r(buf, "-", &save); t;
|
||||||
|
t = strtok_r(NULL, "-", &save)) {
|
||||||
|
if (t == last)
|
||||||
|
break;
|
||||||
|
mods |= mod_from_token(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
int flags = XKB_KEYSYM_CASE_INSENSITIVE;
|
||||||
|
xkb_keysym_t sym = xkb_keysym_from_name(last, flags);
|
||||||
|
if (sym == XKB_KEY_NoSymbol)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
*mods_out = mods;
|
||||||
|
*sym_out = sym;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int push_config_table_from_idx(lua_State *L, int idx_abs)
|
||||||
|
{
|
||||||
|
if (!lua_istable(L, idx_abs))
|
||||||
|
return luaL_error(L, "config: expected table at index %d", idx_abs);
|
||||||
|
lua_pushvalue(L, idx_abs);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int join_string_array(lua_State *L, int idx_abs, char **out)
|
||||||
|
{
|
||||||
|
*out = NULL;
|
||||||
|
if (!lua_istable(L, idx_abs))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
size_t n = (size_t)lua_rawlen(L, idx_abs);
|
||||||
|
if (n == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
size_t total = 1;
|
||||||
|
for (size_t i = 1; i <= n; ++i) {
|
||||||
|
lua_rawgeti(L, idx_abs, (lua_Integer)i);
|
||||||
|
size_t len = 0;
|
||||||
|
(void)lua_tolstring(L, -1, &len);
|
||||||
|
total += len + (i < n ? 1 : 0);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *buf = (char *)malloc(total);
|
||||||
|
if (!buf)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
size_t off = 0;
|
||||||
|
for (size_t i = 1; i <= n; ++i) {
|
||||||
|
lua_rawgeti(L, idx_abs, (lua_Integer)i);
|
||||||
|
size_t len = 0;
|
||||||
|
char const *s = lua_tolstring(L, -1, &len);
|
||||||
|
if (!s) {
|
||||||
|
lua_pop(L, 1);
|
||||||
|
free(buf);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
memcpy(buf + off, s, len);
|
||||||
|
off += len;
|
||||||
|
lua_pop(L, 1);
|
||||||
|
if (i != n)
|
||||||
|
buf[off++] = ',';
|
||||||
|
}
|
||||||
|
buf[off] = 0;
|
||||||
|
*out = buf;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int config_load_ref(lua_State *L, int idx, Config *out)
|
||||||
|
{
|
||||||
|
if (!L || !out)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
memset(out, 0, sizeof(*out));
|
||||||
|
|
||||||
|
// ======== DEFAULTS ========
|
||||||
|
out->space.offset = (Vector3) { 0, 0, 0 };
|
||||||
|
out->space.radius = 1.0f;
|
||||||
|
out->space.window_scale = 0.001f;
|
||||||
|
out->displays.hud.size = 720;
|
||||||
|
out->displays.hud.font_size = 24;
|
||||||
|
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);
|
||||||
|
if (push_config_table_from_idx(L, cfg_abs) != 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
lua_getfield(L, -1, "keybindings");
|
||||||
|
if (!lua_istable(L, -1)) {
|
||||||
|
lua_pop(L, 2);
|
||||||
|
return luaL_error(L, "config: 'keybindings' must be a table (array)");
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t n = (size_t)lua_rawlen(L, -1);
|
||||||
|
if (n) {
|
||||||
|
BindingRef *arr = (BindingRef *)calloc(n, sizeof(*arr));
|
||||||
|
if (!arr) {
|
||||||
|
lua_pop(L, 2);
|
||||||
|
return luaL_error(L, "config: OOM");
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t ok = 0;
|
||||||
|
for (size_t i = 0; i < n; ++i) {
|
||||||
|
lua_rawgeti(L, -1, (lua_Integer)(i + 1));
|
||||||
|
if (!lua_istable(L, -1)) {
|
||||||
|
lua_pop(L, 1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_getfield(L, -1, "bind");
|
||||||
|
char const *bind = lua_tostring(L, -1);
|
||||||
|
if (!bind) {
|
||||||
|
lua_pop(L, 2);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t mods = 0;
|
||||||
|
xkb_keysym_t sym = XKB_KEY_NoSymbol;
|
||||||
|
if (parse_bind(bind, &mods, &sym) != 0) {
|
||||||
|
lua_pop(L, 2);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_getfield(L, -2, "action");
|
||||||
|
if (!lua_isfunction(L, -1)) {
|
||||||
|
lua_pop(L, 3);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ref = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
arr[ok].mods_mask = mods;
|
||||||
|
arr[ok].sym = sym;
|
||||||
|
arr[ok].action_ref = ref;
|
||||||
|
++ok;
|
||||||
|
|
||||||
|
lua_pop(L, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ok == 0) {
|
||||||
|
free(arr);
|
||||||
|
} else if (ok < n) {
|
||||||
|
BindingRef *shr = (BindingRef *)realloc(arr, ok * sizeof(*shr));
|
||||||
|
if (shr)
|
||||||
|
arr = shr;
|
||||||
|
}
|
||||||
|
|
||||||
|
out->keybindings.items = ok ? arr : NULL;
|
||||||
|
out->keybindings.count = ok;
|
||||||
|
}
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
lua_getfield(L, -1, "input");
|
||||||
|
if (lua_istable(L, -1)) {
|
||||||
|
lua_getfield(L, -1, "keyboard");
|
||||||
|
if (lua_istable(L, -1)) {
|
||||||
|
lua_getfield(L, -1, "xkb_options");
|
||||||
|
if (lua_istable(L, -1)) {
|
||||||
|
(void)join_string_array(
|
||||||
|
L, lua_absindex(L, -1), &out->input.keyboard.xkb_options);
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
|
||||||
|
lua_getfield(L, -1, "space");
|
||||||
|
if (lua_istable(L, -1)) {
|
||||||
|
lua_getfield(L, -1, "offset");
|
||||||
|
if (lua_istable(L, -1) || lua_isuserdata(L, -1))
|
||||||
|
out->space.offset = lua_readVector3(L, lua_absindex(L, -1));
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
lua_getfield(L, -1, "radius");
|
||||||
|
if (lua_isnumber(L, -1))
|
||||||
|
out->space.radius = (float)lua_tonumber(L, -1);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
lua_getfield(L, -1, "window_scale");
|
||||||
|
if (lua_isnumber(L, -1))
|
||||||
|
out->space.window_scale = (float)lua_tonumber(L, -1);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
}
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
lua_getfield(L, -1, "xwayland");
|
||||||
|
if (lua_istable(L, -1)) {
|
||||||
|
lua_getfield(L, -1, "enabled");
|
||||||
|
if (lua_isboolean(L, -1))
|
||||||
|
out->xwayland.enabled = lua_toboolean(L, -1);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
lua_getfield(L, -1, "lazy");
|
||||||
|
if (lua_isboolean(L, -1))
|
||||||
|
out->xwayland.lazy = lua_toboolean(L, -1);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
}
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
lua_getfield(L, -1, "displays");
|
||||||
|
if (lua_istable(L, -1)) {
|
||||||
|
lua_getfield(L, -1, "hud");
|
||||||
|
if (lua_istable(L, -1)) {
|
||||||
|
lua_getfield(L, -1, "size");
|
||||||
|
if (lua_isnumber(L, -1))
|
||||||
|
out->displays.hud.size = (float)lua_tonumber(L, -1);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
lua_getfield(L, -1, "font_size");
|
||||||
|
if (lua_isnumber(L, -1))
|
||||||
|
out->displays.hud.font_size = (float)lua_tonumber(L, -1);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
}
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
lua_getfield(L, -1, "virtual");
|
||||||
|
if (lua_istable(L, -1)) {
|
||||||
|
lua_getfield(L, -1, "resolution");
|
||||||
|
if (lua_istable(L, -1) || lua_isuserdata(L, -1))
|
||||||
|
out->displays.virtual.resolution
|
||||||
|
= lua_readVector2(L, lua_absindex(L, -1));
|
||||||
|
lua_pop(L, 1);
|
||||||
|
}
|
||||||
|
lua_pop(L, 1);
|
||||||
|
}
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
lua_getfield(L, -1, "cubemap");
|
||||||
|
if (lua_isstring(L, -1)) {
|
||||||
|
char const *s = lua_tostring(L, -1);
|
||||||
|
if (s && s[0]) {
|
||||||
|
(void)dupstr(s, (char **)&out->cubemap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
lua_pop(L, 1);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void config_unref(lua_State *L, Config *cfg)
|
||||||
|
{
|
||||||
|
if (!cfg)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < cfg->keybindings.count; ++i) {
|
||||||
|
int r = cfg->keybindings.items ? cfg->keybindings.items[i].action_ref
|
||||||
|
: LUA_NOREF;
|
||||||
|
if (r != LUA_NOREF && r != LUA_REFNIL)
|
||||||
|
luaL_unref(L, LUA_REGISTRYINDEX, r);
|
||||||
|
}
|
||||||
|
free(cfg->keybindings.items);
|
||||||
|
cfg->keybindings.items = NULL;
|
||||||
|
cfg->keybindings.count = 0;
|
||||||
|
|
||||||
|
free(cfg->input.keyboard.xkb_options);
|
||||||
|
cfg->input.keyboard.xkb_options = NULL;
|
||||||
|
|
||||||
|
if (cfg->cubemap) {
|
||||||
|
free((void *)cfg->cubemap);
|
||||||
|
cfg->cubemap = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void config_trigger_ref(lua_State *L, Config *cfg, int ref)
|
||||||
|
{
|
||||||
|
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
|
||||||
|
if (!lua_isfunction(L, -1)) {
|
||||||
|
lua_pop(L, 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lua_pcall(L, 0, 0, 0) != LUA_OK) {
|
||||||
|
fprintf(stderr, "config: action error: %s\n", lua_tostring(L, -1));
|
||||||
|
lua_pop(L, 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int load_config_file(lua_State *L, char const *path)
|
||||||
|
{
|
||||||
|
if (!path || !path[0])
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (luaL_loadfile(L, path) != LUA_OK) {
|
||||||
|
char const *err = lua_tostring(L, -1);
|
||||||
|
fprintf(
|
||||||
|
stderr, "config: loadfile failed: %s\n", err ? err : "(unknown)");
|
||||||
|
lua_pop(L, 1);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (lua_pcall(L, 0, 1, 0) != LUA_OK) {
|
||||||
|
char const *err = lua_tostring(L, -1);
|
||||||
|
fprintf(stderr, "config: executing '%s' failed: %s\n", path,
|
||||||
|
err ? err : "(unknown)");
|
||||||
|
lua_pop(L, 1);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!lua_istable(L, -1)) {
|
||||||
|
lua_pop(L, 1);
|
||||||
|
fprintf(stderr, "config: '%s' did not return a table\n", path);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigManager *config_manager_create(char const *path)
|
||||||
|
{
|
||||||
|
ConfigManager *cm = (ConfigManager *)calloc(1, sizeof(*cm));
|
||||||
|
if (!cm)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
cm->L = luaL_newstate();
|
||||||
|
if (!cm->L) {
|
||||||
|
free(cm);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
luaL_openlibs(cm->L);
|
||||||
|
|
||||||
|
if (!path)
|
||||||
|
path = get_config_path();
|
||||||
|
if (path && dupstr(path, &cm->path) != 0) {
|
||||||
|
lua_close(cm->L);
|
||||||
|
free(cm);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cm;
|
||||||
|
}
|
||||||
|
|
||||||
|
void config_manager_destroy(ConfigManager *cm)
|
||||||
|
{
|
||||||
|
if (!cm)
|
||||||
|
return;
|
||||||
|
config_unref(cm->L, &cm->cfg);
|
||||||
|
if (cm->L)
|
||||||
|
lua_close(cm->L);
|
||||||
|
free(cm->path);
|
||||||
|
free(cm);
|
||||||
|
}
|
||||||
|
|
||||||
|
int config_manager_reload(ConfigManager *cm)
|
||||||
|
{
|
||||||
|
if (!cm || !cm->path)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
config_unref(cm->L, &cm->cfg);
|
||||||
|
|
||||||
|
if (load_config_file(cm->L, cm->path) != 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
int rc = config_load_ref(cm->L, -1, &cm->cfg);
|
||||||
|
lua_pop(cm->L, 1);
|
||||||
|
if (rc != 0)
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
if (cm->cfg.cubemap && cm->cfg.cubemap[0] != '/') {
|
||||||
|
char const *slash = strrchr(cm->path, '/');
|
||||||
|
char const *dir = ".";
|
||||||
|
size_t dirlen = 1;
|
||||||
|
if (slash) {
|
||||||
|
dir = cm->path;
|
||||||
|
dirlen = (size_t)(slash - cm->path);
|
||||||
|
}
|
||||||
|
size_t n = dirlen + 1 + strlen(cm->cfg.cubemap)
|
||||||
|
+ 1; // dir + '/' + hdri + '\0'
|
||||||
|
char *full = (char *)malloc(n);
|
||||||
|
if (full) {
|
||||||
|
memcpy(full, dir, dirlen);
|
||||||
|
full[dirlen] = '/';
|
||||||
|
strcpy(full + dirlen + 1, cm->cfg.cubemap);
|
||||||
|
free((void *)cm->cfg.cubemap);
|
||||||
|
cm->cfg.cubemap = full;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_State *config_manager_lua(ConfigManager *cm) { return cm ? cm->L : NULL; }
|
||||||
|
Config const *config_manager_get(ConfigManager *cm)
|
||||||
|
{
|
||||||
|
return cm ? &cm->cfg : NULL;
|
||||||
|
}
|
||||||
|
char const *config_manager_path(ConfigManager *cm)
|
||||||
|
{
|
||||||
|
return cm ? cm->path : NULL;
|
||||||
|
}
|
||||||
79
src/Config.h
Normal file
79
src/Config.h
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
#ifndef CONFIG_H
|
||||||
|
#define CONFIG_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#include <lua.h>
|
||||||
|
#include <raylib.h>
|
||||||
|
|
||||||
|
#include <xkbcommon/xkbcommon.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
xkb_keysym_t sym;
|
||||||
|
uint32_t mods_mask;
|
||||||
|
int action_ref;
|
||||||
|
} BindingRef;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
struct {
|
||||||
|
struct {
|
||||||
|
char *xkb_options;
|
||||||
|
} keyboard;
|
||||||
|
struct {
|
||||||
|
bool invert_x;
|
||||||
|
bool invert_y;
|
||||||
|
} mouse;
|
||||||
|
} input;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
BindingRef *items;
|
||||||
|
size_t count;
|
||||||
|
} keybindings;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
Vector3 offset;
|
||||||
|
float radius;
|
||||||
|
float window_scale;
|
||||||
|
} space;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
bool enabled;
|
||||||
|
bool lazy;
|
||||||
|
} xwayland;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
struct {
|
||||||
|
int size;
|
||||||
|
float font_size;
|
||||||
|
} hud;
|
||||||
|
struct {
|
||||||
|
Vector2 resolution;
|
||||||
|
} virtual;
|
||||||
|
} displays;
|
||||||
|
|
||||||
|
char const *cubemap;
|
||||||
|
} Config;
|
||||||
|
|
||||||
|
char const *get_config_path(void);
|
||||||
|
|
||||||
|
int config_load_ref(lua_State *L, int idx, Config *out);
|
||||||
|
void config_unref(lua_State *L, Config *cfg);
|
||||||
|
void config_trigger_ref(lua_State *L, Config *cfg, int ref);
|
||||||
|
|
||||||
|
struct ConfigManager {
|
||||||
|
lua_State *L;
|
||||||
|
Config cfg;
|
||||||
|
char *path;
|
||||||
|
};
|
||||||
|
typedef struct ConfigManager ConfigManager;
|
||||||
|
|
||||||
|
ConfigManager *config_manager_create(char const *path);
|
||||||
|
void config_manager_destroy(ConfigManager *cm);
|
||||||
|
|
||||||
|
int config_manager_reload(ConfigManager *cm);
|
||||||
|
|
||||||
|
lua_State *config_manager_lua(ConfigManager *cm);
|
||||||
|
Config const *config_manager_get(ConfigManager *cm);
|
||||||
|
char const *config_manager_path(ConfigManager *cm);
|
||||||
|
|
||||||
|
#endif // CONFIG_H
|
||||||
7
src/LunarWM.h
Normal file
7
src/LunarWM.h
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#ifndef LUNAR_WM_H
|
||||||
|
#define LUNAR_WM_H
|
||||||
|
|
||||||
|
#include "LunarWM_core.h"
|
||||||
|
#include "LunarWM_types.h"
|
||||||
|
|
||||||
|
#endif
|
||||||
434
src/LunarWM_core.c
Normal file
434
src/LunarWM_core.c
Normal file
@@ -0,0 +1,434 @@
|
|||||||
|
#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 <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->xr.hand_tracking_system_properties.next = NULL;
|
||||||
|
wm->xr.hand_tracking_system_properties.supportsHandTracking = XR_FALSE;
|
||||||
|
wm->xr.hand_tracking_enabled = false;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
wm->xr.available = false;
|
||||||
|
{
|
||||||
|
char *no_xr = getenv("LWM_NO_XR");
|
||||||
|
bool xr = true;
|
||||||
|
|
||||||
|
if (no_xr != NULL && no_xr[0] != '\0')
|
||||||
|
xr = false;
|
||||||
|
|
||||||
|
if (xr) {
|
||||||
|
if (!LunarWM_xr_init(wm)) {
|
||||||
|
wlr_log(
|
||||||
|
WLR_ERROR, "Failed to initialize OpenXR! Disabling XR...");
|
||||||
|
LunarWM_xr_cleanup(wm);
|
||||||
|
} else {
|
||||||
|
wm->xr.available = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
if (wm->xr.available) {
|
||||||
|
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_enabled) {
|
||||||
|
for (size_t i = 0; i < 2; i++) {
|
||||||
|
LunarWM_Hand *hand = &wm->xr.hands[i];
|
||||||
|
bool const unobstructed = true;
|
||||||
|
if (!wm->xr.LocateHandJointsEXT
|
||||||
|
|| hand->hand_tracker == XR_NULL_HANDLE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
} else {
|
||||||
|
wm->renderer.camera.fovy = 75;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/LunarWM_core.h
Normal file
14
src/LunarWM_core.h
Normal 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
|
||||||
675
src/LunarWM_render.c
Normal file
675
src/LunarWM_render.c
Normal file
@@ -0,0 +1,675 @@
|
|||||||
|
#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->composed_has_alpha) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Texture2D tex = tl->rl_texture;
|
||||||
|
bool y_flip = false;
|
||||||
|
if (IsRenderTextureValid(tl->surface_rt)) {
|
||||||
|
tex = tl->surface_rt.texture;
|
||||||
|
tex.width = tl->rl_texture.width;
|
||||||
|
tex.height = tl->rl_texture.height;
|
||||||
|
y_flip = true;
|
||||||
|
}
|
||||||
|
if (!tex.id)
|
||||||
|
continue;
|
||||||
|
float rad = window->coord.r - 0.01f * (float)i;
|
||||||
|
DrawTextureCyl2(tex, Vector3Zero(), window->coord, rad,
|
||||||
|
this->cman->cfg.space.window_scale, y_flip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LunarWM_render_3d(LunarWM *this, float /*dt*/)
|
||||||
|
{
|
||||||
|
LunarWM_render_windows(this, true);
|
||||||
|
|
||||||
|
for (int h = 0; this->xr.hand_tracking_enabled && h < 2; ++h) {
|
||||||
|
auto *hand_info = &this->xr.hands[h];
|
||||||
|
if (hand_info->hand_tracker == XR_NULL_HANDLE)
|
||||||
|
continue;
|
||||||
|
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);
|
||||||
|
LunarWM_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
11
src/LunarWM_render.h
Normal 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);
|
||||||
|
void LunarWM_render_3d(LunarWM *this, float dt);
|
||||||
|
|
||||||
|
#endif
|
||||||
293
src/LunarWM_types.h
Normal file
293
src/LunarWM_types.h
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
#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_output.h>
|
||||||
|
#include <wlr/types/wlr_pointer.h>
|
||||||
|
#include <wlr/types/wlr_subcompositor.h>
|
||||||
|
#include <wlr/types/wlr_xdg_shell.h>
|
||||||
|
#include <wlr/util/box.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 {
|
||||||
|
struct LunarWM *wm;
|
||||||
|
struct wlr_output *wlr_output;
|
||||||
|
|
||||||
|
struct wl_listener frame;
|
||||||
|
struct wl_listener destroy;
|
||||||
|
} LunarWM_Output;
|
||||||
|
|
||||||
|
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;
|
||||||
|
RenderTexture2D surface_rt;
|
||||||
|
struct wlr_box surface_extents;
|
||||||
|
bool composed_has_alpha;
|
||||||
|
} 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 wl_listener new_surface_listener;
|
||||||
|
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;
|
||||||
|
|
||||||
|
struct wl_listener new_output_listener;
|
||||||
|
LunarWM_Output **v_outputs;
|
||||||
|
|
||||||
|
} wayland;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
bool available;
|
||||||
|
|
||||||
|
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;
|
||||||
|
bool hand_tracking_enabled;
|
||||||
|
|
||||||
|
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
|
||||||
2043
src/LunarWM_wayland.c
Normal file
2043
src/LunarWM_wayland.c
Normal file
File diff suppressed because it is too large
Load Diff
11
src/LunarWM_wayland.h
Normal file
11
src/LunarWM_wayland.h
Normal 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
|
||||||
909
src/LunarWM_xr.c
Normal file
909
src/LunarWM_xr.c
Normal file
@@ -0,0 +1,909 @@
|
|||||||
|
#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 *required_instance_extensions[] = {
|
||||||
|
XR_EXT_DEBUG_UTILS_EXTENSION_NAME,
|
||||||
|
XR_MNDX_EGL_ENABLE_EXTENSION_NAME,
|
||||||
|
XR_KHR_OPENGL_ES_ENABLE_EXTENSION_NAME,
|
||||||
|
};
|
||||||
|
char const *optional_instance_extensions[] = {
|
||||||
|
XR_EXT_HAND_TRACKING_EXTENSION_NAME,
|
||||||
|
};
|
||||||
|
bool hand_tracking_ext_available = false;
|
||||||
|
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(required_instance_extensions); i++) {
|
||||||
|
char const *requested_instance_extension
|
||||||
|
= required_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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < ARRAY_SZ(optional_instance_extensions); i++) {
|
||||||
|
char const *requested_instance_extension
|
||||||
|
= optional_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_INFO, "Optional OpenXR instance extension missing: %s",
|
||||||
|
requested_instance_extension);
|
||||||
|
} else if (strcmp(requested_instance_extension,
|
||||||
|
XR_EXT_HAND_TRACKING_EXTENSION_NAME)
|
||||||
|
== 0) {
|
||||||
|
hand_tracking_ext_available = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
|
||||||
|
this->xr.CreateHandTrackerEXT = NULL;
|
||||||
|
this->xr.DestroyHandTrackerEXT = NULL;
|
||||||
|
this->xr.LocateHandJointsEXT = NULL;
|
||||||
|
|
||||||
|
if (hand_tracking_ext_available) {
|
||||||
|
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 "
|
||||||
|
"(optional): %d",
|
||||||
|
res);
|
||||||
|
hand_tracking_ext_available = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hand_tracking_ext_available) {
|
||||||
|
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 "
|
||||||
|
"(optional): %d",
|
||||||
|
res);
|
||||||
|
hand_tracking_ext_available = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hand_tracking_ext_available) {
|
||||||
|
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 (optional): "
|
||||||
|
"%d",
|
||||||
|
res);
|
||||||
|
hand_tracking_ext_available = 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
this->xr.hand_tracking_enabled = hand_tracking_ext_available;
|
||||||
|
XrSystemProperties system_props = {
|
||||||
|
.type = XR_TYPE_SYSTEM_PROPERTIES,
|
||||||
|
.next = this->xr.hand_tracking_enabled
|
||||||
|
? &this->xr.hand_tracking_system_properties
|
||||||
|
: NULL,
|
||||||
|
};
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
if (this->xr.hand_tracking_enabled
|
||||||
|
&& !this->xr.hand_tracking_system_properties.supportsHandTracking) {
|
||||||
|
wlr_log(WLR_INFO,
|
||||||
|
"Hand tracking extension present but system does not support "
|
||||||
|
"it");
|
||||||
|
this->xr.hand_tracking_enabled = 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));
|
||||||
|
|
||||||
|
wlr_log(WLR_DEBUG, "Creating XR stuff..");
|
||||||
|
|
||||||
|
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!
|
||||||
|
wlr_log(WLR_DEBUG, "Creating XR swapchains...");
|
||||||
|
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_COMPONENT24;
|
||||||
|
auto const swapchain_format_color = GL_RGBA8;
|
||||||
|
|
||||||
|
{
|
||||||
|
bool found = false;
|
||||||
|
for (size_t i = 0; i < a_swapchain_formats_count; i++) {
|
||||||
|
if (a_swapchain_formats[i] == swapchain_format_color) {
|
||||||
|
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_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_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);
|
||||||
|
|
||||||
|
wlr_log(WLR_DEBUG, "Fetching blend modes...");
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
wlr_log(WLR_DEBUG, "Getting reference space...");
|
||||||
|
{ // 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->xr.hand_tracking_enabled && this->xr.CreateHandTrackerEXT) {
|
||||||
|
bool hand_trackers_created = true;
|
||||||
|
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);
|
||||||
|
hand_trackers_created = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hand_trackers_created) {
|
||||||
|
if (this->xr.DestroyHandTrackerEXT) {
|
||||||
|
for (size_t i = 0; i < 2; i++) {
|
||||||
|
auto *hand = &this->xr.hands[i];
|
||||||
|
if (hand->hand_tracker != XR_NULL_HANDLE) {
|
||||||
|
this->xr.DestroyHandTrackerEXT(hand->hand_tracker);
|
||||||
|
hand->hand_tracker = XR_NULL_HANDLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this->xr.hand_tracking_enabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!this->xr.hand_tracking_enabled) {
|
||||||
|
for (size_t i = 0; i < 2; i++) {
|
||||||
|
this->xr.hands[i].hand_tracker = XR_NULL_HANDLE;
|
||||||
|
}
|
||||||
|
this->xr.hand_tracking_system_properties.supportsHandTracking
|
||||||
|
= XR_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(extension_properties);
|
||||||
|
free(a_view_config_types);
|
||||||
|
free(a_swapchain_formats);
|
||||||
|
free(a_environment_blend_modes);
|
||||||
|
vector_free(v_active_instance_extensions);
|
||||||
|
|
||||||
|
wlr_log(WLR_INFO, "OpenXR initialized.");
|
||||||
|
|
||||||
|
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
13
src/LunarWM_xr.h
Normal 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
|
||||||
118
src/RayExt.c
Normal file
118
src/RayExt.c
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
#include "RayExt.h"
|
||||||
|
|
||||||
|
#include <GLES2/gl2.h>
|
||||||
|
|
||||||
|
#include <raylib.h>
|
||||||
|
#include <raymath.h>
|
||||||
|
#include <rlgl.h>
|
||||||
|
|
||||||
|
static char const *SKYBOX_VS
|
||||||
|
= "#version 300 es\n"
|
||||||
|
"precision mediump float;\n"
|
||||||
|
"layout(location=0) in vec3 vertexPosition;\n"
|
||||||
|
"uniform mat4 mvp;\n"
|
||||||
|
"out vec3 vDir;\n"
|
||||||
|
"void main(){\n"
|
||||||
|
"\tvDir = vertexPosition;\n"
|
||||||
|
"\tvec4 pos = mvp * vec4(vertexPosition, 1.0);\n"
|
||||||
|
"\tgl_Position = vec4(pos.xy, pos.w, pos.w); // z := 1 after division\n"
|
||||||
|
"}\n";
|
||||||
|
static char const *SKYBOX_FS
|
||||||
|
= "#version 300 es\n"
|
||||||
|
"precision highp float;\n"
|
||||||
|
"in vec3 vDir;\n"
|
||||||
|
"uniform samplerCube environmentMap;\n"
|
||||||
|
"out vec4 finalColor;\n"
|
||||||
|
"void main(){ vec3 dir=normalize(vDir); "
|
||||||
|
"finalColor=vec4(texture(environmentMap, normalize(vDir)).rgb, 1.0); }\n";
|
||||||
|
|
||||||
|
void Skybox_init(Skybox *skybox, char const *fp)
|
||||||
|
{
|
||||||
|
if (skybox->ok) {
|
||||||
|
Skybox_destroy(skybox);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1) Load cubemap from a 3x4 cross image
|
||||||
|
Image img = LoadImage(fp);
|
||||||
|
if (img.width == 0 || img.height == 0) {
|
||||||
|
TraceLog(LOG_ERROR, "Skybox: failed to load image: %s", fp);
|
||||||
|
skybox->ok = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureCubemap cubemap
|
||||||
|
= LoadTextureCubemap(img, CUBEMAP_LAYOUT_AUTO_DETECT);
|
||||||
|
UnloadImage(img);
|
||||||
|
if (cubemap.id == 0) {
|
||||||
|
TraceLog(LOG_ERROR, "Skybox: failed to create cubemap from %s", fp);
|
||||||
|
skybox->ok = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Make an inward-facing cube mesh
|
||||||
|
Mesh m = GenMeshCube(
|
||||||
|
2.0f, 2.0f, 2.0f); // size doesn't matter; depth writes off
|
||||||
|
// Invert winding so we see the inside
|
||||||
|
for (int i = 0; i < m.triangleCount; ++i) {
|
||||||
|
unsigned short *idx = &m.indices[i * 3];
|
||||||
|
unsigned short tmp = idx[1];
|
||||||
|
idx[1] = idx[2];
|
||||||
|
idx[2] = tmp;
|
||||||
|
}
|
||||||
|
UploadMesh(&m, false);
|
||||||
|
Model cube = LoadModelFromMesh(m);
|
||||||
|
|
||||||
|
Shader sh = LoadShaderFromMemory(SKYBOX_VS, SKYBOX_FS);
|
||||||
|
|
||||||
|
// make raylib aware which sampler is the cubemap
|
||||||
|
sh.locs[SHADER_LOC_MAP_CUBEMAP] = GetShaderLocation(sh, "environmentMap");
|
||||||
|
|
||||||
|
cube.materials[0].shader = sh;
|
||||||
|
|
||||||
|
// put the cubemap in the expected material slot
|
||||||
|
cube.materials[0].maps[MATERIAL_MAP_CUBEMAP].texture = cubemap;
|
||||||
|
|
||||||
|
// nicer defaults
|
||||||
|
SetTextureWrap(cubemap, TEXTURE_WRAP_CLAMP);
|
||||||
|
SetTextureFilter(cubemap, TEXTURE_FILTER_BILINEAR);
|
||||||
|
|
||||||
|
skybox->cubemap = cubemap;
|
||||||
|
skybox->shader = sh;
|
||||||
|
skybox->cube = cube;
|
||||||
|
skybox->ok = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Skybox_destroy(Skybox *skybox)
|
||||||
|
{
|
||||||
|
if (!skybox->ok)
|
||||||
|
return;
|
||||||
|
|
||||||
|
UnloadModel(skybox->cube); // also unloads Mesh
|
||||||
|
UnloadTexture(skybox->cubemap);
|
||||||
|
UnloadShader(skybox->shader);
|
||||||
|
|
||||||
|
*skybox = (Skybox) { 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
void Skybox_draw(Skybox const skybox, Vector3 camPos)
|
||||||
|
{
|
||||||
|
if (!skybox.ok)
|
||||||
|
return;
|
||||||
|
|
||||||
|
rlDisableBackfaceCulling();
|
||||||
|
rlDisableDepthMask();
|
||||||
|
rlDisableColorBlend();
|
||||||
|
|
||||||
|
rlDrawRenderBatchActive();
|
||||||
|
GLint oldFunc = 0;
|
||||||
|
glGetIntegerv(GL_DEPTH_FUNC, &oldFunc);
|
||||||
|
glDepthFunc(GL_LEQUAL);
|
||||||
|
|
||||||
|
DrawModel(skybox.cube, camPos, 500.0f, (Color) { 255, 255, 255, 255 });
|
||||||
|
rlDrawRenderBatchActive();
|
||||||
|
|
||||||
|
glDepthFunc(oldFunc ? oldFunc : GL_LESS);
|
||||||
|
rlEnableColorBlend();
|
||||||
|
rlEnableDepthMask();
|
||||||
|
rlEnableBackfaceCulling();
|
||||||
|
}
|
||||||
18
src/RayExt.h
Normal file
18
src/RayExt.h
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#ifndef RAYEXT_H
|
||||||
|
#define RAYEXT_H
|
||||||
|
|
||||||
|
#include <raylib.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
bool ok;
|
||||||
|
Model cube;
|
||||||
|
Shader shader;
|
||||||
|
TextureCubemap cubemap;
|
||||||
|
} Skybox;
|
||||||
|
|
||||||
|
void Skybox_init(Skybox *skybox, char const *fp);
|
||||||
|
void Skybox_destroy(Skybox *skybox);
|
||||||
|
|
||||||
|
void Skybox_draw(Skybox const skybox, Vector3 position);
|
||||||
|
|
||||||
|
#endif // RAYEXT_H
|
||||||
6
src/common.h
Normal file
6
src/common.h
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#ifndef COMMON_H
|
||||||
|
#define COMMON_H
|
||||||
|
|
||||||
|
#define ARRAY_SZ(arr) (sizeof(arr) / sizeof(*arr))
|
||||||
|
|
||||||
|
#endif // COMMON_H
|
||||||
78
src/lua_helpers.h
Normal file
78
src/lua_helpers.h
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
#ifndef LUA_HELPERS_H
|
||||||
|
#define LUA_HELPERS_H
|
||||||
|
|
||||||
|
#include <lauxlib.h>
|
||||||
|
#include <lua.h>
|
||||||
|
#include <raylib.h>
|
||||||
|
|
||||||
|
static Vector3 lua_readVector3(lua_State *L, int index)
|
||||||
|
{
|
||||||
|
Vector3 v = { 0 };
|
||||||
|
|
||||||
|
if (!lua_istable(L, index))
|
||||||
|
return v;
|
||||||
|
|
||||||
|
lua_getfield(L, index, "x");
|
||||||
|
if (lua_isnumber(L, -1)) {
|
||||||
|
v.x = lua_tonumber(L, -1);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
lua_getfield(L, index, "y");
|
||||||
|
v.y = luaL_optnumber(L, -1, 0);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
lua_getfield(L, index, "z");
|
||||||
|
v.z = luaL_optnumber(L, -1, 0);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
lua_rawgeti(L, index, 1);
|
||||||
|
v.x = luaL_optnumber(L, -1, 0);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
lua_rawgeti(L, index, 2);
|
||||||
|
v.y = luaL_optnumber(L, -1, 0);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
lua_rawgeti(L, index, 3);
|
||||||
|
v.z = luaL_optnumber(L, -1, 0);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Vector2 lua_readVector2(lua_State *L, int index)
|
||||||
|
{
|
||||||
|
Vector2 v = { 0 };
|
||||||
|
|
||||||
|
if (!lua_istable(L, index))
|
||||||
|
return v;
|
||||||
|
|
||||||
|
lua_getfield(L, index, "x");
|
||||||
|
if (lua_isnumber(L, -1)) {
|
||||||
|
v.x = lua_tonumber(L, -1);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
lua_getfield(L, index, "y");
|
||||||
|
v.y = luaL_optnumber(L, -1, 0);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
lua_rawgeti(L, index, 1);
|
||||||
|
v.x = luaL_optnumber(L, -1, 0);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
lua_rawgeti(L, index, 2);
|
||||||
|
v.y = luaL_optnumber(L, -1, 0);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // LUA_HELPERS_H
|
||||||
16
src/main.c
Normal file
16
src/main.c
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
|
#include "LunarWM.h"
|
||||||
|
|
||||||
|
LunarWM g_wm = {};
|
||||||
|
|
||||||
|
void sigint_handler(int) { LunarWM_terminate(&g_wm); }
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
assert(LunarWM_init(&g_wm));
|
||||||
|
signal(SIGINT, sigint_handler);
|
||||||
|
LunarWM_run(&g_wm);
|
||||||
|
LunarWM_destroy(&g_wm);
|
||||||
|
}
|
||||||
148
src/vec.c
Normal file
148
src/vec.c
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
/*
|
||||||
|
BSD 3-Clause License
|
||||||
|
|
||||||
|
Copyright (c) 2024, Mashpoe
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "vec.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
vec_size_t size;
|
||||||
|
vec_size_t capacity;
|
||||||
|
unsigned char data[];
|
||||||
|
} vector_header;
|
||||||
|
|
||||||
|
vector_header* vector_get_header(vector vec) { return &((vector_header*)vec)[-1]; }
|
||||||
|
|
||||||
|
vector vector_create(void)
|
||||||
|
{
|
||||||
|
vector_header* h = (vector_header*)malloc(sizeof(vector_header));
|
||||||
|
h->capacity = 0;
|
||||||
|
h->size = 0;
|
||||||
|
|
||||||
|
return &h->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void vector_free(vector vec) { free(vector_get_header(vec)); }
|
||||||
|
|
||||||
|
vec_size_t vector_size(vector vec) { return vector_get_header(vec)->size; }
|
||||||
|
|
||||||
|
vec_size_t vector_capacity(vector vec) { return vector_get_header(vec)->capacity; }
|
||||||
|
|
||||||
|
vector_header* vector_realloc(vector_header* h, vec_type_t type_size)
|
||||||
|
{
|
||||||
|
vec_size_t new_capacity = (h->capacity == 0) ? 1 : h->capacity * 2;
|
||||||
|
vector_header* new_h = (vector_header*)realloc(h, sizeof(vector_header) + new_capacity * type_size);
|
||||||
|
new_h->capacity = new_capacity;
|
||||||
|
|
||||||
|
return new_h;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool vector_has_space(vector_header* h)
|
||||||
|
{
|
||||||
|
return h->capacity - h->size > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* _vector_add_dst(vector* vec_addr, vec_type_t type_size)
|
||||||
|
{
|
||||||
|
vector_header* h = vector_get_header(*vec_addr);
|
||||||
|
|
||||||
|
if (!vector_has_space(h))
|
||||||
|
{
|
||||||
|
h = vector_realloc(h, type_size);
|
||||||
|
*vec_addr = h->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return &h->data[type_size * h->size++];
|
||||||
|
}
|
||||||
|
|
||||||
|
void* _vector_insert_dst(vector* vec_addr, vec_type_t type_size, vec_size_t pos)
|
||||||
|
{
|
||||||
|
vector_header* h = vector_get_header(*vec_addr);
|
||||||
|
|
||||||
|
vec_size_t new_length = h->size + 1;
|
||||||
|
|
||||||
|
// make sure there is enough room for the new element
|
||||||
|
if (!vector_has_space(h))
|
||||||
|
{
|
||||||
|
h = vector_realloc(h, type_size);
|
||||||
|
*vec_addr = h->data;
|
||||||
|
}
|
||||||
|
// move trailing elements
|
||||||
|
memmove(&h->data[(pos + 1) * type_size],
|
||||||
|
&h->data[pos * type_size],
|
||||||
|
(h->size - pos) * type_size);
|
||||||
|
|
||||||
|
h->size = new_length;
|
||||||
|
|
||||||
|
return &h->data[pos * type_size];
|
||||||
|
}
|
||||||
|
|
||||||
|
void _vector_erase(vector vec, vec_type_t type_size, vec_size_t pos, vec_size_t len)
|
||||||
|
{
|
||||||
|
vector_header* h = vector_get_header(vec);
|
||||||
|
memmove(&h->data[pos * type_size],
|
||||||
|
&h->data[(pos + len) * type_size],
|
||||||
|
(h->size - pos - len) * type_size);
|
||||||
|
|
||||||
|
h->size -= len;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _vector_remove(vector vec, vec_type_t type_size, vec_size_t pos)
|
||||||
|
{
|
||||||
|
_vector_erase(vec, type_size, pos, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void vector_pop(vector vec) { --vector_get_header(vec)->size; }
|
||||||
|
|
||||||
|
void _vector_reserve(vector* vec_addr, vec_type_t type_size, vec_size_t capacity)
|
||||||
|
{
|
||||||
|
vector_header* h = vector_get_header(*vec_addr);
|
||||||
|
if (h->capacity >= capacity)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
h = (vector_header*)realloc(h, sizeof(vector_header) + capacity * type_size);
|
||||||
|
h->capacity = capacity;
|
||||||
|
*vec_addr = &h->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
vector _vector_copy(vector vec, vec_type_t type_size)
|
||||||
|
{
|
||||||
|
vector_header* h = vector_get_header(vec);
|
||||||
|
size_t alloc_size = sizeof(vector_header) + h->size * type_size;
|
||||||
|
vector_header* copy_h = (vector_header*)malloc(alloc_size);
|
||||||
|
memcpy(copy_h, h, alloc_size);
|
||||||
|
copy_h->capacity = copy_h->size;
|
||||||
|
|
||||||
|
return ©_h->data;
|
||||||
|
}
|
||||||
128
src/vec.h
Normal file
128
src/vec.h
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
/*
|
||||||
|
BSD 3-Clause License
|
||||||
|
|
||||||
|
Copyright (c) 2024, Mashpoe
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef vec_h
|
||||||
|
#define vec_h
|
||||||
|
|
||||||
|
#ifdef __cpp_decltype
|
||||||
|
# include <type_traits>
|
||||||
|
# define typeof(T) \
|
||||||
|
std::remove_reference< \
|
||||||
|
std::add_lvalue_reference<decltype(T)>::type>::type
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
// generic type for internal use
|
||||||
|
typedef void *vector;
|
||||||
|
// number of elements in a vector
|
||||||
|
typedef size_t vec_size_t;
|
||||||
|
// number of bytes for a type
|
||||||
|
typedef size_t vec_type_t;
|
||||||
|
|
||||||
|
// TODO: more rigorous check for typeof support with different compilers
|
||||||
|
#if _MSC_VER == 0 || __STDC_VERSION__ >= 202311L || defined __cpp_decltype
|
||||||
|
|
||||||
|
// shortcut defines
|
||||||
|
|
||||||
|
// vec_addr is a vector* (aka type**)
|
||||||
|
# define vector_add_dst(vec_addr) \
|
||||||
|
((typeof(*vec_addr))(_vector_add_dst( \
|
||||||
|
(vector *)vec_addr, sizeof(**vec_addr))))
|
||||||
|
# define vector_insert_dst(vec_addr, pos) \
|
||||||
|
((typeof(*vec_addr))(_vector_insert_dst( \
|
||||||
|
(vector *)vec_addr, sizeof(**vec_addr), pos)))
|
||||||
|
|
||||||
|
# define vector_add(vec_addr, value) (*vector_add_dst(vec_addr) = value)
|
||||||
|
# define vector_insert(vec_addr, pos, value) \
|
||||||
|
(*vector_insert_dst(vec_addr, pos) = value)
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
# define vector_add_dst(vec_addr, type) \
|
||||||
|
((type *)_vector_add_dst((vector *)vec_addr, sizeof(type)))
|
||||||
|
# define vector_insert_dst(vec_addr, type, pos) \
|
||||||
|
((type *)_vector_insert_dst((vector *)vec_addr, sizeof(type), pos))
|
||||||
|
|
||||||
|
# define vector_add(vec_addr, type, value) \
|
||||||
|
(*vector_add_dst(vec_addr, type) = value)
|
||||||
|
# define vector_insert(vec_addr, type, pos, value) \
|
||||||
|
(*vector_insert_dst(vec_addr, type, pos) = value)
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// vec is a vector (aka type*)
|
||||||
|
#define vector_erase(vec, pos, len) \
|
||||||
|
(_vector_erase((vector)vec, sizeof(*vec), pos, len))
|
||||||
|
#define vector_remove(vec, pos) (_vector_remove((vector)vec, sizeof(*vec), pos))
|
||||||
|
|
||||||
|
#define vector_reserve(vec_addr, capacity) \
|
||||||
|
(_vector_reserve((vector *)vec_addr, sizeof(**vec_addr), capacity))
|
||||||
|
|
||||||
|
#define vector_copy(vec) (_vector_copy((vector)vec, sizeof(*vec)))
|
||||||
|
|
||||||
|
vector vector_create(void);
|
||||||
|
|
||||||
|
void vector_free(vector vec);
|
||||||
|
|
||||||
|
void *_vector_add_dst(vector *vec_addr, vec_type_t type_size);
|
||||||
|
|
||||||
|
void *_vector_insert_dst(
|
||||||
|
vector *vec_addr, vec_type_t type_size, vec_size_t pos);
|
||||||
|
|
||||||
|
void _vector_erase(
|
||||||
|
vector vec_addr, vec_type_t type_size, vec_size_t pos, vec_size_t len);
|
||||||
|
|
||||||
|
void _vector_remove(vector vec_addr, vec_type_t type_size, vec_size_t pos);
|
||||||
|
|
||||||
|
void vector_pop(vector vec);
|
||||||
|
|
||||||
|
void _vector_reserve(
|
||||||
|
vector *vec_addr, vec_type_t type_size, vec_size_t capacity);
|
||||||
|
|
||||||
|
vector _vector_copy(vector vec, vec_type_t type_size);
|
||||||
|
|
||||||
|
vec_size_t vector_size(vector vec);
|
||||||
|
|
||||||
|
vec_size_t vector_capacity(vector vec);
|
||||||
|
|
||||||
|
// closing bracket for extern "C"
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* vec_h */
|
||||||
8
tools/format.sh
Executable file
8
tools/format.sh
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT="$(git rev-parse --show-toplevel)"
|
||||||
|
|
||||||
|
find "$ROOT/src" -type f -name '*.c' -o -name '*.h' -print0 | while IFS= read -r -d '' f; do
|
||||||
|
clang-format -i --style=file "$f"
|
||||||
|
done
|
||||||
1
wlroots-lunar
Submodule
1
wlroots-lunar
Submodule
Submodule wlroots-lunar added at a9abd5a6e4
Reference in New Issue
Block a user