changeset 20544:ef2b5b14c3bc draft

-Codechange: [OpenGL] Use shaders to display the video buffer on screen.
author Michael Lutz <michi@icosahedron.de>
date Mon, 03 Jun 2013 23:01:11 +0200
parents 9b23c51de4ae
children ed076b6fca0b
files projects/openttd_vs100.vcxproj projects/openttd_vs100.vcxproj.filters projects/openttd_vs80.vcproj projects/openttd_vs90.vcproj source.list src/table/opengl_shader.h src/video/opengl.cpp src/video/opengl.h
diffstat 8 files changed, 197 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/projects/openttd_vs100.vcxproj
+++ b/projects/openttd_vs100.vcxproj
@@ -879,6 +879,7 @@
     <ClInclude Include="..\src\table\landscape_sprite.h" />
     <ClInclude Include="..\src\table\newgrf_debug_data.h" />
     <ClInclude Include="..\src\table\object_land.h" />
+    <ClInclude Include="..\src\table\opengl_shader.h" />
     <ClInclude Include="..\src\table\palette_convert.h" />
     <ClInclude Include="..\src\table\palettes.h" />
     <ClInclude Include="..\src\table\pricebase.h" />
--- a/projects/openttd_vs100.vcxproj.filters
+++ b/projects/openttd_vs100.vcxproj.filters
@@ -1866,6 +1866,9 @@
     <ClInclude Include="..\src\table\object_land.h">
       <Filter>Tables</Filter>
     </ClInclude>
+    <ClInclude Include="..\src\table\opengl_shader.h">
+      <Filter>Tables</Filter>
+    </ClInclude>
     <ClInclude Include="..\src\table\palette_convert.h">
       <Filter>Tables</Filter>
     </ClInclude>
--- a/projects/openttd_vs80.vcproj
+++ b/projects/openttd_vs80.vcproj
@@ -2815,6 +2815,10 @@
 				>
 			</File>
 			<File
+				RelativePath=".\..\src\table\opengl_shader.h"
+				>
+			</File>
+			<File
 				RelativePath=".\..\src\table\palette_convert.h"
 				>
 			</File>
--- a/projects/openttd_vs90.vcproj
+++ b/projects/openttd_vs90.vcproj
@@ -2812,6 +2812,10 @@
 				>
 			</File>
 			<File
+				RelativePath=".\..\src\table\opengl_shader.h"
+				>
+			</File>
+			<File
 				RelativePath=".\..\src\table\palette_convert.h"
 				>
 			</File>
--- a/source.list
+++ b/source.list
@@ -641,6 +641,7 @@
 table/landscape_sprite.h
 table/newgrf_debug_data.h
 table/object_land.h
+table/opengl_shader.h
 table/palette_convert.h
 table/palettes.h
 table/pricebase.h
new file mode 100644
--- /dev/null
+++ b/src/table/opengl_shader.h
@@ -0,0 +1,28 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** @file opengl_shader.h OpenGL shader programs. */
+
+/** Vertex shader that just passes colour and tex coords through. */
+static const char *_vertex_shader_direct[] = {
+	"#version 110\n",
+	"void main() {",
+	"  gl_TexCoord[0] = gl_MultiTexCoord0;",
+	"  gl_Position = gl_Vertex;",
+	"}",
+};
+
+/** Fragment shader that reads the fragment colour from a 32bpp texture. */
+static const char *_frag_shader_direct[] = {
+	"#version 110\n",
+	"uniform sampler2D colour_tex;",
+	"void main() {",
+	"  gl_FragColor = texture2D(colour_tex, gl_TexCoord[0].st);",
+	"}",
+};
--- a/src/video/opengl.cpp
+++ b/src/video/opengl.cpp
@@ -34,6 +34,8 @@
 #include "../gfx_func.h"
 #include "../debug.h"
 
+#include "../table/opengl_shader.h"
+
 
 static PFNGLGENBUFFERSPROC _glGenBuffers;
 static PFNGLDELETEBUFFERSPROC _glDeleteBuffers;
@@ -46,6 +48,22 @@
 static PFNGLDELETEVERTEXARRAYSPROC _glDeleteVertexArrays;
 static PFNGLBINDVERTEXARRAYPROC _glBindVertexArray;
 
