~ubuntu-branches/ubuntu/utopic/kde-workspace/utopic-proposed

« back to all changes in this revision

Viewing changes to kwin/effects/blur/blurshader.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Michał Zając
  • Date: 2011-07-09 08:31:15 UTC
  • Revision ID: james.westby@ubuntu.com-20110709083115-ohyxn6z93mily9fc
Tags: upstream-4.6.90
Import upstream version 4.6.90

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 *   Copyright © 2010 Fredrik Höglund <fredrik@kde.org>
 
3
 *
 
4
 *   This program is free software; you can redistribute it and/or modify
 
5
 *   it under the terms of the GNU General Public License as published by
 
6
 *   the Free Software Foundation; either version 2 of the License, or
 
7
 *   (at your option) any later version.
 
8
 *
 
9
 *   This program is distributed in the hope that it will be useful,
 
10
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
12
 *   General Public License for more details.
 
13
 *
 
14
 *   You should have received a copy of the GNU General Public License
 
15
 *   along with this program; see the file COPYING.  if not, write to
 
16
 *   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 
17
 *   Boston, MA 02110-1301, USA.
 
18
 */
 
19
 
 
20
#include "blurshader.h"
 
21
 
 
22
#include <kwineffects.h>
 
23
#include <kwinglplatform.h>
 
24
 
 
25
#include <QByteArray>
 
26
#include <QMatrix4x4>
 
27
#include <QTextStream>
 
28
#include <QVector2D>
 
29
#include <KDebug>
 
30
 
 
31
#include <cmath>
 
32
 
 
33
using namespace KWin;
 
34
 
 
35
 
 
36
BlurShader::BlurShader()
 
37
    : mRadius(0), mValid(false)
 
38
{
 
39
}
 
40
 
 
41
BlurShader::~BlurShader()
 
42
{
 
43
}
 
44
 
 
45
BlurShader *BlurShader::create()
 
46
{
 
47
    if (GLSLBlurShader::supported())
 
48
        return new GLSLBlurShader();
 
49
 
 
50
    return new ARBBlurShader();
 
51
}
 
52
 
 
53
void BlurShader::setRadius(int radius)
 
54
{
 
55
    const int r = qMax(radius, 2);
 
56
 
 
57
    if (mRadius != r) {
 
58
        mRadius = r;
 
59
        reset();
 
60
        init();
 
61
    }
 
62
}
 
63
 
 
64
void BlurShader::setDirection(Qt::Orientation direction)
 
65
{
 
66
    mDirection = direction;
 
67
}
 
68
 
 
69
float BlurShader::gaussian(float x, float sigma) const
 
70
{
 
71
    return (1.0 / std::sqrt(2.0 * M_PI) * sigma)
 
72
           * std::exp(-((x * x) / (2.0 * sigma * sigma)));
 
73
}
 
74
 
 
75
QVector<float> BlurShader::gaussianKernel() const
 
76
{
 
77
    int size = qMin(mRadius | 1, maxKernelSize());
 
78
    if (!(size & 0x1))
 
79
        size -= 1;
 
80
 
 
81
    QVector<float> kernel(size);
 
82
    const int center = size / 2;
 
83
    const qreal sigma = (size - 1) / 2.5;
 
84
 
 
85
    // Generate the gaussian kernel
 
86
    kernel[center] = gaussian(0, sigma) * .5;
 
87
    for (int i = 1; i <= center; i++) {
 
88
        const float val = gaussian(1.5 + (i - 1) * 2.0, sigma);
 
89
        kernel[center + i] = val;
 
90
        kernel[center - i] = val;
 
91
    }
 
92
 
 
93
    // Normalize the kernel
 
94
    qreal total = 0;
 
95
    for (int i = 0; i < size; i++)
 
96
        total += kernel[i];
 
97
 
 
98
    for (int i = 0; i < size; i++)
 
99
        kernel[i] /= total;
 
100
 
 
101
    return kernel;
 
102
}
 
