Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
MorsGames
GitHub Repository: MorsGames/sm64plus
Path: blob/master/src/pc/gfx/gfx_sdl2.c
7861 views
1
#include "../compat.h"
2
3
#ifdef __MINGW32__
4
#define FOR_WINDOWS 1
5
#else
6
#define FOR_WINDOWS 0
7
#endif
8
9
#include <stdint.h>
10
#include <stdio.h>
11
#include <unistd.h>
12
13
#ifdef VERSION_EU
14
# define FRAME_RATE 50
15
#else
16
# define FRAME_RATE 60
17
#endif
18
19
#if FOR_WINDOWS
20
#include <GL/glew.h>
21
#include "SDL.h"
22
#include "SDL_syswm.h"
23
#define GL_GLEXT_PROTOTYPES 1
24
#include "SDL_opengl.h"
25
#else
26
#ifndef TARGET_MACOS
27
#include <SDL2/SDL.h>
28
#include <SDL2/SDL_syswm.h>
29
#else
30
#include <SDL_opengl.h>
31
#include <SDL.h>
32
#include <SDL_syswm.h>
33
#include <stdio.h>
34
#endif
35
#define GL_GLEXT_PROTOTYPES 1
36
#ifndef TARGET_MACOS
37
#include <SDL2/SDL_opengles2.h>
38
#endif
39
#endif
40
41
#include "gfx_window_manager_api.h"
42
#include "gfx_screen_config.h"
43
44
#include "game/settings.h"
45
46
#if defined(_WIN32) || defined(_WIN64)
47
#include "../../include/resource.h"
48
#endif
49
50
#define GFX_API_NAME "OpenGL"
51
52
static SDL_Window *wnd;
53
static int inverted_scancode_table[512];
54
55
static unsigned int window_width;
56
static unsigned int window_height;
57
static bool fullscreen_state;
58
static void (*on_fullscreen_changed_callback)(bool is_now_fullscreen);
59
static bool (*on_key_down_callback)(int scancode);
60
static bool (*on_key_up_callback)(int scancode);
61
static void (*on_all_keys_up_callback)(void);
62
static void (*on_mouse_move_callback)(long x, long y);
63
static void (*on_mouse_press_callback)(s8 left, s8 right, s8 middle, s8 wheel);
64
65
static bool relative_mouse_mode_on = false;
66
67
static Uint64 min_ticks_per_frame = 0;
68
static Uint64 next_frame_ticks = 0;
69
static Uint64 perf_freq = 0;
70
71
static int get_display_index(void) {
72
int display_index = (int) configDefaultMonitor - 1;
73
int num_displays = SDL_GetNumVideoDisplays();
74
75
if (display_index < 0 || display_index >= num_displays) {
76
return 0;
77
}
78
79
return display_index;
80
}
81
82
static bool get_configured_fullscreen_mode(SDL_DisplayMode *mode) {
83
if (configFullscreenDisplayMode == 0) {
84
return false;
85
}
86
87
return SDL_GetDisplayMode(get_display_index(), (int) configFullscreenDisplayMode - 1, mode) == 0;
88
}
89
90
static int8_t clamp_s8(long val) {
91
if (val < -128) return -128;
92
if (val > 127) return 127;
93
return val;
94
}
95
96
const SDL_Scancode windows_scancode_table[] =
97
{
98
/* 0 1 2 3 4 5 6 7 */
99
/* 8 9 A B C D E F */
100
SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_ESCAPE, SDL_SCANCODE_1, SDL_SCANCODE_2, SDL_SCANCODE_3, SDL_SCANCODE_4, SDL_SCANCODE_5, SDL_SCANCODE_6, /* 0 */
101
SDL_SCANCODE_7, SDL_SCANCODE_8, SDL_SCANCODE_9, SDL_SCANCODE_0, SDL_SCANCODE_MINUS, SDL_SCANCODE_EQUALS, SDL_SCANCODE_BACKSPACE, SDL_SCANCODE_TAB, /* 0 */
102
103
SDL_SCANCODE_Q, SDL_SCANCODE_W, SDL_SCANCODE_E, SDL_SCANCODE_R, SDL_SCANCODE_T, SDL_SCANCODE_Y, SDL_SCANCODE_U, SDL_SCANCODE_I, /* 1 */
104
SDL_SCANCODE_O, SDL_SCANCODE_P, SDL_SCANCODE_LEFTBRACKET, SDL_SCANCODE_RIGHTBRACKET, SDL_SCANCODE_RETURN, SDL_SCANCODE_LCTRL, SDL_SCANCODE_A, SDL_SCANCODE_S, /* 1 */
105
106
SDL_SCANCODE_D, SDL_SCANCODE_F, SDL_SCANCODE_G, SDL_SCANCODE_H, SDL_SCANCODE_J, SDL_SCANCODE_K, SDL_SCANCODE_L, SDL_SCANCODE_SEMICOLON, /* 2 */
107
SDL_SCANCODE_APOSTROPHE, SDL_SCANCODE_GRAVE, SDL_SCANCODE_LSHIFT, SDL_SCANCODE_BACKSLASH, SDL_SCANCODE_Z, SDL_SCANCODE_X, SDL_SCANCODE_C, SDL_SCANCODE_V, /* 2 */
108
109
SDL_SCANCODE_B, SDL_SCANCODE_N, SDL_SCANCODE_M, SDL_SCANCODE_COMMA, SDL_SCANCODE_PERIOD, SDL_SCANCODE_SLASH, SDL_SCANCODE_RSHIFT, SDL_SCANCODE_PRINTSCREEN,/* 3 */
110
SDL_SCANCODE_LALT, SDL_SCANCODE_SPACE, SDL_SCANCODE_CAPSLOCK, SDL_SCANCODE_F1, SDL_SCANCODE_F2, SDL_SCANCODE_F3, SDL_SCANCODE_F4, SDL_SCANCODE_F5, /* 3 */
111
112
SDL_SCANCODE_F6, SDL_SCANCODE_F7, SDL_SCANCODE_F8, SDL_SCANCODE_F9, SDL_SCANCODE_F10, SDL_SCANCODE_NUMLOCKCLEAR, SDL_SCANCODE_SCROLLLOCK, SDL_SCANCODE_HOME, /* 4 */
113
SDL_SCANCODE_UP, SDL_SCANCODE_PAGEUP, SDL_SCANCODE_KP_MINUS, SDL_SCANCODE_LEFT, SDL_SCANCODE_KP_5, SDL_SCANCODE_RIGHT, SDL_SCANCODE_KP_PLUS, SDL_SCANCODE_END, /* 4 */
114
115
SDL_SCANCODE_DOWN, SDL_SCANCODE_PAGEDOWN, SDL_SCANCODE_INSERT, SDL_SCANCODE_DELETE, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_NONUSBACKSLASH,SDL_SCANCODE_F11, /* 5 */
116
SDL_SCANCODE_F12, SDL_SCANCODE_PAUSE, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_LGUI, SDL_SCANCODE_RGUI, SDL_SCANCODE_APPLICATION, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, /* 5 */
117
118
SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_F13, SDL_SCANCODE_F14, SDL_SCANCODE_F15, SDL_SCANCODE_F16, /* 6 */
119
SDL_SCANCODE_F17, SDL_SCANCODE_F18, SDL_SCANCODE_F19, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, /* 6 */
120
121
SDL_SCANCODE_INTERNATIONAL2, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_INTERNATIONAL1, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, /* 7 */
122
SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_INTERNATIONAL4, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_INTERNATIONAL5, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_INTERNATIONAL3, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN /* 7 */
123
};
124
125
const SDL_Scancode scancode_rmapping_extended[][2] = {
126
{SDL_SCANCODE_KP_ENTER, SDL_SCANCODE_RETURN},
127
{SDL_SCANCODE_RALT, SDL_SCANCODE_LALT},
128
{SDL_SCANCODE_RCTRL, SDL_SCANCODE_LCTRL},
129
{SDL_SCANCODE_KP_DIVIDE, SDL_SCANCODE_SLASH},
130
//{SDL_SCANCODE_KP_PLUS, SDL_SCANCODE_CAPSLOCK}
131
};
132
133
const SDL_Scancode scancode_rmapping_nonextended[][2] = {
134
{SDL_SCANCODE_KP_7, SDL_SCANCODE_HOME},
135
{SDL_SCANCODE_KP_8, SDL_SCANCODE_UP},
136
{SDL_SCANCODE_KP_9, SDL_SCANCODE_PAGEUP},
137
{SDL_SCANCODE_KP_4, SDL_SCANCODE_LEFT},
138
{SDL_SCANCODE_KP_6, SDL_SCANCODE_RIGHT},
139
{SDL_SCANCODE_KP_1, SDL_SCANCODE_END},
140
{SDL_SCANCODE_KP_2, SDL_SCANCODE_DOWN},
141
{SDL_SCANCODE_KP_3, SDL_SCANCODE_PAGEDOWN},
142
{SDL_SCANCODE_KP_0, SDL_SCANCODE_INSERT},
143
{SDL_SCANCODE_KP_PERIOD, SDL_SCANCODE_DELETE},
144
{SDL_SCANCODE_KP_MULTIPLY, SDL_SCANCODE_PRINTSCREEN}
145
};
146
147
static void set_fullscreen(bool on, bool call_callback) {
148
if (fullscreen_state == on) {
149
return;
150
}
151
fullscreen_state = on;
152
153
if (on) {
154
SDL_DisplayMode mode;
155
bool has_custom_display_mode = get_configured_fullscreen_mode(&mode);
156
157
if (has_custom_display_mode) {
158
SDL_SetWindowDisplayMode(wnd, &mode);
159
SDL_SetWindowSize(wnd, mode.w, mode.h);
160
SDL_SetWindowFullscreen(wnd, SDL_WINDOW_FULLSCREEN);
161
} else {
162
SDL_SetWindowDisplayMode(wnd, NULL);
163
SDL_GetDesktopDisplayMode(get_display_index(), &mode);
164
SDL_SetWindowSize(wnd, mode.w, mode.h);
165
SDL_SetWindowFullscreen(wnd, SDL_WINDOW_FULLSCREEN_DESKTOP);
166
}
167
SDL_ShowCursor(false);
168
} else {
169
SDL_SetWindowDisplayMode(wnd, NULL);
170
SDL_SetWindowFullscreen(wnd, 0);
171
SDL_SetWindowSize(wnd, window_width, window_height);
172
SDL_ShowCursor(true);
173
}
174
175
if (on_fullscreen_changed_callback != NULL && call_callback) {
176
on_fullscreen_changed_callback(on);
177
}
178
}
179
180
static void gfx_sdl_init(const char *game_name, bool start_in_fullscreen) {
181
window_width = DESIRED_SCREEN_WIDTH;
182
window_height = DESIRED_SCREEN_HEIGHT;
183
184
SDL_SetHint(SDL_HINT_TIMER_RESOLUTION, "1");
185
186
SDL_Init(SDL_INIT_VIDEO);
187
188
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
189
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
190
191
wnd = SDL_CreateWindow(game_name,
192
SDL_WINDOWPOS_UNDEFINED_DISPLAY(configDefaultMonitor-1),
193
SDL_WINDOWPOS_UNDEFINED_DISPLAY(configDefaultMonitor-1),
194
window_width, window_height, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_ALLOW_HIGHDPI);
195
196
#if defined(_WIN32) || defined(_WIN64)
197
// Set window icon from embedded resources
198
HINSTANCE hInstance = GetModuleHandle(NULL);
199
HICON hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(STAR_ICON));
200
if (hIcon) {
201
// Get the window handle from SDL
202
SDL_SysWMinfo wmInfo;
203
SDL_VERSION(&wmInfo.version);
204
if (SDL_GetWindowWMInfo(wnd, &wmInfo)) {
205
HWND hwnd = wmInfo.info.win.window;
206
// Set both the large and small icons
207
SendMessage(hwnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
208
SendMessage(hwnd, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
209
}
210
}
211
#endif
212
213
if (start_in_fullscreen) {
214
set_fullscreen(true, false);
215
}
216
217
if (configMouseCam) {
218
SDL_SetRelativeMouseMode(true);
219
relative_mouse_mode_on = true;
220
}
221
222
SDL_GL_CreateContext(wnd);
223
224
SDL_GL_SetSwapInterval(configVSync ? 1 : 0);
225
226
SDL_DisableScreenSaver();
227
228
perf_freq = SDL_GetPerformanceFrequency();
229
min_ticks_per_frame = perf_freq / FRAME_RATE;
230
next_frame_ticks = SDL_GetPerformanceCounter() + min_ticks_per_frame;
231
232
for (size_t i = 0; i < sizeof(windows_scancode_table) / sizeof(SDL_Scancode); i++) {
233
inverted_scancode_table[windows_scancode_table[i]] = i;
234
}
235
236
for (size_t i = 0; i < sizeof(scancode_rmapping_extended) / sizeof(scancode_rmapping_extended[0]); i++) {
237
inverted_scancode_table[scancode_rmapping_extended[i][0]] = inverted_scancode_table[scancode_rmapping_extended[i][1]] + 0x100;
238
}
239
240
for (size_t i = 0; i < sizeof(scancode_rmapping_nonextended) / sizeof(scancode_rmapping_nonextended[0]); i++) {
241
inverted_scancode_table[scancode_rmapping_nonextended[i][0]] = inverted_scancode_table[scancode_rmapping_nonextended[i][1]];
242
inverted_scancode_table[scancode_rmapping_nonextended[i][1]] += 0x100;
243
}
244
}
245
246
static void gfx_sdl_set_fullscreen_changed_callback(void (*on_fullscreen_changed)(bool is_now_fullscreen)) {
247
on_fullscreen_changed_callback = on_fullscreen_changed;
248
}
249
250
static void gfx_sdl_set_fullscreen(bool enable) {
251
set_fullscreen(enable, true);
252
}
253
254
static void gfx_sdl_set_vsync(bool enable) {
255
SDL_GL_SetSwapInterval(enable ? 1 : 0);
256
}
257
258
static void gfx_sdl_set_window_size(uint32_t width, uint32_t height) {
259
window_width = width;
260
window_height = height;
261
if (!fullscreen_state) {
262
SDL_SetWindowSize(wnd, width, height);
263
}
264
}
265
266
static void gfx_sdl_set_monitor(uint32_t monitor_index) {
267
configDefaultMonitor = monitor_index;
268
int display_index = get_display_index();
269
270
if (!fullscreen_state) {
271
SDL_SetWindowPosition(wnd, SDL_WINDOWPOS_CENTERED_DISPLAY(display_index), SDL_WINDOWPOS_CENTERED_DISPLAY(display_index));
272
} else {
273
SDL_SetWindowFullscreen(wnd, 0);
274
SDL_SetWindowPosition(wnd, SDL_WINDOWPOS_CENTERED_DISPLAY(display_index), SDL_WINDOWPOS_CENTERED_DISPLAY(display_index));
275
fullscreen_state = false;
276
set_fullscreen(true, false);
277
}
278
}
279
280
static int gfx_sdl_get_num_display_modes(void) {
281
return SDL_GetNumDisplayModes(get_display_index());
282
}
283
284
static void gfx_sdl_set_keyboard_callbacks(bool (*on_key_down)(int scancode), bool (*on_key_up)(int scancode), void (*on_all_keys_up)(void), void (*on_mouse_move)(long x, long y), void (*on_mouse_press)(s8 left, s8 right, s8 middle, s8 wheel)) {
285
on_key_down_callback = on_key_down;
286
on_key_up_callback = on_key_up;
287
on_all_keys_up_callback = on_all_keys_up;
288
on_mouse_move_callback = on_mouse_move;
289
on_mouse_press_callback = on_mouse_press;
290
}
291
292
static void gfx_sdl_main_loop(void (*run_one_game_iter)(void)) {
293
while (1) {
294
run_one_game_iter();
295
if (configMouseCam) {
296
bool want = (SDL_GetWindowFlags(wnd) & SDL_WINDOW_INPUT_FOCUS) == SDL_WINDOW_INPUT_FOCUS;
297
if (want != relative_mouse_mode_on) {
298
SDL_SetRelativeMouseMode(want);
299
relative_mouse_mode_on = want;
300
}
301
} else if (relative_mouse_mode_on) {
302
SDL_SetRelativeMouseMode(false);
303
relative_mouse_mode_on = false;
304
}
305
}
306
}
307
308
static void gfx_sdl_get_dimensions(uint32_t *width, uint32_t *height) {
309
int w, h;
310
SDL_GL_GetDrawableSize(wnd, &w, &h);
311
*width = w;
312
*height = h;
313
}
314
315
static int translate_scancode(int scancode) {
316
if (scancode < 512) {
317
return inverted_scancode_table[scancode];
318
} else {
319
return 0;
320
}
321
}
322
323
static void gfx_sdl_onkeydown(int scancode) {
324
int key = translate_scancode(scancode);
325
if (on_key_down_callback != NULL) {
326
on_key_down_callback(key);
327
}
328
}
329
330
static void gfx_sdl_onkeyup(int scancode) {
331
int key = translate_scancode(scancode);
332
if (on_key_up_callback != NULL) {
333
on_key_up_callback(key);
334
}
335
}
336
337
static void gfx_sdl_handle_events(void) {
338
SDL_Event event;
339
Uint8 *state = SDL_GetKeyboardState(NULL);
340
on_mouse_move_callback(0, 0);
341
while (SDL_PollEvent(&event)) {
342
switch (event.type) {
343
#ifndef TARGET_WEB
344
// Scancodes are broken in Emscripten SDL2: https://bugzilla.libsdl.org/show_bug.cgi?id=3259
345
case SDL_KEYDOWN:
346
if (state[SDL_SCANCODE_RETURN] && state[SDL_SCANCODE_LALT]) {
347
set_fullscreen(!fullscreen_state, true);
348
break;
349
}
350
351
gfx_sdl_onkeydown(event.key.keysym.scancode);
352
break;
353
case SDL_KEYUP:
354
gfx_sdl_onkeyup(event.key.keysym.scancode);
355
break;
356
#endif
357
case SDL_WINDOWEVENT:
358
if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED && (SDL_GetWindowFlags(SDL_GetWindowFromID(event.window.windowID)) & SDL_WINDOW_FULLSCREEN) == 0) {
359
window_width = event.window.data1;
360
window_height = event.window.data2;
361
}
362
break;
363
364
case SDL_QUIT:
365
exit(0);
366
break;
367
368
case SDL_MOUSEMOTION:
369
on_mouse_move_callback(event.motion.xrel*10, event.motion.yrel*10);
370
break;
371
372
case SDL_MOUSEBUTTONDOWN:
373
switch (event.button.button) {
374
case SDL_BUTTON_LEFT:
375
on_mouse_press_callback(1, 0, 0, 0);
376
break;
377
case SDL_BUTTON_RIGHT:
378
on_mouse_press_callback(0, 1, 0, 0);
379
break;
380
case SDL_BUTTON_MIDDLE:
381
on_mouse_press_callback(0, 0, 1, 0);
382
break;
383
}
384
break;
385
386
case SDL_MOUSEBUTTONUP:
387
switch (event.button.button) {
388
case SDL_BUTTON_LEFT:
389
on_mouse_press_callback(-1, 0, 0, 0);
390
break;
391
case SDL_BUTTON_RIGHT:
392
on_mouse_press_callback(0, -1, 0, 0);
393
break;
394
case SDL_BUTTON_MIDDLE:
395
on_mouse_press_callback(0, 0, -1, 0);
396
break;
397
}
398
break;
399
400
case SDL_MOUSEWHEEL:
401
on_mouse_press_callback(0, 0, 0, clamp_s8(event.wheel.y));
402
break;
403
}
404
}
405
}
406
407
static bool gfx_sdl_start_frame(void) {
408
return true;
409
}
410
411
static void gfx_sdl_swap_buffers_begin(void) {
412
SDL_GL_SwapWindow(wnd);
413
}
414
415
static void gfx_sdl_swap_buffers_end(void) {
416
// Get the current time in ticks
417
Uint64 now_ticks = SDL_GetPerformanceCounter();
418
419
// If we're ahead of schedule, wait until it's time for the next frame
420
if (now_ticks < next_frame_ticks) {
421
const Uint64 ticks_remaining = next_frame_ticks - now_ticks;
422
const Uint64 ms_remaining = (ticks_remaining * 1000) / perf_freq;
423
424
// Sleep for most of the remaining time
425
if (ms_remaining >= 2) {
426
Uint32 coarse_ms = (Uint32)(ms_remaining - 1);
427
SDL_Delay(coarse_ms);
428
}
429
430
// Busy-wait for the rest
431
while (true) {
432
now_ticks = SDL_GetPerformanceCounter();
433
if (now_ticks >= next_frame_ticks)
434
break;
435
SDL_Delay(0);
436
}
437
}
438
439
// If we're running behind, then FUCK!!!
440
now_ticks = SDL_GetPerformanceCounter();
441
if (now_ticks >= next_frame_ticks) {
442
next_frame_ticks = now_ticks + min_ticks_per_frame;
443
}
444
}
445
446
static double gfx_sdl_get_time(void) {
447
return (double)SDL_GetTicks64() / 1000.0;
448
}
449
450
struct GfxWindowManagerAPI gfx_sdl = {
451
gfx_sdl_init,
452
gfx_sdl_set_keyboard_callbacks,
453
gfx_sdl_set_fullscreen_changed_callback,
454
gfx_sdl_set_fullscreen,
455
gfx_sdl_set_vsync,
456
gfx_sdl_set_window_size,
457
gfx_sdl_set_monitor,
458
gfx_sdl_get_num_display_modes,
459
gfx_sdl_main_loop,
460
gfx_sdl_get_dimensions,
461
gfx_sdl_handle_events,
462
gfx_sdl_start_frame,
463
gfx_sdl_swap_buffers_begin,
464
gfx_sdl_swap_buffers_end,
465
gfx_sdl_get_time
466
};
467
468