From f4c8789aec7150c6573ad69927eb53ec9f710dcf Mon Sep 17 00:00:00 2001 From: Vaxry Date: Sat, 6 Jul 2024 17:38:27 +0200 Subject: [PATCH] explicit sync --- CMakeLists.txt | 1 + src/helpers/Monitor.cpp | 6 + src/helpers/Monitor.hpp | 9 +- src/helpers/sync/SyncTimeline.cpp | 187 ++++++++++++++++++++ src/helpers/sync/SyncTimeline.hpp | 46 +++++ src/managers/ProtocolManager.cpp | 2 + src/managers/eventLoop/EventLoopManager.hpp | 2 + src/protocols/DRMSyncobj.cpp | 182 +++++++++++++++++++ src/protocols/DRMSyncobj.hpp | 82 +++++++++ src/protocols/Viewporter.cpp | 29 +-- src/protocols/Viewporter.hpp | 5 +- src/protocols/core/Compositor.cpp | 133 +++++++++----- src/protocols/core/Compositor.hpp | 12 +- src/render/OpenGL.cpp | 64 +++++++ src/render/OpenGL.hpp | 20 +++ src/render/Renderer.cpp | 61 ++++--- src/render/Renderer.hpp | 2 + 17 files changed, 757 insertions(+), 86 deletions(-) create mode 100644 src/helpers/sync/SyncTimeline.cpp create mode 100644 src/helpers/sync/SyncTimeline.hpp create mode 100644 src/protocols/DRMSyncobj.cpp create mode 100644 src/protocols/DRMSyncobj.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 54271fc3..1db303f3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -296,6 +296,7 @@ protocolNew("staging/xwayland-shell" "xwayland-shell-v1" false) protocolNew("stable/viewporter" "viewporter" false) protocolNew("stable/linux-dmabuf" "linux-dmabuf-v1" false) protocolNew("staging/drm-lease" "drm-lease-v1" false) +protocolNew("staging/linux-drm-syncobj" "linux-drm-syncobj-v1" false) protocolWayland() diff --git a/src/helpers/Monitor.cpp b/src/helpers/Monitor.cpp index accc7783..495e772e 100644 --- a/src/helpers/Monitor.cpp +++ b/src/helpers/Monitor.cpp @@ -10,6 +10,7 @@ #include "../protocols/DRMLease.hpp" #include "../protocols/core/Output.hpp" #include "../managers/PointerManager.hpp" +#include "sync/SyncTimeline.hpp" #include using namespace Hyprutils::String; @@ -29,6 +30,11 @@ CMonitor::~CMonitor() { void CMonitor::onConnect(bool noRule) { + if (output->supportsExplicit) { + inTimeline = CSyncTimeline::create(g_pCompositor->m_iDRMFD); + outTimeline = CSyncTimeline::create(g_pCompositor->m_iDRMFD); + } + listeners.frame = output->events.frame.registerListener([this](std::any d) { Events::listener_monitorFrame(this, nullptr); }); listeners.destroy = output->events.destroy.registerListener([this](std::any d) { Events::listener_monitorDestroy(this, nullptr); }); listeners.commit = output->events.commit.registerListener([this](std::any d) { Events::listener_monitorCommit(this, nullptr); }); diff --git a/src/helpers/Monitor.hpp b/src/helpers/Monitor.hpp index 640eabe7..7488d7dc 100644 --- a/src/helpers/Monitor.hpp +++ b/src/helpers/Monitor.hpp @@ -40,6 +40,7 @@ struct SMonitorRule { }; class CMonitor; +class CSyncTimeline; class CMonitorState { public: @@ -116,7 +117,13 @@ class CMonitor { SMonitorRule activeMonitorRule; - WP self; + // explicit sync + SP inTimeline; + SP outTimeline; + uint64_t lastWaitPoint = 0; + uint64_t commitSeq = 0; + + WP self; // mirroring CMonitor* pMirrorOf = nullptr; diff --git a/src/helpers/sync/SyncTimeline.cpp b/src/helpers/sync/SyncTimeline.cpp new file mode 100644 index 00000000..1a7286e0 --- /dev/null +++ b/src/helpers/sync/SyncTimeline.cpp @@ -0,0 +1,187 @@ +#include "SyncTimeline.hpp" +#include "../../defines.hpp" +#include "../../managers/eventLoop/EventLoopManager.hpp" + +#include +#include + +SP CSyncTimeline::create(int drmFD_) { + auto timeline = SP(new CSyncTimeline); + timeline->drmFD = drmFD_; + timeline->self = timeline; + + if (drmSyncobjCreate(drmFD_, 0, &timeline->handle)) { + Debug::log(ERR, "CSyncTimeline: failed to create a drm syncobj??"); + return nullptr; + } + + return timeline; +} + +SP CSyncTimeline::create(int drmFD_, int drmSyncobjFD) { + auto timeline = SP(new CSyncTimeline); + timeline->drmFD = drmFD_; + timeline->self = timeline; + + if (drmSyncobjFDToHandle(drmFD_, drmSyncobjFD, &timeline->handle)) { + Debug::log(ERR, "CSyncTimeline: failed to create a drm syncobj from fd??"); + return nullptr; + } + + return timeline; +} + +CSyncTimeline::~CSyncTimeline() { + if (handle == 0) + return; + + drmSyncobjDestroy(drmFD, handle); +} + +std::optional CSyncTimeline::check(uint64_t point, uint32_t flags) { +#ifdef __FreeBSD__ + constexpr int ETIME_ERR = ETIMEDOUT; +#else + constexpr int ETIME_ERR = ETIME; +#endif + + uint32_t signaled = 0; + int ret = drmSyncobjTimelineWait(drmFD, &handle, &point, 1, 0, flags, &signaled); + if (ret != 0 && ret != -ETIME_ERR) { + Debug::log(ERR, "CSyncTimeline::check: drmSyncobjTimelineWait failed"); + return std::nullopt; + } + + return ret == 0; +} + +static int handleWaiterFD(int fd, uint32_t mask, void* data) { + auto waiter = (CSyncTimeline::SWaiter*)data; + + if (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR)) { + Debug::log(ERR, "handleWaiterFD: eventfd error"); + return 0; + } + + if (mask & WL_EVENT_READABLE) { + uint64_t value = 0; + if (read(fd, &value, sizeof(value)) <= 0) + Debug::log(ERR, "handleWaiterFD: failed to read from eventfd"); + } + + wl_event_source_remove(waiter->source); + waiter->source = nullptr; + + if (waiter->fn) + waiter->fn(); + + if (waiter->timeline) + waiter->timeline->removeWaiter(waiter); + + return 0; +} + +bool CSyncTimeline::addWaiter(const std::function& waiter, uint64_t point, uint32_t flags) { + auto w = makeShared(); + w->fn = waiter; + w->timeline = self; + + int eventFD = eventfd(0, EFD_CLOEXEC); + + if (eventFD < 0) { + Debug::log(ERR, "CSyncTimeline::addWaiter: failed to acquire an eventfd"); + return false; + } + + drm_syncobj_eventfd syncobjEventFD = { + .handle = handle, + .flags = flags, + .point = point, + .fd = eventFD, + }; + + if (drmIoctl(drmFD, DRM_IOCTL_SYNCOBJ_EVENTFD, &syncobjEventFD) != 0) { + Debug::log(ERR, "CSyncTimeline::addWaiter: drmIoctl failed"); + close(eventFD); + return false; + } + + w->source = wl_event_loop_add_fd(g_pEventLoopManager->m_sWayland.loop, eventFD, WL_EVENT_READABLE, ::handleWaiterFD, w.get()); + if (!w->source) { + Debug::log(ERR, "CSyncTimeline::addWaiter: wl_event_loop_add_fd failed"); + close(eventFD); + return false; + } + + waiters.emplace_back(w); + + return true; +} + +void CSyncTimeline::removeWaiter(SWaiter* w) { + if (w->source) { + wl_event_source_remove(w->source); + w->source = nullptr; + } + std::erase_if(waiters, [w](const auto& e) { return e.get() == w; }); +} + +int CSyncTimeline::exportAsSyncFileFD(uint64_t src) { + int sync = -1; + + uint32_t syncHandle = 0; + if (drmSyncobjCreate(drmFD, 0, &syncHandle)) { + Debug::log(ERR, "exportAsSyncFileFD: drmSyncobjCreate failed"); + return -1; + } + + if (drmSyncobjTransfer(drmFD, syncHandle, 0, handle, src, 0)) { + Debug::log(ERR, "exportAsSyncFileFD: drmSyncobjTransfer failed"); + drmSyncobjDestroy(drmFD, syncHandle); + return -1; + } + + if (drmSyncobjExportSyncFile(drmFD, syncHandle, &sync)) { + Debug::log(ERR, "exportAsSyncFileFD: drmSyncobjExportSyncFile failed"); + drmSyncobjDestroy(drmFD, syncHandle); + return -1; + } + + drmSyncobjDestroy(drmFD, syncHandle); + return sync; +} + +bool CSyncTimeline::importFromSyncFileFD(uint64_t dst, int fd) { + uint32_t syncHandle = 0; + + if (drmSyncobjCreate(drmFD, 0, &syncHandle)) { + Debug::log(ERR, "importFromSyncFileFD: drmSyncobjCreate failed"); + return false; + } + + if (drmSyncobjImportSyncFile(drmFD, syncHandle, fd)) { + Debug::log(ERR, "importFromSyncFileFD: drmSyncobjImportSyncFile failed"); + drmSyncobjDestroy(drmFD, syncHandle); + return -1; + } + + if (drmSyncobjTransfer(drmFD, handle, dst, syncHandle, 0, 0)) { + Debug::log(ERR, "importFromSyncFileFD: drmSyncobjTransfer failed"); + drmSyncobjDestroy(drmFD, syncHandle); + return -1; + } + + drmSyncobjDestroy(drmFD, syncHandle); + return true; +} + +bool CSyncTimeline::transfer(SP from, uint64_t fromPoint, uint64_t toPoint) { + ASSERT(from->drmFD == drmFD); + + if (drmSyncobjTransfer(drmFD, handle, toPoint, from->handle, fromPoint, 0)) { + Debug::log(ERR, "CSyncTimeline::transfer: drmSyncobjTransfer failed"); + return false; + } + + return true; +} diff --git a/src/helpers/sync/SyncTimeline.hpp b/src/helpers/sync/SyncTimeline.hpp new file mode 100644 index 00000000..4bb8019b --- /dev/null +++ b/src/helpers/sync/SyncTimeline.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include +#include + +/* + Hyprland synchronization timelines are based on the wlroots' ones, which + are based on Vk timeline semaphores: https://www.khronos.org/blog/vulkan-timeline-semaphores +*/ + +struct wl_event_source; + +class CSyncTimeline { + public: + static SP create(int drmFD_); + static SP create(int drmFD_, int drmSyncobjFD); + ~CSyncTimeline(); + + struct SWaiter { + std::function fn; + wl_event_source* source = nullptr; + WP timeline; + }; + + // check if the timeline point has been signaled + // flags: DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT or DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE + // std::nullopt on fail + std::optional check(uint64_t point, uint32_t flags); + + bool addWaiter(const std::function& waiter, uint64_t point, uint32_t flags); + void removeWaiter(SWaiter*); + int exportAsSyncFileFD(uint64_t src); + bool importFromSyncFileFD(uint64_t dst, int fd); + bool transfer(SP from, uint64_t fromPoint, uint64_t toPoint); + + int drmFD = -1; + uint32_t handle = 0; + WP self; + + private: + CSyncTimeline() = default; + + std::vector> waiters; +}; diff --git a/src/managers/ProtocolManager.cpp b/src/managers/ProtocolManager.cpp index 47cf1767..db81e0ae 100644 --- a/src/managers/ProtocolManager.cpp +++ b/src/managers/ProtocolManager.cpp @@ -36,6 +36,7 @@ #include "../protocols/MesaDRM.hpp" #include "../protocols/LinuxDMABUF.hpp" #include "../protocols/DRMLease.hpp" +#include "../protocols/DRMSyncobj.hpp" #include "../protocols/core/Seat.hpp" #include "../protocols/core/DataDevice.hpp" @@ -131,6 +132,7 @@ CProtocolManager::CProtocolManager() { PROTO::dataWlr = std::make_unique(&zwlr_data_control_manager_v1_interface, 2, "DataDeviceWlr"); PROTO::primarySelection = std::make_unique(&zwp_primary_selection_device_manager_v1_interface, 1, "PrimarySelection"); PROTO::xwaylandShell = std::make_unique(&xwayland_shell_v1_interface, 1, "XWaylandShell"); + PROTO::sync = std::make_unique(&wp_linux_drm_syncobj_manager_v1_interface, 1, "DRMSyncobj"); for (auto& b : g_pCompositor->m_pAqBackend->getImplementations()) { if (b->type() != Aquamarine::AQ_BACKEND_DRM) diff --git a/src/managers/eventLoop/EventLoopManager.hpp b/src/managers/eventLoop/EventLoopManager.hpp index eff281d6..6f3e8529 100644 --- a/src/managers/eventLoop/EventLoopManager.hpp +++ b/src/managers/eventLoop/EventLoopManager.hpp @@ -50,6 +50,8 @@ class CEventLoopManager { SIdleData m_sIdle; std::vector> aqPollFDs; + + friend class CSyncTimeline; }; inline std::unique_ptr g_pEventLoopManager; \ No newline at end of file diff --git a/src/protocols/DRMSyncobj.cpp b/src/protocols/DRMSyncobj.cpp new file mode 100644 index 00000000..69b9fae2 --- /dev/null +++ b/src/protocols/DRMSyncobj.cpp @@ -0,0 +1,182 @@ +#include "DRMSyncobj.hpp" +#include + +#include "core/Compositor.hpp" +#include "../helpers/sync/SyncTimeline.hpp" +#include "../Compositor.hpp" + +#include + +#define LOGM PROTO::sync->protoLog + +CDRMSyncobjSurfaceResource::CDRMSyncobjSurfaceResource(SP resource_, SP surface_) : surface(surface_), resource(resource_) { + if (!good()) + return; + + resource->setData(this); + + resource->setOnDestroy([this](CWpLinuxDrmSyncobjSurfaceV1* r) { PROTO::sync->destroyResource(this); }); + resource->setDestroy([this](CWpLinuxDrmSyncobjSurfaceV1* r) { PROTO::sync->destroyResource(this); }); + + resource->setSetAcquirePoint([this](CWpLinuxDrmSyncobjSurfaceV1* r, wl_resource* timeline_, uint32_t hi, uint32_t lo) { + if (!surface) { + resource->error(WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_SURFACE, "Surface is gone"); + return; + } + + auto timeline = CDRMSyncobjTimelineResource::fromResource(timeline_); + acquireTimeline = timeline; + acquirePoint = ((uint64_t)hi << 32) | (uint64_t)lo; + }); + + resource->setSetReleasePoint([this](CWpLinuxDrmSyncobjSurfaceV1* r, wl_resource* timeline_, uint32_t hi, uint32_t lo) { + if (!surface) { + resource->error(WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_SURFACE, "Surface is gone"); + return; + } + + auto timeline = CDRMSyncobjTimelineResource::fromResource(timeline_); + releaseTimeline = timeline; + releasePoint = ((uint64_t)hi << 32) | (uint64_t)lo; + }); + + listeners.surfacePrecommit = surface->events.precommit.registerListener([this](std::any d) { + if (!!acquireTimeline != !!releaseTimeline) { + resource->error(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 ((acquireTimeline || releaseTimeline) && !surface->pending.buffer) { + resource->error(WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_BUFFER, "Missing buffer"); + surface->pending.rejected = true; + return; + } + + if (!acquireTimeline) + return; + + // wait for the acquire timeline to materialize + auto materialized = acquireTimeline->timeline->check(acquirePoint, DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE); + if (!materialized.has_value()) { + resource->noMemory(); + return; + } + + if (materialized) + return; + + surface->lockPendingState(); + acquireTimeline->timeline->addWaiter([this]() { surface->unlockPendingState(); }, acquirePoint, DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE); + }); +} + +bool CDRMSyncobjSurfaceResource::good() { + return resource->resource(); +} + +CDRMSyncobjTimelineResource::CDRMSyncobjTimelineResource(SP resource_, int fd_) : fd(fd_), resource(resource_) { + if (!good()) + return; + + resource->setData(this); + + resource->setOnDestroy([this](CWpLinuxDrmSyncobjTimelineV1* r) { PROTO::sync->destroyResource(this); }); + resource->setDestroy([this](CWpLinuxDrmSyncobjTimelineV1* r) { PROTO::sync->destroyResource(this); }); + + timeline = CSyncTimeline::create(PROTO::sync->drmFD, fd); + + if (!timeline) { + resource->error(WP_LINUX_DRM_SYNCOBJ_MANAGER_V1_ERROR_INVALID_TIMELINE, "Timeline failed importing"); + return; + } +} + +SP CDRMSyncobjTimelineResource::fromResource(wl_resource* res) { + auto data = (CDRMSyncobjTimelineResource*)(((CWpLinuxDrmSyncobjTimelineV1*)wl_resource_get_user_data(res))->data()); + return data ? data->self.lock() : nullptr; +} + +bool CDRMSyncobjTimelineResource::good() { + return resource->resource(); +} + +CDRMSyncobjManagerResource::CDRMSyncobjManagerResource(SP resource_) : resource(resource_) { + if (!good()) + return; + + resource->setOnDestroy([this](CWpLinuxDrmSyncobjManagerV1* r) { PROTO::sync->destroyResource(this); }); + resource->setDestroy([this](CWpLinuxDrmSyncobjManagerV1* r) { PROTO::sync->destroyResource(this); }); + + resource->setGetSurface([this](CWpLinuxDrmSyncobjManagerV1* r, uint32_t id, wl_resource* surf) { + if (!surf) { + resource->error(-1, "Invalid surface"); + return; + } + + auto SURF = CWLSurfaceResource::fromResource(surf); + if (!SURF) { + resource->error(-1, "Invalid surface (2)"); + return; + } + + if (SURF->syncobj) { + resource->error(WP_LINUX_DRM_SYNCOBJ_MANAGER_V1_ERROR_SURFACE_EXISTS, "Surface already has a syncobj attached"); + return; + } + + auto RESOURCE = makeShared(makeShared(resource->client(), resource->version(), id), SURF); + if (!RESOURCE->good()) { + resource->noMemory(); + 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(makeShared(resource->client(), resource->version(), id), fd); + if (!RESOURCE->good()) { + resource->noMemory(); + return; + } + + PROTO::sync->m_vTimelines.emplace_back(RESOURCE); + RESOURCE->self = RESOURCE; + + LOGM(LOG, "New linux_drm_timeline at {:x}", (uintptr_t)RESOURCE.get()); + }); +} + +bool CDRMSyncobjManagerResource::good() { + return resource->resource(); +} + +CDRMSyncobjProtocol::CDRMSyncobjProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { + drmFD = fcntl(g_pCompositor->m_iDRMFD, F_DUPFD_CLOEXEC, 0); +} + +void CDRMSyncobjProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { + const auto RESOURCE = m_vManagers.emplace_back(makeShared(makeShared(client, ver, id))); + + if (!RESOURCE->good()) { + wl_client_post_no_memory(client); + m_vManagers.pop_back(); + return; + } +} + +void CDRMSyncobjProtocol::destroyResource(CDRMSyncobjManagerResource* resource) { + std::erase_if(m_vManagers, [resource](const auto& e) { return e.get() == resource; }); +} + +void CDRMSyncobjProtocol::destroyResource(CDRMSyncobjTimelineResource* resource) { + std::erase_if(m_vTimelines, [resource](const auto& e) { return e.get() == resource; }); +} + +void CDRMSyncobjProtocol::destroyResource(CDRMSyncobjSurfaceResource* resource) { + std::erase_if(m_vSurfaces, [resource](const auto& e) { return e.get() == resource; }); +} diff --git a/src/protocols/DRMSyncobj.hpp b/src/protocols/DRMSyncobj.hpp new file mode 100644 index 00000000..c1c884ff --- /dev/null +++ b/src/protocols/DRMSyncobj.hpp @@ -0,0 +1,82 @@ +#pragma once + +#include +#include +#include "WaylandProtocol.hpp" +#include "linux-drm-syncobj-v1.hpp" +#include "../helpers/signal/Signal.hpp" + +class CWLSurfaceResource; +class CDRMSyncobjTimelineResource; +class CSyncTimeline; + +class CDRMSyncobjSurfaceResource { + public: + CDRMSyncobjSurfaceResource(SP resource_, SP surface_); + + bool good(); + + WP surface; + WP acquireTimeline, releaseTimeline; + uint64_t acquirePoint = 0, releasePoint = 0; + + private: + SP resource; + + struct { + CHyprSignalListener surfacePrecommit; + } listeners; +}; + +class CDRMSyncobjTimelineResource { + public: + CDRMSyncobjTimelineResource(SP resource_, int fd_); + static SP fromResource(wl_resource*); + + bool good(); + + WP self; + int fd = -1; + SP timeline; + + private: + SP resource; +}; + +class CDRMSyncobjManagerResource { + public: + CDRMSyncobjManagerResource(SP resource_); + + bool good(); + + private: + SP resource; +}; + +class CDRMSyncobjProtocol : public IWaylandProtocol { + public: + CDRMSyncobjProtocol(const wl_interface* iface, const int& ver, const std::string& name); + + virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); + + private: + void destroyResource(CDRMSyncobjManagerResource* resource); + void destroyResource(CDRMSyncobjTimelineResource* resource); + void destroyResource(CDRMSyncobjSurfaceResource* resource); + + // + std::vector> m_vManagers; + std::vector> m_vTimelines; + std::vector> m_vSurfaces; + + // + int drmFD = -1; + + friend class CDRMSyncobjManagerResource; + friend class CDRMSyncobjTimelineResource; + friend class CDRMSyncobjSurfaceResource; +}; + +namespace PROTO { + inline UP sync; +}; diff --git a/src/protocols/Viewporter.cpp b/src/protocols/Viewporter.cpp index 8cb69dbe..7fa45e69 100644 --- a/src/protocols/Viewporter.cpp +++ b/src/protocols/Viewporter.cpp @@ -52,6 +52,21 @@ CViewportResource::CViewportResource(SP resource_, SPpending.viewport.hasSource = true; surface->pending.viewport.source = {x, y, w, h}; }); + + listeners.surfacePrecommit = surface->events.precommit.registerListener([this](std::any d) { + if (!surface) + return; + + if (surface->pending.viewport.hasSource) { + auto& src = surface->pending.viewport.source; + + if (src.w + src.x > surface->pending.size.x || src.h + src.y > surface->pending.size.y) { + resource->error(WP_VIEWPORT_ERROR_BAD_VALUE, "Box doesn't fit"); + surface->pending.rejected = true; + return; + } + } + }); } CViewportResource::~CViewportResource() { @@ -66,20 +81,6 @@ bool CViewportResource::good() { return resource->resource(); } -void CViewportResource::verify() { - if (!surface) - return; - - if (surface->pending.viewport.hasSource) { - auto& src = surface->pending.viewport.source; - - if (src.w + src.x > surface->pending.size.x || src.h + src.y > surface->pending.size.y) { - resource->error(WP_VIEWPORT_ERROR_BAD_VALUE, "Box doesn't fit"); - return; - } - } -} - CViewporterResource::CViewporterResource(SP resource_) : resource(resource_) { if (!good()) return; diff --git a/src/protocols/Viewporter.hpp b/src/protocols/Viewporter.hpp index 01278203..fd0da88e 100644 --- a/src/protocols/Viewporter.hpp +++ b/src/protocols/Viewporter.hpp @@ -15,11 +15,14 @@ class CViewportResource { ~CViewportResource(); bool good(); - void verify(); WP surface; private: SP resource; + + struct { + CHyprSignalListener surfacePrecommit; + } listeners; }; class CViewporterResource { diff --git a/src/protocols/core/Compositor.cpp b/src/protocols/core/Compositor.cpp index 5f016918..165f365c 100644 --- a/src/protocols/core/Compositor.cpp +++ b/src/protocols/core/Compositor.cpp @@ -6,6 +6,8 @@ #include "Subcompositor.hpp" #include "../Viewporter.hpp" #include "../../helpers/Monitor.hpp" +#include "../PresentationTime.hpp" +#include "../DRMSyncobj.hpp" #define LOGM PROTO::compositor->protoLog @@ -102,58 +104,14 @@ CWLSurfaceResource::CWLSurfaceResource(SP resource_) : resource(reso pending.size = tfs / pending.scale; } - if (viewportResource) - viewportResource->verify(); - pending.damage.intersect(CBox{{}, pending.size}); - auto previousBuffer = current.buffer; - CRegion previousBufferDamage = accumulateCurrentBufferDamage(); + events.precommit.emit(); + if (pending.rejected) + return; - current = pending; - pending.damage.clear(); - pending.bufferDamage.clear(); - - if (current.buffer && !bufferReleased) { - // without previous dolphin et al are weird vvv - //CRegion surfaceDamage = - // current.damage.copy().scale(current.scale).transform(current.transform, current.size.x, current.size.y).add(current.bufferDamage).add(previousBufferDamage); - current.buffer->update(CBox{{}, {INT32_MAX, INT32_MAX}}); // FIXME: figure this out to not use this hack. QT apps are wonky without this. - - // 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 (current.buffer->isSynchronous()) { - current.buffer->sendRelease(); - bufferReleased = true; - } - } - - // TODO: we should _accumulate_ and not replace above if sync - if (role->role() == SURFACE_ROLE_SUBSURFACE) { - auto subsurface = (CWLSubsurfaceResource*)role.get(); - if (subsurface->sync) - return; - - events.commit.emit(); - } else { - // send commit to all synced surfaces in this tree. - breadthfirst( - [](SP surf, const Vector2D& offset, void* data) { - if (surf->role->role() == SURFACE_ROLE_SUBSURFACE) { - auto subsurface = (CWLSubsurfaceResource*)surf->role.get(); - if (!subsurface->sync) - return; - } - surf->events.commit.emit(); - }, - nullptr); - } - - // for async buffers, we can only release the buffer once we are unrefing it from current. - if (previousBuffer && !previousBuffer->isSynchronous() && !bufferReleased) { - previousBuffer->sendRelease(); - bufferReleased = true; - } + if (stateLocks <= 0) + commitPendingState(); }); 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}); }); @@ -429,6 +387,83 @@ 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() { + auto previousBuffer = current.buffer; + CRegion previousBufferDamage = accumulateCurrentBufferDamage(); + + current = pending; + pending.damage.clear(); + pending.bufferDamage.clear(); + + if (current.buffer && !bufferReleased) { + // without previous dolphin et al are weird vvv + //CRegion surfaceDamage = + // current.damage.copy().scale(current.scale).transform(current.transform, current.size.x, current.size.y).add(current.bufferDamage).add(previousBufferDamage); + current.buffer->update(CBox{{}, {INT32_MAX, INT32_MAX}}); // FIXME: figure this out to not use this hack. QT apps are wonky without this. + + // 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 (current.buffer->isSynchronous()) { + current.buffer->sendRelease(); + bufferReleased = true; + } + } + + // TODO: we should _accumulate_ and not replace above if sync + if (role->role() == SURFACE_ROLE_SUBSURFACE) { + auto subsurface = (CWLSubsurfaceResource*)role.get(); + if (subsurface->sync) + return; + + events.commit.emit(); + } else { + // send commit to all synced surfaces in this tree. + breadthfirst( + [](SP surf, const Vector2D& offset, void* data) { + if (surf->role->role() == SURFACE_ROLE_SUBSURFACE) { + auto subsurface = (CWLSubsurfaceResource*)surf->role.get(); + if (!subsurface->sync) + return; + } + surf->events.commit.emit(); + }, + nullptr); + } + + // for async buffers, we can only release the buffer once we are unrefing it from current. + if (previousBuffer && !previousBuffer->isSynchronous() && !bufferReleased) { + previousBuffer->sendRelease(); + bufferReleased = true; + } +} + +void CWLSurfaceResource::presentFeedback(timespec* when, CMonitor* pMonitor) { + frame(when); + auto FEEDBACK = makeShared(self.lock()); + FEEDBACK->attachMonitor(pMonitor); + FEEDBACK->discarded(); + PROTO::presentation->queueData(FEEDBACK); + + if (!pMonitor || !pMonitor->outTimeline || !syncobj) + return; + + // attach explicit sync + g_pHyprRenderer->explicitPresented.emplace_back(self.lock()); + + if (syncobj->acquirePoint > pMonitor->lastWaitPoint) + pMonitor->lastWaitPoint = syncobj->acquirePoint; +} + CWLCompositorResource::CWLCompositorResource(SP resource_) : resource(resource_) { if (!good()) return; diff --git a/src/protocols/core/Compositor.hpp b/src/protocols/core/Compositor.hpp index a232ac30..0d85a5c9 100644 --- a/src/protocols/core/Compositor.hpp +++ b/src/protocols/core/Compositor.hpp @@ -24,6 +24,7 @@ class CWLSurface; class CWLSurfaceResource; class CWLSubsurfaceResource; class CViewportResource; +class CDRMSyncobjSurfaceResource; class CWLCallbackResource { public: @@ -74,6 +75,7 @@ class CWLSurfaceResource { Vector2D sourceSize(); struct { + CSignal precommit; CSignal commit; CSignal map; CSignal unmap; @@ -81,7 +83,7 @@ class CWLSurfaceResource { CSignal destroy; } events; - struct { + 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; @@ -95,6 +97,7 @@ class CWLSurfaceResource { Vector2D destination; CBox source; } viewport; + bool rejected = false; // void reset() { @@ -115,9 +118,13 @@ class CWLSurfaceResource { std::vector> subsurfaces; WP role; WP viewportResource; + WP syncobj; // may not be present void breadthfirst(std::function, const Vector2D&, void*)> fn, void* data); CRegion accumulateCurrentBufferDamage(); + void presentFeedback(timespec* when, CMonitor* pMonitor); + void lockPendingState(); + void unlockPendingState(); // returns a pair: found surface (null if not found) and surface local coords. // localCoords param is relative to 0,0 of this surface @@ -130,7 +137,10 @@ class CWLSurfaceResource { // tracks whether we should release the buffer bool bufferReleased = false; + int stateLocks = 0; + void destroy(); + void commitPendingState(); void bfHelper(std::vector> nodes, std::function, const Vector2D&, void*)> fn, void* data); }; diff --git a/src/render/OpenGL.cpp b/src/render/OpenGL.cpp index 467c83f4..adc45f2c 100644 --- a/src/render/OpenGL.cpp +++ b/src/render/OpenGL.cpp @@ -171,6 +171,14 @@ CHyprOpenGLImpl::CHyprOpenGLImpl() { loadGLProc(&m_sProc.glEGLImageTargetTexture2DOES, "glEGLImageTargetTexture2DOES"); loadGLProc(&m_sProc.eglDebugMessageControlKHR, "eglDebugMessageControlKHR"); loadGLProc(&m_sProc.eglGetPlatformDisplayEXT, "eglGetPlatformDisplayEXT"); + loadGLProc(&m_sProc.eglCreateSyncKHR, "eglCreateSyncKHR"); + loadGLProc(&m_sProc.eglDestroySyncKHR, "eglDestroySyncKHR"); + loadGLProc(&m_sProc.eglDupNativeFenceFDANDROID, "eglDupNativeFenceFDANDROID"); + loadGLProc(&m_sProc.eglWaitSyncKHR, "eglWaitSyncKHR"); + + RASSERT(m_sProc.eglCreateSyncKHR, "Display driver doesn't support eglCreateSyncKHR"); + RASSERT(m_sProc.eglDupNativeFenceFDANDROID, "Display driver doesn't support eglDupNativeFenceFDANDROID"); + RASSERT(m_sProc.eglWaitSyncKHR, "Display driver doesn't support eglWaitSyncKHR"); if (EGLEXTENSIONS.contains("EGL_EXT_device_base") || EGLEXTENSIONS.contains("EGL_EXT_device_enumeration")) loadGLProc(&m_sProc.eglQueryDevicesEXT, "eglQueryDevicesEXT"); @@ -2727,6 +2735,30 @@ std::vector CHyprOpenGLImpl::getDRMFormats() { return drmFormats; } +SP CHyprOpenGLImpl::createEGLSync(int fenceFD) { + std::vector attribs; + int dupFd = fcntl(fenceFD, F_DUPFD_CLOEXEC, 0); + if (dupFd < 0) { + Debug::log(ERR, "createEGLSync: dup failed"); + return nullptr; + } + + attribs.push_back(EGL_SYNC_NATIVE_FENCE_FD_ANDROID); + attribs.push_back(dupFd); + attribs.push_back(EGL_NONE); + + EGLSyncKHR sync = m_sProc.eglCreateSyncKHR(m_pEglDisplay, EGL_SYNC_NATIVE_FENCE_ANDROID, attribs.data()); + if (sync == EGL_NO_SYNC_KHR) { + Debug::log(ERR, "eglCreateSyncKHR failed"); + close(dupFd); + return nullptr; + } + + auto eglsync = SP(new CEGLSync); + eglsync->sync = sync; + return eglsync; +} + void SRenderModifData::applyToBox(CBox& box) { if (!enabled) return; @@ -2787,3 +2819,35 @@ float SRenderModifData::combinedScale() { } return scale; } + +CEGLSync::~CEGLSync() { + if (sync == EGL_NO_SYNC_KHR) + return; + + if (g_pHyprOpenGL->m_sProc.eglDestroySyncKHR(g_pHyprOpenGL->m_pEglDisplay, sync) != EGL_TRUE) + Debug::log(ERR, "eglDestroySyncKHR failed"); +} + +int CEGLSync::dupFenceFD() { + if (sync == EGL_NO_SYNC_KHR) + return -1; + + int fd = g_pHyprOpenGL->m_sProc.eglDupNativeFenceFDANDROID(g_pHyprOpenGL->m_pEglDisplay, sync); + if (fd == EGL_NO_NATIVE_FENCE_FD_ANDROID) { + Debug::log(ERR, "eglDupNativeFenceFDANDROID failed"); + return -1; + } + + return fd; +} + +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; +} diff --git a/src/render/OpenGL.hpp b/src/render/OpenGL.hpp index 9696c2cc..4c972a8f 100644 --- a/src/render/OpenGL.hpp +++ b/src/render/OpenGL.hpp @@ -123,6 +123,21 @@ struct SCurrentRenderData { float discardOpacity = 0.f; }; +class CEGLSync { + public: + ~CEGLSync(); + + EGLSyncKHR sync = nullptr; + + int dupFenceFD(); + bool wait(); + + private: + CEGLSync() = default; + + friend class CHyprOpenGLImpl; +}; + class CGradientValueData; class CHyprOpenGLImpl { @@ -187,6 +202,7 @@ class CHyprOpenGLImpl { uint32_t getPreferredReadFormat(CMonitor* pMonitor); std::vector getDRMFormats(); EGLImageKHR createEGLImage(const Aquamarine::SDMABUFAttrs& attrs); + SP createEGLSync(int fenceFD); SCurrentRenderData m_RenderData; @@ -219,6 +235,10 @@ class CHyprOpenGLImpl { PFNEGLQUERYDEVICESEXTPROC eglQueryDevicesEXT = nullptr; PFNEGLQUERYDEVICESTRINGEXTPROC eglQueryDeviceStringEXT = nullptr; PFNEGLQUERYDISPLAYATTRIBEXTPROC eglQueryDisplayAttribEXT = nullptr; + PFNEGLCREATESYNCKHRPROC eglCreateSyncKHR = nullptr; + PFNEGLDESTROYSYNCKHRPROC eglDestroySyncKHR = nullptr; + PFNEGLDUPNATIVEFENCEFDANDROIDPROC eglDupNativeFenceFDANDROID = nullptr; + PFNEGLWAITSYNCKHRPROC eglWaitSyncKHR = nullptr; } m_sProc; struct { diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index ac6fd8c1..01429573 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -14,6 +14,8 @@ #include "../protocols/PresentationTime.hpp" #include "../protocols/core/DataDevice.hpp" #include "../protocols/core/Compositor.hpp" +#include "../protocols/DRMSyncobj.hpp" +#include "../helpers/sync/SyncTimeline.hpp" extern "C" { #include @@ -109,6 +111,27 @@ static void renderSurface(SP surface, int x, int y, void* da if (!TEXTURE->m_iTexID) return; + // explicit sync: wait for the timeline, if any + if (surface->syncobj) { + int fd = surface->syncobj->acquireTimeline->timeline->exportAsSyncFileFD(surface->syncobj->acquirePoint); + if (fd < 0) { + Debug::log(ERR, "Renderer: failed to get a fd from explicit timeline"); + return; + } + + auto sync = g_pHyprOpenGL->createEGLSync(fd); + close(fd); + if (!sync) { + Debug::log(ERR, "Renderer: failed to get an eglsync from explicit timeline"); + return; + } + + if (!sync->wait()) { + Debug::log(ERR, "Renderer: failed to wait on an eglsync from explicit timeline"); + return; + } + } + TRACY_GPU_ZONE("RenderSurface"); double outputX = -RDATA->pMonitor->vecPosition.x, outputY = -RDATA->pMonitor->vecPosition.y; @@ -167,13 +190,9 @@ static void renderSurface(SP surface, int x, int y, void* da } if (windowBox.width <= 1 || windowBox.height <= 1) { - if (!g_pHyprRenderer->m_bBlockSurfaceFeedback) { - surface->frame(RDATA->when); - auto FEEDBACK = makeShared(surface); - FEEDBACK->attachMonitor(RDATA->pMonitor); - FEEDBACK->discarded(); - PROTO::presentation->queueData(FEEDBACK); - } + if (!g_pHyprRenderer->m_bBlockSurfaceFeedback) + surface->presentFeedback(RDATA->when, RDATA->pMonitor); + return; // invisible } @@ -225,13 +244,8 @@ static void renderSurface(SP surface, int x, int y, void* da g_pHyprOpenGL->renderTexture(TEXTURE, &windowBox, ALPHA, rounding, false, true); } - if (!g_pHyprRenderer->m_bBlockSurfaceFeedback) { - surface->frame(RDATA->when); - auto FEEDBACK = makeShared(surface); - FEEDBACK->attachMonitor(RDATA->pMonitor); - FEEDBACK->presented(); - PROTO::presentation->queueData(FEEDBACK); - } + if (!g_pHyprRenderer->m_bBlockSurfaceFeedback) + surface->presentFeedback(RDATA->when, RDATA->pMonitor); g_pHyprOpenGL->blend(true); @@ -1111,12 +1125,7 @@ bool CHyprRenderer::attemptDirectScanout(CMonitor* pMonitor) { timespec now; clock_gettime(CLOCK_MONOTONIC, &now); - PSURFACE->frame(&now); - auto FEEDBACK = makeShared(PSURFACE); - FEEDBACK->attachMonitor(pMonitor); - FEEDBACK->presented(); - FEEDBACK->setPresentationType(true); - PROTO::presentation->queueData(FEEDBACK); + PSURFACE->presentFeedback(&now, pMonitor); if (pMonitor->state.commit()) { if (m_pLastScanout.expired()) { @@ -1421,6 +1430,18 @@ void CHyprRenderer::renderMonitor(CMonitor* pMonitor) { pMonitor->output->state->setPresentationMode(shouldTear ? Aquamarine::eOutputPresentationMode::AQ_OUTPUT_PRESENTATION_IMMEDIATE : Aquamarine::eOutputPresentationMode::AQ_OUTPUT_PRESENTATION_VSYNC); + // apply timelines for explicit sync + pMonitor->output->state->setExplicitInFence(pMonitor->inTimeline->exportAsSyncFileFD(pMonitor->lastWaitPoint)); + + for (auto& e : explicitPresented) { + e->syncobj->releaseTimeline->timeline->transfer(pMonitor->outTimeline, pMonitor->commitSeq, e->syncobj->releasePoint); + } + + pMonitor->lastWaitPoint = 0; + explicitPresented.clear(); + pMonitor->output->state->setExplicitOutFence(pMonitor->outTimeline->exportAsSyncFileFD(pMonitor->commitSeq)); + pMonitor->commitSeq++; + if (!pMonitor->state.commit()) { // rollback the buffer to avoid writing to the front buffer that is being // displayed diff --git a/src/render/Renderer.hpp b/src/render/Renderer.hpp index ce37f51b..b15ba04b 100644 --- a/src/render/Renderer.hpp +++ b/src/render/Renderer.hpp @@ -99,6 +99,8 @@ class CHyprRenderer { CTimer m_tRenderTimer; + std::vector> explicitPresented; + struct { int hotspotX; int hotspotY;