st/oxr: New input transform implementation, fixes conformance failures.

Includes unit tests for oxr_input_transform, add to CI

This is a revised implementation using "variants" instead of
"inheritance" in a linked-list.
This commit is contained in:
Ryan Pavlik 2020-06-15 10:58:48 -05:00 committed by Jakob Bornecrantz
parent e011e86fb9
commit 1a5d31b82b
12 changed files with 1081 additions and 108 deletions

View file

@ -143,6 +143,7 @@ format-and-spellcheck:
# List build options
- grep "^XRT_" CMakeCache.txt
- ninja
- ctest --output-on-failure
# "Base" job for a Meson build
.monado.base-job.build-meson:

View file

@ -68,6 +68,9 @@ else()
find_package(OpenGL)
endif()
# This one is named differently because that's what CTest uses
option(BUILD_TESTING "Enable building of the test suite?" ON)
cmake_dependent_option(CMAKE_INTERPROCEDURAL_OPTIMIZATION "Enable inter-procedural (link-time) optimization" OFF "HAS_IPO" OFF)
cmake_dependent_option(XRT_HAVE_WAYLAND "Enable Wayland support" ON "WAYLAND_FOUND AND WAYLAND_SCANNER_FOUND AND WAYLAND_PROTOCOLS_FOUND" OFF)
cmake_dependent_option(XRT_HAVE_XLIB "Enable xlib support" ON "X11_FOUND" OFF)
@ -168,3 +171,8 @@ endif()
add_subdirectory(src)
add_subdirectory(doc)
if(BUILD_TESTING)
include(CTest)
add_subdirectory(tests)
endif()

View file

@ -0,0 +1 @@
OpenXR: Transform input types in a somewhat flexible, composable way. Also, do conversion at sync time, and use the transformed values to evaluate if the input has changed, per the spec.

View file

