syncobj: use eventfd instead of stalling fd checks (#9437)

* syncobj: cleanup and use uniqueptrs

cleanup a bit missing removals if resource not good, erasing from
containers etc. make use of unique ptrs instead. and add default
destructors.

* syncobj: rework syncobj entirerly

remove early buffer release that was breaking explicit sync, the buffer
needs to exist until the surface commit event has been emitted and draw
calls added egl sync points, move to eventfd signaling instead of
stalling sync point checks, and recommit pending commits if waiting on a
signal. add a CDRMSyncPointState helper class. move a few weak pointers
to shared pointers so they dont destruct before we need to use them.

* syncobj: queue pending states for eventfd

eventfd requires us to queue pending stats until ready and then apply to
current, and also when no ready state exist commit the client commit on
the current existing buffer, if there is one.

* syncobj: clear current buffer damage

clear current buffer damage on current buffer commits.

* syncobj: cleanup code and fix hyprlock

remove unused code, and ensure we dont commit a empty texture causing
locksession protocol and gtk4-layer-shell misbehaving.

* syncobj: ensure buffers are cleaned up

ensure the containers having the various buffers actually gets cleaned
up from their containers, incase the CSignal isnt signaled because of
expired smart pointers or just wrong order destruction because mishaps.
also move the acquire/point setting to buffer attaching. instead of on
precommit.

* syncobj: remove unused code, optimize

remove unused code and merge sync fds if fence is valid, remove manual
directscanout buffer dropping that signals release point on pageflip, it
can cause us to signal the release point while still keeping the current
buffer and rendering it yet again causing wrong things.

* syncobj: delay buffer release on non syncobj

delay buffer releases on non syncobj surfaces until next commit, and
check on async buffers if syncobj and drop and signal the release point
on backend buffer release.

* syncobj: ensure we follow protocol

ensure we follow protocol by replacing acquire/release points if they
arrive late and replace already existing ones. also remove unneded
brackets, and dont try to manual lock/release buffers when it comes to
explicit protocol. it doesnt care about buffer releases only about
acquire and release points and signaling them.

* syncobj: lets not complicate things

set points in precommit, before checking protocol errors and we catch
any pending acquire/release points arriving late.

* syncobj: move SSurfaceState to types

remove destructor resource destroying, let resources destroys them on
their events, and move SSurfaceStates to types/SurfaceState.hpp

* syncobj: actually store the merged fd

have to actually store the mergedfd to use it.

* syncobj: cleanup a bit around fences

ensure the current asynchronous buffer is actually released on pageflip
not the previous. cleanup a bit FD handling in
commitPendingAndDoExplicitSync, and reuse the in fence when syncing
surfaces.

* syncobjs: ensure fence FD doesnt leak

calling resetexplicitfence without properly ensuring the FD is closed
before will leak it, store it per monitor and let it close itself with
the CFileDescriptor class.

* syncobj: ensure buffers are actually released

buffers were never being sent released properly.

* types: Defer buffer sync releaser until unlock

* syncobj: store directscanout fence in monitor

ensure the infence fd survives the scope of attemptdirectscanout so it
doesnt close before it should have.

* syncobj: check if if acquire is expired

we might hit a race to finish on exit where the timeline just has
destructed but the buffer waiter is still pending. and such we
removeAllWaiters null dereferences.

* syncobj: code style changes

remove quack comment, change to m_foo and use a std::vector and
weakpointer in the waiter for removal instead of a std::list.

* syncobj: remove unused async buffer drop

remove unused async buffer drop, only related to directscanout and is
handled elsewhere.

---------

Co-authored-by: Lee Bousfield <ljbousfield@gmail.com>
This commit is contained in:
Tom Englund 2025-03-14 15:08:20 +01:00 committed by GitHub
parent c754d7963f
commit 6ffde36466
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 421 additions and 277 deletions

View file

@ -691,7 +691,6 @@ CConfigManager::CConfigManager() {
registerConfigVar("render:expand_undersized_textures", Hyprlang::INT{1}); registerConfigVar("render:expand_undersized_textures", Hyprlang::INT{1});
registerConfigVar("render:xp_mode", Hyprlang::INT{0}); registerConfigVar("render:xp_mode", Hyprlang::INT{0});
registerConfigVar("render:ctm_animation", Hyprlang::INT{2}); 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("render:cm_fs_passthrough", Hyprlang::INT{1});
registerConfigVar("ecosystem:no_update_news", Hyprlang::INT{0}); registerConfigVar("ecosystem:no_update_news", Hyprlang::INT{0});

View file

@ -1391,16 +1391,17 @@ bool CMonitor::attemptDirectScanout() {
auto explicitOptions = g_pHyprRenderer->getExplicitSyncSettings(output); 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) { if (DOEXPLICIT) {
// wait for surface's explicit fence if present // wait for surface's explicit fence if present
CFileDescriptor fd = PSURFACE->syncobj->current.acquireTimeline->timeline->exportAsSyncFileFD(PSURFACE->syncobj->current.acquirePoint); inFence = PSURFACE->current.buffer->acquire->exportAsFD();
if (fd.isValid()) { if (inFence.isValid()) {
Debug::log(TRACE, "attemptDirectScanout: setting IN_FENCE for aq to {}", fd.get()); Debug::log(TRACE, "attemptDirectScanout: setting IN_FENCE for aq to {}", inFence.get());
output->state->setExplicitInFence(fd.get()); output->state->setExplicitInFence(inFence.get());
} else } else {
Debug::log(TRACE, "attemptDirectScanout: failed to acquire an sync file fd for aq IN_FENCE"); Debug::log(TRACE, "attemptDirectScanout: failed to acquire an sync file fd for aq IN_FENCE");
DOEXPLICIT = fd.isValid(); DOEXPLICIT = false;
}
} }
commitSeq++; commitSeq++;
@ -1425,7 +1426,7 @@ bool CMonitor::attemptDirectScanout() {
Debug::log(LOG, "Entered a direct scanout to {:x}: \"{}\"", (uintptr_t)PCANDIDATE.get(), PCANDIDATE->m_szTitle); 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; 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 // lock buffer while DRM/KMS is using it, then release it when page flip happens since DRM/KMS should be done by then

View file

@ -138,11 +138,12 @@ class CMonitor {
SMonitorRule activeMonitorRule; SMonitorRule activeMonitorRule;
// explicit sync // explicit sync
SP<CSyncTimeline> inTimeline; SP<CSyncTimeline> inTimeline;
SP<CSyncTimeline> outTimeline; SP<CSyncTimeline> outTimeline;
uint64_t commitSeq = 0; Hyprutils::OS::CFileDescriptor inFence;
uint64_t commitSeq = 0;
PHLMONITORREF self; PHLMONITORREF self;
// mirroring // mirroring
PHLMONITORREF pMirrorOf; PHLMONITORREF pMirrorOf;

View file

@ -1,25 +1,65 @@
#include "SyncReleaser.hpp" #include "SyncReleaser.hpp"
#include "SyncTimeline.hpp" #include "SyncTimeline.hpp"
#include "../../render/OpenGL.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() { CSyncReleaser::~CSyncReleaser() {
if (timeline.expired()) if (!m_timeline) {
Debug::log(ERR, "CSyncReleaser destructing without a timeline");
return; return;
}
if (sync) if (m_fd.isValid())
timeline->importFromSyncFileFD(point, sync->fd()); m_timeline->importFromSyncFileFD(m_point, m_fd);
else else
timeline->signal(point); m_timeline->signal(m_point);
} }
void CSyncReleaser::addReleaseSync(SP<CEGLSync> sync_) { CFileDescriptor CSyncReleaser::mergeSyncFds(const CFileDescriptor& fd1, const CFileDescriptor& fd2) {
sync = sync_; 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);
}
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() { void CSyncReleaser::drop() {
timeline.reset(); m_timeline.reset();
} }

View file

@ -4,6 +4,7 @@
#include <optional> #include <optional>
#include <vector> #include <vector>
#include <functional> #include <functional>
#include <hyprutils/os/FileDescriptor.hpp>
#include "../memory/Memory.hpp" #include "../memory/Memory.hpp"
/* /*
@ -15,17 +16,19 @@ class CEGLSync;
class CSyncReleaser { class CSyncReleaser {
public: public:
CSyncReleaser(WP<CSyncTimeline> timeline_, uint64_t point_); CSyncReleaser(SP<CSyncTimeline> timeline, uint64_t point);
~CSyncReleaser(); ~CSyncReleaser();
// drops the releaser, will never signal anymore // drops the releaser, will never signal anymore
void drop(); void drop();
// wait for this gpu job to finish before releasing // 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: private:
WP<CSyncTimeline> timeline; SP<CSyncTimeline> m_timeline;
uint64_t point = 0; uint64_t m_point = 0;
SP<CEGLSync> sync; Hyprutils::OS::CFileDescriptor m_fd;
SP<CEGLSync> m_sync;
}; };

View file

@ -33,6 +33,13 @@ SP<CSyncTimeline> CSyncTimeline::create(int drmFD_, int drmSyncobjFD) {
} }
CSyncTimeline::~CSyncTimeline() { CSyncTimeline::~CSyncTimeline() {
for (auto& w : waiters) {
if (w->source) {
wl_event_source_remove(w->source);
w->source = nullptr;
}
}
if (handle == 0) if (handle == 0)
return; return;
@ -124,6 +131,17 @@ void CSyncTimeline::removeWaiter(SWaiter* w) {
std::erase_if(waiters, [w](const auto& e) { return e.get() == 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) { CFileDescriptor CSyncTimeline::exportAsSyncFileFD(uint64_t src) {
int sync = -1; int sync = -1;

View file

@ -34,6 +34,7 @@ class CSyncTimeline {
bool addWaiter(const std::function<void()>& waiter, uint64_t point, uint32_t flags); bool addWaiter(const std::function<void()>& waiter, uint64_t point, uint32_t flags);
void removeWaiter(SWaiter*); void removeWaiter(SWaiter*);
void removeAllWaiters();
Hyprutils::OS::CFileDescriptor exportAsSyncFileFD(uint64_t src); Hyprutils::OS::CFileDescriptor exportAsSyncFileFD(uint64_t src);
bool importFromSyncFileFD(uint64_t dst, Hyprutils::OS::CFileDescriptor& fd); bool importFromSyncFileFD(uint64_t dst, Hyprutils::OS::CFileDescriptor& fd);
bool transfer(SP<CSyncTimeline> from, uint64_t fromPoint, uint64_t toPoint); bool transfer(SP<CSyncTimeline> from, uint64_t fromPoint, uint64_t toPoint);

View file

@ -4,11 +4,82 @@
#include "core/Compositor.hpp" #include "core/Compositor.hpp"
#include "../helpers/sync/SyncTimeline.hpp" #include "../helpers/sync/SyncTimeline.hpp"
#include "../Compositor.hpp" #include "../Compositor.hpp"
#include "render/OpenGL.hpp"
#include <fcntl.h> #include <fcntl.h>
using namespace Hyprutils::OS; 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()) if UNLIKELY (!good())
return; return;
@ -23,9 +94,8 @@ CDRMSyncobjSurfaceResource::CDRMSyncobjSurfaceResource(SP<CWpLinuxDrmSyncobjSurf
return; return;
} }
auto timeline = CDRMSyncobjTimelineResource::fromResource(timeline_); auto timeline = CDRMSyncobjTimelineResource::fromResource(timeline_);
pending.acquireTimeline = timeline; pendingAcquire = {timeline, ((uint64_t)hi << 32) | (uint64_t)lo, true};
pending.acquirePoint = ((uint64_t)hi << 32) | (uint64_t)lo;
}); });
resource->setSetReleasePoint([this](CWpLinuxDrmSyncobjSurfaceV1* r, wl_resource* timeline_, uint32_t hi, uint32_t lo) { resource->setSetReleasePoint([this](CWpLinuxDrmSyncobjSurfaceV1* r, wl_resource* timeline_, uint32_t hi, uint32_t lo) {
@ -34,77 +104,102 @@ CDRMSyncobjSurfaceResource::CDRMSyncobjSurfaceResource(SP<CWpLinuxDrmSyncobjSurf
return; return;
} }
auto timeline = CDRMSyncobjTimelineResource::fromResource(timeline_); auto timeline = CDRMSyncobjTimelineResource::fromResource(timeline_);
pending.releaseTimeline = timeline; pendingRelease = {timeline, ((uint64_t)hi << 32) | (uint64_t)lo, false};
pending.releasePoint = ((uint64_t)hi << 32) | (uint64_t)lo;
}); });
listeners.surfacePrecommit = surface->events.precommit.registerListener([this](std::any d) { listeners.surfacePrecommit = surface->events.precommit.registerListener([this](std::any d) {
if ((pending.acquireTimeline || pending.releaseTimeline) && !surface->pending.texture) { if (!surface->pending.buffer && surface->pending.newBuffer && !surface->pending.texture) {
resource->error(WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_BUFFER, "Missing buffer"); removeAllWaiters();
surface->pending.rejected = true; surface->commitPendingState(surface->pending);
return; // null buffer attached.
}
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 (!surface->pending.buffer && !surface->pending.newBuffer && !surface->current.buffer) {
surface->commitPendingState(surface->pending); // no pending buffer, no current buffer. probably first commit
return; return;
} }
if (!surface->pending.newBuffer) if (!pendingAcquire.expired()) {
return; // this commit does not change the state here 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 (!pending.acquireTimeline) if (!pendingRelease.expired()) {
surface->pending.buffer->release = makeUnique<CDRMSyncPointState>(std::move(pendingRelease));
pendingRelease = {};
}
if (protocolError())
return; return;
if (pending.acquireTimeline && pending.releaseTimeline && pending.acquireTimeline == pending.releaseTimeline) { const auto& state = pendingStates.emplace_back(makeShared<SSurfaceState>(surface->pending));
if (pending.acquirePoint >= pending.releasePoint) { surface->pending.damage.clear();
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.bufferDamage.clear();
surface->pending.rejected = true; 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; return;
}
}
// wait for the acquire timeline to materialize surf->commitPendingState(*wp.lock());
auto materialized = pending.acquireTimeline->timeline->check(pending.acquirePoint, DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE); std::erase(pendingStates, wp);
if (!materialized.has_value()) { });
LOGM(ERR, "Failed to check the acquire timeline");
resource->noMemory();
return;
}
if (materialized.value())
return;
surface->lockPendingState();
pending.acquireTimeline->timeline->addWaiter([this]() { surface->unlockPendingState(); }, pending.acquirePoint, DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE);
}); });
}
listeners.surfaceCommit = surface->events.roleCommit.registerListener([this](std::any d) { void CDRMSyncobjSurfaceResource::removeAllWaiters() {
// apply timelines if new ones have been attached, otherwise don't touch for (auto& s : pendingStates) {
// the current ones if (s && s->buffer && s->buffer->acquire && !s->buffer->acquire->expired())
if (pending.releaseTimeline) { s->buffer->acquire->resource()->timeline->removeAllWaiters();
current.releaseTimeline = pending.releaseTimeline; }
current.releasePoint = pending.releasePoint;
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;
} }
}
if (pending.acquireTimeline) { return false;
current.acquireTimeline = pending.acquireTimeline;
current.acquirePoint = pending.acquirePoint;
}
pending.releaseTimeline.reset();
pending.acquireTimeline.reset();
});
} }
bool CDRMSyncobjSurfaceResource::good() { bool CDRMSyncobjSurfaceResource::good() {
return resource->resource(); 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()) if UNLIKELY (!good())
return; return;
@ -121,16 +216,20 @@ CDRMSyncobjTimelineResource::CDRMSyncobjTimelineResource(SP<CWpLinuxDrmSyncobjTi
} }
} }
SP<CDRMSyncobjTimelineResource> CDRMSyncobjTimelineResource::fromResource(wl_resource* res) { WP<CDRMSyncobjTimelineResource> CDRMSyncobjTimelineResource::fromResource(wl_resource* res) {
auto data = (CDRMSyncobjTimelineResource*)(((CWpLinuxDrmSyncobjTimelineV1*)wl_resource_get_user_data(res))->data()); for (const auto& r : PROTO::sync->m_vTimelines) {
return data ? data->self.lock() : nullptr; if (r && r->resource && r->resource->resource() == res)
return r;
}
return {};
} }
bool CDRMSyncobjTimelineResource::good() { bool CDRMSyncobjTimelineResource::good() {
return resource->resource(); return resource->resource();
} }
CDRMSyncobjManagerResource::CDRMSyncobjManagerResource(SP<CWpLinuxDrmSyncobjManagerV1> resource_) : resource(resource_) { CDRMSyncobjManagerResource::CDRMSyncobjManagerResource(UP<CWpLinuxDrmSyncobjManagerV1>&& resource_) : resource(std::move(resource_)) {
if UNLIKELY (!good()) if UNLIKELY (!good())
return; return;
@ -154,28 +253,28 @@ CDRMSyncobjManagerResource::CDRMSyncobjManagerResource(SP<CWpLinuxDrmSyncobjMana
return; 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()) { if UNLIKELY (!RESOURCE->good()) {
resource->noMemory(); resource->noMemory();
PROTO::sync->m_vSurfaces.pop_back();
return; return;
} }
PROTO::sync->m_vSurfaces.emplace_back(RESOURCE);
SURF->syncobj = RESOURCE; SURF->syncobj = RESOURCE;
LOGM(LOG, "New linux_syncobj at {:x} for surface {:x}", (uintptr_t)RESOURCE.get(), (uintptr_t)SURF.get()); 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) { 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()) { if UNLIKELY (!RESOURCE->good()) {
resource->noMemory(); resource->noMemory();
PROTO::sync->m_vTimelines.pop_back();
return; return;
} }
PROTO::sync->m_vTimelines.emplace_back(RESOURCE);
RESOURCE->self = RESOURCE;
LOGM(LOG, "New linux_drm_timeline at {:x}", (uintptr_t)RESOURCE.get()); LOGM(LOG, "New linux_drm_timeline at {:x}", (uintptr_t)RESOURCE.get());
}); });
} }
@ -184,12 +283,10 @@ bool CDRMSyncobjManagerResource::good() {
return resource->resource(); return resource->resource();
} }
CDRMSyncobjProtocol::CDRMSyncobjProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { CDRMSyncobjProtocol::CDRMSyncobjProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name), drmFD(g_pCompositor->m_iDRMFD) {}
drmFD = g_pCompositor->m_iDRMFD;
}
void CDRMSyncobjProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { 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()) { if UNLIKELY (!RESOURCE->good()) {
wl_client_post_no_memory(client); wl_client_post_no_memory(client);

View file

@ -2,64 +2,95 @@
#include <vector> #include <vector>
#include "WaylandProtocol.hpp" #include "WaylandProtocol.hpp"
#include "helpers/sync/SyncReleaser.hpp"
#include "linux-drm-syncobj-v1.hpp" #include "linux-drm-syncobj-v1.hpp"
#include "../helpers/signal/Signal.hpp" #include "../helpers/signal/Signal.hpp"
#include "types/SurfaceState.hpp"
#include <hyprutils/os/FileDescriptor.hpp> #include <hyprutils/os/FileDescriptor.hpp>
#include <list>
class CWLSurfaceResource; class CWLSurfaceResource;
class CDRMSyncobjTimelineResource; class CDRMSyncobjTimelineResource;
class CSyncTimeline; class CSyncTimeline;
struct SSurfaceState;
class CDRMSyncPointState {
public:
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();
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;
};
class CDRMSyncobjSurfaceResource { class CDRMSyncobjSurfaceResource {
public: public:
CDRMSyncobjSurfaceResource(SP<CWpLinuxDrmSyncobjSurfaceV1> resource_, SP<CWLSurfaceResource> surface_); CDRMSyncobjSurfaceResource(UP<CWpLinuxDrmSyncobjSurfaceV1>&& resource_, SP<CWLSurfaceResource> surface_);
~CDRMSyncobjSurfaceResource();
bool good(); bool protocolError();
bool good();
WP<CWLSurfaceResource> surface;
struct {
WP<CDRMSyncobjTimelineResource> acquireTimeline, releaseTimeline;
uint64_t acquirePoint = 0, releasePoint = 0;
} current, pending;
private: private:
SP<CWpLinuxDrmSyncobjSurfaceV1> resource; void removeAllWaiters();
WP<CWLSurfaceResource> surface;
UP<CWpLinuxDrmSyncobjSurfaceV1> resource;
CDRMSyncPointState pendingAcquire;
CDRMSyncPointState pendingRelease;
std::vector<SP<SSurfaceState>> pendingStates;
struct { struct {
CHyprSignalListener surfacePrecommit; CHyprSignalListener surfacePrecommit;
CHyprSignalListener surfaceCommit;
} listeners; } listeners;
}; };
class CDRMSyncobjTimelineResource { class CDRMSyncobjTimelineResource {
public: public:
CDRMSyncobjTimelineResource(SP<CWpLinuxDrmSyncobjTimelineV1> resource_, Hyprutils::OS::CFileDescriptor&& fd_); CDRMSyncobjTimelineResource(UP<CWpLinuxDrmSyncobjTimelineV1>&& resource_, Hyprutils::OS::CFileDescriptor&& fd_);
~CDRMSyncobjTimelineResource() = default; ~CDRMSyncobjTimelineResource() = default;
static SP<CDRMSyncobjTimelineResource> fromResource(wl_resource*); static WP<CDRMSyncobjTimelineResource> fromResource(wl_resource*);
bool good(); bool good();
WP<CDRMSyncobjTimelineResource> self;
Hyprutils::OS::CFileDescriptor fd; Hyprutils::OS::CFileDescriptor fd;
SP<CSyncTimeline> timeline; SP<CSyncTimeline> timeline;
private: private:
SP<CWpLinuxDrmSyncobjTimelineV1> resource; UP<CWpLinuxDrmSyncobjTimelineV1> resource;
}; };
class CDRMSyncobjManagerResource { class CDRMSyncobjManagerResource {
public: public:
CDRMSyncobjManagerResource(SP<CWpLinuxDrmSyncobjManagerV1> resource_); CDRMSyncobjManagerResource(UP<CWpLinuxDrmSyncobjManagerV1>&& resource_);
~CDRMSyncobjManagerResource() = default;
bool good(); bool good();
private: private:
SP<CWpLinuxDrmSyncobjManagerV1> resource; UP<CWpLinuxDrmSyncobjManagerV1> resource;
}; };
class CDRMSyncobjProtocol : public IWaylandProtocol { class CDRMSyncobjProtocol : public IWaylandProtocol {
public: public:
CDRMSyncobjProtocol(const wl_interface* iface, const int& ver, const std::string& name); 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); 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); void destroyResource(CDRMSyncobjSurfaceResource* resource);
// //
std::vector<SP<CDRMSyncobjManagerResource>> m_vManagers; std::vector<UP<CDRMSyncobjManagerResource>> m_vManagers;
std::vector<SP<CDRMSyncobjTimelineResource>> m_vTimelines; std::vector<UP<CDRMSyncobjTimelineResource>> m_vTimelines;
std::vector<SP<CDRMSyncobjSurfaceResource>> m_vSurfaces; std::vector<UP<CDRMSyncobjSurfaceResource>> m_vSurfaces;
// //
int drmFD = -1; int drmFD = -1;

View file

@ -111,6 +111,9 @@ CLinuxDMABuffer::CLinuxDMABuffer(uint32_t id, wl_client* client, Aquamarine::SDM
} }
CLinuxDMABuffer::~CLinuxDMABuffer() { CLinuxDMABuffer::~CLinuxDMABuffer() {
if (buffer && buffer->resource)
buffer->resource->sendRelease();
buffer.reset(); buffer.reset();
listeners.bufferResourceDestroy.reset(); listeners.bufferResourceDestroy.reset();
} }

View file

@ -97,6 +97,7 @@ class CLinuxDMABUFFeedbackResource {
class CLinuxDMABUFResource { class CLinuxDMABUFResource {
public: public:
CLinuxDMABUFResource(SP<CZwpLinuxDmabufV1> resource_); CLinuxDMABUFResource(SP<CZwpLinuxDmabufV1> resource_);
~CLinuxDMABUFResource() = default;
bool good(); bool good();
void sendMods(); void sendMods();

View file

@ -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"); Debug::log(ERR, "Failed creating a single pixel texture: null texture id");
} }
CSinglePixelBuffer::~CSinglePixelBuffer() {
if (resource)
resource->sendRelease();
}
Aquamarine::eBufferCapability CSinglePixelBuffer::caps() { Aquamarine::eBufferCapability CSinglePixelBuffer::caps() {
return Aquamarine::eBufferCapability::BUFFER_CAPABILITY_DATAPTR; return Aquamarine::eBufferCapability::BUFFER_CAPABILITY_DATAPTR;
} }

View file

@ -9,7 +9,7 @@
class CSinglePixelBuffer : public IHLBuffer { class CSinglePixelBuffer : public IHLBuffer {
public: public:
CSinglePixelBuffer(uint32_t id, wl_client* client, CHyprColor col); CSinglePixelBuffer(uint32_t id, wl_client* client, CHyprColor col);
virtual ~CSinglePixelBuffer() = default; virtual ~CSinglePixelBuffer();
virtual Aquamarine::eBufferCapability caps(); virtual Aquamarine::eBufferCapability caps();
virtual Aquamarine::eBufferType type(); virtual Aquamarine::eBufferType type();

View file

@ -113,8 +113,8 @@ CWLSurfaceResource::CWLSurfaceResource(SP<CWlSurface> resource_) : resource(reso
return; return;
} }
if (stateLocks <= 0) if (!syncobj)
commitPendingState(); 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}); }); 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); return surfaceDamage.scale(current.scale).transform(wlTransformToHyprutils(invertTransform(current.transform)), trc.x, trc.y).add(current.bufferDamage);
} }
void CWLSurfaceResource::lockPendingState() { void CWLSurfaceResource::commitPendingState(SSurfaceState& state) {
stateLocks++; if (state.newBuffer) {
} state.newBuffer = false;
current = state;
void CWLSurfaceResource::unlockPendingState() { state.damage.clear();
stateLocks--; state.bufferDamage.clear();
if (stateLocks <= 0) state.buffer.reset();
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);
if (current.texture) if (current.texture)
current.texture->m_eTransform = wlTransformToHyprutils(current.transform); current.texture->m_eTransform = wlTransformToHyprutils(current.transform);
@ -463,14 +448,6 @@ void CWLSurfaceResource::commitPendingState() {
// TODO: don't update the entire texture // TODO: don't update the entire texture
if (role->role() == SURFACE_ROLE_CURSOR && !DAMAGE.empty()) if (role->role() == SURFACE_ROLE_CURSOR && !DAMAGE.empty())
updateCursorShm(DAMAGE); 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 // TODO: we should _accumulate_ and not replace above if sync
@ -494,11 +471,16 @@ void CWLSurfaceResource::commitPendingState() {
nullptr); 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) { 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) if UNLIKELY (!buf)
return; return;

View file

@ -16,6 +16,7 @@
#include "../../helpers/math/Math.hpp" #include "../../helpers/math/Math.hpp"
#include "../types/Buffer.hpp" #include "../types/Buffer.hpp"
#include "../types/SurfaceRole.hpp" #include "../types/SurfaceRole.hpp"
#include "../types/SurfaceState.hpp"
class CWLOutputResource; class CWLOutputResource;
class CMonitor; class CMonitor;
@ -77,42 +78,15 @@ class CWLSurfaceResource {
Vector2D sourceSize(); Vector2D sourceSize();
struct { struct {
CSignal precommit; // before commit CSignal precommit; // before commit
CSignal roleCommit; // commit for role objects, before regular commit CSignal commit; // after commit
CSignal commit; // after commit
CSignal map; CSignal map;
CSignal unmap; CSignal unmap;
CSignal newSubsurface; CSignal newSubsurface;
CSignal destroy; CSignal destroy;
} events; } events;
struct SState { SSurfaceState current, pending;
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;
std::vector<SP<CWLCallbackResource>> callbacks; std::vector<SP<CWLCallbackResource>> callbacks;
WP<CWLSurfaceResource> self; WP<CWLSurfaceResource> self;
@ -130,28 +104,20 @@ class CWLSurfaceResource {
SP<CWLSurfaceResource> findFirstPreorder(std::function<bool(SP<CWLSurfaceResource>)> fn); SP<CWLSurfaceResource> findFirstPreorder(std::function<bool(SP<CWLSurfaceResource>)> fn);
CRegion accumulateCurrentBufferDamage(); CRegion accumulateCurrentBufferDamage();
void presentFeedback(timespec* when, PHLMONITOR pMonitor, bool discarded = false); void presentFeedback(timespec* when, PHLMONITOR pMonitor, bool discarded = false);
void lockPendingState(); void commitPendingState(SSurfaceState& state);
void unlockPendingState();
// returns a pair: found surface (null if not found) and surface local coords. // returns a pair: found surface (null if not found) and surface local coords.
// localCoords param is relative to 0,0 of this surface // localCoords param is relative to 0,0 of this surface
std::pair<SP<CWLSurfaceResource>, Vector2D> at(const Vector2D& localCoords, bool allowsInput = false); std::pair<SP<CWLSurfaceResource>, Vector2D> at(const Vector2D& localCoords, bool allowsInput = false);
private: private:
SP<CWlSurface> resource; SP<CWlSurface> resource;
wl_client* pClient = nullptr; 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;
void destroy(); void destroy();
void releaseBuffers(bool onlyCurrent = true); void releaseBuffers(bool onlyCurrent = true);
void dropPendingBuffer(); void dropPendingBuffer();
void dropCurrentBuffer(); void dropCurrentBuffer();
void commitPendingState();
void bfHelper(std::vector<SP<CWLSurfaceResource>> const& nodes, std::function<void(SP<CWLSurfaceResource>, const Vector2D&, void*)> fn, void* data); 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); SP<CWLSurfaceResource> findFirstPreorderHelper(SP<CWLSurfaceResource> root, std::function<bool(SP<CWLSurfaceResource>)> fn);
void updateCursorShm(CRegion damage = CBox{0, 0, INT16_MAX, INT16_MAX}); void updateCursorShm(CRegion damage = CBox{0, 0, INT16_MAX, INT16_MAX});

View file

@ -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"); Debug::log(ERR, "Failed creating a shm texture: null texture id");
} }
CWLSHMBuffer::~CWLSHMBuffer() {
if (resource)
resource->sendRelease();
}
Aquamarine::eBufferCapability CWLSHMBuffer::caps() { Aquamarine::eBufferCapability CWLSHMBuffer::caps() {
return Aquamarine::eBufferCapability::BUFFER_CAPABILITY_DATAPTR; return Aquamarine::eBufferCapability::BUFFER_CAPABILITY_DATAPTR;
} }

View file

@ -33,7 +33,7 @@ class CSHMPool {
class CWLSHMBuffer : public IHLBuffer { class CWLSHMBuffer : public IHLBuffer {
public: public:
CWLSHMBuffer(SP<CWLSHMPoolResource> pool, uint32_t id, int32_t offset, const Vector2D& size, int32_t stride, uint32_t fmt); 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::eBufferCapability caps();
virtual Aquamarine::eBufferType type(); virtual Aquamarine::eBufferType type();

View file

@ -18,8 +18,10 @@ void IHLBuffer::unlock() {
ASSERT(nLocks >= 0); ASSERT(nLocks >= 0);
if (nLocks == 0) if (nLocks == 0) {
sendRelease(); sendRelease();
syncReleaser.reset();
}
} }
bool IHLBuffer::locked() { bool IHLBuffer::locked() {
@ -43,7 +45,7 @@ CHLBufferReference::CHLBufferReference(SP<IHLBuffer> buffer_, SP<CWLSurfaceResou
} }
CHLBufferReference::~CHLBufferReference() { CHLBufferReference::~CHLBufferReference() {
if (buffer.expired()) if (!buffer)
return; return;
buffer->unlock(); buffer->unlock();

View file

@ -3,6 +3,7 @@
#include "../../defines.hpp" #include "../../defines.hpp"
#include "../../render/Texture.hpp" #include "../../render/Texture.hpp"
#include "./WLBuffer.hpp" #include "./WLBuffer.hpp"
#include "protocols/DRMSyncobj.hpp"
#include <aquamarine/buffer/Buffer.hpp> #include <aquamarine/buffer/Buffer.hpp>
@ -26,6 +27,7 @@ class IHLBuffer : public Aquamarine::IBuffer {
SP<CTexture> texture; SP<CTexture> texture;
bool opaque = false; bool opaque = false;
SP<CWLBufferResource> resource; SP<CWLBufferResource> resource;
UP<CSyncReleaser> syncReleaser;
struct { struct {
CHyprSignalListener backendRelease; CHyprSignalListener backendRelease;
@ -43,8 +45,9 @@ class CHLBufferReference {
CHLBufferReference(SP<IHLBuffer> buffer, SP<CWLSurfaceResource> surface); CHLBufferReference(SP<IHLBuffer> buffer, SP<CWLSurfaceResource> surface);
~CHLBufferReference(); ~CHLBufferReference();
WP<IHLBuffer> buffer; WP<IHLBuffer> buffer;
SP<CSyncReleaser> releaser; UP<CDRMSyncPointState> acquire;
UP<CDRMSyncPointState> release;
private: private:
WP<CWLSurfaceResource> surface; WP<CWLSurfaceResource> surface;

View file

@ -36,6 +36,9 @@ CDMABuffer::CDMABuffer(uint32_t id, wl_client* client, Aquamarine::SDMABUFAttrs
} }
CDMABuffer::~CDMABuffer() { CDMABuffer::~CDMABuffer() {
if (resource)
resource->sendRelease();
closeFDs(); closeFDs();
} }

View file

@ -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 = {};
}
};

View file

@ -5,7 +5,7 @@
#include "../../helpers/sync/SyncTimeline.hpp" #include "../../helpers/sync/SyncTimeline.hpp"
#include <xf86drm.h> #include <xf86drm.h>
CWLBufferResource::CWLBufferResource(SP<CWlBuffer> resource_) : resource(resource_) { CWLBufferResource::CWLBufferResource(WP<CWlBuffer> resource_) : resource(resource_.lock()) {
if UNLIKELY (!good()) if UNLIKELY (!good())
return; return;
@ -40,7 +40,7 @@ SP<CWLBufferResource> CWLBufferResource::fromResource(wl_resource* res) {
return data ? data->self.lock() : nullptr; 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)); auto p = SP<CWLBufferResource>(new CWLBufferResource(resource));
p->self = p; p->self = p;
return p; return p;

View file

@ -11,7 +11,7 @@ class CWLSurfaceResource;
class CWLBufferResource { class CWLBufferResource {
public: public:
static SP<CWLBufferResource> create(SP<CWlBuffer> resource); static SP<CWLBufferResource> create(WP<CWlBuffer> resource);
static SP<CWLBufferResource> fromResource(wl_resource* res); static SP<CWLBufferResource> fromResource(wl_resource* res);
bool good(); bool good();
@ -23,7 +23,7 @@ class CWLBufferResource {
WP<CWLBufferResource> self; WP<CWLBufferResource> self;
private: private:
CWLBufferResource(SP<CWlBuffer> resource_); CWLBufferResource(WP<CWlBuffer> resource_);
SP<CWlBuffer> resource; SP<CWlBuffer> resource;

View file

@ -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, 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()!"); 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); scissor(nullptr);
} }
void CHyprOpenGLImpl::renderTextureInternalWithDamage(SP<CTexture> tex, const CBox& box, float alpha, const CRegion& damage, int round, float roundingPower, bool discardActive, 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(m_RenderData.pMonitor, "Tried to render texture without begin()!");
RASSERT((tex->m_iTexID > 0), "Attempted to draw nullptr texture!"); 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) if (m_bEndFrame || TRANSFORMS_MATCH)
TRANSFORM = wlTransformToHyprutils(invertTransform(m_RenderData.pMonitor->transform)); TRANSFORM = wlTransformToHyprutils(invertTransform(m_RenderData.pMonitor->transform));
Mat3x3 matrix = m_RenderData.monitorProjection.projectBox(newBox, TRANSFORM, newBox.rot); Mat3x3 matrix = m_RenderData.monitorProjection.projectBox(newBox, TRANSFORM, newBox.rot);
Mat3x3 glMatrix = m_RenderData.projection.copy().multiply(matrix); 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;
}
}
CShader* shader = nullptr; CShader* shader = nullptr;
@ -2915,11 +2908,11 @@ std::vector<SDRMFormat> CHyprOpenGLImpl::getDRMFormats() {
return drmFormats; return drmFormats;
} }
SP<CEGLSync> CHyprOpenGLImpl::createEGLSync(CFileDescriptor fenceFD) { SP<CEGLSync> CHyprOpenGLImpl::createEGLSync(int fenceFD) {
std::vector<EGLint> attribs; std::vector<EGLint> attribs;
CFileDescriptor dupFd; CFileDescriptor dupFd;
if (fenceFD.isValid()) { if (fenceFD >= 0) {
dupFd = fenceFD.duplicate(); dupFd = CFileDescriptor{fcntl(fenceFD, F_DUPFD_CLOEXEC, 0)};
if (!dupFd.isValid()) { if (!dupFd.isValid()) {
Debug::log(ERR, "createEGLSync: dup failed"); Debug::log(ERR, "createEGLSync: dup failed");
return nullptr; return nullptr;
@ -2953,27 +2946,6 @@ SP<CEGLSync> CHyprOpenGLImpl::createEGLSync(CFileDescriptor fenceFD) {
return eglsync; 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) { void SRenderModifData::applyToBox(CBox& box) {
if (!enabled) if (!enabled)
return; return;
@ -3043,17 +3015,10 @@ CEGLSync::~CEGLSync() {
Debug::log(ERR, "eglDestroySyncKHR failed"); Debug::log(ERR, "eglDestroySyncKHR failed");
} }
CFileDescriptor&& CEGLSync::takeFD() {
return std::move(m_iFd);
}
CFileDescriptor& CEGLSync::fd() { CFileDescriptor& CEGLSync::fd() {
return m_iFd; return 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;
}

View file

@ -148,10 +148,10 @@ class CEGLSync {
public: public:
~CEGLSync(); ~CEGLSync();
EGLSyncKHR sync = nullptr; EGLSyncKHR sync = nullptr;
Hyprutils::OS::CFileDescriptor& fd(); Hyprutils::OS::CFileDescriptor&& takeFD();
bool wait(); Hyprutils::OS::CFileDescriptor& fd();
private: private:
CEGLSync() = default; 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 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 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, 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, 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); 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); 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); uint32_t getPreferredReadFormat(PHLMONITOR pMonitor);
std::vector<SDRMFormat> getDRMFormats(); std::vector<SDRMFormat> getDRMFormats();
EGLImageKHR createEGLImage(const Aquamarine::SDMABUFAttrs& attrs); EGLImageKHR createEGLImage(const Aquamarine::SDMABUFAttrs& attrs);
SP<CEGLSync> createEGLSync(Hyprutils::OS::CFileDescriptor fenceFD); SP<CEGLSync> createEGLSync(int fence = -1);
bool waitForTimelinePoint(SP<CSyncTimeline> timeline, uint64_t point);
SCurrentRenderData m_RenderData; SCurrentRenderData m_RenderData;
@ -315,7 +314,7 @@ class CHyprOpenGLImpl {
CFramebuffer* blurMainFramebufferWithDamage(float a, CRegion* damage); 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, 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 renderTexturePrimitive(SP<CTexture> tex, const CBox& box);
void renderSplash(cairo_t* const, cairo_surface_t* const, double offset, const Vector2D& size); void renderSplash(cairo_t* const, cairo_surface_t* const, double offset, const Vector2D& size);

View file

@ -1450,13 +1450,6 @@ static hdr_output_metadata createHDRMetadata(SImageDescription settings, A
bool CHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) { bool CHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) {
pMonitor->commitSeq++; 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"); static auto PPASS = CConfigValue<Hyprlang::INT>("render:cm_fs_passthrough");
const bool PHDR = pMonitor->imageDescription.transferFunction == CM_TRANSFER_FUNCTION_ST2084_PQ; 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(); bool ok = pMonitor->state.commit();
if (!ok) { if (!ok) {
if (inFD.isValid()) { if (pMonitor->inFence.isValid()) {
Debug::log(TRACE, "Monitor state commit failed, retrying without a fence"); Debug::log(TRACE, "Monitor state commit failed, retrying without a fence");
pMonitor->output->state->resetExplicitFences(); pMonitor->output->state->resetExplicitFences();
ok = pMonitor->state.commit(); ok = pMonitor->state.commit();
@ -1537,21 +1530,20 @@ bool CHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) {
return ok; return ok;
Debug::log(TRACE, "Explicit: {} presented", explicitPresented.size()); Debug::log(TRACE, "Explicit: {} presented", explicitPresented.size());
auto sync = g_pHyprOpenGL->createEGLSync({}); auto sync = g_pHyprOpenGL->createEGLSync(pMonitor->inFence.get());
if (!sync) if (!sync)
Debug::log(TRACE, "Explicit: can't add sync, EGLSync failed"); Debug::log(TRACE, "Explicit: can't add sync, EGLSync failed");
else { else {
for (auto const& e : explicitPresented) { 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; continue;
e->current.buffer->releaser->addReleaseSync(sync); e->current.buffer->buffer->syncReleaser->addReleaseSync(sync);
} }
} }
explicitPresented.clear(); explicitPresented.clear();
pMonitor->output->state->resetExplicitFences(); pMonitor->output->state->resetExplicitFences();
return ok; return ok;
@ -2269,7 +2261,7 @@ void CHyprRenderer::endRender() {
auto explicitOptions = getExplicitSyncSettings(PMONITOR->output); auto explicitOptions = getExplicitSyncSettings(PMONITOR->output);
if (PMONITOR->inTimeline && explicitOptions.explicitEnabled && explicitOptions.explicitKMSEnabled) { if (PMONITOR->inTimeline && explicitOptions.explicitEnabled && explicitOptions.explicitKMSEnabled) {
auto sync = g_pHyprOpenGL->createEGLSync({}); auto sync = g_pHyprOpenGL->createEGLSync();
if (!sync) { if (!sync) {
Debug::log(ERR, "renderer: couldn't create an EGLSync for out in endRender"); Debug::log(ERR, "renderer: couldn't create an EGLSync for out in endRender");
return; return;
@ -2281,13 +2273,13 @@ void CHyprRenderer::endRender() {
return; return;
} }
auto fd = PMONITOR->inTimeline->exportAsSyncFileFD(PMONITOR->commitSeq); PMONITOR->inFence = CFileDescriptor{PMONITOR->inTimeline->exportAsSyncFileFD(PMONITOR->commitSeq)};
if (!fd.isValid()) { if (!PMONITOR->inFence.isValid()) {
Debug::log(ERR, "renderer: couldn't export from sync timeline in endRender"); Debug::log(ERR, "renderer: couldn't export from sync timeline in endRender");
return; return;
} }
PMONITOR->output->state->setExplicitInFence(fd.take()); PMONITOR->output->state->setExplicitInFence(PMONITOR->inFence.get());
} else { } else {
if (isNvidia() && *PNVIDIAANTIFLICKER) if (isNvidia() && *PNVIDIAANTIFLICKER)
glFinish(); glFinish();

View file

@ -51,14 +51,6 @@ void CSurfacePassElement::draw(const CRegion& damage) {
if (!TEXTURE->m_iTexID) if (!TEXTURE->m_iTexID)
return; 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; const auto INTERACTIVERESIZEINPROGRESS = data.pWindow && g_pInputManager->currentlyDraggedWindow && g_pInputManager->dragMode == MBIND_RESIZE;
TRACY_GPU_ZONE("RenderSurface"); TRACY_GPU_ZONE("RenderSurface");