diff --git a/src/libkwineffects/kwineffects.cpp b/src/libkwineffects/kwineffects.cpp index d010955bfa..d03ff725d1 100644 --- a/src/libkwineffects/kwineffects.cpp +++ b/src/libkwineffects/kwineffects.cpp @@ -1149,6 +1149,73 @@ void WindowQuadList::makeArrays(float **vertices, float **texcoords, const QSize } } +void RenderGeometry::copy(std::span destination) +{ + Q_ASSERT(int(destination.size()) >= size()); + for (std::size_t i = 0; i < destination.size(); ++i) { + destination[i] = at(i); + } +} + +void RenderGeometry::appendWindowVertex(const WindowVertex &windowVertex, qreal deviceScale) +{ + GLVertex2D glVertex; + glVertex.position = roundVector(QVector2D(windowVertex.x(), windowVertex.y()) * deviceScale); + glVertex.texcoord = QVector2D(windowVertex.u(), windowVertex.v()); + append(glVertex); +} + +void RenderGeometry::appendWindowQuad(const WindowQuad &quad, qreal deviceScale) +{ + // Geometry assumes we're rendering triangles, so add the quad's + // vertices as two triangles. Vertex order is top-left, bottom-left, + // top-right followed by top-right, bottom-left, bottom-right. + appendWindowVertex(quad[0], deviceScale); + appendWindowVertex(quad[3], deviceScale); + appendWindowVertex(quad[1], deviceScale); + + appendWindowVertex(quad[1], deviceScale); + appendWindowVertex(quad[3], deviceScale); + appendWindowVertex(quad[2], deviceScale); +} + +void RenderGeometry::appendSubQuad(const WindowQuad &quad, const QRectF &subquad, qreal deviceScale) +{ + std::array vertices; + vertices[0].position = QVector2D(subquad.topLeft()); + vertices[1].position = QVector2D(subquad.topRight()); + vertices[2].position = QVector2D(subquad.bottomRight()); + vertices[3].position = QVector2D(subquad.bottomLeft()); + + const auto deviceQuad = QRectF{QPointF(std::round(quad.left() * deviceScale), std::round(quad.top() * deviceScale)), + QPointF(std::round(quad.right() * deviceScale), std::round(quad.bottom() * deviceScale))}; + + const QPointF origin = deviceQuad.topLeft(); + const QSizeF size = deviceQuad.size(); + +#pragma GCC unroll 4 + for (int i = 0; i < 4; ++i) { + const double weight1 = (vertices[i].position.x() - origin.x()) / size.width(); + const double weight2 = (vertices[i].position.y() - origin.y()) / size.height(); + const double oneMinW1 = 1.0 - weight1; + const double oneMinW2 = 1.0 - weight2; + + const float u = oneMinW1 * oneMinW2 * quad[0].u() + weight1 * oneMinW2 * quad[1].u() + + weight1 * weight2 * quad[2].u() + oneMinW1 * weight2 * quad[3].u(); + const float v = oneMinW1 * oneMinW2 * quad[0].v() + weight1 * oneMinW2 * quad[1].v() + + weight1 * weight2 * quad[2].v() + oneMinW1 * weight2 * quad[3].v(); + vertices[i].texcoord = QVector2D(u, v); + } + + append(vertices[0]); + append(vertices[3]); + append(vertices[1]); + + append(vertices[1]); + append(vertices[3]); + append(vertices[2]); +} + /*************************************************************** Motion1D ***************************************************************/ diff --git a/src/libkwineffects/kwineffects.h b/src/libkwineffects/kwineffects.h index 8929a6ab4a..bac0a5faa1 100644 --- a/src/libkwineffects/kwineffects.h +++ b/src/libkwineffects/kwineffects.h @@ -41,6 +41,7 @@ #include #include #include +#include class KConfigGroup; class QFont; @@ -2949,6 +2950,68 @@ public: void makeArrays(float **vertices, float **texcoords, const QSizeF &size, bool yInverted) const; }; +/** + * A helper class for render geometry in device coordinates. + * + * This mostly represents a vector of vertices, with some convenience methods + * for easily converting from WindowQuad and related classes to lists of + * GLVertex2D. This class assumes rendering happens as unindexed triangles. + */ +class KWINEFFECTS_EXPORT RenderGeometry : public QVector +{ +public: + /** + * Copy geometry data into another buffer. + * + * This is primarily intended for copying into a vertex buffer for rendering. + * + * @param destination The destination buffer. This needs to be at least large + * enough to contain all elements. + */ + void copy(std::span destination); + /** + * Append a WindowVertex as a geometry vertex. + * + * WindowVertex is assumed to be in logical coordinates. It will be converted + * to device coordinates using the specified device scale and then rounded + * so it fits correctly on the device pixel grid. + * + * @param windowVertex The WindowVertex instance to append. + * @param deviceScale The scaling factor to use to go from logical to device + * coordinates. + */ + void appendWindowVertex(const WindowVertex &windowVertex, qreal deviceScale); + /** + * Append a WindowQuad as two triangles. + * + * This will append the corners of the specified WindowQuad in the right + * order so they make two triangles that can be rendered by OpenGL. The + * corners are converted to device coordinates and rounded, just like + * `appendWindowVertex()` does. + * + * @param quad The WindowQuad instance to append. + * @param deviceScale The scaling factor to use to go from logical to device + * coordinates. + */ + void appendWindowQuad(const WindowQuad &quad, qreal deviceScale); + /** + * Append a sub-quad of a WindowQuad as two triangles. + * + * This will append the sub-quad specified by `intersection` as two + * triangles. The quad is expected to be in logical coordinates, while the + * intersection is expected to be in device coordinates. The texture + * coordinates of the resulting vertices are based upon those of the quad, + * using bilinear interpolation for interpolating how much of the original + * texture coordinates to use. + * + * @param quad The WindowQuad instance to use a sub-quad of. + * @param subquad The sub-quad to append. + * @param deviceScale The scaling factor used to convert from logical to + * device coordinates. + */ + void appendSubQuad(const WindowQuad &quad, const QRectF &subquad, qreal deviceScale); +}; + class KWINEFFECTS_EXPORT WindowPrePaintData { public: diff --git a/src/scenes/opengl/scene_opengl.cpp b/src/scenes/opengl/scene_opengl.cpp index 306efd98db..2012bd53ff 100644 --- a/src/scenes/opengl/scene_opengl.cpp +++ b/src/scenes/opengl/scene_opengl.cpp @@ -286,39 +286,49 @@ static GLTexture *bindSurfaceTexture(SurfaceItem *surfaceItem) return platformSurfaceTexture->texture(); } -static WindowQuadList clipQuads(const Item *item, const SceneOpenGL::RenderContext *context) +static QRectF logicalRectToDeviceRect(const QRectF &logical, qreal deviceScale) +{ + return QRectF(QPointF(std::round(logical.left() * deviceScale), std::round(logical.top() * deviceScale)), + QPointF(std::round(logical.right() * deviceScale), std::round(logical.bottom() * deviceScale))); +} + +static RenderGeometry clipQuads(const Item *item, const SceneOpenGL::RenderContext *context) { const WindowQuadList quads = item->quads(); - if (context->clip != infiniteRegion() && !context->hardwareClipping) { - // transformStack contains translations in device pixels, but clipping - // here happens on WindowQuad which is in logical pixels. So convert - // this position back to logical pixels as WindowQuad is only converted - // to device pixels when the final conversion to GPU geometry happens. - const QPointF offset = context->transformStack.top().map(QPointF(0., 0.)) / context->renderTargetScale; - WindowQuadList ret; - ret.reserve(quads.count()); + // Item to world translation. + const QPointF worldTranslation = context->transformStack.top().map(QPointF(0., 0.)); + const qreal scale = context->renderTargetScale; - // split all quads in bounding rect with the actual rects in the region - for (const WindowQuad &quad : qAsConst(quads)) { - for (const QRect &r : qAsConst(context->clip)) { - const QRectF rf(QRectF(r).translated(-offset)); - const QRectF quadRect(QPointF(quad.left(), quad.top()), QPointF(quad.right(), quad.bottom())); - const QRectF &intersected = rf.intersected(quadRect); + RenderGeometry geometry; + geometry.reserve(quads.count() * 6); + + // split all quads in bounding rect with the actual rects in the region + for (const WindowQuad &quad : qAsConst(quads)) { + if (context->clip != infiniteRegion() && !context->hardwareClipping) { + // Scale to device coordinates, rounding as needed. + QRectF deviceBounds = logicalRectToDeviceRect(quad.bounds(), scale); + + for (const QRect &clipRect : qAsConst(context->clip)) { + QRectF deviceClipRect = logicalRectToDeviceRect(clipRect, scale).translated(-worldTranslation); + + const QRectF &intersected = deviceClipRect.intersected(deviceBounds); if (intersected.isValid()) { - if (quadRect == intersected) { + if (deviceBounds == intersected) { // case 1: completely contains, include and do not check other rects - ret << quad; + geometry.appendWindowQuad(quad, scale); break; } // case 2: intersection - ret << quad.makeSubQuad(intersected.left(), intersected.top(), intersected.right(), intersected.bottom()); + geometry.appendSubQuad(quad, intersected, scale); } } + } else { + geometry.appendWindowQuad(quad, scale); } - return ret; } - return quads; + + return geometry; } void SceneOpenGL::createRenderNode(Item *item, RenderContext *context) @@ -344,13 +354,15 @@ void SceneOpenGL::createRenderNode(Item *item, RenderContext *context) } item->preprocess(); + + RenderGeometry geometry = clipQuads(item, context); + if (auto shadowItem = qobject_cast(item)) { - WindowQuadList quads = clipQuads(item, context); - if (!quads.isEmpty()) { + if (!geometry.isEmpty()) { SceneOpenGLShadow *shadow = static_cast(shadowItem->shadow()); context->renderNodes.append(RenderNode{ .texture = shadow->shadowTexture(), - .quads = quads, + .geometry = geometry, .transformMatrix = context->transformStack.top(), .opacity = context->opacityStack.top(), .hasAlpha = true, @@ -359,12 +371,11 @@ void SceneOpenGL::createRenderNode(Item *item, RenderContext *context) }); } } else if (auto decorationItem = qobject_cast(item)) { - WindowQuadList quads = clipQuads(item, context); - if (!quads.isEmpty()) { + if (!geometry.isEmpty()) { auto renderer = static_cast(decorationItem->renderer()); context->renderNodes.append(RenderNode{ .texture = renderer->texture(), - .quads = quads, + .geometry = geometry, .transformMatrix = context->transformStack.top(), .opacity = context->opacityStack.top(), .hasAlpha = true, @@ -375,13 +386,12 @@ void SceneOpenGL::createRenderNode(Item *item, RenderContext *context) } else if (auto surfaceItem = qobject_cast(item)) { SurfacePixmap *pixmap = surfaceItem->pixmap(); if (pixmap) { - WindowQuadList quads = clipQuads(item, context); - if (!quads.isEmpty()) { + if (!geometry.isEmpty()) { // Don't bother with blending if the entire surface is opaque bool hasAlpha = pixmap->hasAlphaChannel() && !surfaceItem->shape().subtracted(surfaceItem->opaque()).isEmpty(); context->renderNodes.append(RenderNode{ .texture = bindSurfaceTexture(surfaceItem), - .quads = quads, + .geometry = geometry, .transformMatrix = context->transformStack.top(), .opacity = context->opacityStack.top(), .hasAlpha = hasAlpha, @@ -439,18 +449,15 @@ void SceneOpenGL::render(Item *item, int mask, const QRegion ®ion, const Wind createRenderNode(item, &renderContext); - int quadCount = 0; + int totalVertexCount = 0; for (const RenderNode &node : qAsConst(renderContext.renderNodes)) { - quadCount += node.quads.count(); + totalVertexCount += node.geometry.count(); } - if (!quadCount) { + if (totalVertexCount == 0) { return; } - const bool indexedQuads = GLVertexBuffer::supportsIndexedQuads(); - const GLenum primitiveType = indexedQuads ? GL_QUADS : GL_TRIANGLES; - const int verticesPerQuad = indexedQuads ? 4 : 6; - const size_t size = verticesPerQuad * quadCount * sizeof(GLVertex2D); + const size_t size = totalVertexCount * sizeof(GLVertex2D); ShaderTraits shaderTraits = ShaderTrait::MapTexture; @@ -474,7 +481,7 @@ void SceneOpenGL::render(Item *item, int mask, const QRegion ®ion, const Wind for (int i = 0, v = 0; i < renderContext.renderNodes.count(); i++) { RenderNode &renderNode = renderContext.renderNodes[i]; - if (renderNode.quads.isEmpty() || !renderNode.texture) { + if (renderNode.geometry.isEmpty() || !renderNode.texture) { continue; } @@ -483,12 +490,21 @@ void SceneOpenGL::render(Item *item, int mask, const QRegion ®ion, const Wind } renderNode.firstVertex = v; - renderNode.vertexCount = renderNode.quads.count() * verticesPerQuad; + renderNode.vertexCount = renderNode.geometry.count(); - const QMatrix4x4 matrix = renderNode.texture->matrix(renderNode.coordinateType); + const QMatrix4x4 textureMatrix = renderNode.texture->matrix(renderNode.coordinateType); + if (!textureMatrix.isIdentity()) { + // Adjust the vertex' texture coordinates with the specified matrix. + const QVector2D coeff(textureMatrix(0, 0), textureMatrix(1, 1)); + const QVector2D offset(textureMatrix(0, 3), textureMatrix(1, 3)); - renderNode.quads.makeInterleavedArrays(primitiveType, &map[v], matrix, renderNode.scale); - v += renderNode.quads.count() * verticesPerQuad; + for (auto &vertex : renderNode.geometry) { + vertex.texcoord = vertex.texcoord * coeff + offset; + } + } + + renderNode.geometry.copy(std::span(&map[v], renderNode.geometry.count())); + v += renderNode.geometry.count(); } vbo->unmap(); @@ -535,7 +551,7 @@ void SceneOpenGL::render(Item *item, int mask, const QRegion ®ion, const Wind renderNode.texture->setWrapMode(GL_CLAMP_TO_EDGE); renderNode.texture->bind(); - vbo->draw(scissorRegion, primitiveType, renderNode.firstVertex, + vbo->draw(scissorRegion, GL_TRIANGLES, renderNode.firstVertex, renderNode.vertexCount, renderContext.hardwareClipping); } diff --git a/src/scenes/opengl/scene_opengl.h b/src/scenes/opengl/scene_opengl.h index 37993c6824..47070577d1 100644 --- a/src/scenes/opengl/scene_opengl.h +++ b/src/scenes/opengl/scene_opengl.h @@ -31,7 +31,7 @@ public: struct RenderNode { GLTexture *texture = nullptr; - WindowQuadList quads; + RenderGeometry geometry; QMatrix4x4 transformMatrix; int firstVertex = 0; int vertexCount = 0;