@ -42,6 +42,8 @@ set(OXR_SOURCE_FILES
oxr_extension_support.h
oxr_handle_base.c
oxr_input.c
oxr_input_transform.c
oxr_input_transform.h
oxr_instance.c
oxr_logger.c
oxr_logger.h
@ -85,6 +87,8 @@ target_link_libraries(st_oxr PRIVATE
Vulkan::Vulkan
comp_client
)
target_include_directories(st_oxr PRIVATE
target_include_directories(st_oxr
PRIVATE
${CMAKE_CURRENT_BINARY_DIR}
)
INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/..)

View file

@ -55,6 +55,8 @@ lib_st_oxr = static_library(
'oxr_extension_support.h',
'oxr_handle_base.c',
'oxr_input.c',
'oxr_input_transform.c',
'oxr_input_transform.h',
'oxr_instance.c',
'oxr_logger.c',
'oxr_logger.h',

View file

@ -16,6 +16,7 @@
#include "oxr_objects.h"
#include "oxr_logger.h"
#include "oxr_handle.h"
#include "oxr_input_transform.h"
#include <math.h>
#include <stdio.h>
@ -50,9 +51,6 @@ oxr_action_cache_update(struct oxr_logger *log,
int64_t time,
bool select);
/*!
* @private @memberof oxr_action_attachment
*/
static void
oxr_action_attachment_update(struct oxr_logger *log,
struct oxr_session *sess,
@ -82,6 +80,12 @@ oxr_action_bind_inputs(struct oxr_logger *log,
static void
oxr_action_cache_teardown(struct oxr_action_cache *cache)
{
// Clean up input transforms
for (uint32_t i = 0; i < cache->num_inputs; i++) {
struct oxr_action_input *action_input = &cache->inputs[i];
oxr_input_transform_destroy(&(action_input->transforms));
action_input->num_transforms = 0;
}
free(cache->inputs);
cache->inputs = NULL;
free(cache->outputs);
@ -547,54 +551,6 @@ do_io_bindings(struct oxr_binding *b,
return found;
}
static bool
ends_with(const char *str, const char *suffix)
{
int len = strlen(str);
int suffix_len = strlen(suffix);
return (len >= suffix_len) &&
(0 == strcmp(str + (len - suffix_len), suffix));
}
static void
oxr_action_cache_determine_redirect(struct oxr_logger *log,
struct oxr_session *sess,
struct oxr_action *act,
struct oxr_action_cache *cache,
XrPath bound_path)
{
cache->redirect = INPUT_REDIRECT_DEFAULT;
struct oxr_action_input *input = &cache->inputs[0];
if (input == NULL)
return;
const char *str;
size_t length;
oxr_path_get_string(log, sess->sys->inst, bound_path, &str, &length);
enum xrt_input_type t = XRT_GET_INPUT_TYPE(input->input->name);
// trackpad/thumbstick data is kept in vec2f values.
// When a float action binds to ../trackpad/x or ../thumbstick/y, store
// the information which vec2f component to read.
if (act->data->action_type == XR_ACTION_TYPE_FLOAT_INPUT &&
t == XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE) {
if (ends_with(str, "/x")) {
cache->redirect = INPUT_REDIRECT_VEC2_X_TO_VEC1;
} else if (ends_with(str, "/y")) {
cache->redirect = INPUT_REDIRECT_VEC2_Y_TO_VEC1;
} else {
oxr_log(log,
"No rule to get float from vec2f for action "
"%s, binding %s\n",
act->data->name, str);
}
}
}
static XrPath
get_matched_xrpath(struct oxr_binding *b, struct oxr_action *act)
{
@ -732,7 +688,8 @@ oxr_action_attachment_bind(struct oxr_logger *log,
if (act_ref->sub_paths.user || act_ref->sub_paths.any) {
#if 0
oxr_action_bind_inputs(log, slog, sess, act, &act_attached->user, user,
oxr_action_bind_inputs(log, &slog, sess, act,
&act_attached->user, user,
OXR_SUB_ACTION_PATH_USER);
#endif
}
@ -791,6 +748,11 @@ oxr_action_cache_stop_output(struct oxr_logger *log,
}
}
/*!
* Called during xrSyncActions.
*
* @private @memberof oxr_action_cache
*/
static void
oxr_action_cache_update(struct oxr_logger *log,
struct oxr_session *sess,
@ -832,34 +794,50 @@ oxr_action_cache_update(struct oxr_logger *log,
cache->current.active = true;
/*!
* @todo Combine multiple sources for a single subaction path
* and convert type as required.
* @todo Combine multiple sources for a single subaction path.
*/
struct xrt_input *input = cache->inputs[0].input;
struct oxr_action_input *action_input = &(cache->inputs[0]);
struct xrt_input *input = action_input->input;
struct oxr_input_value_tagged raw_input = {
.type = XRT_GET_INPUT_TYPE(input->name),
.value = input->value,
};
struct oxr_input_value_tagged transformed = {0};
if (!oxr_input_transform_process(action_input->transforms,
action_input->num_transforms,
&raw_input, &transformed)) {
// We couldn't transform, how strange. Reset all state.
// At this level we don't know what action this is, etc.
// so a warning message isn't very helpful.
U_ZERO(&cache->current);
return;
}
int64_t timestamp = input->timestamp;
bool changed = false;
switch (XRT_GET_INPUT_TYPE(input->name)) {
switch (transformed.type) {
case XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE:
case XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE: {
changed = (input->value.vec1.x != last.value.vec1.x);
cache->current.value = input->value;
changed =
(transformed.value.vec1.x != last.value.vec1.x);
cache->current.value.vec1.x = transformed.value.vec1.x;
break;
}
case XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE: {
changed = (input->value.vec2.x != last.value.vec2.x) ||
(input->value.vec2.y != last.value.vec2.y);
cache->current.value = input->value;
changed =
(transformed.value.vec2.x != last.value.vec2.x) ||
(transformed.value.vec2.y != last.value.vec2.y);
cache->current.value.vec2.x = transformed.value.vec2.x;
cache->current.value.vec2.y = transformed.value.vec2.y;
break;
}
#if 0
case XRT_INPUT_TYPE_VEC3_MINUS_ONE_TO_ONE: {
changed = (input->value.vec3.x != last.value.vec3.x) ||
(input->value.vec3.y != last.value.vec3.y) ||
(input->value.vec3.z != last.value.vec3.z);
cache->current.value.vec3.x = input->value.vec3.x;
cache->current.value.vec3.y = input->value.vec3.y;
cache->current.value.vec3.z = input->value.vec3.z;
changed = (transformed.value.vec3.x != last.vec3.x) ||
(transformed.value.vec3.y != last.vec3.y) ||
(transformed.value.vec3.z != last.vec3.z);
cache->current.vec3.x = transformed.value.vec3.x;
cache->current.vec3.y = transformed.value.vec3.y;
cache->current.vec3.z = transformed.value.vec3.z;
break;
}
#endif
@ -915,6 +893,11 @@ oxr_action_cache_update(struct oxr_logger *log,
} \
}
/*!
* Called during each xrSyncActions.
*
* @private @memberof oxr_action_attachment
*/
static void
oxr_action_attachment_update(struct oxr_logger *log,
struct oxr_session *sess,
@ -1019,6 +1002,35 @@ oxr_action_attachment_update(struct oxr_logger *log,
act_attached->any_state.active = true;
}
}
/*!
* Try to produce a transform chain to convert the available input into the
* desired input type.
*
* Populates @p action_input->transforms and @p action_input->num_transforms on
* success.
*
* @returns false if it could not, true if it could
*/
static bool
oxr_action_populate_input_transform(struct oxr_logger *log,
struct oxr_sink_logger *slog,
struct oxr_session *sess,
struct oxr_action *act,
struct oxr_action_input *action_input,
XrPath bound_path)
{
assert(action_input->transforms == NULL);
assert(action_input->num_transforms == 0);
const char *str;
size_t length;
oxr_path_get_string(log, sess->sys->inst, bound_path, &str, &length);
enum xrt_input_type t = XRT_GET_INPUT_TYPE(action_input->input->name);
return oxr_input_transform_create_chain(
log, slog, t, act->data->action_type, act->data->name, str,
&action_input->transforms, &action_input->num_transforms);
}
static void
oxr_action_bind_inputs(struct oxr_logger *log,
@ -1034,7 +1046,7 @@ oxr_action_bind_inputs(struct oxr_logger *log,
struct oxr_action_output outputs[16] = {0};
uint32_t num_outputs = 0;
//! @todo Should this be asserted to be none-null?
//! @todo Should this be asserted to be non-null?
XrPath bound_path = XR_NULL_PATH;
get_binding(log, slog, sess, act, profile, sub_path, inputs,
&num_inputs, outputs, &num_outputs, &bound_path);
@ -1046,6 +1058,19 @@ oxr_action_bind_inputs(struct oxr_logger *log,
cache->inputs =
U_TYPED_ARRAY_CALLOC(struct oxr_action_input, num_inputs);
for (uint32_t i = 0; i < num_inputs; i++) {
if (!oxr_action_populate_input_transform(
log, slog, sess, act, &(inputs[i]),
bound_path)) {
/*!
* @todo de-populate this element if we couldn't
* get a transform?
*/
oxr_slog(
slog,
"Could not populate a transform for %s "
"despite it being bound!\n",
act->data->name);
}
cache->inputs[i] = inputs[i];
}
cache->num_inputs = num_inputs;
@ -1060,8 +1085,6 @@ oxr_action_bind_inputs(struct oxr_logger *log,
}
cache->num_outputs = num_outputs;
}
oxr_action_cache_determine_redirect(log, sess, act, cache, bound_path);
}
@ -1303,40 +1326,29 @@ oxr_action_sync_data(struct oxr_logger *log,
static void
get_state_from_state_bool(struct oxr_action_state *state,
XrActionStateBoolean *data,
enum xrt_source_value_redirect redirect)
XrActionStateBoolean *data)
{
data->currentState = state->value.boolean;
data->lastChangeTime = state->timestamp;
data->changedSinceLastSync = state->changed;
data->isActive = XR_TRUE;
data->isActive = state->active;
//! @todo
// data->isActive = XR_TRUE;
}
static void
get_state_from_state_vec1(struct oxr_action_state *state,
XrActionStateFloat *data,
enum xrt_source_value_redirect redirect)
XrActionStateFloat *data)
{
switch (redirect) {
case INPUT_REDIRECT_VEC2_X_TO_VEC1:
data->currentState = state->value.vec2.x;
break;
case INPUT_REDIRECT_VEC2_Y_TO_VEC1:
data->currentState = state->value.vec2.y;
break;
case INPUT_REDIRECT_DEFAULT:
default: data->currentState = state->value.vec1.x; break;
}
data->currentState = state->value.vec1.x;
data->lastChangeTime = state->timestamp;
data->changedSinceLastSync = state->changed;
data->isActive = XR_TRUE;
data->isActive = state->active;
}
static void
get_state_from_state_vec2(struct oxr_action_state *state,
XrActionStateVector2f *data,
enum xrt_source_value_redirect redirect)
XrActionStateVector2f *data)
{
data->currentState.x = state->value.vec2.x;
data->currentState.y = state->value.vec2.y;
@ -1347,30 +1359,27 @@ get_state_from_state_vec2(struct oxr_action_state *state,
#define OXR_ACTION_GET_FILLER(TYPE) \
if (sub_paths.any && act_attached->any_state.active) { \
get_state_from_state_##TYPE(&act_attached->any_state, data, \
INPUT_REDIRECT_DEFAULT); \
get_state_from_state_##TYPE(&act_attached->any_state, data); \
} \
if (sub_paths.user && act_attached->user.current.active) { \
get_state_from_state_##TYPE(&act_attached->user.current, data, \
act_attached->user.redirect); \
get_state_from_state_##TYPE(&act_attached->user.current, \
data); \
} \
if (sub_paths.head && act_attached->head.current.active) { \
get_state_from_state_##TYPE(&act_attached->head.current, data, \
act_attached->head.redirect); \
get_state_from_state_##TYPE(&act_attached->head.current, \
data); \
} \
if (sub_paths.left && act_attached->left.current.active) { \
get_state_from_state_##TYPE(&act_attached->left.current, data, \
act_attached->left.redirect); \
get_state_from_state_##TYPE(&act_attached->left.current, \
data); \
} \
if (sub_paths.right && act_attached->right.current.active) { \
get_state_from_state_##TYPE(&act_attached->right.current, \
data, \
act_attached->right.redirect); \
data); \
} \
if (sub_paths.gamepad && act_attached->gamepad.current.active) { \
get_state_from_state_##TYPE(&act_attached->gamepad.current, \
data, \
act_attached->gamepad.redirect); \
data); \
}

