diff --git a/CMakeLists.txt b/CMakeLists.txt index eb766005..c334ab06 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -285,6 +285,7 @@ protocolNew("staging/xdg-activation/xdg-activation-v1.xml" "xdg-activation-v1" f protocolNew("staging/ext-idle-notify/ext-idle-notify-v1.xml" "ext-idle-notify-v1" false) protocolNew("staging/ext-session-lock/ext-session-lock-v1.xml" "ext-session-lock-v1" false) protocolNew("stable/tablet/tablet-v2.xml" "tablet-v2" false) +protocolNew("stable/presentation-time/presentation-time.xml" "presentation-time" false) # tools add_subdirectory(hyprctl) diff --git a/protocols/meson.build b/protocols/meson.build index 4206b56a..95c9fec4 100644 --- a/protocols/meson.build +++ b/protocols/meson.build @@ -60,6 +60,7 @@ new_protocols = [ [wl_protocol_dir, 'staging/ext-idle-notify/ext-idle-notify-v1.xml'], [wl_protocol_dir, 'staging/ext-session-lock/ext-session-lock-v1.xml'], [wl_protocol_dir, 'stable/tablet/tablet-v2.xml'], + [wl_protocol_dir, 'stable/presentation-time/presentation-time.xml'], ] wl_protos_src = [] diff --git a/src/Compositor.cpp b/src/Compositor.cpp index 72502db3..bed2b203 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -232,8 +232,6 @@ void CCompositor::initServer() { m_sSeat.seat = wlr_seat_create(m_sWLDisplay, "seat0"); - m_sWLRPresentation = wlr_presentation_create(m_sWLDisplay, m_sWLRBackend); - m_sWRLDRMLeaseMgr = wlr_drm_lease_v1_manager_create(m_sWLDisplay, m_sWLRBackend); if (!m_sWRLDRMLeaseMgr) { Debug::log(INFO, "Failed to create wlr_drm_lease_v1_manager"); diff --git a/src/Compositor.hpp b/src/Compositor.hpp index 20437113..8812f8c8 100644 --- a/src/Compositor.hpp +++ b/src/Compositor.hpp @@ -52,7 +52,6 @@ class CCompositor { wlr_drm* m_sWRLDRM; wlr_drm_lease_v1_manager* m_sWRLDRMLeaseMgr; wlr_xdg_shell* m_sWLRXDGShell; - wlr_presentation* m_sWLRPresentation; wlr_egl* m_sWLREGL; int m_iDRMFD; wlr_linux_dmabuf_v1* m_sWLRLinuxDMABuf; diff --git a/src/helpers/Monitor.cpp b/src/helpers/Monitor.cpp index 2814bc84..d13acf0c 100644 --- a/src/helpers/Monitor.cpp +++ b/src/helpers/Monitor.cpp @@ -5,6 +5,7 @@ #include "../protocols/GammaControl.hpp" #include "../devices/ITouch.hpp" #include "../protocols/LayerShell.hpp" +#include "../protocols/PresentationTime.hpp" int ratHandler(void* data) { g_pHyprRenderer->renderMonitor((CMonitor*)data); @@ -30,6 +31,13 @@ CMonitor::~CMonitor() { events.destroy.emit(); } +static void onPresented(void* owner, void* data) { + const auto PMONITOR = (CMonitor*)owner; + auto E = (wlr_output_event_present*)data; + + PROTO::presentation->onPresented(PMONITOR, E->when, E->refresh, E->seq, E->flags); +} + void CMonitor::onConnect(bool noRule) { hyprListener_monitorDestroy.removeCallback(); hyprListener_monitorFrame.removeCallback(); @@ -38,13 +46,15 @@ void CMonitor::onConnect(bool noRule) { hyprListener_monitorNeedsFrame.removeCallback(); hyprListener_monitorCommit.removeCallback(); hyprListener_monitorBind.removeCallback(); - hyprListener_monitorFrame.initCallback(&output->events.frame, &Events::listener_monitorFrame, this); - hyprListener_monitorDestroy.initCallback(&output->events.destroy, &Events::listener_monitorDestroy, this); - hyprListener_monitorStateRequest.initCallback(&output->events.request_state, &Events::listener_monitorStateRequest, this); - hyprListener_monitorDamage.initCallback(&output->events.damage, &Events::listener_monitorDamage, this); - hyprListener_monitorNeedsFrame.initCallback(&output->events.needs_frame, &Events::listener_monitorNeedsFrame, this); - hyprListener_monitorCommit.initCallback(&output->events.commit, &Events::listener_monitorCommit, this); - hyprListener_monitorBind.initCallback(&output->events.bind, &Events::listener_monitorBind, this); + hyprListener_monitorPresented.removeCallback(); + hyprListener_monitorFrame.initCallback(&output->events.frame, &Events::listener_monitorFrame, this, "CMonitor"); + hyprListener_monitorDestroy.initCallback(&output->events.destroy, &Events::listener_monitorDestroy, this, "CMonitor"); + hyprListener_monitorStateRequest.initCallback(&output->events.request_state, &Events::listener_monitorStateRequest, this, "CMonitor"); + hyprListener_monitorDamage.initCallback(&output->events.damage, &Events::listener_monitorDamage, this, "CMonitor"); + hyprListener_monitorNeedsFrame.initCallback(&output->events.needs_frame, &Events::listener_monitorNeedsFrame, this, "CMonitor"); + hyprListener_monitorCommit.initCallback(&output->events.commit, &Events::listener_monitorCommit, this, "CMonitor"); + hyprListener_monitorBind.initCallback(&output->events.bind, &Events::listener_monitorBind, this, "CMonitor"); + hyprListener_monitorPresented.initCallback(&output->events.present, ::onPresented, this, "CMonitor"); tearingState.canTear = wlr_backend_is_drm(output->backend); // tearing only works on drm @@ -247,6 +257,7 @@ void CMonitor::onDisconnect(bool destroy) { } hyprListener_monitorFrame.removeCallback(); + hyprListener_monitorPresented.removeCallback(); hyprListener_monitorDamage.removeCallback(); hyprListener_monitorNeedsFrame.removeCallback(); hyprListener_monitorCommit.removeCallback(); diff --git a/src/helpers/Monitor.hpp b/src/helpers/Monitor.hpp index 48bb67e5..7aa07a86 100644 --- a/src/helpers/Monitor.hpp +++ b/src/helpers/Monitor.hpp @@ -149,6 +149,7 @@ class CMonitor { DYNLISTENER(monitorNeedsFrame); DYNLISTENER(monitorCommit); DYNLISTENER(monitorBind); + DYNLISTENER(monitorPresented); // methods void onConnect(bool noRule); diff --git a/src/includes.hpp b/src/includes.hpp index 98416a1a..eb01f4a1 100644 --- a/src/includes.hpp +++ b/src/includes.hpp @@ -51,7 +51,6 @@ extern "C" { #include #include #include -#include #include #include #include diff --git a/src/managers/ProtocolManager.cpp b/src/managers/ProtocolManager.cpp index 73f33a20..2904ed53 100644 --- a/src/managers/ProtocolManager.cpp +++ b/src/managers/ProtocolManager.cpp @@ -27,6 +27,7 @@ #include "../protocols/FocusGrab.hpp" #include "../protocols/Tablet.hpp" #include "../protocols/LayerShell.hpp" +#include "../protocols/PresentationTime.hpp" CProtocolManager::CProtocolManager() { @@ -57,6 +58,7 @@ CProtocolManager::CProtocolManager() { PROTO::focusGrab = std::make_unique(&hyprland_focus_grab_manager_v1_interface, 1, "FocusGrab"); PROTO::tablet = std::make_unique(&zwp_tablet_manager_v2_interface, 1, "TabletV2"); PROTO::layerShell = std::make_unique(&zwlr_layer_shell_v1_interface, 5, "LayerShell"); + PROTO::presentation = std::make_unique(&wp_presentation_interface, 1, "Presentation"); // Old protocol implementations. // TODO: rewrite them to use hyprwayland-scanner. diff --git a/src/protocols/PresentationTime.cpp b/src/protocols/PresentationTime.cpp new file mode 100644 index 00000000..e21c8403 --- /dev/null +++ b/src/protocols/PresentationTime.cpp @@ -0,0 +1,125 @@ +#include "PresentationTime.hpp" +#include +#include "../helpers/Monitor.hpp" +#include "../managers/HookSystemManager.hpp" + +#define LOGM PROTO::presentation->protoLog + +CQueuedPresentationData::CQueuedPresentationData(wlr_surface* surf) : surface(surf) { + ; +} + +void CQueuedPresentationData::setPresentationType(bool zeroCopy_) { + zeroCopy = zeroCopy_; +} + +void CQueuedPresentationData::attachMonitor(CMonitor* pMonitor_) { + pMonitor = pMonitor_; +} + +void CQueuedPresentationData::presented() { + wasPresented = true; +} + +void CQueuedPresentationData::discarded() { + wasPresented = false; +} + +CPresentationFeedback::CPresentationFeedback(SP resource_, wlr_surface* surf) : resource(resource_), surface(surf) { + if (!good()) + return; + + resource->setOnDestroy([this](CWpPresentationFeedback* pMgr) { + if (!done) // if it's done, it's probably already destroyed. If not, it will be in a sec. + PROTO::presentation->destroyResource(this); + }); +} + +bool CPresentationFeedback::good() { + return resource->resource(); +} + +void CPresentationFeedback::sendQueued(SP data, timespec* when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags) { + auto client = resource->client(); + wl_resource* res; + wl_resource_for_each(res, &data->pMonitor->output->resources) { + if (client == wl_resource_get_client(res)) { + resource->sendSyncOutput(res); + break; + } + } + + uint32_t flags = 0; + if (!data->pMonitor->tearingState.activelyTearing) + flags |= WP_PRESENTATION_FEEDBACK_KIND_VSYNC; + if (data->zeroCopy) + flags |= WP_PRESENTATION_FEEDBACK_KIND_ZERO_COPY; + if (reportedFlags & WLR_OUTPUT_PRESENT_HW_CLOCK) + flags |= WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK; + if (reportedFlags & WLR_OUTPUT_PRESENT_HW_COMPLETION) + flags |= WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION; + + if (data->wasPresented) + resource->sendPresented((uint32_t)(when->tv_sec >> 32), (uint32_t)(when->tv_sec & 0xFFFFFFFF), (uint32_t)(when->tv_nsec), untilRefreshNs, (uint32_t)(seq >> 32), + (uint32_t)(seq & 0xFFFFFFFF), (wpPresentationFeedbackKind)flags); + else + resource->sendDiscarded(); +} + +CPresentationProtocol::CPresentationProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { + static auto P = g_pHookSystem->hookDynamic("monitorRemoved", [this](void* self, SCallbackInfo& info, std::any param) { + const auto PMONITOR = std::any_cast(param); + std::erase_if(m_vQueue, [PMONITOR, this](const auto& other) { return !other->surface || other->pMonitor == PMONITOR; }); + }); +} + +void CPresentationProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { + const auto RESOURCE = m_vManagers.emplace_back(std::make_unique(client, ver, id)).get(); + RESOURCE->setOnDestroy([this](CWpPresentation* p) { this->onManagerResourceDestroy(p->resource()); }); + + RESOURCE->setDestroy([this](CWpPresentation* pMgr) { this->onManagerResourceDestroy(pMgr->resource()); }); + RESOURCE->setFeedback([this](CWpPresentation* pMgr, wl_resource* surf, uint32_t id) { this->onGetFeedback(pMgr, surf, id); }); +} + +void CPresentationProtocol::onManagerResourceDestroy(wl_resource* res) { + std::erase_if(m_vManagers, [&](const auto& other) { return other->resource() == res; }); +} + +void CPresentationProtocol::destroyResource(CPresentationFeedback* feedback) { + std::erase_if(m_vFeedbacks, [&](const auto& other) { return other.get() == feedback; }); +} + +void CPresentationProtocol::onGetFeedback(CWpPresentation* pMgr, wl_resource* surf, uint32_t id) { + const auto CLIENT = pMgr->client(); + const auto RESOURCE = + m_vFeedbacks.emplace_back(makeShared(makeShared(CLIENT, pMgr->version(), id), wlr_surface_from_resource(surf))).get(); + + if (!RESOURCE->good()) { + pMgr->noMemory(); + m_vFeedbacks.pop_back(); + return; + } +} + +void CPresentationProtocol::onPresented(CMonitor* pMonitor, timespec* when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags) { + for (auto& feedback : m_vFeedbacks) { + if (!feedback->surface) + continue; + + for (auto& data : m_vQueue) { + if (!data->surface || data->surface != feedback->surface) + continue; + + feedback->sendQueued(data, when, untilRefreshNs, seq, reportedFlags); + feedback->done = true; + break; + } + } + + std::erase_if(m_vFeedbacks, [pMonitor, this](const auto& other) { return !other->surface || other->done; }); + std::erase_if(m_vQueue, [pMonitor, this](const auto& other) { return !other->surface || other->pMonitor == pMonitor || !other->pMonitor; }); +} + +void CPresentationProtocol::queueData(SP data) { + m_vQueue.emplace_back(data); +} diff --git a/src/protocols/PresentationTime.hpp b/src/protocols/PresentationTime.hpp new file mode 100644 index 00000000..2df1c781 --- /dev/null +++ b/src/protocols/PresentationTime.hpp @@ -0,0 +1,73 @@ +#pragma once + +#include +#include +#include +#include "WaylandProtocol.hpp" +#include "presentation-time.hpp" + +class CMonitor; + +class CQueuedPresentationData { + public: + CQueuedPresentationData(wlr_surface* surf); + + void setPresentationType(bool zeroCopy); + void attachMonitor(CMonitor* pMonitor); + + void presented(); + void discarded(); + + private: + bool wasPresented = false; + bool zeroCopy = false; + CMonitor* pMonitor = nullptr; + wlr_surface* surface = nullptr; // READ-ONLY + + DYNLISTENER(destroySurface); + + friend class CPresentationFeedback; + friend class CPresentationProtocol; +}; + +class CPresentationFeedback { + public: + CPresentationFeedback(SP resource_, wlr_surface* surf); + + bool good(); + + void sendQueued(SP data, timespec* when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags); + + private: + SP resource; + wlr_surface* surface = nullptr; // READ-ONLY + bool done = false; + + friend class CPresentationProtocol; +}; + +class CPresentationProtocol : public IWaylandProtocol { + public: + CPresentationProtocol(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); + + void onPresented(CMonitor* pMonitor, timespec* when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags); + void queueData(SP data); + + private: + void onManagerResourceDestroy(wl_resource* res); + void destroyResource(CPresentationFeedback* feedback); + void onGetFeedback(CWpPresentation* pMgr, wl_resource* surf, uint32_t id); + + // + std::vector> m_vManagers; + std::vector> m_vFeedbacks; + std::vector> m_vQueue; + + friend class CPresentationFeedback; +}; + +namespace PROTO { + inline UP presentation; +}; diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index d1baf461..d02ed4e2 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -10,6 +10,7 @@ #include "../desktop/LayerSurface.hpp" #include "../protocols/SessionLock.hpp" #include "../protocols/LayerShell.hpp" +#include "../protocols/PresentationTime.hpp" extern "C" { #include @@ -206,7 +207,10 @@ static void renderSurface(struct wlr_surface* surface, int x, int y, void* data) if (!g_pHyprRenderer->m_bBlockSurfaceFeedback) { wlr_surface_send_frame_done(surface, RDATA->when); - wlr_presentation_surface_textured_on_output(surface, RDATA->pMonitor->output); + auto FEEDBACK = makeShared(surface); + FEEDBACK->attachMonitor(RDATA->pMonitor); + FEEDBACK->presented(); + PROTO::presentation->queueData(FEEDBACK); } g_pHyprOpenGL->blend(true); @@ -1067,7 +1071,11 @@ bool CHyprRenderer::attemptDirectScanout(CMonitor* pMonitor) { timespec now; clock_gettime(CLOCK_MONOTONIC, &now); wlr_surface_send_frame_done(PSURFACE, &now); - wlr_presentation_surface_scanned_out_on_output(PSURFACE, pMonitor->output); + auto FEEDBACK = makeShared(PSURFACE); + FEEDBACK->attachMonitor(pMonitor); + FEEDBACK->presented(); + FEEDBACK->setPresentationType(true); + PROTO::presentation->queueData(FEEDBACK); if (pMonitor->state.commit()) { if (m_pLastScanout.expired()) {