+static PFNGLCREATEPROGRAMPROC _glCreateProgram;
+static PFNGLDELETEPROGRAMPROC _glDeleteProgram;
+static PFNGLLINKPROGRAMPROC _glLinkProgram;
+static PFNGLUSEPROGRAMPROC _glUseProgram;
+static PFNGLGETPROGRAMIVPROC _glGetProgramiv;
+static PFNGLGETPROGRAMINFOLOGPROC _glGetProgramInfoLog;
+static PFNGLCREATESHADERPROC _glCreateShader;
+static PFNGLDELETESHADERPROC _glDeleteShader;
+static PFNGLSHADERSOURCEPROC _glShaderSource;
+static PFNGLCOMPILESHADERPROC _glCompileShader;
+static PFNGLATTACHSHADERPROC _glAttachShader;
+static PFNGLGETSHADERIVPROC _glGetShaderiv;
+static PFNGLGETSHADERINFOLOGPROC _glGetShaderInfoLog;
+static PFNGLGETUNIFORMLOCATIONPROC _glGetUniformLocation;
+static PFNGLUNIFORM1IPROC _glUniform1i;
+
 /** A simple 2D vertex with just position and texture. */
 struct Simple2DVertex {
 	float x, y;
@@ -192,6 +210,49 @@
 	return _glGenVertexArrays != NULL && _glDeleteVertexArrays != NULL && _glBindVertexArray != NULL;
 }
 
