Skip to main content
Inspiring
April 23, 2014

JOVR 0.3.2.5: Java/Maven bindings for 0.3.2 [Updated 7/22]

Update 7/22:
Updated to 0.3.2.5 (apparently I made a 0.3.2.4 release that I didn't announce. Must not have been very important)

Having had some conversations with the Oculus devs and done some writing on the topic I've come to understand the Timewarp functionality a little better. More importantly I understand the reasoning behind the timing logic in the SDK used to perform the buffer swapping. However, the SDK requirement of native handles is still somewhat overbearing in my opinion.

To rectify the situation and allow the SDK to perform the appropriate timing actions I've altered the SDK slightly to add support for providing a callback function for performing the buffer swap.


typedef void (*ovrSwapBufferCallback)(void * userData);

OVR_EXPORT void ovrHmd_SetSwapBuffersCallback(ovrHmd hmd,
ovrSwapBufferCallback callback,
void * userData);


Note that the ovrDistortionCap_NoSwapBuffers flag still exists, but it should not be used in combination with this function. If you enable ovrDistortionCap_NoSwapBuffers then the SDK will not call your buffer swapping callback.




Update 6/3:
Updated to 0.3.2.3

There are some breaking changes in this API due to some cleanup and some moved classes. Sorry for the inconvenience.


  • Added Guava dependency

  • Added some argument checking with more informative errors than a crash inside the binary would provide

  • Added JSR305 dependency

  • Updated some Pointer types to String

  • Moved OvrLibrary.ovrHmd type to Hmd

  • Removed the need to expose the .ByValue types to clients

  • Added some static methods to Hmd to remove the need to call OvrLibrary.INSTANCE directly

  • Changed some functions which had output arguments to return the output instead: i.e. 'void foo(String[] results)' becomes 'String [] foo()'

  • Defaulted all API values to OpenGL.

  • Added some unit tests


Known Bugs

  • If you call Hmd.startSensor() within 0.07 seconds of calling OvrLibrary.ovrHmd_Initialize(), the reported position can be incorrect. This is a bug in the Oculus SDK itself, and will be fixed in my next build. You can work around the bug by putting a tenth of a second delay in after calling the ovrHmd_Initialize() function.


Update 5/30:
Updated to 0.3.2.2

Modified the OculusSDK to include a new value for ovrDistortionCaps: ovrDistortionCap_NoSwapBuffers

If this value isn't specified in ovrHmd_ConfigureRendering then the Oculus SDK will revert to it's default behavior of performing the SwapBuffers() call inside the SDK. However, if you include this value when configuring rendering, then the SDK will continue to exhibit the modified behavior I've introduced, and NOT perform the SwapBuffers() call in the SDK.

Most Java applications will want to include this new distortion cap value, since the parameters required for buffer swapping are not normally exposed to Java client applications.

Update 5/26:
Updated to 0.3.2.1. The Linux binary was a 32 bit shared object, instead of 64 bit.

Update 5/24:
I've built a new release of the Maven jars to connect with the Oculus SDK version 0.3.2. Like my previous releases, I've disabled the buffer swapping in the SDK, which means you don't need to pass any window information to the SDK in the configureRendering call.

I've also renamed the Artifact so that I can reset the versioning and match the Java jar version to the Oculus SDK version. Adding a dependency on the Java bindings should now look like this.


<dependency>
<groupId>org.saintandreas</groupId>
<artifactId>jovr</artifactId>
<version>[0.3.2.0, 0.3.3)</version>
</dependency>


The Jar no longer depends on another jar for the binaries. Rather, the binary shared library files are baked into the main jar. The only dependency of this jar is the JNA library, which I have bumped up to 4.1.

Since the SDK now supports Linux natively, I'm using their Linux code. I've also added the OSX binary as well. Both Linux and OSX are 64-bit only. Windows still has 32 and 64 bit jars built in.

Update 5/21:
I've migrated my releases to the Maven central repository. You can now use the Oculus SDK simply by adding the following into your maven project file:


<dependency>
<groupId>org.saintandreas</groupId>
<artifactId>jocular</artifactId>
<version>[2.0.2, 3)</version>
</dependency>


My example project has been updated to reflect the latest version of the code and the central repository hosting.

The Java example project can be found independently in it's own repository here: https://github.com/jherico/jocular-examples

The main branch of the examples project consists of only three files. The Maven project file, a RiftApp base class for creating simple applications for the Rift, and a single RiftDemo class which actually draws a simple scene with a skybox and number of cubes surrounding the user.

Update 5/4:

As I mentioned in this thread, I've refactored my Github copy of the Oculus SDK to extract the C API out of the main body of code and made an individual project out of it, which produces a standalone DLL. I've now created a JNA binding for that DLL that will let you use the C API from Java.

The Java project for the bindings can be found here: https://github.com/jherico/jocular <--- new URL

The URL has changed from the old location in my OculusSDK repository, because I forgot how annoying it is to try to release a Maven project from within a larger repository.

Right now, in order to use it, you'll need to use CMake and whatever tool you desire to build the DLL and place it in your system path. Having done so, you can then use the Java OvrLibrary class to access all the C API functions.

This is now usable directly from Maven without the need to build or download the Oculus SDK. The Jocular artifact has a dependency on a 'jocular-platform' artifact which contains binaries for Windows (32 and 64 bit) and Linux (64 bit only).

What works:

  • SDK startup and shutdown

  • Fetching the HMD description

  • Head tracking

  • SDK side rendering distortion (with the caveat that you still have to do your own buffer swapping)

  • Conversion from the struct style matrix, vector and quaternion types in the OVR interface to my own Java types with full functionality (based on the JMonkeyEngine math types)


Anything not explicitly mentioned here is basically untested.

Roadmap:

  • Done. Get SDK distortion rendering functional (without swapbuffers)

  • Done. Create an example Java application

  • Done. Create a build mechanism that creates a jar with the platform binary built

  • Done. Deployment to my maven repository

  • Done. Helper methods to convert the OVR math types to Java equivalents with full functionality

  • Done. Porting to Linux

  • Porting to OSX


Here's an example of what using the API looks like:


OvrLibrary.INSTANCE.ovr_Initialize();
ovrHmd hmd = ovrHmd.create(0);
hmd.startSensor(0, 0);
...
hmd.stopSensor();
hmd.destroy();
OvrLibrary.INSTANCE.ovr_Shutdown();


You can also use my helper class 'RiftApp' which automatically detects the SDK and does all the setup and distortion work. All you have to do is subclass it and implement a renderScene() method like this one:


@Override
public void renderScene() {
glEnable(GL_DEPTH_TEST);
glClearColor(0.2f, 0.2f, 0.2f, 1);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

MatrixStack mv = MatrixStack.MODELVIEW;
mv.push();
{
mv.scale(OvrLibrary.OVR_DEFAULT_IPD);
program.use();
MatrixStack.bindAll(program);
geometry.bindVertexArray();
geometry.draw();
VertexArray.unbind();
Program.clear();
}
mv.pop();
}


You can see the full example code here.

Many props to the developers on the JNA and JNAerator projects that made doing this a breeze.

    46 replies

    Up and Coming
    May 27, 2014
    jovr 0.3.2.1 seems to be working! No more "library not found" and the 64-bit version seems to be working fine. Have some more porting to do, but rickard & I are a bit skiddish of implementing the SDK-side distortion :|
    Up and Coming
    May 27, 2014
    jherico, what license are you using for JOVR? Here is jME's:

    http://hub.jmonkeyengine.org/wiki/doku.php/bsd_license

    ... we just want to make sure there are no conflicts.
    jhericoAuthor
    Inspiring
    May 27, 2014
    "phr00t" wrote:
    jherico, what license are you using for JOVR? Here is jME's:

    http://hub.jmonkeyengine.org/wiki/doku.php/bsd_license

    ... we just want to make sure there are no conflicts.


    Disclaimer: I am not a lawyer, and this is not legal advice.

    The licensing for JOVR is referenced in the POM file (this is actually a requirement for distribution on the Maven primary repository). Specifically JOVR is mostly code that is auto-generated using headers from the Oculus SDK. As such it must be licensed under the Oculus SDK license.

    I'm not super happy with the SDK licensing terms since it means that any work I do to create alternative bindings for the SDK (or otherwise improve it) belongs to OVR, unless it's done from scratch. I've already seen hints in the latest SDK that some of my work may have been incorporated into the SDK proper.

    However, none of this should be an issue for you, because jME won't be covered as a derivative work under the license. Instead, the license text does it's best to establish a clear line between the SDK and things which simply use the SDK, such as engines or applications.

    "The Oculus VR License" wrote:

    This license includes the right to prepare derivative works ("Derivative Works"), whether in source, binary, or object form, based on the SDK, in whole or in part, including third party software unless otherwise noted. Derivative Works are defined as source, binary or object code derived exclusively from the SDK; provided, however, that Derivative Works do not include engines, utilities, applications, content or games which may be developed using the SDK. By way of example a videogame that is developed using the SDK would not be a Derivative Work and a utility or tool set in a pre-existing game engine that is adapted to work with the SDK would not be a Derivative Work.


    (Emphasis mine)

    JOVR is an entirely new creation, but it doesn't really constitute an engine or utility. It's simply an alternative language binding for the SDK. jME on the other hand falls squarely in the 'pre-existing game engine' category and as such is not a derivative work and is free to use the SDK as an upstream dependency without violating the BSD license.

    This is really no different than something like Unity or Unreal using the SDK directly. It doesn't impact the licensing terms they are allowed to redistribute their code under.

    All of the code that I've written that uses JOVR inherits the licensing of my parent POM, which is Apache 2.0. If you're interested in using any of it directly and feel that Apache 2.0 isn't suitable then let me know and I'll see if I can release it under BSD. On the other hand that's unlikely to be necessary as my code is pretty slim and wouldn't be hard to re-implement independently.
    Brad Davis - Developer for High Fidelity Co-author of Oculus Rift in Action
    Up and Coming
    May 30, 2014
    Thank you for the detailed writeup! We think we should be all set too.

    rickard & I got orientation data working using JOVR & jMonkeyEngine, but he had this to say:

    "Sensor is working now. However, the returned result seems extremely jittery. Difficult to describe but it feels like some kind of over-compensation or over-sensitiveness. The image even jumps around when the HMD is still on the desk."

    I used this call to get the orientation data:

    loadedHmd.getSensorState(predictValue).Predicted.Pose.Orientation

    ... I thought a predictValue of 0 wouldn't do any prediction. Another user, Juanjo, said this:

    "If the TimeWarp option is activated, i’d try to disable it to see if the jitter dissappears. It is supplied to the SDK in the ovrHmd_ConfigureRendering()"

    ... which rickard (the only one with a Rift at the moment), hasn't tried yet.

    Would you happen to have any advice? Any would be appreciated!

    Thread reference: http://hub.jmonkeyengine.org/forum/topic/oculus-rift-support/page/16/
    jhericoAuthor
    Inspiring
    May 30, 2014
    "phr00t" wrote:

    loadedHmd.getSensorState(predictValue).Predicted.Pose.Orientation


    I don't know what value you have in predictValue above, but I'm guessing that it's either 0, or some fixed small value like 0.03 seconds.

    In general you don't really need to call getSensorState directly. Instead, you're supposed to use the ovrPose value returned from ovrHmd_BeginEyeRender(), or in the case of JOVR, ovrHmd.beginEyeRender(). The returned value should automatically take prediction into account.

    However, if you do need or want to call ovrHmd.getSensorState(), be aware that, at least in 0.3.1, the documentation is a bit misleading. (I haven't checked on 0.3.2 yet)


    // Returns sensor state reading based on the specified absolute system time.
    // Pass absTime value of 0.0 to request the most recent sensor reading; in this case
    // both PredictedPose and SamplePose will have the same value.
    // ovrHmd_GetEyePredictedSensorState relies on this internally.
    // This may also be used for more refined timing of FrontBuffer rendering logic, etc.
    OVR_EXPORT ovrSensorState ovrHmd_GetSensorState(ovrHmd hmd, double absTime);


    Passing in 0.0 as the time doesn't actually have the stated effect. Instead, it treats the value of 0.0 just as it would any other time for the purpose of prediction. Since the absolute time is constantly increasing, this basically means you're predicting further and further into the past, and the normal noise in the headset starts to get amplified.

    The solution is to always pass in a value relative to OvrLibrary.ovr_GetTimeInSeconds() for absTime. If you don't actually want a predicted orientation, then use ovrSensorState.Recorded. If you do want a predicted time, then as long as the absTime is relative to ovr_GetTimeInSeconds() and very small, you shouldn't see as much jitter.
    Brad Davis - Developer for High Fidelity Co-author of Oculus Rift in Action
    jhericoAuthor
    Inspiring
    May 30, 2014
    Bumping for new version.
    Brad Davis - Developer for High Fidelity Co-author of Oculus Rift in Action
    Participant
    May 30, 2014
    Hi.

    Thought it might be a good idea to introduce myself here :)
    I'll check out your pointers later tonight hopefully.

    I also get an Invalid Memory Access error when calling some methods, like beginEyeRender and endFrame.
    Do you happen to know why this is?
    Stacktrace for reference:
    java.lang.Error: Invalid memory access
    at com.sun.jna.Native.invokeStructure(Native Method)
    at com.sun.jna.Native.invokeStructure(Native.java:1741)
    at com.sun.jna.Function.invoke(Function.java:408)
    at com.sun.jna.Function.invoke(Function.java:315)
    at com.sun.jna.Library$Handler.invoke(Library.java:212)
    at $Proxy0.ovrHmd_BeginEyeRender(Unknown Source)


    Thanks,
    Rickard
    "The television screen has become the retina of the minds eye."
    Participant
    May 30, 2014
    Seems useful. Bookmarked :) Thanks
    jhericoAuthor
    Inspiring
    June 4, 2014
    Bumping for new version.
    Brad Davis - Developer for High Fidelity Co-author of Oculus Rift in Action
    Up and Coming
    June 8, 2014
    I eventually had the time to work on my Oculus Rift project again, and just wrote a first small Scala example using the JOVR library. And I really have to say: Great job Brad! Compared to the situation before JOVR, this really is a difference as night and day. In combination with your book, this really makes it so much more feasible to port JVM based stuff to the Rift.

    Since my current experiment was really pretty straightforward, I couldn't even give you a good advice where to improve. Maybe some minor things: The example code currently relies on some stuff from the "org.saintandreas.gl" package, which probably is not available on github. So the main issues a new user might have are just the things that are not immediate visible in the example: Proper framebuffer setup, proper activation of framebuffer (including set up of glViewport), the infamous quaternion-to-rotation matrix conversion (which involves handedness convention), and the question whether getPerspectiveProjection(.).M returns an array in column or row major format, in case a user wants to work with some other matrix library.

    I'm still curious to see how we will deal with the "swap buffers" problem on the JVM in the long term. I didn't know that waiting for a vsync requires platform specific handles, so for the time being deactivating it completely might be the best solution. Unfortunately, the possibilities to force a vsync with lwjgl are also limited, since it requires running in fullscreen and depending on the display configuration / platform you can only run lwjgl windowed with a position matching the Rift display. In my case I currently can't vsync at all -- but with the DK1 this is not a big deal because tearing is a negligible artifact compared to all other DK1 issues. But I would expect that this may change with DK2. I have no idea whether there is some timewarp related magic behind the "swap buffer" logic in the SDK, so it remains to be seen whether we can rely on buffer swapping by lwjgl in the long term. My experience so far is that even with 600+ fps (no vsync) and timewarp enabled, the latency in my JVM example (using SDK rendering) feels worse compared to native examples that are able to use the full timewarp + swap buffers logic.