diff --git a/src/i_threads.h b/src/i_threads.h
new file mode 100644
index 0000000000000000000000000000000000000000..878e8c388e138d84f6f9fc4eddeb1f23c180d7c6
--- /dev/null
+++ b/src/i_threads.h
@@ -0,0 +1,39 @@
+// SONIC ROBO BLAST 2 KART
+//-----------------------------------------------------------------------------
+// Copyright (C) 2020 by James R.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file  i_threads.h
+/// \brief Multithreading abstraction
+
+#ifdef HAVE_THREADS
+
+#ifndef I_THREADS_H
+#define I_THREADS_H
+
+typedef void (*I_thread_fn)(void *userdata);
+
+typedef void * I_mutex;
+typedef void * I_cond;
+
+void      I_start_threads (void);
+void      I_stop_threads  (void);
+
+void      I_spawn_thread (const char *name, I_thread_fn, void *userdata);
+
+/* check in your thread whether to return early */
+int       I_thread_is_stopped (void);
+
+void      I_lock_mutex      (I_mutex *);
+void      I_unlock_mutex    (I_mutex);
+
+void      I_hold_cond       (I_cond *, I_mutex);
+
+void      I_wake_one_cond   (I_cond);
+void      I_wake_all_cond   (I_cond);
+
+#endif/*I_THREADS_H*/
+#endif/*HAVE_THREADS*/
diff --git a/src/sdl/Makefile.cfg b/src/sdl/Makefile.cfg
index 05b60f7a328ab917363e0a89d870511179e297d3..68e616f01a742dbf9c1636678cfd3db63bb5d843 100644
--- a/src/sdl/Makefile.cfg
+++ b/src/sdl/Makefile.cfg
@@ -88,6 +88,11 @@ else
 endif
 endif
 
+ifndef NOTHREADS
+	OPTS+=-DHAVE_THREADS
+	OBJS+=$(OBJDIR)/i_threads.o
+endif
+
 ifdef SDL_TTF
 	OPTS+=-DHAVE_TTF
 	SDL_LDFLAGS+=-lSDL2_ttf -lfreetype -lz
diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c
index d2ed62516c742cb1fcf4ba63d948202999998287..9ef8e5a110a526b5cbe5d865b016254663eea504 100644
--- a/src/sdl/i_system.c
+++ b/src/sdl/i_system.c
@@ -167,6 +167,7 @@ static char returnWadPath[256];
 #include "../i_video.h"
 #include "../i_sound.h"
 #include "../i_system.h"
+#include "../i_threads.h"
 #include "../screen.h" //vid.WndParent
 #include "../d_net.h"
 #include "../g_game.h"
@@ -2251,6 +2252,10 @@ INT32 I_StartupSystem(void)
 	SDL_version SDLlinked;
 	SDL_VERSION(&SDLcompiled)
 	SDL_GetVersion(&SDLlinked);
+#ifdef HAVE_THREADS
+	I_start_threads();
+	atexit(I_stop_threads);
+#endif
 	I_StartupConsole();
 #ifdef NEWSIGNALHANDLER
 	I_Fork();
