804695cd4de13fa8e78430c1b0dc927db4129bc2
[mercenary-reloaded.git] / src / libsdl / sdl.c
1 /* SDL handling
2  *
3  * (C) 2018 by Andreas Eversberg <jolly@eversberg.eu>
4  * All Rights Reserved
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <stdio.h>
21 #include <stdint.h>
22 #include <errno.h>
23 #include "print.h"
24 #include "../../include/keycodes.h"
25 #include "sdl.h"
26
27 #include <SDL2/SDL.h>
28 #define GL3_PROTOTYPES 1
29 #include <GL/glew.h>
30
31 static int sdl_initialized = 0;
32 static int audio_initialized = 0;
33 static const char *device_string = NULL;
34 static SDL_AudioDeviceID audio_devid = 0;
35 static SDL_Window *gl_window = NULL;
36 static SDL_GLContext gl_context = NULL;
37 static void (*keyboard_sdl)(int down, enum keycode keycode) = NULL;
38 static void (*audio_sdl)(float *data, int len) = NULL;
39 static void (*resize_window_sdl)(int width, int height) = NULL;
40
41 static void audio_cb(void __attribute__((unused)) *userdata, Uint8 *stream, int len)
42 {
43         float audio_data[len / sizeof(float)];
44
45         SDL_memset(stream, 0, len);
46         audio_sdl(audio_data, len / sizeof(float) / 2);
47         SDL_MixAudioFormat(stream, (Uint8 *)audio_data, AUDIO_F32, len, SDL_MIX_MAXVOLUME / 2.0);
48 }
49
50 int init_sdl(const char *progname, int width, int height, int sound_samplerate, int sound_chunk, void (*keyboard)(int down, enum keycode keycode), void (*audio)(float *data, int len), void (*resize_window)(int width, int height), int multisampling, int vbl_sync, int rift)
51 {
52         int rc;
53
54         keyboard_sdl = keyboard;
55         audio_sdl = audio;
56         resize_window_sdl = resize_window;
57
58         /* init SDL library */
59         rc = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
60         if (rc < 0) {
61                 print_error("Failed to init SDL\n");
62                 goto error;
63         }
64         sdl_initialized = 1;
65
66 retry_without_multisampling:
67         if (multisampling > 1) {
68                 SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
69                 SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
70                 SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
71                 SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
72                 SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
73                 SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
74                 SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, multisampling);
75         }
76
77         /* open window */
78         gl_window = SDL_CreateWindow(progname, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
79         if (!gl_window) {
80                 if (multisampling) {
81                         print_info("Failed to open SDL window: %s (retrying without multisampling)\n", SDL_GetError());
82                         SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
83                         SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0);
84                         multisampling = 0;
85                         goto retry_without_multisampling;
86                 }
87                 print_error("Failed to open SDL window: %s\n", SDL_GetError());
88                 SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
89                 SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, multisampling);
90                 rc = EIO;
91                 goto error;
92         }
93
94         /* create GL context */
95         gl_context = SDL_GL_CreateContext(gl_window);
96         if (!gl_context) {
97                 print_error("Failed to create SDL's OpenGL context: %s\n", SDL_GetError());
98                 rc = EIO;
99                 goto error;
100         }
101
102         rc = SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
103         if (rc < 0) {
104                 print_error("Failed to set SDL's OpenGL context profile\n");
105                 goto error;
106         }
107
108         rc = SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
109         if (rc < 0) {
110                 print_error("Failed to set SDL's OpenGL major version\n");
111                 goto error;
112         }
113         rc = SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
114         if (rc < 0) {
115                 print_error("Failed to set SDL's OpenGL minor version\n");
116                 goto error;
117         }
118
119         rc = SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
120         if (rc < 0) {
121                 print_error("Failed to set SDL's OpenGL doublebuffer\n");
122                 goto error;
123         }
124
125         rc = SDL_GL_SetSwapInterval((vbl_sync) ? 1 : 0);
126         if (rc < 0) {
127                 print_error("Failed to set SDL's OpenGL swap interval to VBLANK\n");
128                 // continue anyway
129         }
130
131 // seems like a hack to me. do we need this?
132 #ifndef __APPLE__
133 #if !defined(_WIN32)
134         glewExperimental = GL_TRUE;
135         if (glewInit() != GLEW_OK) {
136                 print_error("Failed to init GLEW\n");
137                 goto error;
138         }
139 #endif
140 #endif
141
142         /* just in case */
143         glDisable(GL_DEPTH_TEST);
144         glDisable(GL_CULL_FACE);
145         if (multisampling > 1)
146                 glEnable(GL_MULTISAMPLE);
147
148         if (rift) {
149                 int i, count;
150                 count = SDL_GetNumAudioDevices(0);
151                 const char *string;
152                 for(i = 0; i < count; i++) {
153                         string = SDL_GetAudioDeviceName(i, 0);
154                         if (strstr(string, "Rift"))
155                                 device_string = string;
156                 }
157                 if (!device_string)
158                         print_error("Oculus Rift headset not found, falling back to default audio device.\n");
159         }
160
161         /* open audio */
162         SDL_AudioSpec want, have;
163         SDL_memset(&want, 0, sizeof(want)); /* or SDL_zero(want) */
164         want.freq = sound_samplerate;
165         want.format = AUDIO_F32; /* we always use float in this project */
166         want.channels = 2;
167         want.samples = sound_chunk; /* must be a power of two */
168         want.callback = audio_cb;
169         audio_devid = SDL_OpenAudioDevice(device_string, 0, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE);
170         if (audio_devid <= 0) {
171                 print_error("Failed to open audio! (No speaker connected?)\n");
172                 rc = -EIO;
173                 goto error;
174         } else if (have.format != want.format) {
175                 print_error("Failed to open audio with desired audio format (want %d, got %d)\n", want.format, have.format);
176                 rc = -EIO;
177                 goto error;
178         } else if (have.freq != want.freq) {
179                 print_error("Failed to open audio with desired sample rate (want %d, got %d)\n", want.freq, have.freq);
180                 rc = -EIO;
181                 goto error;
182         } else if (have.channels != want.channels) {
183                 print_error("Failed to open audio with desired number of channels (want %d, got %d)\n", want.channels, have.channels);
184                 rc = -EIO;
185                 goto error;
186         } else {
187                 SDL_PauseAudioDevice(audio_devid, 0);
188                 audio_initialized = 1;
189         }
190
191         return 0;
192
193 error:
194         exit_sdl();
195         return rc;
196 }
197
198 static enum keycode sdl2keycode(SDL_Keycode sym)
199 {
200         switch (sym) {
201         case SDLK_LCTRL: return KEYCODE_LCTRL;
202         case SDLK_RCTRL: return KEYCODE_RCTRL;
203         case SDLK_LSHIFT: return KEYCODE_LSHIFT;
204         case SDLK_RSHIFT: return KEYCODE_RSHIFT;
205         case SDLK_LALT: return KEYCODE_LALT;
206         case SDLK_RALT: return KEYCODE_RALT;
207         case SDLK_PAUSE: return KEYCODE_PAUSE;
208         case SDLK_LEFT: return KEYCODE_LEFT;
209         case SDLK_RIGHT: return KEYCODE_RIGHT;
210         case SDLK_UP: return KEYCODE_UP;
211         case SDLK_DOWN: return KEYCODE_DOWN;
212         case SDLK_END: return KEYCODE_END;
213         case SDLK_a: return KEYCODE_a;
214         case SDLK_b: return KEYCODE_b;
215         case SDLK_c: return KEYCODE_c;
216         case SDLK_d: return KEYCODE_d;
217         case SDLK_e: return KEYCODE_e;
218         case SDLK_f: return KEYCODE_f;
219         case SDLK_g: return KEYCODE_g;
220         case SDLK_h: return KEYCODE_h;
221         case SDLK_i: return KEYCODE_i;
222         case SDLK_j: return KEYCODE_j;
223         case SDLK_k: return KEYCODE_k;
224         case SDLK_l: return KEYCODE_l;
225         case SDLK_m: return KEYCODE_m;
226         case SDLK_n: return KEYCODE_n;
227         case SDLK_o: return KEYCODE_o;
228         case SDLK_p: return KEYCODE_p;
229         case SDLK_q: return KEYCODE_q;
230         case SDLK_r: return KEYCODE_r;
231         case SDLK_s: return KEYCODE_s;
232         case SDLK_t: return KEYCODE_t;
233         case SDLK_u: return KEYCODE_u;
234         case SDLK_v: return KEYCODE_v;
235         case SDLK_w: return KEYCODE_w;
236         case SDLK_x: return KEYCODE_x;
237         case SDLK_y: return KEYCODE_y;
238         case SDLK_z: return KEYCODE_z;
239         case SDLK_0: return KEYCODE_0;
240         case SDLK_1: return KEYCODE_1;
241         case SDLK_2: return KEYCODE_2;
242         case SDLK_3: return KEYCODE_3;
243         case SDLK_4: return KEYCODE_4;
244         case SDLK_5: return KEYCODE_5;
245         case SDLK_6: return KEYCODE_6;
246         case SDLK_7: return KEYCODE_7;
247         case SDLK_8: return KEYCODE_8;
248         case SDLK_9: return KEYCODE_9;
249         case SDLK_KP_0: return KEYCODE_KP_0;
250         case SDLK_KP_1: return KEYCODE_KP_1;
251         case SDLK_KP_2: return KEYCODE_KP_2;
252         case SDLK_KP_3: return KEYCODE_KP_3;
253         case SDLK_KP_4: return KEYCODE_KP_4;
254         case SDLK_KP_5: return KEYCODE_KP_5;
255         case SDLK_KP_6: return KEYCODE_KP_6;
256         case SDLK_KP_7: return KEYCODE_KP_7;
257         case SDLK_KP_8: return KEYCODE_KP_8;
258         case SDLK_KP_9: return KEYCODE_KP_9;
259         case SDLK_KP_DIVIDE: return KEYCODE_KP_DIVIDE;
260         case SDLK_KP_MULTIPLY: return KEYCODE_KP_MULTIPLY;
261         case SDLK_KP_MINUS: return KEYCODE_KP_MINUS;
262         case SDLK_KP_PLUS: return KEYCODE_KP_PLUS;
263         case SDLK_KP_PERIOD: return KEYCODE_KP_PERIOD;
264         case SDLK_KP_ENTER: return KEYCODE_KP_ENTER;
265         case SDLK_F1: return KEYCODE_F1;
266         case SDLK_F2: return KEYCODE_F2;
267         case SDLK_F3: return KEYCODE_F3;
268         case SDLK_F4: return KEYCODE_F4;
269         case SDLK_F5: return KEYCODE_F5;
270         case SDLK_F6: return KEYCODE_F6;
271         case SDLK_F7: return KEYCODE_F7;
272         case SDLK_F8: return KEYCODE_F8;
273         case SDLK_F9: return KEYCODE_F9;
274         case SDLK_F10: return KEYCODE_F10;
275         case SDLK_SPACE: return KEYCODE_SPACE;
276         case SDLK_BACKSPACE: return KEYCODE_BACKSPACE;
277         case SDLK_TAB: return KEYCODE_TAB;
278         case SDLK_RETURN: return KEYCODE_RETURN;
279         case SDLK_ESCAPE: return KEYCODE_ESCAPE;
280         case SDLK_DELETE: return KEYCODE_DELETE;
281         case SDLK_INSERT: return KEYCODE_INSERT;
282         case SDLK_COMMA: return KEYCODE_COMMA;
283         case SDLK_PERIOD: return KEYCODE_PERIOD;
284         default: return KEYCODE_UNDEFINED;
285         }
286 }
287
288 static int key_ctrl = 0, key_f = 0, fullscreen = 0;
289
290 int event_sdl(void)
291 {
292         int quit = 0;
293         SDL_Event event;
294
295         while (SDL_PollEvent(&event)) {
296                 if (event.type == SDL_QUIT)
297                         quit = 1;
298                 if (event.type == SDL_WINDOWEVENT) {
299                         if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)
300                                 resize_window_sdl(event.window.data1, event.window.data2);
301                 }
302                 if (event.type == SDL_KEYDOWN) {
303                         switch (event.key.keysym.sym) {
304                         case SDLK_f:
305                                 if (key_ctrl && !key_f) {
306                                         if (fullscreen) {
307                                                 fullscreen = 0;
308                                                 SDL_SetWindowFullscreen(gl_window, 0);
309                                                 SDL_ShowCursor(SDL_ENABLE);
310                                         } else {
311                                                 SDL_SetWindowFullscreen(gl_window, SDL_WINDOW_FULLSCREEN_DESKTOP);
312                                                 SDL_ShowCursor(SDL_DISABLE);
313                                                 fullscreen = 1;
314                                         }
315                                 }
316                                 key_f = 1;
317                                 break;
318                         case SDLK_LCTRL:
319                         case SDLK_RCTRL:
320                                 key_ctrl = 1;
321                                 break;
322                         }
323                         keyboard_sdl(1, sdl2keycode(event.key.keysym.sym));
324                 }
325                 if (event.type == SDL_KEYUP) {
326                         switch (event.key.keysym.sym) {
327                         case SDLK_LCTRL:
328                         case SDLK_RCTRL:
329                                 key_ctrl = 0;
330                                 break;
331                         case SDLK_f:
332                                 key_f = 0;
333                                 break;
334                         }
335                         keyboard_sdl(0, sdl2keycode(event.key.keysym.sym));
336                 }
337         }
338
339         return quit;
340 }
341
342 void swap_sdl(void)
343 {
344         SDL_GL_SwapWindow(gl_window);
345 }
346
347 void exit_sdl(void)
348 {
349         /* close window */
350         if (gl_window) {
351                 SDL_DestroyWindow(gl_window);
352                 gl_window = NULL;
353         }
354
355         /* clear OpenGL context */
356         if (gl_context) {
357                 SDL_GL_DeleteContext(gl_context);
358                 gl_context = NULL;
359         }
360
361         /* exit SDL library */
362         if (audio_initialized) {
363                 SDL_PauseAudioDevice(audio_devid, 1);
364                 audio_initialized = 0;
365         }
366         if (audio_devid > 0) {
367                 SDL_CloseAudioDevice(audio_devid);
368                 audio_devid = 0;
369         }
370         if (sdl_initialized) {
371                 SDL_Quit();
372                 sdl_initialized = 0;
373         }
374 }
375
376 uint32_t ticks_sdl(void)
377 {
378         return SDL_GetTicks();
379 }