Refactor the blur shader code a bit, and add a GLSL version.

svn path=/trunk/KDE/kdebase/workspace/; revision=1100848
This commit is contained in:
Fredrik Höglund 2010-03-08 20:45:58 +00:00
parent 17a38a7cdb
commit 3de8c53f21
3 changed files with 334 additions and 69 deletions

View file

@ -38,7 +38,7 @@ KWIN_EFFECT_SUPPORTED(blur, BlurEffect::supported())
BlurEffect::BlurEffect()
: radius(12)
{
shader = new BlurShader;
shader = BlurShader::create();
shader->setRadius(radius);
// Offscreen texture that's used as the target for the horizontal blur pass
@ -101,7 +101,8 @@ void BlurEffect::propertyNotify(EffectWindow *w, long atom)
bool BlurEffect::supported()
{
return GLRenderTarget::supported() && GLTexture::NPOTTextureSupported() &&
hasGLExtension("GL_ARB_fragment_program");
((GLShader::vertexShaderSupported() && GLShader::fragmentShaderSupported()) ||
hasGLExtension("GL_ARB_fragment_program"));
}
QRect BlurEffect::expand(const QRect &rect) const

View file

@ -29,69 +29,35 @@ using namespace KWin;
BlurShader::BlurShader()
: program(0), radius(12)
: mRadius(12)
{
}
BlurShader::~BlurShader()
{
if (program) {
glDeleteProgramsARB(1, &program);
program = 0;
}
BlurShader *BlurShader::create()
{
if (GLShader::vertexShaderSupported() && GLShader::fragmentShaderSupported())
return new GLSLBlurShader();
return new ARBBlurShader();
}
void BlurShader::setRadius(int radius)
{
const int r = qMax(radius, 2);
if (mRadius != r) {
mRadius = r;
reset();
}
}
void BlurShader::setRadius(int _radius)
void BlurShader::setDirection(Qt::Orientation direction)
{
int r = qMax(_radius, 2);
if (radius != r) {
radius = r;
if (program) {
glDeleteProgramsARB(1, &program);
program = 0;
}
}
}
void BlurShader::setDirection(Qt::Orientation orientation)
{
direction = orientation;
}
void BlurShader::setPixelDistance(float val)
{
float firstStep = val * 1.5;
float nextStep = val * 2.0;
if (direction == Qt::Horizontal) {
glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 0, firstStep, 0, 0, 0);
glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 1, nextStep, 0, 0, 0);
} else {
glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 0, 0, firstStep, 0, 0);
glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 1, 0, nextStep, 0, 0);
}
}
void BlurShader::setOpacity(float val)
{
glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 2, val, val, val, val);
}
void BlurShader::bind()
{
if (!program)
init();
glEnable(GL_FRAGMENT_PROGRAM_ARB);
glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, program);
}
void BlurShader::unbind()
{
glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, 0);
glDisable(GL_FRAGMENT_PROGRAM_ARB);
mDirection = direction;
}
float BlurShader::gaussian(float x, float sigma) const
@ -102,9 +68,9 @@ float BlurShader::gaussian(float x, float sigma) const
QVector<float> BlurShader::gaussianKernel() const
{
const int size = radius | 1;
const int size = mRadius | 1;
QVector<float> kernel(size);
const qreal sigma = radius / 2.5;
const qreal sigma = mRadius / 2.5;
const int center = size / 2;
// Generate the gaussian kernel
@ -126,7 +92,246 @@ QVector<float> BlurShader::gaussianKernel() const
return kernel;
}
void BlurShader::init()
// ----------------------------------------------------------------------------
GLSLBlurShader::GLSLBlurShader()
: BlurShader(), program(0)
{
}
GLSLBlurShader::~GLSLBlurShader()
{
reset();
}
void GLSLBlurShader::reset()
{
if (program) {
glDeleteProgram(program);
program = 0;
}
}
void GLSLBlurShader::setPixelDistance(float val)
{
float pixelSize[2] = { 0.0, 0.0 };
if (direction() == Qt::Horizontal)
pixelSize[0] = val;
else
pixelSize[1] = val;
glUniform2fv(uPixelSize, 1, pixelSize);
}
void GLSLBlurShader::setOpacity(float val)
{
const float opacity[4] = { val, val, val, val };
glUniform4fv(uOpacity, 1, opacity);
}
void GLSLBlurShader::bind()
{
if (!program)
init();
glUseProgram(program);
glUniform1i(uTexUnit, 0);
}
void GLSLBlurShader::unbind()
{
glUseProgram(0);
}
GLuint GLSLBlurShader::compile(GLenum type, const QByteArray &source)
{
const char *sourceData = source.constData();
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &sourceData, 0);
glCompileShader(shader);
int status;
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
if (status == GL_FALSE) {
GLsizei size, length;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &size);
QByteArray log(size, 0);
glGetShaderInfoLog(shader, size, &length, log.data());
kError() << "Failed to compile shader: " << log;
glDeleteShader(shader);
shader = 0;
}
return shader;
}
GLuint GLSLBlurShader::link(GLuint vertexShader, GLuint fragmentShader)
{
GLuint program = glCreateProgram();
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);
glLinkProgram(program);
int status;
glGetProgramiv(program, GL_LINK_STATUS, &status);
if (status == GL_FALSE) {
GLsizei size, length;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &size);
QByteArray log(size, 0);
glGetProgramInfoLog(program, size, &length, log.data());
kError() << "Failed to link shader: " << log;
glDeleteProgram(program);
program = 0;
}
return program;
}
void GLSLBlurShader::init()
{
QVector<float> kernel = gaussianKernel();
const int size = kernel.size();
const int center = size / 2;
QByteArray vertexSource;
QByteArray fragmentSource;
// Vertex shader
// ===================================================================
QTextStream stream(&vertexSource);
stream << "uniform vec2 pixelSize;\n\n";
for (int i = 0; i < size; i++)
stream << "varying vec2 samplePos" << i << ";\n";
stream << "\n";
stream << "void main(void)\n";
stream << "{\n";
for (int i = 0; i < center; i++)
stream << " samplePos" << i << " = gl_MultiTexCoord0.st + pixelSize * vec2("
<< -(1.5 + (center - i - 1) * 2.0) << ");\n";
stream << " samplePos" << center << " = gl_MultiTexCoord0.st;\n";
for (int i = center + 1; i < size; i++)
stream << " samplePos" << i << " = gl_MultiTexCoord0.st + pixelSize * vec2("
<< 1.5 + (i - center - 1) * 2.0 << ");\n";
stream << "\n";
stream << " gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\n";
stream << "}\n";
stream.flush();
// Fragment shader
// ===================================================================
QTextStream stream2(&fragmentSource);
stream2 << "uniform sampler2D texUnit;\n";
stream2 << "uniform vec4 opacity;\n\n";
for (int i = 0; i < size; i++)
stream2 << "varying vec2 samplePos" << i << ";\n";
stream2 << "\n";
for (int i = 0; i <= center; i++)
stream2 << "const vec4 kernel" << i << " = vec4(" << kernel[i] << ");\n";
stream2 << "\n";
stream2 << "void main(void)\n";
stream2 << "{\n";
stream2 << " vec4 sum = texture2D(texUnit, samplePos0) * kernel0;\n";
for (int i = 1; i < size; i++)
stream2 << " sum += texture2D(texUnit, samplePos" << i << ") * kernel"
<< (i > center ? size - i - 1 : i) << ";\n";
stream2 << " gl_FragColor = sum * opacity;\n";
stream2 << "}\n";
stream2.flush();
GLuint vertexShader = compile(GL_VERTEX_SHADER, vertexSource);
GLuint fragmentShader = compile(GL_FRAGMENT_SHADER, fragmentSource);
if (vertexShader && fragmentShader)
program = link(vertexShader, fragmentShader);
if (vertexShader)
glDeleteShader(vertexShader);
if (fragmentShader)
glDeleteShader(fragmentShader);
if (program) {
uTexUnit = glGetUniformLocation(program, "texUnit");
uPixelSize = glGetUniformLocation(program, "pixelSize");
uOpacity = glGetUniformLocation(program, "opacity");
}
}
// ----------------------------------------------------------------------------
ARBBlurShader::ARBBlurShader()
: BlurShader(), program(0)
{
}
ARBBlurShader::~ARBBlurShader()
{
reset();
}
void ARBBlurShader::reset()
{
if (program) {
glDeleteProgramsARB(1, &program);
program = 0;
}
}
void ARBBlurShader::setPixelDistance(float val)
{
float firstStep = val * 1.5;
float nextStep = val * 2.0;
if (direction() == Qt::Horizontal) {
glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 0, firstStep, 0, 0, 0);
glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 1, nextStep, 0, 0, 0);
} else {
glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 0, 0, firstStep, 0, 0);
glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 1, 0, nextStep, 0, 0);
}
}
void ARBBlurShader::setOpacity(float val)
{
glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 2, val, val, val, val);
}
void ARBBlurShader::bind()
{
if (!program)
init();
glEnable(GL_FRAGMENT_PROGRAM_ARB);
glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, program);
}
void ARBBlurShader::unbind()
{
glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, 0);
glDisable(GL_FRAGMENT_PROGRAM_ARB);
}
void ARBBlurShader::init()
{
QVector<float> kernel = gaussianKernel();
const int size = kernel.size();
@ -180,7 +385,7 @@ void BlurShader::init()
if (glGetError()) {
const char *error = (const char*)glGetString(GL_PROGRAM_ERROR_STRING_ARB);
kError() << "Error when compiling fragment program:" << error;
kError() << "Failed to compile fragment program:" << error;
}
glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, 0);

