diff --git a/modules/openxr/extensions/openxr_composition_layer_extension.cpp b/modules/openxr/extensions/openxr_composition_layer_extension.cpp index cb117d7bb7a..1fba8e5f8b4 100644 --- a/modules/openxr/extensions/openxr_composition_layer_extension.cpp +++ b/modules/openxr/extensions/openxr_composition_layer_extension.cpp @@ -196,20 +196,20 @@ XrCompositionLayerBaseHeader *OpenXRViewportCompositionLayerProvider::get_compos return nullptr; } - if (swapchain_info.swapchain == XR_NULL_HANDLE) { + if (swapchain_info.get_swapchain() == XR_NULL_HANDLE) { // Don't have a swapchain to display? Ignore our layer. return nullptr; } - if (swapchain_info.image_acquired) { - openxr_api->release_image(swapchain_info); + if (swapchain_info.is_image_acquired()) { + swapchain_info.release(); } // Update the layer struct for the swapchain. switch (composition_layer->type) { case XR_TYPE_COMPOSITION_LAYER_QUAD: { XrCompositionLayerQuad *quad_layer = (XrCompositionLayerQuad *)composition_layer; - quad_layer->subImage.swapchain = swapchain_info.swapchain; + quad_layer->subImage.swapchain = swapchain_info.get_swapchain(); quad_layer->subImage.imageArrayIndex = 0; quad_layer->subImage.imageRect.offset.x = 0; quad_layer->subImage.imageRect.offset.y = 0; @@ -219,7 +219,7 @@ XrCompositionLayerBaseHeader *OpenXRViewportCompositionLayerProvider::get_compos case XR_TYPE_COMPOSITION_LAYER_CYLINDER_KHR: { XrCompositionLayerCylinderKHR *cylinder_layer = (XrCompositionLayerCylinderKHR *)composition_layer; - cylinder_layer->subImage.swapchain = swapchain_info.swapchain; + cylinder_layer->subImage.swapchain = swapchain_info.get_swapchain(); cylinder_layer->subImage.imageArrayIndex = 0; cylinder_layer->subImage.imageRect.offset.x = 0; cylinder_layer->subImage.imageRect.offset.y = 0; @@ -229,7 +229,7 @@ XrCompositionLayerBaseHeader *OpenXRViewportCompositionLayerProvider::get_compos case XR_TYPE_COMPOSITION_LAYER_EQUIRECT2_KHR: { XrCompositionLayerEquirect2KHR *equirect_layer = (XrCompositionLayerEquirect2KHR *)composition_layer; - equirect_layer->subImage.swapchain = swapchain_info.swapchain; + equirect_layer->subImage.swapchain = swapchain_info.get_swapchain(); equirect_layer->subImage.imageArrayIndex = 0; equirect_layer->subImage.imageRect.offset.x = 0; equirect_layer->subImage.imageRect.offset.y = 0; @@ -269,14 +269,16 @@ bool OpenXRViewportCompositionLayerProvider::update_and_acquire_swapchain(bool p } // See if our current swapchain is outdated. - if (swapchain_info.swapchain != XR_NULL_HANDLE) { + if (swapchain_info.get_swapchain() != XR_NULL_HANDLE) { // If this swap chain, or the previous one, were static, then we can't reuse it. if (swapchain_size == viewport_size && !p_static_image && !static_image) { // We're all good! Just acquire it. - return openxr_api->acquire_image(swapchain_info); + // We can ignore should_render here, return will be false. + XrBool32 should_render = true; + return swapchain_info.acquire(should_render); } - openxr_api->free_swapchain(swapchain_info); + swapchain_info.queue_free(); } // Create our new swap chain @@ -287,13 +289,15 @@ bool OpenXRViewportCompositionLayerProvider::update_and_acquire_swapchain(bool p if (p_static_image) { create_flags |= XR_SWAPCHAIN_CREATE_STATIC_IMAGE_BIT; } - if (!openxr_api->create_swapchain(create_flags, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, swapchain_format, viewport_size.width, viewport_size.height, sample_count, array_size, swapchain_info.swapchain, &swapchain_info.swapchain_graphics_data)) { + if (!swapchain_info.create(create_flags, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, swapchain_format, viewport_size.width, viewport_size.height, sample_count, array_size)) { swapchain_size = Size2i(); return false; } - // Acquire our image so we can start rendering into it - bool ret = openxr_api->acquire_image(swapchain_info); + // Acquire our image so we can start rendering into it, + // we can ignore should_render here, ret will be false. + XrBool32 should_render = true; + bool ret = swapchain_info.acquire(should_render); swapchain_size = viewport_size; static_image = p_static_image; @@ -301,8 +305,8 @@ bool OpenXRViewportCompositionLayerProvider::update_and_acquire_swapchain(bool p } void OpenXRViewportCompositionLayerProvider::free_swapchain() { - if (swapchain_info.swapchain != XR_NULL_HANDLE) { - openxr_api->free_swapchain(swapchain_info); + if (swapchain_info.get_swapchain() != XR_NULL_HANDLE) { + swapchain_info.queue_free(); } swapchain_size = Size2i(); @@ -314,5 +318,5 @@ RID OpenXRViewportCompositionLayerProvider::get_current_swapchain_texture() { return RID(); } - return openxr_api->get_image(swapchain_info); + return swapchain_info.get_image(); } diff --git a/modules/openxr/extensions/platform/openxr_vulkan_extension.cpp b/modules/openxr/extensions/platform/openxr_vulkan_extension.cpp index f5e7fc192c2..da613f8435f 100644 --- a/modules/openxr/extensions/platform/openxr_vulkan_extension.cpp +++ b/modules/openxr/extensions/platform/openxr_vulkan_extension.cpp @@ -229,6 +229,10 @@ void OpenXRVulkanExtension::get_usable_swapchain_formats(Vector &p_usab } void OpenXRVulkanExtension::get_usable_depth_formats(Vector &p_usable_swap_chains) { + // Note, it is very likely we do NOT support any of depth formats where we can combine our stencil support (e.g. _S8_UINT). + // Right now this isn't a problem but once stencil support becomes an issue, we need to check for this in the rendering engine + // and create a separate buffer for the stencil. + p_usable_swap_chains.push_back(VK_FORMAT_D24_UNORM_S8_UINT); p_usable_swap_chains.push_back(VK_FORMAT_D32_SFLOAT_S8_UINT); p_usable_swap_chains.push_back(VK_FORMAT_D32_SFLOAT); diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index 91a4839a066..5438f5020cb 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -63,6 +63,198 @@ #define OPENXR_LOADER_NAME "libopenxr_loader.so" #endif +//////////////////////////////////// +// OpenXRAPI::OpenXRSwapChainInfo + +Vector OpenXRAPI::OpenXRSwapChainInfo::free_queue; + +bool OpenXRAPI::OpenXRSwapChainInfo::create(XrSwapchainCreateFlags p_create_flags, XrSwapchainUsageFlags p_usage_flags, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size) { + OpenXRAPI *openxr_api = OpenXRAPI::get_singleton(); + ERR_FAIL_NULL_V(openxr_api, false); + + XrSession xr_session = openxr_api->get_session(); + ERR_FAIL_COND_V(xr_session == XR_NULL_HANDLE, false); + + OpenXRGraphicsExtensionWrapper *xr_graphics_extension = openxr_api->get_graphics_extension(); + ERR_FAIL_NULL_V(xr_graphics_extension, false); + + // We already have a swapchain? + ERR_FAIL_COND_V(swapchain != XR_NULL_HANDLE, false); + + XrResult result; + + void *next_pointer = nullptr; + for (OpenXRExtensionWrapper *wrapper : openxr_api->get_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 + p_create_flags, // createFlags + p_usage_flags, // 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 = openxr_api->xrCreateSwapchain(xr_session, &swapchain_create_info, &new_swapchain); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to get swapchain [", openxr_api->get_error_string(result), "]"); + return false; + } + + if (!xr_graphics_extension->get_swapchain_image_data(new_swapchain, p_swapchain_format, p_width, p_height, p_sample_count, p_array_size, &swapchain_graphics_data)) { + openxr_api->xrDestroySwapchain(new_swapchain); + return false; + } + + swapchain = new_swapchain; + + return true; +} + +void OpenXRAPI::OpenXRSwapChainInfo::queue_free() { + if (image_acquired) { + release(); + } + + if (swapchain != XR_NULL_HANDLE) { + free_queue.push_back(*this); + + swapchain_graphics_data = nullptr; + swapchain = XR_NULL_HANDLE; + } +} + +void OpenXRAPI::OpenXRSwapChainInfo::free_queued() { + for (OpenXRAPI::OpenXRSwapChainInfo &swapchain_info : free_queue) { + swapchain_info.free(); + } + free_queue.clear(); +} + +void OpenXRAPI::OpenXRSwapChainInfo::free() { + OpenXRAPI *openxr_api = OpenXRAPI::get_singleton(); + ERR_FAIL_NULL(openxr_api); + + if (image_acquired) { + release(); + } + + if (openxr_api->get_graphics_extension() && swapchain_graphics_data != nullptr) { + openxr_api->get_graphics_extension()->cleanup_swapchain_graphics_data(&swapchain_graphics_data); + } + + if (swapchain != XR_NULL_HANDLE) { + openxr_api->xrDestroySwapchain(swapchain); + swapchain = XR_NULL_HANDLE; + } +} + +bool OpenXRAPI::OpenXRSwapChainInfo::acquire(XrBool32 &p_should_render) { + ERR_FAIL_COND_V(image_acquired, true); // This was not released when it should be, error out and reuse... + + OpenXRAPI *openxr_api = OpenXRAPI::get_singleton(); + ERR_FAIL_NULL_V(openxr_api, false); + + XrResult result; + + if (!skip_acquire_swapchain) { + XrSwapchainImageAcquireInfo swapchain_image_acquire_info = { + XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO, // type + nullptr // next + }; + + result = openxr_api->xrAcquireSwapchainImage(swapchain, &swapchain_image_acquire_info, &image_index); + if (!XR_UNQUALIFIED_SUCCESS(result)) { + // Make sure end_frame knows we need to submit an empty frame + p_should_render = false; + + if (XR_FAILED(result)) { + // Unexpected failure, log this! + print_line("OpenXR: failed to acquire swapchain image [", openxr_api->get_error_string(result), "]"); + return false; + } else { + // In this scenario we silently fail, the XR runtime is simply not ready yet to acquire the swapchain. + return false; + } + } + } + + XrSwapchainImageWaitInfo swapchain_image_wait_info = { + XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO, // type + nullptr, // next + 17000000 // timeout in nanoseconds + }; + + result = openxr_api->xrWaitSwapchainImage(swapchain, &swapchain_image_wait_info); + if (!XR_UNQUALIFIED_SUCCESS(result)) { + // Make sure end_frame knows we need to submit an empty frame + p_should_render = false; + + if (XR_FAILED(result)) { + // Unexpected failure, log this! + print_line("OpenXR: failed to wait for swapchain image [", openxr_api->get_error_string(result), "]"); + return false; + } else { + // Make sure to skip trying to acquire the swapchain image in the next frame + skip_acquire_swapchain = true; + return false; + } + } else { + skip_acquire_swapchain = false; + } + + image_acquired = true; + return true; +} + +bool OpenXRAPI::OpenXRSwapChainInfo::release() { + if (!image_acquired) { + // Already released or never acquired. + return true; + } + + image_acquired = false; // Regardless if we succeed or not, consider this released. + + OpenXRAPI *openxr_api = OpenXRAPI::get_singleton(); + ERR_FAIL_NULL_V(openxr_api, false); + + XrSwapchainImageReleaseInfo swapchain_image_release_info = { + XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO, // type + nullptr // next + }; + XrResult result = openxr_api->xrReleaseSwapchainImage(swapchain, &swapchain_image_release_info); + if (XR_FAILED(result)) { + print_line("OpenXR: failed to release swapchain image! [", openxr_api->get_error_string(result), "]"); + return false; + } + + return true; +} + +RID OpenXRAPI::OpenXRSwapChainInfo::get_image() { + OpenXRAPI *openxr_api = OpenXRAPI::get_singleton(); + + if (image_acquired && openxr_api && openxr_api->get_graphics_extension()) { + return OpenXRAPI::get_singleton()->get_graphics_extension()->get_texture(swapchain_graphics_data, image_index); + } else { + return RID(); + } +} + +//////////////////////////////////// +// OpenXRAPI + OpenXRAPI *OpenXRAPI::singleton = nullptr; Vector OpenXRAPI::registered_extension_wrappers; @@ -568,6 +760,21 @@ bool OpenXRAPI::load_supported_view_configuration_views(XrViewConfigurationType print_verbose(String(" - recommended render sample count: ") + itos(view_configuration_views[i].recommendedSwapchainSampleCount)); } + // Allocate buffers we'll be populating with view information. + views = (XrView *)memalloc(sizeof(XrView) * view_count); + ERR_FAIL_NULL_V_MSG(views, false, "OpenXR Couldn't allocate memory for views"); + memset(views, 0, sizeof(XrView) * view_count); + + projection_views = (XrCompositionLayerProjectionView *)memalloc(sizeof(XrCompositionLayerProjectionView) * view_count); + ERR_FAIL_NULL_V_MSG(projection_views, false, "OpenXR Couldn't allocate memory for projection views"); + memset(projection_views, 0, sizeof(XrCompositionLayerProjectionView) * view_count); + + if (submit_depth_buffer && OpenXRCompositionLayerDepthExtension::get_singleton()->is_available()) { + depth_views = (XrCompositionLayerDepthInfoKHR *)memalloc(sizeof(XrCompositionLayerDepthInfoKHR) * view_count); + ERR_FAIL_NULL_V_MSG(depth_views, false, "OpenXR Couldn't allocate memory for depth views"); + memset(depth_views, 0, sizeof(XrCompositionLayerDepthInfoKHR) * view_count); + } + return true; } @@ -878,31 +1085,10 @@ bool OpenXRAPI::is_swapchain_format_supported(int64_t p_swapchain_format) { return false; } -bool OpenXRAPI::create_swapchains() { +bool OpenXRAPI::obtain_swapchain_formats() { 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 optimize text rendering and background rendering as OpenXR may choose to reuse 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 do now have (basic) VRS support - */ - - Size2 recommended_size = get_recommended_target_size(); - uint32_t sample_count = 1; - - // We start with our color swapchain... { // Build a vector with swapchain formats we want to use, from best fit to worst Vector usable_swapchain_formats; @@ -923,23 +1109,9 @@ bool OpenXRAPI::create_swapchains() { } else { print_verbose(String("Using color swap chain format:") + get_swapchain_format_name(color_swapchain_format)); } - - if (!create_swapchain(0, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, color_swapchain_format, recommended_size.width, recommended_size.height, sample_count, view_count, swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain, &swapchains[OPENXR_SWAPCHAIN_COLOR].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"); - - // We create our depth swapchain if: - // - we've enabled submitting depth buffer - // - we support our depth layer extension - // - we have our spacewarp extension (not yet implemented) - if (submit_depth_buffer && OpenXRCompositionLayerDepthExtension::get_singleton()->is_available()) { + { // Build a vector with swapchain formats we want to use, from best fit to worst Vector usable_swapchain_formats; depth_swapchain_format = 0; @@ -954,18 +1126,51 @@ bool OpenXRAPI::create_swapchains() { } if (depth_swapchain_format == 0) { - print_line("Couldn't find usable depth swap chain format, depth buffer will not be submitted."); + WARN_PRINT_ONCE("Couldn't find usable depth swap chain format, depth buffer will not be submitted if requested."); } else { print_verbose(String("Using depth swap chain format:") + get_swapchain_format_name(depth_swapchain_format)); + } + } - // Note, if VK_FORMAT_D32_SFLOAT is used here but we're using the forward+ renderer, we should probably output a warning. + return true; +} - if (!create_swapchain(0, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, depth_swapchain_format, recommended_size.width, recommended_size.height, sample_count, view_count, swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain, &swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain_graphics_data)) { - return false; - } +bool OpenXRAPI::create_main_swapchains(Size2i p_size) { + ERR_FAIL_NULL_V(graphics_extension, false); + ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); - depth_views = (XrCompositionLayerDepthInfoKHR *)memalloc(sizeof(XrCompositionLayerDepthInfoKHR) * view_count); - ERR_FAIL_NULL_V_MSG(depth_views, false, "OpenXR Couldn't allocate memory for depth views"); + /* + 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. + + We only creates a swapchain for the main output here. + Additional swapchains may be created through our composition layer extension. + + 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 do now have (basic) VRS support + */ + + main_swapchain_size = p_size; + uint32_t sample_count = 1; + + // We start with our color swapchain... + if (color_swapchain_format != 0) { + if (!main_swapchains[OPENXR_SWAPCHAIN_COLOR].create(0, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, color_swapchain_format, main_swapchain_size.width, main_swapchain_size.height, sample_count, view_count)) { + return false; + } + } + + // We create our depth swapchain if: + // - we've enabled submitting depth buffer + // - we support our depth layer extension + // - we have our spacewarp extension (not yet implemented) + if (depth_swapchain_format != 0 && submit_depth_buffer && OpenXRCompositionLayerDepthExtension::get_singleton()->is_available()) { + if (!main_swapchains[OPENXR_SWAPCHAIN_DEPTH].create(0, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, depth_swapchain_format, main_swapchain_size.width, main_swapchain_size.height, sample_count, view_count)) { + return false; } } @@ -981,24 +1186,24 @@ bool OpenXRAPI::create_swapchains() { projection_views[i].type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW; projection_views[i].next = nullptr; - projection_views[i].subImage.swapchain = swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain; + projection_views[i].subImage.swapchain = main_swapchains[OPENXR_SWAPCHAIN_COLOR].get_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; + projection_views[i].subImage.imageRect.extent.width = main_swapchain_size.width; + projection_views[i].subImage.imageRect.extent.height = main_swapchain_size.height; if (submit_depth_buffer && OpenXRCompositionLayerDepthExtension::get_singleton()->is_available() && depth_views) { projection_views[i].next = &depth_views[i]; depth_views[i].type = XR_TYPE_COMPOSITION_LAYER_DEPTH_INFO_KHR; depth_views[i].next = nullptr; - depth_views[i].subImage.swapchain = swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain; + depth_views[i].subImage.swapchain = main_swapchains[OPENXR_SWAPCHAIN_DEPTH].get_swapchain(); depth_views[i].subImage.imageArrayIndex = i; depth_views[i].subImage.imageRect.offset.x = 0; depth_views[i].subImage.imageRect.offset.y = 0; - depth_views[i].subImage.imageRect.extent.width = recommended_size.width; - depth_views[i].subImage.imageRect.extent.height = recommended_size.height; + depth_views[i].subImage.imageRect.extent.width = main_swapchain_size.width; + depth_views[i].subImage.imageRect.extent.height = main_swapchain_size.height; depth_views[i].minDepth = 0.0; depth_views[i].maxDepth = 1.0; depth_views[i].nearZ = 0.01; // Near and far Z will be set to the correct values in fill_projection_matrix @@ -1029,9 +1234,8 @@ void OpenXRAPI::destroy_session() { depth_views = nullptr; } - for (int i = 0; i < OPENXR_SWAPCHAIN_MAX; i++) { - free_swapchain(swapchains[i]); - } + free_main_swapchains(); + OpenXRSwapChainInfo::free_queued(); if (supported_swapchain_formats != nullptr) { memfree(supported_swapchain_formats); @@ -1064,51 +1268,6 @@ void OpenXRAPI::destroy_session() { } } -bool OpenXRAPI::create_swapchain(XrSwapchainCreateFlags p_create_flags, XrSwapchainUsageFlags p_usage_flags, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, XrSwapchain &r_swapchain, void **r_swapchain_graphics_data) { - 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 - p_create_flags, // createFlags - p_usage_flags, // 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_sample_count, p_array_size, r_swapchain_graphics_data)) { - xrDestroySwapchain(new_swapchain); - return false; - } - - r_swapchain = new_swapchain; - - return true; -} - bool OpenXRAPI::on_state_idle() { print_verbose("On state idle"); @@ -1135,17 +1294,6 @@ bool OpenXRAPI::on_state_ready() { 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_swapchains()) { - return false; - } - // we're running running = true; @@ -1157,8 +1305,6 @@ bool OpenXRAPI::on_state_ready() { xr_interface->on_state_ready(); } - // TODO Tell android - return true; } @@ -1492,6 +1638,11 @@ bool OpenXRAPI::initialize_session() { return false; } + if (!obtain_swapchain_formats()) { + destroy_session(); + return false; + } + return true; } @@ -1798,103 +1949,10 @@ bool OpenXRAPI::process() { return true; } -void OpenXRAPI::free_swapchain(OpenXRSwapChainInfo &p_swapchain) { - if (p_swapchain.image_acquired) { - release_image(p_swapchain); +void OpenXRAPI::free_main_swapchains() { + for (int i = 0; i < OPENXR_SWAPCHAIN_MAX; i++) { + main_swapchains[i].queue_free(); } - - if (graphics_extension && p_swapchain.swapchain_graphics_data != nullptr) { - graphics_extension->cleanup_swapchain_graphics_data(&p_swapchain.swapchain_graphics_data); - } - - if (p_swapchain.swapchain != XR_NULL_HANDLE) { - xrDestroySwapchain(p_swapchain.swapchain); - p_swapchain.swapchain = XR_NULL_HANDLE; - } -} - -bool OpenXRAPI::acquire_image(OpenXRSwapChainInfo &p_swapchain) { - ERR_FAIL_COND_V(p_swapchain.image_acquired, true); // This was not released when it should be, error out and reuse... - - XrResult result; - - if (!p_swapchain.skip_acquire_swapchain) { - XrSwapchainImageAcquireInfo swapchain_image_acquire_info = { - XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO, // type - nullptr // next - }; - - result = xrAcquireSwapchainImage(p_swapchain.swapchain, &swapchain_image_acquire_info, &p_swapchain.image_index); - if (!XR_UNQUALIFIED_SUCCESS(result)) { - // Make sure end_frame knows we need to submit an empty frame - frame_state.shouldRender = false; - - if (XR_FAILED(result)) { - // Unexpected failure, log this! - print_line("OpenXR: failed to acquire swapchain image [", get_error_string(result), "]"); - return false; - } else { - // In this scenario we silently fail, the XR runtime is simply not ready yet to acquire the swapchain. - 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, &swapchain_image_wait_info); - if (!XR_UNQUALIFIED_SUCCESS(result)) { - // Make sure end_frame knows we need to submit an empty frame - frame_state.shouldRender = false; - - if (XR_FAILED(result)) { - // Unexpected failure, log this! - print_line("OpenXR: failed to wait for swapchain image [", get_error_string(result), "]"); - return false; - } else { - // Make sure to skip trying to acquire the swapchain image in the next frame - p_swapchain.skip_acquire_swapchain = true; - return false; - } - } else { - p_swapchain.skip_acquire_swapchain = false; - } - - p_swapchain.image_acquired = true; - return true; -} - -RID OpenXRAPI::get_image(OpenXRSwapChainInfo &p_swapchain) { - if (p_swapchain.image_acquired) { - return graphics_extension->get_texture(p_swapchain.swapchain_graphics_data, p_swapchain.image_index); - } else { - return RID(); - } -} - -bool OpenXRAPI::release_image(OpenXRSwapChainInfo &p_swapchain) { - if (!p_swapchain.image_acquired) { - // Already released or never acquired. - return true; - } - - p_swapchain.image_acquired = false; // Regardless if we succeed or not, consider this released. - - XrSwapchainImageReleaseInfo swapchain_image_release_info = { - XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO, // type - nullptr // next - }; - XrResult result = xrReleaseSwapchainImage(p_swapchain.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() { @@ -1904,6 +1962,18 @@ void OpenXRAPI::pre_render() { return; } + // Process any swapchains that were queued to be freed + OpenXRSwapChainInfo::free_queued(); + + Size2i swapchain_size = get_recommended_target_size(); + if (swapchain_size != main_swapchain_size) { + // Out with the old. + free_main_swapchains(); + + // In with the new. + create_main_swapchains(swapchain_size); + } + // 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 @@ -1996,9 +2066,15 @@ void OpenXRAPI::pre_render() { print_line("OpenXR: failed to being frame [", get_error_string(result), "]"); return; } + + // Reset this, we haven't found a viewport for output yet + has_xr_viewport = false; } bool OpenXRAPI::pre_draw_viewport(RID p_render_target) { + // We found an XR viewport! + has_xr_viewport = true; + if (!can_render()) { return false; } @@ -2007,8 +2083,8 @@ bool OpenXRAPI::pre_draw_viewport(RID p_render_target) { // Acquire our images for (int i = 0; i < OPENXR_SWAPCHAIN_MAX; i++) { - if (!swapchains[i].image_acquired && swapchains[i].swapchain != XR_NULL_HANDLE) { - if (!acquire_image(swapchains[i])) { + if (!main_swapchains[i].is_image_acquired() && main_swapchains[i].get_swapchain() != XR_NULL_HANDLE) { + if (!main_swapchains[i].acquire(frame_state.shouldRender)) { return false; } } @@ -2022,17 +2098,17 @@ bool OpenXRAPI::pre_draw_viewport(RID p_render_target) { } XrSwapchain OpenXRAPI::get_color_swapchain() { - return swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain; + return main_swapchains[OPENXR_SWAPCHAIN_COLOR].get_swapchain(); } RID OpenXRAPI::get_color_texture() { - return get_image(swapchains[OPENXR_SWAPCHAIN_COLOR]); + return main_swapchains[OPENXR_SWAPCHAIN_COLOR].get_image(); } RID OpenXRAPI::get_depth_texture() { // Note, image will not be acquired if we didn't have a suitable swap chain format. if (submit_depth_buffer) { - return get_image(swapchains[OPENXR_SWAPCHAIN_DEPTH]); + return main_swapchains[OPENXR_SWAPCHAIN_DEPTH].get_image(); } else { return RID(); } @@ -2057,15 +2133,19 @@ void OpenXRAPI::end_frame() { return; } - if (frame_state.shouldRender && view_pose_valid && !swapchains[OPENXR_SWAPCHAIN_COLOR].image_acquired) { - print_line("OpenXR: No viewport was marked with use_xr, there is no rendered output!"); + if (frame_state.shouldRender && view_pose_valid) { + if (!has_xr_viewport) { + print_line("OpenXR: No viewport was marked with use_xr, there is no rendered output!"); + } else if (!main_swapchains[OPENXR_SWAPCHAIN_COLOR].is_image_acquired()) { + print_line("OpenXR: No swapchain could be acquired to render to!"); + } } // 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 || !swapchains[OPENXR_SWAPCHAIN_COLOR].image_acquired) { + if (!frame_state.shouldRender || !view_pose_valid || !main_swapchains[OPENXR_SWAPCHAIN_COLOR].is_image_acquired()) { // submit 0 layers when we shouldn't render XrFrameEndInfo frame_end_info = { XR_TYPE_FRAME_END_INFO, // type @@ -2087,8 +2167,8 @@ void OpenXRAPI::end_frame() { // release our swapchain image if we acquired it for (int i = 0; i < OPENXR_SWAPCHAIN_MAX; i++) { - if (swapchains[i].image_acquired) { - release_image(swapchains[i]); + if (main_swapchains[i].is_image_acquired()) { + main_swapchains[i].release(); } } @@ -2332,7 +2412,7 @@ OpenXRAPI::OpenXRAPI() { submit_depth_buffer = GLOBAL_GET("xr/openxr/submit_depth_buffer"); } - // reset a few things that can't be done in our class definition + // Reset a few things that can't be done in our class definition. frame_state.predictedDisplayTime = 0; frame_state.predictedDisplayPeriod = 0; } diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h index 9eb51eee7a2..e8353662004 100644 --- a/modules/openxr/openxr_api.h +++ b/modules/openxr/openxr_api.h @@ -58,12 +58,28 @@ class OpenXRInterface; class OpenXRAPI { public: - struct OpenXRSwapChainInfo { + class OpenXRSwapChainInfo { + private: XrSwapchain swapchain = XR_NULL_HANDLE; void *swapchain_graphics_data = nullptr; uint32_t image_index = 0; bool image_acquired = false; bool skip_acquire_swapchain = false; + + static Vector free_queue; + + public: + _FORCE_INLINE_ XrSwapchain get_swapchain() const { return swapchain; } + _FORCE_INLINE_ bool is_image_acquired() const { return image_acquired; } + + bool create(XrSwapchainCreateFlags p_create_flags, XrSwapchainUsageFlags p_usage_flags, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size); + void queue_free(); + static void free_queued(); + void free(); + + bool acquire(XrBool32 &p_should_render); + bool release(); + RID get_image(); }; private: @@ -148,12 +164,14 @@ private: int64_t color_swapchain_format = 0; int64_t depth_swapchain_format = 0; - OpenXRSwapChainInfo swapchains[OPENXR_SWAPCHAIN_MAX]; + Size2i main_swapchain_size = { 0, 0 }; + OpenXRSwapChainInfo main_swapchains[OPENXR_SWAPCHAIN_MAX]; 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 has_xr_viewport = false; bool emulating_local_floor = false; bool should_reset_emulated_floor_height = false; @@ -241,7 +259,9 @@ private: bool setup_view_space(); bool load_supported_swapchain_formats(); bool is_swapchain_format_supported(int64_t p_swapchain_format); - bool create_swapchains(); + bool obtain_swapchain_formats(); + bool create_main_swapchains(Size2i p_size); + void free_main_swapchains(); void destroy_session(); // action map @@ -312,6 +332,7 @@ public: XrInstance get_instance() const { return instance; }; XrSystemId get_system_id() const { return system_id; }; XrSession get_session() const { return session; }; + OpenXRGraphicsExtensionWrapper *get_graphics_extension() const { return graphics_extension; }; String get_runtime_name() const { return runtime_name; }; String get_runtime_version() const { return runtime_version; }; @@ -406,11 +427,7 @@ public: // swapchains int64_t get_color_swapchain_format() const { return color_swapchain_format; } - bool create_swapchain(XrSwapchainCreateFlags p_create_flags, XrSwapchainUsageFlags p_usage_flags, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, XrSwapchain &r_swapchain, void **r_swapchain_graphics_data); - void free_swapchain(OpenXRSwapChainInfo &p_swapchain); - bool acquire_image(OpenXRSwapChainInfo &p_swapchain); - RID get_image(OpenXRSwapChainInfo &p_swapchain); - bool release_image(OpenXRSwapChainInfo &p_swapchain); + int64_t get_depth_swapchain_format() const { return depth_swapchain_format; } // action map String get_default_action_map_resource_name();