OPENGL: Accelerate palette lookups with shaders.

This currently is limited to GL contexts.
This commit is contained in:
Johannes Schickel 2016-01-02 14:09:41 +01:00
parent de3846923c
commit e66e9e44d3
9 changed files with 416 additions and 3 deletions

View file

@ -45,6 +45,9 @@ void Context::reset() {
NPOTSupported = false;
#if !USE_FORCED_GLES && !USE_FORCED_GLES2
shadersSupported = false;
multitextureSupported = false;
framebufferObjectSupported = false;
textureRGSupported = false;
#endif
#define GL_FUNC_DEF(ret, name, param) name = nullptr;
@ -216,6 +219,12 @@ void OpenGLGraphicsManager::initializeGLContext() {
ARBVertexShader = true;
} else if (token == "GL_ARB_fragment_shader") {
ARBFragmentShader = true;
} else if (token == "GL_ARB_multitexture") {
g_context.multitextureSupported = true;
} else if (token == "GL_ARB_texture_rg") {
g_context.textureRGSupported = true;
} else if (token == "GL_EXT_framebuffer_object") {
g_context.framebufferObjectSupported = true;
#endif
}
}

View file

@ -183,6 +183,9 @@ typedef GLhandleARB GLshader;
#define GL_BGR 0x80E0
#define GL_BGRA 0x80E1
#define GL_RED 0x1903
#define GL_R8 0x8229
/* PixelStoreParameter */
#define GL_UNPACK_ALIGNMENT 0x0CF5
#define GL_PACK_ALIGNMENT 0x0D05
@ -242,8 +245,18 @@ typedef GLhandleARB GLshader;
#define GL_COMPILE_STATUS 0x8B81
#define GL_LINK_STATUS 0x8B82
#define GL_INFO_LOG_LENGTH 0x8B84
#define GL_CURRENT_PROGRAM 0x8B8D
/* Textures */
#define GL_TEXTURE0 0x84C0
#define GL_TEXTURE1 0x84C1
/* GetPName */
#define GL_VIEWPORT 0x0BA2
#define GL_FRAMEBUFFER_BINDING 0x8CA6
/* Framebuffer objects */
#define GL_COLOR_ATTACHMENT0 0x8CE0
#define GL_FRAMEBUFFER 0x8D40
#endif

View file

