module; #include #include #include #include #include #include #include #include #include #include #include extern "C" { #include #include #include #include #include #include #include #include } PFNGLDRAWBUFFERSEXTPROC glDrawBuffersEXT = NULL; #include #include #include export module LunarWM.LunarWM; import std; import LunarWM.Math; using Clock = std::chrono::high_resolution_clock; namespace std { template <> struct formatter { template 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 " "action’s state does not match the action’s 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 file’s 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 " "component’s 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 auto format(XrResult r, FmtCtx &ctx) const { if (spec == 'd') return format_to(ctx.out(), "{}", static_cast(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(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 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 instance; std::optional system_id; XrSession session{XR_NULL_HANDLE}; XrSessionState session_state{XR_SESSION_STATE_UNKNOWN}; struct { std::vector color; std::vector depth; } swapchains; std::unordered_map< XrSwapchain, std::pair>> swapchain_images_map; std::vector 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 layers; XrCompositionLayerProjection layer_projection{ .type = XR_TYPE_COMPOSITION_LAYER_PROJECTION, .next = nullptr, }; std::vector layer_projection_views; }; std::chrono::time_point 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( wl_container_of(listener, static_cast(nullptr), m_wayland.new_input_listener)); auto dev = reinterpret_cast(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 instance_extensions{ XR_EXT_DEBUG_UTILS_EXTENSION_NAME, XR_MNDX_EGL_ENABLE_EXTENSION_NAME, XR_KHR_OPENGL_ES_ENABLE_EXTENSION_NAME, }; std::vector apiLayers; std::vector active_instance_extensions; std::vector activeAPILayers; uint32_t apiLayerCount = 0; std::vector 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 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(activeAPILayers.size()), .enabledApiLayerNames = activeAPILayers.data(), .enabledExtensionCount = static_cast(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 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 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 swapchain_format_depth_needles{ GL_DEPTH_COMPONENT16, // GL_DEPTH_COMPONENT32F, // GL_DEPTH_COMPONENT24, }; const std::vector::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 swapchain_format_color_needles{GL_SRGB8_ALPHA8}; const std::vector::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( 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 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 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(&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(&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(&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( &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(&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 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(&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(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( &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(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