Repository: hyprwm/Hyprland
Pull request #9437: syncobj: use eventfd instead of stalling fd checks
Author: login: gulafaran
Labels: name: core
name: config
name: protocols
name: render
name: helpers
Created: 2025-02-18T17:40:33Z | Merged: 2025-03-14T14:08:20Z

use eventfd and add it to the event loop and when it recieves a signal release the the queued surfacestate, this means we dont stall entire compositor when waiting for materilisation of the fd. and change its related usage.


should solve a lot of "compositor 5 fps while ingame overlay shows 300fps" and other similiar weird stalls.

on nvidia this shuffles compositor stalls to sometimes a noticeable rendering stutter, technically the acquire point isnt signaled fast enough so the buffer is stuck and nothing gets rendered until it is. pretty much the same stall as seen before only now its per surface rendering. but that i contribute to nvidia driver being simply shit, and is visible on other compositors.

https://github.com/NVIDIA/open-gpu-kernel-modules/issues/777
https://github.com/NVIDIA/open-gpu-kernel-modules/issues/743

fixes https://github.com/hyprwm/Hyprland/issues/7857
fixes https://github.com/hyprwm/Hyprland/issues/9340
fixes https://github.com/hyprwm/Hyprland/issues/9340
fixes https://github.com/hyprwm/Hyprland/issues/8588
fixes https://github.com/hyprwm/Hyprland/issues/7643
fixes https://github.com/hyprwm/Hyprland/issues/7317
fixes https://github.com/hyprwm/Hyprland/issues/9376
fixes https://github.com/hyprwm/Hyprland/issues/6844
fixes https://github.com/hyprwm/Hyprland/issues/6912

potentially helps:
https://github.com/hyprwm/Hyprland/issues/6617
https://github.com/hyprwm/Hyprland/issues/9011


### remaining quirks to be fixed
- [ x ] resizing rendering window like vkcube is delayed hard
-  its because we are getting a gazzillion xdg shell configure requests that fill the wayland event loop, and eventfd signal gets behind in the queue.
- [ x ] walker crashes https://paste.pika-os.com/upload/bat-tiger-jaguar
-  https://github.com/wmww/gtk4-layer-shell/issues/50 requires gtk4-layer-shell 1.0.4
- [ ] test and see if implicit/explicit sync didnt break hard or some other card/vendors AMD/Intel/etc
- [ ] test and see if gpu/cpu usage didnt regress into other areas. not resetting damage or similiar
- [ x ] syncobj points for async buffers