+/** Bind extension functions for shader support. */
+static bool BindShaderExtensions()
+{
+	if (IsOpenGLVersionAtLeast(2, 0)) {
+		_glCreateProgram = (PFNGLCREATEPROGRAMPROC)GetOGLProcAddress("glCreateProgram");
+		_glDeleteProgram = (PFNGLDELETEPROGRAMPROC)GetOGLProcAddress("glDeleteProgram");
+		_glLinkProgram = (PFNGLLINKPROGRAMPROC)GetOGLProcAddress("glLinkProgram");
+		_glUseProgram = (PFNGLUSEPROGRAMPROC)GetOGLProcAddress("glUseProgram");
+		_glGetProgramiv = (PFNGLGETPROGRAMIVPROC)GetOGLProcAddress("glGetProgramiv");
+		_glGetProgramInfoLog = (PFNGLGETPROGRAMINFOLOGPROC)GetOGLProcAddress("glGetProgramInfoLog");
+		_glCreateShader = (PFNGLCREATESHADERPROC)GetOGLProcAddress("glCreateShader");
+		_glDeleteShader = (PFNGLDELETESHADERPROC)GetOGLProcAddress("glDeleteShader");
+		_glShaderSource = (PFNGLSHADERSOURCEPROC)GetOGLProcAddress("glShaderSource");
+		_glCompileShader = (PFNGLCOMPILESHADERPROC)GetOGLProcAddress("glCompileShader");
+		_glAttachShader = (PFNGLATTACHSHADERPROC)GetOGLProcAddress("glAttachShader");
+		_glGetShaderiv = (PFNGLGETSHADERIVPROC)GetOGLProcAddress("glGetShaderiv");
+		_glGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC)GetOGLProcAddress("glGetShaderInfoLog");
+		_glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC)GetOGLProcAddress("glGetUniformLocation");
+		_glUniform1i = (PFNGLUNIFORM1IPROC)GetOGLProcAddress("glUniform1i");
+	} else {
+		/* In the ARB extension programs and shaders are in the same object space. */
+		_glCreateProgram = (PFNGLCREATEPROGRAMPROC)GetOGLProcAddress("glCreateProgramObjectARB");
+		_glDeleteProgram = (PFNGLDELETEPROGRAMPROC)GetOGLProcAddress("glDeleteObjectARB");
+		_glLinkProgram = (PFNGLLINKPROGRAMPROC)GetOGLProcAddress("glLinkProgramARB");
+		_glUseProgram = (PFNGLUSEPROGRAMPROC)GetOGLProcAddress("glUseProgramObjectARB");
+		_glGetProgramiv = (PFNGLGETPROGRAMIVPROC)GetOGLProcAddress("glGetObjectParameterivARB");
+		_glGetProgramInfoLog = (PFNGLGETPROGRAMINFOLOGPROC)GetOGLProcAddress("glGetInfoLogARB");
+		_glCreateShader = (PFNGLCREATESHADERPROC)GetOGLProcAddress("glCreateShaderObjectARB");
+		_glDeleteShader = (PFNGLDELETESHADERPROC)GetOGLProcAddress("glDeleteObjectARB");
+		_glShaderSource = (PFNGLSHADERSOURCEPROC)GetOGLProcAddress("glShaderSourceARB");
+		_glCompileShader = (PFNGLCOMPILESHADERPROC)GetOGLProcAddress("glCompileShaderARB");
+		_glAttachShader = (PFNGLATTACHSHADERPROC)GetOGLProcAddress("glAttachObjectARB");
+		_glGetShaderiv = (PFNGLGETSHADERIVPROC)GetOGLProcAddress("glGetObjectParameterivARB");
+		_glGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC)GetOGLProcAddress("glGetInfoLogARB");
+		_glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC)GetOGLProcAddress("glGetUniformLocationARB");
+		_glUniform1i = (PFNGLUNIFORM1IPROC)GetOGLProcAddress("glUniform1iARB");
+	}
+
+	return _glCreateProgram != NULL && _glDeleteProgram != NULL && _glLinkProgram != NULL && _glGetProgramiv != NULL && _glGetProgramInfoLog != NULL &&
+		_glCreateShader != NULL && _glDeleteShader != NULL && _glShaderSource != NULL && _glCompileShader != NULL && _glAttachShader != NULL &&
+		_glGetShaderiv != NULL && _glGetShaderInfoLog != NULL && _glGetUniformLocation != NULL && _glUniform1i != NULL;
+}
+
 
 /**
  * Construct OpenGL back-end class.
@@ -205,6 +266,7 @@
  */
 OpenGLBackend::~OpenGLBackend()
 {
+	_glDeleteProgram(this->vid_program);
 	_glDeleteVertexArrays(1, &this->vao_quad);
 	_glDeleteBuffers(1, &this->vbo_quad);
 	_glDeleteBuffers(1, &this->vid_pbo);
@@ -237,6 +299,13 @@
 	/* Check for vertex array objects. */
 	if (!IsOpenGLVersionAtLeast(3, 0) && (!IsOpenGLExtensionSupported("GL_ARB_vertex_array_object") || !IsOpenGLExtensionSupported("GL_APPLE_vertex_array_object"))) return "Vertex array objects not supported";
 	if (!BindVBAExtension()) return "Failed to bind VBA extension functions";
+	/* Check for shader objects. */
+	if (!IsOpenGLVersionAtLeast(2, 0) && !IsOpenGLExtensionSupported("GL_ARB_shader_objects")) return "No shader support";
+	if (!BindShaderExtensions()) return "Failed to bind shader extension functions";
+
+	DEBUG(driver, 2, "OpenGL shading language version: %s", (const char *)glGetString(GL_SHADING_LANGUAGE_VERSION));
+
+	if (!this->InitShaders()) return "Failed to initialize shaders";
 
 	/* Setup video buffer texture. */
 	glGenTextures(1, &this->vid_texture);
@@ -249,6 +318,11 @@
 	glBindTexture(GL_TEXTURE_2D, 0);
 	if (glGetError() != GL_NO_ERROR) return "Can't generate video buffer texture";
 
+	/* Bind texture to shader program. */
+	GLint tex_location = _glGetUniformLocation(this->vid_program, "colour_tex");
+	_glUseProgram(this->vid_program);
+	_glUniform1i(tex_location, 0); // Texture unit 0.
+
 	/* Create pixel buffer object as video buffer storage. */
 	_glGenBuffers(1, &this->vid_pbo);
 	_glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->vid_pbo);
@@ -273,6 +347,7 @@
 	_glBindBuffer(GL_ARRAY_BUFFER, this->vbo_quad);
 	_glBufferData(GL_ARRAY_BUFFER, sizeof(vert_array), vert_array, GL_STATIC_DRAW);
 	if (glGetError() != GL_NO_ERROR) return "Can't generate VBO for fullscreen quad";
