From bb7f627acc94e4b49ab971ab77b992f897da3091 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Thu, 8 Sep 2022 13:19:53 +0200 Subject: [PATCH] Introduce RenderGeometry class and use it for clipping in device coordinates RenderGeometry is a list of vertices that should be rendered. It's needed so we can convert WindowQuadList to device coordinates and do clipping in device coordinates. The intention is also to use it to replace arbitrary float arrays in effects eventually. --- src/libkwineffects/kwineffects.cpp | 67 +++++++++++++++++++ src/libkwineffects/kwineffects.h | 63 ++++++++++++++++++ src/scenes/opengl/scene_opengl.cpp | 100 +++++++++++++++++------------ src/scenes/opengl/scene_opengl.h | 2 +- 4 files changed, 189 insertions(+), 43 deletions(-) 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;