File indexing completed on 2024-05-19 05:32:20

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 "core/pixelgrid.h"
0009 #include "core/rendertarget.h"
0010 #include "core/renderviewport.h"
0011 #include "effect/effect.h"
0012 #include "platformsupport/scenes/opengl/openglsurfacetexture.h"
0013 #include "scene/decorationitem.h"
0014 #include "scene/imageitem.h"
0015 #include "scene/shadowitem.h"
0016 #include "scene/surfaceitem.h"
0017 #include "scene/workspacescene_opengl.h"
0018 #include "utils/common.h"
0019 
0020 namespace KWin
0021 {
0022 
0023 ItemRendererOpenGL::ItemRendererOpenGL()
0024 {
0025     const QString visualizeOptionsString = qEnvironmentVariable("KWIN_SCENE_VISUALIZE");
0026     if (!visualizeOptionsString.isEmpty()) {
0027         const QStringList visualtizeOptions = visualizeOptionsString.split(';');
0028         m_debug.fractionalEnabled = visualtizeOptions.contains(QLatin1StringView("fractional"));
0029     }
0030 }
0031 
0032 std::unique_ptr<ImageItem> ItemRendererOpenGL::createImageItem(Scene *scene, Item *parent)
0033 {
0034     return std::make_unique<ImageItemOpenGL>(scene, parent);
0035 }
0036 
0037 void ItemRendererOpenGL::beginFrame(const RenderTarget &renderTarget, const RenderViewport &viewport)
0038 {
0039     GLFramebuffer *fbo = renderTarget.framebuffer();
0040     GLFramebuffer::pushFramebuffer(fbo);
0041 
0042     GLVertexBuffer::streamingBuffer()->beginFrame();
0043 }
0044 
0045 void ItemRendererOpenGL::endFrame()
0046 {
0047     GLVertexBuffer::streamingBuffer()->endOfFrame();
0048     GLFramebuffer::popFramebuffer();
0049 }
0050 
0051 QVector4D ItemRendererOpenGL::modulate(float opacity, float brightness) const
0052 {
0053     const float a = opacity;
0054     const float rgb = opacity * brightness;
0055 
0056     return QVector4D(rgb, rgb, rgb, a);
0057 }
0058 
0059 void ItemRendererOpenGL::setBlendEnabled(bool enabled)
0060 {
0061     if (enabled && !m_blendingEnabled) {
0062         glEnable(GL_BLEND);
0063     } else if (!enabled && m_blendingEnabled) {
0064         glDisable(GL_BLEND);
0065     }
0066 
0067     m_blendingEnabled = enabled;
0068 }
0069 
0070 static OpenGLSurfaceContents bindSurfaceTexture(SurfaceItem *surfaceItem)
0071 {
0072     SurfacePixmap *surfacePixmap = surfaceItem->pixmap();
0073     auto platformSurfaceTexture =
0074         static_cast<OpenGLSurfaceTexture *>(surfacePixmap->texture());
0075     if (surfacePixmap->isDiscarded()) {
0076         return platformSurfaceTexture->texture();
0077     }
0078 
0079     if (platformSurfaceTexture->texture().isValid()) {
0080         const QRegion region = surfaceItem->damage();
0081         if (!region.isEmpty()) {
0082             platformSurfaceTexture->update(region);
0083             surfaceItem->resetDamage();
0084         }
0085     } else {
0086         if (!surfacePixmap->isValid()) {
0087             return {};
0088         }
0089         if (!platformSurfaceTexture->create()) {
0090             qCDebug(KWIN_OPENGL) << "Failed to bind window";
0091             return {};
0092         }
0093         surfaceItem->resetDamage();
0094     }
0095 
0096     return platformSurfaceTexture->texture();
0097 }
0098 
0099 static RenderGeometry clipQuads(const Item *item, const ItemRendererOpenGL::RenderContext *context)
0100 {
0101     const WindowQuadList quads = item->quads();
0102 
0103     // Item to world translation.
0104     const QPointF worldTranslation = context->transformStack.top().map(QPointF(0., 0.));
0105     const qreal scale = context->renderTargetScale;
0106 
0107     RenderGeometry geometry;
0108     geometry.reserve(quads.count() * 6);
0109 
0110     // split all quads in bounding rect with the actual rects in the region
0111     for (const WindowQuad &quad : std::as_const(quads)) {
0112         if (context->clip != infiniteRegion() && !context->hardwareClipping) {
0113             // Scale to device coordinates, rounding as needed.
0114             QRectF deviceBounds = snapToPixelGridF(scaledRect(quad.bounds(), scale));
0115 
0116             for (const QRect &clipRect : std::as_const(context->clip)) {
0117                 QRectF deviceClipRect = snapToPixelGridF(scaledRect(clipRect, scale)).translated(-worldTranslation);
0118 
0119                 const QRectF &intersected = deviceClipRect.intersected(deviceBounds);
0120                 if (intersected.isValid()) {
0121                     if (deviceBounds == intersected) {
0122                         // case 1: completely contains, include and do not check other rects
0123                         geometry.appendWindowQuad(quad, scale);
0124                         break;
0125                     }
0126                     // case 2: intersection
0127                     geometry.appendSubQuad(quad, intersected, scale);
0128                 }
0129             }
0130         } else {
0131             geometry.appendWindowQuad(quad, scale);
0132         }
0133     }
0134 
0135     return geometry;
0136 }
0137 
0138 void ItemRendererOpenGL::createRenderNode(Item *item, RenderContext *context)
0139 {
0140     const QList<Item *> sortedChildItems = item->sortedChildItems();
0141 
0142     QMatrix4x4 matrix;
0143     const auto logicalPosition = QVector2D(item->position().x(), item->position().y());
0144     const auto scale = context->renderTargetScale;
0145     matrix.translate(roundVector(logicalPosition * scale).toVector3D());
0146     matrix *= item->transform();
0147     context->transformStack.push(context->transformStack.top() * matrix);
0148 
0149     context->opacityStack.push(context->opacityStack.top() * item->opacity());
0150 
0151     for (Item *childItem : sortedChildItems) {
0152         if (childItem->z() >= 0) {
0153             break;
0154         }
0155         if (childItem->explicitVisible()) {
0156             createRenderNode(childItem, context);
0157         }
0158     }
0159 
0160     item->preprocess();
0161 
0162     RenderGeometry geometry = clipQuads(item, context);
0163 
0164     if (auto shadowItem = qobject_cast<ShadowItem *>(item)) {
0165         if (!geometry.isEmpty()) {
0166             OpenGLShadowTextureProvider *textureProvider = static_cast<OpenGLShadowTextureProvider *>(shadowItem->textureProvider());
0167             context->renderNodes.append(RenderNode{
0168                 .texture = textureProvider->shadowTexture(),
0169                 .geometry = geometry,
0170                 .transformMatrix = context->transformStack.top(),
0171                 .opacity = context->opacityStack.top(),
0172                 .hasAlpha = true,
0173                 .coordinateType = UnnormalizedCoordinates,
0174                 .scale = scale,
0175                 .colorDescription = item->colorDescription(),
0176             });
0177         }
0178     } else if (auto decorationItem = qobject_cast<DecorationItem *>(item)) {
0179         if (!geometry.isEmpty()) {
0180             auto renderer = static_cast<const SceneOpenGLDecorationRenderer *>(decorationItem->renderer());
0181             context->renderNodes.append(RenderNode{
0182                 .texture = renderer->texture(),
0183                 .geometry = geometry,
0184                 .transformMatrix = context->transformStack.top(),
0185                 .opacity = context->opacityStack.top(),
0186                 .hasAlpha = true,
0187                 .coordinateType = UnnormalizedCoordinates,
0188                 .scale = scale,
0189                 .colorDescription = item->colorDescription(),
0190             });
0191         }
0192     } else if (auto surfaceItem = qobject_cast<SurfaceItem *>(item)) {
0193         SurfacePixmap *pixmap = surfaceItem->pixmap();
0194         if (pixmap) {
0195             if (!geometry.isEmpty()) {
0196                 context->renderNodes.append(RenderNode{
0197                     .texture = bindSurfaceTexture(surfaceItem),
0198                     .geometry = geometry,
0199                     .transformMatrix = context->transformStack.top(),
0200                     .opacity = context->opacityStack.top(),
0201                     .hasAlpha = pixmap->hasAlphaChannel(),
0202                     .coordinateType = NormalizedCoordinates,
0203                     .scale = scale,
0204                     .colorDescription = item->colorDescription(),
0205                 });
0206             }
0207         }
0208     } else if (auto imageItem = qobject_cast<ImageItemOpenGL *>(item)) {
0209         if (!geometry.isEmpty()) {
0210             context->renderNodes.append(RenderNode{
0211                 .texture = imageItem->texture(),
0212                 .geometry = geometry,
0213                 .transformMatrix = context->transformStack.top(),
0214                 .opacity = context->opacityStack.top(),
0215                 .hasAlpha = imageItem->image().hasAlphaChannel(),
0216                 .coordinateType = NormalizedCoordinates,
0217                 .scale = scale,
0218                 .colorDescription = item->colorDescription(),
0219             });
0220         }
0221     }
0222 
0223     for (Item *childItem : sortedChildItems) {
0224         if (childItem->z() < 0) {
0225             continue;
0226         }
0227         if (childItem->explicitVisible()) {
0228             createRenderNode(childItem, context);
0229         }
0230     }
0231 
0232     context->transformStack.pop();
0233     context->opacityStack.pop();
0234 }
0235 
0236 void ItemRendererOpenGL::renderBackground(const RenderTarget &renderTarget, const RenderViewport &viewport, const QRegion &region)
0237 {
0238     if (region == infiniteRegion() || (region.rectCount() == 1 && (*region.begin()) == viewport.renderRect())) {
0239         glClearColor(0, 0, 0, 0);
0240         glClear(GL_COLOR_BUFFER_BIT);
0241     } else if (!region.isEmpty()) {
0242         glClearColor(0, 0, 0, 0);
0243         glEnable(GL_SCISSOR_TEST);
0244 
0245         const auto targetSize = renderTarget.size();
0246         for (const QRect &r : region) {
0247             const auto deviceRect = viewport.mapToRenderTarget(r);
0248             glScissor(deviceRect.x(), targetSize.height() - (deviceRect.y() + deviceRect.height()), deviceRect.width(), deviceRect.height());
0249             glClear(GL_COLOR_BUFFER_BIT);
0250         }
0251 
0252         glDisable(GL_SCISSOR_TEST);
0253     }
0254 }
0255 
0256 void ItemRendererOpenGL::renderItem(const RenderTarget &renderTarget, const RenderViewport &viewport, Item *item, int mask, const QRegion &region, const WindowPaintData &data)
0257 {
0258     if (region.isEmpty()) {
0259         return;
0260     }
0261 
0262     RenderContext renderContext{
0263         .projectionMatrix = data.projectionMatrix(),
0264         .clip = region,
0265         .hardwareClipping = region != infiniteRegion() && ((mask & Scene::PAINT_WINDOW_TRANSFORMED) || (mask & Scene::PAINT_SCREEN_TRANSFORMED)),
0266         .renderTargetScale = viewport.scale(),
0267     };
0268 
0269     renderContext.transformStack.push(QMatrix4x4());
0270     renderContext.opacityStack.push(data.opacity());
0271 
0272     item->setTransform(data.toMatrix(renderContext.renderTargetScale));
0273 
0274     createRenderNode(item, &renderContext);
0275 
0276     int totalVertexCount = 0;
0277     for (const RenderNode &node : std::as_const(renderContext.renderNodes)) {
0278         totalVertexCount += node.geometry.count();
0279     }
0280     if (totalVertexCount == 0) {
0281         return;
0282     }
0283 
0284     ShaderTraits baseShaderTraits = ShaderTrait::MapTexture;
0285     if (data.brightness() != 1.0) {
0286         baseShaderTraits |= ShaderTrait::Modulate;
0287     }
0288     if (data.saturation() != 1.0) {
0289         baseShaderTraits |= ShaderTrait::AdjustSaturation;
0290     }
0291 
0292     GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer();
0293     vbo->reset();
0294     vbo->setAttribLayout(std::span(GLVertexBuffer::GLVertex2DLayout), sizeof(GLVertex2D));
0295 
0296     const auto map = vbo->map<GLVertex2D>(totalVertexCount);
0297     if (!map) {
0298         return;
0299     }
0300 
0301     for (int i = 0, v = 0; i < renderContext.renderNodes.count(); i++) {
0302         RenderNode &renderNode = renderContext.renderNodes[i];
0303         if (renderNode.geometry.isEmpty()
0304             || (std::holds_alternative<GLTexture *>(renderNode.texture) && !std::get<GLTexture *>(renderNode.texture))
0305             || (std::holds_alternative<OpenGLSurfaceContents>(renderNode.texture) && !std::get<OpenGLSurfaceContents>(renderNode.texture).isValid())) {
0306             continue;
0307         }
0308 
0309         renderNode.firstVertex = v;
0310         renderNode.vertexCount = renderNode.geometry.count();
0311 
0312         GLTexture *texture = nullptr;
0313         if (std::holds_alternative<GLTexture *>(renderNode.texture)) {
0314             texture = std::get<GLTexture *>(renderNode.texture);
0315         } else {
0316             texture = std::get<OpenGLSurfaceContents>(renderNode.texture).planes.constFirst().get();
0317         }
0318         renderNode.geometry.postProcessTextureCoordinates(texture->matrix(renderNode.coordinateType));
0319 
0320         renderNode.geometry.copy(map->subspan(v));
0321         v += renderNode.geometry.count();
0322     }
0323 
0324     vbo->unmap();
0325     vbo->bindArrays();
0326 
0327     if (renderContext.hardwareClipping) {
0328         glEnable(GL_SCISSOR_TEST);
0329     }
0330 
0331     // Make sure the blend function is set up correctly in case we will be doing blending
0332     glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
0333 
0334     // The scissor region must be in the render target local coordinate system.
0335     QRegion scissorRegion = infiniteRegion();
0336     if (renderContext.hardwareClipping) {
0337         scissorRegion = viewport.mapToRenderTarget(region);
0338     }
0339 
0340     ShaderTraits lastTraits;
0341     GLShader *shader = nullptr;
0342     for (int i = 0; i < renderContext.renderNodes.count(); i++) {
0343         const RenderNode &renderNode = renderContext.renderNodes[i];
0344         if (renderNode.vertexCount == 0) {
0345             continue;
0346         }
0347 
0348         setBlendEnabled(renderNode.hasAlpha || renderNode.opacity < 1.0);
0349 
0350         ShaderTraits traits = baseShaderTraits;
0351         if (renderNode.opacity != 1.0) {
0352             traits |= ShaderTrait::Modulate;
0353         }
0354         if (renderNode.colorDescription != renderTarget.colorDescription()) {
0355             traits |= ShaderTrait::TransformColorspace;
0356         }
0357         if (!shader || traits != lastTraits) {
0358             lastTraits = traits;
0359             if (shader) {
0360                 ShaderManager::instance()->popShader();
0361             }
0362             shader = ShaderManager::instance()->pushShader(traits);
0363             if (traits & ShaderTrait::AdjustSaturation) {
0364                 const auto toXYZ = renderTarget.colorDescription().colorimetry().toXYZ();
0365                 shader->setUniform(GLShader::FloatUniform::Saturation, data.saturation());
0366                 shader->setUniform(GLShader::Vec3Uniform::PrimaryBrightness, QVector3D(toXYZ(1, 0), toXYZ(1, 1), toXYZ(1, 2)));
0367             }
0368 
0369             if (traits & ShaderTrait::MapTexture) {
0370                 shader->setUniform(GLShader::IntUniform::Sampler, 0);
0371                 shader->setUniform(GLShader::IntUniform::Sampler1, 1);
0372             }
0373         }
0374         shader->setUniform(GLShader::Mat4Uniform::ModelViewProjectionMatrix, renderContext.projectionMatrix * renderNode.transformMatrix);
0375         if (traits & ShaderTrait::Modulate) {
0376             shader->setUniform(GLShader::Vec4Uniform::ModulationConstant, modulate(renderNode.opacity, data.brightness()));
0377         }
0378         if (traits & ShaderTrait::TransformColorspace) {
0379             shader->setColorspaceUniforms(renderNode.colorDescription, renderTarget.colorDescription());
0380         }
0381 
0382         if (std::holds_alternative<GLTexture *>(renderNode.texture)) {
0383             const auto texture = std::get<GLTexture *>(renderNode.texture);
0384             glActiveTexture(GL_TEXTURE0);
0385             shader->setUniform("converter", 0);
0386             texture->bind();
0387         } else {
0388             const auto contents = std::get<OpenGLSurfaceContents>(renderNode.texture);
0389             shader->setUniform("converter", contents.planes.count() > 1);
0390             for (int plane = 0; plane < contents.planes.count(); ++plane) {
0391                 glActiveTexture(GL_TEXTURE0 + plane);
0392                 contents.planes[plane]->bind();
0393             }
0394         }
0395 
0396         vbo->draw(scissorRegion, GL_TRIANGLES, renderNode.firstVertex,
0397                   renderNode.vertexCount, renderContext.hardwareClipping);
0398 
0399         if (std::holds_alternative<GLTexture *>(renderNode.texture)) {
0400             auto texture = std::get<GLTexture *>(renderNode.texture);
0401             glActiveTexture(GL_TEXTURE0);
0402             texture->unbind();
0403         } else {
0404             const auto contents = std::get<OpenGLSurfaceContents>(renderNode.texture);
0405             for (int plane = 0; plane < contents.planes.count(); ++plane) {
0406                 glActiveTexture(GL_TEXTURE0 + plane);
0407                 contents.planes[plane]->unbind();
0408             }
0409         }
0410     }
0411     if (shader) {
0412         ShaderManager::instance()->popShader();
0413     }
0414 
0415     if (m_debug.fractionalEnabled) {
0416         visualizeFractional(viewport, scissorRegion, renderContext);
0417     }
0418 
0419     vbo->unbindArrays();
0420 
0421     setBlendEnabled(false);
0422 
0423     if (renderContext.hardwareClipping) {
0424         glDisable(GL_SCISSOR_TEST);
0425     }
0426 }
0427 
0428 void ItemRendererOpenGL::visualizeFractional(const RenderViewport &viewport, const QRegion &region, const RenderContext &renderContext)
0429 {
0430     if (!m_debug.fractionalShader) {
0431         m_debug.fractionalShader = ShaderManager::instance()->generateShaderFromFile(
0432             ShaderTrait::MapTexture,
0433             QStringLiteral(":/scene/shaders/debug_fractional.vert"),
0434             QStringLiteral(":/scene/shaders/debug_fractional.frag"));
0435     }
0436 
0437     if (!m_debug.fractionalShader) {
0438         return;
0439     }
0440 
0441     ShaderBinder debugShaderBinder(m_debug.fractionalShader.get());
0442     m_debug.fractionalShader->setUniform("fractionalPrecision", 0.01f);
0443 
0444     auto screenSize = viewport.renderRect().size() * viewport.scale();
0445     m_debug.fractionalShader->setUniform("screenSize", QVector2D(float(screenSize.width()), float(screenSize.height())));
0446 
0447     GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer();
0448 
0449     for (int i = 0; i < renderContext.renderNodes.count(); i++) {
0450         const RenderNode &renderNode = renderContext.renderNodes[i];
0451         if (renderNode.vertexCount == 0) {
0452             continue;
0453         }
0454 
0455         setBlendEnabled(true);
0456 
0457         QVector2D size;
0458         if (std::holds_alternative<GLTexture *>(renderNode.texture)) {
0459             auto texture = std::get<GLTexture *>(renderNode.texture);
0460             size = QVector2D(texture->width(), texture->height());
0461         } else {
0462             auto texture = std::get<OpenGLSurfaceContents>(renderNode.texture).planes.constFirst().get();
0463             size = QVector2D(texture->width(), texture->height());
0464         }
0465 
0466         m_debug.fractionalShader->setUniform("geometrySize", size);
0467         m_debug.fractionalShader->setUniform(GLShader::Mat4Uniform::ModelViewProjectionMatrix, renderContext.projectionMatrix * renderNode.transformMatrix);
0468 
0469         vbo->draw(region, GL_TRIANGLES, renderNode.firstVertex,
0470                   renderNode.vertexCount, renderContext.hardwareClipping);
0471     }
0472 }
0473 
0474 } // namespace KWin