View file

@ -0,0 +1,329 @@
// Copyright 2018-2020, Collabora, Ltd.
// SPDX-License-Identifier: BSL-1.0
/*!
* @file
* @brief Handles transformation/filtering of input data.
* @author Ryan Pavlik <ryan.pavlik@collabora.com>
* @author Jakob Bornecrantz <jakob@collabora.com>
* @ingroup oxr_input_transform
*/
#include "oxr_input_transform.h"
#include "oxr_logger.h"
#include "oxr_objects.h"
#include "util/u_misc.h"
#include <string.h>
#include <assert.h>
/*!
* Arbitrary but larger than required.
*/
#define OXR_MAX_INPUT_TRANSFORMS 5
void
oxr_input_transform_destroy(struct oxr_input_transform **transform_ptr)
{
struct oxr_input_transform *xform = *transform_ptr;
if (xform == NULL) {
return;
}
free(xform);
*transform_ptr = NULL;
}
bool
oxr_input_transform_init_root(struct oxr_input_transform *transform,
enum xrt_input_type input_type)
{
assert(transform != NULL);
U_ZERO(transform);
transform->type = INPUT_TRANSFORM_IDENTITY;
transform->result_type = input_type;
return true;
}
bool
oxr_input_transform_init_vec2_get_x(struct oxr_input_transform *transform,
const struct oxr_input_transform *parent)
{
assert(transform != NULL);
assert(parent != NULL);
assert(parent->result_type == XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE);
U_ZERO(transform);
transform->type = INPUT_TRANSFORM_VEC2_GET_X;
transform->result_type = XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE;
return true;
}
bool
oxr_input_transform_init_vec2_get_y(struct oxr_input_transform *transform,
const struct oxr_input_transform *parent)
{
assert(transform != NULL);
assert(parent != NULL);
assert(parent->result_type == XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE);
U_ZERO(transform);
transform->type = INPUT_TRANSFORM_VEC2_GET_Y;
transform->result_type = XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE;
return true;
}
bool
oxr_input_transform_init_threshold(struct oxr_input_transform *transform,
const struct oxr_input_transform *parent,
float threshold,
bool invert)
{
assert(transform != NULL);
assert(parent != NULL);
assert((parent->result_type == XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE) ||
(parent->result_type == XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE));
U_ZERO(transform);
transform->type = INPUT_TRANSFORM_THRESHOLD;
transform->result_type = XRT_INPUT_TYPE_BOOLEAN;
transform->data.threshold.threshold = threshold;
transform->data.threshold.invert = invert;
return true;
}
bool
oxr_input_transform_init_bool_to_vec1(struct oxr_input_transform *transform,
const struct oxr_input_transform *parent,
enum xrt_input_type result_type,
float true_val,
float false_val)
{
assert(transform != NULL);
assert(parent != NULL);
assert(parent->result_type == XRT_INPUT_TYPE_BOOLEAN);
assert((result_type == XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE) ||
(result_type == XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE));
U_ZERO(transform);
transform->type = INPUT_TRANSFORM_BOOL_TO_VEC1;
transform->result_type = result_type;
transform->data.bool_to_vec1.true_val = true_val;
transform->data.bool_to_vec1.false_val = false_val;
return true;
}
bool
oxr_input_transform_process(const struct oxr_input_transform *transform,
size_t num_transforms,
const struct oxr_input_value_tagged *input,
struct oxr_input_value_tagged *out)
{
if (transform == NULL) {
return false;
}
struct oxr_input_value_tagged data = *input;
for (size_t i = 0; i < num_transforms; ++i) {
const struct oxr_input_transform *xform = &(transform[i]);
switch (xform->type) {
case INPUT_TRANSFORM_IDENTITY:
// do nothing
break;
case INPUT_TRANSFORM_VEC2_GET_X:
data.value.vec1.x = data.value.vec2.x;
break;
case INPUT_TRANSFORM_VEC2_GET_Y:
data.value.vec1.x = data.value.vec2.y;
break;
case INPUT_TRANSFORM_THRESHOLD: {
bool temp =
data.value.vec1.x > xform->data.threshold.threshold;
if (xform->data.threshold.invert) {
temp = !temp;
}
data.value.boolean = temp;
break;
}
case INPUT_TRANSFORM_BOOL_TO_VEC1: {
data.value.vec1.x =
data.value.boolean
? xform->data.bool_to_vec1.true_val
: xform->data.bool_to_vec1.false_val;
break;
}
case INPUT_TRANSFORM_INVALID:
default: return false;
}
// Update the data type tag
data.type = xform->result_type;
}
*out = data;
return true;
}
static bool
ends_with(const char *str, const char *suffix)
{
int len = strlen(str);
int suffix_len = strlen(suffix);
return (len >= suffix_len) &&
(0 == strcmp(str + (len - suffix_len), suffix));
}
static inline bool
input_is_float(enum xrt_input_type input_type)
{
return (input_type == XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE) ||
(input_type == XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE);
}
static inline uint8_t
input_dim(enum xrt_input_type input_type)
{
switch (input_type) {
case XRT_INPUT_TYPE_BOOLEAN:
case XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE:
case XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE: return 1;
case XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE: return 2;
default: return 0;
}
}
static inline bool
oxr_type_matches_xrt(enum xrt_input_type input_type, XrActionType result_type)
{
switch (result_type) {
case XR_ACTION_TYPE_BOOLEAN_INPUT:
return input_type == XRT_INPUT_TYPE_BOOLEAN;
case XR_ACTION_TYPE_FLOAT_INPUT: return input_is_float(input_type);
case XR_ACTION_TYPE_VECTOR2F_INPUT:
return input_type == XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE;
default: return false;
}
}
static inline bool
extend_transform_array(struct oxr_logger *log,
struct oxr_sink_logger *slog,
struct oxr_input_transform *transform,
const struct oxr_input_transform *parent,
XrActionType result_type,
const char *bound_path_string)
{
enum xrt_input_type input_type = parent->result_type;
if (input_dim(input_type) == 2 &&
result_type != XR_ACTION_TYPE_VECTOR2F_INPUT) {
// reduce dimension
if (ends_with(bound_path_string, "/x")) {
oxr_slog(slog, "Adding transform: get x of Vec2\n");
return oxr_input_transform_init_vec2_get_x(transform,
parent);
}
if (ends_with(bound_path_string, "/y")) {
oxr_slog(slog, "Adding transform: get y of Vec2\n");
return oxr_input_transform_init_vec2_get_y(transform,
parent);
}
oxr_log(log, "No rule to get float from vec2f for binding %s\n",
bound_path_string);
return NULL;
}
if (input_type == XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE &&
result_type == XR_ACTION_TYPE_BOOLEAN_INPUT) {
// 0.2 is for a little deadband around the center.
oxr_slog(slog, "Adding transform: threshold [-1, 1] float\n");
return oxr_input_transform_init_threshold(transform, parent,
0.2f, false);
}
if (input_type == XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE &&
result_type == XR_ACTION_TYPE_BOOLEAN_INPUT) {
// Need it pressed nearly all the way
oxr_slog(slog, "Adding transform: threshold [0, 1] float\n");
return oxr_input_transform_init_threshold(transform, parent,
0.7f, false);
}
if (input_type == XRT_INPUT_TYPE_BOOLEAN &&
result_type == XR_ACTION_TYPE_FLOAT_INPUT) {
// this conversion is in the spec
oxr_slog(slog, "Adding transform: bool to float\n");
return oxr_input_transform_init_bool_to_vec1(
transform, parent, XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE, 1.f,
0.f);
}
return false;
}
struct oxr_input_transform *
oxr_input_transform_clone_chain(struct oxr_input_transform *transforms,
size_t num_transforms)
{
struct oxr_input_transform *ret =
U_TYPED_ARRAY_CALLOC(struct oxr_input_transform, num_transforms);
memcpy(ret, transforms, sizeof(*ret) * num_transforms);
return ret;
}
bool
oxr_input_transform_create_chain(struct oxr_logger *log,
struct oxr_sink_logger *slog,
enum xrt_input_type input_type,
XrActionType result_type,
const char *action_name,
const char *bound_path_string,
struct oxr_input_transform **out_transforms,
size_t *out_num_transforms)
{
struct oxr_input_transform chain[OXR_MAX_INPUT_TRANSFORMS] = {0};
struct oxr_input_transform *current_xform = &(chain[0]);
if (!oxr_input_transform_init_root(current_xform, input_type)) {
*out_num_transforms = 0;
*out_transforms = NULL;
return false;
}
size_t num_transforms = 1;
if (result_type == XR_ACTION_TYPE_POSE_INPUT &&
input_type == XRT_INPUT_TYPE_POSE) {
// No transform needed, just return identity to keep this alive.
*out_num_transforms = num_transforms;
*out_transforms =
oxr_input_transform_clone_chain(chain, num_transforms);
return true;
}
while (!oxr_type_matches_xrt(current_xform->result_type, result_type)) {
if (num_transforms >= OXR_MAX_INPUT_TRANSFORMS) {
// Couldn't finish the transform to the desired type.
oxr_log(
log,
"Seem to have gotten into a loop, trying to make a "
"rule to transform action %s, binding %s\n",
action_name, bound_path_string);
*out_num_transforms = 0;
*out_transforms = NULL;
return false;
}
struct oxr_input_transform *new_xform =
&(chain[num_transforms]);
if (!extend_transform_array(log, slog, new_xform, current_xform,
result_type, bound_path_string)) {
// Couldn't finish the transform to the desired type.
oxr_log(log,
"No rule to transform action %s, binding %s\n",
action_name, bound_path_string);
*out_num_transforms = 0;
*out_transforms = NULL;
return false;
}
num_transforms++;
current_xform = new_xform;
}
*out_num_transforms = num_transforms;
*out_transforms =
oxr_input_transform_clone_chain(chain, num_transforms);
return true;
}