103
 
 
104
 
 
105
 
 
106
// ----------------------------------------------------------------------------
 
107
 
 
108
 
 
109
 
 
110
GLSLBlurShader::GLSLBlurShader()
 
111
    : BlurShader(), shader(NULL)
 
112
{
 
113
}
 
114
 
 
115
GLSLBlurShader::~GLSLBlurShader()
 
116
{
 
117
    reset();
 
118
}
 
119
 
 
120
void GLSLBlurShader::reset()
 
121
{
 
122
    delete shader;
 
123
    shader = NULL;
 
124
 
 
125
    setIsValid(false);
 
126
}
 
127
 
 
128
bool GLSLBlurShader::supported()
 
129
{
 
130
    if (!GLPlatform::instance()->supports(GLSL))
 
131
        return false;
 
132
    if (!ShaderManager::instance()->isValid())
 
133
        return false;
 
134
 
 
135
    (void) glGetError(); // Clear the error state
 
136
 
 
137
#ifndef KWIN_HAVE_OPENGLES
 
138
    // These are the minimum values the implementation is required to support
 
139
    int value = 0;
 
140
 
 
141
    glGetIntegerv(GL_MAX_VARYING_FLOATS, &value);
 
142
    if (value < 32)
 
143
        return false;
 
144
 
 
145
    glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, &value);
 
146
    if (value < 64)
 
147
        return false;
 
148
 
 
149
    glGetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS, &value);
 
150
    if (value < 512)
 
151
        return false;
 
152
#endif
 
153
 
 
154
    if (glGetError() != GL_NO_ERROR)
 
155
        return false;
 
156
 
 
157
    return true;
 
158
}
 
159
 
 
160
void GLSLBlurShader::setPixelDistance(float val)
 
161
{
 
162
    if (!isValid())
 
163
        return;
 
164
 
 
165
    QVector2D pixelSize(0.0, 0.0);
 
166
    if (direction() == Qt::Horizontal)
 
167
        pixelSize.setX(val);
 
168
    else
 
169
        pixelSize.setY(val);
 
170
    shader->setUniform("pixelSize", pixelSize);
 
171
}
 
172
 
 
173
void GLSLBlurShader::setTextureMatrix(const QMatrix4x4 &matrix)
 
174
{
 
175
    if (!isValid()) {
 
176
        return;
 
177
    }
 
178
    shader->setUniform("u_textureMatrix", matrix);
 
179
}
 
180
 
 
181
void GLSLBlurShader::bind()
 
182
{
 
183
    if (!isValid())
 
184
        return;
 
185
 
 
186
    ShaderManager::instance()->pushShader(shader);
 
187
}
 
188
 
 
189
void GLSLBlurShader::unbind()
 
190
{
 
191
    ShaderManager::instance()->popShader();
 
192
}
 
193
 
 
194
int GLSLBlurShader::maxKernelSize() const
 
195
{
 
196
    int value;
 
197
#ifdef KWIN_HAVE_OPENGLES
 
198
    // GL_MAX_VARYING_FLOATS not available in GLES
 
199
    // querying for GL_MAX_VARYING_VECTORS crashes on nouveau
 
200
    // using the minimum value of 8
 
201
    return 8 * 2;
 
202
#else
 
203
    glGetIntegerv(GL_MAX_VARYING_FLOATS, &value);
 
204
    // Maximum number of vec4 varyings * 2
 
205
    // The code generator will pack two vec2's into each vec4.
 
206
    return value / 2;
 
207
#endif
 
208
}
 
209
 
 
210
 
 
211
void GLSLBlurShader::init()
 
