diff --git a/CMakeLists.txt b/CMakeLists.txt index b130c6e7e..6631277ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -369,6 +369,7 @@ option(XRT_FEATURE_OPENXR_DISPLAY_REFRESH_RATE "Enable XR_FB_display_refresh_rat option(XRT_FEATURE_OPENXR_FACE_TRACKING2_FB "Enable XR_FB_face_tracking2" OFF) option(XRT_FEATURE_OPENXR_FACIAL_TRACKING_HTC "Enable XR_HTC_facial_tracking" OFF) option(XRT_FEATURE_OPENXR_FORCE_FEEDBACK_CURL "Enable XR_MNDX_force_feedback_curl" ON) +option(XRT_FEATURE_OPENXR_FUTURE_EXT "Enable XR_EXT_future" ON) option(XRT_FEATURE_OPENXR_HAND_TRACKING_EXT "Enable XR_EXT_hand_tracking" ON) option_with_deps(XRT_FEATURE_OPENXR_HAND_TRACKING_DATA_SOURCE_EXT DEPENDS XRT_FEATURE_OPENXR_HAND_TRACKING_EXT) option(XRT_FEATURE_OPENXR_HAPTIC_PCM "Enable XR_FB_haptic_pcm" OFF) @@ -617,6 +618,7 @@ message(STATUS "# FEATURE_OPENXR_DEBUG_UTILS: ${XRT_FEATUR message(STATUS "# FEATURE_OPENXR_DISPLAY_REFRESH_RATE: ${XRT_FEATURE_OPENXR_DISPLAY_REFRESH_RATE}") message(STATUS "# FEATURE_OPENXR_FACIAL_TRACKING_HTC: ${XRT_FEATURE_OPENXR_FACIAL_TRACKING_HTC}") message(STATUS "# FEATURE_OPENXR_FORCE_FEEDBACK_CURL: ${XRT_FEATURE_OPENXR_FORCE_FEEDBACK_CURL}") +message(STATUS "# FEATURE_OPENXR_FUTURE_EXT: ${XRT_FEATURE_OPENXR_FUTURE_EXT}") message(STATUS "# FEATURE_OPENXR_HAND_TRACKING_EXT: ${XRT_FEATURE_OPENXR_HAND_TRACKING_EXT}") message(STATUS "# FEATURE_OPENXR_HAND_TRACKING_DATA_SOURCE_EXT: ${XRT_FEATURE_OPENXR_HAND_TRACKING_DATA_SOURCE_EXT}") message(STATUS "# FEATURE_OPENXR_HAPTIC_PCM: ${XRT_FEATURE_OPENXR_HAPTIC_PCM}") diff --git a/scripts/generate_oxr_ext_support.py b/scripts/generate_oxr_ext_support.py index c5bcbe029..28d4c434d 100755 --- a/scripts/generate_oxr_ext_support.py +++ b/scripts/generate_oxr_ext_support.py @@ -64,6 +64,7 @@ EXTENSIONS = ( ['XR_EXT_debug_utils', 'XRT_FEATURE_OPENXR_DEBUG_UTILS'], ['XR_EXT_dpad_binding'], ['XR_EXT_eye_gaze_interaction', 'XRT_FEATURE_OPENXR_INTERACTION_EXT_EYE_GAZE'], + ['XR_EXT_future', 'XRT_FEATURE_OPENXR_FUTURE_EXT'], ['XR_EXT_hand_interaction', 'XRT_FEATURE_OPENXR_INTERACTION_EXT_HAND'], ['XR_EXT_hand_tracking', 'XRT_FEATURE_OPENXR_HAND_TRACKING_EXT'], ['XR_EXT_hand_tracking_data_source', 'XRT_FEATURE_OPENXR_HAND_TRACKING_DATA_SOURCE_EXT'], diff --git a/scripts/mapping.imp b/scripts/mapping.imp index bdcede76f..98a038c89 100644 --- a/scripts/mapping.imp +++ b/scripts/mapping.imp @@ -43,6 +43,7 @@ { symbol: ["XRT_FEATURE_OPENXR_FACE_TRACKING2_FB", "public", "\"xrt/xrt_config_build.h\"", "public"] }, { symbol: ["XRT_FEATURE_OPENXR_FACIAL_TRACKING_HTC", "public", "\"xrt/xrt_config_build.h\"", "public"] }, { symbol: ["XRT_FEATURE_OPENXR_FORCE_FEEDBACK_CURL", "public", "\"xrt/xrt_config_build.h\"", "public"] }, +{ symbol: ["XRT_FEATURE_OPENXR_FUTURE_EXT", "public", "\"xrt/xrt_config_build.h\"", "public"] }, { symbol: ["XRT_FEATURE_OPENXR_HAND_TRACKING_EXT", "public", "\"xrt/xrt_config_build.h\"", "public"] }, { symbol: ["XRT_FEATURE_OPENXR_HAND_TRACKING_DATA_SOURCE_EXT", "public", "\"xrt/xrt_config_build.h\"", "public"] }, { symbol: ["XRT_FEATURE_OPENXR_HAPTIC_PCM", "public", "\"xrt/xrt_config_build.h\"", "public"] }, @@ -74,6 +75,7 @@ { symbol: ["XRT_FEATURE_OPENXR_LAYER_FB_PASSTHROUGH", "public", "\"xrt/xrt_config_build.h\"", "public"] }, { symbol: ["XRT_FEATURE_OPENXR_OVERLAY", "public", "\"xrt/xrt_config_build.h\"", "public"] }, { symbol: ["XRT_FEATURE_OPENXR_PLANE_DETECTION", "public", "\"xrt/xrt_config_build.h\"", "public"] }, +{ symbol: ["XRT_FEATURE_OPENXR_USER_PRESENCE", "public", "\"xrt/xrt_config_build.h\"", "public"] }, { symbol: ["XRT_FEATURE_OPENXR_SPACE_LOCAL_FLOOR", "public", "\"xrt/xrt_config_build.h\"", "public"] }, { symbol: ["XRT_FEATURE_OPENXR_SPACE_UNBOUNDED", "public", "\"xrt/xrt_config_build.h\"", "public"] }, { symbol: ["XRT_FEATURE_OPENXR_VISIBILITY_MASK", "public", "\"xrt/xrt_config_build.h\"", "public"] }, diff --git a/src/xrt/auxiliary/util/CMakeLists.txt b/src/xrt/auxiliary/util/CMakeLists.txt index 0a04b10ff..34f2d5a01 100644 --- a/src/xrt/auxiliary/util/CMakeLists.txt +++ b/src/xrt/auxiliary/util/CMakeLists.txt @@ -41,6 +41,8 @@ add_library( u_format.h u_frame.c u_frame.h + u_future.h + u_future.cpp u_generic_callbacks.hpp u_git_tag.h u_hand_tracking.c diff --git a/src/xrt/auxiliary/util/u_future.cpp b/src/xrt/auxiliary/util/u_future.cpp new file mode 100644 index 000000000..46a4499a6 --- /dev/null +++ b/src/xrt/auxiliary/util/u_future.cpp @@ -0,0 +1,265 @@ +// Copyright 2025, Collabora, Ltd. +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief Helper to implement @ref xrt_future, + * A basic CPU based future implementation. + * @author Korcan Hussein + * @ingroup aux_util + */ +#include "u_future.h" + +#include "util/u_debug.h" +#include "util/u_logging.h" + +#include "os/os_threading.h" + +#include +#include +#include + +DEBUG_GET_ONCE_LOG_OPTION(log_level_future, "U_FUTURE_LOG", U_LOGGING_WARN) + +#define UFT_LOG_T(...) U_LOG_IFL_T(debug_get_log_option_log_level_future(), __VA_ARGS__) +#define UFT_LOG_D(...) U_LOG_IFL_D(debug_get_log_option_log_level_future(), __VA_ARGS__) +#define UFT_LOG_I(...) U_LOG_IFL_I(debug_get_log_option_log_level_future(), __VA_ARGS__) +#define UFT_LOG_W(...) U_LOG_IFL_W(debug_get_log_option_log_level_future(), __VA_ARGS__) +#define UFT_LOG_E(...) U_LOG_IFL_E(debug_get_log_option_log_level_future(), __VA_ARGS__) + +#define U_FUTURE_CLEANUP_TIMEOUT_NS (3000000000LL) // 3 seconds + +/*! + * A helper to implement a @ref xrt_future, + * a basic CPU based future implementation + * + * @ingroup aux_util + * @implements xrt_future + */ +struct u_future +{ + struct xrt_future base; + struct os_mutex mtx; + struct os_cond cv; + std::atomic state{XRT_FUTURE_STATE_PENDING}; + std::atomic result{XRT_SUCCESS}; + struct xrt_future_value value = XRT_NULL_FUTURE_VALUE; +}; + +static inline xrt_result_t +u_future_get_xrt_result(const struct u_future *uft) +{ + assert(uft != NULL); + return uft->result.load(std::memory_order::acquire); +} + +static inline xrt_future_state_t +u_future_get_state_priv(const struct u_future *uft) +{ + assert(uft != NULL); + return uft->state.load(std::memory_order::acquire); +} + +static inline void +u_future_set_xrt_result(struct u_future *uft, const xrt_result_t result) +{ + assert(uft != NULL); + uft->result.store(result, std::memory_order::release); +} + +static inline void +u_future_set_state(struct u_future *uft, const xrt_future_state_t new_state) +{ + assert(uft != NULL); + uft->state.store(new_state, std::memory_order::release); +} + +//! internal helper only, does not atomically set both. +static inline void +u_future_set_state_and_xrt_result(struct u_future *uft, const xrt_future_state_t new_state, const xrt_result_t result) +{ + u_future_set_xrt_result(uft, result); + u_future_set_state(uft, new_state); +} + +static inline xrt_result_t +u_future_get_state(const struct xrt_future *xft, enum xrt_future_state *out_state) +{ + const struct u_future *uft = (const struct u_future *)xft; + if (uft == NULL || out_state == NULL) { + return XRT_ERROR_INVALID_ARGUMENT; + } + *out_state = u_future_get_state_priv(uft); + return XRT_SUCCESS; +} + +static xrt_result_t +u_future_get_result(const struct xrt_future *xft, struct xrt_future_result *out_result) +{ + const struct u_future *uft = (const struct u_future *)xft; + if (uft == NULL || out_result == NULL) { + return XRT_ERROR_INVALID_ARGUMENT; + } + + struct os_mutex *mtx = (struct os_mutex *)&uft->mtx; + os_mutex_lock(mtx); + + const xrt_future_state_t curr_state = u_future_get_state_priv(uft); + if (curr_state == XRT_FUTURE_STATE_PENDING) { + os_mutex_unlock(mtx); + return XRT_ERROR_FUTURE_RESULT_NOT_READY; + } + + out_result->result = u_future_get_xrt_result(uft); + if (out_result->result == XRT_SUCCESS && // + curr_state == XRT_FUTURE_STATE_READY) { + out_result->value = uft->value; + } + + os_mutex_unlock(mtx); + return XRT_SUCCESS; +} + +static xrt_result_t +u_future_cancel(struct xrt_future *xft) +{ + struct u_future *uft = (struct u_future *)xft; + if (uft == NULL) { + return XRT_ERROR_INVALID_ARGUMENT; + } + + os_mutex_lock(&uft->mtx); + if (u_future_get_state_priv(uft) == XRT_FUTURE_STATE_PENDING) { + u_future_set_state_and_xrt_result(uft, XRT_FUTURE_STATE_CANCELLED, XRT_OPERATION_CANCELLED); + os_cond_broadcast(&uft->cv); + } + os_mutex_unlock(&uft->mtx); + return XRT_SUCCESS; +} + +static xrt_result_t +u_future_wait(struct xrt_future *xft, int64_t timeout_ns) +{ + struct u_future *uft = (struct u_future *)xft; + if (uft == NULL) { + return XRT_ERROR_INVALID_ARGUMENT; + } + + if (timeout_ns < 0) { + timeout_ns = INT64_MAX; + } + + // on windows pthread_cond_timedwait can not be used with monotonic time + const int64_t start_wait_rt = os_realtime_get_ns(); + const int64_t end_wait_rt = + (start_wait_rt > (INT64_MAX - timeout_ns)) ? INT64_MAX : (start_wait_rt + timeout_ns); + + struct timespec ts = {}; + os_ns_to_timespec(end_wait_rt, &ts); + + xrt_future_state_t curr_state = XRT_FUTURE_STATE_PENDING; + + os_mutex_lock(&uft->mtx); + + while ((curr_state = u_future_get_state_priv(uft)) == XRT_FUTURE_STATE_PENDING) { + const int wait_res = pthread_cond_timedwait(&uft->cv.cond, &uft->mtx.mutex, &ts); + if (wait_res == ETIMEDOUT) { + if (os_realtime_get_ns() >= end_wait_rt) { + // final state check - might have completed during timeout handling + curr_state = u_future_get_state_priv(uft); + break; + } + } else if (wait_res != 0) { + + break; + } + } + + os_mutex_unlock(&uft->mtx); + + if (curr_state == XRT_FUTURE_STATE_PENDING) { + return XRT_TIMEOUT; + } + return u_future_get_xrt_result(uft); +} + +static inline xrt_result_t +u_future_is_cancel_requested(const struct xrt_future *xft, bool *out_request_cancel) +{ + const struct u_future *uft = (const struct u_future *)xft; + if (uft == NULL || out_request_cancel == NULL) { + return XRT_ERROR_INVALID_ARGUMENT; + } + const xrt_future_state_t curr_state = u_future_get_state_priv(uft); + *out_request_cancel = curr_state == XRT_FUTURE_STATE_CANCELLED; + return XRT_SUCCESS; +} + +static xrt_result_t +u_future_complete(struct xrt_future *xft, const struct xrt_future_result *ft_result) +{ + struct u_future *uft = (struct u_future *)xft; + if (uft == NULL || ft_result == NULL) { + return XRT_ERROR_INVALID_ARGUMENT; + } + + os_mutex_lock(&uft->mtx); + const xrt_future_state_t curr_state = u_future_get_state_priv(uft); + if (curr_state != XRT_FUTURE_STATE_PENDING) { + os_mutex_unlock(&uft->mtx); + switch (curr_state) { + case XRT_FUTURE_STATE_READY: return XRT_ERROR_FUTURE_ALREADY_COMPLETE; + case XRT_FUTURE_STATE_CANCELLED: + default: return XRT_OPERATION_CANCELLED; + } + } + + if (ft_result->result == XRT_SUCCESS) { + uft->value = ft_result->value; + } + u_future_set_state_and_xrt_result(uft, XRT_FUTURE_STATE_READY, ft_result->result); + + os_cond_broadcast(&uft->cv); + os_mutex_unlock(&uft->mtx); + return XRT_SUCCESS; +} + +static void +u_future_destroy(struct xrt_future *xft) +{ + struct u_future *uft = (struct u_future *)xft; + if (uft == NULL) { + return; + } + + UFT_LOG_T("destroying u_future:%p", (void *)uft); + + u_future_cancel(&uft->base); + u_future_wait(&uft->base, U_FUTURE_CLEANUP_TIMEOUT_NS); + os_cond_destroy(&uft->cv); + os_mutex_destroy(&uft->mtx); + + UFT_LOG_T("u_future:%p destroyed", (void *)uft); + + delete uft; +} + +struct xrt_future * +u_future_create(void) +{ + std::unique_ptr uft{new struct u_future()}; + os_mutex_init(&uft->mtx); + os_cond_init(&uft->cv); + + struct xrt_future *xft = &uft->base; + xft->reference.count = 1; + xft->get_state = u_future_get_state; + xft->get_result = u_future_get_result; + xft->cancel = u_future_cancel; + xft->wait = u_future_wait; + xft->is_cancel_requested = u_future_is_cancel_requested; + xft->complete = u_future_complete; + xft->destroy = u_future_destroy; + + UFT_LOG_T("created u_future:%p", (void *)xft); + return &uft.release()->base; +} diff --git a/src/xrt/auxiliary/util/u_future.h b/src/xrt/auxiliary/util/u_future.h new file mode 100644 index 000000000..294a31b27 --- /dev/null +++ b/src/xrt/auxiliary/util/u_future.h @@ -0,0 +1,23 @@ +// Copyright 2025, Collabora, Ltd. +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief Helper to implement @ref xrt_future, + * A basic CPU based future implementation. + * @author Korcan Hussein + * @ingroup aux_util + */ +#pragma once + +#include "xrt/xrt_future.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct xrt_future * +u_future_create(void); + +#ifdef __cplusplus +} +#endif diff --git a/src/xrt/include/xrt/xrt_compositor.h b/src/xrt/include/xrt/xrt_compositor.h index 92c5935d5..63785a647 100644 --- a/src/xrt/include/xrt/xrt_compositor.h +++ b/src/xrt/include/xrt/xrt_compositor.h @@ -967,6 +967,7 @@ struct xrt_begin_session_info bool ext_hand_tracking_enabled; bool ext_hand_tracking_data_source_enabled; bool ext_eye_gaze_interaction_enabled; + bool ext_future_enabled; bool ext_hand_interaction_enabled; bool htc_facial_tracking_enabled; bool fb_body_tracking_enabled; diff --git a/src/xrt/include/xrt/xrt_config_build.h.cmake_in b/src/xrt/include/xrt/xrt_config_build.h.cmake_in index c663cbdd1..c640d2c8f 100644 --- a/src/xrt/include/xrt/xrt_config_build.h.cmake_in +++ b/src/xrt/include/xrt/xrt_config_build.h.cmake_in @@ -40,6 +40,7 @@ #cmakedefine XRT_FEATURE_OPENXR_FACE_TRACKING2_FB #cmakedefine XRT_FEATURE_OPENXR_FACIAL_TRACKING_HTC #cmakedefine XRT_FEATURE_OPENXR_FORCE_FEEDBACK_CURL +#cmakedefine XRT_FEATURE_OPENXR_FUTURE_EXT #cmakedefine XRT_FEATURE_OPENXR_HAND_TRACKING_EXT #cmakedefine XRT_FEATURE_OPENXR_HAND_TRACKING_DATA_SOURCE_EXT #cmakedefine XRT_FEATURE_OPENXR_HAPTIC_PCM diff --git a/src/xrt/include/xrt/xrt_future.h b/src/xrt/include/xrt/xrt_future.h new file mode 100644 index 000000000..0acee6043 --- /dev/null +++ b/src/xrt/include/xrt/xrt_future.h @@ -0,0 +1,304 @@ +// Copyright 2025, Collabora, Ltd. +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief Interface for creating futures. + * @author Korcan Hussein + * @ingroup xrt_iface + */ +#pragma once + +#include "xrt/xrt_defines.h" +#include "xrt/xrt_future_value.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/*! + * The (future) status of an asynchronous operation + * + * @see xrt_future::get_state + */ +typedef enum xrt_future_state +{ + XRT_FUTURE_STATE_PENDING, + XRT_FUTURE_STATE_READY, + XRT_FUTURE_STATE_CANCELLED, +} xrt_future_state_t; + +/*! + * The (future) result of an asynchronous operation + * + * @see xrt_future::get_result, xrt_future::complete + */ +struct xrt_future_result +{ + /*! + * The result value of a successfully completed asynchronous operation + * @see xrt_future_value_make + */ + struct xrt_future_value value; + + //! The error/ok status of a completed asynchronous operation + XRT_ALIGNAS(8) xrt_result_t result; +}; + +#define XRT_FUTURE_RESULT(TYPED_VALUE, ERR_CODE) \ + XRT_C11_COMPOUND(struct xrt_future_result) \ + { \ + .value = xrt_future_value_make(TYPED_VALUE), .result = ERR_CODE, \ + } + +/*! + * @interface xrt_future + * @ingroup xrt_iface + * + * @brief A future is a concurrency primitive that provides a mechanism to access results of asynchronous operations. + * + * The xrt_future interface shares similarities with OpenXR futures but is not identical. In comparison to C++ + * std::future, xrt_future combines concepts from both std::shared_future and std::promise with built-in + * cancellation support in a single interface. The interface provides separate method sets for producers + * (result generators) and consumers (result pollers/waiters). + * + * Thread Safety and Reference Counting: + * Each thread that references an xrt_future must properly manage the reference count using + * @ref xrt_future_reference when entering and exiting the thread's scope. + * + * @see "Server-side / driver — implementing async callbacks" in @ref + * [async-functions-and-futures](../../../../doc/async-functions-and-futures.md) for producer example code + */ +struct xrt_future +{ + /*! + * Reference helper. + */ + struct xrt_reference reference; + + /*! + * Destroys the future. + */ + void (*destroy)(struct xrt_future *xft); + + /*! + * @brief Gets the current state of the future + * + * @param[in] xft The future. + * @param[out] out_state The current state of @ref xft + * + * @note Consumer Interface + * + * @note Blocking behavior - non-blocking + * + * @see xrt_future_state + */ + xrt_result_t (*get_state)(const struct xrt_future *xft, enum xrt_future_state *out_state); + + /*! + * @brief Gets the future results (after async operation has finished) + * + * @param[in] xft The future. + * @param[out] out_result The future result of @ref xft + * + * @note Consumer Interface + * + * @note Blocking behavior - Non-blocking w.r.t. result: returns immediately, + * may briefly block acquiring an internal mutex to check/consume state + * + * @note differs from std::future::get in that std::future will block & wait the calling thread + * until the result is ready where as xrt_future::get_result is non-blocking (w.r.t. result) + * to achieve the equivalent without using polling interface would be: + * + * // std::future::get == + * xrt_future_wait(xft, INT64_MAX); + * xrt_get_result(xft, &my_result); + * + * @note Similar to or used by OpenXR future/async complete functions + * + * @see xrt_future::complete, xrt_future_result, xrt_future_value + */ + xrt_result_t (*get_result)(const struct xrt_future *xft, struct xrt_future_result *out_result); + + /*! + * @brief Signals an asynchronous operation associated with the future to cancel. + * + * @param[in] xft The future. + * + * @note Consumer Interface + * + * @note Blocking behavior - Non-blocking, may briefly block acquiring an internal mutex to check/consume state + */ + xrt_result_t (*cancel)(struct xrt_future *xft); + + /*! + * @brief Waits on a pending/cancelled future + * + * @param[in] xft The future. + * @param[in] timeout_ns Timeout in nanoseconds or INT64_MAX for infinite duration + * + * @note Consumer Interface + * + * @note Blocking behavior - Blocking + * + * @see xrt_future::cancel, xrt_future::complete + */ + xrt_result_t (*wait)(struct xrt_future *xft, int64_t timeout_ns); + + /*! + * @brief Waits on a cancelled future + * + * @param[in] xft The future. + * @param[out] out_request_cancel Has the consumer requested to cancel the async operation? + * + * @note Producer interface + * + * @note Blocking behavior - non-blocking + * + * @see xrt_future::cancel + */ + xrt_result_t (*is_cancel_requested)(const struct xrt_future *xft, bool *out_request_cancel); + + /*! + * @brief Signals that the asynchronous operation has completed and sets the future’s result. + * + * @param[in] xft The future. + * @param[in] ft_result the result of an async operation associated with @ref xft. + * + * @note Producer interface + * + * @note Blocking behavior - Non-blocking, may briefly block acquiring an internal mutex to check/consume state + * + * @note Differs from OpenXR future/async complete functions as those are used to only get the results of + * a future once the async-operation has finished where as this callback is used by async operation + * to mark/signal completion and return a result + * + * @note Similar to std::promise::set_value + * + * @see xrt_future::get_result, xrt_future_result, xrt_future_value + */ + xrt_result_t (*complete)(struct xrt_future *xft, const struct xrt_future_result *ft_result); +}; + +/*! + * Update the reference counts on xrt_future(s). + * + * @param[in,out] dst Pointer to a object reference: if the object reference is + * non-null will decrement its counter. The reference that + * @p dst points to will be set to @p src. + * @param[in] src New object for @p dst to refer to (may be null). + * If non-null, will have its refcount increased. + * @ingroup xrt_iface + * @relates xrt_future + */ +static inline void +xrt_future_reference(struct xrt_future **dst, struct xrt_future *src) +{ + struct xrt_future *old_dst = *dst; + + if (old_dst == src) { + return; + } + + if (src) { + xrt_reference_inc(&src->reference); + } + + *dst = src; + + if (old_dst) { + if (xrt_reference_dec_and_is_zero(&old_dst->reference)) { + assert(old_dst->destroy); + old_dst->destroy(old_dst); + } + } +} + +/*! + * Helper function for @ref xrt_future::get_result. + * + * @copydoc xrt_future::get_result + * + * @public @memberof xrt_future + */ +static inline xrt_result_t +xrt_future_get_result(const struct xrt_future *xft, struct xrt_future_result *out_result) +{ + assert(xft && xft->get_result); + return xft->get_result(xft, out_result); +} + +/*! + * Helper function for @ref xrt_future::get_state. + * + * @copydoc xrt_future::get_state + * + * @public @memberof xrt_future + */ +static inline xrt_result_t +xrt_future_get_state(const struct xrt_future *xft, enum xrt_future_state *out_state) +{ + assert(xft && xft->get_state); + return xft->get_state(xft, out_state); +} + +/*! + * Helper function for @ref xrt_future::cancel. + * + * @copydoc xrt_future::cancel + * + * @public @memberof xrt_future + */ +static inline xrt_result_t +xrt_future_cancel(struct xrt_future *xft) +{ + assert(xft && xft->cancel); + return xft->cancel(xft); +} + +/*! + * Helper function for @ref xrt_future::wait. + * + * @copydoc xrt_future::wait + * + * @public @memberof xrt_future + */ +static inline xrt_result_t +xrt_future_wait(struct xrt_future *xft, int64_t timeout) +{ + assert(xft && xft->wait); + return xft->wait(xft, timeout); +} + +/*! + * Helper function for @ref xrt_future::is_cancel_requested. + * + * @copydoc xrt_future::is_cancel_requested + * + * @public @memberof xrt_future + */ +static inline xrt_result_t +xrt_future_is_cancel_requested(const struct xrt_future *xft, bool *out_request_cancel) +{ + assert(xft && xft->is_cancel_requested); + return xft->is_cancel_requested(xft, out_request_cancel); +} + +/*! + * Helper function for @ref xrt_future::complete. + * + * @copydoc xrt_future::complete + * + * @public @memberof xrt_future + */ +static inline xrt_result_t +xrt_future_complete(struct xrt_future *xft, const struct xrt_future_result *ft_result) +{ + assert(xft && xft->complete); + return xft->complete(xft, ft_result); +} + +#ifdef __cplusplus +} +#endif diff --git a/src/xrt/include/xrt/xrt_future_value.h b/src/xrt/include/xrt/xrt_future_value.h new file mode 100644 index 000000000..bcd9d768d --- /dev/null +++ b/src/xrt/include/xrt/xrt_future_value.h @@ -0,0 +1,116 @@ +// Copyright 2025, Collabora, Ltd. +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief Variant/algebraic data-type for holding the values of xrt_futures + * @author Korcan Hussein + * + * @see xrt_future, xrt_future_result + * + * @ingroup xrt_iface + */ +#pragma once + +#include "xrt/xrt_compiler.h" +#include "xrt/xrt_defines.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// X-Macro type definitions: typename/prefix, type, member, user-data +#define XRT_FUTURE_VALUE_TYPES_WITH(_, P) \ + _(UINT64, uint64_t, uint64_, P) \ + _(INT64, int64_t, int64_, P) + +#define XRT_FUTURE_VALUE_WRAP_MACRO(N, T, M, X) X(N, T, M) + +#define XRT_FUTURE_VALUE_TYPES(X) XRT_FUTURE_VALUE_TYPES_WITH(XRT_FUTURE_VALUE_WRAP_MACRO, X) + +typedef enum xrt_future_value_type +{ + XRT_FUTURE_VALUE_TYPE_NONE, + +#define X_ENUM_ENTRY(TYPE_NAME, T, M) XRT_FUTURE_VALUE_TYPE_##TYPE_NAME, + XRT_FUTURE_VALUE_TYPES(X_ENUM_ENTRY) +#undef X_ENUM_ENTRY + + // clang-format off + XRT_FUTURE_VALUE_TYPE_LIST_END, + // clang-format on + XRT_FUTURE_VALUE_TYPE_COUNT = XRT_FUTURE_VALUE_TYPE_LIST_END - 1, +} xrt_future_value_type_t; + +struct xrt_future_value +{ + union { +#define X_MEMBER_ENTRY(N, TYPE, MEMBER) TYPE MEMBER; + XRT_FUTURE_VALUE_TYPES(X_MEMBER_ENTRY) +#undef X_MEMBER_ENTRY + }; + XRT_ALIGNAS(8) xrt_future_value_type_t type; +}; + +static inline bool +xrt_future_value_is_valid(const struct xrt_future_value *xfv) +{ + return xfv && xfv->type != XRT_FUTURE_VALUE_TYPE_NONE; +} + +#define XRT_FUTURE_VALUE_MAKE(TYPE_NAME, MEMBER, VALUE) \ + XRT_C11_COMPOUND(struct xrt_future_value) \ + { \ + .MEMBER = VALUE, .type = XRT_FUTURE_VALUE_TYPE_##TYPE_NAME, \ + } + +#define XRT_NULL_FUTURE_VALUE XRT_FUTURE_VALUE_MAKE(NONE, uint64_, 0) + +static inline struct xrt_future_value +xrt_future_value_make_none(const void *ignore) +{ + (void)ignore; + return XRT_NULL_FUTURE_VALUE; +} + +#define X_MAKE_CONS_FN(TYPE_NAME, TYPE, MEMBER) \ + static inline struct xrt_future_value xrt_future_value_make_##MEMBER(TYPE value) \ + { \ + return XRT_FUTURE_VALUE_MAKE(TYPE_NAME, MEMBER, value); \ + } \ + \ + static inline struct xrt_future_value xrt_future_value_make_##MEMBER##_ptr(const TYPE *value) \ + { \ + assert(value != NULL); \ + return XRT_FUTURE_VALUE_MAKE(TYPE_NAME, MEMBER, (*value)); \ + } +XRT_FUTURE_VALUE_TYPES(X_MAKE_CONS_FN) +#undef X_MAKE_CONS_FN +#ifdef __cplusplus +} +#endif + +#ifdef __cplusplus +extern "C++" { +#define X_MAKE_CONS_FN(TYPE_NAME, TYPE, MEMBER) \ + inline struct xrt_future_value xrt_future_value_make(const TYPE &value) \ + { \ + return XRT_FUTURE_VALUE_MAKE(TYPE_NAME, MEMBER, value); \ + } +XRT_FUTURE_VALUE_TYPES(X_MAKE_CONS_FN) +#undef X_MAKE_CONS_FN +} +#else +// clang-format off +#define XRT_FUTURE_VALUE_TYPECASE(TYPE_NAME, TYPE, MEMBER, P) \ + TYPE: xrt_future_value_make_##MEMBER, \ + const TYPE*: xrt_future_value_make_##MEMBER##_ptr, + +#define xrt_future_value_make(VALUE) \ + _Generic((VALUE), \ + XRT_FUTURE_VALUE_TYPES_WITH(XRT_FUTURE_VALUE_TYPECASE, _) \ + default: xrt_future_value_make_none \ + )(VALUE) +// clang-format on +#endif diff --git a/src/xrt/include/xrt/xrt_instance.h b/src/xrt/include/xrt/xrt_instance.h index f5ff055ab..1e10d6204 100644 --- a/src/xrt/include/xrt/xrt_instance.h +++ b/src/xrt/include/xrt/xrt_instance.h @@ -74,6 +74,7 @@ struct xrt_application_info bool ext_hand_tracking_enabled; bool ext_hand_tracking_data_source_enabled; bool ext_eye_gaze_interaction_enabled; + bool ext_future_enabled; bool ext_hand_interaction_enabled; bool htc_facial_tracking_enabled; bool fb_body_tracking_enabled; diff --git a/src/xrt/ipc/CMakeLists.txt b/src/xrt/ipc/CMakeLists.txt index dc1f90be8..5f9e59dd7 100644 --- a/src/xrt/ipc/CMakeLists.txt +++ b/src/xrt/ipc/CMakeLists.txt @@ -64,6 +64,7 @@ add_library( client/ipc_client_compositor.c client/ipc_client_connection.c client/ipc_client_device.c + client/ipc_client_future.c client/ipc_client_hmd.c client/ipc_client_instance.c client/ipc_client_session.c diff --git a/src/xrt/ipc/client/ipc_client.h b/src/xrt/ipc/client/ipc_client.h index 8122a5c0b..17a74eea6 100644 --- a/src/xrt/ipc/client/ipc_client.h +++ b/src/xrt/ipc/client/ipc_client.h @@ -130,3 +130,6 @@ ipc_client_system_devices_create(struct ipc_connection *ipc_c); struct xrt_session * ipc_client_session_create(struct ipc_connection *ipc_c); + +struct xrt_future * +ipc_client_future_create(struct ipc_connection *ipc_c, uint32_t future_id); diff --git a/src/xrt/ipc/client/ipc_client_future.c b/src/xrt/ipc/client/ipc_client_future.c new file mode 100644 index 000000000..97ac7c6d9 --- /dev/null +++ b/src/xrt/ipc/client/ipc_client_future.c @@ -0,0 +1,117 @@ +// Copyright 2025, Collabora, Ltd. +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief IPC Client futures. + * @author Korcan Hussein + * @ingroup ipc_client + */ + +#include "xrt/xrt_defines.h" + +#include "shared/ipc_message_channel.h" + +#include "client/ipc_client.h" +#include "client/ipc_client_connection.h" + +#include "ipc_client_generated.h" + +struct ipc_client_future +{ + struct xrt_future base; + struct ipc_connection *ipc_c; + uint32_t id; +}; + +static inline struct ipc_client_future * +ipc_client_future(struct xrt_future *xft) +{ + return (struct ipc_client_future *)xft; +} + +static inline const struct ipc_client_future * +const_ipc_client_future(const struct xrt_future *xft) +{ + return (const struct ipc_client_future *)xft; +} + +static xrt_result_t +get_state(const struct xrt_future *xft, enum xrt_future_state *out_state) +{ + if (out_state == NULL) { + return XRT_ERROR_INVALID_ARGUMENT; + } + const struct ipc_client_future *ipc_xft = const_ipc_client_future(xft); + const xrt_result_t xret = ipc_call_future_get_state(ipc_xft->ipc_c, ipc_xft->id, out_state); + IPC_CHK_ALWAYS_RET(ipc_xft->ipc_c, xret, "ipc_call_future_get_state"); +} + +static xrt_result_t +get_result(const struct xrt_future *xft, struct xrt_future_result *out_ft_result) +{ + if (out_ft_result == NULL) { + return XRT_ERROR_INVALID_ARGUMENT; + } + const struct ipc_client_future *ipc_xft = const_ipc_client_future(xft); + const xrt_result_t xret = ipc_call_future_get_result(ipc_xft->ipc_c, ipc_xft->id, out_ft_result); + IPC_CHK_ALWAYS_RET(ipc_xft->ipc_c, xret, "ipc_call_future_get_result"); +} + +static xrt_result_t +cancel(struct xrt_future *xft) +{ + struct ipc_client_future *ipc_xft = ipc_client_future(xft); + const xrt_result_t xret = ipc_call_future_cancel(ipc_xft->ipc_c, ipc_xft->id); + IPC_CHK_ALWAYS_RET(ipc_xft->ipc_c, xret, "ipc_call_future_cancel"); +} + +static xrt_result_t +wait(struct xrt_future *xft, int64_t timeout_ns) +{ + return XRT_ERROR_NOT_IMPLEMENTED; +} + +static xrt_result_t +is_cancel_requested(const struct xrt_future *xft, bool *out_request_cancel) +{ + return XRT_ERROR_NOT_IMPLEMENTED; +} + +static xrt_result_t +complete(struct xrt_future *xft, const struct xrt_future_result *ft_result) +{ + return XRT_ERROR_NOT_IMPLEMENTED; +} + +static void +destroy(struct xrt_future *xft) +{ + struct ipc_client_future *ipc_xft = ipc_client_future(xft); + if (ipc_xft == NULL) { + return; + } + const xrt_result_t xret = ipc_call_future_destroy(ipc_xft->ipc_c, ipc_xft->id); + IPC_CHK_ONLY_PRINT(ipc_xft->ipc_c, xret, "ipc_call_future_destroy"); + + free(ipc_xft); +} + +struct xrt_future * +ipc_client_future_create(struct ipc_connection *ipc_c, uint32_t future_id) +{ + assert(ipc_c != NULL); + struct ipc_client_future *icft = U_TYPED_CALLOC(struct ipc_client_future); + struct xrt_future *xft = &icft->base; + xft->destroy = destroy; + xft->get_state = get_state; + xft->get_result = get_result; + xft->cancel = cancel; + xft->wait = wait; + xft->is_cancel_requested = is_cancel_requested; + xft->complete = complete; + xft->reference.count = 1; + icft->id = future_id; + icft->ipc_c = ipc_c; + + return xft; +} diff --git a/src/xrt/ipc/server/ipc_server.h b/src/xrt/ipc/server/ipc_server.h index 81a682233..40357013b 100644 --- a/src/xrt/ipc/server/ipc_server.h +++ b/src/xrt/ipc/server/ipc_server.h @@ -58,6 +58,7 @@ extern "C" { #define IPC_MAX_CLIENT_SEMAPHORES 8 #define IPC_MAX_CLIENT_SWAPCHAINS (XRT_MAX_LAYERS * 2) #define IPC_MAX_CLIENT_SPACES 128 +#define IPC_MAX_CLIENT_FUTURES 128 struct xrt_instance; struct xrt_compositor; @@ -113,6 +114,9 @@ struct ipc_client_state //! Ptrs to the semaphores. struct xrt_compositor_semaphore *xcsems[IPC_MAX_CLIENT_SEMAPHORES]; + //! Ptrs to the futures. + struct xrt_future *xfts[IPC_MAX_CLIENT_FUTURES]; + struct { uint32_t root; diff --git a/src/xrt/ipc/server/ipc_server_handler.c b/src/xrt/ipc/server/ipc_server_handler.c index a95ca99fe..47ce3a709 100644 --- a/src/xrt/ipc/server/ipc_server_handler.c +++ b/src/xrt/ipc/server/ipc_server_handler.c @@ -285,6 +285,55 @@ create_localspace(volatile struct ipc_client_state *ics, uint32_t *out_local_id, return XRT_SUCCESS; } +XRT_MAYBE_UNUSED xrt_result_t +get_new_future_id(volatile struct ipc_client_state *ics, uint32_t *out_id) +{ + // Our handle is just the index for now. + uint32_t index = 0; + for (; index < IPC_MAX_CLIENT_FUTURES; ++index) { + if (ics->xfts[index] == NULL) { + break; + } + } + + if (index >= IPC_MAX_CLIENT_FUTURES) { + IPC_ERROR(ics->server, "Too many futures!"); + return XRT_ERROR_IPC_FAILURE; + } + + *out_id = index; + + return XRT_SUCCESS; +} + +static inline xrt_result_t +validate_future_id(volatile struct ipc_client_state *ics, uint32_t future_id, struct xrt_future **out_xft) +{ + if (future_id >= IPC_MAX_CLIENT_FUTURES) { + return XRT_ERROR_IPC_FAILURE; + } + + if (ics->xfts[future_id] == NULL) { + return XRT_ERROR_IPC_FAILURE; + } + + *out_xft = (struct xrt_future *)ics->xfts[future_id]; + return (*out_xft != NULL) ? XRT_SUCCESS : XRT_ERROR_ALLOCATION; +} + +static inline xrt_result_t +release_future(volatile struct ipc_client_state *ics, uint32_t future_id) +{ + struct xrt_future *xft = NULL; + xrt_result_t xret = validate_future_id(ics, future_id, &xft); + if (xret != XRT_SUCCESS) { + return xret; + } + xrt_future_reference(&xft, NULL); + ics->xfts[future_id] = NULL; + return XRT_SUCCESS; +} + /* * * Handle functions. @@ -331,6 +380,7 @@ ipc_handle_instance_describe_client(volatile struct ipc_client_state *ics, EXT(ext_hand_tracking_enabled); EXT(ext_hand_tracking_data_source_enabled); EXT(ext_eye_gaze_interaction_enabled); + EXT(ext_future_enabled); EXT(ext_hand_interaction_enabled); EXT(htc_facial_tracking_enabled); EXT(fb_body_tracking_enabled); @@ -428,6 +478,7 @@ ipc_handle_session_begin(volatile struct ipc_client_state *ics) .ext_hand_tracking_enabled = ics->client_state.info.ext_hand_tracking_enabled, .ext_hand_tracking_data_source_enabled = ics->client_state.info.ext_hand_tracking_data_source_enabled, .ext_eye_gaze_interaction_enabled = ics->client_state.info.ext_eye_gaze_interaction_enabled, + .ext_future_enabled = ics->client_state.info.ext_future_enabled, .ext_hand_interaction_enabled = ics->client_state.info.ext_hand_interaction_enabled, .htc_facial_tracking_enabled = ics->client_state.info.htc_facial_tracking_enabled, .fb_body_tracking_enabled = ics->client_state.info.fb_body_tracking_enabled, @@ -2580,3 +2631,44 @@ ipc_handle_device_set_brightness(volatile struct ipc_client_state *ics, uint32_t return xrt_device_set_brightness(xdev, brightness, relative); } + +xrt_result_t +ipc_handle_future_get_state(volatile struct ipc_client_state *ics, uint32_t future_id, enum xrt_future_state *out_state) +{ + struct xrt_future *xft = NULL; + xrt_result_t xret = validate_future_id(ics, future_id, &xft); + if (xret != XRT_SUCCESS) { + return xret; + } + return xrt_future_get_state(xft, out_state); +} + +xrt_result_t +ipc_handle_future_get_result(volatile struct ipc_client_state *ics, + uint32_t future_id, + struct xrt_future_result *out_ft_result) +{ + struct xrt_future *xft = NULL; + xrt_result_t xret = validate_future_id(ics, future_id, &xft); + if (xret != XRT_SUCCESS) { + return xret; + } + return xrt_future_get_result(xft, out_ft_result); +} + +xrt_result_t +ipc_handle_future_cancel(volatile struct ipc_client_state *ics, uint32_t future_id) +{ + struct xrt_future *xft = NULL; + xrt_result_t xret = validate_future_id(ics, future_id, &xft); + if (xret != XRT_SUCCESS) { + return xret; + } + return xrt_future_cancel(xft); +} + +xrt_result_t +ipc_handle_future_destroy(volatile struct ipc_client_state *ics, uint32_t future_id) +{ + return release_future(ics, future_id); +} diff --git a/src/xrt/ipc/server/ipc_server_per_client_thread.c b/src/xrt/ipc/server/ipc_server_per_client_thread.c index c51ba0149..f2f447b26 100644 --- a/src/xrt/ipc/server/ipc_server_per_client_thread.c +++ b/src/xrt/ipc/server/ipc_server_per_client_thread.c @@ -389,6 +389,12 @@ ipc_server_client_destroy_session_and_compositor(volatile struct ipc_client_stat IPC_TRACE(ics->server, "Destroyed compositor semaphore %d.", j); } + for (uint32_t j = 0; j < IPC_MAX_CLIENT_FUTURES; j++) { + // Drop our reference, does NULL checking. Cast away volatile. + xrt_future_reference((struct xrt_future **)&ics->xfts[j], NULL); + IPC_TRACE(ics->server, "Destroyed future %d.", j); + } + os_mutex_unlock(&ics->server->global_state.lock); // Cast away volatile. diff --git a/src/xrt/ipc/shared/ipc_protocol.h b/src/xrt/ipc/shared/ipc_protocol.h index fad28ebe7..8c9251de0 100644 --- a/src/xrt/ipc/shared/ipc_protocol.h +++ b/src/xrt/ipc/shared/ipc_protocol.h @@ -16,6 +16,7 @@ #include "xrt/xrt_compositor.h" #include "xrt/xrt_results.h" #include "xrt/xrt_defines.h" +#include "xrt/xrt_future.h" #include "xrt/xrt_system.h" #include "xrt/xrt_session.h" #include "xrt/xrt_instance.h" diff --git a/src/xrt/ipc/shared/proto.json b/src/xrt/ipc/shared/proto.json index 8a155c88f..5b8cb3d52 100644 --- a/src/xrt/ipc/shared/proto.json +++ b/src/xrt/ipc/shared/proto.json @@ -635,5 +635,35 @@ {"name": "brightness", "type": "float"}, {"name": "relative", "type": "bool"} ] + }, + + "future_get_state": { + "in": [ + {"name": "future_id", "type": "uint32_t"} + ], + "out": [ + {"name": "out_state", "type": "enum xrt_future_state"} + ] + }, + + "future_cancel": { + "in": [ + {"name": "future_id", "type": "uint32_t"} + ] + }, + + "future_destroy": { + "in": [ + {"name": "future_id", "type": "uint32_t"} + ] + }, + + "future_get_result": { + "in": [ + {"name": "future_id", "type": "uint32_t"} + ], + "out": [ + {"name": "out_ft_result", "type": "struct xrt_future_result"} + ] } } diff --git a/src/xrt/state_trackers/oxr/CMakeLists.txt b/src/xrt/state_trackers/oxr/CMakeLists.txt index e26b3759b..6f9f5428e 100644 --- a/src/xrt/state_trackers/oxr/CMakeLists.txt +++ b/src/xrt/state_trackers/oxr/CMakeLists.txt @@ -114,6 +114,10 @@ if(XRT_FEATURE_OPENXR_FACE_TRACKING2_FB) target_sources(st_oxr PRIVATE oxr_api_face_tracking2_fb.c oxr_face_tracking2_fb.c) endif() +if(XRT_FEATURE_OPENXR_FUTURE_EXT) + target_sources(st_oxr PRIVATE oxr_api_future.c oxr_future.c) +endif() + target_link_libraries( st_oxr PRIVATE diff --git a/src/xrt/state_trackers/oxr/oxr_api_funcs.h b/src/xrt/state_trackers/oxr/oxr_api_funcs.h index 471bc4936..59520992e 100644 --- a/src/xrt/state_trackers/oxr/oxr_api_funcs.h +++ b/src/xrt/state_trackers/oxr/oxr_api_funcs.h @@ -765,6 +765,20 @@ oxr_xrSuggestBodyTrackingCalibrationOverrideMETA(XrBodyTrackerFB bodyTracker, const XrBodyTrackingCalibrationInfoMETA *calibrationInfo); #endif +/* + * + * oxr_api_future.c + * + */ + +#ifdef OXR_HAVE_EXT_future +XRAPI_ATTR XrResult XRAPI_CALL +oxr_xrPollFutureEXT(XrInstance instance, const XrFuturePollInfoEXT *pollInfo, XrFuturePollResultEXT *pollResult); + +XRAPI_ATTR XrResult XRAPI_CALL +oxr_xrCancelFutureEXT(XrInstance instance, const XrFutureCancelInfoEXT *cancelInfo); +#endif + /*! * @} */ diff --git a/src/xrt/state_trackers/oxr/oxr_api_future.c b/src/xrt/state_trackers/oxr/oxr_api_future.c new file mode 100644 index 000000000..58e4fbb59 --- /dev/null +++ b/src/xrt/state_trackers/oxr/oxr_api_future.c @@ -0,0 +1,43 @@ +// Copyright 2025, Collabora, Ltd. +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief future related API entrypoint functions. + * @author Korcan Hussein + * @ingroup oxr_api + */ +#include "util/u_trace_marker.h" + +#include "oxr_objects.h" +#include "oxr_logger.h" + +#include "oxr_api_funcs.h" +#include "oxr_api_verify.h" +#include "oxr_handle.h" +#include "oxr_defines.h" + +XRAPI_ATTR XrResult XRAPI_CALL +oxr_xrPollFutureEXT(XrInstance instance, const XrFuturePollInfoEXT *pollInfo, XrFuturePollResultEXT *pollResult) +{ + struct oxr_logger log; + struct oxr_instance *inst = NULL; + struct oxr_future_ext *oxr_future = NULL; + OXR_VERIFY_INSTANCE_AND_INIT_LOG(&log, instance, inst, "xrPollFutureEXT"); + OXR_VERIFY_EXTENSION(&log, inst, EXT_future); + OXR_VERIFY_ARG_TYPE_AND_NOT_NULL(&log, pollInfo, XR_TYPE_FUTURE_POLL_INFO_EXT); + OXR_VERIFY_ARG_TYPE_AND_NOT_NULL(&log, pollResult, XR_TYPE_FUTURE_POLL_RESULT_EXT); + OXR_VERIFY_FUTURE_AND_INIT_LOG(&log, pollInfo->future, oxr_future, "xrPollFutureEXT"); + return oxr_future_ext_poll(&log, oxr_future, pollResult); +} + +XRAPI_ATTR XrResult XRAPI_CALL +oxr_xrCancelFutureEXT(XrInstance instance, const XrFutureCancelInfoEXT *cancelInfo) +{ + struct oxr_logger log; + struct oxr_instance *inst = NULL; + struct oxr_future_ext *oxr_future = NULL; + OXR_VERIFY_INSTANCE_AND_INIT_LOG(&log, instance, inst, "xrCancelFutureEXT"); + OXR_VERIFY_EXTENSION(&log, inst, EXT_future); + OXR_VERIFY_FUTURE_AND_INIT_LOG(&log, cancelInfo->future, oxr_future, "xrCancelFutureEXT"); + return oxr_future_ext_cancel(&log, oxr_future); +} diff --git a/src/xrt/state_trackers/oxr/oxr_api_negotiate.c b/src/xrt/state_trackers/oxr/oxr_api_negotiate.c index ecd6502e1..bf0da596a 100644 --- a/src/xrt/state_trackers/oxr/oxr_api_negotiate.c +++ b/src/xrt/state_trackers/oxr/oxr_api_negotiate.c @@ -412,7 +412,12 @@ handle_non_null(struct oxr_instance *inst, struct oxr_logger *log, const char *n #ifdef OXR_HAVE_META_body_tracking_calibration ENTRY_IF_EXT(xrResetBodyTrackingCalibrationMETA, META_body_tracking_calibration); ENTRY_IF_EXT(xrSuggestBodyTrackingCalibrationOverrideMETA, META_body_tracking_calibration); -#endif +#endif // OXR_HAVE_META_body_tracking_calibration + +#ifdef OXR_HAVE_EXT_future + ENTRY_IF_EXT(xrPollFutureEXT, EXT_future); + ENTRY_IF_EXT(xrCancelFutureEXT, EXT_future); +#endif // OXR_HAVE_EXT_future #ifdef OXR_HAVE_KHR_extended_struct_name_lengths ENTRY_IF_EXT(xrStructureTypeToString2KHR, KHR_extended_struct_name_lengths); diff --git a/src/xrt/state_trackers/oxr/oxr_api_verify.h b/src/xrt/state_trackers/oxr/oxr_api_verify.h index 82116ae96..d46d3f4a3 100644 --- a/src/xrt/state_trackers/oxr/oxr_api_verify.h +++ b/src/xrt/state_trackers/oxr/oxr_api_verify.h @@ -92,6 +92,9 @@ struct oxr_subaction_paths; OXR_VERIFY_AND_SET_AND_INIT(log, thing, new_thing, oxr_xdev_list, XDEVLIST, name, new_thing->sess->sys->inst) #define OXR_VERIFY_PLANE_DETECTOR_AND_INIT_LOG(log, thing, new_thing, name) \ OXR_VERIFY_AND_SET_AND_INIT(log, thing, new_thing, oxr_plane_detector_ext, PLANEDET, name, new_thing->sess->sys->inst) +#define OXR_VERIFY_FUTURE_AND_INIT_LOG(log, thing, new_thing, name) \ + OXR_VERIFY_AND_SET_AND_INIT(log, thing, new_thing, oxr_future_ext, FUTURE, name, new_thing->inst); \ + OXR_VERIFY_FUTURE_VALID(log, new_thing) // clang-format on #define OXR_VERIFY_INSTANCE_NOT_NULL(log, arg, new_arg) OXR_VERIFY_SET(log, arg, new_arg, oxr_instance, INSTANCE); @@ -103,6 +106,7 @@ struct oxr_subaction_paths; #define OXR_VERIFY_ACTIONSET_NOT_NULL(log, arg, new_arg) OXR_VERIFY_SET(log, arg, new_arg, oxr_action_set, ACTIONSET); #define OXR_VERIFY_XDEVLIST_NOT_NULL(log, arg, new_arg) OXR_VERIFY_SET(log, arg, new_arg, oxr_xdev_list, XDEVLIST); +#define OXR_VERIFY_FUTURE_NOT_NULL(log, arg, new_arg) OXR_VERIFY_SET(log, arg, new_arg, oxr_future_ext, FUTURE); /*! * Checks if a required extension is enabled. * @@ -325,6 +329,13 @@ struct oxr_subaction_paths; } \ } while (false) +#define OXR_VERIFY_FUTURE_VALID(LOG, OXR_FT) \ + do { \ + if (OXR_FT->xft == NULL) { \ + return oxr_error(LOG, XR_ERROR_FUTURE_INVALID_EXT, "future is not valid"); \ + } \ + } while (false) + /* * diff --git a/src/xrt/state_trackers/oxr/oxr_defines.h b/src/xrt/state_trackers/oxr/oxr_defines.h index 65be4450b..2d93fb699 100644 --- a/src/xrt/state_trackers/oxr/oxr_defines.h +++ b/src/xrt/state_trackers/oxr/oxr_defines.h @@ -34,6 +34,8 @@ #define OXR_XR_DEBUG_XDEVLIST (*(uint64_t *)"oxrxdli\0") // plane detection #define OXR_XR_DEBUG_PLANEDET (*(uint64_t *)"oxrplan\0") +// futures +#define OXR_XR_DEBUG_FUTURE (*(uint64_t *)"oxrfutr\0") // clang-format on /*! diff --git a/src/xrt/state_trackers/oxr/oxr_extension_support.h b/src/xrt/state_trackers/oxr/oxr_extension_support.h index f84bf2d84..f1b0b3882 100644 --- a/src/xrt/state_trackers/oxr/oxr_extension_support.h +++ b/src/xrt/state_trackers/oxr/oxr_extension_support.h @@ -350,6 +350,17 @@ #endif +/* + * XR_EXT_future + */ +#if defined(XR_EXT_future) && defined(XRT_FEATURE_OPENXR_FUTURE_EXT) +#define OXR_HAVE_EXT_future +#define OXR_EXTENSION_SUPPORT_EXT_future(_) _(EXT_future, EXT_FUTURE) +#else +#define OXR_EXTENSION_SUPPORT_EXT_future(_) +#endif + + /* * XR_EXT_hand_interaction */ @@ -903,6 +914,7 @@ OXR_EXTENSION_SUPPORT_EXT_debug_utils(_) \ OXR_EXTENSION_SUPPORT_EXT_dpad_binding(_) \ OXR_EXTENSION_SUPPORT_EXT_eye_gaze_interaction(_) \ + OXR_EXTENSION_SUPPORT_EXT_future(_) \ OXR_EXTENSION_SUPPORT_EXT_hand_interaction(_) \ OXR_EXTENSION_SUPPORT_EXT_hand_tracking(_) \ OXR_EXTENSION_SUPPORT_EXT_hand_tracking_data_source(_) \ diff --git a/src/xrt/state_trackers/oxr/oxr_future.c b/src/xrt/state_trackers/oxr/oxr_future.c new file mode 100644 index 000000000..3018721ae --- /dev/null +++ b/src/xrt/state_trackers/oxr/oxr_future.c @@ -0,0 +1,94 @@ +// Copyright 2025, Collabora, Ltd. +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief future related functions. + * @author Korcan Hussein + * @ingroup oxr_main + */ +#include "oxr_objects.h" +#include "oxr_logger.h" +#include "oxr_handle.h" +#include "oxr_xret.h" + +static inline XrFutureStateEXT +oxr_to_XrFutureStateEXT(const xrt_future_state_t fts) +{ + switch (fts) { + case XRT_FUTURE_STATE_PENDING: return XR_FUTURE_STATE_PENDING_EXT; + case XRT_FUTURE_STATE_READY: return XR_FUTURE_STATE_READY_EXT; + default: return XR_FUTURE_STATE_MAX_ENUM_EXT; + } +} + +XrResult +oxr_future_invalidate(struct oxr_logger *log, struct oxr_future_ext *oxr_future) +{ + (void)log; + if (oxr_future && oxr_future->xft) { + xrt_future_reference(&oxr_future->xft, NULL); + assert(oxr_future->xft == NULL); + } + return XR_SUCCESS; +} + +XrResult +oxr_future_ext_poll(struct oxr_logger *log, const struct oxr_future_ext *oxr_future, XrFuturePollResultEXT *pollResult) +{ + assert(log && oxr_future && oxr_future->xft && pollResult); + xrt_future_state_t fts; + const xrt_result_t xres = xrt_future_get_state(oxr_future->xft, &fts); + OXR_CHECK_XRET(log, oxr_future->sess, xres, oxr_future_ext_poll); + pollResult->state = oxr_to_XrFutureStateEXT(fts); + return XR_SUCCESS; +} + +XrResult +oxr_future_ext_cancel(struct oxr_logger *log, struct oxr_future_ext *oxr_future) +{ + assert(log && oxr_future && oxr_future->xft); + const xrt_result_t xres = xrt_future_cancel(oxr_future->xft); + OXR_CHECK_XRET(log, oxr_future->sess, xres, oxr_future_ext_cancel); + return oxr_future_invalidate(log, oxr_future); +} + +XrResult +oxr_future_ext_complete(struct oxr_logger *log, + struct oxr_future_ext *oxr_future, + struct xrt_future_result *out_ft_result) +{ + struct oxr_session *sess = oxr_future->sess; + const xrt_result_t xret = xrt_future_get_result(oxr_future->xft, out_ft_result); + OXR_CHECK_XRET(log, sess, xret, oxr_future_ext_complete); + if (xret == XRT_ERROR_FUTURE_RESULT_NOT_READY) { + return oxr_error(log, XR_ERROR_FUTURE_PENDING_EXT, "Call to oxr_future_ext_complete failed"); + } + return oxr_future_invalidate(log, oxr_future); +} + +static XrResult +oxr_future_ext_destroy(struct oxr_logger *log, struct oxr_handle_base *hb) +{ + struct oxr_future_ext *future_ext = (struct oxr_future_ext *)hb; + if (future_ext && future_ext->xft) { + oxr_future_ext_cancel(log, future_ext); + } + free(future_ext); + return XR_SUCCESS; +} + +XrResult +oxr_future_create(struct oxr_logger *log, + struct oxr_session *sess, + struct xrt_future *xft, + struct oxr_handle_base *parent_handle, + struct oxr_future_ext **out_oxr_future_ext) +{ + struct oxr_future_ext *new_future = NULL; + OXR_ALLOCATE_HANDLE_OR_RETURN(log, new_future, OXR_XR_DEBUG_FUTURE, oxr_future_ext_destroy, parent_handle); + new_future->sess = sess; + new_future->inst = sess->sys->inst; + new_future->xft = xft; + *out_oxr_future_ext = new_future; + return XR_SUCCESS; +} diff --git a/src/xrt/state_trackers/oxr/oxr_instance.c b/src/xrt/state_trackers/oxr/oxr_instance.c index 3604cbbd2..247b8ad0c 100644 --- a/src/xrt/state_trackers/oxr/oxr_instance.c +++ b/src/xrt/state_trackers/oxr/oxr_instance.c @@ -310,6 +310,9 @@ oxr_instance_create(struct oxr_logger *log, #ifdef OXR_HAVE_EXT_eye_gaze_interaction .ext_eye_gaze_interaction_enabled = extensions->EXT_eye_gaze_interaction, #endif +#ifdef OXR_HAVE_EXT_future + .ext_future_enabled = extensions->EXT_future, +#endif #ifdef OXR_HAVE_EXT_hand_interaction .ext_hand_interaction_enabled = extensions->EXT_hand_interaction, #endif diff --git a/src/xrt/state_trackers/oxr/oxr_objects.h b/src/xrt/state_trackers/oxr/oxr_objects.h index 518436c26..8a49e36e0 100644 --- a/src/xrt/state_trackers/oxr/oxr_objects.h +++ b/src/xrt/state_trackers/oxr/oxr_objects.h @@ -14,6 +14,7 @@ #include "xrt/xrt_space.h" #include "xrt/xrt_limits.h" #include "xrt/xrt_system.h" +#include "xrt/xrt_future.h" #include "xrt/xrt_device.h" #include "xrt/xrt_tracking.h" #include "xrt/xrt_compositor.h" @@ -2960,6 +2961,64 @@ struct oxr_plane_detector_ext }; #endif // OXR_HAVE_EXT_plane_detection +#ifdef OXR_HAVE_EXT_future +/*! + * EXT futures. + * + * Parent type/handle is @ref oxr_instance + * + * @obj{XrFutureEXT} + * @extends oxr_handle_base + */ +struct oxr_future_ext +{ + //! Common structure for things referred to by OpenXR handles. + struct oxr_handle_base handle; + + //! (weak) reference to instance (may or not be a direct parent handle") + struct oxr_instance *inst; + + //! Owning session. + struct oxr_session *sess; + + //! xrt_future backing this future + struct xrt_future *xft; +}; + +/*! + * To go back to a OpenXR object. + * + * @relates oxr_future_ext + */ +static inline XrFutureEXT +oxr_future_ext_to_openxr(struct oxr_future_ext *future_ext) +{ + return XRT_CAST_PTR_TO_OXR_HANDLE(XrFutureEXT, future_ext); +} + +XrResult +oxr_future_create(struct oxr_logger *log, + struct oxr_session *sess, + struct xrt_future *xft, + struct oxr_handle_base *parent_handle, + struct oxr_future_ext **out_oxr_future_ext); + +XrResult +oxr_future_invalidate(struct oxr_logger *log, struct oxr_future_ext *oxr_future); + +XrResult +oxr_future_ext_poll(struct oxr_logger *log, const struct oxr_future_ext *oxr_future, XrFuturePollResultEXT *pollResult); + +XrResult +oxr_future_ext_cancel(struct oxr_logger *log, struct oxr_future_ext *oxr_future); + +XrResult +oxr_future_ext_complete(struct oxr_logger *log, + struct oxr_future_ext *oxr_future, + struct xrt_future_result *out_ft_result); + +#endif + #ifdef OXR_HAVE_EXT_user_presence XrResult oxr_event_push_XrEventDataUserPresenceChangedEXT(struct oxr_logger *log, struct oxr_session *sess, bool isUserPresent); diff --git a/src/xrt/state_trackers/oxr/oxr_session.c b/src/xrt/state_trackers/oxr/oxr_session.c index 347f511a1..54f11a83f 100644 --- a/src/xrt/state_trackers/oxr/oxr_session.c +++ b/src/xrt/state_trackers/oxr/oxr_session.c @@ -274,6 +274,9 @@ oxr_session_begin(struct oxr_logger *log, struct oxr_session *sess, const XrSess #ifdef OXR_HAVE_EXT_eye_gaze_interaction .ext_eye_gaze_interaction_enabled = extensions->EXT_eye_gaze_interaction, #endif +#ifdef OXR_HAVE_EXT_future + .ext_future_enabled = extensions->EXT_future, +#endif #ifdef OXR_HAVE_EXT_hand_interaction .ext_hand_interaction_enabled = extensions->EXT_hand_interaction, #endif