changeset 1573:a051d8ee4528

Only save config file if something has changed. Re-initialize audio and video with new settings if config has changed
author Michael Pavone <pavone@retrodev.com>
date Fri, 27 Apr 2018 20:08:47 -0700
parents 5efeca06d942
children ade5b8148caa
files blastem.c blastem.h nuklear_ui/blastem_nuklear.c render.h render_sdl.c
diffstat 5 files changed, 439 insertions(+), 335 deletions(-) [+]
line wrap: on
line diff
--- a/blastem.c	Tue Apr 24 20:31:18 2018 -0700
+++ b/blastem.c	Fri Apr 27 20:08:47 2018 -0700
@@ -277,6 +277,11 @@
 	}
 }
 
+void apply_updated_config(void)
+{
+	render_config_updated();
+}
+
 static void on_drag_drop(const char *filename)
 {
 	if (current_system->next_rom) {
@@ -377,11 +382,6 @@
 	update_title(info.name);
 }
 
-static void save_config(void)
-{
-	persist_config(config);
-}
-
 int main(int argc, char ** argv)
 {
 	set_exe_str(argv[0]);
@@ -617,8 +617,6 @@
 		}
 	}
 	
-	atexit(save_config);
-	
 #ifndef DISABLE_NUKLEAR
 	if (use_nuklear) {
 		blastem_nuklear_init(!menu);
--- a/blastem.h	Tue Apr 24 20:31:18 2018 -0700
+++ b/blastem.h	Fri Apr 27 20:08:47 2018 -0700
@@ -18,5 +18,6 @@
 void reload_media(void);
 void lockon_media(char *lock_on_path);
 void init_system_with_media(char *path, system_type force_stype);
+void apply_updated_config(void);
 
 #endif //BLASTEM_H_
--- a/nuklear_ui/blastem_nuklear.c	Tue Apr 24 20:31:18 2018 -0700
+++ b/nuklear_ui/blastem_nuklear.c	Fri Apr 27 20:08:47 2018 -0700
@@ -23,6 +23,7 @@
 static uint32_t view_storage;
 static uint32_t num_prev;
 static struct nk_font *def_font;
+static uint8_t config_dirty;
 
 static void push_view(view_fun new_view)
 {
@@ -486,6 +487,7 @@
 				memcpy(path + prefix_len, name, suffix_len);
 				path[prefix_len + suffix_len] = 0;
 				
+				config_dirty = 1;
 				config = tern_insert_path(config, path, (tern_val){.ptrval = strdup(set_binding)}, TVAL_PTR);
 				free(path);
 				free(name);
@@ -598,6 +600,7 @@
 	nk_label(context, label, NK_TEXT_LEFT);
 	uint8_t newval = nk_check_label(context, "", curval);
 	if (newval != curval) {
+		config_dirty = 1;
 		config = tern_insert_path(config, path, (tern_val){.ptrval = strdup(newval ? "on" : "off")}, TVAL_PTR);
 	}
 }
@@ -616,6 +619,7 @@
 	nk_edit_string(context, NK_EDIT_SIMPLE, buffer, &len, sizeof(buffer)-1, nk_filter_decimal);
 	buffer[len] = 0;
 	if (strcmp(buffer, curstr)) {
+		config_dirty = 1;
 		config = tern_insert_path(config, path, (tern_val){.ptrval = strdup(buffer)}, TVAL_PTR);
 	}
 }
@@ -630,6 +634,7 @@
 	if (val != curval) {
 		char buffer[12];
 		sprintf(buffer, "%d", val);
+		config_dirty = 1;
 		config = tern_insert_path(config, path, (tern_val){.ptrval = strdup(buffer)}, TVAL_PTR);
 	}
 }
@@ -743,6 +748,7 @@
 	nk_label(context, label, NK_TEXT_LEFT);
 	int32_t next = nk_combo(context, opt_display, num_options, current, 30, nk_vec2(300, 300));
 	if (next != current) {
+		config_dirty = 1;
 		config = tern_insert_path(config, path, (tern_val){.ptrval = strdup(options[next])}, TVAL_PTR);
 	}
 	return next;
@@ -804,6 +810,7 @@
 		uint32_t next_selected = nk_combo(context, (const char **)prog_names, num_progs, selected_prog, 30, nk_vec2(300, 300));
 		if (next_selected != selected_prog) {
 			selected_prog = next_selected;
+			config_dirty = 1;
 			config = tern_insert_path(config, "video\0fragment_shader\0", (tern_val){.ptrval = strdup(progs[next_selected].fragment)}, TVAL_PTR);
 			config = tern_insert_path(config, "video\0vertex_shader\0", (tern_val){.ptrval = strdup(progs[next_selected].vertex)}, TVAL_PTR);
 		}
@@ -1024,6 +1031,11 @@
 		last = current;
 		render_update_display();
 	}
