Mercurial > repos > blastem
diff render_sdl.c @ 2053:3414a4423de1 segacd
Merge from default
author | Michael Pavone <pavone@retrodev.com> |
---|---|
date | Sat, 15 Jan 2022 13:15:21 -0800 |
parents | 0757da8ee702 |
children | 46ee354f29bd |
line wrap: on
line diff
--- a/render_sdl.c Sat Jan 05 00:58:08 2019 -0800 +++ b/render_sdl.c Sat Jan 15 13:15:21 2022 -0800 @@ -13,6 +13,7 @@ #include "genesis.h" #include "bindings.h" #include "util.h" +#include "paths.h" #include "ppm.h" #include "png.h" #include "config.h" @@ -45,146 +46,56 @@ static uint32_t last_frame = 0; -static uint32_t buffer_samples, sample_rate; -static uint32_t missing_count; - -static SDL_mutex * audio_mutex; -static SDL_cond * audio_ready; +static SDL_mutex *audio_mutex, *frame_mutex, *free_buffer_mutex; +static SDL_cond *audio_ready, *frame_ready; static uint8_t quitting = 0; -struct audio_source { - SDL_cond *cond; - int16_t *front; - int16_t *back; - double dt; - uint64_t buffer_fraction; - uint64_t buffer_inc; - uint32_t buffer_pos; - uint32_t read_start; - uint32_t read_end; - uint32_t lowpass_alpha; - uint32_t mask; - int16_t last_left; - int16_t last_right; - uint8_t num_channels; - uint8_t front_populated; +enum { + SYNC_AUDIO, + SYNC_AUDIO_THREAD, + SYNC_VIDEO, + SYNC_EXTERNAL }; -static audio_source *audio_sources[8]; -static audio_source *inactive_audio_sources[8]; -static uint8_t num_audio_sources; -static uint8_t num_inactive_audio_sources; -static uint8_t sync_to_audio; +static uint8_t sync_src; static uint32_t min_buffered; -typedef int32_t (*mix_func)(audio_source *audio, void *vstream, int len); +uint32_t **frame_buffers; +uint32_t num_buffers; +uint32_t buffer_storage; -static int32_t mix_s16(audio_source *audio, void *vstream, int len) +uint32_t render_min_buffered(void) { - int samples = len/(sizeof(int16_t)*2); - int16_t *stream = vstream; - int16_t *end = stream + 2*samples; - int16_t *src = audio->front; - uint32_t i = audio->read_start; - uint32_t i_end = audio->read_end; - int16_t *cur = stream; - if (audio->num_channels == 1) { - while (cur < end && i != i_end) - { - *(cur++) += src[i]; - *(cur++) += src[i++]; - i &= audio->mask; - } - } else { - while (cur < end && i != i_end) - { - *(cur++) += src[i++]; - *(cur++) += src[i++]; - i &= audio->mask; - } - } - - if (cur != end) { - 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); - } - if (!sync_to_audio) { - audio->read_start = i; - } - if (cur != end) { - //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); - return (cur-end)/2; - } else { - return ((i_end - i) & audio->mask) / audio->num_channels; - } + return min_buffered; } -static int32_t mix_f32(audio_source *audio, void *vstream, int len) +uint8_t render_is_audio_sync(void) { - int samples = len/(sizeof(float)*2); - float *stream = vstream; - float *end = stream + 2*samples; - int16_t *src = audio->front; - uint32_t i = audio->read_start; - uint32_t i_end = audio->read_end; - float *cur = stream; - if (audio->num_channels == 1) { - while (cur < end && i != i_end) - { - *(cur++) += ((float)src[i]) / 0x7FFF; - *(cur++) += ((float)src[i++]) / 0x7FFF; - i &= audio->mask; - } - } else { - while(cur < end && i != i_end) - { - *(cur++) += ((float)src[i++]) / 0x7FFF; - *(cur++) += ((float)src[i++]) / 0x7FFF; - i &= audio->mask; - } - } - if (!sync_to_audio) { - audio->read_start = i; - } - if (cur != end) { - 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); - return (cur-end)/2; - } else { - return ((i_end - i) & audio->mask) / audio->num_channels; - } + return sync_src < SYNC_VIDEO; } -static int32_t mix_null(audio_source *audio, void *vstream, int len) +uint8_t render_should_release_on_exit(void) { - return 0; + return sync_src != SYNC_AUDIO_THREAD; } -static mix_func mix; +void render_buffer_consumed(audio_source *src) +{ + SDL_CondSignal(src->opaque); +} static void audio_callback(void * userdata, uint8_t *byte_stream, int len) { - uint8_t num_populated; - memset(byte_stream, 0, len); SDL_LockMutex(audio_mutex); + uint8_t all_ready; do { - num_populated = 0; - for (uint8_t i = 0; i < num_audio_sources; i++) - { - if (audio_sources[i]->front_populated) { - num_populated++; - } - } - if (!quitting && num_populated < num_audio_sources) { - fflush(stdout); + all_ready = all_sources_ready(); + if (!quitting && !all_ready) { SDL_CondWait(audio_ready, audio_mutex); } - } while(!quitting && num_populated < num_audio_sources); + } while(!quitting && !all_ready); if (!quitting) { - for (uint8_t i = 0; i < num_audio_sources; i++) - { - mix(audio_sources[i], byte_stream, len); - audio_sources[i]->front_populated = 0; - SDL_CondSignal(audio_sources[i]->cond); - } + mix_and_convert(byte_stream, len, NULL); } SDL_UnlockMutex(audio_mutex); } @@ -199,35 +110,33 @@ static uint32_t min_remaining_buffer; static void audio_callback_drc(void *userData, uint8_t *byte_stream, int len) { - memset(byte_stream, 0, len); if (cur_min_buffered < 0) { //underflow last frame, but main thread hasn't gotten a chance to call SDL_PauseAudio yet return; } - cur_min_buffered = 0x7FFFFFFF; - min_remaining_buffer = 0xFFFFFFFF; - for (uint8_t i = 0; i < num_audio_sources; i++) - { - - int32_t buffered = mix(audio_sources[i], byte_stream, len); - cur_min_buffered = buffered < cur_min_buffered ? buffered : cur_min_buffered; - uint32_t remaining = (audio_sources[i]->mask + 1)/audio_sources[i]->num_channels - buffered; - min_remaining_buffer = remaining < min_remaining_buffer ? remaining : min_remaining_buffer; - } + cur_min_buffered = mix_and_convert(byte_stream, len, &min_remaining_buffer); } -static void lock_audio() +static void audio_callback_run_on_audio(void *user_data, uint8_t *byte_stream, int len) { - if (sync_to_audio) { + if (current_system) { + current_system->resume_context(current_system); + } + mix_and_convert(byte_stream, len, NULL); +} + +void render_lock_audio() +{ + if (sync_src == SYNC_AUDIO) { SDL_LockMutex(audio_mutex); } else { SDL_LockAudio(); } } -static void unlock_audio() +void render_unlock_audio() { - if (sync_to_audio) { + if (sync_src == SYNC_AUDIO) { SDL_UnlockMutex(audio_mutex); } else { SDL_UnlockAudio(); @@ -241,112 +150,86 @@ SDL_CondSignal(audio_ready); SDL_UnlockMutex(audio_mutex); SDL_CloseAudio(); + /* + FIXME: move this to render_audio.c + if (mix_buf) { + free(mix_buf); + mix_buf = NULL; + } + */ } -#define BUFFER_INC_RES 0x40000000UL - -void render_audio_adjust_clock(audio_source *src, uint64_t master_clock, uint64_t sample_divider) +void *render_new_audio_opaque(void) { - src->buffer_inc = ((BUFFER_INC_RES * (uint64_t)sample_rate) / master_clock) * sample_divider; + return SDL_CreateCond(); } -audio_source *render_audio_source(uint64_t master_clock, uint64_t sample_divider, uint8_t channels) +void render_free_audio_opaque(void *opaque) { - audio_source *ret = NULL; - uint32_t alloc_size = sync_to_audio ? channels * buffer_samples : nearest_pow2(min_buffered * 4 * channels); - lock_audio(); - if (num_audio_sources < 8) { - ret = malloc(sizeof(audio_source)); - ret->back = malloc(alloc_size * sizeof(int16_t)); - ret->front = sync_to_audio ? malloc(alloc_size * sizeof(int16_t)) : ret->back; - ret->front_populated = 0; - ret->cond = SDL_CreateCond(); - ret->num_channels = channels; - audio_sources[num_audio_sources++] = ret; - } - unlock_audio(); - if (!ret) { - fatal_error("Too many audio sources!"); - } else { - render_audio_adjust_clock(ret, master_clock, sample_divider); - double lowpass_cutoff = get_lowpass_cutoff(config); - double rc = (1.0 / lowpass_cutoff) / (2.0 * M_PI); - ret->dt = 1.0 / ((double)master_clock / (double)(sample_divider)); - double alpha = ret->dt / (ret->dt + rc); - ret->lowpass_alpha = (int32_t)(((double)0x10000) * alpha); - ret->buffer_pos = 0; - ret->buffer_fraction = 0; - ret->last_left = ret->last_right = 0; - ret->read_start = 0; - ret->read_end = sync_to_audio ? buffer_samples * channels : 0; - ret->mask = sync_to_audio ? 0xFFFFFFFF : alloc_size-1; - } - if (sync_to_audio && SDL_GetAudioStatus() == SDL_AUDIO_PAUSED) { - SDL_PauseAudio(0); - } - return ret; + SDL_DestroyCond(opaque); } -void render_pause_source(audio_source *src) +void render_audio_created(audio_source *source) { - uint8_t need_pause = 0; - lock_audio(); - for (uint8_t i = 0; i < num_audio_sources; i++) - { - if (audio_sources[i] == src) { - audio_sources[i] = audio_sources[--num_audio_sources]; - if (sync_to_audio) { - SDL_CondSignal(audio_ready); - } - break; - } - } - if (!num_audio_sources) { - need_pause = 1; - } - unlock_audio(); - if (need_pause) { - SDL_PauseAudio(1); - } - inactive_audio_sources[num_inactive_audio_sources++] = src; -} - -void render_resume_source(audio_source *src) -{ - lock_audio(); - if (num_audio_sources < 8) { - audio_sources[num_audio_sources++] = src; - } - unlock_audio(); - for (uint8_t i = 0; i < num_inactive_audio_sources; i++) - { - if (inactive_audio_sources[i] == src) { - inactive_audio_sources[i] = inactive_audio_sources[--num_inactive_audio_sources]; + if (sync_src == SYNC_AUDIO) { + //SDL_PauseAudio acquires the audio device lock, which is held while the callback runs + //since our callback can itself be stuck waiting on the audio_ready condition variable + //calling SDL_PauseAudio(0) again for audio sources after the first can deadlock + //fortunately SDL_GetAudioStatus does not acquire the lock so is safe to call here + if (SDL_GetAudioStatus() == SDL_AUDIO_PAUSED) { + SDL_PauseAudio(0); } } - if (sync_to_audio) { - SDL_PauseAudio(0); + if (current_system && sync_src == SYNC_AUDIO_THREAD) { + system_request_exit(current_system, 0); } } -void render_free_source(audio_source *src) +void render_source_paused(audio_source *src, uint8_t remaining_sources) +{ + if (sync_src == SYNC_AUDIO) { + SDL_CondSignal(audio_ready); + } + if (!remaining_sources && render_is_audio_sync()) { + SDL_PauseAudio(1); + if (sync_src == SYNC_AUDIO_THREAD) { + SDL_CondSignal(frame_ready); + } + } +} + +void render_source_resumed(audio_source *src) { - render_pause_source(src); - - free(src->front); - if (sync_to_audio) { - free(src->back); - SDL_DestroyCond(src->cond); + if (sync_src == SYNC_AUDIO) { + //SDL_PauseAudio acquires the audio device lock, which is held while the callback runs + //since our callback can itself be stuck waiting on the audio_ready condition variable + //calling SDL_PauseAudio(0) again for audio sources after the first can deadlock + //fortunately SDL_GetAudioStatus does not acquire the lock so is safe to call here + if (SDL_GetAudioStatus() == SDL_AUDIO_PAUSED) { + SDL_PauseAudio(0); + } + } + if (current_system && sync_src == SYNC_AUDIO_THREAD) { + system_request_exit(current_system, 0); } - free(src); } -static uint32_t sync_samples; -static void do_audio_ready(audio_source *src) + +void render_do_audio_ready(audio_source *src) { - if (sync_to_audio) { + if (sync_src == SYNC_AUDIO_THREAD) { + int16_t *tmp = src->front; + src->front = src->back; + src->back = tmp; + src->front_populated = 1; + src->buffer_pos = 0; + if (all_sources_ready()) { + //we've emulated far enough to fill the current buffer + system_request_exit(current_system, 0); + } + } else if (sync_src == SYNC_AUDIO) { SDL_LockMutex(audio_mutex); while (src->front_populated) { - SDL_CondWait(src->cond, audio_mutex); + SDL_CondWait(src->opaque, audio_mutex); } int16_t *tmp = src->front; src->front = src->back; @@ -367,62 +250,9 @@ } } -static int16_t lowpass_sample(audio_source *src, int16_t last, int16_t current) -{ - int32_t tmp = current * src->lowpass_alpha + last * (0x10000 - src->lowpass_alpha); - current = tmp >> 16; - return current; -} - -static void interp_sample(audio_source *src, int16_t last, int16_t current) -{ - int64_t tmp = last * ((src->buffer_fraction << 16) / src->buffer_inc); - tmp += current * (0x10000 - ((src->buffer_fraction << 16) / src->buffer_inc)); - src->back[src->buffer_pos++] = tmp >> 16; -} - -void render_put_mono_sample(audio_source *src, int16_t value) -{ - value = lowpass_sample(src, src->last_left, value); - src->buffer_fraction += src->buffer_inc; - uint32_t base = sync_to_audio ? 0 : src->read_end; - while (src->buffer_fraction > BUFFER_INC_RES) - { - src->buffer_fraction -= BUFFER_INC_RES; - interp_sample(src, src->last_left, value); - - if (((src->buffer_pos - base) & src->mask) >= sync_samples) { - do_audio_ready(src); - } - src->buffer_pos &= src->mask; - } - src->last_left = value; -} - -void render_put_stereo_sample(audio_source *src, int16_t left, int16_t right) -{ - left = lowpass_sample(src, src->last_left, left); - right = lowpass_sample(src, src->last_right, right); - src->buffer_fraction += src->buffer_inc; - uint32_t base = sync_to_audio ? 0 : src->read_end; - while (src->buffer_fraction > BUFFER_INC_RES) - { - src->buffer_fraction -= BUFFER_INC_RES; - - interp_sample(src, src->last_left, left); - interp_sample(src, src->last_right, right); - - if (((src->buffer_pos - base) & src->mask)/2 >= sync_samples) { - do_audio_ready(src); - } - src->buffer_pos &= src->mask; - } - src->last_left = left; - src->last_right = right; -} - static SDL_Joystick * joysticks[MAX_JOYSTICKS]; static int joystick_sdl_index[MAX_JOYSTICKS]; +static uint8_t joystick_index_locked[MAX_JOYSTICKS]; int render_width() { @@ -448,8 +278,21 @@ #endif } +static uint8_t external_sync; +void render_set_external_sync(uint8_t ext_sync_on) +{ + if (ext_sync_on != external_sync) { + external_sync = ext_sync_on; + if (windowed_width) { + //only do this if render_init has already been called + render_config_updated(); + } + } +} + #ifndef DISABLE_OPENGL -static GLuint textures[3], buffers[2], vshader, fshader, program, un_textures[2], un_width, un_height, at_pos; +static GLuint textures[3], buffers[2], vshader, fshader, program, un_textures[2], un_width, un_height, un_texsize, at_pos; +static int tex_width, tex_height; static GLfloat vertex_data_default[] = { -1.0f, -1.0f, @@ -474,28 +317,39 @@ static GLuint load_shader(char * fname, GLenum shader_type) { + char * shader_path; + FILE *f; + GLchar *text; + long fsize; +#ifndef __ANDROID__ char const * parts[] = {get_home_dir(), "/.config/blastem/shaders/", fname}; - char * shader_path = alloc_concat_m(3, parts); - FILE * f = fopen(shader_path, "rb"); + shader_path = alloc_concat_m(3, parts); + f = fopen(shader_path, "rb"); free(shader_path); - if (!f) { - parts[0] = get_exe_dir(); - parts[1] = "/shaders/"; - shader_path = alloc_concat_m(3, parts); - f = fopen(shader_path, "rb"); + if (f) { + fsize = file_size(f); + text = malloc(fsize); + if (fread(text, 1, fsize, f) != fsize) { + warning("Error reading from shader file %s\n", fname); + free(text); + return 0; + } + } else { +#endif + shader_path = path_append("shaders", fname); + uint32_t fsize32; + text = read_bundled_file(shader_path, &fsize32); free(shader_path); - if (!f) { + if (!text) { warning("Failed to open shader file %s for reading\n", fname); return 0; } + fsize = fsize32; +#ifndef __ANDROID__ } - long fsize = file_size(f); - GLchar * text = malloc(fsize); - if (fread(text, 1, fsize, f) != fsize) { - warning("Error reading from shader file %s\n", fname); - free(text); - return 0; - } +#endif + text[fsize] = 0; + if (strncmp(text, "#version", strlen("#version"))) { GLchar *tmp = text; text = alloc_concat(shader_prefix, tmp); @@ -503,6 +357,10 @@ fsize += strlen(shader_prefix); } GLuint ret = glCreateShader(shader_type); + if (!ret) { + warning("glCreateShader failed with error %d\n", glGetError()); + return 0; + } glShaderSource(ret, 1, (const GLchar **)&text, (const GLint *)&fsize); free(text); glCompileShader(ret); @@ -522,7 +380,9 @@ #endif static uint32_t texture_buf[512 * 513]; -#ifndef DISABLE_OPENGL +#ifdef DISABLE_OPENGL +#define RENDER_FORMAT SDL_PIXELFORMAT_ARGB8888 +#else #ifdef USE_GLES #define INTERNAL_FORMAT GL_RGBA #define SRC_FORMAT GL_RGBA @@ -538,6 +398,15 @@ char *scaling = tern_find_path_default(config, "video\0scaling\0", def, TVAL_PTR).ptrval; GLint filter = strcmp(scaling, "linear") ? GL_NEAREST : GL_LINEAR; glGenTextures(3, textures); + def.ptrval = "off"; + char *npot_textures = tern_find_path_default(config, "video\0npot_textures\0", def, TVAL_PTR).ptrval; + if (!strcmp(npot_textures, "on")) { + tex_width = LINEBUF_SIZE; + tex_height = 294; //PAL height with full borders + } else { + tex_width = tex_height = 512; + } + debug_message("Using %dx%d textures\n", tex_width, tex_height); for (int i = 0; i < 3; i++) { glBindTexture(GL_TEXTURE_2D, textures[i]); @@ -547,7 +416,7 @@ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); if (i < 2) { //TODO: Fixme for PAL + invalid display mode - glTexImage2D(GL_TEXTURE_2D, 0, INTERNAL_FORMAT, 512, 512, 0, SRC_FORMAT, GL_UNSIGNED_BYTE, texture_buf); + glTexImage2D(GL_TEXTURE_2D, 0, INTERNAL_FORMAT, tex_width, tex_height, 0, SRC_FORMAT, GL_UNSIGNED_BYTE, texture_buf); } else { uint32_t blank = 255 << 24; glTexImage2D(GL_TEXTURE_2D, 0, INTERNAL_FORMAT, 1, 1, 0, SRC_FORMAT, GL_UNSIGNED_BYTE, &blank); @@ -576,6 +445,7 @@ un_textures[1] = glGetUniformLocation(program, "textures[1]"); un_width = glGetUniformLocation(program, "width"); un_height = glGetUniformLocation(program, "height"); + un_texsize = glGetUniformLocation(program, "texsize"); at_pos = glGetAttribLocation(program, "pos"); } @@ -595,12 +465,11 @@ if (texture_init) { return; } - sdl_textures= malloc(sizeof(SDL_Texture *) * 2); - num_textures = 2; + sdl_textures= calloc(sizeof(SDL_Texture *), 3); + num_textures = 3; texture_init = 1; #ifndef DISABLE_OPENGL if (render_gl) { - sdl_textures[0] = sdl_textures[1] = NULL; gl_setup(); } else { #endif @@ -667,7 +536,9 @@ static void update_aspect() { //reset default values +#ifndef DISABLE_OPENGL memcpy(vertex_data, vertex_data_default, sizeof(vertex_data)); +#endif main_clip.w = main_width; main_clip.h = main_height; main_clip.x = main_clip.y = 0; @@ -699,13 +570,18 @@ } } -static ui_render_fun on_context_destroyed, on_context_created; +static ui_render_fun on_context_destroyed, on_context_created, on_ui_fb_resized; void render_set_gl_context_handlers(ui_render_fun destroy, ui_render_fun create) { on_context_destroyed = destroy; on_context_created = create; } +void render_set_ui_fb_resize_handler(ui_render_fun resize) +{ + on_ui_fb_resized = resize; +} + static uint8_t scancode_map[SDL_NUM_SCANCODES] = { [SDL_SCANCODE_A] = 0x1C, [SDL_SCANCODE_B] = 0x32, @@ -842,6 +718,16 @@ return -1; } +static int lowest_unlocked_joystick_index(void) +{ + for (int i = 0; i < MAX_JOYSTICKS; i++) { + if (!joystick_index_locked[i]) { + return i; + } + } + return -1; +} + SDL_Joystick *render_get_joystick(int index) { if (index >= MAX_JOYSTICKS) { @@ -863,17 +749,59 @@ SDL_GameController *render_get_controller(int index) { - if (index >= MAX_JOYSTICKS) { + if (index >= MAX_JOYSTICKS || !joysticks[index]) { return NULL; } return SDL_GameControllerOpen(joystick_sdl_index[index]); } +static uint8_t gc_events_enabled; +static SDL_GameController *controllers[MAX_JOYSTICKS]; +void render_enable_gamepad_events(uint8_t enabled) +{ + if (enabled != gc_events_enabled) { + gc_events_enabled = enabled; + for (int i = 0; i < MAX_JOYSTICKS; i++) { + if (enabled) { + controllers[i] = render_get_controller(i); + } else if (controllers[i]) { + SDL_GameControllerClose(controllers[i]); + controllers[i] = NULL; + } + } + } +} + static uint32_t overscan_top[NUM_VID_STD] = {2, 21}; static uint32_t overscan_bot[NUM_VID_STD] = {1, 17}; static uint32_t overscan_left[NUM_VID_STD] = {13, 13}; static uint32_t overscan_right[NUM_VID_STD] = {14, 14}; static vid_std video_standard = VID_NTSC; +static uint8_t need_ui_fb_resize; + +int lock_joystick_index(int joystick, int desired_index) +{ + if (desired_index < 0) { + desired_index = lowest_unlocked_joystick_index(); + if (desired_index < 0 || desired_index >= joystick) { + return joystick; + } + } + SDL_Joystick *tmp_joy = joysticks[joystick]; + int tmp_index = joystick_sdl_index[joystick]; + joysticks[joystick] = joysticks[desired_index]; + joystick_sdl_index[joystick] = joystick_sdl_index[desired_index]; + joystick_index_locked[joystick] = joystick_sdl_index[desired_index]; + joysticks[desired_index] = tmp_joy; + joystick_sdl_index[desired_index] = tmp_index; + joystick_index_locked[desired_index] = 1; + //update bindings as the controllers being swapped may have different mappings + handle_joy_added(desired_index); + if (joysticks[joystick]) { + handle_joy_added(joystick); + } + return desired_index; +} static int32_t handle_event(SDL_Event *event) { @@ -891,13 +819,13 @@ handle_joydown(find_joystick_index(event->jbutton.which), event->jbutton.button); break; case SDL_JOYBUTTONUP: - handle_joyup(find_joystick_index(event->jbutton.which), event->jbutton.button); + handle_joyup(lock_joystick_index(find_joystick_index(event->jbutton.which), -1), event->jbutton.button); break; case SDL_JOYHATMOTION: - handle_joy_dpad(find_joystick_index(event->jhat.which), event->jhat.hat, event->jhat.value); + handle_joy_dpad(lock_joystick_index(find_joystick_index(event->jhat.which), -1), event->jhat.hat, event->jhat.value); break; case SDL_JOYAXISMOTION: - handle_joy_axis(find_joystick_index(event->jaxis.which), event->jaxis.axis, event->jaxis.value); + handle_joy_axis(lock_joystick_index(find_joystick_index(event->jaxis.which), -1), event->jaxis.axis, event->jaxis.value); break; case SDL_JOYDEVICEADDED: if (event->jdevice.which < MAX_JOYSTICKS) { @@ -905,9 +833,13 @@ if (index >= 0) { SDL_Joystick * joy = joysticks[index] = SDL_JoystickOpen(event->jdevice.which); joystick_sdl_index[index] = event->jdevice.which; + joystick_index_locked[index] = 0; + if (gc_events_enabled) { + controllers[index] = SDL_GameControllerOpen(event->jdevice.which); + } if (joy) { - printf("Joystick %d added: %s\n", index, SDL_JoystickName(joy)); - printf("\tNum Axes: %d\n\tNum Buttons: %d\n\tNum Hats: %d\n", SDL_JoystickNumAxes(joy), SDL_JoystickNumButtons(joy), SDL_JoystickNumHats(joy)); + debug_message("Joystick %d added: %s\n", index, SDL_JoystickName(joy)); + debug_message("\tNum Axes: %d\n\tNum Buttons: %d\n\tNum Hats: %d\n", SDL_JoystickNumAxes(joy), SDL_JoystickNumButtons(joy), SDL_JoystickNumHats(joy)); handle_joy_added(index); } } @@ -918,9 +850,13 @@ if (index >= 0) { SDL_JoystickClose(joysticks[index]); joysticks[index] = NULL; - printf("Joystick %d removed\n", index); + if (controllers[index]) { + SDL_GameControllerClose(controllers[index]); + controllers[index] = NULL; + } + debug_message("Joystick %d removed\n", index); } else { - printf("Failed to find removed joystick with instance ID: %d\n", index); + debug_message("Failed to find removed joystick with instance ID: %d\n", index); } break; } @@ -937,8 +873,12 @@ switch (event->window.event) { case SDL_WINDOWEVENT_SIZE_CHANGED: + if (!main_window) { + break; + } main_width = event->window.data1; main_height = event->window.data2; + need_ui_fb_resize = 1; update_aspect(); #ifndef DISABLE_OPENGL if (render_gl) { @@ -956,7 +896,7 @@ #endif break; case SDL_WINDOWEVENT_CLOSE: - if (SDL_GetWindowID(main_window) == event->window.windowID) { + if (main_window && SDL_GetWindowID(main_window) == event->window.windowID) { exit(0); } else { for (int i = 0; i < num_textures - FRAMEBUFFER_USER_START; i++) @@ -1001,6 +941,7 @@ static int source_frame_count; static int frame_repeat[60]; +static uint32_t sample_rate; static void init_audio() { SDL_AudioSpec desired, actual; @@ -1010,35 +951,46 @@ rate = 48000; } desired.freq = rate; - desired.format = AUDIO_S16SYS; + char *config_format = tern_find_path_default(config, "audio\0format\0", (tern_val){.ptrval="f32"}, TVAL_PTR).ptrval; + desired.format = !strcmp(config_format, "s16") ? AUDIO_S16SYS : AUDIO_F32SYS; desired.channels = 2; char * samples_str = tern_find_path(config, "audio\0buffer\0", TVAL_PTR).ptrval; int samples = samples_str ? atoi(samples_str) : 0; if (!samples) { samples = 512; } - printf("config says: %d\n", samples); + debug_message("config says: %d\n", samples); desired.samples = samples*2; - desired.callback = sync_to_audio ? audio_callback : audio_callback_drc; + switch (sync_src) + { + case SYNC_AUDIO: + desired.callback = audio_callback; + break; + case SYNC_AUDIO_THREAD: + desired.callback = audio_callback_run_on_audio; + break; + default: + desired.callback = audio_callback_drc; + } desired.userdata = NULL; if (SDL_OpenAudio(&desired, &actual) < 0) { fatal_error("Unable to open SDL audio: %s\n", SDL_GetError()); } - buffer_samples = actual.samples; sample_rate = actual.freq; - printf("Initialized audio at frequency %d with a %d sample buffer, ", actual.freq, actual.samples); + debug_message("Initialized audio at frequency %d with a %d sample buffer, ", actual.freq, actual.samples); + render_audio_format format = RENDER_AUDIO_UNKNOWN; if (actual.format == AUDIO_S16SYS) { - puts("signed 16-bit int format"); - mix = mix_s16; + debug_message("signed 16-bit int format\n"); + format = RENDER_AUDIO_S16; } else if (actual.format == AUDIO_F32SYS) { - puts("32-bit float format"); - mix = mix_f32; + debug_message("32-bit float format\n"); + format = RENDER_AUDIO_FLOAT; } else { - printf("unsupported format %X\n", actual.format); + debug_message("unsupported format %X\n", actual.format); warning("Unsupported audio sample format: %X\n", actual.format); - mix = mix_null; } + render_audio_initialized(format, actual.freq, actual.channels, actual.samples, SDL_AUDIO_BITSIZE(actual.format) / 8); } void window_setup(void) @@ -1049,11 +1001,31 @@ } tern_val def = {.ptrval = "audio"}; - char *sync_src = tern_find_path_default(config, "system\0sync_source\0", def, TVAL_PTR).ptrval; - sync_to_audio = !strcmp(sync_src, "audio"); + if (external_sync) { + sync_src = SYNC_EXTERNAL; + } else { + char *sync_src_str = tern_find_path_default(config, "system\0sync_source\0", def, TVAL_PTR).ptrval; + if (!strcmp(sync_src_str, "audio")) { + sync_src = SYNC_AUDIO; + } else if (!strcmp(sync_src_str, "audio_thread")) { + sync_src = SYNC_AUDIO_THREAD; + } else { + sync_src = SYNC_VIDEO; + } + } + + if (!num_buffers && (sync_src == SYNC_AUDIO_THREAD || sync_src == SYNC_EXTERNAL)) { + frame_mutex = SDL_CreateMutex(); + free_buffer_mutex = SDL_CreateMutex(); + frame_ready = SDL_CreateCond(); + buffer_storage = 4; + frame_buffers = calloc(buffer_storage, sizeof(uint32_t*)); + frame_buffers[0] = texture_buf; + num_buffers = 1; + } const char *vsync; - if (sync_to_audio) { + if (sync_src == SYNC_AUDIO) { def.ptrval = "off"; vsync = tern_find_path_default(config, "video\0vsync\0", def, TVAL_PTR).ptrval; } else { @@ -1137,7 +1109,11 @@ } if (vsync) { if (SDL_GL_SetSwapInterval(!strcmp("on", vsync)) < 0) { +#ifdef __ANDROID__ + debug_message("Failed to set vsync to %s: %s\n", vsync, SDL_GetError()); +#else warning("Failed to set vsync to %s: %s\n", vsync, SDL_GetError()); +#endif } } } else { @@ -1157,7 +1133,7 @@ } SDL_RendererInfo rinfo; SDL_GetRendererInfo(main_renderer, &rinfo); - printf("SDL2 Render Driver: %s\n", rinfo.name); + debug_message("SDL2 Render Driver: %s\n", rinfo.name); main_clip.x = main_clip.y = 0; main_clip.w = main_width; main_clip.h = main_height; @@ -1166,7 +1142,7 @@ #endif SDL_GetWindowSize(main_window, &main_width, &main_height); - printf("Window created with size: %d x %d\n", main_width, main_height); + debug_message("Window created with size: %d x %d\n", main_width, main_height); update_aspect(); render_alloc_surfaces(); def.ptrval = "off"; @@ -1183,7 +1159,7 @@ float aspect = config_aspect() > 0.0f ? config_aspect() : 4.0f/3.0f; height = ((float)width / aspect) + 0.5f; } - printf("width: %d, height: %d\n", width, height); + debug_message("width: %d, height: %d\n", width, height); windowed_width = width; windowed_height = height; @@ -1216,7 +1192,7 @@ if (db_data) { int added = SDL_GameControllerAddMappingsFromRW(SDL_RWFromMem(db_data, db_size), 1); free(db_data); - printf("Added %d game controller mappings from gamecontrollerdb.txt\n", added); + debug_message("Added %d game controller mappings from gamecontrollerdb.txt\n", added); } controller_add_mappings(); @@ -1227,33 +1203,23 @@ atexit(render_quit); } -#include<unistd.h> -static int in_toggle; -static void update_source(audio_source *src, double rc, uint8_t sync_changed) + +void render_reset_mappings(void) { - double alpha = src->dt / (src->dt + rc); - int32_t lowpass_alpha = (int32_t)(((double)0x10000) * alpha); - src->lowpass_alpha = lowpass_alpha; - if (sync_changed) { - uint32_t alloc_size = sync_to_audio ? src->num_channels * buffer_samples : nearest_pow2(min_buffered * 4 * src->num_channels); - src->back = realloc(src->back, alloc_size * sizeof(int16_t)); - if (sync_to_audio) { - src->front = malloc(alloc_size * sizeof(int16_t)); - } else { - free(src->front); - src->front = src->back; - } - src->mask = sync_to_audio ? 0xFFFFFFFF : alloc_size-1; - src->read_start = 0; - src->read_end = sync_to_audio ? buffer_samples * src->num_channels : 0; - src->buffer_pos = 0; + SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER); + SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER); + uint32_t db_size; + char *db_data = read_bundled_file("gamecontrollerdb.txt", &db_size); + if (db_data) { + int added = SDL_GameControllerAddMappingsFromRW(SDL_RWFromMem(db_data, db_size), 1); + free(db_data); + debug_message("Added %d game controller mappings from gamecontrollerdb.txt\n", added); } } +static int in_toggle; void render_config_updated(void) { - uint8_t old_sync_to_audio = sync_to_audio; - free_surfaces(); #ifndef DISABLE_OPENGL if (render_gl) { @@ -1270,6 +1236,7 @@ #endif in_toggle = 1; SDL_DestroyWindow(main_window); + main_window = NULL; drain_events(); char *config_width = tern_find_path(config, "video\0width\0", TVAL_PTR).ptrval; @@ -1295,6 +1262,9 @@ main_width = windowed_width; main_height = windowed_height; } + if (on_ui_fb_resized) { + on_ui_fb_resized(); + } window_setup(); update_aspect(); @@ -1311,18 +1281,6 @@ init_audio(); render_set_video_standard(video_standard); - double lowpass_cutoff = get_lowpass_cutoff(config); - double rc = (1.0 / lowpass_cutoff) / (2.0 * M_PI); - lock_audio(); - for (uint8_t i = 0; i < num_audio_sources; i++) - { - update_source(audio_sources[i], rc, old_sync_to_audio != sync_to_audio); - } - unlock_audio(); - for (uint8_t i = 0; i < num_inactive_audio_sources; i++) - { - update_source(inactive_audio_sources[i], rc, old_sync_to_audio != sync_to_audio); - } drain_events(); in_toggle = 0; if (!was_paused) { @@ -1335,9 +1293,18 @@ return main_window; } +uint32_t render_audio_syncs_per_sec(void) +{ + //sync samples with audio thread approximately every 8 lines when doing sync to video + return render_is_audio_sync() ? 0 : source_hz * (video_standard == VID_PAL ? 313 : 262) / 8; +} + void render_set_video_standard(vid_std std) { video_standard = std; + if (render_is_audio_sync()) { + return; + } source_hz = std == VID_PAL ? 50 : 60; uint32_t max_repeat = 0; if (abs(source_hz - display_hz) < 2) { @@ -1364,12 +1331,10 @@ } source_frame = 0; source_frame_count = frame_repeat[0]; - //sync samples with audio thread approximately every 8 lines - sync_samples = sync_to_audio ? buffer_samples : 8 * sample_rate / (source_hz * (VID_PAL ? 313 : 262)); max_repeat++; min_buffered = (((float)max_repeat * (float)sample_rate/(float)source_hz)/* / (float)buffer_samples*/);// + 0.9999; //min_buffered *= buffer_samples; - printf("Min samples buffered before audio start: %d\n", min_buffered); + debug_message("Min samples buffered before audio start: %d\n", min_buffered); max_adjust = BASE_MAX_ADJUST / source_hz; } @@ -1448,24 +1413,40 @@ uint32_t locked_pitch; uint32_t *render_get_framebuffer(uint8_t which, int *pitch) { + if (sync_src == SYNC_AUDIO_THREAD || sync_src == SYNC_EXTERNAL) { + *pitch = LINEBUF_SIZE * sizeof(uint32_t); + uint32_t *buffer; + SDL_LockMutex(free_buffer_mutex); + if (num_buffers) { + buffer = frame_buffers[--num_buffers]; + } else { + buffer = calloc(tex_width*(tex_height + 1), sizeof(uint32_t)); + } + SDL_UnlockMutex(free_buffer_mutex); + locked_pixels = buffer; + return buffer; + } #ifndef DISABLE_OPENGL if (render_gl && which <= FRAMEBUFFER_EVEN) { *pitch = LINEBUF_SIZE * sizeof(uint32_t); return texture_buf; } else { #endif + if (which == FRAMEBUFFER_UI && !sdl_textures[which]) { + sdl_textures[which] = SDL_CreateTexture(main_renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, main_width, main_height); + } if (which >= num_textures) { warning("Request for invalid framebuffer number %d\n", which); return NULL; } - void *pixels; - if (SDL_LockTexture(sdl_textures[which], NULL, &pixels, pitch) < 0) { + uint8_t *pixels; + if (SDL_LockTexture(sdl_textures[which], NULL, (void **)&pixels, pitch) < 0) { warning("Failed to lock texture: %s\n", SDL_GetError()); return NULL; } static uint8_t last; if (which <= FRAMEBUFFER_EVEN) { - locked_pixels = pixels; + locked_pixels = (uint32_t *)pixels; if (which == FRAMEBUFFER_EVEN) { pixels += *pitch; } @@ -1475,12 +1456,23 @@ } last = which; } - return pixels; + return (uint32_t *)pixels; #ifndef DISABLE_OPENGL } #endif } +static void release_buffer(uint32_t *buffer) +{ + SDL_LockMutex(free_buffer_mutex); + if (num_buffers == buffer_storage) { + buffer_storage *= 2; + frame_buffers = realloc(frame_buffers, sizeof(uint32_t*)*buffer_storage); + } + frame_buffers[num_buffers++] = buffer; + SDL_UnlockMutex(free_buffer_mutex); +} + uint8_t events_processed; #ifdef __ANDROID__ #define FPS_INTERVAL 10000 @@ -1490,10 +1482,10 @@ static uint32_t last_width, last_height; static uint8_t interlaced; -void render_framebuffer_updated(uint8_t which, int width) +static void process_framebuffer(uint32_t *buffer, uint8_t which, int width) { static uint8_t last; - if (!sync_to_audio && which <= FRAMEBUFFER_EVEN && source_frame_count < 0) { + if (sync_src == SYNC_VIDEO && which <= FRAMEBUFFER_EVEN && source_frame_count < 0) { source_frame++; if (source_frame >= source_hz) { source_frame = 0; @@ -1516,7 +1508,7 @@ #ifndef DISABLE_ZLIB ext = path_extension(screenshot_path); #endif - info_message("Saving screenshot to %s\n", screenshot_path); + debug_message("Saving screenshot to %s\n", screenshot_path); } else { warning("Failed to open screenshot file %s for writing\n", screenshot_path); } @@ -1531,24 +1523,25 @@ if (render_gl && which <= FRAMEBUFFER_EVEN) { SDL_GL_MakeCurrent(main_window, main_context); glBindTexture(GL_TEXTURE_2D, textures[which]); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, LINEBUF_SIZE, height, SRC_FORMAT, GL_UNSIGNED_BYTE, texture_buf + overscan_left[video_standard] + LINEBUF_SIZE * overscan_top[video_standard]); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, LINEBUF_SIZE, height, SRC_FORMAT, GL_UNSIGNED_BYTE, buffer + overscan_left[video_standard] + LINEBUF_SIZE * overscan_top[video_standard]); if (screenshot_file) { //properly supporting interlaced modes here is non-trivial, so only save the odd field for now #ifndef DISABLE_ZLIB if (!strcasecmp(ext, "png")) { free(ext); - save_png(screenshot_file, texture_buf, shot_width, shot_height, LINEBUF_SIZE*sizeof(uint32_t)); + save_png(screenshot_file, buffer, shot_width, shot_height, LINEBUF_SIZE*sizeof(uint32_t)); } else { free(ext); #endif - save_ppm(screenshot_file, texture_buf, shot_width, shot_height, LINEBUF_SIZE*sizeof(uint32_t)); + save_ppm(screenshot_file, buffer, shot_width, shot_height, LINEBUF_SIZE*sizeof(uint32_t)); #ifndef DISABLE_ZLIB } #endif } } else { #endif + //TODO: Support SYNC_AUDIO_THREAD/SYNC_EXTERNAL for render API framebuffers if (which <= FRAMEBUFFER_EVEN && last != which) { uint8_t *cur_dst = (uint8_t *)locked_pixels; uint8_t *cur_saved = (uint8_t *)texture_buf; @@ -1591,6 +1584,16 @@ last_height = height; if (which <= FRAMEBUFFER_EVEN) { render_update_display(); + } else if (which == FRAMEBUFFER_UI) { + SDL_RenderCopy(main_renderer, sdl_textures[which], NULL, NULL); + if (need_ui_fb_resize) { + SDL_DestroyTexture(sdl_textures[which]); + sdl_textures[which] = NULL; + if (on_ui_fb_resized) { + on_ui_fb_resized(); + } + need_ui_fb_resize = 0; + } } else { SDL_RenderCopy(extra_renderers[which - FRAMEBUFFER_USER_START], sdl_textures[which], NULL, NULL); SDL_RenderPresent(extra_renderers[which - FRAMEBUFFER_USER_START]); @@ -1606,7 +1609,7 @@ if ((last_frame - start) > FPS_INTERVAL) { if (start && (last_frame-start)) { #ifdef __ANDROID__ - info_message("%s - %.1f fps", caption, ((float)frame_counter) / (((float)(last_frame-start)) / 1000.0)); + debug_message("%s - %.1f fps", caption, ((float)frame_counter) / (((float)(last_frame-start)) / 1000.0)); #else if (!fps_caption) { fps_caption = malloc(strlen(caption) + strlen(" - 100000000.1 fps") + 1); @@ -1619,7 +1622,7 @@ frame_counter = 0; } } - if (!sync_to_audio) { + if (!render_is_audio_sync()) { int32_t local_cur_min, local_min_remaining; SDL_LockAudio(); if (last_buffered > NO_LAST_BUFFERED) { @@ -1661,10 +1664,8 @@ } if (adjust_ratio != 0.0f) { average_change = 0; - for (uint8_t i = 0; i < num_audio_sources; i++) - { - audio_sources[i]->buffer_inc = ((double)audio_sources[i]->buffer_inc) + ((double)audio_sources[i]->buffer_inc) * adjust_ratio + 0.5; - } + render_audio_adjust_speed(adjust_ratio); + } while (source_frame_count > 0) { @@ -1679,6 +1680,83 @@ } } +typedef struct { + uint32_t *buffer; + int width; + uint8_t which; +} frame; +frame frame_queue[4]; +int frame_queue_len, frame_queue_read, frame_queue_write; + +void render_framebuffer_updated(uint8_t which, int width) +{ + if (sync_src == SYNC_AUDIO_THREAD || sync_src == SYNC_EXTERNAL) { + SDL_LockMutex(frame_mutex); + while (frame_queue_len == 4) { + SDL_CondSignal(frame_ready); + SDL_UnlockMutex(frame_mutex); + SDL_Delay(1); + SDL_LockMutex(frame_mutex); + } + for (int cur = frame_queue_read, i = 0; i < frame_queue_len; i++) { + if (frame_queue[cur].which == which) { + int last = (frame_queue_write - 1) & 3; + frame_queue_len--; + release_buffer(frame_queue[cur].buffer); + if (last != cur) { + frame_queue[cur] = frame_queue[last]; + } + frame_queue_write = last; + break; + } + cur = (cur + 1) & 3; + } + frame_queue[frame_queue_write++] = (frame){ + .buffer = locked_pixels, + .width = width, + .which = which + }; + frame_queue_write &= 0x3; + frame_queue_len++; + SDL_CondSignal(frame_ready); + SDL_UnlockMutex(frame_mutex); + return; + } + //TODO: Maybe fixme for render API + process_framebuffer(texture_buf, which, width); +} + +void render_video_loop(void) +{ + if (sync_src != SYNC_AUDIO_THREAD && sync_src != SYNC_EXTERNAL) { + return; + } + SDL_PauseAudio(0); + SDL_LockMutex(frame_mutex); + for(;;) + { + while (!frame_queue_len && SDL_GetAudioStatus() == SDL_AUDIO_PLAYING) + { + SDL_CondWait(frame_ready, frame_mutex); + } + while (frame_queue_len) + { + frame f = frame_queue[frame_queue_read++]; + frame_queue_read &= 0x3; + frame_queue_len--; + SDL_UnlockMutex(frame_mutex); + process_framebuffer(f.buffer, f.which, f.width); + release_buffer(f.buffer); + SDL_LockMutex(frame_mutex); + } + if (SDL_GetAudioStatus() != SDL_AUDIO_PLAYING) { + break; + } + } + + SDL_UnlockMutex(frame_mutex); +} + static ui_render_fun render_ui; void render_set_ui_render_fun(ui_render_fun fun) { @@ -1703,6 +1781,7 @@ glUniform1f(un_width, render_emulated_width()); glUniform1f(un_height, last_height); + glUniform2f(un_texsize, tex_width, tex_height); glBindBuffer(GL_ARRAY_BUFFER, buffers[0]); glVertexAttribPointer(at_pos, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat[2]), (void *)0); @@ -1762,7 +1841,12 @@ return overscan_top[video_standard]; } -void render_wait_quit(vdp_context * context) +uint32_t render_overscan_bot() +{ + return overscan_bot[video_standard]; +} + +void render_wait_quit(void) { SDL_Event event; while(SDL_WaitEvent(&event)) { @@ -1829,6 +1913,7 @@ } SDL_GameControllerButtonBind cbind; + int32_t is_positive = RENDER_AXIS_POS; if (is_axis) { int sdl_axis = render_lookup_axis(name); @@ -1843,6 +1928,10 @@ SDL_GameControllerClose(control); return RENDER_INVALID_NAME; } + if (sdl_button == SDL_CONTROLLER_BUTTON_DPAD_UP || sdl_button == SDL_CONTROLLER_BUTTON_DPAD_LEFT) { + //assume these will be negative if they are an axis + is_positive = 0; + } cbind = SDL_GameControllerGetBindForButton(control, sdl_button); } SDL_GameControllerClose(control); @@ -1851,7 +1940,7 @@ case SDL_CONTROLLER_BINDTYPE_BUTTON: return cbind.value.button; case SDL_CONTROLLER_BINDTYPE_AXIS: - return RENDER_AXIS_BIT | cbind.value.axis; + return RENDER_AXIS_BIT | cbind.value.axis | is_positive; case SDL_CONTROLLER_BINDTYPE_HAT: return RENDER_DPAD_BIT | (cbind.value.hat.hat << 4) | cbind.value.hat.hat_mask; } @@ -1922,16 +2011,7 @@ SDL_SetWindowSize(main_window, windowed_width, windowed_height); drain_events(); in_toggle = 0; -} - -uint32_t render_audio_buffer() -{ - return buffer_samples; -} - -uint32_t render_sample_rate() -{ - return sample_rate; + need_ui_fb_resize = 1; } void render_errorbox(char *title, char *message) @@ -1977,3 +2057,9 @@ } return 0xFF; } + +uint8_t render_create_thread(render_thread *thread, const char *name, render_thread_fun fun, void *data) +{ + *thread = SDL_CreateThread(fun, name, data); + return *thread != 0; +}