Add support for generating shaders at runtime

This adds new API in ShaderManager that makes it possible to request a
shader based on a set of traits. ShaderManager generates these shaders
on demand and caches them in a hash table.
This commit is contained in:
Fredrik Höglund 2014-04-01 18:07:23 +02:00
parent 623ac3faa3
commit afcc8337de
2 changed files with 193 additions and 0 deletions

View file

@ -661,6 +661,162 @@ ShaderManager::~ShaderManager()
for (int i = 0; i < 3; i++)
delete m_shader[i];
qDeleteAll(m_shaderHash);
m_shaderHash.clear();
}
QByteArray ShaderManager::generateVertexSource(ShaderTraits traits) const
{
QByteArray source;
QTextStream stream(&source);
GLPlatform * const gl = GLPlatform::instance();
QByteArray attribute, varying;
if (!gl->isGLES()) {
const bool glsl_140 = gl->glslVersion() >= kVersionNumber(1, 40);
attribute = glsl_140 ? QByteArrayLiteral("in") : QByteArrayLiteral("attribute");
varying = glsl_140 ? QByteArrayLiteral("out") : QByteArrayLiteral("varying");
if (glsl_140)
stream << "#version 140\n\n";
} else {
const bool glsl_es_300 = gl->glslVersion() >= kVersionNumber(3, 0);
attribute = glsl_es_300 ? QByteArrayLiteral("in") : QByteArrayLiteral("attribute");
varying = glsl_es_300 ? QByteArrayLiteral("out") : QByteArrayLiteral("varying");
if (glsl_es_300)
stream << "#version 300 es\n\n";
}
stream << attribute << " vec4 position;\n";
if (traits & ShaderTrait::MapTexture) {
stream << attribute << " vec4 texcoord;\n\n";
stream << varying << " vec2 texcoord0;\n\n";
} else
stream << "\n";
stream << "uniform mat4 modelViewProjectionMatrix;\n\n";
stream << "void main()\n{\n";
if (traits & ShaderTrait::MapTexture)
stream << " texcoord0 = texcoord.st;\n";
stream << " gl_Position = modelViewProjectionMatrix * position;\n";
stream << "}\n";
stream.flush();
return source;
}
QByteArray ShaderManager::generateFragmentSource(ShaderTraits traits) const
{
QByteArray source;
QTextStream stream(&source);
GLPlatform * const gl = GLPlatform::instance();
QByteArray varying, output, textureLookup;
if (!gl->isGLES()) {
const bool glsl_140 = gl->glslVersion() >= kVersionNumber(1, 40);
if (glsl_140)
stream << "#version 140\n\n";
varying = glsl_140 ? QByteArrayLiteral("in") : QByteArrayLiteral("varying");
textureLookup = glsl_140 ? QByteArrayLiteral("texture") : QByteArrayLiteral("texture2D");
output = glsl_140 ? QByteArrayLiteral("fragColor") : QByteArrayLiteral("gl_FragColor");
} else {
const bool glsl_es_300 = GLPlatform::instance()->glslVersion() >= kVersionNumber(3, 0);
if (glsl_es_300)
stream << "#version 300 es\n\n";
// From the GLSL ES specification:
//
// "The fragment language has no default precision qualifier for floating point types."
stream << "precision highp float;\n\n";
varying = glsl_es_300 ? QByteArrayLiteral("in") : QByteArrayLiteral("varying");
textureLookup = glsl_es_300 ? QByteArrayLiteral("texture") : QByteArrayLiteral("texture2D");
output = glsl_es_300 ? QByteArrayLiteral("fragColor") : QByteArrayLiteral("gl_FragColor");
}
if (traits & ShaderTrait::MapTexture) {
stream << "uniform sampler2D sampler;\n";
if (traits & ShaderTrait::Modulate)
stream << "uniform vec4 modulation;\n";
if (traits & ShaderTrait::AdjustSaturation)
stream << "uniform float saturation;\n";
stream << "\n" << varying << " vec2 texcoord0;\n";
} else if (traits & ShaderTrait::UniformColor)
stream << "uniform vec4 geometryColor;\n";
if (output != QByteArrayLiteral("gl_FragColor"))
stream << "\nout vec4 " << output << ";\n";
stream << "\nvoid main(void)\n{\n";
if (traits & ShaderTrait::MapTexture) {
if (traits & (ShaderTrait::Modulate | ShaderTrait::AdjustSaturation)) {
stream << " vec4 texel = " << textureLookup << "(sampler, texcoord0);\n";
if (traits & ShaderTrait::Modulate)
stream << " texel *= modulation;\n";
if (traits & ShaderTrait::AdjustSaturation)
stream << " texel.rgb = mix(vec3(dot(texel.rgb, vec3(0.2126, 0.7152, 0.0722))), texel.rgb, saturation);\n";
stream << " " << output << " = texel;\n";
} else {
stream << " " << output << " = " << textureLookup << "(sampler, texcoord0);\n";
}
} else if (traits & ShaderTrait::UniformColor)
stream << " " << output << " = geometryColor;\n";
stream << "}";
stream.flush();
return source;
}
GLShader *ShaderManager::generateShader(ShaderTraits traits)
{
const QByteArray vertex = generateVertexSource(traits);
const QByteArray fragment = generateFragmentSource(traits);
#if 0
qDebug() << "**************";
qDebug() << vertex;
qDebug() << "**************";
qDebug() << fragment;
qDebug() << "**************";
#endif
GLShader *shader = new GLShader(GLShader::ExplicitLinking);
shader->load(vertex, fragment);
shader->bindAttributeLocation("position", VA_Position);
shader->bindAttributeLocation("texcoord", VA_TexCoord);
shader->bindFragDataLocation("fragColor", 0);
shader->link();
return shader;
}
GLShader *ShaderManager::shader(ShaderTraits traits)
{
GLShader *shader = m_shaderHash.value(traits);
if (!shader) {
shader = generateShader(traits);
m_shaderHash.insert(traits, shader);
}
return shader;
}
GLShader *ShaderManager::getBoundShader() const
@ -687,6 +843,13 @@ bool ShaderManager::isShaderDebug() const
return m_debug;
}
GLShader *ShaderManager::pushShader(ShaderTraits traits)
{
GLShader *shader = this->shader(traits);
pushShader(shader);
return shader;
}
GLShader *ShaderManager::pushShader(ShaderType type, bool reset)
{
if (m_inited && !m_valid) {

View file

@ -201,6 +201,17 @@ private:
friend class ShaderManager;
};
enum class ShaderTrait {
MapTexture = (1 << 0),
UniformColor = (1 << 1),
Modulate = (1 << 2),
AdjustSaturation = (1 << 3),
};
Q_DECLARE_FLAGS(ShaderTraits, ShaderTrait)
/**
* @short Manager for Shaders.
*
@ -249,6 +260,11 @@ public:
ColorShader
};
/**
* Returns a shader with the given traits, creating it if necessary.
*/
GLShader *shader(ShaderTraits traits);
/**
* @return The currently bound shader or @c null if no shader is bound.
**/
@ -282,6 +298,13 @@ public:
* @see popShader
**/
GLShader *pushShader(ShaderType type, bool reset = false);
/**
* Pushes the current shader onto the stack and binds a shader
* with the given traits.
*/
GLShader *pushShader(ShaderTraits traits);
/**
* Binds the @p shader.
* To unbind the shader use @link popShader. A previous bound shader will be rebound.
@ -363,8 +386,13 @@ private:
void bindFragDataLocations(GLShader *shader);
void bindAttributeLocations(GLShader *shader) const;
QByteArray generateVertexSource(ShaderTraits traits) const;
QByteArray generateFragmentSource(ShaderTraits traits) const;
GLShader *generateShader(ShaderTraits traits);
QStack<GLShader*> m_boundShaders;
GLShader *m_shader[3];
QHash<ShaderTraits, GLShader *> m_shaderHash;
bool m_inited;
bool m_valid;
bool m_debug;
@ -779,6 +807,8 @@ private:
} // namespace
Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::ShaderTraits)
/** @} */
#endif