1/*****************************************************************
2This file is part of the KDE project.
3
4Copyright (C) 2009 Lubos Lunak <l.lunak@kde.org>
5Copyright (C) 2012 Martin Gräßlin <mgraesslin@kde.org>
6
7Permission is hereby granted, free of charge, to any person obtaining a
8copy of this software and associated documentation files (the "Software"),
9to deal in the Software without restriction, including without limitation
10the rights to use, copy, modify, merge, publish, distribute, sublicense,
11and/or sell copies of the Software, and to permit persons to whom the
12Software is furnished to do so, subject to the following conditions:
13
14The above copyright notice and this permission notice shall be included in
15all copies or substantial portions of the Software.
16
17THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
20THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23DEALINGS IN THE SOFTWARE.
24******************************************************************/
25
26#include "paintredirector.h"
27
28#include "client.h"
29#include "deleted.h"
30#include "effects.h"
31#include <kwinglplatform.h>
32#include <kwinglutils.h>
33#include <kwinxrenderutils.h>
34#include <kdebug.h>
35#include <QPaintEngine>
36#include <qevent.h>
37#include <qpainter.h>
38#include <qmath.h>
39
40namespace KWin
41{
42
43PaintRedirector *PaintRedirector::create(Client *c, QWidget *widget)
44{
45 if (effects->isOpenGLCompositing()) {
46 return new OpenGLPaintRedirector(c, widget);
47 } else {
48 if (!Extensions::nonNativePixmaps()) {
49 return new NativeXRenderPaintRedirector(c, widget);
50 }
51 return new RasterXRenderPaintRedirector(c, widget);
52 }
53}
54
55PaintRedirector::PaintRedirector(Client *c, QWidget* w)
56 : QObject(w)
57 , widget(w)
58 , recursionCheck(false)
59 , m_client(c)
60 , m_requiresRepaint(false)
61{
62 added(w);
63}
64
65PaintRedirector::~PaintRedirector()
66{
67}
68
69void PaintRedirector::reparent(Deleted *d)
70{
71 setParent(d);
72 widget = NULL;
73 m_client = NULL;
74}
75
76static int align(int value, int align)
77{
78 return (value + align - 1) & ~(align - 1);
79}
80
81void PaintRedirector::performPendingPaint()
82{
83 if (!widget) {
84 return;
85 }
86 //qDebug() << "### performing paint, pending:" << pending.boundingRect();
87 const QSize size = pending.boundingRect().size();
88 QPaintDevice *scratch = this->scratch();
89 if (scratch->width() < size.width() || scratch->height() < size.height()) {
90 int w = align(size.width(), 128);
91 int h = align(size.height(), 128);
92 scratch = recreateScratch(QSize(qMax(scratch->width(), w), qMax(scratch->height(), h)));
93 }
94 fillScratch(Qt::transparent);
95 recursionCheck = true;
96 // do not use DrawWindowBackground, it's ok to be transparent
97 widget->render(scratch, QPoint(), pending.boundingRect(), QWidget::DrawChildren);
98 recursionCheck = false;
99 cleanupTimer.start(2000, this);
100}
101
102bool PaintRedirector::isToolTip(QWidget *object) const
103{
104 // ### We need a more reliable way of doing this
105 return object->windowFlags() & Qt::ToolTip;
106}
107
108bool PaintRedirector::eventFilter(QObject* o, QEvent* e)
109{
110 if (!widget || !m_client) {
111 return false;
112 }
113 switch(e->type()) {
114 case QEvent::ChildAdded: {
115 QChildEvent* c = static_cast< QChildEvent* >(e);
116 if (c->child()->isWidgetType() && !isToolTip(static_cast< QWidget* >(c->child())))
117 added(static_cast< QWidget* >(c->child()));
118 break;
119 }
120 case QEvent::ChildRemoved: {
121 QChildEvent* c = static_cast< QChildEvent* >(e);
122 if (c->child()->isWidgetType())
123 removed(static_cast< QWidget* >(c->child()));
124 break;
125 }
126 case QEvent::Paint: {
127 if (!recursionCheck) {
128 QPaintEvent* pe = static_cast< QPaintEvent* >(e);
129 QWidget* w = static_cast< QWidget* >(o);
130 pending |= pe->region().translated(w->mapTo(widget, QPoint(0, 0)));
131 scheduled = pending;
132
133 // schedule repaint
134 const int paddingLeft = m_client->paddingLeft();
135 const int paddingTop = m_client->paddingTop();
136 const bool needsTranslate = (paddingLeft != 0 || paddingTop != 0);
137 m_client->addRepaint(needsTranslate ? pending.translated(-paddingLeft, -paddingTop) : pending);
138 m_requiresRepaint = true;
139 return true; // filter out
140 }
141 }
142 default:
143 break;
144 }
145 return false;
146}
147
148QRegion PaintRedirector::pendingRegion() const
149{
150 return pending;
151}
152
153QRegion PaintRedirector::scheduledRepaintRegion()
154{
155 QRegion tempRegion = scheduled;
156 scheduled = QRegion();
157 return tempRegion;
158}
159
160void PaintRedirector::added(QWidget* w)
161{
162 w->installEventFilter(this);
163 foreach (QObject * o, w->children()) {
164 if (o->isWidgetType() && !isToolTip(static_cast< QWidget* >(o)))
165 added(static_cast< QWidget* >(o));
166 }
167}
168
169void PaintRedirector::removed(QWidget* w)
170{
171 foreach (QObject * o, w->children()) {
172 if (o->isWidgetType())
173 removed(static_cast< QWidget* >(o));
174 }
175 w->installEventFilter(this);
176}
177
178void PaintRedirector::timerEvent(QTimerEvent* event)
179{
180 if (event->timerId() == cleanupTimer.timerId()) {
181 cleanupTimer.stop();
182 discardScratch();
183 }
184}
185
186void PaintRedirector::ensurePixmapsPainted()
187{
188 if (pending.isEmpty() || !m_client)
189 return;
190
191 performPendingPaint();
192
193 QRect rects[PixmapCount];
194 m_client->layoutDecorationRects(rects[LeftPixmap], rects[TopPixmap], rects[RightPixmap], rects[BottomPixmap], Client::DecorationRelative);
195
196 updatePixmaps(rects, pending);
197
198 pending = QRegion();
199 scheduled = QRegion();
200
201 xcb_flush(connection());
202}
203
204void PaintRedirector::updatePixmaps(const QRect *rects, const QRegion &region)
205{
206 for (int i = 0; i < PixmapCount; ++i) {
207 if (!rects[i].isValid())
208 continue;
209
210 const QRect bounding = region.boundingRect();
211 const QRegion reg = region & rects[i];
212
213 if (reg.isEmpty())
214 continue;
215
216 paint(DecorationPixmap(i), rects[i], bounding, reg);
217 }
218}
219
220void PaintRedirector::preparePaint(const QPixmap &pending)
221{
222 Q_UNUSED(pending)
223}
224
225void PaintRedirector::resizePixmaps()
226{
227 QRect rects[PixmapCount];
228 m_client->layoutDecorationRects(rects[LeftPixmap], rects[TopPixmap], rects[RightPixmap], rects[BottomPixmap], Client::DecorationRelative);
229
230 resizePixmaps(rects);
231
232 // repaint
233 if (widget) {
234 widget->update();
235 }
236}
237
238void PaintRedirector::resizePixmaps(const QRect *rects)
239{
240 for (int i = 0; i < PixmapCount; ++i) {
241 resize(DecorationPixmap(i), rects[i].size());
242 }
243}
244
245GLTexture *PaintRedirector::texture(PaintRedirector::DecorationPixmap border) const
246{
247 Q_UNUSED(border)
248 return NULL;
249}
250
251xcb_render_picture_t PaintRedirector::picture(PaintRedirector::DecorationPixmap border) const
252{
253 Q_UNUSED(border)
254 return XCB_RENDER_PICTURE_NONE;
255}
256
257void PaintRedirector::resize(DecorationPixmap border, const QSize &size)
258{
259 Q_UNUSED(border)
260 Q_UNUSED(size)
261}
262
263void PaintRedirector::paint(DecorationPixmap border, const QRect& r, const QRect &b, const QRegion &reg)
264{
265 Q_UNUSED(border)
266 Q_UNUSED(r)
267 Q_UNUSED(b)
268 Q_UNUSED(reg)
269}
270
271
272
273
274// ------------------------------------------------------------------
275
276
277
278
279ImageBasedPaintRedirector::ImageBasedPaintRedirector(Client *c, QWidget *widget)
280 : PaintRedirector(c, widget)
281{
282}
283
284ImageBasedPaintRedirector::~ImageBasedPaintRedirector()
285{
286}
287
288QPaintDevice *ImageBasedPaintRedirector::recreateScratch(const QSize &size)
289{
290 m_scratchImage = QImage(size, QImage::Format_ARGB32_Premultiplied);
291 return &m_scratchImage;
292}
293
294QPaintDevice *ImageBasedPaintRedirector::scratch()
295{
296 return &m_scratchImage;
297}
298
299void ImageBasedPaintRedirector::fillScratch(Qt::GlobalColor color)
300{
301 m_scratchImage.fill(color);
302}
303
304void ImageBasedPaintRedirector::discardScratch()
305{
306 m_scratchImage = QImage();
307}
308
309
310
311// ------------------------------------------------------------------
312
313
314OpenGLPaintRedirector::OpenGLPaintRedirector(Client *c, QWidget *widget)
315 : ImageBasedPaintRedirector(c, widget)
316{
317 for (int i = 0; i < TextureCount; ++i)
318 m_textures[i] = NULL;
319
320 PaintRedirector::resizePixmaps();
321}
322
323OpenGLPaintRedirector::~OpenGLPaintRedirector()
324{
325 for (int i = 0; i < TextureCount; ++i)
326 delete m_textures[i];
327}
328
329void OpenGLPaintRedirector::resizePixmaps(const QRect *rects)
330{
331 QSize size[2];
332 size[LeftRight] = QSize(rects[LeftPixmap].width() + rects[RightPixmap].width(),
333 align(qMax(rects[LeftPixmap].height(), rects[RightPixmap].height()), 128));
334 size[TopBottom] = QSize(align(qMax(rects[TopPixmap].width(), rects[BottomPixmap].width()), 128),
335 rects[TopPixmap].height() + rects[BottomPixmap].height());
336
337 if (!GLTexture::NPOTTextureSupported()) {
338 for (int i = 0; i < 2; i++) {
339 size[i].rwidth() = nearestPowerOfTwo(size[i].width());
340 size[i].rheight() = nearestPowerOfTwo(size[i].height());
341 }
342 }
343
344 for (int i = 0; i < 2; i++) {
345 if (m_textures[i] && m_textures[i]->size() == size[i])
346 continue;
347
348 delete m_textures[i];
349 m_textures[i] = NULL;
350
351 if (size[i].isEmpty())
352 continue;
353
354 m_textures[i] = new GLTexture(size[i].width(), size[i].height());
355 m_textures[i]->setYInverted(true);
356 m_textures[i]->setWrapMode(GL_CLAMP_TO_EDGE);
357 m_textures[i]->clear();
358 }
359}
360
361void OpenGLPaintRedirector::preparePaint(const QPixmap &pending)
362{
363 m_tempImage = pending.toImage();
364}
365
366void OpenGLPaintRedirector::updatePixmaps(const QRect *rects, const QRegion &region)
367{
368 const QImage &image = scratchImage();
369 const QRect bounding = region.boundingRect();
370
371 const int leftWidth = rects[LeftPixmap].width();
372 const int topHeight = rects[TopPixmap].height();
373
374 // Top, Right, Bottom, Left
375 GLTexture *textures[4] = { m_textures[TopBottom], m_textures[LeftRight], m_textures[TopBottom], m_textures[LeftRight] };
376 QPoint offsets[4] = { QPoint(0, 0), QPoint(leftWidth, 0), QPoint(0, topHeight), QPoint(0, 0) };
377
378 for (int i = 0; i < 4; i++) {
379 const QRect dirty = (region & rects[i]).boundingRect();
380 if (!textures[i] || dirty.isEmpty())
381 continue;
382
383 const QPoint dst = dirty.topLeft() - rects[i].topLeft() + offsets[i];
384 const QRect src(dirty.topLeft() - bounding.topLeft(), dirty.size());
385
386 textures[i]->update(image, dst, src);
387 }
388}
389
390
391
392
393// ------------------------------------------------------------------
394
395
396
397
398RasterXRenderPaintRedirector::RasterXRenderPaintRedirector(Client *c, QWidget *widget)
399 : ImageBasedPaintRedirector(c, widget)
400 , m_gc(0)
401{
402 for (int i=0; i<PixmapCount; ++i) {
403 m_pixmaps[i] = XCB_PIXMAP_NONE;
404 m_pictures[i] = NULL;
405 }
406 resizePixmaps();
407}
408
409RasterXRenderPaintRedirector::~RasterXRenderPaintRedirector()
410{
411 for (int i=0; i<PixmapCount; ++i) {
412 if (m_pixmaps[i] != XCB_PIXMAP_NONE) {
413 xcb_free_pixmap(connection(), m_pixmaps[i]);
414 }
415 delete m_pictures[i];
416 }
417 if (m_gc != 0) {
418 xcb_free_gc(connection(), m_gc);
419 }
420}
421
422xcb_render_picture_t RasterXRenderPaintRedirector::picture(PaintRedirector::DecorationPixmap border) const
423{
424 return *m_pictures[border];
425}
426
427void RasterXRenderPaintRedirector::resize(PaintRedirector::DecorationPixmap border, const QSize &size)
428{
429 if (m_sizes[border] != size) {
430 if (m_pixmaps[border] != XCB_PIXMAP_NONE) {
431 xcb_free_pixmap(connection(), m_pixmaps[border]);
432 }
433 m_pixmaps[border] = xcb_generate_id(connection());
434 xcb_create_pixmap(connection(), 32, m_pixmaps[border], rootWindow(), size.width(), size.height());
435 delete m_pictures[border];
436 m_pictures[border] = new XRenderPicture(m_pixmaps[border], 32);
437 }
438 // fill transparent
439 xcb_rectangle_t rect = {0, 0, uint16_t(size.width()), uint16_t(size.height())};
440 xcb_render_fill_rectangles(connection(), XCB_RENDER_PICT_OP_SRC, *m_pictures[border], preMultiply(Qt::transparent), 1, &rect);
441}
442
443void RasterXRenderPaintRedirector::preparePaint(const QPixmap &pending)
444{
445 m_tempImage = pending.toImage();
446}
447
448void RasterXRenderPaintRedirector::paint(PaintRedirector::DecorationPixmap border, const QRect &r, const QRect &b, const QRegion &reg)
449{
450 // clip the sub area
451 const QRect bounding = reg.boundingRect();
452 const QPoint offset = bounding.topLeft() - r.topLeft();
453 if (m_gc == 0) {
454 m_gc = xcb_generate_id(connection());
455 xcb_create_gc(connection(), m_gc, m_pixmaps[border], 0, NULL);
456 }
457
458 const QImage img(scratchImage().copy(QRect(bounding.topLeft() - b.topLeft(), bounding.size())));
459 xcb_put_image(connection(), XCB_IMAGE_FORMAT_Z_PIXMAP, m_pixmaps[border], m_gc,
460 img.width(), img.height(), offset.x(), offset.y(), 0, 32, img.byteCount(), img.constBits());
461}
462
463NativeXRenderPaintRedirector::NativeXRenderPaintRedirector(Client *c, QWidget *widget)
464 : PaintRedirector(c, widget)
465{
466 resizePixmaps();
467}
468
469NativeXRenderPaintRedirector::~NativeXRenderPaintRedirector()
470{
471}
472
473xcb_render_picture_t NativeXRenderPaintRedirector::picture(PaintRedirector::DecorationPixmap border) const
474{
475 return m_pixmaps[border].x11PictureHandle();
476}
477
478void NativeXRenderPaintRedirector::resize(PaintRedirector::DecorationPixmap border, const QSize &size)
479{
480 if (m_pixmaps[border].size() != size) {
481 m_pixmaps[border] = QPixmap(size);
482 }
483 m_pixmaps[border].fill(Qt::transparent);
484}
485
486void NativeXRenderPaintRedirector::paint(PaintRedirector::DecorationPixmap border, const QRect &r, const QRect &b, const QRegion &reg)
487{
488 QPainter pt(&m_pixmaps[border]);
489 pt.translate(-r.topLeft());
490 pt.setCompositionMode(QPainter::CompositionMode_Source);
491 pt.setClipRegion(reg);
492 pt.drawPixmap(b.topLeft(), m_scratch);
493 pt.end();
494}
495
496void NativeXRenderPaintRedirector::fillScratch(Qt::GlobalColor color)
497{
498 m_scratch.fill(color);
499}
500
501QPaintDevice *NativeXRenderPaintRedirector::recreateScratch(const QSize &size)
502{
503 m_scratch = QPixmap(size);
504 return &m_scratch;
505}
506
507QPaintDevice *NativeXRenderPaintRedirector::scratch()
508{
509 return &m_scratch;
510}
511
512void NativeXRenderPaintRedirector::discardScratch()
513{
514 m_scratch = QPixmap();
515}
516
517} // namespace
518
519#include "paintredirector.moc"
520