+	if (config_dirty) {
+		apply_updated_config();
+		persist_config(config);
+		config_dirty = 0;
+	}
 }
 static void handle_event(SDL_Event *event)
 {
@@ -1035,7 +1047,7 @@
 
 static void context_destroyed(void)
 {
-	nk_sdl_device_destroy();
+	nk_sdl_shutdown();
 }
 
 static uint32_t *controller_360_buf;
@@ -1067,7 +1079,7 @@
 
 static void context_created(void)
 {
-	nk_sdl_device_create();
+	context = nk_sdl_init(render_get_window());
 	texture_init();
 }
 
--- a/render.h	Tue Apr 24 20:31:18 2018 -0700
+++ b/render.h	Fri Apr 27 20:08:47 2018 -0700
@@ -124,6 +124,7 @@
 void render_pause_source(audio_source *src);
 void render_resume_source(audio_source *src);
 void render_free_source(audio_source *src);
+void render_config_updated(void);
 
 #endif //RENDER_H_
 
--- a/render_sdl.c	Tue Apr 24 20:31:18 2018 -0700
+++ b/render_sdl.c	Fri Apr 27 20:08:47 2018 -0700
@@ -48,6 +48,7 @@
 	SDL_cond *cond;
 	int16_t  *front;
 	int16_t  *back;
+	double   dt;
 	uint64_t buffer_fraction;
 	uint64_t buffer_inc;
 	uint32_t buffer_pos;
@@ -257,8 +258,8 @@
 		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);
-		double dt = 1.0 / ((double)master_clock / (double)(sample_divider));
-		double alpha = dt / (dt + rc);
+		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;
@@ -524,10 +525,9 @@
 }
 #endif
 
+static uint8_t texture_init;
 static void render_alloc_surfaces()
 {
-	static uint8_t texture_init;
-
 	if (texture_init) {
 		return;
 	}
@@ -550,18 +550,26 @@
 #endif
 }
 
+static void free_surfaces(void)
+{
+	for (int i = 0; i < num_textures; i++)
+	{
+		if (sdl_textures[i]) {
+			SDL_DestroyTexture(sdl_textures[i]);
+		}
+	}
+	free(sdl_textures);
+	sdl_textures = NULL;
+	texture_init = 0;
+}
+
 static char * caption = NULL;
 static char * fps_caption = NULL;
 
 static void render_quit()
 {
 	render_close_audio();
-	for (int i = 0; i < num_textures; i++)
-	{
-		if (sdl_textures[i]) {
-			SDL_DestroyTexture(sdl_textures[i]);
-		}
-	}
+	free_surfaces();
 }
 
 static float config_aspect()
@@ -621,55 +629,317 @@
 	}
 }
 