View file

@ -0,0 +1,314 @@
// Copyright 2018-2020, Collabora, Ltd.
// SPDX-License-Identifier: BSL-1.0
/*!
* @file
* @brief Defines ways of performing (possibly multi-step) conversions of input
* data.
* @author Ryan Pavlik <ryan.pavlik@collabora.com>
* @ingroup oxr_input_transform
*/
#pragma once
#include "xrt/xrt_device.h"
// we need no platform-specific defines from OpenXR.
#include "openxr/openxr.h"
#ifdef __cplusplus
extern "C" {
#endif
struct oxr_logger;
struct oxr_sink_logger;
struct oxr_action;
struct oxr_action_cache;
/*!
* @defgroup oxr_input_transform OpenXR input transformation
*
*
* @ingroup oxr
* @{
*/
/*!
* Tag for the input transform
*
* @see oxr_input_transform
*/
enum oxr_input_transform_type
{
/*!
* Invalid value, so that zero-initialization without further assignment
* is caught.
*/
INPUT_TRANSFORM_INVALID = 0,
/*!
* Do not modify the input.
*
* This is only used as the root/head transform, to set the initial
* type.
*/
INPUT_TRANSFORM_IDENTITY,
/*!
* Get the X component of a 2D float input of any range.
*/
INPUT_TRANSFORM_VEC2_GET_X,
/*!
* Get the Y component of a 2D float input of any range.
*/
INPUT_TRANSFORM_VEC2_GET_Y,
/*!
* Apply a threshold to any 1D float input to make a bool.
*
* This transform type has data:
*
* @see oxr_input_transform_threshold_data
*/
INPUT_TRANSFORM_THRESHOLD,
/*!
* Convert a bool to some range of 1D float input.
*
* This transform type has data:
*
* @see oxr_input_transform_bool_to_vec1_data
*/
INPUT_TRANSFORM_BOOL_TO_VEC1,
};
struct oxr_input_transform;
/*!
* Data required for INPUT_TRANSFORM_THRESHOLD
*/
struct oxr_input_transform_threshold_data
{
//! The "greater-than" threshold value
float threshold;
//! If true, values above threshold are false instead of
//! true
bool invert;
};
/*!
* Data required for INPUT_TRANSFORM_BOOL_TO_VEC1
* @see oxr_input_transform
* @see INPUT_TRANSFORM_BOOL_TO_VEC1
*/
struct oxr_input_transform_bool_to_vec1_data
{
//! Value produced if bool is true.
float true_val;
//! Value produced if bool is false.
float false_val;
};
/*!
* Variant type for input transforms.
*
* Some values for @p type do not have any additional data in the union
*/
struct oxr_input_transform
{
/*!
* The type of this transform.
*
* Some values imply that a member of oxr_input_transform::data is
* populated.
*/
enum oxr_input_transform_type type;
//! The type output by this transform.
enum xrt_input_type result_type;
union {
/*!
* Populated when oxr_input_transform::type is
* INPUT_TRANSFORM_THRESHOLD
*/
struct oxr_input_transform_threshold_data threshold;
/*!
* Populated when oxr_input_transform::type is
* INPUT_TRANSFORM_BOOL_TO_VEC1
*/
struct oxr_input_transform_bool_to_vec1_data bool_to_vec1;
} data;
};
/*!
* An input value enum with the associated tag required to interpret it.
*/
struct oxr_input_value_tagged
{
enum xrt_input_type type;
union xrt_input_value value;
};
/*!
* Destroy an array of input transforms.
*
* Performs null check and sets to NULL.
*
* @public @memberof oxr_input_transform
*/
void
oxr_input_transform_destroy(struct oxr_input_transform **transform_ptr);
/*!
* Apply an array of input transforms.
*
* @param[in] transforms An array of input transforms
* @param[in] num_transforms The number of elements in @p transform
* @param[in] input The input value and type
* @param[out] out The transformed value and type
*
* @returns false if there was a type mismatch
* @public @memberof oxr_input_transform
*/
bool
oxr_input_transform_process(const struct oxr_input_transform *transforms,
size_t num_transforms,
const struct oxr_input_value_tagged *input,
struct oxr_input_value_tagged *out);
/*!
* Allocate an identity transform serving as the root/head of the transform
* chain.
*
* Usually called automatically by @ref oxr_input_transform_create_chain
*
* @param[in,out] transform A pointer to the @ref oxr_input_transform struct to
* initialize.
* @param[in] input_type The native input type from the device
*
* @public @memberof oxr_input_transform
*/
bool
oxr_input_transform_init_root(struct oxr_input_transform *transform,
const enum xrt_input_type input_type);
/*!
* Allocate a transform to get the X component of a Vec2.
*
* Usually called automatically by @ref oxr_input_transform_create_chain
*
* @param[in,out] transform A pointer to the @ref oxr_input_transform struct to
* initialize.
* @param[in] parent The preceding transform
*
* @pre parent->result_type is @ref XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE
*
* @public @memberof oxr_input_transform
*/
bool
oxr_input_transform_init_vec2_get_x(struct oxr_input_transform *transform,
const struct oxr_input_transform *parent);
/*!
* Allocate a transform to get the Y component of a Vec2.
*
* Usually called automatically by @ref oxr_input_transform_create_chain
*
* @param[in,out] transform A pointer to the @ref oxr_input_transform struct to
* initialize.
* @param[in] parent The preceding transform
*
* @pre parent->result_type is @ref XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE
*
* @public @memberof oxr_input_transform
*/
bool
oxr_input_transform_init_vec2_get_y(struct oxr_input_transform *transform,
const struct oxr_input_transform *parent);
/*!
* Allocate a transform to threshold a float to a bool.
*
* Usually called automatically by @ref oxr_input_transform_create_chain
*
* @param[in,out] transform A pointer to the @ref oxr_input_transform struct to
* initialize.
* @param[in] parent The preceding transform
* @param[in] threshold Threshold value to use
* @param[in] invert If true, condition is "value <= threshold" instead of
* "value > threshold"
*
* @pre parent->result_type is @ref XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE or
* @ref XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE
*
* @public @memberof oxr_input_transform
*/
bool
oxr_input_transform_init_threshold(struct oxr_input_transform *transform,
const struct oxr_input_transform *parent,
float threshold,
bool invert);
/*!
* Allocate a transform to turn a bool into an arbitrary 1D float.
*
* Usually called automatically by @ref oxr_input_transform_create_chain
*
* @param[in,out] transform A pointer to the @ref oxr_input_transform struct to
* initialize.
* @param[in] parent The preceding transform
* @param[in] result_type Either XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE or
* XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE
* @param[in] true_val Value to return when true
* @param[in] false_val Value to return when false
*
* @pre parent->result_type is XRT_INPUT_TYPE_BOOLEAN
*
* @public @memberof oxr_input_transform
*/
bool
oxr_input_transform_init_bool_to_vec1(struct oxr_input_transform *transform,
const struct oxr_input_transform *parent,
enum xrt_input_type result_type,
float true_val,
float false_val);
/*!
* Create a transform array to convert @p input_type to @p result_type.
*
* @todo In the future, this should be configured using knowledge from the
* device as well as user options.
*
* @param[in] log The logger
* @param[in] slog The sink logger
* @param[in] input_type The type of input received from the hardware
* @param[in] result_type The type of input the application requested
* @param[in] action_name The action name - used for error prints only
* @param[in] bound_path_string The path name string that has been bound.
* @param[out] out_transforms A pointer that will be populated with the output
* array's address, or NULL.
* @param[out] out_num_transforms Where to populate the array size
* @return false if not possible
*
* @relates oxr_input_transform
*/
bool
oxr_input_transform_create_chain(struct oxr_logger *log,
struct oxr_sink_logger *slog,
enum xrt_input_type input_type,
XrActionType result_type,
const char *action_name,
const char *bound_path_string,
struct oxr_input_transform **out_transforms,
size_t *out_num_transforms);
/*!
* @}
*/
#ifdef __cplusplus
}
#endif

