diff --git a/src/gfxinit.cpp b/src/gfxinit.cpp --- a/src/gfxinit.cpp +++ b/src/gfxinit.cpp @@ -263,7 +263,7 @@ static bool SwitchNewGRFBlitter() * between multiple 32bpp blitters, which perform differently with 8bpp sprites. */ uint depth_wanted_by_base = BaseGraphics::GetUsedSet()->blitter == BLT_32BPP ? 32 : 8; - uint depth_wanted_by_grf = _support8bpp == S8BPP_NONE ? 32 : 8; + uint depth_wanted_by_grf = _support8bpp != S8BPP_NONE || VideoDriver::GetInstance()->HasEfficient8Bpp() ? 8 : 32; for (GRFConfig *c = _grfconfig; c != nullptr; c = c->next) { if (c->status == GCS_DISABLED || c->status == GCS_NOT_FOUND || HasBit(c->flags, GCF_INIT_ONLY)) continue; if (c->palette & GRFP_BLT_32BPP) depth_wanted_by_grf = 32; diff --git a/src/table/opengl_shader.h b/src/table/opengl_shader.h --- a/src/table/opengl_shader.h +++ b/src/table/opengl_shader.h @@ -51,3 +51,28 @@ static const char *_frag_shader_direct_1 " colour = texture(colour_tex, colour_tex_uv);", "}", }; + +/** Fragment shader that performs a palette lookup to read the colour from an 8bpp texture. */ +static const char *_frag_shader_palette[] = { + "#version 110\n", + "uniform sampler2D colour_tex;", + "uniform sampler1D palette;", + "varying vec2 colour_tex_uv;", + "void main() {", + " float idx = texture2D(colour_tex, colour_tex_uv).r;", + " gl_FragData[0] = texture1D(palette, idx);", + "}", +}; + +/** GLSL 1.50 fragment shader that performs a palette lookup to read the colour from an 8bpp texture. */ +static const char *_frag_shader_palette_150[] = { + "#version 150\n", + "uniform sampler2D colour_tex;", + "uniform sampler1D palette;", + "in vec2 colour_tex_uv;", + "out vec4 colour;", + "void main() {", + " float idx = texture(colour_tex, colour_tex_uv).r;", + " colour = texture(palette, idx);", + "}", +}; diff --git a/src/video/opengl.cpp b/src/video/opengl.cpp --- a/src/video/opengl.cpp +++ b/src/video/opengl.cpp @@ -23,10 +23,12 @@ #include "../3rdparty/opengl/glext.h" #include "opengl.h" +#include "../core/geometry_func.hpp" #include "../core/mem_func.hpp" -#include "../core/geometry_func.hpp" +#include "../core/math_func.hpp" #include "../gfx_func.h" #include "../debug.h" +#include "../blitter/factory.hpp" #include "../table/opengl_shader.h" @@ -37,6 +39,8 @@ static PFNGLDEBUGMESSAGECONTROLPROC _glDebugMessageControl; static PFNGLDEBUGMESSAGECALLBACKPROC _glDebugMessageCallback; +static PFNGLACTIVETEXTUREPROC _glActiveTexture; + static PFNGLGENBUFFERSPROC _glGenBuffers; static PFNGLDELETEBUFFERSPROC _glDeleteBuffers; static PFNGLBINDBUFFERPROC _glBindBuffer; @@ -155,6 +159,18 @@ bool IsOpenGLVersionAtLeast(byte major, return (_gl_major_ver > major) || (_gl_major_ver == major && _gl_minor_ver >= minor); } +/** Bind texture-related extension functions. */ +static bool BindTextureExtensions() +{ + if (IsOpenGLVersionAtLeast(1, 3)) { + _glActiveTexture = (PFNGLACTIVETEXTUREPROC)GetOGLProcAddress("glActiveTexture"); + } else { + _glActiveTexture = (PFNGLACTIVETEXTUREPROC)GetOGLProcAddress("glActiveTextureARB"); + } + + return _glActiveTexture != nullptr; +} + /** Bind vertex buffer object extension functions. */ static bool BindVBOExtension() { @@ -351,6 +367,7 @@ OpenGLBackend::~OpenGLBackend() { if (_glDeleteProgram != nullptr) { _glDeleteProgram(this->vid_program); + _glDeleteProgram(this->pal_program); } if (_glDeleteVertexArrays != nullptr) _glDeleteVertexArrays(1, &this->vao_quad); if (_glDeleteBuffers != nullptr) { @@ -358,6 +375,7 @@ OpenGLBackend::~OpenGLBackend() _glDeleteBuffers(1, &this->vid_pbo); } glDeleteTextures(1, &this->vid_texture); + glDeleteTextures(1, &this->pal_texture); } /** @@ -385,6 +403,9 @@ const char *OpenGLBackend::Init() if (!IsOpenGLVersionAtLeast(1, 3)) return "OpenGL version >= 1.3 required"; /* Check for non-power-of-two texture support. */ if (!IsOpenGLVersionAtLeast(2, 0) && !IsOpenGLExtensionSupported("GL_ARB_texture_non_power_of_two")) return "Non-power-of-two textures not supported"; + /* Check for single element texture formats. */ + if (!IsOpenGLVersionAtLeast(3, 0) && !IsOpenGLExtensionSupported("GL_ARB_texture_rg")) return "Single element texture formats not supported"; + if (!BindTextureExtensions()) return "Failed to bind texture extension functions"; /* Check for vertex buffer objects. */ if (!IsOpenGLVersionAtLeast(1, 5) && !IsOpenGLExtensionSupported("ARB_vertex_buffer_object")) return "Vertex buffer objects not supported"; if (!BindVBOExtension()) return "Failed to bind VBO extension functions"; @@ -413,10 +434,31 @@ 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. */ + /* Setup palette texture. */ + glGenTextures(1, &this->pal_texture); + glBindTexture(GL_TEXTURE_1D, this->pal_texture); + glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAX_LEVEL, 0); + glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA8, 256, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, nullptr); + glBindTexture(GL_TEXTURE_1D, 0); + if (glGetError() != GL_NO_ERROR) return "Can't generate palette lookup texture"; + + /* Bind uniforms in RGB rendering shader program. */ GLint tex_location = _glGetUniformLocation(this->vid_program, "colour_tex"); + GLint palette_location = _glGetUniformLocation(this->vid_program, "palette"); _glUseProgram(this->vid_program); - _glUniform1i(tex_location, 0); // Texture unit 0. + _glUniform1i(tex_location, 0); // Texture unit 0. + _glUniform1i(palette_location, 1); // Texture unit 1. + + /* Bind uniforms in palette rendering shader program. */ + tex_location = _glGetUniformLocation(this->pal_program, "colour_tex"); + palette_location = _glGetUniformLocation(this->pal_program, "palette"); + _glUseProgram(this->pal_program); + _glUniform1i(tex_location, 0); // Texture unit 0. + _glUniform1i(palette_location, 1); // Texture unit 1. /* Create pixel buffer object as video buffer storage. */ _glGenBuffers(1, &this->vid_pbo); @@ -525,27 +567,42 @@ bool OpenGLBackend::InitShaders() _glCompileShader(vert_shader); if (!VerifyShader(vert_shader)) return false; - /* Create fragment shader. */ - GLuint frag_shader = _glCreateShader(GL_FRAGMENT_SHADER); - _glShaderSource(frag_shader, glsl_150 ? lengthof(_frag_shader_direct_150) : lengthof(_frag_shader_direct), glsl_150 ? _frag_shader_direct_150 : _frag_shader_direct, nullptr); - _glCompileShader(frag_shader); - if (!VerifyShader(frag_shader)) return false; + /* Create fragment shader for plain RGBA. */ + GLuint frag_shader_rgb = _glCreateShader(GL_FRAGMENT_SHADER); + _glShaderSource(frag_shader_rgb, glsl_150 ? lengthof(_frag_shader_direct_150) : lengthof(_frag_shader_direct), glsl_150 ? _frag_shader_direct_150 : _frag_shader_direct, nullptr); + _glCompileShader(frag_shader_rgb); + if (!VerifyShader(frag_shader_rgb)) return false; + + /* Create fragment shader for paletted only. */ + GLuint frag_shader_pal = _glCreateShader(GL_FRAGMENT_SHADER); + _glShaderSource(frag_shader_pal, glsl_150 ? lengthof(_frag_shader_palette_150) : lengthof(_frag_shader_palette), glsl_150 ? _frag_shader_palette_150 : _frag_shader_palette, nullptr); + _glCompileShader(frag_shader_pal); + if (!VerifyShader(frag_shader_pal)) return false; /* Link shaders to program. */ this->vid_program = _glCreateProgram(); _glAttachShader(this->vid_program, vert_shader); - _glAttachShader(this->vid_program, frag_shader); + _glAttachShader(this->vid_program, frag_shader_rgb); + + this->pal_program = _glCreateProgram(); + _glAttachShader(this->pal_program, vert_shader); + _glAttachShader(this->pal_program, frag_shader_pal); if (glsl_150) { /* Bind fragment shader outputs. */ _glBindFragDataLocation(this->vid_program, 0, "colour"); + _glBindFragDataLocation(this->pal_program, 0, "colour"); } _glLinkProgram(this->vid_program); if (!VerifyProgram(this->vid_program)) return false; + _glLinkProgram(this->pal_program); + if (!VerifyProgram(this->pal_program)) return false; + _glDeleteShader(vert_shader); - _glDeleteShader(frag_shader); + _glDeleteShader(frag_shader_rgb); + _glDeleteShader(frag_shader_pal); return true; } @@ -561,27 +618,56 @@ bool OpenGLBackend::Resize(int w, int h, { if (!force && _screen.width == w && _screen.height == h) return false; + int bpp = BlitterFactory::GetCurrentBlitter()->GetScreenDepth(); + int pitch = bpp != 32 ? Align(w, 4) : w; + glViewport(0, 0, w, h); /* Re-allocate video buffer texture and backing store. */ _glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->vid_pbo); - _glBufferData(GL_PIXEL_UNPACK_BUFFER, w * h * 4, nullptr, GL_DYNAMIC_READ); // Buffer content has to persist from frame to frame and is read back by the blitter, which means a READ usage hint. + _glBufferData(GL_PIXEL_UNPACK_BUFFER, pitch * h * bpp / 8, nullptr, GL_DYNAMIC_READ); // Buffer content has to persist from frame to frame and is read back by the blitter, which means a READ usage hint. _glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + _glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, this->vid_texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, nullptr); + switch (bpp) { + case 8: + glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr); + break; + + default: + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, nullptr); + break; + } glBindTexture(GL_TEXTURE_2D, 0); /* Set new viewport. */ _screen.height = h; _screen.width = w; - _screen.pitch = w; + _screen.pitch = pitch; _screen.dst_ptr = nullptr; return true; } /** + * Update the stored palette. + * @param pal Palette array with at least 256 elements. + * @param first First entry to update. + * @param length Number of entries to update. + */ +void OpenGLBackend::UpdatePalette(const Colour *pal, uint first, uint length) +{ + assert(first + length <= 256); + + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + _glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + _glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_1D, this->pal_texture); + glTexSubImage1D(GL_TEXTURE_1D, 0, first, length, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, pal + first); +} + +/** * Render video buffer to the screen. */ void OpenGLBackend::Paint() @@ -589,8 +675,11 @@ void OpenGLBackend::Paint() glClear(GL_COLOR_BUFFER_BIT); /* Blit video buffer to screen. */ + _glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, this->vid_texture); - _glUseProgram(this->vid_program); + _glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_1D, this->pal_texture); + _glUseProgram(BlitterFactory::GetCurrentBlitter()->GetScreenDepth() == 8 ? this->pal_program : this->vid_program); _glBindVertexArray(this->vao_quad); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } @@ -618,9 +707,18 @@ void OpenGLBackend::ReleaseVideoBuffer(c /* Update changed rect of the video buffer texture. */ if (!IsEmptyRect(update_rect)) { + _glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, this->vid_texture); glPixelStorei(GL_UNPACK_ROW_LENGTH, _screen.pitch); - glTexSubImage2D(GL_TEXTURE_2D, 0, update_rect.left, update_rect.top, update_rect.right - update_rect.left, update_rect.bottom - update_rect.top, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, (GLvoid *)(size_t)(update_rect.top * _screen.pitch * 4 + update_rect.left * 4)); + switch (BlitterFactory::GetCurrentBlitter()->GetScreenDepth()) { + case 8: + glTexSubImage2D(GL_TEXTURE_2D, 0, update_rect.left, update_rect.top, update_rect.right - update_rect.left, update_rect.bottom - update_rect.top, GL_RED, GL_UNSIGNED_BYTE, (GLvoid *)(size_t)(update_rect.top * _screen.pitch + update_rect.left)); + break; + + default: + glTexSubImage2D(GL_TEXTURE_2D, 0, update_rect.left, update_rect.top, update_rect.right - update_rect.left, update_rect.bottom - update_rect.top, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, (GLvoid *)(size_t)(update_rect.top * _screen.pitch * 4 + update_rect.left * 4)); + break; + } } } diff --git a/src/video/opengl.h b/src/video/opengl.h --- a/src/video/opengl.h +++ b/src/video/opengl.h @@ -14,6 +14,7 @@ #include "../core/alloc_type.hpp" #include "../core/geometry_type.hpp" +#include "../gfx_type.h" typedef void (*OGLProc)(); typedef OGLProc (*GetOGLProcAddressProc)(const char *proc); @@ -28,9 +29,11 @@ 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 vid_program; ///< Shader program for rendering a RGBA video buffer. + GLuint pal_program; ///< Shader program for rendering a paletted 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. + GLuint pal_texture; ///< Palette lookup texture. OpenGLBackend(); ~OpenGLBackend(); @@ -47,6 +50,7 @@ public: static const char *Create(GetOGLProcAddressProc get_proc); static void Destroy(); + void UpdatePalette(const Colour *pal, uint first, uint length); bool Resize(int w, int h, bool force = false); void Paint(); diff --git a/src/video/video_driver.hpp b/src/video/video_driver.hpp --- a/src/video/video_driver.hpp +++ b/src/video/video_driver.hpp @@ -99,6 +99,15 @@ public: } /** + * Has this video driver an efficient code path for palette animated 8-bpp sprites? + * @return True if the driver has an efficient code path for 8-bpp. + */ + virtual bool HasEfficient8Bpp() const + { + return false; + } + + /** * An edit box lost the input focus. Abort character compositing if necessary. */ virtual void EditBoxLostFocus() {} diff --git a/src/video/win32_v.cpp b/src/video/win32_v.cpp --- a/src/video/win32_v.cpp +++ b/src/video/win32_v.cpp @@ -1417,7 +1417,7 @@ static FVideoDriver_Win32OpenGL iFVideoD const char *VideoDriver_Win32OpenGL::Start(const StringList ¶m) { - if (BlitterFactory::GetCurrentBlitter()->GetScreenDepth() != 32) return "Only 32bpp blitters supported"; + if (BlitterFactory::GetCurrentBlitter()->GetScreenDepth() == 0) return "Only real blitters supported"; Dimension old_res = _cur_resolution; // Save current screen resolution in case of errors, as MakeWindow invalidates it. this->vsync = GetDriverParamBool(param, "vsync"); @@ -1565,6 +1565,9 @@ void VideoDriver_Win32OpenGL::Paint() break; case Blitter::PALETTE_ANIMATION_VIDEO_BACKEND: + OpenGLBackend::Get()->UpdatePalette(_local_palette.palette, _local_palette.first_dirty, _local_palette.count_dirty); + break; + case Blitter::PALETTE_ANIMATION_NONE: break; diff --git a/src/video/win32_v.h b/src/video/win32_v.h --- a/src/video/win32_v.h +++ b/src/video/win32_v.h @@ -138,6 +138,8 @@ public: bool AfterBlitterChange() override; + bool HasEfficient8Bpp() const override { return true; } + const char *GetName() const override { return "win32-opengl"; } protected: