Mercurial > repos > blastem
comparison render_sdl.c @ 1648:b7ecd0d6a77b mame_interp
Merge from default
author | Michael Pavone <pavone@retrodev.com> |
---|---|
date | Tue, 25 Dec 2018 11:12:26 -0800 |
parents | c6b2c0f8cc61 |
children | b500e971da75 |
comparison
equal
deleted
inserted
replaced
1509:36732f5c2281 | 1648:b7ecd0d6a77b |
---|---|
6 #include <stdlib.h> | 6 #include <stdlib.h> |
7 #include <stdio.h> | 7 #include <stdio.h> |
8 #include <string.h> | 8 #include <string.h> |
9 #include <math.h> | 9 #include <math.h> |
10 #include "render.h" | 10 #include "render.h" |
11 #include "render_sdl.h" | |
11 #include "blastem.h" | 12 #include "blastem.h" |
12 #include "genesis.h" | 13 #include "genesis.h" |
13 #include "io.h" | 14 #include "bindings.h" |
14 #include "util.h" | 15 #include "util.h" |
15 #include "ppm.h" | 16 #include "ppm.h" |
17 #include "png.h" | |
18 #include "config.h" | |
19 #include "controller_info.h" | |
16 | 20 |
17 #ifndef DISABLE_OPENGL | 21 #ifndef DISABLE_OPENGL |
18 #include <GL/glew.h> | 22 #include <GL/glew.h> |
19 #endif | 23 #endif |
20 | 24 |
21 #define MAX_EVENT_POLL_PER_FRAME 2 | 25 #define MAX_EVENT_POLL_PER_FRAME 2 |
22 | 26 |
23 static SDL_Window *main_window; | 27 static SDL_Window *main_window; |
28 static SDL_Window **extra_windows; | |
24 static SDL_Renderer *main_renderer; | 29 static SDL_Renderer *main_renderer; |
30 static SDL_Renderer **extra_renderers; | |
25 static SDL_Texture **sdl_textures; | 31 static SDL_Texture **sdl_textures; |
26 static uint8_t num_textures; | 32 static uint8_t num_textures; |
27 static SDL_Rect main_clip; | 33 static SDL_Rect main_clip; |
28 static SDL_GLContext *main_context; | 34 static SDL_GLContext *main_context; |
29 | 35 |
32 static uint8_t render_gl = 1; | 38 static uint8_t render_gl = 1; |
33 static uint8_t scanlines = 0; | 39 static uint8_t scanlines = 0; |
34 | 40 |
35 static uint32_t last_frame = 0; | 41 static uint32_t last_frame = 0; |
36 | 42 |
37 static int16_t * current_psg = NULL; | |
38 static int16_t * current_ym = NULL; | |
39 | |
40 static uint32_t buffer_samples, sample_rate; | 43 static uint32_t buffer_samples, sample_rate; |
41 static uint32_t missing_count; | 44 static uint32_t missing_count; |
42 | 45 |
43 static SDL_mutex * audio_mutex; | 46 static SDL_mutex * audio_mutex; |
44 static SDL_cond * audio_ready; | 47 static SDL_cond * audio_ready; |
45 static SDL_cond * psg_cond; | |
46 static SDL_cond * ym_cond; | |
47 static uint8_t quitting = 0; | 48 static uint8_t quitting = 0; |
48 static uint8_t ym_enabled = 1; | 49 |
50 struct audio_source { | |
51 SDL_cond *cond; | |
52 int16_t *front; | |
53 int16_t *back; | |
54 double dt; | |
55 uint64_t buffer_fraction; | |
56 uint64_t buffer_inc; | |
57 uint32_t buffer_pos; | |
58 uint32_t read_start; | |
59 uint32_t read_end; | |
60 uint32_t lowpass_alpha; | |
61 uint32_t mask; | |
62 int16_t last_left; | |
63 int16_t last_right; | |
64 uint8_t num_channels; | |
65 uint8_t front_populated; | |
66 }; | |
67 | |
68 static audio_source *audio_sources[8]; | |
69 static audio_source *inactive_audio_sources[8]; | |
70 static uint8_t num_audio_sources; | |
71 static uint8_t num_inactive_audio_sources; | |
72 static uint8_t sync_to_audio; | |
73 static uint32_t min_buffered; | |
74 | |
75 typedef int32_t (*mix_func)(audio_source *audio, void *vstream, int len); | |
76 | |
77 static int32_t mix_s16(audio_source *audio, void *vstream, int len) | |
78 { | |
79 int samples = len/(sizeof(int16_t)*2); | |
80 int16_t *stream = vstream; | |
81 int16_t *end = stream + 2*samples; | |
82 int16_t *src = audio->front; | |
83 uint32_t i = audio->read_start; | |
84 uint32_t i_end = audio->read_end; | |
85 int16_t *cur = stream; | |
86 if (audio->num_channels == 1) { | |
87 while (cur < end && i != i_end) | |
88 { | |
89 *(cur++) += src[i]; | |
90 *(cur++) += src[i++]; | |
91 i &= audio->mask; | |
92 } | |
93 } else { | |
94 while (cur < end && i != i_end) | |
95 { | |
96 *(cur++) += src[i++]; | |
97 *(cur++) += src[i++]; | |
98 i &= audio->mask; | |
99 } | |
100 } | |
101 | |
102 if (cur != end) { | |
103 printf("Underflow of %d samples, read_start: %d, read_end: %d, mask: %X\n", (int)(end-cur)/2, audio->read_start, audio->read_end, audio->mask); | |
104 } | |
105 if (!sync_to_audio) { | |
106 audio->read_start = i; | |
107 } | |
108 if (cur != end) { | |
109 //printf("Underflow of %d samples, read_start: %d, read_end: %d, mask: %X\n", (int)(end-cur)/2, audio->read_start, audio->read_end, audio->mask); | |
110 return (cur-end)/2; | |
111 } else { | |
112 return ((i_end - i) & audio->mask) / audio->num_channels; | |
113 } | |
114 } | |
115 | |
116 static int32_t mix_f32(audio_source *audio, void *vstream, int len) | |
117 { | |
118 int samples = len/(sizeof(float)*2); | |
119 float *stream = vstream; | |
120 float *end = stream + 2*samples; | |
121 int16_t *src = audio->front; | |
122 uint32_t i = audio->read_start; | |
123 uint32_t i_end = audio->read_end; | |
124 float *cur = stream; | |
125 if (audio->num_channels == 1) { | |
126 while (cur < end && i != i_end) | |
127 { | |
128 *(cur++) += ((float)src[i]) / 0x7FFF; | |
129 *(cur++) += ((float)src[i++]) / 0x7FFF; | |
130 i &= audio->mask; | |
131 } | |
132 } else { | |
133 while(cur < end && i != i_end) | |
134 { | |
135 *(cur++) += ((float)src[i++]) / 0x7FFF; | |
136 *(cur++) += ((float)src[i++]) / 0x7FFF; | |
137 i &= audio->mask; | |
138 } | |
139 } | |
140 if (!sync_to_audio) { | |
141 audio->read_start = i; | |
142 } | |
143 if (cur != end) { | |
144 printf("Underflow of %d samples, read_start: %d, read_end: %d, mask: %X\n", (int)(end-cur)/2, audio->read_start, audio->read_end, audio->mask); | |
145 return (cur-end)/2; | |
146 } else { | |
147 return ((i_end - i) & audio->mask) / audio->num_channels; | |
148 } | |
149 } | |
150 | |
151 static int32_t mix_null(audio_source *audio, void *vstream, int len) | |
152 { | |
153 return 0; | |
154 } | |
155 | |
156 static mix_func mix; | |
49 | 157 |
50 static void audio_callback(void * userdata, uint8_t *byte_stream, int len) | 158 static void audio_callback(void * userdata, uint8_t *byte_stream, int len) |
51 { | 159 { |
52 //puts("audio_callback"); | 160 uint8_t num_populated; |
53 int16_t * stream = (int16_t *)byte_stream; | 161 memset(byte_stream, 0, len); |
54 int samples = len/(sizeof(int16_t)*2); | |
55 int16_t * psg_buf, * ym_buf; | |
56 uint8_t local_quit; | |
57 SDL_LockMutex(audio_mutex); | 162 SDL_LockMutex(audio_mutex); |
58 psg_buf = NULL; | |
59 ym_buf = NULL; | |
60 do { | 163 do { |
61 if (!psg_buf) { | 164 num_populated = 0; |
62 psg_buf = current_psg; | 165 for (uint8_t i = 0; i < num_audio_sources; i++) |
63 current_psg = NULL; | 166 { |
64 SDL_CondSignal(psg_cond); | 167 if (audio_sources[i]->front_populated) { |
65 } | 168 num_populated++; |
66 if (ym_enabled && !ym_buf) { | 169 } |
67 ym_buf = current_ym; | 170 } |
68 current_ym = NULL; | 171 if (!quitting && num_populated < num_audio_sources) { |
69 SDL_CondSignal(ym_cond); | 172 fflush(stdout); |
70 } | |
71 if (!quitting && (!psg_buf || (ym_enabled && !ym_buf))) { | |
72 SDL_CondWait(audio_ready, audio_mutex); | 173 SDL_CondWait(audio_ready, audio_mutex); |
73 } | 174 } |
74 } while(!quitting && (!psg_buf || (ym_enabled && !ym_buf))); | 175 } while(!quitting && num_populated < num_audio_sources); |
75 | 176 if (!quitting) { |
76 local_quit = quitting; | 177 for (uint8_t i = 0; i < num_audio_sources; i++) |
178 { | |
179 mix(audio_sources[i], byte_stream, len); | |
180 audio_sources[i]->front_populated = 0; | |
181 SDL_CondSignal(audio_sources[i]->cond); | |
182 } | |
183 } | |
77 SDL_UnlockMutex(audio_mutex); | 184 SDL_UnlockMutex(audio_mutex); |
78 if (!local_quit) { | 185 } |
79 if (ym_enabled) { | 186 |
80 for (int i = 0; i < samples; i++) | 187 #define NO_LAST_BUFFERED -2000000000 |
81 { | 188 static int32_t last_buffered = NO_LAST_BUFFERED; |
82 *(stream++) = psg_buf[i] + *(ym_buf++); | 189 static float average_change; |
83 *(stream++) = psg_buf[i] + *(ym_buf++); | 190 #define BUFFER_FRAMES_THRESHOLD 6 |
84 } | 191 #define BASE_MAX_ADJUST 0.0125 |
85 } else { | 192 static float max_adjust; |
86 for (int i = 0; i < samples; i++) | 193 static int32_t cur_min_buffered; |
87 { | 194 static uint32_t min_remaining_buffer; |
88 *(stream++) = psg_buf[i]; | 195 static void audio_callback_drc(void *userData, uint8_t *byte_stream, int len) |
89 *(stream++) = psg_buf[i]; | 196 { |
90 } | 197 memset(byte_stream, 0, len); |
91 } | 198 if (cur_min_buffered < 0) { |
92 } | 199 //underflow last frame, but main thread hasn't gotten a chance to call SDL_PauseAudio yet |
93 } | 200 return; |
94 | 201 } |
95 void render_disable_ym() | 202 cur_min_buffered = 0x7FFFFFFF; |
96 { | 203 min_remaining_buffer = 0xFFFFFFFF; |
97 ym_enabled = 0; | 204 for (uint8_t i = 0; i < num_audio_sources; i++) |
98 } | 205 { |
99 | 206 |
100 void render_enable_ym() | 207 int32_t buffered = mix(audio_sources[i], byte_stream, len); |
101 { | 208 cur_min_buffered = buffered < cur_min_buffered ? buffered : cur_min_buffered; |
102 ym_enabled = 1; | 209 uint32_t remaining = (audio_sources[i]->mask + 1)/audio_sources[i]->num_channels - buffered; |
210 min_remaining_buffer = remaining < min_remaining_buffer ? remaining : min_remaining_buffer; | |
211 } | |
212 } | |
213 | |
214 static void lock_audio() | |
215 { | |
216 if (sync_to_audio) { | |
217 SDL_LockMutex(audio_mutex); | |
218 } else { | |
219 SDL_LockAudio(); | |
220 } | |
221 } | |
222 | |
223 static void unlock_audio() | |
224 { | |
225 if (sync_to_audio) { | |
226 SDL_UnlockMutex(audio_mutex); | |
227 } else { | |
228 SDL_UnlockAudio(); | |
229 } | |
103 } | 230 } |
104 | 231 |
105 static void render_close_audio() | 232 static void render_close_audio() |
106 { | 233 { |
107 SDL_LockMutex(audio_mutex); | 234 SDL_LockMutex(audio_mutex); |
108 quitting = 1; | 235 quitting = 1; |
109 SDL_CondSignal(audio_ready); | 236 SDL_CondSignal(audio_ready); |
110 SDL_UnlockMutex(audio_mutex); | 237 SDL_UnlockMutex(audio_mutex); |
111 SDL_CloseAudio(); | 238 SDL_CloseAudio(); |
239 } | |
240 | |
241 #define BUFFER_INC_RES 0x40000000UL | |
242 | |
243 void render_audio_adjust_clock(audio_source *src, uint64_t master_clock, uint64_t sample_divider) | |
244 { | |
245 src->buffer_inc = ((BUFFER_INC_RES * (uint64_t)sample_rate) / master_clock) * sample_divider; | |
246 } | |
247 | |
248 audio_source *render_audio_source(uint64_t master_clock, uint64_t sample_divider, uint8_t channels) | |
249 { | |
250 audio_source *ret = NULL; | |
251 uint32_t alloc_size = sync_to_audio ? channels * buffer_samples : nearest_pow2(min_buffered * 4 * channels); | |
252 lock_audio(); | |
253 if (num_audio_sources < 8) { | |
254 ret = malloc(sizeof(audio_source)); | |
255 ret->back = malloc(alloc_size * sizeof(int16_t)); | |
256 ret->front = sync_to_audio ? malloc(alloc_size * sizeof(int16_t)) : ret->back; | |
257 ret->front_populated = 0; | |
258 ret->cond = SDL_CreateCond(); | |
259 ret->num_channels = channels; | |
260 audio_sources[num_audio_sources++] = ret; | |
261 } | |
262 unlock_audio(); | |
263 if (!ret) { | |
264 fatal_error("Too many audio sources!"); | |
265 } else { | |
266 render_audio_adjust_clock(ret, master_clock, sample_divider); | |
267 double lowpass_cutoff = get_lowpass_cutoff(config); | |
268 double rc = (1.0 / lowpass_cutoff) / (2.0 * M_PI); | |
269 ret->dt = 1.0 / ((double)master_clock / (double)(sample_divider)); | |
270 double alpha = ret->dt / (ret->dt + rc); | |
271 ret->lowpass_alpha = (int32_t)(((double)0x10000) * alpha); | |
272 ret->buffer_pos = 0; | |
273 ret->buffer_fraction = 0; | |
274 ret->last_left = ret->last_right = 0; | |
275 ret->read_start = 0; | |
276 ret->read_end = sync_to_audio ? buffer_samples * channels : 0; | |
277 ret->mask = sync_to_audio ? 0xFFFFFFFF : alloc_size-1; | |
278 } | |
279 if (sync_to_audio && SDL_GetAudioStatus() == SDL_AUDIO_PAUSED) { | |
280 SDL_PauseAudio(0); | |
281 } | |
282 return ret; | |
283 } | |
284 | |
285 void render_pause_source(audio_source *src) | |
286 { | |
287 uint8_t need_pause = 0; | |
288 lock_audio(); | |
289 for (uint8_t i = 0; i < num_audio_sources; i++) | |
290 { | |
291 if (audio_sources[i] == src) { | |
292 audio_sources[i] = audio_sources[--num_audio_sources]; | |
293 if (sync_to_audio) { | |
294 SDL_CondSignal(audio_ready); | |
295 } | |
296 break; | |
297 } | |
298 } | |
299 if (!num_audio_sources) { | |
300 need_pause = 1; | |
301 } | |
302 unlock_audio(); | |
303 if (need_pause) { | |
304 SDL_PauseAudio(1); | |
305 } | |
306 inactive_audio_sources[num_inactive_audio_sources++] = src; | |
307 } | |
308 | |
309 void render_resume_source(audio_source *src) | |
310 { | |
311 lock_audio(); | |
312 if (num_audio_sources < 8) { | |
313 audio_sources[num_audio_sources++] = src; | |
314 } | |
315 unlock_audio(); | |
316 for (uint8_t i = 0; i < num_inactive_audio_sources; i++) | |
317 { | |
318 if (inactive_audio_sources[i] == src) { | |
319 inactive_audio_sources[i] = inactive_audio_sources[--num_inactive_audio_sources]; | |
320 } | |
321 } | |
322 if (sync_to_audio) { | |
323 SDL_PauseAudio(0); | |
324 } | |
325 } | |
326 | |
327 void render_free_source(audio_source *src) | |
328 { | |
329 render_pause_source(src); | |
330 | |
331 free(src->front); | |
332 if (sync_to_audio) { | |
333 free(src->back); | |
334 SDL_DestroyCond(src->cond); | |
335 } | |
336 free(src); | |
337 } | |
338 static uint32_t sync_samples; | |
339 static void do_audio_ready(audio_source *src) | |
340 { | |
341 if (sync_to_audio) { | |
342 SDL_LockMutex(audio_mutex); | |
343 while (src->front_populated) { | |
344 SDL_CondWait(src->cond, audio_mutex); | |
345 } | |
346 int16_t *tmp = src->front; | |
347 src->front = src->back; | |
348 src->back = tmp; | |
349 src->front_populated = 1; | |
350 src->buffer_pos = 0; | |
351 SDL_CondSignal(audio_ready); | |
352 SDL_UnlockMutex(audio_mutex); | |
353 } else { | |
354 uint32_t num_buffered; | |
355 SDL_LockAudio(); | |
356 src->read_end = src->buffer_pos; | |
357 num_buffered = ((src->read_end - src->read_start) & src->mask) / src->num_channels; | |
358 SDL_UnlockAudio(); | |
359 if (num_buffered >= min_buffered && SDL_GetAudioStatus() == SDL_AUDIO_PAUSED) { | |
360 SDL_PauseAudio(0); | |
361 } | |
362 } | |
363 } | |
364 | |
365 static int16_t lowpass_sample(audio_source *src, int16_t last, int16_t current) | |
366 { | |
367 int32_t tmp = current * src->lowpass_alpha + last * (0x10000 - src->lowpass_alpha); | |
368 current = tmp >> 16; | |
369 return current; | |
370 } | |
371 | |
372 static void interp_sample(audio_source *src, int16_t last, int16_t current) | |
373 { | |
374 int64_t tmp = last * ((src->buffer_fraction << 16) / src->buffer_inc); | |
375 tmp += current * (0x10000 - ((src->buffer_fraction << 16) / src->buffer_inc)); | |
376 src->back[src->buffer_pos++] = tmp >> 16; | |
377 } | |
378 | |
379 void render_put_mono_sample(audio_source *src, int16_t value) | |
380 { | |
381 value = lowpass_sample(src, src->last_left, value); | |
382 src->buffer_fraction += src->buffer_inc; | |
383 uint32_t base = sync_to_audio ? 0 : src->read_end; | |
384 while (src->buffer_fraction > BUFFER_INC_RES) | |
385 { | |
386 src->buffer_fraction -= BUFFER_INC_RES; | |
387 interp_sample(src, src->last_left, value); | |
388 | |
389 if (((src->buffer_pos - base) & src->mask) >= sync_samples) { | |
390 do_audio_ready(src); | |
391 } | |
392 src->buffer_pos &= src->mask; | |
393 } | |
394 src->last_left = value; | |
395 } | |
396 | |
397 void render_put_stereo_sample(audio_source *src, int16_t left, int16_t right) | |
398 { | |
399 left = lowpass_sample(src, src->last_left, left); | |
400 right = lowpass_sample(src, src->last_right, right); | |
401 src->buffer_fraction += src->buffer_inc; | |
402 uint32_t base = sync_to_audio ? 0 : src->read_end; | |
403 while (src->buffer_fraction > BUFFER_INC_RES) | |
404 { | |
405 src->buffer_fraction -= BUFFER_INC_RES; | |
406 | |
407 interp_sample(src, src->last_left, left); | |
408 interp_sample(src, src->last_right, right); | |
409 | |
410 if (((src->buffer_pos - base) & src->mask)/2 >= sync_samples) { | |
411 do_audio_ready(src); | |
412 } | |
413 src->buffer_pos &= src->mask; | |
414 } | |
415 src->last_left = left; | |
416 src->last_right = right; | |
112 } | 417 } |
113 | 418 |
114 static SDL_Joystick * joysticks[MAX_JOYSTICKS]; | 419 static SDL_Joystick * joysticks[MAX_JOYSTICKS]; |
115 static int joystick_sdl_index[MAX_JOYSTICKS]; | 420 static int joystick_sdl_index[MAX_JOYSTICKS]; |
116 | 421 |
237 un_textures[1] = glGetUniformLocation(program, "textures[1]"); | 542 un_textures[1] = glGetUniformLocation(program, "textures[1]"); |
238 un_width = glGetUniformLocation(program, "width"); | 543 un_width = glGetUniformLocation(program, "width"); |
239 un_height = glGetUniformLocation(program, "height"); | 544 un_height = glGetUniformLocation(program, "height"); |
240 at_pos = glGetAttribLocation(program, "pos"); | 545 at_pos = glGetAttribLocation(program, "pos"); |
241 } | 546 } |
242 #endif | 547 |
243 | 548 static void gl_teardown() |
549 { | |
550 glDeleteProgram(program); | |
551 glDeleteShader(vshader); | |
552 glDeleteShader(fshader); | |
553 glDeleteBuffers(2, buffers); | |
554 glDeleteTextures(3, textures); | |
555 } | |
556 #endif | |
557 | |
558 static uint8_t texture_init; | |
244 static void render_alloc_surfaces() | 559 static void render_alloc_surfaces() |
245 { | 560 { |
246 static uint8_t texture_init; | |
247 | |
248 if (texture_init) { | 561 if (texture_init) { |
249 return; | 562 return; |
250 } | 563 } |
251 sdl_textures= malloc(sizeof(SDL_Texture *) * 2); | 564 sdl_textures= malloc(sizeof(SDL_Texture *) * 2); |
252 num_textures = 2; | 565 num_textures = 2; |
265 #ifndef DISABLE_OPENGL | 578 #ifndef DISABLE_OPENGL |
266 } | 579 } |
267 #endif | 580 #endif |
268 } | 581 } |
269 | 582 |
270 static char * caption = NULL; | 583 static void free_surfaces(void) |
271 static char * fps_caption = NULL; | 584 { |
272 | |
273 static void render_quit() | |
274 { | |
275 render_close_audio(); | |
276 for (int i = 0; i < num_textures; i++) | 585 for (int i = 0; i < num_textures; i++) |
277 { | 586 { |
278 if (sdl_textures[i]) { | 587 if (sdl_textures[i]) { |
279 SDL_DestroyTexture(sdl_textures[i]); | 588 SDL_DestroyTexture(sdl_textures[i]); |
280 } | 589 } |
281 } | 590 } |
591 free(sdl_textures); | |
592 sdl_textures = NULL; | |
593 texture_init = 0; | |
594 } | |
595 | |
596 static char * caption = NULL; | |
597 static char * fps_caption = NULL; | |
598 | |
599 static void render_quit() | |
600 { | |
601 render_close_audio(); | |
602 free_surfaces(); | |
603 #ifndef DISABLE_OPENGL | |
604 if (render_gl) { | |
605 gl_teardown(); | |
606 SDL_GL_DeleteContext(main_context); | |
607 } | |
608 #endif | |
282 } | 609 } |
283 | 610 |
284 static float config_aspect() | 611 static float config_aspect() |
285 { | 612 { |
286 static float aspect = 0.0f; | 613 static float aspect = 0.0f; |
336 } | 663 } |
337 #endif | 664 #endif |
338 } | 665 } |
339 } | 666 } |
340 | 667 |
341 static uint32_t overscan_top[NUM_VID_STD] = {2, 21}; | 668 static ui_render_fun on_context_destroyed, on_context_created; |
342 static uint32_t overscan_bot[NUM_VID_STD] = {1, 17}; | 669 void render_set_gl_context_handlers(ui_render_fun destroy, ui_render_fun create) |
343 static uint32_t overscan_left[NUM_VID_STD] = {13, 13}; | 670 { |
344 static uint32_t overscan_right[NUM_VID_STD] = {14, 14}; | 671 on_context_destroyed = destroy; |
345 static vid_std video_standard = VID_NTSC; | 672 on_context_created = create; |
346 static char *vid_std_names[NUM_VID_STD] = {"ntsc", "pal"}; | |
347 void render_init(int width, int height, char * title, uint8_t fullscreen) | |
348 { | |
349 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0) { | |
350 fatal_error("Unable to init SDL: %s\n", SDL_GetError()); | |
351 } | |
352 atexit(SDL_Quit); | |
353 if (height <= 0) { | |
354 float aspect = config_aspect() > 0.0f ? config_aspect() : 4.0f/3.0f; | |
355 height = ((float)width / aspect) + 0.5f; | |
356 } | |
357 printf("width: %d, height: %d\n", width, height); | |
358 windowed_width = width; | |
359 windowed_height = height; | |
360 | |
361 uint32_t flags = SDL_WINDOW_RESIZABLE; | |
362 | |
363 if (fullscreen) { | |
364 flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; | |
365 SDL_DisplayMode mode; | |
366 //TODO: Multiple monitor support | |
367 SDL_GetCurrentDisplayMode(0, &mode); | |
368 //the SDL2 migration guide suggests setting width and height to 0 when using SDL_WINDOW_FULLSCREEN_DESKTOP | |
369 //but that doesn't seem to work right when using OpenGL, at least on Linux anyway | |
370 width = mode.w; | |
371 height = mode.h; | |
372 } | |
373 main_width = width; | |
374 main_height = height; | |
375 is_fullscreen = fullscreen; | |
376 | |
377 render_gl = 0; | |
378 tern_val def = {.ptrval = "off"}; | |
379 char *vsync = tern_find_path_default(config, "video\0vsync\0", def, TVAL_PTR).ptrval; | |
380 | |
381 tern_node *video = tern_find_node(config, "video"); | |
382 if (video) | |
383 { | |
384 for (int i = 0; i < NUM_VID_STD; i++) | |
385 { | |
386 tern_node *std_settings = tern_find_node(video, vid_std_names[i]); | |
387 if (std_settings) { | |
388 char *val = tern_find_path_default(std_settings, "overscan\0top\0", (tern_val){.ptrval = NULL}, TVAL_PTR).ptrval; | |
389 if (val) { | |
390 overscan_top[i] = atoi(val); | |
391 } | |
392 val = tern_find_path_default(std_settings, "overscan\0bottom\0", (tern_val){.ptrval = NULL}, TVAL_PTR).ptrval; | |
393 if (val) { | |
394 overscan_bot[i] = atoi(val); | |
395 } | |
396 val = tern_find_path_default(std_settings, "overscan\0left\0", (tern_val){.ptrval = NULL}, TVAL_PTR).ptrval; | |
397 if (val) { | |
398 overscan_left[i] = atoi(val); | |
399 } | |
400 val = tern_find_path_default(std_settings, "overscan\0right\0", (tern_val){.ptrval = NULL}, TVAL_PTR).ptrval; | |
401 if (val) { | |
402 overscan_right[i] = atoi(val); | |
403 } | |
404 } | |
405 } | |
406 } | |
407 | |
408 #ifndef DISABLE_OPENGL | |
409 char *gl_enabled_str = tern_find_path_default(config, "video\0gl\0", def, TVAL_PTR).ptrval; | |
410 uint8_t gl_enabled = strcmp(gl_enabled_str, "off") != 0; | |
411 if (gl_enabled) | |
412 { | |
413 flags |= SDL_WINDOW_OPENGL; | |
414 SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5); | |
415 SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5); | |
416 SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5); | |
417 SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0); | |
418 SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); | |
419 } | |
420 #endif | |
421 main_window = SDL_CreateWindow(title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, flags); | |
422 if (!main_window) { | |
423 fatal_error("Unable to create SDL window: %s\n", SDL_GetError()); | |
424 } | |
425 #ifndef DISABLE_OPENGL | |
426 if (gl_enabled) | |
427 { | |
428 main_context = SDL_GL_CreateContext(main_window); | |
429 GLenum res = glewInit(); | |
430 if (res != GLEW_OK) { | |
431 warning("Initialization of GLEW failed with code %d\n", res); | |
432 } | |
433 | |
434 if (res == GLEW_OK && GLEW_VERSION_2_0) { | |
435 render_gl = 1; | |
436 if (!strcmp("tear", vsync)) { | |
437 if (SDL_GL_SetSwapInterval(-1) < 0) { | |
438 warning("late tear is not available (%s), using normal vsync\n", SDL_GetError()); | |
439 vsync = "on"; | |
440 } else { | |
441 vsync = NULL; | |
442 } | |
443 } | |
444 if (vsync) { | |
445 if (SDL_GL_SetSwapInterval(!strcmp("on", vsync)) < 0) { | |
446 warning("Failed to set vsync to %s: %s\n", vsync, SDL_GetError()); | |
447 } | |
448 } | |
449 } else { | |
450 warning("OpenGL 2.0 is unavailable, falling back to SDL2 renderer\n"); | |
451 } | |
452 } | |
453 if (!render_gl) { | |
454 #endif | |
455 flags = SDL_RENDERER_ACCELERATED; | |
456 if (!strcmp("on", vsync) || !strcmp("tear", vsync)) { | |
457 flags |= SDL_RENDERER_PRESENTVSYNC; | |
458 } | |
459 main_renderer = SDL_CreateRenderer(main_window, -1, flags); | |
460 | |
461 if (!main_renderer) { | |
462 fatal_error("unable to create SDL renderer: %s\n", SDL_GetError()); | |
463 } | |
464 main_clip.x = main_clip.y = 0; | |
465 main_clip.w = width; | |
466 main_clip.h = height; | |
467 #ifndef DISABLE_OPENGL | |
468 } | |
469 #endif | |
470 | |
471 SDL_GetWindowSize(main_window, &main_width, &main_height); | |
472 printf("Window created with size: %d x %d\n", main_width, main_height); | |
473 update_aspect(); | |
474 render_alloc_surfaces(); | |
475 def.ptrval = "off"; | |
476 scanlines = !strcmp(tern_find_path_default(config, "video\0scanlines\0", def, TVAL_PTR).ptrval, "on"); | |
477 | |
478 caption = title; | |
479 | |
480 audio_mutex = SDL_CreateMutex(); | |
481 psg_cond = SDL_CreateCond(); | |
482 ym_cond = SDL_CreateCond(); | |
483 audio_ready = SDL_CreateCond(); | |
484 | |
485 SDL_AudioSpec desired, actual; | |
486 char * rate_str = tern_find_path(config, "audio\0rate\0", TVAL_PTR).ptrval; | |
487 int rate = rate_str ? atoi(rate_str) : 0; | |
488 if (!rate) { | |
489 rate = 48000; | |
490 } | |
491 desired.freq = rate; | |
492 desired.format = AUDIO_S16SYS; | |
493 desired.channels = 2; | |
494 char * samples_str = tern_find_path(config, "audio\0buffer\0", TVAL_PTR).ptrval; | |
495 int samples = samples_str ? atoi(samples_str) : 0; | |
496 if (!samples) { | |
497 samples = 512; | |
498 } | |
499 printf("config says: %d\n", samples); | |
500 desired.samples = samples*2; | |
501 desired.callback = audio_callback; | |
502 desired.userdata = NULL; | |
503 | |
504 if (SDL_OpenAudio(&desired, &actual) < 0) { | |
505 fatal_error("Unable to open SDL audio: %s\n", SDL_GetError()); | |
506 } | |
507 buffer_samples = actual.samples; | |
508 sample_rate = actual.freq; | |
509 printf("Initialized audio at frequency %d with a %d sample buffer\n", actual.freq, actual.samples); | |
510 SDL_PauseAudio(0); | |
511 | |
512 uint32_t db_size; | |
513 char *db_data = read_bundled_file("gamecontrollerdb.txt", &db_size); | |
514 if (db_data) { | |
515 int added = SDL_GameControllerAddMappingsFromRW(SDL_RWFromMem(db_data, db_size), 1); | |
516 free(db_data); | |
517 printf("Added %d game controller mappings from gamecontrollerdb.txt\n", added); | |
518 } | |
519 | |
520 SDL_JoystickEventState(SDL_ENABLE); | |
521 | |
522 atexit(render_quit); | |
523 } | |
524 | |
525 void render_set_video_standard(vid_std std) | |
526 { | |
527 video_standard = std; | |
528 } | |
529 | |
530 void render_update_caption(char *title) | |
531 { | |
532 caption = title; | |
533 free(fps_caption); | |
534 fps_caption = NULL; | |
535 } | |
536 | |
537 static char *screenshot_path; | |
538 void render_save_screenshot(char *path) | |
539 { | |
540 if (screenshot_path) { | |
541 free(screenshot_path); | |
542 } | |
543 screenshot_path = path; | |
544 } | |
545 | |
546 uint32_t *locked_pixels; | |
547 uint32_t locked_pitch; | |
548 uint32_t *render_get_framebuffer(uint8_t which, int *pitch) | |
549 { | |
550 #ifndef DISABLE_OPENGL | |
551 if (render_gl && which <= FRAMEBUFFER_EVEN) { | |
552 *pitch = LINEBUF_SIZE * sizeof(uint32_t); | |
553 return texture_buf; | |
554 } else { | |
555 #endif | |
556 if (which >= num_textures) { | |
557 warning("Request for invalid framebuffer number %d\n", which); | |
558 return NULL; | |
559 } | |
560 void *pixels; | |
561 if (SDL_LockTexture(sdl_textures[which], NULL, &pixels, pitch) < 0) { | |
562 warning("Failed to lock texture: %s\n", SDL_GetError()); | |
563 return NULL; | |
564 } | |
565 static uint8_t last; | |
566 if (which <= FRAMEBUFFER_EVEN) { | |
567 locked_pixels = pixels; | |
568 if (which == FRAMEBUFFER_EVEN) { | |
569 pixels += *pitch; | |
570 } | |
571 locked_pitch = *pitch; | |
572 if (which != last) { | |
573 *pitch *= 2; | |
574 } | |
575 last = which; | |
576 } | |
577 return pixels; | |
578 #ifndef DISABLE_OPENGL | |
579 } | |
580 #endif | |
581 } | |
582 | |
583 uint8_t events_processed; | |
584 #ifdef __ANDROID__ | |
585 #define FPS_INTERVAL 10000 | |
586 #else | |
587 #define FPS_INTERVAL 1000 | |
588 #endif | |
589 | |
590 static uint32_t last_width; | |
591 void render_framebuffer_updated(uint8_t which, int width) | |
592 { | |
593 static uint8_t last; | |
594 last_width = width; | |
595 uint32_t height = which <= FRAMEBUFFER_EVEN | |
596 ? (video_standard == VID_NTSC ? 243 : 294) - (overscan_top[video_standard] + overscan_bot[video_standard]) | |
597 : 240; | |
598 FILE *screenshot_file = NULL; | |
599 uint32_t shot_height, shot_width; | |
600 if (screenshot_path && which == FRAMEBUFFER_ODD) { | |
601 screenshot_file = fopen(screenshot_path, "wb"); | |
602 if (screenshot_file) { | |
603 info_message("Saving screenshot to %s\n", screenshot_path); | |
604 } else { | |
605 warning("Failed to open screenshot file %s for writing\n", screenshot_path); | |
606 } | |
607 free(screenshot_path); | |
608 screenshot_path = NULL; | |
609 shot_height = video_standard == VID_NTSC ? 243 : 294; | |
610 shot_width = width; | |
611 } | |
612 width -= overscan_left[video_standard] + overscan_right[video_standard]; | |
613 #ifndef DISABLE_OPENGL | |
614 if (render_gl && which <= FRAMEBUFFER_EVEN) { | |
615 glBindTexture(GL_TEXTURE_2D, textures[which]); | |
616 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, LINEBUF_SIZE, height, GL_BGRA, GL_UNSIGNED_BYTE, texture_buf + overscan_left[video_standard] + LINEBUF_SIZE * overscan_top[video_standard]); | |
617 | |
618 glClearColor(0.0f, 0.0f, 0.0f, 1.0f); | |
619 glClear(GL_COLOR_BUFFER_BIT); | |
620 | |
621 glUseProgram(program); | |
622 glActiveTexture(GL_TEXTURE0); | |
623 glBindTexture(GL_TEXTURE_2D, textures[0]); | |
624 glUniform1i(un_textures[0], 0); | |
625 | |
626 glActiveTexture(GL_TEXTURE1); | |
627 glBindTexture(GL_TEXTURE_2D, textures[last != which ? 1 : scanlines ? 2 : 0]); | |
628 glUniform1i(un_textures[1], 1); | |
629 | |
630 glUniform1f(un_width, width); | |
631 glUniform1f(un_height, height); | |
632 | |
633 glBindBuffer(GL_ARRAY_BUFFER, buffers[0]); | |
634 glVertexAttribPointer(at_pos, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat[2]), (void *)0); | |
635 glEnableVertexAttribArray(at_pos); | |
636 | |
637 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[1]); | |
638 glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_SHORT, (void *)0); | |
639 | |
640 glDisableVertexAttribArray(at_pos); | |
641 | |
642 SDL_GL_SwapWindow(main_window); | |
643 | |
644 if (screenshot_file) { | |
645 //properly supporting interlaced modes here is non-trivial, so only save the odd field for now | |
646 save_ppm(screenshot_file, texture_buf, shot_width, shot_height, LINEBUF_SIZE*sizeof(uint32_t)); | |
647 } | |
648 } else { | |
649 #endif | |
650 if (which <= FRAMEBUFFER_EVEN && last != which) { | |
651 uint8_t *cur_dst = (uint8_t *)locked_pixels; | |
652 uint8_t *cur_saved = (uint8_t *)texture_buf; | |
653 uint32_t dst_off = which == FRAMEBUFFER_EVEN ? 0 : locked_pitch; | |
654 uint32_t src_off = which == FRAMEBUFFER_EVEN ? locked_pitch : 0; | |
655 for (int i = 0; i < height; ++i) | |
656 { | |
657 //copy saved line from other field | |
658 memcpy(cur_dst + dst_off, cur_saved, locked_pitch); | |
659 //save line from this field to buffer for next frame | |
660 memcpy(cur_saved, cur_dst + src_off, locked_pitch); | |
661 cur_dst += locked_pitch * 2; | |
662 cur_saved += locked_pitch; | |
663 } | |
664 height = 480; | |
665 } | |
666 if (screenshot_file) { | |
667 uint32_t shot_pitch = locked_pitch; | |
668 if (which == FRAMEBUFFER_EVEN) { | |
669 shot_height *= 2; | |
670 } else { | |
671 shot_pitch *= 2; | |
672 } | |
673 save_ppm(screenshot_file, locked_pixels, shot_width, shot_height, shot_pitch); | |
674 } | |
675 SDL_UnlockTexture(sdl_textures[which]); | |
676 SDL_Rect src_clip = { | |
677 .x = overscan_left[video_standard], | |
678 .y = overscan_top[video_standard], | |
679 .w = width, | |
680 .h = height | |
681 }; | |
682 SDL_SetRenderDrawColor(main_renderer, 0, 0, 0, 255); | |
683 SDL_RenderClear(main_renderer); | |
684 SDL_RenderCopy(main_renderer, sdl_textures[which], &src_clip, &main_clip); | |
685 SDL_RenderPresent(main_renderer); | |
686 #ifndef DISABLE_OPENGL | |
687 } | |
688 #endif | |
689 if (screenshot_file) { | |
690 fclose(screenshot_file); | |
691 } | |
692 if (which <= FRAMEBUFFER_EVEN) { | |
693 last = which; | |
694 static uint32_t frame_counter, start; | |
695 frame_counter++; | |
696 last_frame= SDL_GetTicks(); | |
697 if ((last_frame - start) > FPS_INTERVAL) { | |
698 if (start && (last_frame-start)) { | |
699 #ifdef __ANDROID__ | |
700 info_message("%s - %.1f fps", caption, ((float)frame_counter) / (((float)(last_frame-start)) / 1000.0)); | |
701 #else | |
702 if (!fps_caption) { | |
703 fps_caption = malloc(strlen(caption) + strlen(" - 100000000.1 fps") + 1); | |
704 } | |
705 sprintf(fps_caption, "%s - %.1f fps", caption, ((float)frame_counter) / (((float)(last_frame-start)) / 1000.0)); | |
706 SDL_SetWindowTitle(main_window, fps_caption); | |
707 #endif | |
708 } | |
709 start = last_frame; | |
710 frame_counter = 0; | |
711 } | |
712 } | |
713 if (!events_processed) { | |
714 process_events(); | |
715 } | |
716 events_processed = 0; | |
717 } | |
718 | |
719 uint32_t render_emulated_width() | |
720 { | |
721 return last_width - overscan_left[video_standard] - overscan_right[video_standard]; | |
722 } | |
723 | |
724 uint32_t render_emulated_height() | |
725 { | |
726 return (video_standard == VID_NTSC ? 243 : 294) - overscan_top[video_standard] - overscan_bot[video_standard]; | |
727 } | |
728 | |
729 uint32_t render_overscan_left() | |
730 { | |
731 return overscan_left[video_standard]; | |
732 } | |
733 | |
734 uint32_t render_overscan_top() | |
735 { | |
736 return overscan_top[video_standard]; | |
737 } | |
738 | |
739 void render_wait_quit(vdp_context * context) | |
740 { | |
741 SDL_Event event; | |
742 while(SDL_WaitEvent(&event)) { | |
743 switch (event.type) { | |
744 case SDL_QUIT: | |
745 return; | |
746 } | |
747 } | |
748 } | |
749 | |
750 static int find_joystick_index(SDL_JoystickID instanceID) | |
751 { | |
752 for (int i = 0; i < MAX_JOYSTICKS; i++) { | |
753 if (joysticks[i] && SDL_JoystickInstanceID(joysticks[i]) == instanceID) { | |
754 return i; | |
755 } | |
756 } | |
757 return -1; | |
758 } | |
759 | |
760 static int lowest_unused_joystick_index() | |
761 { | |
762 for (int i = 0; i < MAX_JOYSTICKS; i++) { | |
763 if (!joysticks[i]) { | |
764 return i; | |
765 } | |
766 } | |
767 return -1; | |
768 } | |
769 | |
770 int32_t render_translate_input_name(int32_t controller, char *name, uint8_t is_axis) | |
771 { | |
772 static tern_node *button_lookup, *axis_lookup; | |
773 if (controller > MAX_JOYSTICKS || !joysticks[controller]) { | |
774 return RENDER_NOT_PLUGGED_IN; | |
775 } | |
776 | |
777 if (!SDL_IsGameController(joystick_sdl_index[controller])) { | |
778 return RENDER_NOT_MAPPED; | |
779 } | |
780 SDL_GameController *control = SDL_GameControllerOpen(joystick_sdl_index[controller]); | |
781 if (!control) { | |
782 warning("Failed to open game controller %d: %s\n", controller, SDL_GetError()); | |
783 return RENDER_NOT_PLUGGED_IN; | |
784 } | |
785 | |
786 SDL_GameControllerButtonBind cbind; | |
787 if (is_axis) { | |
788 if (!axis_lookup) { | |
789 for (int i = SDL_CONTROLLER_AXIS_LEFTX; i < SDL_CONTROLLER_AXIS_MAX; i++) | |
790 { | |
791 axis_lookup = tern_insert_int(axis_lookup, SDL_GameControllerGetStringForAxis(i), i); | |
792 } | |
793 //alternative Playstation-style names | |
794 axis_lookup = tern_insert_int(axis_lookup, "l2", SDL_CONTROLLER_AXIS_TRIGGERLEFT); | |
795 axis_lookup = tern_insert_int(axis_lookup, "r2", SDL_CONTROLLER_AXIS_TRIGGERRIGHT); | |
796 } | |
797 intptr_t sdl_axis = tern_find_int(axis_lookup, name, SDL_CONTROLLER_AXIS_INVALID); | |
798 if (sdl_axis == SDL_CONTROLLER_AXIS_INVALID) { | |
799 SDL_GameControllerClose(control); | |
800 return RENDER_INVALID_NAME; | |
801 } | |
802 cbind = SDL_GameControllerGetBindForAxis(control, sdl_axis); | |
803 } else { | |
804 if (!button_lookup) { | |
805 for (int i = SDL_CONTROLLER_BUTTON_A; i < SDL_CONTROLLER_BUTTON_MAX; i++) | |
806 { | |
807 button_lookup = tern_insert_int(button_lookup, SDL_GameControllerGetStringForButton(i), i); | |
808 } | |
809 //alternative Playstation-style names | |
810 button_lookup = tern_insert_int(button_lookup, "cross", SDL_CONTROLLER_BUTTON_A); | |
811 button_lookup = tern_insert_int(button_lookup, "circle", SDL_CONTROLLER_BUTTON_B); | |
812 button_lookup = tern_insert_int(button_lookup, "square", SDL_CONTROLLER_BUTTON_X); | |
813 button_lookup = tern_insert_int(button_lookup, "triangle", SDL_CONTROLLER_BUTTON_Y); | |
814 button_lookup = tern_insert_int(button_lookup, "share", SDL_CONTROLLER_BUTTON_BACK); | |
815 button_lookup = tern_insert_int(button_lookup, "select", SDL_CONTROLLER_BUTTON_BACK); | |
816 button_lookup = tern_insert_int(button_lookup, "options", SDL_CONTROLLER_BUTTON_START); | |
817 button_lookup = tern_insert_int(button_lookup, "l1", SDL_CONTROLLER_BUTTON_LEFTSHOULDER); | |
818 button_lookup = tern_insert_int(button_lookup, "r1", SDL_CONTROLLER_BUTTON_RIGHTSHOULDER); | |
819 button_lookup = tern_insert_int(button_lookup, "l3", SDL_CONTROLLER_BUTTON_LEFTSTICK); | |
820 button_lookup = tern_insert_int(button_lookup, "r3", SDL_CONTROLLER_BUTTON_RIGHTSTICK); | |
821 } | |
822 intptr_t sdl_button = tern_find_int(button_lookup, name, SDL_CONTROLLER_BUTTON_INVALID); | |
823 if (sdl_button == SDL_CONTROLLER_BUTTON_INVALID) { | |
824 SDL_GameControllerClose(control); | |
825 return RENDER_INVALID_NAME; | |
826 } | |
827 cbind = SDL_GameControllerGetBindForButton(control, sdl_button); | |
828 } | |
829 SDL_GameControllerClose(control); | |
830 switch (cbind.bindType) | |
831 { | |
832 case SDL_CONTROLLER_BINDTYPE_BUTTON: | |
833 return cbind.value.button; | |
834 case SDL_CONTROLLER_BINDTYPE_AXIS: | |
835 return RENDER_AXIS_BIT | cbind.value.axis; | |
836 case SDL_CONTROLLER_BINDTYPE_HAT: | |
837 return RENDER_DPAD_BIT | (cbind.value.hat.hat << 4) | cbind.value.hat.hat_mask; | |
838 } | |
839 return RENDER_NOT_MAPPED; | |
840 } | |
841 | |
842 int32_t render_dpad_part(int32_t input) | |
843 { | |
844 return input >> 4 & 0xFFFFFF; | |
845 } | |
846 | |
847 uint8_t render_direction_part(int32_t input) | |
848 { | |
849 return input & 0xF; | |
850 } | |
851 | |
852 int32_t render_axis_part(int32_t input) | |
853 { | |
854 return input & 0xFFFFFFF; | |
855 } | 673 } |
856 | 674 |
857 static uint8_t scancode_map[SDL_NUM_SCANCODES] = { | 675 static uint8_t scancode_map[SDL_NUM_SCANCODES] = { |
858 [SDL_SCANCODE_A] = 0x1C, | 676 [SDL_SCANCODE_A] = 0x1C, |
859 [SDL_SCANCODE_B] = 0x32, | 677 [SDL_SCANCODE_B] = 0x32, |
962 void render_set_drag_drop_handler(drop_handler handler) | 780 void render_set_drag_drop_handler(drop_handler handler) |
963 { | 781 { |
964 drag_drop_handler = handler; | 782 drag_drop_handler = handler; |
965 } | 783 } |
966 | 784 |
785 static event_handler custom_event_handler; | |
786 void render_set_event_handler(event_handler handler) | |
787 { | |
788 custom_event_handler = handler; | |
789 } | |
790 | |
791 static int find_joystick_index(SDL_JoystickID instanceID) | |
792 { | |
793 for (int i = 0; i < MAX_JOYSTICKS; i++) { | |
794 if (joysticks[i] && SDL_JoystickInstanceID(joysticks[i]) == instanceID) { | |
795 return i; | |
796 } | |
797 } | |
798 return -1; | |
799 } | |
800 | |
801 static int lowest_unused_joystick_index() | |
802 { | |
803 for (int i = 0; i < MAX_JOYSTICKS; i++) { | |
804 if (!joysticks[i]) { | |
805 return i; | |
806 } | |
807 } | |
808 return -1; | |
809 } | |
810 | |
811 SDL_Joystick *render_get_joystick(int index) | |
812 { | |
813 if (index >= MAX_JOYSTICKS) { | |
814 return NULL; | |
815 } | |
816 return joysticks[index]; | |
817 } | |
818 | |
819 char* render_joystick_type_id(int index) | |
820 { | |
821 SDL_Joystick *stick = render_get_joystick(index); | |
822 if (!stick) { | |
823 return NULL; | |
824 } | |
825 char *guid_string = malloc(33); | |
826 SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(stick), guid_string, 33); | |
827 return guid_string; | |
828 } | |
829 | |
830 SDL_GameController *render_get_controller(int index) | |
831 { | |
832 if (index >= MAX_JOYSTICKS) { | |
833 return NULL; | |
834 } | |
835 return SDL_GameControllerOpen(joystick_sdl_index[index]); | |
836 } | |
837 | |
838 static uint32_t overscan_top[NUM_VID_STD] = {2, 21}; | |
839 static uint32_t overscan_bot[NUM_VID_STD] = {1, 17}; | |
840 static uint32_t overscan_left[NUM_VID_STD] = {13, 13}; | |
841 static uint32_t overscan_right[NUM_VID_STD] = {14, 14}; | |
842 static vid_std video_standard = VID_NTSC; | |
843 | |
967 static int32_t handle_event(SDL_Event *event) | 844 static int32_t handle_event(SDL_Event *event) |
968 { | 845 { |
846 if (custom_event_handler) { | |
847 custom_event_handler(event); | |
848 } | |
969 switch (event->type) { | 849 switch (event->type) { |
970 case SDL_KEYDOWN: | 850 case SDL_KEYDOWN: |
971 handle_keydown(event->key.keysym.sym, scancode_map[event->key.keysym.scancode]); | 851 handle_keydown(event->key.keysym.sym, scancode_map[event->key.keysym.scancode]); |
972 break; | 852 break; |
973 case SDL_KEYUP: | 853 case SDL_KEYUP: |
978 break; | 858 break; |
979 case SDL_JOYBUTTONUP: | 859 case SDL_JOYBUTTONUP: |
980 handle_joyup(find_joystick_index(event->jbutton.which), event->jbutton.button); | 860 handle_joyup(find_joystick_index(event->jbutton.which), event->jbutton.button); |
981 break; | 861 break; |
982 case SDL_JOYHATMOTION: | 862 case SDL_JOYHATMOTION: |
983 handle_joy_dpad(find_joystick_index(event->jbutton.which), event->jhat.hat, event->jhat.value); | 863 handle_joy_dpad(find_joystick_index(event->jhat.which), event->jhat.hat, event->jhat.value); |
984 break; | 864 break; |
985 case SDL_JOYAXISMOTION: | 865 case SDL_JOYAXISMOTION: |
986 handle_joy_axis(find_joystick_index(event->jaxis.which), event->jaxis.axis, event->jaxis.value); | 866 handle_joy_axis(find_joystick_index(event->jaxis.which), event->jaxis.axis, event->jaxis.value); |
987 break; | 867 break; |
988 case SDL_JOYDEVICEADDED: | 868 case SDL_JOYDEVICEADDED: |
1026 main_width = event->window.data1; | 906 main_width = event->window.data1; |
1027 main_height = event->window.data2; | 907 main_height = event->window.data2; |
1028 update_aspect(); | 908 update_aspect(); |
1029 #ifndef DISABLE_OPENGL | 909 #ifndef DISABLE_OPENGL |
1030 if (render_gl) { | 910 if (render_gl) { |
911 if (on_context_destroyed) { | |
912 on_context_destroyed(); | |
913 } | |
914 gl_teardown(); | |
1031 SDL_GL_DeleteContext(main_context); | 915 SDL_GL_DeleteContext(main_context); |
1032 main_context = SDL_GL_CreateContext(main_window); | 916 main_context = SDL_GL_CreateContext(main_window); |
1033 gl_setup(); | 917 gl_setup(); |
918 if (on_context_created) { | |
919 on_context_created(); | |
920 } | |
1034 } | 921 } |
1035 #endif | 922 #endif |
1036 break; | 923 break; |
1037 } | 924 } |
1038 break; | 925 break; |
1056 { | 943 { |
1057 handle_event(&event); | 944 handle_event(&event); |
1058 } | 945 } |
1059 } | 946 } |
1060 | 947 |
948 static char *vid_std_names[NUM_VID_STD] = {"ntsc", "pal"}; | |
949 static int display_hz; | |
950 static int source_hz; | |
951 static int source_frame; | |
952 static int source_frame_count; | |
953 static int frame_repeat[60]; | |
954 | |
955 static void init_audio() | |
956 { | |
957 SDL_AudioSpec desired, actual; | |
958 char * rate_str = tern_find_path(config, "audio\0rate\0", TVAL_PTR).ptrval; | |
959 int rate = rate_str ? atoi(rate_str) : 0; | |
960 if (!rate) { | |
961 rate = 48000; | |
962 } | |
963 desired.freq = rate; | |
964 desired.format = AUDIO_S16SYS; | |
965 desired.channels = 2; | |
966 char * samples_str = tern_find_path(config, "audio\0buffer\0", TVAL_PTR).ptrval; | |
967 int samples = samples_str ? atoi(samples_str) : 0; | |
968 if (!samples) { | |
969 samples = 512; | |
970 } | |
971 printf("config says: %d\n", samples); | |
972 desired.samples = samples*2; | |
973 desired.callback = sync_to_audio ? audio_callback : audio_callback_drc; | |
974 desired.userdata = NULL; | |
975 | |
976 if (SDL_OpenAudio(&desired, &actual) < 0) { | |
977 fatal_error("Unable to open SDL audio: %s\n", SDL_GetError()); | |
978 } | |
979 buffer_samples = actual.samples; | |
980 sample_rate = actual.freq; | |
981 printf("Initialized audio at frequency %d with a %d sample buffer, ", actual.freq, actual.samples); | |
982 if (actual.format == AUDIO_S16SYS) { | |
983 puts("signed 16-bit int format"); | |
984 mix = mix_s16; | |
985 } else if (actual.format == AUDIO_F32SYS) { | |
986 puts("32-bit float format"); | |
987 mix = mix_f32; | |
988 } else { | |
989 printf("unsupported format %X\n", actual.format); | |
990 warning("Unsupported audio sample format: %X\n", actual.format); | |
991 mix = mix_null; | |
992 } | |
993 } | |
994 | |
995 void window_setup(void) | |
996 { | |
997 uint32_t flags = SDL_WINDOW_RESIZABLE; | |
998 if (is_fullscreen) { | |
999 flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; | |
1000 } | |
1001 | |
1002 tern_val def = {.ptrval = "video"}; | |
1003 char *sync_src = tern_find_path_default(config, "system\0sync_source\0", def, TVAL_PTR).ptrval; | |
1004 sync_to_audio = !strcmp(sync_src, "audio"); | |
1005 | |
1006 const char *vsync; | |
1007 if (sync_to_audio) { | |
1008 def.ptrval = "off"; | |
1009 vsync = tern_find_path_default(config, "video\0vsync\0", def, TVAL_PTR).ptrval; | |
1010 } else { | |
1011 vsync = "on"; | |
1012 } | |
1013 | |
1014 tern_node *video = tern_find_node(config, "video"); | |
1015 if (video) | |
1016 { | |
1017 for (int i = 0; i < NUM_VID_STD; i++) | |
1018 { | |
1019 tern_node *std_settings = tern_find_node(video, vid_std_names[i]); | |
1020 if (std_settings) { | |
1021 char *val = tern_find_path_default(std_settings, "overscan\0top\0", (tern_val){.ptrval = NULL}, TVAL_PTR).ptrval; | |
1022 if (val) { | |
1023 overscan_top[i] = atoi(val); | |
1024 } | |
1025 val = tern_find_path_default(std_settings, "overscan\0bottom\0", (tern_val){.ptrval = NULL}, TVAL_PTR).ptrval; | |
1026 if (val) { | |
1027 overscan_bot[i] = atoi(val); | |
1028 } | |
1029 val = tern_find_path_default(std_settings, "overscan\0left\0", (tern_val){.ptrval = NULL}, TVAL_PTR).ptrval; | |
1030 if (val) { | |
1031 overscan_left[i] = atoi(val); | |
1032 } | |
1033 val = tern_find_path_default(std_settings, "overscan\0right\0", (tern_val){.ptrval = NULL}, TVAL_PTR).ptrval; | |
1034 if (val) { | |
1035 overscan_right[i] = atoi(val); | |
1036 } | |
1037 } | |
1038 } | |
1039 } | |
1040 render_gl = 0; | |
1041 | |
1042 #ifndef DISABLE_OPENGL | |
1043 char *gl_enabled_str = tern_find_path_default(config, "video\0gl\0", def, TVAL_PTR).ptrval; | |
1044 uint8_t gl_enabled = strcmp(gl_enabled_str, "off") != 0; | |
1045 if (gl_enabled) | |
1046 { | |
1047 flags |= SDL_WINDOW_OPENGL; | |
1048 SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5); | |
1049 SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5); | |
1050 SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5); | |
1051 SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0); | |
1052 SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); | |
1053 } | |
1054 #endif | |
1055 main_window = SDL_CreateWindow(caption, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, main_width, main_height, flags); | |
1056 if (!main_window) { | |
1057 fatal_error("Unable to create SDL window: %s\n", SDL_GetError()); | |
1058 } | |
1059 #ifndef DISABLE_OPENGL | |
1060 if (gl_enabled) | |
1061 { | |
1062 main_context = SDL_GL_CreateContext(main_window); | |
1063 GLenum res = glewInit(); | |
1064 if (res != GLEW_OK) { | |
1065 warning("Initialization of GLEW failed with code %d\n", res); | |
1066 } | |
1067 | |
1068 if (res == GLEW_OK && GLEW_VERSION_2_0) { | |
1069 render_gl = 1; | |
1070 if (!strcmp("tear", vsync)) { | |
1071 if (SDL_GL_SetSwapInterval(-1) < 0) { | |
1072 warning("late tear is not available (%s), using normal vsync\n", SDL_GetError()); | |
1073 vsync = "on"; | |
1074 } else { | |
1075 vsync = NULL; | |
1076 } | |
1077 } | |
1078 if (vsync) { | |
1079 if (SDL_GL_SetSwapInterval(!strcmp("on", vsync)) < 0) { | |
1080 warning("Failed to set vsync to %s: %s\n", vsync, SDL_GetError()); | |
1081 } | |
1082 } | |
1083 } else { | |
1084 warning("OpenGL 2.0 is unavailable, falling back to SDL2 renderer\n"); | |
1085 } | |
1086 } | |
1087 if (!render_gl) { | |
1088 #endif | |
1089 flags = SDL_RENDERER_ACCELERATED; | |
1090 if (!strcmp("on", vsync) || !strcmp("tear", vsync)) { | |
1091 flags |= SDL_RENDERER_PRESENTVSYNC; | |
1092 } | |
1093 main_renderer = SDL_CreateRenderer(main_window, -1, flags); | |
1094 | |
1095 if (!main_renderer) { | |
1096 fatal_error("unable to create SDL renderer: %s\n", SDL_GetError()); | |
1097 } | |
1098 main_clip.x = main_clip.y = 0; | |
1099 main_clip.w = main_width; | |
1100 main_clip.h = main_height; | |
1101 #ifndef DISABLE_OPENGL | |
1102 } | |
1103 #endif | |
1104 | |
1105 SDL_GetWindowSize(main_window, &main_width, &main_height); | |
1106 printf("Window created with size: %d x %d\n", main_width, main_height); | |
1107 update_aspect(); | |
1108 render_alloc_surfaces(); | |
1109 def.ptrval = "off"; | |
1110 scanlines = !strcmp(tern_find_path_default(config, "video\0scanlines\0", def, TVAL_PTR).ptrval, "on"); | |
1111 } | |
1112 | |
1113 void render_init(int width, int height, char * title, uint8_t fullscreen) | |
1114 { | |
1115 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0) { | |
1116 fatal_error("Unable to init SDL: %s\n", SDL_GetError()); | |
1117 } | |
1118 atexit(SDL_Quit); | |
1119 if (height <= 0) { | |
1120 float aspect = config_aspect() > 0.0f ? config_aspect() : 4.0f/3.0f; | |
1121 height = ((float)width / aspect) + 0.5f; | |
1122 } | |
1123 printf("width: %d, height: %d\n", width, height); | |
1124 windowed_width = width; | |
1125 windowed_height = height; | |
1126 | |
1127 SDL_DisplayMode mode; | |
1128 //TODO: Explicit multiple monitor support | |
1129 SDL_GetCurrentDisplayMode(0, &mode); | |
1130 display_hz = mode.refresh_rate; | |
1131 | |
1132 if (fullscreen) { | |
1133 //the SDL2 migration guide suggests setting width and height to 0 when using SDL_WINDOW_FULLSCREEN_DESKTOP | |
1134 //but that doesn't seem to work right when using OpenGL, at least on Linux anyway | |
1135 width = mode.w; | |
1136 height = mode.h; | |
1137 } | |
1138 main_width = width; | |
1139 main_height = height; | |
1140 is_fullscreen = fullscreen; | |
1141 | |
1142 caption = title; | |
1143 | |
1144 window_setup(); | |
1145 | |
1146 audio_mutex = SDL_CreateMutex(); | |
1147 audio_ready = SDL_CreateCond(); | |
1148 | |
1149 init_audio(); | |
1150 | |
1151 uint32_t db_size; | |
1152 char *db_data = read_bundled_file("gamecontrollerdb.txt", &db_size); | |
1153 if (db_data) { | |
1154 int added = SDL_GameControllerAddMappingsFromRW(SDL_RWFromMem(db_data, db_size), 1); | |
1155 free(db_data); | |
1156 printf("Added %d game controller mappings from gamecontrollerdb.txt\n", added); | |
1157 } | |
1158 | |
1159 controller_add_mappings(); | |
1160 | |
1161 SDL_JoystickEventState(SDL_ENABLE); | |
1162 | |
1163 render_set_video_standard(VID_NTSC); | |
1164 | |
1165 atexit(render_quit); | |
1166 } | |
1167 #include<unistd.h> | |
1168 static int in_toggle; | |
1169 static void update_source(audio_source *src, double rc, uint8_t sync_changed) | |
1170 { | |
1171 double alpha = src->dt / (src->dt + rc); | |
1172 int32_t lowpass_alpha = (int32_t)(((double)0x10000) * alpha); | |
1173 src->lowpass_alpha = lowpass_alpha; | |
1174 if (sync_changed) { | |
1175 uint32_t alloc_size = sync_to_audio ? src->num_channels * buffer_samples : nearest_pow2(min_buffered * 4 * src->num_channels); | |
1176 src->back = realloc(src->back, alloc_size * sizeof(int16_t)); | |
1177 if (sync_to_audio) { | |
1178 src->front = malloc(alloc_size * sizeof(int16_t)); | |
1179 } else { | |
1180 free(src->front); | |
1181 src->front = src->back; | |
1182 } | |
1183 src->mask = sync_to_audio ? 0xFFFFFFFF : alloc_size-1; | |
1184 src->read_start = 0; | |
1185 src->read_end = sync_to_audio ? buffer_samples * src->num_channels : 0; | |
1186 src->buffer_pos = 0; | |
1187 } | |
1188 } | |
1189 | |
1190 void render_config_updated(void) | |
1191 { | |
1192 uint8_t old_sync_to_audio = sync_to_audio; | |
1193 | |
1194 free_surfaces(); | |
1195 #ifndef DISABLE_OPENGL | |
1196 if (render_gl) { | |
1197 if (on_context_destroyed) { | |
1198 on_context_destroyed(); | |
1199 } | |
1200 gl_teardown(); | |
1201 SDL_GL_DeleteContext(main_context); | |
1202 } else { | |
1203 #endif | |
1204 SDL_DestroyRenderer(main_renderer); | |
1205 #ifndef DISABLE_OPENGL | |
1206 } | |
1207 #endif | |
1208 in_toggle = 1; | |
1209 SDL_DestroyWindow(main_window); | |
1210 drain_events(); | |
1211 | |
1212 char *config_width = tern_find_path(config, "video\0width\0", TVAL_PTR).ptrval; | |
1213 if (config_width) { | |
1214 windowed_width = atoi(config_width); | |
1215 } | |
1216 char *config_height = tern_find_path(config, "video\0height\0", TVAL_PTR).ptrval; | |
1217 if (config_height) { | |
1218 windowed_height = atoi(config_height); | |
1219 } else { | |
1220 float aspect = config_aspect() > 0.0f ? config_aspect() : 4.0f/3.0f; | |
1221 windowed_height = ((float)windowed_width / aspect) + 0.5f; | |
1222 } | |
1223 char *config_fullscreen = tern_find_path(config, "video\0fullscreen\0", TVAL_PTR).ptrval; | |
1224 is_fullscreen = config_fullscreen && !strcmp("on", config_fullscreen); | |
1225 if (is_fullscreen) { | |
1226 SDL_DisplayMode mode; | |
1227 //TODO: Multiple monitor support | |
1228 SDL_GetCurrentDisplayMode(0, &mode); | |
1229 main_width = mode.w; | |
1230 main_height = mode.h; | |
1231 } else { | |
1232 main_width = windowed_width; | |
1233 main_height = windowed_height; | |
1234 } | |
1235 | |
1236 window_setup(); | |
1237 update_aspect(); | |
1238 #ifndef DISABLE_OPENGL | |
1239 //need to check render_gl again after window_setup as render option could have changed | |
1240 if (render_gl && on_context_created) { | |
1241 on_context_created(); | |
1242 } | |
1243 #endif | |
1244 | |
1245 uint8_t was_paused = SDL_GetAudioStatus() == SDL_AUDIO_PAUSED; | |
1246 render_close_audio(); | |
1247 quitting = 0; | |
1248 init_audio(); | |
1249 render_set_video_standard(video_standard); | |
1250 | |
1251 double lowpass_cutoff = get_lowpass_cutoff(config); | |
1252 double rc = (1.0 / lowpass_cutoff) / (2.0 * M_PI); | |
1253 lock_audio(); | |
1254 for (uint8_t i = 0; i < num_audio_sources; i++) | |
1255 { | |
1256 update_source(audio_sources[i], rc, old_sync_to_audio != sync_to_audio); | |
1257 } | |
1258 unlock_audio(); | |
1259 for (uint8_t i = 0; i < num_inactive_audio_sources; i++) | |
1260 { | |
1261 update_source(inactive_audio_sources[i], rc, old_sync_to_audio != sync_to_audio); | |
1262 } | |
1263 drain_events(); | |
1264 in_toggle = 0; | |
1265 if (!was_paused) { | |
1266 SDL_PauseAudio(0); | |
1267 } | |
1268 } | |
1269 | |
1270 SDL_Window *render_get_window(void) | |
1271 { | |
1272 return main_window; | |
1273 } | |
1274 | |
1275 void render_set_video_standard(vid_std std) | |
1276 { | |
1277 video_standard = std; | |
1278 source_hz = std == VID_PAL ? 50 : 60; | |
1279 uint32_t max_repeat = 0; | |
1280 if (abs(source_hz - display_hz) < 2) { | |
1281 memset(frame_repeat, 0, sizeof(int)*display_hz); | |
1282 } else { | |
1283 int inc = display_hz * 100000 / source_hz; | |
1284 int accum = 0; | |
1285 int dst_frames = 0; | |
1286 for (int src_frame = 0; src_frame < source_hz; src_frame++) | |
1287 { | |
1288 frame_repeat[src_frame] = -1; | |
1289 accum += inc; | |
1290 while (accum > 100000) | |
1291 { | |
1292 accum -= 100000; | |
1293 frame_repeat[src_frame]++; | |
1294 max_repeat = frame_repeat[src_frame] > max_repeat ? frame_repeat[src_frame] : max_repeat; | |
1295 dst_frames++; | |
1296 } | |
1297 } | |
1298 if (dst_frames != display_hz) { | |
1299 frame_repeat[source_hz-1] += display_hz - dst_frames; | |
1300 } | |
1301 } | |
1302 source_frame = 0; | |
1303 source_frame_count = frame_repeat[0]; | |
1304 //sync samples with audio thread approximately every 8 lines | |
1305 sync_samples = sync_to_audio ? buffer_samples : 8 * sample_rate / (source_hz * (VID_PAL ? 313 : 262)); | |
1306 max_repeat++; | |
1307 min_buffered = (((float)max_repeat * (float)sample_rate/(float)source_hz)/* / (float)buffer_samples*/);// + 0.9999; | |
1308 //min_buffered *= buffer_samples; | |
1309 printf("Min samples buffered before audio start: %d\n", min_buffered); | |
1310 max_adjust = BASE_MAX_ADJUST / source_hz; | |
1311 } | |
1312 | |
1313 void render_update_caption(char *title) | |
1314 { | |
1315 caption = title; | |
1316 free(fps_caption); | |
1317 fps_caption = NULL; | |
1318 } | |
1319 | |
1320 static char *screenshot_path; | |
1321 void render_save_screenshot(char *path) | |
1322 { | |
1323 if (screenshot_path) { | |
1324 free(screenshot_path); | |
1325 } | |
1326 screenshot_path = path; | |
1327 } | |
1328 | |
1329 uint8_t render_create_window(char *caption, uint32_t width, uint32_t height) | |
1330 { | |
1331 uint8_t win_idx = 0xFF; | |
1332 for (int i = 0; i < num_textures - FRAMEBUFFER_USER_START; i++) | |
1333 { | |
1334 if (!extra_windows[i]) { | |
1335 win_idx = i; | |
1336 break; | |
1337 } | |
1338 } | |
1339 | |
1340 if (win_idx == 0xFF) { | |
1341 num_textures++; | |
1342 sdl_textures = realloc(sdl_textures, num_textures * sizeof(*sdl_textures)); | |
1343 extra_windows = realloc(extra_windows, (num_textures - FRAMEBUFFER_USER_START) * sizeof(*extra_windows)); | |
1344 extra_renderers = realloc(extra_renderers, (num_textures - FRAMEBUFFER_USER_START) * sizeof(*extra_renderers)); | |
1345 win_idx = num_textures - FRAMEBUFFER_USER_START - 1; | |
1346 } | |
1347 extra_windows[win_idx] = SDL_CreateWindow(caption, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, 0); | |
1348 if (!extra_windows[win_idx]) { | |
1349 goto fail_window; | |
1350 } | |
1351 extra_renderers[win_idx] = SDL_CreateRenderer(extra_windows[win_idx], -1, SDL_RENDERER_ACCELERATED); | |
1352 if (!extra_renderers[win_idx]) { | |
1353 goto fail_renderer; | |
1354 } | |
1355 uint8_t texture_idx = win_idx + FRAMEBUFFER_USER_START; | |
1356 sdl_textures[texture_idx] = SDL_CreateTexture(extra_renderers[win_idx], SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, width, height); | |
1357 if (!sdl_textures[texture_idx]) { | |
1358 goto fail_texture; | |
1359 } | |
1360 return texture_idx; | |
1361 | |
1362 fail_texture: | |
1363 SDL_DestroyRenderer(extra_renderers[win_idx]); | |
1364 fail_renderer: | |
1365 SDL_DestroyWindow(extra_windows[win_idx]); | |
1366 fail_window: | |
1367 num_textures--; | |
1368 return 0; | |
1369 } | |
1370 | |
1371 void render_destroy_window(uint8_t which) | |
1372 { | |
1373 uint8_t win_idx = which - FRAMEBUFFER_USER_START; | |
1374 //Destroying the renderers also frees the textures | |
1375 SDL_DestroyRenderer(extra_renderers[win_idx]); | |
1376 SDL_DestroyWindow(extra_windows[win_idx]); | |
1377 | |
1378 extra_renderers[win_idx] = NULL; | |
1379 extra_windows[win_idx] = NULL; | |
1380 } | |
1381 | |
1382 uint32_t *locked_pixels; | |
1383 uint32_t locked_pitch; | |
1384 uint32_t *render_get_framebuffer(uint8_t which, int *pitch) | |
1385 { | |
1386 #ifndef DISABLE_OPENGL | |
1387 if (render_gl && which <= FRAMEBUFFER_EVEN) { | |
1388 *pitch = LINEBUF_SIZE * sizeof(uint32_t); | |
1389 return texture_buf; | |
1390 } else { | |
1391 #endif | |
1392 if (which >= num_textures) { | |
1393 warning("Request for invalid framebuffer number %d\n", which); | |
1394 return NULL; | |
1395 } | |
1396 void *pixels; | |
1397 if (SDL_LockTexture(sdl_textures[which], NULL, &pixels, pitch) < 0) { | |
1398 warning("Failed to lock texture: %s\n", SDL_GetError()); | |
1399 return NULL; | |
1400 } | |
1401 static uint8_t last; | |
1402 if (which <= FRAMEBUFFER_EVEN) { | |
1403 locked_pixels = pixels; | |
1404 if (which == FRAMEBUFFER_EVEN) { | |
1405 pixels += *pitch; | |
1406 } | |
1407 locked_pitch = *pitch; | |
1408 if (which != last) { | |
1409 *pitch *= 2; | |
1410 } | |
1411 last = which; | |
1412 } | |
1413 return pixels; | |
1414 #ifndef DISABLE_OPENGL | |
1415 } | |
1416 #endif | |
1417 } | |
1418 | |
1419 uint8_t events_processed; | |
1420 #ifdef __ANDROID__ | |
1421 #define FPS_INTERVAL 10000 | |
1422 #else | |
1423 #define FPS_INTERVAL 1000 | |
1424 #endif | |
1425 | |
1426 static uint32_t last_width, last_height; | |
1427 static uint8_t interlaced; | |
1428 void render_framebuffer_updated(uint8_t which, int width) | |
1429 { | |
1430 static uint8_t last; | |
1431 if (!sync_to_audio && which <= FRAMEBUFFER_EVEN && source_frame_count < 0) { | |
1432 source_frame++; | |
1433 if (source_frame >= source_hz) { | |
1434 source_frame = 0; | |
1435 } | |
1436 source_frame_count = frame_repeat[source_frame]; | |
1437 //TODO: Figure out what to do about SDL Render API texture locking | |
1438 return; | |
1439 } | |
1440 | |
1441 last_width = width; | |
1442 uint32_t height = which <= FRAMEBUFFER_EVEN | |
1443 ? (video_standard == VID_NTSC ? 243 : 294) - (overscan_top[video_standard] + overscan_bot[video_standard]) | |
1444 : 240; | |
1445 FILE *screenshot_file = NULL; | |
1446 uint32_t shot_height, shot_width; | |
1447 char *ext; | |
1448 if (screenshot_path && which == FRAMEBUFFER_ODD) { | |
1449 screenshot_file = fopen(screenshot_path, "wb"); | |
1450 if (screenshot_file) { | |
1451 #ifndef DISABLE_ZLIB | |
1452 ext = path_extension(screenshot_path); | |
1453 #endif | |
1454 info_message("Saving screenshot to %s\n", screenshot_path); | |
1455 } else { | |
1456 warning("Failed to open screenshot file %s for writing\n", screenshot_path); | |
1457 } | |
1458 free(screenshot_path); | |
1459 screenshot_path = NULL; | |
1460 shot_height = video_standard == VID_NTSC ? 243 : 294; | |
1461 shot_width = width; | |
1462 } | |
1463 interlaced = last != which; | |
1464 width -= overscan_left[video_standard] + overscan_right[video_standard]; | |
1465 #ifndef DISABLE_OPENGL | |
1466 if (render_gl && which <= FRAMEBUFFER_EVEN) { | |
1467 SDL_GL_MakeCurrent(main_window, main_context); | |
1468 glBindTexture(GL_TEXTURE_2D, textures[which]); | |
1469 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, LINEBUF_SIZE, height, GL_BGRA, GL_UNSIGNED_BYTE, texture_buf + overscan_left[video_standard] + LINEBUF_SIZE * overscan_top[video_standard]); | |
1470 | |
1471 if (screenshot_file) { | |
1472 //properly supporting interlaced modes here is non-trivial, so only save the odd field for now | |
1473 #ifndef DISABLE_ZLIB | |
1474 if (!strcasecmp(ext, "png")) { | |
1475 free(ext); | |
1476 save_png(screenshot_file, texture_buf, shot_width, shot_height, LINEBUF_SIZE*sizeof(uint32_t)); | |
1477 } else { | |
1478 free(ext); | |
1479 #endif | |
1480 save_ppm(screenshot_file, texture_buf, shot_width, shot_height, LINEBUF_SIZE*sizeof(uint32_t)); | |
1481 #ifndef DISABLE_ZLIB | |
1482 } | |
1483 #endif | |
1484 } | |
1485 } else { | |
1486 #endif | |
1487 if (which <= FRAMEBUFFER_EVEN && last != which) { | |
1488 uint8_t *cur_dst = (uint8_t *)locked_pixels; | |
1489 uint8_t *cur_saved = (uint8_t *)texture_buf; | |
1490 uint32_t dst_off = which == FRAMEBUFFER_EVEN ? 0 : locked_pitch; | |
1491 uint32_t src_off = which == FRAMEBUFFER_EVEN ? locked_pitch : 0; | |
1492 for (int i = 0; i < height; ++i) | |
1493 { | |
1494 //copy saved line from other field | |
1495 memcpy(cur_dst + dst_off, cur_saved, locked_pitch); | |
1496 //save line from this field to buffer for next frame | |
1497 memcpy(cur_saved, cur_dst + src_off, locked_pitch); | |
1498 cur_dst += locked_pitch * 2; | |
1499 cur_saved += locked_pitch; | |
1500 } | |
1501 height = 480; | |
1502 } | |
1503 if (screenshot_file) { | |
1504 uint32_t shot_pitch = locked_pitch; | |
1505 if (which == FRAMEBUFFER_EVEN) { | |
1506 shot_height *= 2; | |
1507 } else { | |
1508 shot_pitch *= 2; | |
1509 } | |
1510 #ifndef DISABLE_ZLIB | |
1511 if (!strcasecmp(ext, "png")) { | |
1512 free(ext); | |
1513 save_png(screenshot_file, locked_pixels, shot_width, shot_height, shot_pitch); | |
1514 } else { | |
1515 free(ext); | |
1516 #endif | |
1517 save_ppm(screenshot_file, locked_pixels, shot_width, shot_height, shot_pitch); | |
1518 #ifndef DISABLE_ZLIB | |
1519 } | |
1520 #endif | |
1521 } | |
1522 SDL_UnlockTexture(sdl_textures[which]); | |
1523 #ifndef DISABLE_OPENGL | |
1524 } | |
1525 #endif | |
1526 last_height = height; | |
1527 if (which <= FRAMEBUFFER_EVEN) { | |
1528 render_update_display(); | |
1529 } else { | |
1530 SDL_RenderCopy(extra_renderers[which - FRAMEBUFFER_USER_START], sdl_textures[which], NULL, NULL); | |
1531 SDL_RenderPresent(extra_renderers[which - FRAMEBUFFER_USER_START]); | |
1532 } | |
1533 if (screenshot_file) { | |
1534 fclose(screenshot_file); | |
1535 } | |
1536 if (which <= FRAMEBUFFER_EVEN) { | |
1537 last = which; | |
1538 static uint32_t frame_counter, start; | |
1539 frame_counter++; | |
1540 last_frame= SDL_GetTicks(); | |
1541 if ((last_frame - start) > FPS_INTERVAL) { | |
1542 if (start && (last_frame-start)) { | |
1543 #ifdef __ANDROID__ | |
1544 info_message("%s - %.1f fps", caption, ((float)frame_counter) / (((float)(last_frame-start)) / 1000.0)); | |
1545 #else | |
1546 if (!fps_caption) { | |
1547 fps_caption = malloc(strlen(caption) + strlen(" - 100000000.1 fps") + 1); | |
1548 } | |
1549 sprintf(fps_caption, "%s - %.1f fps", caption, ((float)frame_counter) / (((float)(last_frame-start)) / 1000.0)); | |
1550 SDL_SetWindowTitle(main_window, fps_caption); | |
1551 #endif | |
1552 } | |
1553 start = last_frame; | |
1554 frame_counter = 0; | |
1555 } | |
1556 } | |
1557 if (!sync_to_audio) { | |
1558 int32_t local_cur_min, local_min_remaining; | |
1559 SDL_LockAudio(); | |
1560 if (last_buffered > NO_LAST_BUFFERED) { | |
1561 average_change *= 0.9f; | |
1562 average_change += (cur_min_buffered - last_buffered) * 0.1f; | |
1563 } | |
1564 local_cur_min = cur_min_buffered; | |
1565 local_min_remaining = min_remaining_buffer; | |
1566 last_buffered = cur_min_buffered; | |
1567 SDL_UnlockAudio(); | |
1568 float frames_to_problem; | |
1569 if (average_change < 0) { | |
1570 frames_to_problem = (float)local_cur_min / -average_change; | |
1571 } else { | |
1572 frames_to_problem = (float)local_min_remaining / average_change; | |
1573 } | |
1574 float adjust_ratio = 0.0f; | |
1575 if ( | |
1576 frames_to_problem < BUFFER_FRAMES_THRESHOLD | |
1577 || (average_change < 0 && local_cur_min < 3*min_buffered / 4) | |
1578 || (average_change >0 && local_cur_min > 5 * min_buffered / 4) | |
1579 || cur_min_buffered < 0 | |
1580 ) { | |
1581 | |
1582 if (cur_min_buffered < 0) { | |
1583 adjust_ratio = max_adjust; | |
1584 SDL_PauseAudio(1); | |
1585 last_buffered = NO_LAST_BUFFERED; | |
1586 cur_min_buffered = 0; | |
1587 } else { | |
1588 adjust_ratio = -1.0 * average_change / ((float)sample_rate / (float)source_hz); | |
1589 adjust_ratio /= 2.5 * source_hz; | |
1590 if (fabsf(adjust_ratio) > max_adjust) { | |
1591 adjust_ratio = adjust_ratio > 0 ? max_adjust : -max_adjust; | |
1592 } | |
1593 } | |
1594 } else if (local_cur_min < min_buffered / 2) { | |
1595 adjust_ratio = max_adjust; | |
1596 } | |
1597 if (adjust_ratio != 0.0f) { | |
1598 average_change = 0; | |
1599 for (uint8_t i = 0; i < num_audio_sources; i++) | |
1600 { | |
1601 audio_sources[i]->buffer_inc = ((double)audio_sources[i]->buffer_inc) + ((double)audio_sources[i]->buffer_inc) * adjust_ratio + 0.5; | |
1602 } | |
1603 } | |
1604 while (source_frame_count > 0) | |
1605 { | |
1606 render_update_display(); | |
1607 source_frame_count--; | |
1608 } | |
1609 source_frame++; | |
1610 if (source_frame >= source_hz) { | |
1611 source_frame = 0; | |
1612 } | |
1613 source_frame_count = frame_repeat[source_frame]; | |
1614 } | |
1615 } | |
1616 | |
1617 static ui_render_fun render_ui; | |
1618 void render_set_ui_render_fun(ui_render_fun fun) | |
1619 { | |
1620 render_ui = fun; | |
1621 } | |
1622 | |
1623 void render_update_display() | |
1624 { | |
1625 #ifndef DISABLE_OPENGL | |
1626 if (render_gl) { | |
1627 glClearColor(0.0f, 0.0f, 0.0f, 1.0f); | |
1628 glClear(GL_COLOR_BUFFER_BIT); | |
1629 | |
1630 glUseProgram(program); | |
1631 glActiveTexture(GL_TEXTURE0); | |
1632 glBindTexture(GL_TEXTURE_2D, textures[0]); | |
1633 glUniform1i(un_textures[0], 0); | |
1634 | |
1635 glActiveTexture(GL_TEXTURE1); | |
1636 glBindTexture(GL_TEXTURE_2D, textures[interlaced ? 1 : scanlines ? 2 : 0]); | |
1637 glUniform1i(un_textures[1], 1); | |
1638 | |
1639 glUniform1f(un_width, render_emulated_width()); | |
1640 glUniform1f(un_height, last_height); | |
1641 | |
1642 glBindBuffer(GL_ARRAY_BUFFER, buffers[0]); | |
1643 glVertexAttribPointer(at_pos, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat[2]), (void *)0); | |
1644 glEnableVertexAttribArray(at_pos); | |
1645 | |
1646 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[1]); | |
1647 glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_SHORT, (void *)0); | |
1648 | |
1649 glDisableVertexAttribArray(at_pos); | |
1650 | |
1651 if (render_ui) { | |
1652 render_ui(); | |
1653 } | |
1654 | |
1655 SDL_GL_SwapWindow(main_window); | |
1656 } else { | |
1657 #endif | |
1658 SDL_Rect src_clip = { | |
1659 .x = overscan_left[video_standard], | |
1660 .y = overscan_top[video_standard], | |
1661 .w = render_emulated_width(), | |
1662 .h = last_height | |
1663 }; | |
1664 SDL_SetRenderDrawColor(main_renderer, 0, 0, 0, 255); | |
1665 SDL_RenderClear(main_renderer); | |
1666 SDL_RenderCopy(main_renderer, sdl_textures[FRAMEBUFFER_ODD], &src_clip, &main_clip); | |
1667 if (render_ui) { | |
1668 render_ui(); | |
1669 } | |
1670 SDL_RenderPresent(main_renderer); | |
1671 #ifndef DISABLE_OPENGL | |
1672 } | |
1673 #endif | |
1674 if (!events_processed) { | |
1675 process_events(); | |
1676 } | |
1677 events_processed = 0; | |
1678 } | |
1679 | |
1680 uint32_t render_emulated_width() | |
1681 { | |
1682 return last_width - overscan_left[video_standard] - overscan_right[video_standard]; | |
1683 } | |
1684 | |
1685 uint32_t render_emulated_height() | |
1686 { | |
1687 return (video_standard == VID_NTSC ? 243 : 294) - overscan_top[video_standard] - overscan_bot[video_standard]; | |
1688 } | |
1689 | |
1690 uint32_t render_overscan_left() | |
1691 { | |
1692 return overscan_left[video_standard]; | |
1693 } | |
1694 | |
1695 uint32_t render_overscan_top() | |
1696 { | |
1697 return overscan_top[video_standard]; | |
1698 } | |
1699 | |
1700 void render_wait_quit(vdp_context * context) | |
1701 { | |
1702 SDL_Event event; | |
1703 while(SDL_WaitEvent(&event)) { | |
1704 switch (event.type) { | |
1705 case SDL_QUIT: | |
1706 return; | |
1707 } | |
1708 } | |
1709 } | |
1710 | |
1711 int render_lookup_button(char *name) | |
1712 { | |
1713 static tern_node *button_lookup; | |
1714 if (!button_lookup) { | |
1715 for (int i = SDL_CONTROLLER_BUTTON_A; i < SDL_CONTROLLER_BUTTON_MAX; i++) | |
1716 { | |
1717 button_lookup = tern_insert_int(button_lookup, SDL_GameControllerGetStringForButton(i), i); | |
1718 } | |
1719 //alternative Playstation-style names | |
1720 button_lookup = tern_insert_int(button_lookup, "cross", SDL_CONTROLLER_BUTTON_A); | |
1721 button_lookup = tern_insert_int(button_lookup, "circle", SDL_CONTROLLER_BUTTON_B); | |
1722 button_lookup = tern_insert_int(button_lookup, "square", SDL_CONTROLLER_BUTTON_X); | |
1723 button_lookup = tern_insert_int(button_lookup, "triangle", SDL_CONTROLLER_BUTTON_Y); | |
1724 button_lookup = tern_insert_int(button_lookup, "share", SDL_CONTROLLER_BUTTON_BACK); | |
1725 button_lookup = tern_insert_int(button_lookup, "select", SDL_CONTROLLER_BUTTON_BACK); | |
1726 button_lookup = tern_insert_int(button_lookup, "options", SDL_CONTROLLER_BUTTON_START); | |
1727 button_lookup = tern_insert_int(button_lookup, "l1", SDL_CONTROLLER_BUTTON_LEFTSHOULDER); | |
1728 button_lookup = tern_insert_int(button_lookup, "r1", SDL_CONTROLLER_BUTTON_RIGHTSHOULDER); | |
1729 button_lookup = tern_insert_int(button_lookup, "l3", SDL_CONTROLLER_BUTTON_LEFTSTICK); | |
1730 button_lookup = tern_insert_int(button_lookup, "r3", SDL_CONTROLLER_BUTTON_RIGHTSTICK); | |
1731 } | |
1732 return (int)tern_find_int(button_lookup, name, SDL_CONTROLLER_BUTTON_INVALID); | |
1733 } | |
1734 | |
1735 int render_lookup_axis(char *name) | |
1736 { | |
1737 static tern_node *axis_lookup; | |
1738 if (!axis_lookup) { | |
1739 for (int i = SDL_CONTROLLER_AXIS_LEFTX; i < SDL_CONTROLLER_AXIS_MAX; i++) | |
1740 { | |
1741 axis_lookup = tern_insert_int(axis_lookup, SDL_GameControllerGetStringForAxis(i), i); | |
1742 } | |
1743 //alternative Playstation-style names | |
1744 axis_lookup = tern_insert_int(axis_lookup, "l2", SDL_CONTROLLER_AXIS_TRIGGERLEFT); | |
1745 axis_lookup = tern_insert_int(axis_lookup, "r2", SDL_CONTROLLER_AXIS_TRIGGERRIGHT); | |
1746 } | |
1747 return (int)tern_find_int(axis_lookup, name, SDL_CONTROLLER_AXIS_INVALID); | |
1748 } | |
1749 | |
1750 int32_t render_translate_input_name(int32_t controller, char *name, uint8_t is_axis) | |
1751 { | |
1752 tern_node *button_lookup, *axis_lookup; | |
1753 if (controller > MAX_JOYSTICKS || !joysticks[controller]) { | |
1754 return RENDER_NOT_PLUGGED_IN; | |
1755 } | |
1756 | |
1757 if (!SDL_IsGameController(joystick_sdl_index[controller])) { | |
1758 return RENDER_NOT_MAPPED; | |
1759 } | |
1760 SDL_GameController *control = SDL_GameControllerOpen(joystick_sdl_index[controller]); | |
1761 if (!control) { | |
1762 warning("Failed to open game controller %d: %s\n", controller, SDL_GetError()); | |
1763 return RENDER_NOT_PLUGGED_IN; | |
1764 } | |
1765 | |
1766 SDL_GameControllerButtonBind cbind; | |
1767 if (is_axis) { | |
1768 | |
1769 int sdl_axis = render_lookup_axis(name); | |
1770 if (sdl_axis == SDL_CONTROLLER_AXIS_INVALID) { | |
1771 SDL_GameControllerClose(control); | |
1772 return RENDER_INVALID_NAME; | |
1773 } | |
1774 cbind = SDL_GameControllerGetBindForAxis(control, sdl_axis); | |
1775 } else { | |
1776 int sdl_button = render_lookup_button(name); | |
1777 if (sdl_button == SDL_CONTROLLER_BUTTON_INVALID) { | |
1778 SDL_GameControllerClose(control); | |
1779 return RENDER_INVALID_NAME; | |
1780 } | |
1781 cbind = SDL_GameControllerGetBindForButton(control, sdl_button); | |
1782 } | |
1783 SDL_GameControllerClose(control); | |
1784 switch (cbind.bindType) | |
1785 { | |
1786 case SDL_CONTROLLER_BINDTYPE_BUTTON: | |
1787 return cbind.value.button; | |
1788 case SDL_CONTROLLER_BINDTYPE_AXIS: | |
1789 return RENDER_AXIS_BIT | cbind.value.axis; | |
1790 case SDL_CONTROLLER_BINDTYPE_HAT: | |
1791 return RENDER_DPAD_BIT | (cbind.value.hat.hat << 4) | cbind.value.hat.hat_mask; | |
1792 } | |
1793 return RENDER_NOT_MAPPED; | |
1794 } | |
1795 | |
1796 int32_t render_dpad_part(int32_t input) | |
1797 { | |
1798 return input >> 4 & 0xFFFFFF; | |
1799 } | |
1800 | |
1801 uint8_t render_direction_part(int32_t input) | |
1802 { | |
1803 return input & 0xF; | |
1804 } | |
1805 | |
1806 int32_t render_axis_part(int32_t input) | |
1807 { | |
1808 return input & 0xFFFFFFF; | |
1809 } | |
1810 | |
1061 void process_events() | 1811 void process_events() |
1062 { | 1812 { |
1063 if (events_processed > MAX_EVENT_POLL_PER_FRAME) { | 1813 if (events_processed > MAX_EVENT_POLL_PER_FRAME) { |
1064 return; | 1814 return; |
1065 } | 1815 } |
1068 } | 1818 } |
1069 | 1819 |
1070 #define TOGGLE_MIN_DELAY 250 | 1820 #define TOGGLE_MIN_DELAY 250 |
1071 void render_toggle_fullscreen() | 1821 void render_toggle_fullscreen() |
1072 { | 1822 { |
1073 static int in_toggle; | |
1074 //protect against event processing causing us to attempt to toggle while still toggling | 1823 //protect against event processing causing us to attempt to toggle while still toggling |
1075 if (in_toggle) { | 1824 if (in_toggle) { |
1076 return; | 1825 return; |
1077 } | 1826 } |
1078 in_toggle = 1; | 1827 in_toggle = 1; |
1108 SDL_SetWindowSize(main_window, windowed_width, windowed_height); | 1857 SDL_SetWindowSize(main_window, windowed_width, windowed_height); |
1109 drain_events(); | 1858 drain_events(); |
1110 in_toggle = 0; | 1859 in_toggle = 0; |
1111 } | 1860 } |
1112 | 1861 |
1113 void render_wait_psg(psg_context * context) | |
1114 { | |
1115 SDL_LockMutex(audio_mutex); | |
1116 while (current_psg != NULL) { | |
1117 SDL_CondWait(psg_cond, audio_mutex); | |
1118 } | |
1119 current_psg = context->audio_buffer; | |
1120 SDL_CondSignal(audio_ready); | |
1121 | |
1122 context->audio_buffer = context->back_buffer; | |
1123 context->back_buffer = current_psg; | |
1124 SDL_UnlockMutex(audio_mutex); | |
1125 context->buffer_pos = 0; | |
1126 } | |
1127 | |
1128 void render_wait_ym(ym2612_context * context) | |
1129 { | |
1130 SDL_LockMutex(audio_mutex); | |
1131 while (current_ym != NULL) { | |
1132 SDL_CondWait(ym_cond, audio_mutex); | |
1133 } | |
1134 current_ym = context->audio_buffer; | |
1135 SDL_CondSignal(audio_ready); | |
1136 | |
1137 context->audio_buffer = context->back_buffer; | |
1138 context->back_buffer = current_ym; | |
1139 SDL_UnlockMutex(audio_mutex); | |
1140 context->buffer_pos = 0; | |
1141 } | |
1142 | |
1143 uint32_t render_audio_buffer() | 1862 uint32_t render_audio_buffer() |
1144 { | 1863 { |
1145 return buffer_samples; | 1864 return buffer_samples; |
1146 } | 1865 } |
1147 | 1866 |
1163 void render_infobox(char *title, char *message) | 1882 void render_infobox(char *title, char *message) |
1164 { | 1883 { |
1165 SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, title, message, NULL); | 1884 SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, title, message, NULL); |
1166 } | 1885 } |
1167 | 1886 |
1887 uint32_t render_elapsed_ms(void) | |
1888 { | |
1889 return SDL_GetTicks(); | |
1890 } | |
1891 | |
1892 void render_sleep_ms(uint32_t delay) | |
1893 { | |
1894 return SDL_Delay(delay); | |
1895 } | |
1896 | |
1897 uint8_t render_has_gl(void) | |
1898 { | |
1899 return render_gl; | |
1900 } | |
1901 | |
1902 uint8_t render_get_active_framebuffer(void) | |
1903 { | |
1904 if (SDL_GetWindowFlags(main_window) & SDL_WINDOW_INPUT_FOCUS) { | |
1905 return FRAMEBUFFER_ODD; | |
1906 } | |
1907 for (int i = 0; i < num_textures - 2; i++) | |
1908 { | |
1909 if (extra_windows[i] && (SDL_GetWindowFlags(extra_windows[i]) & SDL_WINDOW_INPUT_FOCUS)) { | |
1910 return FRAMEBUFFER_USER_START + i; | |
1911 } | |
1912 } | |
1913 return 0xFF; | |
1914 } |