1/**************************************************************************/
2/* openxr_api.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "openxr_api.h"
32
33#include "extensions/openxr_extension_wrapper_extension.h"
34#include "openxr_interface.h"
35#include "openxr_util.h"
36
37#include "core/config/engine.h"
38#include "core/config/project_settings.h"
39#include "core/os/memory.h"
40#include "core/version.h"
41
42#ifdef TOOLS_ENABLED
43#include "editor/editor_settings.h"
44#endif
45
46// We need to have all the graphics API defines before the Vulkan or OpenGL
47// extensions are included, otherwise we'll only get one graphics API.
48#ifdef VULKAN_ENABLED
49#define XR_USE_GRAPHICS_API_VULKAN
50#endif
51#if defined(GLES3_ENABLED) && !defined(MACOS_ENABLED)
52#ifdef ANDROID_ENABLED
53#define XR_USE_GRAPHICS_API_OPENGL_ES
54#include <EGL/egl.h>
55#include <EGL/eglext.h>
56#include <GLES3/gl3.h>
57#include <GLES3/gl3ext.h>
58#else
59#define XR_USE_GRAPHICS_API_OPENGL
60#endif // ANDROID_ENABLED
61#ifdef X11_ENABLED
62#include OPENGL_INCLUDE_H
63#define GL_GLEXT_PROTOTYPES 1
64#define GL3_PROTOTYPES 1
65#include "thirdparty/glad/glad/gl.h"
66#include "thirdparty/glad/glad/glx.h"
67
68#include <X11/Xlib.h>
69#endif // X11_ENABLED
70#endif // GLES_ENABLED
71
72#ifdef VULKAN_ENABLED
73#include "extensions/openxr_vulkan_extension.h"
74#endif
75
76#if defined(GLES3_ENABLED) && !defined(MACOS_ENABLED)
77#include "extensions/openxr_opengl_extension.h"
78#endif
79
80#include "extensions/openxr_composition_layer_depth_extension.h"
81#include "extensions/openxr_fb_display_refresh_rate_extension.h"
82#include "extensions/openxr_fb_passthrough_extension_wrapper.h"
83
84#ifdef ANDROID_ENABLED
85#define OPENXR_LOADER_NAME "libopenxr_loader.so"
86#endif
87
88OpenXRAPI *OpenXRAPI::singleton = nullptr;
89Vector<OpenXRExtensionWrapper *> OpenXRAPI::registered_extension_wrappers;
90
91bool OpenXRAPI::openxr_is_enabled(bool p_check_run_in_editor) {
92 // @TODO we need an overrule switch so we can force enable openxr, i.e run "godot --openxr_enabled"
93
94 if (Engine::get_singleton()->is_editor_hint() && p_check_run_in_editor) {
95 // Disabled for now, using XR inside of the editor we'll be working on during the coming months.
96 return false;
97 } else {
98 if (XRServer::get_xr_mode() == XRServer::XRMODE_DEFAULT) {
99 return GLOBAL_GET("xr/openxr/enabled");
100 } else {
101 return XRServer::get_xr_mode() == XRServer::XRMODE_ON;
102 }
103 }
104}
105
106String OpenXRAPI::get_default_action_map_resource_name() {
107 String name = GLOBAL_GET("xr/openxr/default_action_map");
108
109 return name;
110}
111
112String OpenXRAPI::get_error_string(XrResult result) {
113 if (XR_SUCCEEDED(result)) {
114 return String("Succeeded");
115 }
116
117 if (instance == XR_NULL_HANDLE) {
118 Array args;
119 args.push_back(Variant(result));
120 return String("Error code {0}").format(args);
121 }
122
123 char resultString[XR_MAX_RESULT_STRING_SIZE];
124 xrResultToString(instance, result, resultString);
125
126 return String(resultString);
127}
128
129String OpenXRAPI::get_swapchain_format_name(int64_t p_swapchain_format) const {
130 // This is rendering engine dependent...
131 if (graphics_extension) {
132 return graphics_extension->get_swapchain_format_name(p_swapchain_format);
133 }
134
135 return String("Swapchain format ") + String::num_int64(int64_t(p_swapchain_format));
136}
137
138bool OpenXRAPI::load_layer_properties() {
139 // This queries additional layers that are available and can be initialized when we create our OpenXR instance
140 if (layer_properties != nullptr) {
141 // already retrieved this
142 return true;
143 }
144
145 // Note, instance is not yet setup so we can't use get_error_string to retrieve our error
146 XrResult result = xrEnumerateApiLayerProperties(0, &num_layer_properties, nullptr);
147 ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate number of api layer properties");
148
149 layer_properties = (XrApiLayerProperties *)memalloc(sizeof(XrApiLayerProperties) * num_layer_properties);
150 ERR_FAIL_NULL_V(layer_properties, false);
151 for (uint32_t i = 0; i < num_layer_properties; i++) {
152 layer_properties[i].type = XR_TYPE_API_LAYER_PROPERTIES;
153 layer_properties[i].next = nullptr;
154 }
155
156 result = xrEnumerateApiLayerProperties(num_layer_properties, &num_layer_properties, layer_properties);
157 ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate api layer properties");
158
159 for (uint32_t i = 0; i < num_layer_properties; i++) {
160 print_verbose(String("OpenXR: Found OpenXR layer ") + layer_properties[i].layerName);
161 }
162
163 return true;
164}
165
166bool OpenXRAPI::load_supported_extensions() {
167 // This queries supported extensions that are available and can be initialized when we create our OpenXR instance
168
169 if (supported_extensions != nullptr) {
170 // already retrieved this
171 return true;
172 }
173
174 // Note, instance is not yet setup so we can't use get_error_string to retrieve our error
175 XrResult result = xrEnumerateInstanceExtensionProperties(nullptr, 0, &num_supported_extensions, nullptr);
176 ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate number of extension properties");
177
178 supported_extensions = (XrExtensionProperties *)memalloc(sizeof(XrExtensionProperties) * num_supported_extensions);
179 ERR_FAIL_NULL_V(supported_extensions, false);
180
181 // set our types
182 for (uint32_t i = 0; i < num_supported_extensions; i++) {
183 supported_extensions[i].type = XR_TYPE_EXTENSION_PROPERTIES;
184 supported_extensions[i].next = nullptr;
185 }
186 result = xrEnumerateInstanceExtensionProperties(nullptr, num_supported_extensions, &num_supported_extensions, supported_extensions);
187 ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate extension properties");
188
189 for (uint32_t i = 0; i < num_supported_extensions; i++) {
190 print_verbose(String("OpenXR: Found OpenXR extension ") + supported_extensions[i].extensionName);
191 }
192
193 return true;
194}
195
196bool OpenXRAPI::is_extension_supported(const String &p_extension) const {
197 for (uint32_t i = 0; i < num_supported_extensions; i++) {
198 if (supported_extensions[i].extensionName == p_extension) {
199 return true;
200 }
201 }
202
203 return false;
204}
205
206bool OpenXRAPI::is_extension_enabled(const String &p_extension) const {
207 CharString extension = p_extension.ascii();
208
209 for (int i = 0; i < enabled_extensions.size(); i++) {
210 if (strcmp(enabled_extensions[i].ptr(), extension.ptr()) == 0) {
211 return true;
212 }
213 }
214
215 return false;
216}
217
218bool OpenXRAPI::is_top_level_path_supported(const String &p_toplevel_path) {
219 String required_extension = OpenXRInteractionProfileMetadata::get_singleton()->get_top_level_extension(p_toplevel_path);
220
221 // If unsupported is returned we likely have a misspelled interaction profile path in our action map. Always output that as an error.
222 ERR_FAIL_COND_V_MSG(required_extension == XR_PATH_UNSUPPORTED_NAME, false, "OpenXR: Unsupported toplevel path " + p_toplevel_path);
223
224 if (required_extension == "") {
225 // no extension needed, core top level are always "supported", they just won't be used if not really supported
226 return true;
227 }
228
229 if (!is_extension_enabled(required_extension)) {
230 // It is very likely we have top level paths for which the extension is not available so don't flood the logs with unnecessary spam.
231 print_verbose("OpenXR: Top level path " + p_toplevel_path + " requires extension " + required_extension);
232 return false;
233 }
234
235 return true;
236}
237
238bool OpenXRAPI::is_interaction_profile_supported(const String &p_ip_path) {
239 String required_extension = OpenXRInteractionProfileMetadata::get_singleton()->get_interaction_profile_extension(p_ip_path);
240
241 // If unsupported is returned we likely have a misspelled interaction profile path in our action map. Always output that as an error.
242 ERR_FAIL_COND_V_MSG(required_extension == XR_PATH_UNSUPPORTED_NAME, false, "OpenXR: Unsupported interaction profile " + p_ip_path);
243
244 if (required_extension == "") {
245 // no extension needed, core interaction profiles are always "supported", they just won't be used if not really supported
246 return true;
247 }
248
249 if (!is_extension_enabled(required_extension)) {
250 // It is very likely we have interaction profiles for which the extension is not available so don't flood the logs with unnecessary spam.
251 print_verbose("OpenXR: Interaction profile " + p_ip_path + " requires extension " + required_extension);
252 return false;
253 }
254
255 return true;
256}
257
258bool OpenXRAPI::interaction_profile_supports_io_path(const String &p_ip_path, const String &p_io_path) {
259 if (!is_interaction_profile_supported(p_ip_path)) {
260 return false;
261 }
262
263 const OpenXRInteractionProfileMetadata::IOPath *io_path = OpenXRInteractionProfileMetadata::get_singleton()->get_io_path(p_ip_path, p_io_path);
264
265 // If the io_path is not part of our metadata we've likely got a misspelled name or a bad action map, report
266 ERR_FAIL_NULL_V_MSG(io_path, false, "OpenXR: Unsupported io path " + String(p_ip_path) + String(p_io_path));
267
268 if (io_path->openxr_extension_name == "") {
269 // no extension needed, core io paths are always "supported", they just won't be used if not really supported
270 return true;
271 }
272
273 if (!is_extension_enabled(io_path->openxr_extension_name)) {
274 // It is very likely we have io paths for which the extension is not available so don't flood the logs with unnecessary spam.
275 print_verbose("OpenXR: IO path " + String(p_ip_path) + String(p_io_path) + " requires extension " + io_path->openxr_extension_name);
276 return false;
277 }
278
279 return true;
280}
281
282void OpenXRAPI::copy_string_to_char_buffer(const String p_string, char *p_buffer, int p_buffer_len) {
283 CharString char_string = p_string.utf8();
284 int len = char_string.length();
285 if (len < p_buffer_len - 1) {
286 // was having weird CI issues with strcpy so....
287 memcpy(p_buffer, char_string.get_data(), len);
288 p_buffer[len] = '\0';
289 } else {
290 memcpy(p_buffer, char_string.get_data(), p_buffer_len - 1);
291 p_buffer[p_buffer_len - 1] = '\0';
292 }
293}
294
295bool OpenXRAPI::create_instance() {
296 // Create our OpenXR instance, this will query any registered extension wrappers for extensions we need to enable.
297
298 // Append the extensions requested by the registered extension wrappers.
299 HashMap<String, bool *> requested_extensions;
300 for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
301 const HashMap<String, bool *> &wrapper_request_extensions = wrapper->get_requested_extensions();
302
303 for (const KeyValue<String, bool *> &requested_extension : wrapper_request_extensions) {
304 requested_extensions[requested_extension.key] = requested_extension.value;
305 }
306 }
307
308 // Check which extensions are supported.
309 enabled_extensions.clear();
310
311 for (KeyValue<String, bool *> &requested_extension : requested_extensions) {
312 if (!is_extension_supported(requested_extension.key)) {
313 if (requested_extension.value == nullptr) {
314 // Null means this is a manditory extension so we fail.
315 ERR_FAIL_V_MSG(false, String("OpenXR: OpenXR Runtime does not support ") + requested_extension.key + String(" extension!"));
316 } else {
317 // Set this extension as not supported.
318 *requested_extension.value = false;
319 }
320 } else if (requested_extension.value != nullptr) {
321 // Set this extension as supported.
322 *requested_extension.value = true;
323
324 // And record that we want to enable it.
325 enabled_extensions.push_back(requested_extension.key.ascii());
326 } else {
327 // Record that we want to enable this.
328 enabled_extensions.push_back(requested_extension.key.ascii());
329 }
330 }
331
332 Vector<const char *> extension_ptrs;
333 for (int i = 0; i < enabled_extensions.size(); i++) {
334 print_verbose(String("OpenXR: Enabling extension ") + String(enabled_extensions[i]));
335 extension_ptrs.push_back(enabled_extensions[i].get_data());
336 }
337
338 // Get our project name
339 String project_name = GLOBAL_GET("application/config/name");
340
341 // Create our OpenXR instance
342 XrApplicationInfo application_info{
343 "", // applicationName, we'll set this down below
344 1, // applicationVersion, we don't currently have this
345 "Godot Game Engine", // engineName
346 VERSION_MAJOR * 10000 + VERSION_MINOR * 100 + VERSION_PATCH, // engineVersion 4.0 -> 40000, 4.0.1 -> 40001, 4.1 -> 40100, etc.
347 XR_CURRENT_API_VERSION // apiVersion
348 };
349
350 void *next_pointer = nullptr;
351 for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
352 void *np = wrapper->set_instance_create_info_and_get_next_pointer(next_pointer);
353 if (np != nullptr) {
354 next_pointer = np;
355 }
356 }
357
358 XrInstanceCreateInfo instance_create_info = {
359 XR_TYPE_INSTANCE_CREATE_INFO, // type
360 next_pointer, // next
361 0, // createFlags
362 application_info, // applicationInfo
363 0, // enabledApiLayerCount, need to find out if we need support for this?
364 nullptr, // enabledApiLayerNames
365 uint32_t(extension_ptrs.size()), // enabledExtensionCount
366 extension_ptrs.ptr() // enabledExtensionNames
367 };
368
369 copy_string_to_char_buffer(project_name, instance_create_info.applicationInfo.applicationName, XR_MAX_APPLICATION_NAME_SIZE);
370
371 XrResult result = xrCreateInstance(&instance_create_info, &instance);
372 ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "Failed to create XR instance.");
373
374 // from this point on we can use get_error_string to get more info about our errors...
375
376 XrInstanceProperties instanceProps = {
377 XR_TYPE_INSTANCE_PROPERTIES, // type;
378 nullptr, // next
379 0, // runtimeVersion, from here will be set by our get call
380 "" // runtimeName
381 };
382
383 OPENXR_API_INIT_XR_FUNC_V(xrGetInstanceProperties);
384
385 result = xrGetInstanceProperties(instance, &instanceProps);
386 if (XR_FAILED(result)) {
387 // not fatal probably
388 print_line("OpenXR: Failed to get XR instance properties [", get_error_string(result), "]");
389
390 runtime_name = "";
391 runtime_version = "";
392 } else {
393 runtime_name = instanceProps.runtimeName;
394 runtime_version = OpenXRUtil::make_xr_version_string(instanceProps.runtimeVersion);
395 print_line("OpenXR: Running on OpenXR runtime: ", runtime_name, " ", runtime_version);
396 }
397
398 for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
399 wrapper->on_instance_created(instance);
400 }
401
402 return true;
403}
404
405bool OpenXRAPI::get_system_info() {
406 // Retrieve basic OpenXR system info based on the form factor we desire
407
408 // Retrieve the system for our form factor, fails if form factor is not available
409 XrSystemGetInfo system_get_info = {
410 XR_TYPE_SYSTEM_GET_INFO, // type;
411 nullptr, // next
412 form_factor // formFactor
413 };
414
415 XrResult result = xrGetSystem(instance, &system_get_info, &system_id);
416 if (XR_FAILED(result)) {
417 print_line("OpenXR: Failed to get system for our form factor [", get_error_string(result), "]");
418 return false;
419 }
420
421 // obtain info about our system, writing this out completely to make CI on Linux happy..
422 void *next_pointer = nullptr;
423 for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
424 void *np = wrapper->set_system_properties_and_get_next_pointer(next_pointer);
425 if (np != nullptr) {
426 next_pointer = np;
427 }
428 }
429
430 XrSystemProperties system_properties = {
431 XR_TYPE_SYSTEM_PROPERTIES, // type
432 next_pointer, // next
433 0, // systemId, from here will be set by our get call
434 0, // vendorId
435 "", // systemName
436 {
437 0, // maxSwapchainImageHeight
438 0, // maxSwapchainImageWidth
439 0, // maxLayerCount
440 }, // graphicsProperties
441 {
442 false, // orientationTracking
443 false // positionTracking
444 } // trackingProperties
445 };
446
447 result = xrGetSystemProperties(instance, system_id, &system_properties);
448 if (XR_FAILED(result)) {
449 print_line("OpenXR: Failed to get System properties [", get_error_string(result), "]");
450 return false;
451 }
452
453 // remember this state, we'll use it later
454 system_name = String(system_properties.systemName);
455 vendor_id = system_properties.vendorId;
456 graphics_properties = system_properties.graphicsProperties;
457 tracking_properties = system_properties.trackingProperties;
458
459 return true;
460}
461
462bool OpenXRAPI::load_supported_view_configuration_types() {
463 // This queries the supported configuration types, likely there will only be one choosing between Mono (phone AR) and Stereo (HMDs)
464
465 ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, false);
466
467 if (supported_view_configuration_types != nullptr) {
468 // free previous results
469 memfree(supported_view_configuration_types);
470 supported_view_configuration_types = nullptr;
471 }
472
473 XrResult result = xrEnumerateViewConfigurations(instance, system_id, 0, &num_view_configuration_types, nullptr);
474 if (XR_FAILED(result)) {
475 print_line("OpenXR: Failed to get view configuration count [", get_error_string(result), "]");
476 return false;
477 }
478
479 supported_view_configuration_types = (XrViewConfigurationType *)memalloc(sizeof(XrViewConfigurationType) * num_view_configuration_types);
480 ERR_FAIL_NULL_V(supported_view_configuration_types, false);
481
482 result = xrEnumerateViewConfigurations(instance, system_id, num_view_configuration_types, &num_view_configuration_types, supported_view_configuration_types);
483 ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerateview configurations");
484 ERR_FAIL_COND_V_MSG(num_view_configuration_types == 0, false, "OpenXR: Failed to enumerateview configurations"); // JIC there should be at least 1!
485
486 for (uint32_t i = 0; i < num_view_configuration_types; i++) {
487 print_verbose(String("OpenXR: Found supported view configuration ") + OpenXRUtil::get_view_configuration_name(supported_view_configuration_types[i]));
488 }
489
490 // Check value we loaded at startup...
491 if (!is_view_configuration_supported(view_configuration)) {
492 print_verbose(String("OpenXR: ") + OpenXRUtil::get_view_configuration_name(view_configuration) + String(" isn't supported, defaulting to ") + OpenXRUtil::get_view_configuration_name(supported_view_configuration_types[0]));
493
494 view_configuration = supported_view_configuration_types[0];
495 }
496
497 return true;
498}
499
500bool OpenXRAPI::load_supported_environmental_blend_modes() {
501 // This queries the supported environmental blend modes.
502
503 ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, false);
504
505 if (supported_environment_blend_modes != nullptr) {
506 // free previous results
507 memfree(supported_environment_blend_modes);
508 supported_environment_blend_modes = nullptr;
509 num_supported_environment_blend_modes = 0;
510 }
511
512 XrResult result = xrEnumerateEnvironmentBlendModes(instance, system_id, view_configuration, 0, &num_supported_environment_blend_modes, nullptr);
513 if (XR_FAILED(result)) {
514 print_line("OpenXR: Failed to get supported environmental blend mode count [", get_error_string(result), "]");
515 return false;
516 }
517
518 supported_environment_blend_modes = (XrEnvironmentBlendMode *)memalloc(sizeof(XrEnvironmentBlendMode) * num_supported_environment_blend_modes);
519 ERR_FAIL_NULL_V(supported_environment_blend_modes, false);
520
521 result = xrEnumerateEnvironmentBlendModes(instance, system_id, view_configuration, num_supported_environment_blend_modes, &num_supported_environment_blend_modes, supported_environment_blend_modes);
522 ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate environmental blend modes");
523 ERR_FAIL_COND_V_MSG(num_supported_environment_blend_modes == 0, false, "OpenXR: Failed to enumerate environmental blend modes"); // JIC there should be at least 1!
524
525 for (uint32_t i = 0; i < num_supported_environment_blend_modes; i++) {
526 print_verbose(String("OpenXR: Found environmental blend mode ") + OpenXRUtil::get_environment_blend_mode_name(supported_environment_blend_modes[i]));
527 }
528
529 // Check value we loaded at startup...
530 if (!is_environment_blend_mode_supported(environment_blend_mode)) {
531 print_verbose(String("OpenXR: ") + OpenXRUtil::get_environment_blend_mode_name(environment_blend_mode) + String(" isn't supported, defaulting to ") + OpenXRUtil::get_environment_blend_mode_name(supported_environment_blend_modes[0]));
532
533 environment_blend_mode = supported_environment_blend_modes[0];
534 }
535
536 return true;
537}
538
539bool OpenXRAPI::is_view_configuration_supported(XrViewConfigurationType p_configuration_type) const {
540 ERR_FAIL_NULL_V(supported_view_configuration_types, false);
541
542 for (uint32_t i = 0; i < num_view_configuration_types; i++) {
543 if (supported_view_configuration_types[i] == p_configuration_type) {
544 return true;
545 }
546 }
547
548 return false;
549}
550
551bool OpenXRAPI::load_supported_view_configuration_views(XrViewConfigurationType p_configuration_type) {
552 // This loads our view configuration for each view so for a stereo HMD, we'll get two entries (that are likely identical)
553 // The returned data supplies us with the recommended render target size
554
555 if (!is_view_configuration_supported(p_configuration_type)) {
556 print_line("OpenXR: View configuration ", OpenXRUtil::get_view_configuration_name(view_configuration), " is not supported.");
557 return false;
558 }
559
560 if (view_configuration_views != nullptr) {
561 // free previous results
562 memfree(view_configuration_views);
563 view_configuration_views = nullptr;
564 }
565
566 XrResult result = xrEnumerateViewConfigurationViews(instance, system_id, p_configuration_type, 0, &view_count, nullptr);
567 if (XR_FAILED(result)) {
568 print_line("OpenXR: Failed to get view configuration count [", get_error_string(result), "]");
569 return false;
570 }
571
572 view_configuration_views = (XrViewConfigurationView *)memalloc(sizeof(XrViewConfigurationView) * view_count);
573 ERR_FAIL_NULL_V(view_configuration_views, false);
574
575 for (uint32_t i = 0; i < view_count; i++) {
576 view_configuration_views[i].type = XR_TYPE_VIEW_CONFIGURATION_VIEW;
577 view_configuration_views[i].next = nullptr;
578 }
579
580 result = xrEnumerateViewConfigurationViews(instance, system_id, p_configuration_type, view_count, &view_count, view_configuration_views);
581 ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate view configurations");
582
583 for (uint32_t i = 0; i < view_count; i++) {
584 print_verbose("OpenXR: Found supported view configuration view");
585 print_verbose(String(" - width: ") + itos(view_configuration_views[i].maxImageRectWidth));
586 print_verbose(String(" - height: ") + itos(view_configuration_views[i].maxImageRectHeight));
587 print_verbose(String(" - sample count: ") + itos(view_configuration_views[i].maxSwapchainSampleCount));
588 print_verbose(String(" - recommended render width: ") + itos(view_configuration_views[i].recommendedImageRectWidth));
589 print_verbose(String(" - recommended render height: ") + itos(view_configuration_views[i].recommendedImageRectHeight));
590 print_verbose(String(" - recommended render sample count: ") + itos(view_configuration_views[i].recommendedSwapchainSampleCount));
591 }
592
593 return true;
594}
595
596void OpenXRAPI::destroy_instance() {
597 if (view_configuration_views != nullptr) {
598 memfree(view_configuration_views);
599 view_configuration_views = nullptr;
600 }
601
602 if (supported_view_configuration_types != nullptr) {
603 memfree(supported_view_configuration_types);
604 supported_view_configuration_types = nullptr;
605 }
606
607 if (supported_environment_blend_modes != nullptr) {
608 memfree(supported_environment_blend_modes);
609 supported_environment_blend_modes = nullptr;
610 num_supported_environment_blend_modes = 0;
611 }
612
613 if (instance != XR_NULL_HANDLE) {
614 for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
615 wrapper->on_instance_destroyed();
616 }
617
618 xrDestroyInstance(instance);
619 instance = XR_NULL_HANDLE;
620 }
621 enabled_extensions.clear();
622
623 if (graphics_extension != nullptr) {
624 unregister_extension_wrapper(graphics_extension);
625 memdelete(graphics_extension);
626 graphics_extension = nullptr;
627 }
628}
629
630bool OpenXRAPI::create_session() {
631 ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, false);
632 ERR_FAIL_COND_V(session != XR_NULL_HANDLE, false);
633
634 void *next_pointer = nullptr;
635 for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
636 void *np = wrapper->set_session_create_and_get_next_pointer(next_pointer);
637 if (np != nullptr) {
638 next_pointer = np;
639 }
640 }
641
642 XrSessionCreateInfo session_create_info = {
643 XR_TYPE_SESSION_CREATE_INFO, // type
644 next_pointer, // next
645 0, // createFlags
646 system_id // systemId
647 };
648
649 XrResult result = xrCreateSession(instance, &session_create_info, &session);
650 if (XR_FAILED(result)) {
651 print_line("OpenXR: Failed to create session [", get_error_string(result), "]");
652 return false;
653 }
654
655 for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
656 wrapper->on_session_created(session);
657 }
658
659 return true;
660}
661
662bool OpenXRAPI::load_supported_reference_spaces() {
663 // loads the supported reference spaces for our OpenXR session
664
665 ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false);
666
667 if (supported_reference_spaces != nullptr) {
668 // free previous results
669 memfree(supported_reference_spaces);
670 supported_reference_spaces = nullptr;
671 }
672
673 XrResult result = xrEnumerateReferenceSpaces(session, 0, &num_reference_spaces, nullptr);
674 if (XR_FAILED(result)) {
675 print_line("OpenXR: Failed to get reference space count [", get_error_string(result), "]");
676 return false;
677 }
678
679 supported_reference_spaces = (XrReferenceSpaceType *)memalloc(sizeof(XrReferenceSpaceType) * num_reference_spaces);
680 ERR_FAIL_NULL_V(supported_reference_spaces, false);
681
682 result = xrEnumerateReferenceSpaces(session, num_reference_spaces, &num_reference_spaces, supported_reference_spaces);
683 ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate reference spaces");
684 ERR_FAIL_COND_V_MSG(num_reference_spaces == 0, false, "OpenXR: Failed to enumerate reference spaces");
685
686 for (uint32_t i = 0; i < num_reference_spaces; i++) {
687 print_verbose(String("OpenXR: Found supported reference space ") + OpenXRUtil::get_reference_space_name(supported_reference_spaces[i]));
688 }
689
690 // Check value we loaded at startup...
691 if (!is_reference_space_supported(reference_space)) {
692 print_verbose(String("OpenXR: ") + OpenXRUtil::get_reference_space_name(reference_space) + String(" isn't supported, defaulting to ") + OpenXRUtil::get_reference_space_name(supported_reference_spaces[0]));
693
694 reference_space = supported_reference_spaces[0];
695 }
696
697 return true;
698}
699
700bool OpenXRAPI::is_reference_space_supported(XrReferenceSpaceType p_reference_space) {
701 ERR_FAIL_NULL_V(supported_reference_spaces, false);
702
703 for (uint32_t i = 0; i < num_reference_spaces; i++) {
704 if (supported_reference_spaces[i] == p_reference_space) {
705 return true;
706 }
707 }
708
709 return false;
710}
711
712bool OpenXRAPI::setup_spaces() {
713 XrResult result;
714
715 XrPosef identityPose = {
716 { 0.0, 0.0, 0.0, 1.0 },
717 { 0.0, 0.0, 0.0 }
718 };
719
720 ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false);
721
722 // create play space
723 {
724 if (!is_reference_space_supported(reference_space)) {
725 print_line("OpenXR: reference space ", OpenXRUtil::get_reference_space_name(reference_space), " is not supported.");
726 return false;
727 }
728
729 XrReferenceSpaceCreateInfo play_space_create_info = {
730 XR_TYPE_REFERENCE_SPACE_CREATE_INFO, // type
731 nullptr, // next
732 reference_space, // referenceSpaceType
733 identityPose // poseInReferenceSpace
734 };
735
736 result = xrCreateReferenceSpace(session, &play_space_create_info, &play_space);
737 if (XR_FAILED(result)) {
738 print_line("OpenXR: Failed to create play space [", get_error_string(result), "]");
739 return false;
740 }
741 }
742
743 // create view space
744 {
745 if (!is_reference_space_supported(XR_REFERENCE_SPACE_TYPE_VIEW)) {
746 print_line("OpenXR: reference space XR_REFERENCE_SPACE_TYPE_VIEW is not supported.");
747 return false;
748 }
749
750 XrReferenceSpaceCreateInfo view_space_create_info = {
751 XR_TYPE_REFERENCE_SPACE_CREATE_INFO, // type
752 nullptr, // next
753 XR_REFERENCE_SPACE_TYPE_VIEW, // referenceSpaceType
754 identityPose // poseInReferenceSpace
755 };
756
757 result = xrCreateReferenceSpace(session, &view_space_create_info, &view_space);
758 if (XR_FAILED(result)) {
759 print_line("OpenXR: Failed to create view space [", get_error_string(result), "]");
760 return false;
761 }
762 }
763
764 return true;
765}
766
767bool OpenXRAPI::load_supported_swapchain_formats() {
768 ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false);
769
770 if (supported_swapchain_formats != nullptr) {
771 // free previous results
772 memfree(supported_swapchain_formats);
773 supported_swapchain_formats = nullptr;
774 }
775
776 XrResult result = xrEnumerateSwapchainFormats(session, 0, &num_swapchain_formats, nullptr);
777 if (XR_FAILED(result)) {
778 print_line("OpenXR: Failed to get swapchain format count [", get_error_string(result), "]");
779 return false;
780 }
781
782 supported_swapchain_formats = (int64_t *)memalloc(sizeof(int64_t) * num_swapchain_formats);
783 ERR_FAIL_NULL_V(supported_swapchain_formats, false);
784
785 result = xrEnumerateSwapchainFormats(session, num_swapchain_formats, &num_swapchain_formats, supported_swapchain_formats);
786 ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate swapchain formats");
787
788 for (uint32_t i = 0; i < num_swapchain_formats; i++) {
789 print_verbose(String("OpenXR: Found supported swapchain format ") + get_swapchain_format_name(supported_swapchain_formats[i]));
790 }
791
792 return true;
793}
794
795bool OpenXRAPI::is_swapchain_format_supported(int64_t p_swapchain_format) {
796 ERR_FAIL_NULL_V(supported_swapchain_formats, false);
797
798 for (uint32_t i = 0; i < num_swapchain_formats; i++) {
799 if (supported_swapchain_formats[i] == p_swapchain_format) {
800 return true;
801 }
802 }
803
804 return false;
805}
806
807bool OpenXRAPI::create_swapchains() {
808 ERR_FAIL_NULL_V(graphics_extension, false);
809 ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false);
810
811 /*
812 TODO: We need to improve on this, for now we're taking our old approach of creating our main swapchains and substituting
813 those for the ones Godot normally creates.
814 This however means we can only use swapchains for our main XR view.
815
816 It would have been nicer if we could override the swapchain creation in Godot with ours but we have a timing issue here.
817 We can't create XR swapchains until after our XR session is fully instantiated, yet Godot creates its swapchain much earlier.
818
819 Also Godot only creates a swapchain for the main output.
820 OpenXR will require us to create swapchains as the render target for additional viewports if we want to use the layer system
821 to optimize text rendering and background rendering as OpenXR may choose to reuse the results for reprojection while we're
822 already rendering the next frame.
823
824 Finally an area we need to expand upon is that Foveated rendering is only enabled for the swap chain we create,
825 as we render 3D content into internal buffers that are copied into the swapchain, we do now have (basic) VRS support
826 */
827
828 Size2 recommended_size = get_recommended_target_size();
829
830 // We start with our color swapchain...
831 {
832 // Build a vector with swapchain formats we want to use, from best fit to worst
833 Vector<int64_t> usable_swapchain_formats;
834 int64_t swapchain_format_to_use = 0;
835
836 graphics_extension->get_usable_swapchain_formats(usable_swapchain_formats);
837
838 // now find out which one is supported
839 for (int i = 0; i < usable_swapchain_formats.size() && swapchain_format_to_use == 0; i++) {
840 if (is_swapchain_format_supported(usable_swapchain_formats[i])) {
841 swapchain_format_to_use = usable_swapchain_formats[i];
842 }
843 }
844
845 if (swapchain_format_to_use == 0) {
846 swapchain_format_to_use = usable_swapchain_formats[0]; // just use the first one and hope for the best...
847 print_line("Couldn't find usable color swap chain format, using", get_swapchain_format_name(swapchain_format_to_use), "instead.");
848 } else {
849 print_verbose(String("Using color swap chain format:") + get_swapchain_format_name(swapchain_format_to_use));
850 }
851
852 if (!create_swapchain(XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, swapchain_format_to_use, recommended_size.width, recommended_size.height, view_configuration_views[0].recommendedSwapchainSampleCount, view_count, swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain, &swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain_graphics_data)) {
853 return false;
854 }
855 }
856
857 views = (XrView *)memalloc(sizeof(XrView) * view_count);
858 ERR_FAIL_NULL_V_MSG(views, false, "OpenXR Couldn't allocate memory for views");
859
860 projection_views = (XrCompositionLayerProjectionView *)memalloc(sizeof(XrCompositionLayerProjectionView) * view_count);
861 ERR_FAIL_NULL_V_MSG(projection_views, false, "OpenXR Couldn't allocate memory for projection views");
862
863 // We create our depth swapchain if:
864 // - we've enabled submitting depth buffer
865 // - we support our depth layer extension
866 // - we have our spacewarp extension (not yet implemented)
867 if (submit_depth_buffer && OpenXRCompositionLayerDepthExtension::get_singleton()->is_available()) {
868 // Build a vector with swapchain formats we want to use, from best fit to worst
869 Vector<int64_t> usable_swapchain_formats;
870 int64_t swapchain_format_to_use = 0;
871
872 graphics_extension->get_usable_depth_formats(usable_swapchain_formats);
873
874 // now find out which one is supported
875 for (int i = 0; i < usable_swapchain_formats.size() && swapchain_format_to_use == 0; i++) {
876 if (is_swapchain_format_supported(usable_swapchain_formats[i])) {
877 swapchain_format_to_use = usable_swapchain_formats[i];
878 }
879 }
880
881 if (swapchain_format_to_use == 0) {
882 print_line("Couldn't find usable depth swap chain format, depth buffer will not be submitted.");
883 } else {
884 print_verbose(String("Using depth swap chain format:") + get_swapchain_format_name(swapchain_format_to_use));
885
886 // Note, if VK_FORMAT_D32_SFLOAT is used here but we're using the forward+ renderer, we should probably output a warning.
887
888 if (!create_swapchain(XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, swapchain_format_to_use, recommended_size.width, recommended_size.height, view_configuration_views[0].recommendedSwapchainSampleCount, view_count, swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain, &swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain_graphics_data)) {
889 return false;
890 }
891
892 depth_views = (XrCompositionLayerDepthInfoKHR *)memalloc(sizeof(XrCompositionLayerDepthInfoKHR) * view_count);
893 ERR_FAIL_NULL_V_MSG(depth_views, false, "OpenXR Couldn't allocate memory for depth views");
894 }
895 }
896
897 // We create our velocity swapchain if:
898 // - we have our spacewarp extension (not yet implemented)
899 {
900 // TBD
901 }
902
903 for (uint32_t i = 0; i < view_count; i++) {
904 views[i].type = XR_TYPE_VIEW;
905 views[i].next = nullptr;
906
907 projection_views[i].type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW;
908 projection_views[i].next = nullptr;
909 projection_views[i].subImage.swapchain = swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain;
910 projection_views[i].subImage.imageArrayIndex = i;
911 projection_views[i].subImage.imageRect.offset.x = 0;
912 projection_views[i].subImage.imageRect.offset.y = 0;
913 projection_views[i].subImage.imageRect.extent.width = recommended_size.width;
914 projection_views[i].subImage.imageRect.extent.height = recommended_size.height;
915
916 if (submit_depth_buffer && OpenXRCompositionLayerDepthExtension::get_singleton()->is_available() && depth_views) {
917 projection_views[i].next = &depth_views[i];
918
919 depth_views[i].type = XR_TYPE_COMPOSITION_LAYER_DEPTH_INFO_KHR;
920 depth_views[i].next = nullptr;
921 depth_views[i].subImage.swapchain = swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain;
922 depth_views[i].subImage.imageArrayIndex = i;
923 depth_views[i].subImage.imageRect.offset.x = 0;
924 depth_views[i].subImage.imageRect.offset.y = 0;
925 depth_views[i].subImage.imageRect.extent.width = recommended_size.width;
926 depth_views[i].subImage.imageRect.extent.height = recommended_size.height;
927 depth_views[i].minDepth = 0.0;
928 depth_views[i].maxDepth = 1.0;
929 depth_views[i].nearZ = 0.01; // Near and far Z will be set to the correct values in fill_projection_matrix
930 depth_views[i].farZ = 100.0;
931 }
932 };
933
934 return true;
935};
936
937void OpenXRAPI::destroy_session() {
938 if (running && session != XR_NULL_HANDLE) {
939 xrEndSession(session);
940 }
941
942 if (graphics_extension) {
943 graphics_extension->cleanup_swapchain_graphics_data(&swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain_graphics_data);
944 }
945
946 if (views != nullptr) {
947 memfree(views);
948 views = nullptr;
949 }
950
951 if (projection_views != nullptr) {
952 memfree(projection_views);
953 projection_views = nullptr;
954 }
955
956 if (depth_views != nullptr) {
957 memfree(depth_views);
958 depth_views = nullptr;
959 }
960
961 for (int i = 0; i < OPENXR_SWAPCHAIN_MAX; i++) {
962 if (swapchains[i].swapchain != XR_NULL_HANDLE) {
963 xrDestroySwapchain(swapchains[i].swapchain);
964 swapchains[i].swapchain = XR_NULL_HANDLE;
965 }
966 }
967
968 if (supported_swapchain_formats != nullptr) {
969 memfree(supported_swapchain_formats);
970 supported_swapchain_formats = nullptr;
971 }
972
973 // destroy our spaces
974 if (play_space != XR_NULL_HANDLE) {
975 xrDestroySpace(play_space);
976 play_space = XR_NULL_HANDLE;
977 }
978 if (view_space != XR_NULL_HANDLE) {
979 xrDestroySpace(view_space);
980 view_space = XR_NULL_HANDLE;
981 }
982
983 if (supported_reference_spaces != nullptr) {
984 // free previous results
985 memfree(supported_reference_spaces);
986 supported_reference_spaces = nullptr;
987 }
988
989 if (session != XR_NULL_HANDLE) {
990 for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
991 wrapper->on_session_destroyed();
992 }
993
994 xrDestroySession(session);
995 session = XR_NULL_HANDLE;
996 }
997}
998
999bool OpenXRAPI::create_swapchain(XrSwapchainUsageFlags p_usage_flags, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, XrSwapchain &r_swapchain, void **r_swapchain_graphics_data) {
1000 ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false);
1001 ERR_FAIL_NULL_V(graphics_extension, false);
1002
1003 XrResult result;
1004
1005 void *next_pointer = nullptr;
1006 for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
1007 void *np = wrapper->set_swapchain_create_info_and_get_next_pointer(next_pointer);
1008 if (np != nullptr) {
1009 next_pointer = np;
1010 }
1011 }
1012
1013 XrSwapchainCreateInfo swapchain_create_info = {
1014 XR_TYPE_SWAPCHAIN_CREATE_INFO, // type
1015 next_pointer, // next
1016 0, // createFlags
1017 p_usage_flags, // usageFlags
1018 p_swapchain_format, // format
1019 p_sample_count, // sampleCount
1020 p_width, // width
1021 p_height, // height
1022 1, // faceCount
1023 p_array_size, // arraySize
1024 1 // mipCount
1025 };
1026
1027 XrSwapchain new_swapchain;
1028 result = xrCreateSwapchain(session, &swapchain_create_info, &new_swapchain);
1029 if (XR_FAILED(result)) {
1030 print_line("OpenXR: Failed to get swapchain [", get_error_string(result), "]");
1031 return false;
1032 }
1033
1034 if (!graphics_extension->get_swapchain_image_data(new_swapchain, p_swapchain_format, p_width, p_height, p_sample_count, p_array_size, r_swapchain_graphics_data)) {
1035 xrDestroySwapchain(new_swapchain);
1036 return false;
1037 }
1038
1039 r_swapchain = new_swapchain;
1040
1041 return true;
1042}
1043
1044bool OpenXRAPI::on_state_idle() {
1045 print_verbose("On state idle");
1046
1047 for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
1048 wrapper->on_state_idle();
1049 }
1050
1051 return true;
1052}
1053
1054bool OpenXRAPI::on_state_ready() {
1055 print_verbose("On state ready");
1056
1057 // begin session
1058 XrSessionBeginInfo session_begin_info = {
1059 XR_TYPE_SESSION_BEGIN_INFO, // type
1060 nullptr, // next
1061 view_configuration // primaryViewConfigurationType
1062 };
1063
1064 XrResult result = xrBeginSession(session, &session_begin_info);
1065 if (XR_FAILED(result)) {
1066 print_line("OpenXR: Failed to begin session [", get_error_string(result), "]");
1067 return false;
1068 }
1069
1070 // This is when we create our swapchain, this can be a "long" time after Godot finishes, we can deal with this for now
1071 // but once we want to provide Viewports for additional layers where OpenXR requires us to create further swapchains,
1072 // we'll be creating those viewport WAY before we reach this point.
1073 // We may need to implement a wait in our init in main.cpp polling our events until the session is ready.
1074 // That will be very very ugly
1075 // The other possibility is to create a separate OpenXRViewport type specifically for this goal as part of our OpenXR module
1076
1077 if (!create_swapchains()) {
1078 return false;
1079 }
1080
1081 // we're running
1082 running = true;
1083
1084 for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
1085 wrapper->on_state_ready();
1086 }
1087
1088 if (xr_interface) {
1089 xr_interface->on_state_ready();
1090 }
1091
1092 // TODO Tell android
1093
1094 return true;
1095}
1096
1097bool OpenXRAPI::on_state_synchronized() {
1098 print_verbose("On state synchronized");
1099
1100 // Just in case, see if we already have active trackers...
1101 List<RID> trackers;
1102 tracker_owner.get_owned_list(&trackers);
1103 for (int i = 0; i < trackers.size(); i++) {
1104 tracker_check_profile(trackers[i]);
1105 }
1106
1107 for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
1108 wrapper->on_state_synchronized();
1109 }
1110
1111 return true;
1112}
1113
1114bool OpenXRAPI::on_state_visible() {
1115 print_verbose("On state visible");
1116
1117 for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
1118 wrapper->on_state_visible();
1119 }
1120
1121 if (xr_interface) {
1122 xr_interface->on_state_visible();
1123 }
1124
1125 return true;
1126}
1127
1128bool OpenXRAPI::on_state_focused() {
1129 print_verbose("On state focused");
1130
1131 for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
1132 wrapper->on_state_focused();
1133 }
1134
1135 if (xr_interface) {
1136 xr_interface->on_state_focused();
1137 }
1138
1139 return true;
1140}
1141
1142bool OpenXRAPI::on_state_stopping() {
1143 print_verbose("On state stopping");
1144
1145 if (xr_interface) {
1146 xr_interface->on_state_stopping();
1147 }
1148
1149 for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
1150 wrapper->on_state_stopping();
1151 }
1152
1153 if (running) {
1154 XrResult result = xrEndSession(session);
1155 if (XR_FAILED(result)) {
1156 // we only report this..
1157 print_line("OpenXR: Failed to end session [", get_error_string(result), "]");
1158 }
1159
1160 running = false;
1161 }
1162
1163 // TODO further cleanup
1164
1165 return true;
1166}
1167
1168bool OpenXRAPI::on_state_loss_pending() {
1169 print_verbose("On state loss pending");
1170
1171 for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
1172 wrapper->on_state_loss_pending();
1173 }
1174
1175 // TODO need to look into the correct action here, read up on the spec but we may need to signal Godot to exit (if it's not already exiting)
1176
1177 return true;
1178}
1179
1180bool OpenXRAPI::on_state_exiting() {
1181 print_verbose("On state existing");
1182
1183 for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
1184 wrapper->on_state_exiting();
1185 }
1186
1187 // TODO need to look into the correct action here, read up on the spec but we may need to signal Godot to exit (if it's not already exiting)
1188
1189 return true;
1190}
1191
1192void OpenXRAPI::set_form_factor(XrFormFactor p_form_factor) {
1193 ERR_FAIL_COND(is_initialized());
1194
1195 form_factor = p_form_factor;
1196}
1197
1198void OpenXRAPI::set_view_configuration(XrViewConfigurationType p_view_configuration) {
1199 ERR_FAIL_COND(is_initialized());
1200
1201 view_configuration = p_view_configuration;
1202}
1203
1204void OpenXRAPI::set_reference_space(XrReferenceSpaceType p_reference_space) {
1205 ERR_FAIL_COND(is_initialized());
1206
1207 reference_space = p_reference_space;
1208}
1209
1210void OpenXRAPI::set_submit_depth_buffer(bool p_submit_depth_buffer) {
1211 ERR_FAIL_COND(is_initialized());
1212
1213 submit_depth_buffer = p_submit_depth_buffer;
1214}
1215
1216bool OpenXRAPI::is_initialized() {
1217 return (instance != XR_NULL_HANDLE);
1218}
1219
1220bool OpenXRAPI::is_running() {
1221 if (instance == XR_NULL_HANDLE) {
1222 return false;
1223 }
1224 if (session == XR_NULL_HANDLE) {
1225 return false;
1226 }
1227
1228 return running;
1229}
1230
1231bool OpenXRAPI::openxr_loader_init() {
1232#ifdef ANDROID_ENABLED
1233 ERR_FAIL_COND_V_MSG(openxr_loader_library_handle != nullptr, false, "OpenXR Loader library is already loaded.");
1234
1235 {
1236 Error error_code = OS::get_singleton()->open_dynamic_library(OPENXR_LOADER_NAME, openxr_loader_library_handle);
1237 ERR_FAIL_COND_V_MSG(error_code != OK, false, "OpenXR loader not found.");
1238 }
1239
1240 {
1241 Error error_code = OS::get_singleton()->get_dynamic_library_symbol_handle(openxr_loader_library_handle, "xrGetInstanceProcAddr", (void *&)xrGetInstanceProcAddr);
1242 ERR_FAIL_COND_V_MSG(error_code != OK, false, "Symbol xrGetInstanceProcAddr not found in OpenXR Loader library.");
1243 }
1244#endif
1245
1246 // Resolve the symbols that don't require an instance
1247 OPENXR_API_INIT_XR_FUNC_V(xrCreateInstance);
1248 OPENXR_API_INIT_XR_FUNC_V(xrEnumerateApiLayerProperties);
1249 OPENXR_API_INIT_XR_FUNC_V(xrEnumerateInstanceExtensionProperties);
1250
1251 return true;
1252}
1253
1254bool OpenXRAPI::resolve_instance_openxr_symbols() {
1255 ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, false);
1256
1257 OPENXR_API_INIT_XR_FUNC_V(xrAcquireSwapchainImage);
1258 OPENXR_API_INIT_XR_FUNC_V(xrApplyHapticFeedback);
1259 OPENXR_API_INIT_XR_FUNC_V(xrAttachSessionActionSets);
1260 OPENXR_API_INIT_XR_FUNC_V(xrBeginFrame);
1261 OPENXR_API_INIT_XR_FUNC_V(xrBeginSession);
1262 OPENXR_API_INIT_XR_FUNC_V(xrCreateAction);
1263 OPENXR_API_INIT_XR_FUNC_V(xrCreateActionSet);
1264 OPENXR_API_INIT_XR_FUNC_V(xrCreateActionSpace);
1265 OPENXR_API_INIT_XR_FUNC_V(xrCreateReferenceSpace);
1266 OPENXR_API_INIT_XR_FUNC_V(xrCreateSession);
1267 OPENXR_API_INIT_XR_FUNC_V(xrCreateSwapchain);
1268 OPENXR_API_INIT_XR_FUNC_V(xrDestroyAction);
1269 OPENXR_API_INIT_XR_FUNC_V(xrDestroyActionSet);
1270 OPENXR_API_INIT_XR_FUNC_V(xrDestroyInstance);
1271 OPENXR_API_INIT_XR_FUNC_V(xrDestroySession);
1272 OPENXR_API_INIT_XR_FUNC_V(xrDestroySpace);
1273 OPENXR_API_INIT_XR_FUNC_V(xrDestroySwapchain);
1274 OPENXR_API_INIT_XR_FUNC_V(xrEndFrame);
1275 OPENXR_API_INIT_XR_FUNC_V(xrEndSession);
1276 OPENXR_API_INIT_XR_FUNC_V(xrEnumerateEnvironmentBlendModes);
1277 OPENXR_API_INIT_XR_FUNC_V(xrEnumerateReferenceSpaces);
1278 OPENXR_API_INIT_XR_FUNC_V(xrEnumerateSwapchainFormats);
1279 OPENXR_API_INIT_XR_FUNC_V(xrEnumerateViewConfigurations);
1280 OPENXR_API_INIT_XR_FUNC_V(xrEnumerateViewConfigurationViews);
1281 OPENXR_API_INIT_XR_FUNC_V(xrGetActionStateBoolean);
1282 OPENXR_API_INIT_XR_FUNC_V(xrGetActionStateFloat);
1283 OPENXR_API_INIT_XR_FUNC_V(xrGetActionStateVector2f);
1284 OPENXR_API_INIT_XR_FUNC_V(xrGetCurrentInteractionProfile);
1285 OPENXR_API_INIT_XR_FUNC_V(xrGetSystem);
1286 OPENXR_API_INIT_XR_FUNC_V(xrGetSystemProperties);
1287 OPENXR_API_INIT_XR_FUNC_V(xrLocateViews);
1288 OPENXR_API_INIT_XR_FUNC_V(xrLocateSpace);
1289 OPENXR_API_INIT_XR_FUNC_V(xrPathToString);
1290 OPENXR_API_INIT_XR_FUNC_V(xrPollEvent);
1291 OPENXR_API_INIT_XR_FUNC_V(xrReleaseSwapchainImage);
1292 OPENXR_API_INIT_XR_FUNC_V(xrResultToString);
1293 OPENXR_API_INIT_XR_FUNC_V(xrStringToPath);
1294 OPENXR_API_INIT_XR_FUNC_V(xrSuggestInteractionProfileBindings);
1295 OPENXR_API_INIT_XR_FUNC_V(xrSyncActions);
1296 OPENXR_API_INIT_XR_FUNC_V(xrWaitFrame);
1297 OPENXR_API_INIT_XR_FUNC_V(xrWaitSwapchainImage);
1298
1299 return true;
1300}
1301
1302XrResult OpenXRAPI::try_get_instance_proc_addr(const char *p_name, PFN_xrVoidFunction *p_addr) {
1303 return xrGetInstanceProcAddr(instance, p_name, p_addr);
1304}
1305
1306XrResult OpenXRAPI::get_instance_proc_addr(const char *p_name, PFN_xrVoidFunction *p_addr) {
1307 XrResult result = try_get_instance_proc_addr(p_name, p_addr);
1308
1309 if (result != XR_SUCCESS) {
1310 String error_message = String("Symbol ") + p_name + " not found in OpenXR instance.";
1311 ERR_FAIL_V_MSG(result, error_message.utf8().get_data());
1312 }
1313
1314 return result;
1315}
1316
1317bool OpenXRAPI::initialize(const String &p_rendering_driver) {
1318 ERR_FAIL_COND_V_MSG(instance != XR_NULL_HANDLE, false, "OpenXR instance was already created");
1319
1320 if (!openxr_loader_init()) {
1321 return false;
1322 }
1323
1324 if (p_rendering_driver == "vulkan") {
1325#ifdef VULKAN_ENABLED
1326 graphics_extension = memnew(OpenXRVulkanExtension);
1327 register_extension_wrapper(graphics_extension);
1328#else
1329 // shouldn't be possible...
1330 ERR_FAIL_V(false);
1331#endif
1332 } else if (p_rendering_driver == "opengl3") {
1333#if defined(GLES3_ENABLED) && !defined(MACOS_ENABLED)
1334 graphics_extension = memnew(OpenXROpenGLExtension);
1335 register_extension_wrapper(graphics_extension);
1336#else
1337 // shouldn't be possible...
1338 ERR_FAIL_V(false);
1339#endif
1340 } else {
1341 ERR_FAIL_V_MSG(false, "OpenXR: Unsupported rendering device.");
1342 }
1343
1344 // initialize
1345 for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
1346 wrapper->on_before_instance_created();
1347 }
1348
1349 if (!load_layer_properties()) {
1350 destroy_instance();
1351 return false;
1352 }
1353
1354 if (!load_supported_extensions()) {
1355 destroy_instance();
1356 return false;
1357 }
1358
1359 if (!create_instance()) {
1360 destroy_instance();
1361 return false;
1362 }
1363
1364 if (!resolve_instance_openxr_symbols()) {
1365 destroy_instance();
1366 return false;
1367 }
1368
1369 if (!get_system_info()) {
1370 destroy_instance();
1371 return false;
1372 }
1373
1374 if (!load_supported_view_configuration_types()) {
1375 destroy_instance();
1376 return false;
1377 }
1378
1379 if (!load_supported_view_configuration_views(view_configuration)) {
1380 destroy_instance();
1381 return false;
1382 }
1383
1384 if (!load_supported_environmental_blend_modes()) {
1385 destroy_instance();
1386 return false;
1387 }
1388
1389 return true;
1390}
1391
1392bool OpenXRAPI::initialize_session() {
1393 if (!create_session()) {
1394 destroy_session();
1395 return false;
1396 }
1397
1398 if (!load_supported_reference_spaces()) {
1399 destroy_session();
1400 return false;
1401 }
1402
1403 if (!setup_spaces()) {
1404 destroy_session();
1405 return false;
1406 }
1407
1408 if (!load_supported_swapchain_formats()) {
1409 destroy_session();
1410 return false;
1411 }
1412
1413 return true;
1414}
1415
1416void OpenXRAPI::finish() {
1417 destroy_session();
1418
1419 destroy_instance();
1420}
1421
1422void OpenXRAPI::set_xr_interface(OpenXRInterface *p_xr_interface) {
1423 xr_interface = p_xr_interface;
1424}
1425
1426void OpenXRAPI::register_extension_wrapper(OpenXRExtensionWrapper *p_extension_wrapper) {
1427 registered_extension_wrappers.push_back(p_extension_wrapper);
1428}
1429
1430void OpenXRAPI::unregister_extension_wrapper(OpenXRExtensionWrapper *p_extension_wrapper) {
1431 registered_extension_wrappers.erase(p_extension_wrapper);
1432}
1433
1434void OpenXRAPI::register_extension_metadata() {
1435 for (OpenXRExtensionWrapper *extension_wrapper : registered_extension_wrappers) {
1436 extension_wrapper->on_register_metadata();
1437 }
1438}
1439
1440void OpenXRAPI::cleanup_extension_wrappers() {
1441 for (OpenXRExtensionWrapper *extension_wrapper : registered_extension_wrappers) {
1442 memdelete(extension_wrapper);
1443 }
1444 registered_extension_wrappers.clear();
1445}
1446
1447Size2 OpenXRAPI::get_recommended_target_size() {
1448 ERR_FAIL_NULL_V(view_configuration_views, Size2());
1449
1450 Size2 target_size;
1451
1452 target_size.width = view_configuration_views[0].recommendedImageRectWidth * render_target_size_multiplier;
1453 target_size.height = view_configuration_views[0].recommendedImageRectHeight * render_target_size_multiplier;
1454
1455 return target_size;
1456}
1457
1458XRPose::TrackingConfidence OpenXRAPI::get_head_center(Transform3D &r_transform, Vector3 &r_linear_velocity, Vector3 &r_angular_velocity) {
1459 XrResult result;
1460
1461 if (!running) {
1462 return XRPose::XR_TRACKING_CONFIDENCE_NONE;
1463 }
1464
1465 // xrWaitFrame not run yet
1466 if (frame_state.predictedDisplayTime == 0) {
1467 return XRPose::XR_TRACKING_CONFIDENCE_NONE;
1468 }
1469
1470 // Get timing for the next frame, as that is the current frame we're processing
1471 XrTime display_time = get_next_frame_time();
1472
1473 XrSpaceVelocity velocity = {
1474 XR_TYPE_SPACE_VELOCITY, // type
1475 nullptr, // next
1476 0, // velocityFlags
1477 { 0.0, 0.0, 0.0 }, // linearVelocity
1478 { 0.0, 0.0, 0.0 } // angularVelocity
1479 };
1480
1481 XrSpaceLocation location = {
1482 XR_TYPE_SPACE_LOCATION, // type
1483 &velocity, // next
1484 0, // locationFlags
1485 {
1486 { 0.0, 0.0, 0.0, 0.0 }, // orientation
1487 { 0.0, 0.0, 0.0 } // position
1488 } // pose
1489 };
1490
1491 result = xrLocateSpace(view_space, play_space, display_time, &location);
1492 if (XR_FAILED(result)) {
1493 print_line("OpenXR: Failed to locate view space in play space [", get_error_string(result), "]");
1494 return XRPose::XR_TRACKING_CONFIDENCE_NONE;
1495 }
1496
1497 XRPose::TrackingConfidence confidence = transform_from_location(location, r_transform);
1498 parse_velocities(velocity, r_linear_velocity, r_angular_velocity);
1499
1500 if (head_pose_confidence != confidence) {
1501 // prevent error spam
1502 head_pose_confidence = confidence;
1503 if (head_pose_confidence == XRPose::XR_TRACKING_CONFIDENCE_NONE) {
1504 print_line("OpenXR head space location not valid (check tracking?)");
1505 } else if (head_pose_confidence == XRPose::XR_TRACKING_CONFIDENCE_LOW) {
1506 print_verbose("OpenVR Head pose now tracking with low confidence");
1507 } else {
1508 print_verbose("OpenVR Head pose now tracking with high confidence");
1509 }
1510 }
1511
1512 return confidence;
1513}
1514
1515bool OpenXRAPI::get_view_transform(uint32_t p_view, Transform3D &r_transform) {
1516 if (!running) {
1517 return false;
1518 }
1519
1520 // xrWaitFrame not run yet
1521 if (frame_state.predictedDisplayTime == 0) {
1522 return false;
1523 }
1524
1525 // we don't have valid view info
1526 if (views == nullptr || !view_pose_valid) {
1527 return false;
1528 }
1529
1530 // Note, the timing of this is set right before rendering, which is what we need here.
1531 r_transform = transform_from_pose(views[p_view].pose);
1532
1533 return true;
1534}
1535
1536bool OpenXRAPI::get_view_projection(uint32_t p_view, double p_z_near, double p_z_far, Projection &p_camera_matrix) {
1537 ERR_FAIL_NULL_V(graphics_extension, false);
1538
1539 if (!running) {
1540 return false;
1541 }
1542
1543 // xrWaitFrame not run yet
1544 if (frame_state.predictedDisplayTime == 0) {
1545 return false;
1546 }
1547
1548 // we don't have valid view info
1549 if (views == nullptr || !view_pose_valid) {
1550 return false;
1551 }
1552
1553 // if we're using depth views, make sure we update our near and far there...
1554 if (depth_views != nullptr) {
1555 for (uint32_t i = 0; i < view_count; i++) {
1556 depth_views[i].nearZ = p_z_near;
1557 depth_views[i].farZ = p_z_far;
1558 }
1559 }
1560
1561 // now update our projection
1562 return graphics_extension->create_projection_fov(views[p_view].fov, p_z_near, p_z_far, p_camera_matrix);
1563}
1564
1565bool OpenXRAPI::poll_events() {
1566 ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, false);
1567
1568 XrEventDataBuffer runtimeEvent;
1569 runtimeEvent.type = XR_TYPE_EVENT_DATA_BUFFER;
1570 runtimeEvent.next = nullptr;
1571 // runtimeEvent.varying = ...
1572
1573 XrResult pollResult = xrPollEvent(instance, &runtimeEvent);
1574 while (pollResult == XR_SUCCESS) {
1575 bool handled = false;
1576 for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
1577 handled |= wrapper->on_event_polled(runtimeEvent);
1578 }
1579 switch (runtimeEvent.type) {
1580 case XR_TYPE_EVENT_DATA_EVENTS_LOST: {
1581 XrEventDataEventsLost *event = (XrEventDataEventsLost *)&runtimeEvent;
1582
1583 // We probably didn't poll fast enough, just output warning
1584 WARN_PRINT("OpenXR EVENT: " + itos(event->lostEventCount) + " event data lost!");
1585 } break;
1586 case XR_TYPE_EVENT_DATA_VISIBILITY_MASK_CHANGED_KHR: {
1587 // XrEventDataVisibilityMaskChangedKHR *event = (XrEventDataVisibilityMaskChangedKHR *)&runtimeEvent;
1588
1589 // TODO implement this in the future, we should call xrGetVisibilityMaskKHR to obtain a mask,
1590 // this will allow us to prevent rendering the part of our view which is never displayed giving us
1591 // a decent performance improvement.
1592
1593 print_verbose("OpenXR EVENT: STUB: visibility mask changed");
1594 } break;
1595 case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING: {
1596 XrEventDataInstanceLossPending *event = (XrEventDataInstanceLossPending *)&runtimeEvent;
1597
1598 // TODO We get this event if we're about to loose our OpenXR instance.
1599 // We should queue exiting Godot at this point.
1600
1601 print_verbose(String("OpenXR EVENT: instance loss pending at ") + itos(event->lossTime));
1602 return false;
1603 } break;
1604 case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: {
1605 XrEventDataSessionStateChanged *event = (XrEventDataSessionStateChanged *)&runtimeEvent;
1606
1607 session_state = event->state;
1608 if (session_state >= XR_SESSION_STATE_MAX_ENUM) {
1609 print_verbose(String("OpenXR EVENT: session state changed to UNKNOWN - ") + itos(session_state));
1610 } else {
1611 print_verbose(String("OpenXR EVENT: session state changed to ") + OpenXRUtil::get_session_state_name(session_state));
1612
1613 switch (session_state) {
1614 case XR_SESSION_STATE_IDLE:
1615 on_state_idle();
1616 break;
1617 case XR_SESSION_STATE_READY:
1618 on_state_ready();
1619 break;
1620 case XR_SESSION_STATE_SYNCHRONIZED:
1621 on_state_synchronized();
1622 break;
1623 case XR_SESSION_STATE_VISIBLE:
1624 on_state_visible();
1625 break;
1626 case XR_SESSION_STATE_FOCUSED:
1627 on_state_focused();
1628 break;
1629 case XR_SESSION_STATE_STOPPING:
1630 on_state_stopping();
1631 break;
1632 case XR_SESSION_STATE_LOSS_PENDING:
1633 on_state_loss_pending();
1634 break;
1635 case XR_SESSION_STATE_EXITING:
1636 on_state_exiting();
1637 break;
1638 default:
1639 break;
1640 }
1641 }
1642 } break;
1643 case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING: {
1644 XrEventDataReferenceSpaceChangePending *event = (XrEventDataReferenceSpaceChangePending *)&runtimeEvent;
1645
1646 print_verbose(String("OpenXR EVENT: reference space type ") + OpenXRUtil::get_reference_space_name(event->referenceSpaceType) + " change pending!");
1647 if (event->poseValid && xr_interface) {
1648 xr_interface->on_pose_recentered();
1649 }
1650 } break;
1651 case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED: {
1652 print_verbose("OpenXR EVENT: interaction profile changed!");
1653
1654 XrEventDataInteractionProfileChanged *event = (XrEventDataInteractionProfileChanged *)&runtimeEvent;
1655
1656 List<RID> trackers;
1657 tracker_owner.get_owned_list(&trackers);
1658 for (int i = 0; i < trackers.size(); i++) {
1659 tracker_check_profile(trackers[i], event->session);
1660 }
1661
1662 } break;
1663 default:
1664 if (!handled) {
1665 print_verbose(String("OpenXR Unhandled event type ") + OpenXRUtil::get_structure_type_name(runtimeEvent.type));
1666 }
1667 break;
1668 }
1669
1670 runtimeEvent.type = XR_TYPE_EVENT_DATA_BUFFER;
1671 pollResult = xrPollEvent(instance, &runtimeEvent);
1672 }
1673
1674 if (pollResult == XR_EVENT_UNAVAILABLE) {
1675 // processed all events in the queue
1676 return true;
1677 } else {
1678 ERR_FAIL_V_MSG(false, "OpenXR: Failed to poll events!");
1679 }
1680}
1681
1682bool OpenXRAPI::process() {
1683 ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, false);
1684
1685 if (!poll_events()) {
1686 return false;
1687 }
1688
1689 if (!running) {
1690 return false;
1691 }
1692
1693 for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
1694 wrapper->on_process();
1695 }
1696
1697 return true;
1698}
1699
1700bool OpenXRAPI::acquire_image(OpenXRSwapChainInfo &p_swapchain) {
1701 ERR_FAIL_COND_V(p_swapchain.image_acquired, true); // This was not released when it should be, error out and reuse...
1702
1703 XrResult result;
1704 XrSwapchainImageAcquireInfo swapchain_image_acquire_info = {
1705 XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO, // type
1706 nullptr // next
1707 };
1708 result = xrAcquireSwapchainImage(p_swapchain.swapchain, &swapchain_image_acquire_info, &p_swapchain.image_index);
1709 if (XR_FAILED(result)) {
1710 print_line("OpenXR: failed to acquire swapchain image [", get_error_string(result), "]");
1711 return false;
1712 }
1713
1714 XrSwapchainImageWaitInfo swapchain_image_wait_info = {
1715 XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO, // type
1716 nullptr, // next
1717 17000000 // timeout in nanoseconds
1718 };
1719
1720 result = xrWaitSwapchainImage(p_swapchain.swapchain, &swapchain_image_wait_info);
1721 if (XR_FAILED(result)) {
1722 print_line("OpenXR: failed to wait for swapchain image [", get_error_string(result), "]");
1723 return false;
1724 }
1725
1726 return true;
1727}
1728
1729bool OpenXRAPI::release_image(OpenXRSwapChainInfo &p_swapchain) {
1730 XrSwapchainImageReleaseInfo swapchain_image_release_info = {
1731 XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO, // type
1732 nullptr // next
1733 };
1734 XrResult result = xrReleaseSwapchainImage(p_swapchain.swapchain, &swapchain_image_release_info);
1735 if (XR_FAILED(result)) {
1736 print_line("OpenXR: failed to release swapchain image! [", get_error_string(result), "]");
1737 return false;
1738 }
1739
1740 return true;
1741}
1742
1743void OpenXRAPI::pre_render() {
1744 ERR_FAIL_COND(instance == XR_NULL_HANDLE);
1745
1746 if (!running) {
1747 return;
1748 }
1749
1750 // Waitframe does 2 important things in our process:
1751 // 1) It provides us with predictive timing, telling us when OpenXR expects to display the frame we're about to commit
1752 // 2) It will use the previous timing to pause our thread so that rendering starts as close to displaying as possible
1753 // This must thus be called as close to when we start rendering as possible
1754 XrFrameWaitInfo frame_wait_info = { XR_TYPE_FRAME_WAIT_INFO, nullptr };
1755 frame_state.predictedDisplayTime = 0;
1756 frame_state.predictedDisplayPeriod = 0;
1757 frame_state.shouldRender = false;
1758
1759 XrResult result = xrWaitFrame(session, &frame_wait_info, &frame_state);
1760 if (XR_FAILED(result)) {
1761 print_line("OpenXR: xrWaitFrame() was not successful [", get_error_string(result), "]");
1762
1763 // reset just in case
1764 frame_state.predictedDisplayTime = 0;
1765 frame_state.predictedDisplayPeriod = 0;
1766 frame_state.shouldRender = false;
1767
1768 return;
1769 }
1770
1771 if (frame_state.predictedDisplayPeriod > 500000000) {
1772 // display period more then 0.5 seconds? must be wrong data
1773 print_verbose(String("OpenXR resetting invalid display period ") + rtos(frame_state.predictedDisplayPeriod));
1774 frame_state.predictedDisplayPeriod = 0;
1775 }
1776
1777 for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
1778 wrapper->on_pre_render();
1779 }
1780
1781 // Get our view info for the frame we're about to render, note from the OpenXR manual:
1782 // "Repeatedly calling xrLocateViews with the same time may not necessarily return the same result. Instead the prediction gets increasingly accurate as the function is called closer to the given time for which a prediction is made"
1783
1784 // We're calling this "relatively" early, the positioning we're obtaining here will be used to do our frustum culling,
1785 // occlusion culling, etc. There is however a technique that we can investigate in the future where after our entire
1786 // Vulkan command buffer is build, but right before vkSubmitQueue is called, we call xrLocateViews one more time and
1787 // update the view and projection matrix once more with a slightly more accurate predication and then submit the
1788 // command queues.
1789
1790 // That is not possible yet but worth investigating in the future.
1791
1792 XrViewLocateInfo view_locate_info = {
1793 XR_TYPE_VIEW_LOCATE_INFO, // type
1794 nullptr, // next
1795 view_configuration, // viewConfigurationType
1796 frame_state.predictedDisplayTime, // displayTime
1797 play_space // space
1798 };
1799 XrViewState view_state = {
1800 XR_TYPE_VIEW_STATE, // type
1801 nullptr, // next
1802 0 // viewStateFlags
1803 };
1804 uint32_t view_count_output;
1805 result = xrLocateViews(session, &view_locate_info, &view_state, view_count, &view_count_output, views);
1806 if (XR_FAILED(result)) {
1807 print_line("OpenXR: Couldn't locate views [", get_error_string(result), "]");
1808 return;
1809 }
1810
1811 bool pose_valid = true;
1812 for (uint64_t i = 0; i < view_count_output; i++) {
1813 if ((view_state.viewStateFlags & XR_VIEW_STATE_ORIENTATION_VALID_BIT) == 0 ||
1814 (view_state.viewStateFlags & XR_VIEW_STATE_POSITION_VALID_BIT) == 0) {
1815 pose_valid = false;
1816 }
1817 }
1818 if (view_pose_valid != pose_valid) {
1819 view_pose_valid = pose_valid;
1820 if (!view_pose_valid) {
1821 print_verbose("OpenXR View pose became invalid");
1822 } else {
1823 print_verbose("OpenXR View pose became valid");
1824 }
1825 }
1826
1827 // let's start our frame..
1828 XrFrameBeginInfo frame_begin_info = {
1829 XR_TYPE_FRAME_BEGIN_INFO, // type
1830 nullptr // next
1831 };
1832 result = xrBeginFrame(session, &frame_begin_info);
1833 if (XR_FAILED(result)) {
1834 print_line("OpenXR: failed to being frame [", get_error_string(result), "]");
1835 return;
1836 }
1837}
1838
1839bool OpenXRAPI::pre_draw_viewport(RID p_render_target) {
1840 if (!can_render()) {
1841 return false;
1842 }
1843
1844 // TODO: at some point in time we may support multiple viewports in which case we need to handle that...
1845
1846 // Acquire our images
1847 for (int i = 0; i < OPENXR_SWAPCHAIN_MAX; i++) {
1848 if (!swapchains[i].image_acquired && swapchains[i].swapchain != XR_NULL_HANDLE) {
1849 if (!acquire_image(swapchains[i])) {
1850 return false;
1851 }
1852 swapchains[i].image_acquired = true;
1853 }
1854 }
1855
1856 for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
1857 wrapper->on_pre_draw_viewport(p_render_target);
1858 }
1859
1860 return true;
1861}
1862
1863RID OpenXRAPI::get_color_texture() {
1864 if (swapchains[OPENXR_SWAPCHAIN_COLOR].image_acquired) {
1865 return graphics_extension->get_texture(swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain_graphics_data, swapchains[OPENXR_SWAPCHAIN_COLOR].image_index);
1866 } else {
1867 return RID();
1868 }
1869}
1870
1871RID OpenXRAPI::get_depth_texture() {
1872 // Note, image will not be acquired if we didn't have a suitable swap chain format.
1873 if (submit_depth_buffer && swapchains[OPENXR_SWAPCHAIN_DEPTH].image_acquired) {
1874 return graphics_extension->get_texture(swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain_graphics_data, swapchains[OPENXR_SWAPCHAIN_DEPTH].image_index);
1875 } else {
1876 return RID();
1877 }
1878}
1879
1880void OpenXRAPI::post_draw_viewport(RID p_render_target) {
1881 if (!can_render()) {
1882 return;
1883 }
1884
1885 for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) {
1886 wrapper->on_post_draw_viewport(p_render_target);
1887 }
1888};
1889
1890void OpenXRAPI::end_frame() {
1891 XrResult result;
1892
1893 ERR_FAIL_COND(instance == XR_NULL_HANDLE);
1894
1895 if (!running) {
1896 return;
1897 }
1898
1899 if (frame_state.shouldRender && view_pose_valid && !swapchains[OPENXR_SWAPCHAIN_COLOR].image_acquired) {
1900 print_line("OpenXR: No viewport was marked with use_xr, there is no rendered output!");
1901 }
1902
1903 // must have:
1904 // - shouldRender set to true
1905 // - a valid view pose for projection_views[eye].pose to submit layer
1906 // - an image to render
1907 if (!frame_state.shouldRender || !view_pose_valid || !swapchains[OPENXR_SWAPCHAIN_COLOR].image_acquired) {
1908 // submit 0 layers when we shouldn't render
1909 XrFrameEndInfo frame_end_info = {
1910 XR_TYPE_FRAME_END_INFO, // type
1911 nullptr, // next
1912 frame_state.predictedDisplayTime, // displayTime
1913 environment_blend_mode, // environmentBlendMode
1914 0, // layerCount
1915 nullptr // layers
1916 };
1917 result = xrEndFrame(session, &frame_end_info);
1918 if (XR_FAILED(result)) {
1919 print_line("OpenXR: failed to end frame! [", get_error_string(result), "]");
1920 return;
1921 }
1922
1923 // neither eye is rendered
1924 return;
1925 }
1926
1927 // release our swapchain image if we acquired it
1928 for (int i = 0; i < OPENXR_SWAPCHAIN_MAX; i++) {
1929 if (swapchains[i].image_acquired) {
1930 swapchains[i].image_acquired = false; // whether we succeed or not, consider this released.
1931
1932 release_image(swapchains[i]);
1933 }
1934 }
1935
1936 for (uint32_t eye = 0; eye < view_count; eye++) {
1937 projection_views[eye].fov = views[eye].fov;
1938 projection_views[eye].pose = views[eye].pose;
1939 }
1940
1941 Vector<const XrCompositionLayerBaseHeader *> layers_list;
1942
1943 // Add composition layers from providers
1944 for (OpenXRCompositionLayerProvider *provider : composition_layer_providers) {
1945 XrCompositionLayerBaseHeader *layer = provider->get_composition_layer();
1946 if (layer) {
1947 layers_list.push_back(layer);
1948 }
1949 }
1950
1951 XrCompositionLayerFlags layer_flags = XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT;
1952 if (layers_list.size() > 0 || environment_blend_mode != XR_ENVIRONMENT_BLEND_MODE_OPAQUE) {
1953 layer_flags |= XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
1954 }
1955
1956 XrCompositionLayerProjection projection_layer = {
1957 XR_TYPE_COMPOSITION_LAYER_PROJECTION, // type
1958 nullptr, // next
1959 layer_flags, // layerFlags
1960 play_space, // space
1961 view_count, // viewCount
1962 projection_views, // views
1963 };
1964 layers_list.push_back((const XrCompositionLayerBaseHeader *)&projection_layer);
1965
1966 XrFrameEndInfo frame_end_info = {
1967 XR_TYPE_FRAME_END_INFO, // type
1968 nullptr, // next
1969 frame_state.predictedDisplayTime, // displayTime
1970 environment_blend_mode, // environmentBlendMode
1971 static_cast<uint32_t>(layers_list.size()), // layerCount
1972 layers_list.ptr() // layers
1973 };
1974 result = xrEndFrame(session, &frame_end_info);
1975 if (XR_FAILED(result)) {
1976 print_line("OpenXR: failed to end frame! [", get_error_string(result), "]");
1977 return;
1978 }
1979}
1980
1981float OpenXRAPI::get_display_refresh_rate() const {
1982 OpenXRDisplayRefreshRateExtension *drrext = OpenXRDisplayRefreshRateExtension::get_singleton();
1983 if (drrext) {
1984 return drrext->get_refresh_rate();
1985 }
1986
1987 return 0.0;
1988}
1989
1990void OpenXRAPI::set_display_refresh_rate(float p_refresh_rate) {
1991 OpenXRDisplayRefreshRateExtension *drrext = OpenXRDisplayRefreshRateExtension::get_singleton();
1992 if (drrext != nullptr) {
1993 drrext->set_refresh_rate(p_refresh_rate);
1994 }
1995}
1996
1997Array OpenXRAPI::get_available_display_refresh_rates() const {
1998 OpenXRDisplayRefreshRateExtension *drrext = OpenXRDisplayRefreshRateExtension::get_singleton();
1999 if (drrext != nullptr) {
2000 return drrext->get_available_refresh_rates();
2001 }
2002
2003 return Array();
2004}
2005
2006double OpenXRAPI::get_render_target_size_multiplier() const {
2007 return render_target_size_multiplier;
2008}
2009
2010void OpenXRAPI::set_render_target_size_multiplier(double multiplier) {
2011 render_target_size_multiplier = multiplier;
2012}
2013
2014OpenXRAPI::OpenXRAPI() {
2015 // OpenXRAPI is only constructed if OpenXR is enabled.
2016 singleton = this;
2017
2018 if (Engine::get_singleton()->is_editor_hint()) {
2019 // Enabled OpenXR in the editor? Adjust our settings for the editor
2020
2021 } else {
2022 // Load settings from project settings
2023 int form_factor_setting = GLOBAL_GET("xr/openxr/form_factor");
2024 switch (form_factor_setting) {
2025 case 0: {
2026 form_factor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY;
2027 } break;
2028 case 1: {
2029 form_factor = XR_FORM_FACTOR_HANDHELD_DISPLAY;
2030 } break;
2031 default:
2032 break;
2033 }
2034
2035 int view_configuration_setting = GLOBAL_GET("xr/openxr/view_configuration");
2036 switch (view_configuration_setting) {
2037 case 0: {
2038 view_configuration = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_MONO;
2039 } break;
2040 case 1: {
2041 view_configuration = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
2042 } break;
2043 /* we don't support quad and observer configurations (yet)
2044 case 2: {
2045 view_configuration = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_QUAD_VARJO;
2046 } break;
2047 case 3: {
2048 view_configuration = XR_VIEW_CONFIGURATION_TYPE_SECONDARY_MONO_FIRST_PERSON_OBSERVER_MSFT;
2049 } break;
2050 */
2051 default:
2052 break;
2053 }
2054
2055 int reference_space_setting = GLOBAL_GET("xr/openxr/reference_space");
2056 switch (reference_space_setting) {
2057 case 0: {
2058 reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL;
2059 } break;
2060 case 1: {
2061 reference_space = XR_REFERENCE_SPACE_TYPE_STAGE;
2062 } break;
2063 default:
2064 break;
2065 }
2066
2067 int environment_blend_mode_setting = GLOBAL_GET("xr/openxr/environment_blend_mode");
2068 switch (environment_blend_mode_setting) {
2069 case 0: {
2070 environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
2071 } break;
2072 case 1: {
2073 environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_ADDITIVE;
2074 } break;
2075 case 2: {
2076 environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND;
2077 } break;
2078 default:
2079 break;
2080 }
2081
2082 submit_depth_buffer = GLOBAL_GET("xr/openxr/submit_depth_buffer");
2083 }
2084
2085 // reset a few things that can't be done in our class definition
2086 frame_state.predictedDisplayTime = 0;
2087 frame_state.predictedDisplayPeriod = 0;
2088}
2089
2090OpenXRAPI::~OpenXRAPI() {
2091 // cleanup our composition layer providers
2092 for (OpenXRCompositionLayerProvider *provider : composition_layer_providers) {
2093 memdelete(provider);
2094 }
2095 composition_layer_providers.clear();
2096
2097 if (supported_extensions != nullptr) {
2098 memfree(supported_extensions);
2099 supported_extensions = nullptr;
2100 }
2101
2102 if (layer_properties != nullptr) {
2103 memfree(layer_properties);
2104 layer_properties = nullptr;
2105 }
2106
2107#ifdef ANDROID_ENABLED
2108 if (openxr_loader_library_handle) {
2109 OS::get_singleton()->close_dynamic_library(openxr_loader_library_handle);
2110 openxr_loader_library_handle = nullptr;
2111 }
2112#endif
2113
2114 singleton = nullptr;
2115}
2116
2117Transform3D OpenXRAPI::transform_from_pose(const XrPosef &p_pose) {
2118 Quaternion q(p_pose.orientation.x, p_pose.orientation.y, p_pose.orientation.z, p_pose.orientation.w);
2119 Basis basis(q);
2120 Vector3 origin(p_pose.position.x, p_pose.position.y, p_pose.position.z);
2121
2122 return Transform3D(basis, origin);
2123}
2124
2125template <typename T>
2126XRPose::TrackingConfidence _transform_from_location(const T &p_location, Transform3D &r_transform) {
2127 Basis basis;
2128 Vector3 origin;
2129 XRPose::TrackingConfidence confidence = XRPose::XR_TRACKING_CONFIDENCE_NONE;
2130 const XrPosef &pose = p_location.pose;
2131
2132 // Check orientation
2133 if (p_location.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) {
2134 Quaternion q(pose.orientation.x, pose.orientation.y, pose.orientation.z, pose.orientation.w);
2135 r_transform.basis = Basis(q);
2136
2137 if (p_location.locationFlags & XR_SPACE_LOCATION_ORIENTATION_TRACKED_BIT) {
2138 // Fully valid orientation, so either 3DOF or 6DOF tracking with high confidence so default to HIGH_TRACKING
2139 confidence = XRPose::XR_TRACKING_CONFIDENCE_HIGH;
2140 } else {
2141 // Orientation is being tracked but we're using old/predicted data, so low tracking confidence
2142 confidence = XRPose::XR_TRACKING_CONFIDENCE_LOW;
2143 }
2144 } else {
2145 r_transform.basis = Basis();
2146 }
2147
2148 // Check location
2149 if (p_location.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) {
2150 r_transform.origin = Vector3(pose.position.x, pose.position.y, pose.position.z);
2151
2152 if (!(p_location.locationFlags & XR_SPACE_LOCATION_ORIENTATION_TRACKED_BIT)) {
2153 // Location is being tracked but we're using old/predicted data, so low tracking confidence
2154 confidence = XRPose::XR_TRACKING_CONFIDENCE_LOW;
2155 } else if (confidence == XRPose::XR_TRACKING_CONFIDENCE_NONE) {
2156 // Position tracking without orientation tracking?
2157 confidence = XRPose::XR_TRACKING_CONFIDENCE_HIGH;
2158 }
2159 } else {
2160 // No tracking or 3DOF I guess..
2161 r_transform.origin = Vector3();
2162 }
2163
2164 return confidence;
2165}
2166
2167XRPose::TrackingConfidence OpenXRAPI::transform_from_location(const XrSpaceLocation &p_location, Transform3D &r_transform) {
2168 return _transform_from_location(p_location, r_transform);
2169}
2170
2171XRPose::TrackingConfidence OpenXRAPI::transform_from_location(const XrHandJointLocationEXT &p_location, Transform3D &r_transform) {
2172 return _transform_from_location(p_location, r_transform);
2173}
2174
2175void OpenXRAPI::parse_velocities(const XrSpaceVelocity &p_velocity, Vector3 &r_linear_velocity, Vector3 &r_angular_velocity) {
2176 if (p_velocity.velocityFlags & XR_SPACE_VELOCITY_LINEAR_VALID_BIT) {
2177 XrVector3f linear_velocity = p_velocity.linearVelocity;
2178 r_linear_velocity = Vector3(linear_velocity.x, linear_velocity.y, linear_velocity.z);
2179 } else {
2180 r_linear_velocity = Vector3();
2181 }
2182 if (p_velocity.velocityFlags & XR_SPACE_VELOCITY_ANGULAR_VALID_BIT) {
2183 XrVector3f angular_velocity = p_velocity.angularVelocity;
2184 r_angular_velocity = Vector3(angular_velocity.x, angular_velocity.y, angular_velocity.z);
2185 } else {
2186 r_angular_velocity = Vector3();
2187 }
2188}
2189
2190bool OpenXRAPI::xr_result(XrResult result, const char *format, Array args) const {
2191 if (XR_SUCCEEDED(result))
2192 return true;
2193
2194 char resultString[XR_MAX_RESULT_STRING_SIZE];
2195 xrResultToString(instance, result, resultString);
2196
2197 print_error(String("OpenXR ") + String(format).format(args) + String(" [") + String(resultString) + String("]"));
2198
2199 return false;
2200}
2201
2202RID OpenXRAPI::get_tracker_rid(XrPath p_path) {
2203 List<RID> current;
2204 tracker_owner.get_owned_list(&current);
2205 for (int i = 0; i < current.size(); i++) {
2206 Tracker *tracker = tracker_owner.get_or_null(current[i]);
2207 if (tracker && tracker->toplevel_path == p_path) {
2208 return current[i];
2209 }
2210 }
2211
2212 return RID();
2213}
2214
2215RID OpenXRAPI::tracker_create(const String p_name) {
2216 ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, RID());
2217
2218 Tracker new_tracker;
2219 new_tracker.name = p_name;
2220 new_tracker.toplevel_path = XR_NULL_PATH;
2221 new_tracker.active_profile_rid = RID();
2222
2223 XrResult result = xrStringToPath(instance, p_name.utf8().get_data(), &new_tracker.toplevel_path);
2224 if (XR_FAILED(result)) {
2225 print_line("OpenXR: failed to get path for ", p_name, "! [", get_error_string(result), "]");
2226 return RID();
2227 }
2228
2229 return tracker_owner.make_rid(new_tracker);
2230}
2231
2232String OpenXRAPI::tracker_get_name(RID p_tracker) {
2233 if (p_tracker.is_null()) {
2234 return String("None");
2235 }
2236
2237 Tracker *tracker = tracker_owner.get_or_null(p_tracker);
2238 ERR_FAIL_NULL_V(tracker, String());
2239
2240 return tracker->name;
2241}
2242
2243void OpenXRAPI::tracker_check_profile(RID p_tracker, XrSession p_session) {
2244 if (p_session == XR_NULL_HANDLE) {
2245 p_session = session;
2246 }
2247
2248 Tracker *tracker = tracker_owner.get_or_null(p_tracker);
2249 ERR_FAIL_NULL(tracker);
2250
2251 if (tracker->toplevel_path == XR_NULL_PATH) {
2252 // no path, how was this even created?
2253 return;
2254 }
2255
2256 XrInteractionProfileState profile_state = {
2257 XR_TYPE_INTERACTION_PROFILE_STATE, // type
2258 nullptr, // next
2259 XR_NULL_PATH // interactionProfile
2260 };
2261
2262 XrResult result = xrGetCurrentInteractionProfile(p_session, tracker->toplevel_path, &profile_state);
2263 if (XR_FAILED(result)) {
2264 print_line("OpenXR: Failed to get interaction profile for", itos(tracker->toplevel_path), "[", get_error_string(result), "]");
2265 return;
2266 }
2267
2268 XrPath new_profile = profile_state.interactionProfile;
2269 XrPath was_profile = get_interaction_profile_path(tracker->active_profile_rid);
2270 if (was_profile != new_profile) {
2271 tracker->active_profile_rid = get_interaction_profile_rid(new_profile);
2272
2273 if (xr_interface) {
2274 xr_interface->tracker_profile_changed(p_tracker, tracker->active_profile_rid);
2275 }
2276 }
2277}
2278
2279void OpenXRAPI::tracker_free(RID p_tracker) {
2280 Tracker *tracker = tracker_owner.get_or_null(p_tracker);
2281 ERR_FAIL_NULL(tracker);
2282
2283 // there is nothing to free here
2284
2285 tracker_owner.free(p_tracker);
2286}
2287
2288RID OpenXRAPI::action_set_create(const String p_name, const String p_localized_name, const int p_priority) {
2289 ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, RID());
2290 ActionSet action_set;
2291
2292 action_set.name = p_name;
2293 action_set.is_attached = false;
2294
2295 // create our action set...
2296 XrActionSetCreateInfo action_set_info = {
2297 XR_TYPE_ACTION_SET_CREATE_INFO, // type
2298 nullptr, // next
2299 "", // actionSetName
2300 "", // localizedActionSetName
2301 uint32_t(p_priority) // priority
2302 };
2303
2304 copy_string_to_char_buffer(p_name, action_set_info.actionSetName, XR_MAX_ACTION_SET_NAME_SIZE);
2305 copy_string_to_char_buffer(p_localized_name, action_set_info.localizedActionSetName, XR_MAX_LOCALIZED_ACTION_SET_NAME_SIZE);
2306
2307 XrResult result = xrCreateActionSet(instance, &action_set_info, &action_set.handle);
2308 if (XR_FAILED(result)) {
2309 print_line("OpenXR: failed to create action set ", p_name, "! [", get_error_string(result), "]");
2310 return RID();
2311 }
2312
2313 return action_set_owner.make_rid(action_set);
2314}
2315
2316String OpenXRAPI::action_set_get_name(RID p_action_set) {
2317 if (p_action_set.is_null()) {
2318 return String("None");
2319 }
2320
2321 ActionSet *action_set = action_set_owner.get_or_null(p_action_set);
2322 ERR_FAIL_NULL_V(action_set, String());
2323
2324 return action_set->name;
2325}
2326
2327bool OpenXRAPI::attach_action_sets(const Vector<RID> &p_action_sets) {
2328 ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false);
2329
2330 Vector<XrActionSet> action_handles;
2331 action_handles.resize(p_action_sets.size());
2332 for (int i = 0; i < p_action_sets.size(); i++) {
2333 ActionSet *action_set = action_set_owner.get_or_null(p_action_sets[i]);
2334 ERR_FAIL_NULL_V(action_set, false);
2335
2336 if (action_set->is_attached) {
2337 return false;
2338 }
2339
2340 action_handles.set(i, action_set->handle);
2341 }
2342
2343 // So according to the docs, once we attach our action set to our session it becomes read only..
2344 // https://www.khronos.org/registry/OpenXR/specs/1.0/man/html/xrAttachSessionActionSets.html
2345 XrSessionActionSetsAttachInfo attach_info = {
2346 XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO, // type
2347 nullptr, // next
2348 (uint32_t)p_action_sets.size(), // countActionSets,
2349 action_handles.ptr() // actionSets
2350 };
2351
2352 XrResult result = xrAttachSessionActionSets(session, &attach_info);
2353 if (XR_FAILED(result)) {
2354 print_line("OpenXR: failed to attach action sets! [", get_error_string(result), "]");
2355 return false;
2356 }
2357
2358 for (int i = 0; i < p_action_sets.size(); i++) {
2359 ActionSet *action_set = action_set_owner.get_or_null(p_action_sets[i]);
2360 ERR_FAIL_NULL_V(action_set, false);
2361 action_set->is_attached = true;
2362 }
2363
2364 /* For debugging:
2365 print_verbose("Attached set " + action_set->name);
2366 List<RID> action_rids;
2367 action_owner.get_owned_list(&action_rids);
2368 for (int i = 0; i < action_rids.size(); i++) {
2369 Action * action = action_owner.get_or_null(action_rids[i]);
2370 if (action && action->action_set_rid == p_action_set) {
2371 print_verbose(" - Action " + action->name + ": " + OpenXRUtil::get_action_type_name(action->action_type));
2372 for (int j = 0; j < action->trackers.size(); j++) {
2373 Tracker * tracker = tracker_owner.get_or_null(action->trackers[j].tracker_rid);
2374 if (tracker) {
2375 print_verbose(" - " + tracker->name);
2376 }
2377 }
2378 }
2379 }
2380 */
2381
2382 return true;
2383}
2384
2385void OpenXRAPI::action_set_free(RID p_action_set) {
2386 ActionSet *action_set = action_set_owner.get_or_null(p_action_set);
2387 ERR_FAIL_NULL(action_set);
2388
2389 if (action_set->handle != XR_NULL_HANDLE) {
2390 xrDestroyActionSet(action_set->handle);
2391 }
2392
2393 action_set_owner.free(p_action_set);
2394}
2395
2396RID OpenXRAPI::get_action_rid(XrAction p_action) {
2397 List<RID> current;
2398 action_owner.get_owned_list(&current);
2399 for (int i = 0; i < current.size(); i++) {
2400 Action *action = action_owner.get_or_null(current[i]);
2401 if (action && action->handle == p_action) {
2402 return current[i];
2403 }
2404 }
2405
2406 return RID();
2407}
2408
2409RID OpenXRAPI::action_create(RID p_action_set, const String p_name, const String p_localized_name, OpenXRAction::ActionType p_action_type, const Vector<RID> &p_trackers) {
2410 ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, RID());
2411
2412 Action action;
2413 action.name = p_name;
2414
2415 ActionSet *action_set = action_set_owner.get_or_null(p_action_set);
2416 ERR_FAIL_NULL_V(action_set, RID());
2417 ERR_FAIL_COND_V(action_set->handle == XR_NULL_HANDLE, RID());
2418 action.action_set_rid = p_action_set;
2419
2420 switch (p_action_type) {
2421 case OpenXRAction::OPENXR_ACTION_BOOL:
2422 action.action_type = XR_ACTION_TYPE_BOOLEAN_INPUT;
2423 break;
2424 case OpenXRAction::OPENXR_ACTION_FLOAT:
2425 action.action_type = XR_ACTION_TYPE_FLOAT_INPUT;
2426 break;
2427 case OpenXRAction::OPENXR_ACTION_VECTOR2:
2428 action.action_type = XR_ACTION_TYPE_VECTOR2F_INPUT;
2429 break;
2430 case OpenXRAction::OPENXR_ACTION_POSE:
2431 action.action_type = XR_ACTION_TYPE_POSE_INPUT;
2432 break;
2433 case OpenXRAction::OPENXR_ACTION_HAPTIC:
2434 action.action_type = XR_ACTION_TYPE_VIBRATION_OUTPUT;
2435 break;
2436 default:
2437 ERR_FAIL_V(RID());
2438 break;
2439 }
2440
2441 Vector<XrPath> toplevel_paths;
2442 for (int i = 0; i < p_trackers.size(); i++) {
2443 Tracker *tracker = tracker_owner.get_or_null(p_trackers[i]);
2444 if (tracker != nullptr && tracker->toplevel_path != XR_NULL_PATH) {
2445 ActionTracker action_tracker = {
2446 p_trackers[i], // tracker
2447 XR_NULL_HANDLE, // space
2448 false // was_location_valid
2449 };
2450 action.trackers.push_back(action_tracker);
2451
2452 toplevel_paths.push_back(tracker->toplevel_path);
2453 }
2454 }
2455
2456 XrActionCreateInfo action_info = {
2457 XR_TYPE_ACTION_CREATE_INFO, // type
2458 nullptr, // next
2459 "", // actionName
2460 action.action_type, // actionType
2461 uint32_t(toplevel_paths.size()), // countSubactionPaths
2462 toplevel_paths.ptr(), // subactionPaths
2463 "" // localizedActionName
2464 };
2465
2466 copy_string_to_char_buffer(p_name, action_info.actionName, XR_MAX_ACTION_NAME_SIZE);
2467 copy_string_to_char_buffer(p_localized_name, action_info.localizedActionName, XR_MAX_LOCALIZED_ACTION_NAME_SIZE);
2468
2469 XrResult result = xrCreateAction(action_set->handle, &action_info, &action.handle);
2470 if (XR_FAILED(result)) {
2471 print_line("OpenXR: failed to create action ", p_name, "! [", get_error_string(result), "]");
2472 return RID();
2473 }
2474
2475 return action_owner.make_rid(action);
2476}
2477
2478String OpenXRAPI::action_get_name(RID p_action) {
2479 if (p_action.is_null()) {
2480 return String("None");
2481 }
2482
2483 Action *action = action_owner.get_or_null(p_action);
2484 ERR_FAIL_NULL_V(action, String());
2485
2486 return action->name;
2487}
2488
2489void OpenXRAPI::action_free(RID p_action) {
2490 Action *action = action_owner.get_or_null(p_action);
2491 ERR_FAIL_NULL(action);
2492
2493 if (action->handle != XR_NULL_HANDLE) {
2494 xrDestroyAction(action->handle);
2495 }
2496
2497 action_owner.free(p_action);
2498}
2499
2500RID OpenXRAPI::get_interaction_profile_rid(XrPath p_path) {
2501 List<RID> current;
2502 interaction_profile_owner.get_owned_list(&current);
2503 for (int i = 0; i < current.size(); i++) {
2504 InteractionProfile *ip = interaction_profile_owner.get_or_null(current[i]);
2505 if (ip && ip->path == p_path) {
2506 return current[i];
2507 }
2508 }
2509
2510 return RID();
2511}
2512
2513XrPath OpenXRAPI::get_interaction_profile_path(RID p_interaction_profile) {
2514 if (p_interaction_profile.is_null()) {
2515 return XR_NULL_PATH;
2516 }
2517
2518 InteractionProfile *ip = interaction_profile_owner.get_or_null(p_interaction_profile);
2519 ERR_FAIL_NULL_V(ip, XR_NULL_PATH);
2520
2521 return ip->path;
2522}
2523
2524RID OpenXRAPI::interaction_profile_create(const String p_name) {
2525 if (!is_interaction_profile_supported(p_name)) {
2526 // The extension enabling this path must not be active, we will silently skip this interaction profile
2527 return RID();
2528 }
2529
2530 InteractionProfile new_interaction_profile;
2531
2532 XrResult result = xrStringToPath(instance, p_name.utf8().get_data(), &new_interaction_profile.path);
2533 if (XR_FAILED(result)) {
2534 print_line("OpenXR: failed to get path for ", p_name, "! [", get_error_string(result), "]");
2535 return RID();
2536 }
2537
2538 RID existing_ip = get_interaction_profile_rid(new_interaction_profile.path);
2539 if (existing_ip.is_valid()) {
2540 return existing_ip;
2541 }
2542
2543 new_interaction_profile.name = p_name;
2544 return interaction_profile_owner.make_rid(new_interaction_profile);
2545}
2546
2547String OpenXRAPI::interaction_profile_get_name(RID p_interaction_profile) {
2548 if (p_interaction_profile.is_null()) {
2549 return String("None");
2550 }
2551
2552 InteractionProfile *ip = interaction_profile_owner.get_or_null(p_interaction_profile);
2553 ERR_FAIL_NULL_V(ip, String());
2554
2555 return ip->name;
2556}
2557
2558void OpenXRAPI::interaction_profile_clear_bindings(RID p_interaction_profile) {
2559 InteractionProfile *ip = interaction_profile_owner.get_or_null(p_interaction_profile);
2560 ERR_FAIL_NULL(ip);
2561
2562 ip->bindings.clear();
2563}
2564
2565bool OpenXRAPI::interaction_profile_add_binding(RID p_interaction_profile, RID p_action, const String p_path) {
2566 InteractionProfile *ip = interaction_profile_owner.get_or_null(p_interaction_profile);
2567 ERR_FAIL_NULL_V(ip, false);
2568
2569 if (!interaction_profile_supports_io_path(ip->name, p_path)) {
2570 return false;
2571 }
2572
2573 XrActionSuggestedBinding binding;
2574
2575 Action *action = action_owner.get_or_null(p_action);
2576 ERR_FAIL_COND_V(action == nullptr || action->handle == XR_NULL_HANDLE, false);
2577
2578 binding.action = action->handle;
2579
2580 XrResult result = xrStringToPath(instance, p_path.utf8().get_data(), &binding.binding);
2581 if (XR_FAILED(result)) {
2582 print_line("OpenXR: failed to get path for ", p_path, "! [", get_error_string(result), "]");
2583 return false;
2584 }
2585
2586 ip->bindings.push_back(binding);
2587
2588 return true;
2589}
2590
2591bool OpenXRAPI::interaction_profile_suggest_bindings(RID p_interaction_profile) {
2592 ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, false);
2593
2594 InteractionProfile *ip = interaction_profile_owner.get_or_null(p_interaction_profile);
2595 ERR_FAIL_NULL_V(ip, false);
2596
2597 const XrInteractionProfileSuggestedBinding suggested_bindings = {
2598 XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING, // type
2599 nullptr, // next
2600 ip->path, // interactionProfile
2601 uint32_t(ip->bindings.size()), // countSuggestedBindings
2602 ip->bindings.ptr() // suggestedBindings
2603 };
2604
2605 XrResult result = xrSuggestInteractionProfileBindings(instance, &suggested_bindings);
2606 if (result == XR_ERROR_PATH_UNSUPPORTED) {
2607 // this is fine, not all runtimes support all devices.
2608 print_verbose("OpenXR Interaction profile " + ip->name + " is not supported on this runtime");
2609 } else if (XR_FAILED(result)) {
2610 print_line("OpenXR: failed to suggest bindings for ", ip->name, "! [", get_error_string(result), "]");
2611 // reporting is enough...
2612 }
2613
2614 /* For debugging:
2615 print_verbose("Suggested bindings for " + ip->name);
2616 for (int i = 0; i < ip->bindings.size(); i++) {
2617 uint32_t strlen;
2618 char path[XR_MAX_PATH_LENGTH];
2619
2620 String action_name = action_get_name(get_action_rid(ip->bindings[i].action));
2621
2622 XrResult result = xrPathToString(instance, ip->bindings[i].binding, XR_MAX_PATH_LENGTH, &strlen, path);
2623 if (XR_FAILED(result)) {
2624 print_line("OpenXR: failed to retrieve bindings for ", action_name, "! [", get_error_string(result), "]");
2625 }
2626 print_verbose(" - " + action_name + " => " + String(path));
2627 }
2628 */
2629
2630 return true;
2631}
2632
2633void OpenXRAPI::interaction_profile_free(RID p_interaction_profile) {
2634 InteractionProfile *ip = interaction_profile_owner.get_or_null(p_interaction_profile);
2635 ERR_FAIL_NULL(ip);
2636
2637 ip->bindings.clear();
2638
2639 interaction_profile_owner.free(p_interaction_profile);
2640}
2641
2642bool OpenXRAPI::sync_action_sets(const Vector<RID> p_active_sets) {
2643 ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false);
2644
2645 if (!running) {
2646 return false;
2647 }
2648
2649 Vector<XrActiveActionSet> active_sets;
2650 for (int i = 0; i < p_active_sets.size(); i++) {
2651 ActionSet *action_set = action_set_owner.get_or_null(p_active_sets[i]);
2652 if (action_set && action_set->handle != XR_NULL_HANDLE) {
2653 XrActiveActionSet aset;
2654 aset.actionSet = action_set->handle;
2655 aset.subactionPath = XR_NULL_PATH;
2656 active_sets.push_back(aset);
2657 }
2658 }
2659
2660 ERR_FAIL_COND_V(active_sets.size() == 0, false);
2661
2662 XrActionsSyncInfo sync_info = {
2663 XR_TYPE_ACTIONS_SYNC_INFO, // type
2664 nullptr, // next
2665 uint32_t(active_sets.size()), // countActiveActionSets
2666 active_sets.ptr() // activeActionSets
2667 };
2668
2669 XrResult result = xrSyncActions(session, &sync_info);
2670 if (XR_FAILED(result)) {
2671 print_line("OpenXR: failed to sync active action sets! [", get_error_string(result), "]");
2672 return false;
2673 }
2674
2675 return true;
2676}
2677
2678bool OpenXRAPI::get_action_bool(RID p_action, RID p_tracker) {
2679 ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false);
2680 Action *action = action_owner.get_or_null(p_action);
2681 ERR_FAIL_NULL_V(action, false);
2682 Tracker *tracker = tracker_owner.get_or_null(p_tracker);
2683 ERR_FAIL_NULL_V(tracker, false);
2684
2685 if (!running) {
2686 return false;
2687 }
2688
2689 ERR_FAIL_COND_V(action->action_type != XR_ACTION_TYPE_BOOLEAN_INPUT, false);
2690
2691 XrActionStateGetInfo get_info = {
2692 XR_TYPE_ACTION_STATE_GET_INFO, // type
2693 nullptr, // next
2694 action->handle, // action
2695 tracker->toplevel_path // subactionPath
2696 };
2697
2698 XrActionStateBoolean result_state;
2699 result_state.type = XR_TYPE_ACTION_STATE_BOOLEAN,
2700 result_state.next = nullptr;
2701 XrResult result = xrGetActionStateBoolean(session, &get_info, &result_state);
2702 if (XR_FAILED(result)) {
2703 print_line("OpenXR: couldn't get action boolean! [", get_error_string(result), "]");
2704 return false;
2705 }
2706
2707 return result_state.isActive && result_state.currentState;
2708}
2709
2710float OpenXRAPI::get_action_float(RID p_action, RID p_tracker) {
2711 ERR_FAIL_COND_V(session == XR_NULL_HANDLE, 0.0);
2712 Action *action = action_owner.get_or_null(p_action);
2713 ERR_FAIL_NULL_V(action, 0.0);
2714 Tracker *tracker = tracker_owner.get_or_null(p_tracker);
2715 ERR_FAIL_NULL_V(tracker, 0.0);
2716
2717 if (!running) {
2718 return 0.0;
2719 }
2720
2721 ERR_FAIL_COND_V(action->action_type != XR_ACTION_TYPE_FLOAT_INPUT, 0.0);
2722
2723 XrActionStateGetInfo get_info = {
2724 XR_TYPE_ACTION_STATE_GET_INFO, // type
2725 nullptr, // next
2726 action->handle, // action
2727 tracker->toplevel_path // subactionPath
2728 };
2729
2730 XrActionStateFloat result_state;
2731 result_state.type = XR_TYPE_ACTION_STATE_FLOAT,
2732 result_state.next = nullptr;
2733 XrResult result = xrGetActionStateFloat(session, &get_info, &result_state);
2734 if (XR_FAILED(result)) {
2735 print_line("OpenXR: couldn't get action float! [", get_error_string(result), "]");
2736 return 0.0;
2737 }
2738
2739 return result_state.isActive ? result_state.currentState : 0.0;
2740}
2741
2742Vector2 OpenXRAPI::get_action_vector2(RID p_action, RID p_tracker) {
2743 ERR_FAIL_COND_V(session == XR_NULL_HANDLE, Vector2());
2744 Action *action = action_owner.get_or_null(p_action);
2745 ERR_FAIL_NULL_V(action, Vector2());
2746 Tracker *tracker = tracker_owner.get_or_null(p_tracker);
2747 ERR_FAIL_NULL_V(tracker, Vector2());
2748
2749 if (!running) {
2750 return Vector2();
2751 }
2752
2753 ERR_FAIL_COND_V(action->action_type != XR_ACTION_TYPE_VECTOR2F_INPUT, Vector2());
2754
2755 XrActionStateGetInfo get_info = {
2756 XR_TYPE_ACTION_STATE_GET_INFO, // type
2757 nullptr, // next
2758 action->handle, // action
2759 tracker->toplevel_path // subactionPath
2760 };
2761
2762 XrActionStateVector2f result_state;
2763 result_state.type = XR_TYPE_ACTION_STATE_VECTOR2F,
2764 result_state.next = nullptr;
2765 XrResult result = xrGetActionStateVector2f(session, &get_info, &result_state);
2766 if (XR_FAILED(result)) {
2767 print_line("OpenXR: couldn't get action vector2! [", get_error_string(result), "]");
2768 return Vector2();
2769 }
2770
2771 return result_state.isActive ? Vector2(result_state.currentState.x, result_state.currentState.y) : Vector2();
2772}
2773
2774XRPose::TrackingConfidence OpenXRAPI::get_action_pose(RID p_action, RID p_tracker, Transform3D &r_transform, Vector3 &r_linear_velocity, Vector3 &r_angular_velocity) {
2775 ERR_FAIL_COND_V(session == XR_NULL_HANDLE, XRPose::XR_TRACKING_CONFIDENCE_NONE);
2776 Action *action = action_owner.get_or_null(p_action);
2777 ERR_FAIL_NULL_V(action, XRPose::XR_TRACKING_CONFIDENCE_NONE);
2778 Tracker *tracker = tracker_owner.get_or_null(p_tracker);
2779 ERR_FAIL_NULL_V(tracker, XRPose::XR_TRACKING_CONFIDENCE_NONE);
2780
2781 if (!running) {
2782 return XRPose::XR_TRACKING_CONFIDENCE_NONE;
2783 }
2784
2785 ERR_FAIL_COND_V(action->action_type != XR_ACTION_TYPE_POSE_INPUT, XRPose::XR_TRACKING_CONFIDENCE_NONE);
2786
2787 // print_verbose("Checking " + action->name + " => " + tracker->name + " (" + itos(tracker->toplevel_path) + ")");
2788
2789 uint64_t index = 0xFFFFFFFF;
2790 uint64_t size = uint64_t(action->trackers.size());
2791 for (uint64_t i = 0; i < size && index == 0xFFFFFFFF; i++) {
2792 if (action->trackers[i].tracker_rid == p_tracker) {
2793 index = i;
2794 }
2795 }
2796
2797 if (index == 0xFFFFFFFF) {
2798 // couldn't find it?
2799 return XRPose::XR_TRACKING_CONFIDENCE_NONE;
2800 }
2801
2802 XrTime display_time = get_next_frame_time();
2803 if (display_time == 0) {
2804 return XRPose::XR_TRACKING_CONFIDENCE_NONE;
2805 }
2806
2807 if (action->trackers[index].space == XR_NULL_HANDLE) {
2808 // if this is a pose we need to define spaces
2809
2810 XrActionSpaceCreateInfo action_space_info = {
2811 XR_TYPE_ACTION_SPACE_CREATE_INFO, // type
2812 nullptr, // next
2813 action->handle, // action
2814 tracker->toplevel_path, // subactionPath
2815 {
2816 { 0.0, 0.0, 0.0, 1.0 }, // orientation
2817 { 0.0, 0.0, 0.0 } // position
2818 } // poseInActionSpace
2819 };
2820
2821 XrSpace space;
2822 XrResult result = xrCreateActionSpace(session, &action_space_info, &space);
2823 if (XR_FAILED(result)) {
2824 print_line("OpenXR: couldn't create action space! [", get_error_string(result), "]");
2825 return XRPose::XR_TRACKING_CONFIDENCE_NONE;
2826 }
2827
2828 action->trackers.ptrw()[index].space = space;
2829 }
2830
2831 XrSpaceVelocity velocity = {
2832 XR_TYPE_SPACE_VELOCITY, // type
2833 nullptr, // next
2834 0, // velocityFlags
2835 { 0.0, 0.0, 0.0 }, // linearVelocity
2836 { 0.0, 0.0, 0.0 } // angularVelocity
2837 };
2838
2839 XrSpaceLocation location = {
2840 XR_TYPE_SPACE_LOCATION, // type
2841 &velocity, // next
2842 0, // locationFlags
2843 {
2844 { 0.0, 0.0, 0.0, 0.0 }, // orientation
2845 { 0.0, 0.0, 0.0 } // position
2846 } // pose
2847 };
2848
2849 XrResult result = xrLocateSpace(action->trackers[index].space, play_space, display_time, &location);
2850 if (XR_FAILED(result)) {
2851 print_line("OpenXR: failed to locate space! [", get_error_string(result), "]");
2852 return XRPose::XR_TRACKING_CONFIDENCE_NONE;
2853 }
2854
2855 XRPose::TrackingConfidence confidence = transform_from_location(location, r_transform);
2856 parse_velocities(velocity, r_linear_velocity, r_angular_velocity);
2857
2858 return confidence;
2859}
2860
2861bool OpenXRAPI::trigger_haptic_pulse(RID p_action, RID p_tracker, float p_frequency, float p_amplitude, XrDuration p_duration_ns) {
2862 ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false);
2863 Action *action = action_owner.get_or_null(p_action);
2864 ERR_FAIL_NULL_V(action, false);
2865 Tracker *tracker = tracker_owner.get_or_null(p_tracker);
2866 ERR_FAIL_NULL_V(tracker, false);
2867
2868 if (!running) {
2869 return false;
2870 }
2871
2872 ERR_FAIL_COND_V(action->action_type != XR_ACTION_TYPE_VIBRATION_OUTPUT, false);
2873
2874 XrHapticActionInfo action_info = {
2875 XR_TYPE_HAPTIC_ACTION_INFO, // type
2876 nullptr, // next
2877 action->handle, // action
2878 tracker->toplevel_path // subactionPath
2879 };
2880
2881 XrHapticVibration vibration = {
2882 XR_TYPE_HAPTIC_VIBRATION, // type
2883 nullptr, // next
2884 p_duration_ns, // duration
2885 p_frequency, // frequency
2886 p_amplitude, // amplitude
2887 };
2888
2889 XrResult result = xrApplyHapticFeedback(session, &action_info, (const XrHapticBaseHeader *)&vibration);
2890 if (XR_FAILED(result)) {
2891 print_line("OpenXR: failed to apply haptic feedback! [", get_error_string(result), "]");
2892 return false;
2893 }
2894
2895 return true;
2896}
2897
2898void OpenXRAPI::register_composition_layer_provider(OpenXRCompositionLayerProvider *provider) {
2899 composition_layer_providers.append(provider);
2900}
2901
2902void OpenXRAPI::unregister_composition_layer_provider(OpenXRCompositionLayerProvider *provider) {
2903 composition_layer_providers.erase(provider);
2904}
2905
2906const XrEnvironmentBlendMode *OpenXRAPI::get_supported_environment_blend_modes(uint32_t &count) {
2907 count = num_supported_environment_blend_modes;
2908 return supported_environment_blend_modes;
2909}
2910
2911bool OpenXRAPI::is_environment_blend_mode_supported(XrEnvironmentBlendMode p_blend_mode) const {
2912 ERR_FAIL_NULL_V(supported_environment_blend_modes, false);
2913
2914 for (uint32_t i = 0; i < num_supported_environment_blend_modes; i++) {
2915 if (supported_environment_blend_modes[i] == p_blend_mode) {
2916 return true;
2917 }
2918 }
2919
2920 return false;
2921}
2922
2923bool OpenXRAPI::set_environment_blend_mode(XrEnvironmentBlendMode p_blend_mode) {
2924 // We allow setting this when not initialised and will check if it is supported when initialising.
2925 // After OpenXR is initialised we verify we're setting a supported blend mode.
2926 if (!is_initialized() || is_environment_blend_mode_supported(p_blend_mode)) {
2927 environment_blend_mode = p_blend_mode;
2928 return true;
2929 }
2930 return false;
2931}
2932