mirror of
https://github.com/hyprwm/Hyprland.git
synced 2026-08-24 10:04:19 -05:00
compositor: merge ready states in the surface queue (#15504)
* compositor: merge ready states in the surface queue each waiter was previously added to wl_event_loop. but the wl_event_loop has no order guarantee, so it could mean fence A, B, C sits in the queue ready but processes B first then C, then A, and now we commit A -> B -> C, change waiters to return a WP to the SReadableWaiter so we can easily track them, iterate in reverse order check if readable. then later in tryprocess merge all ready states into one commit. meaning A merges B and C, now we commit C. fifo is untouched because its FIFO. this is only for fences and mailbox rendering. * fifo: with states merging we dont want empty fifo commits with states merging, we really dont want empty fifo commits that early returns, just makes it a 3 place if else guard for no gain. move it all into the fifo protocol instead. now we only fifo lock if both setSet and setWait is called. the client wants it locked for presentation. however we can only lock it if previous fifo barrier has been satisfied. otherwise its going to wait for a presentation that never comes meaning we never commit the new state that waits for the rendererer to catch it in the render pass. * presentation: discard directly instead of queueing discard presentation directly instead of queuing for a presentation event that might never come. * syncobj: remove a O(n) resource lookup seen in profiling as a big cpu spender use what other protocols uses for fromResource * fifo: dont fifo lock synced subsurfaces The constraint must be ignored if the surface is a subsurface in synchronized mode. also use setBarrier for setting waitingonpresentation because. protocol states this. so any client abusing this has only itself to blame. when the content update containing "set_barrier" was made active at a latching deadline, it will be active for at least one refresh cycle
This commit is contained in:
@@ -645,7 +645,6 @@ std::vector<SP<IValue>> Values::getConfigValues() {
|
||||
MS<Bool>("debug:full_cm_proto", "claims support for all cm proto features (requires restart)", false),
|
||||
MS<Bool>("debug:ds_handle_same_buffer", "Special case for DS with unmodified buffer", true),
|
||||
MS<Bool>("debug:ds_handle_same_buffer_fifo", "Special case for DS with unmodified buffer unlocks fifo", true),
|
||||
MS<Bool>("debug:fifo_pending_workaround", "Fifo workaround for empty pending list", false),
|
||||
MS<Bool>("debug:render_solitary_wo_damage", "Render solitary window with empty damage", false),
|
||||
MS<Bool>("debug:vfr", "controls the VFR status of Hyprland. Do not turn off unless debugging", true),
|
||||
MS<Int>("debug:invalidate_fp16", "allow fp16 buffer invalidation.", 1, {.min = 0, .max = 2, .map = OptionMap{{"disable", 0}, {"enable", 1}, {"auto", 2}}}),
|
||||
|
||||
@@ -64,22 +64,20 @@ std::optional<bool> CSyncTimeline::check(uint64_t point, uint32_t flags) {
|
||||
return ret == 0;
|
||||
}
|
||||
|
||||
bool CSyncTimeline::addWaiter(std::function<void()>&& waiter, uint64_t point, uint32_t flags) {
|
||||
WP<SReadableWaiter> CSyncTimeline::addWaiter(std::function<void()>&& waiter, uint64_t point, uint32_t flags) {
|
||||
auto eventFd = CFileDescriptor(eventfd(0, EFD_CLOEXEC));
|
||||
|
||||
if (!eventFd.isValid()) {
|
||||
Log::logger->log(Log::ERR, "CSyncTimeline::addWaiter: failed to acquire an eventfd");
|
||||
return false;
|
||||
return {};
|
||||
}
|
||||
|
||||
if (drmSyncobjEventfd(m_drmFD, m_handle, point, eventFd.get(), flags)) {
|
||||
Log::logger->log(Log::ERR, "CSyncTimeline::addWaiter: drmSyncobjEventfd failed");
|
||||
return false;
|
||||
return {};
|
||||
}
|
||||
|
||||
g_pEventLoopManager->doOnReadable(std::move(eventFd), std::move(waiter));
|
||||
|
||||
return true;
|
||||
return g_pEventLoopManager->doOnReadable(std::move(eventFd), std::move(waiter));
|
||||
}
|
||||
|
||||
CFileDescriptor CSyncTimeline::exportAsSyncFileFD(uint64_t src) {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
*/
|
||||
|
||||
struct wl_event_source;
|
||||
struct SReadableWaiter;
|
||||
|
||||
class CSyncTimeline {
|
||||
public:
|
||||
@@ -25,7 +26,7 @@ class CSyncTimeline {
|
||||
// std::nullopt on fail
|
||||
std::optional<bool> check(uint64_t point, uint32_t flags);
|
||||
|
||||
bool addWaiter(std::function<void()>&& waiter, uint64_t point, uint32_t flags);
|
||||
WP<SReadableWaiter> addWaiter(std::function<void()>&& waiter, uint64_t point, uint32_t flags);
|
||||
Hyprutils::OS::CFileDescriptor exportAsSyncFileFD(uint64_t src);
|
||||
bool importFromSyncFileFD(uint64_t dst, Hyprutils::OS::CFileDescriptor& fd);
|
||||
bool transfer(SP<CSyncTimeline> from, uint64_t fromPoint, uint64_t toPoint);
|
||||
|
||||
@@ -73,7 +73,7 @@ static int configWatcherWrite(int fd, uint32_t mask, void* data) {
|
||||
}
|
||||
|
||||
static int handleWaiterFD(int fd, uint32_t mask, void* data) {
|
||||
auto waiter = sc<CEventLoopManager::SReadableWaiter*>(data);
|
||||
auto waiter = sc<SReadableWaiter*>(data);
|
||||
|
||||
if (!waiter) {
|
||||
Log::logger->log(Log::ERR, "handleWaiterFD: failed casting waiter");
|
||||
@@ -250,14 +250,24 @@ UP<SEventLoopDoLaterLock> CEventLoopManager::doLaterLock(const std::function<voi
|
||||
return makeUnique<SEventLoopDoLaterLock>(doLater(fn));
|
||||
}
|
||||
|
||||
void CEventLoopManager::doOnReadable(CFileDescriptor fd, std::function<void()>&& fn) {
|
||||
WP<SReadableWaiter> CEventLoopManager::doOnReadable(CFileDescriptor fd, std::function<void()>&& fn) {
|
||||
if (!fd.isValid() || fd.isReadable()) {
|
||||
fn();
|
||||
return;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto& waiter = m_readableWaiters.emplace_back(makeUnique<SReadableWaiter>(nullptr, std::move(fd), std::move(fn)));
|
||||
waiter->source = wl_event_loop_add_fd(g_pEventLoopManager->m_wayland.loop, waiter->fd.get(), WL_EVENT_READABLE, ::handleWaiterFD, waiter.get());
|
||||
|
||||
return waiter;
|
||||
}
|
||||
|
||||
void CEventLoopManager::removeReadableWaiter(const WP<SReadableWaiter>& waiter) {
|
||||
if (!waiter)
|
||||
return;
|
||||
|
||||
// erasing the owning UP runs ~SReadableWaiter, which removes the wl_event_source.
|
||||
std::erase_if(m_readableWaiters, [&waiter](const UP<SReadableWaiter>& w) { return waiter == w; });
|
||||
}
|
||||
|
||||
void CEventLoopManager::syncPollFDs() {
|
||||
|
||||
@@ -21,6 +21,29 @@ struct SEventLoopDoLaterLock {
|
||||
uint64_t seq = 0;
|
||||
};
|
||||
|
||||
struct SReadableWaiter {
|
||||
wl_event_source* source;
|
||||
Hyprutils::OS::CFileDescriptor fd;
|
||||
std::function<void()> fn;
|
||||
|
||||
SReadableWaiter(wl_event_source* src, Hyprutils::OS::CFileDescriptor f, std::function<void()> func) : source(src), fd(std::move(f)), fn(std::move(func)) {}
|
||||
|
||||
~SReadableWaiter() {
|
||||
if (source) {
|
||||
wl_event_source_remove(source);
|
||||
source = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// copy
|
||||
SReadableWaiter(const SReadableWaiter&) = delete;
|
||||
SReadableWaiter& operator=(const SReadableWaiter&) = delete;
|
||||
|
||||
// move
|
||||
SReadableWaiter(SReadableWaiter&& other) noexcept = default;
|
||||
SReadableWaiter& operator=(SReadableWaiter&& other) noexcept = default;
|
||||
};
|
||||
|
||||
class CEventLoopManager {
|
||||
public:
|
||||
CEventLoopManager(wl_display* display, wl_event_loop* wlEventLoop);
|
||||
@@ -49,34 +72,10 @@ class CEventLoopManager {
|
||||
std::vector<std::pair<uint64_t, std::function<void()>>> fns;
|
||||
};
|
||||
|
||||
struct SReadableWaiter {
|
||||
wl_event_source* source;
|
||||
Hyprutils::OS::CFileDescriptor fd;
|
||||
std::function<void()> fn;
|
||||
|
||||
SReadableWaiter(wl_event_source* src, Hyprutils::OS::CFileDescriptor f, std::function<void()> func) : source(src), fd(std::move(f)), fn(std::move(func)) {}
|
||||
|
||||
~SReadableWaiter() {
|
||||
if (source) {
|
||||
wl_event_source_remove(source);
|
||||
source = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// copy
|
||||
SReadableWaiter(const SReadableWaiter&) = delete;
|
||||
SReadableWaiter& operator=(const SReadableWaiter&) = delete;
|
||||
|
||||
// move
|
||||
SReadableWaiter(SReadableWaiter&& other) noexcept = default;
|
||||
SReadableWaiter& operator=(SReadableWaiter&& other) noexcept = default;
|
||||
};
|
||||
|
||||
// schedule function to when fd is readable (WL_EVENT_READABLE / POLLIN),
|
||||
// takes ownership of fd
|
||||
void doOnReadable(Hyprutils::OS::CFileDescriptor fd, std::function<void()>&& fn);
|
||||
void onFdReadable(SReadableWaiter* waiter);
|
||||
void onFdReadableFail(SReadableWaiter* waiter);
|
||||
WP<SReadableWaiter> doOnReadable(Hyprutils::OS::CFileDescriptor fd, std::function<void()>&& fn);
|
||||
void removeReadableWaiter(const WP<SReadableWaiter>& waiter);
|
||||
void onFdReadable(SReadableWaiter* waiter);
|
||||
void onFdReadableFail(SReadableWaiter* waiter);
|
||||
|
||||
private:
|
||||
// Manages the event sources after AQ pollFDs change.
|
||||
|
||||
@@ -27,7 +27,7 @@ UP<CSyncReleaser> CDRMSyncPointState::createSyncRelease() {
|
||||
return makeUnique<CSyncReleaser>(m_timeline, m_point);
|
||||
}
|
||||
|
||||
bool CDRMSyncPointState::addWaiter(std::function<void()>&& waiter) {
|
||||
WP<SReadableWaiter> CDRMSyncPointState::addWaiter(std::function<void()>&& waiter) {
|
||||
m_acquireCommitted = true;
|
||||
return m_timeline->addWaiter(std::move(waiter), m_point, 0u);
|
||||
}
|
||||
@@ -134,12 +134,9 @@ CDRMSyncobjTimelineResource::CDRMSyncobjTimelineResource(UP<CWpLinuxDrmSyncobjTi
|
||||
}
|
||||
|
||||
WP<CDRMSyncobjTimelineResource> CDRMSyncobjTimelineResource::fromResource(wl_resource* res) {
|
||||
for (const auto& r : PROTO::sync->m_timelines) {
|
||||
if (r && r->m_resource && r->m_resource->resource() == res)
|
||||
return r;
|
||||
}
|
||||
|
||||
return {};
|
||||
auto resource = sc<CWpLinuxDrmSyncobjTimelineV1*>(wl_resource_get_user_data(res));
|
||||
auto data = resource ? sc<CDRMSyncobjTimelineResource*>(resource->data()) : nullptr;
|
||||
return data ? data->m_self : WP<CDRMSyncobjTimelineResource>{};
|
||||
}
|
||||
|
||||
bool CDRMSyncobjTimelineResource::good() {
|
||||
@@ -192,6 +189,8 @@ CDRMSyncobjManagerResource::CDRMSyncobjManagerResource(UP<CWpLinuxDrmSyncobjMana
|
||||
return;
|
||||
}
|
||||
|
||||
RESOURCE->m_self = RESOURCE;
|
||||
|
||||
LOGM(Log::DEBUG, "New linux_drm_timeline at {:x}", (uintptr_t)RESOURCE.get());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
class CWLSurfaceResource;
|
||||
class CDRMSyncobjTimelineResource;
|
||||
class CSyncTimeline;
|
||||
struct SReadableWaiter;
|
||||
|
||||
class CDRMSyncPointState {
|
||||
public:
|
||||
@@ -20,7 +21,7 @@ class CDRMSyncPointState {
|
||||
const uint64_t& point();
|
||||
WP<CSyncTimeline> timeline();
|
||||
Hyprutils::Memory::CUniquePointer<CSyncReleaser> createSyncRelease();
|
||||
bool addWaiter(std::function<void()>&& waiter);
|
||||
WP<SReadableWaiter> addWaiter(std::function<void()>&& waiter);
|
||||
bool committed();
|
||||
Hyprutils::OS::CFileDescriptor exportAsFD();
|
||||
void signal();
|
||||
@@ -63,6 +64,7 @@ class CDRMSyncobjTimelineResource {
|
||||
|
||||
bool good();
|
||||
|
||||
WP<CDRMSyncobjTimelineResource> m_self;
|
||||
Hyprutils::OS::CFileDescriptor m_fd;
|
||||
SP<CSyncTimeline> m_timeline;
|
||||
|
||||
|
||||
+76
-53
@@ -1,6 +1,7 @@
|
||||
#include "Fifo.hpp"
|
||||
#include "Compositor.hpp"
|
||||
#include "core/Compositor.hpp"
|
||||
#include "core/Subcompositor.hpp"
|
||||
#include "../output/Monitor.hpp"
|
||||
#include "../event/EventBus.hpp"
|
||||
#include "../state/MonitorState.hpp"
|
||||
@@ -9,6 +10,28 @@
|
||||
#include <algorithm>
|
||||
#include <hyprutils/memory/WeakPtr.hpp>
|
||||
|
||||
// what nvidia says about the empty extra barrier commit.
|
||||
/*
|
||||
* If the window is not visible (occluded, monitor on standby,
|
||||
* etc), then we could be waiting for an indefinite amount of time
|
||||
* for the compositor to send a wp_presentation_feedback::presented
|
||||
* or discarded event.
|
||||
*
|
||||
* But, wp_fifo_v1 is required to unblock in finite time, so we can
|
||||
* send an extra dummy commit with a wp_fifo_v1::wait_barrier.
|
||||
*
|
||||
* If the window is visible, then the compositor will send a
|
||||
* presented event as normal, and if the window is not visible,
|
||||
* then the second commit will trigger a discarded event.
|
||||
*
|
||||
* Note that the compositor may trigger a discarded event
|
||||
* immediately, so we use wp_commit_timer_v1 above to try to
|
||||
* throttle things to a sane rate.
|
||||
*
|
||||
* Ugly as this is, Mesa relies on the same behavior, so it's
|
||||
* probably safe to treat this as the "intended" behavior.
|
||||
*/
|
||||
|
||||
CFifoResource::CFifoResource(UP<CWpFifoV1>&& resource_, SP<CWLSurfaceResource> surface) : m_resource(std::move(resource_)), m_surface(surface) {
|
||||
if UNLIKELY (!m_resource->resource())
|
||||
return;
|
||||
@@ -23,8 +46,7 @@ CFifoResource::CFifoResource(UP<CWpFifoV1>&& resource_, SP<CWLSurfaceResource> s
|
||||
return;
|
||||
}
|
||||
|
||||
m_surface->m_pending.barrierSet = true;
|
||||
m_surface->m_pending.updated.bits.fifo = true;
|
||||
m_surface->m_pending.barrierSet = true;
|
||||
});
|
||||
|
||||
m_resource->setWaitBarrier([this](CWpFifoV1* r) {
|
||||
@@ -33,59 +55,61 @@ CFifoResource::CFifoResource(UP<CWpFifoV1>&& resource_, SP<CWLSurfaceResource> s
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_surface->m_current.barrierSet) {
|
||||
// that might mean an empty commit with a barrier_set alone
|
||||
static const auto PPEND = CConfigValue<Config::INTEGER>("debug:fifo_pending_workaround");
|
||||
if (!m_surface->m_pending.fifoScheduled)
|
||||
m_surface->m_pending.fifoScheduled = checkMonitors(*PPEND);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
m_surface->m_pending.surfaceLocked = true;
|
||||
m_surface->m_pending.barrierWait = true;
|
||||
});
|
||||
|
||||
m_listeners.surfaceStateCommit = m_surface->m_events.stateCommit.listen([this](auto state) {
|
||||
if (!state || !state->surfaceLocked)
|
||||
if (!state)
|
||||
return;
|
||||
|
||||
static const auto PPEND = CConfigValue<Config::INTEGER>("debug:fifo_pending_workaround");
|
||||
static const auto PINVIS = CConfigValue<Hyprlang::INT>("render:not_shown_fifo_lock");
|
||||
|
||||
//#TODO:
|
||||
// this feels wrong, but if we have no pending frames, presented might never come because
|
||||
// we are waiting on the barrier to unlock and no damage is around.
|
||||
// unlock on timeout instead?
|
||||
if (!state->fifoScheduled)
|
||||
state->fifoScheduled = checkMonitors(*PPEND);
|
||||
|
||||
if (!state->fifoScheduled)
|
||||
if (!state->barrierSet && !state->barrierWait)
|
||||
return;
|
||||
|
||||
// only lock once its mapped and visible
|
||||
if (m_surface->m_mapped) {
|
||||
bool shouldLock = *PINVIS == 0 || !m_surface->m_hlSurface; // always && unknown
|
||||
if (!shouldLock && m_surface->m_hlSurface) {
|
||||
const auto& view = m_surface->m_hlSurface->view();
|
||||
if (view) {
|
||||
const auto& window = view->type() == Desktop::View::VIEW_TYPE_WINDOW ? dynamicPointerCast<Desktop::View::CWindow>(view) : nullptr;
|
||||
const bool isVisible = (view && view->visible() && //
|
||||
(!window || std::ranges::any_of(State::monitorState()->monitors(), [window](const auto& mon) {
|
||||
return g_pHyprRenderer->shouldRenderWindow(window, mon);
|
||||
})));
|
||||
if (isVisible)
|
||||
shouldLock = true;
|
||||
else if (*PINVIS == 2) // never
|
||||
shouldLock = false;
|
||||
else if (window && window->m_ruleApplicator->renderUnfocused().valueOr(false))
|
||||
shouldLock = false; // ignore render_unfocused
|
||||
else
|
||||
shouldLock = true;
|
||||
} else
|
||||
shouldLock = true;
|
||||
// the barrier constraint must be ignored for a subsurface in synchronized mode.
|
||||
if (m_surface->m_role->role() == SURFACE_ROLE_SUBSURFACE) {
|
||||
const auto sub = dynamicPointerCast<CSubsurfaceRole>(m_surface->m_role);
|
||||
if (sub) {
|
||||
const auto subsurface = sub->m_subsurface.lock();
|
||||
if (subsurface && subsurface->m_sync)
|
||||
return;
|
||||
}
|
||||
if (shouldLock)
|
||||
}
|
||||
|
||||
// check if entered outputs yet, and they are not tearing.
|
||||
if (!checkMonitors())
|
||||
return;
|
||||
|
||||
// only lock once its mapped and visible and actually has something waiting for a presentation.
|
||||
if (m_surface->m_mapped && m_surface->m_current.waitingOnPresentation) {
|
||||
bool shouldLock = false;
|
||||
if (state->barrierSet && state->barrierWait) {
|
||||
static const auto PINVIS = CConfigValue<Hyprlang::INT>("render:not_shown_fifo_lock");
|
||||
shouldLock = *PINVIS == 0 || !m_surface->m_hlSurface; // always && unknown
|
||||
if (!shouldLock && m_surface->m_hlSurface) {
|
||||
const auto& view = m_surface->m_hlSurface->view();
|
||||
if (view) {
|
||||
const auto& window = view->type() == Desktop::View::VIEW_TYPE_WINDOW ? dynamicPointerCast<Desktop::View::CWindow>(view) : nullptr;
|
||||
const bool isVisible = (view && view->visible() && //
|
||||
(!window || std::ranges::any_of(State::monitorState()->monitors(), [window](const auto& mon) {
|
||||
return g_pHyprRenderer->shouldRenderWindow(window, mon);
|
||||
})));
|
||||
if (isVisible)
|
||||
shouldLock = true;
|
||||
else if (*PINVIS == 2) // never
|
||||
shouldLock = false;
|
||||
else if (window && window->m_ruleApplicator->renderUnfocused().valueOr(false))
|
||||
shouldLock = false; // ignore render_unfocused
|
||||
else
|
||||
shouldLock = true;
|
||||
} else
|
||||
shouldLock = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldLock) {
|
||||
state->updated.bits.fifo = true;
|
||||
m_surface->m_stateQueue.lock(state, LOCK_REASON_FIFO);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -99,11 +123,12 @@ bool CFifoResource::good() {
|
||||
}
|
||||
|
||||
void CFifoResource::presented() {
|
||||
m_surface->m_current.barrierSet = false;
|
||||
m_surface->m_current.waitingOnPresentation = false;
|
||||
m_surface->m_stateQueue.unlockFirst(LOCK_REASON_FIFO);
|
||||
}
|
||||
|
||||
bool CFifoResource::checkMonitors(bool needsSchedule) {
|
||||
bool CFifoResource::checkMonitors() {
|
||||
bool allowFifo = false;
|
||||
if (m_surface->m_enteredOutputs.empty() && m_surface->m_hlSurface) {
|
||||
for (auto& m : State::monitorState()->monitors()) {
|
||||
if (!m || !m->m_enabled)
|
||||
@@ -114,8 +139,7 @@ bool CFifoResource::checkMonitors(bool needsSchedule) {
|
||||
if (m->m_tearingState.activelyTearing)
|
||||
return false; // dont fifo lock on tearing.
|
||||
|
||||
if (needsSchedule)
|
||||
m->scheduleFrame(Aquamarine::IOutput::AQ_SCHEDULE_NEEDS_FRAME);
|
||||
allowFifo = true; // intersects.
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -126,12 +150,11 @@ bool CFifoResource::checkMonitors(bool needsSchedule) {
|
||||
if (m->m_tearingState.activelyTearing)
|
||||
return false; // dont fifo lock on tearing.
|
||||
|
||||
if (needsSchedule)
|
||||
m->scheduleFrame(Aquamarine::IOutput::AQ_SCHEDULE_NEEDS_FRAME);
|
||||
allowFifo = true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return allowFifo;
|
||||
}
|
||||
|
||||
CFifoManagerResource::CFifoManagerResource(UP<CWpFifoManagerV1>&& resource_) : m_resource(std::move(resource_)) {
|
||||
|
||||
@@ -26,7 +26,7 @@ class CFifoResource {
|
||||
} m_listeners;
|
||||
|
||||
void presented();
|
||||
bool checkMonitors(bool needsSchedule = false);
|
||||
bool checkMonitors();
|
||||
|
||||
friend class CFifoProtocol;
|
||||
friend class CFifoManagerResource;
|
||||
|
||||
@@ -591,22 +591,25 @@ CBox CWLSurfaceResource::extends() {
|
||||
}
|
||||
|
||||
void CWLSurfaceResource::scheduleState(WP<SSurfaceState> state) {
|
||||
auto whenReadable = [this, surf = m_self](auto state, auto reason) {
|
||||
auto whenReadable = [this, surf = m_self](WP<SSurfaceState> state) {
|
||||
if (!surf || !state)
|
||||
return;
|
||||
|
||||
m_stateQueue.unlock(state, reason);
|
||||
m_stateQueue.unlockFence(state);
|
||||
};
|
||||
|
||||
if (state->updated.bits.acquire) {
|
||||
// wait on acquire point for this surface, from explicit sync protocol
|
||||
if (!state->acquire.addWaiter([state, whenReadable]() { whenReadable(state, LOCK_REASON_FENCE); })) {
|
||||
Log::logger->log(Log::ERR, "Failed to addWaiter in CWLSurfaceResource::scheduleState");
|
||||
whenReadable(state, LOCK_REASON_FENCE);
|
||||
auto waiter = state->acquire.addWaiter([state, whenReadable]() { whenReadable(state); });
|
||||
// the waiter may have fired (and dropped this state), so re check.
|
||||
if (state) {
|
||||
state->acquireWaiter = waiter;
|
||||
// a null waiter means it either fired immediately or failed to register
|
||||
if (!waiter)
|
||||
whenReadable(state);
|
||||
}
|
||||
} else if (state->buffer && state->buffer->isSynchronous()) {
|
||||
// synchronous (shm) buffers can be read immediately
|
||||
m_stateQueue.unlock(state, LOCK_REASON_FENCE);
|
||||
m_stateQueue.unlockFence(state);
|
||||
} else if (state->buffer && !state->buffer->m_syncFds.empty()) {
|
||||
// async buffer and is dmabuf, then we can wait on implicit fences
|
||||
drainSyncFds(state, LOCK_REASON_FENCE);
|
||||
@@ -624,32 +627,25 @@ void CWLSurfaceResource::drainSyncFds(WP<SSurfaceState> state, eLockReason reaso
|
||||
if (!fds.empty()) {
|
||||
auto fd = std::move(fds.front());
|
||||
fds.erase(fds.begin());
|
||||
g_pEventLoopManager->doOnReadable(std::move(fd), [this, surf = m_self, state, reason]() {
|
||||
auto waiter = g_pEventLoopManager->doOnReadable(std::move(fd), [this, surf = m_self, state, reason]() {
|
||||
if (!surf || !state)
|
||||
return;
|
||||
|
||||
drainSyncFds(state, reason);
|
||||
});
|
||||
|
||||
if (state)
|
||||
state->acquireWaiter = waiter;
|
||||
return;
|
||||
}
|
||||
|
||||
m_stateQueue.unlock(state, reason);
|
||||
m_stateQueue.unlockFence(state);
|
||||
}
|
||||
|
||||
void CWLSurfaceResource::commitState(SSurfaceState& state) {
|
||||
// TODO might be incorrect. needed for VRR with FIFO to avoid same buffer extra frames for second commit when it's used in this way:
|
||||
// wp_fifo_v1#43.set_barrier()
|
||||
// wp_fifo_v1#43.wait_barrier()
|
||||
// wl_surface#3.commit()
|
||||
// wp_fifo_v1#43.wait_barrier()
|
||||
// wl_surface#3.commit()
|
||||
if (!state.updated.all && m_mapped && state.fifoScheduled)
|
||||
if (!state.updated.all && m_mapped)
|
||||
return;
|
||||
|
||||
// only a new buffer supersedes the current, not yet presented content.
|
||||
if (state.updated.bits.buffer)
|
||||
PROTO::presentation->discardFeedbacks(m_current.presentationFeedbacks);
|
||||
|
||||
auto lastTexture = m_current.texture;
|
||||
m_current.updateFrom(state);
|
||||
|
||||
@@ -824,17 +820,19 @@ void CWLSurfaceResource::presentFeedback(const Time::steady_tp& when, PHLMONITOR
|
||||
if (m_current.presentationFeedbacks.empty())
|
||||
return;
|
||||
|
||||
// discarded content will never be scanned out, so there is no present event coming.
|
||||
if (discarded) {
|
||||
PROTO::presentation->discardFeedbacks(m_current.presentationFeedbacks);
|
||||
return;
|
||||
}
|
||||
|
||||
auto FEEDBACK = makeUnique<CQueuedPresentationData>(m_self.lock(), std::move(m_current.presentationFeedbacks));
|
||||
FEEDBACK->attachMonitor(pMonitor);
|
||||
if (discarded)
|
||||
FEEDBACK->discarded();
|
||||
else {
|
||||
FEEDBACK->presented();
|
||||
if (!pMonitor->m_lastScanout.expired()) {
|
||||
const auto WINDOW = m_hlSurface ? Desktop::View::CWindow::fromView(m_hlSurface->view()) : nullptr;
|
||||
if (WINDOW == pMonitor->m_lastScanout)
|
||||
FEEDBACK->setPresentationType(true);
|
||||
}
|
||||
FEEDBACK->presented();
|
||||
if (!pMonitor->m_lastScanout.expired()) {
|
||||
const auto WINDOW = m_hlSurface ? Desktop::View::CWindow::fromView(m_hlSurface->view()) : nullptr;
|
||||
if (WINDOW == pMonitor->m_lastScanout)
|
||||
FEEDBACK->setPresentationType(true);
|
||||
}
|
||||
PROTO::presentation->queueData(std::move(FEEDBACK));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "SurfaceState.hpp"
|
||||
#include "helpers/Format.hpp"
|
||||
#include "protocols/types/Buffer.hpp"
|
||||
#include "protocols/PresentationTime.hpp"
|
||||
#include "managers/eventLoop/EventLoopManager.hpp"
|
||||
#include "render/Renderer.hpp"
|
||||
#include "render/Texture.hpp"
|
||||
|
||||
@@ -76,19 +78,102 @@ void SSurfaceState::reset() {
|
||||
presentationFeedbacks.clear();
|
||||
lockMask = LOCK_REASON_NONE;
|
||||
|
||||
barrierSet = false;
|
||||
surfaceLocked = false;
|
||||
fifoScheduled = false;
|
||||
barrierSet = false;
|
||||
barrierWait = false;
|
||||
waitingOnPresentation = false;
|
||||
|
||||
pendingTimeout.reset();
|
||||
commitTimingTarget.reset();
|
||||
timer.reset(); // CEventLoopManager::nudgeTimers should handle it eventually
|
||||
}
|
||||
|
||||
bool SSurfaceState::isLocked() const {
|
||||
return lockMask != LOCK_REASON_NONE;
|
||||
}
|
||||
|
||||
bool SSurfaceState::fenceSignaled() const {
|
||||
if (buffer) {
|
||||
for (const auto& fd : buffer->m_syncFds) {
|
||||
if (!fd.isReadable())
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (acquireWaiter && !acquireWaiter->fd.isReadable())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SSurfaceState::cancelFenceWaiter() {
|
||||
if (acquireWaiter && g_pEventLoopManager)
|
||||
g_pEventLoopManager->removeReadableWaiter(acquireWaiter);
|
||||
acquireWaiter.reset();
|
||||
}
|
||||
|
||||
void SSurfaceState::mergeFrom(SSurfaceState& ref) {
|
||||
updated.all |= ref.updated.all;
|
||||
|
||||
if (ref.updated.bits.buffer) {
|
||||
if (!presentationFeedbacks.empty())
|
||||
PROTO::presentation->discardFeedbacks(presentationFeedbacks);
|
||||
|
||||
buffer = ref.buffer;
|
||||
texture = ref.texture;
|
||||
size = ref.size;
|
||||
bufferSize = ref.bufferSize;
|
||||
}
|
||||
|
||||
if (ref.updated.bits.damage) {
|
||||
damage.add(ref.damage);
|
||||
bufferDamage.add(ref.bufferDamage);
|
||||
}
|
||||
|
||||
if (ref.updated.bits.input) {
|
||||
input = ref.input;
|
||||
inputIsInfinite = ref.inputIsInfinite;
|
||||
}
|
||||
|
||||
if (ref.updated.bits.opaque)
|
||||
opaque = ref.opaque;
|
||||
|
||||
if (ref.updated.bits.offset)
|
||||
offset = ref.offset;
|
||||
|
||||
if (ref.updated.bits.scale)
|
||||
scale = ref.scale;
|
||||
|
||||
if (ref.updated.bits.transform)
|
||||
transform = ref.transform;
|
||||
|
||||
if (ref.updated.bits.viewport)
|
||||
viewport = ref.viewport;
|
||||
|
||||
if (ref.updated.bits.acquire)
|
||||
acquire = ref.acquire;
|
||||
|
||||
if (ref.updated.bits.acked)
|
||||
ackedSize = ref.ackedSize;
|
||||
|
||||
if (ref.updated.bits.frame) {
|
||||
callbacks.insert(callbacks.end(), std::make_move_iterator(ref.callbacks.begin()), std::make_move_iterator(ref.callbacks.end()));
|
||||
ref.callbacks.clear();
|
||||
}
|
||||
|
||||
if (ref.updated.bits.presentation) {
|
||||
presentationFeedbacks.insert(presentationFeedbacks.end(), std::make_move_iterator(ref.presentationFeedbacks.begin()),
|
||||
std::make_move_iterator(ref.presentationFeedbacks.end()));
|
||||
ref.presentationFeedbacks.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void SSurfaceState::updateFrom(SSurfaceState& ref) {
|
||||
updated = ref.updated;
|
||||
|
||||
if (ref.updated.bits.buffer) {
|
||||
if (!presentationFeedbacks.empty())
|
||||
PROTO::presentation->discardFeedbacks(presentationFeedbacks);
|
||||
|
||||
buffer = ref.buffer;
|
||||
texture = ref.texture;
|
||||
size = ref.size;
|
||||
@@ -142,5 +227,5 @@ void SSurfaceState::updateFrom(SSurfaceState& ref) {
|
||||
}
|
||||
|
||||
if (ref.barrierSet)
|
||||
barrierSet = ref.barrierSet;
|
||||
waitingOnPresentation = true;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace Render {
|
||||
class CDRMSyncPointState;
|
||||
class CWLCallbackResource;
|
||||
class CPresentationFeedback;
|
||||
struct SReadableWaiter;
|
||||
|
||||
enum eLockReason : uint8_t {
|
||||
LOCK_REASON_NONE = 0,
|
||||
@@ -92,17 +93,18 @@ struct SSurfaceState {
|
||||
Vector2D sourceSize();
|
||||
|
||||
// drm syncobj protocol surface state
|
||||
CDRMSyncPointState acquire;
|
||||
eLockReason lockMask = LOCK_REASON_NONE;
|
||||
CDRMSyncPointState acquire;
|
||||
WP<SReadableWaiter> acquireWaiter;
|
||||
eLockReason lockMask = LOCK_REASON_NONE;
|
||||
|
||||
// texture of surface content, used for rendering
|
||||
SP<Render::ITexture> texture;
|
||||
void updateSynchronousTexture(SP<Render::ITexture> lastTexture);
|
||||
|
||||
// fifo
|
||||
bool barrierSet = false;
|
||||
bool surfaceLocked = false;
|
||||
bool fifoScheduled = false;
|
||||
bool barrierSet = false;
|
||||
bool barrierWait = false;
|
||||
bool waitingOnPresentation = false;
|
||||
|
||||
// commit timing
|
||||
std::optional<Time::steady_dur> pendingTimeout;
|
||||
@@ -114,4 +116,9 @@ struct SSurfaceState {
|
||||
CRegion effectiveInputRegion() const; // materializes the input region clipped to the current surface size
|
||||
void updateFrom(SSurfaceState& ref); // updates this state based on a reference state.
|
||||
void reset(); // resets pending state after commit
|
||||
|
||||
bool isLocked() const;
|
||||
bool fenceSignaled() const;
|
||||
void mergeFrom(SSurfaceState& ref);
|
||||
void cancelFenceWaiter();
|
||||
};
|
||||
|
||||
@@ -6,8 +6,10 @@
|
||||
CSurfaceStateQueue::CSurfaceStateQueue(WP<CWLSurfaceResource> surf) : m_surface(std::move(surf)) {}
|
||||
|
||||
void CSurfaceStateQueue::clear() {
|
||||
for (auto& state : m_queue)
|
||||
for (const auto& state : m_queue) {
|
||||
state->cancelFenceWaiter();
|
||||
PROTO::presentation->discardFeedbacks(state->presentationFeedbacks);
|
||||
}
|
||||
|
||||
m_queue.clear();
|
||||
}
|
||||
@@ -17,10 +19,11 @@ WP<SSurfaceState> CSurfaceStateQueue::enqueue(UP<SSurfaceState>&& state) {
|
||||
}
|
||||
|
||||
void CSurfaceStateQueue::dropState(const WP<SSurfaceState>& state) {
|
||||
auto it = find(state);
|
||||
const auto& it = find(state);
|
||||
if (it == m_queue.end())
|
||||
return;
|
||||
|
||||
(*it)->cancelFenceWaiter();
|
||||
PROTO::presentation->discardFeedbacks((*it)->presentationFeedbacks);
|
||||
|
||||
m_queue.erase(it);
|
||||
@@ -28,7 +31,7 @@ void CSurfaceStateQueue::dropState(const WP<SSurfaceState>& state) {
|
||||
|
||||
void CSurfaceStateQueue::lock(const WP<SSurfaceState>& weakState, eLockReason reason) {
|
||||
ASSERT(reason != LOCK_REASON_NONE);
|
||||
auto it = find(weakState);
|
||||
const auto& it = find(weakState);
|
||||
if (it == m_queue.end())
|
||||
return;
|
||||
|
||||
@@ -37,7 +40,7 @@ void CSurfaceStateQueue::lock(const WP<SSurfaceState>& weakState, eLockReason re
|
||||
|
||||
void CSurfaceStateQueue::unlock(const WP<SSurfaceState>& state, eLockReason reason) {
|
||||
ASSERT(reason != LOCK_REASON_NONE);
|
||||
auto it = find(state);
|
||||
const auto& it = find(state);
|
||||
if (it == m_queue.end())
|
||||
return;
|
||||
|
||||
@@ -45,9 +48,25 @@ void CSurfaceStateQueue::unlock(const WP<SSurfaceState>& state, eLockReason reas
|
||||
tryProcess();
|
||||
}
|
||||
|
||||
void CSurfaceStateQueue::unlockFence(const WP<SSurfaceState>& state) {
|
||||
auto it = find(state);
|
||||
if (it == m_queue.end())
|
||||
return;
|
||||
|
||||
for (const auto& s : m_queue) {
|
||||
if (!s->fenceSignaled())
|
||||
continue;
|
||||
|
||||
s->lockMask &= ~LOCK_REASON_FENCE;
|
||||
s->cancelFenceWaiter();
|
||||
}
|
||||
|
||||
tryProcess();
|
||||
}
|
||||
|
||||
void CSurfaceStateQueue::unlockFirst(eLockReason reason) {
|
||||
ASSERT(reason != LOCK_REASON_NONE);
|
||||
for (auto& it : m_queue) {
|
||||
for (const auto& it : m_queue) {
|
||||
if ((it->lockMask & reason) != LOCK_REASON_NONE) {
|
||||
it->lockMask &= ~reason;
|
||||
break;
|
||||
@@ -74,11 +93,17 @@ auto CSurfaceStateQueue::find(const WP<SSurfaceState>& state) -> std::deque<UP<S
|
||||
void CSurfaceStateQueue::tryProcess() {
|
||||
while (!m_queue.empty()) {
|
||||
auto& front = m_queue.front();
|
||||
if (front->lockMask & LOCK_REASON_FIFO && !m_surface->m_current.barrierSet)
|
||||
front->lockMask &= ~LOCK_REASON_FIFO;
|
||||
|
||||
if (front->lockMask != LOCK_REASON_NONE)
|
||||
return;
|
||||
if (front->isLocked())
|
||||
break;
|
||||
|
||||
auto next = std::next(m_queue.begin());
|
||||
if (next != m_queue.end() && !(*next)->isLocked()) {
|
||||
front->mergeFrom(**next);
|
||||
(*next)->cancelFenceWaiter();
|
||||
m_queue.erase(next);
|
||||
continue;
|
||||
}
|
||||
|
||||
m_surface->commitState(*front);
|
||||
m_queue.pop_front();
|
||||
|
||||
@@ -16,6 +16,7 @@ class CSurfaceStateQueue {
|
||||
void dropState(const WP<SSurfaceState>& state);
|
||||
void lock(const WP<SSurfaceState>& state, eLockReason reason);
|
||||
void unlock(const WP<SSurfaceState>& state, eLockReason reason);
|
||||
void unlockFence(const WP<SSurfaceState>& state);
|
||||
void unlockFirst(eLockReason reason);
|
||||
void tryProcess();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user