# HG changeset patch # User John Tsiombikas # Date 1460529577 -10800 # Node ID 806d30b467489c3d5b0ed1f6ca808ec75742a333 OpenVR test initial commit diff -r 000000000000 -r 806d30b46748 .hgignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgignore Wed Apr 13 09:39:37 2016 +0300 @@ -0,0 +1,10 @@ +\.o$ +\.d$ +\.swp$ +^test$ +Debug/ +Release/ +\.sdf$ +\.suo$ +\.opensdf$ +\.exe$ diff -r 000000000000 -r 806d30b46748 openvr_test1.sln --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/openvr_test1.sln Wed Apr 13 09:39:37 2016 +0300 @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "openvr_test1", "openvr_test1.vcxproj", "{9A02CFA8-E1B4-4F9B-B63D-65D9B60E1E50}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9A02CFA8-E1B4-4F9B-B63D-65D9B60E1E50}.Debug|Win32.ActiveCfg = Debug|Win32 + {9A02CFA8-E1B4-4F9B-B63D-65D9B60E1E50}.Debug|Win32.Build.0 = Debug|Win32 + {9A02CFA8-E1B4-4F9B-B63D-65D9B60E1E50}.Release|Win32.ActiveCfg = Release|Win32 + {9A02CFA8-E1B4-4F9B-B63D-65D9B60E1E50}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff -r 000000000000 -r 806d30b46748 openvr_test1.vcxproj --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/openvr_test1.vcxproj Wed Apr 13 09:39:37 2016 +0300 @@ -0,0 +1,92 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {9A02CFA8-E1B4-4F9B-B63D-65D9B60E1E50} + Win32Proj + openvr_test1 + + + + Application + true + v120 + Unicode + + + Application + false + v120 + true + Unicode + + + + + + + + + + + + + true + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + 4305;4244 + + + Console + true + opengl32.lib;glu32.lib;glew32.lib;SDL2.lib;SDL2main.lib;openvr_api.lib;libgmath.lib;%(AdditionalDependencies) + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + 4305;4244 + + + Console + true + true + true + opengl32.lib;glu32.lib;glew32.lib;SDL2.lib;SDL2main.lib;openvr_api.lib;libgmath.lib;%(AdditionalDependencies) + + + + + + + + + + + + + \ No newline at end of file diff -r 000000000000 -r 806d30b46748 openvr_test1.vcxproj.filters --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/openvr_test1.vcxproj.filters Wed Apr 13 09:39:37 2016 +0300 @@ -0,0 +1,30 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + + + Header Files + + + \ No newline at end of file diff -r 000000000000 -r 806d30b46748 src/app.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/app.cc Wed Apr 13 09:39:37 2016 +0300 @@ -0,0 +1,439 @@ +#include +#include +#include +#include +#include +#include "app.h" + +using namespace vr; // OpenVR namespace + +static void draw_scene(); +static void draw_box(float xsz, float ysz, float zsz, float norm_sign); +static Mat4 openvr_matrix4(const HmdMatrix44_t &mat); +static Mat4 openvr_matrix(const HmdMatrix34_t &mat); +static VRTextureBounds_t openvr_tex_bounds(float umin, float vmin, float umax, float vmax); +static void update_rtarg(int width, int height); +static unsigned int next_pow2(unsigned int x); +static unsigned int gen_chess_tex(float r0, float g0, float b0, float r1, float g1, float b1); + +static int win_width, win_height; + +static unsigned int fbo, fb_tex, fb_depth; +static int fb_width, fb_height; +static int fb_tex_width, fb_tex_height; + +static unsigned int chess_tex; + +// openvr stuff +static IVRSystem *vrsys; +static IVRCompositor *vrcomp; +static bool vrdev_state[k_unMaxTrackedDeviceCount]; +static TrackedDevicePose_t vrdev_pose[k_unMaxTrackedDeviceCount]; +static Mat4 eye_matrix[2]; +static Mat4 proj_matrix[2]; +static Texture_t vrtex = {0, API_OpenGL, ColorSpace_Linear}; +static VRTextureBounds_t vrtex_bounds[2]; + +bool app_init() +{ + glewInit(); + + glEnable(GL_DEPTH_TEST); + glEnable(GL_CULL_FACE); + glEnable(GL_LIGHTING); + glEnable(GL_LIGHT0); + glEnable(GL_LIGHT1); + glEnable(GL_NORMALIZE); + + glClearColor(0.1, 0.1, 0.1, 1); + + chess_tex = gen_chess_tex(1.0, 0.7, 0.4, 0.4, 0.7, 1.0); + + // Initialize OpenVR + EVRInitError vrerr; + if(!(vrsys = VR_Init(&vrerr, VRApplication_Scene))) { + fprintf(stderr, "failed to initialize OpenVR: %s\n", + VR_GetVRInitErrorAsEnglishDescription(vrerr)); + return false; + } + if(!(vrcomp = VRCompositor())) { + fprintf(stderr, "failed to initialize OpenVR compositor\n"); + return false; + } + + uint32_t xsz, ysz; + vrsys->GetRecommendedRenderTargetSize(&xsz, &ysz); + fb_width = xsz * 2; + fb_height = ysz; + update_rtarg(fb_width, fb_height); // create render target + + for(int i=0; i<2; i++) { + EVREye eye = i == 0 ? Eye_Left : Eye_Right; + + // projection matrix for each eye + proj_matrix[i] = openvr_matrix4(vrsys->GetProjectionMatrix(eye, 0.5, 500.0, API_OpenGL)); + // eye (relative to head) matrix + eye_matrix[i] = openvr_matrix(vrsys->GetEyeToHeadTransform(eye)); + } + + // XXX this doesn't seem to work correctly for some reason + //vrcomp->ShowMirrorWindow(); + + assert(glGetError() == GL_NO_ERROR); + return true; +} + +void app_shutdown() +{ + // if we call VR_Shutdown while a frame is pending, we'll crash + vrcomp->ClearLastSubmittedFrame(); + VR_Shutdown(); +} + +static void update() +{ + // process OpenVR events (TODO: I think there are more events to handle) + VREvent_t ev; + while(vrsys->PollNextEvent(&ev, sizeof ev)) { + switch(ev.eventType) { + case VREvent_TrackedDeviceActivated: + printf("Device %u activated\n", ev.trackedDeviceIndex); + break; + + case VREvent_TrackedDeviceDeactivated: + printf("Device %u lost\n", ev.trackedDeviceIndex); + break; + + case VREvent_TrackedDeviceUpdated: + printf("Device %u updated(?)\n", ev.trackedDeviceIndex); + break; + } + } + + // TODO implement controllers + for(int i=0; iGetControllerState(i, &st)) { + // XXX ? + vrdev_state[i] = st.ulButtonPressed == 0; + } + } + + // this will probably block at some point... investigate further + vrcomp->WaitGetPoses(vrdev_pose, k_unMaxTrackedDeviceCount, 0, 0); +} + +void app_draw() +{ + update(); + + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + for(int i=0; i<2; i++) { + EVREye eye = i == 0 ? Eye_Left : Eye_Right; + + glViewport(i == 0 ? 0 : fb_width / 2, 0, fb_width / 2, fb_height); + + glMatrixMode(GL_PROJECTION); + glLoadMatrixf(proj_matrix[i][0]); + + Mat4 hmd_mat = openvr_matrix(vrdev_pose[k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking); + Mat4 view_mat = inverse(eye_matrix[i] * hmd_mat); + + glMatrixMode(GL_MODELVIEW); + glLoadMatrixf(view_mat[0]); + + draw_scene(); + vrcomp->Submit(eye, &vrtex, vrtex_bounds + i); + } + /* this is supposed to tell the compositor to get on with showing the frame without waiting for + * the next WaitGetPoses call. + */ + vrcomp->PostPresentHandoff(); + + glUseProgram(0); + + // render window output + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glViewport(0, 0, win_width, win_height); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glPushAttrib(GL_ENABLE_BIT); + glBindTexture(GL_TEXTURE_2D, fb_tex); + glEnable(GL_TEXTURE_2D); + glDisable(GL_LIGHTING); + glDisable(GL_DEPTH_TEST); + + glBegin(GL_QUADS); + float umax = (float)fb_width / fb_tex_width; + float vmax = (float)fb_height / fb_tex_height; + glTexCoord2f(0, 0); glVertex2f(-1, -1); + glTexCoord2f(umax, 0); glVertex2f(1, -1); + glTexCoord2f(umax, vmax); glVertex2f(1, 1); + glTexCoord2f(0, vmax); glVertex2f(-1, 1); + glEnd(); + + glPopAttrib(); + + app_swap_buffers(); +} + +void app_reshape(int x, int y) +{ + win_width = x; + win_height = y; + glViewport(0, 0, x, y); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective(50, (float)x / (float)y, 0.5, 500.0); +} + +static void draw_scene(void) +{ + float grey[] = {0.8, 0.8, 0.8, 1}; + float col[] = {0, 0, 0, 1}; + float lpos[][4] = { + {-8, 2, 10, 1}, + {0, 15, 0, 1} + }; + float lcol[][4] = { + {0.8, 0.8, 0.8, 1}, + {0.4, 0.3, 0.3, 1} + }; + + for(int i=0; i<2; i++) { + glLightfv(GL_LIGHT0 + i, GL_POSITION, lpos[i]); + glLightfv(GL_LIGHT0 + i, GL_DIFFUSE, lcol[i]); + } + + glMatrixMode(GL_MODELVIEW); + + glPushMatrix(); + glTranslatef(0, 10, 0); + glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, grey); + glBindTexture(GL_TEXTURE_2D, chess_tex); + glEnable(GL_TEXTURE_2D); + draw_box(30, 20, 30, -1.0); + glDisable(GL_TEXTURE_2D); + glPopMatrix(); + + for(int i=0; i<4; i++) { + glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, grey); + glPushMatrix(); + glTranslatef(i & 1 ? 5 : -5, 1, i & 2 ? -5 : 5); + draw_box(0.5, 2, 0.5, 1.0); + glPopMatrix(); + + col[0] = i & 1 ? 1.0 : 0.3; + col[1] = i == 0 ? 1.0 : 0.3; + col[2] = i & 2 ? 1.0 : 0.3; + glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, col); + + glPushMatrix(); + if(i & 1) { + glTranslatef(0, 0.25, i & 2 ? 2 : -2); + } else { + glTranslatef(i & 2 ? 2 : -2, 0.25, 0); + } + draw_box(0.5, 0.5, 0.5, 1.0); + glPopMatrix(); + } + + col[0] = 1; + col[1] = 1; + col[2] = 0.4; + glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, col); + draw_box(0.05, 1.2, 6, 1.0); + draw_box(6, 1.2, 0.05, 1.0); +} + +static void draw_box(float xsz, float ysz, float zsz, float norm_sign) +{ + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glScalef(xsz * 0.5, ysz * 0.5, zsz * 0.5); + + if(norm_sign < 0.0) { + glFrontFace(GL_CW); + } + + glBegin(GL_QUADS); + glNormal3f(0, 0, 1 * norm_sign); + glTexCoord2f(0, 0); glVertex3f(-1, -1, 1); + glTexCoord2f(1, 0); glVertex3f(1, -1, 1); + glTexCoord2f(1, 1); glVertex3f(1, 1, 1); + glTexCoord2f(0, 1); glVertex3f(-1, 1, 1); + glNormal3f(1 * norm_sign, 0, 0); + glTexCoord2f(0, 0); glVertex3f(1, -1, 1); + glTexCoord2f(1, 0); glVertex3f(1, -1, -1); + glTexCoord2f(1, 1); glVertex3f(1, 1, -1); + glTexCoord2f(0, 1); glVertex3f(1, 1, 1); + glNormal3f(0, 0, -1 * norm_sign); + glTexCoord2f(0, 0); glVertex3f(1, -1, -1); + glTexCoord2f(1, 0); glVertex3f(-1, -1, -1); + glTexCoord2f(1, 1); glVertex3f(-1, 1, -1); + glTexCoord2f(0, 1); glVertex3f(1, 1, -1); + glNormal3f(-1 * norm_sign, 0, 0); + glTexCoord2f(0, 0); glVertex3f(-1, -1, -1); + glTexCoord2f(1, 0); glVertex3f(-1, -1, 1); + glTexCoord2f(1, 1); glVertex3f(-1, 1, 1); + glTexCoord2f(0, 1); glVertex3f(-1, 1, -1); + glEnd(); + glBegin(GL_TRIANGLE_FAN); + glNormal3f(0, 1 * norm_sign, 0); + glTexCoord2f(0.5, 0.5); glVertex3f(0, 1, 0); + glTexCoord2f(0, 0); glVertex3f(-1, 1, 1); + glTexCoord2f(1, 0); glVertex3f(1, 1, 1); + glTexCoord2f(1, 1); glVertex3f(1, 1, -1); + glTexCoord2f(0, 1); glVertex3f(-1, 1, -1); + glTexCoord2f(0, 0); glVertex3f(-1, 1, 1); + glEnd(); + glBegin(GL_TRIANGLE_FAN); + glNormal3f(0, -1 * norm_sign, 0); + glTexCoord2f(0.5, 0.5); glVertex3f(0, -1, 0); + glTexCoord2f(0, 0); glVertex3f(-1, -1, -1); + glTexCoord2f(1, 0); glVertex3f(1, -1, -1); + glTexCoord2f(1, 1); glVertex3f(1, -1, 1); + glTexCoord2f(0, 1); glVertex3f(-1, -1, 1); + glTexCoord2f(0, 0); glVertex3f(-1, -1, -1); + glEnd(); + + glFrontFace(GL_CCW); + glPopMatrix(); +} + +static Mat4 openvr_matrix4(const HmdMatrix44_t &mat) +{ + return Mat4(mat.m[0][0], mat.m[1][0], mat.m[2][0], mat.m[3][0], + mat.m[0][1], mat.m[1][1], mat.m[2][1], mat.m[3][1], + mat.m[0][2], mat.m[1][2], mat.m[2][2], mat.m[3][2], + mat.m[0][3], mat.m[1][3], mat.m[2][3], mat.m[3][3]); +} + +static Mat4 openvr_matrix(const HmdMatrix34_t &mat) +{ + return Mat4(mat.m[0][0], mat.m[1][0], mat.m[2][0], 0, + mat.m[0][1], mat.m[1][1], mat.m[2][1], 0, + mat.m[0][2], mat.m[1][2], mat.m[2][2], 0, + mat.m[0][3], mat.m[1][3], mat.m[2][3], 1); +} + +static VRTextureBounds_t openvr_tex_bounds(float umin, float vmin, float umax, float vmax) +{ + VRTextureBounds_t res; + res.uMin = umin; + res.uMax = umax; + res.vMin = vmin; + res.vMax = vmax; + return res; +} + +/* update_rtarg creates (and/or resizes) the render target used to draw the two stero views */ +static void update_rtarg(int width, int height) +{ + if(!fbo) { + /* if fbo does not exist, then nothing does... create every opengl object */ + glGenFramebuffers(1, &fbo); + glGenTextures(1, &fb_tex); + glGenRenderbuffers(1, &fb_depth); + + glBindTexture(GL_TEXTURE_2D, fb_tex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } + + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + + /* calculate the next power of two in both dimensions and use that as a texture size */ + fb_tex_width = next_pow2(width); + fb_tex_height = next_pow2(height); + + /* create and attach the texture that will be used as a color buffer */ + glBindTexture(GL_TEXTURE_2D, fb_tex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, fb_tex_width, fb_tex_height, 0, + GL_RGBA, GL_UNSIGNED_BYTE, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb_tex, 0); + + /* create and attach the renderbuffer that will serve as our z-buffer */ + glBindRenderbuffer(GL_RENDERBUFFER, fb_depth); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, fb_tex_width, fb_tex_height); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fb_depth); + + if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + fprintf(stderr, "incomplete framebuffer!\n"); + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + printf("created render target: %dx%d (texture size: %dx%d)\n", width, height, fb_tex_width, fb_tex_height); + + /* update the OpenVR texture descriptors */ + vrtex.handle = (void*)fb_tex; + float umax = (float)fb_width / fb_tex_width; + float vmax = (float)fb_height / fb_tex_height; + vrtex_bounds[0] = openvr_tex_bounds(0, 1.0 - vmax, 0.5 * umax, 1.0); + vrtex_bounds[1] = openvr_tex_bounds(0.5 * umax, 1.0 - vmax, umax, 1.0); +} + + +void app_keyboard(int key, bool pressed) +{ + if(pressed) { + switch(key) { + case 27: + app_quit(); + break; + + case ' ': + case 'r': + vrsys->ResetSeatedZeroPose(); + break; + + default: + break; + } + } +} + +static unsigned int next_pow2(unsigned int x) +{ + x -= 1; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + return x + 1; +} + +/* generate a chessboard texture with tiles colored (r0, g0, b0) and (r1, g1, b1) */ +unsigned int gen_chess_tex(float r0, float g0, float b0, float r1, float g1, float b1) +{ + unsigned int tex; + unsigned char img[8 * 8 * 3]; + unsigned char *pix = img; + + for(int i=0; i<8; i++) { + for(int j=0; j<8; j++) { + int black = (i & 1) == (j & 1); + pix[0] = (black ? r0 : r1) * 255; + pix[1] = (black ? g0 : g1) * 255; + pix[2] = (black ? b0 : b1) * 255; + pix += 3; + } + } + + glGenTextures(1, &tex); + glBindTexture(GL_TEXTURE_2D, tex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 8, 8, 0, GL_RGB, GL_UNSIGNED_BYTE, img); + + return tex; +} diff -r 000000000000 -r 806d30b46748 src/app.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/app.h Wed Apr 13 09:39:37 2016 +0300 @@ -0,0 +1,15 @@ +#ifndef APP_H_ +#define APP_H_ + +bool app_init(); +void app_shutdown(); +void app_draw(); +void app_reshape(int x, int y); +void app_keyboard(int bn, bool pressed); +void app_mouse(int bn, bool pressed, int x, int y); +void app_motion(int x, int y); + +void app_swap_buffers(); +void app_quit(); + +#endif // APP_H_ \ No newline at end of file diff -r 000000000000 -r 806d30b46748 src/main.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main.cc Wed Apr 13 09:39:37 2016 +0300 @@ -0,0 +1,85 @@ +#include +#include +#include +#include +#include +#include "app.h" + +static bool handle_event(SDL_Event *ev); + +static SDL_Window *win; +static SDL_GLContext ctx; +static bool quit; + +int main(int argc, char **argv) +{ + SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER); + int winx = SDL_WINDOWPOS_UNDEFINED; + int winy = SDL_WINDOWPOS_UNDEFINED; + + if(!(win = SDL_CreateWindow("OpenVR test", winx, winy, 1024, 640, SDL_WINDOW_OPENGL))) { + fprintf(stderr, "failed to open window\n"); + return 1; + } + if(!(ctx = SDL_GL_CreateContext(win))) { + fprintf(stderr, "failed to create OpenGL context\n"); + return 1; + } + int xsz, ysz; + SDL_GetWindowSize(win, &xsz, &ysz); + app_reshape(xsz, ysz); + + if(!app_init()) { + return 1; + } + + for(;;) { + SDL_Event ev; + while(SDL_PollEvent(&ev)) { + if(!handle_event(&ev) || quit) { + goto done; + } + } + + app_draw(); + assert(glGetError() == GL_NO_ERROR); + } + +done: + app_shutdown(); + SDL_Quit(); + return 0; +} + +void app_swap_buffers() +{ + SDL_GL_SwapWindow(win); +} + +void app_quit() +{ + quit = true; +} + +static bool handle_event(SDL_Event *ev) +{ + switch(ev->type) { + case SDL_QUIT: + return false; + + case SDL_KEYDOWN: + case SDL_KEYUP: + app_keyboard(ev->key.keysym.sym, ev->key.state == SDL_PRESSED); + break; + + case SDL_WINDOWEVENT: + if(ev->window.event == SDL_WINDOWEVENT_RESIZED) { + app_reshape(ev->window.data1, ev->window.data2); + } + break; + + default: + break; + } + return true; +} \ No newline at end of file