You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
593 lines
25 KiB
593 lines
25 KiB
/* |
|
KWin - the KDE window manager |
|
This file is part of the KDE project. |
|
|
|
SPDX-FileCopyrightText: 2006 Lubos Lunak <l.lunak@kde.org> |
|
|
|
SPDX-License-Identifier: GPL-2.0-or-later |
|
*/ |
|
|
|
#include "compositor_wayland.h" |
|
#include "core/brightnessdevice.h" |
|
#include "core/graphicsbufferview.h" |
|
#include "core/output.h" |
|
#include "core/outputbackend.h" |
|
#include "core/renderbackend.h" |
|
#include "core/renderlayer.h" |
|
#include "cursorsource.h" |
|
#include "effect/effecthandler.h" |
|
#include "ftrace.h" |
|
#include "main.h" |
|
#include "opengl/glplatform.h" |
|
#include "platformsupport/scenes/opengl/openglbackend.h" |
|
#include "platformsupport/scenes/qpainter/qpainterbackend.h" |
|
#include "scene/cursordelegate_opengl.h" |
|
#include "scene/cursordelegate_qpainter.h" |
|
#include "scene/cursorscene.h" |
|
#include "scene/itemrenderer_opengl.h" |
|
#include "scene/itemrenderer_qpainter.h" |
|
#include "scene/workspacescene_opengl.h" |
|
#include "scene/workspacescene_qpainter.h" |
|
#include "window.h" |
|
#include "workspace.h" |
|
|
|
#if KWIN_BUILD_NOTIFICATIONS |
|
#include <KNotification> |
|
#endif |
|
#include <KLocalizedString> |
|
#include <QQuickWindow> |
|
|
|
namespace KWin |
|
{ |
|
|
|
WaylandCompositor *WaylandCompositor::create(QObject *parent) |
|
{ |
|
Q_ASSERT(!s_compositor); |
|
auto *compositor = new WaylandCompositor(parent); |
|
s_compositor = compositor; |
|
return compositor; |
|
} |
|
|
|
WaylandCompositor::WaylandCompositor(QObject *parent) |
|
: Compositor(parent) |
|
{ |
|
} |
|
|
|
WaylandCompositor::~WaylandCompositor() |
|
{ |
|
Q_EMIT aboutToDestroy(); |
|
stop(); // this can't be called in the destructor of Compositor |
|
} |
|
|
|
bool WaylandCompositor::attemptOpenGLCompositing() |
|
{ |
|
std::unique_ptr<OpenGLBackend> backend = kwinApp()->outputBackend()->createOpenGLBackend(); |
|
if (!backend) { |
|
return false; |
|
} |
|
if (!backend->isFailed()) { |
|
backend->init(); |
|
} |
|
if (backend->isFailed()) { |
|
return false; |
|
} |
|
|
|
const QByteArray forceEnv = qgetenv("KWIN_COMPOSE"); |
|
if (!forceEnv.isEmpty()) { |
|
if (qstrcmp(forceEnv, "O2") == 0 || qstrcmp(forceEnv, "O2ES") == 0) { |
|
qCDebug(KWIN_CORE) << "OpenGL 2 compositing enforced by environment variable"; |
|
} else { |
|
// OpenGL 2 disabled by environment variable |
|
return false; |
|
} |
|
} else { |
|
if (backend->openglContext()->glPlatform()->recommendedCompositor() < OpenGLCompositing) { |
|
qCDebug(KWIN_CORE) << "Driver does not recommend OpenGL compositing"; |
|
return false; |
|
} |
|
} |
|
|
|
// We only support the OpenGL 2+ shader API, not GL_ARB_shader_objects |
|
if (!backend->openglContext()->hasVersion(Version(2, 0))) { |
|
qCDebug(KWIN_CORE) << "OpenGL 2.0 is not supported"; |
|
return false; |
|
} |
|
|
|
m_scene = std::make_unique<WorkspaceSceneOpenGL>(backend.get()); |
|
m_cursorScene = std::make_unique<CursorScene>(std::make_unique<ItemRendererOpenGL>(backend->eglDisplayObject())); |
|
m_backend = std::move(backend); |
|
|
|
qCDebug(KWIN_CORE) << "OpenGL compositing has been successfully initialized"; |
|
return true; |
|
} |
|
|
|
bool WaylandCompositor::attemptQPainterCompositing() |
|
{ |
|
std::unique_ptr<QPainterBackend> backend(kwinApp()->outputBackend()->createQPainterBackend()); |
|
if (!backend || backend->isFailed()) { |
|
return false; |
|
} |
|
|
|
m_scene = std::make_unique<WorkspaceSceneQPainter>(backend.get()); |
|
m_cursorScene = std::make_unique<CursorScene>(std::make_unique<ItemRendererQPainter>()); |
|
m_backend = std::move(backend); |
|
|
|
qCDebug(KWIN_CORE) << "QPainter compositing has been successfully initialized"; |
|
return true; |
|
} |
|
|
|
void WaylandCompositor::start() |
|
{ |
|
if (kwinApp()->isTerminating()) { |
|
return; |
|
} |
|
if (m_state != State::Off) { |
|
return; |
|
} |
|
|
|
Q_EMIT aboutToToggleCompositing(); |
|
m_state = State::Starting; |
|
|
|
// If compositing has been restarted, try to use the last used compositing type. |
|
const QList<CompositingType> availableCompositors = kwinApp()->outputBackend()->supportedCompositors(); |
|
QList<CompositingType> candidateCompositors; |
|
|
|
if (m_selectedCompositor != NoCompositing) { |
|
candidateCompositors.append(m_selectedCompositor); |
|
} else { |
|
candidateCompositors = availableCompositors; |
|
|
|
const auto userConfigIt = std::find(candidateCompositors.begin(), candidateCompositors.end(), options->compositingMode()); |
|
if (userConfigIt != candidateCompositors.end()) { |
|
candidateCompositors.erase(userConfigIt); |
|
candidateCompositors.prepend(options->compositingMode()); |
|
} else { |
|
qCWarning(KWIN_CORE) << "Configured compositor not supported by Platform. Falling back to defaults"; |
|
} |
|
} |
|
|
|
for (auto type : std::as_const(candidateCompositors)) { |
|
bool stop = false; |
|
switch (type) { |
|
case OpenGLCompositing: |
|
qCDebug(KWIN_CORE) << "Attempting to load the OpenGL scene"; |
|
stop = attemptOpenGLCompositing(); |
|
break; |
|
case QPainterCompositing: |
|
qCDebug(KWIN_CORE) << "Attempting to load the QPainter scene"; |
|
stop = attemptQPainterCompositing(); |
|
break; |
|
case NoCompositing: |
|
qCDebug(KWIN_CORE) << "Starting without compositing..."; |
|
stop = true; |
|
break; |
|
} |
|
|
|
if (stop) { |
|
break; |
|
} else if (qEnvironmentVariableIsSet("KWIN_COMPOSE")) { |
|
qCCritical(KWIN_CORE) << "Could not fulfill the requested compositing mode in KWIN_COMPOSE:" << type << ". Exiting."; |
|
qApp->quit(); |
|
} |
|
} |
|
|
|
if (!m_backend) { |
|
m_state = State::Off; |
|
|
|
qCCritical(KWIN_CORE) << "The used windowing system requires compositing"; |
|
qCCritical(KWIN_CORE) << "We are going to quit KWin now as it is broken"; |
|
qApp->quit(); |
|
return; |
|
} |
|
|
|
if (m_selectedCompositor == NoCompositing) { |
|
m_selectedCompositor = m_backend->compositingType(); |
|
|
|
switch (m_selectedCompositor) { |
|
case NoCompositing: |
|
break; |
|
case OpenGLCompositing: |
|
QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL); |
|
break; |
|
case QPainterCompositing: |
|
QQuickWindow::setGraphicsApi(QSGRendererInterface::Software); |
|
break; |
|
} |
|
} |
|
|
|
Q_EMIT sceneCreated(); |
|
|
|
const QList<Output *> outputs = workspace()->outputs(); |
|
for (Output *output : outputs) { |
|
addOutput(output); |
|
} |
|
connect(workspace(), &Workspace::outputAdded, this, &WaylandCompositor::addOutput); |
|
connect(workspace(), &Workspace::outputRemoved, this, &WaylandCompositor::removeOutput); |
|
|
|
m_state = State::On; |
|
|
|
const auto windows = workspace()->windows(); |
|
for (Window *window : windows) { |
|
window->setupCompositing(); |
|
} |
|
|
|
// Sets also the 'effects' pointer. |
|
kwinApp()->createEffectsHandler(this, m_scene.get()); |
|
|
|
Q_EMIT compositingToggled(true); |
|
} |
|
|
|
void WaylandCompositor::stop() |
|
{ |
|
if (m_state == State::Off || m_state == State::Stopping) { |
|
return; |
|
} |
|
m_state = State::Stopping; |
|
Q_EMIT aboutToToggleCompositing(); |
|
|
|
// Some effects might need access to effect windows when they are about to |
|
// be destroyed, for example to unreference deleted windows, so we have to |
|
// make sure that effect windows outlive effects. |
|
delete effects; |
|
effects = nullptr; |
|
|
|
if (Workspace::self()) { |
|
const auto windows = workspace()->windows(); |
|
for (Window *window : windows) { |
|
window->finishCompositing(); |
|
} |
|
disconnect(workspace(), &Workspace::outputAdded, this, &WaylandCompositor::addOutput); |
|
disconnect(workspace(), &Workspace::outputRemoved, this, &WaylandCompositor::removeOutput); |
|
} |
|
|
|
if (m_backend->compositingType() == OpenGLCompositing) { |
|
// some layers need a context current for destruction |
|
static_cast<OpenGLBackend *>(m_backend.get())->makeCurrent(); |
|
} |
|
|
|
const auto superlayers = m_superlayers; |
|
for (auto it = superlayers.begin(); it != superlayers.end(); ++it) { |
|
removeSuperLayer(*it); |
|
} |
|
|
|
m_scene.reset(); |
|
m_cursorScene.reset(); |
|
m_backend.reset(); |
|
|
|
m_state = State::Off; |
|
Q_EMIT compositingToggled(false); |
|
} |
|
|
|
static QRect centerBuffer(const QSizeF &bufferSize, const QSize &modeSize) |
|
{ |
|
const double widthScale = bufferSize.width() / double(modeSize.width()); |
|
const double heightScale = bufferSize.height() / double(modeSize.height()); |
|
if (widthScale > heightScale) { |
|
const QSize size = (bufferSize / widthScale).toSize(); |
|
const uint32_t yOffset = (modeSize.height() - size.height()) / 2; |
|
return QRect(QPoint(0, yOffset), size); |
|
} else { |
|
const QSize size = (bufferSize / heightScale).toSize(); |
|
const uint32_t xOffset = (modeSize.width() - size.width()) / 2; |
|
return QRect(QPoint(xOffset, 0), size); |
|
} |
|
} |
|
|
|
static bool checkForBlackBackground(SurfaceItem *background) |
|
{ |
|
if (!background->pixmap() |
|
|| !background->pixmap()->buffer() |
|
|| !background->pixmap()->buffer()->shmAttributes() |
|
|| background->pixmap()->buffer()->shmAttributes()->size != QSize(1, 1)) { |
|
return false; |
|
} |
|
const GraphicsBufferView view(background->pixmap()->buffer()); |
|
if (!view.image()) { |
|
return false; |
|
} |
|
const QRgb rgb = view.image()->pixel(0, 0); |
|
const QVector3D encoded(qRed(rgb) / 255.0, qGreen(rgb) / 255.0, qBlue(rgb) / 255.0); |
|
const QVector3D nits = background->colorDescription().mapTo(encoded, ColorDescription(NamedColorimetry::BT709, TransferFunction(TransferFunction::linear), 100, 0, std::nullopt, std::nullopt), background->renderingIntent()); |
|
// below 0.1 nits, it shouldn't be noticeable that we replace it with black |
|
return nits.lengthSquared() <= (0.1 * 0.1); |
|
} |
|
|
|
void WaylandCompositor::composite(RenderLoop *renderLoop) |
|
{ |
|
if (m_backend->checkGraphicsReset()) { |
|
qCDebug(KWIN_CORE) << "Graphics reset occurred"; |
|
#if KWIN_BUILD_NOTIFICATIONS |
|
KNotification::event(QStringLiteral("graphicsreset"), i18n("Desktop effects were restarted due to a graphics reset")); |
|
#endif |
|
reinitialize(); |
|
return; |
|
} |
|
|
|
Output *output = findOutput(renderLoop); |
|
OutputLayer *primaryLayer = m_backend->primaryLayer(output); |
|
fTraceDuration("Paint (", output->name(), ")"); |
|
|
|
RenderLayer *superLayer = m_superlayers[renderLoop]; |
|
superLayer->setOutputLayer(primaryLayer); |
|
|
|
renderLoop->prepareNewFrame(); |
|
auto frame = std::make_shared<OutputFrame>(renderLoop, std::chrono::nanoseconds(1'000'000'000'000 / output->refreshRate())); |
|
bool directScanout = false; |
|
std::optional<double> desiredArtificalHdrHeadroom; |
|
|
|
// brightness animations should be skipped when |
|
// - the output is new, and we didn't have the output configuration applied yet |
|
// - there's not enough steps to do a smooth animation |
|
// - the brightness device is external, most of them do an animation on their own |
|
if (!output->currentBrightness().has_value() |
|
|| (!output->highDynamicRange() && output->brightnessDevice() && !output->isInternal()) |
|
|| (!output->highDynamicRange() && output->brightnessDevice() && output->brightnessDevice()->brightnessSteps() < 5)) { |
|
frame->setBrightness(output->brightnessSetting()); |
|
} else { |
|
constexpr double changePerSecond = 3; |
|
const double maxChangePerFrame = changePerSecond * 1'000.0 / renderLoop->refreshRate(); |
|
// brightness perception is non-linear, gamma 2.2 encoding *roughly* represents that |
|
const double current = std::pow(*output->currentBrightness(), 1.0 / 2.2); |
|
frame->setBrightness(std::pow(std::clamp(std::pow(output->brightnessSetting(), 1.0 / 2.2), current - maxChangePerFrame, current + maxChangePerFrame), 2.2)); |
|
} |
|
|
|
if (primaryLayer->needsRepaint() || superLayer->needsRepaint()) { |
|
auto totalTimeQuery = std::make_unique<CpuRenderTimeQuery>(); |
|
renderLoop->beginPaint(); |
|
|
|
QRegion surfaceDamage = primaryLayer->repaints(); |
|
primaryLayer->resetRepaints(); |
|
prePaintPass(superLayer, &surfaceDamage); |
|
frame->setDamage(surfaceDamage); |
|
|
|
// slowly adjust the artificial HDR headroom for the next frame |
|
// note that this is only done for internal displays, because external displays usually apply slow animations to brightness changes |
|
if (!output->highDynamicRange() && output->brightnessDevice() && output->currentBrightness() && output->artificialHdrHeadroom() && output->isInternal() && output->colorProfileSource() != Output::ColorProfileSource::ICC) { |
|
const auto desiredHdrHeadroom = superLayer->delegate()->desiredHdrHeadroom(); |
|
// just a rough estimate from the Framework 13 laptop. The less accurate this is, the more the screen will flicker during backlight changes |
|
constexpr double relativeLuminanceAtZeroBrightness = 0.04; |
|
// the higher this is, the more likely the user is to notice the change in backlight brightness |
|
// at the same time, if it's too low, it takes ages until the user sees the HDR effect |
|
constexpr double changePerSecond = 0.5; |
|
// to restrict HDR videos from using all the battery and burning your eyes |
|
// TODO make it a setting, and/or dependent on the power management state? |
|
constexpr double maxHdrHeadroom = 3.0; |
|
// = the headroom at 100% backlight |
|
const double maxPossibleHeadroom = (1 + relativeLuminanceAtZeroBrightness) / (relativeLuminanceAtZeroBrightness + *output->currentBrightness()); |
|
desiredArtificalHdrHeadroom = std::clamp(desiredHdrHeadroom, 1.0, std::min(maxPossibleHeadroom, maxHdrHeadroom)); |
|
const double changePerFrame = changePerSecond * double(frame->refreshDuration().count()) / 1'000'000'000; |
|
const double newHeadroom = std::clamp(*desiredArtificalHdrHeadroom, output->artificialHdrHeadroom() - changePerFrame, output->artificialHdrHeadroom() + changePerFrame); |
|
frame->setArtificialHdrHeadroom(newHeadroom); |
|
} else { |
|
frame->setArtificialHdrHeadroom(1); |
|
} |
|
|
|
Window *const activeWindow = workspace()->activeWindow(); |
|
SurfaceItem *const activeFullscreenItem = activeWindow && activeWindow->isFullScreen() && activeWindow->isOnOutput(output) ? activeWindow->surfaceItem() : nullptr; |
|
frame->setContentType(activeWindow && activeFullscreenItem ? activeFullscreenItem->contentType() : ContentType::None); |
|
|
|
const bool wantsAdaptiveSync = activeWindow && activeWindow->isOnOutput(output) && activeWindow->wantsAdaptiveSync(); |
|
const bool vrr = (output->capabilities() & Output::Capability::Vrr) && (output->vrrPolicy() == VrrPolicy::Always || (output->vrrPolicy() == VrrPolicy::Automatic && wantsAdaptiveSync)); |
|
const bool tearing = (output->capabilities() & Output::Capability::Tearing) && options->allowTearing() && activeFullscreenItem && activeWindow->wantsTearing(activeFullscreenItem->presentationHint() == PresentationModeHint::Async); |
|
if (vrr) { |
|
frame->setPresentationMode(tearing ? PresentationMode::AdaptiveAsync : PresentationMode::AdaptiveSync); |
|
} else { |
|
frame->setPresentationMode(tearing ? PresentationMode::Async : PresentationMode::VSync); |
|
} |
|
|
|
const uint32_t planeCount = 1; |
|
if (const auto scanoutCandidates = superLayer->delegate()->scanoutCandidates(planeCount + 1); !scanoutCandidates.isEmpty()) { |
|
const auto sublayers = superLayer->sublayers(); |
|
bool scanoutPossible = std::none_of(sublayers.begin(), sublayers.end(), [](RenderLayer *sublayer) { |
|
return sublayer->isVisible(); |
|
}); |
|
if (scanoutCandidates.size() > planeCount) { |
|
scanoutPossible &= checkForBlackBackground(scanoutCandidates.back()); |
|
} |
|
if (scanoutPossible) { |
|
primaryLayer->setTargetRect(centerBuffer(output->transform().map(scanoutCandidates.front()->size()), output->modeSize())); |
|
directScanout = primaryLayer->importScanoutBuffer(scanoutCandidates.front(), frame); |
|
if (directScanout) { |
|
// if present works, we don't want to touch the frame object again afterwards, |
|
// so end the time query here instead of later |
|
totalTimeQuery->end(); |
|
frame->addRenderTimeQuery(std::move(totalTimeQuery)); |
|
totalTimeQuery = std::make_unique<CpuRenderTimeQuery>(); |
|
|
|
directScanout &= m_backend->present(output, frame); |
|
} |
|
} |
|
} else { |
|
primaryLayer->notifyNoScanoutCandidate(); |
|
} |
|
|
|
if (!directScanout) { |
|
primaryLayer->setTargetRect(QRect(QPoint(0, 0), output->modeSize())); |
|
if (auto beginInfo = primaryLayer->beginFrame()) { |
|
auto &[renderTarget, repaint] = beginInfo.value(); |
|
|
|
const QRegion bufferDamage = surfaceDamage.united(repaint).intersected(superLayer->rect().toAlignedRect()); |
|
|
|
paintPass(superLayer, renderTarget, bufferDamage); |
|
primaryLayer->endFrame(bufferDamage, surfaceDamage, frame.get()); |
|
} |
|
} |
|
|
|
postPaintPass(superLayer); |
|
if (!directScanout) { |
|
totalTimeQuery->end(); |
|
frame->addRenderTimeQuery(std::move(totalTimeQuery)); |
|
} |
|
} |
|
|
|
if (!directScanout) { |
|
if (!m_backend->present(output, frame)) { |
|
m_backend->repairPresentation(output); |
|
} |
|
} |
|
|
|
framePass(superLayer, frame.get()); |
|
|
|
if ((frame->brightness() && std::abs(*frame->brightness() - output->brightnessSetting()) > 0.001) |
|
|| (desiredArtificalHdrHeadroom && frame->artificialHdrHeadroom() && std::abs(*frame->artificialHdrHeadroom() - *desiredArtificalHdrHeadroom) > 0.001)) { |
|
// we're currently running an animation to change the brightness |
|
renderLoop->scheduleRepaint(); |
|
} |
|
|
|
// TODO: move this into the cursor layer |
|
const auto frameTime = std::chrono::duration_cast<std::chrono::milliseconds>(output->renderLoop()->lastPresentationTimestamp()); |
|
if (!Cursors::self()->isCursorHidden()) { |
|
Cursor *cursor = Cursors::self()->currentCursor(); |
|
if (cursor->geometry().intersects(output->geometry())) { |
|
if (CursorSource *source = cursor->source()) { |
|
source->frame(frameTime); |
|
} |
|
} |
|
} |
|
} |
|
|
|
void WaylandCompositor::addOutput(Output *output) |
|
{ |
|
if (output->isPlaceholder()) { |
|
return; |
|
} |
|
auto workspaceLayer = new RenderLayer(output->renderLoop()); |
|
workspaceLayer->setDelegate(std::make_unique<SceneDelegate>(m_scene.get(), output)); |
|
workspaceLayer->setGeometry(output->rectF()); |
|
connect(output, &Output::geometryChanged, workspaceLayer, [output, workspaceLayer]() { |
|
workspaceLayer->setGeometry(output->rectF()); |
|
}); |
|
|
|
auto cursorLayer = new RenderLayer(output->renderLoop()); |
|
cursorLayer->setVisible(false); |
|
if (m_backend->compositingType() == OpenGLCompositing) { |
|
cursorLayer->setDelegate(std::make_unique<CursorDelegateOpenGL>(m_cursorScene.get(), output)); |
|
} else { |
|
cursorLayer->setDelegate(std::make_unique<CursorDelegateQPainter>(m_cursorScene.get(), output)); |
|
} |
|
cursorLayer->setParent(workspaceLayer); |
|
cursorLayer->setSuperlayer(workspaceLayer); |
|
|
|
static const bool forceSoftwareCursor = qEnvironmentVariableIntValue("KWIN_FORCE_SW_CURSOR") == 1; |
|
|
|
auto updateCursorLayer = [this, output, cursorLayer]() { |
|
const Cursor *cursor = Cursors::self()->currentCursor(); |
|
const QRectF outputLocalRect = output->mapFromGlobal(cursor->geometry()); |
|
const auto outputLayer = m_backend->cursorLayer(output); |
|
if (!cursor->isOnOutput(output)) { |
|
if (outputLayer && outputLayer->isEnabled()) { |
|
outputLayer->setEnabled(false); |
|
output->updateCursorLayer(); |
|
} |
|
cursorLayer->setVisible(false); |
|
return true; |
|
} |
|
const auto renderHardwareCursor = [&]() { |
|
if (!outputLayer || forceSoftwareCursor) { |
|
return false; |
|
} |
|
QRectF nativeCursorRect = output->transform().map(scaledRect(outputLocalRect, output->scale()), output->pixelSize()); |
|
QSize bufferSize(std::ceil(nativeCursorRect.width()), std::ceil(nativeCursorRect.height())); |
|
const auto recommendedSizes = outputLayer->recommendedSizes(); |
|
if (!recommendedSizes.empty()) { |
|
auto bigEnough = recommendedSizes | std::views::filter([bufferSize](const auto &size) { |
|
return size.width() >= bufferSize.width() && size.height() >= bufferSize.height(); |
|
}); |
|
const auto it = std::ranges::min_element(bigEnough, [](const auto &left, const auto &right) { |
|
return left.width() * left.height() < right.width() * right.height(); |
|
}); |
|
if (it == bigEnough.end()) { |
|
// no size found, this most likely won't work |
|
return false; |
|
} |
|
bufferSize = *it; |
|
nativeCursorRect = output->transform().map(QRectF(outputLocalRect.topLeft() * output->scale(), bufferSize), output->pixelSize()); |
|
} |
|
outputLayer->setHotspot(output->transform().map(cursor->hotspot() * output->scale(), bufferSize)); |
|
outputLayer->setTargetRect(QRect(nativeCursorRect.topLeft().toPoint(), bufferSize)); |
|
if (auto beginInfo = outputLayer->beginFrame()) { |
|
const RenderTarget &renderTarget = beginInfo->renderTarget; |
|
|
|
RenderLayer renderLayer(output->renderLoop()); |
|
renderLayer.setDelegate(std::make_unique<SceneDelegate>(m_cursorScene.get(), output)); |
|
renderLayer.setOutputLayer(outputLayer); |
|
|
|
renderLayer.delegate()->prePaint(); |
|
renderLayer.delegate()->paint(renderTarget, infiniteRegion()); |
|
renderLayer.delegate()->postPaint(); |
|
|
|
if (!outputLayer->endFrame(infiniteRegion(), infiniteRegion(), nullptr)) { |
|
return false; |
|
} |
|
} else { |
|
return false; |
|
} |
|
outputLayer->setEnabled(true); |
|
return output->updateCursorLayer(); |
|
}; |
|
if (renderHardwareCursor()) { |
|
cursorLayer->setVisible(false); |
|
return true; |
|
} else { |
|
if (outputLayer && outputLayer->isEnabled()) { |
|
outputLayer->setEnabled(false); |
|
output->updateCursorLayer(); |
|
} |
|
cursorLayer->setVisible(cursor->isOnOutput(output)); |
|
cursorLayer->setGeometry(outputLocalRect); |
|
return false; |
|
} |
|
}; |
|
auto moveCursorLayer = [this, output, cursorLayer, updateCursorLayer]() { |
|
const Cursor *cursor = Cursors::self()->currentCursor(); |
|
const QRectF outputLocalRect = output->mapFromGlobal(cursor->geometry()); |
|
const auto outputLayer = m_backend->cursorLayer(output); |
|
bool hardwareCursor = false; |
|
const bool shouldBeVisible = cursor->isOnOutput(output); |
|
if (outputLayer && !forceSoftwareCursor) { |
|
if (shouldBeVisible) { |
|
const bool enabledBefore = outputLayer->isEnabled(); |
|
if (enabledBefore) { |
|
// just move it |
|
const QRectF nativeCursorRect = output->transform().map(QRectF(outputLocalRect.topLeft() * output->scale(), outputLayer->targetRect().size()), output->pixelSize()); |
|
outputLayer->setTargetRect(QRect(nativeCursorRect.topLeft().toPoint(), outputLayer->targetRect().size())); |
|
outputLayer->setEnabled(true); |
|
hardwareCursor = output->updateCursorLayer(); |
|
if (!hardwareCursor) { |
|
outputLayer->setEnabled(false); |
|
if (enabledBefore) { |
|
output->updateCursorLayer(); |
|
} |
|
} |
|
} else { |
|
// do the full update |
|
hardwareCursor = updateCursorLayer(); |
|
} |
|
} else if (outputLayer->isEnabled()) { |
|
outputLayer->setEnabled(false); |
|
output->updateCursorLayer(); |
|
} |
|
} |
|
cursorLayer->setVisible(shouldBeVisible && !hardwareCursor); |
|
cursorLayer->setGeometry(outputLocalRect); |
|
}; |
|
updateCursorLayer(); |
|
connect(output, &Output::geometryChanged, cursorLayer, updateCursorLayer); |
|
connect(Cursors::self(), &Cursors::currentCursorChanged, cursorLayer, updateCursorLayer); |
|
connect(Cursors::self(), &Cursors::hiddenChanged, cursorLayer, updateCursorLayer); |
|
connect(Cursors::self(), &Cursors::positionChanged, cursorLayer, moveCursorLayer); |
|
|
|
addSuperLayer(workspaceLayer); |
|
} |
|
|
|
void WaylandCompositor::removeOutput(Output *output) |
|
{ |
|
if (output->isPlaceholder()) { |
|
return; |
|
} |
|
removeSuperLayer(m_superlayers[output->renderLoop()]); |
|
} |
|
|
|
} // namespace KWin |
|
|
|
#include "moc_compositor_wayland.cpp"
|
|
|