Diff:
diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp
index eda5c733217..20f959de426 100644
--- a/src/config/ConfigManager.cpp
+++ b/src/config/ConfigManager.cpp
@@ -691,7 +691,6 @@ CConfigManager::CConfigManager() {
     registerConfigVar("render:expand_undersized_textures", Hyprlang::INT{1});
     registerConfigVar("render:xp_mode", Hyprlang::INT{0});
     registerConfigVar("render:ctm_animation", Hyprlang::INT{2});
-    registerConfigVar("render:allow_early_buffer_release", Hyprlang::INT{1});
     registerConfigVar("render:cm_fs_passthrough", Hyprlang::INT{1});
 
     registerConfigVar("ecosystem:no_update_news", Hyprlang::INT{0});
diff --git a/src/helpers/Monitor.cpp b/src/helpers/Monitor.cpp
index ce30b8e7957..71022425093 100644
--- a/src/helpers/Monitor.cpp
+++ b/src/helpers/Monitor.cpp
@@ -1391,16 +1391,17 @@ bool CMonitor::attemptDirectScanout() {
 
     auto explicitOptions = g_pHyprRenderer->getExplicitSyncSettings(output);
 
-    bool DOEXPLICIT = PSURFACE->syncobj && PSURFACE->syncobj->current.acquireTimeline && PSURFACE->syncobj->current.acquireTimeline->timeline && explicitOptions.explicitKMSEnabled;
+    bool DOEXPLICIT = PSURFACE->syncobj && PSURFACE->current.buffer && PSURFACE->current.buffer->acquire && explicitOptions.explicitKMSEnabled;
     if (DOEXPLICIT) {
         // wait for surface's explicit fence if present
-        CFileDescriptor fd = PSURFACE->syncobj->current.acquireTimeline->timeline->exportAsSyncFileFD(PSURFACE->syncobj->current.acquirePoint);
-        if (fd.isValid()) {
-            Debug::log(TRACE, "attemptDirectScanout: setting IN_FENCE for aq to {}", fd.get());
-            output->state->setExplicitInFence(fd.get());
-        } else
+        inFence = PSURFACE->current.buffer->acquire->exportAsFD();
+        if (inFence.isValid()) {
+            Debug::log(TRACE, "attemptDirectScanout: setting IN_FENCE for aq to {}", inFence.get());
+            output->state->setExplicitInFence(inFence.get());
+        } else {
             Debug::log(TRACE, "attemptDirectScanout: failed to acquire an sync file fd for aq IN_FENCE");
-        DOEXPLICIT = fd.isValid();
+            DOEXPLICIT = false;
+        }
     }
 
     commitSeq++;
@@ -1425,7 +1426,7 @@ bool CMonitor::attemptDirectScanout() {
         Debug::log(LOG, "Entered a direct scanout to {:x}: \"{}\"", (uintptr_t)PCANDIDATE.get(), PCANDIDATE->m_szTitle);
     }
 
-    if (!PBUFFER->lockedByBackend)
+    if (!PBUFFER->lockedByBackend || PBUFFER->hlEvents.backendRelease)
         return true;
 
     // lock buffer while DRM/KMS is using it, then release it when page flip happens since DRM/KMS should be done by then
diff --git a/src/helpers/Monitor.hpp b/src/helpers/Monitor.hpp
index 10e807a0575..36959aa28c6 100644
--- a/src/helpers/Monitor.hpp
+++ b/src/helpers/Monitor.hpp
@@ -138,11 +138,12 @@ class CMonitor {
     SMonitorRule                activeMonitorRule;
 
     // explicit sync
-    SP<CSyncTimeline> inTimeline;
-    SP<CSyncTimeline> outTimeline;
-    uint64_t          commitSeq = 0;
+    SP<CSyncTimeline>              inTimeline;
+    SP<CSyncTimeline>              outTimeline;
+    Hyprutils::OS::CFileDescriptor inFence;
+    uint64_t                       commitSeq = 0;
 
-    PHLMONITORREF     self;
+    PHLMONITORREF                  self;
 
     // mirroring
     PHLMONITORREF              pMirrorOf;
diff --git a/src/helpers/sync/SyncReleaser.cpp b/src/helpers/sync/SyncReleaser.cpp
index 198495ab6da..fca7d7c2b14 100644
--- a/src/helpers/sync/SyncReleaser.cpp
+++ b/src/helpers/sync/SyncReleaser.cpp
@@ -1,25 +1,65 @@
 #include "SyncReleaser.hpp"
 #include "SyncTimeline.hpp"
 #include "../../render/OpenGL.hpp"
+#include <sys/ioctl.h>
 
-CSyncReleaser::CSyncReleaser(WP<CSyncTimeline> timeline_, uint64_t point_) : timeline(timeline_), point(point_) {
+#if defined(__linux__)
+#include <linux/sync_file.h>
+#else
+struct sync_merge_data {
+    char  name[32];
+    __s32 fd2;
+    __s32 fence;
+    __u32 flags;
+    __u32 pad;
+};
+#define SYNC_IOC_MAGIC '>'
+#define SYNC_IOC_MERGE _IOWR(SYNC_IOC_MAGIC, 3, struct sync_merge_data)
+#endif
+
+using namespace Hyprutils::OS;
+
+CSyncReleaser::CSyncReleaser(SP<CSyncTimeline> timeline, uint64_t point) : m_timeline(timeline), m_point(point) {
     ;
 }
 
 CSyncReleaser::~CSyncReleaser() {
-    if (timeline.expired())
+    if (!m_timeline) {
+        Debug::log(ERR, "CSyncReleaser destructing without a timeline");
         return;
+    }
+
+    if (m_fd.isValid())
+        m_timeline->importFromSyncFileFD(m_point, m_fd);
+    else
+        m_timeline->signal(m_point);
+}
 
-    if (sync)
-        timeline->importFromSyncFileFD(point, sync->fd());
+CFileDescriptor CSyncReleaser::mergeSyncFds(const CFileDescriptor& fd1, const CFileDescriptor& fd2) {
+    struct sync_merge_data data{
+        .name  = "merged release fence",
+        .fd2   = fd2.get(),
+        .fence = -1,
+    };
+    int err = -1;
+    do {
+        err = ioctl(fd1.get(), SYNC_IOC_MERGE, &data);
+    } while (err == -1 && (errno == EINTR || errno == EAGAIN));
+    if (err < 0)
+        return CFileDescriptor{};
     else
-        timeline->signal(point);
+        return CFileDescriptor(data.fence);
 }
 
-void CSyncReleaser::addReleaseSync(SP<CEGLSync> sync_) {
-    sync = sync_;
+void CSyncReleaser::addReleaseSync(SP<CEGLSync> sync) {
+    if (m_fd.isValid())
+        m_fd = mergeSyncFds(m_fd, sync->takeFD());
+    else
+        m_fd = sync->fd().duplicate();
+
+    m_sync = sync;
 }
 
 void CSyncReleaser::drop() {
-    timeline.reset();
-}
\ No newline at end of file
+    m_timeline.reset();
+}
diff --git a/src/helpers/sync/SyncReleaser.hpp b/src/helpers/sync/SyncReleaser.hpp
index e21d2e34bd2..f04e85c1b20 100644
--- a/src/helpers/sync/SyncReleaser.hpp
+++ b/src/helpers/sync/SyncReleaser.hpp
@@ -4,6 +4,7 @@
 #include <optional>
 #include <vector>
 #include <functional>
+#include <hyprutils/os/FileDescriptor.hpp>
 #include "../memory/Memory.hpp"
 
 /*
@@ -15,17 +16,19 @@ class CEGLSync;
 
 class CSyncReleaser {
   public:
-    CSyncReleaser(WP<CSyncTimeline> timeline_, uint64_t point_);
+    CSyncReleaser(SP<CSyncTimeline> timeline, uint64_t point);
     ~CSyncReleaser();
 
     // drops the releaser, will never signal anymore
     void drop();
 
     // wait for this gpu job to finish before releasing
-    void addReleaseSync(SP<CEGLSync> sync);
+    Hyprutils::OS::CFileDescriptor mergeSyncFds(const Hyprutils::OS::CFileDescriptor& fd1, const Hyprutils::OS::CFileDescriptor& fd2);
+    void                           addReleaseSync(SP<CEGLSync> sync);
 
   private:
-    WP<CSyncTimeline> timeline;
-    uint64_t          point = 0;
-    SP<CEGLSync>      sync;
+    SP<CSyncTimeline>              m_timeline;
+    uint64_t                       m_point = 0;
+    Hyprutils::OS::CFileDescriptor m_fd;
+    SP<CEGLSync>                   m_sync;
 };
diff --git a/src/helpers/sync/SyncTimeline.cpp b/src/helpers/sync/SyncTimeline.cpp
index 46b617bc6d6..d11205bc370 100644
--- a/src/helpers/sync/SyncTimeline.cpp
+++ b/src/helpers/sync/SyncTimeline.cpp
@@ -33,6 +33,13 @@ SP<CSyncTimeline> CSyncTimeline::create(int drmFD_, int drmSyncobjFD) {
 }
 
 CSyncTimeline::~CSyncTimeline() {
+    for (auto& w : waiters) {
+        if (w->source) {
+            wl_event_source_remove(w->source);
+            w->source = nullptr;
+        }
+    }
+
     if (handle == 0)
         return;
 
@@ -124,6 +131,17 @@ void CSyncTimeline::removeWaiter(SWaiter* w) {
     std::erase_if(waiters, [w](const auto& e) { return e.get() == w; });
 }
 
+void CSyncTimeline::removeAllWaiters() {
+    for (auto& w : waiters) {
+        if (w->source) {
+            wl_event_source_remove(w->source);
+            w->source = nullptr;
+        }
+    }
+
+    waiters.clear();
+}
+
 CFileDescriptor CSyncTimeline::exportAsSyncFileFD(uint64_t src) {
     int      sync = -1;
 
diff --git a/src/helpers/sync/SyncTimeline.hpp b/src/helpers/sync/SyncTimeline.hpp
index ba65e004d0b..bfd70416106 100644
--- a/src/helpers/sync/SyncTimeline.hpp
+++ b/src/helpers/sync/SyncTimeline.hpp
@@ -34,6 +34,7 @@ class CSyncTimeline {
 
     bool                           addWaiter(const std::function<void()>& waiter, uint64_t point, uint32_t flags);
     void                           removeWaiter(SWaiter*);
+    void                           removeAllWaiters();
     Hyprutils::OS::CFileDescriptor exportAsSyncFileFD(uint64_t src);
     bool                           importFromSyncFileFD(uint64_t dst, Hyprutils::OS::CFileDescriptor& fd);
     bool                           transfer(SP<CSyncTimeline> from, uint64_t fromPoint, uint64_t toPoint);
diff --git a/src/protocols/DRMSyncobj.cpp b/src/protocols/DRMSyncobj.cpp
index 38aab305af3..406b55e03cf 100644
--- a/src/protocols/DRMSyncobj.cpp
+++ b/src/protocols/DRMSyncobj.cpp
@@ -4,11 +4,82 @@
 #include "core/Compositor.hpp"
 #include "../helpers/sync/SyncTimeline.hpp"
 #include "../Compositor.hpp"
+#include "render/OpenGL.hpp"
 
 #include <fcntl.h>
 using namespace Hyprutils::OS;
 
-CDRMSyncobjSurfaceResource::CDRMSyncobjSurfaceResource(SP<CWpLinuxDrmSyncobjSurfaceV1> resource_, SP<CWLSurfaceResource> surface_) : surface(surface_), resource(resource_) {
+CDRMSyncPointState::CDRMSyncPointState(WP<CDRMSyncobjTimelineResource> resource_, uint64_t point_, bool acquirePoint) :
+    m_resource(resource_), m_point(point_), m_acquirePoint(acquirePoint) {}
+
+const uint64_t& CDRMSyncPointState::point() {
+    return m_point;
+}
+
+WP<CDRMSyncobjTimelineResource> CDRMSyncPointState::resource() {
+    return m_resource;
+}
+
+WP<CSyncTimeline> CDRMSyncPointState::timeline() {
+    if (expired()) {
+        Debug::log(ERR, "CDRMSyncPointState: getting a timeline on a expired point");
+        return {};
+    }
+
+    return m_resource->timeline;
+}
+
+bool CDRMSyncPointState::expired() {
+    return !m_resource || !m_resource->timeline;
+}
+
+UP<CSyncReleaser> CDRMSyncPointState::createSyncRelease() {
+    if (expired()) {
+        Debug::log(ERR, "CDRMSyncPointState: creating a sync releaser on an expired point");
+        return nullptr;
+    }
+
+    if (m_releaseTaken)
+        Debug::log(ERR, "CDRMSyncPointState: creating a sync releaser on an already created SyncRelease");
+
+    m_releaseTaken = true;
+    return makeUnique<CSyncReleaser>(m_resource->timeline, m_point);
+}
+
+bool CDRMSyncPointState::addWaiter(const std::function<void()>& waiter) {
+    if (expired()) {
+        Debug::log(ERR, "CDRMSyncPointState: adding a waiter on an expired point");
+        return false;
+    }
+
+    m_acquireCommitted = true;
+    return m_resource->timeline->addWaiter(waiter, m_point, 0u);
+}
+
+bool CDRMSyncPointState::comitted() {
+    return m_acquireCommitted;
+}
+
+CFileDescriptor CDRMSyncPointState::exportAsFD() {
+    if (expired()) {
+        Debug::log(ERR, "CDRMSyncPointState: exporting a FD on an expired point");
+        return {};
+    }
+
+    return m_resource->timeline->exportAsSyncFileFD(m_point);
+}
+
+void CDRMSyncPointState::signal() {
+    if (expired()) {
+        Debug::log(ERR, "CDRMSyncPointState: signaling on an expired point");
+        return;
+    }
+
+    m_resource->timeline->signal(m_point);
+}
+
+CDRMSyncobjSurfaceResource::CDRMSyncobjSurfaceResource(UP<CWpLinuxDrmSyncobjSurfaceV1>&& resource_, SP<CWLSurfaceResource> surface_) :
+    surface(surface_), resource(std::move(resource_)) {
     if UNLIKELY (!good())
         return;
 
@@ -23,9 +94,8 @@ CDRMSyncobjSurfaceResource::CDRMSyncobjSurfaceResource(SP<CWpLinuxDrmSyncobjSurf
             return;
         }
 
-        auto timeline           = CDRMSyncobjTimelineResource::fromResource(timeline_);
-        pending.acquireTimeline = timeline;
-        pending.acquirePoint    = ((uint64_t)hi << 32) | (uint64_t)lo;
+        auto timeline  = CDRMSyncobjTimelineResource::fromResource(timeline_);
+        pendingAcquire = {timeline, ((uint64_t)hi << 32) | (uint64_t)lo, true};
     });
 
     resource->setSetReleasePoint([this](CWpLinuxDrmSyncobjSurfaceV1* r, wl_resource* timeline_, uint32_t hi, uint32_t lo) {
@@ -34,77 +104,102 @@ CDRMSyncobjSurfaceResource::CDRMSyncobjSurfaceResource(SP<CWpLinuxDrmSyncobjSurf
             return;
         }
 
-        auto timeline           = CDRMSyncobjTimelineResource::fromResource(timeline_);
-        pending.releaseTimeline = timeline;
-        pending.releasePoint    = ((uint64_t)hi << 32) | (uint64_t)lo;
+        auto timeline  = CDRMSyncobjTimelineResource::fromResource(timeline_);
+        pendingRelease = {timeline, ((uint64_t)hi << 32) | (uint64_t)lo, false};
     });
 
     listeners.surfacePrecommit = surface->events.precommit.registerListener([this](std::any d) {
-        if ((pending.acquireTimeline || pending.releaseTimeline) && !surface->pending.texture) {
-            resource->error(WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_BUFFER, "Missing buffer");
-            surface->pending.rejected = true;
-            return;
+        if (!surface->pending.buffer && surface->pending.newBuffer && !surface->pending.texture) {
+            removeAllWaiters();
+            surface->commitPendingState(surface->pending);
+            return; // null buffer attached.
         }
 
-        if (!surface->pending.newBuffer)
-            return; // this commit does not change the state here
-
-        if (!!pending.acquireTimeline != !!pending.releaseTimeline) {
-            resource->error(pending.acquireTimeline ? WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_RELEASE_POINT : WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_ACQUIRE_POINT,
-                            "Missing timeline");
-            surface->pending.rejected = true;
-            return;
+        if (!surface->pending.buffer && !surface->pending.newBuffer && surface->current.buffer) {
+            surface->current.bufferDamage.clear();
+            surface->current.damage.clear();
+            surface->commitPendingState(surface->current);
+            return; // no new buffer, but we still have current around and a commit happend, commit current again.
         }
 
-        if (!pending.acquireTimeline)
+        if (!surface->pending.buffer && !surface->pending.newBuffer && !surface->current.buffer) {
+            surface->commitPendingState(surface->pending); // no pending buffer, no current buffer. probably first commit
             return;
+        }
 
-        if (pending.acquireTimeline && pending.releaseTimeline && pending.acquireTimeline == pending.releaseTimeline) {
-            if (pending.acquirePoint >= pending.releasePoint) {
-                resource->error(WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_CONFLICTING_POINTS, "Acquire and release points are on the same timeline, and acquire >= release");
-                surface->pending.rejected = true;
-                return;
-            }
+        if (!pendingAcquire.expired()) {
+            surface->pending.buffer->acquire = makeUnique<CDRMSyncPointState>(std::move(pendingAcquire));
+            pendingAcquire                   = {};
         }
 
-        // wait for the acquire timeline to materialize
-        auto materialized = pending.acquireTimeline->timeline->check(pending.acquirePoint, DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE);
-        if (!materialized.has_value()) {
-            LOGM(ERR, "Failed to check the acquire timeline");
-            resource->noMemory();
-            return;
+        if (!pendingRelease.expired()) {
+            surface->pending.buffer->release = makeUnique<CDRMSyncPointState>(std::move(pendingRelease));
+            pendingRelease                   = {};
         }
 
-        if (materialized.value())
+        if (protocolError())
             return;
 
-        surface->lockPendingState();
-        pending.acquireTimeline->timeline->addWaiter([this]() { surface->unlockPendingState(); }, pending.acquirePoint, DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE);
+        const auto& state = pendingStates.emplace_back(makeShared<SSurfaceState>(surface->pending));
+        surface->pending.damage.clear();
+        surface->pending.bufferDamage.clear();
+        surface->pending.newBuffer = false;
+        surface->pending.buffer.reset();
+
+        state->buffer->buffer->syncReleaser = state->buffer->release->createSyncRelease();
+        state->buffer->acquire->addWaiter([this, surf = surface, wp = CWeakPointer<SSurfaceState>(*std::prev(pendingStates.end()))] {
+            if (!surf)
+                return;
+
+            surf->commitPendingState(*wp.lock());
+            std::erase(pendingStates, wp);
+        });
     });
+}
 
-    listeners.surfaceCommit = surface->events.roleCommit.registerListener([this](std::any d) {
-        // apply timelines if new ones have been attached, otherwise don't touch
-        // the current ones
-        if (pending.releaseTimeline) {
-            current.releaseTimeline = pending.releaseTimeline;
-            current.releasePoint    = pending.releasePoint;
-        }
+void CDRMSyncobjSurfaceResource::removeAllWaiters() {
+    for (auto& s : pendingStates) {
+        if (s && s->buffer && s->buffer->acquire && !s->buffer->acquire->expired())
+            s->buffer->acquire->resource()->timeline->removeAllWaiters();
+    }
 
-        if (pending.acquireTimeline) {
-            current.acquireTimeline = pending.acquireTimeline;
-            current.acquirePoint    = pending.acquirePoint;
+    pendingStates.clear();
+}
+
+CDRMSyncobjSurfaceResource::~CDRMSyncobjSurfaceResource() {
+    removeAllWaiters();
+}
+
+bool CDRMSyncobjSurfaceResource::protocolError() {
+    if (!surface->pending.texture) {
+        resource->error(WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_BUFFER, "Missing buffer");
+        surface->pending.rejected = true;
+        return true;
+    }
+
+    if (!!surface->pending.buffer->acquire != !!surface->pending.buffer->release) {
+        resource->error(surface->pending.buffer->acquire ? WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_RELEASE_POINT : WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_ACQUIRE_POINT,
+                        "Missing timeline");
+        surface->pending.rejected = true;
+        return true;
+    }
+
+    if (surface->pending.buffer->acquire->timeline() == surface->pending.buffer->release->timeline()) {
+        if (surface->pending.buffer->acquire->point() >= surface->pending.buffer->release->point()) {
+            resource->error(WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_CONFLICTING_POINTS, "Acquire and release points are on the same timeline, and acquire >= release");
+            surface->pending.rejected = true;
+            return true;
         }
+    }
 
-        pending.releaseTimeline.reset();
-        pending.acquireTimeline.reset();
-    });
+    return false;
 }
 
 bool CDRMSyncobjSurfaceResource::good() {
     return resource->resource();
 }
 
-CDRMSyncobjTimelineResource::CDRMSyncobjTimelineResource(SP<CWpLinuxDrmSyncobjTimelineV1> resource_, CFileDescriptor&& fd_) : fd(std::move(fd_)), resource(resource_) {
+CDRMSyncobjTimelineResource::CDRMSyncobjTimelineResource(UP<CWpLinuxDrmSyncobjTimelineV1>&& resource_, CFileDescriptor&& fd_) : fd(std::move(fd_)), resource(std::move(resource_)) {
     if UNLIKELY (!good())
         return;
 
@@ -121,16 +216,20 @@ CDRMSyncobjTimelineResource::CDRMSyncobjTimelineResource(SP<CWpLinuxDrmSyncobjTi
     }
 }
 
-SP<CDRMSyncobjTimelineResource> CDRMSyncobjTimelineResource::fromResource(wl_resource* res) {
-    auto data = (CDRMSyncobjTimelineResource*)(((CWpLinuxDrmSyncobjTimelineV1*)wl_resource_get_user_data(res))->data());
-    return data ? data->self.lock() : nullptr;
+WP<CDRMSyncobjTimelineResource> CDRMSyncobjTimelineResource::fromResource(wl_resource* res) {
+    for (const auto& r : PROTO::sync->m_vTimelines) {
+        if (r && r->resource && r->resource->resource() == res)
+            return r;
+    }
+
+    return {};
 }
 
 bool CDRMSyncobjTimelineResource::good() {
     return resource->resource();
 }
 
-CDRMSyncobjManagerResource::CDRMSyncobjManagerResource(SP<CWpLinuxDrmSyncobjManagerV1> resource_) : resource(resource_) {
+CDRMSyncobjManagerResource::CDRMSyncobjManagerResource(UP<CWpLinuxDrmSyncobjManagerV1>&& resource_) : resource(std::move(resource_)) {
     if UNLIKELY (!good())
         return;
 
@@ -154,28 +253,28 @@ CDRMSyncobjManagerResource::CDRMSyncobjManagerResource(SP<CWpLinuxDrmSyncobjMana
             return;
         }
 
-        auto RESOURCE = makeShared<CDRMSyncobjSurfaceResource>(makeShared<CWpLinuxDrmSyncobjSurfaceV1>(resource->client(), resource->version(), id), SURF);
+        const auto& RESOURCE = PROTO::sync->m_vSurfaces.emplace_back(
+            makeUnique<CDRMSyncobjSurfaceResource>(makeUnique<CWpLinuxDrmSyncobjSurfaceV1>(resource->client(), resource->version(), id), SURF));
         if UNLIKELY (!RESOURCE->good()) {
             resource->noMemory();
+            PROTO::sync->m_vSurfaces.pop_back();
             return;
         }
 
-        PROTO::sync->m_vSurfaces.emplace_back(RESOURCE);
         SURF->syncobj = RESOURCE;
 
         LOGM(LOG, "New linux_syncobj at {:x} for surface {:x}", (uintptr_t)RESOURCE.get(), (uintptr_t)SURF.get());
     });
 
     resource->setImportTimeline([this](CWpLinuxDrmSyncobjManagerV1* r, uint32_t id, int32_t fd) {
-        auto RESOURCE = makeShared<CDRMSyncobjTimelineResource>(makeShared<CWpLinuxDrmSyncobjTimelineV1>(resource->client(), resource->version(), id), CFileDescriptor{fd});
+        const auto& RESOURCE = PROTO::sync->m_vTimelines.emplace_back(
+            makeUnique<CDRMSyncobjTimelineResource>(makeUnique<CWpLinuxDrmSyncobjTimelineV1>(resource->client(), resource->version(), id), CFileDescriptor{fd}));
         if UNLIKELY (!RESOURCE->good()) {
             resource->noMemory();
+            PROTO::sync->m_vTimelines.pop_back();
             return;
         }
 
-        PROTO::sync->m_vTimelines.emplace_back(RESOURCE);
-        RESOURCE->self = RESOURCE;
-
         LOGM(LOG, "New linux_drm_timeline at {:x}", (uintptr_t)RESOURCE.get());
     });
 }
@@ -184,12 +283,10 @@ bool CDRMSyncobjManagerResource::good() {
     return resource->resource();
 }
 
-CDRMSyncobjProtocol::CDRMSyncobjProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {
-    drmFD = g_pCompositor->m_iDRMFD;
-}
+CDRMSyncobjProtocol::CDRMSyncobjProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name), drmFD(g_pCompositor->m_iDRMFD) {}
 
 void CDRMSyncobjProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {
-    const auto RESOURCE = m_vManagers.emplace_back(makeShared<CDRMSyncobjManagerResource>(makeShared<CWpLinuxDrmSyncobjManagerV1>(client, ver, id)));
+    const auto& RESOURCE = m_vManagers.emplace_back(makeUnique<CDRMSyncobjManagerResource>(makeUnique<CWpLinuxDrmSyncobjManagerV1>(client, ver, id)));
 
     if UNLIKELY (!RESOURCE->good()) {
         wl_client_post_no_memory(client);
diff --git a/src/protocols/DRMSyncobj.hpp b/src/protocols/DRMSyncobj.hpp
index 9895dff17e3..78905d3d2d3 100644
--- a/src/protocols/DRMSyncobj.hpp
+++ b/src/protocols/DRMSyncobj.hpp
@@ -2,64 +2,95 @@
 
 #include <vector>
 #include "WaylandProtocol.hpp"
+#include "helpers/sync/SyncReleaser.hpp"
 #include "linux-drm-syncobj-v1.hpp"
 #include "../helpers/signal/Signal.hpp"
+#include "types/SurfaceState.hpp"
 #include <hyprutils/os/FileDescriptor.hpp>
+#include <list>
 
 class CWLSurfaceResource;
 class CDRMSyncobjTimelineResource;
 class CSyncTimeline;
+struct SSurfaceState;
 
-class CDRMSyncobjSurfaceResource {
+class CDRMSyncPointState {
   public:
-    CDRMSyncobjSurfaceResource(SP<CWpLinuxDrmSyncobjSurfaceV1> resource_, SP<CWLSurfaceResource> surface_);
+    CDRMSyncPointState() = default;
+    CDRMSyncPointState(WP<CDRMSyncobjTimelineResource> resource_, uint64_t point_, bool acquirePoint);
+    ~CDRMSyncPointState() = default;
+
+    const uint64_t&                                  point();
+    WP<CDRMSyncobjTimelineResource>                  resource();
+    WP<CSyncTimeline>                                timeline();
+    bool                                             expired();
+    Hyprutils::Memory::CUniquePointer<CSyncReleaser> createSyncRelease();
+    bool                                             addWaiter(const std::function<void()>& waiter);
+    bool                                             comitted();
+    Hyprutils::OS::CFileDescriptor                   exportAsFD();
+    void                                             signal();
 
-    bool                   good();
+  private:
+    WP<CDRMSyncobjTimelineResource> m_resource         = {};
+    uint64_t                        m_point            = 0;
+    WP<CSyncTimeline>               m_timeline         = {};
+    bool                            m_acquirePoint     = false;
+    bool                            m_acquireCommitted = false;
+    bool                            m_releaseTaken     = false;
+};
 
-    WP<CWLSurfaceResource> surface;
-    struct {
-        WP<CDRMSyncobjTimelineResource> acquireTimeline, releaseTimeline;
-        uint64_t                        acquirePoint = 0, releasePoint = 0;
-    } current, pending;
+class CDRMSyncobjSurfaceResource {
+  public:
+    CDRMSyncobjSurfaceResource(UP<CWpLinuxDrmSyncobjSurfaceV1>&& resource_, SP<CWLSurfaceResource> surface_);
+    ~CDRMSyncobjSurfaceResource();
+
+    bool protocolError();
+    bool good();
 
   private:
-    SP<CWpLinuxDrmSyncobjSurfaceV1> resource;
+    void                            removeAllWaiters();
+    WP<CWLSurfaceResource>          surface;
+    UP<CWpLinuxDrmSyncobjSurfaceV1> resource;
+
+    CDRMSyncPointState              pendingAcquire;
+    CDRMSyncPointState              pendingRelease;
+    std::vector<SP<SSurfaceState>>  pendingStates;
 
     struct {
         CHyprSignalListener surfacePrecommit;
-        CHyprSignalListener surfaceCommit;
     } listeners;
 };
 
 class CDRMSyncobjTimelineResource {
   public:
-    CDRMSyncobjTimelineResource(SP<CWpLinuxDrmSyncobjTimelineV1> resource_, Hyprutils::OS::CFileDescriptor&& fd_);
+    CDRMSyncobjTimelineResource(UP<CWpLinuxDrmSyncobjTimelineV1>&& resource_, Hyprutils::OS::CFileDescriptor&& fd_);
     ~CDRMSyncobjTimelineResource() = default;
-    static SP<CDRMSyncobjTimelineResource> fromResource(wl_resource*);
+    static WP<CDRMSyncobjTimelineResource> fromResource(wl_resource*);
 
     bool                                   good();
 
-    WP<CDRMSyncobjTimelineResource>        self;
     Hyprutils::OS::CFileDescriptor         fd;
     SP<CSyncTimeline>                      timeline;
 
   private:
-    SP<CWpLinuxDrmSyncobjTimelineV1> resource;
+    UP<CWpLinuxDrmSyncobjTimelineV1> resource;
 };
 
 class CDRMSyncobjManagerResource {
   public:
-    CDRMSyncobjManagerResource(SP<CWpLinuxDrmSyncobjManagerV1> resource_);
+    CDRMSyncobjManagerResource(UP<CWpLinuxDrmSyncobjManagerV1>&& resource_);
+    ~CDRMSyncobjManagerResource() = default;
 
     bool good();
 
   private:
-    SP<CWpLinuxDrmSyncobjManagerV1> resource;
+    UP<CWpLinuxDrmSyncobjManagerV1> resource;
 };
 
 class CDRMSyncobjProtocol : public IWaylandProtocol {
   public:
     CDRMSyncobjProtocol(const wl_interface* iface, const int& ver, const std::string& name);
+    ~CDRMSyncobjProtocol() = default;
 
     virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);
 
@@ -69,9 +100,9 @@ class CDRMSyncobjProtocol : public IWaylandProtocol {
     void destroyResource(CDRMSyncobjSurfaceResource* resource);
 
     //
-    std::vector<SP<CDRMSyncobjManagerResource>>  m_vManagers;
-    std::vector<SP<CDRMSyncobjTimelineResource>> m_vTimelines;
-    std::vector<SP<CDRMSyncobjSurfaceResource>>  m_vSurfaces;
+    std::vector<UP<CDRMSyncobjManagerResource>>  m_vManagers;
+    std::vector<UP<CDRMSyncobjTimelineResource>> m_vTimelines;
+    std::vector<UP<CDRMSyncobjSurfaceResource>>  m_vSurfaces;
 
     //
     int drmFD = -1;
diff --git a/src/protocols/LinuxDMABUF.cpp b/src/protocols/LinuxDMABUF.cpp
index b7b91594847..7202228522f 100644
--- a/src/protocols/LinuxDMABUF.cpp
+++ b/src/protocols/LinuxDMABUF.cpp
@@ -111,6 +111,9 @@ CLinuxDMABuffer::CLinuxDMABuffer(uint32_t id, wl_client* client, Aquamarine::SDM
 }
 
 CLinuxDMABuffer::~CLinuxDMABuffer() {
+    if (buffer && buffer->resource)
+        buffer->resource->sendRelease();
+
     buffer.reset();
     listeners.bufferResourceDestroy.reset();
 }
diff --git a/src/protocols/LinuxDMABUF.hpp b/src/protocols/LinuxDMABUF.hpp
index 6c9678787e0..d55ce8a7fb2 100644
--- a/src/protocols/LinuxDMABUF.hpp
+++ b/src/protocols/LinuxDMABUF.hpp
@@ -97,6 +97,7 @@ class CLinuxDMABUFFeedbackResource {
 class CLinuxDMABUFResource {
   public:
     CLinuxDMABUFResource(SP<CZwpLinuxDmabufV1> resource_);
+    ~CLinuxDMABUFResource() = default;
 
     bool good();
     void sendMods();
diff --git a/src/protocols/SinglePixel.cpp b/src/protocols/SinglePixel.cpp
index 4d643a53d25..c383dbb3b32 100644
--- a/src/protocols/SinglePixel.cpp
+++ b/src/protocols/SinglePixel.cpp
@@ -24,6 +24,11 @@ CSinglePixelBuffer::CSinglePixelBuffer(uint32_t id, wl_client* client, CHyprColo
         Debug::log(ERR, "Failed creating a single pixel texture: null texture id");
 }
 
+CSinglePixelBuffer::~CSinglePixelBuffer() {
+    if (resource)
+        resource->sendRelease();
+}
+
 Aquamarine::eBufferCapability CSinglePixelBuffer::caps() {
     return Aquamarine::eBufferCapability::BUFFER_CAPABILITY_DATAPTR;
 }
diff --git a/src/protocols/SinglePixel.hpp b/src/protocols/SinglePixel.hpp
index bd0607d6d5f..478f3e35462 100644
--- a/src/protocols/SinglePixel.hpp
+++ b/src/protocols/SinglePixel.hpp
@@ -9,7 +9,7 @@
 class CSinglePixelBuffer : public IHLBuffer {
   public:
     CSinglePixelBuffer(uint32_t id, wl_client* client, CHyprColor col);
-    virtual ~CSinglePixelBuffer() = default;
+    virtual ~CSinglePixelBuffer();
 
     virtual Aquamarine::eBufferCapability          caps();
     virtual Aquamarine::eBufferType                type();
diff --git a/src/protocols/core/Compositor.cpp b/src/protocols/core/Compositor.cpp
index f7af95041cb..a9fe33dbbf6 100644
--- a/src/protocols/core/Compositor.cpp
+++ b/src/protocols/core/Compositor.cpp
@@ -113,8 +113,8 @@ CWLSurfaceResource::CWLSurfaceResource(SP<CWlSurface> resource_) : resource(reso
             return;
         }
 
-        if (stateLocks <= 0)
-            commitPendingState();
+        if (!syncobj)
+            commitPendingState(pending);
     });
 
     resource->setDamage([this](CWlSurface* r, int32_t x, int32_t y, int32_t w, int32_t h) { pending.damage.add(CBox{x, y, w, h}); });
@@ -428,29 +428,14 @@ CRegion CWLSurfaceResource::accumulateCurrentBufferDamage() {
     return surfaceDamage.scale(current.scale).transform(wlTransformToHyprutils(invertTransform(current.transform)), trc.x, trc.y).add(current.bufferDamage);
 }
 
-void CWLSurfaceResource::lockPendingState() {
-    stateLocks++;
-}
-
-void CWLSurfaceResource::unlockPendingState() {
-    stateLocks--;
-    if (stateLocks <= 0)
-        commitPendingState();
-}
-
-void CWLSurfaceResource::commitPendingState() {
-    static auto PDROP = CConfigValue<Hyprlang::INT>("render:allow_early_buffer_release");
-    current           = pending;
-    pending.damage.clear();
-    pending.bufferDamage.clear();
-    pending.newBuffer = false;
-    if (!*PDROP)
-        dropPendingBuffer(); // at this point current.buffer holds the same SP and we don't use pending anymore
-
-    events.roleCommit.emit();
-
-    if (syncobj && syncobj->current.releaseTimeline && syncobj->current.releaseTimeline->timeline && current.buffer && current.buffer->buffer)
-        current.buffer->releaser = makeShared<CSyncReleaser>(syncobj->current.releaseTimeline->timeline, syncobj->current.releasePoint);
+void CWLSurfaceResource::commitPendingState(SSurfaceState& state) {
+    if (state.newBuffer) {
+        state.newBuffer = false;
+        current         = state;
+        state.damage.clear();
+        state.bufferDamage.clear();
+        state.buffer.reset();
+    }
 
     if (current.texture)
         current.texture->m_eTransform = wlTransformToHyprutils(current.transform);
@@ -463,14 +448,6 @@ void CWLSurfaceResource::commitPendingState() {
         // TODO: don't update the entire texture
         if (role->role() == SURFACE_ROLE_CURSOR && !DAMAGE.empty())
             updateCursorShm(DAMAGE);
-
-        // release the buffer if it's synchronous as update() has done everything thats needed
-        // so we can let the app know we're done.
-        // Some clients aren't ready to receive a release this early. Should be fine to release it on the next commitPendingState.
-        if (current.buffer->buffer->isSynchronous() && *PDROP) {
-            dropCurrentBuffer();
-            dropPendingBuffer(); // at this point current.buffer holds the same SP and we don't use pending anymore
-        }
     }
 
     // TODO: we should _accumulate_ and not replace above if sync
@@ -494,11 +471,16 @@ void CWLSurfaceResource::commitPendingState() {
             nullptr);
     }
 
-    lastBuffer = current.buffer ? current.buffer->buffer : WP<IHLBuffer>{};
+    // release the buffer if it's synchronous as update() has done everything thats needed
+    // so we can let the app know we're done.
+    // if (!syncobj && current.buffer && current.buffer->buffer && current.buffer->buffer->isSynchronous()) {
+    // dropCurrentBuffer(); // lets not drop it at all, it will get dropped on next commit if a new buffer arrives.
+    // solves flickering on nonsyncobj apps on explicit sync.
+    // }
 }
 
 void CWLSurfaceResource::updateCursorShm(CRegion damage) {
-    auto buf = current.buffer ? current.buffer->buffer : lastBuffer;
+    auto buf = current.buffer ? current.buffer->buffer : WP<IHLBuffer>{};
 
     if UNLIKELY (!buf)
         return;
diff --git a/src/protocols/core/Compositor.hpp b/src/protocols/core/Compositor.hpp
index 396900179f6..f958528c5c0 100644
--- a/src/protocols/core/Compositor.hpp
+++ b/src/protocols/core/Compositor.hpp
@@ -16,6 +16,7 @@
 #include "../../helpers/math/Math.hpp"
 #include "../types/Buffer.hpp"
 #include "../types/SurfaceRole.hpp"
+#include "../types/SurfaceState.hpp"
 
 class CWLOutputResource;
 class CMonitor;
@@ -77,42 +78,15 @@ class CWLSurfaceResource {
     Vector2D                      sourceSize();
 
     struct {
-        CSignal precommit;  // before commit
-        CSignal roleCommit; // commit for role objects, before regular commit
-        CSignal commit;     // after commit
+        CSignal precommit; // before commit
+        CSignal commit;    // after commit
         CSignal map;
         CSignal unmap;
         CSignal newSubsurface;
         CSignal destroy;
     } events;
 
-    struct SState {
-        CRegion                opaque, input = CBox{{}, {INT32_MAX, INT32_MAX}}, damage, bufferDamage = CBox{{}, {INT32_MAX, INT32_MAX}} /* initial damage */;
-        wl_output_transform    transform = WL_OUTPUT_TRANSFORM_NORMAL;
-        int                    scale     = 1;
-        SP<CHLBufferReference> buffer; // buffer ref will be released once the buffer is no longer locked. For checking if a buffer is attached to this state, check texture.
-        SP<CTexture>           texture;
-        Vector2D               offset;
-        Vector2D               size, bufferSize;
-        struct {
-            bool     hasDestination = false;
-            bool     hasSource      = false;
-            Vector2D destination;
-            CBox     source;
-        } viewport;
-        bool rejected  = false;
-        bool newBuffer = false;
-
-        //
-        void reset() {
-            damage.clear();
-            bufferDamage.clear();
-            transform = WL_OUTPUT_TRANSFORM_NORMAL;
-            scale     = 1;
-            offset    = {};
-            size      = {};
-        }
-    } current, pending;
+    SSurfaceState                          current, pending;
 
     std::vector<SP<CWLCallbackResource>>   callbacks;
     WP<CWLSurfaceResource>                 self;
@@ -130,28 +104,20 @@ class CWLSurfaceResource {
     SP<CWLSurfaceResource>                 findFirstPreorder(std::function<bool(SP<CWLSurfaceResource>)> fn);
     CRegion                                accumulateCurrentBufferDamage();
     void                                   presentFeedback(timespec* when, PHLMONITOR pMonitor, bool discarded = false);
-    void                                   lockPendingState();
-    void                                   unlockPendingState();
+    void                                   commitPendingState(SSurfaceState& state);
 
     // returns a pair: found surface (null if not found) and surface local coords.
     // localCoords param is relative to 0,0 of this surface
     std::pair<SP<CWLSurfaceResource>, Vector2D> at(const Vector2D& localCoords, bool allowsInput = false);
 
   private:
-    SP<CWlSurface> resource;
-    wl_client*     pClient = nullptr;
-
-    // this is for cursor dumb copy. Due to our (and wayland's...) architecture,
-    // this stupid-ass hack is used
-    WP<IHLBuffer>          lastBuffer;
-
-    int                    stateLocks = 0;
+    SP<CWlSurface>         resource;
+    wl_client*             pClient = nullptr;
 
     void                   destroy();
     void                   releaseBuffers(bool onlyCurrent = true);
     void                   dropPendingBuffer();
     void                   dropCurrentBuffer();
-    void                   commitPendingState();
     void                   bfHelper(std::vector<SP<CWLSurfaceResource>> const& nodes, std::function<void(SP<CWLSurfaceResource>, const Vector2D&, void*)> fn, void* data);
     SP<CWLSurfaceResource> findFirstPreorderHelper(SP<CWLSurfaceResource> root, std::function<bool(SP<CWLSurfaceResource>)> fn);
     void                   updateCursorShm(CRegion damage = CBox{0, 0, INT16_MAX, INT16_MAX});
diff --git a/src/protocols/core/Shm.cpp b/src/protocols/core/Shm.cpp
index e0e6256049d..37a48fd31d2 100644
--- a/src/protocols/core/Shm.cpp
+++ b/src/protocols/core/Shm.cpp
@@ -37,6 +37,11 @@ CWLSHMBuffer::CWLSHMBuffer(SP<CWLSHMPoolResource> pool_, uint32_t id, int32_t of
         Debug::log(ERR, "Failed creating a shm texture: null texture id");
 }
 
+CWLSHMBuffer::~CWLSHMBuffer() {
+    if (resource)
+        resource->sendRelease();
+}
+
 Aquamarine::eBufferCapability CWLSHMBuffer::caps() {
     return Aquamarine::eBufferCapability::BUFFER_CAPABILITY_DATAPTR;
 }
diff --git a/src/protocols/core/Shm.hpp b/src/protocols/core/Shm.hpp
index fef821cb36e..fa5f29a404f 100644
--- a/src/protocols/core/Shm.hpp
+++ b/src/protocols/core/Shm.hpp
@@ -33,7 +33,7 @@ class CSHMPool {
 class CWLSHMBuffer : public IHLBuffer {
   public:
     CWLSHMBuffer(SP<CWLSHMPoolResource> pool, uint32_t id, int32_t offset, const Vector2D& size, int32_t stride, uint32_t fmt);
-    virtual ~CWLSHMBuffer() = default;
+    virtual ~CWLSHMBuffer();
 
     virtual Aquamarine::eBufferCapability          caps();
     virtual Aquamarine::eBufferType                type();
diff --git a/src/protocols/types/Buffer.cpp b/src/protocols/types/Buffer.cpp
index 9fb6294f9d0..2623435ba0c 100644
--- a/src/protocols/types/Buffer.cpp
+++ b/src/protocols/types/Buffer.cpp
@@ -18,8 +18,10 @@ void IHLBuffer::unlock() {
 
     ASSERT(nLocks >= 0);
 
-    if (nLocks == 0)
+    if (nLocks == 0) {
         sendRelease();
+        syncReleaser.reset();
+    }
 }
 
 bool IHLBuffer::locked() {
@@ -43,7 +45,7 @@ CHLBufferReference::CHLBufferReference(SP<IHLBuffer> buffer_, SP<CWLSurfaceResou
 }
 
 CHLBufferReference::~CHLBufferReference() {
-    if (buffer.expired())
+    if (!buffer)
         return;
 
     buffer->unlock();
diff --git a/src/protocols/types/Buffer.hpp b/src/protocols/types/Buffer.hpp
index bd02e2db4c4..79cc6ce6f14 100644
--- a/src/protocols/types/Buffer.hpp
+++ b/src/protocols/types/Buffer.hpp
@@ -3,6 +3,7 @@
 #include "../../defines.hpp"
 #include "../../render/Texture.hpp"
 #include "./WLBuffer.hpp"
+#include "protocols/DRMSyncobj.hpp"
 
 #include <aquamarine/buffer/Buffer.hpp>
 
@@ -26,6 +27,7 @@ class IHLBuffer : public Aquamarine::IBuffer {
     SP<CTexture>                          texture;
     bool                                  opaque = false;
     SP<CWLBufferResource>                 resource;
+    UP<CSyncReleaser>                     syncReleaser;
 
     struct {
         CHyprSignalListener backendRelease;
@@ -43,8 +45,9 @@ class CHLBufferReference {
     CHLBufferReference(SP<IHLBuffer> buffer, SP<CWLSurfaceResource> surface);
     ~CHLBufferReference();
 
-    WP<IHLBuffer>     buffer;
-    SP<CSyncReleaser> releaser;
+    WP<IHLBuffer>          buffer;
+    UP<CDRMSyncPointState> acquire;
+    UP<CDRMSyncPointState> release;
 
   private:
     WP<CWLSurfaceResource> surface;
diff --git a/src/protocols/types/DMABuffer.cpp b/src/protocols/types/DMABuffer.cpp
index 3f53225c2fb..2e39bf79f47 100644
--- a/src/protocols/types/DMABuffer.cpp
+++ b/src/protocols/types/DMABuffer.cpp
@@ -36,6 +36,9 @@ CDMABuffer::CDMABuffer(uint32_t id, wl_client* client, Aquamarine::SDMABUFAttrs
 }
 
 CDMABuffer::~CDMABuffer() {
+    if (resource)
+        resource->sendRelease();
+
     closeFDs();
 }
 
diff --git a/src/protocols/types/SurfaceState.hpp b/src/protocols/types/SurfaceState.hpp
new file mode 100644
index 00000000000..cd9be5caadd
--- /dev/null
+++ b/src/protocols/types/SurfaceState.hpp
@@ -0,0 +1,35 @@
+#pragma once
+
+#include "../../helpers/math/Math.hpp"
+#include "../WaylandProtocol.hpp"
+
+class CHLBufferReference;
+class CTexture;
+
+struct SSurfaceState {
+    CRegion                opaque, input = CBox{{}, {INT32_MAX, INT32_MAX}}, damage, bufferDamage = CBox{{}, {INT32_MAX, INT32_MAX}} /* initial damage */;
+    wl_output_transform    transform = WL_OUTPUT_TRANSFORM_NORMAL;
+    int                    scale     = 1;
+    SP<CHLBufferReference> buffer; // buffer ref will be released once the buffer is no longer locked. For checking if a buffer is attached to this state, check texture.
+    SP<CTexture>           texture;
+    Vector2D               offset;
+    Vector2D               size, bufferSize;
+    struct {
+        bool     hasDestination = false;
+        bool     hasSource      = false;
+        Vector2D destination;
+        CBox     source;
+    } viewport;
+    bool rejected  = false;
+    bool newBuffer = false;
+
+    //
+    void reset() {
+        damage.clear();
+        bufferDamage.clear();
+        transform = WL_OUTPUT_TRANSFORM_NORMAL;
+        scale     = 1;
+        offset    = {};
+        size      = {};
+    }
+};
diff --git a/src/protocols/types/WLBuffer.cpp b/src/protocols/types/WLBuffer.cpp
index 4bd115a2b85..f2b81c002c9 100644
--- a/src/protocols/types/WLBuffer.cpp
+++ b/src/protocols/types/WLBuffer.cpp
@@ -5,7 +5,7 @@
 #include "../../helpers/sync/SyncTimeline.hpp"
 #include <xf86drm.h>
 
-CWLBufferResource::CWLBufferResource(SP<CWlBuffer> resource_) : resource(resource_) {
+CWLBufferResource::CWLBufferResource(WP<CWlBuffer> resource_) : resource(resource_.lock()) {
     if UNLIKELY (!good())
         return;
 
@@ -40,7 +40,7 @@ SP<CWLBufferResource> CWLBufferResource::fromResource(wl_resource* res) {
     return data ? data->self.lock() : nullptr;
 }
 
-SP<CWLBufferResource> CWLBufferResource::create(SP<CWlBuffer> resource) {
+SP<CWLBufferResource> CWLBufferResource::create(WP<CWlBuffer> resource) {
     auto p  = SP<CWLBufferResource>(new CWLBufferResource(resource));
     p->self = p;
     return p;
diff --git a/src/protocols/types/WLBuffer.hpp b/src/protocols/types/WLBuffer.hpp
index 3894affc42c..c7e21d4cd18 100644
--- a/src/protocols/types/WLBuffer.hpp
+++ b/src/protocols/types/WLBuffer.hpp
@@ -11,7 +11,7 @@ class CWLSurfaceResource;
 
 class CWLBufferResource {
   public:
-    static SP<CWLBufferResource> create(SP<CWlBuffer> resource);
+    static SP<CWLBufferResource> create(WP<CWlBuffer> resource);
     static SP<CWLBufferResource> fromResource(wl_resource* res);
 
     bool                         good();
@@ -23,7 +23,7 @@ class CWLBufferResource {
     WP<CWLBufferResource>        self;
 
   private:
-    CWLBufferResource(SP<CWlBuffer> resource_);
+    CWLBufferResource(WP<CWlBuffer> resource_);
 
     SP<CWlBuffer> resource;
 
diff --git a/src/render/OpenGL.cpp b/src/render/OpenGL.cpp
index a8a38bac259..86e81efb67f 100644
--- a/src/render/OpenGL.cpp
+++ b/src/render/OpenGL.cpp
@@ -1292,16 +1292,16 @@ void CHyprOpenGLImpl::renderTexture(SP<CTexture> tex, const CBox& box, float alp
 }
 
 void CHyprOpenGLImpl::renderTextureWithDamage(SP<CTexture> tex, const CBox& box, const CRegion& damage, float alpha, int round, float roundingPower, bool discardActive,
-                                              bool allowCustomUV, SP<CSyncTimeline> waitTimeline, uint64_t waitPoint) {
+                                              bool allowCustomUV) {
     RASSERT(m_RenderData.pMonitor, "Tried to render texture without begin()!");
 
-    renderTextureInternalWithDamage(tex, box, alpha, damage, round, roundingPower, discardActive, false, allowCustomUV, true, waitTimeline, waitPoint);
+    renderTextureInternalWithDamage(tex, box, alpha, damage, round, roundingPower, discardActive, false, allowCustomUV, true);
 
     scissor(nullptr);
 }
 
 void CHyprOpenGLImpl::renderTextureInternalWithDamage(SP<CTexture> tex, const CBox& box, float alpha, const CRegion& damage, int round, float roundingPower, bool discardActive,
-                                                      bool noAA, bool allowCustomUV, bool allowDim, SP<CSyncTimeline> waitTimeline, uint64_t waitPoint) {
+                                                      bool noAA, bool allowCustomUV, bool allowDim) {
     RASSERT(m_RenderData.pMonitor, "Tried to render texture without begin()!");
     RASSERT((tex->m_iTexID > 0), "Attempted to draw nullptr texture!");
 
@@ -1324,15 +1324,8 @@ void CHyprOpenGLImpl::renderTextureInternalWithDamage(SP<CTexture> tex, const CB
     if (m_bEndFrame || TRANSFORMS_MATCH)
         TRANSFORM = wlTransformToHyprutils(invertTransform(m_RenderData.pMonitor->transform));
 
-    Mat3x3 matrix   = m_RenderData.monitorProjection.projectBox(newBox, TRANSFORM, newBox.rot);
-    Mat3x3 glMatrix = m_RenderData.projection.copy().multiply(matrix);
-
-    if (waitTimeline != nullptr) {
-        if (!waitForTimelinePoint(waitTimeline, waitPoint)) {
-            Debug::log(ERR, "renderTextureInternalWithDamage: failed to wait for explicit sync point {}", waitPoint);
-            return;
-        }
-    }
+    Mat3x3     matrix   = m_RenderData.monitorProjection.projectBox(newBox, TRANSFORM, newBox.rot);
+    Mat3x3     glMatrix = m_RenderData.projection.copy().multiply(matrix);
 
     CShader*   shader = nullptr;
 
@@ -2915,11 +2908,11 @@ std::vector<SDRMFormat> CHyprOpenGLImpl::getDRMFormats() {
     return drmFormats;
 }
 
-SP<CEGLSync> CHyprOpenGLImpl::createEGLSync(CFileDescriptor fenceFD) {
+SP<CEGLSync> CHyprOpenGLImpl::createEGLSync(int fenceFD) {
     std::vector<EGLint> attribs;
     CFileDescriptor     dupFd;
-    if (fenceFD.isValid()) {
-        dupFd = fenceFD.duplicate();
+    if (fenceFD >= 0) {
+        dupFd = CFileDescriptor{fcntl(fenceFD, F_DUPFD_CLOEXEC, 0)};
         if (!dupFd.isValid()) {
             Debug::log(ERR, "createEGLSync: dup failed");
             return nullptr;
@@ -2953,27 +2946,6 @@ SP<CEGLSync> CHyprOpenGLImpl::createEGLSync(CFileDescriptor fenceFD) {
     return eglsync;
 }
 
-bool CHyprOpenGLImpl::waitForTimelinePoint(SP<CSyncTimeline> timeline, uint64_t point) {
-    auto fd = timeline->exportAsSyncFileFD(point);
-    if (!fd.isValid()) {
-        Debug::log(ERR, "waitForTimelinePoint: failed to get a fd from explicit timeline");
-        return false;
-    }
-
-    auto sync = g_pHyprOpenGL->createEGLSync(std::move(fd));
-    if (!sync) {
-        Debug::log(ERR, "waitForTimelinePoint: failed to get an eglsync from explicit timeline");
-        return false;
-    }
-
-    if (!sync->wait()) {
-        Debug::log(ERR, "waitForTimelinePoint: failed to wait on an eglsync from explicit timeline");
-        return false;
-    }
-
-    return true;
-}
-
 void SRenderModifData::applyToBox(CBox& box) {
     if (!enabled)
         return;
@@ -3043,17 +3015,10 @@ CEGLSync::~CEGLSync() {
         Debug::log(ERR, "eglDestroySyncKHR failed");
 }
 
-CFileDescriptor& CEGLSync::fd() {
-    return m_iFd;
+CFileDescriptor&& CEGLSync::takeFD() {
+    return std::move(m_iFd);
 }
 
-bool CEGLSync::wait() {
-    if (sync == EGL_NO_SYNC_KHR)
-        return false;
-
-    if (g_pHyprOpenGL->m_sProc.eglWaitSyncKHR(g_pHyprOpenGL->m_pEglDisplay, sync, 0) != EGL_TRUE) {
-        Debug::log(ERR, "eglWaitSyncKHR failed");
-        return false;
-    }
-    return true;
+CFileDescriptor& CEGLSync::fd() {
+    return m_iFd;
 }
diff --git a/src/render/OpenGL.hpp b/src/render/OpenGL.hpp
index 110795918c8..3933bfc4368 100644
--- a/src/render/OpenGL.hpp
+++ b/src/render/OpenGL.hpp
@@ -148,10 +148,10 @@ class CEGLSync {
   public:
     ~CEGLSync();
 
-    EGLSyncKHR                      sync = nullptr;
+    EGLSyncKHR                       sync = nullptr;
 
-    Hyprutils::OS::CFileDescriptor& fd();
-    bool                            wait();
+    Hyprutils::OS::CFileDescriptor&& takeFD();
+    Hyprutils::OS::CFileDescriptor&  fd();
 
   private:
     CEGLSync() = default;
@@ -177,7 +177,7 @@ class CHyprOpenGLImpl {
     void renderRectWithDamage(const CBox&, const CHyprColor&, const CRegion& damage, int round = 0, float roundingPower = 2.0f);
     void renderTexture(SP<CTexture>, const CBox&, float a, int round = 0, float roundingPower = 2.0f, bool discardActive = false, bool allowCustomUV = false);
     void renderTextureWithDamage(SP<CTexture>, const CBox&, const CRegion& damage, float a, int round = 0, float roundingPower = 2.0f, bool discardActive = false,
-                                 bool allowCustomUV = false, SP<CSyncTimeline> waitTimeline = nullptr, uint64_t waitPoint = 0);
+                                 bool allowCustomUV = false);
     void renderTextureWithBlur(SP<CTexture>, const CBox&, float a, SP<CWLSurfaceResource> pSurface, int round = 0, float roundingPower = 2.0f, bool blockBlurOptimization = false,
                                float blurA = 1.f, float overallA = 1.f);
     void renderRoundedShadow(const CBox&, int round, float roundingPower, int range, const CHyprColor& color, float a = 1.0);
@@ -230,8 +230,7 @@ class CHyprOpenGLImpl {
     uint32_t                             getPreferredReadFormat(PHLMONITOR pMonitor);
     std::vector<SDRMFormat>              getDRMFormats();
     EGLImageKHR                          createEGLImage(const Aquamarine::SDMABUFAttrs& attrs);
-    SP<CEGLSync>                         createEGLSync(Hyprutils::OS::CFileDescriptor fenceFD);
-    bool                                 waitForTimelinePoint(SP<CSyncTimeline> timeline, uint64_t point);
+    SP<CEGLSync>                         createEGLSync(int fence = -1);
 
     SCurrentRenderData                   m_RenderData;
 
@@ -315,7 +314,7 @@ class CHyprOpenGLImpl {
     CFramebuffer* blurMainFramebufferWithDamage(float a, CRegion* damage);
 
     void renderTextureInternalWithDamage(SP<CTexture>, const CBox& box, float a, const CRegion& damage, int round = 0, float roundingPower = 2.0f, bool discardOpaque = false,
-                                         bool noAA = false, bool allowCustomUV = false, bool allowDim = false, SP<CSyncTimeline> = nullptr, uint64_t waitPoint = 0);
+                                         bool noAA = false, bool allowCustomUV = false, bool allowDim = false);
     void renderTexturePrimitive(SP<CTexture> tex, const CBox& box);
     void renderSplash(cairo_t* const, cairo_surface_t* const, double offset, const Vector2D& size);
 
diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp
index 88de1adab01..e7d3630aa95 100644
--- a/src/render/Renderer.cpp
+++ b/src/render/Renderer.cpp
@@ -1450,13 +1450,6 @@ static hdr_output_metadata       createHDRMetadata(SImageDescription settings, A
 bool CHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) {
     pMonitor->commitSeq++;
 
-    // apply timelines for explicit sync
-    // save inFD otherwise reset will reset it
-    CFileDescriptor inFD{pMonitor->output->state->state().explicitInFence};
-    pMonitor->output->state->resetExplicitFences();
-    if (inFD.isValid())
-        pMonitor->output->state->setExplicitInFence(inFD.get());
-
     static auto PPASS = CConfigValue<Hyprlang::INT>("render:cm_fs_passthrough");
     const bool  PHDR  = pMonitor->imageDescription.transferFunction == CM_TRANSFER_FUNCTION_ST2084_PQ;
 
@@ -1517,7 +1510,7 @@ bool CHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) {
 
     bool ok = pMonitor->state.commit();
     if (!ok) {
-        if (inFD.isValid()) {
+        if (pMonitor->inFence.isValid()) {
             Debug::log(TRACE, "Monitor state commit failed, retrying without a fence");
             pMonitor->output->state->resetExplicitFences();
             ok = pMonitor->state.commit();
@@ -1537,21 +1530,20 @@ bool CHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) {
         return ok;
 
     Debug::log(TRACE, "Explicit: {} presented", explicitPresented.size());
-    auto sync = g_pHyprOpenGL->createEGLSync({});
+    auto sync = g_pHyprOpenGL->createEGLSync(pMonitor->inFence.get());
 
     if (!sync)
         Debug::log(TRACE, "Explicit: can't add sync, EGLSync failed");
     else {
         for (auto const& e : explicitPresented) {
-            if (!e->current.buffer || !e->current.buffer->releaser)
+            if (!e->current.buffer || !e->current.buffer->buffer || !e->current.buffer->buffer->syncReleaser)
                 continue;
 
-            e->current.buffer->releaser->addReleaseSync(sync);
+            e->current.buffer->buffer->syncReleaser->addReleaseSync(sync);
         }
     }
 
     explicitPresented.clear();
-
     pMonitor->output->state->resetExplicitFences();
 
     return ok;
@@ -2269,7 +2261,7 @@ void CHyprRenderer::endRender() {
         auto explicitOptions = getExplicitSyncSettings(PMONITOR->output);
 
         if (PMONITOR->inTimeline && explicitOptions.explicitEnabled && explicitOptions.explicitKMSEnabled) {
-            auto sync = g_pHyprOpenGL->createEGLSync({});
+            auto sync = g_pHyprOpenGL->createEGLSync();
             if (!sync) {
                 Debug::log(ERR, "renderer: couldn't create an EGLSync for out in endRender");
                 return;
@@ -2281,13 +2273,13 @@ void CHyprRenderer::endRender() {
                 return;
             }
 
-            auto fd = PMONITOR->inTimeline->exportAsSyncFileFD(PMONITOR->commitSeq);
-            if (!fd.isValid()) {
+            PMONITOR->inFence = CFileDescriptor{PMONITOR->inTimeline->exportAsSyncFileFD(PMONITOR->commitSeq)};
+            if (!PMONITOR->inFence.isValid()) {
                 Debug::log(ERR, "renderer: couldn't export from sync timeline in endRender");
                 return;
             }
 
-            PMONITOR->output->state->setExplicitInFence(fd.take());
+            PMONITOR->output->state->setExplicitInFence(PMONITOR->inFence.get());
         } else {
             if (isNvidia() && *PNVIDIAANTIFLICKER)
                 glFinish();
diff --git a/src/render/pass/SurfacePassElement.cpp b/src/render/pass/SurfacePassElement.cpp
index 46c6d221499..813ab8ea4b7 100644
--- a/src/render/pass/SurfacePassElement.cpp
+++ b/src/render/pass/SurfacePassElement.cpp
@@ -51,14 +51,6 @@ void CSurfacePassElement::draw(const CRegion& damage) {
     if (!TEXTURE->m_iTexID)
         return;
 
-    // explicit sync: wait for the timeline, if any
-    if (data.surface->syncobj && data.surface->syncobj->current.acquireTimeline) {
-        if (!g_pHyprOpenGL->waitForTimelinePoint(data.surface->syncobj->current.acquireTimeline->timeline, data.surface->syncobj->current.acquirePoint)) {
-            Debug::log(ERR, "Renderer: failed to wait for explicit timeline");
-            return;
-        }
-    }
-
     const auto INTERACTIVERESIZEINPROGRESS = data.pWindow && g_pInputManager->currentlyDraggedWindow && g_pInputManager->dragMode == MBIND_RESIZE;
     TRACY_GPU_ZONE("RenderSurface");
 


Reviews:
user: login: vaxerski
body: huge if works
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-19T15:55:53Z
user: login: gulafaran
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-22T07:38:55Z
user: login: gulafaran
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-22T13:53:52Z
user: login: gulafaran
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-22T13:54:00Z
user: login: vaxerski
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-23T02:17:16Z
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-05T16:31:31Z
user: login: gulafaran
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T16:46:40Z
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-05T16:49:27Z
user: login: gulafaran
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T17:53:58Z
user: login: gulafaran
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T17:54:08Z
user: login: vaxerski
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T19:39:20Z
user: login: gulafaran
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T19:42:46Z
user: login: gulafaran
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T19:45:26Z
user: login: gulafaran
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T19:47:39Z
user: login: vaxerski
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T19:55:51Z
user: login: vaxerski
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T19:56:18Z
user: login: gulafaran
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T09:35:05Z
user: login: gulafaran
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T09:35:18Z
user: login: gulafaran
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T09:35:59Z
user: login: vaxerski
body: fine by me. Runs alright on my end, but I don't know about others.
state: DISMISSED
author_association: MEMBER
submitted_at: 2025-03-06T16:25:52Z
user: login: vaxerski
body: me pullin up to merge a banger mr where I did not write a single line

https://github.com/user-attachments/assets/b9fc3bb0-52a6-4e67-8cff-9ed49eaaeb67


state: DISMISSED
author_association: MEMBER
submitted_at: 2025-03-06T16:42:59Z
user: login: vaxerski
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-12T14:18:22Z
user: login: gulafaran
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T14:22:23Z
user: login: vaxerski
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-12T14:24:56Z
user: login: PlasmaPower
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T14:26:21Z
user: login: gulafaran
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T14:27:19Z
user: login: vaxerski
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-12T14:40:50Z
user: login: gulafaran
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T15:17:37Z
user: login: gulafaran
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T15:17:43Z
user: login: gulafaran
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T15:18:49Z
user: login: gulafaran
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T15:18:56Z
user: login: ikalco
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T18:06:28Z
user: login: vaxerski
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-12T18:30:12Z
user: login: PlasmaPower
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T19:07:00Z
user: login: gulafaran
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T08:39:16Z
user: login: gulafaran
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T08:43:35Z
user: login: UjinT34
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T09:04:23Z
user: login: gulafaran
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T09:07:56Z
user: login: ikalco
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T12:23:58Z
user: login: gulafaran
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T12:27:26Z
user: login: ikalco
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T12:33:02Z
user: login: ikalco
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T12:37:43Z
user: login: gulafaran
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T12:38:59Z
user: login: gulafaran
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T13:09:10Z
user: login: UjinT34
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T13:09:47Z
user: login: ikalco
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T13:09:55Z
user: login: gulafaran
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T14:59:34Z
user: login: vaxerski
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-13T16:09:31Z
user: login: PlasmaPower
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T23:55:25Z
user: login: vaxerski
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-14T01:22:40Z
user: login: gulafaran
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T06:50:13Z
user: login: vaxerski
body: lgtm, are there any issues left? Works fine for me
state: DISMISSED
author_association: MEMBER
submitted_at: 2025-03-14T13:05:51Z
user: login: vaxerski
body: lets merge this shit
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-14T13:57:46Z

Review comments:
diff_hunk: @@ -13,21 +14,26 @@ class CSyncTimeline;
 class CDRMSyncobjSurfaceResource {
   public:
     CDRMSyncobjSurfaceResource(SP<CWpLinuxDrmSyncobjSurfaceV1> resource_, SP<CWLSurfaceResource> surface_);
+    ~CDRMSyncobjSurfaceResource();
 
     bool                   good();
 
     WP<CWLSurfaceResource> surface;
-    struct {
-        WP<CDRMSyncobjTimelineResource> acquireTimeline, releaseTimeline;
-        uint64_t                        acquirePoint = 0, releasePoint = 0;
-    } current, pending;
+    struct TimeLineState {
path: src/protocols/DRMSyncobj.hpp
user: login: vaxerski
body: STimelineState
created_at: 2025-02-19T15:55:11Z
author_association: MEMBER
diff_hunk: @@ -412,16 +411,17 @@ CRegion CWLSurfaceResource::accumulateCurrentBufferDamage() {
 }
 
 void CWLSurfaceResource::lockPendingState() {
-    stateLocks++;
+    stateLocked = true;
path: src/protocols/core/Compositor.cpp
user: login: vaxerski
body: why remove the proper lock system?
created_at: 2025-02-19T15:55:38Z
author_association: MEMBER
diff_hunk: @@ -433,8 +433,9 @@ void CWLSurfaceResource::commitPendingState() {
 
     events.roleCommit.emit();
 
-    if (syncobj && syncobj->current.releaseTimeline && syncobj->current.releaseTimeline->timeline && current.buffer && current.buffer->buffer)
-        current.buffer->releaser = makeShared<CSyncReleaser>(syncobj->current.releaseTimeline->timeline, syncobj->current.releasePoint);
+    if (syncobj && syncobj->release.resource && syncobj->release.resource->timeline && current.buffer && current.buffer->buffer) {
path: src/protocols/core/Compositor.cpp
user: login: vaxerski
body: no {}
created_at: 2025-02-19T15:55:43Z
author_association: MEMBER
diff_hunk: @@ -412,16 +411,17 @@ CRegion CWLSurfaceResource::accumulateCurrentBufferDamage() {
 }
 
 void CWLSurfaceResource::lockPendingState() {
-    stateLocks++;
+    stateLocked = true;
path: src/protocols/core/Compositor.cpp
user: login: gulafaran
body: as i saw it the only place where the locking/unlocking was used is from drmsyncobj, and it locks until an acquire point is signaled, but with the "ref" counting it became an rather annoyance to keep track of unrefing and refing when either a new buffer appears because a move/resize of window or similiar so a new acquire point arrives without the first signaled, and if you suddenly do some other shenanigans making the same race condition. when all we want to know is, is it locked or not, so the ref count suddenly isnt +2 but the waiter is gone and new arrives and wont unlock and render and poof trouble. its doable but just felt easier to change it to a locked or not, true/false
created_at: 2025-02-22T07:38:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -433,8 +433,9 @@ void CWLSurfaceResource::commitPendingState() {
 
     events.roleCommit.emit();
 
-    if (syncobj && syncobj->current.releaseTimeline && syncobj->current.releaseTimeline->timeline && current.buffer && current.buffer->buffer)
-        current.buffer->releaser = makeShared<CSyncReleaser>(syncobj->current.releaseTimeline->timeline, syncobj->current.releasePoint);
+    if (syncobj && syncobj->release.resource && syncobj->release.resource->timeline && current.buffer && current.buffer->buffer) {
path: src/protocols/core/Compositor.cpp
user: login: gulafaran
body: changed
created_at: 2025-02-22T13:53:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -13,21 +14,26 @@ class CSyncTimeline;
 class CDRMSyncobjSurfaceResource {
   public:
     CDRMSyncobjSurfaceResource(SP<CWpLinuxDrmSyncobjSurfaceV1> resource_, SP<CWLSurfaceResource> surface_);
+    ~CDRMSyncobjSurfaceResource();
 
     bool                   good();
 
     WP<CWLSurfaceResource> surface;
-    struct {
-        WP<CDRMSyncobjTimelineResource> acquireTimeline, releaseTimeline;
-        uint64_t                        acquirePoint = 0, releasePoint = 0;
-    } current, pending;
+    struct TimeLineState {
path: src/protocols/DRMSyncobj.hpp
user: login: gulafaran
body: changed
created_at: 2025-02-22T13:54:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -412,16 +411,17 @@ CRegion CWLSurfaceResource::accumulateCurrentBufferDamage() {
 }
 
 void CWLSurfaceResource::lockPendingState() {
-    stateLocks++;
+    stateLocked = true;
path: src/protocols/core/Compositor.cpp
user: login: vaxerski
body: mhm, ok
created_at: 2025-02-23T02:17:16Z
author_association: MEMBER
diff_hunk: @@ -1,25 +1,66 @@
 #include "SyncReleaser.hpp"
 #include "SyncTimeline.hpp"
 #include "../../render/OpenGL.hpp"
+#include <sys/ioctl.h>
 
-CSyncReleaser::CSyncReleaser(WP<CSyncTimeline> timeline_, uint64_t point_) : timeline(timeline_), point(point_) {
+#if defined(Q_OS_LINUX)
+#include <linux/sync_file.h>
+#else
+struct sync_merge_data {
+    char  name[32];
+    __s32 fd2;
+    __s32 fence;
+    __u32 flags;
+    __u32 pad;
+};
+#define SYNC_IOC_MAGIC '>'
+#define SYNC_IOC_MERGE _IOWR(SYNC_IOC_MAGIC, 3, struct sync_merge_data)
+#endif
+
+using namespace Hyprutils::OS;
+
+CSyncReleaser::CSyncReleaser(SP<CSyncTimeline> timeline_, uint64_t point_) : timeline(timeline_), point(point_) {
     ;
 }
 
 CSyncReleaser::~CSyncReleaser() {
-    if (timeline.expired())
+    if (!timeline) {
+        Debug::log(ERR, "CSyncReleaser destructing without a timeline");
         return;
+    }
 
-    if (sync)
-        timeline->importFromSyncFileFD(point, sync->fd());
+    if (m_fd.isValid())
+        timeline->importFromSyncFileFD(point, m_fd);
     else
         timeline->signal(point);
 }
 
-void CSyncReleaser::addReleaseSync(SP<CEGLSync> sync_) {
-    sync = sync_;
+CFileDescriptor CSyncReleaser::mergeSyncFds(const CFileDescriptor& fd1, const CFileDescriptor& fd2) {
+    struct sync_merge_data data{
+        .name  = "merged release fence",
+        .fd2   = fd2.get(),
+        .fence = -1,
+    };
+    int err = -1;
+    do {
+        err = ioctl(fd1.get(), SYNC_IOC_MERGE, &data);
+    } while (err == -1 && (errno == EINTR || errno == EAGAIN));
+    if (err < 0) {
+        return CFileDescriptor{};
+    } else {
+        return CFileDescriptor(data.fence);
+    }
path: src/helpers/sync/SyncReleaser.cpp
body: ```suggestion
    if (err < 0)
        return CFileDescriptor{};
    else
        return CFileDescriptor(data.fence);
```
created_at: 2025-03-05T16:21:09Z
author_association: NONE
diff_hunk: @@ -34,77 +104,109 @@ CDRMSyncobjSurfaceResource::CDRMSyncobjSurfaceResource(SP<CWpLinuxDrmSyncobjSurf
             return;
         }
 
-        auto timeline           = CDRMSyncobjTimelineResource::fromResource(timeline_);
-        pending.releaseTimeline = timeline;
-        pending.releasePoint    = ((uint64_t)hi << 32) | (uint64_t)lo;
+        auto timeline  = CDRMSyncobjTimelineResource::fromResource(timeline_);
+        pendingRelease = {timeline, ((uint64_t)hi << 32) | (uint64_t)lo, false};
     });
 
-    listeners.surfacePrecommit = surface->events.precommit.registerListener([this](std::any d) {
-        if ((pending.acquireTimeline || pending.releaseTimeline) && !surface->pending.texture) {
-            resource->error(WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_BUFFER, "Missing buffer");
-            surface->pending.rejected = true;
-            return;
-        }
+    listeners.surfaceBufferAttach = surface->events.bufferAttach.registerListener([this](std::any d) {
+        if (!surface->pending.buffer && surface->pending.newBuffer && !surface->pending.texture)
+            return; // null buffer attached.
 
-        if (!surface->pending.newBuffer)
-            return; // this commit does not change the state here
+        if (!pendingAcquire.expired()) {
+            surface->pending.buffer->acquire = makeUnique<CDRMSyncPointState>(std::move(pendingAcquire));
+            pendingAcquire                   = {};
+        }
 
-        if (!!pending.acquireTimeline != !!pending.releaseTimeline) {
-            resource->error(pending.acquireTimeline ? WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_RELEASE_POINT : WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_ACQUIRE_POINT,
-                            "Missing timeline");
-            surface->pending.rejected = true;
-            return;
+        if (!pendingRelease.expired()) {
+            surface->pending.buffer->release      = makeUnique<CDRMSyncPointState>(std::move(pendingRelease));
+            surface->pending.buffer->syncReleaser = surface->pending.buffer->release->createSyncRelease();
+            pendingRelease                        = {};
         }
+    });
 
-        if (!pending.acquireTimeline)
-            return;
+    listeners.surfacePrecommit = surface->events.precommit.registerListener([this](std::any d) {
+        if (!surface->pending.buffer && surface->pending.newBuffer && !surface->pending.texture) {
+            removeAllWaiters();
+            surface->commitPendingState(surface->pending);
+            return; // null buffer attached.
+        }
 
-        if (pending.acquireTimeline && pending.releaseTimeline && pending.acquireTimeline == pending.releaseTimeline) {
-            if (pending.acquirePoint >= pending.releasePoint) {
-                resource->error(WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_CONFLICTING_POINTS, "Acquire and release points are on the same timeline, and acquire >= release");
-                surface->pending.rejected = true;
-                return;
-            }
+        if (!surface->pending.buffer && !surface->pending.newBuffer && surface->current.buffer) {
+            surface->current.bufferDamage.clear();
+            surface->current.damage.clear();
+            surface->commitPendingState(surface->current);
+            return; // no new buffer, but we still have current around and a commit happend, commit current again.
         }
 
-        // wait for the acquire timeline to materialize
-        auto materialized = pending.acquireTimeline->timeline->check(pending.acquirePoint, DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE);
-        if (!materialized.has_value()) {
-            LOGM(ERR, "Failed to check the acquire timeline");
-            resource->noMemory();
+        if (!surface->pending.buffer && !surface->pending.newBuffer && !surface->current.buffer) {
+            surface->commitPendingState(surface->pending); // no pending buffer, no current buffer. probably first commit
             return;
         }
 
-        if (materialized.value())
-            return;
+        if (protocolError()) {
+            return; // bad client.. return.
+        }
path: src/protocols/DRMSyncobj.cpp
body: ```suggestion
        if (protocolError())
            return; // bad client.. return.
```
created_at: 2025-03-05T16:21:52Z
author_association: NONE
diff_hunk: @@ -1315,10 +1315,11 @@ bool CMonitor::attemptDirectScanout() {
     auto explicitOptions = g_pHyprRenderer->getExplicitSyncSettings(output);
 
     // wait for the explicit fence if present, and if kms explicit is allowed
-    bool DOEXPLICIT = PSURFACE->syncobj && PSURFACE->syncobj->current.acquireTimeline && PSURFACE->syncobj->current.acquireTimeline->timeline && explicitOptions.explicitKMSEnabled;
+    bool DOEXPLICIT =
+        PSURFACE->syncobj && PSURFACE->current.buffer && PSURFACE->current.buffer->acquire && !PSURFACE->current.buffer->acquire->expired() && explicitOptions.explicitKMSEnabled;
path: src/helpers/Monitor.cpp
body: current.buffer could be null
created_at: 2025-03-05T16:28:37Z
author_association: NONE
diff_hunk: @@ -1315,10 +1315,11 @@ bool CMonitor::attemptDirectScanout() {
     auto explicitOptions = g_pHyprRenderer->getExplicitSyncSettings(output);
 
     // wait for the explicit fence if present, and if kms explicit is allowed
-    bool DOEXPLICIT = PSURFACE->syncobj && PSURFACE->syncobj->current.acquireTimeline && PSURFACE->syncobj->current.acquireTimeline->timeline && explicitOptions.explicitKMSEnabled;
+    bool DOEXPLICIT =
+        PSURFACE->syncobj && PSURFACE->current.buffer && PSURFACE->current.buffer->acquire && !PSURFACE->current.buffer->acquire->expired() && explicitOptions.explicitKMSEnabled;
path: src/helpers/Monitor.cpp
user: login: gulafaran
body: it already checks for that? `PSURFACE->syncobj && PSURFACE->current.buffer`
created_at: 2025-03-05T16:46:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1315,10 +1315,11 @@ bool CMonitor::attemptDirectScanout() {
     auto explicitOptions = g_pHyprRenderer->getExplicitSyncSettings(output);
 
     // wait for the explicit fence if present, and if kms explicit is allowed
-    bool DOEXPLICIT = PSURFACE->syncobj && PSURFACE->syncobj->current.acquireTimeline && PSURFACE->syncobj->current.acquireTimeline->timeline && explicitOptions.explicitKMSEnabled;
+    bool DOEXPLICIT =
+        PSURFACE->syncobj && PSURFACE->current.buffer && PSURFACE->current.buffer->acquire && !PSURFACE->current.buffer->acquire->expired() && explicitOptions.explicitKMSEnabled;
path: src/helpers/Monitor.cpp
body: huh i misread it xd
created_at: 2025-03-05T16:49:27Z
author_association: NONE
diff_hunk: @@ -34,77 +104,109 @@ CDRMSyncobjSurfaceResource::CDRMSyncobjSurfaceResource(SP<CWpLinuxDrmSyncobjSurf
             return;
         }
 
-        auto timeline           = CDRMSyncobjTimelineResource::fromResource(timeline_);
-        pending.releaseTimeline = timeline;
-        pending.releasePoint    = ((uint64_t)hi << 32) | (uint64_t)lo;
+        auto timeline  = CDRMSyncobjTimelineResource::fromResource(timeline_);
+        pendingRelease = {timeline, ((uint64_t)hi << 32) | (uint64_t)lo, false};
     });
 
-    listeners.surfacePrecommit = surface->events.precommit.registerListener([this](std::any d) {
-        if ((pending.acquireTimeline || pending.releaseTimeline) && !surface->pending.texture) {
-            resource->error(WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_BUFFER, "Missing buffer");
-            surface->pending.rejected = true;
-            return;
-        }
+    listeners.surfaceBufferAttach = surface->events.bufferAttach.registerListener([this](std::any d) {
+        if (!surface->pending.buffer && surface->pending.newBuffer && !surface->pending.texture)
+            return; // null buffer attached.
 
-        if (!surface->pending.newBuffer)
-            return; // this commit does not change the state here
+        if (!pendingAcquire.expired()) {
+            surface->pending.buffer->acquire = makeUnique<CDRMSyncPointState>(std::move(pendingAcquire));
+            pendingAcquire                   = {};
+        }
 
-        if (!!pending.acquireTimeline != !!pending.releaseTimeline) {
-            resource->error(pending.acquireTimeline ? WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_RELEASE_POINT : WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_ACQUIRE_POINT,
-                            "Missing timeline");
-            surface->pending.rejected = true;
-            return;
+        if (!pendingRelease.expired()) {
+            surface->pending.buffer->release      = makeUnique<CDRMSyncPointState>(std::move(pendingRelease));
+            surface->pending.buffer->syncReleaser = surface->pending.buffer->release->createSyncRelease();
+            pendingRelease                        = {};
         }
+    });
 
-        if (!pending.acquireTimeline)
-            return;
+    listeners.surfacePrecommit = surface->events.precommit.registerListener([this](std::any d) {
+        if (!surface->pending.buffer && surface->pending.newBuffer && !surface->pending.texture) {
+            removeAllWaiters();
+            surface->commitPendingState(surface->pending);
+            return; // null buffer attached.
+        }
 
-        if (pending.acquireTimeline && pending.releaseTimeline && pending.acquireTimeline == pending.releaseTimeline) {
-            if (pending.acquirePoint >= pending.releasePoint) {
-                resource->error(WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_CONFLICTING_POINTS, "Acquire and release points are on the same timeline, and acquire >= release");
-                surface->pending.rejected = true;
-                return;
-            }
+        if (!surface->pending.buffer && !surface->pending.newBuffer && surface->current.buffer) {
+            surface->current.bufferDamage.clear();
+            surface->current.damage.clear();
+            surface->commitPendingState(surface->current);
+            return; // no new buffer, but we still have current around and a commit happend, commit current again.
         }
 
-        // wait for the acquire timeline to materialize
-        auto materialized = pending.acquireTimeline->timeline->check(pending.acquirePoint, DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE);
-        if (!materialized.has_value()) {
-            LOGM(ERR, "Failed to check the acquire timeline");
-            resource->noMemory();
+        if (!surface->pending.buffer && !surface->pending.newBuffer && !surface->current.buffer) {
+            surface->commitPendingState(surface->pending); // no pending buffer, no current buffer. probably first commit
             return;
         }
 
-        if (materialized.value())
-            return;
+        if (protocolError()) {
+            return; // bad client.. return.
+        }
path: src/protocols/DRMSyncobj.cpp
user: login: gulafaran
body: thanks, changed.
created_at: 2025-03-05T17:53:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,25 +1,66 @@
 #include "SyncReleaser.hpp"
 #include "SyncTimeline.hpp"
 #include "../../render/OpenGL.hpp"
+#include <sys/ioctl.h>
 
-CSyncReleaser::CSyncReleaser(WP<CSyncTimeline> timeline_, uint64_t point_) : timeline(timeline_), point(point_) {
+#if defined(Q_OS_LINUX)
+#include <linux/sync_file.h>
+#else
+struct sync_merge_data {
+    char  name[32];
+    __s32 fd2;
+    __s32 fence;
+    __u32 flags;
+    __u32 pad;
+};
+#define SYNC_IOC_MAGIC '>'
+#define SYNC_IOC_MERGE _IOWR(SYNC_IOC_MAGIC, 3, struct sync_merge_data)
+#endif
+
+using namespace Hyprutils::OS;
+
+CSyncReleaser::CSyncReleaser(SP<CSyncTimeline> timeline_, uint64_t point_) : timeline(timeline_), point(point_) {
     ;
 }
 
 CSyncReleaser::~CSyncReleaser() {
-    if (timeline.expired())
+    if (!timeline) {
+        Debug::log(ERR, "CSyncReleaser destructing without a timeline");
         return;
+    }
 
-    if (sync)
-        timeline->importFromSyncFileFD(point, sync->fd());
+    if (m_fd.isValid())
+        timeline->importFromSyncFileFD(point, m_fd);
     else
         timeline->signal(point);
 }
 
-void CSyncReleaser::addReleaseSync(SP<CEGLSync> sync_) {
-    sync = sync_;
+CFileDescriptor CSyncReleaser::mergeSyncFds(const CFileDescriptor& fd1, const CFileDescriptor& fd2) {
+    struct sync_merge_data data{
+        .name  = "merged release fence",
+        .fd2   = fd2.get(),
+        .fence = -1,
+    };
+    int err = -1;
+    do {
+        err = ioctl(fd1.get(), SYNC_IOC_MERGE, &data);
+    } while (err == -1 && (errno == EINTR || errno == EAGAIN));
+    if (err < 0) {
+        return CFileDescriptor{};
+    } else {
+        return CFileDescriptor(data.fence);
+    }
path: src/helpers/sync/SyncReleaser.cpp
user: login: gulafaran
body: thanks, changed.
created_at: 2025-03-05T17:54:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,25 +1,65 @@
 #include "SyncReleaser.hpp"
 #include "SyncTimeline.hpp"
 #include "../../render/OpenGL.hpp"
+#include <sys/ioctl.h>
 
-CSyncReleaser::CSyncReleaser(WP<CSyncTimeline> timeline_, uint64_t point_) : timeline(timeline_), point(point_) {
+#if defined(Q_OS_LINUX)
path: src/helpers/sync/SyncReleaser.cpp
user: login: vaxerski
body: buddy this aint qt
created_at: 2025-03-05T19:34:44Z
author_association: MEMBER
diff_hunk: @@ -113,6 +113,7 @@ CLinuxDMABuffer::CLinuxDMABuffer(uint32_t id, wl_client* client, Aquamarine::SDM
 CLinuxDMABuffer::~CLinuxDMABuffer() {
     buffer.reset();
     listeners.bufferResourceDestroy.reset();
+    PROTO::linuxDma->destroyResource(this);
path: src/protocols/LinuxDMABUF.cpp
user: login: vaxerski
body: we shouldnt do this. The resource should only be destroyed once the client asks for it.
created_at: 2025-03-05T19:36:34Z
author_association: MEMBER
diff_hunk: @@ -191,6 +192,10 @@ CLinuxDMABUFParamsResource::CLinuxDMABUFParamsResource(SP<CZwpLinuxBufferParamsV
     });
 }
 
+CLinuxDMABUFParamsResource::~CLinuxDMABUFParamsResource() {
+    PROTO::linuxDma->destroyResource(this);
path: src/protocols/LinuxDMABUF.cpp
user: login: vaxerski
body: same here and in other places
created_at: 2025-03-05T19:36:44Z
author_association: MEMBER
diff_hunk: @@ -428,30 +428,16 @@ CRegion CWLSurfaceResource::accumulateCurrentBufferDamage() {
     return surfaceDamage.scale(current.scale).transform(wlTransformToHyprutils(invertTransform(current.transform)), trc.x, trc.y).add(current.bufferDamage);
 }
 
-void CWLSurfaceResource::lockPendingState() {
-    stateLocks++;
-}
-
-void CWLSurfaceResource::unlockPendingState() {
-    stateLocks--;
-    if (stateLocks <= 0)
-        commitPendingState();
-}
+void CWLSurfaceResource::commitPendingState(SSurfaceState& state) {
path: src/protocols/core/Compositor.cpp
user: login: vaxerski
body: is this ever called with anything that isnt pending? Why the param
created_at: 2025-03-05T19:37:56Z
author_association: MEMBER
diff_hunk: @@ -53,6 +53,34 @@ class CWLRegionResource {
     SP<CWlRegion> resource;
 };
 
+struct SSurfaceState {
path: src/protocols/core/Compositor.hpp
user: login: vaxerski
body: why extract this struct from the surface resource?
created_at: 2025-03-05T19:38:38Z
author_association: MEMBER
diff_hunk: @@ -428,30 +428,16 @@ CRegion CWLSurfaceResource::accumulateCurrentBufferDamage() {
     return surfaceDamage.scale(current.scale).transform(wlTransformToHyprutils(invertTransform(current.transform)), trc.x, trc.y).add(current.bufferDamage);
 }
 
-void CWLSurfaceResource::lockPendingState() {
-    stateLocks++;
-}
-
-void CWLSurfaceResource::unlockPendingState() {
-    stateLocks--;
-    if (stateLocks <= 0)
-        commitPendingState();
-}
+void CWLSurfaceResource::commitPendingState(SSurfaceState& state) {
path: src/protocols/core/Compositor.cpp
user: login: gulafaran
body: yeah in drmsyncobj we now store a copy of the pending state in a container and add a waiter that waits for the eventfd to signal. and meanwhile doing so a new pending might arrived. so pretty much current, pending, ready. and inside drmsyncobj i send in the waiter the ready state. nonsyncobj just straight up uses pending, and if same buffer commits happends in drmsyncobj it reuses current state.
created_at: 2025-03-05T19:42:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,25 +1,65 @@
 #include "SyncReleaser.hpp"
 #include "SyncTimeline.hpp"
 #include "../../render/OpenGL.hpp"
+#include <sys/ioctl.h>
 
-CSyncReleaser::CSyncReleaser(WP<CSyncTimeline> timeline_, uint64_t point_) : timeline(timeline_), point(point_) {
+#if defined(Q_OS_LINUX)
path: src/helpers/sync/SyncReleaser.cpp
user: login: gulafaran
body: true that, ;)
created_at: 2025-03-05T19:45:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -53,6 +53,34 @@ class CWLRegionResource {
     SP<CWlRegion> resource;
 };
 
+struct SSurfaceState {
path: src/protocols/core/Compositor.hpp
user: login: gulafaran
body: i wanted the states in drmsyncobj protocol to store ready/waiting states in a container/queue
created_at: 2025-03-05T19:47:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -428,30 +428,16 @@ CRegion CWLSurfaceResource::accumulateCurrentBufferDamage() {
     return surfaceDamage.scale(current.scale).transform(wlTransformToHyprutils(invertTransform(current.transform)), trc.x, trc.y).add(current.bufferDamage);
 }
 
-void CWLSurfaceResource::lockPendingState() {
-    stateLocks++;
-}
-
-void CWLSurfaceResource::unlockPendingState() {
-    stateLocks--;
-    if (stateLocks <= 0)
-        commitPendingState();
-}
+void CWLSurfaceResource::commitPendingState(SSurfaceState& state) {
path: src/protocols/core/Compositor.cpp
user: login: vaxerski
body: gotcha
created_at: 2025-03-05T19:55:51Z
author_association: MEMBER
diff_hunk: @@ -53,6 +53,34 @@ class CWLRegionResource {
     SP<CWlRegion> resource;
 };
 
+struct SSurfaceState {
path: src/protocols/core/Compositor.hpp
user: login: vaxerski
body: move it to types/
created_at: 2025-03-05T19:56:18Z
author_association: MEMBER
diff_hunk: @@ -1,25 +1,65 @@
 #include "SyncReleaser.hpp"
 #include "SyncTimeline.hpp"
 #include "../../render/OpenGL.hpp"
+#include <sys/ioctl.h>
 
-CSyncReleaser::CSyncReleaser(WP<CSyncTimeline> timeline_, uint64_t point_) : timeline(timeline_), point(point_) {
+#if defined(Q_OS_LINUX)
path: src/helpers/sync/SyncReleaser.cpp
user: login: gulafaran
body: changed.
created_at: 2025-03-06T09:35:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -113,6 +113,7 @@ CLinuxDMABuffer::CLinuxDMABuffer(uint32_t id, wl_client* client, Aquamarine::SDM
 CLinuxDMABuffer::~CLinuxDMABuffer() {
     buffer.reset();
     listeners.bufferResourceDestroy.reset();
+    PROTO::linuxDma->destroyResource(this);
path: src/protocols/LinuxDMABUF.cpp
user: login: gulafaran
body: yeah fair enough, changed.
created_at: 2025-03-06T09:35:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -53,6 +53,34 @@ class CWLRegionResource {
     SP<CWlRegion> resource;
 };
 
+struct SSurfaceState {
path: src/protocols/core/Compositor.hpp
user: login: gulafaran
body: moved it to types/SurfaceState.hpp , i assume this is inline of how you visioned it.
created_at: 2025-03-06T09:35:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -15,17 +16,19 @@ class CEGLSync;
 
 class CSyncReleaser {
   public:
-    CSyncReleaser(WP<CSyncTimeline> timeline_, uint64_t point_);
+    CSyncReleaser(SP<CSyncTimeline> timeline_, uint64_t point_);
     ~CSyncReleaser();
 
     // drops the releaser, will never signal anymore
     void drop();
 
     // wait for this gpu job to finish before releasing
-    void addReleaseSync(SP<CEGLSync> sync);
+    Hyprutils::OS::CFileDescriptor mergeSyncFds(const Hyprutils::OS::CFileDescriptor& fd1, const Hyprutils::OS::CFileDescriptor& fd2);
+    void                           addReleaseSync(SP<CEGLSync> sync);
 
   private:
-    WP<CSyncTimeline> timeline;
-    uint64_t          point = 0;
-    SP<CEGLSync>      sync;
+    SP<CSyncTimeline>              timeline;
path: src/helpers/sync/SyncReleaser.hpp
user: login: vaxerski
body: why do we mix m_ and no m_?

Just add m_ to all as we now do
created_at: 2025-03-12T14:14:33Z
author_association: MEMBER
diff_hunk: @@ -34,77 +104,102 @@ CDRMSyncobjSurfaceResource::CDRMSyncobjSurfaceResource(SP<CWpLinuxDrmSyncobjSurf
             return;
         }
 
-        auto timeline           = CDRMSyncobjTimelineResource::fromResource(timeline_);
-        pending.releaseTimeline = timeline;
-        pending.releasePoint    = ((uint64_t)hi << 32) | (uint64_t)lo;
+        auto timeline  = CDRMSyncobjTimelineResource::fromResource(timeline_);
+        pendingRelease = {timeline, ((uint64_t)hi << 32) | (uint64_t)lo, false};
     });
 
     listeners.surfacePrecommit = surface->events.precommit.registerListener([this](std::any d) {
-        if ((pending.acquireTimeline || pending.releaseTimeline) && !surface->pending.texture) {
-            resource->error(WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_BUFFER, "Missing buffer");
-            surface->pending.rejected = true;
-            return;
+        if (!surface->pending.buffer && surface->pending.newBuffer && !surface->pending.texture) {
+            removeAllWaiters();
+            surface->commitPendingState(surface->pending);
+            return; // null buffer attached.
         }
 
-        if (!surface->pending.newBuffer)
-            return; // this commit does not change the state here
-
-        if (!!pending.acquireTimeline != !!pending.releaseTimeline) {
-            resource->error(pending.acquireTimeline ? WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_RELEASE_POINT : WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_ACQUIRE_POINT,
-                            "Missing timeline");
-            surface->pending.rejected = true;
-            return;
+        if (!surface->pending.buffer && !surface->pending.newBuffer && surface->current.buffer) {
+            surface->current.bufferDamage.clear();
+            surface->current.damage.clear();
+            surface->commitPendingState(surface->current);
+            return; // no new buffer, but we still have current around and a commit happend, commit current again.
         }
 
-        if (!pending.acquireTimeline)
+        if (!surface->pending.buffer && !surface->pending.newBuffer && !surface->current.buffer) {
+            surface->commitPendingState(surface->pending); // no pending buffer, no current buffer. probably first commit
             return;
+        }
 
-        if (pending.acquireTimeline && pending.releaseTimeline && pending.acquireTimeline == pending.releaseTimeline) {
-            if (pending.acquirePoint >= pending.releasePoint) {
-                resource->error(WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_CONFLICTING_POINTS, "Acquire and release points are on the same timeline, and acquire >= release");
-                surface->pending.rejected = true;
-                return;
-            }
+        if (!pendingAcquire.expired()) {
+            surface->pending.buffer->acquire = makeUnique<CDRMSyncPointState>(std::move(pendingAcquire));
+            pendingAcquire                   = {};
         }
 
-        // wait for the acquire timeline to materialize
-        auto materialized = pending.acquireTimeline->timeline->check(pending.acquirePoint, DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE);
-        if (!materialized.has_value()) {
-            LOGM(ERR, "Failed to check the acquire timeline");
-            resource->noMemory();
-            return;
+        if (!pendingRelease.expired()) {
+            surface->pending.buffer->release = makeUnique<CDRMSyncPointState>(std::move(pendingRelease));
+            pendingRelease                   = {};
         }
 
-        if (materialized.value())
-            return;
+        if (protocolError())
+            return; // bad client.. return.
path: src/protocols/DRMSyncobj.cpp
user: login: vaxerski
body: ```
// this quacks
quack();
```

created_at: 2025-03-12T14:15:34Z
author_association: MEMBER
diff_hunk: @@ -2,64 +2,95 @@
 
 #include <vector>
 #include "WaylandProtocol.hpp"
+#include "helpers/sync/SyncReleaser.hpp"
 #include "linux-drm-syncobj-v1.hpp"
 #include "../helpers/signal/Signal.hpp"
+#include "types/SurfaceState.hpp"
 #include <hyprutils/os/FileDescriptor.hpp>
+#include <list>
 
 class CWLSurfaceResource;
 class CDRMSyncobjTimelineResource;
 class CSyncTimeline;
+struct SSurfaceState;
 
-class CDRMSyncobjSurfaceResource {
+class CDRMSyncPointState {
   public:
-    CDRMSyncobjSurfaceResource(SP<CWpLinuxDrmSyncobjSurfaceV1> resource_, SP<CWLSurfaceResource> surface_);
+    CDRMSyncPointState() = default;
+    CDRMSyncPointState(WP<CDRMSyncobjTimelineResource> resource_, uint64_t point_, bool acquirePoint);
+    ~CDRMSyncPointState() = default;
+
+    const uint64_t&                                  point();
+    WP<CDRMSyncobjTimelineResource>                  resource();
+    WP<CSyncTimeline>                                timeline();
+    bool                                             expired();
+    Hyprutils::Memory::CUniquePointer<CSyncReleaser> createSyncRelease();
+    bool                                             addWaiter(const std::function<void()>& waiter);
+    bool                                             comitted();
+    Hyprutils::OS::CFileDescriptor                   exportAsFD();
+    void                                             signal();
 
-    bool                   good();
+  private:
+    WP<CDRMSyncobjTimelineResource> m_resource         = {};
+    uint64_t                        m_point            = 0;
+    WP<CSyncTimeline>               m_timeline         = {};
+    bool                            m_acquirePoint     = false;
+    bool                            m_acquireCommitted = false;
+    bool                            m_releaseTaken     = false;
+};
 
-    WP<CWLSurfaceResource> surface;
-    struct {
-        WP<CDRMSyncobjTimelineResource> acquireTimeline, releaseTimeline;
-        uint64_t                        acquirePoint = 0, releasePoint = 0;
-    } current, pending;
+class CDRMSyncobjSurfaceResource {
+  public:
+    CDRMSyncobjSurfaceResource(UP<CWpLinuxDrmSyncobjSurfaceV1>&& resource_, SP<CWLSurfaceResource> surface_);
+    ~CDRMSyncobjSurfaceResource();
+
+    bool protocolError();
+    bool good();
 
   private:
-    SP<CWpLinuxDrmSyncobjSurfaceV1> resource;
+    void                            removeAllWaiters();
+    WP<CWLSurfaceResource>          surface;
+    UP<CWpLinuxDrmSyncobjSurfaceV1> resource;
+
+    CDRMSyncPointState              pendingAcquire;
+    CDRMSyncPointState              pendingRelease;
+    std::list<SSurfaceState>        pendingStates;
path: src/protocols/DRMSyncobj.hpp
user: login: vaxerski
body: _please_ don't use a list.
created_at: 2025-03-12T14:16:44Z
author_association: MEMBER
diff_hunk: @@ -494,11 +471,25 @@ void CWLSurfaceResource::commitPendingState() {
             nullptr);
     }
 
-    lastBuffer = current.buffer ? current.buffer->buffer : WP<IHLBuffer>{};
+    // release the buffer if it's synchronous as update() has done everything thats needed
+    // so we can let the app know we're done.
+    if (!syncobj && current.buffer && current.buffer->buffer && current.buffer->buffer->isSynchronous()) {
path: src/protocols/core/Compositor.cpp
user: login: vaxerski
body: if can be commented out too
created_at: 2025-03-12T14:17:16Z
author_association: MEMBER
diff_hunk: @@ -2,64 +2,95 @@
 
 #include <vector>
 #include "WaylandProtocol.hpp"
+#include "helpers/sync/SyncReleaser.hpp"
 #include "linux-drm-syncobj-v1.hpp"
 #include "../helpers/signal/Signal.hpp"
+#include "types/SurfaceState.hpp"
 #include <hyprutils/os/FileDescriptor.hpp>
+#include <list>
 
 class CWLSurfaceResource;
 class CDRMSyncobjTimelineResource;
 class CSyncTimeline;
+struct SSurfaceState;
 
-class CDRMSyncobjSurfaceResource {
+class CDRMSyncPointState {
   public:
-    CDRMSyncobjSurfaceResource(SP<CWpLinuxDrmSyncobjSurfaceV1> resource_, SP<CWLSurfaceResource> surface_);
+    CDRMSyncPointState() = default;
+    CDRMSyncPointState(WP<CDRMSyncobjTimelineResource> resource_, uint64_t point_, bool acquirePoint);
+    ~CDRMSyncPointState() = default;
+
+    const uint64_t&                                  point();
+    WP<CDRMSyncobjTimelineResource>                  resource();
+    WP<CSyncTimeline>                                timeline();
+    bool                                             expired();
+    Hyprutils::Memory::CUniquePointer<CSyncReleaser> createSyncRelease();
+    bool                                             addWaiter(const std::function<void()>& waiter);
+    bool                                             comitted();
+    Hyprutils::OS::CFileDescriptor                   exportAsFD();
+    void                                             signal();
 
-    bool                   good();
+  private:
+    WP<CDRMSyncobjTimelineResource> m_resource         = {};
+    uint64_t                        m_point            = 0;
+    WP<CSyncTimeline>               m_timeline         = {};
+    bool                            m_acquirePoint     = false;
+    bool                            m_acquireCommitted = false;
+    bool                            m_releaseTaken     = false;
+};
 
-    WP<CWLSurfaceResource> surface;
-    struct {
-        WP<CDRMSyncobjTimelineResource> acquireTimeline, releaseTimeline;
-        uint64_t                        acquirePoint = 0, releasePoint = 0;
-    } current, pending;
+class CDRMSyncobjSurfaceResource {
+  public:
+    CDRMSyncobjSurfaceResource(UP<CWpLinuxDrmSyncobjSurfaceV1>&& resource_, SP<CWLSurfaceResource> surface_);
+    ~CDRMSyncobjSurfaceResource();
+
+    bool protocolError();
+    bool good();
 
   private:
-    SP<CWpLinuxDrmSyncobjSurfaceV1> resource;
+    void                            removeAllWaiters();
+    WP<CWLSurfaceResource>          surface;
+    UP<CWpLinuxDrmSyncobjSurfaceV1> resource;
+
+    CDRMSyncPointState              pendingAcquire;
+    CDRMSyncPointState              pendingRelease;
+    std::list<SSurfaceState>        pendingStates;
path: src/protocols/DRMSyncobj.hpp
user: login: gulafaran
body: its used for iterator validity after removal in the waiter
created_at: 2025-03-12T14:22:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2,64 +2,95 @@
 
 #include <vector>
 #include "WaylandProtocol.hpp"
+#include "helpers/sync/SyncReleaser.hpp"
 #include "linux-drm-syncobj-v1.hpp"
 #include "../helpers/signal/Signal.hpp"
+#include "types/SurfaceState.hpp"
 #include <hyprutils/os/FileDescriptor.hpp>
+#include <list>
 
 class CWLSurfaceResource;
 class CDRMSyncobjTimelineResource;
 class CSyncTimeline;
+struct SSurfaceState;
 
-class CDRMSyncobjSurfaceResource {
+class CDRMSyncPointState {
   public:
-    CDRMSyncobjSurfaceResource(SP<CWpLinuxDrmSyncobjSurfaceV1> resource_, SP<CWLSurfaceResource> surface_);
+    CDRMSyncPointState() = default;
+    CDRMSyncPointState(WP<CDRMSyncobjTimelineResource> resource_, uint64_t point_, bool acquirePoint);
+    ~CDRMSyncPointState() = default;
+
+    const uint64_t&                                  point();
+    WP<CDRMSyncobjTimelineResource>                  resource();
+    WP<CSyncTimeline>                                timeline();
+    bool                                             expired();
+    Hyprutils::Memory::CUniquePointer<CSyncReleaser> createSyncRelease();
+    bool                                             addWaiter(const std::function<void()>& waiter);
+    bool                                             comitted();
+    Hyprutils::OS::CFileDescriptor                   exportAsFD();
+    void                                             signal();
 
-    bool                   good();
+  private:
+    WP<CDRMSyncobjTimelineResource> m_resource         = {};
+    uint64_t                        m_point            = 0;
+    WP<CSyncTimeline>               m_timeline         = {};
+    bool                            m_acquirePoint     = false;
+    bool                            m_acquireCommitted = false;
+    bool                            m_releaseTaken     = false;
+};
 
-    WP<CWLSurfaceResource> surface;
-    struct {
-        WP<CDRMSyncobjTimelineResource> acquireTimeline, releaseTimeline;
-        uint64_t                        acquirePoint = 0, releasePoint = 0;
-    } current, pending;
+class CDRMSyncobjSurfaceResource {
+  public:
+    CDRMSyncobjSurfaceResource(UP<CWpLinuxDrmSyncobjSurfaceV1>&& resource_, SP<CWLSurfaceResource> surface_);
+    ~CDRMSyncobjSurfaceResource();
+
+    bool protocolError();
+    bool good();
 
   private:
-    SP<CWpLinuxDrmSyncobjSurfaceV1> resource;
+    void                            removeAllWaiters();
+    WP<CWLSurfaceResource>          surface;
+    UP<CWpLinuxDrmSyncobjSurfaceV1> resource;
+
+    CDRMSyncPointState              pendingAcquire;
+    CDRMSyncPointState              pendingRelease;
+    std::list<SSurfaceState>        pendingStates;
path: src/protocols/DRMSyncobj.hpp
user: login: vaxerski
body: `std::vector<UP<SSurfaceState>>`
created_at: 2025-03-12T14:24:56Z
author_association: MEMBER
diff_hunk: @@ -2,64 +2,95 @@
 
 #include <vector>
 #include "WaylandProtocol.hpp"
+#include "helpers/sync/SyncReleaser.hpp"
 #include "linux-drm-syncobj-v1.hpp"
 #include "../helpers/signal/Signal.hpp"
+#include "types/SurfaceState.hpp"
 #include <hyprutils/os/FileDescriptor.hpp>
+#include <list>
 
 class CWLSurfaceResource;
 class CDRMSyncobjTimelineResource;
 class CSyncTimeline;
+struct SSurfaceState;
 
-class CDRMSyncobjSurfaceResource {
+class CDRMSyncPointState {
   public:
-    CDRMSyncobjSurfaceResource(SP<CWpLinuxDrmSyncobjSurfaceV1> resource_, SP<CWLSurfaceResource> surface_);
+    CDRMSyncPointState() = default;
+    CDRMSyncPointState(WP<CDRMSyncobjTimelineResource> resource_, uint64_t point_, bool acquirePoint);
+    ~CDRMSyncPointState() = default;
+
+    const uint64_t&                                  point();
+    WP<CDRMSyncobjTimelineResource>                  resource();
+    WP<CSyncTimeline>                                timeline();
+    bool                                             expired();
+    Hyprutils::Memory::CUniquePointer<CSyncReleaser> createSyncRelease();
+    bool                                             addWaiter(const std::function<void()>& waiter);
+    bool                                             comitted();
+    Hyprutils::OS::CFileDescriptor                   exportAsFD();
+    void                                             signal();
 
-    bool                   good();
+  private:
+    WP<CDRMSyncobjTimelineResource> m_resource         = {};
+    uint64_t                        m_point            = 0;
+    WP<CSyncTimeline>               m_timeline         = {};
+    bool                            m_acquirePoint     = false;
+    bool                            m_acquireCommitted = false;
+    bool                            m_releaseTaken     = false;
+};
 
-    WP<CWLSurfaceResource> surface;
-    struct {
-        WP<CDRMSyncobjTimelineResource> acquireTimeline, releaseTimeline;
-        uint64_t                        acquirePoint = 0, releasePoint = 0;
-    } current, pending;
+class CDRMSyncobjSurfaceResource {
+  public:
+    CDRMSyncobjSurfaceResource(UP<CWpLinuxDrmSyncobjSurfaceV1>&& resource_, SP<CWLSurfaceResource> surface_);
+    ~CDRMSyncobjSurfaceResource();
+
+    bool protocolError();
+    bool good();
 
   private:
-    SP<CWpLinuxDrmSyncobjSurfaceV1> resource;
+    void                            removeAllWaiters();
+    WP<CWLSurfaceResource>          surface;
+    UP<CWpLinuxDrmSyncobjSurfaceV1> resource;
+
+    CDRMSyncPointState              pendingAcquire;
+    CDRMSyncPointState              pendingRelease;
+    std::list<SSurfaceState>        pendingStates;
path: src/protocols/DRMSyncobj.hpp
user: login: PlasmaPower
body: Is your concern with the list cache locality / performance?
created_at: 2025-03-12T14:26:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2,64 +2,95 @@
 
 #include <vector>
 #include "WaylandProtocol.hpp"
+#include "helpers/sync/SyncReleaser.hpp"
 #include "linux-drm-syncobj-v1.hpp"
 #include "../helpers/signal/Signal.hpp"
+#include "types/SurfaceState.hpp"
 #include <hyprutils/os/FileDescriptor.hpp>
+#include <list>
 
 class CWLSurfaceResource;
 class CDRMSyncobjTimelineResource;
 class CSyncTimeline;
+struct SSurfaceState;
 
-class CDRMSyncobjSurfaceResource {
+class CDRMSyncPointState {
   public:
-    CDRMSyncobjSurfaceResource(SP<CWpLinuxDrmSyncobjSurfaceV1> resource_, SP<CWLSurfaceResource> surface_);
+    CDRMSyncPointState() = default;
+    CDRMSyncPointState(WP<CDRMSyncobjTimelineResource> resource_, uint64_t point_, bool acquirePoint);
+    ~CDRMSyncPointState() = default;
+
+    const uint64_t&                                  point();
+    WP<CDRMSyncobjTimelineResource>                  resource();
+    WP<CSyncTimeline>                                timeline();
+    bool                                             expired();
+    Hyprutils::Memory::CUniquePointer<CSyncReleaser> createSyncRelease();
+    bool                                             addWaiter(const std::function<void()>& waiter);
+    bool                                             comitted();
+    Hyprutils::OS::CFileDescriptor                   exportAsFD();
+    void                                             signal();
 
-    bool                   good();
+  private:
+    WP<CDRMSyncobjTimelineResource> m_resource         = {};
+    uint64_t                        m_point            = 0;
+    WP<CSyncTimeline>               m_timeline         = {};
+    bool                            m_acquirePoint     = false;
+    bool                            m_acquireCommitted = false;
+    bool                            m_releaseTaken     = false;
+};
 
-    WP<CWLSurfaceResource> surface;
-    struct {
-        WP<CDRMSyncobjTimelineResource> acquireTimeline, releaseTimeline;
-        uint64_t                        acquirePoint = 0, releasePoint = 0;
-    } current, pending;
+class CDRMSyncobjSurfaceResource {
+  public:
+    CDRMSyncobjSurfaceResource(UP<CWpLinuxDrmSyncobjSurfaceV1>&& resource_, SP<CWLSurfaceResource> surface_);
+    ~CDRMSyncobjSurfaceResource();
+
+    bool protocolError();
+    bool good();
 
   private:
-    SP<CWpLinuxDrmSyncobjSurfaceV1> resource;
+    void                            removeAllWaiters();
+    WP<CWLSurfaceResource>          surface;
+    UP<CWpLinuxDrmSyncobjSurfaceV1> resource;
+
+    CDRMSyncPointState              pendingAcquire;
+    CDRMSyncPointState              pendingRelease;
+    std::list<SSurfaceState>        pendingStates;
path: src/protocols/DRMSyncobj.hpp
user: login: gulafaran
body: yeah but, as far as i understand it erasing `it` on anything but an std::list here is undefined behaviour.

```
state.buffer->acquire->addWaiter([this, surf = surface, it = std::prev(pendingStates.end())] {
            if (!surf)
                return;

            surf->commitPendingState(*it);
            pendingStates.erase(it);
        });
```
created_at: 2025-03-12T14:27:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2,64 +2,95 @@
 
 #include <vector>
 #include "WaylandProtocol.hpp"
+#include "helpers/sync/SyncReleaser.hpp"
 #include "linux-drm-syncobj-v1.hpp"
 #include "../helpers/signal/Signal.hpp"
+#include "types/SurfaceState.hpp"
 #include <hyprutils/os/FileDescriptor.hpp>
+#include <list>
 
 class CWLSurfaceResource;
 class CDRMSyncobjTimelineResource;
 class CSyncTimeline;
+struct SSurfaceState;
 
-class CDRMSyncobjSurfaceResource {
+class CDRMSyncPointState {
   public:
-    CDRMSyncobjSurfaceResource(SP<CWpLinuxDrmSyncobjSurfaceV1> resource_, SP<CWLSurfaceResource> surface_);
+    CDRMSyncPointState() = default;
+    CDRMSyncPointState(WP<CDRMSyncobjTimelineResource> resource_, uint64_t point_, bool acquirePoint);
+    ~CDRMSyncPointState() = default;
+
+    const uint64_t&                                  point();
+    WP<CDRMSyncobjTimelineResource>                  resource();
+    WP<CSyncTimeline>                                timeline();
+    bool                                             expired();
+    Hyprutils::Memory::CUniquePointer<CSyncReleaser> createSyncRelease();
+    bool                                             addWaiter(const std::function<void()>& waiter);
+    bool                                             comitted();
+    Hyprutils::OS::CFileDescriptor                   exportAsFD();
+    void                                             signal();
 
-    bool                   good();
+  private:
+    WP<CDRMSyncobjTimelineResource> m_resource         = {};
+    uint64_t                        m_point            = 0;
+    WP<CSyncTimeline>               m_timeline         = {};
+    bool                            m_acquirePoint     = false;
+    bool                            m_acquireCommitted = false;
+    bool                            m_releaseTaken     = false;
+};
 
-    WP<CWLSurfaceResource> surface;
-    struct {
-        WP<CDRMSyncobjTimelineResource> acquireTimeline, releaseTimeline;
-        uint64_t                        acquirePoint = 0, releasePoint = 0;
-    } current, pending;
+class CDRMSyncobjSurfaceResource {
+  public:
+    CDRMSyncobjSurfaceResource(UP<CWpLinuxDrmSyncobjSurfaceV1>&& resource_, SP<CWLSurfaceResource> surface_);
+    ~CDRMSyncobjSurfaceResource();
+
+    bool protocolError();
+    bool good();
 
   private:
-    SP<CWpLinuxDrmSyncobjSurfaceV1> resource;
+    void                            removeAllWaiters();
+    WP<CWLSurfaceResource>          surface;
+    UP<CWpLinuxDrmSyncobjSurfaceV1> resource;
+
+    CDRMSyncPointState              pendingAcquire;
+    CDRMSyncPointState              pendingRelease;
+    std::list<SSurfaceState>        pendingStates;
path: src/protocols/DRMSyncobj.hpp
user: login: vaxerski
body: make it a sp vec then and
```cpp
state.buffer->acquire->addWaiter([this, surf = surface, wp = CWeakPointer<SSurfaceState>(*pendingStates.end())] {
            if (!surf)
                return;

            surf->commitPendingState(wp.lock());
            std::erase(pendingStates, wp);
        });
```
created_at: 2025-03-12T14:40:49Z
author_association: MEMBER
diff_hunk: @@ -15,17 +16,19 @@ class CEGLSync;
 
 class CSyncReleaser {
   public:
-    CSyncReleaser(WP<CSyncTimeline> timeline_, uint64_t point_);
+    CSyncReleaser(SP<CSyncTimeline> timeline_, uint64_t point_);
     ~CSyncReleaser();
 
     // drops the releaser, will never signal anymore
     void drop();
 
     // wait for this gpu job to finish before releasing
-    void addReleaseSync(SP<CEGLSync> sync);
+    Hyprutils::OS::CFileDescriptor mergeSyncFds(const Hyprutils::OS::CFileDescriptor& fd1, const Hyprutils::OS::CFileDescriptor& fd2);
+    void                           addReleaseSync(SP<CEGLSync> sync);
 
   private:
-    WP<CSyncTimeline> timeline;
-    uint64_t          point = 0;
-    SP<CEGLSync>      sync;
+    SP<CSyncTimeline>              timeline;
path: src/helpers/sync/SyncReleaser.hpp
user: login: gulafaran
body: changed
created_at: 2025-03-12T15:17:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -34,77 +104,102 @@ CDRMSyncobjSurfaceResource::CDRMSyncobjSurfaceResource(SP<CWpLinuxDrmSyncobjSurf
             return;
         }
 
-        auto timeline           = CDRMSyncobjTimelineResource::fromResource(timeline_);
-        pending.releaseTimeline = timeline;
-        pending.releasePoint    = ((uint64_t)hi << 32) | (uint64_t)lo;
+        auto timeline  = CDRMSyncobjTimelineResource::fromResource(timeline_);
+        pendingRelease = {timeline, ((uint64_t)hi << 32) | (uint64_t)lo, false};
     });
 
     listeners.surfacePrecommit = surface->events.precommit.registerListener([this](std::any d) {
-        if ((pending.acquireTimeline || pending.releaseTimeline) && !surface->pending.texture) {
-            resource->error(WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_BUFFER, "Missing buffer");
-            surface->pending.rejected = true;
-            return;
+        if (!surface->pending.buffer && surface->pending.newBuffer && !surface->pending.texture) {
+            removeAllWaiters();
+            surface->commitPendingState(surface->pending);
+            return; // null buffer attached.
         }
 
-        if (!surface->pending.newBuffer)
-            return; // this commit does not change the state here
-
-        if (!!pending.acquireTimeline != !!pending.releaseTimeline) {
-            resource->error(pending.acquireTimeline ? WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_RELEASE_POINT : WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_ACQUIRE_POINT,
-                            "Missing timeline");
-            surface->pending.rejected = true;
-            return;
+        if (!surface->pending.buffer && !surface->pending.newBuffer && surface->current.buffer) {
+            surface->current.bufferDamage.clear();
+            surface->current.damage.clear();
+            surface->commitPendingState(surface->current);
+            return; // no new buffer, but we still have current around and a commit happend, commit current again.
         }
 
-        if (!pending.acquireTimeline)
+        if (!surface->pending.buffer && !surface->pending.newBuffer && !surface->current.buffer) {
+            surface->commitPendingState(surface->pending); // no pending buffer, no current buffer. probably first commit
             return;
+        }
 
-        if (pending.acquireTimeline && pending.releaseTimeline && pending.acquireTimeline == pending.releaseTimeline) {
-            if (pending.acquirePoint >= pending.releasePoint) {
-                resource->error(WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_CONFLICTING_POINTS, "Acquire and release points are on the same timeline, and acquire >= release");
-                surface->pending.rejected = true;
-                return;
-            }
+        if (!pendingAcquire.expired()) {
+            surface->pending.buffer->acquire = makeUnique<CDRMSyncPointState>(std::move(pendingAcquire));
+            pendingAcquire                   = {};
         }
 
-        // wait for the acquire timeline to materialize
-        auto materialized = pending.acquireTimeline->timeline->check(pending.acquirePoint, DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE);
-        if (!materialized.has_value()) {
-            LOGM(ERR, "Failed to check the acquire timeline");
-            resource->noMemory();
-            return;
+        if (!pendingRelease.expired()) {
+            surface->pending.buffer->release = makeUnique<CDRMSyncPointState>(std::move(pendingRelease));
+            pendingRelease                   = {};
         }
 
-        if (materialized.value())
-            return;
+        if (protocolError())
+            return; // bad client.. return.
path: src/protocols/DRMSyncobj.cpp
user: login: gulafaran
body: removed.
created_at: 2025-03-12T15:17:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2,64 +2,95 @@
 
 #include <vector>
 #include "WaylandProtocol.hpp"
+#include "helpers/sync/SyncReleaser.hpp"
 #include "linux-drm-syncobj-v1.hpp"
 #include "../helpers/signal/Signal.hpp"
+#include "types/SurfaceState.hpp"
 #include <hyprutils/os/FileDescriptor.hpp>
+#include <list>
 
 class CWLSurfaceResource;
 class CDRMSyncobjTimelineResource;
 class CSyncTimeline;
+struct SSurfaceState;
 
-class CDRMSyncobjSurfaceResource {
+class CDRMSyncPointState {
   public:
-    CDRMSyncobjSurfaceResource(SP<CWpLinuxDrmSyncobjSurfaceV1> resource_, SP<CWLSurfaceResource> surface_);
+    CDRMSyncPointState() = default;
+    CDRMSyncPointState(WP<CDRMSyncobjTimelineResource> resource_, uint64_t point_, bool acquirePoint);
+    ~CDRMSyncPointState() = default;
+
+    const uint64_t&                                  point();
+    WP<CDRMSyncobjTimelineResource>                  resource();
+    WP<CSyncTimeline>                                timeline();
+    bool                                             expired();
+    Hyprutils::Memory::CUniquePointer<CSyncReleaser> createSyncRelease();
+    bool                                             addWaiter(const std::function<void()>& waiter);
+    bool                                             comitted();
+    Hyprutils::OS::CFileDescriptor                   exportAsFD();
+    void                                             signal();
 
-    bool                   good();
+  private:
+    WP<CDRMSyncobjTimelineResource> m_resource         = {};
+    uint64_t                        m_point            = 0;
+    WP<CSyncTimeline>               m_timeline         = {};
+    bool                            m_acquirePoint     = false;
+    bool                            m_acquireCommitted = false;
+    bool                            m_releaseTaken     = false;
+};
 
-    WP<CWLSurfaceResource> surface;
-    struct {
-        WP<CDRMSyncobjTimelineResource> acquireTimeline, releaseTimeline;
-        uint64_t                        acquirePoint = 0, releasePoint = 0;
-    } current, pending;
+class CDRMSyncobjSurfaceResource {
+  public:
+    CDRMSyncobjSurfaceResource(UP<CWpLinuxDrmSyncobjSurfaceV1>&& resource_, SP<CWLSurfaceResource> surface_);
+    ~CDRMSyncobjSurfaceResource();
+
+    bool protocolError();
+    bool good();
 
   private:
-    SP<CWpLinuxDrmSyncobjSurfaceV1> resource;
+    void                            removeAllWaiters();
+    WP<CWLSurfaceResource>          surface;
+    UP<CWpLinuxDrmSyncobjSurfaceV1> resource;
+
+    CDRMSyncPointState              pendingAcquire;
+    CDRMSyncPointState              pendingRelease;
+    std::list<SSurfaceState>        pendingStates;
path: src/protocols/DRMSyncobj.hpp
user: login: gulafaran
body: ok changed it its now a vector with shared pointers.

```
state->buffer->acquire->addWaiter([this, surf = surface, wp = CWeakPointer<SSurfaceState>(*std::prev(pendingStates.end()))] {
            if (!surf)
                return;

            surf->commitPendingState(*wp.lock());
            std::erase(pendingStates, wp);
        });
```
created_at: 2025-03-12T15:18:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -494,11 +471,25 @@ void CWLSurfaceResource::commitPendingState() {
             nullptr);
     }
 
-    lastBuffer = current.buffer ? current.buffer->buffer : WP<IHLBuffer>{};
+    // release the buffer if it's synchronous as update() has done everything thats needed
+    // so we can let the app know we're done.
+    if (!syncobj && current.buffer && current.buffer->buffer && current.buffer->buffer->isSynchronous()) {
path: src/protocols/core/Compositor.cpp
user: login: gulafaran
body: commented out.
created_at: 2025-03-12T15:18:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -494,11 +471,25 @@ void CWLSurfaceResource::commitPendingState() {
             nullptr);
     }
 
-    lastBuffer = current.buffer ? current.buffer->buffer : WP<IHLBuffer>{};
+    // release the buffer if it's synchronous as update() has done everything thats needed
+    // so we can let the app know we're done.
+    // if (!syncobj && current.buffer && current.buffer->buffer && current.buffer->buffer->isSynchronous()) {
+    // dropCurrentBuffer(); // lets not drop it at all, it will get dropped on next commit if a new buffer arrives.
+    // solves flickering on nonsyncobj apps on explicit sync.
+    // }
+
+    // for async buffers, we can only release the buffer once we are unrefing it from current.
+    // if the backend took it, ref it with the lambda. Otherwise, the end of this scope will release it.
+    if (current.buffer && current.buffer->buffer && !current.buffer->buffer->isSynchronous()) {
+        if (current.buffer->buffer->lockedByBackend && !current.buffer->buffer->hlEvents.backendRelease) {
+            current.buffer->buffer->lock();
+            current.buffer->buffer->onBackendRelease([this]() { current.buffer->buffer->unlock(); });
+        }
+    }
path: src/protocols/core/Compositor.cpp
user: login: ikalco
body: this should be removed, the lockedByBackend mechanism only needs to be used in attemptDirectScanout
created_at: 2025-03-12T18:06:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -494,11 +471,25 @@ void CWLSurfaceResource::commitPendingState() {
             nullptr);
     }
 
-    lastBuffer = current.buffer ? current.buffer->buffer : WP<IHLBuffer>{};
+    // release the buffer if it's synchronous as update() has done everything thats needed
+    // so we can let the app know we're done.
+    // if (!syncobj && current.buffer && current.buffer->buffer && current.buffer->buffer->isSynchronous()) {
+    // dropCurrentBuffer(); // lets not drop it at all, it will get dropped on next commit if a new buffer arrives.
+    // solves flickering on nonsyncobj apps on explicit sync.
+    // }
+
+    // for async buffers, we can only release the buffer once we are unrefing it from current.
+    // if the backend took it, ref it with the lambda. Otherwise, the end of this scope will release it.
+    if (current.buffer && current.buffer->buffer && !current.buffer->buffer->isSynchronous()) {
+        if (current.buffer->buffer->lockedByBackend && !current.buffer->buffer->hlEvents.backendRelease) {
+            current.buffer->buffer->lock();
+            current.buffer->buffer->onBackendRelease([this]() { current.buffer->buffer->unlock(); });
+        }
+    }
path: src/protocols/core/Compositor.cpp
user: login: vaxerski
body: yes, it doesnt make sense here
created_at: 2025-03-12T18:30:12Z
author_association: MEMBER
diff_hunk: @@ -2,64 +2,95 @@
 
 #include <vector>
 #include "WaylandProtocol.hpp"
+#include "helpers/sync/SyncReleaser.hpp"
 #include "linux-drm-syncobj-v1.hpp"
 #include "../helpers/signal/Signal.hpp"
+#include "types/SurfaceState.hpp"
 #include <hyprutils/os/FileDescriptor.hpp>
+#include <list>
 
 class CWLSurfaceResource;
 class CDRMSyncobjTimelineResource;
 class CSyncTimeline;
+struct SSurfaceState;
 
-class CDRMSyncobjSurfaceResource {
+class CDRMSyncPointState {
   public:
-    CDRMSyncobjSurfaceResource(SP<CWpLinuxDrmSyncobjSurfaceV1> resource_, SP<CWLSurfaceResource> surface_);
+    CDRMSyncPointState() = default;
+    CDRMSyncPointState(WP<CDRMSyncobjTimelineResource> resource_, uint64_t point_, bool acquirePoint);
+    ~CDRMSyncPointState() = default;
+
+    const uint64_t&                                  point();
+    WP<CDRMSyncobjTimelineResource>                  resource();
+    WP<CSyncTimeline>                                timeline();
+    bool                                             expired();
+    Hyprutils::Memory::CUniquePointer<CSyncReleaser> createSyncRelease();
+    bool                                             addWaiter(const std::function<void()>& waiter);
+    bool                                             comitted();
+    Hyprutils::OS::CFileDescriptor                   exportAsFD();
+    void                                             signal();
 
-    bool                   good();
+  private:
+    WP<CDRMSyncobjTimelineResource> m_resource         = {};
+    uint64_t                        m_point            = 0;
+    WP<CSyncTimeline>               m_timeline         = {};
+    bool                            m_acquirePoint     = false;
+    bool                            m_acquireCommitted = false;
+    bool                            m_releaseTaken     = false;
+};
 
-    WP<CWLSurfaceResource> surface;
-    struct {
-        WP<CDRMSyncobjTimelineResource> acquireTimeline, releaseTimeline;
-        uint64_t                        acquirePoint = 0, releasePoint = 0;
-    } current, pending;
+class CDRMSyncobjSurfaceResource {
+  public:
+    CDRMSyncobjSurfaceResource(UP<CWpLinuxDrmSyncobjSurfaceV1>&& resource_, SP<CWLSurfaceResource> surface_);
+    ~CDRMSyncobjSurfaceResource();
+
+    bool protocolError();
+    bool good();
 
   private:
-    SP<CWpLinuxDrmSyncobjSurfaceV1> resource;
+    void                            removeAllWaiters();
+    WP<CWLSurfaceResource>          surface;
+    UP<CWpLinuxDrmSyncobjSurfaceV1> resource;
+
+    CDRMSyncPointState              pendingAcquire;
+    CDRMSyncPointState              pendingRelease;
+    std::list<SSurfaceState>        pendingStates;
path: src/protocols/DRMSyncobj.hpp
user: login: PlasmaPower
body: Not that this should block merging, but isn't an unordered_set strictly better than a vector here?
created_at: 2025-03-12T19:07:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -494,11 +471,25 @@ void CWLSurfaceResource::commitPendingState() {
             nullptr);
     }
 
-    lastBuffer = current.buffer ? current.buffer->buffer : WP<IHLBuffer>{};
+    // release the buffer if it's synchronous as update() has done everything thats needed
+    // so we can let the app know we're done.
+    // if (!syncobj && current.buffer && current.buffer->buffer && current.buffer->buffer->isSynchronous()) {
+    // dropCurrentBuffer(); // lets not drop it at all, it will get dropped on next commit if a new buffer arrives.
+    // solves flickering on nonsyncobj apps on explicit sync.
+    // }
+
+    // for async buffers, we can only release the buffer once we are unrefing it from current.
+    // if the backend took it, ref it with the lambda. Otherwise, the end of this scope will release it.
+    if (current.buffer && current.buffer->buffer && !current.buffer->buffer->isSynchronous()) {
+        if (current.buffer->buffer->lockedByBackend && !current.buffer->buffer->hlEvents.backendRelease) {
+            current.buffer->buffer->lock();
+            current.buffer->buffer->onBackendRelease([this]() { current.buffer->buffer->unlock(); });
+        }
+    }
path: src/protocols/core/Compositor.cpp
user: login: gulafaran
body: no we want it here, otherwise sync releaser wont be reset until the new buffer arrives which causes it to become a bit delayish. especially noticeable in minecraft vsync on. like we are always a buffer behind. thats atleast my interpretation and testing of it.
created_at: 2025-03-13T08:39:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -494,11 +471,25 @@ void CWLSurfaceResource::commitPendingState() {
             nullptr);
     }
 
-    lastBuffer = current.buffer ? current.buffer->buffer : WP<IHLBuffer>{};
+    // release the buffer if it's synchronous as update() has done everything thats needed
+    // so we can let the app know we're done.
+    // if (!syncobj && current.buffer && current.buffer->buffer && current.buffer->buffer->isSynchronous()) {
+    // dropCurrentBuffer(); // lets not drop it at all, it will get dropped on next commit if a new buffer arrives.
+    // solves flickering on nonsyncobj apps on explicit sync.
+    // }
+
+    // for async buffers, we can only release the buffer once we are unrefing it from current.
+    // if the backend took it, ref it with the lambda. Otherwise, the end of this scope will release it.
+    if (current.buffer && current.buffer->buffer && !current.buffer->buffer->isSynchronous()) {
+        if (current.buffer->buffer->lockedByBackend && !current.buffer->buffer->hlEvents.backendRelease) {
+            current.buffer->buffer->lock();
+            current.buffer->buffer->onBackendRelease([this]() { current.buffer->buffer->unlock(); });
+        }
+    }
path: src/protocols/core/Compositor.cpp
user: login: gulafaran
body: in short we want async buffers to signal as early as possible to let clients know they are free to reuse the same shared memory. and this does it after its been drawn and on next pageflip, not when a new buffer arrives on the next commit.
created_at: 2025-03-13T08:43:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -494,11 +471,25 @@ void CWLSurfaceResource::commitPendingState() {
             nullptr);
     }
 
-    lastBuffer = current.buffer ? current.buffer->buffer : WP<IHLBuffer>{};
+    // release the buffer if it's synchronous as update() has done everything thats needed
+    // so we can let the app know we're done.
+    // if (!syncobj && current.buffer && current.buffer->buffer && current.buffer->buffer->isSynchronous()) {
+    // dropCurrentBuffer(); // lets not drop it at all, it will get dropped on next commit if a new buffer arrives.
+    // solves flickering on nonsyncobj apps on explicit sync.
+    // }
+
+    // for async buffers, we can only release the buffer once we are unrefing it from current.
+    // if the backend took it, ref it with the lambda. Otherwise, the end of this scope will release it.
+    if (current.buffer && current.buffer->buffer && !current.buffer->buffer->isSynchronous()) {
+        if (current.buffer->buffer->lockedByBackend && !current.buffer->buffer->hlEvents.backendRelease) {
+            current.buffer->buffer->lock();
+            current.buffer->buffer->onBackendRelease([this]() { current.buffer->buffer->unlock(); });
+        }
+    }
path: src/protocols/core/Compositor.cpp
user: login: UjinT34
body: https://github.com/hyprwm/Hyprland/pull/8966#issuecomment-2575817415 might be relevant
created_at: 2025-03-13T09:04:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -494,11 +471,25 @@ void CWLSurfaceResource::commitPendingState() {
             nullptr);
     }
 
-    lastBuffer = current.buffer ? current.buffer->buffer : WP<IHLBuffer>{};
+    // release the buffer if it's synchronous as update() has done everything thats needed
+    // so we can let the app know we're done.
+    // if (!syncobj && current.buffer && current.buffer->buffer && current.buffer->buffer->isSynchronous()) {
+    // dropCurrentBuffer(); // lets not drop it at all, it will get dropped on next commit if a new buffer arrives.
+    // solves flickering on nonsyncobj apps on explicit sync.
+    // }
+
+    // for async buffers, we can only release the buffer once we are unrefing it from current.
+    // if the backend took it, ref it with the lambda. Otherwise, the end of this scope will release it.
+    if (current.buffer && current.buffer->buffer && !current.buffer->buffer->isSynchronous()) {
+        if (current.buffer->buffer->lockedByBackend && !current.buffer->buffer->hlEvents.backendRelease) {
+            current.buffer->buffer->lock();
+            current.buffer->buffer->onBackendRelease([this]() { current.buffer->buffer->unlock(); });
+        }
+    }
path: src/protocols/core/Compositor.cpp
user: login: gulafaran
body: might even be worth testing dropping synchronous buffers in same manner too and not wait until next buffer attach+commit. 
created_at: 2025-03-13T09:07:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -494,11 +471,25 @@ void CWLSurfaceResource::commitPendingState() {
             nullptr);
     }
 
-    lastBuffer = current.buffer ? current.buffer->buffer : WP<IHLBuffer>{};
+    // release the buffer if it's synchronous as update() has done everything thats needed
+    // so we can let the app know we're done.
+    // if (!syncobj && current.buffer && current.buffer->buffer && current.buffer->buffer->isSynchronous()) {
+    // dropCurrentBuffer(); // lets not drop it at all, it will get dropped on next commit if a new buffer arrives.
+    // solves flickering on nonsyncobj apps on explicit sync.
+    // }
+
+    // for async buffers, we can only release the buffer once we are unrefing it from current.
+    // if the backend took it, ref it with the lambda. Otherwise, the end of this scope will release it.
+    if (current.buffer && current.buffer->buffer && !current.buffer->buffer->isSynchronous()) {
+        if (current.buffer->buffer->lockedByBackend && !current.buffer->buffer->hlEvents.backendRelease) {
+            current.buffer->buffer->lock();
+            current.buffer->buffer->onBackendRelease([this]() { current.buffer->buffer->unlock(); });
+        }
+    }
path: src/protocols/core/Compositor.cpp
user: login: ikalco
body: >no we want it here, otherwise sync releaser wont be reset until the new buffer arrives which causes it to become a bit delayish.

<details>
<summary>The buffer release is supposed to be delayed so that there is always a valid buffer during scanout</summary>
Basically, once a buffer is committed to KMS (using `output->commit();`), it is locked until the next frame.
Then in the new frame's attemptDirectScanout, if we have a new buffer to commit, we will commit it to KMS and then the AQ backend will unlock the original buffer once the new one is being scanned out
</details>

Also `current.buffer->buffer->lockedByBackend` will never be set here because it is only set after a KMS `output->commit()` of the surface buffer, which only happens in attemptDirectScanout in the _next_ onMonitorFrame after a surface commit provides a new/different buffer
created_at: 2025-03-13T12:23:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -494,11 +471,25 @@ void CWLSurfaceResource::commitPendingState() {
             nullptr);
     }
 
-    lastBuffer = current.buffer ? current.buffer->buffer : WP<IHLBuffer>{};
+    // release the buffer if it's synchronous as update() has done everything thats needed
+    // so we can let the app know we're done.
+    // if (!syncobj && current.buffer && current.buffer->buffer && current.buffer->buffer->isSynchronous()) {
+    // dropCurrentBuffer(); // lets not drop it at all, it will get dropped on next commit if a new buffer arrives.
+    // solves flickering on nonsyncobj apps on explicit sync.
+    // }
+
+    // for async buffers, we can only release the buffer once we are unrefing it from current.
+    // if the backend took it, ref it with the lambda. Otherwise, the end of this scope will release it.
+    if (current.buffer && current.buffer->buffer && !current.buffer->buffer->isSynchronous()) {
+        if (current.buffer->buffer->lockedByBackend && !current.buffer->buffer->hlEvents.backendRelease) {
+            current.buffer->buffer->lock();
+            current.buffer->buffer->onBackendRelease([this]() { current.buffer->buffer->unlock(); });
+        }
+    }
path: src/protocols/core/Compositor.cpp
user: login: gulafaran
body: im not entirerly read up on lockedByBackend but try it yourself, ommit that buffer lock/unlock enter minecraft. and this is just a windowed game without directscanout, set it to vsync and notice the input delay for actual movement/turning/rotation
created_at: 2025-03-13T12:27:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -494,11 +471,25 @@ void CWLSurfaceResource::commitPendingState() {
             nullptr);
     }
 
-    lastBuffer = current.buffer ? current.buffer->buffer : WP<IHLBuffer>{};
+    // release the buffer if it's synchronous as update() has done everything thats needed
+    // so we can let the app know we're done.
+    // if (!syncobj && current.buffer && current.buffer->buffer && current.buffer->buffer->isSynchronous()) {
+    // dropCurrentBuffer(); // lets not drop it at all, it will get dropped on next commit if a new buffer arrives.
+    // solves flickering on nonsyncobj apps on explicit sync.
+    // }
+
+    // for async buffers, we can only release the buffer once we are unrefing it from current.
+    // if the backend took it, ref it with the lambda. Otherwise, the end of this scope will release it.
+    if (current.buffer && current.buffer->buffer && !current.buffer->buffer->isSynchronous()) {
+        if (current.buffer->buffer->lockedByBackend && !current.buffer->buffer->hlEvents.backendRelease) {
+            current.buffer->buffer->lock();
+            current.buffer->buffer->onBackendRelease([this]() { current.buffer->buffer->unlock(); });
+        }
+    }
path: src/protocols/core/Compositor.cpp
user: login: ikalco
body: >might even be worth testing dropping synchronous buffers in same manner too and not wait until next buffer attach+commit.

it's definitely possible to release synchronous buffers earlier since technically we are done with them inside commitPendingState after we copy their data with `buffer->update();`

for asynchronous buffers we are only done with them once OpenGL finishes rendering to the final buffer (when rendering, not direct scanout),
which means async buffers can be released (both wl_buffer.release and release point) with the eglSync mechanism inside `endRender`, since when the eglSync signals that means OpenGL has finished all its queued commands
created_at: 2025-03-13T12:33:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -494,11 +471,25 @@ void CWLSurfaceResource::commitPendingState() {
             nullptr);
     }
 
-    lastBuffer = current.buffer ? current.buffer->buffer : WP<IHLBuffer>{};
+    // release the buffer if it's synchronous as update() has done everything thats needed
+    // so we can let the app know we're done.
+    // if (!syncobj && current.buffer && current.buffer->buffer && current.buffer->buffer->isSynchronous()) {
+    // dropCurrentBuffer(); // lets not drop it at all, it will get dropped on next commit if a new buffer arrives.
+    // solves flickering on nonsyncobj apps on explicit sync.
+    // }
+
+    // for async buffers, we can only release the buffer once we are unrefing it from current.
+    // if the backend took it, ref it with the lambda. Otherwise, the end of this scope will release it.
+    if (current.buffer && current.buffer->buffer && !current.buffer->buffer->isSynchronous()) {
+        if (current.buffer->buffer->lockedByBackend && !current.buffer->buffer->hlEvents.backendRelease) {
+            current.buffer->buffer->lock();
+            current.buffer->buffer->onBackendRelease([this]() { current.buffer->buffer->unlock(); });
+        }
+    }
path: src/protocols/core/Compositor.cpp
user: login: ikalco
body: >im not entirerly read up on lockedByBackend but try it yourself, ommit that buffer lock/unlock enter minecraft. and this is just a windowed game without directscanout, set it to vsync and notice the input delay for actual movement/turning/rotation

page didn't refresh lol so I just saw this
Anyway, I'm not at my computer right now but try putting a Debug::log in there to see if the `current.buffer->buffer->lock();` is actually running, if it is then it's a bug somewhere else because lockedByBackend shouldn't be true here
created_at: 2025-03-13T12:37:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -494,11 +471,25 @@ void CWLSurfaceResource::commitPendingState() {
             nullptr);
     }
 
-    lastBuffer = current.buffer ? current.buffer->buffer : WP<IHLBuffer>{};
+    // release the buffer if it's synchronous as update() has done everything thats needed
+    // so we can let the app know we're done.
+    // if (!syncobj && current.buffer && current.buffer->buffer && current.buffer->buffer->isSynchronous()) {
+    // dropCurrentBuffer(); // lets not drop it at all, it will get dropped on next commit if a new buffer arrives.
+    // solves flickering on nonsyncobj apps on explicit sync.
+    // }
+
+    // for async buffers, we can only release the buffer once we are unrefing it from current.
+    // if the backend took it, ref it with the lambda. Otherwise, the end of this scope will release it.
+    if (current.buffer && current.buffer->buffer && !current.buffer->buffer->isSynchronous()) {
+        if (current.buffer->buffer->lockedByBackend && !current.buffer->buffer->hlEvents.backendRelease) {
+            current.buffer->buffer->lock();
+            current.buffer->buffer->onBackendRelease([this]() { current.buffer->buffer->unlock(); });
+        }
+    }
path: src/protocols/core/Compositor.cpp
user: login: gulafaran
body: if we skip directscanout entirerly, heres what im seeing and thinking. applications are either using sync or async buffers, sometimes both. they mostly use double buffering. can even be triple buffering. up to the client.

client attaches A, commits A, we draw A. but we never release it or signal it, sometimes client even commits more times on A, then client sends B, commits B, we draw B. only now client is stuck until A is released/signaled. and we get a delay, because we are done with B but dont recieve a new buffer until client has both seen release of A and had its time to do stuff to A

with that in mind, the reason for not dropping current buffer is both allowing same buffer commits but also because it causes issues with non syncobj applications like gtk2, it seems there is a way around that too by actually checking if the buffer fd[0] is readable with https://github.com/hyprwm/hyprutils/blob/dd1f720cbc2dbb3c71167c9598045dd3261d27b3/src/os/FileDescriptor.cpp#L82, and keep them also in a queue/waiter. thats how kwin does it.
created_at: 2025-03-13T12:38:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -494,11 +471,25 @@ void CWLSurfaceResource::commitPendingState() {
             nullptr);
     }
 
-    lastBuffer = current.buffer ? current.buffer->buffer : WP<IHLBuffer>{};
+    // release the buffer if it's synchronous as update() has done everything thats needed
+    // so we can let the app know we're done.
+    // if (!syncobj && current.buffer && current.buffer->buffer && current.buffer->buffer->isSynchronous()) {
+    // dropCurrentBuffer(); // lets not drop it at all, it will get dropped on next commit if a new buffer arrives.
+    // solves flickering on nonsyncobj apps on explicit sync.
+    // }
+
+    // for async buffers, we can only release the buffer once we are unrefing it from current.
+    // if the backend took it, ref it with the lambda. Otherwise, the end of this scope will release it.
+    if (current.buffer && current.buffer->buffer && !current.buffer->buffer->isSynchronous()) {
+        if (current.buffer->buffer->lockedByBackend && !current.buffer->buffer->hlEvents.backendRelease) {
+            current.buffer->buffer->lock();
+            current.buffer->buffer->onBackendRelease([this]() { current.buffer->buffer->unlock(); });
+        }
+    }
path: src/protocols/core/Compositor.cpp
user: login: gulafaran
body: maybe even move the buffer drop to buffer attach so when a pending comes in, drop current, that would solve most issues i think. allows same buffer commits and also drops A as fast as B comes in.
created_at: 2025-03-13T13:09:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -494,11 +471,25 @@ void CWLSurfaceResource::commitPendingState() {
             nullptr);
     }
 
-    lastBuffer = current.buffer ? current.buffer->buffer : WP<IHLBuffer>{};
+    // release the buffer if it's synchronous as update() has done everything thats needed
+    // so we can let the app know we're done.
+    // if (!syncobj && current.buffer && current.buffer->buffer && current.buffer->buffer->isSynchronous()) {
+    // dropCurrentBuffer(); // lets not drop it at all, it will get dropped on next commit if a new buffer arrives.
+    // solves flickering on nonsyncobj apps on explicit sync.
+    // }
+
+    // for async buffers, we can only release the buffer once we are unrefing it from current.
+    // if the backend took it, ref it with the lambda. Otherwise, the end of this scope will release it.
+    if (current.buffer && current.buffer->buffer && !current.buffer->buffer->isSynchronous()) {
+        if (current.buffer->buffer->lockedByBackend && !current.buffer->buffer->hlEvents.backendRelease) {
+            current.buffer->buffer->lock();
+            current.buffer->buffer->onBackendRelease([this]() { current.buffer->buffer->unlock(); });
+        }
+    }
path: src/protocols/core/Compositor.cpp
user: login: UjinT34
body: A double buffered client will want to switch buffers immediately after it commits one of them. There is a small gap between scanout finish an a new buffer commit. We want to unlock the buffer within this gap. Unlocking it after the commit disrupts the client's pacing and doesn't leave enough time to prepare a new frame in time. This causes stutters and missed frames. Noticeable on lower-end hw or with very high framerates or with strict vsync timing requirements.

How do you tell a sync buffer from async? `buffer->isSynchronous()` is always a predefined constant. Do we get different buffer classes inside a commit? Which ones are used by clients with issues?
Might need different logic for every possible drm/shm - sync/async - explicit/implicit combo.
created_at: 2025-03-13T13:09:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -494,11 +471,25 @@ void CWLSurfaceResource::commitPendingState() {
             nullptr);
     }
 
-    lastBuffer = current.buffer ? current.buffer->buffer : WP<IHLBuffer>{};
+    // release the buffer if it's synchronous as update() has done everything thats needed
+    // so we can let the app know we're done.
+    // if (!syncobj && current.buffer && current.buffer->buffer && current.buffer->buffer->isSynchronous()) {
+    // dropCurrentBuffer(); // lets not drop it at all, it will get dropped on next commit if a new buffer arrives.
+    // solves flickering on nonsyncobj apps on explicit sync.
+    // }
+
+    // for async buffers, we can only release the buffer once we are unrefing it from current.
+    // if the backend took it, ref it with the lambda. Otherwise, the end of this scope will release it.
+    if (current.buffer && current.buffer->buffer && !current.buffer->buffer->isSynchronous()) {
+        if (current.buffer->buffer->lockedByBackend && !current.buffer->buffer->hlEvents.backendRelease) {
+            current.buffer->buffer->lock();
+            current.buffer->buffer->onBackendRelease([this]() { current.buffer->buffer->unlock(); });
+        }
+    }
path: src/protocols/core/Compositor.cpp
user: login: ikalco
body: >client attaches A, commits A, we draw A. but we never release it or signal it, sometimes client even commits more times on A, then client sends B, commits B, we draw B. only now client is stuck until A is released/signaled. and we get a delay, because we are done with B but dont recieve a new buffer until client has both seen release of A and had its time to do stuff to A

so where is A released currently? because it shouldn't be through lockedByBackend, if it is then that's unintended
So, the minecraft stuttering being dependent on lockedByBackend when rendering (not ds) would 100% be unintended

AFAIK currently, with explicit sync, we release A in the explicit presented thing at the end of `commitPendingAndDoExplicit`, but without explicit sync A is released on next surface/buffer commit by client, which is fine but potentially a bit performance wasteful but we can optimize later in another pr

My point is, this code should be removed because it _shouldn't_ be doing anything, if it is, then that's a bug

>with that in mind, the reason for not dropping current buffer is both allowing same buffer commits but also because it causes issues with non syncobj applications like gtk2, it seems there is a way around that too by actually checking if the buffer fd[0] is readable with https://github.com/hyprwm/hyprutils/blob/dd1f720cbc2dbb3c71167c9598045dd3261d27b3/src/os/FileDescriptor.cpp#L82, and keep them also in a queue/waiter. thats how kwin does it.

yeah I agree we should do this whenever we optimize the buffer releasing, but I would say out of the scope of this pr
created_at: 2025-03-13T13:09:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -494,11 +471,25 @@ void CWLSurfaceResource::commitPendingState() {
             nullptr);
     }
 
-    lastBuffer = current.buffer ? current.buffer->buffer : WP<IHLBuffer>{};
+    // release the buffer if it's synchronous as update() has done everything thats needed
+    // so we can let the app know we're done.
+    // if (!syncobj && current.buffer && current.buffer->buffer && current.buffer->buffer->isSynchronous()) {
+    // dropCurrentBuffer(); // lets not drop it at all, it will get dropped on next commit if a new buffer arrives.
+    // solves flickering on nonsyncobj apps on explicit sync.
+    // }
+
+    // for async buffers, we can only release the buffer once we are unrefing it from current.
+    // if the backend took it, ref it with the lambda. Otherwise, the end of this scope will release it.
+    if (current.buffer && current.buffer->buffer && !current.buffer->buffer->isSynchronous()) {
+        if (current.buffer->buffer->lockedByBackend && !current.buffer->buffer->hlEvents.backendRelease) {
+            current.buffer->buffer->lock();
+            current.buffer->buffer->onBackendRelease([this]() { current.buffer->buffer->unlock(); });
+        }
+    }
path: src/protocols/core/Compositor.cpp
user: login: gulafaran
body: i could live with something like this. but yeah `commitPendingAndDoExplicit` merely adds the fence, not until the destruction of the timeline is it merged/signaled. there is certainly some work to be done about buffer releases :p

```
diff --git a/src/protocols/core/Compositor.cpp b/src/protocols/core/Compositor.cpp
index 6ccb398e..bf3da781 100644
--- a/src/protocols/core/Compositor.cpp
+++ b/src/protocols/core/Compositor.cpp
@@ -81,6 +81,10 @@ CWLSurfaceResource::CWLSurfaceResource(SP<CWlSurface> resource_) : resource(reso
             pending.size       = res && res->buffer ? res->buffer->size : Vector2D{};
             pending.texture    = res && res->buffer ? res->buffer->texture : nullptr;
             pending.bufferSize = res && res->buffer ? res->buffer->size : Vector2D{};
+
+            if (pending.buffer && current.buffer && current.buffer->buffer) {
+                dropCurrentBuffer();
+            }
         }
 
         Vector2D oldBufSize = current.buffer ? current.bufferSize : Vector2D{};
@@ -470,22 +474,6 @@ void CWLSurfaceResource::commitPendingState(SSurfaceState& state) {
             },
             nullptr);
     }
-
-    // release the buffer if it's synchronous as update() has done everything thats needed
-    // so we can let the app know we're done.
-    // if (!syncobj && current.buffer && current.buffer->buffer && current.buffer->buffer->isSynchronous()) {
-    // dropCurrentBuffer(); // lets not drop it at all, it will get dropped on next commit if a new buffer arrives.
-    // solves flickering on nonsyncobj apps on explicit sync.
-    // }
-
-    // for async buffers, we can only release the buffer once we are unrefing it from current.
-    // if the backend took it, ref it with the lambda. Otherwise, the end of this scope will release it.
-    if (current.buffer && current.buffer->buffer && !current.buffer->buffer->isSynchronous()) {
-        if (current.buffer->buffer->lockedByBackend && !current.buffer->buffer->hlEvents.backendRelease) {
-            current.buffer->buffer->lock();
-            current.buffer->buffer->onBackendRelease([this]() { current.buffer->buffer->unlock(); });
-        }
-    }
 }
 
 void CWLSurfaceResource::updateCursorShm(CRegion damage) {

```
created_at: 2025-03-13T14:59:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -494,11 +471,25 @@ void CWLSurfaceResource::commitPendingState() {
             nullptr);
     }
 
-    lastBuffer = current.buffer ? current.buffer->buffer : WP<IHLBuffer>{};
+    // release the buffer if it's synchronous as update() has done everything thats needed
+    // so we can let the app know we're done.
+    // if (!syncobj && current.buffer && current.buffer->buffer && current.buffer->buffer->isSynchronous()) {
+    // dropCurrentBuffer(); // lets not drop it at all, it will get dropped on next commit if a new buffer arrives.
+    // solves flickering on nonsyncobj apps on explicit sync.
+    // }
+
+    // for async buffers, we can only release the buffer once we are unrefing it from current.
+    // if the backend took it, ref it with the lambda. Otherwise, the end of this scope will release it.
+    if (current.buffer && current.buffer->buffer && !current.buffer->buffer->isSynchronous()) {
+        if (current.buffer->buffer->lockedByBackend && !current.buffer->buffer->hlEvents.backendRelease) {
+            current.buffer->buffer->lock();
+            current.buffer->buffer->onBackendRelease([this]() { current.buffer->buffer->unlock(); });
+        }
+    }
path: src/protocols/core/Compositor.cpp
user: login: vaxerski
body: `current.buffer->buffer->lockedByBackend` is never set to true here. Or, well, shouldn't be.
created_at: 2025-03-13T16:09:31Z
author_association: MEMBER
diff_hunk: @@ -494,11 +471,25 @@ void CWLSurfaceResource::commitPendingState() {
             nullptr);
     }
 
-    lastBuffer = current.buffer ? current.buffer->buffer : WP<IHLBuffer>{};
+    // release the buffer if it's synchronous as update() has done everything thats needed
+    // so we can let the app know we're done.
+    // if (!syncobj && current.buffer && current.buffer->buffer && current.buffer->buffer->isSynchronous()) {
+    // dropCurrentBuffer(); // lets not drop it at all, it will get dropped on next commit if a new buffer arrives.
+    // solves flickering on nonsyncobj apps on explicit sync.
+    // }
+
+    // for async buffers, we can only release the buffer once we are unrefing it from current.
+    // if the backend took it, ref it with the lambda. Otherwise, the end of this scope will release it.
+    if (current.buffer && current.buffer->buffer && !current.buffer->buffer->isSynchronous()) {
+        if (current.buffer->buffer->lockedByBackend && !current.buffer->buffer->hlEvents.backendRelease) {
+            current.buffer->buffer->lock();
+            current.buffer->buffer->onBackendRelease([this]() { current.buffer->buffer->unlock(); });
+        }
+    }
path: src/protocols/core/Compositor.cpp
user: login: PlasmaPower
body: I think it can be set here if the client commits the same frame twice. I believe that's valid for the client to do even if we haven't released the buffer yet as long as the client doesn't modify the buffer when committing the second frame, though the spec is a bit unclear to me on how double commits are supposed to work exactly.
created_at: 2025-03-13T23:55:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -494,11 +471,25 @@ void CWLSurfaceResource::commitPendingState() {
             nullptr);
     }
 
-    lastBuffer = current.buffer ? current.buffer->buffer : WP<IHLBuffer>{};
+    // release the buffer if it's synchronous as update() has done everything thats needed
+    // so we can let the app know we're done.
+    // if (!syncobj && current.buffer && current.buffer->buffer && current.buffer->buffer->isSynchronous()) {
+    // dropCurrentBuffer(); // lets not drop it at all, it will get dropped on next commit if a new buffer arrives.
+    // solves flickering on nonsyncobj apps on explicit sync.
+    // }
+
+    // for async buffers, we can only release the buffer once we are unrefing it from current.
+    // if the backend took it, ref it with the lambda. Otherwise, the end of this scope will release it.
+    if (current.buffer && current.buffer->buffer && !current.buffer->buffer->isSynchronous()) {
+        if (current.buffer->buffer->lockedByBackend && !current.buffer->buffer->hlEvents.backendRelease) {
+            current.buffer->buffer->lock();
+            current.buffer->buffer->onBackendRelease([this]() { current.buffer->buffer->unlock(); });
+        }
+    }
path: src/protocols/core/Compositor.cpp
user: login: vaxerski
body: it still shouldnt have to be handled here tho
created_at: 2025-03-14T01:22:40Z
author_association: MEMBER
diff_hunk: @@ -494,11 +471,25 @@ void CWLSurfaceResource::commitPendingState() {
             nullptr);
     }
 
-    lastBuffer = current.buffer ? current.buffer->buffer : WP<IHLBuffer>{};
+    // release the buffer if it's synchronous as update() has done everything thats needed
+    // so we can let the app know we're done.
+    // if (!syncobj && current.buffer && current.buffer->buffer && current.buffer->buffer->isSynchronous()) {
+    // dropCurrentBuffer(); // lets not drop it at all, it will get dropped on next commit if a new buffer arrives.
+    // solves flickering on nonsyncobj apps on explicit sync.
+    // }
+
+    // for async buffers, we can only release the buffer once we are unrefing it from current.
+    // if the backend took it, ref it with the lambda. Otherwise, the end of this scope will release it.
+    if (current.buffer && current.buffer->buffer && !current.buffer->buffer->isSynchronous()) {
+        if (current.buffer->buffer->lockedByBackend && !current.buffer->buffer->hlEvents.backendRelease) {
+            current.buffer->buffer->lock();
+            current.buffer->buffer->onBackendRelease([this]() { current.buffer->buffer->unlock(); });
+        }
+    }
path: src/protocols/core/Compositor.cpp
user: login: gulafaran
body: https://github.com/hyprwm/Hyprland/pull/9437/commits/a14d37c92faadceabc5cd3d9bda2d0b8368d7a10

but yeah i feel like buffer drops needs a lookover perhaps. even as mentioned above making a queue/waiter for non syncobj buffers and check if fd is readable etc. seems the delay i noticed came from having asan turned on the test build -_- , but yeah adding an Debug::log its not triggering.
created_at: 2025-03-14T06:50:13Z
author_association: CONTRIBUTOR

PR comments:
user: login: ikalco
created_at: 2025-02-26T05:12:15Z
body: wait... for ds, shouldn't we release the wl_buffer whenever DRM signals the out fence?
so the client can reuse that buffer, and the output won't artifact cause drm said scanout is done

edit:
uuuh it seems HL just isn't using the out fence lol, am I missing something?
for reference (https://docs.kernel.org/gpu/drm-kms.html)
>“OUT_FENCE_PTR”:
      Use this property to pass a file descriptor pointer to DRM. Once the Atomic Commit request call returns OUT_FENCE_PTR will be filled with the file descriptor number of a Sync File. This Sync File contains the CRTC fence that <b>will be signaled when all framebuffers present on the Atomic Commit * request for that given CRTC are scanned out on the screen.</b>
author_association: CONTRIBUTOR
user: login: gulafaran
created_at: 2025-02-26T06:23:25Z
body: > wait... for ds, shouldn't we release the wl_buffer whenever DRM signals the out fence? so the client can reuse that buffer, and the output won't artifact cause drm said scanout is done
> 
> edit: uuuh it seems HL just isn't using the out fence lol, am I missing something? for reference (https://docs.kernel.org/gpu/drm-kms.html)
> 
> > “OUT_FENCE_PTR”:
> > Use this property to pass a file descriptor pointer to DRM. Once the Atomic Commit request call returns OUT_FENCE_PTR will be filled with the file descriptor number of a Sync File. This Sync File contains the CRTC fence that will be signaled when all framebuffers present on the Atomic Commit * request for that given CRTC are scanned out on the screen.

There is a lot wrong at the moment, explicit sync currently relies on new buffer attached, the client can commit multiple times on the same buffer if it wants to, and does sometimes, and this PR probably exposed that quirk even more, and yeah direct scanout i havent begun reading into but removed that buffer manual buffer shenanigans because it was conflicting with the changes i made, there is also a few other things im checking that has popped up while testing
author_association: CONTRIBUTOR
user: login: gulafaran
created_at: 2025-02-26T06:29:50Z
body: > wait... for ds, shouldn't we release the wl_buffer whenever DRM signals the out fence? so the client can reuse that buffer, and the output won't artifact cause drm said scanout is done
> 
> edit: uuuh it seems HL just isn't using the out fence lol, am I missing something? for reference (https://docs.kernel.org/gpu/drm-kms.html)
> 
> > “OUT_FENCE_PTR”:
> > Use this property to pass a file descriptor pointer to DRM. Once the Atomic Commit request call returns OUT_FENCE_PTR will be filled with the file descriptor number of a Sync File. This Sync File contains the CRTC fence that will be signaled when all framebuffers present on the Atomic Commit * request for that given CRTC are scanned out on the screen.

And "releasing" when it comes to explicit sync is sending the release point not the buffer .release
author_association: CONTRIBUTOR
user: login: UjinT34
created_at: 2025-02-26T09:06:43Z
body: `output->state->enableExplicitOutFenceForNextCommit()` should be called before the commit if out fence is needed. It fills `output->state->state().explicitOutFence`.
Who should handle that fd?

Note that `OUT_FENCE_PTR` shouldn't be used when tearing is allowed.
author_association: CONTRIBUTOR
user: login: JunaidQrysh
created_at: 2025-02-26T14:18:58Z
body: I tested this pr & it fixed glmark2 lag but minecraft lag is still the same and also it introduced slight lag in other areas like workspace switching and cursor lag when wlogout is opened.
author_association: NONE
user: login: ikalco
created_at: 2025-02-26T15:37:07Z
body: > `output->state->enableExplicitOutFenceForNextCommit()` should be called before the commit if out fence is needed. It fills `output->state->state().explicitOutFence`. Who should handle that fd?
> 
> Note that `OUT_FENCE_PTR` shouldn't be used when tearing is allowed.

we use that fd, i think we import it as a sync_file, then wait for it to signal, meaning aq plane is done with the primary buffer
author_association: CONTRIBUTOR
user: login: gulafaran
created_at: 2025-02-26T16:10:07Z
body: > I tested this pr & it fixed glmark2 lag but minecraft lag is still the same and also it introduced slight lag in other areas like workspace switching and cursor lag when wlogout is opened.

yeah got a few very broken things to figure out, im trying tho. 
author_association: CONTRIBUTOR
user: login: JunaidQrysh
created_at: 2025-03-02T09:39:41Z
body: @gulafaran you did amazing work, now everything runs smoothly. I ran various gpu stress tests, they ran without any lag.
This will be a major change in hyprland for nvidia users. I can finally ditch kde plasma for gaming.
author_association: NONE
user: login: ikalco
created_at: 2025-03-02T15:58:48Z
body: for this part
```
// #TODO does this apply to explicit sync?
    if (!syncobj && previousBuffer && previousBuffer->buffer && !previousBuffer->buffer->isSynchronous()) {
```

the lockedByBackend is unlocked when page flip buffer swap happens, so technically that's when we should signal release point since DRM scanout is no longer using the buffer
but also, im pretty sure this is what the DRM out fence does, so you could use that instead of lockedByBackend
author_association: CONTRIBUTOR
user: login: czM1K3
created_at: 2025-03-02T18:12:35Z
body: I can also say that this fixed my issue with my RX 580 so it might fix something like #8119 and others.
author_association: NONE
user: login: gulafaran
created_at: 2025-03-02T18:21:23Z
body: > I can also say that this fixed my issue with my RX 580 so it might fix something like #8119 and others.

Probably will, and also some minecraft flickering and other things, im pushing soon a fix for a gtk issue and hyprlock issue so its not quite flawless yet 👍
author_association: CONTRIBUTOR
user: login: gulafaran
created_at: 2025-03-03T03:47:11Z
body: > I tested this pr & it fixed glmark2 lag but minecraft lag is still the same and also it introduced slight lag in other areas like workspace switching and cursor lag when wlogout is opened.

should be in a better state now, but not done yet. 
author_association: CONTRIBUTOR
user: login: fufexan
created_at: 2025-03-06T16:31:49Z
body: WFM as well, fixed desync problems.
author_association: MEMBER
user: login: fxzzi
created_at: 2025-03-06T16:32:41Z
body: Also running great for me :)

tested on my nvidia desktop, haven't tested any other configurations.
author_association: CONTRIBUTOR
user: login: gulafaran
created_at: 2025-03-06T16:39:30Z
body: yeah there has been mostly limited desktop nvidia testing from what i can tell, but so far it improves the nvidia situation drasticly, and exposing a configure xdg shell spam issue. but wouldnt hurt if some intel/amd weird combos test things but i suspect its in a rather workable state right now atleast, always room for improvements.
author_association: CONTRIBUTOR
user: login: Skidamek
created_at: 2025-03-06T16:43:58Z
body: It breaks vrr for me 
author_association: CONTRIBUTOR
user: login: vaxerski
created_at: 2025-03-06T16:44:39Z
body: ah there it is. Man I did not even get to press the merge button yet
author_association: MEMBER
user: login: vaxerski
created_at: 2025-03-06T16:49:24Z
body: what even breaks?
author_association: MEMBER
user: login: fxzzi
created_at: 2025-03-06T16:51:38Z
body: > what even breaks?

currently trying to dbg in #hyprland-dev, vrr just doesn't activate for them and OSD fps counter is stuck at max (aka not active)
author_association: CONTRIBUTOR
user: login: JunaidQrysh
created_at: 2025-03-06T17:29:24Z
body: 
https://github.com/user-attachments/assets/70271d9e-fb21-46ef-83f0-3e1299f0fb85


https://github.com/user-attachments/assets/ee57e350-bc4e-46c5-a205-32749aa8ed86

Wallpaper glitch on startup.
The latest commit fixed lag slightly. The whole hyprland lagged before the latest commit.
Also wlogout lag is not visible enough due to 60 fps video but it looks horrible on 120 hz monitor.

author_association: NONE
user: login: gulafaran
created_at: 2025-03-06T17:38:36Z
body: > ThisMR.mp4
> LatestHyprland.mp4
> 
> Wallpaper glitch on startup. The latest commit fixed lag slightly. The whole hyprland lagged before the latest commit. Also wlogout lag is not visible enough due to 60 fps video but it looks horrible on 120 hz monitor.

is that on intel?
author_association: CONTRIBUTOR
user: login: JunaidQrysh
created_at: 2025-03-06T17:39:11Z
body: intel + nvidia
author_association: NONE
user: login: gulafaran
created_at: 2025-03-06T17:39:52Z
body: > intel + nvidia

yeah but only internal connected or an external? so its only using intel or both?
author_association: CONTRIBUTOR
user: login: JunaidQrysh
created_at: 2025-03-06T17:41:31Z
body: only internal connected ( intel as primary gpu )

author_association: NONE
user: login: gulafaran
created_at: 2025-03-07T14:11:31Z
body: > only internal connected ( intel as primary gpu )

does something like this make it any better?

[sync.patch](https://github.com/user-attachments/files/19127739/sync.txt)

doesnt work on nvidia tho but.

author_association: CONTRIBUTOR
user: login: JunaidQrysh
created_at: 2025-03-07T14:25:01Z
body: > > only internal connected ( intel as primary gpu )
> 
> does something like this make it any better?
> 
> [sync.patch](https://github.com/user-attachments/files/19127739/sync.txt)
> 
> doesnt work on nvidia tho but.

still the same
author_association: NONE
user: login: gulafaran
created_at: 2025-03-07T18:10:09Z
body: > > > only internal connected ( intel as primary gpu )
> > 
> > 
> > does something like this make it any better?
> > [sync.patch](https://github.com/user-attachments/files/19127739/sync.txt)
> > doesnt work on nvidia tho but.
> 
> still the same

i figured out i need to double buffer this, not single buffer it like im doing right now. il try and put something together this weekend. most likely why it works on our nuclear gaming rigs while it falls short on something a bit less powerful
author_association: CONTRIBUTOR
user: login: JunaidQrysh
created_at: 2025-03-07T19:23:24Z
body: @gulafaran The MRs in this will be helpful.
https://invent.kde.org/plasma/kwin/-/issues/124.
Kwin uses triple buffering when on Intel gpu otherwise always double buffering. I think hyprland should also do the same( though I might be wrong ).
author_association: NONE
user: login: vaxerski
created_at: 2025-03-08T19:24:59Z
body: > Kwin uses triple buffering when on Intel gpu otherwise always double buffering

different kind of triple buffering, unrelated to the problem at hand here
author_association: MEMBER
user: login: gulafaran
created_at: 2025-03-09T19:42:02Z
body: > > > only internal connected ( intel as primary gpu )
> > 
> > 
> > does something like this make it any better?
> > [sync.patch](https://github.com/user-attachments/files/19127739/sync.txt)
> > doesnt work on nvidia tho but.
> 
> still the same

seems we were supposed to lock the current async buffer and not the previous. also made a bit cleanup in eglsyncing. is it to much to hope that fixed your lag? :)
author_association: CONTRIBUTOR
user: login: JunaidQrysh
created_at: 2025-03-09T22:31:13Z
body: It only improved just slightly but comparing to latest main branch the difference is night and day. Especially on battery.
The main branch also introduces lag(but very little) on battery, But this MR just makes the core i9 11 gen look like a celeron. I tuned on debug overlay and it showed constant 60 fps but they look like 20 on battery and 40 and on ac.
author_association: NONE
user: login: gulafaran
created_at: 2025-03-10T05:45:35Z
body: > It only improved just slightly but comparing to latest main branch the difference is night and day. Especially on battery. The main branch also introduces lag(but very little) on battery, But this MR just makes the core i9 11 gen look like a celeron. I tuned on debug overlay and it showed constant 60 fps but they look like 20 on battery and 40 and on ac.

yeah i believe you. i just find this so mind boggling. works like chefs kiss on my 4080 and my onboard intel, but apparently last change makes one nvidia card vram leak, and one other guys VRR stop working. but not for others. and yours lag hard. but not for others. and i cant reproduce either -_-
author_association: CONTRIBUTOR
user: login: JunaidQrysh
created_at: 2025-03-10T07:27:36Z
body: > > It only improved just slightly but comparing to latest main branch the difference is night and day. Especially on battery. The main branch also introduces lag(but very little) on battery, But this MR just makes the core i9 11 gen look like a celeron. I tuned on debug overlay and it showed constant 60 fps but they look like 20 on battery and 40 and on ac.
> 
> yeah i believe you. i just find this so mind boggling. works like chefs kiss on my 4080 and my onboard intel, but apparently last change makes one nvidia card vram leak, and one other guys VRR stop working. but not for others. and yours lag hard. but not for others. and i cant reproduce either -_-

I think it should be merged so that hopefully it can be debugged easily if more people experience it. Otherwise atleast games run flawlessly and i barely use my laptop on battery.
author_association: NONE
user: login: gulafaran
created_at: 2025-03-10T07:30:35Z
body: > > > It only improved just slightly but comparing to latest main branch the difference is night and day. Especially on battery. The main branch also introduces lag(but very little) on battery, But this MR just makes the core i9 11 gen look like a celeron. I tuned on debug overlay and it showed constant 60 fps but they look like 20 on battery and 40 and on ac.
> > 
> > 
> > yeah i believe you. i just find this so mind boggling. works like chefs kiss on my 4080 and my onboard intel, but apparently last change makes one nvidia card vram leak, and one other guys VRR stop working. but not for others. and yours lag hard. but not for others. and i cant reproduce either -_-
> 
> I think it should be merged so that hopefully it can be debugged easily if more people experience it. Otherwise atleast games run flawlessly and i barely use my laptop on battery.

feels kinda wrong merging something that regresses, i rather just have it here pending and rebase towards main until its figured out better.
author_association: CONTRIBUTOR
user: login: JunaidQrysh
created_at: 2025-03-10T07:38:02Z
body: @gulafaran I wish I knew cpp, i want to make this works so bad. Btw i know enough C to write some basic programs. So how much time do you think it will take me to to learn cpp enough to contribute to hyprland. I can spend HOURS daily.
author_association: NONE
user: login: PlasmaPower
created_at: 2025-03-11T01:23:34Z
body: I made a PR based on this one, https://github.com/gulafaran/Hyprland/pull/1

It gets rid of the buffer reference system in favor of a new "attached buffer" abstraction, which makes it a lot easier to reason about the lifetimes of buffer attachments and when they get released imo. It should also fix an issue in the current codebase of syncReleaser not respecting lockedByBackend. Let me know what you think.
author_association: CONTRIBUTOR
user: login: gulafaran
created_at: 2025-03-11T17:48:17Z
body: big thanks to @PlasmaPower @ikalco @UjinT34 and others, we now have working directscanout, async buffers seemingly dropped correctly and things should behave saner. so everyone rebuild retest see if we kind find any other quirks or if we actually solved them with doing it more right. if in luck VRR regression might be solved otherwise there is work pending for such things too https://github.com/hyprwm/Hyprland/pull/9334 , so unless anything major pops up besides VRR i would say this PR is getting close to done. and VRR can be worked on followup PR
author_association: CONTRIBUTOR
user: login: calebaden
created_at: 2025-03-12T01:29:16Z
body: I've been testing this PR for the past couple of days with VRR and it works fine for me
author_association: NONE