diff options
author | Bastiaan Olij <mux213@gmail.com> | 2021-12-14 12:44:12 +1100 |
---|---|---|
committer | RĂ©mi Verschelde <rverschelde@gmail.com> | 2022-02-23 12:02:24 +0100 |
commit | a78a9fee7194297fca8867b4983deb4da0ba18c2 (patch) | |
tree | 342d988883aa6779b8c2014c3723dfcbf97116fa | |
parent | 65bae5a3411a6fd2dbfc9f8e364040024fa81b04 (diff) |
Implementing OpenXR driver
53 files changed, 6359 insertions, 168 deletions
diff --git a/SConstruct b/SConstruct index a27e5490a5..233a79f635 100644 --- a/SConstruct +++ b/SConstruct @@ -173,6 +173,7 @@ opts.Add(BoolVariable("minizip", "Enable ZIP archive support using minizip", Tru opts.Add(BoolVariable("xaudio2", "Enable the XAudio2 audio driver", False)) opts.Add(BoolVariable("vulkan", "Enable the vulkan video driver", True)) opts.Add(BoolVariable("opengl3", "Enable the OpenGL/GLES3 video driver", True)) +opts.Add(BoolVariable("openxr", "Enable the OpenXR driver", True)) opts.Add("custom_modules", "A list of comma-separated directory paths containing custom modules to build.", "") opts.Add(BoolVariable("custom_modules_recursive", "Detect custom modules recursively for each specified path.", True)) opts.Add(BoolVariable("use_volk", "Use the volk library to load the Vulkan loader dynamically", True)) diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index da35b3260e..a3810bb575 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -1915,8 +1915,23 @@ </member> <member name="rendering/vulkan/staging_buffer/texture_upload_region_size_px" type="int" setter="" getter="" default="64"> </member> - <member name="rendering/xr/enabled" type="bool" setter="" getter="" default="false"> - If [code]true[/code], XR support is enabled in Godot, this ensures required shaders are compiled. + <member name="xr/openxr/default_action_map" type="String" setter="" getter="" default=""res://default_action_map.tres""> + Action map configuration to load by default. + </member> + <member name="xr/openxr/enabled" type="bool" setter="" getter="" default="false"> + If [code]true[/code] Godot will setup and initialise OpenXR on startup. + </member> + <member name="xr/openxr/form_factor" type="int" setter="" getter="" default=""0""> + Specify whether OpenXR should be configured for an HMD or a hand held device. + </member> + <member name="xr/openxr/reference_space" type="int" setter="" getter="" default=""1""> + Specify the default reference space. + </member> + <member name="xr/openxr/view_configuration" type="int" setter="" getter="" default=""1""> + Specify the view configuration with which to configure OpenXR settting up either Mono or Stereo rendering. + </member> + <member name="xr/shaders/enabled" type="bool" setter="" getter="" default="false"> + If [code]true[/code], Godot will compile shaders required for XR. </member> </members> </class> diff --git a/doc/classes/XRController3D.xml b/doc/classes/XRController3D.xml index a77f8cf9ca..deea888b8f 100644 --- a/doc/classes/XRController3D.xml +++ b/doc/classes/XRController3D.xml @@ -41,12 +41,6 @@ </description> </method> </methods> - <members> - <member name="rumble" type="float" setter="set_rumble" getter="get_rumble" default="0.0"> - The degree to which the controller vibrates. Ranges from [code]0.0[/code] to [code]1.0[/code] with precision [code].01[/code]. If changed, updates [member XRPositionalTracker.rumble] accordingly. - This is a useful property to animate if you want the controller to vibrate for a limited duration. - </member> - </members> <signals> <signal name="button_pressed"> <argument index="0" name="name" type="String" /> diff --git a/doc/classes/XRPositionalTracker.xml b/doc/classes/XRPositionalTracker.xml index c544ef3cfa..da378759d8 100644 --- a/doc/classes/XRPositionalTracker.xml +++ b/doc/classes/XRPositionalTracker.xml @@ -72,9 +72,6 @@ - [code]left_hand[/code] identifies the controller held in the players left hand - [code]right_hand[/code] identifies the controller held in the players right hand </member> - <member name="rumble" type="float" setter="set_rumble" getter="get_rumble" default="0.0"> - The degree to which the tracker rumbles. Ranges from [code]0.0[/code] to [code]1.0[/code] with precision [code].01[/code]. - </member> <member name="type" type="int" setter="set_tracker_type" getter="get_tracker_type" enum="XRServer.TrackerType" default="128"> The type of tracker. </member> diff --git a/drivers/vulkan/rendering_device_vulkan.cpp b/drivers/vulkan/rendering_device_vulkan.cpp index ddeac220ec..cda8871f3e 100644 --- a/drivers/vulkan/rendering_device_vulkan.cpp +++ b/drivers/vulkan/rendering_device_vulkan.cpp @@ -2118,6 +2118,124 @@ RID RenderingDeviceVulkan::texture_create_shared(const TextureView &p_view, RID return id; } +RID RenderingDeviceVulkan::texture_create_from_extension(TextureType p_type, DataFormat p_format, TextureSamples p_samples, uint64_t p_flags, uint64_t p_image, uint64_t p_width, uint64_t p_height, uint64_t p_depth, uint64_t p_layers) { + _THREAD_SAFE_METHOD_ + // This method creates a texture object using a VkImage created by an extension, module or other external source (OpenXR uses this). + VkImage image = (VkImage)p_image; + + Texture texture; + texture.image = image; + // if we leave texture.allocation as a nullptr, would that be enough to detect we don't "own" the image? + // also leave texture.allocation_info alone + // we'll set texture.view later on + texture.type = p_type; + texture.format = p_format; + texture.samples = p_samples; + texture.width = p_width; + texture.height = p_height; + texture.depth = p_depth; + texture.layers = p_layers; + texture.mipmaps = 0; // maybe make this settable too? + texture.usage_flags = p_flags; + texture.base_mipmap = 0; + texture.base_layer = 0; + texture.allowed_shared_formats.push_back(RD::DATA_FORMAT_R8G8B8A8_UNORM); + texture.allowed_shared_formats.push_back(RD::DATA_FORMAT_R8G8B8A8_SRGB); + + // Do we need to do something with texture.layout ? + + if (texture.usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) { + texture.read_aspect_mask = VK_IMAGE_ASPECT_DEPTH_BIT; + texture.barrier_aspect_mask = VK_IMAGE_ASPECT_DEPTH_BIT; + + // if (format_has_stencil(p_format.format)) { + // texture.barrier_aspect_mask |= VK_IMAGE_ASPECT_STENCIL_BIT; + // } + } else { + texture.read_aspect_mask = VK_IMAGE_ASPECT_COLOR_BIT; + texture.barrier_aspect_mask = VK_IMAGE_ASPECT_COLOR_BIT; + } + + // Create a view for us to use + + VkImageViewCreateInfo image_view_create_info; + image_view_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + image_view_create_info.pNext = nullptr; + image_view_create_info.flags = 0; + image_view_create_info.image = texture.image; + + static const VkImageViewType view_types[TEXTURE_TYPE_MAX] = { + VK_IMAGE_VIEW_TYPE_1D, + VK_IMAGE_VIEW_TYPE_2D, + VK_IMAGE_VIEW_TYPE_3D, + VK_IMAGE_VIEW_TYPE_CUBE, + VK_IMAGE_VIEW_TYPE_1D_ARRAY, + VK_IMAGE_VIEW_TYPE_2D_ARRAY, + VK_IMAGE_VIEW_TYPE_CUBE_ARRAY, + }; + + image_view_create_info.viewType = view_types[texture.type]; + image_view_create_info.format = vulkan_formats[texture.format]; + + static const VkComponentSwizzle component_swizzles[TEXTURE_SWIZZLE_MAX] = { + VK_COMPONENT_SWIZZLE_IDENTITY, + VK_COMPONENT_SWIZZLE_ZERO, + VK_COMPONENT_SWIZZLE_ONE, + VK_COMPONENT_SWIZZLE_R, + VK_COMPONENT_SWIZZLE_G, + VK_COMPONENT_SWIZZLE_B, + VK_COMPONENT_SWIZZLE_A + }; + + // hardcode for now, maybe make this settable from outside.. + image_view_create_info.components.r = component_swizzles[TEXTURE_SWIZZLE_R]; + image_view_create_info.components.g = component_swizzles[TEXTURE_SWIZZLE_G]; + image_view_create_info.components.b = component_swizzles[TEXTURE_SWIZZLE_B]; + image_view_create_info.components.a = component_swizzles[TEXTURE_SWIZZLE_A]; + + image_view_create_info.subresourceRange.baseMipLevel = 0; + image_view_create_info.subresourceRange.levelCount = texture.mipmaps; + image_view_create_info.subresourceRange.baseArrayLayer = 0; + image_view_create_info.subresourceRange.layerCount = texture.layers; + if (texture.usage_flags & TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) { + image_view_create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + } else { + image_view_create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + } + + VkResult err = vkCreateImageView(device, &image_view_create_info, nullptr, &texture.view); + + if (err) { + // vmaDestroyImage(allocator, texture.image, texture.allocation); + ERR_FAIL_V_MSG(RID(), "vkCreateImageView failed with error " + itos(err) + "."); + } + + //barrier to set layout + { + VkImageMemoryBarrier image_memory_barrier; + image_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + image_memory_barrier.pNext = nullptr; + image_memory_barrier.srcAccessMask = 0; + image_memory_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + image_memory_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + image_memory_barrier.newLayout = texture.layout; + image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + image_memory_barrier.image = texture.image; + image_memory_barrier.subresourceRange.aspectMask = texture.barrier_aspect_mask; + image_memory_barrier.subresourceRange.baseMipLevel = 0; + image_memory_barrier.subresourceRange.levelCount = texture.mipmaps; + image_memory_barrier.subresourceRange.baseArrayLayer = 0; + image_memory_barrier.subresourceRange.layerCount = texture.layers; + + vkCmdPipelineBarrier(frames[frame].setup_command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &image_memory_barrier); + } + + RID id = texture_owner.make_rid(texture); + + return id; +} + RID RenderingDeviceVulkan::texture_create_shared_from_slice(const TextureView &p_view, RID p_with_texture, uint32_t p_layer, uint32_t p_mipmap, uint32_t p_mipmaps, TextureSliceType p_slice_type) { _THREAD_SAFE_METHOD_ diff --git a/drivers/vulkan/rendering_device_vulkan.h b/drivers/vulkan/rendering_device_vulkan.h index 6510893196..3e4327667b 100644 --- a/drivers/vulkan/rendering_device_vulkan.h +++ b/drivers/vulkan/rendering_device_vulkan.h @@ -1037,6 +1037,7 @@ class RenderingDeviceVulkan : public RenderingDevice { public: virtual RID texture_create(const TextureFormat &p_format, const TextureView &p_view, const Vector<Vector<uint8_t>> &p_data = Vector<Vector<uint8_t>>()); virtual RID texture_create_shared(const TextureView &p_view, RID p_with_texture); + virtual RID texture_create_from_extension(TextureType p_type, DataFormat p_format, TextureSamples p_samples, uint64_t p_flags, uint64_t p_image, uint64_t p_width, uint64_t p_height, uint64_t p_depth, uint64_t p_layers); virtual RID texture_create_shared_from_slice(const TextureView &p_view, RID p_with_texture, uint32_t p_layer, uint32_t p_mipmap, uint32_t p_mipmaps = 1, TextureSliceType p_slice_type = TEXTURE_SLICE_2D); virtual Error texture_update(RID p_texture, uint32_t p_layer, const Vector<uint8_t> &p_data, uint32_t p_post_barrier = BARRIER_MASK_ALL); diff --git a/drivers/vulkan/vulkan_context.cpp b/drivers/vulkan/vulkan_context.cpp index e4aab8d868..3551b5d6c4 100644 --- a/drivers/vulkan/vulkan_context.cpp +++ b/drivers/vulkan/vulkan_context.cpp @@ -46,6 +46,8 @@ #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) #define APP_SHORT_NAME "GodotEngine" +VulkanHooks *VulkanContext::vulkan_hooks = nullptr; + VKAPI_ATTR VkBool32 VKAPI_CALL VulkanContext::_debug_messenger_callback( VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, @@ -695,19 +697,27 @@ Error VulkanContext::_create_instance() { inst_info.pNext = &dbg_report_callback_create_info; } - VkResult err = vkCreateInstance(&inst_info, nullptr, &inst); - ERR_FAIL_COND_V_MSG(err == VK_ERROR_INCOMPATIBLE_DRIVER, ERR_CANT_CREATE, - "Cannot find a compatible Vulkan installable client driver (ICD).\n\n" - "vkCreateInstance Failure"); - ERR_FAIL_COND_V_MSG(err == VK_ERROR_EXTENSION_NOT_PRESENT, ERR_CANT_CREATE, - "Cannot find a specified extension library.\n" - "Make sure your layers path is set appropriately.\n" - "vkCreateInstance Failure"); - ERR_FAIL_COND_V_MSG(err, ERR_CANT_CREATE, - "vkCreateInstance failed.\n\n" - "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" - "Please look at the Getting Started guide for additional information.\n" - "vkCreateInstance Failure"); + VkResult err; + + if (vulkan_hooks) { + if (!vulkan_hooks->create_vulkan_instance(&inst_info, &inst)) { + return ERR_CANT_CREATE; + } + } else { + err = vkCreateInstance(&inst_info, nullptr, &inst); + ERR_FAIL_COND_V_MSG(err == VK_ERROR_INCOMPATIBLE_DRIVER, ERR_CANT_CREATE, + "Cannot find a compatible Vulkan installable client driver (ICD).\n\n" + "vkCreateInstance Failure"); + ERR_FAIL_COND_V_MSG(err == VK_ERROR_EXTENSION_NOT_PRESENT, ERR_CANT_CREATE, + "Cannot find a specified extension library.\n" + "Make sure your layers path is set appropriately.\n" + "vkCreateInstance Failure"); + ERR_FAIL_COND_V_MSG(err, ERR_CANT_CREATE, + "vkCreateInstance failed.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n" + "vkCreateInstance Failure"); + } inst_initialized = true; @@ -820,107 +830,122 @@ Error VulkanContext::_create_physical_device(VkSurfaceKHR p_surface) { { 0, nullptr }, }; - // TODO: At least on Linux Laptops integrated GPUs fail with Vulkan in many instances. - // The device should really be a preference, but for now choosing a discrete GPU over the - // integrated one is better than the default. - int32_t device_index = -1; - int type_selected = -1; - print_verbose("Vulkan devices:"); - for (uint32_t i = 0; i < gpu_count; ++i) { - VkPhysicalDeviceProperties props; - vkGetPhysicalDeviceProperties(physical_devices[i], &props); - - bool present_supported = false; - - uint32_t device_queue_family_count = 0; - vkGetPhysicalDeviceQueueFamilyProperties(physical_devices[i], &device_queue_family_count, nullptr); - VkQueueFamilyProperties *device_queue_props = (VkQueueFamilyProperties *)malloc(device_queue_family_count * sizeof(VkQueueFamilyProperties)); - vkGetPhysicalDeviceQueueFamilyProperties(physical_devices[i], &device_queue_family_count, device_queue_props); - for (uint32_t j = 0; j < device_queue_family_count; j++) { - VkBool32 supports; - vkGetPhysicalDeviceSurfaceSupportKHR(physical_devices[i], j, p_surface, &supports); - if (supports && ((device_queue_props[j].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0)) { - present_supported = true; - } else { - continue; - } - } - String name = props.deviceName; - String vendor = "Unknown"; - String dev_type; - switch (props.deviceType) { - case VkPhysicalDeviceType::VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: { - dev_type = "Discrete"; - } break; - case VkPhysicalDeviceType::VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: { - dev_type = "Integrated"; - } break; - case VkPhysicalDeviceType::VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: { - dev_type = "Virtual"; - } break; - case VkPhysicalDeviceType::VK_PHYSICAL_DEVICE_TYPE_CPU: { - dev_type = "CPU"; - } break; - default: { - dev_type = "Other"; - } break; + if (vulkan_hooks) { + if (!vulkan_hooks->get_physical_device(&gpu)) { + return ERR_CANT_CREATE; } - uint32_t vendor_idx = 0; - while (vendor_names[vendor_idx].name != nullptr) { - if (props.vendorID == vendor_names[vendor_idx].id) { - vendor = vendor_names[vendor_idx].name; + + // not really needed but nice to print the correct entry + for (uint32_t i = 0; i < gpu_count; ++i) { + if (physical_devices[i] == gpu) { + device_index = i; break; } - vendor_idx++; } - free(device_queue_props); - print_verbose(" #" + itos(i) + ": " + vendor + " " + name + " - " + (present_supported ? "Supported" : "Unsupported") + ", " + dev_type); - - if (present_supported) { // Select first supported device of preferred type: Discrete > Integrated > Virtual > CPU > Other. + } else { + // TODO: At least on Linux Laptops integrated GPUs fail with Vulkan in many instances. + // The device should really be a preference, but for now choosing a discrete GPU over the + // integrated one is better than the default. + + int type_selected = -1; + print_verbose("Vulkan devices:"); + for (uint32_t i = 0; i < gpu_count; ++i) { + VkPhysicalDeviceProperties props; + vkGetPhysicalDeviceProperties(physical_devices[i], &props); + + bool present_supported = false; + + uint32_t device_queue_family_count = 0; + vkGetPhysicalDeviceQueueFamilyProperties(physical_devices[i], &device_queue_family_count, nullptr); + VkQueueFamilyProperties *device_queue_props = (VkQueueFamilyProperties *)malloc(device_queue_family_count * sizeof(VkQueueFamilyProperties)); + vkGetPhysicalDeviceQueueFamilyProperties(physical_devices[i], &device_queue_family_count, device_queue_props); + for (uint32_t j = 0; j < device_queue_family_count; j++) { + VkBool32 supports; + vkGetPhysicalDeviceSurfaceSupportKHR(physical_devices[i], j, p_surface, &supports); + if (supports && ((device_queue_props[j].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0)) { + present_supported = true; + } else { + continue; + } + } + String name = props.deviceName; + String vendor = "Unknown"; + String dev_type; switch (props.deviceType) { case VkPhysicalDeviceType::VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: { - if (type_selected < 4) { - type_selected = 4; - device_index = i; - } + dev_type = "Discrete"; } break; case VkPhysicalDeviceType::VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: { - if (type_selected < 3) { - type_selected = 3; - device_index = i; - } + dev_type = "Integrated"; } break; case VkPhysicalDeviceType::VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: { - if (type_selected < 2) { - type_selected = 2; - device_index = i; - } + dev_type = "Virtual"; } break; case VkPhysicalDeviceType::VK_PHYSICAL_DEVICE_TYPE_CPU: { - if (type_selected < 1) { - type_selected = 1; - device_index = i; - } + dev_type = "CPU"; } break; default: { - if (type_selected < 0) { - type_selected = 0; - device_index = i; - } + dev_type = "Other"; } break; } + uint32_t vendor_idx = 0; + while (vendor_names[vendor_idx].name != nullptr) { + if (props.vendorID == vendor_names[vendor_idx].id) { + vendor = vendor_names[vendor_idx].name; + break; + } + vendor_idx++; + } + free(device_queue_props); + print_verbose(" #" + itos(i) + ": " + vendor + " " + name + " - " + (present_supported ? "Supported" : "Unsupported") + ", " + dev_type); + + if (present_supported) { // Select first supported device of preffered type: Discrete > Integrated > Virtual > CPU > Other. + switch (props.deviceType) { + case VkPhysicalDeviceType::VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: { + if (type_selected < 4) { + type_selected = 4; + device_index = i; + } + } break; + case VkPhysicalDeviceType::VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: { + if (type_selected < 3) { + type_selected = 3; + device_index = i; + } + } break; + case VkPhysicalDeviceType::VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: { + if (type_selected < 2) { + type_selected = 2; + device_index = i; + } + } break; + case VkPhysicalDeviceType::VK_PHYSICAL_DEVICE_TYPE_CPU: { + if (type_selected < 1) { + type_selected = 1; + device_index = i; + } + } break; + default: { + if (type_selected < 0) { + type_selected = 0; + device_index = i; + } + } break; + } + } } - } - int32_t user_device_index = Engine::get_singleton()->get_gpu_index(); // Force user selected GPU. - if (user_device_index >= 0 && user_device_index < (int32_t)gpu_count) { - device_index = user_device_index; - } + int32_t user_device_index = Engine::get_singleton()->get_gpu_index(); // Force user selected GPU. + if (user_device_index >= 0 && user_device_index < (int32_t)gpu_count) { + device_index = user_device_index; + } - ERR_FAIL_COND_V_MSG(device_index == -1, ERR_CANT_CREATE, "None of Vulkan devices supports both graphics and present queues."); + ERR_FAIL_COND_V_MSG(device_index == -1, ERR_CANT_CREATE, "None of Vulkan devices supports both graphics and present queues."); + + gpu = physical_devices[device_index]; + } - gpu = physical_devices[device_index]; free(physical_devices); /* Look for device extensions */ @@ -1148,8 +1173,14 @@ Error VulkanContext::_create_device() { sdevice.queueCreateInfoCount = 2; } - err = vkCreateDevice(gpu, &sdevice, nullptr, &device); - ERR_FAIL_COND_V(err, ERR_CANT_CREATE); + if (vulkan_hooks) { + if (!vulkan_hooks->create_vulkan_device(&sdevice, &device)) { + return ERR_CANT_CREATE; + } + } else { + err = vkCreateDevice(gpu, &sdevice, nullptr, &device); + ERR_FAIL_COND_V(err, ERR_CANT_CREATE); + } return OK; } diff --git a/drivers/vulkan/vulkan_context.h b/drivers/vulkan/vulkan_context.h index d4052666e3..8c0111714c 100644 --- a/drivers/vulkan/vulkan_context.h +++ b/drivers/vulkan/vulkan_context.h @@ -45,6 +45,8 @@ #include <vulkan/vulkan.h> #endif +#include "vulkan_hooks.h" + class VulkanContext { public: struct SubgroupCapabilities { @@ -86,6 +88,7 @@ private: FRAME_LAG = 2 }; + static VulkanHooks *vulkan_hooks; VkInstance inst = VK_NULL_HANDLE; VkPhysicalDevice gpu = VK_NULL_HANDLE; VkPhysicalDeviceProperties gpu_props; @@ -267,6 +270,8 @@ public: VkQueue get_graphics_queue() const; uint32_t get_graphics_queue_family_index() const; + static void set_vulkan_hooks(VulkanHooks *p_vulkan_hooks) { vulkan_hooks = p_vulkan_hooks; }; + void window_resize(DisplayServer::WindowID p_window_id, int p_width, int p_height); int window_get_width(DisplayServer::WindowID p_window = 0); int window_get_height(DisplayServer::WindowID p_window = 0); diff --git a/drivers/vulkan/vulkan_hooks.h b/drivers/vulkan/vulkan_hooks.h new file mode 100644 index 0000000000..3f244b4d45 --- /dev/null +++ b/drivers/vulkan/vulkan_hooks.h @@ -0,0 +1,48 @@ +/*************************************************************************/ +/* vulkan_hooks.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef VULKAN_HOOKS_H +#define VULKAN_HOOKS_H + +#ifdef USE_VOLK +#include <volk.h> +#else +#include <vulkan/vulkan.h> +#endif + +class VulkanHooks { +public: + virtual bool create_vulkan_instance(const VkInstanceCreateInfo *p_vulkan_create_info, VkInstance *r_instance) { return false; }; + virtual bool get_physical_device(VkPhysicalDevice *r_device) { return false; }; + virtual bool create_vulkan_device(const VkDeviceCreateInfo *p_device_create_info, VkDevice *r_device) { return false; }; + virtual ~VulkanHooks(){}; +}; + +#endif diff --git a/main/main.cpp b/main/main.cpp index a68792105e..246a26025c 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -1963,6 +1963,10 @@ Error Main::setup2(Thread::ID p_main_tid_override) { return OK; } +String Main::get_rendering_driver_name() { + return rendering_driver; +} + // everything the main loop needs to know about frame timings static MainTimerSync main_timer_sync; diff --git a/main/main.h b/main/main.h index cec31545c7..14a8fb4147 100644 --- a/main/main.h +++ b/main/main.h @@ -49,6 +49,7 @@ public: static int test_entrypoint(int argc, char *argv[], bool &tests_need_run); static Error setup(const char *execpath, int argc, char *argv[], bool p_second_phase = true); static Error setup2(Thread::ID p_main_tid_override = 0); + static String get_rendering_driver_name(); #ifdef TESTS_ENABLED static Error test_setup(); static void test_cleanup(); diff --git a/modules/openxr/SCsub b/modules/openxr/SCsub new file mode 100644 index 0000000000..37a8f3909a --- /dev/null +++ b/modules/openxr/SCsub @@ -0,0 +1,87 @@ +#!/usr/bin/env python + +Import("env") +Import("env_modules") + +env_openxr = env_modules.Clone() + +################################################# +# Add in our Khronos OpenXR loader + +thirdparty_obj = [] +thirdparty_dir = "#thirdparty/openxr" + +env_openxr.Prepend( + CPPPATH=[ + thirdparty_dir, + thirdparty_dir + "/include", + thirdparty_dir + "/src", + thirdparty_dir + "/src/common", + thirdparty_dir + "/src/external/jsoncpp/include", + thirdparty_dir + "/src/loader", + ] +) + +# may need to check and set: +# - XR_USE_TIMESPEC + +env_thirdparty = env_openxr.Clone() +env_thirdparty.disable_warnings() +env_thirdparty.AppendUnique(CPPDEFINES=["DISABLE_STD_FILESYSTEM"]) + +if env["platform"] == "android": + # may need to set OPENXR_ANDROID_VERSION_SUFFIX + env_thirdparty.AppendUnique(CPPDEFINES=["XR_OS_ANDROID", "XR_USE_PLATFORM_ANDROID"]) + + # may need to include java parts of the openxr loader +elif env["platform"] == "linuxbsd": + env_thirdparty.AppendUnique(CPPDEFINES=["XR_OS_LINUX", "XR_USE_PLATFORM_XLIB"]) + # FIXME: Review what needs to be set for Android and macOS. + env_thirdparty.AppendUnique(CPPDEFINES=["HAVE_SECURE_GETENV"]) +elif env["platform"] == "windows": + env_thirdparty.AppendUnique(CPPDEFINES=["XR_OS_WINDOWS", "NOMINMAX", "XR_USE_PLATFORM_WIN32"]) + +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/xr_generated_dispatch_table.c") + +# add in common files (hope these don't clash with us) +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/common/filesystem_utils.cpp") +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/common/object_info.cpp") + +# add in external jsoncpp dependency +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/external/jsoncpp/src/lib_json/json_reader.cpp") +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/external/jsoncpp/src/lib_json/json_value.cpp") +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/external/jsoncpp/src/lib_json/json_writer.cpp") + +# add in load +if env["platform"] == "android": + env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/loader/android_utilities.cpp") + +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/loader/api_layer_interface.cpp") +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/loader/loader_core.cpp") +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/loader/loader_instance.cpp") +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/loader/loader_logger_recorders.cpp") +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/loader/loader_logger.cpp") +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/loader/manifest_file.cpp") +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/loader/runtime_interface.cpp") +env_thirdparty.add_source_files(thirdparty_obj, thirdparty_dir + "/src/loader/xr_generated_loader.cpp") + +env.modules_sources += thirdparty_obj + +################################################# +# And include our module source + +module_obj = [] + +env_openxr.add_source_files(module_obj, "*.cpp") +env_openxr.add_source_files(module_obj, "action_map/*.cpp") + +# We're a little more targetted with our extensions +if env["platform"] == "android": + env_openxr.add_source_files(module_obj, "extensions/openxr_android_extension.cpp") +if env["vulkan"]: + env_openxr.add_source_files(module_obj, "extensions/openxr_vulkan_extension.cpp") + +env.modules_sources += module_obj + +# Needed to force rebuilding the module files when the thirdparty library is updated. +env.Depends(module_obj, thirdparty_obj) diff --git a/modules/openxr/action_map/openxr_action.cpp b/modules/openxr/action_map/openxr_action.cpp new file mode 100644 index 0000000000..59ee3f4292 --- /dev/null +++ b/modules/openxr/action_map/openxr_action.cpp @@ -0,0 +1,91 @@ +/*************************************************************************/ +/* openxr_action.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "openxr_action.h" + +void OpenXRAction::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_localized_name", "localized_name"), &OpenXRAction::set_localized_name); + ClassDB::bind_method(D_METHOD("get_localized_name"), &OpenXRAction::get_localized_name); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "localized_name"), "set_localized_name", "get_localized_name"); + + ClassDB::bind_method(D_METHOD("set_action_type", "action_type"), &OpenXRAction::set_action_type); + ClassDB::bind_method(D_METHOD("get_action_type"), &OpenXRAction::get_action_type); + ADD_PROPERTY(PropertyInfo(Variant::INT, "action_type", PROPERTY_HINT_ENUM, "bool,float,vector2,pose"), "set_action_type", "get_action_type"); + + ClassDB::bind_method(D_METHOD("set_toplevel_paths", "toplevel_paths"), &OpenXRAction::set_toplevel_paths); + ClassDB::bind_method(D_METHOD("get_toplevel_paths"), &OpenXRAction::get_toplevel_paths); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "toplevel_paths", PROPERTY_HINT_ARRAY_TYPE, "STRING"), "set_toplevel_paths", "get_toplevel_paths"); + + BIND_ENUM_CONSTANT(OPENXR_ACTION_BOOL); + BIND_ENUM_CONSTANT(OPENXR_ACTION_FLOAT); + BIND_ENUM_CONSTANT(OPENXR_ACTION_VECTOR2); + BIND_ENUM_CONSTANT(OPENXR_ACTION_POSE); +} + +Ref<OpenXRAction> OpenXRAction::new_action(const char *p_name, const char *p_localized_name, const ActionType p_action_type, const char *p_toplevel_paths) { + // This is a helper function to help build our default action sets + + Ref<OpenXRAction> action; + action.instantiate(); + action->set_name(String(p_name)); + action->set_localized_name(String(p_localized_name)); + action->set_action_type(p_action_type); + action->parse_toplevel_paths(String(p_toplevel_paths)); + + return action; +} + +void OpenXRAction::set_localized_name(const String p_localized_name) { + localized_name = p_localized_name; +} + +String OpenXRAction::get_localized_name() const { + return localized_name; +} + +void OpenXRAction::set_action_type(const OpenXRAction::ActionType p_action_type) { + action_type = p_action_type; +} + +OpenXRAction::ActionType OpenXRAction::get_action_type() const { + return action_type; +} + +void OpenXRAction::set_toplevel_paths(const PackedStringArray p_toplevel_paths) { + toplevel_paths = p_toplevel_paths; +} + +PackedStringArray OpenXRAction::get_toplevel_paths() const { + return toplevel_paths; +} + +void OpenXRAction::parse_toplevel_paths(const String p_toplevel_paths) { + toplevel_paths = p_toplevel_paths.split(",", false); +} diff --git a/modules/openxr/action_map/openxr_action.h b/modules/openxr/action_map/openxr_action.h new file mode 100644 index 0000000000..e2cfe79e64 --- /dev/null +++ b/modules/openxr/action_map/openxr_action.h @@ -0,0 +1,74 @@ +/*************************************************************************/ +/* openxr_action.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef OPENXR_ACTION_H +#define OPENXR_ACTION_H + +#include "core/io/resource.h" + +class OpenXRAction : public Resource { + GDCLASS(OpenXRAction, Resource); + +public: + enum ActionType { + OPENXR_ACTION_BOOL, + OPENXR_ACTION_FLOAT, + OPENXR_ACTION_VECTOR2, + OPENXR_ACTION_POSE, + OPENXR_ACTION_HAPTIC, + }; + +private: + String localized_name; + ActionType action_type = OPENXR_ACTION_FLOAT; + + PackedStringArray toplevel_paths; + +protected: + static void _bind_methods(); + +public: + static Ref<OpenXRAction> new_action(const char *p_name, const char *p_localized_name, const ActionType p_action_type, const char *p_toplevel_paths); + + void set_localized_name(const String p_localized_name); + String get_localized_name() const; + + void set_action_type(const ActionType p_action_type); + ActionType get_action_type() const; + + void set_toplevel_paths(const PackedStringArray p_toplevel_paths); + PackedStringArray get_toplevel_paths() const; + + void parse_toplevel_paths(const String p_toplevel_paths); +}; + +VARIANT_ENUM_CAST(OpenXRAction::ActionType); + +#endif // !OPENXR_ACTION_H diff --git a/modules/openxr/action_map/openxr_action_map.cpp b/modules/openxr/action_map/openxr_action_map.cpp new file mode 100644 index 0000000000..5391f9569a --- /dev/null +++ b/modules/openxr/action_map/openxr_action_map.cpp @@ -0,0 +1,261 @@ +/*************************************************************************/ +/* openxr_action_map.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "openxr_action_map.h" + +void OpenXRActionMap::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_action_sets", "action_sets"), &OpenXRActionMap::set_action_sets); + ClassDB::bind_method(D_METHOD("get_action_sets"), &OpenXRActionMap::get_action_sets); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "action_sets", PROPERTY_HINT_RESOURCE_TYPE, "OpenXRActionSet", PROPERTY_USAGE_NO_EDITOR), "set_action_sets", "get_action_sets"); + + ClassDB::bind_method(D_METHOD("add_action_set", "action_set"), &OpenXRActionMap::add_action_set); + ClassDB::bind_method(D_METHOD("remove_action_set", "action_set"), &OpenXRActionMap::remove_action_set); + + ClassDB::bind_method(D_METHOD("set_interaction_profiles", "interaction_profiles"), &OpenXRActionMap::set_interaction_profiles); + ClassDB::bind_method(D_METHOD("get_interaction_profiles"), &OpenXRActionMap::get_interaction_profiles); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "interaction_profiles", PROPERTY_HINT_RESOURCE_TYPE, "OpenXRInteractionProfile", PROPERTY_USAGE_NO_EDITOR), "set_interaction_profiles", "get_interaction_profiles"); + + ClassDB::bind_method(D_METHOD("add_interaction_profile", "interaction_profile"), &OpenXRActionMap::add_interaction_profile); + ClassDB::bind_method(D_METHOD("remove_interaction_profile", "interaction_profile"), &OpenXRActionMap::remove_interaction_profile); + + ClassDB::bind_method(D_METHOD("create_default_action_sets"), &OpenXRActionMap::create_default_action_sets); +} + +void OpenXRActionMap::set_action_sets(Array p_action_sets) { + action_sets = p_action_sets; +} + +Array OpenXRActionMap::get_action_sets() const { + return action_sets; +} + +void OpenXRActionMap::add_action_set(Ref<OpenXRActionSet> p_action_set) { + ERR_FAIL_COND(p_action_set.is_null()); + + if (action_sets.find(p_action_set) == -1) { + action_sets.push_back(p_action_set); + } +} + +void OpenXRActionMap::remove_action_set(Ref<OpenXRActionSet> p_action_set) { + int idx = action_sets.find(p_action_set); + if (idx != -1) { + action_sets.remove_at(idx); + } +} + +void OpenXRActionMap::set_interaction_profiles(Array p_interaction_profiles) { + interaction_profiles = p_interaction_profiles; +} + +Array OpenXRActionMap::get_interaction_profiles() const { + return interaction_profiles; +} + +void OpenXRActionMap::add_interaction_profile(Ref<OpenXRInteractionProfile> p_interaction_profile) { + ERR_FAIL_COND(p_interaction_profile.is_null()); + + if (interaction_profiles.find(p_interaction_profile) == -1) { + interaction_profiles.push_back(p_interaction_profile); + } +} + +void OpenXRActionMap::remove_interaction_profile(Ref<OpenXRInteractionProfile> p_interaction_profile) { + int idx = interaction_profiles.find(p_interaction_profile); + if (idx != -1) { + interaction_profiles.remove_at(idx); + } +} + +void OpenXRActionMap::create_default_action_sets() { + // Note, if you make changes here make sure to delete your default_action_map.tres file of it will load an old version. + + // Create our Godot action set + Ref<OpenXRActionSet> action_set = OpenXRActionSet::new_action_set("godot", "Godot action set"); + add_action_set(action_set); + + // Create our actions + Ref<OpenXRAction> trigger = action_set->add_new_action("trigger", "Trigger", OpenXRAction::OPENXR_ACTION_FLOAT, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> trigger_click = action_set->add_new_action("trigger_click", "Trigger click", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> trigger_touch = action_set->add_new_action("trigger_touch", "Trigger touching", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> grip = action_set->add_new_action("grip", "Grip", OpenXRAction::OPENXR_ACTION_FLOAT, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> grip_click = action_set->add_new_action("grip_click", "Grip click", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> grip_touch = action_set->add_new_action("grip_touch", "Grip touching", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> primary = action_set->add_new_action("primary", "Primary joystick/thumbstick/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> primary_click = action_set->add_new_action("primary_click", "Primary joystick/thumbstick/trackpad click", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> primary_touch = action_set->add_new_action("primary_touch", "Primary joystick/thumbstick/trackpad touching", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> secondary = action_set->add_new_action("secondary", "Secondary joystick/thumbstick/trackpad", OpenXRAction::OPENXR_ACTION_VECTOR2, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> secondary_click = action_set->add_new_action("secondary_click", "Secondary joystick/thumbstick/trackpad click", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> secondary_touch = action_set->add_new_action("secondary_touch", "Secondary joystick/thumbstick/trackpad touching", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> menu_button = action_set->add_new_action("menu_button", "Menu button", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> select_button = action_set->add_new_action("select_button", "Select button", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> ax_button = action_set->add_new_action("ax_button", "A/X button", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> ax_touch = action_set->add_new_action("ax_touch", "A/X touching", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> by_button = action_set->add_new_action("by_button", "B/Y button", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> by_touch = action_set->add_new_action("by_touch", "B/Y touching", OpenXRAction::OPENXR_ACTION_BOOL, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> default_pose = action_set->add_new_action("default_pose", "Default pose", OpenXRAction::OPENXR_ACTION_POSE, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> aim_pose = action_set->add_new_action("aim_pose", "Aim pose", OpenXRAction::OPENXR_ACTION_POSE, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> grip_pose = action_set->add_new_action("grip_pose", "Grip pose", OpenXRAction::OPENXR_ACTION_POSE, "/user/hand/left,/user/hand/right"); + Ref<OpenXRAction> haptic = action_set->add_new_action("haptic", "Haptic", OpenXRAction::OPENXR_ACTION_HAPTIC, "/user/hand/left,/user/hand/right"); + + // Create our interaction profiles + Ref<OpenXRInteractionProfile> profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/khr/simple_controller"); + profile->add_new_binding(default_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose"); + profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + profile->add_new_binding(menu_button, "/user/hand/left/input/menu/click,/user/hand/right/input/menu/click"); + profile->add_new_binding(select_button, "/user/hand/left/input/select/click,/user/hand/right/input/select/click"); + // generic has no support for triggers, grip, A/B buttons, nor joystick/trackpad inputs + profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic"); + add_interaction_profile(profile); + + // Create our Vive controller profile + profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/htc/vive_controller"); + profile->add_new_binding(default_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose"); + profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + profile->add_new_binding(menu_button, "/user/hand/left/input/menu/click,/user/hand/right/input/menu/click"); + profile->add_new_binding(select_button, "/user/hand/left/input/system/click,/user/hand/right/input/system/click"); + // wmr controller has no a/b/x/y buttons + profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); + profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/click,/user/hand/right/input/trigger/click"); + profile->add_new_binding(grip, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click"); // OpenXR will convert bool to float + profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click"); + // primary on our vive controller is our trackpad + profile->add_new_binding(primary, "/user/hand/left/input/trackpad,/user/hand/right/input/trackpad"); + profile->add_new_binding(primary_click, "/user/hand/left/input/trackpad/click,/user/hand/right/input/trackpad/click"); + profile->add_new_binding(primary_touch, "/user/hand/left/input/trackpad/touch,/user/hand/right/input/trackpad/touch"); + // vive controllers have no secondary input + profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic"); + add_interaction_profile(profile); + + // Create our WMR controller profile + profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/microsoft/motion_controller"); + profile->add_new_binding(default_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose"); + profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + // wmr controllers have no select button we can use + profile->add_new_binding(menu_button, "/user/hand/left/input/menu/click,/user/hand/right/input/menu/click"); + // wmr controller has no a/b/x/y buttons + profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); + profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); // OpenXR will conver float to bool + profile->add_new_binding(grip, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click"); // OpenXR will convert bool to float + profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/click,/user/hand/right/input/squeeze/click"); + // primary on our wmr controller is our thumbstick, no touch + profile->add_new_binding(primary, "/user/hand/left/input/thumbstick,/user/hand/right/input/thumbstick"); + profile->add_new_binding(primary_click, "/user/hand/left/input/thumbstick/click,/user/hand/right/input/thumbstick/click"); + // secondary on our wmr controller is our trackpad + profile->add_new_binding(secondary, "/user/hand/left/input/trackpad,/user/hand/right/input/trackpad"); + profile->add_new_binding(secondary_click, "/user/hand/left/input/trackpad/click,/user/hand/right/input/trackpad/click"); + profile->add_new_binding(secondary_touch, "/user/hand/left/input/trackpad/touch,/user/hand/right/input/trackpad/touch"); + profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic"); + add_interaction_profile(profile); + + // Create our HP MR controller profile + profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/hp/mixed_reality_controller"); + profile->add_new_binding(default_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose"); + profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + // hpmr controllers have no select button we can use + profile->add_new_binding(menu_button, "/user/hand/left/input/menu/click,/user/hand/right/input/menu/click"); + // hpmr controllers only register click, not touch, on our a/b/x/y buttons + profile->add_new_binding(ax_button, "/user/hand/left/input/x/click,/user/hand/right/input/a/click"); // x on left hand, a on right hand + profile->add_new_binding(by_button, "/user/hand/left/input/y/click,/user/hand/right/input/b/click"); // y on left hand, b on right hand + profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); + profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); + profile->add_new_binding(grip, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value"); + profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value"); + // primary on our hpmr controller is our thumbstick + profile->add_new_binding(primary, "/user/hand/left/input/thumbstick,/user/hand/right/input/thumbstick"); + profile->add_new_binding(primary_click, "/user/hand/left/input/thumbstick/click,/user/hand/right/input/thumbstick/click"); + // No secondary on our hpmr controller + profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic"); + add_interaction_profile(profile); + + // Create our Meta touch controller profile + profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/oculus/touch_controller"); + profile->add_new_binding(default_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose"); + profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + // touch controllers have no select button we can use + profile->add_new_binding(menu_button, "/user/hand/left/input/menu/click,/user/hand/right/input/system/click"); // right hand system click may not be available + profile->add_new_binding(ax_button, "/user/hand/left/input/x/click,/user/hand/right/input/a/click"); // x on left hand, a on right hand + profile->add_new_binding(ax_touch, "/user/hand/left/input/x/touch,/user/hand/right/input/a/touch"); + profile->add_new_binding(by_button, "/user/hand/left/input/y/click,/user/hand/right/input/b/click"); // y on left hand, b on right hand + profile->add_new_binding(by_touch, "/user/hand/left/input/y/touch,/user/hand/right/input/b/touch"); + profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); + profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); // should be converted to boolean + profile->add_new_binding(trigger_touch, "/user/hand/left/input/trigger/touch,/user/hand/right/input/trigger/touch"); + profile->add_new_binding(grip, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value"); // should be converted to boolean + profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value"); + // primary on our touch controller is our thumbstick + profile->add_new_binding(primary, "/user/hand/left/input/thumbstick,/user/hand/right/input/thumbstick"); + profile->add_new_binding(primary_click, "/user/hand/left/input/thumbstick/click,/user/hand/right/input/thumbstick/click"); + profile->add_new_binding(primary_touch, "/user/hand/left/input/thumbstick/touch,/user/hand/right/input/thumbstick/touch"); + // touch controller has no secondary input + profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic"); + add_interaction_profile(profile); + + // Create our Valve index controller profile + profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/valve/index_controller"); + profile->add_new_binding(default_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + profile->add_new_binding(aim_pose, "/user/hand/left/input/aim/pose,/user/hand/right/input/aim/pose"); + profile->add_new_binding(grip_pose, "/user/hand/left/input/grip/pose,/user/hand/right/input/grip/pose"); + // index controllers have no select button we can use + profile->add_new_binding(menu_button, "/user/hand/left/input/system/click,/user/hand/right/input/system/click"); + profile->add_new_binding(ax_button, "/user/hand/left/input/a/click,/user/hand/right/input/a/click"); // a on both controllers + profile->add_new_binding(ax_touch, "/user/hand/left/input/a/touch,/user/hand/right/input/a/touch"); + profile->add_new_binding(by_button, "/user/hand/left/input/b/click,/user/hand/right/input/b/click"); // b on both controllers + profile->add_new_binding(by_touch, "/user/hand/left/input/b/touch,/user/hand/right/input/b/touch"); + profile->add_new_binding(trigger, "/user/hand/left/input/trigger/value,/user/hand/right/input/trigger/value"); + profile->add_new_binding(trigger_click, "/user/hand/left/input/trigger/click,/user/hand/right/input/trigger/click"); + profile->add_new_binding(trigger_touch, "/user/hand/left/input/trigger/touch,/user/hand/right/input/trigger/touch"); + profile->add_new_binding(grip, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value"); + profile->add_new_binding(grip_click, "/user/hand/left/input/squeeze/value,/user/hand/right/input/squeeze/value"); // this should do a float to bool conversion + // primary on our index controller is our thumbstick + profile->add_new_binding(primary, "/user/hand/left/input/thumbstick,/user/hand/right/input/thumbstick"); + profile->add_new_binding(primary_click, "/user/hand/left/input/thumbstick/click,/user/hand/right/input/thumbstick/click"); + profile->add_new_binding(primary_touch, "/user/hand/left/input/thumbstick/touch,/user/hand/right/input/thumbstick/touch"); + // secondary on our index controller is our trackpad + profile->add_new_binding(secondary, "/user/hand/left/input/trackpad,/user/hand/right/input/trackpad"); + profile->add_new_binding(secondary_click, "/user/hand/left/input/trackpad/force,/user/hand/right/input/trackpad/force"); // not sure if this will work but doesn't seem to support click... + profile->add_new_binding(secondary_touch, "/user/hand/left/input/trackpad/touch,/user/hand/right/input/trackpad/touch"); + profile->add_new_binding(haptic, "/user/hand/left/output/haptic,/user/hand/right/output/haptic"); + add_interaction_profile(profile); +} + +void OpenXRActionMap::create_editor_action_sets() { + // TODO implement +} + +OpenXRActionMap::~OpenXRActionMap() { + action_sets.clear(); + interaction_profiles.clear(); +} diff --git a/modules/openxr/action_map/openxr_action_map.h b/modules/openxr/action_map/openxr_action_map.h new file mode 100644 index 0000000000..866e170468 --- /dev/null +++ b/modules/openxr/action_map/openxr_action_map.h @@ -0,0 +1,68 @@ +/*************************************************************************/ +/* openxr_action_map.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef OPENXR_ACTION_SETS_H +#define OPENXR_ACTION_SETS_H + +#include "core/io/resource.h" + +#include "openxr_action_set.h" +#include "openxr_interaction_profile.h" + +class OpenXRActionMap : public Resource { + GDCLASS(OpenXRActionMap, Resource); + +private: + Array action_sets; + Array interaction_profiles; + +protected: + static void _bind_methods(); + +public: + void set_action_sets(Array p_action_sets); + Array get_action_sets() const; + + void add_action_set(Ref<OpenXRActionSet> p_action_set); + void remove_action_set(Ref<OpenXRActionSet> p_action_set); + + void set_interaction_profiles(Array p_interaction_profiles); + Array get_interaction_profiles() const; + + void add_interaction_profile(Ref<OpenXRInteractionProfile> p_interaction_profile); + void remove_interaction_profile(Ref<OpenXRInteractionProfile> p_interaction_profile); + + void create_default_action_sets(); + void create_editor_action_sets(); + + ~OpenXRActionMap(); +}; + +#endif // !OPENXR_ACTION_SETS_H diff --git a/modules/openxr/action_map/openxr_action_set.cpp b/modules/openxr/action_map/openxr_action_set.cpp new file mode 100644 index 0000000000..465a709b60 --- /dev/null +++ b/modules/openxr/action_map/openxr_action_set.cpp @@ -0,0 +1,111 @@ +/*************************************************************************/ +/* openxr_action_set.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "openxr_action_set.h" + +void OpenXRActionSet::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_localized_name", "localized_name"), &OpenXRActionSet::set_localized_name); + ClassDB::bind_method(D_METHOD("get_localized_name"), &OpenXRActionSet::get_localized_name); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "localized_name"), "set_localized_name", "get_localized_name"); + + ClassDB::bind_method(D_METHOD("set_priority", "priority"), &OpenXRActionSet::set_priority); + ClassDB::bind_method(D_METHOD("get_priority"), &OpenXRActionSet::get_priority); + ADD_PROPERTY(PropertyInfo(Variant::INT, "priority"), "set_priority", "get_priority"); + + ClassDB::bind_method(D_METHOD("set_actions", "actions"), &OpenXRActionSet::set_actions); + ClassDB::bind_method(D_METHOD("get_actions"), &OpenXRActionSet::get_actions); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "actions", PROPERTY_HINT_RESOURCE_TYPE, "OpenXRAction", PROPERTY_USAGE_NO_EDITOR), "set_actions", "get_actions"); + + ClassDB::bind_method(D_METHOD("add_action", "action"), &OpenXRActionSet::add_action); + ClassDB::bind_method(D_METHOD("remove_action", "action"), &OpenXRActionSet::remove_action); +} + +Ref<OpenXRActionSet> OpenXRActionSet::new_action_set(const char *p_name, const char *p_localized_name, const int p_priority) { + // This is a helper function to help build our default action sets + + Ref<OpenXRActionSet> action_set; + action_set.instantiate(); + action_set->set_name(String(p_name)); + action_set->set_localized_name(p_localized_name); + action_set->set_priority(p_priority); + + return action_set; +} + +void OpenXRActionSet::set_localized_name(const String p_localized_name) { + localized_name = p_localized_name; +} + +String OpenXRActionSet::get_localized_name() const { + return localized_name; +} + +void OpenXRActionSet::set_priority(const int p_priority) { + priority = p_priority; +} + +int OpenXRActionSet::get_priority() const { + return priority; +} + +void OpenXRActionSet::set_actions(Array p_actions) { + actions = p_actions; +} + +Array OpenXRActionSet::get_actions() const { + return actions; +} + +void OpenXRActionSet::add_action(Ref<OpenXRAction> p_action) { + ERR_FAIL_COND(p_action.is_null()); + + if (actions.find(p_action) == -1) { + actions.push_back(p_action); + } +} + +void OpenXRActionSet::remove_action(Ref<OpenXRAction> p_action) { + int idx = actions.find(p_action); + if (idx != -1) { + actions.remove_at(idx); + } +} + +Ref<OpenXRAction> OpenXRActionSet::add_new_action(const char *p_name, const char *p_localized_name, const OpenXRAction::ActionType p_action_type, const char *p_toplevel_paths) { + // This is a helper function to help build our default action sets + + Ref<OpenXRAction> new_action = OpenXRAction::new_action(p_name, p_localized_name, p_action_type, p_toplevel_paths); + add_action(new_action); + return new_action; +} + +OpenXRActionSet::~OpenXRActionSet() { + actions.clear(); +} diff --git a/modules/openxr/action_map/openxr_action_set.h b/modules/openxr/action_map/openxr_action_set.h new file mode 100644 index 0000000000..012a088b1c --- /dev/null +++ b/modules/openxr/action_map/openxr_action_set.h @@ -0,0 +1,70 @@ +/*************************************************************************/ +/* openxr_action_set.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef OPENXR_ACTION_SET_H +#define OPENXR_ACTION_SET_H + +#include "core/io/resource.h" + +#include "openxr_action.h" + +class OpenXRActionSet : public Resource { + GDCLASS(OpenXRActionSet, Resource); + +private: + String localized_name; + int priority = 0; + + Array actions; + +protected: + static void _bind_methods(); + +public: + static Ref<OpenXRActionSet> new_action_set(const char *p_name, const char *p_localized_name, const int p_priority = 0); + + void set_localized_name(const String p_localized_name); + String get_localized_name() const; + + void set_priority(const int p_priority); + int get_priority() const; + + void set_actions(Array p_actions); + Array get_actions() const; + + void add_action(Ref<OpenXRAction> p_action); + void remove_action(Ref<OpenXRAction> p_action); + + Ref<OpenXRAction> add_new_action(const char *p_name, const char *p_localized_name, const OpenXRAction::ActionType p_action_type, const char *p_toplevel_paths); + + ~OpenXRActionSet(); +}; + +#endif // !OPENXR_ACTION_SET_H diff --git a/modules/openxr/action_map/openxr_interaction_profile.cpp b/modules/openxr/action_map/openxr_interaction_profile.cpp new file mode 100644 index 0000000000..bc33814f17 --- /dev/null +++ b/modules/openxr/action_map/openxr_interaction_profile.cpp @@ -0,0 +1,136 @@ +/*************************************************************************/ +/* openxr_interaction_profile.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "openxr_interaction_profile.h" + +void OpenXRIPBinding::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_action", "action"), &OpenXRIPBinding::set_action); + ClassDB::bind_method(D_METHOD("get_action"), &OpenXRIPBinding::get_action); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "action", PROPERTY_HINT_RESOURCE_TYPE, "OpenXRAction"), "set_action", "get_action"); + + ClassDB::bind_method(D_METHOD("set_paths", "paths"), &OpenXRIPBinding::set_paths); + ClassDB::bind_method(D_METHOD("get_paths"), &OpenXRIPBinding::get_paths); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "paths", PROPERTY_HINT_ARRAY_TYPE, "STRING"), "set_paths", "get_paths"); +} + +Ref<OpenXRIPBinding> OpenXRIPBinding::new_binding(const Ref<OpenXRAction> p_action, const char *p_paths) { + // This is a helper function to help build our default action sets + + Ref<OpenXRIPBinding> binding; + binding.instantiate(); + binding->set_action(p_action); + binding->parse_paths(String(p_paths)); + + return binding; +} + +void OpenXRIPBinding::set_action(const Ref<OpenXRAction> p_action) { + action = p_action; +} + +Ref<OpenXRAction> OpenXRIPBinding::get_action() const { + return action; +} + +void OpenXRIPBinding::set_paths(const PackedStringArray p_paths) { + paths = p_paths; +} + +PackedStringArray OpenXRIPBinding::get_paths() const { + return paths; +} + +void OpenXRIPBinding::parse_paths(const String p_paths) { + paths = p_paths.split(",", false); +} + +OpenXRIPBinding::~OpenXRIPBinding() { + action.unref(); +} + +void OpenXRInteractionProfile::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_interaction_profile_path", "interaction_profile_path"), &OpenXRInteractionProfile::set_interaction_profile_path); + ClassDB::bind_method(D_METHOD("get_interaction_profile_path"), &OpenXRInteractionProfile::get_interaction_profile_path); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "interaction_profile_path"), "set_interaction_profile_path", "get_interaction_profile_path"); + + ClassDB::bind_method(D_METHOD("set_bindings", "bindings"), &OpenXRInteractionProfile::set_bindings); + ClassDB::bind_method(D_METHOD("get_bindings"), &OpenXRInteractionProfile::get_bindings); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "bindings", PROPERTY_HINT_RESOURCE_TYPE, "OpenXRIPBinding", PROPERTY_USAGE_NO_EDITOR), "set_bindings", "get_bindings"); +} + +Ref<OpenXRInteractionProfile> OpenXRInteractionProfile::new_profile(const char *p_input_profile_path) { + Ref<OpenXRInteractionProfile> profile; + profile.instantiate(); + profile->set_interaction_profile_path(String(p_input_profile_path)); + + return profile; +} + +void OpenXRInteractionProfile::set_interaction_profile_path(const String p_input_profile_path) { + interaction_profile_path = p_input_profile_path; +} + +String OpenXRInteractionProfile::get_interaction_profile_path() const { + return interaction_profile_path; +} + +void OpenXRInteractionProfile::set_bindings(Array p_bindings) { + bindings = p_bindings; +} + +Array OpenXRInteractionProfile::get_bindings() const { + return bindings; +} + +void OpenXRInteractionProfile::add_binding(Ref<OpenXRIPBinding> p_binding) { + ERR_FAIL_COND(p_binding.is_null()); + + if (bindings.find(p_binding) == -1) { + bindings.push_back(p_binding); + } +} + +void OpenXRInteractionProfile::remove_binding(Ref<OpenXRIPBinding> p_binding) { + int idx = bindings.find(p_binding); + if (idx != -1) { + bindings.remove_at(idx); + } +} + +void OpenXRInteractionProfile::add_new_binding(const Ref<OpenXRAction> p_action, const char *p_paths) { + // This is a helper function to help build our default action sets + + Ref<OpenXRIPBinding> binding = OpenXRIPBinding::new_binding(p_action, p_paths); + add_binding(binding); +} + +OpenXRInteractionProfile::~OpenXRInteractionProfile() { + bindings.clear(); +} diff --git a/modules/openxr/action_map/openxr_interaction_profile.h b/modules/openxr/action_map/openxr_interaction_profile.h new file mode 100644 index 0000000000..abbc429e7d --- /dev/null +++ b/modules/openxr/action_map/openxr_interaction_profile.h @@ -0,0 +1,89 @@ +/*************************************************************************/ +/* openxr_interaction_profile.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef OPENXR_INTERACTION_PROFILE_H +#define OPENXR_INTERACTION_PROFILE_H + +#include "core/io/resource.h" + +#include "openxr_action.h" + +class OpenXRIPBinding : public Resource { + GDCLASS(OpenXRIPBinding, Resource); + +private: + Ref<OpenXRAction> action; + PackedStringArray paths; + +protected: + static void _bind_methods(); + +public: + static Ref<OpenXRIPBinding> new_binding(const Ref<OpenXRAction> p_action, const char *p_paths); + + void set_action(const Ref<OpenXRAction> p_action); + Ref<OpenXRAction> get_action() const; + + void set_paths(const PackedStringArray p_paths); + PackedStringArray get_paths() const; + + void parse_paths(const String p_paths); + + ~OpenXRIPBinding(); +}; + +class OpenXRInteractionProfile : public Resource { + GDCLASS(OpenXRInteractionProfile, Resource); + +private: + String interaction_profile_path; + Array bindings; + +protected: + static void _bind_methods(); + +public: + static Ref<OpenXRInteractionProfile> new_profile(const char *p_input_profile_path); + + void set_interaction_profile_path(const String p_input_profile_path); + String get_interaction_profile_path() const; + + void set_bindings(Array p_bindings); + Array get_bindings() const; + + void add_binding(Ref<OpenXRIPBinding> p_binding); + void remove_binding(Ref<OpenXRIPBinding> p_binding); + + void add_new_binding(const Ref<OpenXRAction> p_action, const char *p_paths); + + ~OpenXRInteractionProfile(); +}; + +#endif // !OPENXR_INTERACTION_PROFILE_H diff --git a/modules/openxr/config.py b/modules/openxr/config.py new file mode 100644 index 0000000000..f91cb1359f --- /dev/null +++ b/modules/openxr/config.py @@ -0,0 +1,27 @@ +def can_build(env, platform): + if ( + platform == "linuxbsd" or platform == "windows" + ): # or platform == "android" -- temporarily disabled android support + return env["openxr"] + else: + # not supported on these platforms + return False + + +def configure(env): + pass + + +def get_doc_classes(): + return [ + "OpenXRInterface", + "OpenXRAction", + "OpenXRActionSet", + "OpenXRActionMap", + "OpenXRInteractionProfile", + "OpenXRIPBinding", + ] + + +def get_doc_path(): + return "doc_classes" diff --git a/modules/openxr/doc_classes/OpenXRAction.xml b/modules/openxr/doc_classes/OpenXRAction.xml new file mode 100644 index 0000000000..6ff8c1ad26 --- /dev/null +++ b/modules/openxr/doc_classes/OpenXRAction.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="OpenXRAction" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> + <brief_description> + An OpenXR action. + </brief_description> + <description> + This resource defines an OpenXR action. Actions can be used both for inputs (buttons/joystick/trigger/etc) and outputs (haptics). + OpenXR performs automatic conversion between action type and input type whenever possible. An analogue trigger bound to a boolean action will thus return [code]false[/core] if the trigger is depressed and [code]true[/code] if pressed fully. + Actions are not directly bound to specific devices, instead OpenXR recognises a limited number of top level paths that identify devices by usage. We can restrict which devices an action can be bound to by these top level paths. For instance an action that should only be used for hand held controllers can have the top level paths "/user/hand/left" and "/user/hand/right" associated with them. See the [url=https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#semantic-path-reserved]reserved path section in the OpenXR specification[/url] for more info on the top level paths. + Note that the name of the resource is used to register the action with. + </description> + <tutorials> + </tutorials> + <members> + <member name="action_type" type="int" setter="set_action_type" getter="get_action_type" enum="OpenXRAction.ActionType" default="1"> + The type of action. + </member> + <member name="localized_name" type="String" setter="set_localized_name" getter="get_localized_name" default=""""> + The localised description of this action. + </member> + <member name="toplevel_paths" type="PackedStringArray" setter="set_toplevel_paths" getter="get_toplevel_paths" default="PackedStringArray()"> + A collections of toplevel paths to which this action can be bound. + </member> + </members> + <constants> + <constant name="OPENXR_ACTION_BOOL" value="0" enum="ActionType"> + This action provides a boolean value. + </constant> + <constant name="OPENXR_ACTION_FLOAT" value="1" enum="ActionType"> + This action provides a float value between [code]0.0[/code] and [code]1.0[/code] for any analogue input such as triggers. + </constant> + <constant name="OPENXR_ACTION_VECTOR2" value="2" enum="ActionType"> + This action provides a vector2 value and can be bound to embedded trackpads and joysticks + </constant> + <constant name="OPENXR_ACTION_POSE" value="3" enum="ActionType"> + </constant> + </constants> +</class> diff --git a/modules/openxr/doc_classes/OpenXRActionMap.xml b/modules/openxr/doc_classes/OpenXRActionMap.xml new file mode 100644 index 0000000000..f1def8aad8 --- /dev/null +++ b/modules/openxr/doc_classes/OpenXRActionMap.xml @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="OpenXRActionMap" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> + <brief_description> + Collection of [OpenXRActionSet] and [OpenXRInteractionProfile] resources for the OpenXR module. + </brief_description> + <description> + OpenXR uses an action system similar to Godots Input map system to bind inputs and outputs on various types of XR controllers to named actions. OpenXR specifies more detail on these inputs and outputs than Godot supports. + Another important distinction is that OpenXR offers no control over these bindings. The bindings we register are suggestions, it is up to the XR runtime to offer users the ability to change these bindings. This allows the XR runtime to fill in the gaps if new hardware becomes available. + The action map therefor needs to be loaded at startup and can't be changed afterwards. This resource is a container for the entire action map. + </description> + <tutorials> + </tutorials> + <methods> + <method name="add_action_set"> + <return type="void" /> + <argument index="0" name="action_set" type="OpenXRActionSet" /> + <description> + Add an action set. + </description> + </method> + <method name="add_interaction_profile"> + <return type="void" /> + <argument index="0" name="interaction_profile" type="OpenXRInteractionProfile" /> + <description> + Add an interaction profile. + </description> + </method> + <method name="create_default_action_sets"> + <return type="void" /> + <description> + Setup this action set with our default actions. + </description> + </method> + <method name="remove_action_set"> + <return type="void" /> + <argument index="0" name="action_set" type="OpenXRActionSet" /> + <description> + Remove an action set. + </description> + </method> + <method name="remove_interaction_profile"> + <return type="void" /> + <argument index="0" name="interaction_profile" type="OpenXRInteractionProfile" /> + <description> + Remove an interaction profile. + </description> + </method> + </methods> + <members> + <member name="action_sets" type="Array" setter="set_action_sets" getter="get_action_sets" default="[]"> + </member> + <member name="interaction_profiles" type="Array" setter="set_interaction_profiles" getter="get_interaction_profiles" default="[]"> + </member> + </members> +</class> diff --git a/modules/openxr/doc_classes/OpenXRActionSet.xml b/modules/openxr/doc_classes/OpenXRActionSet.xml new file mode 100644 index 0000000000..5a87de463e --- /dev/null +++ b/modules/openxr/doc_classes/OpenXRActionSet.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="OpenXRActionSet" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> + <brief_description> + Collection of [OpenXRAction] resources that make up an action set. + </brief_description> + <description> + Action sets in OpenXR define a collection of actions that can be activated in unison. This allows games to easily change between different states that require different inputs or need to reinterpret inputs. For instance we could have an action set that is active when a menu is open, an action set that is active when the player is freely walking around and an action set that is active when the player is controlling a vehicle. + Action sets can contain the same actions, or actions with the same name, if such action sets are active at the same time the action set with the highest priority defines which binding is active. + Note that the name of the resource is used to identify the action set within OpenXR. + </description> + <tutorials> + </tutorials> + <methods> + <method name="add_action"> + <return type="void" /> + <argument index="0" name="action" type="OpenXRAction" /> + <description> + Add an action to this action set. + </description> + </method> + <method name="remove_action"> + <return type="void" /> + <argument index="0" name="action" type="OpenXRAction" /> + <description> + Remove an action from this action set. + </description> + </method> + </methods> + <members> + <member name="actions" type="Array" setter="set_actions" getter="get_actions" default="[]"> + Collection of actions for this action set. + </member> + <member name="localized_name" type="String" setter="set_localized_name" getter="get_localized_name" default=""""> + The localised name of this action set. + </member> + <member name="priority" type="int" setter="set_priority" getter="get_priority" default="0"> + The priority for this action set. + </member> + </members> +</class> diff --git a/modules/openxr/doc_classes/OpenXRIPBinding.xml b/modules/openxr/doc_classes/OpenXRIPBinding.xml new file mode 100644 index 0000000000..3fdcde5eb5 --- /dev/null +++ b/modules/openxr/doc_classes/OpenXRIPBinding.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="OpenXRIPBinding" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> + <brief_description> + Defines a binding between an [OpenXRAction] and an XR input or output. + </brief_description> + <description> + This binding resource binds an OpenXR action to inputs or outputs. As most controllers have left hand and right versions that are handled by the same interaction profile we can specify multiple bindings. For instance an action "Fire" could be bound to both "/user/hand/left/input/trigger" and "/user/hand/right/input/trigger". + </description> + <tutorials> + </tutorials> + <members> + <member name="action" type="OpenXRAction" setter="set_action" getter="get_action"> + Action that is bound to these paths. + </member> + <member name="paths" type="PackedStringArray" setter="set_paths" getter="get_paths" default="PackedStringArray()"> + Paths that define the inputs or outputs bound on the device. + </member> + </members> +</class> diff --git a/modules/openxr/doc_classes/OpenXRInteractionProfile.xml b/modules/openxr/doc_classes/OpenXRInteractionProfile.xml new file mode 100644 index 0000000000..a8629caae4 --- /dev/null +++ b/modules/openxr/doc_classes/OpenXRInteractionProfile.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="OpenXRInteractionProfile" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> + <brief_description> + Suggested bindings object for OpenXR. + </brief_description> + <description> + This object stores suggested bindings for an interaction profile. Interaction profiles define the meta data for a tracked XR device such as an XR controller. + For more information see the [url=https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#semantic-path-interaction-profiles]interaction profiles info in the OpenXR specification[/url]. + </description> + <tutorials> + </tutorials> + <members> + <member name="bindings" type="Array" setter="set_bindings" getter="get_bindings" default="[]"> + Action bindings for this interaction profile. + </member> + <member name="interaction_profile_path" type="String" setter="set_interaction_profile_path" getter="get_interaction_profile_path" default=""""> + The interaction profile path identifying the XR device. + </member> + </members> +</class> diff --git a/modules/openxr/doc_classes/OpenXRInterface.xml b/modules/openxr/doc_classes/OpenXRInterface.xml new file mode 100644 index 0000000000..1160061e04 --- /dev/null +++ b/modules/openxr/doc_classes/OpenXRInterface.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="OpenXRInterface" inherits="XRInterface" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> + <brief_description> + Our OpenXR interface. + </brief_description> + <description> + The OpenXR interface allows Godot to interact with OpenXR runtimes and make it possible to create XR experiences and games. + Due to the needs of OpenXR this interface works slightly different then other plugin based XR interfaces. It needs to be initialised when Godot starts. You need to enable OpenXR, settings for this can be found in your games project settings under the XR heading. You do need to mark a viewport for use with XR in order for Godot to know which render result should be output to the headset. + </description> + <tutorials> + <link title="OpenXR documentation">$DOCS_URL/tutorials/vr/openxr/index.html</link> + </tutorials> +</class> diff --git a/modules/openxr/extensions/openxr_android_extension.cpp b/modules/openxr/extensions/openxr_android_extension.cpp new file mode 100644 index 0000000000..3bd4db169c --- /dev/null +++ b/modules/openxr/extensions/openxr_android_extension.cpp @@ -0,0 +1,71 @@ +/*************************************************************************/ +/* openxr_android_extension.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "openxr_android_extension.h" + +#include <openxr/openxr.h> +#include <openxr/openxr_platform.h> + +OpenXRAndroidExtension *OpenXRAndroidExtension::singleton = nullptr; + +OpenXRAndroidExtension *OpenXRAndroidExtension::get_singleton() { + return singleton; +} + +OpenXRAndroidExtension::OpenXRAndroidExtension(OpenXRAPI *p_openxr_api) : + OpenXRExtensionWrapper(p_openxr_api) { + singleton = this; + + request_extensions[XR_KHR_ANDROID_THREAD_SETTINGS_EXTENSION_NAME] = nullptr; // must be available + + // Initialize the loader + PFN_xrInitializeLoaderKHR xrInitializeLoaderKHR; + result = xrGetInstanceProcAddr(XR_NULL_HANDLE, "xrInitializeLoaderKHR", (PFN_xrVoidFunction *)(&xrInitializeLoaderKHR)); + ERR_FAIL_COND_MSG(XR_FAILED(result), "Failed to retrieve pointer to xrInitializeLoaderKHR"); + + // TODO fix this code, this is still code from GDNative! + JNIEnv *env = android_api->godot_android_get_env(); + JavaVM *vm; + env->GetJavaVM(&vm); + jobject activity_object = env->NewGlobalRef(android_api->godot_android_get_activity()); + + XrLoaderInitInfoAndroidKHR loader_init_info_android = { + .type = XR_TYPE_LOADER_INIT_INFO_ANDROID_KHR, + .next = nullptr, + .applicationVM = vm, + .applicationContext = activity_object + }; + xrInitializeLoaderKHR((const XrLoaderInitInfoBaseHeaderKHR *)&loader_init_info_android); + ERR_FAIL_COND_MSG(XR_FAILED(result), "Failed to call xrInitializeLoaderKHR"); +} + +OpenXRAndroidExtension::~OpenXRAndroidExtension() { + singleton = nullptr; +} diff --git a/modules/openxr/extensions/openxr_android_extension.h b/modules/openxr/extensions/openxr_android_extension.h new file mode 100644 index 0000000000..e102197a55 --- /dev/null +++ b/modules/openxr/extensions/openxr_android_extension.h @@ -0,0 +1,47 @@ +/*************************************************************************/ +/* openxr_android_extension.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef OPENXR_ANDROID_EXTENSION_H +#define OPENXR_ANDROID_EXTENSION_H + +#include "openxr_extension_wrapper.h" + +class OpenXRAndroidExtension : public OpenXRExtensionWrapper { +public: + static OpenXRAndroidExtension *get_singleton(); + + OpenXRAndroidExtension(OpenXRAPI *p_openxr_api); + virtual ~OpenXRAndroidExtension() override; + +private: + static OpenXRAndroidExtension *singleton; +}; + +#endif // !OPENXR_ANDROID_EXTENSION_H diff --git a/modules/openxr/extensions/openxr_composition_layer_provider.h b/modules/openxr/extensions/openxr_composition_layer_provider.h new file mode 100644 index 0000000000..019dffa2a8 --- /dev/null +++ b/modules/openxr/extensions/openxr_composition_layer_provider.h @@ -0,0 +1,45 @@ +/*************************************************************************/ +/* openxr_composition_layer_provider.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef OPENXR_COMPOSITION_LAYER_PROVIDER_H +#define OPENXR_COMPOSITION_LAYER_PROVIDER_H + +#include <openxr/openxr.h> + +// Interface for OpenXR extensions that provide a composition layer. +class OpenXRCompositionLayerProvider { +public: + // TODO changed to normal method definition for now + // CI complains until we implement this, haven't ported it yet from plugin + // virtual XrCompositionLayerBaseHeader *get_composition_layer() = 0; + XrCompositionLayerBaseHeader *get_composition_layer() { return nullptr; }; +}; + +#endif // OPENXR_COMPOSITION_LAYER_PROVIDER_H diff --git a/modules/openxr/extensions/openxr_extension_wrapper.h b/modules/openxr/extensions/openxr_extension_wrapper.h new file mode 100644 index 0000000000..5242ee6063 --- /dev/null +++ b/modules/openxr/extensions/openxr_extension_wrapper.h @@ -0,0 +1,106 @@ +/*************************************************************************/ +/* openxr_extension_wrapper.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef OPENXR_EXTENSION_WRAPPER_H +#define OPENXR_EXTENSION_WRAPPER_H + +#include "core/error/error_macros.h" +#include "core/math/camera_matrix.h" +#include "core/templates/map.h" +#include "core/templates/rid.h" + +#include "thirdparty/openxr/src/common/xr_linear.h" +#include <openxr/openxr.h> + +class OpenXRAPI; + +class OpenXRExtensionWrapper { +protected: + OpenXRAPI *openxr_api; + + // Store extension we require. + // If bool pointer is a nullptr this means this extension is mandatory and initialisation will fail if it is not available + // If bool pointer is set, value will be set to true or false depending on whether extension is available + Map<const char *, bool *> request_extensions; + +public: + virtual Map<const char *, bool *> get_request_extensions() { + return request_extensions; + } + + // These functions allow an extension to add entries to a struct chain. + // `p_next_pointer` points to the last struct that was created for this chain + // and should be used as the value for the `pNext` pointer in the first struct you add. + // You should return the pointer to the last struct you define as your result. + // If you are not adding any structs, just return `p_next_pointer`. + // See existing extensions for examples of this implementation. + virtual void *set_system_properties_and_get_next_pointer(void *p_next_pointer) { return p_next_pointer; } + virtual void *set_session_create_and_get_next_pointer(void *p_next_pointer) { return p_next_pointer; } + virtual void *set_swapchain_create_info_and_get_next_pointer(void *p_next_pointer) { return p_next_pointer; } + + virtual void on_instance_created(const XrInstance p_instance) {} + virtual void on_instance_destroyed() {} + virtual void on_session_created(const XrSession p_instance) {} + virtual void on_process() {} + virtual void on_pre_render() {} + virtual void on_session_destroyed() {} + + virtual void on_state_idle() {} + virtual void on_state_ready() {} + virtual void on_state_synchronized() {} + virtual void on_state_visible() {} + virtual void on_state_focused() {} + virtual void on_state_stopping() {} + virtual void on_state_loss_pending() {} + virtual void on_state_exiting() {} + + // Returns true if the event was handled, false otherwise. + virtual bool on_event_polled(const XrEventDataBuffer &event) { + return false; + } + + OpenXRExtensionWrapper(OpenXRAPI *p_openxr_api) { openxr_api = p_openxr_api; }; + virtual ~OpenXRExtensionWrapper() = default; +}; + +class OpenXRGraphicsExtensionWrapper : public OpenXRExtensionWrapper { +public: + virtual void get_usable_swapchain_formats(Vector<int64_t> &p_usable_swap_chains) = 0; + virtual String get_swapchain_format_name(int64_t p_swapchain_format) const = 0; + virtual bool get_swapchain_image_data(XrSwapchain p_swapchain, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, void **r_swapchain_graphics_data) = 0; + virtual void cleanup_swapchain_graphics_data(void **p_swapchain_graphics_data) = 0; + virtual bool create_projection_fov(const XrFovf p_fov, double p_z_near, double p_z_far, CameraMatrix &r_camera_matrix) = 0; + virtual bool copy_render_target_to_image(RID p_from_render_target, void *p_swapchain_graphics_data, int p_image_index) = 0; + + OpenXRGraphicsExtensionWrapper(OpenXRAPI *p_openxr_api) : + OpenXRExtensionWrapper(p_openxr_api){}; +}; + +#endif // ~OPENXR_EXTENSION_WRAPPER_H diff --git a/modules/openxr/extensions/openxr_vulkan_extension.cpp b/modules/openxr/extensions/openxr_vulkan_extension.cpp new file mode 100644 index 0000000000..ba790500f9 --- /dev/null +++ b/modules/openxr/extensions/openxr_vulkan_extension.cpp @@ -0,0 +1,695 @@ +/*************************************************************************/ +/* openxr_vulkan_extension.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "core/string/print_string.h" + +#include "modules/openxr/extensions/openxr_vulkan_extension.h" +#include "modules/openxr/openxr_api.h" +#include "modules/openxr/openxr_util.h" +#include "servers/rendering/renderer_rd/renderer_storage_rd.h" +#include "servers/rendering/rendering_server_globals.h" +#include "servers/rendering_server.h" + +// need to include Vulkan so we know of type definitions +#define XR_USE_GRAPHICS_API_VULKAN + +#ifdef WINDOWS_ENABLED +// Including windows.h here is absolutely evil, we shouldn't be doing this outside of platform +// however due to the way the openxr headers are put together, we have no choice. +#include <windows.h> +#endif + +// include platform dependent structs +#include <openxr/openxr_platform.h> + +PFN_xrGetVulkanGraphicsRequirements2KHR xrGetVulkanGraphicsRequirements2KHR_ptr = nullptr; +PFN_xrCreateVulkanInstanceKHR xrCreateVulkanInstanceKHR_ptr = nullptr; +PFN_xrGetVulkanGraphicsDevice2KHR xrGetVulkanGraphicsDevice2KHR_ptr = nullptr; +PFN_xrCreateVulkanDeviceKHR xrCreateVulkanDeviceKHR_ptr = nullptr; + +OpenXRVulkanExtension::OpenXRVulkanExtension(OpenXRAPI *p_openxr_api) : + OpenXRGraphicsExtensionWrapper(p_openxr_api) { + VulkanContext::set_vulkan_hooks(this); + + request_extensions[XR_KHR_VULKAN_ENABLE2_EXTENSION_NAME] = nullptr; // must be available + + ERR_FAIL_NULL(openxr_api); +} + +OpenXRVulkanExtension::~OpenXRVulkanExtension() { + VulkanContext::set_vulkan_hooks(nullptr); +} + +void OpenXRVulkanExtension::on_instance_created(const XrInstance p_instance) { + XrResult result; + + ERR_FAIL_NULL(openxr_api); + + // Obtain pointers to functions we're accessing here, they are (not yet) part of core. + result = xrGetInstanceProcAddr(p_instance, "xrGetVulkanGraphicsRequirements2KHR", (PFN_xrVoidFunction *)&xrGetVulkanGraphicsRequirements2KHR_ptr); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to xrGetVulkanGraphicsRequirements2KHR entry point [", openxr_api->get_error_string(result), "]"); + } + + result = xrGetInstanceProcAddr(p_instance, "xrCreateVulkanInstanceKHR", (PFN_xrVoidFunction *)&xrCreateVulkanInstanceKHR_ptr); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to xrCreateVulkanInstanceKHR entry point [", openxr_api->get_error_string(result), "]"); + } + + result = xrGetInstanceProcAddr(p_instance, "xrGetVulkanGraphicsDevice2KHR", (PFN_xrVoidFunction *)&xrGetVulkanGraphicsDevice2KHR_ptr); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to xrGetVulkanGraphicsDevice2KHR entry point [", openxr_api->get_error_string(result), "]"); + } + + result = xrGetInstanceProcAddr(p_instance, "xrCreateVulkanDeviceKHR", (PFN_xrVoidFunction *)&xrCreateVulkanDeviceKHR_ptr); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to xrCreateVulkanDeviceKHR entry point [", openxr_api->get_error_string(result), "]"); + } +} + +XrResult OpenXRVulkanExtension::xrGetVulkanGraphicsRequirements2KHR(XrInstance p_instance, XrSystemId p_system_id, XrGraphicsRequirementsVulkanKHR *p_graphics_requirements) { + ERR_FAIL_NULL_V(xrGetVulkanGraphicsRequirements2KHR_ptr, XR_ERROR_HANDLE_INVALID); + + return (*xrGetVulkanGraphicsRequirements2KHR_ptr)(p_instance, p_system_id, p_graphics_requirements); +} + +bool OpenXRVulkanExtension::check_graphics_api_support(XrVersion p_desired_version) { + ERR_FAIL_NULL_V(openxr_api, false); + + XrGraphicsRequirementsVulkan2KHR vulkan_requirements = { + XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN2_KHR, // type + nullptr, // next + 0, // minApiVersionSupported + 0 // maxApiVersionSupported + }; + + XrResult result = xrGetVulkanGraphicsRequirements2KHR(openxr_api->get_instance(), openxr_api->get_system_id(), &vulkan_requirements); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to get vulkan graphics requirements [", openxr_api->get_error_string(result), "]"); + return false; + } + + // #ifdef DEBUG + print_line("OpenXR: XrGraphicsRequirementsVulkan2KHR:"); + print_line(" - minApiVersionSupported: ", OpenXRUtil::make_xr_version_string(vulkan_requirements.minApiVersionSupported)); + print_line(" - maxApiVersionSupported: ", OpenXRUtil::make_xr_version_string(vulkan_requirements.maxApiVersionSupported)); + // #endif + + if (p_desired_version < vulkan_requirements.minApiVersionSupported) { + print_line("OpenXR: Requested Vulkan version does not meet the minimum version this runtime supports."); + print_line("- desired_version ", OpenXRUtil::make_xr_version_string(p_desired_version)); + print_line("- minApiVersionSupported ", OpenXRUtil::make_xr_version_string(vulkan_requirements.minApiVersionSupported)); + print_line("- maxApiVersionSupported ", OpenXRUtil::make_xr_version_string(vulkan_requirements.maxApiVersionSupported)); + return false; + } + + if (p_desired_version > vulkan_requirements.maxApiVersionSupported) { + print_line("OpenXR: Requested Vulkan version exceeds the maximum version this runtime has been tested on and is known to support."); + print_line("- desired_version ", OpenXRUtil::make_xr_version_string(p_desired_version)); + print_line("- minApiVersionSupported ", OpenXRUtil::make_xr_version_string(vulkan_requirements.minApiVersionSupported)); + print_line("- maxApiVersionSupported ", OpenXRUtil::make_xr_version_string(vulkan_requirements.maxApiVersionSupported)); + } + + return true; +} + +XrResult OpenXRVulkanExtension::xrCreateVulkanInstanceKHR(XrInstance p_instance, const XrVulkanInstanceCreateInfoKHR *p_create_info, VkInstance *r_vulkan_instance, VkResult *r_vulkan_result) { + ERR_FAIL_NULL_V(xrCreateVulkanInstanceKHR_ptr, XR_ERROR_HANDLE_INVALID); + + return (*xrCreateVulkanInstanceKHR_ptr)(p_instance, p_create_info, r_vulkan_instance, r_vulkan_result); +} + +bool OpenXRVulkanExtension::create_vulkan_instance(const VkInstanceCreateInfo *p_vulkan_create_info, VkInstance *r_instance) { + // get the vulkan version we are creating + uint32_t vulkan_version = p_vulkan_create_info->pApplicationInfo->apiVersion; + uint32_t major_version = VK_VERSION_MAJOR(vulkan_version); + uint32_t minor_version = VK_VERSION_MINOR(vulkan_version); + uint32_t patch_version = VK_VERSION_PATCH(vulkan_version); + XrVersion desired_version = XR_MAKE_VERSION(major_version, minor_version, patch_version); + + // check if this is supported + if (!check_graphics_api_support(desired_version)) { + return false; + } + + XrVulkanInstanceCreateInfoKHR xr_vulkan_instance_info = { + XR_TYPE_VULKAN_INSTANCE_CREATE_INFO_KHR, // type + nullptr, // next + openxr_api->get_system_id(), // systemId + 0, // createFlags + vkGetInstanceProcAddr, // pfnGetInstanceProcAddr + p_vulkan_create_info, // vulkanCreateInfo + nullptr, // vulkanAllocator + }; + + VkResult vk_result = VK_SUCCESS; + XrResult result = xrCreateVulkanInstanceKHR(openxr_api->get_instance(), &xr_vulkan_instance_info, &vulkan_instance, &vk_result); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to create vulkan instance [", openxr_api->get_error_string(result), "]"); + return false; + } + + ERR_FAIL_COND_V_MSG(vk_result == VK_ERROR_INCOMPATIBLE_DRIVER, false, + "Cannot find a compatible Vulkan installable client driver (ICD).\n\n" + "vkCreateInstance Failure"); + ERR_FAIL_COND_V_MSG(vk_result == VK_ERROR_EXTENSION_NOT_PRESENT, false, + "Cannot find a specified extension library.\n" + "Make sure your layers path is set appropriately.\n" + "vkCreateInstance Failure"); + ERR_FAIL_COND_V_MSG(vk_result, false, + "vkCreateInstance failed.\n\n" + "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" + "Please look at the Getting Started guide for additional information.\n" + "vkCreateInstance Failure"); + + *r_instance = vulkan_instance; + + return true; +} + +XrResult OpenXRVulkanExtension::xrGetVulkanGraphicsDevice2KHR(XrInstance p_instance, const XrVulkanGraphicsDeviceGetInfoKHR *p_get_info, VkPhysicalDevice *r_vulkan_physical_device) { + ERR_FAIL_NULL_V(xrGetVulkanGraphicsDevice2KHR_ptr, XR_ERROR_HANDLE_INVALID); + + return (*xrGetVulkanGraphicsDevice2KHR_ptr)(p_instance, p_get_info, r_vulkan_physical_device); +} + +bool OpenXRVulkanExtension::get_physical_device(VkPhysicalDevice *r_device) { + ERR_FAIL_NULL_V(openxr_api, false); + + XrVulkanGraphicsDeviceGetInfoKHR get_info = { + XR_TYPE_VULKAN_GRAPHICS_DEVICE_GET_INFO_KHR, // type + nullptr, // next + openxr_api->get_system_id(), // systemId + vulkan_instance, // vulkanInstance + }; + + XrResult result = xrGetVulkanGraphicsDevice2KHR(openxr_api->get_instance(), &get_info, &vulkan_physical_device); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to obtain vulkan physical device [", openxr_api->get_error_string(result), "]"); + return false; + } + + *r_device = vulkan_physical_device; + + return true; +} + +XrResult OpenXRVulkanExtension::xrCreateVulkanDeviceKHR(XrInstance p_instance, const XrVulkanDeviceCreateInfoKHR *p_create_info, VkDevice *r_device, VkResult *r_result) { + ERR_FAIL_NULL_V(xrCreateVulkanDeviceKHR_ptr, XR_ERROR_HANDLE_INVALID); + + return (*xrCreateVulkanDeviceKHR_ptr)(p_instance, p_create_info, r_device, r_result); +} + +bool OpenXRVulkanExtension::create_vulkan_device(const VkDeviceCreateInfo *p_device_create_info, VkDevice *r_device) { + ERR_FAIL_NULL_V(openxr_api, false); + + // the first entry in our queue list should be the one we need to remember... + vulkan_queue_family_index = p_device_create_info->pQueueCreateInfos[0].queueFamilyIndex; + vulkan_queue_index = 0; // ?? + + XrVulkanDeviceCreateInfoKHR create_info = { + XR_TYPE_VULKAN_DEVICE_CREATE_INFO_KHR, // type + nullptr, // next + openxr_api->get_system_id(), // systemId + 0, // createFlags + vkGetInstanceProcAddr, // pfnGetInstanceProcAddr + vulkan_physical_device, // vulkanPhysicalDevice + p_device_create_info, // vulkanCreateInfo + nullptr // vulkanAllocator + }; + + VkResult vk_result = VK_SUCCESS; + XrResult result = xrCreateVulkanDeviceKHR(openxr_api->get_instance(), &create_info, &vulkan_device, &vk_result); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to create vulkan device [", openxr_api->get_error_string(result), "]"); + return false; + } + + if (vk_result != VK_SUCCESS) { + print_line("OpenXR: Failed to create vulkan device [vulkan error", vk_result, "]"); + } + + *r_device = vulkan_device; + + return true; +} + +XrGraphicsBindingVulkanKHR OpenXRVulkanExtension::graphics_binding_vulkan; + +void *OpenXRVulkanExtension::set_session_create_and_get_next_pointer(void *p_next_pointer) { + graphics_binding_vulkan.type = XR_TYPE_GRAPHICS_BINDING_VULKAN_KHR; + graphics_binding_vulkan.next = p_next_pointer; + graphics_binding_vulkan.instance = vulkan_instance; + graphics_binding_vulkan.physicalDevice = vulkan_physical_device; + graphics_binding_vulkan.device = vulkan_device; + graphics_binding_vulkan.queueFamilyIndex = vulkan_queue_family_index; + graphics_binding_vulkan.queueIndex = vulkan_queue_index; + + return &graphics_binding_vulkan; +} + +void OpenXRVulkanExtension::get_usable_swapchain_formats(Vector<int64_t> &p_usable_swap_chains) { + // We might want to do more here especially if we keep things in linear color space + // Possibly add in R10G10B10A2 as an option if we're using the mobile renderer. + p_usable_swap_chains.push_back(VK_FORMAT_R8G8B8A8_SRGB); + p_usable_swap_chains.push_back(VK_FORMAT_B8G8R8A8_SRGB); + p_usable_swap_chains.push_back(VK_FORMAT_R8G8B8A8_UINT); + p_usable_swap_chains.push_back(VK_FORMAT_B8G8R8A8_UINT); +} + +bool OpenXRVulkanExtension::get_swapchain_image_data(XrSwapchain p_swapchain, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, void **r_swapchain_graphics_data) { + XrSwapchainImageVulkanKHR *images = nullptr; + + RenderingServer *rendering_server = RenderingServer::get_singleton(); + ERR_FAIL_NULL_V(rendering_server, false); + RenderingDevice *rendering_device = rendering_server->get_rendering_device(); + ERR_FAIL_NULL_V(rendering_device, false); + + uint32_t swapchain_length; + XrResult result = xrEnumerateSwapchainImages(p_swapchain, 0, &swapchain_length, nullptr); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to get swapchaim image count [", openxr_api->get_error_string(result), "]"); + return false; + } + + images = (XrSwapchainImageVulkanKHR *)memalloc(sizeof(XrSwapchainImageVulkanKHR) * swapchain_length); + ERR_FAIL_NULL_V_MSG(images, false, "OpenXR Couldn't allocate memory for swap chain image"); + + for (uint64_t i = 0; i < swapchain_length; i++) { + images[i].type = XR_TYPE_SWAPCHAIN_IMAGE_VULKAN_KHR; + images[i].next = nullptr; + images[i].image = nullptr; + } + + result = xrEnumerateSwapchainImages(p_swapchain, swapchain_length, &swapchain_length, (XrSwapchainImageBaseHeader *)images); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to get swapchaim images [", openxr_api->get_error_string(result), "]"); + memfree(images); + return false; + } + + // SwapchainGraphicsData *data = (SwapchainGraphicsData *)memalloc(sizeof(SwapchainGraphicsData)); + SwapchainGraphicsData *data = memnew(SwapchainGraphicsData); + if (data == nullptr) { + print_line("OpenXR: Failed to allocate memory for swapchain data"); + memfree(images); + return false; + } + *r_swapchain_graphics_data = data; + data->is_multiview = (p_array_size > 1); + + RenderingDevice::DataFormat format = RenderingDevice::DATA_FORMAT_R8G8B8A8_SRGB; // TODO set this based on p_swapchain_format + RenderingDevice::TextureSamples samples = RenderingDevice::TEXTURE_SAMPLES_1; // TODO set this based on p_sample_count + uint64_t usage_flags = RenderingDevice::TEXTURE_USAGE_SAMPLING_BIT | RenderingDevice::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT; + + Vector<RID> image_rids; + Vector<RID> framebuffers; + + // create Godot texture objects for each entry in our swapchain + for (uint64_t i = 0; i < swapchain_length; i++) { + RID image_rid = rendering_device->texture_create_from_extension( + p_array_size == 1 ? RenderingDevice::TEXTURE_TYPE_2D : RenderingDevice::TEXTURE_TYPE_2D_ARRAY, + format, + samples, + usage_flags, + (uint64_t)images[i].image, + p_width, + p_height, + 1, + p_array_size); + + image_rids.push_back(image_rid); + + { + Vector<RID> fb; + fb.push_back(image_rid); + + RID fb_rid = rendering_device->framebuffer_create(fb, RenderingDevice::INVALID_ID, p_array_size); + framebuffers.push_back(fb_rid); + } + } + + data->image_rids = image_rids; + data->framebuffers = framebuffers; + + memfree(images); + + return true; +} + +bool OpenXRVulkanExtension::create_projection_fov(const XrFovf p_fov, double p_z_near, double p_z_far, CameraMatrix &r_camera_matrix) { + // Even though this is a Vulkan renderer we're using OpenGL coordinate systems + XrMatrix4x4f matrix; + XrMatrix4x4f_CreateProjectionFov(&matrix, GRAPHICS_OPENGL, p_fov, (float)p_z_near, (float)p_z_far); + + for (int j = 0; j < 4; j++) { + for (int i = 0; i < 4; i++) { + r_camera_matrix.matrix[j][i] = matrix.m[j * 4 + i]; + } + } + + return true; +} + +bool OpenXRVulkanExtension::copy_render_target_to_image(RID p_from_render_target, void *p_swapchain_graphics_data, int p_image_index) { + SwapchainGraphicsData *data = (SwapchainGraphicsData *)p_swapchain_graphics_data; + ERR_FAIL_NULL_V(data, false); + ERR_FAIL_COND_V(p_from_render_target.is_null(), false); + ERR_FAIL_NULL_V(RendererStorageRD::base_singleton, false); + + RID source_image = RendererStorageRD::base_singleton->render_target_get_rd_texture(p_from_render_target); + ERR_FAIL_COND_V(source_image.is_null(), false); + + RID depth_image; // TODO implement + + ERR_FAIL_INDEX_V(p_image_index, data->framebuffers.size(), false); + RID fb = data->framebuffers[p_image_index]; + ERR_FAIL_COND_V(fb.is_null(), false); + + // Our vulkan extension can only be used in conjunction with our vulkan renderer. + // We need access to the effects object in order to have access to our copy logic. + // Breaking all the rules but there is no nice way to do this. + EffectsRD *effects = RendererStorageRD::base_singleton->get_effects(); + ERR_FAIL_NULL_V(effects, false); + effects->copy_to_fb_rect(source_image, fb, Rect2i(), false, false, false, false, depth_image, data->is_multiview); + + return true; +} + +void OpenXRVulkanExtension::cleanup_swapchain_graphics_data(void **p_swapchain_graphics_data) { + if (*p_swapchain_graphics_data == nullptr) { + return; + } + + SwapchainGraphicsData *data = (SwapchainGraphicsData *)*p_swapchain_graphics_data; + + RenderingServer *rendering_server = RenderingServer::get_singleton(); + ERR_FAIL_NULL(rendering_server); + RenderingDevice *rendering_device = rendering_server->get_rendering_device(); + ERR_FAIL_NULL(rendering_device); + + for (int i = 0; i < data->image_rids.size(); i++) { + // This should clean up our RIDs and associated texture objects but shouldn't destroy the images, they are owned by our XrSwapchain + rendering_device->free(data->image_rids[i]); + } + data->image_rids.clear(); + + for (int i = 0; i < data->framebuffers.size(); i++) { + // This should clean up our RIDs and associated texture objects but shouldn't destroy the images, they are owned by our XrSwapchain + rendering_device->free(data->framebuffers[i]); + } + data->framebuffers.clear(); + + memdelete(data); + *p_swapchain_graphics_data = nullptr; +} + +#define ENUM_TO_STRING_CASE(e) \ + case e: { \ + return String(#e); \ + } break; + +String OpenXRVulkanExtension::get_swapchain_format_name(int64_t p_swapchain_format) const { + // This really should be in vulkan_context... + VkFormat format = VkFormat(p_swapchain_format); + switch (format) { + ENUM_TO_STRING_CASE(VK_FORMAT_UNDEFINED) + ENUM_TO_STRING_CASE(VK_FORMAT_R4G4_UNORM_PACK8) + ENUM_TO_STRING_CASE(VK_FORMAT_R4G4B4A4_UNORM_PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_B4G4R4A4_UNORM_PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_R5G6B5_UNORM_PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_B5G6R5_UNORM_PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_R5G5B5A1_UNORM_PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_B5G5R5A1_UNORM_PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_A1R5G5B5_UNORM_PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_R8_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R8_SNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R8_USCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R8_SSCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R8_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R8_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R8_SRGB) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8_SNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8_USCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8_SSCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8_SRGB) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8_SNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8_USCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8_SSCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8_SRGB) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8_SNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8_USCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8_SSCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8_SRGB) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8A8_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8A8_SNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8A8_USCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8A8_SSCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8A8_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8A8_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R8G8B8A8_SRGB) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8A8_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8A8_SNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8A8_USCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8A8_SSCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8A8_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8A8_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8A8_SRGB) + ENUM_TO_STRING_CASE(VK_FORMAT_A8B8G8R8_UNORM_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A8B8G8R8_SNORM_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A8B8G8R8_USCALED_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A8B8G8R8_SSCALED_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A8B8G8R8_UINT_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A8B8G8R8_SINT_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A8B8G8R8_SRGB_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A2R10G10B10_UNORM_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A2R10G10B10_SNORM_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A2R10G10B10_USCALED_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A2R10G10B10_SSCALED_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A2R10G10B10_UINT_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A2R10G10B10_SINT_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A2B10G10R10_UNORM_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A2B10G10R10_SNORM_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A2B10G10R10_USCALED_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A2B10G10R10_SSCALED_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A2B10G10R10_UINT_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_A2B10G10R10_SINT_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_R16_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R16_SNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R16_USCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R16_SSCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R16_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R16_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R16_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16_SNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16_USCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16_SSCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16_SNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16_USCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16_SSCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16A16_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16A16_SNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16A16_USCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16A16_SSCALED) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16A16_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16A16_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R16G16B16A16_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_R32_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R32_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R32_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_R32G32_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R32G32_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R32G32_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_R32G32B32_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R32G32B32_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R32G32B32_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_R32G32B32A32_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R32G32B32A32_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R32G32B32A32_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_R64_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R64_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R64_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_R64G64_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R64G64_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R64G64_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_R64G64B64_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R64G64B64_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R64G64B64_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_R64G64B64A64_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R64G64B64A64_SINT) + ENUM_TO_STRING_CASE(VK_FORMAT_R64G64B64A64_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_B10G11R11_UFLOAT_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_E5B9G9R9_UFLOAT_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_D16_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_X8_D24_UNORM_PACK32) + ENUM_TO_STRING_CASE(VK_FORMAT_D32_SFLOAT) + ENUM_TO_STRING_CASE(VK_FORMAT_S8_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_D16_UNORM_S8_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_D24_UNORM_S8_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_D32_SFLOAT_S8_UINT) + ENUM_TO_STRING_CASE(VK_FORMAT_BC1_RGB_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC1_RGB_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC1_RGBA_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC1_RGBA_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC2_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC2_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC3_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC3_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC4_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC4_SNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC5_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC5_SNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC6H_UFLOAT_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC6H_SFLOAT_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC7_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_BC7_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_EAC_R11_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_EAC_R11_SNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_EAC_R11G11_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_EAC_R11G11_SNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_4x4_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_4x4_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_5x4_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_5x4_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_5x5_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_5x5_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_6x5_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_6x5_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_6x6_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_6x6_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_8x5_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_8x5_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_8x6_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_8x6_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_8x8_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_8x8_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_10x5_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_10x5_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_10x6_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_10x6_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_10x8_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_10x8_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_10x10_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_10x10_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_12x10_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_12x10_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_12x12_UNORM_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_12x12_SRGB_BLOCK) + ENUM_TO_STRING_CASE(VK_FORMAT_G8B8G8R8_422_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_B8G8R8G8_422_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_G8_B8R8_2PLANE_420_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_G8_B8R8_2PLANE_422_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_R10X6_UNORM_PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_R10X6G10X6_UNORM_2PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_R12X4_UNORM_PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_R12X4G12X4_UNORM_2PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16) + ENUM_TO_STRING_CASE(VK_FORMAT_G16B16G16R16_422_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_B16G16R16G16_422_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_G16_B16R16_2PLANE_420_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_G16_B16R16_2PLANE_422_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM) + ENUM_TO_STRING_CASE(VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG) + ENUM_TO_STRING_CASE(VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG) + ENUM_TO_STRING_CASE(VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG) + ENUM_TO_STRING_CASE(VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG) + ENUM_TO_STRING_CASE(VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG) + ENUM_TO_STRING_CASE(VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG) + ENUM_TO_STRING_CASE(VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG) + ENUM_TO_STRING_CASE(VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_5x4_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_5x5_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_6x5_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_6x6_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_8x5_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_8x6_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_10x5_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_10x6_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_10x8_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_10x10_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_12x10_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_ASTC_12x12_SFLOAT_BLOCK_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_G8_B8R8_2PLANE_444_UNORM_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_G10X6_B10X6R10X6_2PLANE_444_UNORM_3PACK16_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_G12X4_B12X4R12X4_2PLANE_444_UNORM_3PACK16_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_G16_B16R16_2PLANE_444_UNORM_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_A4R4G4B4_UNORM_PACK16_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT) + ENUM_TO_STRING_CASE(VK_FORMAT_MAX_ENUM) + default: { + return String("Swapchain format ") + String::num_int64(int64_t(p_swapchain_format)); + } break; + } +} diff --git a/modules/openxr/extensions/openxr_vulkan_extension.h b/modules/openxr/extensions/openxr_vulkan_extension.h new file mode 100644 index 0000000000..c04ea2c1dd --- /dev/null +++ b/modules/openxr/extensions/openxr_vulkan_extension.h @@ -0,0 +1,93 @@ +/*************************************************************************/ +/* openxr_vulkan_extension.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef OPENXR_VULKAN_EXTENSION_H +#define OPENXR_VULKAN_EXTENSION_H + +#include "core/templates/vector.h" +#include "openxr_extension_wrapper.h" + +#include "drivers/vulkan/vulkan_context.h" + +// Forward declare these so we don't need OpenXR headers where-ever this is included +// Including OpenXR at this point gives loads and loads of compile issues especially +// on Windows because windows.h is EVIL and really shouldn't be included outside of platform +// but we really don't have a choice in the matter + +struct XrGraphicsRequirementsVulkanKHR; +struct XrVulkanInstanceCreateInfoKHR; +struct XrVulkanGraphicsDeviceGetInfoKHR; +struct XrVulkanDeviceCreateInfoKHR; +struct XrGraphicsBindingVulkanKHR; + +class OpenXRVulkanExtension : public OpenXRGraphicsExtensionWrapper, VulkanHooks { +public: + OpenXRVulkanExtension(OpenXRAPI *p_openxr_api); + virtual ~OpenXRVulkanExtension() override; + + virtual void on_instance_created(const XrInstance p_instance) override; + virtual void *set_session_create_and_get_next_pointer(void *p_next_pointer) override; + + virtual bool create_vulkan_instance(const VkInstanceCreateInfo *p_vulkan_create_info, VkInstance *r_instance); + virtual bool get_physical_device(VkPhysicalDevice *r_device); + virtual bool create_vulkan_device(const VkDeviceCreateInfo *p_device_create_info, VkDevice *r_device); + + virtual void get_usable_swapchain_formats(Vector<int64_t> &p_usable_swap_chains) override; + virtual String get_swapchain_format_name(int64_t p_swapchain_format) const override; + virtual bool get_swapchain_image_data(XrSwapchain p_swapchain, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, void **r_swapchain_graphics_data) override; + virtual void cleanup_swapchain_graphics_data(void **p_swapchain_graphics_data) override; + virtual bool create_projection_fov(const XrFovf p_fov, double p_z_near, double p_z_far, CameraMatrix &r_camera_matrix) override; + virtual bool copy_render_target_to_image(RID p_from_render_target, void *p_swapchain_graphics_data, int p_image_index) override; + +private: + static OpenXRVulkanExtension *singleton; + static XrGraphicsBindingVulkanKHR graphics_binding_vulkan; // declaring this as static so we don't need to know its size and we only need it once when creating our session + + struct SwapchainGraphicsData { + bool is_multiview; + Vector<RID> image_rids; + Vector<RID> framebuffers; + }; + + bool check_graphics_api_support(XrVersion p_desired_version); + + VkInstance vulkan_instance; + VkPhysicalDevice vulkan_physical_device; + VkDevice vulkan_device; + uint32_t vulkan_queue_family_index; + uint32_t vulkan_queue_index; + + XrResult xrGetVulkanGraphicsRequirements2KHR(XrInstance p_instance, XrSystemId p_system_id, XrGraphicsRequirementsVulkanKHR *p_graphics_requirements); + XrResult xrCreateVulkanInstanceKHR(XrInstance p_instance, const XrVulkanInstanceCreateInfoKHR *p_create_info, VkInstance *r_vulkan_instance, VkResult *r_vulkan_result); + XrResult xrGetVulkanGraphicsDevice2KHR(XrInstance p_instance, const XrVulkanGraphicsDeviceGetInfoKHR *p_get_info, VkPhysicalDevice *r_vulkan_physical_device); + XrResult xrCreateVulkanDeviceKHR(XrInstance p_instance, const XrVulkanDeviceCreateInfoKHR *p_create_info, VkDevice *r_device, VkResult *r_result); +}; + +#endif // !OPENXR_VULKAN_EXTENSION_H diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp new file mode 100644 index 0000000000..23fd0404d5 --- /dev/null +++ b/modules/openxr/openxr_api.cpp @@ -0,0 +1,2171 @@ +/*************************************************************************/ +/* openxr_api.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "openxr_api.h" +#include "openxr_util.h" + +#include "core/config/engine.h" +#include "core/config/project_settings.h" +#include "core/os/memory.h" +#include "core/version.h" + +#ifdef TOOLS_ENABLED +#include "editor/editor_settings.h" +#endif + +#ifdef ANDROID_ENABLED +#include "extensions/openxr_android_extension.h" +#endif + +#ifdef VULKAN_ENABLED +#include "extensions/openxr_vulkan_extension.h" +#endif + +OpenXRAPI *OpenXRAPI::singleton = nullptr; + +void OpenXRAPI::setup_global_defs() { + // As OpenXRAPI is not constructed if OpenXR is not enabled, we register our project and editor settings here + + // Project settings + GLOBAL_DEF_BASIC("xr/openxr/enabled", false); + GLOBAL_DEF_BASIC("xr/openxr/default_action_map", "res://default_action_map.tres"); + ProjectSettings::get_singleton()->set_custom_property_info("xr/openxr/default_action_map", PropertyInfo(Variant::STRING, "xr/openxr/default_action_map", PROPERTY_HINT_FILE, "*.tres")); + + GLOBAL_DEF_BASIC("xr/openxr/form_factor", "0"); + ProjectSettings::get_singleton()->set_custom_property_info("xr/openxr/form_factor", PropertyInfo(Variant::INT, "xr/openxr/form_factor", PROPERTY_HINT_ENUM, "Head mounted,Handheld")); + + GLOBAL_DEF_BASIC("xr/openxr/view_configuration", "1"); + ProjectSettings::get_singleton()->set_custom_property_info("xr/openxr/view_configuration", PropertyInfo(Variant::INT, "xr/openxr/view_configuration", PROPERTY_HINT_ENUM, "Mono,Stereo")); // "Mono,Stereo,Quad,Observer" + + GLOBAL_DEF_BASIC("xr/openxr/reference_space", "1"); + ProjectSettings::get_singleton()->set_custom_property_info("xr/openxr/reference_space", PropertyInfo(Variant::INT, "xr/openxr/reference_space", PROPERTY_HINT_ENUM, "Local,Stage")); + +#ifdef TOOLS_ENABLED + // Disabled for now, using XR inside of the editor we'll be working on during the coming months. + + // editor settings (it seems we're too early in the process when setting up rendering, to access editor settings...) + // EDITOR_DEF_RST("xr/openxr/in_editor", false); + // GLOBAL_DEF("xr/openxr/in_editor", false); +#endif +} + +bool OpenXRAPI::openxr_is_enabled() { + // @TODO we need an overrule switch so we can force enable openxr, i.e run "godot --openxr_enabled" + + if (Engine::get_singleton()->is_editor_hint()) { +#ifdef TOOLS_ENABLED + // Disabled for now, using XR inside of the editor we'll be working on during the coming months. + return false; + + // bool enabled = GLOBAL_GET("xr/openxr/in_editor"); // EDITOR_GET("xr/openxr/in_editor"); + // return enabled; +#else + // we should never get here, editor hint won't be true if the editor isn't compiled in. + return false; +#endif + } else { + bool enabled = GLOBAL_GET("xr/openxr/enabled"); + return enabled; + } +} + +OpenXRAPI *OpenXRAPI::get_singleton() { + if (singleton != nullptr) { + // already constructed, return our singleton + return singleton; + } else if (openxr_is_enabled()) { + // construct our singleton and return it + singleton = memnew(OpenXRAPI); + return singleton; + } else { + // not enabled, don't instantiate, return nullptr + return nullptr; + } +} + +String OpenXRAPI::get_default_action_map_resource_name() { + String name = GLOBAL_GET("xr/openxr/default_action_map"); + + return name; +} + +String OpenXRAPI::get_error_string(XrResult result) { + if (XR_SUCCEEDED(result)) { + return String("Succeeded"); + } + + if (instance == XR_NULL_HANDLE) { + Array args; + args.push_back(Variant(result)); + return String("Error code {0}").format(args); + } + + char resultString[XR_MAX_RESULT_STRING_SIZE]; + xrResultToString(instance, result, resultString); + + return String(resultString); +} + +String OpenXRAPI::get_swapchain_format_name(int64_t p_swapchain_format) const { + // This is rendering engine dependend... + if (graphics_extension) { + return graphics_extension->get_swapchain_format_name(p_swapchain_format); + } + + return String("Swapchain format ") + String::num_int64(int64_t(p_swapchain_format)); +} + +bool OpenXRAPI::load_layer_properties() { + // This queries additional layers that are available and can be initialised when we create our OpenXR instance + if (layer_properties != nullptr) { + // already retrieved this + return true; + } + + // Note, instance is not yet setup so we can't use get_error_string to retrieve our error + XrResult result = xrEnumerateApiLayerProperties(0, &num_layer_properties, nullptr); + ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate number of api layer properties"); + + layer_properties = (XrApiLayerProperties *)memalloc(sizeof(XrApiLayerProperties) * num_layer_properties); + ERR_FAIL_NULL_V(layer_properties, false); + for (uint32_t i = 0; i < num_layer_properties; i++) { + layer_properties[i].type = XR_TYPE_API_LAYER_PROPERTIES; + layer_properties[i].next = nullptr; + } + + result = xrEnumerateApiLayerProperties(num_layer_properties, &num_layer_properties, layer_properties); + ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate api layer properties"); + +#ifdef DEBUG + for (uint32_t i = 0; i < num_layer_properties; i++) { + print_line("OpenXR: Found OpenXR layer ", layer_properties[i].layerName); + } +#endif + + return true; +} + +bool OpenXRAPI::load_supported_extensions() { + // This queries supported extensions that are available and can be initialised when we create our OpenXR instance + + if (supported_extensions != nullptr) { + // already retrieved this + return true; + } + + // Note, instance is not yet setup so we can't use get_error_string to retrieve our error + XrResult result = xrEnumerateInstanceExtensionProperties(nullptr, 0, &num_supported_extensions, nullptr); + ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate number of extension properties"); + + supported_extensions = (XrExtensionProperties *)memalloc(sizeof(XrExtensionProperties) * num_supported_extensions); + ERR_FAIL_NULL_V(supported_extensions, false); + + // set our types + for (uint32_t i = 0; i < num_supported_extensions; i++) { + supported_extensions[i].type = XR_TYPE_EXTENSION_PROPERTIES; + supported_extensions[i].next = nullptr; + } + result = xrEnumerateInstanceExtensionProperties(nullptr, num_supported_extensions, &num_supported_extensions, supported_extensions); + ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate extension properties"); + +#ifdef DEBUG + for (uint32_t i = 0; i < num_supported_extensions; i++) { + print_line("OpenXR: Found OpenXR extension ", supported_extensions[i].extensionName); + } +#endif + + return true; +} + +bool OpenXRAPI::is_extension_supported(const char *p_extension) const { + for (uint32_t i = 0; i < num_supported_extensions; i++) { + if (strcmp(supported_extensions[i].extensionName, p_extension)) { + return true; + } + } + + return false; +} + +void OpenXRAPI::copy_string_to_char_buffer(const String p_string, char *p_buffer, int p_buffer_len) { + CharString char_string = p_string.utf8(); + int len = char_string.length(); + if (len < p_buffer_len - 1) { + // was having weird CI issues with strcpy so.... + memcpy(p_buffer, char_string.get_data(), len); + p_buffer[len] = '\0'; + } else { + memcpy(p_buffer, char_string.get_data(), p_buffer_len - 1); + p_buffer[p_buffer_len - 1] = '\0'; + } +} + +bool OpenXRAPI::create_instance() { + // Create our OpenXR instance, this will query any registered extension wrappers for extensions we need to enable. + + // Append the extensions requested by the registered extension wrappers. + Map<const char *, bool *> requested_extensions; + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + Map<const char *, bool *> wrapper_request_extensions = wrapper->get_request_extensions(); + + // requested_extensions.insert(wrapper_request_extensions.begin(), wrapper_request_extensions.end()); + for (auto &requested_extension : wrapper_request_extensions) { + requested_extensions[requested_extension.key] = requested_extension.value; + } + } + + // Check which extensions are supported + enabled_extensions.clear(); + for (auto &requested_extension : requested_extensions) { + if (!is_extension_supported(requested_extension.key)) { + if (requested_extension.value == nullptr) { + // nullptr means this is a manditory extension so we fail + ERR_FAIL_V_MSG(false, "OpenXR: OpenXR Runtime does not support OpenGL extension!"); + } else { + // set this extension as not supported + *requested_extension.value = false; + } + } else if (requested_extension.value != nullptr) { + // set this extension as supported + *requested_extension.value = true; + + // and record that we want to enable it + enabled_extensions.push_back(requested_extension.key); + } else { + // record that we want to enable this + enabled_extensions.push_back(requested_extension.key); + } + } + + // Get our project name + String project_name = GLOBAL_GET("application/config/name"); + + // Create our OpenXR instance + XrApplicationInfo application_info{ + "", // applicationName, we'll set this down below + 1, // applicationVersion, we don't currently have this + "Godot Game Engine", // engineName + VERSION_MAJOR * 10000 + VERSION_MINOR * 100 + VERSION_PATCH, // engineVersion 4.0 -> 40000, 4.0.1 -> 40001, 4.1 -> 40100, etc. + XR_CURRENT_API_VERSION // apiVersion + }; + + XrInstanceCreateInfo instance_create_info = { + XR_TYPE_INSTANCE_CREATE_INFO, // type + nullptr, // next + 0, // createFlags + application_info, // applicationInfo + 0, // enabledApiLayerCount, need to find out if we need support for this? + nullptr, // enabledApiLayerNames + uint32_t(enabled_extensions.size()), // enabledExtensionCount + enabled_extensions.ptr() // enabledExtensionNames + }; + + copy_string_to_char_buffer(project_name, instance_create_info.applicationInfo.applicationName, XR_MAX_APPLICATION_NAME_SIZE); + + XrResult result = xrCreateInstance(&instance_create_info, &instance); + ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "Failed to create XR instance."); + + // from this point on we can use get_error_string to get more info about our errors... + + XrInstanceProperties instanceProps = { + XR_TYPE_INSTANCE_PROPERTIES, // type; + nullptr, // next + 0, // runtimeVersion, from here will be set by our get call + "" // runtimeName + }; + result = xrGetInstanceProperties(instance, &instanceProps); + if (XR_FAILED(result)) { + // not fatal probably + print_line("OpenXR: Failed to get XR instance properties [", get_error_string(result), "]"); + } else { + print_line("OpenXR: Running on OpenXR runtime: ", instanceProps.runtimeName, " ", OpenXRUtil::make_xr_version_string(instanceProps.runtimeVersion)); + } + + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_instance_created(instance); + } + + return true; +} + +bool OpenXRAPI::get_system_info() { + // Retrieve basic OpenXR system info based on the form factor we desire + + // Retrieve the system for our form factor, fails if form factor is not available + XrSystemGetInfo system_get_info = { + XR_TYPE_SYSTEM_GET_INFO, // type; + nullptr, // next + form_factor // formFactor + }; + + XrResult result = xrGetSystem(instance, &system_get_info, &system_id); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to get system for our form factor [", get_error_string(result), "]"); + return false; + } + + // obtain info about our system, writing this out completely to make CI on Linux happy.. + void *next_pointer = nullptr; + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + void *np = wrapper->set_system_properties_and_get_next_pointer(next_pointer); + if (np != nullptr) { + next_pointer = np; + } + } + + XrSystemProperties system_properties = { + XR_TYPE_SYSTEM_PROPERTIES, // type + next_pointer, // next + 0, // systemId, from here will be set by our get call + 0, // vendorId + "", // systemName + { + 0, // maxSwapchainImageHeight + 0, // maxSwapchainImageWidth + 0, // maxLayerCount + }, // graphicsProperties + { + false, // orientationTracking + false // positionTracking + } // trackingProperties + }; + + result = xrGetSystemProperties(instance, system_id, &system_properties); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to get System properties [", get_error_string(result), "]"); + return false; + } + + // remember this state, we'll use it later + system_name = String(system_properties.systemName); + vendor_id = system_properties.vendorId; + graphics_properties = system_properties.graphicsProperties; + tracking_properties = system_properties.trackingProperties; + + return true; +} + +bool OpenXRAPI::load_supported_view_configuration_types() { + // This queries the supported configuration types, likely there will only be one chosing between Mono (phone AR) and Stereo (HMDs) + + ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, false); + + if (supported_view_configuration_types != nullptr) { + // free previous results + memfree(supported_view_configuration_types); + supported_view_configuration_types = nullptr; + } + + XrResult result = xrEnumerateViewConfigurations(instance, system_id, 0, &num_view_configuration_types, nullptr); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to get view configuration count [", get_error_string(result), "]"); + return false; + } + + supported_view_configuration_types = (XrViewConfigurationType *)memalloc(sizeof(XrViewConfigurationType) * num_view_configuration_types); + ERR_FAIL_NULL_V(supported_view_configuration_types, false); + + result = xrEnumerateViewConfigurations(instance, system_id, num_view_configuration_types, &num_view_configuration_types, supported_view_configuration_types); + ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerateview configurations"); + +#ifdef DEBUG + for (uint32_t i = 0; i < num_view_configuration_types; i++) { + print_line("OpenXR: Found supported view configuration ", OpenXRUtil::get_view_configuration_name(supported_view_configuration_types[i])); + } +#endif + + return true; +} + +bool OpenXRAPI::is_view_configuration_supported(XrViewConfigurationType p_configuration_type) const { + ERR_FAIL_NULL_V(supported_view_configuration_types, false); + + for (uint32_t i = 0; i < num_view_configuration_types; i++) { + if (supported_view_configuration_types[i] == p_configuration_type) { + return true; + } + } + + return false; +} + +bool OpenXRAPI::load_supported_view_configuration_views(XrViewConfigurationType p_configuration_type) { + // This loads our view configuration for each view so for a stereo HMD, we'll get two entries (that are likely identical) + // The returned data supplies us with the recommended render target size + + if (!is_view_configuration_supported(p_configuration_type)) { + print_line("OpenXR: View configuration ", OpenXRUtil::get_view_configuration_name(view_configuration), " is not supported."); + return false; + } + + if (view_configuration_views != nullptr) { + // free previous results + memfree(view_configuration_views); + view_configuration_views = nullptr; + } + + XrResult result = xrEnumerateViewConfigurationViews(instance, system_id, p_configuration_type, 0, &view_count, nullptr); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to get view configuration count [", get_error_string(result), "]"); + return false; + } + + view_configuration_views = (XrViewConfigurationView *)memalloc(sizeof(XrViewConfigurationView) * view_count); + ERR_FAIL_NULL_V(view_configuration_views, false); + + for (uint32_t i = 0; i < view_count; i++) { + view_configuration_views[i].type = XR_TYPE_VIEW_CONFIGURATION_VIEW; + view_configuration_views[i].next = NULL; + } + + result = xrEnumerateViewConfigurationViews(instance, system_id, p_configuration_type, view_count, &view_count, view_configuration_views); + ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate view configurations"); + +#ifdef DEBUG + for (uint32_t i = 0; i < view_count; i++) { + print_line("OpenXR: Found supported view configuration view"); + print_line(" - width: ", view_configuration_views[i].maxImageRectWidth); + print_line(" - height: ", view_configuration_views[i].maxImageRectHeight); + print_line(" - sample count: ", view_configuration_views[i].maxSwapchainSampleCount); + print_line(" - recommended render width: ", view_configuration_views[i].recommendedImageRectWidth); + print_line(" - recommended render height: ", view_configuration_views[i].recommendedImageRectHeight); + print_line(" - recommended render sample count: ", view_configuration_views[i].recommendedSwapchainSampleCount); + } +#endif + + return true; +} + +void OpenXRAPI::destroy_instance() { + if (view_configuration_views != nullptr) { + memfree(view_configuration_views); + view_configuration_views = nullptr; + } + + if (supported_view_configuration_types != nullptr) { + memfree(supported_view_configuration_types); + supported_view_configuration_types = nullptr; + } + + if (instance != XR_NULL_HANDLE) { + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_instance_destroyed(); + } + + xrDestroyInstance(instance); + instance = XR_NULL_HANDLE; + } + enabled_extensions.clear(); +} + +bool OpenXRAPI::create_session() { + ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, false); + ERR_FAIL_COND_V(session != XR_NULL_HANDLE, false); + + void *next_pointer = nullptr; + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + void *np = wrapper->set_session_create_and_get_next_pointer(next_pointer); + if (np != nullptr) { + next_pointer = np; + } + } + + XrSessionCreateInfo session_create_info = { + XR_TYPE_SESSION_CREATE_INFO, // type + next_pointer, // next + 0, // createFlags + system_id // systemId + }; + + XrResult result = xrCreateSession(instance, &session_create_info, &session); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to create session [", get_error_string(result), "]"); + return false; + } + + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_session_created(session); + } + + return true; +} + +bool OpenXRAPI::load_supported_reference_spaces() { + // loads the supported reference spaces for our OpenXR session + + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); + + if (supported_reference_spaces != nullptr) { + // free previous results + memfree(supported_reference_spaces); + supported_reference_spaces = nullptr; + } + + XrResult result = xrEnumerateReferenceSpaces(session, 0, &num_reference_spaces, nullptr); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to get reference space count [", get_error_string(result), "]"); + return false; + } + + supported_reference_spaces = (XrReferenceSpaceType *)memalloc(sizeof(XrReferenceSpaceType) * num_reference_spaces); + ERR_FAIL_NULL_V(supported_reference_spaces, false); + + result = xrEnumerateReferenceSpaces(session, num_reference_spaces, &num_reference_spaces, supported_reference_spaces); + ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate reference spaces"); + + // #ifdef DEBUG + for (uint32_t i = 0; i < num_reference_spaces; i++) { + print_line("OpenXR: Found supported reference space ", OpenXRUtil::get_reference_space_name(supported_reference_spaces[i])); + } + // #endif + + return true; +} + +bool OpenXRAPI::is_reference_space_supported(XrReferenceSpaceType p_reference_space) { + ERR_FAIL_NULL_V(supported_reference_spaces, false); + + for (uint32_t i = 0; i < num_reference_spaces; i++) { + if (supported_reference_spaces[i] == p_reference_space) { + return true; + } + } + + return false; +} + +bool OpenXRAPI::setup_spaces() { + XrResult result; + + XrPosef identityPose = { + { 0.0, 0.0, 0.0, 1.0 }, + { 0.0, 0.0, 0.0 } + }; + + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); + + // create play space + { + if (!is_reference_space_supported(reference_space)) { + print_line("OpenXR: reference space ", OpenXRUtil::get_reference_space_name(reference_space), " is not supported."); + return false; + } + + XrReferenceSpaceCreateInfo play_space_create_info = { + XR_TYPE_REFERENCE_SPACE_CREATE_INFO, // type + nullptr, // next + reference_space, // referenceSpaceType + identityPose // poseInReferenceSpace + }; + + result = xrCreateReferenceSpace(session, &play_space_create_info, &play_space); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to create play space [", get_error_string(result), "]"); + return false; + } + } + + // create view space + { + if (!is_reference_space_supported(XR_REFERENCE_SPACE_TYPE_VIEW)) { + print_line("OpenXR: reference space XR_REFERENCE_SPACE_TYPE_VIEW is not supported."); + return false; + } + + XrReferenceSpaceCreateInfo view_space_create_info = { + XR_TYPE_REFERENCE_SPACE_CREATE_INFO, // type + nullptr, // next + XR_REFERENCE_SPACE_TYPE_VIEW, // referenceSpaceType + identityPose // poseInReferenceSpace + }; + + result = xrCreateReferenceSpace(session, &view_space_create_info, &view_space); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to create view space [", get_error_string(result), "]"); + return false; + } + } + + return true; +} + +bool OpenXRAPI::load_supported_swapchain_formats() { + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); + + if (supported_swapchain_formats != nullptr) { + // free previous results + memfree(supported_swapchain_formats); + supported_swapchain_formats = nullptr; + } + + XrResult result = xrEnumerateSwapchainFormats(session, 0, &num_swapchain_formats, nullptr); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to get swapchain format count [", get_error_string(result), "]"); + return false; + } + + supported_swapchain_formats = (int64_t *)memalloc(sizeof(int64_t) * num_swapchain_formats); + ERR_FAIL_NULL_V(supported_swapchain_formats, false); + + result = xrEnumerateSwapchainFormats(session, num_swapchain_formats, &num_swapchain_formats, supported_swapchain_formats); + ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "OpenXR: Failed to enumerate swapchain formats"); + + // #ifdef DEBUG + for (uint32_t i = 0; i < num_swapchain_formats; i++) { + print_line("OpenXR: Found supported swapchain format ", get_swapchain_format_name(supported_swapchain_formats[i])); + } + // #endif + + return true; +} + +bool OpenXRAPI::is_swapchain_format_supported(int64_t p_swapchain_format) { + ERR_FAIL_NULL_V(supported_swapchain_formats, false); + + for (uint32_t i = 0; i < num_swapchain_formats; i++) { + if (supported_swapchain_formats[i] == p_swapchain_format) { + return true; + } + } + + return false; +} + +bool OpenXRAPI::create_main_swapchain() { + ERR_FAIL_NULL_V(graphics_extension, false); + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); + + /* + TODO: We need to improve on this, for now we're taking our old approach of creating our main swapchains and substituting + those for the ones Godot normally creates. + This however means we can only use swapchains for our main XR view. + + It would have been nicer if we could override the swapchain creation in Godot with ours but we have a timing issue here. + We can't create XR swapchains until after our XR session is fully instantiated, yet Godot creates its swapchain much earlier. + + Also Godot only creates a swapchain for the main output. + OpenXR will require us to create swapchains as the render target for additional viewports if we want to use the layer system + to optimise text rendering and background rendering as OpenXR may choose to re-use the results for reprojection while we're + already rendering the next frame. + + Finally an area we need to expand upon is that Foveated rendering is only enabled for the swap chain we create, + as we render 3D content into internal buffers that are copied into the swapchain, we don't get any of the performance gains + until such time as we implement VRS. + */ + + // Build a vector with swapchain formats we want to use, from best fit to worst + Vector<int64_t> usable_swapchain_formats; + int64_t swapchain_format_to_use = 0; + + graphics_extension->get_usable_swapchain_formats(usable_swapchain_formats); + + // now find out which one is supported + for (int i = 0; i < usable_swapchain_formats.size() && swapchain_format_to_use == 0; i++) { + if (is_swapchain_format_supported(usable_swapchain_formats[i])) { + swapchain_format_to_use = usable_swapchain_formats[i]; + } + } + + if (swapchain_format_to_use == 0) { + swapchain_format_to_use = usable_swapchain_formats[0]; // just use the first one and hope for the best... + print_line("Couldn't find usable swap chain format, using", get_swapchain_format_name(swapchain_format_to_use), "instead."); + } else { + print_line("Using swap chain format:", get_swapchain_format_name(swapchain_format_to_use)); + } + + Size2 recommended_size = get_recommended_target_size(); + + if (!create_swapchain(swapchain_format_to_use, recommended_size.width, recommended_size.height, view_configuration_views[0].recommendedSwapchainSampleCount, view_count, swapchain, &swapchain_graphics_data)) { + return false; + } + + views = (XrView *)memalloc(sizeof(XrView) * view_count); + ERR_FAIL_NULL_V_MSG(views, false, "OpenXR Couldn't allocate memory for views"); + + projection_views = (XrCompositionLayerProjectionView *)memalloc(sizeof(XrCompositionLayerProjectionView) * view_count); + ERR_FAIL_NULL_V_MSG(projection_views, false, "OpenXR Couldn't allocate memory for projection views"); + + for (uint32_t i = 0; i < view_count; i++) { + views[i].type = XR_TYPE_VIEW; + views[i].next = NULL; + + projection_views[i].type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW; + projection_views[i].next = NULL; + projection_views[i].subImage.swapchain = swapchain; + projection_views[i].subImage.imageArrayIndex = i; + projection_views[i].subImage.imageRect.offset.x = 0; + projection_views[i].subImage.imageRect.offset.y = 0; + projection_views[i].subImage.imageRect.extent.width = recommended_size.width; + projection_views[i].subImage.imageRect.extent.height = recommended_size.height; + }; + + return true; +}; + +void OpenXRAPI::destroy_session() { + if (running && session != XR_NULL_HANDLE) { + xrEndSession(session); + } + + if (graphics_extension) { + graphics_extension->cleanup_swapchain_graphics_data(&swapchain_graphics_data); + } + + if (views != nullptr) { + memfree(views); + views = nullptr; + } + + if (projection_views != nullptr) { + memfree(projection_views); + projection_views = nullptr; + } + + if (swapchain != XR_NULL_HANDLE) { + xrDestroySwapchain(swapchain); + swapchain = XR_NULL_HANDLE; + } + + if (supported_swapchain_formats != nullptr) { + memfree(supported_swapchain_formats); + supported_swapchain_formats = nullptr; + } + + // destroy our spaces + if (play_space != XR_NULL_HANDLE) { + xrDestroySpace(play_space); + play_space = XR_NULL_HANDLE; + } + if (view_space != XR_NULL_HANDLE) { + xrDestroySpace(view_space); + view_space = XR_NULL_HANDLE; + } + + if (supported_reference_spaces != nullptr) { + // free previous results + memfree(supported_reference_spaces); + supported_reference_spaces = nullptr; + } + + if (session != XR_NULL_HANDLE) { + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_session_destroyed(); + } + + xrDestroySession(session); + session = XR_NULL_HANDLE; + } +} + +bool OpenXRAPI::create_swapchain(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) { + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); + ERR_FAIL_NULL_V(graphics_extension, false); + + XrResult result; + + void *next_pointer = nullptr; + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + void *np = wrapper->set_swapchain_create_info_and_get_next_pointer(next_pointer); + if (np != nullptr) { + next_pointer = np; + } + } + + XrSwapchainCreateInfo swapchain_create_info = { + XR_TYPE_SWAPCHAIN_CREATE_INFO, // type + next_pointer, // next + 0, // createFlags + XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT, // usageFlags + p_swapchain_format, // format + p_sample_count, // sampleCount + p_width, // width + p_height, // height + 1, // faceCount + p_array_size, // arraySize + 1 // mipCount + }; + + XrSwapchain new_swapchain; + result = xrCreateSwapchain(session, &swapchain_create_info, &new_swapchain); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to get swapchain [", get_error_string(result), "]"); + return false; + } + + if (!graphics_extension->get_swapchain_image_data(new_swapchain, p_swapchain_format, p_width, p_height, p_array_size, p_array_size, r_swapchain_graphics_data)) { + xrDestroySwapchain(new_swapchain); + return false; + } + + r_swapchain = new_swapchain; + + return true; +} + +bool OpenXRAPI::on_state_idle() { +#ifdef DEBUG + print_line("On state idle"); +#endif + + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_state_idle(); + } + + return true; +} + +bool OpenXRAPI::on_state_ready() { +#ifdef DEBUG + print_line("On state ready"); +#endif + + // begin session + XrSessionBeginInfo session_begin_info = { + XR_TYPE_SESSION_BEGIN_INFO, // type + nullptr, // next + view_configuration // primaryViewConfigurationType + }; + + XrResult result = xrBeginSession(session, &session_begin_info); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to begin session [", get_error_string(result), "]"); + return false; + } + + // This is when we create our swapchain, this can be a "long" time after Godot finishes, we can deal with this for now + // but once we want to provide Viewports for additional layers where OpenXR requires us to create further swapchains, + // we'll be creating those viewport WAY before we reach this point. + // We may need to implement a wait in our init in main.cpp polling our events until the session is ready. + // That will be very very ugly + // The other possibility is to create a separate OpenXRViewport type specifically for this goal as part of our OpenXR module + + if (!create_main_swapchain()) { + return false; + } + + // we're running + running = true; + + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_state_ready(); + } + + // TODO emit signal + + // TODO Tell android + + return true; +} + +bool OpenXRAPI::on_state_synchronized() { +#ifdef DEBUG + print_line("On state synchronized"); +#endif + + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_state_synchronized(); + } + + return true; +} + +bool OpenXRAPI::on_state_visible() { +#ifdef DEBUG + print_line("On state visible"); +#endif + + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_state_visible(); + } + + // TODO emit signal + + return true; +} + +bool OpenXRAPI::on_state_focused() { +#ifdef DEBUG + print_line("On state focused"); +#endif + + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_state_focused(); + } + + // TODO emit signal + + return true; +} + +bool OpenXRAPI::on_state_stopping() { +#ifdef DEBUG + print_line("On state stopping"); +#endif + + // TODO emit signal + + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_state_stopping(); + } + + if (running) { + XrResult result = xrEndSession(session); + if (XR_FAILED(result)) { + // we only report this.. + print_line("OpenXR: Failed to end session [", get_error_string(result), "]"); + } + + running = false; + } + + // TODO further cleanup + + return true; +} + +bool OpenXRAPI::on_state_loss_pending() { +#ifdef DEBUG + print_line("On state loss pending"); +#endif + + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_state_loss_pending(); + } + + // 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) + + return true; +} + +bool OpenXRAPI::on_state_exiting() { +#ifdef DEBUG + print_line("On state existing"); +#endif + + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_state_exiting(); + } + + // 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) + + return true; +} + +bool OpenXRAPI::is_initialized() { + return (instance != XR_NULL_HANDLE); +} + +bool OpenXRAPI::is_running() { + if (instance == XR_NULL_HANDLE) { + return false; + } + if (session == XR_NULL_HANDLE) { + return false; + } + + return running; +} + +bool OpenXRAPI::initialise(const String &p_rendering_driver) { + ERR_FAIL_COND_V_MSG(instance != XR_NULL_HANDLE, false, "OpenXR instance was already created"); + + if (p_rendering_driver == "vulkan") { +#ifdef VULKAN_ENABLED + graphics_extension = memnew(OpenXRVulkanExtension(this)); + register_extension_wrapper(graphics_extension); +#else + // shouldn't be possible... + ERR_FAIL_V(false); +#endif + } else if (p_rendering_driver == "opengl3") { +#ifdef OPENGL3_ENABLED + // graphics_extension = memnew(OpenXROpenGLExtension(this)); + // register_extension_wrapper(graphics_extension); + ERR_FAIL_V_MSG(false, "OpenXR: OpenGL is not supported at this time."); +#else + // shouldn't be possible... + ERR_FAIL_V(false); +#endif + } else { + ERR_FAIL_V_MSG(false, "OpenXR: Unsupported rendering device."); + } + + // initialise + if (!load_layer_properties()) { + destroy_instance(); + return false; + } + + if (!load_supported_extensions()) { + destroy_instance(); + return false; + } + + if (!create_instance()) { + destroy_instance(); + return false; + } + + if (!get_system_info()) { + destroy_instance(); + return false; + } + + if (!load_supported_view_configuration_types()) { + destroy_instance(); + return false; + } + + if (!load_supported_view_configuration_views(view_configuration)) { + destroy_instance(); + return false; + } + + return true; +} + +bool OpenXRAPI::initialise_session() { + if (!create_session()) { + destroy_session(); + return false; + } + + if (!load_supported_reference_spaces()) { + destroy_session(); + return false; + } + + if (!setup_spaces()) { + destroy_session(); + return false; + } + + if (!load_supported_swapchain_formats()) { + destroy_session(); + return false; + } + + return true; +} + +void OpenXRAPI::finish() { + destroy_session(); + + destroy_instance(); +} + +void OpenXRAPI::register_extension_wrapper(OpenXRExtensionWrapper *p_extension_wrapper) { + registered_extension_wrappers.push_back(p_extension_wrapper); +} + +Size2 OpenXRAPI::get_recommended_target_size() { + ERR_FAIL_NULL_V(view_configuration_views, Size2()); + + Size2 target_size; + + target_size.width = view_configuration_views[0].recommendedImageRectWidth; + target_size.height = view_configuration_views[0].recommendedImageRectHeight; + + return target_size; +} + +XRPose::TrackingConfidence OpenXRAPI::get_head_center(Transform3D &r_transform, Vector3 &r_linear_velocity, Vector3 &r_angular_velocity) { + XrResult result; + + ERR_FAIL_COND_V(!running, XRPose::XR_TRACKING_CONFIDENCE_NONE); + + // xrWaitFrame not run yet + if (frame_state.predictedDisplayTime == 0) { + return XRPose::XR_TRACKING_CONFIDENCE_NONE; + } + + // Get timing for the next frame, as that is the current frame we're processing + XrTime display_time = get_next_frame_time(); + + XrSpaceVelocity velocity = { + XR_TYPE_SPACE_VELOCITY, // type + nullptr, // next + 0, // velocityFlags + { 0.0, 0.0, 0.0 }, // linearVelocity + { 0.0, 0.0, 0.0 } // angularVelocity + }; + + XrSpaceLocation location = { + XR_TYPE_SPACE_LOCATION, // type + &velocity, // next + 0, // locationFlags + { + { 0.0, 0.0, 0.0, 0.0 }, // orientation + { 0.0, 0.0, 0.0 } // position + } // pose + }; + + result = xrLocateSpace(view_space, play_space, display_time, &location); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to locate view space in play space [", get_error_string(result), "]"); + return XRPose::XR_TRACKING_CONFIDENCE_NONE; + } + + XRPose::TrackingConfidence confidence = transform_from_location(location, r_transform); + parse_velocities(velocity, r_linear_velocity, r_angular_velocity); + + if (head_pose_confidence != confidence) { + // prevent error spam + head_pose_confidence = confidence; + if (head_pose_confidence == XRPose::XR_TRACKING_CONFIDENCE_NONE) { + print_line("OpenXR head space location not valid (check tracking?)"); +#ifdef DEBUG + } else if (head_pose_confidence == XRPose::XR_TRACKING_CONFIDENCE_LOW) { + print_line("OpenVR Head pose now tracking with low confidence"); + } else { + print_line("OpenVR Head pose now tracking with high confidence"); +#endif + } + } + + return confidence; +} + +bool OpenXRAPI::get_view_transform(uint32_t p_view, Transform3D &r_transform) { + ERR_FAIL_COND_V(!running, false); + + // xrWaitFrame not run yet + if (frame_state.predictedDisplayTime == 0) { + return false; + } + + // we don't have valid view info + if (views == NULL || !view_pose_valid) { + return false; + } + + // Note, the timing of this is set right before rendering, which is what we need here. + r_transform = transform_from_pose(views[p_view].pose); + + return true; +} + +bool OpenXRAPI::get_view_projection(uint32_t p_view, double p_z_near, double p_z_far, CameraMatrix &p_camera_matrix) { + ERR_FAIL_COND_V(!running, false); + ERR_FAIL_NULL_V(graphics_extension, false); + + // xrWaitFrame not run yet + if (frame_state.predictedDisplayTime == 0) { + return false; + } + + // we don't have valid view info + if (views == NULL || !view_pose_valid) { + return false; + } + + return graphics_extension->create_projection_fov(views[p_view].fov, p_z_near, p_z_far, p_camera_matrix); +} + +bool OpenXRAPI::poll_events() { + ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, false); + + XrEventDataBuffer runtimeEvent; + runtimeEvent.type = XR_TYPE_EVENT_DATA_BUFFER; + runtimeEvent.next = nullptr; + // runtimeEvent.varying = ... + + XrResult pollResult = xrPollEvent(instance, &runtimeEvent); + while (pollResult == XR_SUCCESS) { + bool handled = false; + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + handled |= wrapper->on_event_polled(runtimeEvent); + } + switch (runtimeEvent.type) { + // case XR_TYPE_EVENT_DATA_EVENTS_LOST: { + // } break; + // case XR_TYPE_EVENT_DATA_VISIBILITY_MASK_CHANGED_KHR: { + // } break; + // case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING: { + // } break; + case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: { + XrEventDataSessionStateChanged *event = (XrEventDataSessionStateChanged *)&runtimeEvent; + + session_state = event->state; + if (session_state >= XR_SESSION_STATE_MAX_ENUM) { + print_line("OpenXR EVENT: session state changed to UNKNOWN -", session_state); + } else { + print_line("OpenXR EVENT: session state changed to", OpenXRUtil::get_session_state_name(session_state)); + + switch (session_state) { + case XR_SESSION_STATE_IDLE: + on_state_idle(); + break; + case XR_SESSION_STATE_READY: + on_state_ready(); + break; + case XR_SESSION_STATE_SYNCHRONIZED: + on_state_synchronized(); + break; + case XR_SESSION_STATE_VISIBLE: + on_state_visible(); + break; + case XR_SESSION_STATE_FOCUSED: + on_state_focused(); + break; + case XR_SESSION_STATE_STOPPING: + on_state_stopping(); + break; + case XR_SESSION_STATE_LOSS_PENDING: + on_state_loss_pending(); + break; + case XR_SESSION_STATE_EXITING: + on_state_exiting(); + break; + default: + break; + } + } + } break; + // case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING: { + // } break; + // case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED: { + // } break; + default: + if (!handled) { + print_line("OpenXR Unhandled event type", OpenXRUtil::get_structure_type_name(runtimeEvent.type)); + } + break; + } + + runtimeEvent.type = XR_TYPE_EVENT_DATA_BUFFER; + pollResult = xrPollEvent(instance, &runtimeEvent); + } + + if (pollResult == XR_EVENT_UNAVAILABLE) { + // processed all events in the queue + return true; + } else { + ERR_FAIL_V_MSG(false, "OpenXR: Failed to poll events!"); + } +} + +bool OpenXRAPI::process() { + ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, false); + + if (!poll_events()) { + return false; + } + + if (!running) { + return false; + } + + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_process(); + } + + return true; +} + +bool OpenXRAPI::acquire_image(XrSwapchain p_swapchain, uint32_t &r_image_index) { + ERR_FAIL_COND_V(image_acquired, true); // this was not released when it should be, error out and re-use... + + XrResult result; + XrSwapchainImageAcquireInfo swapchain_image_acquire_info = { + XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO, // type + nullptr // next + }; + result = xrAcquireSwapchainImage(p_swapchain, &swapchain_image_acquire_info, &r_image_index); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to acquire swapchain image [", get_error_string(result), "]"); + return false; + } + + XrSwapchainImageWaitInfo swapchain_image_wait_info = { + XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO, // type + nullptr, // next + 17000000 // timeout in nanoseconds + }; + + result = xrWaitSwapchainImage(p_swapchain, &swapchain_image_wait_info); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to wait for swapchain image [", get_error_string(result), "]"); + return false; + } + + return true; +} + +bool OpenXRAPI::release_image(XrSwapchain p_swapchain) { + XrSwapchainImageReleaseInfo swapchain_image_release_info = { + XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO, // type + nullptr // next + }; + XrResult result = xrReleaseSwapchainImage(swapchain, &swapchain_image_release_info); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to release swapchain image! [", get_error_string(result), "]"); + return false; + } + + return true; +} + +void OpenXRAPI::pre_render() { + ERR_FAIL_COND(instance == XR_NULL_HANDLE); + + if (!running) { + return; + } + + // Waitframe does 2 important things in our process: + // 1) It provides us with predictive timing, telling us when OpenXR expects to display the frame we're about to commit + // 2) It will use the previous timing to pause our thread so that rendering starts as close to displaying as possible + // This must thus be called as close to when we start rendering as possible + XrFrameWaitInfo frame_wait_info = { XR_TYPE_FRAME_WAIT_INFO, nullptr }; + XrResult result = xrWaitFrame(session, &frame_wait_info, &frame_state); + if (XR_FAILED(result)) { + print_line("OpenXR: xrWaitFrame() was not successful [", get_error_string(result), "]"); + return; + } + + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + wrapper->on_pre_render(); + } + + // Get our view info for the frame we're about to render, note from the OpenXR manual: + // "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" + + // We're calling this "relatively" early, the positioning we're obtaining here will be used to do our frustum culling, + // occlusion culling, etc. There is however a technique that we can investigate in the future where after our entire + // Vulkan command buffer is build, but right before vkSubmitQueue is called, we call xrLocateViews one more time and + // update the view and projection matrix once more with a slightly more accurate predication and then submit the + // command queues. + + // That is not possible yet but worth investigating in the future. + + XrViewLocateInfo view_locate_info = { + XR_TYPE_VIEW_LOCATE_INFO, // type + nullptr, // next + view_configuration, // viewConfigurationType + frame_state.predictedDisplayTime, // displayTime + play_space // space + }; + XrViewState view_state = { + XR_TYPE_VIEW_STATE, // type + nullptr, // next + 0 // viewStateFlags + }; + uint32_t view_count_output; + result = xrLocateViews(session, &view_locate_info, &view_state, view_count, &view_count_output, views); + if (XR_FAILED(result)) { + print_line("OpenXR: Couldn't locate views [", get_error_string(result), "]"); + return; + } + + bool pose_valid = true; + for (uint64_t i = 0; i < view_count_output; i++) { + if ((view_state.viewStateFlags & XR_VIEW_STATE_ORIENTATION_VALID_BIT) == 0 || + (view_state.viewStateFlags & XR_VIEW_STATE_POSITION_VALID_BIT) == 0) { + pose_valid = false; + } + } + if (view_pose_valid != pose_valid) { + view_pose_valid = pose_valid; +#ifdef DEBUG + if (!view_pose_valid) { + print_line("OpenXR View pose became invalid"); + } else { + print_line("OpenXR View pose became valid"); + } +#endif + } + + // let's start our frame.. + XrFrameBeginInfo frame_begin_info = { + XR_TYPE_FRAME_BEGIN_INFO, // type + nullptr // next + }; + result = xrBeginFrame(session, &frame_begin_info); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to being frame [", get_error_string(result), "]"); + return; + } +} + +bool OpenXRAPI::pre_draw_viewport(RID p_render_target) { + if (!can_render()) { + return false; + } + + // TODO: at some point in time we may support multiple viewports in which case we need to handle that... + + return true; +} + +void OpenXRAPI::post_draw_viewport(RID p_render_target) { + if (!can_render()) { + return; + } + + // TODO: at some point in time we may support multiple viewports in which case we need to handle that... + + // TODO: if we can get PR 51179 to work properly we can change away from this approach and move this into get_external_texture or something + if (!image_acquired) { + if (!acquire_image(swapchain, image_index)) { + return; + } + image_acquired = true; + + // print_line("OpenXR: acquired image " + itos(image_index) + ", copying..."); + + // Copy our buffer into our swap chain (remove once PR 51179 is done) + graphics_extension->copy_render_target_to_image(p_render_target, swapchain_graphics_data, image_index); + } +}; + +void OpenXRAPI::end_frame() { + XrResult result; + + ERR_FAIL_COND(instance == XR_NULL_HANDLE); + + if (!running) { + return; + } + + if (frame_state.shouldRender && view_pose_valid && !image_acquired) { + print_line("OpenXR: No viewport was marked with use_xr, there is no rendered output!"); + } + + // must have: + // - shouldRender set to true + // - a valid view pose for projection_views[eye].pose to submit layer + // - an image to render + if (!frame_state.shouldRender || !view_pose_valid || !image_acquired) { + // submit 0 layers when we shouldn't render + XrFrameEndInfo frame_end_info = { + XR_TYPE_FRAME_END_INFO, // type + nullptr, // next + frame_state.predictedDisplayTime, // displayTime + XR_ENVIRONMENT_BLEND_MODE_OPAQUE, // environmentBlendMode + 0, // layerCount + nullptr // layers + }; + result = xrEndFrame(session, &frame_end_info); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to end frame! [", get_error_string(result), "]"); + return; + } + + // neither eye is rendered + return; + } + + // release our swapchain image if we acquired it + if (image_acquired) { + image_acquired = false; // whether we succeed or not, consider this released. + + release_image(swapchain); + } + + for (uint32_t eye = 0; eye < view_count; eye++) { + projection_views[eye].fov = views[eye].fov; + projection_views[eye].pose = views[eye].pose; + } + + Vector<const XrCompositionLayerBaseHeader *> layers_list; + + // Add composition layers from providers + for (OpenXRCompositionLayerProvider *provider : composition_layer_providers) { + XrCompositionLayerBaseHeader *layer = provider->get_composition_layer(); + if (layer) { + layers_list.push_back(layer); + } + } + + XrCompositionLayerProjection projection_layer = { + XR_TYPE_COMPOSITION_LAYER_PROJECTION, // type + nullptr, // next + layers_list.size() > 1 ? XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT | XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT : XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT, // layerFlags + play_space, // space + view_count, // viewCount + projection_views, // views + }; + layers_list.push_back((const XrCompositionLayerBaseHeader *)&projection_layer); + + XrFrameEndInfo frame_end_info = { + XR_TYPE_FRAME_END_INFO, // type + nullptr, // next + frame_state.predictedDisplayTime, // displayTime + XR_ENVIRONMENT_BLEND_MODE_OPAQUE, // environmentBlendMode + static_cast<uint32_t>(layers_list.size()), // layerCount + layers_list.ptr() // layers + }; + result = xrEndFrame(session, &frame_end_info); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to end frame! [", get_error_string(result), "]"); + return; + } +} + +OpenXRAPI::OpenXRAPI() { + // OpenXRAPI is only constructed if OpenXR is enabled. + // It will be constructed when the rendering device first accesses OpenXR (be it the Vulkan or OpenGL rendering system) + + if (Engine::get_singleton()->is_editor_hint()) { + // Enabled OpenXR in the editor? Adjust our settings for the editor + + } else { + // Load settings from project settings + int ff = GLOBAL_GET("xr/openxr/form_factor"); + switch (ff) { + case 0: { + form_factor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY; + } break; + case 1: { + form_factor = XR_FORM_FACTOR_HANDHELD_DISPLAY; + } break; + default: + break; + } + + int vc = GLOBAL_GET("xr/openxr/view_configuration"); + switch (vc) { + case 0: { + view_configuration = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_MONO; + } break; + case 1: { + view_configuration = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; + } break; + /* we don't support quad and observer configurations (yet) + case 2: { + view_configuration = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_QUAD_VARJO; + } break; + case 3: { + view_configuration = XR_VIEW_CONFIGURATION_TYPE_SECONDARY_MONO_FIRST_PERSON_OBSERVER_MSFT; + } break; + */ + default: + break; + } + + int rs = GLOBAL_GET("xr/openxr/reference_space"); + switch (rs) { + case 0: { + reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL; + } break; + case 1: { + reference_space = XR_REFERENCE_SPACE_TYPE_STAGE; + } break; + default: + break; + } + } + + // reset a few things that can't be done in our class definition + frame_state.predictedDisplayTime = 0; + frame_state.predictedDisplayPeriod = 0; + +#ifdef ANDROID_ENABLED + // our android wrapper will initialise our android loader at this point + register_extension_wrapper(memnew(OpenXRAndroidExtension(this))); +#endif +} + +OpenXRAPI::~OpenXRAPI() { + // cleanup our composition layer providers + for (OpenXRCompositionLayerProvider *provider : composition_layer_providers) { + memdelete(provider); + } + composition_layer_providers.clear(); + + // cleanup our extension wrappers + for (OpenXRExtensionWrapper *extension_wrapper : registered_extension_wrappers) { + memdelete(extension_wrapper); + } + registered_extension_wrappers.clear(); + + if (supported_extensions != nullptr) { + memfree(supported_extensions); + supported_extensions = nullptr; + } + + if (layer_properties != nullptr) { + memfree(layer_properties); + layer_properties = nullptr; + } +} + +Transform3D OpenXRAPI::transform_from_pose(const XrPosef &p_pose) { + Quaternion q(p_pose.orientation.x, p_pose.orientation.y, p_pose.orientation.z, p_pose.orientation.w); + Basis basis(q); + Vector3 origin(p_pose.position.x, p_pose.position.y, p_pose.position.z); + + return Transform3D(basis, origin); +} + +template <typename T> +XRPose::TrackingConfidence _transform_from_location(const T &p_location, Transform3D &r_transform) { + Basis basis; + Vector3 origin; + XRPose::TrackingConfidence confidence = XRPose::XR_TRACKING_CONFIDENCE_NONE; + const auto &pose = p_location.pose; + + // Check orientation + if (p_location.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) { + Quaternion q(pose.orientation.x, pose.orientation.y, pose.orientation.z, pose.orientation.w); + r_transform.basis = Basis(q); + + if (p_location.locationFlags & XR_SPACE_LOCATION_ORIENTATION_TRACKED_BIT) { + // Fully valid orientation, so either 3DOF or 6DOF tracking with high confidence so default to HIGH_TRACKING + confidence = XRPose::XR_TRACKING_CONFIDENCE_HIGH; + } else { + // Orientation is being tracked but we're using old/predicted data, so low tracking confidence + confidence = XRPose::XR_TRACKING_CONFIDENCE_LOW; + } + } else { + r_transform.basis = Basis(); + } + + // Check location + if (p_location.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) { + r_transform.origin = Vector3(pose.position.x, pose.position.y, pose.position.z); + + if (!(p_location.locationFlags & XR_SPACE_LOCATION_ORIENTATION_TRACKED_BIT)) { + // Location is being tracked but we're using old/predicted data, so low tracking confidence + confidence = XRPose::XR_TRACKING_CONFIDENCE_LOW; + } else if (confidence == XRPose::XR_TRACKING_CONFIDENCE_NONE) { + // Position tracking without orientation tracking? + confidence = XRPose::XR_TRACKING_CONFIDENCE_HIGH; + } + } else { + // No tracking or 3DOF I guess.. + r_transform.origin = Vector3(); + } + + return confidence; +} + +XRPose::TrackingConfidence OpenXRAPI::transform_from_location(const XrSpaceLocation &p_location, Transform3D &r_transform) { + return _transform_from_location(p_location, r_transform); +} + +XRPose::TrackingConfidence OpenXRAPI::transform_from_location(const XrHandJointLocationEXT &p_location, Transform3D &r_transform) { + return _transform_from_location(p_location, r_transform); +} + +void OpenXRAPI::parse_velocities(const XrSpaceVelocity &p_velocity, Vector3 &r_linear_velocity, Vector3 r_angular_velocity) { + if (p_velocity.velocityFlags & XR_SPACE_VELOCITY_LINEAR_VALID_BIT) { + XrVector3f linear_velocity = p_velocity.linearVelocity; + r_linear_velocity = Vector3(linear_velocity.x, linear_velocity.y, linear_velocity.z); + } else { + r_linear_velocity = Vector3(); + } + if (p_velocity.velocityFlags & XR_SPACE_VELOCITY_ANGULAR_VALID_BIT) { + XrVector3f angular_velocity = p_velocity.angularVelocity; + r_angular_velocity = Vector3(angular_velocity.x, angular_velocity.y, angular_velocity.z); + } else { + r_angular_velocity = Vector3(); + } +} + +RID OpenXRAPI::path_create(const String p_name) { + ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, RID()); + + // Encoding our path as a RID is probably overkill but it does future proof this + // Note that we only do this for XrPaths that we access from outside of this class! + + Path new_path; + + print_line("Parsing path ", p_name); + + XrResult result = xrStringToPath(instance, p_name.utf8().get_data(), &new_path.path); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to get path for ", p_name, "! [", get_error_string(result), "]"); + return RID(); + } + + return xr_path_owner.make_rid(new_path); +} + +void OpenXRAPI::path_free(RID p_path) { + Path *path = xr_path_owner.get_or_null(p_path); + ERR_FAIL_NULL(path); + + // there is nothing to free here + + xr_path_owner.free(p_path); +} + +RID OpenXRAPI::action_set_create(const String p_name, const String p_localized_name, const int p_priority) { + ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, RID()); + ActionSet action_set; + + action_set.is_attached = false; + + // create our action set... + XrActionSetCreateInfo action_set_info = { + XR_TYPE_ACTION_SET_CREATE_INFO, // type + nullptr, // next + "", // actionSetName + "", // localizedActionSetName + uint32_t(p_priority) // priority + }; + + copy_string_to_char_buffer(p_name, action_set_info.actionSetName, XR_MAX_ACTION_SET_NAME_SIZE); + copy_string_to_char_buffer(p_localized_name, action_set_info.localizedActionSetName, XR_MAX_LOCALIZED_ACTION_SET_NAME_SIZE); + + print_line("Creating action set ", action_set_info.actionSetName, " - ", action_set_info.localizedActionSetName, " (", itos(action_set_info.priority), ")"); + + XrResult result = xrCreateActionSet(instance, &action_set_info, &action_set.handle); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to create action set ", p_name, "! [", get_error_string(result), "]"); + return RID(); + } + + return action_set_owner.make_rid(action_set); +} + +bool OpenXRAPI::action_set_attach(RID p_action_set) { + ActionSet *action_set = action_set_owner.get_or_null(p_action_set); + ERR_FAIL_NULL_V(action_set, false); + + if (action_set->is_attached) { + // already attached + return true; + } + + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); + + // So according to the docs, once we attach our action set to our session it becomes read only.. + // https://www.khronos.org/registry/OpenXR/specs/1.0/man/html/xrAttachSessionActionSets.html + XrSessionActionSetsAttachInfo attach_info = { + XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO, // type + nullptr, // next + 1, // countActionSets, + &action_set->handle // actionSets + }; + + XrResult result = xrAttachSessionActionSets(session, &attach_info); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to attach action set! [", get_error_string(result), "]"); + return false; + } + + action_set->is_attached = true; + + return true; +} + +void OpenXRAPI::action_set_free(RID p_action_set) { + ActionSet *action_set = action_set_owner.get_or_null(p_action_set); + ERR_FAIL_NULL(action_set); + + if (action_set->handle != XR_NULL_HANDLE) { + xrDestroyActionSet(action_set->handle); + } + + action_set_owner.free(p_action_set); +} + +RID 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_toplevel_paths) { + ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, RID()); + + Action action; + + ActionSet *action_set = action_set_owner.get_or_null(p_action_set); + ERR_FAIL_NULL_V(action_set, RID()); + ERR_FAIL_COND_V(action_set->handle == XR_NULL_HANDLE, RID()); + + switch (p_action_type) { + case OpenXRAction::OPENXR_ACTION_BOOL: + action.action_type = XR_ACTION_TYPE_BOOLEAN_INPUT; + break; + case OpenXRAction::OPENXR_ACTION_FLOAT: + action.action_type = XR_ACTION_TYPE_FLOAT_INPUT; + break; + case OpenXRAction::OPENXR_ACTION_VECTOR2: + action.action_type = XR_ACTION_TYPE_VECTOR2F_INPUT; + break; + case OpenXRAction::OPENXR_ACTION_POSE: + action.action_type = XR_ACTION_TYPE_POSE_INPUT; + break; + case OpenXRAction::OPENXR_ACTION_HAPTIC: + action.action_type = XR_ACTION_TYPE_VIBRATION_OUTPUT; + break; + default: + ERR_FAIL_V(RID()); + break; + } + + Vector<XrPath> toplevel_paths; + for (int i = 0; i < p_toplevel_paths.size(); i++) { + Path *xr_path = xr_path_owner.get_or_null(p_toplevel_paths[i]); + if (xr_path != nullptr && xr_path->path != XR_NULL_PATH) { + PathWithSpace path_with_space = { + xr_path->path, // toplevel_path + XR_NULL_HANDLE, // space + false // was_location_valid + }; + action.toplevel_paths.push_back(path_with_space); + + toplevel_paths.push_back(xr_path->path); + } + } + + XrActionCreateInfo action_info = { + XR_TYPE_ACTION_CREATE_INFO, // type + nullptr, // next + "", // actionName + action.action_type, // actionType + uint32_t(toplevel_paths.size()), // countSubactionPaths + toplevel_paths.ptr(), // subactionPaths + "" // localizedActionName + }; + + copy_string_to_char_buffer(p_name, action_info.actionName, XR_MAX_ACTION_NAME_SIZE); + copy_string_to_char_buffer(p_localized_name, action_info.localizedActionName, XR_MAX_LOCALIZED_ACTION_NAME_SIZE); + + print_line("Creating action ", action_info.actionName, action_info.localizedActionName, action_info.countSubactionPaths); + + XrResult result = xrCreateAction(action_set->handle, &action_info, &action.handle); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to create action ", p_name, "! [", get_error_string(result), "]"); + return RID(); + } + + return action_owner.make_rid(action); +} + +void OpenXRAPI::action_free(RID p_action) { + Action *action = action_owner.get_or_null(p_action); + ERR_FAIL_NULL(action); + + if (action->handle != XR_NULL_HANDLE) { + xrDestroyAction(action->handle); + } + + action_owner.free(p_action); +} + +bool OpenXRAPI::suggest_bindings(const String p_interaction_profile, const Vector<Binding> p_bindings) { + ERR_FAIL_COND_V(instance == XR_NULL_HANDLE, false); + + XrPath interaction_profile; + Vector<XrActionSuggestedBinding> bindings; + + XrResult result = xrStringToPath(instance, p_interaction_profile.utf8().get_data(), &interaction_profile); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to get path for ", p_interaction_profile, "! [", get_error_string(result), "]"); + return false; + } + + for (int i = 0; i < p_bindings.size(); i++) { + XrActionSuggestedBinding binding; + + Action *action = action_owner.get_or_null(p_bindings[i].action); + if (action == nullptr || action->handle == XR_NULL_HANDLE) { + // just skip it + continue; + } + + binding.action = action->handle; + + result = xrStringToPath(instance, p_bindings[i].path.utf8().get_data(), &binding.binding); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to get path for ", p_bindings[i].path, "! [", get_error_string(result), "]"); + continue; + } + + bindings.push_back(binding); + } + + const XrInteractionProfileSuggestedBinding suggested_bindings = { + XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING, // type + nullptr, // next + interaction_profile, // interactionProfile + uint32_t(bindings.size()), // countSuggestedBindings + bindings.ptr() // suggestedBindings + }; + + result = xrSuggestInteractionProfileBindings(instance, &suggested_bindings); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to suggest bindings for ", p_interaction_profile, "! [", get_error_string(result), "]"); + // reporting is enough... + } + + return true; +} + +bool OpenXRAPI::sync_action_sets(const Vector<RID> p_active_sets) { + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); + + if (!running) { + return false; + } + + Vector<XrActiveActionSet> active_sets; + for (int i = 0; i < p_active_sets.size(); i++) { + ActionSet *action_set = action_set_owner.get_or_null(p_active_sets[i]); + if (action_set && action_set->handle != XR_NULL_HANDLE) { + XrActiveActionSet aset; + aset.actionSet = action_set->handle; + aset.subactionPath = XR_NULL_PATH; + active_sets.push_back(aset); + } + } + + ERR_FAIL_COND_V(active_sets.size() == 0, false); + + XrActionsSyncInfo sync_info = { + XR_TYPE_ACTIONS_SYNC_INFO, // type + nullptr, // next + uint32_t(active_sets.size()), // countActiveActionSets + active_sets.ptr() // activeActionSets + }; + + XrResult result = xrSyncActions(session, &sync_info); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to sync active action sets! [", get_error_string(result), "]"); + return false; + } + + return true; +} + +bool OpenXRAPI::get_action_bool(RID p_action, RID p_path) { + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); + Action *action = action_owner.get_or_null(p_action); + ERR_FAIL_NULL_V(action, false); + Path *path = xr_path_owner.get_or_null(p_path); + ERR_FAIL_NULL_V(path, false); + + if (!running) { + return false; + } + + ERR_FAIL_COND_V(action->action_type != XR_ACTION_TYPE_BOOLEAN_INPUT, false); + + XrActionStateGetInfo get_info = { + XR_TYPE_ACTION_STATE_GET_INFO, // type + nullptr, // next + action->handle, // action + path->path // subactionPath + }; + + XrActionStateBoolean result_state; + result_state.type = XR_TYPE_ACTION_STATE_BOOLEAN, + result_state.next = nullptr; + XrResult result = xrGetActionStateBoolean(session, &get_info, &result_state); + if (XR_FAILED(result)) { + print_line("OpenXR: couldn't get action boolean! [", get_error_string(result), "]"); + return false; + } + + return result_state.isActive && result_state.currentState; +} + +float OpenXRAPI::get_action_float(RID p_action, RID p_path) { + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, 0.0); + Action *action = action_owner.get_or_null(p_action); + ERR_FAIL_NULL_V(action, 0.0); + Path *path = xr_path_owner.get_or_null(p_path); + ERR_FAIL_NULL_V(path, 0.0); + + if (!running) { + return 0.0; + } + + ERR_FAIL_COND_V(action->action_type != XR_ACTION_TYPE_FLOAT_INPUT, 0.0); + + XrActionStateGetInfo get_info = { + XR_TYPE_ACTION_STATE_GET_INFO, // type + nullptr, // next + action->handle, // action + path->path // subactionPath + }; + + XrActionStateFloat result_state; + result_state.type = XR_TYPE_ACTION_STATE_FLOAT, + result_state.next = nullptr; + XrResult result = xrGetActionStateFloat(session, &get_info, &result_state); + if (XR_FAILED(result)) { + print_line("OpenXR: couldn't get action float! [", get_error_string(result), "]"); + return 0.0; + } + + return result_state.isActive ? result_state.currentState : 0.0; +} + +Vector2 OpenXRAPI::get_action_vector2(RID p_action, RID p_path) { + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, Vector2()); + Action *action = action_owner.get_or_null(p_action); + ERR_FAIL_NULL_V(action, Vector2()); + Path *path = xr_path_owner.get_or_null(p_path); + ERR_FAIL_NULL_V(path, Vector2()); + + if (!running) { + return Vector2(); + } + + ERR_FAIL_COND_V(action->action_type != XR_ACTION_TYPE_VECTOR2F_INPUT, Vector2()); + + XrActionStateGetInfo get_info = { + XR_TYPE_ACTION_STATE_GET_INFO, // type + nullptr, // next + action->handle, // action + path->path // subactionPath + }; + + XrActionStateVector2f result_state; + result_state.type = XR_TYPE_ACTION_STATE_VECTOR2F, + result_state.next = nullptr; + XrResult result = xrGetActionStateVector2f(session, &get_info, &result_state); + if (XR_FAILED(result)) { + print_line("OpenXR: couldn't get action vector2! [", get_error_string(result), "]"); + return Vector2(); + } + + return result_state.isActive ? Vector2(result_state.currentState.x, result_state.currentState.y) : Vector2(); +} + +XRPose::TrackingConfidence OpenXRAPI::get_action_pose(RID p_action, RID p_path, Transform3D &r_transform, Vector3 &r_linear_velocity, Vector3 &r_angular_velocity) { + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, XRPose::XR_TRACKING_CONFIDENCE_NONE); + Action *action = action_owner.get_or_null(p_action); + ERR_FAIL_NULL_V(action, XRPose::XR_TRACKING_CONFIDENCE_NONE); + Path *path = xr_path_owner.get_or_null(p_path); + ERR_FAIL_NULL_V(path, XRPose::XR_TRACKING_CONFIDENCE_NONE); + + if (!running) { + return XRPose::XR_TRACKING_CONFIDENCE_NONE; + } + + ERR_FAIL_COND_V(action->action_type != XR_ACTION_TYPE_POSE_INPUT, XRPose::XR_TRACKING_CONFIDENCE_NONE); + + uint64_t index = 0xFFFFFFFF; + uint64_t size = uint64_t(action->toplevel_paths.size()); + for (uint64_t i = 0; i < size && index == 0xFFFFFFFF; i++) { + if (action->toplevel_paths[i].toplevel_path == path->path) { + index = i; + } + } + + if (index == 0xFFFFFFFF) { + // couldn't find it? + return XRPose::XR_TRACKING_CONFIDENCE_NONE; + } + + if (action->toplevel_paths[index].space == XR_NULL_HANDLE) { + // if this is a pose we need to define spaces + + XrActionSpaceCreateInfo action_space_info = { + XR_TYPE_ACTION_SPACE_CREATE_INFO, // type + nullptr, // next + action->handle, // action + action->toplevel_paths[index].toplevel_path, // subactionPath + { + { 0.0, 0.0, 0.0, 1.0 }, // orientation + { 0.0, 0.0, 0.0 } // position + } // poseInActionSpace + }; + + XrSpace space; + XrResult result = xrCreateActionSpace(session, &action_space_info, &space); + if (XR_FAILED(result)) { + print_line("OpenXR: couldn't create action space! [", get_error_string(result), "]"); + return XRPose::XR_TRACKING_CONFIDENCE_NONE; + } + + action->toplevel_paths.ptrw()[index].space = space; + } + + XrTime display_time = get_next_frame_time(); + + XrSpaceVelocity velocity = { + XR_TYPE_SPACE_VELOCITY, // type + nullptr, // next + 0, // velocityFlags + { 0.0, 0.0, 0.0 }, // linearVelocity + { 0.0, 0.0, 0.0 } // angularVelocity + }; + + XrSpaceLocation location = { + XR_TYPE_SPACE_LOCATION, // type + &velocity, // next + 0, // locationFlags + { + { 0.0, 0.0, 0.0, 0.0 }, // orientation + { 0.0, 0.0, 0.0 } // position + } // pose + }; + + XrResult result = xrLocateSpace(action->toplevel_paths[index].space, play_space, display_time, &location); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to locate space! [", get_error_string(result), "]"); + return XRPose::XR_TRACKING_CONFIDENCE_NONE; + } + + XRPose::TrackingConfidence confidence = transform_from_location(location, r_transform); + parse_velocities(velocity, r_linear_velocity, r_angular_velocity); + + return confidence; +} + +bool OpenXRAPI::trigger_haptic_pulse(RID p_action, RID p_path, float p_frequency, float p_amplitude, XrDuration p_duration_ns) { + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); + Action *action = action_owner.get_or_null(p_action); + ERR_FAIL_NULL_V(action, false); + Path *path = xr_path_owner.get_or_null(p_path); + ERR_FAIL_NULL_V(path, false); + + if (!running) { + return false; + } + + ERR_FAIL_COND_V(action->action_type != XR_ACTION_TYPE_VIBRATION_OUTPUT, false); + + XrHapticActionInfo action_info = { + XR_TYPE_HAPTIC_ACTION_INFO, // type + nullptr, // next + action->handle, // action + path->path // subactionPath + }; + + XrHapticVibration vibration = { + XR_TYPE_HAPTIC_VIBRATION, // type + nullptr, // next + p_duration_ns, // duration + p_frequency, // frequency + p_amplitude, // amplitude + }; + + XrResult result = xrApplyHapticFeedback(session, &action_info, (const XrHapticBaseHeader *)&vibration); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to apply haptic feedback! [", get_error_string(result), "]"); + return false; + } + + return true; +} diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h new file mode 100644 index 0000000000..33b503543a --- /dev/null +++ b/modules/openxr/openxr_api.h @@ -0,0 +1,261 @@ +/*************************************************************************/ +/* openxr_api.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef OPENXR_DRIVER_H +#define OPENXR_DRIVER_H + +#include "core/error/error_macros.h" +#include "core/math/camera_matrix.h" +#include "core/math/transform_3d.h" +#include "core/math/vector2.h" +#include "core/os/memory.h" +#include "core/string/ustring.h" +#include "core/templates/map.h" +#include "core/templates/rid_owner.h" +#include "core/templates/vector.h" +#include "servers/xr/xr_pose.h" + +#include "thirdparty/openxr/src/common/xr_linear.h" +#include <openxr/openxr.h> + +#include "action_map/openxr_action.h" + +#include "extensions/openxr_composition_layer_provider.h" +#include "extensions/openxr_extension_wrapper.h" + +// Note, OpenXR code that we wrote for our plugin makes use of C++20 notation for initialising structs which ensures zeroing out unspecified members. +// Godot is currently restricted to C++17 which doesn't allow this notation. Make sure critical fields are set. + +// forward declarations, we don't want to include these fully +class OpenXRVulkanExtension; + +class OpenXRAPI { +private: + // our singleton + static OpenXRAPI *singleton; + + // layers + uint32_t num_layer_properties = 0; + XrApiLayerProperties *layer_properties = nullptr; + + // extensions + uint32_t num_supported_extensions = 0; + XrExtensionProperties *supported_extensions = nullptr; + Vector<OpenXRExtensionWrapper *> registered_extension_wrappers; + Vector<const char *> enabled_extensions; + + // composition layer providers + Vector<OpenXRCompositionLayerProvider *> composition_layer_providers; + + // view configuration + uint32_t num_view_configuration_types = 0; + XrViewConfigurationType *supported_view_configuration_types = nullptr; + + // reference spaces + uint32_t num_reference_spaces = 0; + XrReferenceSpaceType *supported_reference_spaces = nullptr; + + // swapchains (note these are platform dependent) + uint32_t num_swapchain_formats = 0; + int64_t *supported_swapchain_formats = nullptr; + + // configuration + XrFormFactor form_factor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY; + XrViewConfigurationType view_configuration = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; + XrReferenceSpaceType reference_space = XR_REFERENCE_SPACE_TYPE_STAGE; + XrEnvironmentBlendMode environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE; + + // state + XrInstance instance = XR_NULL_HANDLE; + XrSystemId system_id; + String system_name; + uint32_t vendor_id; + XrSystemTrackingProperties tracking_properties; + XrSession session = XR_NULL_HANDLE; + XrSessionState session_state = XR_SESSION_STATE_UNKNOWN; + bool running = false; + XrFrameState frame_state = { XR_TYPE_FRAME_STATE, NULL, 0, 0, false }; + + OpenXRGraphicsExtensionWrapper *graphics_extension = nullptr; + XrSystemGraphicsProperties graphics_properties; + void *swapchain_graphics_data = nullptr; + uint32_t image_index = 0; + bool image_acquired = false; + + uint32_t view_count = 0; + XrViewConfigurationView *view_configuration_views = nullptr; + XrView *views = nullptr; + XrCompositionLayerProjectionView *projection_views = nullptr; + XrSwapchain swapchain = XR_NULL_HANDLE; + + XrSpace play_space = XR_NULL_HANDLE; + XrSpace view_space = XR_NULL_HANDLE; + bool view_pose_valid = false; + XRPose::TrackingConfidence head_pose_confidence = XRPose::XR_TRACKING_CONFIDENCE_NONE; + + bool load_layer_properties(); + bool load_supported_extensions(); + bool is_extension_supported(const char *p_extension) const; + + // instance + bool create_instance(); + bool get_system_info(); + bool load_supported_view_configuration_types(); + bool is_view_configuration_supported(XrViewConfigurationType p_configuration_type) const; + bool load_supported_view_configuration_views(XrViewConfigurationType p_configuration_type); + void destroy_instance(); + + // session + bool create_session(); + bool load_supported_reference_spaces(); + bool is_reference_space_supported(XrReferenceSpaceType p_reference_space); + bool setup_spaces(); + bool load_supported_swapchain_formats(); + bool is_swapchain_format_supported(int64_t p_swapchain_format); + bool create_main_swapchain(); + void destroy_session(); + + // swapchains + bool create_swapchain(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); + bool acquire_image(XrSwapchain p_swapchain, uint32_t &r_image_index); + bool release_image(XrSwapchain p_swapchain); + + // action map + struct Path { + XrPath path; + }; + RID_Owner<Path, true> xr_path_owner; + + struct ActionSet { + bool is_attached; + XrActionSet handle; + }; + RID_Owner<ActionSet, true> action_set_owner; + + struct PathWithSpace { + XrPath toplevel_path; + XrSpace space; + bool was_location_valid; + }; + + struct Action { + XrActionType action_type; + Vector<PathWithSpace> toplevel_paths; + XrAction handle; + }; + RID_Owner<Action, true> action_owner; + + // state changes + bool poll_events(); + bool on_state_idle(); + bool on_state_ready(); + bool on_state_synchronized(); + bool on_state_visible(); + bool on_state_focused(); + bool on_state_stopping(); + bool on_state_loss_pending(); + bool on_state_exiting(); + + // convencience + void copy_string_to_char_buffer(const String p_string, char *p_buffer, int p_buffer_len); + +protected: + friend class OpenXRVulkanExtension; + + XrInstance get_instance() const { return instance; }; + XrSystemId get_system_id() const { return system_id; }; + XrSession get_session() const { return session; }; + + // helper method to convert an XrPosef to a Transform3D + Transform3D transform_from_pose(const XrPosef &p_pose); + + // helper method to get a valid Transform3D from an openxr space location + XRPose::TrackingConfidence transform_from_location(const XrSpaceLocation &p_location, Transform3D &r_transform); + XRPose::TrackingConfidence transform_from_location(const XrHandJointLocationEXT &p_location, Transform3D &r_transform); + void parse_velocities(const XrSpaceVelocity &p_velocity, Vector3 &r_linear_velocity, Vector3 r_angular_velocity); + +public: + static void setup_global_defs(); + static bool openxr_is_enabled(); + static OpenXRAPI *get_singleton(); + + String get_error_string(XrResult result); + String get_swapchain_format_name(int64_t p_swapchain_format) const; + + void register_extension_wrapper(OpenXRExtensionWrapper *p_extension_wrapper); + + bool is_initialized(); + bool is_running(); + bool initialise(const String &p_rendering_driver); + bool initialise_session(); + void finish(); + + XrTime get_next_frame_time() { return frame_state.predictedDisplayTime + frame_state.predictedDisplayPeriod; }; + bool can_render() { return instance != XR_NULL_HANDLE && session != XR_NULL_HANDLE && running && view_pose_valid && frame_state.shouldRender; }; + + Size2 get_recommended_target_size(); + XRPose::TrackingConfidence get_head_center(Transform3D &r_transform, Vector3 &r_linear_velocity, Vector3 &r_angular_velocity); + bool get_view_transform(uint32_t p_view, Transform3D &r_transform); + bool get_view_projection(uint32_t p_view, double p_z_near, double p_z_far, CameraMatrix &p_camera_matrix); + bool process(); + + void pre_render(); + bool pre_draw_viewport(RID p_render_target); + void post_draw_viewport(RID p_render_target); + void end_frame(); + + // action map + String get_default_action_map_resource_name(); + RID path_create(const String p_name); + void path_free(RID p_path); + RID action_set_create(const String p_name, const String p_localized_name, const int p_priority); + bool action_set_attach(RID p_action_set); + void action_set_free(RID p_action_set); + RID action_create(RID p_action_set, const String p_name, const String p_localized_name, OpenXRAction::ActionType p_action_type, const Vector<RID> &p_toplevel_paths); + void action_free(RID p_action); + + struct Binding { + RID action; + String path; + }; + bool suggest_bindings(const String p_interaction_profile, const Vector<Binding> p_bindings); + + bool sync_action_sets(const Vector<RID> p_active_sets); + bool get_action_bool(RID p_action, RID p_path); + float get_action_float(RID p_action, RID p_path); + Vector2 get_action_vector2(RID p_action, RID p_path); + XRPose::TrackingConfidence get_action_pose(RID p_action, RID p_path, Transform3D &r_transform, Vector3 &r_linear_velocity, Vector3 &r_angular_velocity); + bool trigger_haptic_pulse(RID p_action, RID p_path, float p_frequency, float p_amplitude, XrDuration p_duration_ns); + + OpenXRAPI(); + ~OpenXRAPI(); +}; + +#endif // !OPENXR_DRIVER_H diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp new file mode 100644 index 0000000000..394f634687 --- /dev/null +++ b/modules/openxr/openxr_interface.cpp @@ -0,0 +1,663 @@ +/*************************************************************************/ +/* openxr_interface.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "openxr_interface.h" + +#include "core/io/resource_loader.h" +#include "core/io/resource_saver.h" +#include "servers/rendering/rendering_server_globals.h" + +void OpenXRInterface::_bind_methods() { + // todo +} + +StringName OpenXRInterface::get_name() const { + return StringName("OpenXR"); +}; + +uint32_t OpenXRInterface::get_capabilities() const { + return XRInterface::XR_VR + XRInterface::XR_STEREO; +}; + +XRInterface::TrackingStatus OpenXRInterface::get_tracking_status() const { + return tracking_state; +} + +void OpenXRInterface::_load_action_map() { + ERR_FAIL_NULL(openxr_api); + + // This may seem a bit duplicitous to a little bit of background info here. + // OpenXRActionMap (with all its sub resource classes) is a class that allows us to configure and store an action map in. + // This gives the user the ability to edit the action map in a UI and customise the actions. + // OpenXR however requires us to submit an action map and it takes over from that point and we can no longer change it. + // This system does that push and we store the info needed to then work with this action map going forward. + + // Within our openxr device we maintain a number of classes that wrap the relevant OpenXR objects for this. + // Within OpenXRInterface we have a few internal classes that keep track of what we've created. + // This allow us to process the relevant actions each frame. + + // just in case clean up + free_action_sets(); + free_trackers(); + + Ref<OpenXRActionMap> action_map; + if (Engine::get_singleton()->is_editor_hint()) { +#ifdef TOOLS_ENABLED + action_map.instantiate(); + action_map->create_editor_action_sets(); +#endif + } else { + String default_tres_name = openxr_api->get_default_action_map_resource_name(); + + // Check if we can load our default + if (ResourceLoader::exists(default_tres_name)) { + action_map = ResourceLoader::load(default_tres_name); + } + + // Check if we need to create default action set + if (action_map.is_null()) { + action_map.instantiate(); + action_map->create_default_action_sets(); +#ifdef TOOLS_ENABLED + // Save our action sets so our user can + action_map->set_path(default_tres_name, true); + ResourceSaver::save(default_tres_name, action_map); +#endif + } + } + + // process our action map + if (action_map.is_valid()) { + Map<Ref<OpenXRAction>, RID> action_rids; + + Array action_sets = action_map->get_action_sets(); + for (int i = 0; i < action_sets.size(); i++) { + // Create our action set + Ref<OpenXRActionSet> xr_action_set = action_sets[i]; + ActionSet *action_set = create_action_set(xr_action_set->get_name(), xr_action_set->get_localized_name(), xr_action_set->get_priority()); + if (!action_set) { + continue; + } + + // Now create our actions for these + Array actions = xr_action_set->get_actions(); + for (int j = 0; j < actions.size(); j++) { + Ref<OpenXRAction> xr_action = actions[j]; + + PackedStringArray toplevel_paths = xr_action->get_toplevel_paths(); + Vector<RID> toplevel_rids; + Vector<Tracker *> trackers; + + for (int k = 0; k < toplevel_paths.size(); k++) { + Tracker *tracker = get_tracker(toplevel_paths[k]); + if (tracker) { + toplevel_rids.push_back(tracker->path_rid); + trackers.push_back(tracker); + } + } + + Action *action = create_action(action_set, xr_action->get_name(), xr_action->get_localized_name(), xr_action->get_action_type(), toplevel_rids); + if (action) { + // we link our actions back to our trackers so we know which actions to check when we're processing our trackers + for (int t = 0; t < trackers.size(); t++) { + link_action_to_tracker(trackers[t], action); + } + + // add this to our map for creating our interaction profiles + action_rids[xr_action] = action->action_rid; + } + } + } + + // now do our suggestions + Array interaction_profiles = action_map->get_interaction_profiles(); + for (int i = 0; i < interaction_profiles.size(); i++) { + Vector<OpenXRAPI::Binding> bindings; + Ref<OpenXRInteractionProfile> xr_interaction_profile = interaction_profiles[i]; + + Array xr_bindings = xr_interaction_profile->get_bindings(); + for (int j = 0; j < xr_bindings.size(); j++) { + Ref<OpenXRIPBinding> xr_binding = xr_bindings[j]; + Ref<OpenXRAction> xr_action = xr_binding->get_action(); + OpenXRAPI::Binding binding; + + if (action_rids.has(xr_action)) { + binding.action = action_rids[xr_action]; + } else { + print_line("Action ", xr_action->get_name(), " isn't part of an action set!"); + continue; + } + + PackedStringArray xr_paths = xr_binding->get_paths(); + for (int k = 0; k < xr_paths.size(); k++) { + binding.path = xr_paths[k]; + bindings.push_back(binding); + } + } + + openxr_api->suggest_bindings(xr_interaction_profile->get_interaction_profile_path(), bindings); + } + } +} + +OpenXRInterface::ActionSet *OpenXRInterface::create_action_set(const String &p_action_set_name, const String &p_localized_name, const int p_priority) { + ERR_FAIL_NULL_V(openxr_api, nullptr); + + // find if it already exists + for (int i = 0; i < action_sets.size(); i++) { + if (action_sets[i]->action_set_name == p_action_set_name) { + // already exists in this set + return nullptr; + } + } + + ActionSet *action_set = memnew(ActionSet); + action_set->action_set_name = p_action_set_name; + action_set->is_active = true; + action_set->action_set_rid = openxr_api->action_set_create(p_action_set_name, p_localized_name, p_priority); + action_sets.push_back(action_set); + + return action_set; +} + +void OpenXRInterface::free_action_sets() { + ERR_FAIL_NULL(openxr_api); + + for (int i = 0; i < action_sets.size(); i++) { + ActionSet *action_set = action_sets[i]; + + openxr_api->path_free(action_set->action_set_rid); + free_actions(action_set); + + memfree(action_set); + } + action_sets.clear(); +} + +OpenXRInterface::Action *OpenXRInterface::create_action(ActionSet *p_action_set, const String &p_action_name, const String &p_localized_name, OpenXRAction::ActionType p_action_type, const Vector<RID> p_toplevel_paths) { + ERR_FAIL_NULL_V(openxr_api, nullptr); + + for (int i = 0; i < p_action_set->actions.size(); i++) { + if (p_action_set->actions[i]->action_name == p_action_name) { + // already exists in this set + return nullptr; + } + } + + Action *action = memnew(Action); + action->action_name = p_action_name; + action->action_type = p_action_type; + action->action_rid = openxr_api->action_create(p_action_set->action_set_rid, p_action_name, p_localized_name, p_action_type, p_toplevel_paths); + p_action_set->actions.push_back(action); + + return action; +} + +OpenXRInterface::Action *OpenXRInterface::find_action(const String &p_action_name) { + // We just find the first action by this name + + for (int i = 0; i < action_sets.size(); i++) { + for (int j = 0; j < action_sets[i]->actions.size(); j++) { + if (action_sets[i]->actions[j]->action_name == p_action_name) { + return action_sets[i]->actions[j]; + } + } + } + + // not found + return nullptr; +} + +void OpenXRInterface::free_actions(ActionSet *p_action_set) { + ERR_FAIL_NULL(openxr_api); + + for (int i = 0; i < p_action_set->actions.size(); i++) { + Action *action = p_action_set->actions[i]; + + openxr_api->action_free(action->action_rid); + + memdelete(action); + } + p_action_set->actions.clear(); +} + +OpenXRInterface::Tracker *OpenXRInterface::get_tracker(const String &p_path_name) { + XRServer *xr_server = XRServer::get_singleton(); + ERR_FAIL_NULL_V(xr_server, nullptr); + ERR_FAIL_NULL_V(openxr_api, nullptr); + + Tracker *tracker = nullptr; + for (int i = 0; i < trackers.size(); i++) { + tracker = trackers[i]; + if (tracker->path_name == p_path_name) { + return tracker; + } + } + + // create our positional tracker + Ref<XRPositionalTracker> positional_tracker; + positional_tracker.instantiate(); + + // We have standardised some names to make things nicer to the user so lets recognise the toplevel paths related to these. + if (p_path_name == "/user/hand/left") { + positional_tracker->set_tracker_type(XRServer::TRACKER_CONTROLLER); + positional_tracker->set_tracker_name("left_hand"); + positional_tracker->set_tracker_desc("Left hand controller"); + positional_tracker->set_tracker_hand(XRPositionalTracker::TRACKER_HAND_LEFT); + } else if (p_path_name == "/user/hand/right") { + positional_tracker->set_tracker_type(XRServer::TRACKER_CONTROLLER); + positional_tracker->set_tracker_name("right_hand"); + positional_tracker->set_tracker_desc("Right hand controller"); + positional_tracker->set_tracker_hand(XRPositionalTracker::TRACKER_HAND_RIGHT); + } else { + positional_tracker->set_tracker_type(XRServer::TRACKER_CONTROLLER); + positional_tracker->set_tracker_name(p_path_name); + positional_tracker->set_tracker_desc(p_path_name); + } + xr_server->add_tracker(positional_tracker); + + // create a new entry + tracker = memnew(Tracker); + tracker->path_name = p_path_name; + tracker->path_rid = openxr_api->path_create(p_path_name); + tracker->positional_tracker = positional_tracker; + trackers.push_back(tracker); + + return tracker; +} + +OpenXRInterface::Tracker *OpenXRInterface::find_tracker(const String &p_positional_tracker_name) { + for (int i = 0; i < trackers.size(); i++) { + Tracker *tracker = trackers[i]; + if (tracker->positional_tracker.is_valid() && tracker->positional_tracker->get_tracker_name() == p_positional_tracker_name) { + return tracker; + } + } + + return nullptr; +} + +void OpenXRInterface::link_action_to_tracker(Tracker *p_tracker, Action *p_action) { + if (p_tracker->actions.find(p_action) == -1) { + p_tracker->actions.push_back(p_action); + } +} + +void OpenXRInterface::handle_tracker(Tracker *p_tracker) { + ERR_FAIL_NULL(openxr_api); + ERR_FAIL_COND(p_tracker->positional_tracker.is_null()); + + // handle all the actions + for (int i = 0; i < p_tracker->actions.size(); i++) { + Action *action = p_tracker->actions[i]; + switch (action->action_type) { + case OpenXRAction::OPENXR_ACTION_BOOL: { + bool pressed = openxr_api->get_action_bool(action->action_rid, p_tracker->path_rid); + p_tracker->positional_tracker->set_input(action->action_name, Variant(pressed)); + } break; + case OpenXRAction::OPENXR_ACTION_FLOAT: { + real_t value = openxr_api->get_action_float(action->action_rid, p_tracker->path_rid); + p_tracker->positional_tracker->set_input(action->action_name, Variant(value)); + } break; + case OpenXRAction::OPENXR_ACTION_VECTOR2: { + Vector2 value = openxr_api->get_action_vector2(action->action_rid, p_tracker->path_rid); + p_tracker->positional_tracker->set_input(action->action_name, Variant(value)); + } break; + case OpenXRAction::OPENXR_ACTION_POSE: { + Transform3D transform; + Vector3 linear, angular; + XRPose::TrackingConfidence confidence = openxr_api->get_action_pose(action->action_rid, p_tracker->path_rid, transform, linear, angular); + if (confidence != XRPose::XR_TRACKING_CONFIDENCE_NONE) { + String name; + // We can't have dual action names in OpenXR hence we added _pose, but default, aim and grip and default pose action names in Godot so rename them on the tracker. + // NOTE need to decide on whether we should keep the naming convention or rename it on Godots side + if (action->action_name == "default_pose") { + name = "default"; + } else if (action->action_name == "aim_pose") { + name = "aim"; + } else if (action->action_name == "grip_pose") { + name = "grip"; + } else { + name = action->action_name; + } + p_tracker->positional_tracker->set_pose(name, transform, linear, angular, confidence); + } + } break; + default: { + // not yet supported + } break; + } + } +} + +void OpenXRInterface::trigger_haptic_pulse(const String &p_action_name, const StringName &p_tracker_name, double p_frequency, double p_amplitude, double p_duration_sec, double p_delay_sec) { + ERR_FAIL_NULL(openxr_api); + Action *action = find_action(p_action_name); + ERR_FAIL_NULL(action); + Tracker *tracker = find_tracker(p_tracker_name); + ERR_FAIL_NULL(tracker); + + // TODO OpenXR does not support delay, so we may need to add support for that somehow... + + XrDuration duration = XrDuration(p_duration_sec * 1000000000.0); // seconds -> nanoseconds + + openxr_api->trigger_haptic_pulse(action->action_rid, tracker->path_rid, p_frequency, p_amplitude, duration); +} + +void OpenXRInterface::free_trackers() { + XRServer *xr_server = XRServer::get_singleton(); + ERR_FAIL_NULL(xr_server); + ERR_FAIL_NULL(openxr_api); + + for (int i = 0; i < trackers.size(); i++) { + Tracker *tracker = trackers[i]; + + openxr_api->path_free(tracker->path_rid); + xr_server->remove_tracker(tracker->positional_tracker); + tracker->positional_tracker.unref(); + + memdelete(tracker); + } + trackers.clear(); +} + +bool OpenXRInterface::initialise_on_startup() const { + if (openxr_api == nullptr) { + return false; + } else if (!openxr_api->is_initialized()) { + return false; + } else { + return true; + } +} + +bool OpenXRInterface::is_initialized() const { + return initialized; +}; + +bool OpenXRInterface::initialize() { + XRServer *xr_server = XRServer::get_singleton(); + ERR_FAIL_NULL_V(xr_server, false); + + if (openxr_api == nullptr) { + return false; + } else if (!openxr_api->is_initialized()) { + return false; + } else if (initialized) { + return true; + } + + // load up our action sets before setting up our session, note that our profiles are suggestions, OpenXR takes ownership of (re)binding + _load_action_map(); + + if (!openxr_api->initialise_session()) { + return false; + } + + // we must create a tracker for our head + head.instantiate(); + head->set_tracker_type(XRServer::TRACKER_HEAD); + head->set_tracker_name("head"); + head->set_tracker_desc("Players head"); + xr_server->add_tracker(head); + + // attach action sets + for (int i = 0; i < action_sets.size(); i++) { + openxr_api->action_set_attach(action_sets[i]->action_set_rid); + } + + // make this our primary interface + xr_server->set_primary_interface(this); + + initialized = true; + + return initialized; +} + +void OpenXRInterface::uninitialize() { + // Our OpenXR driver will clean itself up properly when Godot exits, so we just do some basic stuff here + + // end the session if we need to? + + // cleanup stuff + free_action_sets(); + free_trackers(); + + XRServer *xr_server = XRServer::get_singleton(); + if (xr_server) { + if (head.is_valid()) { + xr_server->remove_tracker(head); + + head.unref(); + } + } + + initialized = false; +} + +bool OpenXRInterface::supports_play_area_mode(XRInterface::PlayAreaMode p_mode) { + return false; +} + +XRInterface::PlayAreaMode OpenXRInterface::get_play_area_mode() const { + return XRInterface::XR_PLAY_AREA_UNKNOWN; +} + +bool OpenXRInterface::set_play_area_mode(XRInterface::PlayAreaMode p_mode) { + return false; +} + +Size2 OpenXRInterface::get_render_target_size() { + if (openxr_api == nullptr) { + return Size2(); + } else { + return openxr_api->get_recommended_target_size(); + } +} + +uint32_t OpenXRInterface::get_view_count() { + // TODO set this based on our configuration + return 2; +} + +void OpenXRInterface::_set_default_pos(Transform3D &p_transform, double p_world_scale, uint64_t p_eye) { + p_transform = Transform3D(); + + // if we're not tracking, don't put our head on the floor... + p_transform.origin.y = 1.5 * p_world_scale; + + // overkill but.. + if (p_eye == 1) { + p_transform.origin.x = 0.03 * p_world_scale; + } else if (p_eye == 2) { + p_transform.origin.x = -0.03 * p_world_scale; + } +} + +Transform3D OpenXRInterface::get_camera_transform() { + XRServer *xr_server = XRServer::get_singleton(); + ERR_FAIL_NULL_V(xr_server, Transform3D()); + + Transform3D hmd_transform; + double world_scale = xr_server->get_world_scale(); + + // head_transform should be updated in process + + hmd_transform.basis = head_transform.basis; + hmd_transform.origin = head_transform.origin * world_scale; + + return hmd_transform; +} + +Transform3D OpenXRInterface::get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) { + XRServer *xr_server = XRServer::get_singleton(); + ERR_FAIL_NULL_V(xr_server, Transform3D()); + + Transform3D t; + if (openxr_api && openxr_api->get_view_transform(p_view, t)) { + // update our cached value if we have a valid transform + transform_for_view[p_view] = t; + } else { + // reuse cached value + t = transform_for_view[p_view]; + } + + // Apply our world scale + double world_scale = xr_server->get_world_scale(); + t.origin *= world_scale; + + return p_cam_transform * xr_server->get_reference_frame() * t; +} + +CameraMatrix OpenXRInterface::get_projection_for_view(uint32_t p_view, double p_aspect, double p_z_near, double p_z_far) { + CameraMatrix cm; + + if (openxr_api) { + if (openxr_api->get_view_projection(p_view, p_z_near, p_z_far, cm)) { + return cm; + } + } + + // Failed to get from our OpenXR device? Default to some sort of sensible camera matrix.. + cm.set_for_hmd(p_view + 1, 1.0, 6.0, 14.5, 4.0, 1.5, p_z_near, p_z_far); + + return cm; +} + +void OpenXRInterface::process() { + if (openxr_api) { + // do our normal process + if (openxr_api->process()) { + Transform3D t; + Vector3 linear_velocity; + Vector3 angular_velocity; + XRPose::TrackingConfidence confidence = openxr_api->get_head_center(t, linear_velocity, angular_velocity); + if (confidence != XRPose::XR_TRACKING_CONFIDENCE_NONE) { + // Only update our transform if we have one to update it with + // note that poses are stored without world scale and reference frame applied! + head_transform = t; + head_linear_velocity = linear_velocity; + head_angular_velocity = angular_velocity; + } + } + + // handle our action sets.... + Vector<RID> active_sets; + for (int i = 0; i < action_sets.size(); i++) { + if (action_sets[i]->is_active) { + active_sets.push_back(action_sets[i]->action_set_rid); + } + } + + if (openxr_api->sync_action_sets(active_sets)) { + for (int i = 0; i < trackers.size(); i++) { + handle_tracker(trackers[i]); + } + } + } + + if (head.is_valid()) { + // TODO figure out how to get our velocities + + head->set_pose("default", head_transform, head_linear_velocity, head_angular_velocity); + + // TODO set confidence on pose once we support tracking this.. + } +} + +void OpenXRInterface::pre_render() { + if (openxr_api) { + openxr_api->pre_render(); + } +} + +bool OpenXRInterface::pre_draw_viewport(RID p_render_target) { + if (openxr_api) { + return openxr_api->pre_draw_viewport(p_render_target); + } else { + // don't render + return false; + } +} + +Vector<BlitToScreen> OpenXRInterface::post_draw_viewport(RID p_render_target, const Rect2 &p_screen_rect) { + Vector<BlitToScreen> blit_to_screen; + + // If separate HMD we should output one eye to screen + if (p_screen_rect != Rect2()) { + BlitToScreen blit; + + blit.render_target = p_render_target; + blit.multi_view.use_layer = true; + blit.multi_view.layer = 0; + blit.lens_distortion.apply = false; + + Size2 render_size = get_render_target_size(); + Rect2 dst_rect = p_screen_rect; + float new_height = dst_rect.size.x * (render_size.y / render_size.x); + if (new_height > dst_rect.size.y) { + dst_rect.position.y = (0.5 * dst_rect.size.y) - (0.5 * new_height); + dst_rect.size.y = new_height; + } else { + float new_width = dst_rect.size.y * (render_size.x / render_size.y); + + dst_rect.position.x = (0.5 * dst_rect.size.x) - (0.5 * new_width); + dst_rect.size.x = new_width; + } + + blit.dst_rect = dst_rect; + blit_to_screen.push_back(blit); + } + + if (openxr_api) { + openxr_api->post_draw_viewport(p_render_target); + } + + return blit_to_screen; +} + +void OpenXRInterface::end_frame() { + if (openxr_api) { + openxr_api->end_frame(); + } +} + +OpenXRInterface::OpenXRInterface() { + openxr_api = OpenXRAPI::get_singleton(); + + // while we don't have head tracking, don't put the headset on the floor... + _set_default_pos(head_transform, 1.0, 0); + _set_default_pos(transform_for_view[0], 1.0, 1); + _set_default_pos(transform_for_view[1], 1.0, 2); +} + +OpenXRInterface::~OpenXRInterface() { + openxr_api = nullptr; +} diff --git a/modules/openxr/openxr_interface.h b/modules/openxr/openxr_interface.h new file mode 100644 index 0000000000..ede7d481d2 --- /dev/null +++ b/modules/openxr/openxr_interface.h @@ -0,0 +1,129 @@ +/*************************************************************************/ +/* openxr_interface.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef OPENXR_INTERFACE_H +#define OPENXR_INTERFACE_H + +#include "servers/xr/xr_interface.h" +#include "servers/xr/xr_positional_tracker.h" + +#include "action_map/openxr_action_map.h" +#include "openxr_api.h" + +class OpenXRInterface : public XRInterface { + GDCLASS(OpenXRInterface, XRInterface); + +private: + OpenXRAPI *openxr_api = nullptr; + bool initialized = false; + XRInterface::TrackingStatus tracking_state; + + // At a minimum we need a tracker for our head + Ref<XRPositionalTracker> head; + Transform3D head_transform; + Vector3 head_linear_velocity; + Vector3 head_angular_velocity; + Transform3D transform_for_view[2]; // We currently assume 2, but could be 4 for VARJO which we do not support yet + + void _load_action_map(); + + struct Action { + String action_name; + OpenXRAction::ActionType action_type; + RID action_rid; + }; + struct ActionSet { + String action_set_name; + bool is_active; + RID action_set_rid; + Vector<Action *> actions; + }; + struct Tracker { + String path_name; + RID path_rid; + Ref<XRPositionalTracker> positional_tracker; + Vector<Action *> actions; + }; + + Vector<ActionSet *> action_sets; + Vector<Tracker *> trackers; + + ActionSet *create_action_set(const String &p_action_set_name, const String &p_localized_name, const int p_priority); + void free_action_sets(); + + Action *create_action(ActionSet *p_action_set, const String &p_action_name, const String &p_localized_name, OpenXRAction::ActionType p_action_type, const Vector<RID> p_toplevel_paths); + Action *find_action(const String &p_action_name); + void free_actions(ActionSet *p_action_set); + + Tracker *get_tracker(const String &p_path_name); + Tracker *find_tracker(const String &p_positional_tracker_name); + void link_action_to_tracker(Tracker *p_tracker, Action *p_action); + void handle_tracker(Tracker *p_tracker); + void free_trackers(); + + void _set_default_pos(Transform3D &p_transform, double p_world_scale, uint64_t p_eye); + +protected: + static void _bind_methods(); + +public: + virtual StringName get_name() const override; + virtual uint32_t get_capabilities() const override; + + virtual TrackingStatus get_tracking_status() const override; + + bool initialise_on_startup() const; + virtual bool is_initialized() const override; + virtual bool initialize() override; + virtual void uninitialize() override; + + virtual void trigger_haptic_pulse(const String &p_action_name, const StringName &p_tracker_name, double p_frequency, double p_amplitude, double p_duration_sec, double p_delay_sec = 0) override; + + virtual bool supports_play_area_mode(XRInterface::PlayAreaMode p_mode) override; + virtual XRInterface::PlayAreaMode get_play_area_mode() const override; + virtual bool set_play_area_mode(XRInterface::PlayAreaMode p_mode) override; + + virtual Size2 get_render_target_size() override; + virtual uint32_t get_view_count() override; + virtual Transform3D get_camera_transform() override; + virtual Transform3D get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) override; + virtual CameraMatrix get_projection_for_view(uint32_t p_view, double p_aspect, double p_z_near, double p_z_far) override; + + virtual void process() override; + virtual void pre_render() override; + bool pre_draw_viewport(RID p_render_target) override; + virtual Vector<BlitToScreen> post_draw_viewport(RID p_render_target, const Rect2 &p_screen_rect) override; + virtual void end_frame() override; + + OpenXRInterface(); + ~OpenXRInterface(); +}; + +#endif // !OPENXR_INTERFACE_H diff --git a/modules/openxr/openxr_util.cpp b/modules/openxr/openxr_util.cpp new file mode 100644 index 0000000000..e515336daa --- /dev/null +++ b/modules/openxr/openxr_util.cpp @@ -0,0 +1,291 @@ +/*************************************************************************/ +/* openxr_util.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "openxr_util.h" + +#define ENUM_TO_STRING_CASE(e) \ + case e: { \ + return String(#e); \ + } break; + +// TODO see if we can generate this code further using the xml file with meta data supplied by OpenXR + +String OpenXRUtil::get_view_configuration_name(XrViewConfigurationType p_view_configuration) { + switch (p_view_configuration) { + ENUM_TO_STRING_CASE(XR_VIEW_CONFIGURATION_TYPE_PRIMARY_MONO) + ENUM_TO_STRING_CASE(XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO) + ENUM_TO_STRING_CASE(XR_VIEW_CONFIGURATION_TYPE_PRIMARY_QUAD_VARJO) + ENUM_TO_STRING_CASE(XR_VIEW_CONFIGURATION_TYPE_SECONDARY_MONO_FIRST_PERSON_OBSERVER_MSFT) + ENUM_TO_STRING_CASE(XR_VIEW_CONFIGURATION_TYPE_MAX_ENUM) + default: { + return String("View Configuration ") + String::num_int64(int64_t(p_view_configuration)); + } break; + } +} + +String OpenXRUtil::get_reference_space_name(XrReferenceSpaceType p_reference_space) { + switch (p_reference_space) { + ENUM_TO_STRING_CASE(XR_REFERENCE_SPACE_TYPE_VIEW) + ENUM_TO_STRING_CASE(XR_REFERENCE_SPACE_TYPE_LOCAL) + ENUM_TO_STRING_CASE(XR_REFERENCE_SPACE_TYPE_STAGE) + ENUM_TO_STRING_CASE(XR_REFERENCE_SPACE_TYPE_UNBOUNDED_MSFT) + ENUM_TO_STRING_CASE(XR_REFERENCE_SPACE_TYPE_COMBINED_EYE_VARJO) + ENUM_TO_STRING_CASE(XR_REFERENCE_SPACE_TYPE_MAX_ENUM) + default: { + return String("Reference space ") + String::num_int64(int64_t(p_reference_space)); + } break; + } +} + +String OpenXRUtil::get_structure_type_name(XrStructureType p_structure_type) { + switch (p_structure_type) { + ENUM_TO_STRING_CASE(XR_TYPE_UNKNOWN) + ENUM_TO_STRING_CASE(XR_TYPE_API_LAYER_PROPERTIES) + ENUM_TO_STRING_CASE(XR_TYPE_EXTENSION_PROPERTIES) + ENUM_TO_STRING_CASE(XR_TYPE_INSTANCE_CREATE_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_GET_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_PROPERTIES) + ENUM_TO_STRING_CASE(XR_TYPE_VIEW_LOCATE_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_VIEW) + ENUM_TO_STRING_CASE(XR_TYPE_SESSION_CREATE_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_CREATE_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_SESSION_BEGIN_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_VIEW_STATE) + ENUM_TO_STRING_CASE(XR_TYPE_FRAME_END_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_HAPTIC_VIBRATION) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_BUFFER) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED) + ENUM_TO_STRING_CASE(XR_TYPE_ACTION_STATE_BOOLEAN) + ENUM_TO_STRING_CASE(XR_TYPE_ACTION_STATE_FLOAT) + ENUM_TO_STRING_CASE(XR_TYPE_ACTION_STATE_VECTOR2F) + ENUM_TO_STRING_CASE(XR_TYPE_ACTION_STATE_POSE) + ENUM_TO_STRING_CASE(XR_TYPE_ACTION_SET_CREATE_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_ACTION_CREATE_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_INSTANCE_PROPERTIES) + ENUM_TO_STRING_CASE(XR_TYPE_FRAME_WAIT_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_PROJECTION) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_QUAD) + ENUM_TO_STRING_CASE(XR_TYPE_REFERENCE_SPACE_CREATE_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_ACTION_SPACE_CREATE_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING) + ENUM_TO_STRING_CASE(XR_TYPE_VIEW_CONFIGURATION_VIEW) + ENUM_TO_STRING_CASE(XR_TYPE_SPACE_LOCATION) + ENUM_TO_STRING_CASE(XR_TYPE_SPACE_VELOCITY) + ENUM_TO_STRING_CASE(XR_TYPE_FRAME_STATE) + ENUM_TO_STRING_CASE(XR_TYPE_VIEW_CONFIGURATION_PROPERTIES) + ENUM_TO_STRING_CASE(XR_TYPE_FRAME_BEGIN_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_EVENTS_LOST) + ENUM_TO_STRING_CASE(XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED) + ENUM_TO_STRING_CASE(XR_TYPE_INTERACTION_PROFILE_STATE) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_ACTION_STATE_GET_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_HAPTIC_ACTION_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_ACTIONS_SYNC_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_BOUND_SOURCES_FOR_ACTION_ENUMERATE_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_INPUT_SOURCE_LOCALIZED_NAME_GET_INFO) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_CUBE_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_INSTANCE_CREATE_INFO_ANDROID_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_DEPTH_INFO_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_VULKAN_SWAPCHAIN_FORMAT_LIST_CREATE_INFO_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_PERF_SETTINGS_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_CYLINDER_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_EQUIRECT_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_DEBUG_UTILS_MESSENGER_CALLBACK_DATA_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_DEBUG_UTILS_LABEL_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_BINDING_OPENGL_WIN32_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_BINDING_OPENGL_XLIB_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_BINDING_OPENGL_XCB_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_BINDING_OPENGL_WAYLAND_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_BINDING_OPENGL_ES_ANDROID_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_ES_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_ES_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_BINDING_VULKAN_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_IMAGE_VULKAN_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_BINDING_D3D11_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_IMAGE_D3D11_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_REQUIREMENTS_D3D11_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_BINDING_D3D12_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_IMAGE_D3D12_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_REQUIREMENTS_D3D12_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_EYE_GAZE_INTERACTION_PROPERTIES_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_EYE_GAZE_SAMPLE_TIME_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_VISIBILITY_MASK_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_VISIBILITY_MASK_CHANGED_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_SESSION_CREATE_INFO_OVERLAY_EXTX) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_MAIN_SESSION_VISIBILITY_CHANGED_EXTX) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_COLOR_SCALE_BIAS_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_SPATIAL_ANCHOR_CREATE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SPATIAL_ANCHOR_SPACE_CREATE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_IMAGE_LAYOUT_FB) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_ALPHA_BLEND_FB) + ENUM_TO_STRING_CASE(XR_TYPE_VIEW_CONFIGURATION_DEPTH_RANGE_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_GRAPHICS_BINDING_EGL_MNDX) + ENUM_TO_STRING_CASE(XR_TYPE_SPATIAL_GRAPH_NODE_SPACE_CREATE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_HAND_TRACKING_PROPERTIES_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_JOINTS_LOCATE_INFO_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_JOINT_LOCATIONS_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_JOINT_VELOCITIES_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_HAND_TRACKING_MESH_PROPERTIES_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_MESH_SPACE_CREATE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_MESH_UPDATE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_MESH_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_POSE_TYPE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SECONDARY_VIEW_CONFIGURATION_SESSION_BEGIN_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SECONDARY_VIEW_CONFIGURATION_STATE_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SECONDARY_VIEW_CONFIGURATION_FRAME_STATE_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SECONDARY_VIEW_CONFIGURATION_FRAME_END_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SECONDARY_VIEW_CONFIGURATION_LAYER_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SECONDARY_VIEW_CONFIGURATION_SWAPCHAIN_CREATE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_CONTROLLER_MODEL_KEY_STATE_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_CONTROLLER_MODEL_NODE_PROPERTIES_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_CONTROLLER_MODEL_PROPERTIES_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_CONTROLLER_MODEL_NODE_STATE_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_CONTROLLER_MODEL_STATE_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_VIEW_CONFIGURATION_VIEW_FOV_EPIC) + ENUM_TO_STRING_CASE(XR_TYPE_HOLOGRAPHIC_WINDOW_ATTACHMENT_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_REPROJECTION_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_REPROJECTION_PLANE_OVERRIDE_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_ANDROID_SURFACE_SWAPCHAIN_CREATE_INFO_FB) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_SECURE_CONTENT_FB) + ENUM_TO_STRING_CASE(XR_TYPE_INTERACTION_PROFILE_ANALOG_THRESHOLD_VALVE) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_JOINTS_MOTION_RANGE_INFO_EXT) + ENUM_TO_STRING_CASE(XR_TYPE_LOADER_INIT_INFO_ANDROID_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_VULKAN_INSTANCE_CREATE_INFO_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_VULKAN_DEVICE_CREATE_INFO_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_VULKAN_GRAPHICS_DEVICE_GET_INFO_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_EQUIRECT2_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_OBSERVER_CREATE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_CREATE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_NEW_SCENE_COMPUTE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_VISUAL_MESH_COMPUTE_LOD_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_COMPONENTS_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_COMPONENTS_GET_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_COMPONENT_LOCATIONS_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_COMPONENTS_LOCATE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_OBJECTS_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_COMPONENT_PARENT_FILTER_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_OBJECT_TYPES_FILTER_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_PLANES_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_PLANE_ALIGNMENT_FILTER_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_MESHES_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_MESH_BUFFERS_GET_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_MESH_BUFFERS_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_MESH_VERTEX_BUFFER_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_MESH_INDICES_UINT32_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_MESH_INDICES_UINT16_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SERIALIZED_SCENE_FRAGMENT_DATA_GET_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SCENE_DESERIALIZE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_DISPLAY_REFRESH_RATE_CHANGED_FB) + ENUM_TO_STRING_CASE(XR_TYPE_VIVE_TRACKER_PATHS_HTCX) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_VIVE_TRACKER_CONNECTED_HTCX) + ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_COLOR_SPACE_PROPERTIES_FB) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_TRACKING_MESH_FB) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_TRACKING_SCALE_FB) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_TRACKING_AIM_STATE_FB) + ENUM_TO_STRING_CASE(XR_TYPE_HAND_TRACKING_CAPSULES_STATE_FB) + ENUM_TO_STRING_CASE(XR_TYPE_FOVEATION_PROFILE_CREATE_INFO_FB) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_CREATE_INFO_FOVEATION_FB) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_STATE_FOVEATION_FB) + ENUM_TO_STRING_CASE(XR_TYPE_FOVEATION_LEVEL_PROFILE_CREATE_INFO_FB) + ENUM_TO_STRING_CASE(XR_TYPE_TRIANGLE_MESH_CREATE_INFO_FB) + ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_PASSTHROUGH_PROPERTIES_FB) + ENUM_TO_STRING_CASE(XR_TYPE_PASSTHROUGH_CREATE_INFO_FB) + ENUM_TO_STRING_CASE(XR_TYPE_PASSTHROUGH_LAYER_CREATE_INFO_FB) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_FB) + ENUM_TO_STRING_CASE(XR_TYPE_GEOMETRY_INSTANCE_CREATE_INFO_FB) + ENUM_TO_STRING_CASE(XR_TYPE_GEOMETRY_INSTANCE_TRANSFORM_FB) + ENUM_TO_STRING_CASE(XR_TYPE_PASSTHROUGH_STYLE_FB) + ENUM_TO_STRING_CASE(XR_TYPE_PASSTHROUGH_COLOR_MAP_MONO_TO_RGBA_FB) + ENUM_TO_STRING_CASE(XR_TYPE_PASSTHROUGH_COLOR_MAP_MONO_TO_MONO_FB) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_PASSTHROUGH_STATE_CHANGED_FB) + ENUM_TO_STRING_CASE(XR_TYPE_BINDING_MODIFICATIONS_KHR) + ENUM_TO_STRING_CASE(XR_TYPE_VIEW_LOCATE_FOVEATED_RENDERING_VARJO) + ENUM_TO_STRING_CASE(XR_TYPE_FOVEATED_VIEW_CONFIGURATION_VIEW_VARJO) + ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_FOVEATED_RENDERING_PROPERTIES_VARJO) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_DEPTH_TEST_VARJO) + ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_MARKER_TRACKING_PROPERTIES_VARJO) + ENUM_TO_STRING_CASE(XR_TYPE_EVENT_DATA_MARKER_TRACKING_UPDATE_VARJO) + ENUM_TO_STRING_CASE(XR_TYPE_MARKER_SPACE_CREATE_INFO_VARJO) + ENUM_TO_STRING_CASE(XR_TYPE_SPATIAL_ANCHOR_PERSISTENCE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SPATIAL_ANCHOR_FROM_PERSISTED_ANCHOR_CREATE_INFO_MSFT) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_IMAGE_FOVEATION_VULKAN_FB) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_STATE_ANDROID_SURFACE_DIMENSIONS_FB) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_STATE_SAMPLER_OPENGL_ES_FB) + ENUM_TO_STRING_CASE(XR_TYPE_SWAPCHAIN_STATE_SAMPLER_VULKAN_FB) + ENUM_TO_STRING_CASE(XR_TYPE_COMPOSITION_LAYER_SPACE_WARP_INFO_FB) + ENUM_TO_STRING_CASE(XR_TYPE_SYSTEM_SPACE_WARP_PROPERTIES_FB) + ENUM_TO_STRING_CASE(XR_STRUCTURE_TYPE_MAX_ENUM) + default: { + return String("Structure type ") + String::num_int64(int64_t(p_structure_type)); + } break; + } +} + +String OpenXRUtil::get_session_state_name(XrSessionState p_session_state) { + switch (p_session_state) { + ENUM_TO_STRING_CASE(XR_SESSION_STATE_UNKNOWN) + ENUM_TO_STRING_CASE(XR_SESSION_STATE_IDLE) + ENUM_TO_STRING_CASE(XR_SESSION_STATE_READY) + ENUM_TO_STRING_CASE(XR_SESSION_STATE_SYNCHRONIZED) + ENUM_TO_STRING_CASE(XR_SESSION_STATE_VISIBLE) + ENUM_TO_STRING_CASE(XR_SESSION_STATE_FOCUSED) + ENUM_TO_STRING_CASE(XR_SESSION_STATE_STOPPING) + ENUM_TO_STRING_CASE(XR_SESSION_STATE_LOSS_PENDING) + ENUM_TO_STRING_CASE(XR_SESSION_STATE_EXITING) + ENUM_TO_STRING_CASE(XR_SESSION_STATE_MAX_ENUM) + default: { + return String("Session state ") + String::num_int64(int64_t(p_session_state)); + } break; + } +} + +String OpenXRUtil::make_xr_version_string(XrVersion p_version) { + String version; + + version += String::num_int64(XR_VERSION_MAJOR(p_version)); + version += String("."); + version += String::num_int64(XR_VERSION_MINOR(p_version)); + version += String("."); + version += String::num_int64(XR_VERSION_PATCH(p_version)); + + return version; +} diff --git a/modules/openxr/openxr_util.h b/modules/openxr/openxr_util.h new file mode 100644 index 0000000000..1261268376 --- /dev/null +++ b/modules/openxr/openxr_util.h @@ -0,0 +1,46 @@ +/*************************************************************************/ +/* openxr_util.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef OPENXR_UTIL_H +#define OPENXR_UTIL_H + +#include "core/string/ustring.h" +#include <openxr/openxr.h> + +class OpenXRUtil { +public: + static String get_view_configuration_name(XrViewConfigurationType p_view_configuration); + static String get_reference_space_name(XrReferenceSpaceType p_reference_space); + static String get_structure_type_name(XrStructureType p_structure_type); + static String get_session_state_name(XrSessionState p_session_state); + static String make_xr_version_string(XrVersion p_version); +}; + +#endif // !OPENXR_UTIL_H diff --git a/modules/openxr/register_types.cpp b/modules/openxr/register_types.cpp new file mode 100644 index 0000000000..86ff368619 --- /dev/null +++ b/modules/openxr/register_types.cpp @@ -0,0 +1,91 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "register_types.h" +#include "main/main.h" + +#include "openxr_interface.h" + +#include "action_map/openxr_action.h" +#include "action_map/openxr_action_map.h" +#include "action_map/openxr_action_set.h" +#include "action_map/openxr_interaction_profile.h" + +OpenXRAPI *openxr_api = nullptr; +Ref<OpenXRInterface> openxr_interface; + +void preregister_openxr_types() { + // For now we create our openxr device here. If we merge it with openxr_interface we'll create that here soon. + + OpenXRAPI::setup_global_defs(); + openxr_api = OpenXRAPI::get_singleton(); + if (openxr_api) { + if (!openxr_api->initialise(Main::get_rendering_driver_name())) { + return; + } + } +} + +void register_openxr_types() { + GDREGISTER_CLASS(OpenXRInterface); + + GDREGISTER_CLASS(OpenXRAction); + GDREGISTER_CLASS(OpenXRActionSet); + GDREGISTER_CLASS(OpenXRActionMap); + GDREGISTER_CLASS(OpenXRIPBinding); + GDREGISTER_CLASS(OpenXRInteractionProfile); + + XRServer *xr_server = XRServer::get_singleton(); + if (xr_server) { + openxr_interface.instantiate(); + xr_server->add_interface(openxr_interface); + + if (openxr_interface->initialise_on_startup()) { + openxr_interface->initialize(); + } + } +} + +void unregister_openxr_types() { + if (openxr_interface.is_valid()) { + // unregister our interface from the XR server + if (XRServer::get_singleton()) { + XRServer::get_singleton()->remove_interface(openxr_interface); + } + + // and release + openxr_interface.unref(); + } + + if (openxr_api) { + openxr_api->finish(); + memdelete(openxr_api); + } +} diff --git a/modules/openxr/register_types.h b/modules/openxr/register_types.h new file mode 100644 index 0000000000..fb42770750 --- /dev/null +++ b/modules/openxr/register_types.h @@ -0,0 +1,40 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef OPENXR_REGISTER_TYPES_H +#define OPENXR_REGISTER_TYPES_H + +#define MODULE_OPENXR_HAS_PREREGISTER + +void preregister_openxr_types(); +void register_openxr_types(); +void unregister_openxr_types(); + +#endif // OPENXR_REGISTER_TYPES_H diff --git a/scene/3d/xr_nodes.cpp b/scene/3d/xr_nodes.cpp index b18819c920..efae81e048 100644 --- a/scene/3d/xr_nodes.cpp +++ b/scene/3d/xr_nodes.cpp @@ -448,11 +448,6 @@ void XRController3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_tracker_hand"), &XRController3D::get_tracker_hand); - ClassDB::bind_method(D_METHOD("get_rumble"), &XRController3D::get_rumble); - ClassDB::bind_method(D_METHOD("set_rumble", "rumble"), &XRController3D::set_rumble); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rumble", PROPERTY_HINT_RANGE, "0.0,1.0,0.01"), "set_rumble", "get_rumble"); - ADD_PROPERTY_DEFAULT("rumble", 0.0); - ADD_SIGNAL(MethodInfo("button_pressed", PropertyInfo(Variant::STRING, "name"))); ADD_SIGNAL(MethodInfo("button_released", PropertyInfo(Variant::STRING, "name"))); ADD_SIGNAL(MethodInfo("input_value_changed", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::FLOAT, "value"))); @@ -558,20 +553,6 @@ Vector2 XRController3D::get_axis(const StringName &p_name) const { } } -real_t XRController3D::get_rumble() const { - if (!tracker.is_valid()) { - return 0.0; - } - - return tracker->get_rumble(); -} - -void XRController3D::set_rumble(real_t p_rumble) { - if (tracker.is_valid()) { - tracker->set_rumble(p_rumble); - } -} - XRPositionalTracker::TrackerHand XRController3D::get_tracker_hand() const { // get our XRServer if (!tracker.is_valid()) { @@ -612,7 +593,7 @@ TypedArray<String> XROrigin3D::get_configuration_warnings() const { } } - bool xr_enabled = GLOBAL_GET("rendering/xr/enabled"); + bool xr_enabled = GLOBAL_GET("xr/shaders/enabled"); if (!xr_enabled) { warnings.push_back(TTR("XR is not enabled in rendering project settings. Stereoscopic output is not supported unless this is enabled.")); } diff --git a/scene/3d/xr_nodes.h b/scene/3d/xr_nodes.h index 5675cbd944..3079e20dc7 100644 --- a/scene/3d/xr_nodes.h +++ b/scene/3d/xr_nodes.h @@ -139,9 +139,6 @@ public: float get_value(const StringName &p_name) const; Vector2 get_axis(const StringName &p_name) const; - real_t get_rumble() const; - void set_rumble(real_t p_rumble); - XRPositionalTracker::TrackerHand get_tracker_hand() const; XRController3D() {} diff --git a/servers/rendering/renderer_compositor.cpp b/servers/rendering/renderer_compositor.cpp index 82e8bd6ef9..fa4d9c8b31 100644 --- a/servers/rendering/renderer_compositor.cpp +++ b/servers/rendering/renderer_compositor.cpp @@ -45,7 +45,7 @@ bool RendererCompositor::is_xr_enabled() const { } RendererCompositor::RendererCompositor() { - xr_enabled = GLOBAL_GET("rendering/xr/enabled"); + xr_enabled = GLOBAL_GET("xr/shaders/enabled"); } RendererCanvasRender *RendererCanvasRender::singleton = nullptr; diff --git a/servers/rendering/renderer_rd/effects_rd.cpp b/servers/rendering/renderer_rd/effects_rd.cpp index f16a4b1d19..7883a2d816 100644 --- a/servers/rendering/renderer_rd/effects_rd.cpp +++ b/servers/rendering/renderer_rd/effects_rd.cpp @@ -332,7 +332,7 @@ void EffectsRD::copy_to_atlas_fb(RID p_source_rd_texture, RID p_dest_framebuffer RD::get_singleton()->draw_list_draw(draw_list, true); } -void EffectsRD::copy_to_fb_rect(RID p_source_rd_texture, RID p_dest_framebuffer, const Rect2i &p_rect, bool p_flip_y, bool p_force_luminance, bool p_alpha_to_zero, bool p_srgb, RID p_secondary) { +void EffectsRD::copy_to_fb_rect(RID p_source_rd_texture, RID p_dest_framebuffer, const Rect2i &p_rect, bool p_flip_y, bool p_force_luminance, bool p_alpha_to_zero, bool p_srgb, RID p_secondary, bool p_multiview) { memset(©_to_fb.push_constant, 0, sizeof(CopyToFbPushConstant)); if (p_flip_y) { @@ -348,10 +348,18 @@ void EffectsRD::copy_to_fb_rect(RID p_source_rd_texture, RID p_dest_framebuffer, copy_to_fb.push_constant.srgb = true; } + CopyToFBMode mode; + if (p_multiview) { + mode = p_secondary.is_valid() ? COPY_TO_FB_MULTIVIEW_WITH_DEPTH : COPY_TO_FB_MULTIVIEW; + } else { + mode = p_secondary.is_valid() ? COPY_TO_FB_COPY2 : COPY_TO_FB_COPY; + } + RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(p_dest_framebuffer, RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_KEEP, RD::FINAL_ACTION_DISCARD, Vector<Color>(), 1.0, 0, p_rect); - RD::get_singleton()->draw_list_bind_render_pipeline(draw_list, copy_to_fb.pipelines[p_secondary.is_valid() ? COPY_TO_FB_COPY2 : COPY_TO_FB_COPY].get_render_pipeline(RD::INVALID_ID, RD::get_singleton()->framebuffer_get_format(p_dest_framebuffer))); + RD::get_singleton()->draw_list_bind_render_pipeline(draw_list, copy_to_fb.pipelines[mode].get_render_pipeline(RD::INVALID_ID, RD::get_singleton()->framebuffer_get_format(p_dest_framebuffer))); RD::get_singleton()->draw_list_bind_uniform_set(draw_list, _get_uniform_set_from_texture(p_source_rd_texture), 0); if (p_secondary.is_valid()) { + // TODO may need to do this differently when reading from depth buffer for multiview RD::get_singleton()->draw_list_bind_uniform_set(draw_list, _get_uniform_set_from_texture(p_secondary), 1); } RD::get_singleton()->draw_list_bind_index_array(draw_list, index_array); @@ -2367,15 +2375,26 @@ EffectsRD::EffectsRD(bool p_prefer_raster_effects) { copy_modes.push_back("\n"); copy_modes.push_back("\n#define MODE_PANORAMA_TO_DP\n"); copy_modes.push_back("\n#define MODE_TWO_SOURCES\n"); + copy_modes.push_back("\n#define MULTIVIEW\n"); + copy_modes.push_back("\n#define MULTIVIEW\n#define MODE_TWO_SOURCES\n"); copy_to_fb.shader.initialize(copy_modes); + if (!RendererCompositorRD::singleton->is_xr_enabled()) { + copy_to_fb.shader.set_variant_enabled(COPY_TO_FB_MULTIVIEW, false); + copy_to_fb.shader.set_variant_enabled(COPY_TO_FB_MULTIVIEW_WITH_DEPTH, false); + } + copy_to_fb.shader_version = copy_to_fb.shader.version_create(); //use additive for (int i = 0; i < COPY_TO_FB_MAX; i++) { - copy_to_fb.pipelines[i].setup(copy_to_fb.shader.version_get_shader(copy_to_fb.shader_version, i), RD::RENDER_PRIMITIVE_TRIANGLES, RD::PipelineRasterizationState(), RD::PipelineMultisampleState(), RD::PipelineDepthStencilState(), RD::PipelineColorBlendState::create_disabled(), 0); + if (copy_to_fb.shader.is_variant_enabled(i)) { + copy_to_fb.pipelines[i].setup(copy_to_fb.shader.version_get_shader(copy_to_fb.shader_version, i), RD::RENDER_PRIMITIVE_TRIANGLES, RD::PipelineRasterizationState(), RD::PipelineMultisampleState(), RD::PipelineDepthStencilState(), RD::PipelineColorBlendState::create_disabled(), 0); + } else { + copy_to_fb.pipelines[i].clear(); + } } } diff --git a/servers/rendering/renderer_rd/effects_rd.h b/servers/rendering/renderer_rd/effects_rd.h index f5e5b1ace7..eca5e09800 100644 --- a/servers/rendering/renderer_rd/effects_rd.h +++ b/servers/rendering/renderer_rd/effects_rd.h @@ -203,6 +203,9 @@ private: COPY_TO_FB_COPY, COPY_TO_FB_COPY_PANORAMA_TO_DP, COPY_TO_FB_COPY2, + + COPY_TO_FB_MULTIVIEW, + COPY_TO_FB_MULTIVIEW_WITH_DEPTH, COPY_TO_FB_MAX, }; @@ -893,7 +896,7 @@ public: bool get_prefer_raster_effects(); void fsr_upscale(RID p_source_rd_texture, RID p_secondary_texture, RID p_destination_texture, const Size2i &p_internal_size, const Size2i &p_size, float p_fsr_upscale_sharpness); - void copy_to_fb_rect(RID p_source_rd_texture, RID p_dest_framebuffer, const Rect2i &p_rect, bool p_flip_y = false, bool p_force_luminance = false, bool p_alpha_to_zero = false, bool p_srgb = false, RID p_secondary = RID()); + void copy_to_fb_rect(RID p_source_rd_texture, RID p_dest_framebuffer, const Rect2i &p_rect, bool p_flip_y = false, bool p_force_luminance = false, bool p_alpha_to_zero = false, bool p_srgb = false, RID p_secondary = RID(), bool p_multiview = false); void copy_to_rect(RID p_source_rd_texture, RID p_dest_texture, const Rect2i &p_rect, bool p_flip_y = false, bool p_force_luminance = false, bool p_all_source = false, bool p_8_bit_dst = false, bool p_alpha_to_one = false); void copy_cubemap_to_panorama(RID p_source_cube, RID p_dest_panorama, const Size2i &p_panorama_size, float p_lod, bool p_is_array); void copy_depth_to_rect(RID p_source_rd_texture, RID p_dest_framebuffer, const Rect2i &p_rect, bool p_flip_y = false); diff --git a/servers/rendering/renderer_rd/shaders/copy_to_fb.glsl b/servers/rendering/renderer_rd/shaders/copy_to_fb.glsl index 2f1f9c4765..9787c9879d 100644 --- a/servers/rendering/renderer_rd/shaders/copy_to_fb.glsl +++ b/servers/rendering/renderer_rd/shaders/copy_to_fb.glsl @@ -4,7 +4,20 @@ #VERSION_DEFINES +#ifdef MULTIVIEW +#ifdef has_VK_KHR_multiview +#extension GL_EXT_multiview : enable +#define ViewIndex gl_ViewIndex +#else // has_VK_KHR_multiview +#define ViewIndex 0 +#endif // has_VK_KHR_multiview +#endif //MULTIVIEW + +#ifdef MULTIVIEW +layout(location = 0) out vec3 uv_interp; +#else layout(location = 0) out vec2 uv_interp; +#endif layout(push_constant, std430) uniform Params { vec4 section; @@ -19,9 +32,11 @@ params; void main() { vec2 base_arr[4] = vec2[](vec2(0.0, 0.0), vec2(0.0, 1.0), vec2(1.0, 1.0), vec2(1.0, 0.0)); - uv_interp = base_arr[gl_VertexIndex]; - - vec2 vpos = uv_interp; + uv_interp.xy = base_arr[gl_VertexIndex]; +#ifdef MULTIVIEW + uv_interp.z = ViewIndex; +#endif + vec2 vpos = uv_interp.xy; if (params.use_section) { vpos = params.section.xy + vpos * params.section.zw; } @@ -39,6 +54,15 @@ void main() { #VERSION_DEFINES +#ifdef MULTIVIEW +#ifdef has_VK_KHR_multiview +#extension GL_EXT_multiview : enable +#define ViewIndex gl_ViewIndex +#else // has_VK_KHR_multiview +#define ViewIndex 0 +#endif // has_VK_KHR_multiview +#endif //MULTIVIEW + layout(push_constant, std430) uniform Params { vec4 section; vec2 pixel_size; @@ -52,12 +76,25 @@ layout(push_constant, std430) uniform Params { } params; +#ifdef MULTIVIEW +layout(location = 0) in vec3 uv_interp; +#else layout(location = 0) in vec2 uv_interp; +#endif +#ifdef MULTIVIEW +layout(set = 0, binding = 0) uniform sampler2DArray source_color; +#ifdef MODE_TWO_SOURCES +layout(set = 1, binding = 0) uniform sampler2DArray source_depth; +layout(location = 1) out float depth; +#endif /* MODE_TWO_SOURCES */ +#else layout(set = 0, binding = 0) uniform sampler2D source_color; #ifdef MODE_TWO_SOURCES layout(set = 1, binding = 0) uniform sampler2D source_color2; -#endif +#endif /* MODE_TWO_SOURCES */ +#endif /* MULTIVIEW */ + layout(location = 0) out vec4 frag_color; vec3 linear_to_srgb(vec3 color) { @@ -68,9 +105,14 @@ vec3 linear_to_srgb(vec3 color) { } void main() { +#ifdef MULTIVIEW + vec3 uv = uv_interp; +#else vec2 uv = uv_interp; +#endif #ifdef MODE_PANORAMA_TO_DP + // Note, multiview and panorama should not be mixed at this time //obtain normal from dual paraboloid uv #define M_PI 3.14159265359 @@ -98,10 +140,20 @@ void main() { uv = 1.0 - uv; } #endif + +#ifdef MULTIVIEW + vec4 color = textureLod(source_color, uv, 0.0); +#ifdef MODE_TWO_SOURCES + // In multiview our 2nd input will be our depth map + depth = textureLod(source_depth, uv, 0.0).r; +#endif /* MODE_TWO_SOURCES */ + +#else vec4 color = textureLod(source_color, uv, 0.0); #ifdef MODE_TWO_SOURCES color += textureLod(source_color2, uv, 0.0); -#endif +#endif /* MODE_TWO_SOURCES */ +#endif /* MULTIVIEW */ if (params.force_luminance) { color.rgb = vec3(max(max(color.r, color.g), color.b)); } diff --git a/servers/rendering/renderer_viewport.cpp b/servers/rendering/renderer_viewport.cpp index 5a84bace2d..69c017a1c8 100644 --- a/servers/rendering/renderer_viewport.cpp +++ b/servers/rendering/renderer_viewport.cpp @@ -548,7 +548,6 @@ void RendererViewport::draw_viewports() { // get our xr interface in case we need it Ref<XRInterface> xr_interface; - XRServer *xr_server = XRServer::get_singleton(); if (xr_server != nullptr) { // let our XR server know we're about to render our frames so we can get our frame timing diff --git a/servers/rendering/rendering_device.h b/servers/rendering/rendering_device.h index 655a32a805..49d89bcadc 100644 --- a/servers/rendering/rendering_device.h +++ b/servers/rendering/rendering_device.h @@ -500,6 +500,7 @@ public: virtual RID texture_create(const TextureFormat &p_format, const TextureView &p_view, const Vector<Vector<uint8_t>> &p_data = Vector<Vector<uint8_t>>()) = 0; virtual RID texture_create_shared(const TextureView &p_view, RID p_with_texture) = 0; + virtual RID texture_create_from_extension(TextureType p_type, DataFormat p_format, TextureSamples p_samples, uint64_t p_flags, uint64_t p_image, uint64_t p_width, uint64_t p_height, uint64_t p_depth, uint64_t p_layers) = 0; enum TextureSliceType { TEXTURE_SLICE_2D, diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp index cbe90de2f8..c9ac42863a 100644 --- a/servers/rendering_server.cpp +++ b/servers/rendering_server.cpp @@ -2999,7 +2999,7 @@ RenderingServer::RenderingServer() { GLOBAL_DEF("rendering/limits/cluster_builder/max_clustered_elements", 512); ProjectSettings::get_singleton()->set_custom_property_info("rendering/limits/cluster_builder/max_clustered_elements", PropertyInfo(Variant::FLOAT, "rendering/limits/cluster_builder/max_clustered_elements", PROPERTY_HINT_RANGE, "32,8192,1")); - GLOBAL_DEF_RST("rendering/xr/enabled", false); + GLOBAL_DEF_RST_BASIC("xr/shaders/enabled", false); GLOBAL_DEF_RST("rendering/2d/options/use_software_skinning", true); GLOBAL_DEF_RST("rendering/2d/options/ninepatch_mode", 1); diff --git a/servers/xr/xr_positional_tracker.cpp b/servers/xr/xr_positional_tracker.cpp index 7b21eeba04..671eb7a111 100644 --- a/servers/xr/xr_positional_tracker.cpp +++ b/servers/xr/xr_positional_tracker.cpp @@ -65,10 +65,6 @@ void XRPositionalTracker::_bind_methods() { ADD_SIGNAL(MethodInfo("button_released", PropertyInfo(Variant::STRING, "name"))); ADD_SIGNAL(MethodInfo("input_value_changed", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::FLOAT, "value"))); ADD_SIGNAL(MethodInfo("input_axis_changed", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::VECTOR2, "vector"))); - - ClassDB::bind_method(D_METHOD("get_rumble"), &XRPositionalTracker::get_rumble); - ClassDB::bind_method(D_METHOD("set_rumble", "rumble"), &XRPositionalTracker::set_rumble); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rumble"), "set_rumble", "get_rumble"); }; void XRPositionalTracker::set_tracker_type(XRServer::TrackerType p_type) { @@ -206,21 +202,8 @@ void XRPositionalTracker::set_input(const StringName &p_action_name, const Varia } } -real_t XRPositionalTracker::get_rumble() const { - return rumble; -}; - -void XRPositionalTracker::set_rumble(real_t p_rumble) { - if (p_rumble > 0.0) { - rumble = p_rumble; - } else { - rumble = 0.0; - }; -}; - XRPositionalTracker::XRPositionalTracker() { type = XRServer::TRACKER_UNKNOWN; name = "Unknown"; hand = TRACKER_HAND_UNKNOWN; - rumble = 0.0; }; diff --git a/servers/xr/xr_positional_tracker.h b/servers/xr/xr_positional_tracker.h index 2f358cbb21..ccb30bbbe6 100644 --- a/servers/xr/xr_positional_tracker.h +++ b/servers/xr/xr_positional_tracker.h @@ -62,10 +62,6 @@ private: Map<StringName, Ref<XRPose>> poses; Map<StringName, Variant> inputs; - int joy_id; // if we also have a related joystick entity, the id of the joystick - Ref<Mesh> mesh; // when available, a mesh that can be used to render this tracker - real_t rumble; // rumble strength, 0.0 is off, 1.0 is maximum, note that we only record here, xr_interface is responsible for execution - protected: static void _bind_methods(); @@ -87,10 +83,6 @@ public: Variant get_input(const StringName &p_action_name) const; void set_input(const StringName &p_action_name, const Variant &p_value); - // TODO replace by new implementation - real_t get_rumble() const; - void set_rumble(real_t p_rumble); - XRPositionalTracker(); ~XRPositionalTracker() {} }; diff --git a/servers/xr_server.cpp b/servers/xr_server.cpp index dbfe76a127..e32b41c7ae 100644 --- a/servers/xr_server.cpp +++ b/servers/xr_server.cpp @@ -348,9 +348,10 @@ PackedStringArray XRServer::get_suggested_pose_names(const StringName &p_tracker } void XRServer::_process() { - /* called from renderer_viewport.draw_viewports right before we start drawing our viewports */ + // called from our main game loop before we handle physics and game logic + // note that we can have multiple interfaces active if we have interfaces that purely handle tracking - /* process all active interfaces */ + // process all active interfaces for (int i = 0; i < interfaces.size(); i++) { if (!interfaces[i].is_valid()) { // ignore, not a valid reference |