mirror of
https://github.com/hyprwm/Hyprland
synced 2024-12-25 07:09:48 +01:00
explicit sync
This commit is contained in:
parent
82e27597db
commit
f4c8789aec
17 changed files with 757 additions and 86 deletions
|
@ -296,6 +296,7 @@ protocolNew("staging/xwayland-shell" "xwayland-shell-v1" false)
|
||||||
protocolNew("stable/viewporter" "viewporter" false)
|
protocolNew("stable/viewporter" "viewporter" false)
|
||||||
protocolNew("stable/linux-dmabuf" "linux-dmabuf-v1" false)
|
protocolNew("stable/linux-dmabuf" "linux-dmabuf-v1" false)
|
||||||
protocolNew("staging/drm-lease" "drm-lease-v1" false)
|
protocolNew("staging/drm-lease" "drm-lease-v1" false)
|
||||||
|
protocolNew("staging/linux-drm-syncobj" "linux-drm-syncobj-v1" false)
|
||||||
|
|
||||||
protocolWayland()
|
protocolWayland()
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include "../protocols/DRMLease.hpp"
|
#include "../protocols/DRMLease.hpp"
|
||||||
#include "../protocols/core/Output.hpp"
|
#include "../protocols/core/Output.hpp"
|
||||||
#include "../managers/PointerManager.hpp"
|
#include "../managers/PointerManager.hpp"
|
||||||
|
#include "sync/SyncTimeline.hpp"
|
||||||
#include <hyprutils/string/String.hpp>
|
#include <hyprutils/string/String.hpp>
|
||||||
using namespace Hyprutils::String;
|
using namespace Hyprutils::String;
|
||||||
|
|
||||||
|
@ -29,6 +30,11 @@ CMonitor::~CMonitor() {
|
||||||
|
|
||||||
void CMonitor::onConnect(bool noRule) {
|
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.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.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); });
|
listeners.commit = output->events.commit.registerListener([this](std::any d) { Events::listener_monitorCommit(this, nullptr); });
|
||||||
|
|
|
@ -40,6 +40,7 @@ struct SMonitorRule {
|
||||||
};
|
};
|
||||||
|
|
||||||
class CMonitor;
|
class CMonitor;
|
||||||
|
class CSyncTimeline;
|
||||||
|
|
||||||
class CMonitorState {
|
class CMonitorState {
|
||||||
public:
|
public:
|
||||||
|
@ -116,7 +117,13 @@ class CMonitor {
|
||||||
|
|
||||||
SMonitorRule activeMonitorRule;
|
SMonitorRule activeMonitorRule;
|
||||||
|
|
||||||
WP<CMonitor> self;
|
// explicit sync
|
||||||
|
SP<CSyncTimeline> inTimeline;
|
||||||
|
SP<CSyncTimeline> outTimeline;
|
||||||
|
uint64_t lastWaitPoint = 0;
|
||||||
|
uint64_t commitSeq = 0;
|
||||||
|
|
||||||
|
WP<CMonitor> self;
|
||||||
|
|
||||||
// mirroring
|
// mirroring
|
||||||
CMonitor* pMirrorOf = nullptr;
|
CMonitor* pMirrorOf = nullptr;
|
||||||
|
|
187
src/helpers/sync/SyncTimeline.cpp
Normal file
187
src/helpers/sync/SyncTimeline.cpp
Normal file
|
@ -0,0 +1,187 @@
|
||||||
|
#include "SyncTimeline.hpp"
|
||||||
|
#include "../../defines.hpp"
|
||||||
|
#include "../../managers/eventLoop/EventLoopManager.hpp"
|
||||||
|
|
||||||
|
#include <xf86drm.h>
|
||||||
|
#include <sys/eventfd.h>
|
||||||
|
|
||||||
|
SP<CSyncTimeline> CSyncTimeline::create(int drmFD_) {
|
||||||
|
auto timeline = SP<CSyncTimeline>(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> CSyncTimeline::create(int drmFD_, int drmSyncobjFD) {
|
||||||
|
auto timeline = SP<CSyncTimeline>(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<bool> 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<void()>& waiter, uint64_t point, uint32_t flags) {
|
||||||
|
auto w = makeShared<SWaiter>();
|
||||||
|
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<CSyncTimeline> 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;
|
||||||
|
}
|
46
src/helpers/sync/SyncTimeline.hpp
Normal file
46
src/helpers/sync/SyncTimeline.hpp
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <optional>
|
||||||
|
#include <vector>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
/*
|
||||||
|
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<CSyncTimeline> create(int drmFD_);
|
||||||
|
static SP<CSyncTimeline> create(int drmFD_, int drmSyncobjFD);
|
||||||
|
~CSyncTimeline();
|
||||||
|
|
||||||
|
struct SWaiter {
|
||||||
|
std::function<void()> fn;
|
||||||
|
wl_event_source* source = nullptr;
|
||||||
|
WP<CSyncTimeline> 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<bool> check(uint64_t point, uint32_t flags);
|
||||||
|
|
||||||
|
bool addWaiter(const std::function<void()>& 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<CSyncTimeline> from, uint64_t fromPoint, uint64_t toPoint);
|
||||||
|
|
||||||
|
int drmFD = -1;
|
||||||
|
uint32_t handle = 0;
|
||||||
|
WP<CSyncTimeline> self;
|
||||||
|
|
||||||
|
private:
|
||||||
|
CSyncTimeline() = default;
|
||||||
|
|
||||||
|
std::vector<SP<SWaiter>> waiters;
|
||||||
|
};
|
|
@ -36,6 +36,7 @@
|
||||||
#include "../protocols/MesaDRM.hpp"
|
#include "../protocols/MesaDRM.hpp"
|
||||||
#include "../protocols/LinuxDMABUF.hpp"
|
#include "../protocols/LinuxDMABUF.hpp"
|
||||||
#include "../protocols/DRMLease.hpp"
|
#include "../protocols/DRMLease.hpp"
|
||||||
|
#include "../protocols/DRMSyncobj.hpp"
|
||||||
|
|
||||||
#include "../protocols/core/Seat.hpp"
|
#include "../protocols/core/Seat.hpp"
|
||||||
#include "../protocols/core/DataDevice.hpp"
|
#include "../protocols/core/DataDevice.hpp"
|
||||||
|
@ -131,6 +132,7 @@ CProtocolManager::CProtocolManager() {
|
||||||
PROTO::dataWlr = std::make_unique<CDataDeviceWLRProtocol>(&zwlr_data_control_manager_v1_interface, 2, "DataDeviceWlr");
|
PROTO::dataWlr = std::make_unique<CDataDeviceWLRProtocol>(&zwlr_data_control_manager_v1_interface, 2, "DataDeviceWlr");
|
||||||
PROTO::primarySelection = std::make_unique<CPrimarySelectionProtocol>(&zwp_primary_selection_device_manager_v1_interface, 1, "PrimarySelection");
|
PROTO::primarySelection = std::make_unique<CPrimarySelectionProtocol>(&zwp_primary_selection_device_manager_v1_interface, 1, "PrimarySelection");
|
||||||
PROTO::xwaylandShell = std::make_unique<CXWaylandShellProtocol>(&xwayland_shell_v1_interface, 1, "XWaylandShell");
|
PROTO::xwaylandShell = std::make_unique<CXWaylandShellProtocol>(&xwayland_shell_v1_interface, 1, "XWaylandShell");
|
||||||
|
PROTO::sync = std::make_unique<CDRMSyncobjProtocol>(&wp_linux_drm_syncobj_manager_v1_interface, 1, "DRMSyncobj");
|
||||||
|
|
||||||
for (auto& b : g_pCompositor->m_pAqBackend->getImplementations()) {
|
for (auto& b : g_pCompositor->m_pAqBackend->getImplementations()) {
|
||||||
if (b->type() != Aquamarine::AQ_BACKEND_DRM)
|
if (b->type() != Aquamarine::AQ_BACKEND_DRM)
|
||||||
|
|
|
@ -50,6 +50,8 @@ class CEventLoopManager {
|
||||||
|
|
||||||
SIdleData m_sIdle;
|
SIdleData m_sIdle;
|
||||||
std::vector<SP<Aquamarine::SPollFD>> aqPollFDs;
|
std::vector<SP<Aquamarine::SPollFD>> aqPollFDs;
|
||||||
|
|
||||||
|
friend class CSyncTimeline;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline std::unique_ptr<CEventLoopManager> g_pEventLoopManager;
|
inline std::unique_ptr<CEventLoopManager> g_pEventLoopManager;
|
182
src/protocols/DRMSyncobj.cpp
Normal file
182
src/protocols/DRMSyncobj.cpp
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
#include "DRMSyncobj.hpp"
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "core/Compositor.hpp"
|
||||||
|
#include "../helpers/sync/SyncTimeline.hpp"
|
||||||
|
#include "../Compositor.hpp"
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
#define LOGM PROTO::sync->protoLog
|
||||||
|
|
||||||
|
CDRMSyncobjSurfaceResource::CDRMSyncobjSurfaceResource(SP<CWpLinuxDrmSyncobjSurfaceV1> resource_, SP<CWLSurfaceResource> 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<CWpLinuxDrmSyncobjTimelineV1> 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> 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<CWpLinuxDrmSyncobjManagerV1> 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<CDRMSyncobjSurfaceResource>(makeShared<CWpLinuxDrmSyncobjSurfaceV1>(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<CDRMSyncobjTimelineResource>(makeShared<CWpLinuxDrmSyncobjTimelineV1>(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<CDRMSyncobjManagerResource>(makeShared<CWpLinuxDrmSyncobjManagerV1>(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; });
|
||||||
|
}
|
82
src/protocols/DRMSyncobj.hpp
Normal file
82
src/protocols/DRMSyncobj.hpp
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
#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<CWpLinuxDrmSyncobjSurfaceV1> resource_, SP<CWLSurfaceResource> surface_);
|
||||||
|
|
||||||
|
bool good();
|
||||||
|
|
||||||
|
WP<CWLSurfaceResource> surface;
|
||||||
|
WP<CDRMSyncobjTimelineResource> acquireTimeline, releaseTimeline;
|
||||||
|
uint64_t acquirePoint = 0, releasePoint = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
SP<CWpLinuxDrmSyncobjSurfaceV1> resource;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
CHyprSignalListener surfacePrecommit;
|
||||||
|
} listeners;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CDRMSyncobjTimelineResource {
|
||||||
|
public:
|
||||||
|
CDRMSyncobjTimelineResource(SP<CWpLinuxDrmSyncobjTimelineV1> resource_, int fd_);
|
||||||
|
static SP<CDRMSyncobjTimelineResource> fromResource(wl_resource*);
|
||||||
|
|
||||||
|
bool good();
|
||||||
|
|
||||||
|
WP<CDRMSyncobjTimelineResource> self;
|
||||||
|
int fd = -1;
|
||||||
|
SP<CSyncTimeline> timeline;
|
||||||
|
|
||||||
|
private:
|
||||||
|
SP<CWpLinuxDrmSyncobjTimelineV1> resource;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CDRMSyncobjManagerResource {
|
||||||
|
public:
|
||||||
|
CDRMSyncobjManagerResource(SP<CWpLinuxDrmSyncobjManagerV1> resource_);
|
||||||
|
|
||||||
|
bool good();
|
||||||
|
|
||||||
|
private:
|
||||||
|
SP<CWpLinuxDrmSyncobjManagerV1> 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<SP<CDRMSyncobjManagerResource>> m_vManagers;
|
||||||
|
std::vector<SP<CDRMSyncobjTimelineResource>> m_vTimelines;
|
||||||
|
std::vector<SP<CDRMSyncobjSurfaceResource>> m_vSurfaces;
|
||||||
|
|
||||||
|
//
|
||||||
|
int drmFD = -1;
|
||||||
|
|
||||||
|
friend class CDRMSyncobjManagerResource;
|
||||||
|
friend class CDRMSyncobjTimelineResource;
|
||||||
|
friend class CDRMSyncobjSurfaceResource;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace PROTO {
|
||||||
|
inline UP<CDRMSyncobjProtocol> sync;
|
||||||
|
};
|
|
@ -52,6 +52,21 @@ CViewportResource::CViewportResource(SP<CWpViewport> resource_, SP<CWLSurfaceRes
|
||||||
surface->pending.viewport.hasSource = true;
|
surface->pending.viewport.hasSource = true;
|
||||||
surface->pending.viewport.source = {x, y, w, h};
|
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() {
|
CViewportResource::~CViewportResource() {
|
||||||
|
@ -66,20 +81,6 @@ bool CViewportResource::good() {
|
||||||
return resource->resource();
|
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<CWpViewporter> resource_) : resource(resource_) {
|
CViewporterResource::CViewporterResource(SP<CWpViewporter> resource_) : resource(resource_) {
|
||||||
if (!good())
|
if (!good())
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -15,11 +15,14 @@ class CViewportResource {
|
||||||
~CViewportResource();
|
~CViewportResource();
|
||||||
|
|
||||||
bool good();
|
bool good();
|
||||||
void verify();
|
|
||||||
WP<CWLSurfaceResource> surface;
|
WP<CWLSurfaceResource> surface;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SP<CWpViewport> resource;
|
SP<CWpViewport> resource;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
CHyprSignalListener surfacePrecommit;
|
||||||
|
} listeners;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CViewporterResource {
|
class CViewporterResource {
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
#include "Subcompositor.hpp"
|
#include "Subcompositor.hpp"
|
||||||
#include "../Viewporter.hpp"
|
#include "../Viewporter.hpp"
|
||||||
#include "../../helpers/Monitor.hpp"
|
#include "../../helpers/Monitor.hpp"
|
||||||
|
#include "../PresentationTime.hpp"
|
||||||
|
#include "../DRMSyncobj.hpp"
|
||||||
|
|
||||||
#define LOGM PROTO::compositor->protoLog
|
#define LOGM PROTO::compositor->protoLog
|
||||||
|
|
||||||
|
@ -102,58 +104,14 @@ CWLSurfaceResource::CWLSurfaceResource(SP<CWlSurface> resource_) : resource(reso
|
||||||
pending.size = tfs / pending.scale;
|
pending.size = tfs / pending.scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (viewportResource)
|
|
||||||
viewportResource->verify();
|
|
||||||
|
|
||||||
pending.damage.intersect(CBox{{}, pending.size});
|
pending.damage.intersect(CBox{{}, pending.size});
|
||||||
|
|
||||||
auto previousBuffer = current.buffer;
|
events.precommit.emit();
|
||||||
CRegion previousBufferDamage = accumulateCurrentBufferDamage();
|
if (pending.rejected)
|
||||||
|
return;
|
||||||
|
|
||||||
current = pending;
|
if (stateLocks <= 0)
|
||||||
pending.damage.clear();
|
commitPendingState();
|
||||||
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<CWLSurfaceResource> 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;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
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}); });
|
||||||
|
@ -429,6 +387,83 @@ 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() {
|
||||||
|
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<CWLSurfaceResource> 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<CQueuedPresentationData>(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<CWlCompositor> resource_) : resource(resource_) {
|
CWLCompositorResource::CWLCompositorResource(SP<CWlCompositor> resource_) : resource(resource_) {
|
||||||
if (!good())
|
if (!good())
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -24,6 +24,7 @@ class CWLSurface;
|
||||||
class CWLSurfaceResource;
|
class CWLSurfaceResource;
|
||||||
class CWLSubsurfaceResource;
|
class CWLSubsurfaceResource;
|
||||||
class CViewportResource;
|
class CViewportResource;
|
||||||
|
class CDRMSyncobjSurfaceResource;
|
||||||
|
|
||||||
class CWLCallbackResource {
|
class CWLCallbackResource {
|
||||||
public:
|
public:
|
||||||
|
@ -74,6 +75,7 @@ class CWLSurfaceResource {
|
||||||
Vector2D sourceSize();
|
Vector2D sourceSize();
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
|
CSignal precommit;
|
||||||
CSignal commit;
|
CSignal commit;
|
||||||
CSignal map;
|
CSignal map;
|
||||||
CSignal unmap;
|
CSignal unmap;
|
||||||
|
@ -81,7 +83,7 @@ class CWLSurfaceResource {
|
||||||
CSignal destroy;
|
CSignal destroy;
|
||||||
} events;
|
} events;
|
||||||
|
|
||||||
struct {
|
struct SState {
|
||||||
CRegion opaque, input = CBox{{}, {INT32_MAX, INT32_MAX}}, damage, bufferDamage = CBox{{}, {INT32_MAX, INT32_MAX}} /* initial damage */;
|
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;
|
wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL;
|
||||||
int scale = 1;
|
int scale = 1;
|
||||||
|
@ -95,6 +97,7 @@ class CWLSurfaceResource {
|
||||||
Vector2D destination;
|
Vector2D destination;
|
||||||
CBox source;
|
CBox source;
|
||||||
} viewport;
|
} viewport;
|
||||||
|
bool rejected = false;
|
||||||
|
|
||||||
//
|
//
|
||||||
void reset() {
|
void reset() {
|
||||||
|
@ -115,9 +118,13 @@ class CWLSurfaceResource {
|
||||||
std::vector<WP<CWLSubsurfaceResource>> subsurfaces;
|
std::vector<WP<CWLSubsurfaceResource>> subsurfaces;
|
||||||
WP<ISurfaceRole> role;
|
WP<ISurfaceRole> role;
|
||||||
WP<CViewportResource> viewportResource;
|
WP<CViewportResource> viewportResource;
|
||||||
|
WP<CDRMSyncobjSurfaceResource> syncobj; // may not be present
|
||||||
|
|
||||||
void breadthfirst(std::function<void(SP<CWLSurfaceResource>, const Vector2D&, void*)> fn, void* data);
|
void breadthfirst(std::function<void(SP<CWLSurfaceResource>, const Vector2D&, void*)> fn, void* data);
|
||||||
CRegion accumulateCurrentBufferDamage();
|
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.
|
// 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
|
||||||
|
@ -130,7 +137,10 @@ class CWLSurfaceResource {
|
||||||
// tracks whether we should release the buffer
|
// tracks whether we should release the buffer
|
||||||
bool bufferReleased = false;
|
bool bufferReleased = false;
|
||||||
|
|
||||||
|
int stateLocks = 0;
|
||||||
|
|
||||||
void destroy();
|
void destroy();
|
||||||
|
void commitPendingState();
|
||||||
void bfHelper(std::vector<SP<CWLSurfaceResource>> nodes, std::function<void(SP<CWLSurfaceResource>, const Vector2D&, void*)> fn, void* data);
|
void bfHelper(std::vector<SP<CWLSurfaceResource>> nodes, std::function<void(SP<CWLSurfaceResource>, const Vector2D&, void*)> fn, void* data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -171,6 +171,14 @@ CHyprOpenGLImpl::CHyprOpenGLImpl() {
|
||||||
loadGLProc(&m_sProc.glEGLImageTargetTexture2DOES, "glEGLImageTargetTexture2DOES");
|
loadGLProc(&m_sProc.glEGLImageTargetTexture2DOES, "glEGLImageTargetTexture2DOES");
|
||||||
loadGLProc(&m_sProc.eglDebugMessageControlKHR, "eglDebugMessageControlKHR");
|
loadGLProc(&m_sProc.eglDebugMessageControlKHR, "eglDebugMessageControlKHR");
|
||||||
loadGLProc(&m_sProc.eglGetPlatformDisplayEXT, "eglGetPlatformDisplayEXT");
|
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"))
|
if (EGLEXTENSIONS.contains("EGL_EXT_device_base") || EGLEXTENSIONS.contains("EGL_EXT_device_enumeration"))
|
||||||
loadGLProc(&m_sProc.eglQueryDevicesEXT, "eglQueryDevicesEXT");
|
loadGLProc(&m_sProc.eglQueryDevicesEXT, "eglQueryDevicesEXT");
|
||||||
|
@ -2727,6 +2735,30 @@ std::vector<SDRMFormat> CHyprOpenGLImpl::getDRMFormats() {
|
||||||
return drmFormats;
|
return drmFormats;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SP<CEGLSync> CHyprOpenGLImpl::createEGLSync(int fenceFD) {
|
||||||
|
std::vector<EGLint> 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<CEGLSync>(new CEGLSync);
|
||||||
|
eglsync->sync = sync;
|
||||||
|
return eglsync;
|
||||||
|
}
|
||||||
|
|
||||||
void SRenderModifData::applyToBox(CBox& box) {
|
void SRenderModifData::applyToBox(CBox& box) {
|
||||||
if (!enabled)
|
if (!enabled)
|
||||||
return;
|
return;
|
||||||
|
@ -2787,3 +2819,35 @@ float SRenderModifData::combinedScale() {
|
||||||
}
|
}
|
||||||
return scale;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -123,6 +123,21 @@ struct SCurrentRenderData {
|
||||||
float discardOpacity = 0.f;
|
float discardOpacity = 0.f;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class CEGLSync {
|
||||||
|
public:
|
||||||
|
~CEGLSync();
|
||||||
|
|
||||||
|
EGLSyncKHR sync = nullptr;
|
||||||
|
|
||||||
|
int dupFenceFD();
|
||||||
|
bool wait();
|
||||||
|
|
||||||
|
private:
|
||||||
|
CEGLSync() = default;
|
||||||
|
|
||||||
|
friend class CHyprOpenGLImpl;
|
||||||
|
};
|
||||||
|
|
||||||
class CGradientValueData;
|
class CGradientValueData;
|
||||||
|
|
||||||
class CHyprOpenGLImpl {
|
class CHyprOpenGLImpl {
|
||||||
|
@ -187,6 +202,7 @@ class CHyprOpenGLImpl {
|
||||||
uint32_t getPreferredReadFormat(CMonitor* pMonitor);
|
uint32_t getPreferredReadFormat(CMonitor* pMonitor);
|
||||||
std::vector<SDRMFormat> getDRMFormats();
|
std::vector<SDRMFormat> getDRMFormats();
|
||||||
EGLImageKHR createEGLImage(const Aquamarine::SDMABUFAttrs& attrs);
|
EGLImageKHR createEGLImage(const Aquamarine::SDMABUFAttrs& attrs);
|
||||||
|
SP<CEGLSync> createEGLSync(int fenceFD);
|
||||||
|
|
||||||
SCurrentRenderData m_RenderData;
|
SCurrentRenderData m_RenderData;
|
||||||
|
|
||||||
|
@ -219,6 +235,10 @@ class CHyprOpenGLImpl {
|
||||||
PFNEGLQUERYDEVICESEXTPROC eglQueryDevicesEXT = nullptr;
|
PFNEGLQUERYDEVICESEXTPROC eglQueryDevicesEXT = nullptr;
|
||||||
PFNEGLQUERYDEVICESTRINGEXTPROC eglQueryDeviceStringEXT = nullptr;
|
PFNEGLQUERYDEVICESTRINGEXTPROC eglQueryDeviceStringEXT = nullptr;
|
||||||
PFNEGLQUERYDISPLAYATTRIBEXTPROC eglQueryDisplayAttribEXT = nullptr;
|
PFNEGLQUERYDISPLAYATTRIBEXTPROC eglQueryDisplayAttribEXT = nullptr;
|
||||||
|
PFNEGLCREATESYNCKHRPROC eglCreateSyncKHR = nullptr;
|
||||||
|
PFNEGLDESTROYSYNCKHRPROC eglDestroySyncKHR = nullptr;
|
||||||
|
PFNEGLDUPNATIVEFENCEFDANDROIDPROC eglDupNativeFenceFDANDROID = nullptr;
|
||||||
|
PFNEGLWAITSYNCKHRPROC eglWaitSyncKHR = nullptr;
|
||||||
} m_sProc;
|
} m_sProc;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
|
|
|
@ -14,6 +14,8 @@
|
||||||
#include "../protocols/PresentationTime.hpp"
|
#include "../protocols/PresentationTime.hpp"
|
||||||
#include "../protocols/core/DataDevice.hpp"
|
#include "../protocols/core/DataDevice.hpp"
|
||||||
#include "../protocols/core/Compositor.hpp"
|
#include "../protocols/core/Compositor.hpp"
|
||||||
|
#include "../protocols/DRMSyncobj.hpp"
|
||||||
|
#include "../helpers/sync/SyncTimeline.hpp"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <xf86drm.h>
|
#include <xf86drm.h>
|
||||||
|
@ -109,6 +111,27 @@ static void renderSurface(SP<CWLSurfaceResource> surface, int x, int y, void* da
|
||||||
if (!TEXTURE->m_iTexID)
|
if (!TEXTURE->m_iTexID)
|
||||||
return;
|
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");
|
TRACY_GPU_ZONE("RenderSurface");
|
||||||
|
|
||||||
double outputX = -RDATA->pMonitor->vecPosition.x, outputY = -RDATA->pMonitor->vecPosition.y;
|
double outputX = -RDATA->pMonitor->vecPosition.x, outputY = -RDATA->pMonitor->vecPosition.y;
|
||||||
|
@ -167,13 +190,9 @@ static void renderSurface(SP<CWLSurfaceResource> surface, int x, int y, void* da
|
||||||
}
|
}
|
||||||
|
|
||||||
if (windowBox.width <= 1 || windowBox.height <= 1) {
|
if (windowBox.width <= 1 || windowBox.height <= 1) {
|
||||||
if (!g_pHyprRenderer->m_bBlockSurfaceFeedback) {
|
if (!g_pHyprRenderer->m_bBlockSurfaceFeedback)
|
||||||
surface->frame(RDATA->when);
|
surface->presentFeedback(RDATA->when, RDATA->pMonitor);
|
||||||
auto FEEDBACK = makeShared<CQueuedPresentationData>(surface);
|
|
||||||
FEEDBACK->attachMonitor(RDATA->pMonitor);
|
|
||||||
FEEDBACK->discarded();
|
|
||||||
PROTO::presentation->queueData(FEEDBACK);
|
|
||||||
}
|
|
||||||
return; // invisible
|
return; // invisible
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,13 +244,8 @@ static void renderSurface(SP<CWLSurfaceResource> surface, int x, int y, void* da
|
||||||
g_pHyprOpenGL->renderTexture(TEXTURE, &windowBox, ALPHA, rounding, false, true);
|
g_pHyprOpenGL->renderTexture(TEXTURE, &windowBox, ALPHA, rounding, false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!g_pHyprRenderer->m_bBlockSurfaceFeedback) {
|
if (!g_pHyprRenderer->m_bBlockSurfaceFeedback)
|
||||||
surface->frame(RDATA->when);
|
surface->presentFeedback(RDATA->when, RDATA->pMonitor);
|
||||||
auto FEEDBACK = makeShared<CQueuedPresentationData>(surface);
|
|
||||||
FEEDBACK->attachMonitor(RDATA->pMonitor);
|
|
||||||
FEEDBACK->presented();
|
|
||||||
PROTO::presentation->queueData(FEEDBACK);
|
|
||||||
}
|
|
||||||
|
|
||||||
g_pHyprOpenGL->blend(true);
|
g_pHyprOpenGL->blend(true);
|
||||||
|
|
||||||
|
@ -1111,12 +1125,7 @@ bool CHyprRenderer::attemptDirectScanout(CMonitor* pMonitor) {
|
||||||
|
|
||||||
timespec now;
|
timespec now;
|
||||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||||
PSURFACE->frame(&now);
|
PSURFACE->presentFeedback(&now, pMonitor);
|
||||||
auto FEEDBACK = makeShared<CQueuedPresentationData>(PSURFACE);
|
|
||||||
FEEDBACK->attachMonitor(pMonitor);
|
|
||||||
FEEDBACK->presented();
|
|
||||||
FEEDBACK->setPresentationType(true);
|
|
||||||
PROTO::presentation->queueData(FEEDBACK);
|
|
||||||
|
|
||||||
if (pMonitor->state.commit()) {
|
if (pMonitor->state.commit()) {
|
||||||
if (m_pLastScanout.expired()) {
|
if (m_pLastScanout.expired()) {
|
||||||
|
@ -1421,6 +1430,18 @@ void CHyprRenderer::renderMonitor(CMonitor* pMonitor) {
|
||||||
pMonitor->output->state->setPresentationMode(shouldTear ? Aquamarine::eOutputPresentationMode::AQ_OUTPUT_PRESENTATION_IMMEDIATE :
|
pMonitor->output->state->setPresentationMode(shouldTear ? Aquamarine::eOutputPresentationMode::AQ_OUTPUT_PRESENTATION_IMMEDIATE :
|
||||||
Aquamarine::eOutputPresentationMode::AQ_OUTPUT_PRESENTATION_VSYNC);
|
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()) {
|
if (!pMonitor->state.commit()) {
|
||||||
// rollback the buffer to avoid writing to the front buffer that is being
|
// rollback the buffer to avoid writing to the front buffer that is being
|
||||||
// displayed
|
// displayed
|
||||||
|
|
|
@ -99,6 +99,8 @@ class CHyprRenderer {
|
||||||
|
|
||||||
CTimer m_tRenderTimer;
|
CTimer m_tRenderTimer;
|
||||||
|
|
||||||
|
std::vector<SP<CWLSurfaceResource>> explicitPresented;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
int hotspotX;
|
int hotspotX;
|
||||||
int hotspotY;
|
int hotspotY;
|
||||||
|
|
Loading…
Reference in a new issue