+static ui_render_fun on_context_destroyed, on_context_created;
+void render_set_gl_context_handlers(ui_render_fun destroy, ui_render_fun create)
+{
+	on_context_destroyed = destroy;
+	on_context_created = create;
+}
+
+static uint8_t scancode_map[SDL_NUM_SCANCODES] = {
+	[SDL_SCANCODE_A] = 0x1C,
+	[SDL_SCANCODE_B] = 0x32,
+	[SDL_SCANCODE_C] = 0x21,
+	[SDL_SCANCODE_D] = 0x23,
+	[SDL_SCANCODE_E] = 0x24,
+	[SDL_SCANCODE_F] = 0x2B,
+	[SDL_SCANCODE_G] = 0x34,
+	[SDL_SCANCODE_H] = 0x33,
+	[SDL_SCANCODE_I] = 0x43,
+	[SDL_SCANCODE_J] = 0x3B,
+	[SDL_SCANCODE_K] = 0x42,
+	[SDL_SCANCODE_L] = 0x4B,
+	[SDL_SCANCODE_M] = 0x3A,
+	[SDL_SCANCODE_N] = 0x31,
+	[SDL_SCANCODE_O] = 0x44,
+	[SDL_SCANCODE_P] = 0x4D,
+	[SDL_SCANCODE_Q] = 0x15,
+	[SDL_SCANCODE_R] = 0x2D,
+	[SDL_SCANCODE_S] = 0x1B,
+	[SDL_SCANCODE_T] = 0x2C,
+	[SDL_SCANCODE_U] = 0x3C,
+	[SDL_SCANCODE_V] = 0x2A,
+	[SDL_SCANCODE_W] = 0x1D,
+	[SDL_SCANCODE_X] = 0x22,
+	[SDL_SCANCODE_Y] = 0x35,
+	[SDL_SCANCODE_Z] = 0x1A,
+	[SDL_SCANCODE_1] = 0x16,
+	[SDL_SCANCODE_2] = 0x1E,
+	[SDL_SCANCODE_3] = 0x26,
+	[SDL_SCANCODE_4] = 0x25,
+	[SDL_SCANCODE_5] = 0x2E,
+	[SDL_SCANCODE_6] = 0x36,
+	[SDL_SCANCODE_7] = 0x3D,
+	[SDL_SCANCODE_8] = 0x3E,
+	[SDL_SCANCODE_9] = 0x46,
+	[SDL_SCANCODE_0] = 0x45,
+	[SDL_SCANCODE_RETURN] = 0x5A,
+	[SDL_SCANCODE_ESCAPE] = 0x76,
+	[SDL_SCANCODE_SPACE] = 0x29,
+	[SDL_SCANCODE_TAB] = 0x0D,
+	[SDL_SCANCODE_BACKSPACE] = 0x66,
+	[SDL_SCANCODE_MINUS] = 0x4E,
+	[SDL_SCANCODE_EQUALS] = 0x55,
+	[SDL_SCANCODE_LEFTBRACKET] = 0x54,
+	[SDL_SCANCODE_RIGHTBRACKET] = 0x5B,
+	[SDL_SCANCODE_BACKSLASH] = 0x5D,
+	[SDL_SCANCODE_SEMICOLON] = 0x4C,
+	[SDL_SCANCODE_APOSTROPHE] = 0x52,
+	[SDL_SCANCODE_GRAVE] = 0x0E,
+	[SDL_SCANCODE_COMMA] = 0x41,
+	[SDL_SCANCODE_PERIOD] = 0x49,
+	[SDL_SCANCODE_SLASH] = 0x4A,
+	[SDL_SCANCODE_CAPSLOCK] = 0x58,
+	[SDL_SCANCODE_F1] = 0x05,
+	[SDL_SCANCODE_F2] = 0x06,
+	[SDL_SCANCODE_F3] = 0x04,
+	[SDL_SCANCODE_F4] = 0x0C,
+	[SDL_SCANCODE_F5] = 0x03,
+	[SDL_SCANCODE_F6] = 0x0B,
+	[SDL_SCANCODE_F7] = 0x83,
+	[SDL_SCANCODE_F8] = 0x0A,
+	[SDL_SCANCODE_F9] = 0x01,
+	[SDL_SCANCODE_F10] = 0x09,
+	[SDL_SCANCODE_F11] = 0x78,
+	[SDL_SCANCODE_F12] = 0x07,
+	[SDL_SCANCODE_LCTRL] = 0x14,
+	[SDL_SCANCODE_LSHIFT] = 0x12,
+	[SDL_SCANCODE_LALT] = 0x11,
+	[SDL_SCANCODE_RCTRL] = 0x18,
+	[SDL_SCANCODE_RSHIFT] = 0x59,
+	[SDL_SCANCODE_RALT] = 0x17,
+	[SDL_SCANCODE_INSERT] = 0x81,
+	[SDL_SCANCODE_PAUSE] = 0x82,
+	[SDL_SCANCODE_PRINTSCREEN] = 0x84,
+	[SDL_SCANCODE_SCROLLLOCK] = 0x7E,
+	[SDL_SCANCODE_DELETE] = 0x85,
+	[SDL_SCANCODE_LEFT] = 0x86,
+	[SDL_SCANCODE_HOME] = 0x87,
+	[SDL_SCANCODE_END] = 0x88,
+	[SDL_SCANCODE_UP] = 0x89,
+	[SDL_SCANCODE_DOWN] = 0x8A,
+	[SDL_SCANCODE_PAGEUP] = 0x8B,
+	[SDL_SCANCODE_PAGEDOWN] = 0x8C,
+	[SDL_SCANCODE_RIGHT] = 0x8D,
+	[SDL_SCANCODE_NUMLOCKCLEAR] = 0x77,
+	[SDL_SCANCODE_KP_DIVIDE] = 0x80,
+	[SDL_SCANCODE_KP_MULTIPLY] = 0x7C,
+	[SDL_SCANCODE_KP_MINUS] = 0x7B,
+	[SDL_SCANCODE_KP_PLUS] = 0x79,
+	[SDL_SCANCODE_KP_ENTER] = 0x19,
+	[SDL_SCANCODE_KP_1] = 0x69,
+	[SDL_SCANCODE_KP_2] = 0x72,
+	[SDL_SCANCODE_KP_3] = 0x7A,
+	[SDL_SCANCODE_KP_4] = 0x6B,
+	[SDL_SCANCODE_KP_5] = 0x73,
+	[SDL_SCANCODE_KP_6] = 0x74,
+	[SDL_SCANCODE_KP_7] = 0x6C,
+	[SDL_SCANCODE_KP_8] = 0x75,
+	[SDL_SCANCODE_KP_9] = 0x7D,
+	[SDL_SCANCODE_KP_0] = 0x70,
+	[SDL_SCANCODE_KP_PERIOD] = 0x71,
+};
+
+static drop_handler drag_drop_handler;
+void render_set_drag_drop_handler(drop_handler handler)
+{
+	drag_drop_handler = handler;
+}
+
+static event_handler custom_event_handler;
+void render_set_event_handler(event_handler handler)
+{
+	custom_event_handler = handler;
+}
+
+static int find_joystick_index(SDL_JoystickID instanceID)
+{
+	for (int i = 0; i < MAX_JOYSTICKS; i++) {
+		if (joysticks[i] && SDL_JoystickInstanceID(joysticks[i]) == instanceID) {
+			return i;
+		}
+	}
+	return -1;
+}
+
+static int lowest_unused_joystick_index()
+{
+	for (int i = 0; i < MAX_JOYSTICKS; i++) {
+		if (!joysticks[i]) {
+			return i;
+		}
+	}
+	return -1;
+}
+
 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 int32_t handle_event(SDL_Event *event)