+
 	/* Set vertex state. */
 	glEnableClientState(GL_VERTEX_ARRAY);
 	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
@@ -282,12 +357,88 @@
 
 	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
 	glDisable(GL_DEPTH_TEST);
-	glEnable(GL_TEXTURE_2D);
 
 	return NULL;
 }
 
 /**
+ * Check a shader for compilation errors and log them if necessary.
+ * @param shader Shader to check.
+ * @return True if the shader is valid.
+ */
+static bool VerifyShader(GLuint shader)
+{
+	static ReusableBuffer<char> log_buf;
+
+	GLint result;
+	_glGetShaderiv(shader, GL_COMPILE_STATUS, &result);
+
+	/* Output log if there is one. */
+	GLint log_len;
+	_glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_len);
+	if (log_len > 0) {
+		_glGetShaderInfoLog(shader, log_len, NULL, log_buf.Allocate(log_len));
+		DEBUG(driver, result != GL_TRUE ? 0 : 2, "%s", log_buf.GetBuffer()); // Always print on failure.
+	}
+
+	return result == GL_TRUE;
+}
+
+/**
+ * Check a program for link errors and log them if necessary.
+ * @param program Program to check.
+ * @return True if the program is valid.
+ */
+static bool VerifyProgram(GLuint program)
+{
+	static ReusableBuffer<char> log_buf;
+
+	GLint result;
+	_glGetProgramiv(program, GL_LINK_STATUS, &result);
+
+	/* Output log if there is one. */
+	GLint log_len;
+	_glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_len);
+	if (log_len > 0) {
+		_glGetProgramInfoLog(program, log_len, NULL, log_buf.Allocate(log_len));
+		DEBUG(driver, result != GL_TRUE ? 0 : 2, "%s", log_buf.GetBuffer()); // Always print on failure.
+	}
+
+	return result == GL_TRUE;
+}
+
+/**
+ * Create all needed shader programs.
+ * @return True if successful, false otherwise.
+ */
+bool OpenGLBackend::InitShaders()
+{
+	/* Create vertex shader. */
+	GLuint vert_shader = _glCreateShader(GL_VERTEX_SHADER);
+	_glShaderSource(vert_shader, lengthof(_vertex_shader_direct), _vertex_shader_direct, NULL);
+	_glCompileShader(vert_shader);
+	if (!VerifyShader(vert_shader)) return false;
+
+	/* Create fragment shader. */
+	GLuint frag_shader = _glCreateShader(GL_FRAGMENT_SHADER);
+	_glShaderSource(frag_shader, lengthof(_frag_shader_direct), _frag_shader_direct, NULL);
+	_glCompileShader(frag_shader);
+	if (!VerifyShader(frag_shader)) return false;
+
+	/* Link shaders to program. */
+	this->vid_program = _glCreateProgram();
+	_glAttachShader(this->vid_program, vert_shader);
+	_glAttachShader(this->vid_program, frag_shader);
+	_glLinkProgram(this->vid_program);
+	if (!VerifyProgram(this->vid_program)) return false;
+
+	_glDeleteShader(vert_shader);
+	_glDeleteShader(frag_shader);
+
+	return true;
+}
+
+/**
  * Change the size of the drawing window and allocate matching resources.
  * @param w New width of the window.
  * @param h New height of the window.
@@ -325,6 +476,7 @@
 
 	/* Blit video buffer to screen. */
 	glBindTexture(GL_TEXTURE_2D, this->vid_texture);
+	_glUseProgram(this->vid_program);
 	_glBindVertexArray(this->vao_quad);
 	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
 
--- a/src/video/opengl.h
+++ b/src/video/opengl.h
@@ -24,9 +24,12 @@
 private:
 	GLuint vid_pbo;     ///< Pixel buffer object storing the memory used for the video driver to draw to.
 	GLuint vid_texture; ///< Texture handle for the video buffer texture.
+	GLuint vid_program; ///< Shader program for rendering the video buffer.
 	GLuint vao_quad;    ///< Vertex array object storing the rendering state for the fullscreen quad.
 	GLuint vbo_quad;    ///< Vertex buffer with a fullscreen quad.
 
+	bool InitShaders();
+
 public:
 	OpenGLBackend();
 	~OpenGLBackend();