Add support for time warp on Oculus Rift
authorMikko Rasa <tdb@tdb.fi>
Tue, 23 Dec 2014 21:41:02 +0000 (23:41 +0200)
committerMikko Rasa <tdb@tdb.fi>
Tue, 23 Dec 2014 21:41:02 +0000 (23:41 +0200)
To make use of it, the program must call device->begin_frame() before it
starts rendering and device->end_frame just after swap_buffers.

source/displaydevice.h
source/oculusriftcamera.cpp
source/oculusriftcombiner.cpp
source/oculusriftcombiner.h
source/oculusriftdevice.cpp
source/oculusriftdevice.h
source/oculusriftdevice_private.h

index b309a8341bd1f01afafe7c8f54ec1c3af3654e12..81a9d240adb8189b24d64b0c54eac92f242fc5b1 100644 (file)
@@ -20,6 +20,9 @@ public:
        virtual void configure_view(StereoView &) const = 0;
        virtual HeadTrackingCamera *create_camera(const GL::Camera &) const = 0;
        virtual StereoCombiner *create_combiner() const = 0;
+
+       virtual void begin_frame() = 0;
+       virtual void end_frame() = 0;
 };
 
 } // namespace VR
index 3aa35be1f8c672c218d82f5ad1b30231d2736018..0eede270de864b92f17bf18031c0d012cf2b1a65 100644 (file)
@@ -20,7 +20,13 @@ void OculusRiftCamera::reset_tracking()
 
 void OculusRiftCamera::update()
 {
-       ovrTrackingState state = ovrHmd_GetTrackingState(device.get_private().ovr_hmd, ovr_GetTimeInSeconds());
+       double time;
+       if(device.is_timing_active())
+               time = device.get_tracking_time();
+       else
+               time = device.get_current_time();
+
+       ovrTrackingState state = ovrHmd_GetTrackingState(device.get_private().ovr_hmd, time);
        OVR::Posef head_pose = state.HeadPose.ThePose;
        OVR::Matrix4f tracking_matrix(head_pose.Rotation);
        OVR::Vector3f trans = head_pose.Translation;
index 5e76cc8305a21ee6c3594ee666d9d50076016ade..0cfd4ae1c6398701350c0a3c00114a0435447b8e 100644 (file)
@@ -11,16 +11,26 @@ using namespace std;
 namespace {
 
 const char vs_source[] =
+       "uniform mat4 timewarp[2];\n"
+       "uniform vec2 uv_scale;\n"
+       "uniform vec2 uv_offset;\n"
        "varying vec2 texcoord_r;\n"
        "varying vec2 texcoord_g;\n"
        "varying vec2 texcoord_b;\n"
        "varying float vignette;\n"
+       "vec2 apply_timewarp(vec2 coords, float factor)\n"
+       "{\n"
+       "       vec4 coords4 = vec4(coords, 1.0, 1.0);\n"
+       "       vec4 warped = mix(timewarp[0]*coords4, timewarp[1]*coords4, factor);\n"
+       "       return (warped.xy/warped.z)*uv_scale+uv_offset;\n"
+       "}\n"
        "void main()\n"
        "{\n"
        "       gl_Position = vec4(gl_Vertex.xy, 0.5, 1.0);\n"
-       "       texcoord_r = gl_MultiTexCoord0.xy;\n"
-       "       texcoord_g = gl_MultiTexCoord1.xy;\n"
-       "       texcoord_b = gl_MultiTexCoord2.xy;\n"
+       "       float tw_factor = gl_MultiTexCoord3.y;\n"
+       "       texcoord_r = apply_timewarp(gl_MultiTexCoord0.xy, tw_factor);\n"
+       "       texcoord_g = apply_timewarp(gl_MultiTexCoord1.xy, tw_factor);\n"
+       "       texcoord_b = apply_timewarp(gl_MultiTexCoord2.xy, tw_factor);\n"
        "       vignette = gl_MultiTexCoord3.x;\n"
        "}\n";
 
@@ -41,26 +51,16 @@ const char fs_source[] =
 void create_distortion_mesh(Msp::GL::Mesh &mesh, ovrHmd hmd, ovrEyeType eye, const ovrFovPort &fov)
 {
        ovrDistortionMesh ovr_mesh;
-       ovrHmd_CreateDistortionMesh(hmd, eye, fov, ovrDistortionCap_Chromatic|ovrDistortionCap_Vignette, &ovr_mesh);
-
-       ovrSizei tex_size = ovrHmd_GetFovTextureSize(hmd, eye, fov, 1.0);
-       ovrRecti view_rect;
-       view_rect.Pos.x = 0;
-       view_rect.Pos.y = 0;
-       view_rect.Size = tex_size;
-       ovrVector2f uv_scale_offset[2];
-       ovrHmd_GetRenderScaleAndOffset(fov, tex_size, view_rect, uv_scale_offset);
-       ovrVector2f &scale = uv_scale_offset[0];
-       ovrVector2f &offset = uv_scale_offset[1];
+       ovrHmd_CreateDistortionMesh(hmd, eye, fov, ovrDistortionCap_Chromatic|ovrDistortionCap_Vignette|ovrDistortionCap_TimeWarp, &ovr_mesh);
 
        Msp::GL::MeshBuilder bld(mesh);
        for(unsigned i=0; i<ovr_mesh.VertexCount; ++i)
        {
                ovrDistortionVertex &v = ovr_mesh.pVertexData[i];
-               bld.multitexcoord(0, v.TanEyeAnglesR.x*scale.x+offset.x, 1.0f-(v.TanEyeAnglesR.y*scale.y+offset.y));
-               bld.multitexcoord(1, v.TanEyeAnglesG.x*scale.x+offset.x, 1.0f-(v.TanEyeAnglesG.y*scale.y+offset.y));
-               bld.multitexcoord(2, v.TanEyeAnglesB.x*scale.x+offset.x, 1.0f-(v.TanEyeAnglesB.y*scale.y+offset.y));
-               bld.multitexcoord(3, v.VignetteFactor);
+               bld.multitexcoord(0, v.TanEyeAnglesR.x, v.TanEyeAnglesR.y);
+               bld.multitexcoord(1, v.TanEyeAnglesG.x, v.TanEyeAnglesG.y);
+               bld.multitexcoord(2, v.TanEyeAnglesB.x, v.TanEyeAnglesB.y);
+               bld.multitexcoord(3, v.VignetteFactor, v.TimeWarpFactor);
                bld.vertex(v.ScreenPosNDC.x, v.ScreenPosNDC.y);
        }
 
@@ -78,8 +78,8 @@ namespace VR {
 
 OculusRiftCombiner::OculusRiftCombiner(const OculusRiftDevice &d):
        device(d),
-       left_mesh((GL::VERTEX2, GL::TEXCOORD2,0, GL::TEXCOORD2,1, GL::TEXCOORD2,2, GL::TEXCOORD1,3)),
-       right_mesh((GL::VERTEX2, GL::TEXCOORD2,0, GL::TEXCOORD2,1, GL::TEXCOORD2,2, GL::TEXCOORD1,3)),
+       left_mesh((GL::VERTEX2, GL::TEXCOORD2,0, GL::TEXCOORD2,1, GL::TEXCOORD2,2, GL::TEXCOORD2,3)),
+       right_mesh((GL::VERTEX2, GL::TEXCOORD2,0, GL::TEXCOORD2,1, GL::TEXCOORD2,2, GL::TEXCOORD2,3)),
        shprog(vs_source, fs_source)
 {
        ovrHmd hmd = device.get_private().ovr_hmd;
@@ -107,19 +107,53 @@ OculusRiftCombiner::OculusRiftCombiner(const OculusRiftDevice &d):
        float aspect = (inner+outer)/(vertical*2);
        aspect_factor = aspect*hmd->Resolution.h/hmd->Resolution.w;
 
-       shdata.uniform("texture", 0);
+       left_shdata.uniform("texture", 0);
+       right_shdata.uniform("texture", 0);
 
+       ovrRecti view_rect;
+       view_rect.Pos.x = 0;
+       view_rect.Pos.y = 0;
+       view_rect.Size = tex_size;
+       ovrVector2f uv_scale_offset[2];
+       ovrHmd_GetRenderScaleAndOffset(left_fov, tex_size, view_rect, uv_scale_offset);
+       left_shdata.uniform("uv_scale", uv_scale_offset[0].x, -uv_scale_offset[0].y);
+       left_shdata.uniform("uv_offset", uv_scale_offset[1].x, 1-uv_scale_offset[1].y);
+       ovrHmd_GetRenderScaleAndOffset(right_fov, tex_size, view_rect, uv_scale_offset);
+       right_shdata.uniform("uv_scale", uv_scale_offset[0].x, -uv_scale_offset[0].y);
+       right_shdata.uniform("uv_offset", uv_scale_offset[1].x, 1-uv_scale_offset[1].y);
 }
 
 void OculusRiftCombiner::render(const GL::Texture2D &left, const GL::Texture2D &right) const
 {
        GL::Bind bind_shprog(shprog);
-       shdata.apply();
+
+       ovrHmd hmd = device.get_private().ovr_hmd;
+
+       if(device.is_timing_active())
+       {
+               ovr_WaitTillTime(device.get_timewarp_time());
+               ovrTrackingState state = ovrHmd_GetTrackingState(hmd, device.get_tracking_time());
+
+               ovrMatrix4f matrices[2];
+               ovrHmd_GetEyeTimewarpMatrices(hmd, ovrEye_Left, state.HeadPose.ThePose, matrices);
+               left_shdata.uniform_matrix4_array("timewarp", 2, &matrices[0].M[0][0]);
+
+               ovrHmd_GetEyeTimewarpMatrices(hmd, ovrEye_Right, state.HeadPose.ThePose, matrices);
+               right_shdata.uniform_matrix4_array("timewarp", 2, &matrices[0].M[0][0]);
+       }
+       else
+       {
+               GL::Matrix matrices[2];
+               left_shdata.uniform_matrix4_array("timewarp", 2, matrices[0].data());
+               right_shdata.uniform_matrix4_array("timewarp", 2, matrices[0].data());
+       }
 
        GL::Bind bind_tex(left);
+       left_shdata.apply();
        left_mesh.draw();
 
        right.bind();
+       right_shdata.apply();
        right_mesh.draw();
 }
 
index 435ccb3f18b0bc903a10d462efcd7ebe5188a46c..32ab9cce825ac47ae34984124462c544fcb84f17 100644 (file)
@@ -22,7 +22,8 @@ private:
        GL::Mesh left_mesh;
        GL::Mesh right_mesh;
        GL::Program shprog;
-       GL::ProgramData shdata;
+       mutable GL::ProgramData left_shdata;
+       mutable GL::ProgramData right_shdata;
 
 public:
        OculusRiftCombiner(const OculusRiftDevice &);
index f521a0e18ba86ec423022526806968fca65933b6..f73371f6c77d921f4ccc7ad3a04b6d2fb86060de 100644 (file)
@@ -10,7 +10,8 @@ namespace VR {
 unsigned OculusRiftDevice::n_instances = 0;
 
 OculusRiftDevice::OculusRiftDevice():
-       priv(new Private)
+       priv(new Private),
+       frame_index(0)
 {
        if(!n_instances)
                ovr_Initialize();
@@ -51,5 +52,37 @@ OculusRiftCombiner *OculusRiftDevice::create_combiner() const
        return new OculusRiftCombiner(*this);
 }
 
+void OculusRiftDevice::begin_frame()
+{
+       priv->frame_timing = ovrHmd_BeginFrameTiming(priv->ovr_hmd, ++frame_index);
+       timing_active = true;
+}
+
+void OculusRiftDevice::end_frame()
+{
+       glFinish();
+       ovrHmd_EndFrameTiming(priv->ovr_hmd);
+       timing_active = false;
+}
+
+double OculusRiftDevice::get_tracking_time() const
+{
+       if(!timing_active)
+               throw logic_error("timing not active");
+       return priv->frame_timing.ScanoutMidpointSeconds;
+}
+
+double OculusRiftDevice::get_timewarp_time() const
+{
+       if(!timing_active)
+               throw logic_error("timing not active");
+       return priv->frame_timing.TimewarpPointSeconds;
+}
+
+double OculusRiftDevice::get_current_time() const
+{
+       return ovr_GetTimeInSeconds();
+}
+
 } // namespace VR
 } // namespace Msp
index ecff70ea4401a8a26cafed39e6f5458b7da104b0..9eee6e33735fa35d9c7cf9a0d1ecd0ddc1d2f69c 100644 (file)
@@ -15,6 +15,8 @@ public:
 
 private:
        Private *priv;
+       unsigned frame_index;
+       bool timing_active;
 
        static unsigned n_instances;
 
@@ -27,6 +29,13 @@ public:
        virtual void configure_view(StereoView &) const;
        virtual OculusRiftCamera *create_camera(const GL::Camera &) const;
        virtual OculusRiftCombiner *create_combiner() const;
+
+       virtual void begin_frame();
+       virtual void end_frame();
+       bool is_timing_active() const { return timing_active; }
+       double get_tracking_time() const;
+       double get_timewarp_time() const;
+       double get_current_time() const;
 };
 
 } // namespace VR
index 2b9c695e71c8daee8d9ba05e19d138a2662a4ae1..26587ff5d488131c3aa2082757d242d4ea829224 100644 (file)
@@ -10,6 +10,7 @@ namespace VR {
 struct OculusRiftDevice::Private
 {
        ovrHmd ovr_hmd;
+       ovrFrameTiming frame_timing;
 };
 
 } // namespace VR