212
{
 
213
    QVector<float> kernel = gaussianKernel();
 
214
    const int size = kernel.size();
 
215
    const int center = size / 2;
 
216
 
 
217
    QByteArray vertexSource;
 
218
    QByteArray fragmentSource;
 
219
 
 
220
    // Vertex shader
 
221
    // ===================================================================
 
222
    QTextStream stream(&vertexSource);
 
223
 
 
224
    stream << "uniform mat4 u_modelViewProjectionMatrix;\n";
 
225
    stream << "uniform mat4 u_textureMatrix;\n";
 
226
    stream << "uniform vec2 pixelSize;\n\n";
 
227
    stream << "attribute vec4 vertex;\n";
 
228
    stream << "attribute vec4 texCoord;\n\n";
 
229
    stream << "varying vec4 samplePos[" << std::ceil(size / 2.0) << "];\n";
 
230
    stream << "\n";
 
231
    stream << "void main(void)\n";
 
232
    stream << "{\n";
 
233
    stream << "    vec4 center = vec4(u_textureMatrix * texCoord).stst;\n";
 
234
    stream << "    vec4 ps = pixelSize.stst;\n\n";
 
235
    for (int i = 0; i < size; i += 2) {
 
236
        float offset1, offset2;
 
237
        if (i < center) {
 
238
            offset1 = -(1.5 + (center - i - 1) * 2.0);
 
239
            offset2 = (i + 1) == center ? 0 : offset1 + 2;
 
240
        } else if (i > center) {
 
241
            offset1 = 1.5 + (i - center - 1) * 2.0;
 
242
            offset2 = (i + 1) == size ? 0 : offset1 + 2;
 
243
        } else {
 
244
            offset1 = 0;
 
245
            offset2 = 1.5;
 
246
        }
 
247
        stream << "    samplePos[" << i / 2 << "] = center + ps * vec4("
 
248
               << offset1 << ", " << offset1 << ", " << offset2 << ", " << offset2 << ");\n";
 
249
    }
 
250
    stream << "\n";
 
251
    stream << "    gl_Position = u_modelViewProjectionMatrix * vertex;\n";
 
252
    stream << "}\n";
 
253
    stream.flush();
 
254
 
 
255
    // Fragment shader
 
256
    // ===================================================================
 
257
    QTextStream stream2(&fragmentSource);
 
258
 
 
259
    stream2 << "uniform sampler2D texUnit;\n";
 
260
    stream2 << "varying vec4 samplePos[" << std::ceil(size / 2.0) << "];\n\n";
 
261
 
 
262
    for (int i = 0; i <= center; i++)
 
263
        stream2 << "const vec4 kernel" << i << " = vec4(" << kernel[i] << ");\n";
 
264
    stream2 << "\n";
 
265
    stream2 << "void main(void)\n";
 
266
    stream2 << "{\n";
 
267
    stream2 << "    vec4 sum = texture2D(texUnit, samplePos[0].st) * kernel0;\n";
 
268
    for (int i = 1; i < size; i++)
 
269
        stream2 << "    sum = sum + texture2D(texUnit, samplePos[" << i / 2 << ((i % 2) ? "].pq)" : "].st)")
 
270
                << " * kernel" << (i > center ? size - i - 1 : i) << ";\n";
 
271
    stream2 << "    gl_FragColor = sum;\n";
 
272
    stream2 << "}\n";
 
273
    stream2.flush();
 
274
 
 
275
    shader = ShaderManager::instance()->loadShaderFromCode(vertexSource, fragmentSource);
 
276
    if (shader->isValid()) {
 
277
        QMatrix4x4 modelViewProjection;
 
278
        modelViewProjection.ortho(0, displayWidth(), displayHeight(), 0, 0, 65535);
 
279
        ShaderManager::instance()->pushShader(shader);
 
280
        shader->setUniform("texUnit", 0);
 
281
        shader->setUniform("u_textureMatrix", QMatrix4x4());
 
282
        shader->setUniform("u_modelViewProjectionMatrix", modelViewProjection);
 
283
        ShaderManager::instance()->popShader();
 
284
    }
 
285
 
 
286
    setIsValid(shader->isValid());
 
287
}
 