diff --git a/src/sdl/i_threads.c b/src/sdl/i_threads.c
new file mode 100644
index 0000000000000000000000000000000000000000..99e574561b3958f841b42f8c324ede87830e8778
--- /dev/null
+++ b/src/sdl/i_threads.c
@@ -0,0 +1,338 @@
+// SONIC ROBO BLAST 2 KART
+//-----------------------------------------------------------------------------
+// Copyright (C) 2020 by James R.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file  i_threads.c
+/// \brief Multithreading abstraction
+
+#include "../doomdef.h"
+#include "../i_threads.h"
+
+#include <SDL.h>
+
+typedef void * (*Create_fn)(void);
+
+struct Link;
+struct Thread;
+
+typedef struct Link   * Link;
+typedef struct Thread * Thread;
+
+struct Link
+{
+	void * data;
+	Link   next;
+	Link   prev;
+};
+
+struct Thread
+{
+	I_thread_fn   entry;
+	void        * userdata;
+
+	SDL_Thread  * thread;
+};
+
+static Link    i_thread_pool;
+static Link    i_mutex_pool;
+static Link    i_cond_pool;
+
+static I_mutex        i_thread_pool_mutex;
+static I_mutex        i_mutex_pool_mutex;
+static I_mutex        i_cond_pool_mutex;
+
+static SDL_atomic_t   i_threads_running = {1};
+
+static Link
+Insert_link (
+		Link * head,
+		Link   link
+){
+	link->prev = NULL;
+	link->next = (*head);
+	if ((*head))
+		(*head)->prev = link;
+	(*head)    = link;
+	return link;
+}
+
+static void
+Free_link (
+		Link * head,
+		Link   link
+){
+	if (link->prev)
+		link->prev->next = link->next;
+	else
+		(*head) = link->next;
+
+	if (link->next)
+		link->next->prev = link->prev;
+
+	free(link->data);
+	free(link);
+}
+
+static Link
+New_link (void *data)
+{
+	Link link;
+
+	link = malloc(sizeof *link);
+
+	if (! link)
+		abort();
+
+	link->data = data;
+
+	return link;
+}
+
+static void *
+Identity (
+		Link      *  pool_anchor,
+		I_mutex      pool_mutex,
+
+		void      ** anchor,
+
+		Create_fn    create_fn
+){
+	void * id;
+
+	id = SDL_AtomicGetPtr(anchor);
+
+	if (! id)
+	{
+		I_lock_mutex(&pool_mutex);
+		{
+			id = SDL_AtomicGetPtr(anchor);
+
+			if (! id)
+			{
+				id = (*create_fn)();
+
+				if (! id)
+					abort();
+
+				Insert_link(pool_anchor, New_link(id));
+
+				SDL_AtomicSetPtr(anchor, id);
+			}
+		}
+		I_unlock_mutex(pool_mutex);
+	}
+
+	return id;
+}
+
+static int
+Worker (
+		Link link
+){
+	Thread th;
+
+	th = link->data;
+
+	(*th->entry)(th->userdata);
+
+	if (SDL_AtomicGet(&i_threads_running))
+	{
+		I_lock_mutex(&i_thread_pool_mutex);
+		{
+			if (SDL_AtomicGet(&i_threads_running))
+			{
+				SDL_DetachThread(th->thread);
+				Free_link(&i_thread_pool, link);
+			}
+		}
+		I_unlock_mutex(i_thread_pool_mutex);
+	}
+
+	return 0;
+}
+
+void
+I_spawn_thread (
+		const char  * name,
+		I_thread_fn   entry,
+		void        * userdata
+){
+	Link   link;
+	Thread th;
+
+	th = malloc(sizeof *th);
+
+	if (! th)
+		abort();/* this is pretty GNU of me */
+
+	th->entry    = entry;
+	th->userdata = userdata;
+
+	I_lock_mutex(&i_thread_pool_mutex);
+	{
+		link = Insert_link(&i_thread_pool, New_link(th));
+
+		if (SDL_AtomicGet(&i_threads_running))
+		{
+			th->thread = SDL_CreateThread(
+					(SDL_ThreadFunction)Worker,
+					name,
+					link
+			);
+
+			if (! th->thread)
+				abort();
+		}
+	}
+	I_unlock_mutex(i_thread_pool_mutex);
+}
+
+int
+I_thread_is_stopped (void)
+{
+	return ( ! SDL_AtomicGet(&i_threads_running) );
+}
+
+void
+I_start_threads (void)
+{
+	i_thread_pool_mutex = SDL_CreateMutex();
+	i_mutex_pool_mutex  = SDL_CreateMutex();
+	i_cond_pool_mutex   = SDL_CreateMutex();
+
+	if (!(
+				i_thread_pool_mutex &&
+				i_mutex_pool_mutex  &&
+				i_cond_pool_mutex
+	)){
+		abort();
+	}
+}
+
+void
+I_stop_threads (void)
+{
+	Link        link;
+	Link        next;
+
+	Thread      th;
+	SDL_mutex * mutex;
+	SDL_cond  * cond;
+
+	if (i_threads_running.value)
+	{
+		/* rely on the good will of thread-san */
+		SDL_AtomicSet(&i_threads_running, 0);
+
+		I_lock_mutex(&i_thread_pool_mutex);
+		{
+			for (
+					link = i_thread_pool;
+					link;
+					link = next
+			){
+				next = link->next;
+				th   = link->data;
+
+				SDL_WaitThread(th->thread, NULL);
+
+				free(th);
+				free(link);
+			}
+		}
+		I_unlock_mutex(i_thread_pool_mutex);
+
+		for (
+				link = i_mutex_pool;
+				link;
+				link = next
+		){
+			next  = link->next;
+			mutex = link->data;
+
+			SDL_DestroyMutex(mutex);
+
+			free(link);
+		}
+
+		for (
+				link = i_cond_pool;
+				link;
+				link = next
+		){
+			next = link->next;
+			cond = link->data;
+
+			SDL_DestroyCond(cond);
+
+			free(link);
+		}
+
+		SDL_DestroyMutex(i_thread_pool_mutex);
+		SDL_DestroyMutex(i_mutex_pool_mutex);
+		SDL_DestroyMutex(i_cond_pool_mutex);
+	}
+}
+
+void
+I_lock_mutex (
+		I_mutex * anchor
+){
+	SDL_mutex * mutex;
+
+	mutex = Identity(
+			&i_mutex_pool,
+			i_mutex_pool_mutex,
+			anchor,
+			(Create_fn)SDL_CreateMutex
+	);
+
+	if (SDL_LockMutex(mutex) == -1)
+		abort();
+}
+
+void
+I_unlock_mutex (
+		I_mutex id
+){
+	if (SDL_UnlockMutex(id) == -1)
+		abort();
+}
+
+void
+I_hold_cond (
+		I_cond  * cond_anchor,
+		I_mutex   mutex_id
+){
+	SDL_cond * cond;
+
+	cond = Identity(
+			&i_cond_pool,
+			i_cond_pool_mutex,
+			cond_anchor,
+			(Create_fn)SDL_CreateCond
+	);
+
+	if (SDL_CondWait(cond, mutex_id) == -1)
+		abort();
+}
+
+void
+I_wake_one_cond (
+		I_cond id
+){
+	if (SDL_CondSignal(id) == -1)
+		abort();
+}
+
+void
+I_wake_all_cond (
+		I_cond id
+){
+	if (SDL_CondBroadcast(id) == -1)
+		abort();
+}