1 | /******************************************************************** |
2 | KWin - the KDE window manager |
3 | This file is part of the KDE project. |
4 | |
5 | Copyright (C) 2010 by Fredrik Höglund <fredrik@kde.org> |
6 | Copyright (C) 2010 Martin Gräßlin <mgraesslin@kde.org> |
7 | |
8 | This program is free software; you can redistribute it and/or modify |
9 | it under the terms of the GNU General Public License as published by |
10 | the Free Software Foundation; either version 2 of the License, or |
11 | (at your option) any later version. |
12 | |
13 | This program is distributed in the hope that it will be useful, |
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16 | GNU General Public License for more details. |
17 | |
18 | You should have received a copy of the GNU General Public License |
19 | along 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 | |
39 | namespace KWin |
40 | { |
41 | |
42 | LanczosFilter::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 | |
54 | LanczosFilter::~LanczosFilter() |
55 | { |
56 | delete m_offscreenTarget; |
57 | delete m_offscreenTex; |
58 | } |
59 | |
60 | void 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 | |
103 | void 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 | |
123 | static float sinc(float x) |
124 | { |
125 | return std::sin(x * M_PI) / (x * M_PI); |
126 | } |
127 | |
128 | static 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 | |
139 | void 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 | |
170 | void 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 | |
179 | void 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 | |
375 | void 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 | |
399 | void 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 | |
408 | void 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 | |