+{
+	if (custom_event_handler) {
+		custom_event_handler(event);
+	}
+	switch (event->type) {
+	case SDL_KEYDOWN:
+		handle_keydown(event->key.keysym.sym, scancode_map[event->key.keysym.scancode]);
+		break;
+	case SDL_KEYUP:
+		handle_keyup(event->key.keysym.sym, scancode_map[event->key.keysym.scancode]);
+		break;
+	case SDL_JOYBUTTONDOWN:
+		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);
+		break;
+	case SDL_JOYHATMOTION:
+		handle_joy_dpad(find_joystick_index(event->jbutton.which), 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);
+		break;
+	case SDL_JOYDEVICEADDED:
+		if (event->jdevice.which < MAX_JOYSTICKS) {
+			int index = lowest_unused_joystick_index();
+			if (index >= 0) {
+				SDL_Joystick * joy = joysticks[index] = SDL_JoystickOpen(event->jdevice.which);
+				joystick_sdl_index[index] = 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));
+					handle_joy_added(index);
+				}
+			}
+		}
+		break;
+	case SDL_JOYDEVICEREMOVED: {
+		int index = find_joystick_index(event->jdevice.which);
+		if (index >= 0) {
+			SDL_JoystickClose(joysticks[index]);
+			joysticks[index] = NULL;
+			printf("Joystick %d removed\n", index);
+		} else {
+			printf("Failed to find removed joystick with instance ID: %d\n", index);
+		}
+		break;
+	}
+	case SDL_MOUSEMOTION:
+		handle_mouse_moved(event->motion.which, event->motion.x, event->motion.y + overscan_top[video_standard], event->motion.xrel, event->motion.yrel);
+		break;
+	case SDL_MOUSEBUTTONDOWN:
+		handle_mousedown(event->button.which, event->button.button);
+		break;
+	case SDL_MOUSEBUTTONUP:
+		handle_mouseup(event->button.which, event->button.button);
+		break;
+	case SDL_WINDOWEVENT:
+		switch (event->window.event)
+		{
+		case SDL_WINDOWEVENT_SIZE_CHANGED:
+			main_width = event->window.data1;
+			main_height = event->window.data2;
+			update_aspect();
+#ifndef DISABLE_OPENGL
+			if (render_gl) {
+				if (on_context_destroyed) {
+					on_context_destroyed();
+				}
+				SDL_GL_DeleteContext(main_context);
+				main_context = SDL_GL_CreateContext(main_window);
+				gl_setup();
+				if (on_context_created) {
+					on_context_created();
+				}
+			}
+#endif
+			break;
+		}
+		break;
+	case SDL_DROPFILE:
+		if (drag_drop_handler) {
+			drag_drop_handler(event->drop.file);
+		}
+		SDL_free(event->drop.file);
+		break;
+	case SDL_QUIT:
+		puts("");
+		exit(0);
+	}
+	return 0;
+}
+
+static void drain_events()
+{
+	SDL_Event event;
+	while(SDL_PollEvent(&event))
+	{
+		handle_event(&event);
+	}
+}
+
 static char *vid_std_names[NUM_VID_STD] = {"ntsc", "pal"};
 static int display_hz;
 static int source_hz;
 static int source_frame;
 static int source_frame_count;
 static int frame_repeat[60];
-void render_init(int width, int height, char * title, uint8_t fullscreen)
+
+static void init_audio()
 {
-	if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0) {
-		fatal_error("Unable to init SDL: %s\n", SDL_GetError());
+	SDL_AudioSpec desired, actual;
+    char * rate_str = tern_find_path(config, "audio\0rate\0", TVAL_PTR).ptrval;
+   	int rate = rate_str ? atoi(rate_str) : 0;
+   	if (!rate) {
+   		rate = 48000;
+   	}
+    desired.freq = rate;
+	desired.format = AUDIO_S16SYS;
+	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);
+    desired.samples = samples*2;
+	desired.callback = sync_to_audio ? audio_callback : audio_callback_drc;
+	desired.userdata = NULL;
+
+	if (SDL_OpenAudio(&desired, &actual) < 0) {
+		fatal_error("Unable to open SDL audio: %s\n", SDL_GetError());
 	}
