diff --git a/CMakeLists.txt b/CMakeLists.txt index 4822bc69158..03d93c413b7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -384,6 +384,8 @@ protocolnew("staging/xdg-system-bell" "xdg-system-bell-v1" false) protocolnew("staging/ext-workspace" "ext-workspace-v1" false) protocolnew("staging/ext-data-control" "ext-data-control-v1" false) protocolnew("staging/pointer-warp" "pointer-warp-v1" false) +protocolnew("staging/fifo" "fifo-v1" false) +protocolnew("staging/commit-timing" "commit-timing-v1" false) protocolwayland() diff --git a/protocols/meson.build b/protocols/meson.build index dc23eaa17a9..33663fa386f 100644 --- a/protocols/meson.build +++ b/protocols/meson.build @@ -78,6 +78,8 @@ protocols = [ wayland_protocol_dir / 'staging/ext-workspace/ext-workspace-v1.xml', wayland_protocol_dir / 'staging/ext-data-control/ext-data-control-v1.xml', wayland_protocol_dir / 'staging/pointer-warp/pointer-warp-v1.xml', + wayland_protocol_dir / 'staging/fifo/fifo-v1.xml', + wayland_protocol_dir / 'staging/commit-timing/commit-timing-v1.xml', ] wl_protocols = [] diff --git a/src/helpers/Monitor.cpp b/src/helpers/Monitor.cpp index 273bc1c778e..22fd9ad3563 100644 --- a/src/helpers/Monitor.cpp +++ b/src/helpers/Monitor.cpp @@ -139,6 +139,8 @@ void CMonitor::onConnect(bool noRule) { } m_frameScheduler->onPresented(); + + m_events.presented.emit(); }); m_listeners.destroy = m_output->events.destroy.listen([this] { diff --git a/src/helpers/Monitor.hpp b/src/helpers/Monitor.hpp index d112f74766d..25c47e988c8 100644 --- a/src/helpers/Monitor.hpp +++ b/src/helpers/Monitor.hpp @@ -214,6 +214,7 @@ class CMonitor { CSignalT<> disconnect; CSignalT<> dpmsChanged; CSignalT<> modeChanged; + CSignalT<> presented; } m_events; std::array, 4> m_layerSurfaceLayers; diff --git a/src/managers/ProtocolManager.cpp b/src/managers/ProtocolManager.cpp index c9690abadda..0c7e342de73 100644 --- a/src/managers/ProtocolManager.cpp +++ b/src/managers/ProtocolManager.cpp @@ -65,6 +65,8 @@ #include "../protocols/ExtWorkspace.hpp" #include "../protocols/ExtDataDevice.hpp" #include "../protocols/PointerWarp.hpp" +#include "../protocols/Fifo.hpp" +#include "../protocols/CommitTiming.hpp" #include "../helpers/Monitor.hpp" #include "../render/Renderer.hpp" @@ -194,6 +196,8 @@ CProtocolManager::CProtocolManager() { PROTO::extWorkspace = makeUnique(&ext_workspace_manager_v1_interface, 1, "ExtWorkspace"); PROTO::extDataDevice = makeUnique(&ext_data_control_manager_v1_interface, 1, "ExtDataDevice"); PROTO::pointerWarp = makeUnique(&wp_pointer_warp_v1_interface, 1, "PointerWarp"); + PROTO::fifo = makeUnique(&wp_fifo_manager_v1_interface, 1, "Fifo"); + PROTO::commitTiming = makeUnique(&wp_commit_timing_manager_v1_interface, 1, "CommitTiming"); if (*PENABLECM) PROTO::colorManagement = makeUnique(&wp_color_manager_v1_interface, 1, "ColorManagement", *PDEBUGCM); @@ -298,6 +302,8 @@ CProtocolManager::~CProtocolManager() { PROTO::extWorkspace.reset(); PROTO::extDataDevice.reset(); PROTO::pointerWarp.reset(); + PROTO::fifo.reset(); + PROTO::commitTiming.reset(); for (auto& [_, lease] : PROTO::lease) { lease.reset(); @@ -353,6 +359,8 @@ bool CProtocolManager::isGlobalPrivileged(const wl_global* global) { PROTO::hyprlandSurface->getGlobal(), PROTO::xdgTag->getGlobal(), PROTO::xdgBell->getGlobal(), + PROTO::fifo->getGlobal(), + PROTO::commitTiming->getGlobal(), PROTO::sync ? PROTO::sync->getGlobal() : nullptr, PROTO::mesaDRM ? PROTO::mesaDRM->getGlobal() : nullptr, PROTO::linuxDma ? PROTO::linuxDma->getGlobal() : nullptr, diff --git a/src/protocols/CommitTiming.cpp b/src/protocols/CommitTiming.cpp new file mode 100644 index 00000000000..9403cf3dd53 --- /dev/null +++ b/src/protocols/CommitTiming.cpp @@ -0,0 +1,132 @@ +#include "CommitTiming.hpp" +#include "core/Compositor.hpp" +#include "../managers/eventLoop/EventLoopManager.hpp" +#include "../managers/eventLoop/EventLoopTimer.hpp" + +CCommitTimerResource::CCommitTimerResource(UP&& resource_, SP surface) : m_resource(std::move(resource_)), m_surface(surface) { + if UNLIKELY (!m_resource->resource()) + return; + + m_resource->setDestroy([this](CWpCommitTimerV1* r) { PROTO::commitTiming->destroyResource(this); }); + m_resource->setOnDestroy([this](CWpCommitTimerV1* r) { PROTO::commitTiming->destroyResource(this); }); + + m_resource->setSetTimestamp([this](CWpCommitTimerV1* r, uint32_t tvHi, uint32_t tvLo, uint32_t tvNsec) { + if (!m_surface) { + r->error(WP_COMMIT_TIMER_V1_ERROR_SURFACE_DESTROYED, "Surface was gone"); + return; + } + + if (m_timerPresent) { + r->error(WP_COMMIT_TIMER_V1_ERROR_TIMESTAMP_EXISTS, "Timestamp is already set"); + return; + } + + timespec ts; + ts.tv_sec = (((uint64_t)tvHi) << 32) | (uint64_t)tvLo; + ts.tv_nsec = tvNsec; + + const auto TIME = Time::fromTimespec(&ts); + const auto TIME_NOW = Time::steadyNow(); + + if (TIME_NOW > TIME) { + // TODO: should we err here? + // for now just do nothing I guess, thats some lag. + return; + } + + m_timerPresent = true; + + // FIXME: this doesn't *exactly* guarantee we wont fire a few dozen ns before + // the desired time... + m_timers.emplace_back(makeShared(STimerLock{ + .timer = makeShared( + TIME - TIME_NOW, + [this](SP self, void* data) { + // unlock the state if applicable + std::erase_if(m_timers, [self](const auto& e) { return e->timer == self; }); + }, + nullptr), + .lock = CSurfaceScopeLock::create(m_surface->m_pending.lock), + })); + }); + + m_listeners.surfacePrecommit = m_surface->m_events.precommit.listen([this] { m_timerPresent = false; }); +} + +CCommitTimerResource::~CCommitTimerResource() { + ; +} + +bool CCommitTimerResource::good() { + return m_resource->resource(); +} + +CCommitTimingManagerResource::CCommitTimingManagerResource(UP&& resource_) : m_resource(std::move(resource_)) { + if UNLIKELY (!m_resource->resource()) + return; + + m_resource->setDestroy([this](CWpCommitTimingManagerV1* r) { PROTO::commitTiming->destroyResource(this); }); + m_resource->setOnDestroy([this](CWpCommitTimingManagerV1* r) { PROTO::commitTiming->destroyResource(this); }); + + m_resource->setGetTimer([](CWpCommitTimingManagerV1* r, uint32_t id, wl_resource* surfResource) { + if (!surfResource) { + r->error(-1, "No resource for commit timing"); + return; + } + + auto surf = CWLSurfaceResource::fromResource(surfResource); + + if (!surf) { + r->error(-1, "No surface for commit timing"); + return; + } + + for (const auto& timer : PROTO::commitTiming->m_timers) { + if (timer->m_surface == surf) { + r->error(WP_COMMIT_TIMING_MANAGER_V1_ERROR_COMMIT_TIMER_EXISTS, "Surface already has a commit timing"); + return; + } + } + + const auto CLIENT = r->client(); + const auto RESOURCE = PROTO::commitTiming->m_timers.emplace_back(makeUnique(makeUnique(CLIENT, r->version(), id), surf)).get(); + + if (!RESOURCE->good()) { + r->noMemory(); + PROTO::commitTiming->m_timers.pop_back(); + return; + } + + RESOURCE->m_self = PROTO::commitTiming->m_timers.back(); + }); +} + +CCommitTimingManagerResource::~CCommitTimingManagerResource() { + ; +} + +bool CCommitTimingManagerResource::good() { + return m_resource->resource(); +} + +CCommitTimingProtocol::CCommitTimingProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { + ; +} + +void CCommitTimingProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { + const auto RESOURCE = m_managers.emplace_back(makeUnique(makeUnique(client, ver, id))).get(); + + if (!RESOURCE->good()) { + wl_client_post_no_memory(client); + m_managers.pop_back(); + return; + } +} + +void CCommitTimingProtocol::destroyResource(CCommitTimingManagerResource* res) { + std::erase_if(m_managers, [&](const auto& other) { return other.get() == res; }); +} + +void CCommitTimingProtocol::destroyResource(CCommitTimerResource* res) { + std::erase_if(m_timers, [&](const auto& other) { return other.get() == res; }); +} diff --git a/src/protocols/CommitTiming.hpp b/src/protocols/CommitTiming.hpp new file mode 100644 index 00000000000..5d05fdd06ad --- /dev/null +++ b/src/protocols/CommitTiming.hpp @@ -0,0 +1,76 @@ +#pragma once + +#include +#include +#include "WaylandProtocol.hpp" +#include "commit-timing-v1.hpp" + +#include "../helpers/signal/Signal.hpp" + +class CWLSurfaceResource; +class CEventLoopTimer; +class CSurfaceScopeLock; + +class CCommitTimerResource { + public: + CCommitTimerResource(UP&& resource_, SP surface); + ~CCommitTimerResource(); + + bool good(); + + WP m_self; + + private: + UP m_resource; + + WP m_surface; + + bool m_timerPresent = false; + + struct STimerLock { + SP timer; + SP lock; + }; + + std::vector> m_timers; + + struct { + CHyprSignalListener surfacePrecommit; + } m_listeners; + + friend class CCommitTimingProtocol; + friend class CCommitTimingManagerResource; +}; + +class CCommitTimingManagerResource { + public: + CCommitTimingManagerResource(UP&& resource_); + ~CCommitTimingManagerResource(); + + bool good(); + + private: + UP m_resource; +}; + +class CCommitTimingProtocol : public IWaylandProtocol { + public: + CCommitTimingProtocol(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(CCommitTimingManagerResource* resource); + void destroyResource(CCommitTimerResource* resource); + + // + std::vector> m_managers; + std::vector> m_timers; + + friend class CCommitTimingManagerResource; + friend class CCommitTimerResource; +}; + +namespace PROTO { + inline UP commitTiming; +}; diff --git a/src/protocols/Fifo.cpp b/src/protocols/Fifo.cpp new file mode 100644 index 00000000000..46df7cb90a6 --- /dev/null +++ b/src/protocols/Fifo.cpp @@ -0,0 +1,152 @@ +#include "Fifo.hpp" +#include "core/Compositor.hpp" +#include "../managers/HookSystemManager.hpp" +#include "../helpers/Monitor.hpp" + +CFifoResource::CFifoResource(UP&& resource_, SP surface) : m_resource(std::move(resource_)), m_surface(surface) { + if UNLIKELY (!m_resource->resource()) + return; + + m_resource->setDestroy([this](CWpFifoV1* r) { PROTO::fifo->destroyResource(this); }); + m_resource->setOnDestroy([this](CWpFifoV1* r) { PROTO::fifo->destroyResource(this); }); + + m_resource->setSetBarrier([this](CWpFifoV1* r) { + if (!m_surface) { + r->error(WP_FIFO_V1_ERROR_SURFACE_DESTROYED, "Surface was gone"); + return; + } + + m_pending.barrierSet = true; + }); + + m_resource->setWaitBarrier([this](CWpFifoV1* r) { + if (!m_surface) { + r->error(WP_FIFO_V1_ERROR_SURFACE_DESTROYED, "Surface was gone"); + return; + } + + if (!m_pending.barrierSet) + return; + + m_pending.surfaceLocked = true; + }); + + m_listeners.surfacePrecommit = m_surface->m_events.precommit.listen([this]() { + m_current = m_pending; + + if (!m_current.surfaceLocked) + return; + + m_current.lock = CSurfaceScopeLock::create(m_surface->m_pending.lock); + }); +} + +CFifoResource::~CFifoResource() { + ; +} + +bool CFifoResource::good() { + return m_resource->resource(); +} + +void CFifoResource::presented() { + // reset barrier if the app did setBarrier then we had a presentation and then + // it did wait because it's already past + if (m_current.barrierSet && !m_current.surfaceLocked) + m_current.barrierSet = false; + + if (m_current.surfaceLocked && m_surface) { + m_current.lock.reset(); + m_current.surfaceLocked = false; + m_current.barrierSet = false; + } +} + +CFifoManagerResource::CFifoManagerResource(UP&& resource_) : m_resource(std::move(resource_)) { + if UNLIKELY (!m_resource->resource()) + return; + + m_resource->setDestroy([this](CWpFifoManagerV1* r) { PROTO::fifo->destroyResource(this); }); + m_resource->setOnDestroy([this](CWpFifoManagerV1* r) { PROTO::fifo->destroyResource(this); }); + + m_resource->setGetFifo([](CWpFifoManagerV1* r, uint32_t id, wl_resource* surfResource) { + if (!surfResource) { + r->error(-1, "No resource for fifo"); + return; + } + + auto surf = CWLSurfaceResource::fromResource(surfResource); + + if (!surf) { + r->error(-1, "No surface for fifo"); + return; + } + + for (const auto& fifo : PROTO::fifo->m_fifos) { + if (fifo->m_surface == surf) { + r->error(WP_FIFO_MANAGER_V1_ERROR_ALREADY_EXISTS, "Surface already has a fifo"); + return; + } + } + + const auto CLIENT = r->client(); + const auto RESOURCE = PROTO::fifo->m_fifos.emplace_back(makeUnique(makeUnique(CLIENT, r->version(), id), surf)).get(); + + if (!RESOURCE->good()) { + r->noMemory(); + PROTO::fifo->m_fifos.pop_back(); + return; + } + }); +} + +CFifoManagerResource::~CFifoManagerResource() { + ; +} + +bool CFifoManagerResource::good() { + return m_resource->resource(); +} + +CFifoProtocol::CFifoProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { + static auto P = g_pHookSystem->hookDynamic("monitorAdded", [this](void* self, SCallbackInfo& info, std::any param) { + auto M = std::any_cast(param); + + M->m_events.presented.listenStatic([this, m = PHLMONITORREF{M}]() { + if (!m || !PROTO::fifo) + return; + + onMonitorPresent(m.lock()); + }); + }); +} + +void CFifoProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { + const auto RESOURCE = m_managers.emplace_back(makeUnique(makeUnique(client, ver, id))).get(); + + if (!RESOURCE->good()) { + wl_client_post_no_memory(client); + m_managers.pop_back(); + return; + } +} + +void CFifoProtocol::destroyResource(CFifoManagerResource* res) { + std::erase_if(m_managers, [&](const auto& other) { return other.get() == res; }); +} + +void CFifoProtocol::destroyResource(CFifoResource* res) { + std::erase_if(m_fifos, [&](const auto& other) { return other.get() == res; }); +} + +void CFifoProtocol::onMonitorPresent(PHLMONITOR m) { + for (const auto& fifo : m_fifos) { + if (!fifo->m_surface) + continue; + + // Signal all surfaces + // TODO: should we wait for the correct monitor(s)? + + fifo->presented(); + } +} diff --git a/src/protocols/Fifo.hpp b/src/protocols/Fifo.hpp new file mode 100644 index 00000000000..37328c180fb --- /dev/null +++ b/src/protocols/Fifo.hpp @@ -0,0 +1,76 @@ +#pragma once + +#include +#include +#include "WaylandProtocol.hpp" +#include "fifo-v1.hpp" + +#include "../helpers/signal/Signal.hpp" + +class CWLSurfaceResource; +class CSurfaceScopeLock; + +class CFifoResource { + public: + CFifoResource(UP&& resource_, SP surface); + ~CFifoResource(); + + bool good(); + + private: + UP m_resource; + + WP m_surface; + + struct SState { + bool barrierSet = false; + bool surfaceLocked = false; + SP lock; + }; + + SState m_current, m_pending; + + struct { + CHyprSignalListener surfacePrecommit; + } m_listeners; + + void presented(); + + friend class CFifoProtocol; + friend class CFifoManagerResource; +}; + +class CFifoManagerResource { + public: + CFifoManagerResource(UP&& resource_); + ~CFifoManagerResource(); + + bool good(); + + private: + UP m_resource; +}; + +class CFifoProtocol : public IWaylandProtocol { + public: + CFifoProtocol(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(CFifoManagerResource* resource); + void destroyResource(CFifoResource* resource); + + void onMonitorPresent(PHLMONITOR m); + + // + std::vector> m_managers; + std::vector> m_fifos; + + friend class CFifoManagerResource; + friend class CFifoResource; +}; + +namespace PROTO { + inline UP fifo; +}; diff --git a/src/protocols/core/Compositor.cpp b/src/protocols/core/Compositor.cpp index 8ad2a3734e5..0d32027ba31 100644 --- a/src/protocols/core/Compositor.cpp +++ b/src/protocols/core/Compositor.cpp @@ -129,28 +129,19 @@ CWLSurfaceResource::CWLSurfaceResource(SP resource_) : m_resource(re return; } - // null buffer attached - if (!m_pending.buffer && m_pending.updated.bits.buffer) { - commitState(m_pending); - - // remove any pending states. - while (!m_pendingStates.empty()) { - m_pendingStates.pop(); - } - - m_pendingWaiting = false; - m_pending.reset(); - return; - } - // save state while we wait for buffer to become ready to read const auto& state = m_pendingStates.emplace(makeUnique(m_pending)); m_pending.reset(); - if (!m_pendingWaiting) { - m_pendingWaiting = true; - scheduleState(state); + state->lock->lock(); + + // null buffer attached + if (!state->buffer && state->updated.bits.buffer) { + state->lock->unlock(); + return; } + + scheduleState(state); }); m_resource->setDamage([this](CWlSurface* r, int32_t x, int32_t y, int32_t w, int32_t h) { @@ -246,6 +237,13 @@ void CWLSurfaceResource::dropCurrentBuffer() { m_current.buffer = {}; } +void CWLSurfaceResource::progressStates() { + while (!m_pendingStates.empty() && !m_pendingStates.front()->lock->locked()) { + commitState(*m_pendingStates.front()); + m_pendingStates.pop(); + } +} + SP CWLSurfaceResource::fromResource(wl_resource* res) { auto data = sc(sc(wl_resource_get_user_data(res))->data()); return data ? data->m_self.lock() : nullptr; @@ -483,20 +481,9 @@ void CWLSurfaceResource::scheduleState(WP state) { if (!surf || state.expired() || m_pendingStates.empty()) return; - while (!m_pendingStates.empty() && m_pendingStates.front() != state) { - commitState(*m_pendingStates.front()); - m_pendingStates.pop(); - } - - commitState(*m_pendingStates.front()); - m_pendingStates.pop(); + state->lock->unlock(); - // If more states are queued, schedule next state - if (!m_pendingStates.empty()) { - scheduleState(m_pendingStates.front()); - } else { - m_pendingWaiting = false; - } + progressStates(); }; if (state->updated.bits.acquire) { @@ -652,6 +639,11 @@ void CWLSurfaceResource::presentFeedback(const Time::steady_tp& when, PHLMONITOR PROTO::presentation->queueData(std::move(FEEDBACK)); } +void CWLSurfaceResource::init() { + m_pending.lock = makeShared(m_self.lock()); + m_current.lock = makeShared(m_self.lock()); +} + CWLCompositorResource::CWLCompositorResource(SP resource_) : m_resource(resource_) { if UNLIKELY (!good()) return; @@ -668,6 +660,7 @@ CWLCompositorResource::CWLCompositorResource(SP resource_) : m_re } RESOURCE->m_self = RESOURCE; + RESOURCE->init(); LOGM(LOG, "New wl_surface with id {} at {:x}", id, (uintptr_t)RESOURCE.get()); diff --git a/src/protocols/core/Compositor.hpp b/src/protocols/core/Compositor.hpp index dc61917d427..2bf389b2fa7 100644 --- a/src/protocols/core/Compositor.hpp +++ b/src/protocols/core/Compositor.hpp @@ -87,6 +87,7 @@ class CWLSurfaceResource { SP getResource(); CBox extends(); void resetRole(); + void init(); struct { CSignalT<> precommit; // before commit @@ -101,8 +102,10 @@ class CWLSurfaceResource { SSurfaceState m_current; SSurfaceState m_pending; + std::queue> m_pendingStates; - bool m_pendingWaiting = false; + + void progressStates(); WP m_self; WP m_hlSurface; diff --git a/src/protocols/types/SurfaceState.cpp b/src/protocols/types/SurfaceState.cpp index 248ce835654..42c81266bae 100644 --- a/src/protocols/types/SurfaceState.cpp +++ b/src/protocols/types/SurfaceState.cpp @@ -2,6 +2,40 @@ #include "helpers/Format.hpp" #include "protocols/types/Buffer.hpp" #include "render/Texture.hpp" +#include "../core/Compositor.hpp" + +CSurfaceStateLock::CSurfaceStateLock(SP surf) : m_surface(surf) { + ; +} + +void CSurfaceStateLock::lock() { + m_locks++; +} + +void CSurfaceStateLock::unlock() { + RASSERT(m_locks, "Tried to unlock an unlocked surface state"); + + m_locks--; + + if (!m_locks) + m_surface->progressStates(); +} + +bool CSurfaceStateLock::locked() { + return m_locks; +} + +SP CSurfaceScopeLock::create(SP lock) { + return makeShared(lock); +} + +CSurfaceScopeLock::CSurfaceScopeLock(SP lock) : m_lock(lock) { + m_lock->lock(); +} + +CSurfaceScopeLock::~CSurfaceScopeLock() { + m_lock->unlock(); +} Vector2D SSurfaceState::sourceSize() { if UNLIKELY (!texture) diff --git a/src/protocols/types/SurfaceState.hpp b/src/protocols/types/SurfaceState.hpp index e6764edaa42..6076ba4943d 100644 --- a/src/protocols/types/SurfaceState.hpp +++ b/src/protocols/types/SurfaceState.hpp @@ -8,6 +8,32 @@ class CTexture; class CDRMSyncPointState; class CWLCallbackResource; +class CSurfaceStateLock { + public: + CSurfaceStateLock(SP surf); + ~CSurfaceStateLock() = default; + + void lock(); + void unlock(); + + bool locked(); + + private: + size_t m_locks = 0; + WP m_surface; +}; + +class CSurfaceScopeLock { + public: + static SP create(SP lock); + + CSurfaceScopeLock(SP lock); + ~CSurfaceScopeLock(); + + private: + SP m_lock; +}; + struct SSurfaceState { union { uint16_t all = 0; @@ -66,4 +92,8 @@ struct SSurfaceState { CRegion accumulateBufferDamage(); // transforms state.damage and merges it into state.bufferDamage void updateFrom(SSurfaceState& ref); // updates this state based on a reference state. void reset(); // resets pending state after commit + + // lock mechanism + SP lock; + WP surface; };