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