Files
lunarwm/src/LunarWM.cppm
Slendi 9fa2a1e8c7 Raylib
Signed-off-by: Slendi <slendi@socopon.com>
2025-07-09 07:20:12 +03:00

1722 lines
69 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

module;
#include <cassert>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <GLES3/gl3.h>
#include <openxr/openxr.h>
#include <openxr/openxr_platform.h>
#include <wayland-client-protocol.h>
#include <wayland-egl.h>
#include <wayland-server-core.h>
extern "C" {
#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_data_device.h>
#include <wlr/types/wlr_subcompositor.h>
#include <wlr/util/log.h>
}
PFNGLDRAWBUFFERSEXTPROC glDrawBuffersEXT = NULL;
#include <raylib.h>
#include <raymath.h>
#include <rlgl.h>
export module LunarWM.LunarWM;
import std;
import LunarWM.Math;
using Clock = std::chrono::high_resolution_clock;
namespace std {
template <> struct formatter<XrResult, char> {
template <class ParseContext> constexpr auto parse(ParseContext &ctx) {
return ctx.begin();
}
static constexpr std::string_view to_string(XrResult r) {
switch (r) {
case XR_FRAME_DISCARDED:
return "XR_FRAME_DISCARDED";
case XR_ERROR_VALIDATION_FAILURE:
return "XR_ERROR_VALIDATION_FAILURE: The function usage was invalid in "
"some way.";
case XR_ERROR_RUNTIME_FAILURE:
return "XR_ERROR_RUNTIME_FAILURE: The runtime failed to handle the "
"function in an unexpected way that is not covered by another "
"error result.";
case XR_ERROR_OUT_OF_MEMORY:
return "XR_ERROR_OUT_OF_MEMORY: A memory allocation has failed.";
case XR_ERROR_API_VERSION_UNSUPPORTED:
return "XR_ERROR_API_VERSION_UNSUPPORTED: The runtime does not support "
"the requested API version.";
case XR_ERROR_INITIALIZATION_FAILED:
return "XR_ERROR_INITIALIZATION_FAILED: Initialization of object could "
"not be completed.";
case XR_ERROR_FUNCTION_UNSUPPORTED:
return "XR_ERROR_FUNCTION_UNSUPPORTED: The requested function was not "
"found or is otherwise unsupported.";
case XR_ERROR_FEATURE_UNSUPPORTED:
return "XR_ERROR_FEATURE_UNSUPPORTED: The requested feature is not "
"supported.";
case XR_ERROR_EXTENSION_NOT_PRESENT:
return "XR_ERROR_EXTENSION_NOT_PRESENT: A requested extension is not "
"supported.";
case XR_ERROR_LIMIT_REACHED:
return "XR_ERROR_LIMIT_REACHED: The runtime supports no more of the "
"requested resource.";
case XR_ERROR_SIZE_INSUFFICIENT:
return "XR_ERROR_SIZE_INSUFFICIENT: The supplied size was smaller than "
"required.";
case XR_ERROR_HANDLE_INVALID:
return "XR_ERROR_HANDLE_INVALID: A supplied object handle was invalid.";
case XR_ERROR_INSTANCE_LOST:
return "XR_ERROR_INSTANCE_LOST: The XrInstance was lost or could not be "
"found. It will need to be destroyed and optionally recreated.";
case XR_ERROR_SESSION_RUNNING:
return "XR_ERROR_SESSION_RUNNING: The session is already running.";
case XR_ERROR_SESSION_NOT_RUNNING:
return "XR_ERROR_SESSION_NOT_RUNNING: The session is not yet running.";
case XR_ERROR_SESSION_LOST:
return "XR_ERROR_SESSION_LOST: The XrSession was lost. It will need to "
"be destroyed and optionally recreated.";
case XR_ERROR_SYSTEM_INVALID:
return "XR_ERROR_SYSTEM_INVALID: The provided XrSystemId was invalid.";
case XR_ERROR_PATH_INVALID:
return "XR_ERROR_PATH_INVALID: The provided XrPath was not valid.";
case XR_ERROR_PATH_COUNT_EXCEEDED:
return "XR_ERROR_PATH_COUNT_EXCEEDED: The maximum number of supported "
"semantic paths has been reached.";
case XR_ERROR_PATH_FORMAT_INVALID:
return "XR_ERROR_PATH_FORMAT_INVALID: The semantic path character format "
"is invalid.";
case XR_ERROR_PATH_UNSUPPORTED:
return "XR_ERROR_PATH_UNSUPPORTED: The semantic path is unsupported.";
case XR_ERROR_LAYER_INVALID:
return "XR_ERROR_LAYER_INVALID: The layer was NULL or otherwise invalid.";
case XR_ERROR_LAYER_LIMIT_EXCEEDED:
return "XR_ERROR_LAYER_LIMIT_EXCEEDED: The number of specified layers is "
"greater than the supported number.";
case XR_ERROR_SWAPCHAIN_RECT_INVALID:
return "XR_ERROR_SWAPCHAIN_RECT_INVALID: The image rect was negatively "
"sized or otherwise invalid.";
case XR_ERROR_SWAPCHAIN_FORMAT_UNSUPPORTED:
return "XR_ERROR_SWAPCHAIN_FORMAT_UNSUPPORTED: The image format is not "
"supported by the runtime or platform.";
case XR_ERROR_ACTION_TYPE_MISMATCH:
return "XR_ERROR_ACTION_TYPE_MISMATCH: The API used to retrieve an "
"actions state does not match the actions type.";
case XR_ERROR_SESSION_NOT_READY:
return "XR_ERROR_SESSION_NOT_READY: The session is not in the ready "
"state.";
case XR_ERROR_SESSION_NOT_STOPPING:
return "XR_ERROR_SESSION_NOT_STOPPING: The session is not in the "
"stopping state.";
case XR_ERROR_TIME_INVALID:
return "XR_ERROR_TIME_INVALID: The provided XrTime was zero, negative, "
"or out of range.";
case XR_ERROR_REFERENCE_SPACE_UNSUPPORTED:
return "XR_ERROR_REFERENCE_SPACE_UNSUPPORTED: The specified reference "
"space is not supported by the runtime or system.";
case XR_ERROR_FILE_ACCESS_ERROR:
return "XR_ERROR_FILE_ACCESS_ERROR: The file could not be accessed.";
case XR_ERROR_FILE_CONTENTS_INVALID:
return "XR_ERROR_FILE_CONTENTS_INVALID: The files contents were "
"invalid.";
case XR_ERROR_FORM_FACTOR_UNSUPPORTED:
return "XR_ERROR_FORM_FACTOR_UNSUPPORTED: The specified form factor is "
"not supported by the current runtime or platform.";
case XR_ERROR_FORM_FACTOR_UNAVAILABLE:
return "XR_ERROR_FORM_FACTOR_UNAVAILABLE: The specified form factor is "
"supported, but the device is currently not available, e.g. not "
"plugged in or powered off.";
case XR_ERROR_API_LAYER_NOT_PRESENT:
return "XR_ERROR_API_LAYER_NOT_PRESENT: A requested API layer is not "
"present or could not be loaded.";
case XR_ERROR_CALL_ORDER_INVALID:
return "XR_ERROR_CALL_ORDER_INVALID: The call was made without having "
"made a previously required call.";
case XR_ERROR_GRAPHICS_DEVICE_INVALID:
return "XR_ERROR_GRAPHICS_DEVICE_INVALID: The given graphics device is "
"not in a valid state. The graphics device could be lost or "
"initialized without meeting graphics requirements.";
case XR_ERROR_POSE_INVALID:
return "XR_ERROR_POSE_INVALID: The supplied pose was invalid with "
"respect to the requirements.";
case XR_ERROR_INDEX_OUT_OF_RANGE:
return "XR_ERROR_INDEX_OUT_OF_RANGE: The supplied index was outside the "
"range of valid indices.";
case XR_ERROR_VIEW_CONFIGURATION_TYPE_UNSUPPORTED:
return "XR_ERROR_VIEW_CONFIGURATION_TYPE_UNSUPPORTED: The specified view "
"configuration type is not supported by the runtime or platform.";
case XR_ERROR_ENVIRONMENT_BLEND_MODE_UNSUPPORTED:
return "XR_ERROR_ENVIRONMENT_BLEND_MODE_UNSUPPORTED: The specified "
"environment blend mode is not supported by the runtime or "
"platform.";
case XR_ERROR_NAME_DUPLICATED:
return "XR_ERROR_NAME_DUPLICATED: The name provided was a duplicate of "
"an already-existing resource.";
case XR_ERROR_NAME_INVALID:
return "XR_ERROR_NAME_INVALID: The name provided was invalid.";
case XR_ERROR_ACTIONSET_NOT_ATTACHED:
return "XR_ERROR_ACTIONSET_NOT_ATTACHED: A referenced action set is not "
"attached to the session.";
case XR_ERROR_ACTIONSETS_ALREADY_ATTACHED:
return "XR_ERROR_ACTIONSETS_ALREADY_ATTACHED: The session already has "
"attached action sets.";
case XR_ERROR_LOCALIZED_NAME_DUPLICATED:
return "XR_ERROR_LOCALIZED_NAME_DUPLICATED: The localized name provided "
"was a duplicate of an already-existing resource.";
case XR_ERROR_LOCALIZED_NAME_INVALID:
return "XR_ERROR_LOCALIZED_NAME_INVALID: The localized name provided was "
"invalid.";
case XR_ERROR_GRAPHICS_REQUIREMENTS_CALL_MISSING:
return "XR_ERROR_GRAPHICS_REQUIREMENTS_CALL_MISSING: The "
"xrGetGraphicsRequirements* call was not made before calling "
"xrCreateSession.";
case XR_ERROR_RUNTIME_UNAVAILABLE:
return "XR_ERROR_RUNTIME_UNAVAILABLE: The loader was unable to find or "
"load a runtime.";
case XR_ERROR_EXTENSION_DEPENDENCY_NOT_ENABLED:
return "XR_ERROR_EXTENSION_DEPENDENCY_NOT_ENABLED: One or more of the "
"extensions being enabled has dependency on extensions that are "
"not enabled.";
case XR_ERROR_PERMISSION_INSUFFICIENT:
return "XR_ERROR_PERMISSION_INSUFFICIENT: Insufficient permissions. This "
"error is included for use by vendor extensions. The precise "
"definition of XR_ERROR_PERMISSION_INSUFFICIENT and actions "
"possible by the developer or user to resolve it can vary by "
"platform, extension or function. The developer should refer to "
"the documentation of the function that returned the error code "
"and extension it was defined.";
case XR_ERROR_ANDROID_THREAD_SETTINGS_ID_INVALID_KHR:
return "XR_ERROR_ANDROID_THREAD_SETTINGS_ID_INVALID_KHR: "
"xrSetAndroidApplicationThreadKHR failed as thread id is invalid. "
"(Added by the XR_KHR_android_thread_settings extension)";
case XR_ERROR_ANDROID_THREAD_SETTINGS_FAILURE_KHR:
return "XR_ERROR_ANDROID_THREAD_SETTINGS_FAILURE_KHR: "
"xrSetAndroidApplicationThreadKHR failed setting the thread "
"attributes/priority. (Added by the "
"XR_KHR_android_thread_settings extension)";
case XR_ERROR_CREATE_SPATIAL_ANCHOR_FAILED_MSFT:
return "XR_ERROR_CREATE_SPATIAL_ANCHOR_FAILED_MSFT: Spatial anchor could "
"not be created at that location. (Added by the "
"XR_MSFT_spatial_anchor extension)";
case XR_ERROR_SECONDARY_VIEW_CONFIGURATION_TYPE_NOT_ENABLED_MSFT:
return "XR_ERROR_SECONDARY_VIEW_CONFIGURATION_TYPE_NOT_ENABLED_MSFT: The "
"secondary view configuration was not enabled when creating the "
"session. (Added by the XR_MSFT_secondary_view_configuration "
"extension)";
case XR_ERROR_CONTROLLER_MODEL_KEY_INVALID_MSFT:
return "XR_ERROR_CONTROLLER_MODEL_KEY_INVALID_MSFT: The controller model "
"key is invalid. (Added by the XR_MSFT_controller_model "
"extension)";
case XR_ERROR_REPROJECTION_MODE_UNSUPPORTED_MSFT:
return "XR_ERROR_REPROJECTION_MODE_UNSUPPORTED_MSFT: The reprojection "
"mode is not supported. (Added by the "
"XR_MSFT_composition_layer_reprojection extension)";
case XR_ERROR_COMPUTE_NEW_SCENE_NOT_COMPLETED_MSFT:
return "XR_ERROR_COMPUTE_NEW_SCENE_NOT_COMPLETED_MSFT: Compute new scene "
"not completed. (Added by the XR_MSFT_scene_understanding "
"extension)";
case XR_ERROR_SCENE_COMPONENT_ID_INVALID_MSFT:
return "XR_ERROR_SCENE_COMPONENT_ID_INVALID_MSFT: Scene component id "
"invalid. (Added by the XR_MSFT_scene_understanding extension)";
case XR_ERROR_SCENE_COMPONENT_TYPE_MISMATCH_MSFT:
return "XR_ERROR_SCENE_COMPONENT_TYPE_MISMATCH_MSFT: Scene component "
"type mismatch. (Added by the XR_MSFT_scene_understanding "
"extension)";
case XR_ERROR_SCENE_MESH_BUFFER_ID_INVALID_MSFT:
return "XR_ERROR_SCENE_MESH_BUFFER_ID_INVALID_MSFT: Scene mesh buffer id "
"invalid. (Added by the XR_MSFT_scene_understanding extension)";
case XR_ERROR_SCENE_COMPUTE_FEATURE_INCOMPATIBLE_MSFT:
return "XR_ERROR_SCENE_COMPUTE_FEATURE_INCOMPATIBLE_MSFT: Scene compute "
"feature incompatible. (Added by the XR_MSFT_scene_understanding "
"extension)";
case XR_ERROR_SCENE_COMPUTE_CONSISTENCY_MISMATCH_MSFT:
return "XR_ERROR_SCENE_COMPUTE_CONSISTENCY_MISMATCH_MSFT: Scene compute "
"consistency mismatch. (Added by the XR_MSFT_scene_understanding "
"extension)";
case XR_ERROR_DISPLAY_REFRESH_RATE_UNSUPPORTED_FB:
return "XR_ERROR_DISPLAY_REFRESH_RATE_UNSUPPORTED_FB: The display "
"refresh rate is not supported by the platform. (Added by the "
"XR_FB_display_refresh_rate extension)";
case XR_ERROR_COLOR_SPACE_UNSUPPORTED_FB:
return "XR_ERROR_COLOR_SPACE_UNSUPPORTED_FB: The color space is not "
"supported by the runtime. (Added by the XR_FB_color_space "
"extension)";
case XR_ERROR_SPACE_COMPONENT_NOT_SUPPORTED_FB:
return "XR_ERROR_SPACE_COMPONENT_NOT_SUPPORTED_FB: The component type is "
"not supported for this space. (Added by the XR_FB_spatial_entity "
"extension)";
case XR_ERROR_SPACE_COMPONENT_NOT_ENABLED_FB:
return "XR_ERROR_SPACE_COMPONENT_NOT_ENABLED_FB: The required component "
"is not enabled for this space. (Added by the "
"XR_FB_spatial_entity extension)";
case XR_ERROR_SPACE_COMPONENT_STATUS_PENDING_FB:
return "XR_ERROR_SPACE_COMPONENT_STATUS_PENDING_FB: A request to set the "
"components status is currently pending. (Added by the "
"XR_FB_spatial_entity extension)";
case XR_ERROR_SPACE_COMPONENT_STATUS_ALREADY_SET_FB:
return "XR_ERROR_SPACE_COMPONENT_STATUS_ALREADY_SET_FB: The component is "
"already set to the requested value. (Added by the "
"XR_FB_spatial_entity extension)";
case XR_ERROR_UNEXPECTED_STATE_PASSTHROUGH_FB:
return "XR_ERROR_UNEXPECTED_STATE_PASSTHROUGH_FB: The object state is "
"unexpected for the issued command. (Added by the "
"XR_FB_passthrough extension)";
case XR_ERROR_FEATURE_ALREADY_CREATED_PASSTHROUGH_FB:
return "XR_ERROR_FEATURE_ALREADY_CREATED_PASSTHROUGH_FB: Trying to "
"create an MR feature when one was already created and only one "
"instance is allowed. (Added by the XR_FB_passthrough extension)";
case XR_ERROR_FEATURE_REQUIRED_PASSTHROUGH_FB:
return "XR_ERROR_FEATURE_REQUIRED_PASSTHROUGH_FB: Requested "
"functionality requires a feature to be created first. (Added by "
"the XR_FB_passthrough extension)";
case XR_ERROR_NOT_PERMITTED_PASSTHROUGH_FB:
return "XR_ERROR_NOT_PERMITTED_PASSTHROUGH_FB: Requested functionality "
"is not permitted - application is not allowed to perform the "
"requested operation. (Added by the XR_FB_passthrough extension)";
case XR_ERROR_INSUFFICIENT_RESOURCES_PASSTHROUGH_FB:
return "XR_ERROR_INSUFFICIENT_RESOURCES_PASSTHROUGH_FB: There were "
"insufficient resources available to perform an operation. (Added "
"by the XR_FB_passthrough extension)";
case XR_ERROR_UNKNOWN_PASSTHROUGH_FB:
return "XR_ERROR_UNKNOWN_PASSTHROUGH_FB: Unknown Passthrough error (no "
"further details provided). (Added by the XR_FB_passthrough "
"extension)";
case XR_ERROR_RENDER_MODEL_KEY_INVALID_FB:
return "XR_ERROR_RENDER_MODEL_KEY_INVALID_FB: The model key is invalid. "
"(Added by the XR_FB_render_model extension)";
case XR_ERROR_MARKER_NOT_TRACKED_VARJO:
return "XR_ERROR_MARKER_NOT_TRACKED_VARJO: Marker tracking is disabled "
"or the specified marker is not currently tracked. (Added by the "
"XR_VARJO_marker_tracking extension)";
case XR_ERROR_MARKER_ID_INVALID_VARJO:
return "XR_ERROR_MARKER_ID_INVALID_VARJO: The specified marker ID is not "
"valid. (Added by the XR_VARJO_marker_tracking extension)";
case XR_ERROR_MARKER_DETECTOR_PERMISSION_DENIED_ML:
return "XR_ERROR_MARKER_DETECTOR_PERMISSION_DENIED_ML: The "
"com.magicleap.permission.MARKER_TRACKING permission was denied. "
"(Added by the XR_ML_marker_understanding extension)";
case XR_ERROR_MARKER_DETECTOR_LOCATE_FAILED_ML:
return "XR_ERROR_MARKER_DETECTOR_LOCATE_FAILED_ML: The specified marker "
"could not be located spatially. (Added by the "
"XR_ML_marker_understanding extension)";
case XR_ERROR_MARKER_DETECTOR_INVALID_DATA_QUERY_ML:
return "XR_ERROR_MARKER_DETECTOR_INVALID_DATA_QUERY_ML: The marker "
"queried does not contain data of the requested type. (Added by "
"the XR_ML_marker_understanding extension)";
case XR_ERROR_MARKER_DETECTOR_INVALID_CREATE_INFO_ML:
return "XR_ERROR_MARKER_DETECTOR_INVALID_CREATE_INFO_ML: createInfo "
"contains mutually exclusive parameters, such as setting "
"XR_MARKER_DETECTOR_CORNER_REFINE_METHOD_APRIL_TAG_ML with "
"XR_MARKER_TYPE_ARUCO_ML. (Added by the "
"XR_ML_marker_understanding extension)";
case XR_ERROR_MARKER_INVALID_ML:
return "XR_ERROR_MARKER_INVALID_ML: The marker id passed to the function "
"was invalid. (Added by the XR_ML_marker_understanding extension)";
case XR_ERROR_LOCALIZATION_MAP_INCOMPATIBLE_ML:
return "XR_ERROR_LOCALIZATION_MAP_INCOMPATIBLE_ML: The localization map "
"being imported is not compatible with current OS or mode. (Added "
"by the XR_ML_localization_map extension)";
case XR_ERROR_LOCALIZATION_MAP_UNAVAILABLE_ML:
return "XR_ERROR_LOCALIZATION_MAP_UNAVAILABLE_ML: The localization map "
"requested is not available. (Added by the XR_ML_localization_map "
"extension)";
case XR_ERROR_LOCALIZATION_MAP_FAIL_ML:
return "XR_ERROR_LOCALIZATION_MAP_FAIL_ML: The map localization service "
"failed to fulfill the request, retry later. (Added by the "
"XR_ML_localization_map extension)";
case XR_ERROR_LOCALIZATION_MAP_IMPORT_EXPORT_PERMISSION_DENIED_ML:
return "XR_ERROR_LOCALIZATION_MAP_IMPORT_EXPORT_PERMISSION_DENIED_ML: "
"The com.magicleap.permission.SPACE_IMPORT_EXPORT permission was "
"denied. (Added by the XR_ML_localization_map extension)";
case XR_ERROR_LOCALIZATION_MAP_PERMISSION_DENIED_ML:
return "XR_ERROR_LOCALIZATION_MAP_PERMISSION_DENIED_ML: The "
"com.magicleap.permission.SPACE_MANAGER permission was denied. "
"(Added by the XR_ML_localization_map extension)";
case XR_ERROR_LOCALIZATION_MAP_ALREADY_EXISTS_ML:
return "XR_ERROR_LOCALIZATION_MAP_ALREADY_EXISTS_ML: The map being "
"imported already exists in the system. (Added by the "
"XR_ML_localization_map extension)";
case XR_ERROR_LOCALIZATION_MAP_CANNOT_EXPORT_CLOUD_MAP_ML:
return "XR_ERROR_LOCALIZATION_MAP_CANNOT_EXPORT_CLOUD_MAP_ML: The map "
"localization service cannot export cloud based maps. (Added by "
"the XR_ML_localization_map extension)";
case XR_ERROR_SPATIAL_ANCHORS_PERMISSION_DENIED_ML:
return "XR_ERROR_SPATIAL_ANCHORS_PERMISSION_DENIED_ML: The "
"com.magicleap.permission.SPATIAL_ANCHOR permission was not "
"granted. (Added by the XR_ML_spatial_anchors extension)";
case XR_ERROR_SPATIAL_ANCHORS_NOT_LOCALIZED_ML:
return "XR_ERROR_SPATIAL_ANCHORS_NOT_LOCALIZED_ML: Operation failed "
"because the system is not localized into a localization map. "
"(Added by the XR_ML_spatial_anchors extension)";
case XR_ERROR_SPATIAL_ANCHORS_OUT_OF_MAP_BOUNDS_ML:
return "XR_ERROR_SPATIAL_ANCHORS_OUT_OF_MAP_BOUNDS_ML: Operation failed "
"because it is performed outside of the localization map. (Added "
"by the XR_ML_spatial_anchors extension)";
case XR_ERROR_SPATIAL_ANCHORS_SPACE_NOT_LOCATABLE_ML:
return "XR_ERROR_SPATIAL_ANCHORS_SPACE_NOT_LOCATABLE_ML: Operation "
"failed because the space referenced cannot be located. (Added by "
"the XR_ML_spatial_anchors extension)";
case XR_ERROR_SPATIAL_ANCHORS_ANCHOR_NOT_FOUND_ML:
return "XR_ERROR_SPATIAL_ANCHORS_ANCHOR_NOT_FOUND_ML: The anchor "
"references was not found. (Added by the "
"XR_ML_spatial_anchors_storage extension)";
case XR_ERROR_SPATIAL_ANCHOR_NAME_NOT_FOUND_MSFT:
return "XR_ERROR_SPATIAL_ANCHOR_NAME_NOT_FOUND_MSFT: A spatial anchor "
"was not found associated with the spatial anchor name provided "
"(Added by the XR_MSFT_spatial_anchor_persistence extension)";
case XR_ERROR_SPATIAL_ANCHOR_NAME_INVALID_MSFT:
return "XR_ERROR_SPATIAL_ANCHOR_NAME_INVALID_MSFT: The spatial anchor "
"name provided was not valid (Added by the "
"XR_MSFT_spatial_anchor_persistence extension)";
case XR_ERROR_SPACE_MAPPING_INSUFFICIENT_FB:
return "XR_ERROR_SPACE_MAPPING_INSUFFICIENT_FB: Anchor import from cloud "
"or export from device failed. (Added by the "
"XR_FB_spatial_entity_sharing extension)";
case XR_ERROR_SPACE_LOCALIZATION_FAILED_FB:
return "XR_ERROR_SPACE_LOCALIZATION_FAILED_FB: Anchors were downloaded "
"from the cloud but failed to be imported/aligned on the device. "
"(Added by the XR_FB_spatial_entity_sharing extension)";
case XR_ERROR_SPACE_NETWORK_TIMEOUT_FB:
return "XR_ERROR_SPACE_NETWORK_TIMEOUT_FB: Timeout occurred while "
"waiting for network request to complete. (Added by the "
"XR_FB_spatial_entity_sharing extension)";
case XR_ERROR_SPACE_NETWORK_REQUEST_FAILED_FB:
return "XR_ERROR_SPACE_NETWORK_REQUEST_FAILED_FB: The network request "
"failed. (Added by the XR_FB_spatial_entity_sharing extension)";
case XR_ERROR_SPACE_CLOUD_STORAGE_DISABLED_FB:
return "XR_ERROR_SPACE_CLOUD_STORAGE_DISABLED_FB: Cloud storage is "
"required for this operation but is currently disabled. (Added by "
"the XR_FB_spatial_entity_sharing extension)";
case XR_ERROR_PASSTHROUGH_COLOR_LUT_BUFFER_SIZE_MISMATCH_META:
return "XR_ERROR_PASSTHROUGH_COLOR_LUT_BUFFER_SIZE_MISMATCH_META: The "
"provided data buffer did not match the required size. (Added by "
"the XR_META_passthrough_color_lut extension)";
case XR_ERROR_RENDER_MODEL_ID_INVALID_EXT:
return "XR_ERROR_RENDER_MODEL_ID_INVALID_EXT: The render model ID is "
"invalid. (Added by the XR_EXT_render_model extension)";
case XR_ERROR_RENDER_MODEL_ASSET_UNAVAILABLE_EXT:
return "XR_ERROR_RENDER_MODEL_ASSET_UNAVAILABLE_EXT: The render model "
"asset is unavailable. (Added by the XR_EXT_render_model "
"extension)";
case XR_ERROR_RENDER_MODEL_GLTF_EXTENSION_REQUIRED_EXT:
return "XR_ERROR_RENDER_MODEL_GLTF_EXTENSION_REQUIRED_EXT: A glTF "
"extension is required. (Added by the XR_EXT_render_model "
"extension)";
case XR_ERROR_NOT_INTERACTION_RENDER_MODEL_EXT:
return "XR_ERROR_NOT_INTERACTION_RENDER_MODEL_EXT: The provided "
"XrRenderModelEXT was not created from a XrRenderModelIdEXT from "
"[XR_EXT_interaction_render_model] (Added by the "
"XR_EXT_interaction_render_model extension)";
case XR_ERROR_HINT_ALREADY_SET_QCOM:
return "XR_ERROR_HINT_ALREADY_SET_QCOM: Tracking optimization hint is "
"already set for the domain. (Added by the "
"XR_QCOM_tracking_optimization_settings extension)";
case XR_ERROR_NOT_AN_ANCHOR_HTC:
return "XR_ERROR_NOT_AN_ANCHOR_HTC: The provided space is valid but not "
"an anchor. (Added by the XR_HTC_anchor extension)";
case XR_ERROR_SPATIAL_ENTITY_ID_INVALID_BD:
return "XR_ERROR_SPATIAL_ENTITY_ID_INVALID_BD: The spatial entity id is "
"invalid. (Added by the XR_BD_spatial_sensing extension)";
case XR_ERROR_SPATIAL_SENSING_SERVICE_UNAVAILABLE_BD:
return "XR_ERROR_SPATIAL_SENSING_SERVICE_UNAVAILABLE_BD: The spatial "
"sensing service is unavailable. (Added by the "
"XR_BD_spatial_sensing extension)";
case XR_ERROR_ANCHOR_NOT_SUPPORTED_FOR_ENTITY_BD:
return "XR_ERROR_ANCHOR_NOT_SUPPORTED_FOR_ENTITY_BD: The spatial entity "
"does not support anchor. (Added by the XR_BD_spatial_sensing "
"extension)";
case XR_ERROR_SCENE_CAPTURE_FAILURE_BD:
return "XR_ERROR_SCENE_CAPTURE_FAILURE_BD: The scene capture is failed, "
"for example exiting abnormally. (Added by the "
"XR_BD_spatial_scene extension)";
case XR_ERROR_SPACE_NOT_LOCATABLE_EXT:
return "XR_ERROR_SPACE_NOT_LOCATABLE_EXT: The space passed to the "
"function was not locatable. (Added by the XR_EXT_plane_detection "
"extension)";
case XR_ERROR_PLANE_DETECTION_PERMISSION_DENIED_EXT:
return "XR_ERROR_PLANE_DETECTION_PERMISSION_DENIED_EXT: The permission "
"for this resource was not granted. (Added by the "
"XR_EXT_plane_detection extension)";
case XR_ERROR_FUTURE_PENDING_EXT:
return "XR_ERROR_FUTURE_PENDING_EXT: Returned by completion function to "
"indicate future is not ready. (Added by the XR_EXT_future "
"extension)";
case XR_ERROR_FUTURE_INVALID_EXT:
return "XR_ERROR_FUTURE_INVALID_EXT: Returned by completion function to "
"indicate future is not valid. (Added by the XR_EXT_future "
"extension)";
case XR_ERROR_SYSTEM_NOTIFICATION_PERMISSION_DENIED_ML:
return "XR_ERROR_SYSTEM_NOTIFICATION_PERMISSION_DENIED_ML: The "
"com.magicleap.permission.SYSTEM_NOTIFICATION permission was not "
"granted. (Added by the XR_ML_system_notifications extension)";
case XR_ERROR_SYSTEM_NOTIFICATION_INCOMPATIBLE_SKU_ML:
return "XR_ERROR_SYSTEM_NOTIFICATION_INCOMPATIBLE_SKU_ML: Incompatible "
"SKU detected. (Added by the XR_ML_system_notifications "
"extension)";
case XR_ERROR_WORLD_MESH_DETECTOR_PERMISSION_DENIED_ML:
return "XR_ERROR_WORLD_MESH_DETECTOR_PERMISSION_DENIED_ML: The world "
"mesh detector permission was not granted. (Added by the "
"XR_ML_world_mesh_detection extension)";
case XR_ERROR_WORLD_MESH_DETECTOR_SPACE_NOT_LOCATABLE_ML:
return "XR_ERROR_WORLD_MESH_DETECTOR_SPACE_NOT_LOCATABLE_ML: At the time "
"of the call the runtime was unable to locate the space and "
"cannot fulfill your request. (Added by the "
"XR_ML_world_mesh_detection extension)";
case XR_ERROR_COLOCATION_DISCOVERY_NETWORK_FAILED_META:
return "XR_ERROR_COLOCATION_DISCOVERY_NETWORK_FAILED_META: The network "
"request failed. (Added by the XR_META_colocation_discovery "
"extension)";
case XR_ERROR_COLOCATION_DISCOVERY_NO_DISCOVERY_METHOD_META:
return "XR_ERROR_COLOCATION_DISCOVERY_NO_DISCOVERY_METHOD_META: The "
"runtime does not have any methods available to perform "
"discovery. (Added by the XR_META_colocation_discovery extension)";
case XR_ERROR_SPACE_GROUP_NOT_FOUND_META:
return "XR_ERROR_SPACE_GROUP_NOT_FOUND_META: The group UUID was not "
"found within the runtime (Added by the "
"XR_META_spatial_entity_group_sharing extension)";
case XR_ERROR_SPATIAL_CAPABILITY_UNSUPPORTED_EXT:
return "XR_ERROR_SPATIAL_CAPABILITY_UNSUPPORTED_EXT: The specified "
"spatial capability is not supported by the runtime or the "
"system. (Added by the XR_EXT_spatial_entity extension)";
case XR_ERROR_SPATIAL_ENTITY_ID_INVALID_EXT:
return "XR_ERROR_SPATIAL_ENTITY_ID_INVALID_EXT: The specified spatial "
"entity id is invalid or an entity with that id does not exist in "
"the environment. (Added by the XR_EXT_spatial_entity extension)";
case XR_ERROR_SPATIAL_BUFFER_ID_INVALID_EXT:
return "XR_ERROR_SPATIAL_BUFFER_ID_INVALID_EXT: The specified spatial "
"buffer id is invalid or does not exist in the spatial snapshot "
"being used to query for the buffer data. (Added by the "
"XR_EXT_spatial_entity extension)";
case XR_ERROR_SPATIAL_COMPONENT_UNSUPPORTED_FOR_CAPABILITY_EXT:
return "XR_ERROR_SPATIAL_COMPONENT_UNSUPPORTED_FOR_CAPABILITY_EXT: The "
"specified spatial component is not supported by the runtime or "
"the system for the given capability. (Added by the "
"XR_EXT_spatial_entity extension)";
case XR_ERROR_SPATIAL_CAPABILITY_CONFIGURATION_INVALID_EXT:
return "XR_ERROR_SPATIAL_CAPABILITY_CONFIGURATION_INVALID_EXT: The "
"specified spatial capability configuration is invalid. (Added by "
"the XR_EXT_spatial_entity extension)";
case XR_ERROR_SPATIAL_COMPONENT_NOT_ENABLED_EXT:
return "XR_ERROR_SPATIAL_COMPONENT_NOT_ENABLED_EXT: The specified "
"spatial component is not enabled for the spatial context. (Added "
"by the XR_EXT_spatial_entity extension)";
case XR_ERROR_SPATIAL_PERSISTENCE_SCOPE_UNSUPPORTED_EXT:
return "XR_ERROR_SPATIAL_PERSISTENCE_SCOPE_UNSUPPORTED_EXT: The "
"specified spatial persistence scope is not supported by the "
"runtime or the system. (Added by the XR_EXT_spatial_persistence "
"extension)";
case XR_ERROR_SPATIAL_PERSISTENCE_SCOPE_INCOMPATIBLE_EXT:
return "XR_ERROR_SPATIAL_PERSISTENCE_SCOPE_INCOMPATIBLE_EXT: THe scope "
"configured for the persistence context is incompatible for the "
"current spatial entity. (Added by the "
"XR_EXT_spatial_persistence_operations extension)";
default:
return "";
}
}
template <class FmtCtx> auto format(XrResult r, FmtCtx &ctx) const {
if (spec == 'd')
return format_to(ctx.out(), "{}", static_cast<int32_t>(r));
std::string_view str = this->to_string(r);
if (!str.empty())
return format_to(ctx.out(), "{}", str);
return format_to(ctx.out(), "<0x{:x}>", static_cast<uint32_t>(r));
}
private:
char spec{'s'};
};
} // namespace std
namespace LunarWM {
static Matrix xr_projection_matrix(const XrFovf& fov)
{
static_assert(RL_CULL_DISTANCE_FAR > RL_CULL_DISTANCE_NEAR);
Matrix matrix{};
const float near = (float)RL_CULL_DISTANCE_NEAR;
const float far = (float)RL_CULL_DISTANCE_FAR;
const float tan_angle_left = tanf(fov.angleLeft);
const float tan_angle_right = tanf(fov.angleRight);
const float tan_angle_down = tanf(fov.angleDown);
const float tan_angle_up = tanf(fov.angleUp);
const float tan_angle_width = tan_angle_right - tan_angle_left;
const float 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 Matrix xr_matrix(const XrPosef& pose)
{
Matrix translation = MatrixTranslate(pose.position.x, pose.position.y, pose.position.z);
Matrix rotation = QuaternionToMatrix(Quaternion{pose.orientation.x, pose.orientation.y, pose.orientation.z, pose.orientation.w});
return rotation * translation;
}
enum class SwapchainType {
Color,
Depth,
};
struct SwapchainInfo {
XrSwapchain swapchain{XR_NULL_HANDLE};
std::int64_t swapchain_format{};
std::vector<GLuint> image_views;
};
export struct LunarWM {
LunarWM() = default;
~LunarWM();
void init();
void run();
void terminate();
private:
struct RenderLayerInfo;
void init_wayland();
void init_xr();
void poll_events_xr();
bool render_layer(RenderLayerInfo &info, float dt);
void render_3d(float dt);
bool m_initialized{};
struct {
wl_display *display{};
wl_event_loop *event_loop{};
wlr_backend *backend{};
wlr_renderer *renderer{};
wlr_egl *egl{};
EGLDisplay egl_display;
EGLContext egl_context;
EGLConfig egl_config;
wlr_allocator *allocator{};
wlr_compositor *compositor{};
wlr_subcompositor *subcompositor{};
wlr_data_device_manager *data_device_manager{};
wlr_seat *seat{};
wl_list keyboards;
wl_listener new_input_listener{};
} m_wayland;
struct {
std::optional<XrInstance> instance;
std::optional<XrSystemId> system_id;
XrSession session{XR_NULL_HANDLE};
XrSessionState session_state{XR_SESSION_STATE_UNKNOWN};
struct {
std::vector<SwapchainInfo> color;
std::vector<SwapchainInfo> depth;
} swapchains;
std::unordered_map<
XrSwapchain,
std::pair<SwapchainType, std::vector<XrSwapchainImageOpenGLESKHR>>>
swapchain_images_map;
std::vector<XrViewConfigurationView> view_configuration_views;
XrEnvironmentBlendMode environment_blend_mode;
XrSpace local_space{XR_NULL_HANDLE};
XrSpace view_space{XR_NULL_HANDLE};
std::uint32_t get_swapchain_image(XrSwapchain swapchain, uint32_t index) {
return swapchain_images_map[swapchain].second[index].image;
}
} m_xr;
struct RendererFrame {
GLuint fbo{0};
uint32_t color_idx{UINT32_MAX};
uint32_t depth_idx{UINT32_MAX};
bool has_depth{};
};
struct {
GLuint fbo{};
RenderTexture2D tmp_rt{};
Camera3D camera{
.position = { 0, 0, 0 },
.target = { 0, 0, 1 },
.up = { 0, 1, 0 },
.fovy = 45,
.projection = CAMERA_PERSPECTIVE,
};
bool begin_render(GLuint color_tex, GLuint depth_tex, uint32_t w, uint32_t h, const XrView& view) {
if (!fbo) // create once
glGenFramebuffers(1, &fbo);
rlFramebufferAttach(fbo, color_tex, RL_ATTACHMENT_COLOR_CHANNEL0,
RL_ATTACHMENT_TEXTURE2D, 0);
assert(rlFramebufferComplete(fbo));
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
GLenum fbStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if(fbStatus != GL_FRAMEBUFFER_COMPLETE){
printf("FBO incomplete: 0x%04x\n", fbStatus);
return false;
}
tmp_rt = {.id = fbo,
.texture = {color_tex, (int)w, (int)h, 1, -1},
.depth = {depth_tex, (int)w, (int)h, 1, -1}};
BeginTextureMode(tmp_rt);
rlEnableStereoRender();
const XrFovf &fov = view.fov;
Matrix proj = xr_projection_matrix(fov);
Matrix viewM = MatrixInvert(xr_matrix(view.pose));
rlSetMatrixProjectionStereo(proj, proj);
rlSetMatrixViewOffsetStereo(viewM, viewM);
return true;
}
void end_render(LunarWM &wm, SwapchainInfo &color_sc,
SwapchainInfo *depth_sc) {
rlDisableStereoRender();
EndTextureMode();
}
void update_camera(LunarWM &wm, Camera3D &camera, RenderLayerInfo &info)
{
const XrTime time = info.predicted_display_time;
XrSpaceLocation view_location{ XR_TYPE_SPACE_LOCATION };
XrResult result = xrLocateSpace(wm.m_xr.view_space, wm.m_xr.local_space, time, &view_location);
if (result != XR_SUCCESS) {
return;
}
if (view_location.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) {
const auto& pos = view_location.pose.position;
camera.position = Vector3{ pos.x, pos.y, pos.z };
}
if (view_location.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) {
const auto& rot = view_location.pose.orientation;
const auto forward = Vector3RotateByQuaternion(Vector3{ 0, 0, -1 }, Quaternion{ rot.x, rot.y, rot.z, rot.w });
const auto up = Vector3RotateByQuaternion(Vector3{ 0, 1, 0 }, Quaternion{ rot.x, rot.y, rot.z, rot.w });
camera.target = camera.position + forward;
camera.up = up;
}
}
} m_renderer;
struct RenderLayerInfo {
XrTime predicted_display_time;
std::vector<XrCompositionLayerBaseHeader *> layers;
XrCompositionLayerProjection layer_projection{
.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION,
.next = nullptr,
};
std::vector<XrCompositionLayerProjectionView> layer_projection_views;
};
std::chrono::time_point<Clock> m_last_tick;
bool m_running{};
bool m_session_running{};
bool m_session_state{};
};
void LunarWM::init() {
this->init_wayland();
wlr_log(WLR_INFO, "0");
auto draw = eglGetCurrentSurface(EGL_DRAW);
auto read = eglGetCurrentSurface(EGL_READ);
if (eglMakeCurrent(m_wayland.egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
m_wayland.egl_context) == EGL_FALSE) {
throw std::runtime_error("Failed to eglMakeCurrent");
}
wlr_log(WLR_INFO, "1");
this->init_xr();
wlr_log(WLR_INFO, "2");
wlr_log(WLR_INFO, "OpenGL ES version: %s", glGetString(GL_VERSION));
InitWindow(0, 0, "");
wlr_log(WLR_INFO, "3");
if (eglMakeCurrent(m_wayland.egl_display, read, draw,
m_wayland.egl_context) == EGL_FALSE) {
throw std::runtime_error("Failed to eglMakeCurrent");
}
wlr_log(WLR_INFO, "4");
m_initialized = true;
}
void LunarWM::init_wayland() {
wlr_log_init(WLR_DEBUG, NULL);
m_wayland.display = wl_display_create();
if (!m_wayland.display) {
throw std::runtime_error("Failed to create wayland display");
}
m_wayland.event_loop = wl_display_get_event_loop(m_wayland.display);
if (!m_wayland.event_loop) {
throw std::runtime_error("Failed to get wayland event loop");
}
m_wayland.backend = wlr_backend_autocreate(m_wayland.event_loop, nullptr);
if (!m_wayland.backend) {
throw std::runtime_error("Failed to create wlroots backend");
}
setenv("WLR_RENDERER", "gles2", 1);
m_wayland.renderer = wlr_renderer_autocreate(m_wayland.backend);
if (!m_wayland.renderer) {
throw std::runtime_error("Failed to create wlroots renderer");
}
m_wayland.egl = wlr_gles2_renderer_get_egl(m_wayland.renderer);
if (!m_wayland.egl) {
throw std::runtime_error("Failed to get egl information from renderer");
}
m_wayland.egl_display = wlr_egl_get_display(m_wayland.egl);
m_wayland.egl_context = wlr_egl_get_context(m_wayland.egl);
m_wayland.egl_config = EGL_NO_CONFIG_KHR;
if (!wlr_renderer_init_wl_display(m_wayland.renderer, m_wayland.display)) {
throw std::runtime_error(
"Failed to initialize renderer with wayland display");
}
m_wayland.allocator =
wlr_allocator_autocreate(m_wayland.backend, m_wayland.renderer);
if (!m_wayland.allocator) {
throw std::runtime_error("Failed to create wlroots allocator");
}
m_wayland.compositor =
wlr_compositor_create(m_wayland.display, 5, m_wayland.renderer);
if (!m_wayland.compositor) {
throw std::runtime_error("Failed to create wlroots compositor");
}
m_wayland.subcompositor = wlr_subcompositor_create(m_wayland.display);
if (!m_wayland.subcompositor) {
throw std::runtime_error("Failed to create wlroots subcompositor");
}
m_wayland.data_device_manager =
wlr_data_device_manager_create(m_wayland.display);
if (!m_wayland.data_device_manager) {
throw std::runtime_error("Failed to create wlroots data device manager");
}
wl_list_init(&m_wayland.keyboards);
m_wayland.new_input_listener.notify = [](wl_listener *listener, void *data) {
auto wm = reinterpret_cast<LunarWM *>(
wl_container_of(listener, static_cast<LunarWM *>(nullptr),
m_wayland.new_input_listener));
auto dev = reinterpret_cast<wlr_input_device *>(data);
if (dev->type == WLR_INPUT_DEVICE_KEYBOARD) {
// FIXME: Implement
} else if (dev->type == WLR_INPUT_DEVICE_POINTER) {
// FIXME: Implement
}
std::uint32_t caps = WL_SEAT_CAPABILITY_POINTER;
if (!wl_list_empty(&wm->m_wayland.keyboards)) {
caps |= WL_SEAT_CAPABILITY_KEYBOARD;
}
assert(wm->m_wayland.seat);
wlr_seat_set_capabilities(wm->m_wayland.seat, caps);
};
wl_signal_add(&m_wayland.backend->events.new_input,
&m_wayland.new_input_listener);
m_wayland.seat = wlr_seat_create(m_wayland.display, "seat0");
if (!m_wayland.seat) {
throw std::runtime_error("Failed to create wlroots seat");
}
}
void LunarWM::init_xr() {
XrApplicationInfo app_info{
.applicationVersion = 1,
.engineVersion = 1,
.apiVersion = XR_CURRENT_API_VERSION,
};
strncpy(app_info.applicationName, "LunarWM", XR_MAX_APPLICATION_NAME_SIZE);
strncpy(app_info.engineName, "LunarWM Engine", XR_MAX_ENGINE_NAME_SIZE);
std::vector<std::string> instance_extensions{
XR_EXT_DEBUG_UTILS_EXTENSION_NAME,
XR_MNDX_EGL_ENABLE_EXTENSION_NAME,
XR_KHR_OPENGL_ES_ENABLE_EXTENSION_NAME,
};
std::vector<std::string> apiLayers;
std::vector<char const *> active_instance_extensions;
std::vector<char const *> activeAPILayers;
uint32_t apiLayerCount = 0;
std::vector<XrApiLayerProperties> apiLayerProperties;
if (xrEnumerateApiLayerProperties(0, &apiLayerCount, nullptr) != XR_SUCCESS) {
throw std::runtime_error("Failed to enumerate API layer properties");
}
apiLayerProperties.resize(apiLayerCount, {XR_TYPE_API_LAYER_PROPERTIES});
if (xrEnumerateApiLayerProperties(apiLayerCount, &apiLayerCount,
apiLayerProperties.data()) != XR_SUCCESS) {
throw std::runtime_error("Failed to enumerate API layer properties");
}
for (auto &requestLayer : apiLayers) {
for (auto &layerProperty : apiLayerProperties) {
if (strcmp(requestLayer.c_str(), layerProperty.layerName) != 0) {
continue;
} else {
activeAPILayers.push_back(requestLayer.c_str());
break;
}
}
}
uint32_t extensionCount = 0;
std::vector<XrExtensionProperties> extensionProperties;
if (xrEnumerateInstanceExtensionProperties(nullptr, 0, &extensionCount,
nullptr) != XR_SUCCESS) {
throw std::runtime_error(
"Failed to enumerate OpenXR instance extension properties");
}
extensionProperties.resize(extensionCount, {XR_TYPE_EXTENSION_PROPERTIES});
if (xrEnumerateInstanceExtensionProperties(
nullptr, extensionCount, &extensionCount,
extensionProperties.data()) != XR_SUCCESS) {
throw std::runtime_error(
"Failed to enumerate OpenXR instance extension properties");
}
for (auto &requestedInstanceExtension : instance_extensions) {
bool found = false;
for (auto &extensionProperty : extensionProperties) {
if (strcmp(requestedInstanceExtension.c_str(),
extensionProperty.extensionName) != 0) {
continue;
} else {
active_instance_extensions.push_back(
requestedInstanceExtension.c_str());
found = true;
break;
}
}
if (!found) {
throw std::runtime_error(
std::format("Failed to find OpenXR instance extension: {}",
requestedInstanceExtension));
}
}
{
XrInstanceCreateInfo ci{
.type = XR_TYPE_INSTANCE_CREATE_INFO,
.next = NULL,
.createFlags = 0,
.applicationInfo = app_info,
.enabledApiLayerCount =
static_cast<std::uint32_t>(activeAPILayers.size()),
.enabledApiLayerNames = activeAPILayers.data(),
.enabledExtensionCount =
static_cast<std::uint32_t>(active_instance_extensions.size()),
.enabledExtensionNames = active_instance_extensions.data(),
};
XrInstance instance;
if (xrCreateInstance(&ci, &instance) != XR_SUCCESS) {
throw std::runtime_error("Failed to create OpenXR instance");
}
m_xr.instance = instance;
}
{
XrSystemGetInfo gi{
.type = XR_TYPE_SYSTEM_GET_INFO,
.next = nullptr,
};
XrFormFactor factors[]{
XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY,
XR_FORM_FACTOR_HANDHELD_DISPLAY,
};
for (auto const factor : factors) {
gi.formFactor = factor;
XrSystemId system_id;
if (xrGetSystem(*m_xr.instance, &gi, &system_id) == XR_SUCCESS) {
m_xr.system_id = system_id;
break;
}
}
if (!m_xr.system_id) {
throw std::runtime_error("Failed to find valid form factor");
}
}
XrGraphicsRequirementsOpenGLESKHR reqs{
.type = XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_ES_KHR,
.next = nullptr,
};
PFN_xrGetOpenGLESGraphicsRequirementsKHR xrGetOpenGLESGraphicsRequirementsKHR;
xrGetInstanceProcAddr(
*m_xr.instance, "xrGetOpenGLESGraphicsRequirementsKHR",
(PFN_xrVoidFunction *)&xrGetOpenGLESGraphicsRequirementsKHR);
if (xrGetOpenGLESGraphicsRequirementsKHR(*m_xr.instance, *m_xr.system_id,
&reqs) != XR_SUCCESS) {
throw std::runtime_error("Failed to get GLES graphics requirements");
}
printf("OpenGL ES range: %d.%d.%d %d.%d.%d\n",
XR_VERSION_MAJOR(reqs.minApiVersionSupported),
XR_VERSION_MINOR(reqs.minApiVersionSupported),
XR_VERSION_PATCH(reqs.minApiVersionSupported),
XR_VERSION_MAJOR(reqs.maxApiVersionSupported),
XR_VERSION_MINOR(reqs.maxApiVersionSupported),
XR_VERSION_PATCH(reqs.maxApiVersionSupported));
glEnable(GL_DEBUG_OUTPUT_KHR);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR);
{
glDrawBuffersEXT = (PFNGLDRAWBUFFERSEXTPROC) eglGetProcAddress("glDrawBuffersEXT");
XrGraphicsBindingEGLMNDX gbind = {
.type = XR_TYPE_GRAPHICS_BINDING_EGL_MNDX,
.next = nullptr,
.getProcAddress = eglGetProcAddress,
.display = m_wayland.egl_display,
.config = m_wayland.egl_config,
.context = m_wayland.egl_context,
};
XrSessionCreateInfo ci{
.type = XR_TYPE_SESSION_CREATE_INFO,
.next = &gbind,
.createFlags = 0,
.systemId = *m_xr.system_id,
};
if (xrCreateSession(*m_xr.instance, &ci, &m_xr.session) != XR_SUCCESS) {
throw std::runtime_error("Failed to create OpenXR session");
}
}
// Swapchain time!
std::vector<XrViewConfigurationType> view_config_types;
{
std::uint32_t count;
if (xrEnumerateViewConfigurations(*m_xr.instance, *m_xr.system_id, 0,
&count, nullptr) != XR_SUCCESS) {
throw std::runtime_error(
"Failed to get amount of OpenXR view configurations");
}
view_config_types.resize(count);
if (xrEnumerateViewConfigurations(*m_xr.instance, *m_xr.system_id, count,
&count,
view_config_types.data()) != XR_SUCCESS) {
throw std::runtime_error(
"Failed to enumerate OpenXR view configurations");
}
}
if (!std::ranges::contains(view_config_types,
XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO)) {
throw std::runtime_error(
"XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO not present");
}
{
uint32_t count = 0;
if (xrEnumerateViewConfigurationViews(
*m_xr.instance, *m_xr.system_id,
XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, 0, &count,
nullptr) != XR_SUCCESS) {
throw std::runtime_error(
"Failed to get amount of OpenXR view configuration views");
}
m_xr.view_configuration_views.resize(count,
{XR_TYPE_VIEW_CONFIGURATION_VIEW});
if (xrEnumerateViewConfigurationViews(
*m_xr.instance, *m_xr.system_id,
XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, count, &count,
m_xr.view_configuration_views.data()) != XR_SUCCESS) {
throw std::runtime_error(
"Failed to enumerate OpenXR view configuration views");
}
}
std::vector<std::int64_t> swapchain_formats;
{
std::uint32_t count;
if (xrEnumerateSwapchainFormats(m_xr.session, 0, &count, nullptr) !=
XR_SUCCESS) {
throw std::runtime_error(
"Failed to get amount of OpenXR swapchain formats");
}
swapchain_formats.resize(count);
if (xrEnumerateSwapchainFormats(m_xr.session, count, &count,
swapchain_formats.data()) != XR_SUCCESS) {
throw std::runtime_error("Failed to enumerate OpenXR swapchain formats");
}
}
std::vector<std::int64_t> swapchain_format_depth_needles{
GL_DEPTH_COMPONENT16,
// GL_DEPTH_COMPONENT32F,
// GL_DEPTH_COMPONENT24,
};
const std::vector<int64_t>::const_iterator &swapchain_format_depth_it =
std::find_first_of(swapchain_formats.begin(), swapchain_formats.end(),
std::begin(swapchain_format_depth_needles),
std::end(swapchain_format_depth_needles));
if (swapchain_format_depth_it == swapchain_formats.end()) {
throw std::runtime_error(
"Failed to find satisfying depth swapchain format");
}
auto const swapchain_format_depth = *swapchain_format_depth_it;
std::vector<std::int64_t> swapchain_format_color_needles{GL_SRGB8_ALPHA8};
const std::vector<int64_t>::const_iterator &swapchain_format_color_it =
std::find_first_of(swapchain_formats.begin(), swapchain_formats.end(),
std::begin(swapchain_format_color_needles),
std::end(swapchain_format_color_needles));
if (swapchain_format_color_it == swapchain_formats.end()) {
throw std::runtime_error(
"Failed to find satisfying color swapchain format");
}
auto const swapchain_format_color = *swapchain_format_color_it;
auto const AllocateSwapchainImageData =
[&](XrSwapchain swapchain, SwapchainType type, uint32_t count) {
m_xr.swapchain_images_map[swapchain].first = type;
m_xr.swapchain_images_map[swapchain].second.resize(
count, {XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_ES_KHR});
return reinterpret_cast<XrSwapchainImageBaseHeader *>(
m_xr.swapchain_images_map[swapchain].second.data());
};
const uint32_t view_count = (uint32_t)m_xr.view_configuration_views.size();
const uint32_t eyeW = m_xr.view_configuration_views[0].recommendedImageRectWidth;
const uint32_t eyeH = m_xr.view_configuration_views[0].recommendedImageRectHeight;
const uint32_t bufW = eyeW * view_count;
m_xr.swapchains.color.resize(1);
m_xr.swapchains.depth.resize(1);
{
auto &color_sc = m_xr.swapchains.color[0];
auto &depth_sc = m_xr.swapchains.depth[0];
auto &vcv = m_xr.view_configuration_views[0];
{ // Create color swapchain
XrSwapchainCreateInfo ci{
.type = XR_TYPE_SWAPCHAIN_CREATE_INFO,
.next = nullptr,
.createFlags = 0,
.usageFlags = XR_SWAPCHAIN_USAGE_SAMPLED_BIT |
XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT,
.format = swapchain_format_color,
.sampleCount = vcv.recommendedSwapchainSampleCount,
.width = bufW,
.height = eyeH,
.faceCount = 1,
.arraySize = 1,
.mipCount = 1,
};
color_sc.swapchain_format = ci.format;
if (xrCreateSwapchain(m_xr.session, &ci, &color_sc.swapchain) !=
XR_SUCCESS) {
throw std::runtime_error("Failed to create OpenXR color swapchain");
}
}
{ // Create depth swapchain
XrSwapchainCreateInfo ci{
.type = XR_TYPE_SWAPCHAIN_CREATE_INFO,
.next = nullptr,
.createFlags = 0,
.usageFlags = XR_SWAPCHAIN_USAGE_SAMPLED_BIT |
XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
.format = swapchain_format_depth,
.sampleCount = vcv.recommendedSwapchainSampleCount,
.width = bufW,
.height = eyeH,
.faceCount = 1,
.arraySize = 1,
.mipCount = 1,
};
depth_sc.swapchain_format = ci.format;
if (xrCreateSwapchain(m_xr.session, &ci, &depth_sc.swapchain) !=
XR_SUCCESS) {
throw std::runtime_error("Failed to create OpenXR color swapchain");
}
}
XrResult res;
// Enumerate swapchain images
uint32_t color_swapchain_image_count = 0;
if (xrEnumerateSwapchainImages(color_sc.swapchain, 0,
&color_swapchain_image_count,
nullptr) != XR_SUCCESS) {
throw std::runtime_error("Failed to get Color Swapchain Images count.");
}
XrSwapchainImageBaseHeader *color_swapchain_images =
AllocateSwapchainImageData(color_sc.swapchain, SwapchainType::Color,
color_swapchain_image_count);
if (xrEnumerateSwapchainImages(color_sc.swapchain,
color_swapchain_image_count,
&color_swapchain_image_count,
color_swapchain_images) != XR_SUCCESS) {
throw std::runtime_error("Failed to enumerate Color Swapchain Images.");
}
uint32_t depth_swapchain_image_count = 0;
if ((res = xrEnumerateSwapchainImages(depth_sc.swapchain, 0,
&depth_swapchain_image_count,
nullptr)) != XR_SUCCESS) {
throw std::runtime_error(
std::format("Failed to get Depth Swapchain Images count. {}", res));
}
XrSwapchainImageBaseHeader *depth_swapchain_images =
AllocateSwapchainImageData(depth_sc.swapchain, SwapchainType::Depth,
depth_swapchain_image_count);
if ((res = xrEnumerateSwapchainImages(
depth_sc.swapchain, depth_swapchain_image_count,
&depth_swapchain_image_count, depth_swapchain_images)) !=
XR_SUCCESS) {
throw std::runtime_error("Failed to enumerate Depth Swapchain Images.");
}
// Get image views
for (uint32_t j = 0; j < color_swapchain_image_count; j++) {
GLuint framebuffer = 0;
glGenFramebuffers(1, &framebuffer);
GLenum attachment = GL_COLOR_ATTACHMENT0;
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, attachment, GL_TEXTURE_2D,
m_xr.get_swapchain_image(color_sc.swapchain, j),
0);
GLenum result = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER);
if (result != GL_FRAMEBUFFER_COMPLETE) {
throw std::runtime_error("Failed to create color framebuffer");
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
color_sc.image_views.push_back(framebuffer);
}
for (uint32_t j = 0; j < depth_swapchain_image_count; j++) {
GLuint framebuffer = 0;
glGenFramebuffers(1, &framebuffer);
GLenum attachment = GL_DEPTH_ATTACHMENT;
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, attachment, GL_TEXTURE_2D,
m_xr.get_swapchain_image(depth_sc.swapchain, j),
0);
GLenum result = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER);
if (result != GL_FRAMEBUFFER_COMPLETE) {
throw std::runtime_error("Failed to create depth framebuffer");
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
depth_sc.image_views.push_back(framebuffer);
}
}
std::vector<XrEnvironmentBlendMode> environment_blend_modes;
{ // Get available blend modes
std::uint32_t count{};
if (xrEnumerateEnvironmentBlendModes(
*m_xr.instance, *m_xr.system_id,
XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, 0, &count,
nullptr) != XR_SUCCESS) {
throw std::runtime_error(
"Failed to get OpenXR environment blend mode count");
}
environment_blend_modes.resize(count);
if (xrEnumerateEnvironmentBlendModes(
*m_xr.instance, *m_xr.system_id,
XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, count, &count,
environment_blend_modes.data()) != XR_SUCCESS) {
throw std::runtime_error("Failed to get XR environment blend modes");
}
}
std::vector<XrEnvironmentBlendMode> requested_environment_blend_modes{
XR_ENVIRONMENT_BLEND_MODE_OPAQUE,
XR_ENVIRONMENT_BLEND_MODE_ADDITIVE,
XR_ENVIRONMENT_BLEND_MODE_MAX_ENUM,
};
for (auto const &bm : requested_environment_blend_modes) {
m_xr.environment_blend_mode = bm;
if (std::ranges::contains(environment_blend_modes, bm)) {
break;
}
}
if (m_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.");
m_xr.environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
}
{ // Reference space
XrReferenceSpaceCreateInfo ci{
.type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO,
.next = nullptr,
.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE,
.poseInReferenceSpace = {.orientation = {0.0f, 0.0f, 0.0f, 1.0f},
.position = {0.0f, 0.0f, 0.0f}},
};
if (xrCreateReferenceSpace(m_xr.session, &ci, &m_xr.local_space) !=
XR_SUCCESS) {
throw std::runtime_error("Failed to create OpenXR reference space");
}
}
{ // View reference space
XrReferenceSpaceCreateInfo 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(m_xr.session, &ci, &m_xr.view_space) !=
XR_SUCCESS) {
throw std::runtime_error("Failed to create OpenXR reference space");
}
}
}
void LunarWM::poll_events_xr() {
XrEventDataBuffer event_data{XR_TYPE_EVENT_DATA_BUFFER};
auto XrPollEvents = [&]() -> bool {
event_data = {XR_TYPE_EVENT_DATA_BUFFER};
return xrPollEvent(*m_xr.instance, &event_data) == XR_SUCCESS;
};
while (XrPollEvents()) {
switch (event_data.type) {
case XR_TYPE_EVENT_DATA_EVENTS_LOST: {
XrEventDataEventsLost *el =
reinterpret_cast<XrEventDataEventsLost *>(&event_data);
wlr_log(WLR_INFO, "OPENXR: Events Lost: %d", el->lostEventCount);
break;
}
case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING: {
XrEventDataInstanceLossPending *ilp =
reinterpret_cast<XrEventDataInstanceLossPending *>(&event_data);
wlr_log(WLR_INFO, "OPENXR: Instance Loss Pending at: %ld", ilp->lossTime);
m_session_running = false;
m_running = false;
break;
}
case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED: {
XrEventDataInteractionProfileChanged *ipc =
reinterpret_cast<XrEventDataInteractionProfileChanged *>(&event_data);
wlr_log(WLR_INFO, "OPENXR: Interaction Profile changed for Session: %p",
ipc->session);
if (ipc->session != m_xr.session) {
wlr_log(WLR_ERROR,
"XrEventDataInteractionProfileChanged for unknown Session");
break;
}
break;
}
case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING: {
XrEventDataReferenceSpaceChangePending *scp =
reinterpret_cast<XrEventDataReferenceSpaceChangePending *>(
&event_data);
wlr_log(WLR_INFO,
"OPENXR: Reference Space Change pending for Session: %p",
scp->session);
if (scp->session != m_xr.session) {
wlr_log(WLR_ERROR,
"XrEventDataReferenceSpaceChangePending for unknown Session");
break;
}
break;
}
case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: {
XrEventDataSessionStateChanged *sc =
reinterpret_cast<XrEventDataSessionStateChanged *>(&event_data);
if (sc->session != m_xr.session) {
wlr_log(WLR_ERROR,
"XrEventDataSessionStateChanged for unknown Session");
break;
}
if (sc->state == XR_SESSION_STATE_READY) {
XrSessionBeginInfo bi{XR_TYPE_SESSION_BEGIN_INFO};
bi.primaryViewConfigurationType =
XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
bi.primaryViewConfigurationType =
XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
if (xrBeginSession(m_xr.session, &bi) != XR_SUCCESS) {
throw std::runtime_error("Failed to begin session");
}
m_session_running = true;
}
if (sc->state == XR_SESSION_STATE_STOPPING) {
if (xrEndSession(m_xr.session) != XR_SUCCESS) {
throw std::runtime_error("Failed to end session");
}
m_session_running = false;
}
if (sc->state == XR_SESSION_STATE_EXITING) {
m_session_running = false;
m_running = false;
}
if (sc->state == XR_SESSION_STATE_LOSS_PENDING) {
m_session_running = false;
m_running = false;
}
m_xr.session_state = sc->state;
break;
}
default: {
break;
}
}
}
}
bool LunarWM::render_layer(RenderLayerInfo &info, float dt)
{
const uint32_t view_count = (uint32_t)m_xr.view_configuration_views.size();
std::vector<XrView> views(view_count, { XR_TYPE_VIEW });
XrViewLocateInfo locInfo{ XR_TYPE_VIEW_LOCATE_INFO };
locInfo.viewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
locInfo.displayTime = info.predicted_display_time;
locInfo.space = m_xr.local_space;
XrViewState viewState{ XR_TYPE_VIEW_STATE };
uint32_t located = 0;
if (xrLocateViews(m_xr.session, &locInfo, &viewState,
view_count, &located, views.data()) != XR_SUCCESS ||
located != view_count)
{
wlr_log(WLR_ERROR, "Failed to locate views");
return false;
}
auto &color_sc = m_xr.swapchains.color[0];
auto &depth_sc = m_xr.swapchains.depth[0];
auto acquire_wait = [](XrSwapchain sc, uint32_t &idx)->bool{
XrSwapchainImageAcquireInfo ai{ XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO };
if (xrAcquireSwapchainImage(sc, &ai, &idx) != XR_SUCCESS) return false;
XrSwapchainImageWaitInfo wi{ XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO };
wi.timeout = XR_INFINITE_DURATION;
return xrWaitSwapchainImage(sc, &wi) == XR_SUCCESS;
};
uint32_t colIdx = 0, depIdx = 0;
if (!acquire_wait(color_sc.swapchain, colIdx) ||
!acquire_wait(depth_sc.swapchain, depIdx))
{
wlr_log(WLR_ERROR, "Swap-chain acquire failed");
return false;
}
GLuint color_tex = m_xr.get_swapchain_image(color_sc.swapchain, colIdx);
GLuint depth_tex = m_xr.get_swapchain_image(depth_sc.swapchain, depIdx);
if (!m_renderer.fbo) glGenFramebuffers(1, &m_renderer.fbo);
glBindFramebuffer(GL_FRAMEBUFFER, m_renderer.fbo);
rlFramebufferAttach(m_renderer.fbo, color_tex, RL_ATTACHMENT_COLOR_CHANNEL0,
RL_ATTACHMENT_TEXTURE2D, 0);
rlFramebufferAttach(m_renderer.fbo, depth_tex, RL_ATTACHMENT_DEPTH,
RL_ATTACHMENT_TEXTURE2D, 0);
assert(rlFramebufferComplete(m_renderer.fbo));
const uint32_t eyeW = m_xr.view_configuration_views[0].recommendedImageRectWidth;
const uint32_t eyeH = m_xr.view_configuration_views[0].recommendedImageRectHeight;
m_renderer.tmp_rt = { m_renderer.fbo,
{ color_tex, (int)(eyeW*view_count), (int)eyeH, 1, -1 },
{ depth_tex, (int)(eyeW*view_count), (int)eyeH, 1, -1 } };
Matrix projL = xr_projection_matrix(views[0].fov);
Matrix projR = xr_projection_matrix(views[1].fov);
Matrix viewL = MatrixInvert( xr_matrix(views[0].pose) );
Matrix viewR = MatrixInvert( xr_matrix(views[1].pose) );
BeginTextureMode(m_renderer.tmp_rt);
rlEnableStereoRender();
rlSetMatrixProjectionStereo(projL, projR);
rlSetMatrixViewOffsetStereo(viewL, viewR);
glViewport(0, 0, (GLsizei)(eyeW*view_count), (GLsizei)eyeH);
ClearBackground(RAYWHITE);
BeginMode3D(m_renderer.camera);
{
this->render_3d(dt);
}
EndMode3D();
rlDisableStereoRender();
EndTextureMode();
XrSwapchainImageReleaseInfo ri{ XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO };
xrReleaseSwapchainImage(color_sc.swapchain, &ri);
xrReleaseSwapchainImage(depth_sc.swapchain, &ri);
info.layer_projection_views.resize(view_count,
{ XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW });
for (uint32_t i = 0; i < view_count; ++i)
{
const int32_t xOff = (int32_t)i * (int32_t)eyeW;
auto &pv = info.layer_projection_views[i];
pv.pose = views[i].pose;
pv.fov = views[i].fov;
pv.subImage.swapchain = color_sc.swapchain;
pv.subImage.imageRect = { { xOff, 0 },
{ (int32_t)eyeW, (int32_t)eyeH } };
pv.subImage.imageArrayIndex = 0;
}
info.layer_projection.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION;
info.layer_projection.space = m_xr.local_space;
info.layer_projection.viewCount = view_count;
info.layer_projection.views = info.layer_projection_views.data();
info.layer_projection.layerFlags =
XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT |
XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT;
info.layers.clear();
info.layers.push_back(
reinterpret_cast<XrCompositionLayerBaseHeader*>(&info.layer_projection));
return true;
}
void LunarWM::render_3d(float dt) {
static float animT {0.0f};
animT += dt;
DrawGrid(10, 1);
Vector3 forward = Vector3Normalize(Vector3Subtract(m_renderer.camera.target, m_renderer.camera.position));
float distance = 5.0f + sinf(animT) * 3.0f;
Vector3 spherePos = Vector3Add(m_renderer.camera.position, Vector3Scale(forward, distance));
DrawSphere(spherePos, 0.5f, YELLOW);
}
LunarWM::~LunarWM() {
assert(m_initialized);
if (m_xr.local_space == XR_NULL_HANDLE) {
xrDestroySpace(m_xr.local_space);
}
for (size_t i = 0; i < m_xr.swapchains.color.size(); i++) {
auto &color_sc = m_xr.swapchains.color[i];
auto &depth_sc = m_xr.swapchains.depth[i];
for (auto &iv : color_sc.image_views) {
glDeleteFramebuffers(1, &iv);
}
for (auto &iv : depth_sc.image_views) {
glDeleteFramebuffers(1, &iv);
}
xrDestroySwapchain(color_sc.swapchain);
xrDestroySwapchain(depth_sc.swapchain);
}
if (m_xr.session != XR_NULL_HANDLE) {
xrDestroySession(m_xr.session);
}
if (m_xr.instance) {
xrDestroyInstance(*m_xr.instance);
}
wl_list_remove(&m_wayland.keyboards);
wl_display_destroy_clients(m_wayland.display);
if (!m_wayland.allocator) {
wlr_allocator_destroy(m_wayland.allocator);
}
if (!m_wayland.renderer) {
wlr_renderer_destroy(m_wayland.renderer);
}
if (!m_wayland.backend) {
wlr_backend_destroy(m_wayland.backend);
}
if (!m_wayland.display) {
wl_display_destroy(m_wayland.display);
}
}
void LunarWM::run() {
if (!wlr_backend_start(m_wayland.backend)) {
throw std::runtime_error("Failed to start backend");
}
auto const *socket = wl_display_add_socket_auto(m_wayland.display);
if (!socket) {
throw std::runtime_error("Failed to add wayland socket to display");
}
setenv("WAYLAND_DISPLAY", socket, true);
wlr_log(WLR_INFO, "Running compositor on WAYLAND_DISPLAY=%s", socket);
m_running = true;
while (m_running) {
auto now = Clock::now();
float dt = std::chrono::duration<float>(now - m_last_tick).count();
m_last_tick = now;
wl_display_flush_clients(m_wayland.display);
wl_event_loop_dispatch(m_wayland.event_loop, 0);
auto draw = eglGetCurrentSurface(EGL_DRAW);
auto read = eglGetCurrentSurface(EGL_READ);
if (eglMakeCurrent(m_wayland.egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
m_wayland.egl_context) == EGL_FALSE) {
throw std::runtime_error("Failed to eglMakeCurrent");
}
poll_events_xr();
XrFrameState frame_state{
.type = XR_TYPE_FRAME_STATE,
};
XrFrameWaitInfo frame_wait_info{
.type = XR_TYPE_FRAME_WAIT_INFO,
.next = nullptr,
};
if (xrWaitFrame(m_xr.session, &frame_wait_info, &frame_state) !=
XR_SUCCESS) {
throw std::runtime_error("Failed to wait for OpenXR frame");
}
XrFrameBeginInfo frame_begin_info{
.type = XR_TYPE_FRAME_BEGIN_INFO,
.next = nullptr,
};
XrResult res = xrBeginFrame(m_xr.session, &frame_begin_info);
if (res != XR_FRAME_DISCARDED && res != XR_SUCCESS) {
throw std::runtime_error(
std::format("Failed to begin the OpenXR Frame: {}", res));
}
WindowShouldClose();
RenderLayerInfo render_layer_info{
.predicted_display_time = frame_state.predictedDisplayTime,
};
bool session_active =
(m_xr.session_state == XR_SESSION_STATE_SYNCHRONIZED ||
m_xr.session_state == XR_SESSION_STATE_VISIBLE ||
m_xr.session_state == XR_SESSION_STATE_FOCUSED);
if (session_active && frame_state.shouldRender) {
auto rendered = this->render_layer(render_layer_info, dt);
if (rendered) {
render_layer_info.layers.push_back(
reinterpret_cast<XrCompositionLayerBaseHeader *>(
&render_layer_info.layer_projection));
}
}
XrFrameEndInfo frame_end_info{
.type = XR_TYPE_FRAME_END_INFO,
.displayTime = frame_state.predictedDisplayTime,
.environmentBlendMode = m_xr.environment_blend_mode,
.layerCount = static_cast<uint32_t>(render_layer_info.layers.size()),
.layers = render_layer_info.layers.data(),
};
if (xrEndFrame(m_xr.session, &frame_end_info) != XR_SUCCESS) {
throw std::runtime_error("Failed to end OpenXR frame");
}
BeginDrawing();
EndDrawing();
if (eglMakeCurrent(m_wayland.egl_display, read, draw,
m_wayland.egl_context) == EGL_FALSE) {
throw std::runtime_error("Failed to eglMakeCurrent");
}
}
}
void LunarWM::terminate() {
wlr_log(WLR_INFO, "Stopping compositor");
m_running = false;
}
} // namespace LunarWM