View file

@ -29,32 +29,91 @@ class BlurShader
{
public:
BlurShader();
~BlurShader();
virtual ~BlurShader();
static BlurShader *create();
// Sets the radius in pixels
void setRadius(int radius);
int radius() const { return mRadius; }
// Sets the blur direction
void setDirection(Qt::Orientation orientation);
void setDirection(Qt::Orientation direction);
Qt::Orientation direction() const { return mDirection; }
// Sets the distance between two pixels
void setPixelDistance(float val);
virtual void setPixelDistance(float val) = 0;
// The opacity of the resulting pixels is multiplied by this value
void setOpacity(float val);
virtual void setOpacity(float val) = 0;
virtual void bind() = 0;
virtual void unbind() = 0;
protected:
float gaussian(float x, float sigma) const;
QVector<float> gaussianKernel() const;
virtual void init() = 0;
virtual void reset() = 0;
private:
int mRadius;
Qt::Orientation mDirection;
};
// ----------------------------------------------------------------------------
class GLSLBlurShader : public BlurShader
{
public:
GLSLBlurShader();
~GLSLBlurShader();
void setPixelDistance(float val);
void setOpacity(float val);
void bind();
void unbind();
private:
float gaussian(float x, float sigma) const;
QVector<float> gaussianKernel() const;
protected:
void init();
void reset();
GLuint compile(GLenum type, const QByteArray &source);
GLuint link(GLuint vertexShader, GLuint fragmentShader);
private:
GLuint program;
int uTexUnit;
int uPixelSize;
int uOpacity;
};
// ----------------------------------------------------------------------------
class ARBBlurShader : public BlurShader
{
public:
ARBBlurShader();
~ARBBlurShader();
void setPixelDistance(float val);
void setOpacity(float val);
void bind();
void unbind();
protected:
void init();
void reset();
private:
GLuint program;
int radius;
Qt::Orientation direction;
};
} // namespace KWin