KWin
Loading...
Searching...
No Matches
itemrenderer_opengl.cpp
Go to the documentation of this file.
1/*
2 SPDX-FileCopyrightText: 2022 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
8#include "core/pixelgrid.h"
9#include "core/rendertarget.h"
10#include "core/renderviewport.h"
11#include "effect/effect.h"
14#include "scene/imageitem.h"
15#include "scene/shadowitem.h"
16#include "scene/surfaceitem.h"
18#include "utils/common.h"
19
20namespace KWin
21{
22
24{
25 const QString visualizeOptionsString = qEnvironmentVariable("KWIN_SCENE_VISUALIZE");
26 if (!visualizeOptionsString.isEmpty()) {
27 const QStringList visualtizeOptions = visualizeOptionsString.split(';');
28 m_debug.fractionalEnabled = visualtizeOptions.contains(QLatin1StringView("fractional"));
29 }
30}
31
32std::unique_ptr<ImageItem> ItemRendererOpenGL::createImageItem(Scene *scene, Item *parent)
33{
34 return std::make_unique<ImageItemOpenGL>(scene, parent);
35}
36
37void ItemRendererOpenGL::beginFrame(const RenderTarget &renderTarget, const RenderViewport &viewport)
38{
39 GLFramebuffer *fbo = renderTarget.framebuffer();
41
43}
44
50
51QVector4D ItemRendererOpenGL::modulate(float opacity, float brightness) const
52{
53 const float a = opacity;
54 const float rgb = opacity * brightness;
55
56 return QVector4D(rgb, rgb, rgb, a);
57}
58
59void ItemRendererOpenGL::setBlendEnabled(bool enabled)
60{
61 if (enabled && !m_blendingEnabled) {
62 glEnable(GL_BLEND);
63 } else if (!enabled && m_blendingEnabled) {
64 glDisable(GL_BLEND);
65 }
66
67 m_blendingEnabled = enabled;
68}
69
70static OpenGLSurfaceContents bindSurfaceTexture(SurfaceItem *surfaceItem)
71{
72 SurfacePixmap *surfacePixmap = surfaceItem->pixmap();
73 auto platformSurfaceTexture =
74 static_cast<OpenGLSurfaceTexture *>(surfacePixmap->texture());
75 if (surfacePixmap->isDiscarded()) {
76 return platformSurfaceTexture->texture();
77 }
78
79 if (platformSurfaceTexture->texture().isValid()) {
80 const QRegion region = surfaceItem->damage();
81 if (!region.isEmpty()) {
82 platformSurfaceTexture->update(region);
83 surfaceItem->resetDamage();
84 }
85 } else {
86 if (!surfacePixmap->isValid()) {
87 return {};
88 }
89 if (!platformSurfaceTexture->create()) {
90 qCDebug(KWIN_OPENGL) << "Failed to bind window";
91 return {};
92 }
93 surfaceItem->resetDamage();
94 }
95
96 return platformSurfaceTexture->texture();
97}
98
99static RenderGeometry clipQuads(const Item *item, const ItemRendererOpenGL::RenderContext *context)
100{
101 const WindowQuadList quads = item->quads();
102
103 // Item to world translation.
104 const QPointF worldTranslation = context->transformStack.top().map(QPointF(0., 0.));
105 const qreal scale = context->renderTargetScale;
106
107 RenderGeometry geometry;
108 geometry.reserve(quads.count() * 6);
109
110 // split all quads in bounding rect with the actual rects in the region
111 for (const WindowQuad &quad : std::as_const(quads)) {
112 if (context->clip != infiniteRegion() && !context->hardwareClipping) {
113 // Scale to device coordinates, rounding as needed.
114 QRectF deviceBounds = snapToPixelGridF(scaledRect(quad.bounds(), scale));
115
116 for (const QRect &clipRect : std::as_const(context->clip)) {
117 QRectF deviceClipRect = snapToPixelGridF(scaledRect(clipRect, scale)).translated(-worldTranslation);
118
119 const QRectF &intersected = deviceClipRect.intersected(deviceBounds);
120 if (intersected.isValid()) {
121 if (deviceBounds == intersected) {
122 // case 1: completely contains, include and do not check other rects
123 geometry.appendWindowQuad(quad, scale);
124 break;
125 }
126 // case 2: intersection
127 geometry.appendSubQuad(quad, intersected, scale);
128 }
129 }
130 } else {
131 geometry.appendWindowQuad(quad, scale);
132 }
133 }
134
135 return geometry;
136}
137
138void ItemRendererOpenGL::createRenderNode(Item *item, RenderContext *context)
139{
140 const QList<Item *> sortedChildItems = item->sortedChildItems();
141
142 QMatrix4x4 matrix;
143 const auto logicalPosition = QVector2D(item->position().x(), item->position().y());
144 const auto scale = context->renderTargetScale;
145 matrix.translate(roundVector(logicalPosition * scale).toVector3D());
146 matrix *= item->transform();
147 context->transformStack.push(context->transformStack.top() * matrix);
148
149 context->opacityStack.push(context->opacityStack.top() * item->opacity());
150
151 for (Item *childItem : sortedChildItems) {
152 if (childItem->z() >= 0) {
153 break;
154 }
155 if (childItem->explicitVisible()) {
156 createRenderNode(childItem, context);
157 }
158 }
159
160 item->preprocess();
161
162 RenderGeometry geometry = clipQuads(item, context);
163
164 if (auto shadowItem = qobject_cast<ShadowItem *>(item)) {
165 if (!geometry.isEmpty()) {
166 OpenGLShadowTextureProvider *textureProvider = static_cast<OpenGLShadowTextureProvider *>(shadowItem->textureProvider());
167 context->renderNodes.append(RenderNode{
168 .texture = textureProvider->shadowTexture(),
169 .geometry = geometry,
170 .transformMatrix = context->transformStack.top(),
171 .opacity = context->opacityStack.top(),
172 .hasAlpha = true,
173 .coordinateType = UnnormalizedCoordinates,
174 .scale = scale,
175 .colorDescription = item->colorDescription(),
176 });
177 }
178 } else if (auto decorationItem = qobject_cast<DecorationItem *>(item)) {
179 if (!geometry.isEmpty()) {
180 auto renderer = static_cast<const SceneOpenGLDecorationRenderer *>(decorationItem->renderer());
181 context->renderNodes.append(RenderNode{
182 .texture = renderer->texture(),
183 .geometry = geometry,
184 .transformMatrix = context->transformStack.top(),
185 .opacity = context->opacityStack.top(),
186 .hasAlpha = true,
187 .coordinateType = UnnormalizedCoordinates,
188 .scale = scale,
189 .colorDescription = item->colorDescription(),
190 });
191 }
192 } else if (auto surfaceItem = qobject_cast<SurfaceItem *>(item)) {
193 SurfacePixmap *pixmap = surfaceItem->pixmap();
194 if (pixmap) {
195 if (!geometry.isEmpty()) {
196 context->renderNodes.append(RenderNode{
197 .texture = bindSurfaceTexture(surfaceItem),
198 .geometry = geometry,
199 .transformMatrix = context->transformStack.top(),
200 .opacity = context->opacityStack.top(),
201 .hasAlpha = pixmap->hasAlphaChannel(),
202 .coordinateType = NormalizedCoordinates,
203 .scale = scale,
204 .colorDescription = item->colorDescription(),
205 });
206 }
207 }
208 } else if (auto imageItem = qobject_cast<ImageItemOpenGL *>(item)) {
209 if (!geometry.isEmpty()) {
210 context->renderNodes.append(RenderNode{
211 .texture = imageItem->texture(),
212 .geometry = geometry,
213 .transformMatrix = context->transformStack.top(),
214 .opacity = context->opacityStack.top(),
215 .hasAlpha = imageItem->image().hasAlphaChannel(),
216 .coordinateType = NormalizedCoordinates,
217 .scale = scale,
218 .colorDescription = item->colorDescription(),
219 });
220 }
221 }
222
223 for (Item *childItem : sortedChildItems) {
224 if (childItem->z() < 0) {
225 continue;
226 }
227 if (childItem->explicitVisible()) {
228 createRenderNode(childItem, context);
229 }
230 }
231
232 context->transformStack.pop();
233 context->opacityStack.pop();
234}
235
236void ItemRendererOpenGL::renderBackground(const RenderTarget &renderTarget, const RenderViewport &viewport, const QRegion &region)
237{
238 if (region == infiniteRegion() || (region.rectCount() == 1 && (*region.begin()) == viewport.renderRect())) {
239 glClearColor(0, 0, 0, 0);
240 glClear(GL_COLOR_BUFFER_BIT);
241 } else if (!region.isEmpty()) {
242 glClearColor(0, 0, 0, 0);
243 glEnable(GL_SCISSOR_TEST);
244
245 const auto targetSize = renderTarget.size();
246 for (const QRect &r : region) {
247 const auto deviceRect = viewport.mapToRenderTarget(r);
248 glScissor(deviceRect.x(), targetSize.height() - (deviceRect.y() + deviceRect.height()), deviceRect.width(), deviceRect.height());
249 glClear(GL_COLOR_BUFFER_BIT);
250 }
251
252 glDisable(GL_SCISSOR_TEST);
253 }
254}
255
256void ItemRendererOpenGL::renderItem(const RenderTarget &renderTarget, const RenderViewport &viewport, Item *item, int mask, const QRegion &region, const WindowPaintData &data)
257{
258 if (region.isEmpty()) {
259 return;
260 }
261
262 RenderContext renderContext{
264 .clip = region,
265 .hardwareClipping = region != infiniteRegion() && ((mask & Scene::PAINT_WINDOW_TRANSFORMED) || (mask & Scene::PAINT_SCREEN_TRANSFORMED)),
266 .renderTargetScale = viewport.scale(),
267 };
268
269 renderContext.transformStack.push(QMatrix4x4());
270 renderContext.opacityStack.push(data.opacity());
271
272 item->setTransform(data.toMatrix(renderContext.renderTargetScale));
273
274 createRenderNode(item, &renderContext);
275
276 int totalVertexCount = 0;
277 for (const RenderNode &node : std::as_const(renderContext.renderNodes)) {
278 totalVertexCount += node.geometry.count();
279 }
280 if (totalVertexCount == 0) {
281 return;
282 }
283
284 ShaderTraits baseShaderTraits = ShaderTrait::MapTexture;
285 if (data.brightness() != 1.0) {
286 baseShaderTraits |= ShaderTrait::Modulate;
287 }
288 if (data.saturation() != 1.0) {
289 baseShaderTraits |= ShaderTrait::AdjustSaturation;
290 }
291
293 vbo->reset();
295
296 const auto map = vbo->map<GLVertex2D>(totalVertexCount);
297 if (!map) {
298 return;
299 }
300
301 for (int i = 0, v = 0; i < renderContext.renderNodes.count(); i++) {
302 RenderNode &renderNode = renderContext.renderNodes[i];
303 if (renderNode.geometry.isEmpty()
304 || (std::holds_alternative<GLTexture *>(renderNode.texture) && !std::get<GLTexture *>(renderNode.texture))
305 || (std::holds_alternative<OpenGLSurfaceContents>(renderNode.texture) && !std::get<OpenGLSurfaceContents>(renderNode.texture).isValid())) {
306 continue;
307 }
308
309 renderNode.firstVertex = v;
310 renderNode.vertexCount = renderNode.geometry.count();
311
312 GLTexture *texture = nullptr;
313 if (std::holds_alternative<GLTexture *>(renderNode.texture)) {
314 texture = std::get<GLTexture *>(renderNode.texture);
315 } else {
316 texture = std::get<OpenGLSurfaceContents>(renderNode.texture).planes.constFirst().get();
317 }
318 renderNode.geometry.postProcessTextureCoordinates(texture->matrix(renderNode.coordinateType));
319
320 renderNode.geometry.copy(map->subspan(v));
321 v += renderNode.geometry.count();
322 }
323
324 vbo->unmap();
325 vbo->bindArrays();
326
327 if (renderContext.hardwareClipping) {
328 glEnable(GL_SCISSOR_TEST);
329 }
330
331 // Make sure the blend function is set up correctly in case we will be doing blending
332 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
333
334 // The scissor region must be in the render target local coordinate system.
335 QRegion scissorRegion = infiniteRegion();
336 if (renderContext.hardwareClipping) {
337 scissorRegion = viewport.mapToRenderTarget(region);
338 }
339
340 ShaderTraits lastTraits;
341 GLShader *shader = nullptr;
342 for (int i = 0; i < renderContext.renderNodes.count(); i++) {
343 const RenderNode &renderNode = renderContext.renderNodes[i];
344 if (renderNode.vertexCount == 0) {
345 continue;
346 }
347
348 setBlendEnabled(renderNode.hasAlpha || renderNode.opacity < 1.0);
349
350 ShaderTraits traits = baseShaderTraits;
351 if (renderNode.opacity != 1.0) {
352 traits |= ShaderTrait::Modulate;
353 }
354 if (renderNode.colorDescription != renderTarget.colorDescription()) {
356 }
357 if (!shader || traits != lastTraits) {
358 lastTraits = traits;
359 if (shader) {
361 }
362 shader = ShaderManager::instance()->pushShader(traits);
363 if (traits & ShaderTrait::AdjustSaturation) {
364 const auto toXYZ = renderTarget.colorDescription().colorimetry().toXYZ();
366 shader->setUniform(GLShader::Vec3Uniform::PrimaryBrightness, QVector3D(toXYZ(1, 0), toXYZ(1, 1), toXYZ(1, 2)));
367 }
368
369 if (traits & ShaderTrait::MapTexture) {
372 }
373 }
374 shader->setUniform(GLShader::Mat4Uniform::ModelViewProjectionMatrix, renderContext.projectionMatrix * renderNode.transformMatrix);
375 if (traits & ShaderTrait::Modulate) {
376 shader->setUniform(GLShader::Vec4Uniform::ModulationConstant, modulate(renderNode.opacity, data.brightness()));
377 }
379 shader->setColorspaceUniforms(renderNode.colorDescription, renderTarget.colorDescription());
380 }
381
382 if (std::holds_alternative<GLTexture *>(renderNode.texture)) {
383 const auto texture = std::get<GLTexture *>(renderNode.texture);
384 glActiveTexture(GL_TEXTURE0);
385 shader->setUniform("converter", 0);
386 texture->bind();
387 } else {
388 const auto contents = std::get<OpenGLSurfaceContents>(renderNode.texture);
389 shader->setUniform("converter", contents.planes.count() > 1);
390 for (int plane = 0; plane < contents.planes.count(); ++plane) {
391 glActiveTexture(GL_TEXTURE0 + plane);
392 contents.planes[plane]->bind();
393 }
394 }
395
396 vbo->draw(scissorRegion, GL_TRIANGLES, renderNode.firstVertex,
397 renderNode.vertexCount, renderContext.hardwareClipping);
398
399 if (std::holds_alternative<GLTexture *>(renderNode.texture)) {
400 auto texture = std::get<GLTexture *>(renderNode.texture);
401 glActiveTexture(GL_TEXTURE0);
402 texture->unbind();
403 } else {
404 const auto contents = std::get<OpenGLSurfaceContents>(renderNode.texture);
405 for (int plane = 0; plane < contents.planes.count(); ++plane) {
406 glActiveTexture(GL_TEXTURE0 + plane);
407 contents.planes[plane]->unbind();
408 }
409 }
410 }
411 if (shader) {
413 }
414
415 if (m_debug.fractionalEnabled) {
416 visualizeFractional(viewport, scissorRegion, renderContext);
417 }
418
419 vbo->unbindArrays();
420
421 setBlendEnabled(false);
422
423 if (renderContext.hardwareClipping) {
424 glDisable(GL_SCISSOR_TEST);
425 }
426}
427
428void ItemRendererOpenGL::visualizeFractional(const RenderViewport &viewport, const QRegion &region, const RenderContext &renderContext)
429{
430 if (!m_debug.fractionalShader) {
431 m_debug.fractionalShader = ShaderManager::instance()->generateShaderFromFile(
433 QStringLiteral(":/scene/shaders/debug_fractional.vert"),
434 QStringLiteral(":/scene/shaders/debug_fractional.frag"));
435 }
436
437 if (!m_debug.fractionalShader) {
438 return;
439 }
440
441 ShaderBinder debugShaderBinder(m_debug.fractionalShader.get());
442 m_debug.fractionalShader->setUniform("fractionalPrecision", 0.01f);
443
444 auto screenSize = viewport.renderRect().size() * viewport.scale();
445 m_debug.fractionalShader->setUniform("screenSize", QVector2D(float(screenSize.width()), float(screenSize.height())));
446
447 GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer();
448
449 for (int i = 0; i < renderContext.renderNodes.count(); i++) {
450 const RenderNode &renderNode = renderContext.renderNodes[i];
451 if (renderNode.vertexCount == 0) {
452 continue;
453 }
454
455 setBlendEnabled(true);
456
457 QVector2D size;
458 if (std::holds_alternative<GLTexture *>(renderNode.texture)) {
459 auto texture = std::get<GLTexture *>(renderNode.texture);
460 size = QVector2D(texture->width(), texture->height());
461 } else {
462 auto texture = std::get<OpenGLSurfaceContents>(renderNode.texture).planes.constFirst().get();
463 size = QVector2D(texture->width(), texture->height());
464 }
465
466 m_debug.fractionalShader->setUniform("geometrySize", size);
467 m_debug.fractionalShader->setUniform(GLShader::Mat4Uniform::ModelViewProjectionMatrix, renderContext.projectionMatrix * renderNode.transformMatrix);
468
469 vbo->draw(region, GL_TRIANGLES, renderNode.firstVertex,
470 renderNode.vertexCount, renderContext.hardwareClipping);
471 }
472}
473
474} // namespace KWin
const Colorimetry & colorimetry() const
const QMatrix4x4 & toXYZ() const
OpenGL framebuffer object.
static GLFramebuffer * popFramebuffer()
static void pushFramebuffer(GLFramebuffer *fbo)
bool setUniform(const char *name, float value)
Definition glshader.cpp:301
bool setColorspaceUniforms(const ColorDescription &src, const ColorDescription &dst)
Definition glshader.cpp:447
QMatrix4x4 matrix(TextureCoordinateType type) const
Vertex Buffer Object.
void draw(GLenum primitiveMode, int first, int count)
void setAttribLayout(std::span< const GLVertexAttrib > attribs, size_t stride)
static constexpr std::array GLVertex2DLayout
static GLVertexBuffer * streamingBuffer()
std::optional< std::span< T > > map(size_t count)
void setTransform(const QMatrix4x4 &transform)
Definition item.cpp:205
void beginFrame(const RenderTarget &renderTarget, const RenderViewport &viewport) override
std::unique_ptr< ImageItem > createImageItem(Scene *scene, Item *parent=nullptr) override
void renderItem(const RenderTarget &renderTarget, const RenderViewport &viewport, Item *item, int mask, const QRegion &region, const WindowPaintData &data) override
void renderBackground(const RenderTarget &renderTarget, const RenderViewport &viewport, const QRegion &region) override
void postProcessTextureCoordinates(const QMatrix4x4 &textureMatrix)
void copy(std::span< GLVertex2D > destination)
GLFramebuffer * framebuffer() const
QSize size() const
const ColorDescription & colorDescription() const
QRectF renderRect() const
QRectF mapToRenderTarget(const QRectF &logicalGeometry) const
@ PAINT_SCREEN_TRANSFORMED
Definition scene.h:60
@ PAINT_WINDOW_TRANSFORMED
Definition scene.h:55
std::unique_ptr< GLShader > generateShaderFromFile(ShaderTraits traits, const QString &vertexFile=QString(), const QString &fragmentFile=QString())
static ShaderManager * instance()
GLShader * pushShader(ShaderTraits traits)
qreal saturation() const
Definition effect.cpp:264
QMatrix4x4 toMatrix(qreal deviceScale) const
Definition effect.cpp:191
QMatrix4x4 projectionMatrix() const
Definition effect.cpp:332
qreal brightness() const
Definition effect.cpp:269
qreal opacity() const
Definition effect.cpp:259
KWIN_EXPORT QRect infiniteRegion()
Definition globals.h:234
KWIN_EXPORT QPointF snapToPixelGridF(const QPointF &point)
Definition pixelgrid.h:21
KWIN_EXPORT QRectF scaledRect(const QRectF &rect, qreal scale)
Definition globals.h:243
@ NormalizedCoordinates
Definition gltexture.h:35
@ UnnormalizedCoordinates
Definition gltexture.h:36
KWIN_EXPORT QVector2D roundVector(const QVector2D &input)
Definition globals.h:251
std::variant< GLTexture *, OpenGLSurfaceContents > texture