diff --git a/src/hardware/hw_shaders.c b/src/hardware/hw_shaders.c
index 8bc4cde9caf6daeef3bd83302a81b5be546180b9..cb929ae6e9faa9fd10346c3b5e0560985a5adae1 100644
--- a/src/hardware/hw_shaders.c
+++ b/src/hardware/hw_shaders.c
@@ -370,6 +370,8 @@ static shader_t gl_shaders[NUMSHADERTARGETS*2];
 
 static shadertarget_t gl_shadertargets[NUMSHADERTARGETS];
 
+#define WHITESPACE_CHARS " \t"
+
 #define MODEL_LIGHTING_DEFINE "#define SRB2_MODEL_LIGHTING"
 #define PALETTE_RENDERING_DEFINE "#define SRB2_PALETTE_RENDERING"
 
@@ -416,12 +418,14 @@ static char *HWR_PreprocessShader(char *original)
 	const char *line_ending = "\n";
 	int line_ending_len;
 	char *read_pos = original;
-	int insertion_pos = 0;
 	int original_len = strlen(original);
 	int distance_to_end = original_len;
 	int new_len;
 	char *new_shader;
 	char *write_pos;
+	char shader_glsl_version[3];
+	int version_pos = -1;
+	int version_len = 0;
 
 	if (strstr(original, "\r\n"))
 	{
@@ -446,44 +450,62 @@ static char *HWR_PreprocessShader(char *original)
 
 	line_ending_len = strlen(line_ending);
 
-	// We need to find a place to put the #define commands.
-	// To stay within GLSL specs, they must be *after* the #version define,
-	// if there is any. So we need to look for that. And also let's not
-	// get fooled if there is a #version inside a comment!
+	// Find the #version directive, if it exists. Also don't get fooled if it's
+	// inside a comment. Copy the version digits so they can be used in the preamble.
 	// Time for some string parsing :D
 
 #define STARTSWITH(str, with_what) !strncmp(str, with_what, sizeof(with_what)-1)
-#define ADVANCE(amount) read_pos += amount; distance_to_end -= amount;
+#define ADVANCE(amount) read_pos += (amount); distance_to_end -= (amount);
 	while (true)
 	{
 		// we're at the start of a line or at the end of a block comment.
 		// first get any possible whitespace out of the way
-		int whitespace_len = strspn(read_pos, " \t");
+		int whitespace_len = strspn(read_pos, WHITESPACE_CHARS);
 		if (whitespace_len == distance_to_end)
 			break; // we got to the end
 		ADVANCE(whitespace_len)
 
 		if (STARTSWITH(read_pos, "#version"))
 		{
-			// getting closer
-			INT32 newline_pos = strstr_int(read_pos, line_ending);
-			INT32 line_comment_pos = strstr_int(read_pos, "//");
-			INT32 block_comment_pos = strstr_int(read_pos, "/*");
-			if (newline_pos == INT32_MAX && line_comment_pos == INT32_MAX &&
-				block_comment_pos == INT32_MAX)
+			// found a version directive (and it's not inside a comment)
+			// now locate, verify and read the version number
+			int version_number_len;
+			version_pos = read_pos - original;
+			ADVANCE(sizeof("#version") - 1)
+			whitespace_len = strspn(read_pos, WHITESPACE_CHARS);
+			if (!whitespace_len)
 			{
-				// #version is at the end of the file. Probably not a valid shader.
-				CONS_Alert(CONS_ERROR, "HWR_PreprocessShader: Shader unexpectedly ends after #version.\n");
+				CONS_Alert(CONS_ERROR, "HWR_PreprocessShader: Syntax error in #version. Expected space after #version, but got other text.\n");
 				return NULL;
 			}
-			else
+			else if (whitespace_len == distance_to_end)
 			{
-				// insert at the earliest occurence of newline or comment after #version
-				insertion_pos = min(line_comment_pos, block_comment_pos);
-				insertion_pos = min(newline_pos, insertion_pos);
-				insertion_pos += read_pos - original;
-				break;
+				CONS_Alert(CONS_ERROR, "HWR_PreprocessShader: Syntax error in #version. Expected version number, but got end of file.\n");
+				return NULL;
+			}
+			ADVANCE(whitespace_len)
+			version_number_len = strspn(read_pos, "0123456789");
+			if (!version_number_len)
+			{
+				CONS_Alert(CONS_ERROR, "HWR_PreprocessShader: Syntax error in #version. Expected version number, but got other text.\n");
+				return NULL;
 			}
+			else if (version_number_len != 3)
+			{
+				CONS_Alert(CONS_ERROR, "HWR_PreprocessShader: Syntax error in #version. Expected version with 3 digits, but got %d digits.\n", version_number_len);
+				return NULL;
+			}
+			M_Memcpy(shader_glsl_version, read_pos, 3);
+			ADVANCE(version_number_len)
+			version_len = (read_pos - original) - version_pos;
+			whitespace_len = strspn(read_pos, WHITESPACE_CHARS);
+			ADVANCE(whitespace_len)
+			if (STARTSWITH(read_pos, "es"))
+			{
+				CONS_Alert(CONS_ERROR, "HWR_PreprocessShader: Support for ES shaders is not implemented.\n");
+				return NULL;
+			}
+			break;
 		}
 		else
 		{
@@ -544,12 +566,25 @@ static char *HWR_PreprocessShader(char *original)
 #undef STARTSWITH
 #undef ADVANCE
 
+#define ADD_TO_LEN(def) new_len += sizeof(def) - 1 + line_ending_len;
+
 	// Calculate length of modified shader.
 	new_len = original_len;
 	if (cv_glmodellighting.value)
-		new_len += sizeof(MODEL_LIGHTING_DEFINE) - 1 + 2 * line_ending_len;
+		ADD_TO_LEN(MODEL_LIGHTING_DEFINE)
 	if (cv_glpaletterendering.value)
-		new_len += sizeof(PALETTE_RENDERING_DEFINE) - 1 + 2 * line_ending_len;
+		ADD_TO_LEN(PALETTE_RENDERING_DEFINE)
+
+#undef ADD_TO_LEN
+
+#define VERSION_PART "#version "
+
+	if (new_len != original_len)
+	{
+		if (version_pos != -1)
+			new_len += sizeof(VERSION_PART) - 1 + 3 + line_ending_len;
+		new_len += sizeof("#line 0") - 1 + line_ending_len;
+	}
 
 	// Allocate memory for modified shader.
 	new_shader = Z_Malloc(new_len + 1, PU_STATIC, NULL);
@@ -557,22 +592,27 @@ static char *HWR_PreprocessShader(char *original)
 	read_pos = original;
 	write_pos = new_shader;
 
-	// Copy the part before our additions.
-	M_Memcpy(write_pos, original, insertion_pos);
-	read_pos += insertion_pos;
-	write_pos += insertion_pos;
+	if (new_len != original_len && version_pos != -1)
+	{
+		strcpy(write_pos, VERSION_PART);
+		write_pos += sizeof(VERSION_PART) - 1;
+		M_Memcpy(write_pos, shader_glsl_version, 3);
+		write_pos += 3;
+		strcpy(write_pos, line_ending);
+		write_pos += line_ending_len;
+	}
+
+#undef VERSION_PART
 
 #define WRITE_DEFINE(define) \
 	{ \
-		strcpy(write_pos, line_ending); \
-		write_pos += line_ending_len; \
 		strcpy(write_pos, define); \
 		write_pos += sizeof(define) - 1; \
 		strcpy(write_pos, line_ending); \
 		write_pos += line_ending_len; \
 	}
 
-	// Write the additions.
+	// Write the defines.
 	if (cv_glmodellighting.value)
 		WRITE_DEFINE(MODEL_LIGHTING_DEFINE)
 	if (cv_glpaletterendering.value)
@@ -580,8 +620,26 @@ static char *HWR_PreprocessShader(char *original)
 
 #undef WRITE_DEFINE
 
-	// Copy the part after our additions.
-	M_Memcpy(write_pos, read_pos, original_len - insertion_pos);
+	// Write a #line directive, so compiler errors will report line numbers from the
+	// original shader without our preamble lines.
+	if (new_len != original_len)
+	{
+		// line numbering in the #line directive is different for versions 110-150
+		if (version_pos == -1 || shader_glsl_version[0] == '1')
+			strcpy(write_pos, "#line 0");
+		else
+			strcpy(write_pos, "#line 1");
+		write_pos += sizeof("#line 0") - 1;
+		strcpy(write_pos, line_ending);
+		write_pos += line_ending_len;
+	}
+
+	// Copy the original shader.
+	M_Memcpy(write_pos, read_pos, original_len);
+
+	// Erase the original #version directive, if it exists and was copied.
+	if (new_len != original_len && version_pos != -1)
+		memset(write_pos + version_pos, ' ', version_len);
 
 	// Terminate the new string.
 	new_shader[new_len] = '\0';