Creating and Using Swapchains
Swapchains, which are queues of images to be displayed to the user, are required for graphics pipelining. This topic covers the key considerations for using swapchains in OpenXR Native development.
To enumerate the available swapchain texture formats, you must use the xrEnumerateSwapchainFormats
function. On Quest devices, these texture formats include OpenGL internal formats like GL_RGB10_A2
(10 bits for Red, 10 bits for Green, 10 bits for Blue, 2 bits for Alpha) for OpenGL-based graphics, and Vulkan formats like VK_FORMAT_B8G8R8A8_SRGB
(8 bits for Red, 8 bits for Green, 8 bits for Blue, 8 bits for Alpha).
The Khronos Group’s
hello_xr sample app uses
xrEnumerateSwapchainFormats
to count and store swapchain formats as follows:
// Select a swapchain format.
uint32_t swapchainFormatCount;
CHECK_XRCMD(xrEnumerateSwapchainFormats(m_session, 0, &swapchainFormatCount, nullptr));
std::vector<int64_t> swapchainFormats(swapchainFormatCount);
CHECK_XRCMD(xrEnumerateSwapchainFormats(m_session, (uint32_t)swapchainFormats.size(), &swapchainFormatCount, swapchainFormats.data()));
The m_session
is an XrSession
handle that is initially defined as:
XrSession m_session{XR_NULL_HANDLE};
Swapchain formats are stored in a swapchainFormats
vector (implementation-specific). Successful calls to xrEnumerateSwapchainFormats
return:
XR_SUCCESS
XR_SESSION_LOSS_PENDING
The XR_SESSION_LOSS_PENDING
result indicates that the function temporarily simulates success because the session will soon be lost. This is returned only for an unspecified period, after which the runtime may return XR_ERROR_SESSION_LOST
, when function actually fails and the session is lost, making the XrSession
handle and all its child handles unusable. To free resources, destroy the XrSession
handle immediately.
Error codes include:
XR_ERROR_VALIDATION_FAILURE
XR_ERROR_RUNTIME_FAILURE
XR_ERROR_HANDLE_INVALID
XR_ERROR_INSTANCE_LOST
XR_ERROR_SESSION_LOST
XR_ERROR_SIZE_INSUFFICIENT
For more information on these error codes, refer to the
Error Codes section in the OpenXR specification.
A valid session is required to create swapchains. Create a swapchain using the xrCreateSwapchain
function. The hello_xr app creates swapchains as follows:
The app invokes xrGetSystemProperties
to receive information about the system the app runs on, such as graphics and tracking properties.
XrSystemProperties systemProperties{XR_TYPE_SYSTEM_PROPERTIES};
CHECK_XRCMD(xrGetSystemProperties(m_instance, m_systemId, &systemProperties));
In the following code snippet, m_configViews
is a vector of XrViewConfigurationView
. The xrEnumerateViewConfigurationViews
function enumerates system view configurations.
uint32_t viewCount;
CHECK_XRCMD(xrEnumerateViewConfigurationViews(m_instance, m_systemId, m_viewConfigType, 0, &viewCount, nullptr));
m_configViews.resize(viewCount, {XR_TYPE_VIEW_CONFIGURATION_VIEW});
CHECK_XRCMD(xrEnumerateViewConfigurationViews(m_instance, m_systemId, m_viewConfigType, viewCount, &viewCount m_configViews.data()));
The app creates a swapchain for each view by looping through all views:
// Create a swapchain for each view.
for (uint32_t i = 0; i < viewCount; i++) {
const XrViewConfigurationView& vp = m_configViews[i];
...
}
Inside the loop, the app creates each swapchain by using an
XrSwapchainCreateInfo
struct for info such as width, height, face count (that is number of faces, meaning either six textures for a swapchain per view for cubemaps, or one), and by calling
xrCreateSwapchain
:
// Create the swapchain.
XrSwapchainCreateInfo swapchainCreateInfo{XR_TYPE_SWAPCHAIN_CREATE_INFO};
swapchainCreateInfo.arraySize = 1;
swapchainCreateInfo.format = m_colorSwapchainFormat;
swapchainCreateInfo.width = vp.recommendedImageRectWidth;
swapchainCreateInfo.height = vp.recommendedImageRectHeight;
swapchainCreateInfo.mipCount = 1;
swapchainCreateInfo.faceCount = 1;
swapchainCreateInfo.sampleCount = m_graphicsPlugin->GetSupportedSwapchainSampleCount(vp);
swapchainCreateInfo.usageFlags = XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT;
Swapchain swapchain;
swapchain.width = swapchainCreateInfo.width;
swapchain.height = swapchainCreateInfo.height;
CHECK_XRCMD(xrCreateSwapchain(m_session, &swapchainCreateInfo, &swapchain.handle));
m_swapchains.push_back(swapchain);
The m_swapchains
is a vector containing swapchain handles, widths, and heights. XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT
is a flag meaning the image might be a color rendering target and m_colorSwapchainFormat
contains the selected swapchain format, initially set to -1
(implementation-specific detail):
int64_t m_colorSwapchainFormat{-1};
Calling xrEnumerateSwapchainImages
returns the number of allocated images and invoking xrEnumerateSwapchainImages
fills an array of graphics API-specific XrSwapchainImage
struct.
uint32_t imageCount;
CHECK_XRCMD(xrEnumerateSwapchainImages(swapchain.handle, 0, &imageCount, nullptr));
std::vector<XrSwapchainImageBaseHeader*> swapchainImages =
m_graphicsPlugin->AllocateSwapchainImageStructs(imageCount, swapchainCreateInfo);
CHECK_XRCMD(xrEnumerateSwapchainImages(swapchain.handle, imageCount, &imageCount, swapchainImages[0]));
m_swapchainImages.insert(std::make_pair(swapchain.handle, std::move(swapchainImages)));
The XrSwapchainImage
struct overrides XrSwapchainImageBaseHeader
. The function AllocateSwapchainImageStructs
is implementation-specific. This function allocates the buffer, initializes it, and returns an array of pointers to each swapchain image struct.
Render View to Parts of Swapchain Images
For each view, there is a separate swapchain (implementation-specific).
const Swapchain viewSwapchain = m_swapchains[i];
Each swapchain is acquired in an XrSwapchainImageAcquireInfo
struct which is set to as an enum value of XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO
.
XrSwapchainImageAcquireInfo acquireInfo{XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO};
Calling xrAcquireSwapchainImage
acquires the image in the array position returned by xrEnumerateSwapchainImages
.
uint32_t swapchainImageIndex;
CHECK_XRCMD(xrAcquireSwapchainImage(viewSwapchain.handle, &acquireInfo, &swapchainImageIndex));
An XrSwapchainImageWaitInfo
struct describes the “waiting” process for a swapchain image to be read by the compositor. Apps must wait for the compositor to finish reading from any image. By calling the xrWaitSwapchainImage
function, apps wait for the oldest acquired swapchain image, and then they must release it before waiting on the next acquired one.
XrSwapchainImageWaitInfo waitInfo{XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO};
waitInfo.timeout = XR_INFINITE_DURATION;
CHECK_XRCMD(xrWaitSwapchainImage(viewSwapchain.handle, &waitInfo));
The XrCompositionLayerProjection
struct represents projected images rendered per eye and the XrCompositionLayerProjectionView
struct contains info such as Field of View, location, or orientation of a projection element. This information populates a projectionLayerViews
vector (implementation-specific detail).
projectionLayerViews[i] = {XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW};
projectionLayerViews[i].pose = m_views[i].pose;
projectionLayerViews[i].fov = m_views[i].fov;
projectionLayerViews[i].subImage.swapchain = viewSwapchain.handle;
projectionLayerViews[i].subImage.imageRect.offset = {0, 0};
projectionLayerViews[i].subImage.imageRect.extent = {viewSwapchain.width, viewSwapchain.height};
Based on the graphics plugin, cubes primitives image renders (implementation-specific detail) at a certain position and with a given width and height.
const XrSwapchainImageBaseHeader* const swapchainImage = m_swapchainImages[viewSwapchain.handle][swapchainImageIndex];
m_graphicsPlugin->RenderView(projectionLayerViews[i], swapchainImage, m_colorSwapchainFormat, cubes);
The app then releases the swapchain image through xrReleaseSwapchainImage
.
XrSwapchainImageReleaseInfo releaseInfo{XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO};
CHECK_XRCMD(xrReleaseSwapchainImage(viewSwapchain.handle, &releaseInfo));