diff --git a/src/m_anigif.c b/src/m_anigif.c
index 56d3c87075fc7a8779e04c46a9467d5fb70df6d1..f761db143b0cb016ee0395e9f045c8cdeb49d444 100644
--- a/src/m_anigif.c
+++ b/src/m_anigif.c
@@ -20,6 +20,10 @@
 #include "i_video.h"
 #include "m_misc.h"
 
+#ifdef HWRENDER
+#include "hardware/hw_main.h"
+#endif
+
 // GIFs are always little-endian
 #include "byteptr.h"
 
@@ -29,6 +33,7 @@ consvar_t cv_gif_downscale =  {"gif_downscale", "On", CV_SAVE, CV_OnOff, NULL, 0
 #ifdef HAVE_ANIGIF
 static boolean gif_optimize = false; // So nobody can do something dumb
 static boolean gif_downscale = false; // like changing cvars mid output
+static RGBA_t *gif_palette = NULL;
 
 static FILE *gif_out = NULL;
 static INT32 gif_frames = 0;
@@ -428,10 +433,7 @@ static void GIF_headwrite(void)
 
 	// write color table
 	{
-		RGBA_t *pal = ((cv_screenshot_colorprofile.value)
-		? pLocalPalette
-		: pMasterPalette);
-
+		RGBA_t *pal = gif_palette;
 		for (i = 0; i < 256; i++)
 		{
 			WRITEUINT8(p, pal[i].s.red);
@@ -457,6 +459,32 @@ const UINT8 gifframe_gchead[4] = {0x21,0xF9,0x04,0x04}; // GCE, bytes, packed by
 static UINT8 *gifframe_data = NULL;
 static size_t gifframe_size = 8192;
 
+#ifdef HWRENDER
+static void hwrconvert(void)
+{
+	UINT8 *linear = HWR_GetScreenshot();
+	UINT8 *dest = screens[2];
+	UINT8 r, g, b;
+	INT32 x, y;
+	size_t i = 0;
+
+	InitColorLUT(gif_palette);
+
+	for (y = 0; y < vid.height; y++)
+	{
+		for (x = 0; x < vid.width; x++, i += 3)
+		{
+			r = (UINT8)linear[i];
+			g = (UINT8)linear[i + 1];
+			b = (UINT8)linear[i + 2];
+			dest[(y * vid.width) + x] = colorlookup[r >> SHIFTCOLORBITS][g >> SHIFTCOLORBITS][b >> SHIFTCOLORBITS];
+		}
+	}
+
+	free(linear);
+}
+#endif
+
 //
 // GIF_framewrite
 // writes a frame into the file.
@@ -482,7 +510,12 @@ static void GIF_framewrite(void)
 		GIF_optimizeregion(cur_screen, movie_screen, &blitx, &blity, &blitw, &blith);
 
 		// blit to temp screen
-		I_ReadScreen(movie_screen);
+		if (rendermode == render_soft)
+			I_ReadScreen(movie_screen);
+#ifdef HWRENDER
+		else if (rendermode == render_opengl)
+			hwrconvert();
+#endif
 	}
 	else
 	{
@@ -491,7 +524,18 @@ static void GIF_framewrite(void)
 		blith = vid.height;
 
 		if (gif_frames == 0)
-			I_ReadScreen(movie_screen);
+		{
+			if (rendermode == render_soft)
+				I_ReadScreen(movie_screen);
+#ifdef HWRENDER
+			else if (rendermode == render_opengl)
+			{
+				hwrconvert();
+				VID_BlitLinearScreen(screens[2], screens[0], vid.width*vid.bpp, vid.height, vid.width*vid.bpp, vid.rowbytes);
+			}
+#endif
+		}
+
 		movie_screen = screens[0];
 	}
 
@@ -580,7 +624,7 @@ static void GIF_framewrite(void)
 //
 INT32 GIF_open(const char *filename)
 {
-#ifdef HWRENDER
+#if 0
 	if (rendermode != render_soft)
 	{
 		CONS_Alert(CONS_WARNING, M_GetText("GIFs cannot be taken in non-software modes!\n"));
@@ -594,6 +638,16 @@ INT32 GIF_open(const char *filename)
 
 	gif_optimize = (!!cv_gif_optimize.value);
 	gif_downscale = (!!cv_gif_downscale.value);
+
+	// GIF color table
+	// In hardware mode, uses the master palette
+	gif_palette = ((cv_screenshot_colorprofile.value
+#ifdef HWRENDER
+	&& (rendermode == render_soft)
+#endif
+	) ? pLocalPalette
+	: pMasterPalette);
+
 	GIF_headwrite();
 	gif_frames = 0;
 	return 1;
diff --git a/src/m_misc.c b/src/m_misc.c
index d973833857a339c4aa61589348aa473c959c0d3d..c8383d3fe1d55df99cdc05bd274bc93824940029 100644
--- a/src/m_misc.c
+++ b/src/m_misc.c
@@ -1161,12 +1161,8 @@ void M_StartMovie(void)
 	switch (cv_moviemode.value)
 	{
 		case MM_GIF:
-			if (rendermode == render_soft)
-			{
-				moviemode = M_StartMovieGIF(pathname);
-				break;
-			}
-			/* FALLTHRU */
+			moviemode = M_StartMovieGIF(pathname);
+			break;
 		case MM_APNG:
 			moviemode = M_StartMovieAPNG(pathname);
 			break;
diff --git a/src/r_data.h b/src/r_data.h
index 3dcb22ec4989e870cf72dc623f3494a902611aba..f028f2f5d16ef54da544d56780448adc84d8b858 100644
--- a/src/r_data.h
+++ b/src/r_data.h
@@ -171,6 +171,8 @@ const char *R_NameForColormap(extracolormap_t *extra_colormap);
 #define R_PutRgbaRGB(r, g, b) (R_PutRgbaR(r) + R_PutRgbaG(g) + R_PutRgbaB(b))
 #define R_PutRgbaRGBA(r, g, b, a) (R_PutRgbaRGB(r, g, b) + R_PutRgbaA(a))
 
+UINT8 NearestColor(UINT8 r, UINT8 g, UINT8 b);
+
 extern INT32 numtextures;
 
 #endif
diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c
index c876a323f3fbbce1b4a716994cc92a34a09c8913..e054381eec6183cba9b46d4948dc4f0723a0b088 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -1441,6 +1441,7 @@ INT32 VID_SetMode(INT32 modeNum)
 	//Impl_SetWindowName("SRB2 "VERSIONSTRING);
 
 	SDLSetMode(vid.width, vid.height, USE_FULLSCREEN);
+	Impl_VideoSetupBuffer();
 
 	if (rendermode == render_soft)
 	{
@@ -1449,8 +1450,6 @@ INT32 VID_SetMode(INT32 modeNum)
 			SDL_FreeSurface(bufSurface);
 			bufSurface = NULL;
 		}
-
-		Impl_VideoSetupBuffer();
 	}
 
 	return SDL_TRUE;
@@ -1573,7 +1572,7 @@ static void Impl_VideoSetupSDLBuffer(void)
 static void Impl_VideoSetupBuffer(void)
 {
 	// Set up game's software render buffer
-	if (rendermode == render_soft)
+	//if (rendermode == render_soft)
 	{
 		vid.rowbytes = vid.width * vid.bpp;
 		vid.direct = NULL;
diff --git a/src/v_video.c b/src/v_video.c
index c6ec22767f6f4518e7ba1ee231ac73edf800fc65..c91e5f213e8fb7ec0eca1ddc9d29176dbc2d8440 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -3244,6 +3244,29 @@ Unoptimized version
 #endif
 }
 
+// Taken from my videos-in-SRB2 project
+// Generates a color look-up table
+// which has up to 64 colors at each channel
+// (see the defines in v_video.h)
+
+UINT8 colorlookup[CLUTSIZE][CLUTSIZE][CLUTSIZE];
+
+void InitColorLUT(RGBA_t *palette)
+{
+	UINT8 r, g, b;
+	static boolean clutinit = false;
+	static RGBA_t *lastpalette = NULL;
+	if ((!clutinit) || (lastpalette != palette))
+	{
+		for (r = 0; r < CLUTSIZE; r++)
+			for (g = 0; g < CLUTSIZE; g++)
+				for (b = 0; b < CLUTSIZE; b++)
+					colorlookup[r][g][b] = NearestColor(r << SHIFTCOLORBITS, g << SHIFTCOLORBITS, b << SHIFTCOLORBITS);
+		clutinit = true;
+		lastpalette = palette;
+	}
+}
+
 // V_Init
 // old software stuff, buffers are allocated at video mode setup
 // here we set the screens[x] pointers accordingly
@@ -3255,13 +3278,9 @@ void V_Init(void)
 	const INT32 screensize = vid.rowbytes * vid.height;
 
 	LoadMapPalette();
-	// hardware modes do not use screens[] pointers
+
 	for (i = 0; i < NUMSCREENS; i++)
 		screens[i] = NULL;
-	if (rendermode != render_soft)
-	{
-		return; // be sure to cause a NULL read/write error so we detect it, in case of..
-	}
 
 	// start address of NUMSCREENS * width*height vidbuffers
 	if (base)
diff --git a/src/v_video.h b/src/v_video.h
index 2434fec816b002f3fa61412901ef16f39bfa5f13..36d1914af6edec7f1b9996e4c767d507dcaf8a71 100644
--- a/src/v_video.h
+++ b/src/v_video.h
@@ -37,6 +37,18 @@ cv_allcaps;
 // Allocates buffer screens, call before R_Init.
 void V_Init(void);
 
+// Taken from my videos-in-SRB2 project
+// Generates a color look-up table
+// which has up to 64 colors at each channel
+
+#define COLORBITS 6
+#define SHIFTCOLORBITS (8-COLORBITS)
+#define CLUTSIZE (1<<COLORBITS)
+
+extern UINT8 colorlookup[CLUTSIZE][CLUTSIZE][CLUTSIZE];
+
+void InitColorLUT(RGBA_t *palette);
+
 // Set the current RGB palette lookup to use for palettized graphics
 void V_SetPalette(INT32 palettenum);