From e870986406db813e244236622fab7dab9d4f487f Mon Sep 17 00:00:00 2001 From: Xaver Hugl Date: Thu, 28 Nov 2024 19:02:08 +0100 Subject: [PATCH] autotests/integration: add a color management test Only checks for protocol things right now, but will be extended to do more later --- autotests/integration/CMakeLists.txt | 12 +- autotests/integration/kwin_wayland_test.h | 12 +- .../integration/test_colormanagement.cpp | 229 ++++++++++++++++++ autotests/integration/test_helpers.cpp | 22 ++ 4 files changed, 269 insertions(+), 6 deletions(-) create mode 100644 autotests/integration/test_colormanagement.cpp diff --git a/autotests/integration/CMakeLists.txt b/autotests/integration/CMakeLists.txt index 41893a6b4e..de49b0607f 100644 --- a/autotests/integration/CMakeLists.txt +++ b/autotests/integration/CMakeLists.txt @@ -14,16 +14,17 @@ qt6_generate_wayland_protocol_client_sources(KWinIntegrationTestFramework qt6_generate_wayland_protocol_client_sources(KWinIntegrationTestFramework ${private_code_option} FILES - ${WaylandProtocols_DATADIR}/unstable/text-input/text-input-unstable-v3.xml ${CMAKE_SOURCE_DIR}/src/wayland/protocols/wlr-layer-shell-unstable-v1.xml + ${CMAKE_SOURCE_DIR}/src/wayland/protocols/xx-color-management-v4.xml ${WaylandProtocols_DATADIR}/stable/xdg-shell/xdg-shell.xml - ${WaylandProtocols_DATADIR}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml - ${WaylandProtocols_DATADIR}/unstable/idle-inhibit/idle-inhibit-unstable-v1.xml - ${WaylandProtocols_DATADIR}/unstable/tablet/tablet-unstable-v2.xml - ${WaylandProtocols_DATADIR}/staging/fractional-scale/fractional-scale-v1.xml ${WaylandProtocols_DATADIR}/staging/cursor-shape/cursor-shape-v1.xml + ${WaylandProtocols_DATADIR}/staging/fractional-scale/fractional-scale-v1.xml ${WaylandProtocols_DATADIR}/staging/security-context/security-context-v1.xml ${WaylandProtocols_DATADIR}/staging/xdg-dialog/xdg-dialog-v1.xml + ${WaylandProtocols_DATADIR}/unstable/idle-inhibit/idle-inhibit-unstable-v1.xml + ${WaylandProtocols_DATADIR}/unstable/tablet/tablet-unstable-v2.xml + ${WaylandProtocols_DATADIR}/unstable/text-input/text-input-unstable-v3.xml + ${WaylandProtocols_DATADIR}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml ${PLASMA_WAYLAND_PROTOCOLS_DIR}/kde-output-device-v2.xml ${PLASMA_WAYLAND_PROTOCOLS_DIR}/kde-output-management-v2.xml @@ -142,6 +143,7 @@ integrationTest(NAME testSecurityContext SRCS security_context_test.cpp) integrationTest(NAME testStickyKeys SRCS sticky_keys_test.cpp) integrationTest(NAME testWorkspace SRCS workspace_test.cpp) integrationTest(NAME testMouseActions SRCS mouseactions_test.cpp LIBS) +integrationTest(NAME testColorManagement SRCS test_colormanagement.cpp) if(KWIN_BUILD_X11) integrationTest(NAME testDontCrashEmptyDeco SRCS dont_crash_empty_deco.cpp LIBS KDecoration3::KDecoration) diff --git a/autotests/integration/kwin_wayland_test.h b/autotests/integration/kwin_wayland_test.h index 8b4043a230..206f5c4245 100644 --- a/autotests/integration/kwin_wayland_test.h +++ b/autotests/integration/kwin_wayland_test.h @@ -32,9 +32,10 @@ #include "qwayland-text-input-unstable-v3.h" #include "qwayland-wlr-layer-shell-unstable-v1.h" #include "qwayland-xdg-decoration-unstable-v1.h" +#include "qwayland-xdg-dialog-v1.h" #include "qwayland-xdg-shell.h" +#include "qwayland-xx-color-management-v4.h" #include "qwayland-zkde-screencast-unstable-v1.h" -#include "qwayland-xdg-dialog-v1.h" namespace KWayland { @@ -603,6 +604,7 @@ enum class AdditionalWaylandInterface { FakeInput = 1 << 19, SecurityContextManagerV1 = 1 << 20, XdgDialogV1 = 1 << 21, + ColorManagement = 1 << 22, }; Q_DECLARE_FLAGS(AdditionalWaylandInterfaces, AdditionalWaylandInterface) @@ -672,6 +674,13 @@ private: bool m_tabletTool = false; }; +class XXColorManagerV4 : public QtWayland::xx_color_manager_v4 +{ +public: + explicit XXColorManagerV4(::wl_registry *registry, uint32_t id, int version); + ~XXColorManagerV4() override; +}; + void keyboardKeyPressed(quint32 key, quint32 time); void keyboardKeyReleased(quint32 key, quint32 time); void pointerAxisHorizontal(qreal delta, @@ -732,6 +741,7 @@ ScreencastingV1 *screencasting(); QList waylandOutputDevicesV2(); FakeInput *waylandFakeInput(); SecurityContextManagerV1 *waylandSecurityContextManagerV1(); +XXColorManagerV4 *colorManager(); bool waitForWaylandSurface(Window *window); diff --git a/autotests/integration/test_colormanagement.cpp b/autotests/integration/test_colormanagement.cpp new file mode 100644 index 0000000000..93d6abd1b8 --- /dev/null +++ b/autotests/integration/test_colormanagement.cpp @@ -0,0 +1,229 @@ +/* + SPDX-FileCopyrightText: 2024 Xaver Hugl + + 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 +#include +#include + +#include "qwayland-xx-color-management-v4.h" + +using namespace std::chrono_literals; + +namespace KWin +{ + +static const QString s_socketName = QStringLiteral("wayland_test_color_management-0"); + +class ImageDescription : public QObject, public QtWayland::xx_image_description_v4 +{ + Q_OBJECT +public: + explicit ImageDescription(::xx_image_description_v4 *descr) + : QtWayland::xx_image_description_v4(descr) + { + } + + ~ImageDescription() override + { + xx_image_description_v4_destroy(object()); + } + + void xx_image_description_v4_ready(uint32_t identity) override + { + Q_EMIT ready(); + } + +Q_SIGNALS: + void ready(); +}; + +class ColorManagementSurface : public QObject, public QtWayland::xx_color_management_surface_v4 +{ + Q_OBJECT +public: + explicit ColorManagementSurface(::xx_color_management_surface_v4 *obj) + : QtWayland::xx_color_management_surface_v4(obj) + { + } + + ~ColorManagementSurface() override + { + xx_color_management_surface_v4_destroy(object()); + } +}; + +class ColorManagementTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testSetImageDescription_data(); + void testSetImageDescription(); + void testUnsupportedPrimaries(); + void testNoPrimaries(); + void testNoTf(); +}; + +void ColorManagementTest::initTestCase() +{ + qRegisterMetaType(); + + QVERIFY(waylandServer()->init(s_socketName)); + Test::setOutputConfig({ + QRect(0, 0, 1280, 1024), + QRect(1280, 0, 1280, 1024), + }); + + kwinApp()->start(); + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); +} + +void ColorManagementTest::init() +{ + Test::setOutputConfig({ + QRect(0, 0, 1280, 1024), + QRect(1280, 0, 1280, 1024), + }); + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::ColorManagement)); + + workspace()->setActiveOutput(QPoint(640, 512)); + input()->pointer()->warp(QPoint(640, 512)); +} + +void ColorManagementTest::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void ColorManagementTest::testSetImageDescription_data() +{ + QTest::addColumn("input"); + QTest::addColumn("protocolError"); + + QTest::addRow("sRGB") << ColorDescription::sRGB << false; + QTest::addRow("rec.2020 PQ") << ColorDescription(NamedColorimetry::BT2020, TransferFunction(TransferFunction::PerceptualQuantizer), 203, 0, 400, 400) << false; + QTest::addRow("scRGB") << ColorDescription(NamedColorimetry::BT709, TransferFunction(TransferFunction::linear, 0, 80), 80, 0, 80, 80) << false; + QTest::addRow("custom") << ColorDescription(NamedColorimetry::BT2020, TransferFunction(TransferFunction::gamma22, 0.05, 400), 203, 0, 400, 400) << false; + QTest::addRow("invalid tf") << ColorDescription(NamedColorimetry::BT2020, TransferFunction(TransferFunction::gamma22, 204, 205), 203, 0, 400, 400) << true; + // TODO this should fail with the wp protocol version + QTest::addRow("invalid HDR metadata") << ColorDescription(NamedColorimetry::BT2020, TransferFunction(TransferFunction::PerceptualQuantizer), 203, 500, 400, 400) << false; +} + +void ColorManagementTest::testSetImageDescription() +{ + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + + auto cmSurf = std::make_unique(Test::colorManager()->get_surface(*surface)); + + QtWayland::xx_image_description_creator_params_v4 creator(Test::colorManager()->new_parametric_creator()); + + QFETCH(ColorDescription, input); + + creator.set_primaries(std::round(10'000 * input.containerColorimetry().red().toxyY().x), + std::round(10'000 * input.containerColorimetry().red().toxyY().y), + std::round(10'000 * input.containerColorimetry().green().toxyY().x), + std::round(10'000 * input.containerColorimetry().green().toxyY().y), + std::round(10'000 * input.containerColorimetry().blue().toxyY().x), + std::round(10'000 * input.containerColorimetry().blue().toxyY().y), + std::round(10'000 * input.containerColorimetry().white().toxyY().x), + std::round(10'000 * input.containerColorimetry().white().toxyY().y)); + switch (input.transferFunction().type) { + case TransferFunction::sRGB: + creator.set_tf_named(XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_SRGB); + creator.set_luminances(std::round(input.transferFunction().minLuminance * 10'000), std::round(input.transferFunction().maxLuminance), std::round(input.referenceLuminance())); + break; + case TransferFunction::gamma22: + creator.set_tf_named(XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_GAMMA22); + creator.set_luminances(std::round(input.transferFunction().minLuminance * 10'000), std::round(input.transferFunction().maxLuminance), std::round(input.referenceLuminance())); + break; + case TransferFunction::linear: + creator.set_tf_named(XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_LINEAR); + creator.set_luminances(std::round(input.transferFunction().minLuminance * 10'000), std::round(input.transferFunction().maxLuminance), std::round(input.referenceLuminance())); + break; + case TransferFunction::PerceptualQuantizer: + creator.set_tf_named(XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_ST2084_PQ); + creator.set_max_fall(std::round(input.maxAverageLuminance().value_or(0))); + creator.set_max_cll(std::round(input.maxHdrLuminance().value_or(0))); + break; + } + creator.set_mastering_luminance(std::round(input.minLuminance() * 10'000), std::round(input.maxHdrLuminance().value_or(0))); + + ImageDescription imageDescr(xx_image_description_creator_params_v4_create(creator.object())); + + QFETCH(bool, protocolError); + if (protocolError) { + QSignalSpy error(Test::waylandConnection(), &KWayland::Client::ConnectionThread::errorOccurred); + QVERIFY(error.wait(50ms)); + return; + } + + QSignalSpy ready(&imageDescr, &ImageDescription::ready); + QVERIFY(ready.wait(50ms)); + + cmSurf->set_image_description(imageDescr.object(), XX_COLOR_MANAGER_V4_RENDER_INTENT_PERCEPTUAL); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + + QSignalSpy colorChange(window->surface(), &SurfaceInterface::colorDescriptionChanged); + QVERIFY(colorChange.wait()); + + QCOMPARE(window->surface()->colorDescription(), input); +} + +void ColorManagementTest::testUnsupportedPrimaries() +{ + QtWayland::xx_image_description_creator_params_v4 creator = QtWayland::xx_image_description_creator_params_v4(Test::colorManager()->new_parametric_creator()); + creator.set_primaries_named(-1); + xx_image_description_creator_params_v4_create(creator.object()); + QSignalSpy error(Test::waylandConnection(), &KWayland::Client::ConnectionThread::errorOccurred); + QVERIFY(error.wait(50ms)); +} + +void ColorManagementTest::testNoPrimaries() +{ + QtWayland::xx_image_description_creator_params_v4 creator = QtWayland::xx_image_description_creator_params_v4(Test::colorManager()->new_parametric_creator()); + creator.set_tf_named(XX_COLOR_MANAGER_V4_TRANSFER_FUNCTION_LINEAR); + xx_image_description_creator_params_v4_create(creator.object()); + QSignalSpy error(Test::waylandConnection(), &KWayland::Client::ConnectionThread::errorOccurred); + QVERIFY(error.wait(50ms)); +} + +void ColorManagementTest::testNoTf() +{ + QtWayland::xx_image_description_creator_params_v4 creator = QtWayland::xx_image_description_creator_params_v4(Test::colorManager()->new_parametric_creator()); + creator.set_primaries_named(XX_COLOR_MANAGER_V4_PRIMARIES_CIE1931_XYZ); + xx_image_description_creator_params_v4_create(creator.object()); + QSignalSpy error(Test::waylandConnection(), &KWayland::Client::ConnectionThread::errorOccurred); + QVERIFY(error.wait(50ms)); +} + +} // namespace KWin + +WAYLANDTEST_MAIN(KWin::ColorManagementTest) +#include "test_colormanagement.moc" diff --git a/autotests/integration/test_helpers.cpp b/autotests/integration/test_helpers.cpp index 8b2555ba89..567126d22b 100644 --- a/autotests/integration/test_helpers.cpp +++ b/autotests/integration/test_helpers.cpp @@ -319,6 +319,7 @@ static struct FakeInput *fakeInput = nullptr; SecurityContextManagerV1 *securityContextManagerV1 = nullptr; XdgWmDialogV1 *xdgWmDialogV1; + std::unique_ptr colorManager; } s_waylandConnection; MockInputMethod *inputMethod() @@ -540,6 +541,11 @@ bool setupWaylandConnection(AdditionalWaylandInterfaces flags) s_waylandConnection.xdgWmDialogV1->init(*registry, name, version); } } + if (flags & AdditionalWaylandInterface::ColorManagement) { + if (interface == xx_color_manager_v4_interface.name) { + s_waylandConnection.colorManager = std::make_unique(*registry, name, version); + } + } }); QSignalSpy allAnnounced(registry, &KWayland::Client::Registry::interfacesAnnounced); @@ -667,6 +673,7 @@ void destroyWaylandConnection() s_waylandConnection.securityContextManagerV1 = nullptr; delete s_waylandConnection.xdgWmDialogV1; s_waylandConnection.xdgWmDialogV1 = nullptr; + s_waylandConnection.colorManager.reset(); delete s_waylandConnection.queue; // Must be destroyed last s_waylandConnection.queue = nullptr; @@ -783,6 +790,11 @@ SecurityContextManagerV1 *waylandSecurityContextManagerV1() return s_waylandConnection.securityContextManagerV1; } +XXColorManagerV4 *colorManager() +{ + return s_waylandConnection.colorManager.get(); +} + bool waitForWaylandSurface(Window *window) { if (window->surface()) { @@ -1698,6 +1710,16 @@ bool VirtualInputDevice::isLidSwitch() const return m_lidSwitch; } +XXColorManagerV4::XXColorManagerV4(::wl_registry *registry, uint32_t id, int version) + : QtWayland::xx_color_manager_v4(registry, id, version) +{ +} + +XXColorManagerV4::~XXColorManagerV4() +{ + xx_color_manager_v4_destroy(object()); +} + void keyboardKeyPressed(quint32 key, quint32 time) { auto virtualKeyboard = static_cast(kwinApp())->virtualKeyboard();