288
 
 
289
 
 
290
 
 
291
// ----------------------------------------------------------------------------
 
292
 
 
293
 
 
294
 
 
295
ARBBlurShader::ARBBlurShader()
 
296
    : BlurShader(), program(0)
 
297
{
 
298
}
 
299
 
 
300
ARBBlurShader::~ARBBlurShader()
 
301
{
 
302
    reset();
 
303
}
 
304
 
 
305
void ARBBlurShader::reset()
 
306
{
 
307
#ifndef KWIN_HAVE_OPENGLES
 
308
    if (program) {
 
309
        glDeleteProgramsARB(1, &program);
 
310
        program = 0;
 
311
    }
 
312
 
 
313
    setIsValid(false);
 
314
#endif
 
315
}
 
316
 
 
317
bool ARBBlurShader::supported()
 
318
{
 
319
#ifdef KWIN_HAVE_OPENGLES
 
320
    return false;
 
321
#else
 
322
    if (!hasGLExtension("GL_ARB_fragment_program"))
 
323
        return false;
 
324
 
 
325
    (void) glGetError(); // Clear the error state
 
326
 
 
327
    // These are the minimum values the implementation is required to support
 
328
    int value = 0;
 
329
 
 
330
    glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_MAX_PROGRAM_PARAMETERS_ARB, &value);
 
331
    if (value < 24)
 
332
        return false;
 
333
 
 
334
    glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_MAX_PROGRAM_TEMPORARIES_ARB, &value);
 
335
    if (value < 16)
 
336
        return false;
 
337
 
 
338
    glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_MAX_PROGRAM_INSTRUCTIONS_ARB, &value);
 
339
    if (value < 72)
 
340
        return false;
 
341
 
 
342
    glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_MAX_PROGRAM_TEX_INSTRUCTIONS_ARB, &value);
 
343
    if (value < 24)
 
344
        return false;
 
345
 
 
346
    glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_MAX_PROGRAM_TEX_INDIRECTIONS_ARB, &value);
 
347
    if (value < 4)
 
348
        return false;
 
349
 
 
350
    if (glGetError() != GL_NO_ERROR)
 
351
        return false;
 
352
 
 
353
    return true;
 
354
#endif
 
355
}
 
356
 
 
357
void ARBBlurShader::setPixelDistance(float val)
 
358
{
 
359
#ifdef KWIN_HAVE_OPENGLES
 
360
    Q_UNUSED(val)
 
361
#else
 
362
    float firstStep = val * 1.5;
 
363
    float nextStep = val * 2.0;
 
364
 
 
365
    if (direction() == Qt::Horizontal) {
 
366
        glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 0, firstStep, 0, 0, 0);
 
367
        glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 1, nextStep, 0, 0, 0);
 
368
    } else {
 
369
        glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 0, 0, firstStep, 0, 0);
 
370
        glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 1, 0, nextStep, 0, 0);
 
371
    }
 
372
#endif
 
373
}
 
374
 
 
375
void ARBBlurShader::bind()
 
376
{
 
377
#ifndef KWIN_HAVE_OPENGLES
 
378
    if (!isValid())
 
379
        return;
 
380
 
 
381
    glEnable(GL_FRAGMENT_PROGRAM_ARB);
 
382
    glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, program);
 
383
#endif
 
384
}
 
385
 
 
386
void ARBBlurShader::unbind()
 
387
{
 
388
#ifndef KWIN_HAVE_OPENGLES
 
389
    int boundObject;
 
390
    glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_BINDING_ARB, &boundObject);
 
391
    if (boundObject == program) {
 
392
        glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, 0);
 
393
        glDisable(GL_FRAGMENT_PROGRAM_ARB);
 
394
    }
 
395
#endif
 
396
}
 
397
 
 
398
int ARBBlurShader::maxKernelSize() const
 
