1 /*
2  * Copyright (C) 2003 Robert Kooima
3  *
4  * NEVERBALL is  free software; you can redistribute  it and/or modify
5  * it under the  terms of the GNU General  Public License as published
6  * by the Free  Software Foundation; either version 2  of the License,
7  * or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT  ANY  WARRANTY;  without   even  the  implied  warranty  of
11  * MERCHANTABILITY or  FITNESS FOR A PARTICULAR PURPOSE.   See the GNU
12  * General Public License for more details.
13  */
14 
15 #include <SDL.h>
16 
17 #include "video.h"
18 #include "common.h"
19 #include "image.h"
20 #include "vec3.h"
21 #include "glext.h"
22 #include "config.h"
23 #include "gui.h"
24 #include "hmd.h"
25 
26 extern const char TITLE[];
27 extern const char ICON[];
28 
29 struct video video;
30 
31 /*---------------------------------------------------------------------------*/
32 
33 /* Normally...... show the system cursor and hide the virtual cursor.        */
34 /* In HMD mode... show the virtual cursor and hide the system cursor.        */
35 
video_show_cursor()36 void video_show_cursor()
37 {
38     if (hmd_stat())
39     {
40         gui_set_cursor(1);
41         SDL_ShowCursor(SDL_DISABLE);
42     }
43     else
44     {
45         gui_set_cursor(0);
46         SDL_ShowCursor(SDL_ENABLE);
47     }
48 }
49 
50 /* When the cursor is to be hidden, make sure neither the virtual cursor     */
51 /* nor the system cursor is visible.                                         */
52 
video_hide_cursor()53 void video_hide_cursor()
54 {
55     gui_set_cursor(0);
56     SDL_ShowCursor(SDL_DISABLE);
57 }
58 
59 /*---------------------------------------------------------------------------*/
60 
61 static char snapshot_path[MAXSTR] = "";
62 
snapshot_init(void)63 static void snapshot_init(void)
64 {
65     snapshot_path[0] = 0;
66 }
67 
snapshot_prep(const char * path)68 static void snapshot_prep(const char *path)
69 {
70     if (path && *path)
71         SAFECPY(snapshot_path, path);
72 }
73 
snapshot_take(void)74 static void snapshot_take(void)
75 {
76     if (snapshot_path[0])
77     {
78         image_snap(snapshot_path);
79         snapshot_path[0] = 0;
80     }
81 }
82 
video_snap(const char * path)83 void video_snap(const char *path)
84 {
85     snapshot_prep(path);
86 }
87 
88 /*---------------------------------------------------------------------------*/
89 
90 static SDL_Window    *window;
91 static SDL_GLContext  context;
92 
set_window_title(const char * title)93 static void set_window_title(const char *title)
94 {
95     SDL_SetWindowTitle(window, title);
96 }
97 
set_window_icon(const char * filename)98 static void set_window_icon(const char *filename)
99 {
100 #if !defined(__APPLE__)
101     SDL_Surface *icon;
102 
103     if ((icon = load_surface(filename)))
104     {
105         SDL_SetWindowIcon(window, icon);
106         free(icon->pixels);
107         SDL_FreeSurface(icon);
108     }
109 #endif
110     return;
111 }
112 
video_display(void)113 int video_display(void)
114 {
115     if (window)
116         return SDL_GetWindowDisplayIndex(window);
117     else
118         return -1;
119 }
120 
video_init(void)121 int video_init(void)
122 {
123     if (!video_mode(config_get_d(CONFIG_FULLSCREEN),
124                     config_get_d(CONFIG_WIDTH),
125                     config_get_d(CONFIG_HEIGHT)))
126     {
127         log_printf("Failure to create window (%s)\n", SDL_GetError());
128         return 0;
129     }
130 
131     return 1;
132 }
133 
video_mode(int f,int w,int h)134 int video_mode(int f, int w, int h)
135 {
136     int stereo  = config_get_d(CONFIG_STEREO)      ? 1 : 0;
137     int stencil = config_get_d(CONFIG_REFLECTION)  ? 1 : 0;
138     int buffers = config_get_d(CONFIG_MULTISAMPLE) ? 1 : 0;
139     int samples = config_get_d(CONFIG_MULTISAMPLE);
140     int vsync   = config_get_d(CONFIG_VSYNC)       ? 1 : 0;
141     int hmd     = config_get_d(CONFIG_HMD)         ? 1 : 0;
142     int highdpi = config_get_d(CONFIG_HIGHDPI)     ? 1 : 0;
143 
144     int dpy = config_get_d(CONFIG_DISPLAY);
145 
146     int X = SDL_WINDOWPOS_CENTERED_DISPLAY(dpy);
147     int Y = SDL_WINDOWPOS_CENTERED_DISPLAY(dpy);
148 
149     hmd_free();
150 
151     if (window)
152     {
153         SDL_GL_DeleteContext(context);
154         SDL_DestroyWindow(window);
155     }
156 
157     SDL_GL_SetAttribute(SDL_GL_STEREO,             stereo);
158     SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE,       stencil);
159     SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, buffers);
160     SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, samples);
161 
162     /* Require 16-bit double buffer with 16-bit depth buffer. */
163 
164     SDL_GL_SetAttribute(SDL_GL_RED_SIZE,     5);
165     SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE,   5);
166     SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE,    5);
167     SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,  16);
168     SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
169 
170     /* Try to set the currently specified mode. */
171 
172     log_printf("Creating a window (%dx%d, %s)\n",
173                w, h, (f ? "fullscreen" : "windowed"));
174 
175     window = SDL_CreateWindow("", X, Y, w, h,
176                               SDL_WINDOW_OPENGL |
177                               (highdpi ? SDL_WINDOW_ALLOW_HIGHDPI : 0) |
178                               (f ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0));
179 
180     if (window)
181     {
182         if ((context = SDL_GL_CreateContext(window)))
183         {
184             int buf, smp;
185 
186             SDL_GL_GetAttribute(SDL_GL_MULTISAMPLEBUFFERS, &buf);
187             SDL_GL_GetAttribute(SDL_GL_MULTISAMPLESAMPLES, &smp);
188 
189             /*
190              * Work around SDL+WGL returning pixel formats below
191              * minimum specifications instead of failing, thus
192              * bypassing our fallback path. SDL tries to ensure that
193              * WGL plays by the rules, but forgets about extended
194              * context attributes such as multisample. See SDL
195              * Bugzilla #77.
196              */
197 
198             if (buf < buffers || smp < samples)
199             {
200                 log_printf("GL context does not meet minimum specifications\n");
201                 SDL_GL_DeleteContext(context);
202                 context = NULL;
203             }
204         }
205     }
206 
207     if (window && context)
208     {
209         set_window_title(TITLE);
210         set_window_icon(ICON);
211 
212         /*
213          * SDL_GetWindowSize can be unreliable when going fullscreen
214          * on OSX (and possibly elsewhere). We should really be
215          * waiting for a resize / size change event, but for now we're
216          * doing this lazy thing instead.
217          */
218 
219         if (f)
220         {
221             SDL_DisplayMode dm;
222 
223             if (SDL_GetDesktopDisplayMode(video_display(), &dm) == 0)
224             {
225                 video.window_w = dm.w;
226                 video.window_h = dm.h;
227             }
228         }
229         else
230         {
231             SDL_GetWindowSize(window, &video.window_w, &video.window_h);
232         }
233 
234         if (highdpi)
235         {
236             SDL_GL_GetDrawableSize(window, &video.device_w, &video.device_h);
237         }
238         else
239         {
240             video.device_w = video.window_w;
241             video.device_h = video.window_h;
242         }
243 
244         video.device_scale = (float) video.device_h / (float) video.window_h;
245 
246         log_printf("Created a window (%u, %dx%d, %s)\n",
247                    SDL_GetWindowID(window),
248                    video.window_w, video.window_h,
249                    (f ? "fullscreen" : "windowed"));
250 
251         config_set_d(CONFIG_DISPLAY,    video_display());
252         config_set_d(CONFIG_FULLSCREEN, f);
253         config_set_d(CONFIG_WIDTH,      video.window_w);
254         config_set_d(CONFIG_HEIGHT,     video.window_h);
255 
256         SDL_GL_SetSwapInterval(vsync);
257 
258         if (!glext_init())
259             return 0;
260 
261         glViewport(0, 0, video.device_w, video.device_h);
262         glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
263 
264         glEnable(GL_NORMALIZE);
265         glEnable(GL_CULL_FACE);
266         glEnable(GL_DEPTH_TEST);
267         glEnable(GL_TEXTURE_2D);
268         glEnable(GL_LIGHTING);
269         glEnable(GL_BLEND);
270 
271 #if !ENABLE_OPENGLES
272         glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL,
273                       GL_SEPARATE_SPECULAR_COLOR);
274 #endif
275 
276         glPixelStorei(GL_PACK_ALIGNMENT, 1);
277         glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
278 
279         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
280         glDepthFunc(GL_LEQUAL);
281 
282         /* If GL supports multisample, and SDL got a multisample buffer... */
283 
284         if (glext_check("ARB_multisample"))
285         {
286             SDL_GL_GetAttribute(SDL_GL_MULTISAMPLEBUFFERS, &buffers);
287             if (buffers) glEnable(GL_MULTISAMPLE);
288         }
289 
290         /* Set up HMD display if requested. */
291 
292         if (hmd)
293             hmd_init();
294 
295         /* Initialize screen snapshotting. */
296 
297         snapshot_init();
298 
299         video_show_cursor();
300 
301         /* Grab input immediately in HMD mode. */
302 
303         if (hmd_stat())
304             SDL_SetWindowGrab(window, SDL_TRUE);
305 
306         return 1;
307     }
308 
309     /* If the mode failed, try it without stereo. */
310 
311     else if (stereo)
312     {
313         config_set_d(CONFIG_STEREO, 0);
314         return video_mode(f, w, h);
315     }
316 
317     /* If the mode failed, try decreasing the level of multisampling. */
318 
319     else if (buffers)
320     {
321         config_set_d(CONFIG_MULTISAMPLE, samples / 2);
322         return video_mode(f, w, h);
323     }
324 
325     /* If that mode failed, try it without reflections. */
326 
327     else if (stencil)
328     {
329         config_set_d(CONFIG_REFLECTION, 0);
330         return video_mode(f, w, h);
331     }
332 
333     /* If THAT mode failed, punt. */
334 
335     return 0;
336 }
337 
338 /*---------------------------------------------------------------------------*/
339 
340 static float ms     = 0;
341 static int   fps    = 0;
342 static int   last   = 0;
343 static int   ticks  = 0;
344 static int   frames = 0;
345 
video_perf(void)346 int  video_perf(void)
347 {
348     return fps;
349 }
350 
video_swap(void)351 void video_swap(void)
352 {
353     int dt;
354 
355     if (hmd_stat())
356         hmd_swap();
357 
358     /* Take a screenshot of the complete back buffer and swap it. */
359 
360     snapshot_take();
361 
362     SDL_GL_SwapWindow(window);
363 
364     /* Accumulate time passed and frames rendered. */
365 
366     dt = (int) SDL_GetTicks() - last;
367 
368     frames +=  1;
369     ticks  += dt;
370     last   += dt;
371 
372     /* Average over 250ms. */
373 
374     if (ticks > 1000)
375     {
376         /* Round the frames-per-second value to the nearest integer. */
377 
378         double k = 1000.0 * frames / ticks;
379         double f = floor(k);
380         double c = ceil (k);
381 
382         /* Compute frame time and frames-per-second stats. */
383 
384         fps = (int) ((c - k < k - f) ? c : f);
385         ms  = (float) ticks / (float) frames;
386 
387         /* Reset the counters for the next update. */
388 
389         frames = 0;
390         ticks  = 0;
391 
392         /* Output statistics if configured. */
393 
394         if (config_get_d(CONFIG_STATS))
395             fprintf(stdout, "%4d %8.4f\n", fps, (double) ms);
396     }
397 }
398 
399 /*---------------------------------------------------------------------------*/
400 
401 static int grabbed = 0;
402 
video_set_grab(int w)403 void video_set_grab(int w)
404 {
405 #ifdef NDEBUG
406     if (w)
407     {
408         SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE);
409 
410         SDL_WarpMouseInWindow(window,
411                               video.window_w / 2,
412                               video.window_h / 2);
413 
414         SDL_EventState(SDL_MOUSEMOTION, SDL_ENABLE);
415     }
416 
417     SDL_SetRelativeMouseMode(SDL_TRUE);
418     SDL_SetWindowGrab(window, SDL_TRUE);
419     video_hide_cursor();
420 #endif
421 
422     grabbed = 1;
423 }
424 
video_clr_grab(void)425 void video_clr_grab(void)
426 {
427 #ifdef NDEBUG
428     SDL_SetRelativeMouseMode(SDL_FALSE);
429 
430     /* Never release the grab in HMD mode. */
431 
432     if (!hmd_stat())
433         SDL_SetWindowGrab(window, SDL_FALSE);
434 
435     video_show_cursor();
436 #endif
437     grabbed = 0;
438 }
439 
video_get_grab(void)440 int  video_get_grab(void)
441 {
442     return grabbed;
443 }
444 
445 /*---------------------------------------------------------------------------*/
446 
video_calc_view(float * M,const float * c,const float * p,const float * u)447 void video_calc_view(float *M, const float *c,
448                                const float *p,
449                                const float *u)
450 {
451     float x[3];
452     float y[3];
453     float z[3];
454 
455     v_sub(z, p, c);
456     v_nrm(z, z);
457     v_crs(x, u, z);
458     v_nrm(x, x);
459     v_crs(y, z, x);
460 
461     m_basis(M, x, y, z);
462 }
463 
464 /*---------------------------------------------------------------------------*/
465 
video_push_persp(float fov,float n,float f)466 void video_push_persp(float fov, float n, float f)
467 {
468     if (hmd_stat())
469         hmd_persp(n, f);
470     else
471     {
472         GLfloat m[4][4];
473 
474         GLfloat r = fov / 2 * V_PI / 180;
475         GLfloat s = fsinf(r);
476         GLfloat c = fcosf(r) / s;
477 
478         GLfloat a = ((GLfloat) video.device_w /
479                      (GLfloat) video.device_h);
480 
481         glMatrixMode(GL_PROJECTION);
482         {
483             glLoadIdentity();
484 
485             m[0][0] = c / a;
486             m[0][1] =  0.0f;
487             m[0][2] =  0.0f;
488             m[0][3] =  0.0f;
489             m[1][0] =  0.0f;
490             m[1][1] =     c;
491             m[1][2] =  0.0f;
492             m[1][3] =  0.0f;
493             m[2][0] =  0.0f;
494             m[2][1] =  0.0f;
495             m[2][2] = -(f + n) / (f - n);
496             m[2][3] = -1.0f;
497             m[3][0] =  0.0f;
498             m[3][1] =  0.0f;
499             m[3][2] = -2.0f * n * f / (f - n);
500             m[3][3] =  0.0f;
501 
502             glMultMatrixf(&m[0][0]);
503         }
504         glMatrixMode(GL_MODELVIEW);
505         {
506             glLoadIdentity();
507         }
508     }
509 }
510 
video_push_ortho(void)511 void video_push_ortho(void)
512 {
513     if (hmd_stat())
514         hmd_ortho();
515     else
516     {
517         GLfloat w = (GLfloat) video.device_w;
518         GLfloat h = (GLfloat) video.device_h;
519 
520         glMatrixMode(GL_PROJECTION);
521         {
522             glLoadIdentity();
523             glOrtho_(0.0, w, 0.0, h, -1.0, +1.0);
524         }
525         glMatrixMode(GL_MODELVIEW);
526         {
527             glLoadIdentity();
528         }
529     }
530 }
531 
video_pop_matrix(void)532 void video_pop_matrix(void)
533 {
534 }
535 
video_clear(void)536 void video_clear(void)
537 {
538     if (config_get_d(CONFIG_REFLECTION))
539         glClear(GL_COLOR_BUFFER_BIT |
540                 GL_DEPTH_BUFFER_BIT |
541                 GL_STENCIL_BUFFER_BIT);
542     else
543         glClear(GL_COLOR_BUFFER_BIT |
544                 GL_DEPTH_BUFFER_BIT);
545 }
546 
547 /*---------------------------------------------------------------------------*/
548