422 lines
16 KiB
C++
422 lines
16 KiB
C++
|
|
/*
|
|
SPDX-FileCopyrightText: 2024 Xaver Hugl <xaver.hugl@gmail.com>
|
|
|
|
SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
|
|
#include "kwin_wayland_test.h"
|
|
|
|
#include "core/colorpipeline.h"
|
|
#include "core/output.h"
|
|
#include "core/outputbackend.h"
|
|
#include "core/outputconfiguration.h"
|
|
#include "outputconfigurationstore.h"
|
|
#include "pointer_input.h"
|
|
#include "tiles/tilemanager.h"
|
|
#include "wayland/surface.h"
|
|
#include "wayland_server.h"
|
|
#include "window.h"
|
|
#include "workspace.h"
|
|
|
|
#include <KWayland/Client/connection_thread.h>
|
|
#include <KWayland/Client/subsurface.h>
|
|
#include <KWayland/Client/surface.h>
|
|
#include <format>
|
|
|
|
using namespace std::chrono_literals;
|
|
|
|
namespace KWin
|
|
{
|
|
|
|
static const QString s_socketName = QStringLiteral("wayland_test_fifo-0");
|
|
|
|
class FifoTest : public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
private Q_SLOTS:
|
|
void initTestCase();
|
|
void init();
|
|
void cleanup();
|
|
|
|
void testFifo_data();
|
|
void testFifo();
|
|
void testFifoInitiallyHidden();
|
|
void testFifoBarriersOnly();
|
|
void testFifoWaitOnly();
|
|
void testFifoOnSubsurfaces();
|
|
void testBarrierNotClearedByEmptyCommit();
|
|
};
|
|
|
|
class FifoV1Surface : public QObject, public QtWayland::wp_fifo_v1
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
explicit FifoV1Surface(::wp_fifo_v1 *obj)
|
|
: QtWayland::wp_fifo_v1(obj)
|
|
{
|
|
}
|
|
|
|
~FifoV1Surface() override
|
|
{
|
|
wp_fifo_v1_destroy(object());
|
|
}
|
|
};
|
|
|
|
void FifoTest::initTestCase()
|
|
{
|
|
qRegisterMetaType<Window *>();
|
|
|
|
QVERIFY(waylandServer()->init(s_socketName));
|
|
kwinApp()->start();
|
|
}
|
|
|
|
void FifoTest::init()
|
|
{
|
|
QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::FifoV1 | Test::AdditionalWaylandInterface::PresentationTime));
|
|
Test::setupWaylandConnection();
|
|
|
|
workspace()->setActiveOutput(QPoint(640, 512));
|
|
input()->pointer()->warp(QPoint(640, 512));
|
|
}
|
|
|
|
void FifoTest::cleanup()
|
|
{
|
|
Test::destroyWaylandConnection();
|
|
}
|
|
|
|
void FifoTest::testFifo_data()
|
|
{
|
|
QTest::addColumn<uint32_t>("refreshRate");
|
|
|
|
QTest::addRow("50Hz") << 50'000u;
|
|
QTest::addRow("24Hz") << 24'000u;
|
|
}
|
|
|
|
void FifoTest::testFifo()
|
|
{
|
|
QFETCH(uint32_t, refreshRate);
|
|
Test::setOutputConfig({
|
|
Test::OutputInfo{
|
|
.geometry = Rect(0, 0, 200, 200),
|
|
.modes = {
|
|
std::make_tuple(QSize(200, 200), refreshRate, OutputMode::Flag::Preferred),
|
|
},
|
|
},
|
|
});
|
|
|
|
std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
|
|
std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
|
|
auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
|
|
QVERIFY(window);
|
|
|
|
auto fifo = std::make_unique<FifoV1Surface>(Test::fifoManager()->get_fifo(*surface));
|
|
|
|
std::array<std::unique_ptr<Test::WpPresentationFeedback>, 3> frames;
|
|
|
|
// commit 3 frames in quick succession: without fifo, the first two should be discarded, and the last should be presented
|
|
for (auto &frame : frames) {
|
|
frame = std::make_unique<Test::WpPresentationFeedback>(Test::presentationTime()->feedback(*surface));
|
|
surface->commit(KWayland::Client::Surface::CommitFlag::None);
|
|
}
|
|
|
|
{
|
|
QSignalSpy discarded1(frames[0].get(), &Test::WpPresentationFeedback::discarded);
|
|
QSignalSpy discarded2(frames[1].get(), &Test::WpPresentationFeedback::discarded);
|
|
QSignalSpy presented(frames[2].get(), &Test::WpPresentationFeedback::presented);
|
|
|
|
QVERIFY(presented.wait(100));
|
|
QVERIFY(discarded1.count());
|
|
QVERIFY(discarded2.count());
|
|
}
|
|
|
|
// do it again; this time with fifo, all frames should be presented
|
|
fifo->set_barrier();
|
|
surface->commit(KWayland::Client::Surface::CommitFlag::None);
|
|
for (auto &frame : frames) {
|
|
frame = std::make_unique<Test::WpPresentationFeedback>(Test::presentationTime()->feedback(*surface));
|
|
fifo->set_barrier();
|
|
fifo->wait_barrier();
|
|
surface->commit(KWayland::Client::Surface::CommitFlag::None);
|
|
}
|
|
|
|
{
|
|
std::array<QSignalSpy, frames.size()> spies = {
|
|
QSignalSpy(frames[0].get(), &Test::WpPresentationFeedback::presented),
|
|
QSignalSpy(frames[1].get(), &Test::WpPresentationFeedback::presented),
|
|
QSignalSpy(frames[2].get(), &Test::WpPresentationFeedback::presented),
|
|
};
|
|
for (size_t i = 0; i < frames.size(); i++) {
|
|
QVERIFY(spies[i].wait(100));
|
|
if (i > 0) {
|
|
// each frame should be presented in the refresh cycle after the last one
|
|
const auto thisTimestamp = spies[i].last().at(0).value<std::chrono::nanoseconds>();
|
|
const auto lastTimestamp = spies[i - 1].last().at(0).value<std::chrono::nanoseconds>();
|
|
const auto refreshDuration = spies[i].last().at(1).value<std::chrono::nanoseconds>();
|
|
const auto diff = thisTimestamp - lastTimestamp;
|
|
QCOMPARE_GT(diff, refreshDuration / 2);
|
|
// sometimes, the compositor seems to drop a frame -> don't test for this
|
|
// the only requirement for fifo is that the commit is shown for at least one frame
|
|
// so this doesn't technically change the test result
|
|
// QCOMPARE_LT(diff, refreshDuration * 3 / 2);
|
|
}
|
|
}
|
|
}
|
|
|
|
// even if the window is hidden, forward progress must be guaranteed
|
|
window->setMinimized(true);
|
|
|
|
for (auto &frame : frames) {
|
|
frame = std::make_unique<Test::WpPresentationFeedback>(Test::presentationTime()->feedback(*surface));
|
|
fifo->set_barrier();
|
|
fifo->wait_barrier();
|
|
surface->commit(KWayland::Client::Surface::CommitFlag::None);
|
|
}
|
|
// another commit, so that the last one will be discarded as well
|
|
fifo->set_barrier();
|
|
fifo->wait_barrier();
|
|
surface->commit(KWayland::Client::Surface::CommitFlag::None);
|
|
|
|
{
|
|
const std::chrono::nanoseconds targetRefreshDuration = std::chrono::nanoseconds(1'000'000'000'000 / std::min(30'000u, refreshRate));
|
|
std::array<QSignalSpy, frames.size()> spies = {
|
|
QSignalSpy(frames[0].get(), &Test::WpPresentationFeedback::discarded),
|
|
QSignalSpy(frames[1].get(), &Test::WpPresentationFeedback::discarded),
|
|
QSignalSpy(frames[2].get(), &Test::WpPresentationFeedback::discarded),
|
|
};
|
|
for (size_t i = 0; i < frames.size(); i++) {
|
|
const auto before = std::chrono::steady_clock::now();
|
|
QVERIFY(spies[i].wait(100));
|
|
const auto diff = std::chrono::steady_clock::now() - before;
|
|
QCOMPARE_GT(diff, targetRefreshDuration * 2 / 3);
|
|
QCOMPARE_LT(diff, targetRefreshDuration * 2);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FifoTest::testFifoInitiallyHidden()
|
|
{
|
|
// this test verifies that even when the window has never been presented once,
|
|
// we still ensure forward progress
|
|
|
|
std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
|
|
std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
|
|
auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
|
|
QVERIFY(window);
|
|
window->setMinimized(true);
|
|
|
|
auto fifo = std::make_unique<FifoV1Surface>(Test::fifoManager()->get_fifo(*surface));
|
|
|
|
std::array<std::unique_ptr<Test::WpPresentationFeedback>, 3> frames;
|
|
|
|
for (auto &frame : frames) {
|
|
frame = std::make_unique<Test::WpPresentationFeedback>(Test::presentationTime()->feedback(*surface));
|
|
fifo->set_barrier();
|
|
fifo->wait_barrier();
|
|
surface->commit(KWayland::Client::Surface::CommitFlag::None);
|
|
}
|
|
// another commit, so that the last one will be discarded as well
|
|
fifo->set_barrier();
|
|
fifo->wait_barrier();
|
|
surface->commit(KWayland::Client::Surface::CommitFlag::None);
|
|
|
|
{
|
|
std::array<QSignalSpy, frames.size()> spies = {
|
|
QSignalSpy(frames[0].get(), &Test::WpPresentationFeedback::discarded),
|
|
QSignalSpy(frames[1].get(), &Test::WpPresentationFeedback::discarded),
|
|
QSignalSpy(frames[2].get(), &Test::WpPresentationFeedback::discarded),
|
|
};
|
|
for (size_t i = 0; i < frames.size(); i++) {
|
|
const auto before = std::chrono::steady_clock::now();
|
|
QVERIFY(spies[i].wait(100));
|
|
const auto diff = std::chrono::steady_clock::now() - before;
|
|
QCOMPARE_GT(diff, std::chrono::milliseconds(30));
|
|
QCOMPARE_LT(diff, std::chrono::milliseconds(100));
|
|
}
|
|
}
|
|
}
|
|
|
|
void FifoTest::testFifoBarriersOnly()
|
|
{
|
|
// this test verifies that if you only set barriers but don't wait on them,
|
|
// commits still get applied immediately
|
|
|
|
std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
|
|
std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
|
|
auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
|
|
QVERIFY(window);
|
|
|
|
auto fifo = std::make_unique<FifoV1Surface>(Test::fifoManager()->get_fifo(*surface));
|
|
|
|
std::array<std::unique_ptr<Test::WpPresentationFeedback>, 3> frames;
|
|
|
|
for (auto &frame : frames) {
|
|
frame = std::make_unique<Test::WpPresentationFeedback>(Test::presentationTime()->feedback(*surface));
|
|
fifo->set_barrier();
|
|
surface->commit(KWayland::Client::Surface::CommitFlag::None);
|
|
}
|
|
// another commit, so that the last one will be discarded as well
|
|
fifo->set_barrier();
|
|
surface->commit(KWayland::Client::Surface::CommitFlag::None);
|
|
|
|
{
|
|
std::array<QSignalSpy, frames.size()> spies = {
|
|
QSignalSpy(frames[0].get(), &Test::WpPresentationFeedback::discarded),
|
|
QSignalSpy(frames[1].get(), &Test::WpPresentationFeedback::discarded),
|
|
QSignalSpy(frames[2].get(), &Test::WpPresentationFeedback::discarded),
|
|
};
|
|
for (auto &spy : spies) {
|
|
QVERIFY(spy.count() || spy.wait(100));
|
|
}
|
|
}
|
|
}
|
|
|
|
void FifoTest::testFifoWaitOnly()
|
|
{
|
|
// this test verifies that if you only wait on barriers but don't set any,
|
|
// commits still get applied immediately
|
|
|
|
std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
|
|
std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
|
|
auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
|
|
QVERIFY(window);
|
|
|
|
auto fifo = std::make_unique<FifoV1Surface>(Test::fifoManager()->get_fifo(*surface));
|
|
|
|
std::array<std::unique_ptr<Test::WpPresentationFeedback>, 3> frames;
|
|
|
|
for (auto &frame : frames) {
|
|
frame = std::make_unique<Test::WpPresentationFeedback>(Test::presentationTime()->feedback(*surface));
|
|
fifo->wait_barrier();
|
|
surface->commit(KWayland::Client::Surface::CommitFlag::None);
|
|
}
|
|
// another commit, so that the last one will be discarded as well
|
|
fifo->wait_barrier();
|
|
surface->commit(KWayland::Client::Surface::CommitFlag::None);
|
|
|
|
{
|
|
std::array<QSignalSpy, frames.size()> spies = {
|
|
QSignalSpy(frames[0].get(), &Test::WpPresentationFeedback::discarded),
|
|
QSignalSpy(frames[1].get(), &Test::WpPresentationFeedback::discarded),
|
|
QSignalSpy(frames[2].get(), &Test::WpPresentationFeedback::discarded),
|
|
};
|
|
for (auto &spy : spies) {
|
|
QVERIFY(spy.count() || spy.wait(100));
|
|
}
|
|
}
|
|
}
|
|
|
|
void FifoTest::testFifoOnSubsurfaces()
|
|
{
|
|
// this test verifies that fifo works correctly with subsurfaces
|
|
Test::setOutputConfig({
|
|
Test::OutputInfo{
|
|
.geometry = Rect(0, 0, 200, 200),
|
|
.modes = {
|
|
std::make_tuple(QSize(200, 200), 60'000, OutputMode::Flag::Preferred),
|
|
},
|
|
},
|
|
});
|
|
|
|
std::unique_ptr<KWayland::Client::Surface> parentSurface(Test::createSurface());
|
|
std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(parentSurface.get()));
|
|
auto window = Test::renderAndWaitForShown(parentSurface.get(), QSize(100, 50), Qt::blue);
|
|
QVERIFY(window);
|
|
|
|
std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
|
|
auto subsurface = Test::createSubSurface(surface.get(), parentSurface.get());
|
|
Test::render(surface.get(), QSize(100, 50), Qt::red);
|
|
|
|
auto fifo = std::make_unique<FifoV1Surface>(Test::fifoManager()->get_fifo(*surface));
|
|
std::array<std::unique_ptr<Test::WpPresentationFeedback>, 3> frames;
|
|
|
|
// when the surface is desync, fifo should work like on a toplevel surface
|
|
subsurface->setMode(KWayland::Client::SubSurface::Mode::Desynchronized);
|
|
for (auto &frame : frames) {
|
|
frame = std::make_unique<Test::WpPresentationFeedback>(Test::presentationTime()->feedback(*surface));
|
|
fifo->set_barrier();
|
|
fifo->wait_barrier();
|
|
surface->commit(KWayland::Client::Surface::CommitFlag::None);
|
|
}
|
|
// another commit, so that the last one will be discarded as well
|
|
fifo->set_barrier();
|
|
fifo->wait_barrier();
|
|
surface->commit(KWayland::Client::Surface::CommitFlag::None);
|
|
|
|
{
|
|
std::array<QSignalSpy, frames.size()> spies = {
|
|
QSignalSpy(frames[0].get(), &Test::WpPresentationFeedback::presented),
|
|
QSignalSpy(frames[1].get(), &Test::WpPresentationFeedback::presented),
|
|
QSignalSpy(frames[2].get(), &Test::WpPresentationFeedback::presented),
|
|
};
|
|
for (auto &spy : spies) {
|
|
QVERIFY(spy.wait(100));
|
|
}
|
|
}
|
|
|
|
// when the surface is synchronized though, fifo should be ignored
|
|
subsurface->setMode(KWayland::Client::SubSurface::Mode::Synchronized);
|
|
for (auto &frame : frames) {
|
|
frame = std::make_unique<Test::WpPresentationFeedback>(Test::presentationTime()->feedback(*surface));
|
|
fifo->set_barrier();
|
|
fifo->wait_barrier();
|
|
surface->commit(KWayland::Client::Surface::CommitFlag::None);
|
|
}
|
|
// another commit, so that the last one will be discarded as well
|
|
fifo->set_barrier();
|
|
fifo->wait_barrier();
|
|
surface->commit(KWayland::Client::Surface::CommitFlag::None);
|
|
|
|
{
|
|
std::array<QSignalSpy, frames.size()> spies = {
|
|
QSignalSpy(frames[0].get(), &Test::WpPresentationFeedback::discarded),
|
|
QSignalSpy(frames[1].get(), &Test::WpPresentationFeedback::discarded),
|
|
QSignalSpy(frames[2].get(), &Test::WpPresentationFeedback::discarded),
|
|
};
|
|
for (auto &spy : spies) {
|
|
QVERIFY(spy.count() || spy.wait(100));
|
|
}
|
|
}
|
|
}
|
|
|
|
void FifoTest::testBarrierNotClearedByEmptyCommit()
|
|
{
|
|
std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
|
|
std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
|
|
auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
|
|
QVERIFY(window);
|
|
|
|
auto fifo = std::make_unique<FifoV1Surface>(Test::fifoManager()->get_fifo(*surface));
|
|
|
|
std::array<std::unique_ptr<Test::WpPresentationFeedback>, 2> frames;
|
|
|
|
fifo->set_barrier();
|
|
surface->commit(KWayland::Client::Surface::CommitFlag::None);
|
|
|
|
// this additional commit must not clear the barrier
|
|
auto firstFrame = std::make_unique<Test::WpPresentationFeedback>(Test::presentationTime()->feedback(*surface));
|
|
surface->commit(KWayland::Client::Surface::CommitFlag::None);
|
|
|
|
auto secondFrame = std::make_unique<Test::WpPresentationFeedback>(Test::presentationTime()->feedback(*surface));
|
|
fifo->wait_barrier();
|
|
surface->commit(KWayland::Client::Surface::CommitFlag::None);
|
|
|
|
QSignalSpy firstSpy(firstFrame.get(), &Test::WpPresentationFeedback::presented);
|
|
QVERIFY(firstSpy.wait(100));
|
|
|
|
QSignalSpy secondSpy(secondFrame.get(), &Test::WpPresentationFeedback::presented);
|
|
QVERIFY(secondSpy.wait(100));
|
|
|
|
// the second frame should be presented in the refresh cycle after the first one
|
|
const auto thisTimestamp = secondSpy.last().at(0).value<std::chrono::nanoseconds>();
|
|
const auto lastTimestamp = firstSpy.last().at(0).value<std::chrono::nanoseconds>();
|
|
const auto refreshDuration = secondSpy.last().at(1).value<std::chrono::nanoseconds>();
|
|
const auto diff = thisTimestamp - lastTimestamp;
|
|
QCOMPARE_GT(diff, refreshDuration / 2);
|
|
}
|
|
}
|
|
|
|
WAYLANDTEST_MAIN(KWin::FifoTest)
|
|
#include "test_fifo.moc"
|