-	atexit(SDL_Quit);
-	if (height <= 0) {
-		float aspect = config_aspect() > 0.0f ? config_aspect() : 4.0f/3.0f;
-		height = ((float)width / aspect) + 0.5f;
+	buffer_samples = actual.samples;
+	sample_rate = actual.freq;
+	printf("Initialized audio at frequency %d with a %d sample buffer, ", actual.freq, actual.samples);
+	if (actual.format == AUDIO_S16SYS) {
+		puts("signed 16-bit int format");
+		mix = mix_s16;
+	} else if (actual.format == AUDIO_F32SYS) {
+		puts("32-bit float format");
+		mix = mix_f32;
+	} else {
+		printf("unsupported format %X\n", actual.format);
+		warning("Unsupported audio sample format: %X\n", actual.format);
+		mix = mix_null;
 	}
-	printf("width: %d, height: %d\n", width, height);
-	windowed_width = width;
-	windowed_height = height;
-	
+}
+
+void window_setup(void)
+{
 	uint32_t flags = SDL_WINDOW_RESIZABLE;
-	
-	SDL_DisplayMode mode;
-	//TODO: Explicit multiple monitor support
-	SDL_GetCurrentDisplayMode(0, &mode);
-	display_hz = mode.refresh_rate;
-
-	if (fullscreen) {
+	if (is_fullscreen) {
 		flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
-		//the SDL2 migration guide suggests setting width and height to 0 when using SDL_WINDOW_FULLSCREEN_DESKTOP
-		//but that doesn't seem to work right when using OpenGL, at least on Linux anyway
-		width = mode.w;
-		height = mode.h;
 	}
-	main_width = width;
-	main_height = height;
-	is_fullscreen = fullscreen;
 	
 	tern_val def = {.ptrval = "video"};
 	char *sync_src = tern_find_path_default(config, "system\0sync_source\0", def, TVAL_PTR).ptrval;
 	sync_to_audio = !strcmp(sync_src, "audio");
-
-	render_gl = 0;
-	char *vsync;
+	
+	const char *vsync;
 	if (sync_to_audio) {
 		def.ptrval = "off";
 		vsync = tern_find_path_default(config, "video\0vsync\0", def, TVAL_PTR).ptrval;
@@ -703,7 +973,7 @@
 			}
 		}
 	}
-
+	
 #ifndef DISABLE_OPENGL
 	char *gl_enabled_str = tern_find_path_default(config, "video\0gl\0", def, TVAL_PTR).ptrval;
 	uint8_t gl_enabled = strcmp(gl_enabled_str, "off") != 0;
@@ -717,7 +987,7 @@
 		SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
 	}
 #endif
-	main_window = SDL_CreateWindow(title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, flags);
+	main_window = SDL_CreateWindow(caption, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, main_width, main_height, flags);
 	if (!main_window) {
 		fatal_error("Unable to create SDL window: %s\n", SDL_GetError());
 	}
@@ -761,8 +1031,8 @@
 			fatal_error("unable to create SDL renderer: %s\n", SDL_GetError());
 		}
 		main_clip.x = main_clip.y = 0;
-		main_clip.w = width;
-		main_clip.h = height;
+		main_clip.w = main_width;
+		main_clip.h = main_height;
 #ifndef DISABLE_OPENGL
 	}
 #endif
@@ -773,48 +1043,45 @@
 	render_alloc_surfaces();
 	def.ptrval = "off";
 	scanlines = !strcmp(tern_find_path_default(config, "video\0scanlines\0", def, TVAL_PTR).ptrval, "on");
+}
 
+void render_init(int width, int height, char * title, uint8_t fullscreen)
+{
+	if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0) {
+		fatal_error("Unable to init SDL: %s\n", SDL_GetError());
+	}
+	atexit(SDL_Quit);
+	if (height <= 0) {
+		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);
+	windowed_width = width;
+	windowed_height = height;
+	
+	SDL_DisplayMode mode;
+	//TODO: Explicit multiple monitor support
+	SDL_GetCurrentDisplayMode(0, &mode);
+	display_hz = mode.refresh_rate;
+
+	if (fullscreen) {
+		//the SDL2 migration guide suggests setting width and height to 0 when using SDL_WINDOW_FULLSCREEN_DESKTOP
+		//but that doesn't seem to work right when using OpenGL, at least on Linux anyway
+		width = mode.w;
+		height = mode.h;
+	}
+	main_width = width;
+	main_height = height;
+	is_fullscreen = fullscreen;
+	
 	caption = title;
+	
+	window_setup();
 
 	audio_mutex = SDL_CreateMutex();
 	audio_ready = SDL_CreateCond();
