Răsfoiți Sursa

OpenXR: Improve swapchain logic and fix swapchain update when render target multiplier is changed.

Bastiaan Olij 1 an în urmă
părinte
comite
c388fe2ba7

+ 19 - 15
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();
 }

+ 4 - 0
modules/openxr/extensions/platform/openxr_vulkan_extension.cpp

@@ -229,6 +229,10 @@ void OpenXRVulkanExtension::get_usable_swapchain_formats(Vector<int64_t> &p_usab
 }
 
 void OpenXRVulkanExtension::get_usable_depth_formats(Vector<int64_t> &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);

+ 298 - 218
modules/openxr/openxr_api.cpp

@@ -63,6 +63,198 @@
 #define OPENXR_LOADER_NAME "libopenxr_loader.so"
 #endif
 
+////////////////////////////////////
+// OpenXRAPI::OpenXRSwapChainInfo
+
+Vector<OpenXRAPI::OpenXRSwapChainInfo> 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<OpenXRExtensionWrapper *> 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<int64_t> 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<int64_t> 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);
-	}
-
-	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;
+void OpenXRAPI::free_main_swapchains() {
+	for (int i = 0; i < OPENXR_SWAPCHAIN_MAX; i++) {
+		main_swapchains[i].queue_free();
 	}
-
-	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;
 }

+ 25 - 8
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<OpenXRSwapChainInfo> 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();