KWin
Loading...
Searching...
No Matches
workspacescene_opengl.cpp
Go to the documentation of this file.
1/*
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5 SPDX-FileCopyrightText: 2006 Lubos Lunak <l.lunak@kde.org>
6 SPDX-FileCopyrightText: 2009, 2010, 2011 Martin Gräßlin <mgraesslin@kde.org>
7 SPDX-FileCopyrightText: 2019 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
8
9 Based on glcompmgr code by Felix Bellaby.
10 Using code from Compiz and Beryl.
11
12 SPDX-License-Identifier: GPL-2.0-or-later
13*/
15
16#include "opengl/glplatform.h"
17
18#include "compositor.h"
19#include "core/output.h"
22#include "shadow.h"
23#include "window.h"
24
25#include <cmath>
26#include <cstddef>
27
28#include <QMatrix4x4>
29#include <QPainter>
30#include <QStringList>
31#include <QVector2D>
32#include <QVector4D>
33#include <QtMath>
34
35namespace KWin
36{
37
38/************************************************
39 * SceneOpenGL
40 ***********************************************/
41
43 : WorkspaceScene(std::make_unique<ItemRendererOpenGL>())
44 , m_backend(backend)
45{
46}
47
52
54{
55 return m_backend->makeCurrent();
56}
57
62
64{
65 return m_backend->supportsNativeFence();
66}
67
69{
70 return std::make_unique<SceneOpenGLDecorationRenderer>(impl);
71}
72
73std::unique_ptr<ShadowTextureProvider> WorkspaceSceneOpenGL::createShadowTextureProvider(Shadow *shadow)
74{
75 return std::make_unique<OpenGLShadowTextureProvider>(shadow);
76}
77
82
83std::pair<std::shared_ptr<GLTexture>, ColorDescription> WorkspaceSceneOpenGL::textureForOutput(Output *output) const
84{
85 return m_backend->textureForOutput(output);
86}
87
88//****************************************
89// SceneOpenGL::Shadow
90//****************************************
92{
93public:
97
98 void unregister(ShadowTextureProvider *provider);
99 std::shared_ptr<GLTexture> getTexture(ShadowTextureProvider *provider);
100
101private:
103 struct Data
104 {
105 std::shared_ptr<GLTexture> texture;
106 QList<ShadowTextureProvider *> providers;
107 };
108 QHash<KDecoration2::DecorationShadow *, Data> m_cache;
109};
110
116
118{
119 Q_ASSERT(m_cache.isEmpty());
120}
121
123{
124 auto it = m_cache.begin();
125 while (it != m_cache.end()) {
126 auto &d = it.value();
127 // check whether the Vector of Shadows contains our shadow and remove all of them
128 auto glIt = d.providers.begin();
129 while (glIt != d.providers.end()) {
130 if (*glIt == provider) {
131 glIt = d.providers.erase(glIt);
132 } else {
133 glIt++;
134 }
135 }
136 // if there are no shadows any more we can erase the cache entry
137 if (d.providers.isEmpty()) {
138 it = m_cache.erase(it);
139 } else {
140 it++;
141 }
142 }
143}
144
146{
147 Shadow *shadow = provider->shadow();
148 Q_ASSERT(shadow->hasDecorationShadow());
149 unregister(provider);
150 const auto decoShadow = shadow->decorationShadow().lock();
151 Q_ASSERT(decoShadow);
152 auto it = m_cache.find(decoShadow.get());
153 if (it != m_cache.end()) {
154 Q_ASSERT(!it.value().providers.contains(provider));
155 it.value().providers << provider;
156 return it.value().texture;
157 }
158 Data d;
159 d.providers << provider;
160 d.texture = GLTexture::upload(shadow->decorationShadowImage());
161 if (!d.texture) {
162 return nullptr;
163 }
164 d.texture->setFilter(GL_LINEAR);
165 d.texture->setWrapMode(GL_CLAMP_TO_EDGE);
166 m_cache.insert(decoShadow.get(), d);
167 return d.texture;
168}
169
174
183
185{
187 // simplifies a lot by going directly to
189 return;
190 }
191
192 const QSize top(m_shadow->shadowElement(Shadow::ShadowElementTop).size());
193 const QSize topRight(m_shadow->shadowElement(Shadow::ShadowElementTopRight).size());
194 const QSize right(m_shadow->shadowElement(Shadow::ShadowElementRight).size());
195 const QSize bottom(m_shadow->shadowElement(Shadow::ShadowElementBottom).size());
196 const QSize bottomLeft(m_shadow->shadowElement(Shadow::ShadowElementBottomLeft).size());
197 const QSize left(m_shadow->shadowElement(Shadow::ShadowElementLeft).size());
198 const QSize topLeft(m_shadow->shadowElement(Shadow::ShadowElementTopLeft).size());
199 const QSize bottomRight(m_shadow->shadowElement(Shadow::ShadowElementBottomRight).size());
200
201 const int width = std::max({topLeft.width(), left.width(), bottomLeft.width()}) + std::max(top.width(), bottom.width()) + std::max({topRight.width(), right.width(), bottomRight.width()});
202 const int height = std::max({topLeft.height(), top.height(), topRight.height()}) + std::max(left.height(), right.height()) + std::max({bottomLeft.height(), bottom.height(), bottomRight.height()});
203
204 if (width == 0 || height == 0) {
205 return;
206 }
207
208 QImage image(width, height, QImage::Format_ARGB32);
209 image.fill(Qt::transparent);
210
211 const int innerRectTop = std::max({topLeft.height(), top.height(), topRight.height()});
212 const int innerRectLeft = std::max({topLeft.width(), left.width(), bottomLeft.width()});
213
214 QPainter p;
215 p.begin(&image);
216
217 p.drawImage(QRectF(0, 0, topLeft.width(), topLeft.height()), m_shadow->shadowElement(Shadow::ShadowElementTopLeft));
218 p.drawImage(QRectF(innerRectLeft, 0, top.width(), top.height()), m_shadow->shadowElement(Shadow::ShadowElementTop));
219 p.drawImage(QRectF(width - topRight.width(), 0, topRight.width(), topRight.height()), m_shadow->shadowElement(Shadow::ShadowElementTopRight));
220
221 p.drawImage(QRectF(0, innerRectTop, left.width(), left.height()), m_shadow->shadowElement(Shadow::ShadowElementLeft));
222 p.drawImage(QRectF(width - right.width(), innerRectTop, right.width(), right.height()), m_shadow->shadowElement(Shadow::ShadowElementRight));
223
224 p.drawImage(QRectF(0, height - bottomLeft.height(), bottomLeft.width(), bottomLeft.height()), m_shadow->shadowElement(Shadow::ShadowElementBottomLeft));
225 p.drawImage(QRectF(innerRectLeft, height - bottom.height(), bottom.width(), bottom.height()), m_shadow->shadowElement(Shadow::ShadowElementBottom));
226 p.drawImage(QRectF(width - bottomRight.width(), height - bottomRight.height(), bottomRight.width(), bottomRight.height()), m_shadow->shadowElement(Shadow::ShadowElementBottomRight));
227
228 p.end();
229
230 // Check if the image is alpha-only in practice, and if so convert it to an 8-bpp format
232 QImage alphaImage(image.size(), QImage::Format_Alpha8);
233 bool alphaOnly = true;
234
235 for (ptrdiff_t y = 0; alphaOnly && y < image.height(); y++) {
236 const uint32_t *const src = reinterpret_cast<const uint32_t *>(image.scanLine(y));
237 uint8_t *const dst = reinterpret_cast<uint8_t *>(alphaImage.scanLine(y));
238
239 for (ptrdiff_t x = 0; x < image.width(); x++) {
240 if (src[x] & 0x00ffffff) {
241 alphaOnly = false;
242 }
243
244 dst[x] = qAlpha(src[x]);
245 }
246 }
247
248 if (alphaOnly) {
249 image = alphaImage;
250 }
251 }
252
253 m_texture = GLTexture::upload(image);
254 if (!m_texture) {
255 return;
256 }
257 m_texture->setFilter(GL_LINEAR);
258 m_texture->setWrapMode(GL_CLAMP_TO_EDGE);
259
260 if (m_texture->internalFormat() == GL_R8) {
261 // Swizzle red to alpha and all other channels to zero
262 m_texture->bind();
263 m_texture->setSwizzle(GL_ZERO, GL_ZERO, GL_ZERO, GL_RED);
264 }
265}
266
272
274{
275 if (WorkspaceScene *scene = Compositor::self()->scene()) {
276 scene->makeOpenGLContextCurrent();
277 }
278}
279
280static void clamp_row(int left, int width, int right, const uint32_t *src, uint32_t *dest)
281{
282 std::fill_n(dest, left, *src);
283 std::copy(src, src + width, dest + left);
284 std::fill_n(dest + left + width, right, *(src + width - 1));
285}
286
287static void clamp_sides(int left, int width, int right, const uint32_t *src, uint32_t *dest)
288{
289 std::fill_n(dest, left, *src);
290 std::fill_n(dest + left + width, right, *(src + width - 1));
291}
292
293static void clamp(QImage &image, const QRect &viewport)
294{
295 Q_ASSERT(image.depth() == 32);
296 if (viewport.isEmpty()) {
297 image = {};
298 return;
299 }
300
301 const QRect rect = image.rect();
302
303 const int left = viewport.left() - rect.left();
304 const int top = viewport.top() - rect.top();
305 const int right = rect.right() - viewport.right();
306 const int bottom = rect.bottom() - viewport.bottom();
307
308 const int width = rect.width() - left - right;
309 const int height = rect.height() - top - bottom;
310
311 const uint32_t *firstRow = reinterpret_cast<uint32_t *>(image.scanLine(top));
312 const uint32_t *lastRow = reinterpret_cast<uint32_t *>(image.scanLine(top + height - 1));
313
314 for (int i = 0; i < top; ++i) {
315 uint32_t *dest = reinterpret_cast<uint32_t *>(image.scanLine(i));
316 clamp_row(left, width, right, firstRow + left, dest);
317 }
318
319 for (int i = 0; i < height; ++i) {
320 uint32_t *dest = reinterpret_cast<uint32_t *>(image.scanLine(top + i));
321 clamp_sides(left, width, right, dest + left, dest);
322 }
323
324 for (int i = 0; i < bottom; ++i) {
325 uint32_t *dest = reinterpret_cast<uint32_t *>(image.scanLine(top + height + i));
326 clamp_row(left, width, right, lastRow + left, dest);
327 }
328}
329
330void SceneOpenGLDecorationRenderer::render(const QRegion &region)
331{
332 if (areImageSizesDirty()) {
333 resizeTexture();
335 }
336
337 if (!m_texture) {
338 // for invalid sizes we get no texture, see BUG 361551
339 return;
340 }
341
342 QRectF left, top, right, bottom;
343 client()->window()->layoutDecorationRects(left, top, right, bottom);
344
346 const int topHeight = std::round(top.height() * devicePixelRatio);
347 const int bottomHeight = std::round(bottom.height() * devicePixelRatio);
348 const int leftWidth = std::round(left.width() * devicePixelRatio);
349
350 const QPoint topPosition(0, 0);
351 const QPoint bottomPosition(0, topPosition.y() + topHeight + (2 * TexturePad));
352 const QPoint leftPosition(0, bottomPosition.y() + bottomHeight + (2 * TexturePad));
353 const QPoint rightPosition(0, leftPosition.y() + leftWidth + (2 * TexturePad));
354
355 const QRect dirtyRect = region.boundingRect();
356
357 renderPart(top.toRect().intersected(dirtyRect), top.toRect(), topPosition, devicePixelRatio);
358 renderPart(bottom.toRect().intersected(dirtyRect), bottom.toRect(), bottomPosition, devicePixelRatio);
359 renderPart(left.toRect().intersected(dirtyRect), left.toRect(), leftPosition, devicePixelRatio, true);
360 renderPart(right.toRect().intersected(dirtyRect), right.toRect(), rightPosition, devicePixelRatio, true);
361}
362
363void SceneOpenGLDecorationRenderer::renderPart(const QRect &rect, const QRect &partRect,
364 const QPoint &textureOffset,
365 qreal devicePixelRatio, bool rotated)
366{
367 if (!rect.isValid() || !m_texture) {
368 return;
369 }
370 // We allow partial decoration updates and it might just so happen that the
371 // dirty region is completely contained inside the decoration part, i.e.
372 // the dirty region doesn't touch any of the decoration's edges. In that
373 // case, we should **not** pad the dirty region.
374 const QMargins padding = texturePadForPart(rect, partRect);
375 int verticalPadding = padding.top() + padding.bottom();
376 int horizontalPadding = padding.left() + padding.right();
377
378 QSize imageSize(toNativeSize(rect.width()), toNativeSize(rect.height()));
379 if (rotated) {
380 imageSize = QSize(imageSize.height(), imageSize.width());
381 }
382 QSize paddedImageSize = imageSize;
383 paddedImageSize.rheight() += verticalPadding;
384 paddedImageSize.rwidth() += horizontalPadding;
385 QImage image(paddedImageSize, QImage::Format_ARGB32_Premultiplied);
386 image.setDevicePixelRatio(devicePixelRatio);
387 image.fill(Qt::transparent);
388
389 QRect padClip = QRect(padding.left(), padding.top(), imageSize.width(), imageSize.height());
390 QPainter painter(&image);
391 const qreal inverseScale = 1.0 / devicePixelRatio;
392 painter.scale(inverseScale, inverseScale);
393 painter.setRenderHint(QPainter::Antialiasing);
394 painter.setClipRect(padClip);
395 painter.translate(padding.left(), padding.top());
396 if (rotated) {
397 painter.translate(0, imageSize.height());
398 painter.rotate(-90);
399 }
400 painter.scale(devicePixelRatio, devicePixelRatio);
401 painter.translate(-rect.topLeft());
402 renderToPainter(&painter, rect);
403 painter.end();
404
405 // fill padding pixels by copying from the neighbour row
406 clamp(image, padClip);
407
408 QPoint dirtyOffset = (rect.topLeft() - partRect.topLeft()) * devicePixelRatio;
409 if (padding.top() == 0) {
410 dirtyOffset.ry() += TexturePad;
411 }
412 if (padding.left() == 0) {
413 dirtyOffset.rx() += TexturePad;
414 }
415 m_texture->update(image, textureOffset + dirtyOffset);
416}
417
418const QMargins SceneOpenGLDecorationRenderer::texturePadForPart(
419 const QRect &rect, const QRect &partRect)
420{
421 QMargins result = QMargins(0, 0, 0, 0);
422 if (rect.top() == partRect.top()) {
423 result.setTop(TexturePad);
424 }
425 if (rect.bottom() == partRect.bottom()) {
426 result.setBottom(TexturePad);
427 }
428 if (rect.left() == partRect.left()) {
429 result.setLeft(TexturePad);
430 }
431 if (rect.right() == partRect.right()) {
432 result.setRight(TexturePad);
433 }
434 return result;
435}
436
437static int align(int value, int align)
438{
439 return (value + align - 1) & ~(align - 1);
440}
441
442void SceneOpenGLDecorationRenderer::resizeTexture()
443{
444 QRectF left, top, right, bottom;
445 client()->window()->layoutDecorationRects(left, top, right, bottom);
446 QSize size;
447
448 size.rwidth() = toNativeSize(std::max(std::max(top.width(), bottom.width()),
449 std::max(left.height(), right.height())));
450 size.rheight() = toNativeSize(top.height()) + toNativeSize(bottom.height()) + toNativeSize(left.width()) + toNativeSize(right.width());
451
452 size.rheight() += 4 * (2 * TexturePad);
453 size.rwidth() += 2 * TexturePad;
454 size.rwidth() = align(size.width(), 128);
455
456 if (m_texture && m_texture->size() == size) {
457 return;
458 }
459
460 if (!size.isEmpty()) {
461 m_texture = GLTexture::allocate(GL_RGBA8, size);
462 if (!m_texture) {
463 return;
464 }
465 m_texture->setContentTransform(OutputTransform::FlipY);
466 m_texture->setFilter(GL_LINEAR);
467 m_texture->setWrapMode(GL_CLAMP_TO_EDGE);
468 m_texture->clear();
469 } else {
470 m_texture.reset();
471 }
472}
473
474int SceneOpenGLDecorationRenderer::toNativeSize(int size) const
475{
476 return std::round(size * effectiveDevicePixelRatio());
477}
478
479} // namespace
480
481#include "moc_workspacescene_opengl.cpp"
WorkspaceScene * scene() const
Definition compositor.h:60
static Compositor * self()
static const int TexturePad
void renderToPainter(QPainter *painter, const QRect &rect)
Decoration::DecoratedClientImpl * client() const
bool areImageSizesDirty() const
qreal effectiveDevicePixelRatio() const
static DecorationShadowTextureCache & instance()
std::shared_ptr< GLTexture > getTexture(ShadowTextureProvider *provider)
void unregister(ShadowTextureProvider *provider)
DecorationShadowTextureCache(const DecorationShadowTextureCache &)=delete
static GLPlatform * instance()
Definition glplatform.h:394
bool isSoftwareEmulation() const
static std::unique_ptr< GLTexture > allocate(GLenum internalFormat, const QSize &size, int levels=1)
static bool supportsFormatRG()
static std::unique_ptr< GLTexture > upload(const QImage &image)
static bool supportsSwizzle()
The OpenGLBackend creates and holds the OpenGL context and is responsible for Texture from Pixmap.
virtual std::pair< std::shared_ptr< GLTexture >, ColorDescription > textureForOutput(Output *output) const
virtual void doneCurrent()=0
bool supportsNativeFence() const
virtual bool makeCurrent()=0
void render(const QRegion &region) override
SceneOpenGLDecorationRenderer(Decoration::DecoratedClientImpl *client)
Class representing a Window's Shadow to be rendered by the Compositor.
Definition shadow.h:48
bool hasDecorationShadow() const
Definition shadow.h:79
@ ShadowElementLeft
Definition shadow.h:97
@ ShadowElementTopRight
Definition shadow.h:92
@ ShadowElementBottomLeft
Definition shadow.h:96
@ ShadowElementBottomRight
Definition shadow.h:94
@ ShadowElementTopLeft
Definition shadow.h:98
@ ShadowElementRight
Definition shadow.h:93
@ ShadowElementTop
Definition shadow.h:91
@ ShadowElementBottom
Definition shadow.h:95
const QImage & shadowElement(ShadowElements element) const
Definition shadow.h:111
QImage decorationShadowImage() const
Definition shadow.cpp:323
std::weak_ptr< KDecoration2::DecorationShadow > decorationShadow() const
Definition shadow.h:85
Shadow * shadow() const
Definition shadowitem.h:23
void layoutDecorationRects(QRectF &left, QRectF &top, QRectF &right, QRectF &bottom) const
Definition window.cpp:2690
virtual bool makeOpenGLContextCurrent()
std::unique_ptr< ShadowTextureProvider > createShadowTextureProvider(Shadow *shadow) override
std::unique_ptr< DecorationRenderer > createDecorationRenderer(Decoration::DecoratedClientImpl *impl) override
WorkspaceSceneOpenGL(OpenGLBackend *backend)
std::pair< std::shared_ptr< GLTexture >, ColorDescription > textureForOutput(Output *output) const override
bool animationsSupported() const override
bool supportsNativeFence() const override
T align(T value, int bytes)