-
-	SDL_AudioSpec desired, actual;
-    char * rate_str = tern_find_path(config, "audio\0rate\0", TVAL_PTR).ptrval;
-   	int rate = rate_str ? atoi(rate_str) : 0;
-   	if (!rate) {
-   		rate = 48000;
-   	}
-    desired.freq = rate;
-	desired.format = AUDIO_S16SYS;
-	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);
-    desired.samples = samples*2;
-	desired.callback = sync_to_audio ? audio_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);
-	if (actual.format == AUDIO_S16SYS) {
-		puts("signed 16-bit int format");
-		mix = mix_s16;
-	} else if (actual.format == AUDIO_F32SYS) {
-		puts("32-bit float format");
-		mix = mix_f32;
-	} else {
-		printf("unsupported format %X\n", actual.format);
-		warning("Unsupported audio sample format: %X\n", actual.format);
-		mix = mix_null;
-	}
+	
+	init_audio();
 	
 	uint32_t db_size;
 	char *db_data = read_bundled_file("gamecontrollerdb.txt", &db_size);
@@ -830,6 +1097,78 @@
 
 	atexit(render_quit);
 }
+#include<unistd.h>
+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) {
+		if (on_context_destroyed) {
+			on_context_destroyed();
+		}
+		SDL_GL_DeleteContext(main_context);
+	} else {
+#endif
+		SDL_DestroyRenderer(main_renderer);
+#ifndef DISABLE_OPENGL
+	}
+#endif
+	in_toggle = 1;
+	SDL_DestroyWindow(main_window);
+	drain_events();
+	
+	char *config_width = tern_find_path(config, "video\0width\0", TVAL_PTR).ptrval;
+	if (config_width) {
+		windowed_width = atoi(config_width);
+	}
+	char *config_height = tern_find_path(config, "video\0height\0", TVAL_PTR).ptrval;
+	if (config_height) {
+		windowed_height = atoi(config_height);
+	} else {
+		float aspect = config_aspect() > 0.0f ? config_aspect() : 4.0f/3.0f;
+		windowed_height = ((float)windowed_width / aspect) + 0.5f;
+	}
+	char *config_fullscreen = tern_find_path(config, "video\0fullscreen\0", TVAL_PTR).ptrval;
+	is_fullscreen = config_fullscreen && !strcmp("on", config_fullscreen);
+	if (is_fullscreen) {
+		SDL_DisplayMode mode;
+		//TODO: Multiple monitor support
+		SDL_GetCurrentDisplayMode(0, &mode);
+		main_width = mode.w;
+		main_height = mode.h;
+	} else {
+		main_width = windowed_width;
+		main_height = windowed_height;
+	}
+	
+	window_setup();
+	update_aspect();
+#ifndef DISABLE_OPENGL
+	//need to check render_gl again after window_setup as render option could have changed
+	if (render_gl && on_context_created) {
+		on_context_created();
+	}
+#endif
+
+	SDL_CloseAudio();
+	init_audio();
+	
+	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++)
+		{
+			double alpha = audio_sources[i]->dt / (audio_sources[i]->dt + rc);
+			int32_t lowpass_alpha = (int32_t)(((double)0x10000) * alpha);
+			audio_sources[i]->lowpass_alpha = lowpass_alpha;
+		}
+	unlock_audio();
+	drain_events();
+	in_toggle = 0;
+}
 
 SDL_Window *render_get_window(void)
 {
@@ -1211,26 +1550,6 @@
 	}
 }
 
-static int find_joystick_index(SDL_JoystickID instanceID)
-{
-	for (int i = 0; i < MAX_JOYSTICKS; i++) {
-		if (joysticks[i] && SDL_JoystickInstanceID(joysticks[i]) == instanceID) {
-			return i;
-		}
-	}
-	return -1;
-}
-
-static int lowest_unused_joystick_index()
-{
-	for (int i = 0; i < MAX_JOYSTICKS; i++) {
-		if (!joysticks[i]) {
-			return i;
-		}
-	}
-	return -1;
-}
-
 int32_t render_translate_input_name(int32_t controller, char *name, uint8_t is_axis)
 {
 	static tern_node *button_lookup, *axis_lookup;
@@ -1318,232 +1637,6 @@
 	return input & 0xFFFFFFF;
 }
 
