#ifndef XR_CPP
#define XR_CPP
// OpenXR metaprogramming boilerplate.
#if defined(_WIN32)
#define XR_USE_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
// We are using SDL's Windows video driver.
#define SDL_VIDEO_DRIVER_WINDOWS
#elif defined(__ANDROID__)
#define XR_USE_PLATFORM_ANDROID
// TODO?
#else
#define XR_USE_PLATFORM_XLIB
#include <X11/Xlib.h>
// We are using SDL's X11 video driver.
#define SDL_VIDEO_DRIVER_X11
// Fix for possible SDL bug: Not using Windows? Don't use the Windows video driver.
//#undef SDL_VIDEO_DRIVER_WINDOWS
// Define GLX stuff to avoid including GLX, which conflicts with glad.
typedef struct __GLXFBConfigRec *GLXFBConfig;
typedef XID GLXDrawable;
typedef struct __GLXcontextRec *GLXContext;
extern "C" {
//extern Display *XOpenDisplay(_Xconst char*); // XOpenDisplay requires linking Xlib.
extern GLXDrawable glXGetCurrentDrawable();
extern GLXContext glXGetCurrentContext();
}
#endif
#include <SDL2/SDL.h>
#define XR_USE_GRAPHICS_API_OPENGL
#include <openxr/openxr.h>
#include <openxr/openxr_platform.h>
// OpenXR swapchains.
struct xr_swapchain_t {
uint32_t *swapchain_lengths;
XrSwapchainImageOpenGLKHR **images;
XrSwapchain *swapchains;
uint32_t swapchain_count;
};
// OpenXR state.
struct xr_state_t {
// Platform-specific OpenXR global variables.
#if defined(_WIN32)
XrGraphicsBinding??? bind; // TODO.
#elif defined(__ANDROID__)
// TODO?
#else
XrGraphicsBindingOpenGLXlibKHR bind;
#endif
XrInstance xri;
XrSystemId sys;
XrSession session;
XrReferenceSpaceType spacetype;
XrSpace space;
uint32_t view_count;
XrViewConfigurationView config_views[2];
XrView views[2];
XrCompositionLayerProjectionView projection_views[2];
// Swapchains correspond to PROJECTION, DEPTH, and LAST.
// (See the openxr-simple-playground Swapchain enum.)
xr_swapchain_t swapchains[3];
};
// Functions.
void XR_AllocateSwapchain(xr_swapchain_t *swapchain, uint32_t view_count);
void XR_FreeSwapchain(xr_swapchain_t *swapchain);
bool XR_Init(const char *name, bool apilayer);
bool XR_StartSession(SDL_Window *window);
xr_state_t *XR_GetState();
// Implementation begins here.
#ifndef XR_SEPARATE_COMPILATION
#include <SDL2/SDL_syswm.h>
#include <stdio.h>
#include <string.h>
xr_state_t xrstate = {};
static XrPosef xr_identity = {.orientation = {.x = 0, .y = 0, .z = 0, .w = 1.0},
.position = {.x = 0, .y = 0, .z = 0}};
void XR_AllocateSwapchain(xr_swapchain_t *swapchain, uint32_t view_count){
swapchain->swapchains = (XrSwapchain*)malloc(sizeof(XrSwapchain) * view_count);
swapchain->swapchain_lengths = (uint32_t*)malloc(sizeof(uint32_t) * view_count);
swapchain->images = (XrSwapchainImageOpenGLKHR**)malloc(sizeof(XrSwapchainImageOpenGLKHR*) * view_count);
swapchain->swapchain_count = view_count;
}
void XR_FreeSwapchain(xr_swapchain_t *swapchain){
free(swapchain->swapchains);
free(swapchain->images);
free(swapchain->swapchain_lengths);
}
/*
xr_swapchain_t CreateSwapchains(
int64_t format,
uint32_t sample_count,
uint32_t w,
uint32_t h,
XrSwapchainUsageFlags usage_flags){
XrSwapchainCreateInfo swapchain_create_info = {
.type = XR_TYPE_SWAPCHAIN_CREATE_INFO,
.usageFlags = usage_flags,
.createFlags = 0,
.format = format,
.sampleCount = sample_count,
.width = w,
.height = h,
.faceCount = 1,
.arraySize = 1,
.mipCount = 1,
.next = nullptr
};
XrResult result;
result =
xrCreateSwapchain(session, &swapchain_create_info, &swapchain->swapchains[num_swapchain]);
if(result != XR_SUCCESS){
fprintf(stderr, "Failed to create swapchain\n", i);
return false;
}
// The runtime controls how many textures we have to be able to render to
// (e.g. "triple buffering")
result = xrEnumerateSwapchainImages(swapchain->swapchains[num_swapchain], 0,
&swapchain->swapchain_lengths[num_swapchain], nullptr);
if(result != XR_SUCCESS){
fprintf(stderr, "Failed to enumerate swapchains\n", i);
return false;
}
swapchain->images[num_swapchain] =
malloc(sizeof(XrSwapchainImageOpenGLKHR) * swapchain->swapchain_lengths[num_swapchain]);
for(uint32_t j = 0; j < swapchain->swapchain_lengths[num_swapchain]; j++){
swapchain->images[num_swapchain][j].type = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_KHR;
swapchain->images[num_swapchain][j].next = nullptr;
}
result = xrEnumerateSwapchainImages(
swapchain->swapchains[num_swapchain], swapchain->swapchain_lengths[num_swapchain],
&swapchain->swapchain_lengths[num_swapchain],
(XrSwapchainImageBaseHeader*)swapchain->images[num_swapchain]);
}
??? CreateSwapchains(???){
// In the frame loop we render into OpenGL textures we receive from the runtime here.
swapchains = malloc(sizeof(XrSwapchain) * view_count);
swapchain_lengths = malloc(sizeof(uint32_t) * view_count);
images = malloc(sizeof(XrSwapchainImageOpenGLKHR*) * view_count);
for(uint32_t i = 0; i < view_count; i++){
XrSwapchainCreateInfo swapchain_create_info = {
.type = XR_TYPE_SWAPCHAIN_CREATE_INFO,
.usageFlags = XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT,
.createFlags = 0,
.format = color_format,
.sampleCount = viewconfig_views[i].recommendedSwapchainSampleCount,
.width = viewconfig_views[i].recommendedImageRectWidth,
.height = viewconfig_views[i].recommendedImageRectHeight,
.faceCount = 1,
.arraySize = 1,
.mipCount = 1,
.next = nullptr
};
result = xrCreateSwapchain(session, &swapchain_create_info, &swapchains[i]);
if(result != XR_SUCCESS){
fprintf(stderr, "Failed to create swapchain %d\n", i);
return 1;
}
// The runtime controls how many textures we have to be able to render to
// (e.g. "triple buffering")
result = xrEnumerateSwapchainImages(swapchains[i], 0, &swapchain_lengths[i], nullptr);
if(result != XR_SUCCESS){
fprintf(stderr, "Failed to enumerate swapchains\n");
return 1;
}
images[i] = malloc(sizeof(XrSwapchainImageOpenGLKHR) * swapchain_lengths[i]);
for (uint32_t j = 0; j < swapchain_lengths[i]; j++) {
images[i][j].type = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_KHR;
images[i][j].next = nullptr;
}
result =
xrEnumerateSwapchainImages(swapchains[i], swapchain_lengths[i], &swapchain_lengths[i],
(XrSwapchainImageBaseHeader*)images[i]);
if(result != XR_SUCCESS){
fprintf(stderr, "Failed to enumerate swapchain images\n");
return 1;
}
}
}
void CreateSwapchains(){
// Read graphics properties for preferred swapchain length and logging.
XrSystemProperties systemProperties{XR_TYPE_SYSTEM_PROPERTIES};
CHECK_XRCMD(xrGetSystemProperties(m_instance.Get(), m_systemId, &systemProperties));
// Select color and depth swapchain pixel formats.
const auto [colorSwapchainFormat, depthSwapchainFormat] = SelectSwapchainPixelFormats();
// Query and cache view configuration views.
uint32_t viewCount;
CHECK_XRCMD(xrEnumerateViewConfigurationViews(m_instance.Get(), m_systemId, m_primaryViewConfigType, 0, &viewCount, nullptr));
CHECK(viewCount == m_stereoViewCount);
m_renderResources->ConfigViews.resize(viewCount, {XR_TYPE_VIEW_CONFIGURATION_VIEW});
CHECK_XRCMD(xrEnumerateViewConfigurationViews(
m_instance.Get(), m_systemId, m_primaryViewConfigType, viewCount, &viewCount, m_renderResources->ConfigViews.data()));
// Using texture array for better performance, so requiring left/right views have identical sizes.
const XrViewConfigurationView& view = m_renderResources->ConfigViews[0];
CHECK(m_renderResources->ConfigViews[0].recommendedImageRectWidth ==
m_renderResources->ConfigViews[1].recommendedImageRectWidth);
CHECK(m_renderResources->ConfigViews[0].recommendedImageRectHeight ==
m_renderResources->ConfigViews[1].recommendedImageRectHeight);
CHECK(m_renderResources->ConfigViews[0].recommendedSwapchainSampleCount ==
m_renderResources->ConfigViews[1].recommendedSwapchainSampleCount);
// Use the system's recommended rendering parameters.
const uint32_t imageRectWidth = view.recommendedImageRectWidth;
const uint32_t imageRectHeight = view.recommendedImageRectHeight;
const uint32_t swapchainSampleCount = view.recommendedSwapchainSampleCount;
// Create swapchains with texture array for color and depth images.
// The texture array has the size of viewCount, and they are rendered in a single pass using VPRT.
const uint32_t textureArraySize = viewCount;
m_renderResources->ColorSwapchain =
CreateSwapchainD3D11(m_session.Get(),
colorSwapchainFormat,
imageRectWidth,
imageRectHeight,
textureArraySize,
swapchainSampleCount,
0,
XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT);
m_renderResources->DepthSwapchain =
CreateSwapchainD3D11(m_session.Get(),
depthSwapchainFormat,
imageRectWidth,
imageRectHeight,
textureArraySize,
swapchainSampleCount,
0,
XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT);
// Preallocate view buffers for xrLocateViews later inside frame loop.
m_renderResources->Views.resize(viewCount, {XR_TYPE_VIEW});
}
*/
bool XR_Init(const char *appname, bool apilayer){
// OpenXR setup.
const char *extensions[] = {
XR_KHR_OPENGL_ENABLE_EXTENSION_NAME,
XR_EXT_DEBUG_UTILS_EXTENSION_NAME // Needed for validation layer.
};
uint32_t extension_count = sizeof(extensions) / sizeof(extensions[0]);
const char *layers[] = {
"XR_APILAYER_LUNARG_core_validation" // Magic string needed because this is not core.
};
uint32_t layer_count = sizeof(layers) / sizeof(layers[0]);
XrInstanceCreateInfo create_info = {};
create_info.type = XR_TYPE_INSTANCE_CREATE_INFO;
create_info.next = nullptr;
create_info.createFlags = 0;
create_info.enabledExtensionCount = extension_count;
create_info.enabledExtensionNames = (const char * const *)extensions;
create_info.enabledApiLayerNames = (const char * const *)layers;
create_info.applicationInfo.applicationVersion = 1;
create_info.applicationInfo.engineVersion = 1;
create_info.applicationInfo.apiVersion = XR_CURRENT_API_VERSION;
// Safely copy the strings, leaving room for the null character.
strncpy(create_info.applicationInfo.applicationName, appname, XR_MAX_APPLICATION_NAME_SIZE - 1);
strncpy(create_info.applicationInfo.engineName, "RSOD Engine", XR_MAX_APPLICATION_NAME_SIZE - 1);
// Create the instance and get the system.
XrSystemGetInfo sys_info = {};
XrResult result;
// Try for the validation layer if applicable, then try without.
for(int i = 0; i < 2; i++){
// API layer toggle.
create_info.enabledApiLayerCount = apilayer ? layer_count : 0;
if((result = xrCreateInstance(&create_info, &xrstate.xri)) == XR_SUCCESS){
// Instance creation successful. Get the system and break.
printf("xrCreateInstance succeeded.\n");
sys_info.type = XR_TYPE_SYSTEM_GET_INFO;
sys_info.next = nullptr;
sys_info.formFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY;
if((result = xrGetSystem(xrstate.xri, &sys_info, &xrstate.sys)) != XR_SUCCESS)
fprintf( stderr, "xrGetSystem failed: %d\n", (int)result );
if(result == XR_ERROR_FORM_FACTOR_UNAVAILABLE)
fprintf(stderr, "You must switch your device to VR mode.\n");
if(result == XR_ERROR_FORM_FACTOR_UNSUPPORTED)
fprintf(stderr, "No VR device found.\n");
break;
}else{
// Instance creation failed. Try again if applicable.
fprintf(stderr, "xrCreateInstance failed: %d\n", (int)result);
if(apilayer && result == XR_ERROR_API_LAYER_NOT_PRESENT){
apilayer = false;
}else{
return false;
}
}
}
// Get XR view info.
if(result == XR_SUCCESS){
xrstate.config_views[0].type = XR_TYPE_VIEW_CONFIGURATION_VIEW;
xrstate.config_views[1].type = XR_TYPE_VIEW_CONFIGURATION_VIEW;
result = xrEnumerateViewConfigurationViews(
xrstate.xri, xrstate.sys,
XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO,
2, &xrstate.view_count, xrstate.config_views
);
if(xrstate.view_count != 2){
fprintf(stderr,
"XR device has %d views, needed exactly 2. Aborting XR initialization.\n",
(int)xrstate.view_count);
return false;
}
printf("Successfully created an XR instance and system handle.\n");
}else{
fprintf(stderr, "Failed to create an XR instance and system handle.\n");
return false;
}
// Initialize view arrays.
XR_AllocateSwapchain(&xrstate.swapchains[0], xrstate.view_count);
// TODO: Swapchains.
for(uint32_t i = 0; i < xrstate.view_count; i++){
xrstate.views[i].type = XR_TYPE_VIEW;
xrstate.views[i].next = nullptr;
xrstate.projection_views[i].type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW;
xrstate.projection_views[i].next = nullptr;
// TODO: swapchains
xrstate.projection_views[i].subImage.swapchain = xrstate.swapchains[0].swapchains[i];
xrstate.projection_views[i].subImage.imageArrayIndex = 0;
xrstate.projection_views[i].subImage.imageRect.offset.x = 0;
xrstate.projection_views[i].subImage.imageRect.offset.y = 0;
xrstate.projection_views[i].subImage.imageRect.extent.width =
xrstate.config_views[i].recommendedImageRectWidth;
xrstate.projection_views[i].subImage.imageRect.extent.height =
xrstate.config_views[i].recommendedImageRectHeight;
// projection_views[i].{pose, fov} have to be filled every frame in frame loop
}
printf("Eye resolution: %dx%d - %dx%d\n",
xrstate.config_views[0].recommendedImageRectWidth,
xrstate.config_views[0].recommendedImageRectHeight,
xrstate.config_views[0].maxImageRectWidth,
xrstate.config_views[0].maxImageRectHeight);
printf("Swapchain samples: %d-%d\n",
xrstate.config_views[0].recommendedSwapchainSampleCount,
xrstate.config_views[0].maxSwapchainSampleCount);
// Graphics requirements.
// Note: OpenXR drivers don't support GLES on desktop. Use full OpenGL.
PFN_xrGetOpenGLGraphicsRequirementsKHR GetRequirements = nullptr;
result = xrGetInstanceProcAddr(
xrstate.xri,
"xrGetOpenGLGraphicsRequirementsKHR",
(PFN_xrVoidFunction*)&GetRequirements
);
if(result != XR_SUCCESS){
fprintf(stderr, "Failed to link xrGetOpenGLGraphicsRequirementsKHR\n");
return false;
}
XrGraphicsRequirementsOpenGLKHR gfx_req = {};
gfx_req.type = XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_KHR;
gfx_req.next = nullptr;
gfx_req.minApiVersionSupported = XR_MAKE_VERSION(3, 3, 0);
gfx_req.maxApiVersionSupported = XR_MAKE_VERSION(3, 3, 0);
result = GetRequirements(xrstate.xri, xrstate.sys, &gfx_req);
if(result != XR_SUCCESS){
fprintf(stderr, "Your current OpenXR runtime does not support OpenGL 3.3. Try Monado or SteamVR instead.\n");
return false;
}
return true;
}
bool XR_StartSession(SDL_Window *window){
SDL_SysWMinfo wmi;
SDL_VERSION(&wmi.version);
if(!SDL_GetWindowWMInfo(window, &wmi)){
fprintf(stderr, "SDL_GetWindowWMInfo failed.\n");
return false;
}
// Graphics binding.
// https://amini-allight.org/post/openxr-tutorial-part-5
xrstate.bind = {};
xrstate.bind.type = XR_TYPE_GRAPHICS_BINDING_OPENGL_XLIB_KHR;
xrstate.bind.next = nullptr;
//xrstate.bind.xDisplay = XOpenDisplay(nullptr);
xrstate.bind.xDisplay = wmi.info.x11.display; // X11 display
xrstate.bind.glxDrawable = glXGetCurrentDrawable(); // GLX drawable
xrstate.bind.glxContext = glXGetCurrentContext(); // GLX context
//xrstate.bind.visualid = ???; // visualid is optional.
//xrstate.bind.glxFBConfig = ???; // GLX config is optional.
// Session create info.
XrSessionCreateInfo session_info = {};
session_info.type = XR_TYPE_SESSION_CREATE_INFO;
session_info.next = &xrstate.bind;
session_info.createFlags = 0;
session_info.systemId = xrstate.sys;
XrResult result = xrCreateSession(xrstate.xri, &session_info, &xrstate.session);
if(result == XR_SUCCESS){
printf("Successfully created XR session.\n");
}else{
fprintf(stderr, "Failed to create XR session.\n");
return false;
}
// Many runtimes support at least STAGE and LOCAL but not all do.
// Sophisticated apps might check if the chosen one is supported and try another one if not.
// Here we will get an error from xrCreateReferenceSpace() and exit.
XrReferenceSpaceCreateInfo play_space_create_info = {.type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO,
.next = nullptr,
.referenceSpaceType =
XR_REFERENCE_SPACE_TYPE_STAGE,
.poseInReferenceSpace = xr_identity};
result = xrCreateReferenceSpace(xrstate.session, &play_space_create_info, &xrstate.space);
if(result == XR_SUCCESS){
printf("Successfully created play space.\n");
}else{
fprintf(stderr, "Failed to create play space.\n");
return false;
}
// TODO: Swapchain formats, action sets, bindings, glXMakeCurrent, xrAttachSessionActionSets.
return true;
}
xr_state_t *XR_GetState(){
return &xrstate;
}
#endif // ifndef XR_SEPARATE_COMPILATION
#endif // ifndef XR_CPP