summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMichael Lutz <michi@icosahedron.de>2021-02-24 22:10:25 +0100
committerGitHub <noreply@github.com>2021-02-24 22:10:25 +0100
commite1e8cc38513c0131ad6a5764219b308f912dfee9 (patch)
tree25ab453bbfef04587ea79bca9ddaef5cae41bb08 /src
parent6a8c461e38360d14fc36d77a63150a07c6e64ff9 (diff)
downloadopenttd-e1e8cc38513c0131ad6a5764219b308f912dfee9.tar.xz
Fix #8734: [OpenGL] Apply palette remap to cursor sprites. (#8742)
Diffstat (limited to 'src')
-rw-r--r--src/gfxinit.cpp1
-rw-r--r--src/table/opengl_shader.h55
-rw-r--r--src/video/opengl.cpp115
-rw-r--r--src/video/opengl.h16
4 files changed, 176 insertions, 11 deletions
diff --git a/src/gfxinit.cpp b/src/gfxinit.cpp
index 7884f89b5..f569f6edf 100644
--- a/src/gfxinit.cpp
+++ b/src/gfxinit.cpp
@@ -344,6 +344,7 @@ void GfxLoadSprites()
DEBUG(sprite, 2, "Loading sprite set %d", _settings_game.game_creation.landscape);
SwitchNewGRFBlitter();
+ VideoDriver::GetInstance()->ClearSystemSprites();
ClearFontCache();
GfxInitSpriteMem();
LoadSpriteTables();
diff --git a/src/table/opengl_shader.h b/src/table/opengl_shader.h
index b78938615..5f4244ef1 100644
--- a/src/table/opengl_shader.h
+++ b/src/table/opengl_shader.h
@@ -146,3 +146,58 @@ static const char *_frag_shader_rgb_mask_blend_150[] = {
" colour.rgb = idx > 0.0 ? adj_brightness(remap_col.rgb, max3(rgb_col.rgb)) : rgb_col.rgb;",
"}",
};
+
+/** Fragment shader that performs a palette lookup to read the colour from a sprite texture. */
+static const char *_frag_shader_sprite_blend[] = {
+ "#version 110\n",
+ "#extension GL_ATI_shader_texture_lod: enable\n",
+ "#extension GL_ARB_shader_texture_lod: enable\n",
+ "uniform sampler2D colour_tex;",
+ "uniform sampler1D palette;",
+ "uniform sampler2D remap_tex;",
+ "uniform sampler1D pal;",
+ "uniform float zoom;",
+ "uniform bool rgb;",
+ "uniform bool crash;",
+ "varying vec2 colour_tex_uv;",
+ "",
+ _frag_shader_remap_func,
+ "",
+ "void main() {",
+ " float idx = texture2DLod(remap_tex, colour_tex_uv, zoom).r;",
+ " float r = texture1D(pal, idx).r;",
+ " vec4 remap_col = texture1D(palette, idx);",
+ " vec4 rgb_col = texture2DLod(colour_tex, colour_tex_uv, zoom);",
+ "",
+ " if (crash && idx == 0.0) rgb_col.rgb = vec2(dot(rgb_col.rgb, vec3(0.199325561523, 0.391342163085, 0.076004028320)), 0.0).rrr;"
+ " gl_FragData[0].a = rgb && (r > 0.0 || idx == 0.0) ? rgb_col.a : remap_col.a;",
+ " gl_FragData[0].rgb = idx > 0.0 ? adj_brightness(remap_col.rgb, max3(rgb_col.rgb)) : rgb_col.rgb;",
+ "}",
+};
+
+/** GLSL 1.50 fragment shader that performs a palette lookup to read the colour from a sprite texture. */
+static const char *_frag_shader_sprite_blend_150[] = {
+ "#version 150\n",
+ "uniform sampler2D colour_tex;",
+ "uniform sampler1D palette;",
+ "uniform sampler2D remap_tex;",
+ "uniform sampler1D pal;",
+ "uniform float zoom;",
+ "uniform bool rgb;",
+ "uniform bool crash;",
+ "in vec2 colour_tex_uv;",
+ "out vec4 colour;",
+ "",
+ _frag_shader_remap_func,
+ "",
+ "void main() {",
+ " float idx = textureLod(remap_tex, colour_tex_uv, zoom).r;"
+ " float r = texture(pal, idx).r;",
+ " vec4 remap_col = texture(palette, r);",
+ " vec4 rgb_col = textureLod(colour_tex, colour_tex_uv, zoom);",
+ "",
+ " if (crash && idx == 0.0) rgb_col.rgb = vec2(dot(rgb_col.rgb, vec3(0.199325561523, 0.391342163085, 0.076004028320)), 0.0).rrr;"
+ " colour.a = rgb && (r > 0.0 || idx == 0.0) ? rgb_col.a : remap_col.a;",
+ " colour.rgb = idx > 0.0 ? adj_brightness(remap_col.rgb, max3(rgb_col.rgb)) : rgb_col.rgb;",
+ "}",
+};
diff --git a/src/video/opengl.cpp b/src/video/opengl.cpp
index 4a22744cc..9720ba44e 100644
--- a/src/video/opengl.cpp
+++ b/src/video/opengl.cpp
@@ -36,8 +36,11 @@
#include "../debug.h"
#include "../blitter/factory.hpp"
#include "../zoom_func.h"
+#include <array>
+#include <numeric>
#include "../table/opengl_shader.h"
+#include "../table/sprites.h"
#include "../safeguards.h"
@@ -52,6 +55,7 @@ static PFNGLGENBUFFERSPROC _glGenBuffers;
static PFNGLDELETEBUFFERSPROC _glDeleteBuffers;
static PFNGLBINDBUFFERPROC _glBindBuffer;
static PFNGLBUFFERDATAPROC _glBufferData;
+static PFNGLBUFFERSUBDATAPROC _glBufferSubData;
static PFNGLMAPBUFFERPROC _glMapBuffer;
static PFNGLUNMAPBUFFERPROC _glUnmapBuffer;
static PFNGLCLEARBUFFERSUBDATAPROC _glClearBufferSubData;
@@ -199,6 +203,7 @@ static bool BindVBOExtension()
_glDeleteBuffers = (PFNGLDELETEBUFFERSPROC)GetOGLProcAddress("glDeleteBuffers");
_glBindBuffer = (PFNGLBINDBUFFERPROC)GetOGLProcAddress("glBindBuffer");
_glBufferData = (PFNGLBUFFERDATAPROC)GetOGLProcAddress("glBufferData");
+ _glBufferSubData = (PFNGLBUFFERSUBDATAPROC)GetOGLProcAddress("glBufferSubData");
_glMapBuffer = (PFNGLMAPBUFFERPROC)GetOGLProcAddress("glMapBuffer");
_glUnmapBuffer = (PFNGLUNMAPBUFFERPROC)GetOGLProcAddress("glUnmapBuffer");
} else {
@@ -206,6 +211,7 @@ static bool BindVBOExtension()
_glDeleteBuffers = (PFNGLDELETEBUFFERSPROC)GetOGLProcAddress("glDeleteBuffersARB");
_glBindBuffer = (PFNGLBINDBUFFERPROC)GetOGLProcAddress("glBindBufferARB");
_glBufferData = (PFNGLBUFFERDATAPROC)GetOGLProcAddress("glBufferDataARB");
+ _glBufferSubData = (PFNGLBUFFERSUBDATAPROC)GetOGLProcAddress("glBufferSubDataARB");
_glMapBuffer = (PFNGLMAPBUFFERPROC)GetOGLProcAddress("glMapBufferARB");
_glUnmapBuffer = (PFNGLUNMAPBUFFERPROC)GetOGLProcAddress("glUnmapBufferARB");
}
@@ -216,7 +222,7 @@ static bool BindVBOExtension()
_glClearBufferSubData = nullptr;
}
- return _glGenBuffers != nullptr && _glDeleteBuffers != nullptr && _glBindBuffer != nullptr && _glBufferData != nullptr && _glMapBuffer != nullptr && _glUnmapBuffer != nullptr;
+ return _glGenBuffers != nullptr && _glDeleteBuffers != nullptr && _glBindBuffer != nullptr && _glBufferData != nullptr && _glBufferSubData != nullptr && _glMapBuffer != nullptr && _glUnmapBuffer != nullptr;
}
/** Bind vertex array object extension functions. */
@@ -430,6 +436,7 @@ OpenGLBackend::~OpenGLBackend()
_glDeleteProgram(this->remap_program);
_glDeleteProgram(this->vid_program);
_glDeleteProgram(this->pal_program);
+ _glDeleteProgram(this->sprite_program);
}
if (_glDeleteVertexArrays != nullptr) _glDeleteVertexArrays(1, &this->vao_quad);
if (_glDeleteBuffers != nullptr) {
@@ -505,7 +512,7 @@ const char *OpenGLBackend::Init()
/* Check available texture units. */
GLint max_tex_units = 0;
glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &max_tex_units);
- if (max_tex_units < 3) return "Not enough simultaneous textures supported";
+ if (max_tex_units < 4) return "Not enough simultaneous textures supported";
DEBUG(driver, 2, "OpenGL shading language version: %s, texture units = %d", (const char *)glGetString(GL_SHADING_LANGUAGE_VERSION), (int)max_tex_units);
@@ -580,6 +587,22 @@ const char *OpenGLBackend::Init()
_glUniform1i(tex_location, 0); // Texture unit 0.
_glUniform1i(palette_location, 1); // Texture unit 1.
_glUniform1i(remap_location, 2); // Texture unit 2.
+
+ /* Bind uniforms in sprite shader program. */
+ tex_location = _glGetUniformLocation(this->sprite_program, "colour_tex");
+ palette_location = _glGetUniformLocation(this->sprite_program, "palette");
+ remap_location = _glGetUniformLocation(this->sprite_program, "remap_tex");
+ GLint pal_location = _glGetUniformLocation(this->sprite_program, "pal");
+ this->sprite_sprite_loc = _glGetUniformLocation(this->sprite_program, "sprite");
+ this->sprite_screen_loc = _glGetUniformLocation(this->sprite_program, "screen");
+ this->sprite_zoom_loc = _glGetUniformLocation(this->sprite_program, "zoom");
+ this->sprite_rgb_loc = _glGetUniformLocation(this->sprite_program, "rgb");
+ this->sprite_crash_loc = _glGetUniformLocation(this->sprite_program, "crash");
+ _glUseProgram(this->sprite_program);
+ _glUniform1i(tex_location, 0); // Texture unit 0.
+ _glUniform1i(palette_location, 1); // Texture unit 1.
+ _glUniform1i(remap_location, 2); // Texture unit 2.
+ _glUniform1i(pal_location, 3); // Texture unit 3.
(void)glGetError(); // Clear errors.
/* Create pixel buffer object as video buffer storage. */
@@ -720,6 +743,12 @@ bool OpenGLBackend::InitShaders()
_glCompileShader(remap_shader);
if (!VerifyShader(remap_shader)) return false;
+ /* Sprite fragment shader. */
+ GLuint sprite_shader = _glCreateShader(GL_FRAGMENT_SHADER);
+ _glShaderSource(sprite_shader, glsl_150 ? lengthof(_frag_shader_sprite_blend_150) : lengthof(_frag_shader_sprite_blend), glsl_150 ? _frag_shader_sprite_blend_150 : _frag_shader_sprite_blend, nullptr);
+ _glCompileShader(sprite_shader);
+ if (!VerifyShader(sprite_shader)) return false;
+
/* Link shaders to program. */
this->vid_program = _glCreateProgram();
_glAttachShader(this->vid_program, vert_shader);
@@ -733,11 +762,16 @@ bool OpenGLBackend::InitShaders()
_glAttachShader(this->remap_program, vert_shader);
_glAttachShader(this->remap_program, remap_shader);
+ this->sprite_program = _glCreateProgram();
+ _glAttachShader(this->sprite_program, vert_shader);
+ _glAttachShader(this->sprite_program, sprite_shader);
+
if (glsl_150) {
/* Bind fragment shader outputs. */
_glBindFragDataLocation(this->vid_program, 0, "colour");
_glBindFragDataLocation(this->pal_program, 0, "colour");
_glBindFragDataLocation(this->remap_program, 0, "colour");
+ _glBindFragDataLocation(this->sprite_program, 0, "colour");
}
_glLinkProgram(this->vid_program);
@@ -749,10 +783,14 @@ bool OpenGLBackend::InitShaders()
_glLinkProgram(this->remap_program);
if (!VerifyProgram(this->remap_program)) return false;
+ _glLinkProgram(this->sprite_program);
+ if (!VerifyProgram(this->sprite_program)) return false;
+
_glDeleteShader(vert_shader);
_glDeleteShader(frag_shader_rgb);
_glDeleteShader(frag_shader_pal);
_glDeleteShader(remap_shader);
+ _glDeleteShader(sprite_shader);
return true;
}
@@ -926,7 +964,7 @@ void OpenGLBackend::DrawMouseCursor()
}
}
- this->RenderOglSprite((OpenGLSprite *)this->cursor_cache.Get(sprite)->data, _cursor.pos.x + _cursor.sprite_pos[i].x, _cursor.pos.y + _cursor.sprite_pos[i].y, ZOOM_LVL_GUI);
+ this->RenderOglSprite((OpenGLSprite *)this->cursor_cache.Get(sprite)->data, _cursor.sprite_seq[i].pal, _cursor.pos.x + _cursor.sprite_pos[i].x, _cursor.pos.y + _cursor.sprite_pos[i].y, ZOOM_LVL_GUI);
}
}
@@ -935,6 +973,8 @@ void OpenGLBackend::DrawMouseCursor()
*/
void OpenGLBackend::ClearCursorCache()
{
+ this->last_sprite_pal = (PaletteID)-1;
+
Sprite *sp;
while ((sp = this->cursor_cache.Pop()) != nullptr) {
OpenGLSprite *sprite = (OpenGLSprite *)sp->data;
@@ -1091,20 +1131,41 @@ void OpenGLBackend::ReleaseAnimBuffer(const Rect &update_rect)
* @param y Y position of the sprite.
* @param zoom Zoom level to use.
*/
-void OpenGLBackend::RenderOglSprite(OpenGLSprite *gl_sprite, uint x, uint y, ZoomLevel zoom)
+void OpenGLBackend::RenderOglSprite(OpenGLSprite *gl_sprite, PaletteID pal, uint x, uint y, ZoomLevel zoom)
{
/* Set textures. */
bool rgb = gl_sprite->BindTextures();
_glActiveTexture(GL_TEXTURE0 + 1);
glBindTexture(GL_TEXTURE_1D, this->pal_texture);
+ /* Set palette remap. */
+ _glActiveTexture(GL_TEXTURE0 + 3);
+ if (pal != PAL_NONE) {
+ glBindTexture(GL_TEXTURE_1D, OpenGLSprite::pal_tex);
+ if (pal != this->last_sprite_pal) {
+ /* Different remap palette in use, update texture. */
+ _glBindBuffer(GL_PIXEL_UNPACK_BUFFER, OpenGLSprite::pal_pbo);
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+
+ _glBufferSubData(GL_PIXEL_UNPACK_BUFFER, 0, 256, GetNonSprite(GB(pal, 0, PALETTE_WIDTH), ST_RECOLOUR) + 1);
+ glTexSubImage1D(GL_TEXTURE_1D, 0, 0, 256, GL_RED, GL_UNSIGNED_BYTE, 0);
+
+ _glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+
+ this->last_sprite_pal = pal;
+ }
+ } else {
+ glBindTexture(GL_TEXTURE_1D, OpenGLSprite::pal_identity);
+ }
+
/* Set up shader program. */
Dimension dim = gl_sprite->GetSize(zoom);
- _glUseProgram(this->remap_program);
- _glUniform4f(this->remap_sprite_loc, (float)x, (float)y, (float)dim.width, (float)dim.height);
- _glUniform1f(this->remap_zoom_loc, (float)(zoom - ZOOM_LVL_BEGIN));
- _glUniform2f(this->remap_screen_loc, (float)_screen.width, (float)_screen.height);
- _glUniform1i(this->remap_rgb_loc, rgb ? 1 : 0);
+ _glUseProgram(this->sprite_program);
+ _glUniform4f(this->sprite_sprite_loc, (float)x, (float)y, (float)dim.width, (float)dim.height);
+ _glUniform1f(this->sprite_zoom_loc, (float)(zoom - ZOOM_LVL_BEGIN));
+ _glUniform2f(this->sprite_screen_loc, (float)_screen.width, (float)_screen.height);
+ _glUniform1i(this->sprite_rgb_loc, rgb ? 1 : 0);
+ _glUniform1i(this->sprite_crash_loc, pal == PALETTE_CRASH ? 1 : 0);
_glBindVertexArray(this->vao_quad);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
@@ -1112,6 +1173,9 @@ void OpenGLBackend::RenderOglSprite(OpenGLSprite *gl_sprite, uint x, uint y, Zoo
/* static */ GLuint OpenGLSprite::dummy_tex[] = { 0, 0 };
+/* static */ GLuint OpenGLSprite::pal_identity = 0;
+/* static */ GLuint OpenGLSprite::pal_tex = 0;
+/* static */ GLuint OpenGLSprite::pal_pbo = 0;
/**
* Create all common resources for sprite rendering.
@@ -1144,6 +1208,36 @@ void OpenGLBackend::RenderOglSprite(OpenGLSprite *gl_sprite, uint x, uint y, Zoo
glBindTexture(GL_TEXTURE_2D, OpenGLSprite::dummy_tex[TEX_REMAP]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, 1, 1, 0, GL_RED, GL_UNSIGNED_BYTE, &pal);
+ /* Create palette remap textures. */
+ std::array<uint8, 256> identity_pal;
+ std::iota(std::begin(identity_pal), std::end(identity_pal), 0);
+
+ /* Permanent texture for identity remap. */
+ glGenTextures(1, &OpenGLSprite::pal_identity);
+ glBindTexture(GL_TEXTURE_1D, OpenGLSprite::pal_identity);
+ 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_R8, 256, 0, GL_RED, GL_UNSIGNED_BYTE, identity_pal.data());
+
+ /* Dynamically updated texture for remaps. */
+ glGenTextures(1, &OpenGLSprite::pal_tex);
+ glBindTexture(GL_TEXTURE_1D, OpenGLSprite::pal_tex);
+ 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_R8, 256, 0, GL_RED, GL_UNSIGNED_BYTE, identity_pal.data());
+
+ /* Pixel buffer for remap updates. */
+ _glGenBuffers(1, &OpenGLSprite::pal_pbo);
+ _glBindBuffer(GL_PIXEL_UNPACK_BUFFER, OpenGLSprite::pal_pbo);
+ _glBufferData(GL_PIXEL_UNPACK_BUFFER, 256, identity_pal.data(), GL_DYNAMIC_DRAW);
+ _glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+
return glGetError() == GL_NO_ERROR;
}
@@ -1151,6 +1245,9 @@ void OpenGLBackend::RenderOglSprite(OpenGLSprite *gl_sprite, uint x, uint y, Zoo
/* static */ void OpenGLSprite::Destroy()
{
glDeleteTextures(NUM_TEX, OpenGLSprite::dummy_tex);
+ glDeleteTextures(1, &OpenGLSprite::pal_identity);
+ glDeleteTextures(1, &OpenGLSprite::pal_tex);
+ if (_glDeleteBuffers != nullptr) _glDeleteBuffers(1, &OpenGLSprite::pal_pbo);
}
/**
diff --git a/src/video/opengl.h b/src/video/opengl.h
index 86c0b0d17..373345063 100644
--- a/src/video/opengl.h
+++ b/src/video/opengl.h
@@ -54,7 +54,15 @@ private:
GLint remap_zoom_loc; ///< Uniform location for sprite zoom;
GLint remap_rgb_loc; ///< Uniform location for RGB mode flag;
- LRUCache<SpriteID, Sprite> cursor_cache; ///< Cache of encoded cursor sprites.
+ GLuint sprite_program; ///< Shader program for blending and rendering a sprite to the video buffer.
+ GLint sprite_sprite_loc; ///< Uniform location for sprite parameters.
+ GLint sprite_screen_loc; ///< Uniform location for screen size;
+ GLint sprite_zoom_loc; ///< Uniform location for sprite zoom;
+ GLint sprite_rgb_loc; ///< Uniform location for RGB mode flag;
+ GLint sprite_crash_loc; ///< Uniform location for crash remap mode flag;
+
+ LRUCache<SpriteID, Sprite> cursor_cache; ///< Cache of encoded cursor sprites.
+ PaletteID last_sprite_pal = (PaletteID)-1; ///< Last uploaded remap palette.
OpenGLBackend();
~OpenGLBackend();
@@ -62,7 +70,7 @@ private:
const char *Init();
bool InitShaders();
- void RenderOglSprite(OpenGLSprite *gl_sprite, uint x, uint y, ZoomLevel zoom);
+ void RenderOglSprite(OpenGLSprite *gl_sprite, PaletteID pal, uint x, uint y, ZoomLevel zoom);
public:
/** Get singleton instance of this class. */
@@ -110,6 +118,10 @@ private:
static GLuint dummy_tex[NUM_TEX]; ///< 1x1 dummy textures to substitute for unused sprite components.
+ static GLuint pal_identity; ///< Identity texture mapping.
+ static GLuint pal_tex; ///< Texture for palette remap.
+ static GLuint pal_pbo; ///< Pixel buffer object for remap upload.
+
static bool Create();
static void Destroy();