kwin/src/scene/itemrenderer_opengl.cpp
Vlad Zahorodnii 0fd4d821ca libkwineffects: Forward declare RenderTarget and RenderViewport in kwineffects.h
kwineffects.h is included in many files and not everyone needs the
RenderTarget and RenderViewport, so forward declare them to reduce the
amount of parsing work for the compiler.
2023-03-10 07:19:33 +00:00

370 lines
13 KiB
C++

/*
SPDX-FileCopyrightText: 2022 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "scene/itemrenderer_opengl.h"
#include "libkwineffects/rendertarget.h"
#include "libkwineffects/renderviewport.h"
#include "platformsupport/scenes/opengl/openglsurfacetexture.h"
#include "scene/decorationitem.h"
#include "scene/imageitem.h"
#include "scene/shadowitem.h"
#include "scene/surfaceitem.h"
#include "scene/workspacescene_opengl.h"
namespace KWin
{
ItemRendererOpenGL::ItemRendererOpenGL()
{
}
ImageItem *ItemRendererOpenGL::createImageItem(Scene *scene, Item *parent)
{
return new ImageItemOpenGL(scene, parent);
}
void ItemRendererOpenGL::beginFrame(const RenderTarget &renderTarget, const RenderViewport &viewport)
{
GLFramebuffer *fbo = renderTarget.framebuffer();
GLFramebuffer::pushFramebuffer(fbo);
GLVertexBuffer::streamingBuffer()->beginFrame();
}
void ItemRendererOpenGL::endFrame()
{
GLVertexBuffer::streamingBuffer()->endOfFrame();
GLFramebuffer::popFramebuffer();
}
QVector4D ItemRendererOpenGL::modulate(float opacity, float brightness) const
{
const float a = opacity;
const float rgb = opacity * brightness;
return QVector4D(rgb, rgb, rgb, a);
}
void ItemRendererOpenGL::setBlendEnabled(bool enabled)
{
if (enabled && !m_blendingEnabled) {
glEnable(GL_BLEND);
} else if (!enabled && m_blendingEnabled) {
glDisable(GL_BLEND);
}
m_blendingEnabled = enabled;
}
static GLTexture *bindSurfaceTexture(SurfaceItem *surfaceItem)
{
SurfacePixmap *surfacePixmap = surfaceItem->pixmap();
auto platformSurfaceTexture =
static_cast<OpenGLSurfaceTexture *>(surfacePixmap->texture());
if (surfacePixmap->isDiscarded()) {
return platformSurfaceTexture->texture();
}
if (platformSurfaceTexture->texture()) {
const QRegion region = surfaceItem->damage();
if (!region.isEmpty()) {
platformSurfaceTexture->update(region);
surfaceItem->resetDamage();
}
} else {
if (!surfacePixmap->isValid()) {
return nullptr;
}
if (!platformSurfaceTexture->create()) {
qCDebug(KWIN_OPENGL) << "Failed to bind window";
return nullptr;
}
surfaceItem->resetDamage();
}
return platformSurfaceTexture->texture();
}
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 ItemRendererOpenGL::RenderContext *context)
{
const WindowQuadList quads = item->quads();
// Item to world translation.
const QPointF worldTranslation = context->transformStack.top().map(QPointF(0., 0.));
const qreal scale = context->renderTargetScale;
RenderGeometry geometry;
geometry.reserve(quads.count() * 6);
// split all quads in bounding rect with the actual rects in the region
for (const WindowQuad &quad : std::as_const(quads)) {
if (context->clip != infiniteRegion() && !context->hardwareClipping) {
// Scale to device coordinates, rounding as needed.
QRectF deviceBounds = logicalRectToDeviceRect(quad.bounds(), scale);
for (const QRect &clipRect : std::as_const(context->clip)) {
QRectF deviceClipRect = logicalRectToDeviceRect(clipRect, scale).translated(-worldTranslation);
const QRectF &intersected = deviceClipRect.intersected(deviceBounds);
if (intersected.isValid()) {
if (deviceBounds == intersected) {
// case 1: completely contains, include and do not check other rects
geometry.appendWindowQuad(quad, scale);
break;
}
// case 2: intersection
geometry.appendSubQuad(quad, intersected, scale);
}
}
} else {
geometry.appendWindowQuad(quad, scale);
}
}
return geometry;
}
void ItemRendererOpenGL::createRenderNode(Item *item, RenderContext *context)
{
const QList<Item *> sortedChildItems = item->sortedChildItems();
QMatrix4x4 matrix;
const auto logicalPosition = QVector2D(item->position().x(), item->position().y());
const auto scale = context->renderTargetScale;
matrix.translate(roundVector(logicalPosition * scale).toVector3D());
matrix *= item->transform();
context->transformStack.push(context->transformStack.top() * matrix);
context->opacityStack.push(context->opacityStack.top() * item->opacity());
for (Item *childItem : sortedChildItems) {
if (childItem->z() >= 0) {
break;
}
if (childItem->explicitVisible()) {
createRenderNode(childItem, context);
}
}
item->preprocess();
RenderGeometry geometry = clipQuads(item, context);
if (auto shadowItem = qobject_cast<ShadowItem *>(item)) {
if (!geometry.isEmpty()) {
OpenGLShadowTextureProvider *textureProvider = static_cast<OpenGLShadowTextureProvider *>(shadowItem->textureProvider());
context->renderNodes.append(RenderNode{
.texture = textureProvider->shadowTexture(),
.geometry = geometry,
.transformMatrix = context->transformStack.top(),
.opacity = context->opacityStack.top(),
.hasAlpha = true,
.coordinateType = UnnormalizedCoordinates,
.scale = scale,
});
}
} else if (auto decorationItem = qobject_cast<DecorationItem *>(item)) {
if (!geometry.isEmpty()) {
auto renderer = static_cast<const SceneOpenGLDecorationRenderer *>(decorationItem->renderer());
context->renderNodes.append(RenderNode{
.texture = renderer->texture(),
.geometry = geometry,
.transformMatrix = context->transformStack.top(),
.opacity = context->opacityStack.top(),
.hasAlpha = true,
.coordinateType = UnnormalizedCoordinates,
.scale = scale,
});
}
} else if (auto surfaceItem = qobject_cast<SurfaceItem *>(item)) {
SurfacePixmap *pixmap = surfaceItem->pixmap();
if (pixmap) {
if (!geometry.isEmpty()) {
context->renderNodes.append(RenderNode{
.texture = bindSurfaceTexture(surfaceItem),
.geometry = geometry,
.transformMatrix = context->transformStack.top(),
.opacity = context->opacityStack.top(),
.hasAlpha = pixmap->hasAlphaChannel(),
.coordinateType = NormalizedCoordinates,
.scale = scale,
});
}
}
} else if (auto imageItem = qobject_cast<ImageItemOpenGL *>(item)) {
if (!geometry.isEmpty()) {
context->renderNodes.append(RenderNode{
.texture = imageItem->texture(),
.geometry = geometry,
.transformMatrix = context->transformStack.top(),
.opacity = context->opacityStack.top(),
.hasAlpha = imageItem->image().hasAlphaChannel(),
.coordinateType = NormalizedCoordinates,
.scale = scale,
});
}
}
for (Item *childItem : sortedChildItems) {
if (childItem->z() < 0) {
continue;
}
if (childItem->explicitVisible()) {
createRenderNode(childItem, context);
}
}
context->transformStack.pop();
context->opacityStack.pop();
}
void ItemRendererOpenGL::renderBackground(const RenderTarget &renderTarget, const RenderViewport &viewport, const QRegion &region)
{
if (region == infiniteRegion() || (region.rectCount() == 1 && (*region.begin()) == viewport.renderRect())) {
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
} else if (!region.isEmpty()) {
glClearColor(0, 0, 0, 0);
glEnable(GL_SCISSOR_TEST);
const auto targetSize = viewport.mapToRenderTarget(viewport.renderRect());
for (const QRect &r : region) {
const auto deviceRect = viewport.mapToRenderTarget(r);
glScissor(deviceRect.x(), targetSize.height() - (deviceRect.y() + deviceRect.height()), deviceRect.width(), deviceRect.height());
glClear(GL_COLOR_BUFFER_BIT);
}
glDisable(GL_SCISSOR_TEST);
}
}
void ItemRendererOpenGL::renderItem(const RenderTarget &renderTarget, const RenderViewport &viewport, Item *item, int mask, const QRegion &region, const WindowPaintData &data)
{
if (region.isEmpty()) {
return;
}
RenderContext renderContext{
.clip = region,
.hardwareClipping = region != infiniteRegion() && ((mask & Scene::PAINT_WINDOW_TRANSFORMED) || (mask & Scene::PAINT_SCREEN_TRANSFORMED)),
.renderTargetScale = viewport.scale(),
};
renderContext.transformStack.push(QMatrix4x4());
renderContext.opacityStack.push(data.opacity());
item->setTransform(data.toMatrix(renderContext.renderTargetScale));
createRenderNode(item, &renderContext);
int totalVertexCount = 0;
for (const RenderNode &node : std::as_const(renderContext.renderNodes)) {
totalVertexCount += node.geometry.count();
}
if (totalVertexCount == 0) {
return;
}
const size_t size = totalVertexCount * sizeof(GLVertex2D);
ShaderTraits shaderTraits = ShaderTrait::MapTexture;
if (data.brightness() != 1.0) {
shaderTraits |= ShaderTrait::Modulate;
}
if (data.saturation() != 1.0) {
shaderTraits |= ShaderTrait::AdjustSaturation;
}
GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer();
vbo->reset();
vbo->setAttribLayout(GLVertexBuffer::GLVertex2DLayout, 2, sizeof(GLVertex2D));
GLVertex2D *map = (GLVertex2D *)vbo->map(size);
for (int i = 0, v = 0; i < renderContext.renderNodes.count(); i++) {
RenderNode &renderNode = renderContext.renderNodes[i];
if (renderNode.geometry.isEmpty() || !renderNode.texture) {
continue;
}
if (renderNode.opacity != 1.0) {
shaderTraits |= ShaderTrait::Modulate;
}
renderNode.firstVertex = v;
renderNode.vertexCount = renderNode.geometry.count();
renderNode.geometry.postProcessTextureCoordinates(renderNode.texture->matrix(renderNode.coordinateType));
renderNode.geometry.copy(std::span(&map[v], renderNode.geometry.count()));
v += renderNode.geometry.count();
}
vbo->unmap();
vbo->bindArrays();
GLShader *shader = ShaderManager::instance()->pushShader(shaderTraits);
shader->setUniform(GLShader::Saturation, data.saturation());
if (renderContext.hardwareClipping) {
glEnable(GL_SCISSOR_TEST);
}
// Make sure the blend function is set up correctly in case we will be doing blending
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
float opacity = -1.0;
// The scissor region must be in the render target local coordinate system.
QRegion scissorRegion = infiniteRegion();
if (renderContext.hardwareClipping) {
scissorRegion = viewport.mapToRenderTarget(region);
}
const QMatrix4x4 projectionMatrix = data.projectionMatrix();
for (int i = 0; i < renderContext.renderNodes.count(); i++) {
const RenderNode &renderNode = renderContext.renderNodes[i];
if (renderNode.vertexCount == 0) {
continue;
}
setBlendEnabled(renderNode.hasAlpha || renderNode.opacity < 1.0);
shader->setUniform(GLShader::ModelViewProjectionMatrix, projectionMatrix * renderNode.transformMatrix);
if (opacity != renderNode.opacity) {
shader->setUniform(GLShader::ModulationConstant,
modulate(renderNode.opacity, data.brightness()));
opacity = renderNode.opacity;
}
renderNode.texture->setFilter(GL_LINEAR);
renderNode.texture->setWrapMode(GL_CLAMP_TO_EDGE);
renderNode.texture->bind();
vbo->draw(scissorRegion, GL_TRIANGLES, renderNode.firstVertex,
renderNode.vertexCount, renderContext.hardwareClipping);
}
vbo->unbindArrays();
setBlendEnabled(false);
ShaderManager::instance()->popShader();
if (renderContext.hardwareClipping) {
glDisable(GL_SCISSOR_TEST);
}
}
} // namespace KWin