Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package aquamarine for openSUSE:Factory checked in at 2026-07-28 17:50:20 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/aquamarine (Old) and /work/SRC/openSUSE:Factory/.aquamarine.new.2004 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "aquamarine" Tue Jul 28 17:50:20 2026 rev:11 rq:1368167 version:0.14.0 Changes: -------- --- /work/SRC/openSUSE:Factory/aquamarine/aquamarine.changes 2026-07-17 01:45:25.809192737 +0200 +++ /work/SRC/openSUSE:Factory/.aquamarine.new.2004/aquamarine.changes 2026-07-28 17:51:38.079307455 +0200 @@ -1,0 +2,28 @@ +Tue Jul 28 06:59:50 UTC 2026 - Nassim Lhadj <[email protected]> + +- Update to aquamarine version 0.14.0: + - A New ABI breaking release: + * drm: do not stash failed commits + * drm: add support for COLOR_RANGE prop + * drm: add frameguard for idle frame + * drm: dont set HW_CLOCK on nvidia reporting seq 0 + +------------------------------------------------------------------- +Thu Jul 23 16:27:27 UTC 2026 - Nassim Lhadj <[email protected]> + +- Update to aquamarine version 0.13.0: + - A new major ABI breaking release for aquamarine: + * renderer: use version-compatible EGL debug context attributes + * drm: fix resource leaks + * atomic: fix undefined behavior in cursor plane position cast + * frame: ensure only one frame when pageflip occurs + * internal: chase hu fixes + * backend: fix implicit bool cast + * drm: deterministic page-flip scheduling + coalescing (#304,#306) + * drm: attach a right-sized swapchain buffer on modeset commits + * backend: enable libinput plugins if available + * drm: snapshot the commit for stashing + * backend/drm: fix dangling "this" in a callback + * drm: don't reset needFrame on stashing + +------------------------------------------------------------------- @@ -12 +40,2 @@ - * drm: CRTC starvation recovery + clear stale page-flip state after suspend + * drm: CRTC starvation recovery + clear stale page-flip state + after suspend @@ -18 +47,2 @@ - * drm: fall back to first render node on single-renderD systems when parent-syspath match fails + * drm: fall back to first render node on single-renderD systems + when parent-syspath match fails @@ -20 +50,2 @@ - * session: Add AQ_NO_KMS_REQUIREMENT environment variable for bypassing KMS requirement + * session: Add AQ_NO_KMS_REQUIREMENT environment variable for + bypassing KMS requirement @@ -50 +81,2 @@ - * backend/drm: fix multi-GPU buffer import failure during restoreAfterVT + * backend/drm: fix multi-GPU buffer import failure during + restoreAfterVT @@ -52,2 +84,4 @@ - * backend: handle unhandled libinput switch enum with default case - * drm: guard logging in connector disconnect to avoid null dereference + * backend: handle unhandled libinput switch enum with + default case + * drm: guard logging in connector disconnect to avoid + null dereference @@ -56 +90,2 @@ - * refactor: avoid repeated vector access in backend and DRM loops + * refactor: avoid repeated vector access in backend + and DRM loops Old: ---- aquamarine-0.12.1.tar.xz New: ---- aquamarine-0.14.0.tar.xz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ aquamarine.spec ++++++ --- /var/tmp/diff_new_pack.i5w4ly/_old 2026-07-28 17:51:39.751365922 +0200 +++ /var/tmp/diff_new_pack.i5w4ly/_new 2026-07-28 17:51:39.755366062 +0200 @@ -16,13 +16,13 @@ # -%define sover 11 +%define sover 13 %define _description %{expand: Aquamarine is a very light linux rendering backend library. It provides basic abstractions for an application to render on a Wayland session (in a window) or a native DRM session.} Name: aquamarine -Version: 0.12.1 +Version: 0.14.0 Release: 0 Summary: Rendering backend library License: BSD-3-Clause ++++++ aquamarine-0.12.1.tar.xz -> aquamarine-0.14.0.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aquamarine-0.12.1/.gitignore new/aquamarine-0.14.0/.gitignore --- old/aquamarine-0.12.1/.gitignore 2026-06-11 18:17:06.000000000 +0200 +++ new/aquamarine-0.14.0/.gitignore 2026-07-27 18:34:54.000000000 +0200 @@ -36,4 +36,7 @@ .cache/ protocols/*.cpp -protocols/*.hpp \ No newline at end of file +protocols/*.hpp + +.envrc +.direnv/ \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aquamarine-0.12.1/CMakeLists.txt new/aquamarine-0.14.0/CMakeLists.txt --- old/aquamarine-0.12.1/CMakeLists.txt 2026-06-11 18:17:06.000000000 +0200 +++ new/aquamarine-0.14.0/CMakeLists.txt 2026-07-27 18:34:54.000000000 +0200 @@ -61,12 +61,15 @@ file(GLOB_RECURSE PUBLIC_HEADERS CONFIGURE_DEPENDS "include/*.hpp") add_library(aquamarine SHARED ${SRCFILES}) +if(deps_libinput_VERSION VERSION_GREATER_EQUAL "1.30") + target_compile_definitions(aquamarine PRIVATE AQUAMARINE_HAS_LIBINPUT_PLUGINS) +endif() target_include_directories( aquamarine PUBLIC "./include" PRIVATE "./src" "./src/include" "./protocols" "${CMAKE_BINARY_DIR}") set_target_properties(aquamarine PROPERTIES VERSION ${AQUAMARINE_VERSION} - SOVERSION 11) + SOVERSION 13) target_link_libraries(aquamarine OpenGL::EGL OpenGL::OpenGL PkgConfig::deps) check_include_file("sys/timerfd.h" HAS_TIMERFD) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aquamarine-0.12.1/VERSION new/aquamarine-0.14.0/VERSION --- old/aquamarine-0.12.1/VERSION 2026-06-11 18:17:06.000000000 +0200 +++ new/aquamarine-0.14.0/VERSION 2026-07-27 18:34:54.000000000 +0200 @@ -1 +1 @@ -0.12.1 +0.14.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aquamarine-0.12.1/docs/env.md new/aquamarine-0.14.0/docs/env.md --- old/aquamarine-0.12.1/docs/env.md 2026-06-11 18:17:06.000000000 +0200 +++ new/aquamarine-0.14.0/docs/env.md 2026-07-27 18:34:54.000000000 +0200 @@ -9,6 +9,10 @@ `AQ_MGPU_NO_EXPLICIT` -> Disables explicit syncing on mgpu buffers `AQ_NO_MODIFIERS` -> Disables modifiers for DRM buffers +### Input + +`AQ_LIBINPUT_NO_PLUGINS` -> Disables libinput plugin loading + ### Debugging `AQ_TRACE` -> Enables trace (very verbose) logging diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aquamarine-0.12.1/include/aquamarine/backend/Backend.hpp new/aquamarine-0.14.0/include/aquamarine/backend/Backend.hpp --- old/aquamarine-0.12.1/include/aquamarine/backend/Backend.hpp 2026-06-11 18:17:06.000000000 +0200 +++ new/aquamarine-0.14.0/include/aquamarine/backend/Backend.hpp 2026-07-27 18:34:54.000000000 +0200 @@ -30,6 +30,15 @@ AQ_BACKEND_NULL, }; + enum eBackendGPUDriver : uint32_t { + AQ_BACKEND_GPU_DRIVER_UNKNOWN = 0, + AQ_BACKEND_GPU_DRIVER_INTEL, // i915, xe + AQ_BACKEND_GPU_DRIVER_AMD, // amdgpu, radeon + AQ_BACKEND_GPU_DRIVER_NVIDIA, // proprietary nvidia-drm + AQ_BACKEND_GPU_DRIVER_NOUVEAU, // open source nouveau + AQ_BACKEND_GPU_DRIVER_EVDI, // displayLink virtual driver + }; + enum eBackendRequestMode : uint32_t { /* Require the provided backend, will error out if it's not available. @@ -95,6 +104,7 @@ virtual std::vector<Hyprutils::Memory::CSharedPointer<IAllocator>> getAllocators() = 0; virtual Hyprutils::Memory::CWeakPointer<IBackendImplementation> getPrimary() = 0; virtual int drmRenderNodeFD() = 0; + virtual eBackendGPUDriver gpuDriver(); }; class CBackend { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aquamarine-0.12.1/include/aquamarine/backend/DRM.hpp new/aquamarine-0.14.0/include/aquamarine/backend/DRM.hpp --- old/aquamarine-0.12.1/include/aquamarine/backend/DRM.hpp 2026-06-11 18:17:06.000000000 +0200 +++ new/aquamarine-0.14.0/include/aquamarine/backend/DRM.hpp 2026-07-27 18:34:54.000000000 +0200 @@ -4,6 +4,7 @@ #include "../allocator/Swapchain.hpp" #include "../output/Output.hpp" #include "../input/Input.hpp" +#include "FrameScheduler.hpp" #include <hyprutils/memory/WeakPtr.hpp> #include <wayland-client.h> #include <xf86drmMode.h> @@ -139,10 +140,21 @@ uint32_t hotspot_x; uint32_t hotspot_y; uint32_t in_fence_fd; + uint32_t color_range; } values; - uint32_t props[17] = {0}; + uint32_t props[18] = {0}; }; UDRMPlaneProps props; + + // only valid when color_range != 0 + union UDRMPlaneColorRange { + struct { + uint32_t Full_YCbCr; + uint32_t Limited_YCbCr; + } values; + uint32_t props[2] = {0}; + }; + UDRMPlaneColorRange colorRange; }; struct SDRMCRTC { @@ -155,13 +167,13 @@ } legacy; struct { - bool ownModeID = false; - uint32_t modeID = 0; - uint32_t gammaLut = 0; - uint32_t ctm = 0; - uint32_t hdr = 0; + bool ownModeID = false; + uint32_t modeID = 0; + uint32_t gammaLut = 0; + uint32_t ctm = 0; + uint32_t hdr = 0; // true once a real commit has made the kernel CTM match our state. - bool ctmStateKnown = false; + bool ctmStateKnown = false; } atomic; Hyprutils::Memory::CSharedPointer<SDRMPlane> primary; @@ -205,6 +217,7 @@ virtual size_t getDeGammaSize(); virtual std::vector<SDRMFormat> getRenderFormats(); virtual bool pendingPageFlip(); + virtual bool pendingIdleFrame(); void releaseMgpuResources(); int getConnectorID(); @@ -225,6 +238,7 @@ Hyprutils::Memory::CWeakPointer<CDRMBackend> backend; Hyprutils::Memory::CSharedPointer<SDRMConnector> connector; Hyprutils::Memory::CSharedPointer<std::function<void(void)>> frameIdle; + Hyprutils::Signal::CHyprSignalListener frameReadyListener; struct { Hyprutils::Memory::CSharedPointer<CSwapchain> swapchain; @@ -243,10 +257,13 @@ struct SDRMConnectorCommitData { Hyprutils::Memory::CSharedPointer<CDRMFB> mainFB, cursorFB; - bool modeset = false; - bool blocking = false; - uint32_t flags = 0; - bool test = false; + bool modeset = false; + bool blocking = false; + uint32_t flags = 0; + bool test = false; + bool enabled = false; + uint32_t committed = 0; + Hyprutils::Math::CRegion damage; drmModeModeInfo modeInfo; std::optional<Hyprutils::Math::Mat3x3> ctm; std::optional<hdr_output_metadata> hdrMetadata; @@ -324,11 +341,8 @@ Hyprutils::Math::Vector2D cursorPos, cursorSize, cursorHotspot; Hyprutils::Memory::CSharedPointer<CDRMFB> pendingCursorFB; - bool isPageFlipPending = false; - uint64_t pageFlipPendingAtMs = 0; // CLOCK_BOOTTIME ms when isPageFlipPending was set SDRMPageFlip pendingPageFlip; - bool frameEventScheduled = false; - bool isFrameRunning = false; + CFrameScheduler sched; // the current state is invalid and won't commit, don't try to modeset. bool commitTainted = false; @@ -420,6 +434,7 @@ std::vector<FIdleCallback> idleCallbacks; std::string gpuName; virtual int drmRenderNodeFD(); + virtual eBackendGPUDriver gpuDriver(); private: CDRMBackend(Hyprutils::Memory::CSharedPointer<CBackend> backend); @@ -453,6 +468,7 @@ } rendererState; bool rendererRequired = true; + eBackendGPUDriver driver = AQ_BACKEND_GPU_DRIVER_UNKNOWN; Hyprutils::Memory::CWeakPointer<CBackend> backend; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aquamarine-0.12.1/include/aquamarine/backend/FrameScheduler.hpp new/aquamarine-0.14.0/include/aquamarine/backend/FrameScheduler.hpp --- old/aquamarine-0.12.1/include/aquamarine/backend/FrameScheduler.hpp 1970-01-01 01:00:00.000000000 +0100 +++ new/aquamarine-0.14.0/include/aquamarine/backend/FrameScheduler.hpp 2026-07-27 18:34:54.000000000 +0200 @@ -0,0 +1,63 @@ +#pragma once + +#include <hyprutils/signal/Signal.hpp> + +namespace Aquamarine { + // Per-output frame scheduling state, shared by the DRM and Wayland backends. + // + // Both backends produce an end-of-frame event — DRM from the kernel page-flip + // completion, Wayland from wl_callback.done on a wl_surface.frame. This class + // models that lifecycle uniformly and fires frameReady on completion. + class CFrameScheduler { + public: + // a frame was submitted to the host with a completion event requested. + // DRM: page-flip submitted with PAGE_FLIP_EVENT. Wayland: sendCommit followed + // by a wl_surface.frame whose done is wired to onFrameComplete. + void onFrameSubmitted(); + + // the host delivered the frame completion event. + // DRM: kernel page-flip handler. Wayland: wl_callback.done. + // State-only — clears m_pending. The backend fires frameReady explicitly + // at the right point in its handler (preserving the present-before-frame + // ordering on DRM). + void onFrameComplete(); + + // the in-flight frame (if any) is void and no completion event will arrive; + // reset the frame bookkeeping. + void invalidate(); + + // is a frame in flight awaiting completion? + bool frameInFlight() const; + + // may a new frame be scheduled? (does not consider output enabled state) + bool canSchedule() const; + + // State accessors. setFrameScheduled / frameScheduled() drive the idle-frame + // loop; setFrameRunning / frameRunning() guard event emission on completion. + bool frameScheduled() const; + void setFrameScheduled(bool v); + bool frameRunning() const; + void setFrameRunning(bool v); + + // Fires from onFrameComplete. The output wires this to events.frame.emit. + Hyprutils::Signal::CSignalT<> frameReady; + + private: + bool m_pending = false; + bool m_frameScheduled = false; + bool m_frameRunning = false; + }; + + // RAII pair for isFrameRunning: set on ctor, cleared on every exit path so a + // reentrant emit handler cannot strand it. + class CFrameRunningGuard { + public: + explicit CFrameRunningGuard(CFrameScheduler& s); + ~CFrameRunningGuard(); + CFrameRunningGuard(const CFrameRunningGuard&) = delete; + CFrameRunningGuard& operator=(const CFrameRunningGuard&) = delete; + + private: + CFrameScheduler& m_s; + }; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aquamarine-0.12.1/include/aquamarine/backend/Headless.hpp new/aquamarine-0.14.0/include/aquamarine/backend/Headless.hpp --- old/aquamarine-0.12.1/include/aquamarine/backend/Headless.hpp 2026-06-11 18:17:06.000000000 +0200 +++ new/aquamarine-0.14.0/include/aquamarine/backend/Headless.hpp 2026-07-27 18:34:54.000000000 +0200 @@ -21,6 +21,7 @@ virtual bool destroy(); virtual std::vector<SDRMFormat> getRenderFormats(); virtual bool pendingPageFlip(); + virtual bool pendingIdleFrame(); Hyprutils::Memory::CWeakPointer<CHeadlessOutput> self; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aquamarine-0.12.1/include/aquamarine/backend/Wayland.hpp new/aquamarine-0.14.0/include/aquamarine/backend/Wayland.hpp --- old/aquamarine-0.12.1/include/aquamarine/backend/Wayland.hpp 2026-06-11 18:17:06.000000000 +0200 +++ new/aquamarine-0.14.0/include/aquamarine/backend/Wayland.hpp 2026-07-27 18:34:54.000000000 +0200 @@ -1,6 +1,7 @@ #pragma once #include "./Backend.hpp" +#include "./FrameScheduler.hpp" #include "../allocator/Swapchain.hpp" #include "../output/Output.hpp" #include "../input/Input.hpp" @@ -51,6 +52,7 @@ virtual bool destroy(); virtual std::vector<SDRMFormat> getRenderFormats(); virtual bool pendingPageFlip(); + virtual bool pendingIdleFrame(); Hyprutils::Memory::CWeakPointer<CWaylandOutput> self; @@ -61,14 +63,13 @@ Hyprutils::Memory::CSharedPointer<CWaylandBuffer> wlBufferFromBuffer(Hyprutils::Memory::CSharedPointer<IBuffer> buffer); - void sendFrameAndSetCallback(); void onFrameDone(); void onEnter(Hyprutils::Memory::CSharedPointer<CCWlPointer> pointer, uint32_t serial); - // frame loop - bool frameScheduledWhileWaiting = false; - bool readyForFrameCallback = false; // true after attaching a buffer - bool frameScheduled = false; + // frame loop — unified scheduler shared with DRM. See CFrameScheduler. + CFrameScheduler sched; + Hyprutils::Signal::CHyprSignalListener frameReadyListener; + Hyprutils::Memory::CSharedPointer<std::function<void(void)>> frameIdle; struct { std::vector<std::pair<Hyprutils::Memory::CWeakPointer<IBuffer>, Hyprutils::Memory::CSharedPointer<CWaylandBuffer>>> buffers; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aquamarine-0.12.1/include/aquamarine/output/Output.hpp new/aquamarine-0.14.0/include/aquamarine/output/Output.hpp --- old/aquamarine-0.12.1/include/aquamarine/output/Output.hpp 2026-06-11 18:17:06.000000000 +0200 +++ new/aquamarine-0.14.0/include/aquamarine/output/Output.hpp 2026-07-27 18:34:54.000000000 +0200 @@ -37,6 +37,12 @@ AQ_SUBPIXEL_VERTICAL_BGR, }; + enum eOutputColorRange : uint32_t { + AQ_OUTPUT_COLOR_RANGE_AUTO = 0, // leave the driver default + AQ_OUTPUT_COLOR_RANGE_FULL, // force full range + AQ_OUTPUT_COLOR_RANGE_LIMITED, // force limited (16-235) range + }; + class IOutput; class COutputState { @@ -75,10 +81,11 @@ uint32_t drmFormat = DRM_FORMAT_INVALID; Hyprutils::Memory::CSharedPointer<IBuffer> buffer; int32_t explicitInFence = -1, explicitOutFence = -1; - Hyprutils::Math::Mat3x3 ctm = Hyprutils::Math::Mat3x3::identity(); + Hyprutils::Math::Mat3x3 ctm = Hyprutils::Math::Mat3x3::identity(); bool wideColorGamut = false; hdr_output_metadata hdrMetadata; uint16_t contentType = DRM_MODE_CONTENT_TYPE_GRAPHICS; + eOutputColorRange colorRange = AQ_OUTPUT_COLOR_RANGE_AUTO; }; const SInternalState& state(); @@ -101,6 +108,7 @@ void setWideColorGamut(bool wcg); void setHDRMetadata(const hdr_output_metadata& metadata); void setContentType(const uint16_t drmContentType); + void setColorRange(eOutputColorRange range); private: SInternalState internalState; @@ -171,7 +179,8 @@ virtual size_t getGammaSize(); virtual size_t getDeGammaSize(); virtual bool destroy(); // not all backends allow this!!! - virtual bool pendingPageFlip() = 0; + virtual bool pendingPageFlip() = 0; + virtual bool pendingIdleFrame() = 0; std::string name, description, make, model, serial; SParsedEDID parsedEDID; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aquamarine-0.12.1/src/backend/Backend.cpp new/aquamarine-0.14.0/src/backend/Backend.cpp --- old/aquamarine-0.12.1/src/backend/Backend.cpp 2026-06-11 18:17:06.000000000 +0200 +++ new/aquamarine-0.14.0/src/backend/Backend.cpp 2026-07-27 18:34:54.000000000 +0200 @@ -110,6 +110,9 @@ } Aquamarine::CBackend::~CBackend() { + if (idle.fd >= 0) + close(idle.fd); + // Tear down implementations before the logger is destroyed, // as backends may log during teardown (e.g. SDRMConnector::disconnect). implementations.clear(); @@ -130,7 +133,7 @@ for (size_t i = 0; i < implementations.size(); ++i) { const auto& impl = implementations.at(i); - const bool ok = impl->start(); + const bool ok = impl->start(); if (!ok) { log(AQ_LOG_ERROR, std::format("Requested backend ({}) could not start, enabling fallbacks", backendTypeToName(impl->type()))); @@ -237,7 +240,7 @@ } bool Aquamarine::CBackend::hasSession() { - return session; + return !!session; } std::vector<SDRMFormat> Aquamarine::CBackend::getPrimaryRenderFormats() { @@ -386,3 +389,7 @@ std::vector<SDRMFormat> Aquamarine::IBackendImplementation::getRenderableFormats() { return {}; } + +eBackendGPUDriver Aquamarine::IBackendImplementation::gpuDriver() { + return AQ_BACKEND_GPU_DRIVER_UNKNOWN; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aquamarine-0.12.1/src/backend/FrameScheduler.cpp new/aquamarine-0.14.0/src/backend/FrameScheduler.cpp --- old/aquamarine-0.12.1/src/backend/FrameScheduler.cpp 1970-01-01 01:00:00.000000000 +0100 +++ new/aquamarine-0.14.0/src/backend/FrameScheduler.cpp 2026-07-27 18:34:54.000000000 +0200 @@ -0,0 +1,49 @@ +#include <aquamarine/backend/FrameScheduler.hpp> + +using namespace Aquamarine; + +void CFrameScheduler::onFrameSubmitted() { + m_pending = true; +} + +void CFrameScheduler::onFrameComplete() { + m_pending = false; +} + +void CFrameScheduler::invalidate() { + m_pending = false; + m_frameRunning = false; + m_frameScheduled = false; +} + +bool CFrameScheduler::frameInFlight() const { + return m_pending; +} + +bool CFrameScheduler::canSchedule() const { + return !m_pending && !m_frameRunning && !m_frameScheduled; +} + +bool CFrameScheduler::frameScheduled() const { + return m_frameScheduled; +} + +void CFrameScheduler::setFrameScheduled(bool v) { + m_frameScheduled = v; +} + +bool CFrameScheduler::frameRunning() const { + return m_frameRunning; +} + +void CFrameScheduler::setFrameRunning(bool v) { + m_frameRunning = v; +} + +CFrameRunningGuard::CFrameRunningGuard(CFrameScheduler& s) : m_s(s) { + m_s.setFrameRunning(true); +} + +CFrameRunningGuard::~CFrameRunningGuard() { + m_s.setFrameRunning(false); +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aquamarine-0.12.1/src/backend/Headless.cpp new/aquamarine-0.14.0/src/backend/Headless.cpp --- old/aquamarine-0.12.1/src/backend/Headless.cpp 2026-06-11 18:17:06.000000000 +0200 +++ new/aquamarine-0.14.0/src/backend/Headless.cpp 2026-07-27 18:34:54.000000000 +0200 @@ -44,6 +44,10 @@ return false; } +bool Aquamarine::CHeadlessOutput::pendingIdleFrame() { + return frameScheduled; +} + Hyprutils::Memory::CSharedPointer<IBackendImplementation> Aquamarine::CHeadlessOutput::getBackend() { return backend.lock(); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aquamarine-0.12.1/src/backend/Session.cpp new/aquamarine-0.14.0/src/backend/Session.cpp --- old/aquamarine-0.12.1/src/backend/Session.cpp 2026-06-11 18:17:06.000000000 +0200 +++ new/aquamarine-0.14.0/src/backend/Session.cpp 2026-07-27 18:34:54.000000000 +0200 @@ -1,4 +1,5 @@ #include <aquamarine/backend/Backend.hpp> +#include <cerrno> #include <fcntl.h> #include "Shared.hpp" @@ -344,14 +345,29 @@ return nullptr; } + libinput_log_set_handler(session->libinputHandle, ::libinputLog); + libinput_log_set_priority(session->libinputHandle, LIBINPUT_LOG_PRIORITY_DEBUG); + +#ifdef AQUAMARINE_HAS_LIBINPUT_PLUGINS + if (envEnabled("AQ_LIBINPUT_NO_PLUGINS")) + session->backend->log(AQ_LOG_DEBUG, "libinput: plugin loading disabled by AQ_LIBINPUT_NO_PLUGINS"); + else { + libinput_plugin_system_append_default_paths(session->libinputHandle); + + if (int ret = libinput_plugin_system_load_plugins(session->libinputHandle, LIBINPUT_PLUGIN_SYSTEM_FLAG_NONE); ret < 0) { + if (ret == -ENOSYS) + session->backend->log(AQ_LOG_WARNING, "libinput: plugin support is not available in this libinput build"); + else + session->backend->log(AQ_LOG_WARNING, std::format("libinput: failed to load plugins: {}", strerror(-ret))); + } + } +#endif + if (libinput_udev_assign_seat(session->libinputHandle, session->seatName.c_str())) { session->backend->log(AQ_LOG_ERROR, "libinput: failed to assign a seat"); return nullptr; } - libinput_log_set_handler(session->libinputHandle, ::libinputLog); - libinput_log_set_priority(session->libinputHandle, LIBINPUT_LOG_PRIORITY_DEBUG); - return session; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aquamarine-0.12.1/src/backend/Wayland.cpp new/aquamarine-0.14.0/src/backend/Wayland.cpp --- old/aquamarine-0.12.1/src/backend/Wayland.cpp 2026-06-11 18:17:06.000000000 +0200 +++ new/aquamarine-0.14.0/src/backend/Wayland.cpp 2026-07-27 18:34:54.000000000 +0200 @@ -61,8 +61,28 @@ } Aquamarine::CWaylandBackend::~CWaylandBackend() { - if (drmState.fd >= 0) + outputs.clear(); + keyboards.clear(); + pointers.clear(); + idleCallbacks.clear(); + + waylandState.dmabufFeedback.reset(); + waylandState.dmabuf.reset(); + waylandState.shm.reset(); + waylandState.compositor.reset(); + waylandState.xdg.reset(); + waylandState.seat.reset(); + waylandState.registry.reset(); + + if (waylandState.display) { + wl_display_disconnect(waylandState.display); + waylandState.display = nullptr; + } + + if (drmState.fd >= 0) { close(drmState.fd); + drmState.fd = -1; + } } eBackendType Aquamarine::CWaylandBackend::type() { @@ -472,6 +492,21 @@ Aquamarine::CWaylandOutput::CWaylandOutput(const std::string& name_, Hyprutils::Memory::CWeakPointer<CWaylandBackend> backend_) : backend(backend_) { name = name_; + // The scheduler's frameReady signal drives the public events.frame on this output. + frameReadyListener = sched.frameReady.listen([this]() { events.frame.emit(); }); + + // Idle that emits the scheduled frame, fired via the core backend's idle queue + // (addIdleEvent), which the consumer's event loop pumps every iteration. Deliberately + // not the backend-local idleCallbacks vector: that only drains inside dispatchEvents, + // i.e. when the wayland fd is readable, so a frame scheduled with no host traffic + // pending (e.g. right after a focus change) would never fire. + frameIdle = makeShared<std::function<void(void)>>([this]() { + sched.setFrameScheduled(false); + if (sched.frameInFlight() || sched.frameRunning()) + return; + sched.frameReady.emit(); + }); + waylandState.surface = makeShared<CCWlSurface>(backend->waylandState.compositor->sendCreateSurface()); if (!waylandState.surface->resource()) { @@ -509,7 +544,11 @@ h = 720; } events.state.emit(SStateEvent{.size = {w, h}}); - sendFrameAndSetCallback(); + // Kick off the first frame synchronously: the consumer expects events.frame in + // the same dispatch cycle as the toplevel configure. Deferring via scheduleFrame's + // idle races the first commit and can leave the output blank until the next event. + needsFrame = true; + sched.frameReady.emit(); }); waylandState.xdgToplevel->setClose([this](CCXdgToplevel* r) { destroy(); }); @@ -531,6 +570,8 @@ Aquamarine::CWaylandOutput::~CWaylandOutput() { events.destroy.emit(); + // frameIdle captures a raw this and may still be queued; pull it before we die. + backend->backend->removeIdleEvent(frameIdle); if (waylandState.xdgToplevel) waylandState.xdgToplevel->sendDestroy(); if (waylandState.xdgSurface) @@ -547,7 +588,11 @@ } bool Aquamarine::CWaylandOutput::pendingPageFlip() { - return false; + return sched.frameInFlight(); +} + +bool Aquamarine::CWaylandOutput::pendingIdleFrame() { + return sched.frameScheduled(); } bool Aquamarine::CWaylandOutput::destroy() { @@ -555,6 +600,7 @@ waylandState.surface->sendAttach(nullptr, 0, 0); waylandState.surface->sendCommit(); waylandState.frameCallback.reset(); + sched.invalidate(); std::erase(backend->outputs, self.lock()); return true; } @@ -618,9 +664,29 @@ waylandState.surface->sendAttach(wlBuffer->waylandState.buffer.get(), 0, 0); waylandState.surface->sendDamageBuffer(0, 0, INT32_MAX, INT32_MAX); + + // Register the next wl_surface.frame callback as part of this commit's pending state, + // but only if one isn't already pending. A consumer that commits twice in quick + // succession (e.g. cursor + buffer) shares the in-flight callback — when it fires, + // onFrameDone runs and the next render cycle registers a fresh one. Mirrors DRM's + // "one flip in flight per CRTC" invariant, expressed via the scheduler. + // + // PROTOCOL: wl_surface.frame becomes part of pending state and takes effect on the + // NEXT wl_surface.commit. So the frame request must precede sendCommit() — sending + // it after commit would queue the callback for a future commit that never happens. + if (!sched.frameInFlight()) { + waylandState.frameCallback = makeShared<CCWlCallback>(waylandState.surface->sendFrame()); + waylandState.frameCallback->setDone([this](CCWlCallback* r, uint32_t ms) { onFrameDone(); }); + sched.onFrameSubmitted(); + } + waylandState.surface->sendCommit(); - readyForFrameCallback = true; + // Flush immediately: commit() runs from the consumer's render path, outside + // dispatchEvents() (which flushes only at its top). Without this the buffer commit and + // frame request sit unflushed, the host never sends wl_callback.done, the fd never + // becomes readable, and the frame loop deadlocks. + wl_display_flush(backend->waylandState.display); events.commit.emit(); state->onCommit(); @@ -654,33 +720,19 @@ return wlBuffer; } -void Aquamarine::CWaylandOutput::sendFrameAndSetCallback() { - events.frame.emit(); - frameScheduled = false; - if (waylandState.frameCallback || !readyForFrameCallback) - return; - - waylandState.frameCallback = makeShared<CCWlCallback>(waylandState.surface->sendFrame()); - waylandState.frameCallback->setDone([this](CCWlCallback* r, uint32_t ms) { onFrameDone(); }); - - waylandState.surface->sendCommit(); -} - void Aquamarine::CWaylandOutput::onFrameDone() { + // Mirrors DRM's handlePF: settle scheduler state, emit present, then emit frame + // via the scheduler signal. The loop self-limits — if the consumer doesn't + // commit (its needsFrame bit clears in renderMonitor), no new wl_surface.frame + // is registered (registration lives in commit()), so no further done arrives. waylandState.frameCallback.reset(); - readyForFrameCallback = false; - events.present.emit(IOutput::SPresentEvent{.presented = true}); + sched.onFrameComplete(); - // FIXME: this is wrong, but otherwise we get bugs. - // thanks @phonetic112 - scheduleFrame(AQ_SCHEDULE_NEEDS_FRAME); - - if (frameScheduledWhileWaiting) - sendFrameAndSetCallback(); - else - events.frame.emit(); + CFrameRunningGuard frameRunning(sched); - frameScheduledWhileWaiting = false; + events.present.emit(IOutput::SPresentEvent{.presented = true}); + + sched.frameReady.emit(); } bool Aquamarine::CWaylandOutput::setCursor(Hyprutils::Memory::CSharedPointer<IBuffer> buffer, const Hyprutils::Math::Vector2D& hotspot) { @@ -785,22 +837,14 @@ void Aquamarine::CWaylandOutput::scheduleFrame(const scheduleFrameReason reason) { TRACE(backend->backend->log(AQ_LOG_TRACE, - std::format("CWaylandOutput::scheduleFrame: reason {}, needsFrame {}, frameScheduled {}", (uint32_t)reason, needsFrame, frameScheduled))); + std::format("CWaylandOutput::scheduleFrame: reason {}, needsFrame {}, canSchedule {}", (uint32_t)reason, needsFrame, sched.canSchedule()))); needsFrame = true; - if (frameScheduled) + if (!sched.canSchedule()) return; - frameScheduled = true; - - if (waylandState.frameCallback) - frameScheduledWhileWaiting = true; - else { - backend->idleCallbacks.emplace_back([w = self]() { - if (auto o = w.lock()) - o->sendFrameAndSetCallback(); - }); - } + sched.setFrameScheduled(true); + backend->backend->addIdleEvent(frameIdle); } Aquamarine::CWaylandBuffer::CWaylandBuffer(SP<IBuffer> buffer_, Hyprutils::Memory::CWeakPointer<CWaylandBackend> backend_) : buffer(buffer_), backend(backend_) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aquamarine-0.12.1/src/backend/drm/DRM.cpp new/aquamarine-0.14.0/src/backend/drm/DRM.cpp --- old/aquamarine-0.12.1/src/backend/drm/DRM.cpp 2026-06-11 18:17:06.000000000 +0200 +++ new/aquamarine-0.14.0/src/backend/drm/DRM.cpp 2026-07-27 18:34:54.000000000 +0200 @@ -19,7 +19,7 @@ #include <unordered_set> #include <sys/mman.h> #include <fcntl.h> - +#include <unistd.h> extern "C" { #include <libseat.h> #include <libudev.h> @@ -37,6 +37,9 @@ #include "hwdata.hpp" #include "Renderer.hpp" +#include <hyprutils/utils/ScopeGuard.hpp> +using Hyprutils::Utils::CScopeGuard; + using namespace Aquamarine; using namespace Hyprutils::Memory; using namespace Hyprutils::Math; @@ -92,10 +95,11 @@ int num = 0; for (int i = 0; i < resources->count_connectors; ++i) { auto drmConn = drmModeGetConnector(gpu->fd, resources->connectors[i]); - if (!drmConn || drmConn->connection != DRM_MODE_CONNECTED) + if (!drmConn) continue; - if (drmConn->connector_type == DRM_MODE_CONNECTOR_LVDS || drmConn->connector_type == DRM_MODE_CONNECTOR_eDP || drmConn->connector_type == DRM_MODE_CONNECTOR_DSI) + if (drmConn->connection == DRM_MODE_CONNECTED && + (drmConn->connector_type == DRM_MODE_CONNECTOR_LVDS || drmConn->connector_type == DRM_MODE_CONNECTOR_eDP || drmConn->connector_type == DRM_MODE_CONNECTOR_DSI)) num++; drmModeFreeConnector(drmConn); @@ -115,8 +119,8 @@ } if (!udev_enumerate_get_list_entry(enumerate)) { - // TODO: wait for them. backend->log(AQ_LOG_ERROR, "drm: No gpus in scanGPUs."); + udev_enumerate_unref(enumerate); return {}; } @@ -404,11 +408,9 @@ // restore, but isPageFlipPending is already false so the = false // assignment is a harmless no-op. for (auto const& c : connectors) { - if (c->isPageFlipPending || c->isFrameRunning) { + if (c->sched.frameInFlight() || c->sched.frameRunning()) { backend->log(AQ_LOG_DEBUG, std::format("drm: Clearing stale page-flip state for {}", c->szName)); - c->isPageFlipPending = false; - c->isFrameRunning = false; - c->frameEventScheduled = false; + c->sched.invalidate(); } } @@ -430,6 +432,8 @@ .blocking = true, .flags = 0, .test = false, + .enabled = STATE.enabled, + .committed = STATE.committed, .hdrMetadata = STATE.hdrMetadata, }; @@ -464,8 +468,8 @@ } if (!drmFB) { - backend->log(AQ_LOG_DEBUG, std::format("drm: Buffer unavailable for crtc {} restore ({}), requesting re-render", - c->crtc->id, shouldBlit() ? "multi-gpu" : "import failed")); + backend->log(AQ_LOG_DEBUG, + std::format("drm: Buffer unavailable for crtc {} restore ({}), requesting re-render", c->crtc->id, shouldBlit() ? "multi-gpu" : "import failed")); noMode.emplace_back(c); continue; } @@ -560,10 +564,21 @@ bool Aquamarine::CDRMBackend::initResources() { auto resources = drmModeGetResources(gpu->fd); if (!resources) { - backend->log(AQ_LOG_ERROR, std::format("drm: drmModeGetResources failed")); + backend->log(AQ_LOG_ERROR, "drm: drmModeGetResources failed"); return false; } + CScopeGuard resourcesGuard([resources] { drmModeFreeResources(resources); }); + + bool success = false; + + CScopeGuard rollbackGuard([&] { + if (!success) { + crtcs.clear(); + planes.clear(); + } + }); + backend->log(AQ_LOG_DEBUG, std::format("drm: found {} CRTCs", resources->count_crtcs)); for (int i = 0; i < resources->count_crtcs; ++i) { @@ -574,18 +589,15 @@ auto drmCRTC = drmModeGetCrtc(gpu->fd, CRTC->id); if (!drmCRTC) { backend->log(AQ_LOG_ERROR, std::format("drm: drmModeGetCrtc for crtc {} failed", CRTC->id)); - drmModeFreeResources(resources); - crtcs.clear(); return false; } + CScopeGuard crtcGuard([drmCRTC] { drmModeFreeCrtc(drmCRTC); }); + CRTC->legacy.gammaSize = drmCRTC->gamma_size; - drmModeFreeCrtc(drmCRTC); if (!getDRMCRTCProps(gpu->fd, CRTC->id, &CRTC->props)) { backend->log(AQ_LOG_ERROR, std::format("drm: getDRMCRTCProps for crtc {} failed", CRTC->id)); - drmModeFreeResources(resources); - crtcs.clear(); return false; } @@ -597,50 +609,43 @@ return false; } - // initialize planes auto planeResources = drmModeGetPlaneResources(gpu->fd); if (!planeResources) { - backend->log(AQ_LOG_ERROR, std::format("drm: drmModeGetPlaneResources failed")); + backend->log(AQ_LOG_ERROR, "drm: drmModeGetPlaneResources failed"); return false; } - backend->log(AQ_LOG_DEBUG, std::format("drm: found {} planes", planeResources->count_planes)); + CScopeGuard planeResourcesGuard([planeResources] { drmModeFreePlaneResources(planeResources); }); for (uint32_t i = 0; i < planeResources->count_planes; ++i) { - auto id = planeResources->planes[i]; - auto plane = drmModeGetPlane(gpu->fd, id); + const auto id = planeResources->planes[i]; + + auto plane = drmModeGetPlane(gpu->fd, id); if (!plane) { backend->log(AQ_LOG_ERROR, std::format("drm: drmModeGetPlane for plane {} failed", id)); - drmModeFreeResources(resources); - crtcs.clear(); - planes.clear(); return false; } - auto aqPlane = makeShared<SDRMPlane>(); - aqPlane->backend = self; - aqPlane->self = aqPlane; - if (!aqPlane->init((drmModePlane*)plane)) { + CScopeGuard planeGuard([plane] { drmModeFreePlane(plane); }); + + auto aqPlane = makeShared<SDRMPlane>(); + aqPlane->backend = self; + aqPlane->self = aqPlane; + + if (!aqPlane->init(plane)) { backend->log(AQ_LOG_ERROR, std::format("drm: aqPlane->init for plane {} failed", id)); - drmModeFreeResources(resources); - crtcs.clear(); - planes.clear(); return false; } planes.emplace_back(aqPlane); - - drmModeFreePlane(plane); } - drmModeFreePlaneResources(planeResources); - drmModeFreeResources(resources); - + success = true; return true; } bool Aquamarine::CDRMBackend::shouldBlit() { - return primary; + return !!primary; } bool Aquamarine::CDRMBackend::initMgpu() { @@ -768,8 +773,8 @@ } for (size_t i = 0; i < crtcs.size(); ++i) { - const auto& crtc = crtcs.at(i); - bool taken = false; + const auto& crtc = crtcs.at(i); + bool taken = false; for (auto const& c : connectors) { if (c->crtc != crtc) continue; @@ -868,15 +873,31 @@ gpu = gpu_; primary = primary_; + auto driverFromName = [](const std::string_view name) { + if (name == "i915" || name == "xe") + return AQ_BACKEND_GPU_DRIVER_INTEL; + if (name == "amdgpu" || name == "radeon") + return AQ_BACKEND_GPU_DRIVER_AMD; + if (name == "nvidia-drm") + return AQ_BACKEND_GPU_DRIVER_NVIDIA; + if (name == "nouveau") + return AQ_BACKEND_GPU_DRIVER_NOUVEAU; + if (name == "evdi") + return AQ_BACKEND_GPU_DRIVER_EVDI; + + return AQ_BACKEND_GPU_DRIVER_UNKNOWN; + }; + auto drmName = drmGetDeviceNameFromFd2(gpu->fd); auto drmVer = drmGetVersion(gpu->fd); - gpuName = drmName; + gpuName = drmName ? drmName : "unknown"; - auto drmVerName = drmVer->name ? drmVer->name : "unknown"; - if (std::string_view(drmVerName) == "evdi") { + auto drmVerName = drmVer && drmVer->name ? drmVer->name : "unknown"; + driver = driverFromName(drmVerName); + if (driver == AQ_BACKEND_GPU_DRIVER_EVDI) { // DisplayLink/evdi exposes KMS without a usable EGL renderer. - primary = {}; + primary = {}; rendererRequired = false; } @@ -884,8 +905,10 @@ std::format("drm: Starting backend for {}, with driver {}{}", drmName ? drmName : "unknown", drmVerName, (primary ? std::format(" with primary {}", primary->gpu->path) : ""))); - drmFreeVersion(drmVer); + free(drmName); + if (drmVer) + drmFreeVersion(drmVer); listeners.gpuChange = gpu->events.change.listen([this](const CSessionDevice::SChangeEvent& E) { if (E.type == CSessionDevice::AQ_SESSION_EVENT_CHANGE_HOTPLUG) { backend->log(AQ_LOG_DEBUG, std::format("drm: Got a hotplug event for {}", gpuName)); @@ -1034,6 +1057,7 @@ if (!conn->init(drmConn)) { backend->log(AQ_LOG_ERROR, std::format("drm: Connector id {} failed initializing", connectorID)); connectors.pop_back(); + drmModeFreeConnector(drmConn); continue; } } else { @@ -1128,13 +1152,17 @@ return gpu->renderNodeFd; } +eBackendGPUDriver Aquamarine::CDRMBackend::gpuDriver() { + return driver; +} + static void handlePF(int fd, unsigned seq, unsigned tv_sec, unsigned tv_usec, unsigned crtc_id, void* data) { auto pageFlip = (SDRMPageFlip*)data; if (!pageFlip || !pageFlip->connector) return; - pageFlip->connector->isPageFlipPending = false; + pageFlip->connector->sched.onFrameComplete(); const auto& BACKEND = pageFlip->connector->backend; @@ -1145,8 +1173,9 @@ return; } - if (BACKEND->sessionActive() && pageFlip->connector->output->enabledState) - pageFlip->connector->isFrameRunning = true; + // hold isFrameRunning around the emit (RAII pair, so reentrant enable/disable + // can't strand it). + CFrameRunningGuard frameRunning(pageFlip->connector->sched); pageFlip->connector->onPresent(); @@ -1154,6 +1183,12 @@ timespec presented = {.tv_sec = (time_t)tv_sec, .tv_nsec = (long)(tv_usec * 1000)}; + // nvidia-drm registers no vblank counter, unless module options 'nvidia_drm vblank=1' is set. + // kernel checks for vblank support and fallbacks to setting seq 0 and the timestamp is a plain ktime_get() + // this is not a HW clock, its just a plain software clock fetched from whenever the event was called. + if (BACKEND->gpuDriver() == AQ_BACKEND_GPU_DRIVER_NVIDIA && seq == 0) + flags &= ~IOutput::AQ_OUTPUT_PRESENT_HW_CLOCK; + pageFlip->connector->output->events.present.emit(IOutput::SPresentEvent{ .presented = BACKEND->sessionActive(), .when = &presented, @@ -1162,10 +1197,9 @@ .flags = flags, }); - if (BACKEND->sessionActive() && pageFlip->connector->output->enabledState) { - pageFlip->connector->output->events.frame.emit(); - pageFlip->connector->isFrameRunning = false; - } + // Skip if an idle frame is already queued: it emits events.frame itself, and #325 forbids double-firing. + if (BACKEND->sessionActive() && pageFlip->connector->output->enabledState && !pageFlip->connector->sched.frameScheduled()) + pageFlip->connector->sched.frameReady.emit(); } bool Aquamarine::CDRMBackend::dispatchEvents() { @@ -1287,6 +1321,7 @@ if (drmIsMaster(fd) && drmDropMaster(fd) < 0) { backend->log(AQ_LOG_ERROR, "drm: couldn't drop master from duped fd"); + close(fd); return -1; } @@ -1311,6 +1346,9 @@ if (!getDRMPlaneProps(backend->gpu->fd, id, &props)) return false; + if (props.values.color_range) + getDRMPlaneColorRange(backend->gpu->fd, props.values.color_range, &colorRange); + if (!getDRMProp(backend->gpu->fd, id, props.values.type, &type)) return false; @@ -1581,6 +1619,9 @@ model = mod ? mod : ""; serial = ser ? ser : ""; + free(mod); + free(ser); + parsed.make = make; parsed.model = model; parsed.serial = serial; @@ -1789,8 +1830,7 @@ void Aquamarine::SDRMConnector::disconnect() { if (!output) { if (backend && backend->backend) - backend->backend->log(AQ_LOG_DEBUG, - std::format("drm: Not disconnecting connector {} because it's already disconnected", szName)); + backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Not disconnecting connector {} because it's already disconnected", szName)); return; } @@ -1824,14 +1864,16 @@ pendingCursorFB.reset(); - if (output->state->state().committed & COutputState::AQ_OUTPUT_STATE_MODE) + if (data.committed & COutputState::AQ_OUTPUT_STATE_MODE) refresh = calculateRefresh(data.modeInfo); const bool wasEnabled = output->enabledState; - output->enabledState = output->state->state().enabled; + output->enabledState = data.enabled; - if (!output->enabledState) + if (!output->enabledState) { releaseFBReferences(); + sched.invalidate(); + } if (!backend->updateSecondaryRendererState()) backend->backend->log(AQ_LOG_ERROR, std::format("drm: Failed to update renderer state for {} on applyCommit", szName)); @@ -1883,8 +1925,8 @@ Aquamarine::CDRMOutput::~CDRMOutput() { if (backend && backend->backend) backend->backend->removeIdleEvent(frameIdle); - connector->isPageFlipPending = false; - connector->frameEventScheduled = false; + connector->sched.onFrameComplete(); + connector->sched.setFrameScheduled(false); } void Aquamarine::CDRMOutput::releaseMgpuResources() { @@ -1998,31 +2040,16 @@ backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Disabling output {}", name)); } - if (STATE.enabled && (NEEDS_RECONFIG || (COMMITTED & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_BUFFER)) && connector->isPageFlipPending) { - // Check if the pending page-flip is stale (>500ms — well beyond - // any vblank interval, even at low refresh rates). Stale flips - // occur after S3/S4 suspend when page-flip completion events are - // lost because the display hardware was powered off. - struct timespec ts; - clock_gettime(CLOCK_BOOTTIME, &ts); - uint64_t nowMs = ts.tv_sec * 1000ULL + ts.tv_nsec / 1000000ULL; - bool staleFlip = (nowMs - connector->pageFlipPendingAtMs) > 500; - - if (NEEDS_RECONFIG && staleFlip) { - // A blocking modeset uses DRM_MODE_ATOMIC_ALLOW_MODESET which - // fully resets the CRTC, implicitly cancelling any stale - // page-flip at the kernel level. Clear the stale userspace - // bookkeeping to match. - backend->backend->log(AQ_LOG_DEBUG, - std::format("drm: Clearing stale page-flip state for {} during modeset (pending for {}ms)", name, - nowMs - connector->pageFlipPendingAtMs)); - connector->isPageFlipPending = false; - connector->isFrameRunning = false; - connector->frameEventScheduled = false; - } else { - backend->backend->log(AQ_LOG_ERROR, "drm: Cannot commit when a page-flip is awaiting"); - return false; - } + if (STATE.enabled && NEEDS_RECONFIG && connector->sched.frameInFlight()) { + // ALLOW_MODESET resets the CRTC and cancels the in-flight flip; no + // completion event will arrive, so clear the userspace state. + backend->backend->log(AQ_LOG_DEBUG, std::format("drm: page-flip on {} cancelled by modeset, clearing flip state", name)); + connector->sched.invalidate(); + } + + if (STATE.enabled && (COMMITTED & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_BUFFER) && connector->sched.frameInFlight()) { + backend->backend->log(AQ_LOG_ERROR, "drm: Cannot commit when a page-flip is awaiting"); + return false; } if (STATE.enabled && (COMMITTED & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_BUFFER)) @@ -2141,15 +2168,35 @@ if (COMMITTED & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_HDR) data.hdrMetadata = STATE.hdrMetadata; - data.blocking = BLOCKING || formatMismatch; - data.modeset = NEEDS_RECONFIG || lastCommitNoBuffer || formatMismatch; - data.flags = flags; - data.test = onlyTest; + data.blocking = BLOCKING || formatMismatch; + data.modeset = NEEDS_RECONFIG || lastCommitNoBuffer || formatMismatch; + data.flags = flags; + data.test = onlyTest; + data.enabled = STATE.enabled; + data.committed = COMMITTED; + if (COMMITTED & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_DAMAGE) + data.damage = STATE.damage.copy(); if (MODE->modeInfo.has_value()) data.modeInfo = *MODE->modeInfo; else data.calculateMode(connector); + // A modeset commit must carry a buffer sized for the new mode: the atomic path + // derives the plane geometry from it (and legacy SetCrtc has the same requirement), + // so drivers without plane scaling (e.g. virtio-gpu) reject stale-size buffers, + // making larger modes unreachable. + // If the consumer already reconfigured the swapchain to the new size, attach a fresh buffer from it. + bool acquiredModesetBuffer = false; + if (data.modeset && data.mainFB && swapchain && data.mainFB->buffer->size != MODE->pixelSize && swapchain->currentOptions().size == MODE->pixelSize) { + if (auto newBuf = swapchain->next(nullptr); newBuf) { + if (auto newFB = CDRMFB::create(newBuf, backend, nullptr); newFB && !newFB->dead) { + data.mainFB = newFB; + acquiredModesetBuffer = true; + } else + swapchain->rollback(); + } + } + // Re-send CTM when it changes, when preserving a non-identity CTM over a modeset, // or when we need one identity blob to clear unknown kernel state from before us. // Once a real CTM commit succeeds, ordinary identity modesets can skip the blob. @@ -2158,6 +2205,11 @@ bool ok = connector->commitState(data); + // a buffer acquired only to validate/attempt the modeset isn't consumed by the + // consumer: rewind the swapchain so the acquire cursor stays in sync with it. + if (acquiredModesetBuffer && (onlyTest || !ok)) + swapchain->rollback(); + if (!ok && !data.modeset && !connector->commitTainted) { // attempt to re-modeset, however, flip a tainted flag if the modesetting fails // to avoid doing this over and over. @@ -2310,13 +2362,33 @@ void Aquamarine::CDRMOutput::scheduleFrame(const scheduleFrameReason reason) { TRACE(backend->backend->log(AQ_LOG_TRACE, std::format("CDRMOutput::scheduleFrame: reason {}, needsFrame {}, isPageFlipPending {}, frameEventScheduled {}", (uint32_t)reason, needsFrame, - connector->isPageFlipPending, connector->frameEventScheduled))); + connector->sched.frameInFlight(), connector->sched.frameScheduled()))); needsFrame = true; - if (connector->isPageFlipPending || connector->isFrameRunning || connector->frameEventScheduled || !enabledState) + if (!connector->sched.canSchedule() || !enabledState) return; - connector->frameEventScheduled = true; + connector->sched.setFrameScheduled(true); + + if (!frameIdle) { + frameIdle = makeShared<std::function<void(void)>>([this, self_ = self, backend_ = backend]() { + if (!self) + return; + + connector->sched.setFrameScheduled(false); + if (connector->sched.frameInFlight() || connector->sched.frameRunning()) + return; + + CFrameRunningGuard frameRunning(connector->sched); + connector->sched.frameReady.emit(); + + // above frame scheduled, and then committed, remove the idle frame. the pageflip will emit the frame. + if (backend_ && backend_->backend && connector->sched.frameScheduled() && connector->sched.frameInFlight()) { + backend_->backend->removeIdleEvent(frameIdle); + connector->sched.setFrameScheduled(false); + } + }); + } backend->backend->addIdleEvent(frameIdle); } @@ -2374,7 +2446,11 @@ } bool Aquamarine::CDRMOutput::pendingPageFlip() { - return connector->isPageFlipPending; + return connector->sched.frameInFlight(); +} + +bool Aquamarine::CDRMOutput::pendingIdleFrame() { + return connector->sched.frameScheduled(); } int Aquamarine::CDRMOutput::getConnectorID() { @@ -2385,12 +2461,8 @@ backend(backend_), connector(connector_) { name = name_; - frameIdle = makeShared<std::function<void(void)>>([this]() { - connector->frameEventScheduled = false; - if (connector->isPageFlipPending || connector->isFrameRunning) - return; - events.frame.emit(); - }); + // The scheduler's frameReady signal drives the public events.frame on this output. + frameReadyListener = connector->sched.frameReady.listen([this]() { events.frame.emit(); }); } SP<CDRMFB> Aquamarine::CDRMFB::create(SP<IBuffer> buffer_, Hyprutils::Memory::CWeakPointer<CDRMBackend> backend_, bool* isNew) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aquamarine-0.12.1/src/backend/drm/Props.cpp new/aquamarine-0.14.0/src/backend/drm/Props.cpp --- old/aquamarine-0.12.1/src/backend/drm/Props.cpp 2026-06-11 18:17:06.000000000 +0200 +++ new/aquamarine-0.14.0/src/backend/drm/Props.cpp 2026-07-27 18:34:54.000000000 +0200 @@ -57,6 +57,7 @@ static const struct prop_info plane_info[] = { #define INDEX(name) (offsetof(SDRMPlane::UDRMPlaneProps, values.name) / sizeof(uint32_t)) + {.name = "COLOR_RANGE", .index = INDEX(color_range)}, {.name = "CRTC_H", .index = INDEX(crtc_h)}, {.name = "CRTC_ID", .index = INDEX(crtc_id)}, {.name = "CRTC_W", .index = INDEX(crtc_w)}, @@ -77,6 +78,13 @@ #undef INDEX }; +static const struct prop_info colorrange_info[] = { +#define INDEX(name) (offsetof(SDRMPlane::UDRMPlaneColorRange, values.name) / sizeof(uint32_t)) + {.name = "YCbCr full range", .index = INDEX(Full_YCbCr)}, + {.name = "YCbCr limited range", .index = INDEX(Limited_YCbCr)}, +#undef INDEX +}; + namespace Aquamarine { static int comparePropInfo(const void* arg1, const void* arg2) { @@ -139,6 +147,10 @@ return scanProperties(fd, id, DRM_MODE_OBJECT_PLANE, out->props, plane_info, sizeof(plane_info) / sizeof(plane_info[0])); } + bool getDRMPlaneColorRange(int fd, uint32_t id, SDRMPlane::UDRMPlaneColorRange* out) { + return scanPropertyEnum(fd, id, out->props, colorrange_info, sizeof(colorrange_info) / sizeof(colorrange_info[0])); + } + bool getDRMProp(int fd, uint32_t obj, uint32_t prop, uint64_t* ret) { drmModeObjectProperties* props = drmModeObjectGetProperties(fd, obj, DRM_MODE_OBJECT_ANY); if (!props) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aquamarine-0.12.1/src/backend/drm/Props.hpp new/aquamarine-0.14.0/src/backend/drm/Props.hpp --- old/aquamarine-0.12.1/src/backend/drm/Props.hpp 2026-06-11 18:17:06.000000000 +0200 +++ new/aquamarine-0.14.0/src/backend/drm/Props.hpp 2026-07-27 18:34:54.000000000 +0200 @@ -7,6 +7,7 @@ bool getDRMConnectorColorspace(int fd, uint32_t id, SDRMConnector::UDRMConnectorColorspace* out); bool getDRMCRTCProps(int fd, uint32_t id, SDRMCRTC::UDRMCRTCProps* out); bool getDRMPlaneProps(int fd, uint32_t id, SDRMPlane::UDRMPlaneProps* out); + bool getDRMPlaneColorRange(int fd, uint32_t id, SDRMPlane::UDRMPlaneColorRange* out); bool getDRMProp(int fd, uint32_t obj, uint32_t prop, uint64_t* ret); void* getDRMPropBlob(int fd, uint32_t obj, uint32_t prop, size_t* ret_len); char* getDRMPropEnum(int fd, uint32_t obj, uint32_t prop_id); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aquamarine-0.12.1/src/backend/drm/Renderer.cpp new/aquamarine-0.14.0/src/backend/drm/Renderer.cpp --- old/aquamarine-0.12.1/src/backend/drm/Renderer.cpp 2026-06-11 18:17:06.000000000 +0200 +++ new/aquamarine-0.14.0/src/backend/drm/Renderer.cpp 2026-07-27 18:34:54.000000000 +0200 @@ -475,8 +475,15 @@ attrs.push_back(EGL_CONTEXT_RELEASE_BEHAVIOR_NONE_KHR); // or _FLUSH_KHR } - attrs.push_back(EGL_CONTEXT_OPENGL_DEBUG); - attrs.push_back(Aquamarine::isTrace() ? EGL_TRUE : EGL_FALSE); + if (Aquamarine::isTrace()) { + if (major > 1 || (major == 1 && minor >= 5)) { + attrs.push_back(EGL_CONTEXT_OPENGL_DEBUG); + attrs.push_back(EGL_TRUE); + } else if (EGLEXTENSIONS.contains("EGL_KHR_create_context")) { + attrs.push_back(EGL_CONTEXT_FLAGS_KHR); + attrs.push_back(EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR); + } + } auto attrsNoVer = attrs; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aquamarine-0.12.1/src/backend/drm/impl/Atomic.cpp new/aquamarine-0.14.0/src/backend/drm/impl/Atomic.cpp --- old/aquamarine-0.12.1/src/backend/drm/impl/Atomic.cpp 2026-06-11 18:17:06.000000000 +0200 +++ new/aquamarine-0.14.0/src/backend/drm/impl/Atomic.cpp 2026-07-27 18:34:54.000000000 +0200 @@ -6,6 +6,7 @@ #include <xf86drmMode.h> #include <sys/mman.h> #include <sstream> +#include <optional> #include "Shared.hpp" #include "aquamarine/output/Output.hpp" @@ -88,8 +89,8 @@ TRACE(backend->log(AQ_LOG_TRACE, std::format("atomic planeProps: disabling plane {}", plane->id))); add(plane->id, plane->props.values.fb_id, 0); add(plane->id, plane->props.values.crtc_id, 0); - add(plane->id, plane->props.values.crtc_x, (uint64_t)pos.x); - add(plane->id, plane->props.values.crtc_y, (uint64_t)pos.y); + add(plane->id, plane->props.values.crtc_x, (uint64_t)(int64_t)pos.x); + add(plane->id, plane->props.values.crtc_y, (uint64_t)(int64_t)pos.y); return; } @@ -107,6 +108,23 @@ add(plane->id, plane->props.values.crtc_h, (uint32_t)fb->buffer->size.y); add(plane->id, plane->props.values.fb_id, fb->id); add(plane->id, plane->props.values.crtc_id, crtc); + + if (plane->props.values.color_range) { + const auto colorRange = conn && conn->output ? conn->output->state->state().colorRange : AQ_OUTPUT_COLOR_RANGE_AUTO; + std::optional<uint32_t> rangeVal; + switch (colorRange) { + case AQ_OUTPUT_COLOR_RANGE_FULL: rangeVal = plane->colorRange.values.Full_YCbCr; break; + case AQ_OUTPUT_COLOR_RANGE_LIMITED: rangeVal = plane->colorRange.values.Limited_YCbCr; break; + case AQ_OUTPUT_COLOR_RANGE_AUTO: + // https://github.com/NVIDIA/open-gpu-kernel-modules/discussions/1105 + if (backend->gpuDriver() == AQ_BACKEND_GPU_DRIVER_NVIDIA) + rangeVal = plane->colorRange.values.Full_YCbCr; + break; + } + if (rangeVal) + add(plane->id, plane->props.values.color_range, *rangeVal); + } + planePropsPos(plane, pos); } @@ -117,8 +135,8 @@ TRACE(backend->log(AQ_LOG_TRACE, std::format("atomic planeProps: pos blobs: crtc_x {}, crtc_y {}", plane->props.values.crtc_x, plane->props.values.crtc_y))); - add(plane->id, plane->props.values.crtc_x, (uint64_t)pos.x); - add(plane->id, plane->props.values.crtc_y, (uint64_t)pos.y); + add(plane->id, plane->props.values.crtc_x, (uint64_t)(int64_t)pos.x); + add(plane->id, plane->props.values.crtc_y, (uint64_t)(int64_t)pos.y); } void Aquamarine::CDRMAtomicRequest::setConnector(Hyprutils::Memory::CSharedPointer<SDRMConnector> connector) { @@ -127,7 +145,7 @@ void Aquamarine::CDRMAtomicRequest::addConnector(Hyprutils::Memory::CSharedPointer<SDRMConnector> connector, SDRMConnectorCommitData& data) { const auto& STATE = connector->output->state->state(); - const bool enable = STATE.enabled && data.mainFB; + const bool enable = data.enabled && data.mainFB; TRACE(backend->log(AQ_LOG_TRACE, std::format("atomic addConnector blobs: mode_id {}, active {}, crtc_id {}, link_status {}, content_type {}", connector->crtc->props.values.mode_id, @@ -188,15 +206,15 @@ add(connector->id, connector->props.values.content_type, newContentType); } - data.atomic.maxBpc = newMaxBpc; - data.atomic.colorspace = newColorspace; - data.atomic.contentType = newContentType; - data.atomic.crtcID = newCrtcID; + data.atomic.maxBpc = newMaxBpc; + data.atomic.colorspace = newColorspace; + data.atomic.contentType = newContentType; + data.atomic.crtcID = newCrtcID; add(connector->crtc->id, connector->crtc->props.values.active, enable); if (enable) { - if (connector->output->supportsExplicit && STATE.committed & COutputState::AQ_OUTPUT_STATE_EXPLICIT_OUT_FENCE) + if (connector->output->supportsExplicit && data.committed & COutputState::AQ_OUTPUT_STATE_EXPLICIT_OUT_FENCE) add(connector->crtc->id, connector->crtc->props.values.out_fence_ptr, (uintptr_t)&STATE.explicitOutFence); if (connector->crtc->props.values.gamma_lut && data.atomic.gammad) @@ -227,8 +245,7 @@ if (!data.modeset) return; - const auto& STATE = connector->output->state->state(); - const bool enable = STATE.enabled && data.mainFB; + const bool enable = data.enabled && data.mainFB; data.atomic.blobbed = true; @@ -244,13 +261,12 @@ if (!connector->crtc->cursor) return; - const auto& STATE = connector->output->state->state(); - const bool enable = STATE.enabled && data.mainFB; + const bool enable = data.enabled && data.mainFB; if (enable) { - if (STATE.committed & COutputState::AQ_OUTPUT_STATE_CURSOR_SHAPE || STATE.committed & COutputState::AQ_OUTPUT_STATE_CURSOR_POS) { - TRACE(backend->log(AQ_LOG_TRACE, STATE.committed & COutputState::AQ_OUTPUT_STATE_CURSOR_SHAPE ? "atomic addConnector cursor shape" : "atomic addConnector cursor pos")); - if (STATE.committed & COutputState::AQ_OUTPUT_STATE_CURSOR_SHAPE) { + if (data.committed & COutputState::AQ_OUTPUT_STATE_CURSOR_SHAPE || data.committed & COutputState::AQ_OUTPUT_STATE_CURSOR_POS) { + TRACE(backend->log(AQ_LOG_TRACE, data.committed & COutputState::AQ_OUTPUT_STATE_CURSOR_SHAPE ? "atomic addConnector cursor shape" : "atomic addConnector cursor pos")); + if (data.committed & COutputState::AQ_OUTPUT_STATE_CURSOR_SHAPE) { if (!connector->output->cursorVisible) planeProps(connector->crtc->cursor, nullptr, 0, {}); else @@ -350,7 +366,7 @@ bool Aquamarine::CDRMAtomicImpl::prepareConnector(Hyprutils::Memory::CSharedPointer<SDRMConnector> connector, SDRMConnectorCommitData& data) { const auto& STATE = connector->output->state->state(); - const bool enable = STATE.enabled; + const bool enable = data.enabled; const auto& MODE = STATE.mode ? STATE.mode : STATE.customMode; if (data.modeset) { @@ -400,10 +416,10 @@ // clears the kernel state. only ride the modeset path when the prop actually // exists, otherwise we'd log a spurious "no gamma_lut prop" error per modeset. // (see #127) - if ((data.modeset && connector->crtc->props.values.gamma_lut) || (STATE.committed & COutputState::AQ_OUTPUT_STATE_GAMMA_LUT)) + if ((data.modeset && connector->crtc->props.values.gamma_lut) || (data.committed & COutputState::AQ_OUTPUT_STATE_GAMMA_LUT)) data.atomic.gammad = prepareGammaBlob(connector->crtc->props.values.gamma_lut, STATE.gammaLut, &data.atomic.gammaLut); - if ((data.modeset && connector->crtc->props.values.degamma_lut) || (STATE.committed & COutputState::AQ_OUTPUT_STATE_DEGAMMA_LUT)) + if ((data.modeset && connector->crtc->props.values.degamma_lut) || (data.committed & COutputState::AQ_OUTPUT_STATE_DEGAMMA_LUT)) data.atomic.degammad = prepareGammaBlob(connector->crtc->props.values.degamma_lut, STATE.degammaLut, &data.atomic.degammaLut); if (data.ctm.has_value()) { @@ -430,7 +446,7 @@ } } - if ((data.modeset || (STATE.committed & COutputState::AQ_OUTPUT_STATE_HDR)) && data.hdrMetadata.has_value()) { + if ((data.modeset || (data.committed & COutputState::AQ_OUTPUT_STATE_HDR)) && data.hdrMetadata.has_value()) { if (!connector->props.values.hdr_output_metadata) connector->backend->backend->log(AQ_LOG_ERROR, "atomic drm: failed to commit hdr metadata: no HDR_OUTPUT_METADATA prop support"); else { @@ -457,12 +473,12 @@ } } - if ((STATE.committed & COutputState::AQ_OUTPUT_STATE_DAMAGE) && connector->crtc->primary->props.values.fb_damage_clips && MODE) { - if (STATE.damage.empty()) + if ((data.committed & COutputState::AQ_OUTPUT_STATE_DAMAGE) && connector->crtc->primary->props.values.fb_damage_clips && MODE) { + if (data.damage.empty()) data.atomic.fbDamage = 0; else { TRACE(connector->backend->backend->log(AQ_LOG_TRACE, std::format("atomic drm: clipping damage to pixel size {}", MODE->pixelSize))); - std::vector<pixman_box32_t> rects = STATE.damage.copy().intersect(CBox{{}, MODE->pixelSize}).getRects(); + std::vector<pixman_box32_t> rects = data.damage.copy().intersect(CBox{{}, MODE->pixelSize}).getRects(); if (drmModeCreatePropertyBlob(connector->backend->gpu->fd, rects.data(), sizeof(pixman_box32_t) * rects.size(), &data.atomic.fbDamage)) { connector->backend->backend->log(AQ_LOG_ERROR, "atomic drm: failed to create a damage blob"); return false; @@ -504,12 +520,8 @@ if (data.atomic.ctmd) connector->crtc->atomic.ctmStateKnown = true; - if (data.mainFB && connector->output->state->state().enabled && (flags & DRM_MODE_PAGE_FLIP_EVENT)) { - connector->isPageFlipPending = true; - struct timespec ts; - clock_gettime(CLOCK_BOOTTIME, &ts); - connector->pageFlipPendingAtMs = ts.tv_sec * 1000ULL + ts.tv_nsec / 1000000ULL; - } + if (data.mainFB && data.enabled && (flags & DRM_MODE_PAGE_FLIP_EVENT)) + connector->sched.onFrameSubmitted(); } } else request.rollback(data); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aquamarine-0.12.1/src/backend/drm/impl/Legacy.cpp new/aquamarine-0.14.0/src/backend/drm/impl/Legacy.cpp --- old/aquamarine-0.12.1/src/backend/drm/impl/Legacy.cpp 2026-06-11 18:17:06.000000000 +0200 +++ new/aquamarine-0.14.0/src/backend/drm/impl/Legacy.cpp 2026-07-27 18:34:54.000000000 +0200 @@ -27,7 +27,7 @@ bool Aquamarine::CDRMLegacyImpl::commitInternal(Hyprutils::Memory::CSharedPointer<SDRMConnector> connector, SDRMConnectorCommitData& data) { const auto& STATE = connector->output->state->state(); SP<CDRMFB> mainFB; - bool enable = STATE.enabled; + bool enable = data.enabled; if (enable) { if (!data.mainFB) @@ -65,7 +65,7 @@ } } - if (STATE.committed & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_ADAPTIVE_SYNC) { + if (data.committed & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_ADAPTIVE_SYNC) { if (STATE.adaptiveSync && !connector->canDoVrr) { connector->backend->backend->log(AQ_LOG_ERROR, std::format("legacy drm: connector {} can't do vrr", connector->id)); return false; @@ -87,7 +87,7 @@ // TODO: gamma if (data.cursorFB && connector->crtc->cursor && connector->output->cursorVisible && enable && - (STATE.committed & COutputState::AQ_OUTPUT_STATE_CURSOR_SHAPE || STATE.committed & COutputState::AQ_OUTPUT_STATE_CURSOR_POS)) { + (data.committed & COutputState::AQ_OUTPUT_STATE_CURSOR_SHAPE || data.committed & COutputState::AQ_OUTPUT_STATE_CURSOR_POS)) { uint32_t boHandle = 0; auto attrs = data.cursorFB->buffer->dmabuf(); @@ -137,10 +137,7 @@ return false; } - connector->isPageFlipPending = true; - struct timespec ts; - clock_gettime(CLOCK_BOOTTIME, &ts); - connector->pageFlipPendingAtMs = ts.tv_sec * 1000ULL + ts.tv_nsec / 1000000ULL; + connector->sched.onFrameSubmitted(); return true; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aquamarine-0.12.1/src/output/Output.cpp new/aquamarine-0.14.0/src/output/Output.cpp --- old/aquamarine-0.12.1/src/output/Output.cpp 2026-06-11 18:17:06.000000000 +0200 +++ new/aquamarine-0.14.0/src/output/Output.cpp 2026-07-27 18:34:54.000000000 +0200 @@ -142,6 +142,10 @@ internalState.contentType = drmContentType; } +void Aquamarine::COutputState::setColorRange(eOutputColorRange range) { + internalState.colorRange = range; +} + void Aquamarine::COutputState::onCommit() { internalState.committed = 0; internalState.damage.clear(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aquamarine-0.12.1/tests/Attachments.cpp new/aquamarine-0.14.0/tests/Attachments.cpp --- old/aquamarine-0.12.1/tests/Attachments.cpp 2026-06-11 18:17:06.000000000 +0200 +++ new/aquamarine-0.14.0/tests/Attachments.cpp 2026-07-27 18:34:54.000000000 +0200 @@ -17,16 +17,17 @@ Aquamarine::CAttachmentManager attachments; int ret = 0; EXPECT(attachments.has<CFooAttachment>(), false); - EXPECT(attachments.get<CFooAttachment>(), nullptr); + EXPECT(!!attachments.get<CFooAttachment>(), false); EXPECT(attachments.has<CBarAttachment>(), false); - EXPECT(attachments.get<CBarAttachment>(), nullptr); + EXPECT(!!attachments.get<CBarAttachment>(), false); auto foo = Hyprutils::Memory::makeShared<CFooAttachment>(); attachments.add(foo); EXPECT(attachments.has<CFooAttachment>(), true); EXPECT(attachments.has<CBarAttachment>(), false); foo->counter++; - EXPECT(attachments.get<CFooAttachment>(), foo); + // cant work for now + //EXPECT(attachments.get<CFooAttachment>(), foo); EXPECT(attachments.get<CFooAttachment>()->counter, 1); attachments.add(Hyprutils::Memory::makeShared<CBarAttachment>()); ++++++ aquamarine.obsinfo ++++++ --- /var/tmp/diff_new_pack.i5w4ly/_old 2026-07-28 17:51:40.111378510 +0200 +++ /var/tmp/diff_new_pack.i5w4ly/_new 2026-07-28 17:51:40.147379769 +0200 @@ -1,5 +1,5 @@ name: aquamarine -version: 0.12.1 -mtime: 1781194626 -commit: 06669631175b4db2383b94e7f8c13f45a9d28757 +version: 0.14.0 +mtime: 1785170094 +commit: a79fb21b2e2a82dd061a6d071802bcf38bd5c383