399
{
 
400
#ifdef KWIN_HAVE_OPENGLES
 
401
    return 0;
 
402
#else
 
403
    int value;
 
404
    int result;
 
405
 
 
406
    glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_MAX_PROGRAM_PARAMETERS_ARB, &value);
 
407
    result = (value - 1) * 2; // We only need to store half the kernel, since it's symmetrical
 
408
 
 
409
    glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_MAX_PROGRAM_INSTRUCTIONS_ARB, &value);
 
410
    result = qMin(result, value / 3); // We need 3 instructions / sample
 
411
 
 
412
    return result;
 
413
#endif
 
414
}
 
415
 
 
416
void ARBBlurShader::init()
 
417
{
 
418
#ifndef KWIN_HAVE_OPENGLES
 
419
    QVector<float> kernel = gaussianKernel();
 
420
    const int size = kernel.size();
 
421
    const int center = size / 2;
 
422
 
 
423
    QByteArray text;
 
424
    QTextStream stream(&text);
 
425
 
 
426
    stream << "!!ARBfp1.0\n";
 
427
 
 
428
    // The kernel values are hardcoded into the program
 
429
    for (int i = 0; i <= center; i++)
 
430
        stream << "PARAM kernel" << i << " = " << kernel[center + i] << ";\n";
 
431
 
 
432
    stream << "PARAM firstSample = program.local[0];\n"; // Distance from gl_TexCoord[0] to the next sample
 
433
    stream << "PARAM nextSample  = program.local[1];\n"; // Distance to the subsequent sample
 
434
 
 
435
    // Temporary variables to hold coordinates and texture samples
 
436
    for (int i = 0; i < size; i++)
 
437
        stream << "TEMP temp" << i << ";\n";
 
438
 
 
439
    // Compute the texture coordinates
 
440
    stream << "ADD temp1, fragment.texcoord[0], firstSample;\n"; // temp1 = gl_TexCoord[0] + firstSample
 
441
    stream << "SUB temp2, fragment.texcoord[0], firstSample;\n"; // temp2 = gl_TexCoord[0] - firstSample
 
442
    for (int i = 1, j = 3; i < center; i++, j += 2) {
 
443
        stream << "ADD temp" << j + 0 << ", temp" << j - 2 << ", nextSample;\n";
 
444
        stream << "SUB temp" << j + 1 << ", temp" << j - 1 << ", nextSample;\n";
 
445
    }
 
446
 
 
447
    // Sample the texture coordinates
 
448
    stream << "TEX temp0, fragment.texcoord[0], texture[0], 2D;\n";
 
449
    for (int i = 1; i < size; i++)
 
450
        stream << "TEX temp" << i << ", temp" << i << ", texture[0], 2D;\n";
 
451
 
 
452
    // Multiply the samples with the kernel values and compute the sum
 
453
    stream << "MUL temp0, temp0, kernel0;\n";
 
454
    for (int i = 0, j = 1; i < center; i++) {
 
455
        stream << "MAD temp0, temp" << j++ << ", kernel" << i + 1 << ", temp0;\n";
 
456
        stream << "MAD temp0, temp" << j++ << ", kernel" << i + 1 << ", temp0;\n";
 
457
    }
 
458
 
 
459
    stream << "MOV result.color, temp0;\n";  // gl_FragColor = temp0
 
460
    stream << "END\n";
 
461
    stream.flush();
 
462
 
 
463
    glGenProgramsARB(1, &program);
 
464
    glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, program);
 
465
    glProgramStringARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, text.length(), text.constData());
 
466
 
 
467
    if (glGetError()) {
 
468
        const char *error = (const char*)glGetString(GL_PROGRAM_ERROR_STRING_ARB);
 
469
        kError() << "Failed to compile fragment program:" << error;
 
470
        setIsValid(false);
 
471
    } else
 
472
        setIsValid(true);
 
473
 
 
474
    glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, 0);
 
475
#endif
 
476
}
 
477