1/********************************************************************
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5Copyright (C) 2010 by Fredrik Höglund <fredrik@kde.org>
6Copyright (C) 2010 Martin Gräßlin <mgraesslin@kde.org>
7
8This program is free software; you can redistribute it and/or modify
9it under the terms of the GNU General Public License as published by
10the Free Software Foundation; either version 2 of the License, or
11(at your option) any later version.
12
13This program is distributed in the hope that it will be useful,
14but WITHOUT ANY WARRANTY; without even the implied warranty of
15MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16GNU General Public License for more details.
17
18You should have received a copy of the GNU General Public License
19along with this program. If not, see <http://www.gnu.org/licenses/>.
20*********************************************************************/
21
22#include "lanczosfilter.h"
23#include "client.h"
24#include "deleted.h"
25#include "effects.h"
26#include "unmanaged.h"
27#include "options.h"
28#include "workspace.h"
29
30#include <kwinglutils.h>
31#include <kwinglplatform.h>
32
33#include <kwineffects.h>
34#include <KDE/KGlobalSettings>
35
36#include <qmath.h>
37#include <cmath>
38
39namespace KWin
40{
41
42LanczosFilter::LanczosFilter(QObject* parent)
43 : QObject(parent)
44 , m_offscreenTex(0)
45 , m_offscreenTarget(0)
46 , m_inited(false)
47 , m_shader(0)
48 , m_uTexUnit(0)
49 , m_uOffsets(0)
50 , m_uKernel(0)
51{
52}
53
54LanczosFilter::~LanczosFilter()
55{
56 delete m_offscreenTarget;
57 delete m_offscreenTex;
58}
59
60void LanczosFilter::init()
61{
62 if (m_inited)
63 return;
64 m_inited = true;
65 const bool force = (qstrcmp(qgetenv("KWIN_FORCE_LANCZOS"), "1") == 0);
66 if (force) {
67 kWarning(1212) << "Lanczos Filter forced on by environment variable";
68 }
69
70 if (!force && options->glSmoothScale() != 2)
71 return; // disabled by config
72 if (!GLRenderTarget::supported())
73 return;
74
75 GLPlatform *gl = GLPlatform::instance();
76 if (!force) {
77 // The lanczos filter is reported to be broken with the Intel driver prior SandyBridge
78 if (gl->driver() == Driver_Intel && gl->chipClass() < SandyBridge)
79 return;
80 // Broken on Intel chips with Mesa 9.1 - BUG 313613
81 if (gl->driver() == Driver_Intel && gl->mesaVersion() >= kVersionNumber(9, 1) && gl->mesaVersion() < kVersionNumber(9, 2))
82 return;
83 // also radeon before R600 has trouble
84 if (gl->isRadeon() && gl->chipClass() < R600)
85 return;
86 }
87 m_shader.reset(ShaderManager::instance()->loadFragmentShader(ShaderManager::SimpleShader,
88 gl->glslVersion() >= kVersionNumber(1, 40) ?
89 ":/resources/shaders/1.40/lanczos-fragment.glsl" :
90 ":/resources/shaders/1.10/lanczos-fragment.glsl"));
91 if (m_shader->isValid()) {
92 ShaderBinder binder(m_shader.data());
93 m_uTexUnit = m_shader->uniformLocation("texUnit");
94 m_uKernel = m_shader->uniformLocation("kernel");
95 m_uOffsets = m_shader->uniformLocation("offsets");
96 } else {
97 kDebug(1212) << "Shader is not valid";
98 m_shader.reset();
99 }
100}
101
102
103void LanczosFilter::updateOffscreenSurfaces()
104{
105 int w = displayWidth();
106 int h = displayHeight();
107 if (!GLTexture::NPOTTextureSupported()) {
108 w = nearestPowerOfTwo(w);
109 h = nearestPowerOfTwo(h);
110 }
111 if (!m_offscreenTex || m_offscreenTex->width() != w || m_offscreenTex->height() != h) {
112 if (m_offscreenTex) {
113 delete m_offscreenTex;
114 delete m_offscreenTarget;
115 }
116 m_offscreenTex = new GLTexture(w, h);
117 m_offscreenTex->setFilter(GL_LINEAR);
118 m_offscreenTex->setWrapMode(GL_CLAMP_TO_EDGE);
119 m_offscreenTarget = new GLRenderTarget(*m_offscreenTex);
120 }
121}
122
123static float sinc(float x)
124{
125 return std::sin(x * M_PI) / (x * M_PI);
126}
127
128static float lanczos(float x, float a)
129{
130 if (qFuzzyCompare(x + 1.0, 1.0))
131 return 1.0;
132
133 if (qAbs(x) >= a)
134 return 0.0;
135
136 return sinc(x) * sinc(x / a);
137}
138
139void LanczosFilter::createKernel(float delta, int *size)
140{
141 const float a = 2.0;
142
143 // The two outermost samples always fall at points where the lanczos
144 // function returns 0, so we'll skip them.
145 const int sampleCount = qBound(3, qCeil(delta * a) * 2 + 1 - 2, 29);
146 const int center = sampleCount / 2;
147 const int kernelSize = center + 1;
148 const float factor = 1.0 / delta;
149
150 QVector<float> values(kernelSize);
151 float sum = 0;
152
153 for (int i = 0; i < kernelSize; i++) {
154 const float val = lanczos(i * factor, a);
155 sum += i > 0 ? val * 2 : val;
156 values[i] = val;
157 }
158
159 memset(m_kernel, 0, 16 * sizeof(QVector4D));
160
161 // Normalize the kernel
162 for (int i = 0; i < kernelSize; i++) {
163 const float val = values[i] / sum;
164 m_kernel[i] = QVector4D(val, val, val, val);
165 }
166
167 *size = kernelSize;
168}
169
170void LanczosFilter::createOffsets(int count, float width, Qt::Orientation direction)
171{
172 memset(m_offsets, 0, 16 * sizeof(QVector2D));
173 for (int i = 0; i < count; i++) {
174 m_offsets[i] = (direction == Qt::Horizontal) ?
175 QVector2D(i / width, 0) : QVector2D(0, i / width);
176 }
177}
178
179void LanczosFilter::performPaint(EffectWindowImpl* w, int mask, QRegion region, WindowPaintData& data)
180{
181 if ((data.xScale() < 0.9 || data.yScale() < 0.9) &&
182 KGlobalSettings::graphicEffectsLevel() & KGlobalSettings::SimpleAnimationEffects) {
183 if (!m_inited)
184 init();
185 const QRect screenRect = Workspace::self()->clientArea(ScreenArea, w->screen(), w->desktop());
186 // window geometry may not be bigger than screen geometry to fit into the FBO
187 QRect winGeo(w->expandedGeometry());
188 if (m_shader && winGeo.width() <= screenRect.width() && winGeo.height() <= screenRect.height()) {
189 winGeo.translate(-w->geometry().topLeft());
190 double left = winGeo.left();
191 double top = winGeo.top();
192 double width = winGeo.right() - left;
193 double height = winGeo.bottom() - top;
194
195 int tx = data.xTranslation() + w->x() + left * data.xScale();
196 int ty = data.yTranslation() + w->y() + top * data.yScale();
197 int tw = width * data.xScale();
198 int th = height * data.yScale();
199 const QRect textureRect(tx, ty, tw, th);
200 const bool hardwareClipping = !(QRegion(textureRect)-region).isEmpty();
201
202 int sw = width;
203 int sh = height;
204
205 GLTexture *cachedTexture = static_cast< GLTexture*>(w->data(LanczosCacheRole).value<void*>());
206 if (cachedTexture) {
207 if (cachedTexture->width() == tw && cachedTexture->height() == th) {
208 cachedTexture->bind();
209 if (hardwareClipping) {
210 glEnable(GL_SCISSOR_TEST);
211 }
212
213 glEnable(GL_BLEND);
214 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
215
216 const qreal rgb = data.brightness() * data.opacity();
217 const qreal a = data.opacity();
218
219 ShaderBinder binder(ShaderManager::SimpleShader);
220 GLShader *shader = binder.shader();
221 shader->setUniform(GLShader::Offset, QVector2D(0, 0));
222 shader->setUniform(GLShader::ModulationConstant, QVector4D(rgb, rgb, rgb, a));
223 shader->setUniform(GLShader::Saturation, data.saturation());
224
225 cachedTexture->render(region, textureRect, hardwareClipping);
226
227 glDisable(GL_BLEND);
228 if (hardwareClipping) {
229 glDisable(GL_SCISSOR_TEST);
230 }
231 cachedTexture->unbind();
232 m_timer.start(5000, this);
233 return;
234 } else {
235 // offscreen texture not matching - delete
236 delete cachedTexture;
237 cachedTexture = 0;
238 w->setData(LanczosCacheRole, QVariant());
239 }
240 }
241
242 WindowPaintData thumbData = data;
243 thumbData.setXScale(1.0);
244 thumbData.setYScale(1.0);
245 thumbData.setXTranslation(-w->x() - left);
246 thumbData.setYTranslation(-w->y() - top);
247 thumbData.setBrightness(1.0);
248 thumbData.setOpacity(1.0);
249 thumbData.setSaturation(1.0);
250
251 // Bind the offscreen FBO and draw the window on it unscaled
252 updateOffscreenSurfaces();
253 GLRenderTarget::pushRenderTarget(m_offscreenTarget);
254
255 glClearColor(0.0, 0.0, 0.0, 0.0);
256 glClear(GL_COLOR_BUFFER_BIT);
257 w->sceneWindow()->performPaint(mask, infiniteRegion(), thumbData);
258
259 // Create a scratch texture and copy the rendered window into it
260 GLTexture tex(sw, sh);
261 tex.setFilter(GL_LINEAR);
262 tex.setWrapMode(GL_CLAMP_TO_EDGE);
263 tex.bind();
264
265 glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, m_offscreenTex->height() - sh, sw, sh);
266
267 // Set up the shader for horizontal scaling
268 float dx = sw / float(tw);
269 int kernelSize;
270 createKernel(dx, &kernelSize);
271 createOffsets(kernelSize, sw, Qt::Horizontal);
272
273 ShaderManager::instance()->pushShader(m_shader.data());
274 setUniforms();
275
276 // Draw the window back into the FBO, this time scaled horizontally
277 glClear(GL_COLOR_BUFFER_BIT);
278 QVector<float> verts;
279 QVector<float> texCoords;
280 verts.reserve(12);
281 texCoords.reserve(12);
282
283 texCoords << 1.0 << 0.0; verts << tw << 0.0; // Top right
284 texCoords << 0.0 << 0.0; verts << 0.0 << 0.0; // Top left
285 texCoords << 0.0 << 1.0; verts << 0.0 << sh; // Bottom left
286 texCoords << 0.0 << 1.0; verts << 0.0 << sh; // Bottom left
287 texCoords << 1.0 << 1.0; verts << tw << sh; // Bottom right
288 texCoords << 1.0 << 0.0; verts << tw << 0.0; // Top right
289 GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer();
290 vbo->reset();
291 vbo->setData(6, 2, verts.constData(), texCoords.constData());
292 vbo->render(GL_TRIANGLES);
293
294 // At this point we don't need the scratch texture anymore
295 tex.unbind();
296 tex.discard();
297
298 // create scratch texture for second rendering pass
299 GLTexture tex2(tw, sh);
300 tex2.setFilter(GL_LINEAR);
301 tex2.setWrapMode(GL_CLAMP_TO_EDGE);
302 tex2.bind();
303
304 glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, m_offscreenTex->height() - sh, tw, sh);
305
306 // Set up the shader for vertical scaling
307 float dy = sh / float(th);
308 createKernel(dy, &kernelSize);
309 createOffsets(kernelSize, m_offscreenTex->height(), Qt::Vertical);
310 setUniforms();
311
312 // Now draw the horizontally scaled window in the FBO at the right
313 // coordinates on the screen, while scaling it vertically and blending it.
314 glClear(GL_COLOR_BUFFER_BIT);
315
316 verts.clear();
317
318 verts << tw << 0.0; // Top right
319 verts << 0.0 << 0.0; // Top left
320 verts << 0.0 << th; // Bottom left
321 verts << 0.0 << th; // Bottom left
322 verts << tw << th; // Bottom right
323 verts << tw << 0.0; // Top right
324 vbo->setData(6, 2, verts.constData(), texCoords.constData());
325 vbo->render(GL_TRIANGLES);
326
327 tex2.unbind();
328 tex2.discard();
329 ShaderManager::instance()->popShader();
330
331 // create cache texture
332 GLTexture *cache = new GLTexture(tw, th);
333
334 cache->setFilter(GL_LINEAR);
335 cache->setWrapMode(GL_CLAMP_TO_EDGE);
336 cache->bind();
337 glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, m_offscreenTex->height() - th, tw, th);
338 GLRenderTarget::popRenderTarget();
339
340 if (hardwareClipping) {
341 glEnable(GL_SCISSOR_TEST);
342 }
343
344 glEnable(GL_BLEND);
345 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
346
347 const qreal rgb = data.brightness() * data.opacity();
348 const qreal a = data.opacity();
349
350 ShaderBinder binder(ShaderManager::SimpleShader);
351 GLShader *shader = binder.shader();
352 shader->setUniform(GLShader::Offset, QVector2D(0, 0));
353 shader->setUniform(GLShader::ModulationConstant, QVector4D(rgb, rgb, rgb, a));
354 shader->setUniform(GLShader::Saturation, data.saturation());
355
356 cache->render(region, textureRect, hardwareClipping);
357
358 glDisable(GL_BLEND);
359
360 if (hardwareClipping) {
361 glDisable(GL_SCISSOR_TEST);
362 }
363
364 cache->unbind();
365 w->setData(LanczosCacheRole, QVariant::fromValue(static_cast<void*>(cache)));
366
367 // Delete the offscreen surface after 5 seconds
368 m_timer.start(5000, this);
369 return;
370 }
371 } // if ( effects->compositingType() == KWin::OpenGLCompositing )
372 w->sceneWindow()->performPaint(mask, region, data);
373} // End of function
374
375void LanczosFilter::timerEvent(QTimerEvent *event)
376{
377 if (event->timerId() == m_timer.timerId()) {
378 m_timer.stop();
379
380 delete m_offscreenTarget;
381 delete m_offscreenTex;
382 m_offscreenTarget = 0;
383 m_offscreenTex = 0;
384 foreach (Client *c, Workspace::self()->clientList()) {
385 discardCacheTexture(c->effectWindow());
386 }
387 foreach (Client *c, Workspace::self()->desktopList()) {
388 discardCacheTexture(c->effectWindow());
389 }
390 foreach (Unmanaged *u, Workspace::self()->unmanagedList()) {
391 discardCacheTexture(u->effectWindow());
392 }
393 foreach (Deleted *d, Workspace::self()->deletedList()) {
394 discardCacheTexture(d->effectWindow());
395 }
396 }
397}
398
399void LanczosFilter::discardCacheTexture(EffectWindow *w)
400{
401 QVariant cachedTextureVariant = w->data(LanczosCacheRole);
402 if (cachedTextureVariant.isValid()) {
403 delete static_cast< GLTexture*>(cachedTextureVariant.value<void*>());
404 w->setData(LanczosCacheRole, QVariant());
405 }
406}
407
408void LanczosFilter::setUniforms()
409{
410 glUniform1i(m_uTexUnit, 0);
411 glUniform2fv(m_uOffsets, 16, (const GLfloat*)m_offsets);
412 glUniform4fv(m_uKernel, 16, (const GLfloat*)m_kernel);
413}
414
415} // namespace
416
417