mse / RSOD (public) (License: CC0 and other licenses) (since 2025-03-01) (hash sha1)
Free software FPS engine

/src/xr.cpp (30f842d8b6bef7a4bc7ac450aace260a6b204e22) (16747 bytes) (mode 100644) (type blob)

#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


Mode Type Size Ref File
100644 blob 170 42b08f467f099371d96fc6f35757c4510bfe7987 .gitignore
100644 blob 13711 25dbc408cf6dce13f6aac062d31879240d7f4f5e Makefile
100644 blob 2396 fc392e2bdb9b9ac4abb5fded56a32df18102c407 README.md
040000 tree - 56f2c438287fb1f800a0493d6b0d6907dc648241 base
100644 blob 487269 29cfd3578eb40b1f039e271bcaa81af49d1b7f3c gamecontrollerdb.txt
040000 tree - 99c807d76953d139e1c10f9f5c053455b9a79d94 include
100755 blob 257 5da83586fddf2984c55ed40e03f43c504552e8aa makeanib
100755 blob 677 e3ce6d4069311dcbb89f1d1166a00a2f9d2934b5 package-steam
100644 blob 879 1aa6cc46749b8ad10c792dd501a23d62886ae951 package-steam-build-demo.vdf
100644 blob 887 accdaa60338652380422b5c2caa0651cc8b0ea7e package-steam-build-playtest.vdf
100644 blob 879 0b12a202f0ef30f781c82678a68c9a5093365e7e package-steam-build.vdf
100644 blob 1182 81610ae8ed0d39b7b1412ef22eff86289fe2adb2 rsod.cbp
040000 tree - 43b2388b0f36e437807a966f6cf9a0dfae5fef7a src
Hints:
Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://rocketgit.com/user/mse/RSOD

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@ssh.rocketgit.com/user/mse/RSOD

Clone this repository using git:
git clone git://git.rocketgit.com/user/mse/RSOD

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a merge request:
... clone the repository ...
... make some changes and some commits ...
git push origin main