The open source OpenXR runtime
1// Copyright 2020-2024, Collabora, Ltd.
2// SPDX-License-Identifier: BSL-1.0
3/*!
4 * @file
5 * @brief Sample HMD device, use as a starting point to make your own device driver.
6 *
7 *
8 * Based largely on simulated_hmd.c
9 *
10 * @author Jakob Bornecrantz <jakob@collabora.com>
11 * @author Rylie Pavlik <rylie.pavlik@collabora.com>
12 * @ingroup drv_sample
13 */
14
15#include "os/os_time.h"
16#include "xrt/xrt_defines.h"
17#include "xrt/xrt_device.h"
18
19#include "math/m_relation_history.h"
20#include "math/m_api.h"
21#include "math/m_mathinclude.h" // IWYU pragma: keep
22
23#include "util/u_debug.h"
24#include "util/u_device.h"
25#include "util/u_distortion_mesh.h"
26#include "util/u_logging.h"
27#include "util/u_misc.h"
28#include "util/u_time.h"
29#include "util/u_var.h"
30#include "util/u_visibility_mask.h"
31#include "xrt/xrt_results.h"
32
33#include <stdio.h>
34
35
36/*
37 *
38 * Structs and defines.
39 *
40 */
41
42/*!
43 * A sample HMD device.
44 *
45 * @implements xrt_device
46 */
47struct sample_hmd
48{
49 struct xrt_device base;
50
51 struct xrt_pose pose;
52
53 enum u_logging_level log_level;
54
55 // has built-in mutex so thread safe
56 struct m_relation_history *relation_hist;
57};
58
59
60/// Casting helper function
61static inline struct sample_hmd *
62sample_hmd(struct xrt_device *xdev)
63{
64 return (struct sample_hmd *)xdev;
65}
66
67DEBUG_GET_ONCE_LOG_OPTION(sample_log, "SAMPLE_LOG", U_LOGGING_WARN)
68
69#define HMD_TRACE(hmd, ...) U_LOG_XDEV_IFL_T(&hmd->base, hmd->log_level, __VA_ARGS__)
70#define HMD_DEBUG(hmd, ...) U_LOG_XDEV_IFL_D(&hmd->base, hmd->log_level, __VA_ARGS__)
71#define HMD_INFO(hmd, ...) U_LOG_XDEV_IFL_I(&hmd->base, hmd->log_level, __VA_ARGS__)
72#define HMD_ERROR(hmd, ...) U_LOG_XDEV_IFL_E(&hmd->base, hmd->log_level, __VA_ARGS__)
73
74static void
75sample_hmd_destroy(struct xrt_device *xdev)
76{
77 struct sample_hmd *hmd = sample_hmd(xdev);
78
79 // Remove the variable tracking.
80 u_var_remove_root(hmd);
81
82
83 m_relation_history_destroy(&hmd->relation_hist);
84
85 u_device_free(&hmd->base);
86}
87
88static xrt_result_t
89sample_hmd_update_inputs(struct xrt_device *xdev)
90{
91 /*
92 * Empty for the sample driver, if you need to you should
93 * put code to update the attached inputs fields. If not you can use
94 * the u_device_noop_update_inputs helper to make it a no-op.
95 */
96 return XRT_SUCCESS;
97}
98
99static xrt_result_t
100sample_hmd_get_tracked_pose(struct xrt_device *xdev,
101 enum xrt_input_name name,
102 int64_t at_timestamp_ns,
103 struct xrt_space_relation *out_relation)
104{
105 struct sample_hmd *hmd = sample_hmd(xdev);
106
107 if (name != XRT_INPUT_GENERIC_HEAD_POSE) {
108 U_LOG_XDEV_UNSUPPORTED_INPUT(&hmd->base, hmd->log_level, name);
109 return XRT_ERROR_INPUT_UNSUPPORTED;
110 }
111
112 struct xrt_space_relation relation = XRT_SPACE_RELATION_ZERO;
113
114 enum m_relation_history_result history_result =
115 m_relation_history_get(hmd->relation_hist, at_timestamp_ns, &relation);
116 if (history_result == M_RELATION_HISTORY_RESULT_INVALID) {
117 // If you get in here, it means you did not push any poses into the relation history.
118 // You may want to handle this differently.
119 HMD_ERROR(hmd, "Internal error: no poses pushed?");
120 }
121
122 if ((relation.relation_flags & XRT_SPACE_RELATION_ORIENTATION_VALID_BIT) != 0) {
123 // If we provide an orientation, make sure that it is normalized.
124 math_quat_normalize(&relation.pose.orientation);
125 }
126
127 *out_relation = relation;
128 return XRT_SUCCESS;
129}
130
131static xrt_result_t
132sample_hmd_get_view_poses(struct xrt_device *xdev,
133 const struct xrt_vec3 *default_eye_relation,
134 int64_t at_timestamp_ns,
135 enum xrt_view_type view_type,
136 uint32_t view_count,
137 struct xrt_space_relation *out_head_relation,
138 struct xrt_fov *out_fovs,
139 struct xrt_pose *out_poses)
140{
141 /*
142 * For HMDs you can call this function or directly set
143 * the `get_view_poses` function on the device to it.
144 */
145 return u_device_get_view_poses( //
146 xdev, //
147 default_eye_relation, //
148 at_timestamp_ns, //
149 view_type, //
150 view_count, //
151 out_head_relation, //
152 out_fovs, //
153 out_poses); //
154}
155
156static xrt_result_t
157sample_hmd_get_visibility_mask(struct xrt_device *xdev,
158 enum xrt_visibility_mask_type type,
159 uint32_t view_index,
160 struct xrt_visibility_mask **out_mask)
161{
162 struct xrt_fov fov = xdev->hmd->distortion.fov[view_index];
163 u_visibility_mask_get_default(type, &fov, out_mask);
164 return XRT_SUCCESS;
165}
166
167struct xrt_device *
168sample_hmd_create(void)
169{
170 // This indicates you won't be using Monado's built-in tracking algorithms.
171 enum u_device_alloc_flags flags =
172 (enum u_device_alloc_flags)(U_DEVICE_ALLOC_HMD | U_DEVICE_ALLOC_TRACKING_NONE);
173
174 struct sample_hmd *hmd = U_DEVICE_ALLOCATE(struct sample_hmd, flags, 1, 0);
175
176 // This list should be ordered, most preferred first.
177 size_t idx = 0;
178 hmd->base.hmd->blend_modes[idx++] = XRT_BLEND_MODE_OPAQUE;
179 hmd->base.hmd->blend_mode_count = idx;
180
181 hmd->base.update_inputs = sample_hmd_update_inputs;
182 hmd->base.get_tracked_pose = sample_hmd_get_tracked_pose;
183 hmd->base.get_view_poses = sample_hmd_get_view_poses;
184 hmd->base.get_visibility_mask = sample_hmd_get_visibility_mask;
185 hmd->base.destroy = sample_hmd_destroy;
186
187 // Distortion information, fills in xdev->compute_distortion().
188 u_distortion_mesh_set_none(&hmd->base);
189
190 // populate this with something more complex if required
191 // hmd->base.compute_distortion = sample_hmd_compute_distortion;
192
193 hmd->pose = (struct xrt_pose)XRT_POSE_IDENTITY;
194 hmd->log_level = debug_get_log_option_sample_log();
195
196 // Print name.
197 snprintf(hmd->base.str, XRT_DEVICE_NAME_LEN, "Sample HMD");
198 snprintf(hmd->base.serial, XRT_DEVICE_NAME_LEN, "Sample HMD S/N");
199
200 m_relation_history_create(&hmd->relation_hist);
201
202 // Setup input.
203 hmd->base.name = XRT_DEVICE_GENERIC_HMD;
204 hmd->base.device_type = XRT_DEVICE_TYPE_HMD;
205 hmd->base.inputs[0].name = XRT_INPUT_GENERIC_HEAD_POSE;
206 hmd->base.supported.orientation_tracking = true;
207 hmd->base.supported.position_tracking = true;
208
209 // Set up display details
210 // refresh rate
211 hmd->base.hmd->screens[0].nominal_frame_interval_ns = time_s_to_ns(1.0f / 90.0f);
212 hmd->base.hmd->screens[0].scanout_direction = XRT_SCANOUT_DIRECTION_NONE;
213 hmd->base.hmd->screens[0].scanout_time_ns = 0;
214
215 const double hFOV = 90 * (M_PI / 180.0);
216 const double vFOV = 96.73 * (M_PI / 180.0);
217 // center of projection
218 const double hCOP = 0.529;
219 const double vCOP = 0.5;
220 if (
221 /* right eye */
222 !math_compute_fovs(1, hCOP, hFOV, 1, vCOP, vFOV, &hmd->base.hmd->distortion.fov[1]) ||
223 /*
224 * left eye - same as right eye, except the horizontal center of projection is moved in the opposite
225 * direction now
226 */
227 !math_compute_fovs(1, 1.0 - hCOP, hFOV, 1, vCOP, vFOV, &hmd->base.hmd->distortion.fov[0])) {
228 // If those failed, it means our math was impossible.
229 HMD_ERROR(hmd, "Failed to setup basic device info");
230 sample_hmd_destroy(&hmd->base);
231 return NULL;
232 }
233 const int panel_w = 1080;
234 const int panel_h = 1200;
235
236 // Single "screen" (always the case)
237 hmd->base.hmd->screens[0].w_pixels = panel_w * 2;
238 hmd->base.hmd->screens[0].h_pixels = panel_h;
239
240 // Left, Right
241 for (uint8_t eye = 0; eye < 2; ++eye) {
242 hmd->base.hmd->views[eye].display.w_pixels = panel_w;
243 hmd->base.hmd->views[eye].display.h_pixels = panel_h;
244 hmd->base.hmd->views[eye].viewport.y_pixels = 0;
245 hmd->base.hmd->views[eye].viewport.w_pixels = panel_w;
246 hmd->base.hmd->views[eye].viewport.h_pixels = panel_h;
247 // if rotation is not identity, the dimensions can get more complex.
248 hmd->base.hmd->views[eye].rot = u_device_rotation_ident;
249 }
250 // left eye starts at x=0, right eye starts at x=panel_width
251 hmd->base.hmd->views[0].viewport.x_pixels = 0;
252 hmd->base.hmd->views[1].viewport.x_pixels = panel_w;
253
254 // Distortion information, fills in xdev->compute_distortion().
255 u_distortion_mesh_set_none(&hmd->base);
256
257 // Just put an initial identity value in the tracker
258 struct xrt_space_relation identity = XRT_SPACE_RELATION_ZERO;
259 identity.relation_flags = (enum xrt_space_relation_flags)(XRT_SPACE_RELATION_ORIENTATION_TRACKED_BIT |
260 XRT_SPACE_RELATION_ORIENTATION_VALID_BIT);
261 uint64_t now = os_monotonic_get_ns();
262 m_relation_history_push(hmd->relation_hist, &identity, now);
263
264 // Setup variable tracker: Optional but useful for debugging
265 u_var_add_root(hmd, "Sample HMD", true);
266 u_var_add_log_level(hmd, &hmd->log_level, "log_level");
267
268
269 return &hmd->base;
270}