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

This commit is contained in:
Bastiaan Olij 2024-01-22 20:32:41 +11:00
parent 9d6bdbc56e
commit c388fe2ba7
4 changed files with 346 additions and 241 deletions

View File

@ -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();
}

View File

@ -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);

View File

@ -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);
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;
}

View File

@ -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();