rev |
line source |
nuclear@0
|
1 #ifdef WIN32
|
nuclear@0
|
2 #define OVR_OS_WIN32
|
nuclear@24
|
3 #elif defined(__APPLE__)
|
nuclear@9
|
4 #define OVR_OS_MAC
|
nuclear@24
|
5 #else
|
nuclear@24
|
6 #define OVR_OS_LINUX
|
nuclear@9
|
7 #endif
|
nuclear@0
|
8
|
nuclear@0
|
9 #include "vr_impl.h"
|
nuclear@0
|
10
|
nuclear@0
|
11 #ifdef USE_LIBOVR
|
nuclear@0
|
12 #include <stdio.h>
|
nuclear@0
|
13 #include <stdlib.h>
|
nuclear@9
|
14 #include <string.h>
|
nuclear@0
|
15 #include <assert.h>
|
nuclear@30
|
16 #include "opengl.h"
|
nuclear@0
|
17 #include "opt.h"
|
nuclear@0
|
18
|
nuclear@0
|
19 #include <OVR_CAPI.h>
|
nuclear@0
|
20 #include <OVR_CAPI_GL.h>
|
nuclear@0
|
21
|
nuclear@30
|
22 static PFNGLUSEPROGRAMPROC gl_use_program;
|
nuclear@0
|
23
|
nuclear@0
|
24 static ovrHmd hmd;
|
nuclear@0
|
25 static void *optdb;
|
nuclear@0
|
26 static ovrEyeRenderDesc eye_render_desc[2];
|
nuclear@0
|
27 static ovrSizei eye_res[2];
|
nuclear@0
|
28 static ovrGLTexture eye_tex[2];
|
nuclear@0
|
29 static ovrFovPort eye_fov[2];
|
nuclear@19
|
30 static ovrPosef pose[2] = {
|
nuclear@19
|
31 { {0, 0, 0, 1}, {0, 0, 0} },
|
nuclear@19
|
32 { {0, 0, 0, 1}, {0, 0, 0} }
|
nuclear@19
|
33 };
|
nuclear@0
|
34 static int deferred_init_done;
|
nuclear@0
|
35
|
nuclear@19
|
36 static int inside_begin_end;
|
nuclear@19
|
37
|
nuclear@0
|
38 static int init(void)
|
nuclear@0
|
39 {
|
nuclear@0
|
40 int i, num_hmds;
|
nuclear@9
|
41 int use_fake = 0;
|
nuclear@14
|
42 ovrTrackingCaps tracking;
|
nuclear@0
|
43
|
nuclear@28
|
44 if(!ovr_Initialize(0)) {
|
nuclear@0
|
45 return -1;
|
nuclear@0
|
46 }
|
nuclear@0
|
47 printf("initialized LibOVR %s\n", ovr_GetVersionString());
|
nuclear@0
|
48
|
nuclear@0
|
49 if(!(num_hmds = ovrHmd_Detect())) {
|
nuclear@9
|
50 if(getenv("VR_LIBOVR_FAKE")) {
|
nuclear@9
|
51 use_fake = 1;
|
nuclear@9
|
52 num_hmds = 1;
|
nuclear@9
|
53 } else {
|
nuclear@9
|
54 ovr_Shutdown();
|
nuclear@9
|
55 return -1;
|
nuclear@9
|
56 }
|
nuclear@0
|
57 }
|
nuclear@0
|
58 printf("%d Oculus HMD(s) found\n", num_hmds);
|
nuclear@0
|
59
|
nuclear@0
|
60 hmd = 0;
|
nuclear@0
|
61 for(i=0; i<num_hmds; i++) {
|
nuclear@9
|
62 ovrHmd h = use_fake ? ovrHmd_CreateDebug(ovrHmd_DK2) : ovrHmd_Create(i);
|
nuclear@9
|
63 if(!h) {
|
nuclear@0
|
64 break;
|
nuclear@0
|
65 }
|
nuclear@0
|
66 printf(" [%d]: %s - %s\n", i, h->Manufacturer, h->ProductName);
|
nuclear@0
|
67
|
nuclear@0
|
68 if(!hmd) {
|
nuclear@0
|
69 hmd = h;
|
nuclear@0
|
70 } else {
|
nuclear@0
|
71 ovrHmd_Destroy(h);
|
nuclear@0
|
72 }
|
nuclear@0
|
73 }
|
nuclear@0
|
74
|
nuclear@0
|
75 if(!hmd) {
|
nuclear@0
|
76 fprintf(stderr, "failed to initialize any Oculus HMDs\n");
|
nuclear@0
|
77 return -1;
|
nuclear@0
|
78 }
|
nuclear@0
|
79
|
nuclear@14
|
80 tracking = ovrTrackingCap_Orientation | ovrTrackingCap_Position |
|
nuclear@14
|
81 ovrTrackingCap_MagYawCorrection;
|
nuclear@14
|
82 ovrHmd_ConfigureTracking(hmd, tracking, 0);
|
nuclear@0
|
83
|
nuclear@0
|
84 eye_fov[0] = hmd->DefaultEyeFov[0];
|
nuclear@0
|
85 eye_fov[1] = hmd->DefaultEyeFov[1];
|
nuclear@0
|
86
|
nuclear@0
|
87 eye_res[0] = ovrHmd_GetFovTextureSize(hmd, ovrEye_Left, eye_fov[0], 1.0);
|
nuclear@0
|
88 eye_res[1] = ovrHmd_GetFovTextureSize(hmd, ovrEye_Right, eye_fov[1], 1.0);
|
nuclear@0
|
89
|
nuclear@0
|
90 /* create the options database */
|
nuclear@0
|
91 if((optdb = create_options())) {
|
nuclear@11
|
92 set_option_int(optdb, VR_DISPLAY_WIDTH, hmd->Resolution.w);
|
nuclear@11
|
93 set_option_int(optdb, VR_DISPLAY_HEIGHT, hmd->Resolution.h);
|
nuclear@11
|
94 set_option_int(optdb, VR_LEYE_XRES, eye_res[0].w);
|
nuclear@11
|
95 set_option_int(optdb, VR_LEYE_YRES, eye_res[0].h);
|
nuclear@11
|
96 set_option_int(optdb, VR_REYE_XRES, eye_res[1].w);
|
nuclear@11
|
97 set_option_int(optdb, VR_REYE_YRES, eye_res[1].h);
|
nuclear@29
|
98 set_option_int(optdb, VR_RENDER_XRES, eye_res[0].w + eye_res[1].w);
|
nuclear@29
|
99 set_option_int(optdb, VR_RENDER_YRES, eye_res[0].h > eye_res[1].h ? eye_res[0].h : eye_res[1].h);
|
nuclear@16
|
100 set_option_float(optdb, VR_RENDER_RES_SCALE, 1.0);
|
nuclear@11
|
101 set_option_float(optdb, VR_EYE_HEIGHT, ovrHmd_GetFloat(hmd, OVR_KEY_EYE_HEIGHT, OVR_DEFAULT_EYE_HEIGHT));
|
nuclear@11
|
102 set_option_float(optdb, VR_IPD, ovrHmd_GetFloat(hmd, OVR_KEY_IPD, OVR_DEFAULT_IPD));
|
nuclear@11
|
103 set_option_int(optdb, VR_WIN_XOFFS, hmd->WindowsPos.x);
|
nuclear@11
|
104 set_option_int(optdb, VR_WIN_YOFFS, hmd->WindowsPos.y);
|
nuclear@0
|
105 }
|
nuclear@0
|
106
|
nuclear@30
|
107 if(!(gl_use_program = (PFNGLUSEPROGRAMPROC)vrimp_glfunc("glUseProgram"))) {
|
nuclear@30
|
108 gl_use_program = (PFNGLUSEPROGRAMPROC)vrimp_glfunc("glUseProgramObjectARB");
|
nuclear@30
|
109 }
|
nuclear@30
|
110
|
nuclear@0
|
111 deferred_init_done = 0;
|
nuclear@0
|
112 return 0;
|
nuclear@0
|
113 }
|
nuclear@0
|
114
|
nuclear@0
|
115 static void deferred_init(void)
|
nuclear@0
|
116 {
|
nuclear@0
|
117 union ovrGLConfig glcfg;
|
nuclear@0
|
118 unsigned int dcaps;
|
nuclear@0
|
119 void *win = 0;
|
nuclear@19
|
120 float leye_offs[3], reye_offs[3];
|
nuclear@0
|
121
|
nuclear@0
|
122 deferred_init_done = 1;
|
nuclear@0
|
123
|
nuclear@0
|
124 memset(&glcfg, 0, sizeof glcfg);
|
nuclear@0
|
125 glcfg.OGL.Header.API = ovrRenderAPI_OpenGL;
|
nuclear@21
|
126 glcfg.OGL.Header.BackBufferSize = hmd->Resolution;
|
nuclear@0
|
127 glcfg.OGL.Header.Multisample = 1;
|
nuclear@0
|
128 #ifdef WIN32
|
nuclear@0
|
129 win = GetActiveWindow();
|
nuclear@24
|
130 glcfg.OGL.Window = win;
|
nuclear@0
|
131 glcfg.OGL.DC = wglGetCurrentDC();
|
nuclear@24
|
132 #else
|
nuclear@24
|
133 glcfg.OGL.Disp = glXGetCurrentDisplay();
|
nuclear@24
|
134 win = (void*)glXGetCurrentDrawable();
|
nuclear@24
|
135
|
nuclear@24
|
136 /* on linux the Oculus SDK docs are instructing users to leave the DK2 screen in
|
nuclear@24
|
137 * portrait mode. So we'll have to flip width and height
|
nuclear@24
|
138 */
|
nuclear@24
|
139 glcfg.OGL.Header.BackBufferSize.w = hmd->Resolution.h;
|
nuclear@24
|
140 glcfg.OGL.Header.BackBufferSize.h = hmd->Resolution.w;
|
nuclear@0
|
141 #endif
|
nuclear@0
|
142
|
nuclear@0
|
143 if(!(hmd->HmdCaps & ovrHmdCap_ExtendDesktop)) {
|
nuclear@0
|
144 ovrHmd_AttachToWindow(hmd, win, 0, 0);
|
nuclear@0
|
145 printf("running in \"direct-to-rift\" mode\n");
|
nuclear@0
|
146 } else {
|
nuclear@0
|
147 printf("running in \"extended desktop\" mode\n");
|
nuclear@0
|
148 }
|
nuclear@0
|
149 ovrHmd_SetEnabledCaps(hmd, ovrHmdCap_LowPersistence | ovrHmdCap_DynamicPrediction);
|
nuclear@0
|
150
|
nuclear@28
|
151 dcaps = ovrDistortionCap_TimeWarp | ovrDistortionCap_Overdrive;
|
nuclear@24
|
152 #ifdef OVR_OS_LINUX
|
nuclear@24
|
153 dcaps |= ovrDistortionCap_LinuxDevFullscreen;
|
nuclear@24
|
154 #endif
|
nuclear@0
|
155
|
nuclear@0
|
156 if(!ovrHmd_ConfigureRendering(hmd, &glcfg.Config, dcaps, eye_fov, eye_render_desc)) {
|
nuclear@0
|
157 fprintf(stderr, "failed to configure LibOVR distortion renderer\n");
|
nuclear@0
|
158 }
|
nuclear@0
|
159
|
nuclear@19
|
160 /* set the eye offset options */
|
nuclear@21
|
161 leye_offs[0] = eye_render_desc[ovrEye_Left].HmdToEyeViewOffset.x;
|
nuclear@21
|
162 leye_offs[1] = eye_render_desc[ovrEye_Left].HmdToEyeViewOffset.y;
|
nuclear@21
|
163 leye_offs[2] = eye_render_desc[ovrEye_Left].HmdToEyeViewOffset.z;
|
nuclear@21
|
164 reye_offs[0] = eye_render_desc[ovrEye_Right].HmdToEyeViewOffset.x;
|
nuclear@21
|
165 reye_offs[1] = eye_render_desc[ovrEye_Right].HmdToEyeViewOffset.y;
|
nuclear@21
|
166 reye_offs[2] = eye_render_desc[ovrEye_Right].HmdToEyeViewOffset.z;
|
nuclear@24
|
167
|
nuclear@24
|
168 /* sanity check ... on linux it seems I'm getting the eye offsets reversed for some reason */
|
nuclear@24
|
169 if(leye_offs[0] > reye_offs[0]) {
|
nuclear@24
|
170 fprintf(stderr, "BUG %s:%d: eye offset reversed?! fixing but wtf...\n", __FILE__, __LINE__);
|
nuclear@24
|
171 set_option_vec(optdb, VR_LEYE_OFFSET, reye_offs);
|
nuclear@24
|
172 set_option_vec(optdb, VR_REYE_OFFSET, leye_offs);
|
nuclear@24
|
173 } else {
|
nuclear@24
|
174 set_option_vec(optdb, VR_LEYE_OFFSET, leye_offs);
|
nuclear@24
|
175 set_option_vec(optdb, VR_REYE_OFFSET, reye_offs);
|
nuclear@24
|
176 }
|
nuclear@0
|
177 }
|
nuclear@0
|
178
|
nuclear@0
|
179 static void cleanup(void)
|
nuclear@0
|
180 {
|
nuclear@0
|
181 if(hmd) {
|
nuclear@0
|
182 ovrHmd_Destroy(hmd);
|
nuclear@0
|
183 ovr_Shutdown();
|
nuclear@0
|
184 }
|
nuclear@5
|
185 destroy_options(optdb);
|
nuclear@0
|
186 }
|
nuclear@0
|
187
|
nuclear@0
|
188 static int set_option(const char *opt, enum opt_type type, void *valp)
|
nuclear@0
|
189 {
|
nuclear@15
|
190 float fval;
|
nuclear@15
|
191
|
nuclear@0
|
192 switch(type) {
|
nuclear@0
|
193 case OTYPE_INT:
|
nuclear@15
|
194 fval = (float)*(int*)valp;
|
nuclear@0
|
195 set_option_int(optdb, opt, *(int*)valp);
|
nuclear@0
|
196 break;
|
nuclear@0
|
197
|
nuclear@0
|
198 case OTYPE_FLOAT:
|
nuclear@15
|
199 fval = *(float*)valp;
|
nuclear@15
|
200 set_option_float(optdb, opt, fval);
|
nuclear@0
|
201 break;
|
nuclear@24
|
202
|
nuclear@24
|
203 case OTYPE_VEC:
|
nuclear@24
|
204 set_option_vec(optdb, opt, valp);
|
nuclear@24
|
205 break;
|
nuclear@0
|
206 }
|
nuclear@15
|
207
|
nuclear@16
|
208 if(hmd && strcmp(opt, VR_RENDER_RES_SCALE) == 0) {
|
nuclear@15
|
209 eye_res[0] = ovrHmd_GetFovTextureSize(hmd, ovrEye_Left, eye_fov[0], fval);
|
nuclear@15
|
210 eye_res[1] = ovrHmd_GetFovTextureSize(hmd, ovrEye_Right, eye_fov[1], fval);
|
nuclear@15
|
211
|
nuclear@15
|
212 set_option_int(optdb, VR_LEYE_XRES, eye_res[0].w);
|
nuclear@15
|
213 set_option_int(optdb, VR_LEYE_YRES, eye_res[0].h);
|
nuclear@15
|
214 set_option_int(optdb, VR_REYE_XRES, eye_res[1].w);
|
nuclear@15
|
215 set_option_int(optdb, VR_REYE_YRES, eye_res[1].h);
|
nuclear@15
|
216 }
|
nuclear@15
|
217
|
nuclear@0
|
218 return 0;
|
nuclear@0
|
219 }
|
nuclear@0
|
220
|
nuclear@0
|
221 static int get_option(const char *opt, enum opt_type type, void *valp)
|
nuclear@0
|
222 {
|
nuclear@0
|
223 switch(type) {
|
nuclear@0
|
224 case OTYPE_INT:
|
nuclear@0
|
225 return get_option_int(optdb, opt, valp);
|
nuclear@0
|
226 case OTYPE_FLOAT:
|
nuclear@0
|
227 return get_option_float(optdb, opt, valp);
|
nuclear@24
|
228 case OTYPE_VEC:
|
nuclear@24
|
229 return get_option_vec(optdb, opt, valp);
|
nuclear@0
|
230 }
|
nuclear@0
|
231 return -1;
|
nuclear@0
|
232 }
|
nuclear@0
|
233
|
nuclear@7
|
234 static void translation(int eye, float *vec)
|
nuclear@0
|
235 {
|
nuclear@0
|
236 if(!hmd) {
|
nuclear@0
|
237 vec[0] = vec[1] = vec[2] = 0;
|
nuclear@9
|
238 return;
|
nuclear@0
|
239 }
|
nuclear@0
|
240
|
nuclear@19
|
241 /* if we're inside the begin-end block we can get a fresh pose, otherwise we'll just
|
nuclear@19
|
242 * reuse the one we got last frame.
|
nuclear@19
|
243 */
|
nuclear@19
|
244 if(inside_begin_end) {
|
nuclear@21
|
245 pose[eye] = ovrHmd_GetHmdPosePerEye(hmd, eye == VR_EYE_LEFT ? ovrEye_Left : ovrEye_Right);
|
nuclear@19
|
246 }
|
nuclear@19
|
247
|
nuclear@19
|
248 vec[0] = pose[eye].Position.x;
|
nuclear@19
|
249 vec[1] = pose[eye].Position.y;
|
nuclear@19
|
250 vec[2] = pose[eye].Position.z;
|
nuclear@0
|
251 }
|
nuclear@0
|
252
|
nuclear@7
|
253 static void rotation(int eye, float *quat)
|
nuclear@0
|
254 {
|
nuclear@0
|
255 if(!hmd) {
|
nuclear@0
|
256 quat[0] = quat[1] = quat[2] = 0.0f;
|
nuclear@0
|
257 quat[3] = 1.0f;
|
nuclear@9
|
258 return;
|
nuclear@0
|
259 }
|
nuclear@0
|
260
|
nuclear@19
|
261 /* same as above (translation) */
|
nuclear@19
|
262 if(inside_begin_end) {
|
nuclear@21
|
263 pose[eye] = ovrHmd_GetHmdPosePerEye(hmd, eye == VR_EYE_LEFT ? ovrEye_Left : ovrEye_Right);
|
nuclear@19
|
264 }
|
nuclear@19
|
265
|
nuclear@0
|
266 quat[0] = pose[eye].Orientation.x;
|
nuclear@0
|
267 quat[1] = pose[eye].Orientation.y;
|
nuclear@0
|
268 quat[2] = pose[eye].Orientation.z;
|
nuclear@0
|
269 quat[3] = pose[eye].Orientation.w;
|
nuclear@0
|
270 }
|
nuclear@0
|
271
|
nuclear@0
|
272 static const float idmat[] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1};
|
nuclear@0
|
273
|
nuclear@0
|
274 static void proj_matrix(int eye, float znear, float zfar, float *mat)
|
nuclear@0
|
275 {
|
nuclear@0
|
276 int i, j;
|
nuclear@0
|
277 ovrMatrix4f vmat;
|
nuclear@0
|
278
|
nuclear@0
|
279 if(!hmd) {
|
nuclear@0
|
280 memcpy(mat, idmat, sizeof idmat);
|
nuclear@0
|
281 return;
|
nuclear@0
|
282 }
|
nuclear@0
|
283
|
nuclear@0
|
284 vmat = ovrMatrix4f_Projection(eye_render_desc[eye].Fov, znear, zfar, 1);
|
nuclear@0
|
285
|
nuclear@0
|
286 for(i=0; i<4; i++) {
|
nuclear@0
|
287 for(j=0; j<4; j++) {
|
nuclear@0
|
288 *mat++ = vmat.M[j][i];
|
nuclear@0
|
289 }
|
nuclear@0
|
290 }
|
nuclear@0
|
291 }
|
nuclear@0
|
292
|
nuclear@0
|
293 static void begin(int eye)
|
nuclear@0
|
294 {
|
nuclear@0
|
295 if(!hmd) return;
|
nuclear@0
|
296
|
nuclear@0
|
297 if(!deferred_init_done) {
|
nuclear@0
|
298 deferred_init();
|
nuclear@0
|
299 }
|
nuclear@0
|
300
|
nuclear@19
|
301 if(!inside_begin_end) {
|
nuclear@0
|
302 ovrHmd_BeginFrame(hmd, 0);
|
nuclear@19
|
303 inside_begin_end = 1;
|
nuclear@0
|
304 }
|
nuclear@0
|
305 }
|
nuclear@0
|
306
|
nuclear@0
|
307 static int present(void)
|
nuclear@0
|
308 {
|
nuclear@30
|
309 int cur_prog = 0;
|
nuclear@24
|
310
|
nuclear@0
|
311 if(!hmd) return 0;
|
nuclear@0
|
312
|
nuclear@30
|
313 if(gl_use_program) {
|
nuclear@30
|
314 glGetIntegerv(GL_CURRENT_PROGRAM, &cur_prog);
|
nuclear@30
|
315 }
|
nuclear@24
|
316
|
nuclear@0
|
317 ovrHmd_EndFrame(hmd, pose, &eye_tex[0].Texture);
|
nuclear@19
|
318 inside_begin_end = 0;
|
nuclear@0
|
319
|
nuclear@30
|
320 if(gl_use_program) {
|
nuclear@30
|
321 gl_use_program(cur_prog);
|
nuclear@24
|
322 }
|
nuclear@24
|
323
|
nuclear@0
|
324 return 1;
|
nuclear@0
|
325 }
|
nuclear@0
|
326
|
nuclear@0
|
327 static void set_eye_texture(int eye, unsigned int tex, float umin, float vmin, float umax, float vmax)
|
nuclear@0
|
328 {
|
nuclear@0
|
329 ovrSizei texsz;
|
nuclear@0
|
330 ovrRecti rect;
|
nuclear@0
|
331
|
nuclear@0
|
332 glBindTexture(GL_TEXTURE_2D, tex);
|
nuclear@0
|
333 glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &texsz.w);
|
nuclear@0
|
334 glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &texsz.h);
|
nuclear@0
|
335
|
nuclear@0
|
336 rect.Pos.x = (int)(umin * texsz.w);
|
nuclear@21
|
337 rect.Pos.y = (int)(vmin * texsz.h);
|
nuclear@0
|
338 rect.Size.w = (int)((umax - umin) * texsz.w);
|
nuclear@0
|
339 rect.Size.h = (int)((vmax - vmin) * texsz.h);
|
nuclear@0
|
340
|
nuclear@0
|
341 eye_tex[eye].OGL.Header.API = ovrRenderAPI_OpenGL;
|
nuclear@0
|
342 eye_tex[eye].OGL.Header.TextureSize = texsz;
|
nuclear@0
|
343 eye_tex[eye].OGL.Header.RenderViewport = rect;
|
nuclear@0
|
344 eye_tex[eye].OGL.TexId = tex;
|
nuclear@0
|
345 }
|
nuclear@0
|
346
|
nuclear@0
|
347 static void recenter(void)
|
nuclear@0
|
348 {
|
nuclear@0
|
349 if(hmd) {
|
nuclear@0
|
350 ovrHmd_RecenterPose(hmd);
|
nuclear@0
|
351 }
|
nuclear@0
|
352 }
|
nuclear@0
|
353
|
nuclear@0
|
354 struct vr_module *vr_module_libovr(void)
|
nuclear@0
|
355 {
|
nuclear@0
|
356 static struct vr_module m;
|
nuclear@0
|
357
|
nuclear@0
|
358 if(!m.init) {
|
nuclear@0
|
359 m.name = "libovr";
|
nuclear@0
|
360 m.init = init;
|
nuclear@0
|
361 m.cleanup = cleanup;
|
nuclear@0
|
362 m.set_option = set_option;
|
nuclear@0
|
363 m.get_option = get_option;
|
nuclear@0
|
364 m.translation = translation;
|
nuclear@0
|
365 m.rotation = rotation;
|
nuclear@0
|
366 m.proj_matrix = proj_matrix;
|
nuclear@0
|
367 m.begin = begin;
|
nuclear@0
|
368 m.present = present;
|
nuclear@0
|
369 m.set_eye_texture = set_eye_texture;
|
nuclear@0
|
370 m.recenter = recenter;
|
nuclear@0
|
371 }
|
nuclear@0
|
372 return &m;
|
nuclear@0
|
373 }
|
nuclear@0
|
374
|
nuclear@0
|
375 #else /* no libovr */
|
nuclear@0
|
376
|
nuclear@0
|
377 static int init(void)
|
nuclear@0
|
378 {
|
nuclear@0
|
379 return -1;
|
nuclear@0
|
380 }
|
nuclear@0
|
381
|
nuclear@0
|
382 struct vr_module *vr_module_libovr(void)
|
nuclear@0
|
383 {
|
nuclear@0
|
384 static struct vr_module m;
|
nuclear@0
|
385
|
nuclear@0
|
386 if(!m.init) {
|
nuclear@0
|
387 m.name = "libovr";
|
nuclear@0
|
388 m.init = init;
|
nuclear@0
|
389 }
|
nuclear@0
|
390 return &m;
|
nuclear@0
|
391 }
|
nuclear@0
|
392
|
nuclear@0
|
393 #endif /* USE_LIBOVR */
|