-static uint8_t scancode_map[SDL_NUM_SCANCODES] = {
-	[SDL_SCANCODE_A] = 0x1C,
-	[SDL_SCANCODE_B] = 0x32,
-	[SDL_SCANCODE_C] = 0x21,
-	[SDL_SCANCODE_D] = 0x23,
-	[SDL_SCANCODE_E] = 0x24,
-	[SDL_SCANCODE_F] = 0x2B,
-	[SDL_SCANCODE_G] = 0x34,
-	[SDL_SCANCODE_H] = 0x33,
-	[SDL_SCANCODE_I] = 0x43,
-	[SDL_SCANCODE_J] = 0x3B,
-	[SDL_SCANCODE_K] = 0x42,
-	[SDL_SCANCODE_L] = 0x4B,
-	[SDL_SCANCODE_M] = 0x3A,
-	[SDL_SCANCODE_N] = 0x31,
-	[SDL_SCANCODE_O] = 0x44,
-	[SDL_SCANCODE_P] = 0x4D,
-	[SDL_SCANCODE_Q] = 0x15,
-	[SDL_SCANCODE_R] = 0x2D,
-	[SDL_SCANCODE_S] = 0x1B,
-	[SDL_SCANCODE_T] = 0x2C,
-	[SDL_SCANCODE_U] = 0x3C,
-	[SDL_SCANCODE_V] = 0x2A,
-	[SDL_SCANCODE_W] = 0x1D,
-	[SDL_SCANCODE_X] = 0x22,
-	[SDL_SCANCODE_Y] = 0x35,
-	[SDL_SCANCODE_Z] = 0x1A,
-	[SDL_SCANCODE_1] = 0x16,
-	[SDL_SCANCODE_2] = 0x1E,
-	[SDL_SCANCODE_3] = 0x26,
-	[SDL_SCANCODE_4] = 0x25,
-	[SDL_SCANCODE_5] = 0x2E,
-	[SDL_SCANCODE_6] = 0x36,
-	[SDL_SCANCODE_7] = 0x3D,
-	[SDL_SCANCODE_8] = 0x3E,
-	[SDL_SCANCODE_9] = 0x46,
-	[SDL_SCANCODE_0] = 0x45,
-	[SDL_SCANCODE_RETURN] = 0x5A,
-	[SDL_SCANCODE_ESCAPE] = 0x76,
-	[SDL_SCANCODE_SPACE] = 0x29,
-	[SDL_SCANCODE_TAB] = 0x0D,
-	[SDL_SCANCODE_BACKSPACE] = 0x66,
-	[SDL_SCANCODE_MINUS] = 0x4E,
-	[SDL_SCANCODE_EQUALS] = 0x55,
-	[SDL_SCANCODE_LEFTBRACKET] = 0x54,
-	[SDL_SCANCODE_RIGHTBRACKET] = 0x5B,
-	[SDL_SCANCODE_BACKSLASH] = 0x5D,
-	[SDL_SCANCODE_SEMICOLON] = 0x4C,
-	[SDL_SCANCODE_APOSTROPHE] = 0x52,
-	[SDL_SCANCODE_GRAVE] = 0x0E,
-	[SDL_SCANCODE_COMMA] = 0x41,
-	[SDL_SCANCODE_PERIOD] = 0x49,
-	[SDL_SCANCODE_SLASH] = 0x4A,
-	[SDL_SCANCODE_CAPSLOCK] = 0x58,
-	[SDL_SCANCODE_F1] = 0x05,
-	[SDL_SCANCODE_F2] = 0x06,
-	[SDL_SCANCODE_F3] = 0x04,
-	[SDL_SCANCODE_F4] = 0x0C,
-	[SDL_SCANCODE_F5] = 0x03,
-	[SDL_SCANCODE_F6] = 0x0B,
-	[SDL_SCANCODE_F7] = 0x83,
-	[SDL_SCANCODE_F8] = 0x0A,
-	[SDL_SCANCODE_F9] = 0x01,
-	[SDL_SCANCODE_F10] = 0x09,
-	[SDL_SCANCODE_F11] = 0x78,
-	[SDL_SCANCODE_F12] = 0x07,
-	[SDL_SCANCODE_LCTRL] = 0x14,
-	[SDL_SCANCODE_LSHIFT] = 0x12,
-	[SDL_SCANCODE_LALT] = 0x11,
-	[SDL_SCANCODE_RCTRL] = 0x18,
-	[SDL_SCANCODE_RSHIFT] = 0x59,
-	[SDL_SCANCODE_RALT] = 0x17,
-	[SDL_SCANCODE_INSERT] = 0x81,
-	[SDL_SCANCODE_PAUSE] = 0x82,
-	[SDL_SCANCODE_PRINTSCREEN] = 0x84,
-	[SDL_SCANCODE_SCROLLLOCK] = 0x7E,
-	[SDL_SCANCODE_DELETE] = 0x85,
-	[SDL_SCANCODE_LEFT] = 0x86,
-	[SDL_SCANCODE_HOME] = 0x87,
-	[SDL_SCANCODE_END] = 0x88,
-	[SDL_SCANCODE_UP] = 0x89,
-	[SDL_SCANCODE_DOWN] = 0x8A,
-	[SDL_SCANCODE_PAGEUP] = 0x8B,
-	[SDL_SCANCODE_PAGEDOWN] = 0x8C,
-	[SDL_SCANCODE_RIGHT] = 0x8D,
-	[SDL_SCANCODE_NUMLOCKCLEAR] = 0x77,
-	[SDL_SCANCODE_KP_DIVIDE] = 0x80,
-	[SDL_SCANCODE_KP_MULTIPLY] = 0x7C,
-	[SDL_SCANCODE_KP_MINUS] = 0x7B,
-	[SDL_SCANCODE_KP_PLUS] = 0x79,
-	[SDL_SCANCODE_KP_ENTER] = 0x19,
-	[SDL_SCANCODE_KP_1] = 0x69,
-	[SDL_SCANCODE_KP_2] = 0x72,
-	[SDL_SCANCODE_KP_3] = 0x7A,
-	[SDL_SCANCODE_KP_4] = 0x6B,
-	[SDL_SCANCODE_KP_5] = 0x73,
-	[SDL_SCANCODE_KP_6] = 0x74,
-	[SDL_SCANCODE_KP_7] = 0x6C,
-	[SDL_SCANCODE_KP_8] = 0x75,
-	[SDL_SCANCODE_KP_9] = 0x7D,
-	[SDL_SCANCODE_KP_0] = 0x70,
-	[SDL_SCANCODE_KP_PERIOD] = 0x71,
-};
-
-static drop_handler drag_drop_handler;
-void render_set_drag_drop_handler(drop_handler handler)
-{
-	drag_drop_handler = handler;
-}
-
-static ui_render_fun on_context_destroyed, on_context_created;
-void render_set_gl_context_handlers(ui_render_fun destroy, ui_render_fun create)
-{
-	on_context_destroyed = destroy;
-	on_context_created = create;
-}
-
-static event_handler custom_event_handler;
-void render_set_event_handler(event_handler handler)
-{
-	custom_event_handler = handler;
-}
-
-static int32_t handle_event(SDL_Event *event)
-{
-	if (custom_event_handler) {
-		custom_event_handler(event);
-	}
-	switch (event->type) {
-	case SDL_KEYDOWN:
-		handle_keydown(event->key.keysym.sym, scancode_map[event->key.keysym.scancode]);
-		break;
-	case SDL_KEYUP:
-		handle_keyup(event->key.keysym.sym, scancode_map[event->key.keysym.scancode]);
-		break;
-	case SDL_JOYBUTTONDOWN:
-		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);
-		break;
-	case SDL_JOYHATMOTION:
-		handle_joy_dpad(find_joystick_index(event->jbutton.which), 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);
-		break;
-	case SDL_JOYDEVICEADDED:
-		if (event->jdevice.which < MAX_JOYSTICKS) {
-			int index = lowest_unused_joystick_index();
-			if (index >= 0) {
-				SDL_Joystick * joy = joysticks[index] = SDL_JoystickOpen(event->jdevice.which);
-				joystick_sdl_index[index] = 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));
-					handle_joy_added(index);
-				}
-			}
-		}
-		break;
-	case SDL_JOYDEVICEREMOVED: {
-		int index = find_joystick_index(event->jdevice.which);
-		if (index >= 0) {
-			SDL_JoystickClose(joysticks[index]);
-			joysticks[index] = NULL;
-			printf("Joystick %d removed\n", index);
-		} else {
-			printf("Failed to find removed joystick with instance ID: %d\n", index);
-		}
-		break;
-	}
-	case SDL_MOUSEMOTION:
-		handle_mouse_moved(event->motion.which, event->motion.x, event->motion.y + overscan_top[video_standard], event->motion.xrel, event->motion.yrel);
-		break;
-	case SDL_MOUSEBUTTONDOWN:
-		handle_mousedown(event->button.which, event->button.button);
-		break;
-	case SDL_MOUSEBUTTONUP:
-		handle_mouseup(event->button.which, event->button.button);
-		break;
-	case SDL_WINDOWEVENT:
-		switch (event->window.event)
-		{
-		case SDL_WINDOWEVENT_SIZE_CHANGED:
-			main_width = event->window.data1;
-			main_height = event->window.data2;
-			update_aspect();
-#ifndef DISABLE_OPENGL
-			if (render_gl) {
-				if (on_context_destroyed) {
-					on_context_destroyed();
-				}
-				SDL_GL_DeleteContext(main_context);
-				main_context = SDL_GL_CreateContext(main_window);
-				gl_setup();
-				if (on_context_created) {
-					on_context_created();
-				}
-			}
-#endif
-			break;
-		}
-		break;
-	case SDL_DROPFILE:
-		if (drag_drop_handler) {
-			drag_drop_handler(event->drop.file);
-		}
-		SDL_free(event->drop.file);
-		break;
-	case SDL_QUIT:
-		puts("");
-		exit(0);
-	}
-	return 0;
-}
-
-static void drain_events()
-{
-	SDL_Event event;
-	while(SDL_PollEvent(&event))
-	{
-		handle_event(&event);
-	}
-}
-
 void process_events()
 {
 	if (events_processed > MAX_EVENT_POLL_PER_FRAME) {
@@ -1556,7 +1649,6 @@
 #define TOGGLE_MIN_DELAY 250
 void render_toggle_fullscreen()
 {
-	static int in_toggle;
 	//protect against event processing causing us to attempt to toggle while still toggling
 	if (in_toggle) {
 		return;