View file

@ -1400,7 +1400,8 @@ struct oxr_action_state
};
/*!
* A input action pair of a @ref xrt_input and a @ref xrt_device.
* A input action pair of a @ref xrt_input and a @ref xrt_device, along with the
* required transform.
*
* @see xrt_device
* @see xrt_input
@ -1409,6 +1410,8 @@ struct oxr_action_input
{
struct xrt_device *xdev;
struct xrt_input *input;
struct oxr_input_transform *transforms;
size_t num_transforms;
};
/*!
@ -1424,7 +1427,10 @@ struct oxr_action_output
};
/*!
* A set of inputs for a single sub action path.
* The set of inputs/outputs for a single sub-action path for an action.
*
* Each @ref oxr_action_attachment has one of these for every known sub-action
* path in the spec. Many, or even most, will be "empty".
*
* @see oxr_action_attachment
*/
@ -1438,8 +1444,6 @@ struct oxr_action_cache
int64_t stop_output_time;
size_t num_outputs;
struct oxr_action_output *outputs;
enum xrt_source_value_redirect redirect;
};
/*!

19
tests/CMakeLists.txt Normal file
View file

@ -0,0 +1,19 @@
# Copyright 2018-2020, Collabora, Ltd. SPDX-License-Identifier: BSL-1.0
# Catch2 main test driver
add_library(tests_main STATIC tests_main.cpp)
target_link_libraries(tests_main PUBLIC xrt-external-catch2)
if(ANDROID)
target_link_libraries(tests_main PUBLIC log)
endif()
# Input transform test
add_executable(tests_input_transform tests_input_transform.cpp)
target_link_libraries(tests_input_transform PRIVATE tests_main)
target_link_libraries(tests_input_transform PRIVATE
st_oxr
xrt-interfaces
xrt-external-openxr
aux_util)
add_test(NAME input_transform COMMAND tests_input_transform --success)

View file

@ -0,0 +1,272 @@
// Copyright 2018-2020, Collabora, Ltd.
// SPDX-License-Identifier: BSL-1.0
/*!
* @file
* @brief Input transform tests.
* @author Ryan Pavlik <ryan.pavlik@collabora.com>
*/
#include "catch/catch.hpp"
#include <xrt/xrt_defines.h>
#include <oxr/oxr_input_transform.h>
#include <oxr/oxr_logger.h>
#include <oxr/oxr_objects.h>
using Catch::Generators::values;
TEST_CASE("input_transform") {
struct oxr_logger log;
oxr_log_init(&log, "test");
struct oxr_sink_logger slog = {};
struct oxr_input_transform *transforms = NULL;
size_t num_transforms = 0;
oxr_input_value_tagged input = {};
oxr_input_value_tagged output = {};
SECTION("Float action") {
XrActionType action_type = XR_ACTION_TYPE_FLOAT_INPUT;
SECTION("From Vec1 -1 to 1 identity") {
input.type = XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE;
CHECK(oxr_input_transform_create_chain(
&log, &slog, input.type, action_type, "float_action",
"/dummy_float", &transforms, &num_transforms));
// Just identity
CHECK(num_transforms == 1);
CHECK(transforms != nullptr);
SECTION("Roundtrip") {
auto value =
GENERATE(values({-1.f, -0.5f, 0.f, -0.f, 0.5f, 1.f}));
input.value.vec1.x = value;
CHECK(oxr_input_transform_process(transforms, num_transforms,
&input, &output));
CHECK(input.value.vec1.x == output.value.vec1.x);
}
}
SECTION("From Vec1 0 to 1 identity") {
input.type = XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE;
CHECK(oxr_input_transform_create_chain(
&log, &slog, input.type, action_type, "float_action",
"/dummy_float", &transforms, &num_transforms));
// Just identity
CHECK(num_transforms == 1);
CHECK(transforms != nullptr);
SECTION("Roundtrip") {
auto value = GENERATE(values({0.f, -0.f, 0.5f, 1.f}));
input.value.vec1.x = value;
CHECK(oxr_input_transform_process(transforms, num_transforms,
&input, &output));
CHECK(input.value.vec1.x == output.value.vec1.x);
}
}
SECTION("From Vec2 input") {
input.type = XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE;
input.value.vec2.x = -1;
input.value.vec2.y = 1;
SECTION("path component x") {
CHECK(oxr_input_transform_create_chain(
&log, &slog, input.type, action_type, "float_action",
"/dummy_vec2/x", &transforms, &num_transforms));
// Identity and a get-x
CHECK(num_transforms == 2);
CHECK(transforms != nullptr);
CHECK(oxr_input_transform_process(transforms, num_transforms,
&input, &output));
CHECK(input.value.vec2.x == output.value.vec1.x);
}
SECTION("path component y") {
CHECK(oxr_input_transform_create_chain(
&log, &slog, input.type, action_type, "float_action",
"/dummy_vec2/y", &transforms, &num_transforms));
// Identity and a get-y
CHECK(num_transforms == 2);
CHECK(transforms != nullptr);
CHECK(oxr_input_transform_process(transforms, num_transforms,
&input, &output));
CHECK(input.value.vec2.y == output.value.vec1.x);
}
SECTION("no component") {
CHECK_FALSE(oxr_input_transform_create_chain(
&log, &slog, input.type, action_type, "float_action",
"/dummy_vec2", &transforms, &num_transforms));
// Shouldn't make a transform, not possible
CHECK(num_transforms == 0);
CHECK(transforms == nullptr);
// shouldn't do anything, but shouldn't explode.
CHECK_FALSE(oxr_input_transform_process(
transforms, num_transforms, &input, &output));
}
}
SECTION("From bool input") {
input.type = XRT_INPUT_TYPE_BOOLEAN;
CHECK(oxr_input_transform_create_chain(
&log, &slog, input.type, action_type, "float_action",
"/dummy_bool", &transforms, &num_transforms));
// Identity and a bool-to-float
CHECK(num_transforms == 2);
CHECK(transforms != nullptr);
SECTION("False") {
input.value.boolean = false;
CHECK(oxr_input_transform_process(transforms, num_transforms,
&input, &output));
CHECK(0.0f == output.value.vec1.x);
}
SECTION("True") {
input.value.boolean = true;
CHECK(oxr_input_transform_process(transforms, num_transforms,
&input, &output));
CHECK(1.0f == output.value.vec1.x);
}
}
}
SECTION("Bool action") {
XrActionType action_type = XR_ACTION_TYPE_BOOLEAN_INPUT;
SECTION("From Bool identity") {
input.type = XRT_INPUT_TYPE_BOOLEAN;
CHECK(oxr_input_transform_create_chain(
&log, &slog, input.type, action_type, "bool_action",
"/dummy_bool", &transforms, &num_transforms));
CHECK(num_transforms == 1);
CHECK(transforms != nullptr);
SECTION("Roundtrip") {
auto value = GENERATE(values({0, 1}));
input.value.boolean = bool(value);
CHECK(oxr_input_transform_process(transforms, num_transforms,
&input, &output));
CHECK(input.value.boolean == output.value.boolean);
}
}
SECTION("From Vec1 -1 to 1") {
input.type = XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE;
CHECK(oxr_input_transform_create_chain(
&log, &slog, input.type, action_type, "bool_action",
"/dummy_float", &transforms, &num_transforms));
CHECK(num_transforms == 2);
CHECK(transforms != nullptr);
SECTION("True") {
auto value = GENERATE(values({0.5f, 1.f}));
input.value.vec1.x = value;
CHECK(oxr_input_transform_process(transforms, num_transforms,
&input, &output));
CHECK(output.value.boolean == true);
}
SECTION("False") {
auto value = GENERATE(values({0.0f, -1.f}));
input.value.vec1.x = value;
CHECK(oxr_input_transform_process(transforms, num_transforms,
&input, &output));
CHECK(output.value.boolean == false);
}
}
SECTION("From Vec1 0 to 1") {
input.type = XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE;
CHECK(oxr_input_transform_create_chain(
&log, &slog, input.type, action_type, "bool_action",
"/dummy_float", &transforms, &num_transforms));
CHECK(num_transforms == 2);
CHECK(transforms != nullptr);
SECTION("True") {
auto value = GENERATE(values({0.95f, 1.f}));
input.value.vec1.x = value;
CHECK(oxr_input_transform_process(transforms, num_transforms,
&input, &output));
CHECK(output.value.boolean == true);
}
SECTION("False") {
auto value = GENERATE(values({0.0f, 0.5f}));
input.value.vec1.x = value;
CHECK(oxr_input_transform_process(transforms, num_transforms,
&input, &output));
CHECK(output.value.boolean == false);
}
}
SECTION("From Vec2") {
input.type = XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE;
input.value.vec2.x = -1;
input.value.vec2.y = 1;
SECTION("x") {
CHECK(oxr_input_transform_create_chain(
&log, &slog, input.type, action_type, "float_action",
"/dummy_vec2/x", &transforms, &num_transforms));
CHECK(num_transforms == 3);
CHECK(transforms != nullptr);
CHECK(oxr_input_transform_process(transforms, num_transforms,
&input, &output));
CHECK(false == output.value.boolean);
}
SECTION("y") {
CHECK(oxr_input_transform_create_chain(
&log, &slog, input.type, action_type, "float_action",
"/dummy_vec2/y", &transforms, &num_transforms));
CHECK(num_transforms == 3);
CHECK(transforms != nullptr);
CHECK(oxr_input_transform_process(transforms, num_transforms,
&input, &output));
CHECK(true == output.value.boolean);
}
SECTION("no component") {
CHECK_FALSE(oxr_input_transform_create_chain(
&log, &slog, input.type, action_type, "float_action",
"/dummy", &transforms, &num_transforms));
// Shouldn't make a transform, not possible
CHECK(num_transforms == 0);
CHECK(transforms == nullptr);
// shouldn't do anything, but shouldn't explode.
CHECK_FALSE(oxr_input_transform_process(
transforms, num_transforms, &input, &output));
}
}
}
SECTION("Pose action") {
XrActionType action_type = XR_ACTION_TYPE_POSE_INPUT;
SECTION("From Pose identity") {
input.type = XRT_INPUT_TYPE_POSE;
CHECK(oxr_input_transform_create_chain(
&log, &slog, input.type, action_type, "pose_action",
"/dummy_pose", &transforms, &num_transforms));
// Identity, just so this binding doesn't get culled.
CHECK(num_transforms == 1);
}
SECTION("From other input") {
auto input_type = GENERATE(values(
{XRT_INPUT_TYPE_BOOLEAN, XRT_INPUT_TYPE_VEC1_MINUS_ONE_TO_ONE,
XRT_INPUT_TYPE_VEC1_ZERO_TO_ONE,
XRT_INPUT_TYPE_VEC2_MINUS_ONE_TO_ONE,
XRT_INPUT_TYPE_VEC3_MINUS_ONE_TO_ONE}));
CAPTURE(input_type);
input.type = input_type;
CHECK_FALSE(oxr_input_transform_create_chain(
&log, &slog, input.type, action_type, "pose_action", "/dummy",
&transforms, &num_transforms));
// not possible
CHECK(num_transforms == 0);
CHECK(transforms == nullptr);
}
}
oxr_log_slog(&log, &slog);
oxr_input_transform_destroy(&transforms);
CHECK(NULL == transforms);
}

10
tests/tests_main.cpp Normal file
View file

@ -0,0 +1,10 @@
// Copyright 2020, Collabora, Ltd.
// SPDX-License-Identifier: BSL-1.0
/*!
* @file
* @brief Translation unit to build Catch2 main.
* @author Ryan Pavlik <ryan.pavlik@collabora.com>
*/
#define CATCH_CONFIG_MAIN
#include "catch/catch.hpp"