mirror of
https://gitlab.freedesktop.org/monado/monado.git
synced 2025-01-24 07:31:48 +00:00
576 lines
14 KiB
C
576 lines
14 KiB
C
|
// Copyright 2023, Collabora, Ltd.
|
||
|
// SPDX-License-Identifier: BSL-1.0
|
||
|
/*!
|
||
|
* @file
|
||
|
* @brief A implementation of the @ref xrt_space_overseer interface.
|
||
|
*
|
||
|
* @author Jakob Bornecrantz <jakob@collabora.com>
|
||
|
* @ingroup aux_util
|
||
|
*/
|
||
|
|
||
|
#include "xrt/xrt_space.h"
|
||
|
#include "xrt/xrt_device.h"
|
||
|
#include "xrt/xrt_tracking.h"
|
||
|
|
||
|
#include "math/m_space.h"
|
||
|
|
||
|
#include "util/u_misc.h"
|
||
|
#include "util/u_hashmap.h"
|
||
|
#include "util/u_logging.h"
|
||
|
#include "util/u_space_overseer.h"
|
||
|
|
||
|
#include <assert.h>
|
||
|
#include <pthread.h>
|
||
|
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
* Structs and defines.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
/*!
|
||
|
* Keeps track of what kind of space it is.
|
||
|
*/
|
||
|
enum u_space_type
|
||
|
{
|
||
|
U_SPACE_TYPE_NULL,
|
||
|
U_SPACE_TYPE_POSE,
|
||
|
U_SPACE_TYPE_OFFSET,
|
||
|
U_SPACE_TYPE_ROOT,
|
||
|
};
|
||
|
|
||
|
/*!
|
||
|
* Representing a single space, can be several ones. There should only be one
|
||
|
* root space per overseer.
|
||
|
*/
|
||
|
struct u_space
|
||
|
{
|
||
|
struct xrt_space base;
|
||
|
|
||
|
/*!
|
||
|
* The space this space is in.
|
||
|
*/
|
||
|
struct u_space *next;
|
||
|
|
||
|
/*!
|
||
|
* The type of the space.
|
||
|
*/
|
||
|
enum u_space_type type;
|
||
|
|
||
|
union {
|
||
|
struct
|
||
|
{
|
||
|
struct xrt_device *xdev;
|
||
|
enum xrt_input_name xname;
|
||
|
} pose;
|
||
|
|
||
|
struct
|
||
|
{
|
||
|
struct xrt_pose pose;
|
||
|
} offset;
|
||
|
};
|
||
|
};
|
||
|
|
||
|
/*!
|
||
|
* Default implementation of the xrt_space_overseer object.
|
||
|
*/
|
||
|
struct u_space_overseer
|
||
|
{
|
||
|
struct xrt_space_overseer base;
|
||
|
|
||
|
//! Main graph lock.
|
||
|
pthread_rwlock_t lock;
|
||
|
|
||
|
//! Map from xdev to space, each entry holds a reference.
|
||
|
struct u_hashmap_int *xdev_map;
|
||
|
};
|
||
|
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
* Helper functions.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
static inline struct u_space *
|
||
|
u_space(struct xrt_space *xs)
|
||
|
{
|
||
|
return (struct u_space *)xs;
|
||
|
}
|
||
|
|
||
|
static inline struct u_space_overseer *
|
||
|
u_space_overseer(struct xrt_space_overseer *xso)
|
||
|
{
|
||
|
return (struct u_space_overseer *)xso;
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
* A lot of code here uses u_space directly and need to change reference count
|
||
|
* so this helper is here to make that easier.
|
||
|
*/
|
||
|
static inline void
|
||
|
u_space_reference(struct u_space **dst, struct u_space *src)
|
||
|
{
|
||
|
struct u_space *old_dst = *dst;
|
||
|
|
||
|
if (old_dst == src) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (src) {
|
||
|
xrt_reference_inc(&src->base.reference);
|
||
|
}
|
||
|
|
||
|
*dst = src;
|
||
|
|
||
|
if (old_dst) {
|
||
|
if (xrt_reference_dec(&old_dst->base.reference)) {
|
||
|
old_dst->base.destroy(&old_dst->base);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
* Helper function when clearing a hashmap to also unreference a space.
|
||
|
*/
|
||
|
static void
|
||
|
hashmap_unreference_space_items(void *item, void *priv)
|
||
|
{
|
||
|
struct u_space *us = (struct u_space *)item;
|
||
|
u_space_reference(&us, NULL);
|
||
|
}
|
||
|
|
||
|
static struct u_space *
|
||
|
find_xdev_space_read_locked(struct u_space_overseer *uso, struct xrt_device *xdev)
|
||
|
{
|
||
|
void *ptr = NULL;
|
||
|
uint64_t key = (uint64_t)(intptr_t)xdev;
|
||
|
u_hashmap_int_find(uso->xdev_map, key, &ptr);
|
||
|
|
||
|
if (ptr == NULL) {
|
||
|
U_LOG_E("Looking for space belonging to unknown xrt_device! '%s'", xdev->str);
|
||
|
}
|
||
|
assert(ptr != NULL);
|
||
|
|
||
|
return (struct u_space *)ptr;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
* Graph traversing functions.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
/*!
|
||
|
* For each space, push the relation of that space and then traverse by calling
|
||
|
* @p push_then_traverse again with the parent space. That means traverse goes
|
||
|
* from a leaf space to a the root space, relations are pushed in the same
|
||
|
* order.
|
||
|
*/
|
||
|
static void
|
||
|
push_then_traverse(struct xrt_relation_chain *xrc, struct u_space *space, uint64_t at_timestamp_ns)
|
||
|
{
|
||
|
switch (space->type) {
|
||
|
case U_SPACE_TYPE_NULL: break; // No-op
|
||
|
case U_SPACE_TYPE_POSE: {
|
||
|
assert(space->pose.xdev != NULL);
|
||
|
assert(space->pose.xname != 0);
|
||
|
|
||
|
struct xrt_space_relation xsr;
|
||
|
xrt_device_get_tracked_pose(space->pose.xdev, space->pose.xname, at_timestamp_ns, &xsr);
|
||
|
m_relation_chain_push_relation(xrc, &xsr);
|
||
|
} break;
|
||
|
case U_SPACE_TYPE_OFFSET: m_relation_chain_push_pose_if_not_identity(xrc, &space->offset.pose); break;
|
||
|
case U_SPACE_TYPE_ROOT: return; // Stops the traversing.
|
||
|
}
|
||
|
|
||
|
// Please tail-call optimise this miss compiler.
|
||
|
assert(space->next != NULL);
|
||
|
push_then_traverse(xrc, space->next, at_timestamp_ns);
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
* For each space, traverse by calling @p traverse_then_push_inverse again with
|
||
|
* the parent space then push the inverse of the relation of that. That means
|
||
|
* traverse goes from a leaf space to a the root space, relations are pushed in
|
||
|
* the reversed order.
|
||
|
*/
|
||
|
static void
|
||
|
traverse_then_push_inverse(struct xrt_relation_chain *xrc, struct u_space *space, uint64_t at_timestamp_ns)
|
||
|
{
|
||
|
// Done traversing.
|
||
|
switch (space->type) {
|
||
|
case U_SPACE_TYPE_NULL: break;
|
||
|
case U_SPACE_TYPE_POSE: break;
|
||
|
case U_SPACE_TYPE_OFFSET: break;
|
||
|
case U_SPACE_TYPE_ROOT: return; // Stops the traversing.
|
||
|
}
|
||
|
|
||
|
// Can't tail-call optimise this one :(
|
||
|
assert(space->next != NULL);
|
||
|
traverse_then_push_inverse(xrc, space->next, at_timestamp_ns);
|
||
|
|
||
|
switch (space->type) {
|
||
|
case U_SPACE_TYPE_NULL: break; // No-op
|
||
|
case U_SPACE_TYPE_POSE: {
|
||
|
assert(space->pose.xdev != NULL);
|
||
|
assert(space->pose.xname != 0);
|
||
|
|
||
|
struct xrt_space_relation xsr;
|
||
|
xrt_device_get_tracked_pose(space->pose.xdev, space->pose.xname, at_timestamp_ns, &xsr);
|
||
|
m_relation_chain_push_inverted_relation(xrc, &xsr);
|
||
|
} break;
|
||
|
case U_SPACE_TYPE_OFFSET: m_relation_chain_push_inverted_pose_if_not_identity(xrc, &space->offset.pose); break;
|
||
|
case U_SPACE_TYPE_ROOT: assert(false); // Should not get here.
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
build_relation_chain_read_locked(struct u_space_overseer *uso,
|
||
|
struct xrt_relation_chain *xrc,
|
||
|
struct u_space *base,
|
||
|
struct u_space *target,
|
||
|
uint64_t at_timestamp_ns)
|
||
|
{
|
||
|
assert(xrc != NULL);
|
||
|
assert(base != NULL);
|
||
|
assert(target != NULL);
|
||
|
|
||
|
push_then_traverse(xrc, target, at_timestamp_ns);
|
||
|
traverse_then_push_inverse(xrc, base, at_timestamp_ns);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
build_relation_chain(struct u_space_overseer *uso,
|
||
|
struct xrt_relation_chain *xrc,
|
||
|
struct u_space *base,
|
||
|
struct u_space *target,
|
||
|
uint64_t at_timestamp_ns)
|
||
|
{
|
||
|
pthread_rwlock_rdlock(&uso->lock);
|
||
|
build_relation_chain_read_locked(uso, xrc, base, target, at_timestamp_ns);
|
||
|
pthread_rwlock_unlock(&uso->lock);
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
special_resolve(struct xrt_relation_chain *xrc, struct xrt_space_relation *out_relation)
|
||
|
{
|
||
|
// A space chain with zero step is always valid.
|
||
|
if (xrc->step_count == 0) {
|
||
|
out_relation->pose = (struct xrt_pose)XRT_POSE_IDENTITY;
|
||
|
out_relation->relation_flags = //
|
||
|
XRT_SPACE_RELATION_ORIENTATION_VALID_BIT | //
|
||
|
XRT_SPACE_RELATION_ORIENTATION_TRACKED_BIT | //
|
||
|
XRT_SPACE_RELATION_POSITION_VALID_BIT | //
|
||
|
XRT_SPACE_RELATION_POSITION_TRACKED_BIT;
|
||
|
} else {
|
||
|
m_relation_chain_resolve(xrc, out_relation);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
* Direct space functions.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
space_destroy(struct xrt_space *xs)
|
||
|
{
|
||
|
struct u_space *us = u_space(xs);
|
||
|
|
||
|
assert(us->next != NULL || us->type == U_SPACE_TYPE_ROOT);
|
||
|
|
||
|
u_space_reference(&us->next, NULL);
|
||
|
|
||
|
free(us);
|
||
|
}
|
||
|
|
||
|
/*!
|
||
|
* Creates a space, returns with a reference of one.
|
||
|
*/
|
||
|
static struct u_space *
|
||
|
create_space(enum u_space_type type, struct u_space *parent)
|
||
|
{
|
||
|
assert(parent != NULL || type == U_SPACE_TYPE_ROOT);
|
||
|
|
||
|
struct u_space *us = U_TYPED_CALLOC(struct u_space);
|
||
|
us->base.reference.count = 1;
|
||
|
us->base.destroy = space_destroy;
|
||
|
us->type = type;
|
||
|
|
||
|
u_space_reference(&us->next, parent);
|
||
|
|
||
|
return us;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
create_and_set_root_space(struct u_space_overseer *uso)
|
||
|
{
|
||
|
assert(uso->base.semantic.root == NULL);
|
||
|
|
||
|
struct u_space *us = create_space(U_SPACE_TYPE_ROOT, NULL);
|
||
|
|
||
|
// Created with one reference.
|
||
|
uso->base.semantic.root = &us->base;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
* Member functions.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
static xrt_result_t
|
||
|
create_offset_space(struct xrt_space_overseer *xso,
|
||
|
struct xrt_space *parent,
|
||
|
const struct xrt_pose *offset,
|
||
|
struct xrt_space **out_space)
|
||
|
{
|
||
|
assert(out_space != NULL);
|
||
|
assert(*out_space == NULL);
|
||
|
|
||
|
struct u_space *uparent = u_space(parent);
|
||
|
struct u_space *us = NULL;
|
||
|
|
||
|
if (m_pose_is_identity(offset)) { // Small optimisation.
|
||
|
us = create_space(U_SPACE_TYPE_NULL, uparent);
|
||
|
} else {
|
||
|
us = create_space(U_SPACE_TYPE_OFFSET, uparent);
|
||
|
us->offset.pose = *offset;
|
||
|
}
|
||
|
|
||
|
// Created with one references.
|
||
|
*out_space = &us->base;
|
||
|
|
||
|
return XRT_SUCCESS;
|
||
|
}
|
||
|
|
||
|
static xrt_result_t
|
||
|
create_pose_space(struct xrt_space_overseer *xso,
|
||
|
struct xrt_device *xdev,
|
||
|
enum xrt_input_name name,
|
||
|
struct xrt_space **out_space)
|
||
|
{
|
||
|
assert(out_space != NULL);
|
||
|
assert(*out_space == NULL);
|
||
|
|
||
|
struct u_space_overseer *uso = u_space_overseer(xso);
|
||
|
|
||
|
// Only need the read lock.
|
||
|
pthread_rwlock_rdlock(&uso->lock);
|
||
|
|
||
|
struct u_space *uparent = find_xdev_space_read_locked(uso, xdev);
|
||
|
struct u_space *us = create_space(U_SPACE_TYPE_POSE, uparent);
|
||
|
|
||
|
// Safe to unlock now.
|
||
|
pthread_rwlock_unlock(&uso->lock);
|
||
|
|
||
|
us->pose.xdev = xdev;
|
||
|
us->pose.xname = name;
|
||
|
|
||
|
// Created with one references.
|
||
|
*out_space = &us->base;
|
||
|
|
||
|
return XRT_SUCCESS;
|
||
|
}
|
||
|
|
||
|
static xrt_result_t
|
||
|
locate_space(struct xrt_space_overseer *xso,
|
||
|
struct xrt_space *base_space,
|
||
|
const struct xrt_pose *base_offset,
|
||
|
uint64_t at_timestamp_ns,
|
||
|
struct xrt_space *space,
|
||
|
const struct xrt_pose *offset,
|
||
|
struct xrt_space_relation *out_relation)
|
||
|
{
|
||
|
struct u_space_overseer *uso = u_space_overseer(xso);
|
||
|
|
||
|
struct u_space *ubase_space = u_space(base_space);
|
||
|
struct u_space *uspace = u_space(space);
|
||
|
|
||
|
struct xrt_relation_chain xrc = {0};
|
||
|
|
||
|
m_relation_chain_push_pose_if_not_identity(&xrc, offset);
|
||
|
build_relation_chain(uso, &xrc, ubase_space, uspace, at_timestamp_ns);
|
||
|
m_relation_chain_push_inverted_pose_if_not_identity(&xrc, base_offset);
|
||
|
|
||
|
// For base_space =~= space (approx equals).
|
||
|
special_resolve(&xrc, out_relation);
|
||
|
|
||
|
return XRT_SUCCESS;
|
||
|
}
|
||
|
|
||
|
static xrt_result_t
|
||
|
locate_device(struct xrt_space_overseer *xso,
|
||
|
struct xrt_space *base_space,
|
||
|
const struct xrt_pose *base_offset,
|
||
|
uint64_t at_timestamp_ns,
|
||
|
struct xrt_device *xdev,
|
||
|
struct xrt_space_relation *out_relation)
|
||
|
{
|
||
|
struct u_space_overseer *uso = u_space_overseer(xso);
|
||
|
|
||
|
struct u_space *ubase_space = u_space(base_space);
|
||
|
|
||
|
struct xrt_relation_chain xrc = {0};
|
||
|
|
||
|
// Only need the read lock.
|
||
|
pthread_rwlock_rdlock(&uso->lock);
|
||
|
|
||
|
struct u_space *uspace = find_xdev_space_read_locked(uso, xdev);
|
||
|
build_relation_chain_read_locked(uso, &xrc, ubase_space, uspace, at_timestamp_ns);
|
||
|
|
||
|
// Safe to unlock now.
|
||
|
pthread_rwlock_unlock(&uso->lock);
|
||
|
|
||
|
// Do as much work outside of the lock.
|
||
|
m_relation_chain_push_inverted_pose_if_not_identity(&xrc, base_offset);
|
||
|
special_resolve(&xrc, out_relation);
|
||
|
|
||
|
return XRT_SUCCESS;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
destroy(struct xrt_space_overseer *xso)
|
||
|
{
|
||
|
struct u_space_overseer *uso = u_space_overseer(xso);
|
||
|
|
||
|
xrt_space_reference(&uso->base.semantic.unbounded, NULL);
|
||
|
xrt_space_reference(&uso->base.semantic.stage, NULL);
|
||
|
xrt_space_reference(&uso->base.semantic.local, NULL);
|
||
|
xrt_space_reference(&uso->base.semantic.view, NULL);
|
||
|
xrt_space_reference(&uso->base.semantic.root, NULL);
|
||
|
|
||
|
// Each device has a reference to its space, make sure to unreference before creating.
|
||
|
u_hashmap_int_clear_and_call_for_each(uso->xdev_map, hashmap_unreference_space_items, uso);
|
||
|
u_hashmap_int_destroy(&uso->xdev_map);
|
||
|
|
||
|
pthread_rwlock_destroy(&uso->lock);
|
||
|
|
||
|
free(uso);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
* 'Exported' functions.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
struct u_space_overseer *
|
||
|
u_space_overseer_create(void)
|
||
|
{
|
||
|
struct u_space_overseer *uso = U_TYPED_CALLOC(struct u_space_overseer);
|
||
|
uso->base.create_offset_space = create_offset_space;
|
||
|
uso->base.create_pose_space = create_pose_space;
|
||
|
uso->base.locate_space = locate_space;
|
||
|
uso->base.locate_device = locate_device;
|
||
|
uso->base.destroy = destroy;
|
||
|
|
||
|
XRT_MAYBE_UNUSED int ret = 0;
|
||
|
|
||
|
ret = pthread_rwlock_init(&uso->lock, NULL);
|
||
|
assert(ret == 0);
|
||
|
|
||
|
ret = u_hashmap_int_create(&uso->xdev_map);
|
||
|
assert(ret == 0);
|
||
|
|
||
|
create_and_set_root_space(uso);
|
||
|
|
||
|
return uso;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
u_space_overseer_legacy_setup(struct u_space_overseer *uso,
|
||
|
struct xrt_device **xdevs,
|
||
|
uint32_t xdev_count,
|
||
|
struct xrt_device *head,
|
||
|
const struct xrt_pose *local_offset)
|
||
|
{
|
||
|
struct xrt_space *root = uso->base.semantic.root; // Convenience
|
||
|
|
||
|
struct u_hashmap_int *torig_map = NULL;
|
||
|
u_hashmap_int_create(&torig_map);
|
||
|
|
||
|
|
||
|
for (uint32_t i = 0; i < xdev_count; i++) {
|
||
|
struct xrt_device *xdev = xdevs[i];
|
||
|
struct xrt_tracking_origin *torig = xdev->tracking_origin;
|
||
|
uint64_t key = (uint64_t)(intptr_t)torig;
|
||
|
struct xrt_space *xs = NULL;
|
||
|
|
||
|
void *ptr = NULL;
|
||
|
u_hashmap_int_find(torig_map, key, &ptr);
|
||
|
|
||
|
if (ptr != NULL) {
|
||
|
xs = (struct xrt_space *)ptr;
|
||
|
} else {
|
||
|
u_space_overseer_create_offset_space(uso, root, &torig->offset, &xs);
|
||
|
u_hashmap_int_insert(torig_map, key, xs);
|
||
|
}
|
||
|
|
||
|
u_space_overseer_link_space_to_device(uso, xs, xdev);
|
||
|
}
|
||
|
|
||
|
// Each item has a exrta reference make sure to clear before destroying.
|
||
|
u_hashmap_int_clear_and_call_for_each(torig_map, hashmap_unreference_space_items, uso);
|
||
|
u_hashmap_int_destroy(&torig_map);
|
||
|
|
||
|
// If these are set something is probably wrong, but just in case unset them.
|
||
|
assert(uso->base.semantic.view == NULL);
|
||
|
assert(uso->base.semantic.stage == NULL);
|
||
|
assert(uso->base.semantic.local == NULL);
|
||
|
xrt_space_reference(&uso->base.semantic.view, NULL);
|
||
|
xrt_space_reference(&uso->base.semantic.stage, NULL);
|
||
|
xrt_space_reference(&uso->base.semantic.local, NULL);
|
||
|
|
||
|
xrt_space_reference(&uso->base.semantic.stage, uso->base.semantic.root);
|
||
|
u_space_overseer_create_offset_space(uso, uso->base.semantic.root, local_offset, &uso->base.semantic.local);
|
||
|
if (head != NULL) {
|
||
|
u_space_overseer_create_pose_space(uso, head, XRT_INPUT_GENERIC_HEAD_POSE, &uso->base.semantic.view);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void
|
||
|
u_space_overseer_create_null_space(struct u_space_overseer *uso, struct xrt_space *parent, struct xrt_space **out_space)
|
||
|
{
|
||
|
assert(out_space != NULL);
|
||
|
assert(*out_space == NULL);
|
||
|
|
||
|
struct u_space *uparent = u_space(parent);
|
||
|
struct u_space *us = create_space(U_SPACE_TYPE_NULL, uparent);
|
||
|
|
||
|
// Created with one references.
|
||
|
*out_space = &us->base;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
u_space_overseer_link_space_to_device(struct u_space_overseer *uso, struct xrt_space *xs, struct xrt_device *xdev)
|
||
|
{
|
||
|
pthread_rwlock_wrlock(&uso->lock);
|
||
|
|
||
|
void *ptr = NULL;
|
||
|
uint64_t key = (uint64_t)(intptr_t)xdev;
|
||
|
u_hashmap_int_find(uso->xdev_map, key, &ptr);
|
||
|
if (ptr != NULL) {
|
||
|
U_LOG_W("Device '%s' already have a space attached!", xdev->str);
|
||
|
}
|
||
|
|
||
|
// Each xdev needs to add a reference to the space.
|
||
|
struct xrt_space *new_space = NULL;
|
||
|
xrt_space_reference(&new_space, xs);
|
||
|
|
||
|
u_hashmap_int_insert(uso->xdev_map, (uint64_t)(intptr_t)xdev, new_space);
|
||
|
|
||
|
pthread_rwlock_unlock(&uso->lock);
|
||
|
|
||
|
// Dereferrence old space outside of lock.
|
||
|
struct xrt_space *old_space = (struct xrt_space *)ptr;
|
||
|
xrt_space_reference(&old_space, NULL);
|
||
|
}
|