diff --git a/effects/blur/blur.cpp b/effects/blur/blur.cpp index 246637442b..d7d1c5049f 100644 --- a/effects/blur/blur.cpp +++ b/effects/blur/blur.cpp @@ -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 diff --git a/effects/blur/blurshader.cpp b/effects/blur/blurshader.cpp index 2aa2e2425d..8330b41657 100644 --- a/effects/blur/blurshader.cpp +++ b/effects/blur/blurshader.cpp @@ -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 BlurShader::gaussianKernel() const { - const int size = radius | 1; + const int size = mRadius | 1; QVector 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 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 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 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); diff --git a/effects/blur/blurshader.h b/effects/blur/blurshader.h index 6a0cdf223c..10948ef6b3 100644 --- a/effects/blur/blurshader.h +++ b/effects/blur/blurshader.h @@ -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 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 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