File indexing completed on 2024-05-19 16:34:46

0001 /*
0002     SPDX-FileCopyrightText: 2022 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "scene/itemrenderer_opengl.h"
0008 #include "platformsupport/scenes/opengl/openglsurfacetexture.h"
0009 #include "scene/decorationitem.h"
0010 #include "scene/imageitem.h"
0011 #include "scene/shadowitem.h"
0012 #include "scene/surfaceitem.h"
0013 #include "scene/workspacescene_opengl.h"
0014 
0015 namespace KWin
0016 {
0017 
0018 ItemRendererOpenGL::ItemRendererOpenGL()
0019 {
0020 }
0021 
0022 ImageItem *ItemRendererOpenGL::createImageItem(Scene *scene, Item *parent)
0023 {
0024     return new ImageItemOpenGL(scene, parent);
0025 }
0026 
0027 void ItemRendererOpenGL::beginFrame(RenderTarget *renderTarget)
0028 {
0029     GLFramebuffer *fbo = std::get<GLFramebuffer *>(renderTarget->nativeHandle());
0030     GLFramebuffer::pushFramebuffer(fbo);
0031 
0032     GLVertexBuffer::streamingBuffer()->beginFrame();
0033 }
0034 
0035 void ItemRendererOpenGL::endFrame()
0036 {
0037     GLVertexBuffer::streamingBuffer()->endOfFrame();
0038     GLFramebuffer::popFramebuffer();
0039 }
0040 
0041 QVector4D ItemRendererOpenGL::modulate(float opacity, float brightness) const
0042 {
0043     const float a = opacity;
0044     const float rgb = opacity * brightness;
0045 
0046     return QVector4D(rgb, rgb, rgb, a);
0047 }
0048 
0049 void ItemRendererOpenGL::setBlendEnabled(bool enabled)
0050 {
0051     if (enabled && !m_blendingEnabled) {
0052         glEnable(GL_BLEND);
0053     } else if (!enabled && m_blendingEnabled) {
0054         glDisable(GL_BLEND);
0055     }
0056 
0057     m_blendingEnabled = enabled;
0058 }
0059 
0060 static GLTexture *bindSurfaceTexture(SurfaceItem *surfaceItem)
0061 {
0062     SurfacePixmap *surfacePixmap = surfaceItem->pixmap();
0063     auto platformSurfaceTexture =
0064         static_cast<OpenGLSurfaceTexture *>(surfacePixmap->texture());
0065     if (surfacePixmap->isDiscarded()) {
0066         return platformSurfaceTexture->texture();
0067     }
0068 
0069     if (platformSurfaceTexture->texture()) {
0070         const QRegion region = surfaceItem->damage();
0071         if (!region.isEmpty()) {
0072             platformSurfaceTexture->update(region);
0073             surfaceItem->resetDamage();
0074         }
0075     } else {
0076         if (!surfacePixmap->isValid()) {
0077             return nullptr;
0078         }
0079         if (!platformSurfaceTexture->create()) {
0080             qCDebug(KWIN_OPENGL) << "Failed to bind window";
0081             return nullptr;
0082         }
0083         surfaceItem->resetDamage();
0084     }
0085 
0086     return platformSurfaceTexture->texture();
0087 }
0088 
0089 static QRectF logicalRectToDeviceRect(const QRectF &logical, qreal deviceScale)
0090 {
0091     return QRectF(QPointF(std::round(logical.left() * deviceScale), std::round(logical.top() * deviceScale)),
0092                   QPointF(std::round(logical.right() * deviceScale), std::round(logical.bottom() * deviceScale)));
0093 }
0094 
0095 static RenderGeometry clipQuads(const Item *item, const ItemRendererOpenGL::RenderContext *context)
0096 {
0097     const WindowQuadList quads = item->quads();
0098 
0099     // Item to world translation.
0100     const QPointF worldTranslation = context->transformStack.top().map(QPointF(0., 0.));
0101     const qreal scale = context->renderTargetScale;
0102 
0103     RenderGeometry geometry;
0104     geometry.reserve(quads.count() * 6);
0105 
0106     // split all quads in bounding rect with the actual rects in the region
0107     for (const WindowQuad &quad : std::as_const(quads)) {
0108         if (context->clip != infiniteRegion() && !context->hardwareClipping) {
0109             // Scale to device coordinates, rounding as needed.
0110             QRectF deviceBounds = logicalRectToDeviceRect(quad.bounds(), scale);
0111 
0112             for (const QRect &clipRect : std::as_const(context->clip)) {
0113                 QRectF deviceClipRect = logicalRectToDeviceRect(clipRect, scale).translated(-worldTranslation);
0114 
0115                 const QRectF &intersected = deviceClipRect.intersected(deviceBounds);
0116                 if (intersected.isValid()) {
0117                     if (deviceBounds == intersected) {
0118                         // case 1: completely contains, include and do not check other rects
0119                         geometry.appendWindowQuad(quad, scale);
0120                         break;
0121                     }
0122                     // case 2: intersection
0123                     geometry.appendSubQuad(quad, intersected, scale);
0124                 }
0125             }
0126         } else {
0127             geometry.appendWindowQuad(quad, scale);
0128         }
0129     }
0130 
0131     return geometry;
0132 }
0133 
0134 void ItemRendererOpenGL::createRenderNode(Item *item, RenderContext *context)
0135 {
0136     const QList<Item *> sortedChildItems = item->sortedChildItems();
0137 
0138     QMatrix4x4 matrix;
0139     const auto logicalPosition = QVector2D(item->position().x(), item->position().y());
0140     const auto scale = context->renderTargetScale;
0141     matrix.translate(roundVector(logicalPosition * scale).toVector3D());
0142     matrix *= item->transform();
0143     context->transformStack.push(context->transformStack.top() * matrix);
0144 
0145     context->opacityStack.push(context->opacityStack.top() * item->opacity());
0146 
0147     for (Item *childItem : sortedChildItems) {
0148         if (childItem->z() >= 0) {
0149             break;
0150         }
0151         if (childItem->explicitVisible()) {
0152             createRenderNode(childItem, context);
0153         }
0154     }
0155 
0156     item->preprocess();
0157 
0158     RenderGeometry geometry = clipQuads(item, context);
0159 
0160     if (auto shadowItem = qobject_cast<ShadowItem *>(item)) {
0161         if (!geometry.isEmpty()) {
0162             SceneOpenGLShadow *shadow = static_cast<SceneOpenGLShadow *>(shadowItem->shadow());
0163             context->renderNodes.append(RenderNode{
0164                 .texture = shadow->shadowTexture(),
0165                 .geometry = geometry,
0166                 .transformMatrix = context->transformStack.top(),
0167                 .opacity = context->opacityStack.top(),
0168                 .hasAlpha = true,
0169                 .coordinateType = UnnormalizedCoordinates,
0170                 .scale = scale,
0171             });
0172         }
0173     } else if (auto decorationItem = qobject_cast<DecorationItem *>(item)) {
0174         if (!geometry.isEmpty()) {
0175             auto renderer = static_cast<const SceneOpenGLDecorationRenderer *>(decorationItem->renderer());
0176             context->renderNodes.append(RenderNode{
0177                 .texture = renderer->texture(),
0178                 .geometry = geometry,
0179                 .transformMatrix = context->transformStack.top(),
0180                 .opacity = context->opacityStack.top(),
0181                 .hasAlpha = true,
0182                 .coordinateType = UnnormalizedCoordinates,
0183                 .scale = scale,
0184             });
0185         }
0186     } else if (auto surfaceItem = qobject_cast<SurfaceItem *>(item)) {
0187         SurfacePixmap *pixmap = surfaceItem->pixmap();
0188         if (pixmap) {
0189             if (!geometry.isEmpty()) {
0190                 context->renderNodes.append(RenderNode{
0191                     .texture = bindSurfaceTexture(surfaceItem),
0192                     .geometry = geometry,
0193                     .transformMatrix = context->transformStack.top(),
0194                     .opacity = context->opacityStack.top(),
0195                     .hasAlpha = pixmap->hasAlphaChannel(),
0196                     .coordinateType = NormalizedCoordinates,
0197                     .scale = scale,
0198                 });
0199             }
0200         }
0201     } else if (auto imageItem = qobject_cast<ImageItemOpenGL *>(item)) {
0202         if (!geometry.isEmpty()) {
0203             context->renderNodes.append(RenderNode{
0204                 .texture = imageItem->texture(),
0205                 .geometry = geometry,
0206                 .transformMatrix = context->transformStack.top(),
0207                 .opacity = context->opacityStack.top(),
0208                 .hasAlpha = imageItem->image().hasAlphaChannel(),
0209                 .coordinateType = NormalizedCoordinates,
0210                 .scale = scale,
0211             });
0212         }
0213     }
0214 
0215     for (Item *childItem : sortedChildItems) {
0216         if (childItem->z() < 0) {
0217             continue;
0218         }
0219         if (childItem->explicitVisible()) {
0220             createRenderNode(childItem, context);
0221         }
0222     }
0223 
0224     context->transformStack.pop();
0225     context->opacityStack.pop();
0226 }
0227 
0228 void ItemRendererOpenGL::renderBackground(const QRegion &region)
0229 {
0230     if (region == infiniteRegion() || (region.rectCount() == 1 && (*region.begin()) == renderTargetRect())) {
0231         glClearColor(0, 0, 0, 0);
0232         glClear(GL_COLOR_BUFFER_BIT);
0233     } else if (!region.isEmpty()) {
0234         glClearColor(0, 0, 0, 0);
0235         glEnable(GL_SCISSOR_TEST);
0236 
0237         const auto scale = renderTargetScale();
0238         const auto targetRect = scaledRect(renderTargetRect(), scale).toRect();
0239 
0240         for (const QRect &r : region) {
0241             auto deviceRect = scaledRect(r, scale).toAlignedRect();
0242             glScissor(deviceRect.x(), targetRect.height() - (deviceRect.y() + deviceRect.height()), deviceRect.width(), deviceRect.height());
0243             glClear(GL_COLOR_BUFFER_BIT);
0244         }
0245 
0246         glDisable(GL_SCISSOR_TEST);
0247     }
0248 }
0249 
0250 void ItemRendererOpenGL::renderItem(Item *item, int mask, const QRegion &region, const WindowPaintData &data)
0251 {
0252     if (region.isEmpty()) {
0253         return;
0254     }
0255 
0256     RenderContext renderContext{
0257         .clip = region,
0258         .hardwareClipping = region != infiniteRegion() && ((mask & Scene::PAINT_WINDOW_TRANSFORMED) || (mask & Scene::PAINT_SCREEN_TRANSFORMED)),
0259         .renderTargetScale = data.renderTargetScale().value_or(renderTargetScale()),
0260     };
0261 
0262     renderContext.transformStack.push(QMatrix4x4());
0263     renderContext.opacityStack.push(data.opacity());
0264 
0265     item->setTransform(data.toMatrix(renderContext.renderTargetScale));
0266 
0267     createRenderNode(item, &renderContext);
0268 
0269     int totalVertexCount = 0;
0270     for (const RenderNode &node : std::as_const(renderContext.renderNodes)) {
0271         totalVertexCount += node.geometry.count();
0272     }
0273     if (totalVertexCount == 0) {
0274         return;
0275     }
0276 
0277     const size_t size = totalVertexCount * sizeof(GLVertex2D);
0278 
0279     ShaderTraits shaderTraits = ShaderTrait::MapTexture;
0280 
0281     if (data.brightness() != 1.0) {
0282         shaderTraits |= ShaderTrait::Modulate;
0283     }
0284     if (data.saturation() != 1.0) {
0285         shaderTraits |= ShaderTrait::AdjustSaturation;
0286     }
0287 
0288     GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer();
0289     vbo->reset();
0290     vbo->setAttribLayout(GLVertexBuffer::GLVertex2DLayout, 2, sizeof(GLVertex2D));
0291 
0292     GLVertex2D *map = (GLVertex2D *)vbo->map(size);
0293 
0294     for (int i = 0, v = 0; i < renderContext.renderNodes.count(); i++) {
0295         RenderNode &renderNode = renderContext.renderNodes[i];
0296         if (renderNode.geometry.isEmpty() || !renderNode.texture) {
0297             continue;
0298         }
0299 
0300         if (renderNode.opacity != 1.0) {
0301             shaderTraits |= ShaderTrait::Modulate;
0302         }
0303 
0304         renderNode.firstVertex = v;
0305         renderNode.vertexCount = renderNode.geometry.count();
0306 
0307         renderNode.geometry.postProcessTextureCoordinates(renderNode.texture->matrix(renderNode.coordinateType));
0308 
0309         renderNode.geometry.copy(std::span(&map[v], renderNode.geometry.count()));
0310         v += renderNode.geometry.count();
0311     }
0312 
0313     vbo->unmap();
0314     vbo->bindArrays();
0315 
0316     GLShader *shader = ShaderManager::instance()->pushShader(shaderTraits);
0317     shader->setUniform(GLShader::Saturation, data.saturation());
0318 
0319     if (renderContext.hardwareClipping) {
0320         glEnable(GL_SCISSOR_TEST);
0321     }
0322 
0323     // Make sure the blend function is set up correctly in case we will be doing blending
0324     glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
0325 
0326     float opacity = -1.0;
0327 
0328     // The scissor region must be in the render target local coordinate system.
0329     QRegion scissorRegion = infiniteRegion();
0330     if (renderContext.hardwareClipping) {
0331         scissorRegion = mapToRenderTarget(region);
0332     }
0333 
0334     const QMatrix4x4 projectionMatrix = data.projectionMatrix();
0335     for (int i = 0; i < renderContext.renderNodes.count(); i++) {
0336         const RenderNode &renderNode = renderContext.renderNodes[i];
0337         if (renderNode.vertexCount == 0) {
0338             continue;
0339         }
0340 
0341         setBlendEnabled(renderNode.hasAlpha || renderNode.opacity < 1.0);
0342 
0343         shader->setUniform(GLShader::ModelViewProjectionMatrix, projectionMatrix * renderNode.transformMatrix);
0344         if (opacity != renderNode.opacity) {
0345             shader->setUniform(GLShader::ModulationConstant,
0346                                modulate(renderNode.opacity, data.brightness()));
0347             opacity = renderNode.opacity;
0348         }
0349 
0350         renderNode.texture->setFilter(GL_LINEAR);
0351         renderNode.texture->setWrapMode(GL_CLAMP_TO_EDGE);
0352         renderNode.texture->bind();
0353 
0354         vbo->draw(scissorRegion, GL_TRIANGLES, renderNode.firstVertex,
0355                   renderNode.vertexCount, renderContext.hardwareClipping);
0356     }
0357 
0358     vbo->unbindArrays();
0359 
0360     setBlendEnabled(false);
0361 
0362     ShaderManager::instance()->popShader();
0363 
0364     if (renderContext.hardwareClipping) {
0365         glDisable(GL_SCISSOR_TEST);
0366     }
0367 }
0368 
0369 } // namespace KWin