@ -77,6 +77,7 @@
GL_FUNC_DEF(void, glEnable, (GLenum cap));
GL_FUNC_DEF(void, glDisable, (GLenum cap));
GL_FUNC_DEF(GLboolean, glIsEnabled, (GLenum cap));
GL_FUNC_DEF(void, glClear, (GLbitfield mask));
GL_FUNC_DEF(void, glColor4f, (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha));
GL_FUNC_DEF(void, glViewport, (GLint x, GLint y, GLsizei width, GLsizei height));
@ -128,10 +129,16 @@ GL_FUNC_2_DEF(void, glGetShaderiv, glGetObjectParameterivARB, (GLshader shader,
GL_FUNC_2_DEF(void, glGetShaderInfoLog, glGetInfoLogARB, (GLshader shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog));
GL_FUNC_2_DEF(void, glShaderSource, glShaderSourceARB, (GLshader shader, GLsizei count, const GLchar *const *string, const GLint *length));
GL_FUNC_2_DEF(void, glCompileShader, glCompileShaderARB, (GLshader shader));
#if !USE_FORCED_GLES2
GL_FUNC_2_DEF(void, glBindFramebuffer, glBindFramebufferEXT, (GLenum target, GLuint renderbuffer));
GL_FUNC_2_DEF(void, glDeleteFramebuffers, glDeleteFramebuffersEXT, (GLsizei n, const GLuint *framebuffers));
GL_FUNC_2_DEF(void, glGenFramebuffers, glGenFramebuffersEXT, (GLsizei n, GLuint *renderbuffers));
GL_FUNC_2_DEF(void, glFramebufferTexture2D, glFramebufferTexture2DEXT, (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level));
GL_FUNC_2_DEF(GLenum, glCheckFramebufferStatus, glCheckFramebufferStatusEXT, (GLenum target));
#endif
#if !USE_FORCED_GL && !USE_FORCED_GLES
GL_FUNC_DEF(void, glActiveTexture, (GLenum texture));
GL_FUNC_2_DEF(void, glActiveTexture, glActiveTextureARB, (GLenum texture));
#endif
#ifdef DEFINED_GL_EXT_FUNC_DEF

View file

@ -1031,6 +1031,12 @@ void OpenGLGraphicsManager::setMousePosition(int x, int y) {
Surface *OpenGLGraphicsManager::createSurface(const Graphics::PixelFormat &format, bool wantAlpha) {
GLenum glIntFormat, glFormat, glType;
if (format.bytesPerPixel == 1) {
#if !USE_FORCED_GLES && !USE_FORCED_GLES2
if (TextureCLUT8GPU::isSupportedByContext()) {
return new TextureCLUT8GPU();
}
#endif
const Graphics::PixelFormat &virtFormat = wantAlpha ? _defaultFormatAlpha : _defaultFormat;
const bool supported = getGLPixelFormat(virtFormat, glIntFormat, glFormat, glType);
if (!supported) {

View file

@ -111,6 +111,15 @@ struct Context {
#if !USE_FORCED_GLES && !USE_FORCED_GLES2
/** Whether shader support is available or not. */
bool shadersSupported;
/** Whether multi texture support is available or not. */
bool multitextureSupported;
/** Whether (GLES2) RG texture formats are supported. */
bool textureRGSupported;
/** Whether FBO support is available or not. */
bool framebufferObjectSupported;
#endif
#define GL_FUNC_DEF(ret, name, param) ret (GL_CALL_CONV *name)param

View file

@ -171,6 +171,16 @@ void Shader::activate(const GLfloat *projectionMatrix) {
GL_CALL(glUniform1i(_textureLocation, 0));
}
GLint Shader::getUniformLocation(const char *name) const {
GLint result = -1;
GL_ASSIGN(result, glGetUniformLocation(_program, name));
return result;
}
void Shader::setUniformI(GLint location, GLint value) {
GL_CALL(glUniform1i(location, value));
}
GLshader Shader::compileShader(const char *source, GLenum shaderType) {
GLshader handle;
GL_ASSIGN(handle, glCreateShader(shaderType));

View file

@ -71,6 +71,24 @@ public:
* @param projectionMatrix Projection matrix to use.
*/
void activate(const GLfloat *projectionMatrix);
/**
* Return location for uniform with given name.
*
* @param name Name of the uniform to look up in the shader.
* @return The location or -1 if uniform was not found.
*/
GLint getUniformLocation(const char *name) const;
/**
* Bind value to uniform.
*
* Note: this only works when the shader is actived by activate.
*
* @param location Location of the uniform.
* @param value The value to be set.
*/
void setUniformI(GLint location, GLint value);
protected:
/**
* Vertex shader sources.

View file

@ -504,4 +504,274 @@ void TextureRGB555::updateTexture() {
}
#endif // !USE_FORCED_GL
#if !USE_FORCED_GLES && !USE_FORCED_GLES2
namespace {
const char *const g_lookUpFragmentShaderGL =
"varying vec2 texCoord;\n"
"varying vec4 blendColor;\n"
"\n"
"uniform sampler2D texture;\n"
"uniform sampler2D palette;\n"
"\n"
"const float adjustFactor = 255.0 / 256.0 + 1.0 / (2.0 * 256.0);"
"\n"
"void main(void) {\n"
"\tvec4 index = texture2D(texture, texCoord);\n"
"\tgl_FragColor = blendColor * texture2D(palette, vec2(index.x * adjustFactor, 0.0));\n"
"}\n";
} // End of anonymous namespace
TextureCLUT8GPU::TextureCLUT8GPU()
: _clut8Texture(GL_R8, GL_RED, GL_UNSIGNED_BYTE),
_paletteTexture(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE),
_glTexture(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE),
_glFBO(0), _clut8Vertices(), _projectionMatrix(),
_lookUpShader(nullptr), _paletteLocation(-1),
_clut8Data(), _userPixelData(), _palette(), _paletteDirty(false) {
// Allocate space for 256 colors.
_paletteTexture.setSize(256, 1);
_lookUpShader = new Shader(g_defaultVertexShader, g_lookUpFragmentShaderGL);
_lookUpShader->recreate();
_paletteLocation = _lookUpShader->getUniformLocation("palette");
setupFBO();
}
TextureCLUT8GPU::~TextureCLUT8GPU() {
delete _lookUpShader;
GL_CALL_SAFE(glDeleteFramebuffers, (1, &_glFBO));
_clut8Data.free();
}
void TextureCLUT8GPU::destroy() {
_clut8Texture.destroy();
_paletteTexture.destroy();
_glTexture.destroy();
_lookUpShader->destroy();
GL_CALL(glDeleteFramebuffers(1, &_glFBO));
_glFBO = 0;
}
void TextureCLUT8GPU::recreate() {
_clut8Texture.create();
_paletteTexture.create();
_glTexture.create();
_lookUpShader->recreate();
_paletteLocation = _lookUpShader->getUniformLocation("palette");
setupFBO();
// In case image date exists assure it will be completely refreshed next
// time.
if (_clut8Data.getPixels()) {
flagDirty();
_paletteDirty = true;
}
}
void TextureCLUT8GPU::enableLinearFiltering(bool enable) {
_glTexture.enableLinearFiltering(enable);
}
void TextureCLUT8GPU::allocate(uint width, uint height) {
// Assure the texture can contain our user data.
_clut8Texture.setSize(width, height);
_glTexture.setSize(width, height);
// In case the needed texture dimension changed we will reinitialize the
// texture data buffer.
if (_clut8Texture.getWidth() != _clut8Data.w || _clut8Texture.getHeight() != _clut8Data.h) {
// Create a buffer for the texture data.
_clut8Data.create(_clut8Texture.getWidth(), _clut8Texture.getHeight(), Graphics::PixelFormat::createFormatCLUT8());
}
// Create a sub-buffer for raw access.
_userPixelData = _clut8Data.getSubArea(Common::Rect(width, height));
// Setup structures for internal rendering to _glTexture.
_clut8Vertices[0] = 0;
_clut8Vertices[1] = 0;
_clut8Vertices[2] = width;
_clut8Vertices[3] = 0;
_clut8Vertices[4] = 0;
_clut8Vertices[5] = height;
_clut8Vertices[6] = width;
_clut8Vertices[7] = height;
_projectionMatrix[ 0] = 2.0f / _glTexture.getWidth();
_projectionMatrix[ 1] = 0.0f;
_projectionMatrix[ 2] = 0.0f;
_projectionMatrix[ 3] = 0.0f;
_projectionMatrix[ 4] = 0.0f;
_projectionMatrix[ 5] = 2.0f / _glTexture.getHeight();
_projectionMatrix[ 6] = 0.0f;
_projectionMatrix[ 7] = 0.0f;
_projectionMatrix[ 8] = 0.0f;
_projectionMatrix[ 9] = 0.0f;
_projectionMatrix[10] = 0.0f;
_projectionMatrix[11] = 0.0f;
_projectionMatrix[12] = -1.0f;
_projectionMatrix[13] = -1.0f;
_projectionMatrix[14] = 0.0f;
_projectionMatrix[15] = 1.0f;
}
void TextureCLUT8GPU::draw(GLfloat x, GLfloat y, GLfloat w, GLfloat h) {
// Only do any processing when the Texture is initialized.
if (!_clut8Data.getPixels()) {
return;
}
// First update any potentional changes.
updateTextures();
// Set the texture.
_glTexture.bind();
// Calculate the screen rect where the texture will be drawn.
const GLfloat vertices[4*2] = {
x, y,
x + w, y,
x, y + h,
x + w, y + h
};
// Setup coordinates for drawing.
g_context.setDrawCoordinates(vertices, _glTexture.getTexCoords());
// Draw the texture to the screen buffer.
GL_CALL(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4));
}
Graphics::PixelFormat TextureCLUT8GPU::getFormat() const {
return Graphics::PixelFormat::createFormatCLUT8();
}
void TextureCLUT8GPU::setColorKey(uint colorKey) {
_palette[colorKey * 4 + 3] = 0x00;
_paletteDirty = true;
}
void TextureCLUT8GPU::setPalette(uint start, uint colors, const byte *palData) {
byte *dst = _palette + start * 4;
while (colors-- > 0) {
memcpy(dst, palData, 3);
dst[3] = 0xFF;
dst += 4;
palData += 3;
}
_paletteDirty = true;
}
void TextureCLUT8GPU::updateTextures() {
const bool needLookUp = Surface::isDirty() || _paletteDirty;
// Update CLUT8 texture if necessary.
if (Surface::isDirty()) {
_clut8Texture.updateArea(getDirtyArea(), _clut8Data);
clearDirty();
}
// Update palette if necessary.
if (_paletteDirty) {
Graphics::Surface palSurface;
palSurface.init(256, 1, 256, _palette,
#ifdef SCUMM_LITTLE_ENDIAN
Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24) // ABGR8888
#else
Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0) // RGBA8888
#endif
);
_paletteTexture.updateArea(Common::Rect(256, 1), palSurface);
_paletteDirty = false;
}
// In case any data changed, do color look up and save result in _glTexture.
if (needLookUp) {
lookUpColors();
}
}
void TextureCLUT8GPU::lookUpColors() {
// Save old state.
GLint oldProgram = 0;
GL_CALL(glGetIntegerv(GL_CURRENT_PROGRAM, &oldProgram));
GLint viewport[4];
GL_CALL(glGetIntegerv(GL_VIEWPORT, viewport));
GLboolean scissorState;
GL_ASSIGN(scissorState, glIsEnabled(GL_SCISSOR_TEST));
GLboolean blendState;
GL_ASSIGN(blendState, glIsEnabled(GL_BLEND));
GLint oldFBO = 0;
GL_CALL(glGetIntegerv(GL_FRAMEBUFFER_BINDING, &oldFBO));
// Update state.
GL_CALL(glViewport(0, 0, _glTexture.getWidth(), _glTexture.getHeight()));
GL_CALL(glDisable(GL_SCISSOR_TEST));
GL_CALL(glDisable(GL_BLEND));
// Bind framebuffer.
GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, _glFBO));
// Set the palette texture.
GL_CALL(glActiveTexture(GL_TEXTURE1));
_paletteTexture.bind();
// Set the clut8 texture.
GL_CALL(glActiveTexture(GL_TEXTURE0));
_clut8Texture.bind();
// Do color look up.
_lookUpShader->activate(_projectionMatrix);
_lookUpShader->setUniformI(_paletteLocation, 1);
g_context.setDrawCoordinates(_clut8Vertices, _clut8Texture.getTexCoords());
GL_CALL(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4));
// Restore old state.
GL_CALL(glUseProgram(oldProgram));
GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, oldFBO));
if (blendState) {
GL_CALL(glEnable(GL_BLEND));
}
if (scissorState) {
GL_CALL(glEnable(GL_SCISSOR_TEST));
}
GL_CALL(glViewport(viewport[0], viewport[1], viewport[2], viewport[3]));
}
void TextureCLUT8GPU::setupFBO() {
// Allocate framebuffer object if necessary.
if (!_glFBO) {
GL_CALL(glGenFramebuffers(1, &_glFBO));
}
// Save old FBO.
GLint oldFBO = 0;
GL_CALL(glGetIntegerv(GL_FRAMEBUFFER_BINDING, &oldFBO));
// Attach destination texture to FBO.
GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, _glFBO));
GL_CALL(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _glTexture.getGLTexture(), 0));
// Restore old FBO.
GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, oldFBO));
}
#endif // !USE_FORCED_GLES && !USE_FORCED_GLES2
} // End of namespace OpenGL

View file

@ -32,6 +32,8 @@
namespace OpenGL {
class Shader;
/**
* A simple GL texture object abstraction.
*
@ -104,6 +106,14 @@ public:
* Obtain texture coordinates for rectangular drawing.
*/
const GLfloat *getTexCoords() const { return _texCoords; }
/**
* Obtain texture name.
*
* Beware that the texture name changes whenever create is used.
* destroy will invalidate the texture name.
*/
GLuint getGLTexture() const { return _glTexture; }
private:
const GLenum _glIntFormat;
const GLenum _glFormat;
@ -175,7 +185,7 @@ public:
virtual void draw(GLfloat x, GLfloat y, GLfloat w, GLfloat h) = 0;
void flagDirty() { _allDirty = true; }
bool isDirty() const { return _allDirty || !_dirtyArea.isEmpty(); }
virtual bool isDirty() const { return _allDirty || !_dirtyArea.isEmpty(); }
virtual uint getWidth() const = 0;
virtual uint getHeight() const = 0;
@ -307,6 +317,67 @@ private:
};
#endif // !USE_FORCED_GL
#if !USE_FORCED_GLES && !USE_FORCED_GLES2
class TextureCLUT8GPU : public Surface {
public:
TextureCLUT8GPU();
virtual ~TextureCLUT8GPU();
virtual void destroy();
virtual void recreate();
virtual void enableLinearFiltering(bool enable);
virtual void allocate(uint width, uint height);
virtual void draw(GLfloat x, GLfloat y, GLfloat w, GLfloat h);
virtual bool isDirty() const { return _paletteDirty || Surface::isDirty(); }
virtual uint getWidth() const { return _userPixelData.w; }
virtual uint getHeight() const { return _userPixelData.h; }
virtual Graphics::PixelFormat getFormat() const;
virtual bool hasPalette() const { return true; }
virtual void setColorKey(uint colorKey);
virtual void setPalette(uint start, uint colors, const byte *palData);
virtual Graphics::Surface *getSurface() { return &_userPixelData; }
virtual const Graphics::Surface *getSurface() const { return &_userPixelData; }
static bool isSupportedByContext() {
return g_context.shadersSupported
&& g_context.multitextureSupported
&& g_context.textureRGSupported
&& g_context.framebufferObjectSupported;
}
private:
void updateTextures();
void lookUpColors();
GLTexture _clut8Texture;
GLTexture _paletteTexture;
GLTexture _glTexture;
void setupFBO();
GLuint _glFBO;
GLfloat _clut8Vertices[4*2];
GLfloat _projectionMatrix[4*4];
Shader *_lookUpShader;
GLint _paletteLocation;
Graphics::Surface _clut8Data;
Graphics::Surface _userPixelData;
byte _palette[4 * 256];
bool _paletteDirty;
};
#endif // !USE_FORCED_GLES && !USE_FORCED_GLES2
} // End of namespace OpenGL
#endif