Codechange: [OpenGL] Use shaders to display the video buffer on screen.

This commit is contained in:
Michael Lutz 2021-01-16 16:43:20 +01:00
parent b181859629
commit acf59f6b68
4 changed files with 191 additions and 1 deletions

View File

@ -85,3 +85,8 @@ add_files(
unicode.h
water_land.h
)
add_files(
opengl_shader.h
CONDITION OPENGL_FOUND
)

28
src/table/opengl_shader.h Normal file
View File

@ -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);",
"}",
};

View File

@ -28,6 +28,9 @@
#include "../gfx_func.h"
#include "../debug.h"
#include "../table/opengl_shader.h"
#include "../safeguards.h"
@ -45,6 +48,22 @@ static PFNGLGENVERTEXARRAYSPROC _glGenVertexArrays;
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;
@ -171,6 +190,49 @@ static bool BindVBAExtension()
return _glGenVertexArrays != nullptr && _glDeleteVertexArrays != nullptr && _glBindVertexArray != nullptr;
}
/** 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 != nullptr && _glDeleteProgram != nullptr && _glLinkProgram != nullptr && _glGetProgramiv != nullptr && _glGetProgramInfoLog != nullptr &&
_glCreateShader != nullptr && _glDeleteShader != nullptr && _glShaderSource != nullptr && _glCompileShader != nullptr && _glAttachShader != nullptr &&
_glGetShaderiv != nullptr && _glGetShaderInfoLog != nullptr && _glGetUniformLocation != nullptr && _glUniform1i != nullptr;
}
/** Callback to receive OpenGL debug messages. */
void APIENTRY DebugOutputCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam)
{
@ -261,6 +323,9 @@ OpenGLBackend::OpenGLBackend()
*/
OpenGLBackend::~OpenGLBackend()
{
if (_glDeleteProgram != nullptr) {
_glDeleteProgram(this->vid_program);
}
if (_glDeleteVertexArrays != nullptr) _glDeleteVertexArrays(1, &this->vao_quad);
if (_glDeleteBuffers != nullptr) {
_glDeleteBuffers(1, &this->vbo_quad);
@ -302,6 +367,13 @@ const char *OpenGLBackend::Init()
/* 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") || !IsOpenGLExtensionSupported("GL_ARB_fragment_shader") || !IsOpenGLExtensionSupported("GL_ARB_vertex_shader"))) 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);
@ -314,6 +386,11 @@ const char *OpenGLBackend::Init()
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);
@ -338,6 +415,7 @@ const char *OpenGLBackend::Init()
_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);
@ -347,12 +425,88 @@ const char *OpenGLBackend::Init()
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glDisable(GL_DEPTH_TEST);
glEnable(GL_TEXTURE_2D);
(void)glGetError(); // Clear errors.
return nullptr;
}
/**
* 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 = GL_FALSE;
_glGetShaderiv(shader, GL_COMPILE_STATUS, &result);
/* Output log if there is one. */
GLint log_len = 0;
_glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_len);
if (log_len > 0) {
_glGetShaderInfoLog(shader, log_len, nullptr, 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 = GL_FALSE;
_glGetProgramiv(program, GL_LINK_STATUS, &result);
/* Output log if there is one. */
GLint log_len = 0;
_glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_len);
if (log_len > 0) {
_glGetProgramInfoLog(program, log_len, nullptr, 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, nullptr);
_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, nullptr);
_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.
@ -393,6 +547,7 @@ void OpenGLBackend::Paint()
/* 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);
}

View File

@ -28,6 +28,7 @@ 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.
@ -35,6 +36,7 @@ private:
~OpenGLBackend();
const char *Init();
bool InitShaders();
public:
/** Get singleton instance of this class. */