cf5de22586
If the build option is enabled KWIN_HAVE_OPENGL_1 is passed as a compile flag when build against OpenGL. This compile flag is meant to replace the KWIN_HAVE_OPENGLES. So far code has been ifdefed for special behavior of OpenGL ES 2.0 and to remove fixed functionality calls which are not available in OpenGL ES 2.0. With this build flag the fixed functionality calls which are only used in the OpenGL1 Compositor can be removed and keeping the KWIN_HAVE_OPENGLES for the real differences between OpenGL 2.x and OpenGL ES 2.0. E.g. a call like glColor4f should be in an glColor4f(1.0, 1.0, 1.0, 1.0); while a call like glPolygonMode should be in an glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); Building for OpenGL ES 2.0 of course implies that KWIN_HAVE_OPENGL_1 is not defined.
485 lines
13 KiB
C++
485 lines
13 KiB
C++
/*
|
|
* Copyright © 2010 Fredrik Höglund <fredrik@kde.org>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; see the file COPYING. if not, write to
|
|
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "blurshader.h"
|
|
|
|
#include <kwineffects.h>
|
|
#include <kwinglplatform.h>
|
|
|
|
#include <QByteArray>
|
|
#include <QMatrix4x4>
|
|
#include <QTextStream>
|
|
#include <QVector2D>
|
|
#include <KDebug>
|
|
|
|
#include <cmath>
|
|
|
|
using namespace KWin;
|
|
|
|
|
|
BlurShader::BlurShader()
|
|
: mRadius(0), mValid(false)
|
|
{
|
|
}
|
|
|
|
BlurShader::~BlurShader()
|
|
{
|
|
}
|
|
|
|
BlurShader *BlurShader::create()
|
|
{
|
|
if (GLSLBlurShader::supported())
|
|
return new GLSLBlurShader();
|
|
|
|
return new ARBBlurShader();
|
|
}
|
|
|
|
void BlurShader::setRadius(int radius)
|
|
{
|
|
const int r = qMax(radius, 2);
|
|
|
|
if (mRadius != r) {
|
|
mRadius = r;
|
|
reset();
|
|
init();
|
|
}
|
|
}
|
|
|
|
void BlurShader::setDirection(Qt::Orientation direction)
|
|
{
|
|
mDirection = direction;
|
|
}
|
|
|
|
float BlurShader::gaussian(float x, float sigma) const
|
|
{
|
|
return (1.0 / std::sqrt(2.0 * M_PI) * sigma)
|
|
* std::exp(-((x * x) / (2.0 * sigma * sigma)));
|
|
}
|
|
|
|
QVector<float> BlurShader::gaussianKernel() const
|
|
{
|
|
int size = qMin(mRadius | 1, maxKernelSize());
|
|
if (!(size & 0x1))
|
|
size -= 1;
|
|
|
|
QVector<float> kernel(size);
|
|
const int center = size / 2;
|
|
const qreal sigma = (size - 1) / 2.5;
|
|
|
|
// Generate the gaussian kernel
|
|
kernel[center] = gaussian(0, sigma) * .5;
|
|
for (int i = 1; i <= center; i++) {
|
|
const float val = gaussian(1.5 + (i - 1) * 2.0, sigma);
|
|
kernel[center + i] = val;
|
|
kernel[center - i] = val;
|
|
}
|
|
|
|
// Normalize the kernel
|
|
qreal total = 0;
|
|
for (int i = 0; i < size; i++)
|
|
total += kernel[i];
|
|
|
|
for (int i = 0; i < size; i++)
|
|
kernel[i] /= total;
|
|
|
|
return kernel;
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
GLSLBlurShader::GLSLBlurShader()
|
|
: BlurShader(), shader(NULL)
|
|
{
|
|
}
|
|
|
|
GLSLBlurShader::~GLSLBlurShader()
|
|
{
|
|
reset();
|
|
}
|
|
|
|
void GLSLBlurShader::reset()
|
|
{
|
|
delete shader;
|
|
shader = NULL;
|
|
|
|
setIsValid(false);
|
|
}
|
|
|
|
bool GLSLBlurShader::supported()
|
|
{
|
|
if (!GLPlatform::instance()->supports(GLSL))
|
|
return false;
|
|
if (effects->compositingType() == OpenGL1Compositing)
|
|
return false;
|
|
|
|
(void) glGetError(); // Clear the error state
|
|
|
|
#ifdef KWIN_HAVE_OPENGL_1
|
|
// These are the minimum values the implementation is required to support
|
|
int value = 0;
|
|
|
|
glGetIntegerv(GL_MAX_VARYING_FLOATS, &value);
|
|
if (value < 32)
|
|
return false;
|
|
|
|
glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, &value);
|
|
if (value < 64)
|
|
return false;
|
|
|
|
glGetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS, &value);
|
|
if (value < 512)
|
|
return false;
|
|
#endif
|
|
|
|
if (glGetError() != GL_NO_ERROR)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void GLSLBlurShader::setPixelDistance(float val)
|
|
{
|
|
if (!isValid())
|
|
return;
|
|
|
|
QVector2D pixelSize(0.0, 0.0);
|
|
if (direction() == Qt::Horizontal)
|
|
pixelSize.setX(val);
|
|
else
|
|
pixelSize.setY(val);
|
|
shader->setUniform("pixelSize", pixelSize);
|
|
}
|
|
|
|
void GLSLBlurShader::setTextureMatrix(const QMatrix4x4 &matrix)
|
|
{
|
|
if (!isValid()) {
|
|
return;
|
|
}
|
|
shader->setUniform("u_textureMatrix", matrix);
|
|
}
|
|
|
|
void GLSLBlurShader::setModelViewProjectionMatrix(const QMatrix4x4 &matrix)
|
|
{
|
|
if (!isValid()) {
|
|
return;
|
|
}
|
|
shader->setUniform("u_modelViewProjectionMatrix", matrix);
|
|
}
|
|
|
|
void GLSLBlurShader::bind()
|
|
{
|
|
if (!isValid())
|
|
return;
|
|
|
|
ShaderManager::instance()->pushShader(shader);
|
|
}
|
|
|
|
void GLSLBlurShader::unbind()
|
|
{
|
|
ShaderManager::instance()->popShader();
|
|
}
|
|
|
|
int GLSLBlurShader::maxKernelSize() const
|
|
{
|
|
#ifdef KWIN_HAVE_OPENGLES
|
|
// GL_MAX_VARYING_FLOATS not available in GLES
|
|
// querying for GL_MAX_VARYING_VECTORS crashes on nouveau
|
|
// using the minimum value of 8
|
|
return 8 * 2;
|
|
#else
|
|
int value;
|
|
glGetIntegerv(GL_MAX_VARYING_FLOATS, &value);
|
|
// Maximum number of vec4 varyings * 2
|
|
// The code generator will pack two vec2's into each vec4.
|
|
return value / 2;
|
|
#endif
|
|
}
|
|
|
|
|
|
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 mat4 u_modelViewProjectionMatrix;\n";
|
|
stream << "uniform mat4 u_textureMatrix;\n";
|
|
stream << "uniform vec2 pixelSize;\n\n";
|
|
stream << "attribute vec4 vertex;\n";
|
|
stream << "attribute vec4 texCoord;\n\n";
|
|
stream << "varying vec4 samplePos[" << std::ceil(size / 2.0) << "];\n";
|
|
stream << "\n";
|
|
stream << "void main(void)\n";
|
|
stream << "{\n";
|
|
stream << " vec4 center = vec4(u_textureMatrix * texCoord).stst;\n";
|
|
stream << " vec4 ps = pixelSize.stst;\n\n";
|
|
for (int i = 0; i < size; i += 2) {
|
|
float offset1, offset2;
|
|
if (i < center) {
|
|
offset1 = -(1.5 + (center - i - 1) * 2.0);
|
|
offset2 = (i + 1) == center ? 0 : offset1 + 2;
|
|
} else if (i > center) {
|
|
offset1 = 1.5 + (i - center - 1) * 2.0;
|
|
offset2 = (i + 1) == size ? 0 : offset1 + 2;
|
|
} else {
|
|
offset1 = 0;
|
|
offset2 = 1.5;
|
|
}
|
|
stream << " samplePos[" << i / 2 << "] = center + ps * vec4("
|
|
<< offset1 << ", " << offset1 << ", " << offset2 << ", " << offset2 << ");\n";
|
|
}
|
|
stream << "\n";
|
|
stream << " gl_Position = u_modelViewProjectionMatrix * vertex;\n";
|
|
stream << "}\n";
|
|
stream.flush();
|
|
|
|
// Fragment shader
|
|
// ===================================================================
|
|
QTextStream stream2(&fragmentSource);
|
|
|
|
stream2 << "uniform sampler2D texUnit;\n";
|
|
stream2 << "varying vec4 samplePos[" << std::ceil(size / 2.0) << "];\n\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, samplePos[0].st) * kernel0;\n";
|
|
for (int i = 1; i < size; i++)
|
|
stream2 << " sum = sum + texture2D(texUnit, samplePos[" << i / 2 << ((i % 2) ? "].pq)" : "].st)")
|
|
<< " * kernel" << (i > center ? size - i - 1 : i) << ";\n";
|
|
stream2 << " gl_FragColor = sum;\n";
|
|
stream2 << "}\n";
|
|
stream2.flush();
|
|
|
|
shader = ShaderManager::instance()->loadShaderFromCode(vertexSource, fragmentSource);
|
|
if (shader->isValid()) {
|
|
QMatrix4x4 modelViewProjection;
|
|
modelViewProjection.ortho(0, displayWidth(), displayHeight(), 0, 0, 65535);
|
|
ShaderManager::instance()->pushShader(shader);
|
|
shader->setUniform("texUnit", 0);
|
|
shader->setUniform("u_textureMatrix", QMatrix4x4());
|
|
shader->setUniform("u_modelViewProjectionMatrix", modelViewProjection);
|
|
ShaderManager::instance()->popShader();
|
|
}
|
|
|
|
setIsValid(shader->isValid());
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
ARBBlurShader::ARBBlurShader()
|
|
: BlurShader(), program(0)
|
|
{
|
|
}
|
|
|
|
ARBBlurShader::~ARBBlurShader()
|
|
{
|
|
reset();
|
|
}
|
|
|
|
void ARBBlurShader::reset()
|
|
{
|
|
#ifdef KWIN_HAVE_OPENGL_1
|
|
if (program) {
|
|
glDeleteProgramsARB(1, &program);
|
|
program = 0;
|
|
}
|
|
|
|
setIsValid(false);
|
|
#endif
|
|
}
|
|
|
|
bool ARBBlurShader::supported()
|
|
{
|
|
#ifdef KWIN_HAVE_OPENGLES
|
|
return false;
|
|
#else
|
|
if (!hasGLExtension("GL_ARB_fragment_program"))
|
|
return false;
|
|
|
|
(void) glGetError(); // Clear the error state
|
|
|
|
// These are the minimum values the implementation is required to support
|
|
int value = 0;
|
|
|
|
glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_MAX_PROGRAM_PARAMETERS_ARB, &value);
|
|
if (value < 24)
|
|
return false;
|
|
|
|
glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_MAX_PROGRAM_TEMPORARIES_ARB, &value);
|
|
if (value < 16)
|
|
return false;
|
|
|
|
glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_MAX_PROGRAM_INSTRUCTIONS_ARB, &value);
|
|
if (value < 72)
|
|
return false;
|
|
|
|
glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_MAX_PROGRAM_TEX_INSTRUCTIONS_ARB, &value);
|
|
if (value < 24)
|
|
return false;
|
|
|
|
glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_MAX_PROGRAM_TEX_INDIRECTIONS_ARB, &value);
|
|
if (value < 4)
|
|
return false;
|
|
|
|
if (glGetError() != GL_NO_ERROR)
|
|
return false;
|
|
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
void ARBBlurShader::setPixelDistance(float val)
|
|
{
|
|
#ifdef KWIN_HAVE_OPENGLES
|
|
Q_UNUSED(val)
|
|
#else
|
|
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);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void ARBBlurShader::bind()
|
|
{
|
|
#ifdef KWIN_HAVE_OPENGL_1
|
|
if (!isValid())
|
|
return;
|
|
|
|
glEnable(GL_FRAGMENT_PROGRAM_ARB);
|
|
glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, program);
|
|
#endif
|
|
}
|
|
|
|
void ARBBlurShader::unbind()
|
|
{
|
|
#ifdef KWIN_HAVE_OPENGL_1
|
|
int boundObject;
|
|
glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_BINDING_ARB, &boundObject);
|
|
if (boundObject == (int)program) {
|
|
glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, 0);
|
|
glDisable(GL_FRAGMENT_PROGRAM_ARB);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
int ARBBlurShader::maxKernelSize() const
|
|
{
|
|
#ifdef KWIN_HAVE_OPENGLES
|
|
return 0;
|
|
#else
|
|
int value;
|
|
int result;
|
|
|
|
glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_MAX_PROGRAM_PARAMETERS_ARB, &value);
|
|
result = (value - 1) * 2; // We only need to store half the kernel, since it's symmetrical
|
|
|
|
glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_MAX_PROGRAM_INSTRUCTIONS_ARB, &value);
|
|
result = qMin(result, value / 3); // We need 3 instructions / sample
|
|
|
|
return result;
|
|
#endif
|
|
}
|
|
|
|
void ARBBlurShader::init()
|
|
{
|
|
#ifdef KWIN_HAVE_OPENGL_1
|
|
QVector<float> kernel = gaussianKernel();
|
|
const int size = kernel.size();
|
|
const int center = size / 2;
|
|
|
|
QByteArray text;
|
|
QTextStream stream(&text);
|
|
|
|
stream << "!!ARBfp1.0\n";
|
|
|
|
// The kernel values are hardcoded into the program
|
|
for (int i = 0; i <= center; i++)
|
|
stream << "PARAM kernel" << i << " = " << kernel[center + i] << ";\n";
|
|
|
|
stream << "PARAM firstSample = program.local[0];\n"; // Distance from gl_TexCoord[0] to the next sample
|
|
stream << "PARAM nextSample = program.local[1];\n"; // Distance to the subsequent sample
|
|
|
|
// Temporary variables to hold coordinates and texture samples
|
|
for (int i = 0; i < size; i++)
|
|
stream << "TEMP temp" << i << ";\n";
|
|
|
|
// Compute the texture coordinates
|
|
stream << "ADD temp1, fragment.texcoord[0], firstSample;\n"; // temp1 = gl_TexCoord[0] + firstSample
|
|
stream << "SUB temp2, fragment.texcoord[0], firstSample;\n"; // temp2 = gl_TexCoord[0] - firstSample
|
|
for (int i = 1, j = 3; i < center; i++, j += 2) {
|
|
stream << "ADD temp" << j + 0 << ", temp" << j - 2 << ", nextSample;\n";
|
|
stream << "SUB temp" << j + 1 << ", temp" << j - 1 << ", nextSample;\n";
|
|
}
|
|
|
|
// Sample the texture coordinates
|
|
stream << "TEX temp0, fragment.texcoord[0], texture[0], 2D;\n";
|
|
for (int i = 1; i < size; i++)
|
|
stream << "TEX temp" << i << ", temp" << i << ", texture[0], 2D;\n";
|
|
|
|
// Multiply the samples with the kernel values and compute the sum
|
|
stream << "MUL temp0, temp0, kernel0;\n";
|
|
for (int i = 0, j = 1; i < center; i++) {
|
|
stream << "MAD temp0, temp" << j++ << ", kernel" << i + 1 << ", temp0;\n";
|
|
stream << "MAD temp0, temp" << j++ << ", kernel" << i + 1 << ", temp0;\n";
|
|
}
|
|
|
|
stream << "MOV result.color, temp0;\n"; // gl_FragColor = temp0
|
|
stream << "END\n";
|
|
stream.flush();
|
|
|
|
glGenProgramsARB(1, &program);
|
|
glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, program);
|
|
glProgramStringARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, text.length(), text.constData());
|
|
|
|
if (glGetError()) {
|
|
const char *error = (const char*)glGetString(GL_PROGRAM_ERROR_STRING_ARB);
|
|
kError() << "Failed to compile fragment program:" << error;
|
|
setIsValid(false);
|
|
} else
|
|
setIsValid(true);
|
|
|
|
glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, 0);
|
|
#endif
|
|
}
|
|
|