During the past few weeks I have been experimenting with Virtual Reality, and more precisely with the OpenXR standard. My initial objective is simple:  to enhance one of the existing atmospheric simulation to a 3D model and visualise it using a VR headset.

The initial thinking was to use a headset like the Oculus Quest. Unfortunately, that does not really work, and I soon realised that the best option was to use a tethered headset: That is, where the 3D/VR engine runs directly on a Linux server, and where the headset is just used as a simple display and orientation sensor.

While looking for an affordable VR headset (aka HMD, for Head Mounted Device), I stumbled on the Viulux V9,  which can be bought for $60 on Taobao.

Viulux V9 HMD (Head Mounted Device), aka VR headset

This HMD has a quite impressive spec considering its cost: 2*1440*1440 displays with a 120 Hz refresh rate, 9 DOF orientational sensor, and extension for external SLAM sensors like  NOLA. So, I just bought one from the Taobao shop.

Inside the Viulux V9 hardware

Opening the Viulux V9 does not require a screw driver: Just need to remove the lid which is clipped into the headset.

The inner PCB is not overly complex: It is composed of DP to MIPI display IC,  two STM IC, and an Eprom, most likely to store the display information.

The display IC is the TC358860 from Toshiba and is just used to "convert the Embedded Display Port (eDPTM) video stream into an MIPI® DSI stream". The two other ICs are STM32, and it is not quite clear why the board needs two of them. Most likely, one is dedicated just to handle the real-time sensor data, and the other to handle the USB connection and other controls (audio volume, etc).

The orientational sensors are located on the other side of the PCB. It consist of a 6 DOF Accelerometer, Gyroscope (MPU 6000) and 3DOF Compass (HMC58883L).

Voila, that's it for the hardware. Now, let's have a look at what it takes from the software side to get the Viulux V9 to work.

OpenXR, aka the AR/VR standard

When it comes to Virtual Reality, the standard is called OpenXR. At first glance, it can look quite complex, but to make thing simple, one can consider OpenXR as a compositor, which helps a 3D rendering application to compose the frames needed for the two displays, by providing to the application the scene orientation, position and timing. Open XR also has an extensive set of API for handling auxiliary devices (eg gears), and relative spaces positioning, but we can skip this part for now.

Monado, the OpenXR runtime

To get started with getting the VR application to run on the Viulux HMD, one first need an OpenXR runtime which implements the above mentioned compositor. The very good news is that  the team at Collabora together with Khronos have released an open source version on the OpenXR runtime called Monado, which does support many headset. The bad news is that the Viulux HMD does not work out of the box, but fortunately, it is an easy thing to fix.

Getting Monado to support Viulux

The Viulux V9 HMD has been designed to be a drop-in replacement for the Oculus Rift HMD, meaning that both USB and Video data should be inter-exchangeable. When plugin the USB  cable in, one can see the following device (using lsusb)

Bus 001 Device 046: ID 2833:0001 Oculus VR, Inc. Rift Developer Kit 1
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               2.00
  bDeviceClass            0
  bDeviceSubClass         0
  bDeviceProtocol         0
  bMaxPacketSize0        64
  idVendor           0x2833 Oculus VR, Inc.
  idProduct          0x0001 Rift Developer Kit 1
  bcdDevice            0.01
  iManufacturer           1 Inlife 3D, Inc.
  iProduct                2 Tracker DK
  iSerial                 3 AAAAAAAAAAAA
  bNumConfigurations      1

It does present itself as an Oculus Rift, with a manufacturer defined as "Inline 3D, Inc." which is the actual Viulux manufacturer name.

The way Monado handles the HMD is by using the OpenHMD project, which implements the driver for many headsets, and specifically  the Oculus Rift one.

Introducing OpenHMD

OpenHMD supports many head mounted devices (HMD). The way it detects HMD is mainly by scanning the USB devices and matching against a specific Product name. In the case of the Oculus rift it is done in the OpenHMD/ src/ drv_oculus_rift/ rift.c file:

static void get_device_list(ohmd_driver* driver, ohmd_device_list* list)
{
	// enumerate HID devices and add any Rifts found to the device list

	rift_devices rd[RIFT_ID_COUNT] = {
		{ "Rift (DK1)", OCULUS_VR_INC_ID, 0x0001,	-1, REV_DK1 },
		{ "Rift (DK2)", OCULUS_VR_INC_ID, 0x0021,	-1, REV_DK2 },
		{ "Rift (DK2)", OCULUS_VR_INC_ID, 0x2021,	-1, REV_DK2 },
        ...
	};

	for(int i = 0; i < RIFT_ID_COUNT; i++){
		struct hid_device_info* devs = hid_enumerate(rd[i].company, rd[i].id);
		struct hid_device_info* cur_dev = devs;

		while (cur_dev) {
			// We need to check the manufacturer because other companies (eg: VR-Tek)
			// are reusing the Oculus DK1 USB ID for their own HMDs
			if(ohmd_wstring_match(cur_dev->manufacturer_string, L"Oculus VR, Inc.") &&
			   (rd[i].iface == -1 || cur_dev->interface_number == rd[i].iface)) {
				int id = 0;
				ohmd_device_desc* desc = &list->devices[list->num_devices++];

The issue in the above detection code is that it explicitly checks for manufacturer. The reason is that other companies (like VR-Tek, or Inlife in our case) use the same idProduct and idVendor, but have different distortion and aberration  parameters. So, to avoid using the wrong parameters for non Oculus manufactured HMD, OpenHMD is explicitly disabling those devices.

There are many solutions to this issue, and one straightforward is to create a new ID for those non Oculus manufactured devices, and check agains the manufacturer name. This, IMO, is more efficient that just duplicating the rift driver and make it a Viulux copy, since 99% of the code is reused between Viulux and Rift. The change proposal can be see from this commit

All fine, let's run the app then

With the previous change, the Viulux headset is automatically detected by OpenHMD. By default, OpenHMD will create a new window (using SDL) on the main screen, and to active the Viulux display, one needs to "move" the window on the Viulux display. Using fedora, this can be done by right clicking on the window and selecting the "Move to the monitor on the (...|right)".  

Voila, that's all you need to get OpenXR to run on the Viulux HMD. Next, Just move the headset and checkout the scene!

Next Steps

That was a quick post to get familiar with OpenXR, Monado and OpenHMD.

Next step is to port the exiting 2D atmospheric simulation to a 3D simulating running using OpenXR. I will describe this experiment in the next post.