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 ®ion) 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 ®ion, 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