Files
lunarwm/src/LunarWM.cppm

1722 lines
69 KiB
Plaintext
Raw Normal View History

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