1722 lines
69 KiB
C++
1722 lines
69 KiB
C++
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 "
|
||
"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 <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
|