buggy frame scheduler ugh

This commit is contained in:
Vaxry 2024-02-27 21:18:22 +00:00
parent 63e3668529
commit e5f379cd40
12 changed files with 352 additions and 42 deletions

View File

@ -13,6 +13,8 @@
#include <ranges>
#include "helpers/VarList.hpp"
#include "managers/FrameSchedulingManager.hpp"
int handleCritSignal(int signo, void* data) {
Debug::log(LOG, "Hyprland received signal {}", signo);
@ -520,6 +522,9 @@ void CCompositor::initManagers(eManagersInitStage stage) {
Debug::log(LOG, "Creating the CursorManager!");
g_pCursorManager = std::make_unique<CCursorManager>();
Debug::log(LOG, "Creating the FrameSchedulingManager!");
g_pFrameSchedulingManager = std::make_unique<CFrameSchedulingManager>();
} break;
default: UNREACHABLE();
}
@ -2375,7 +2380,10 @@ void CCompositor::scheduleFrameForMonitor(CMonitor* pMonitor) {
if (pMonitor->renderingActive)
pMonitor->pendingFrame = true;
wlr_output_schedule_frame(pMonitor->output);
if (!pMonitor->frameNeededSource)
return;
wl_event_source_timer_update(pMonitor->frameNeededSource, 1);
}
CWindow* CCompositor::getWindowByRegex(const std::string& regexp) {

View File

@ -84,6 +84,7 @@ namespace Events {
DYNLISTENFUNC(monitorNeedsFrame);
DYNLISTENFUNC(monitorCommit);
DYNLISTENFUNC(monitorBind);
DYNLISTENFUNC(monitorPresent);
// XWayland
LISTENER(readyXWayland);

View File

@ -5,6 +5,7 @@
#include "Events.hpp"
#include "../debug/HyprCtl.hpp"
#include "../config/ConfigValue.hpp"
#include "../managers/FrameSchedulingManager.hpp"
// --------------------------------------------------------- //
// __ __ ____ _ _ _____ _______ ____ _____ _____ //
@ -126,34 +127,7 @@ void Events::listener_monitorFrame(void* owner, void* data) {
CMonitor* const PMONITOR = (CMonitor*)owner;
if ((g_pCompositor->m_sWLRSession && !g_pCompositor->m_sWLRSession->active) || !g_pCompositor->m_bSessionActive || g_pCompositor->m_bUnsafeState) {
Debug::log(WARN, "Attempted to render frame on inactive session!");
if (g_pCompositor->m_bUnsafeState && std::ranges::any_of(g_pCompositor->m_vMonitors.begin(), g_pCompositor->m_vMonitors.end(), [&](auto& m) {
return m->output != g_pCompositor->m_pUnsafeOutput->output;
})) {
// restore from unsafe state
g_pCompositor->leaveUnsafeState();
}
return; // cannot draw on session inactive (different tty)
}
if (!PMONITOR->m_bEnabled)
return;
g_pHyprRenderer->recheckSolitaryForMonitor(PMONITOR);
PMONITOR->tearingState.busy = false;
if (PMONITOR->tearingState.activelyTearing && PMONITOR->solitaryClient /* can be invalidated by a recheck */) {
if (!PMONITOR->tearingState.frameScheduledWhileBusy)
return; // we did not schedule a frame yet to be displayed, but we are tearing. Why render?
PMONITOR->tearingState.nextRenderTorn = true;
PMONITOR->tearingState.frameScheduledWhileBusy = false;
}
return; // TODO: remove completely
static auto PENABLERAT = CConfigValue<Hyprlang::INT>("misc:render_ahead_of_time");
static auto PRATSAFE = CConfigValue<Hyprlang::INT>("misc:render_ahead_safezone");
@ -260,3 +234,8 @@ void Events::listener_monitorCommit(void* owner, void* data) {
void Events::listener_monitorBind(void* owner, void* data) {
;
}
void Events::listener_monitorPresent(void* owner, void* data) {
const auto PMONITOR = (CMonitor*)owner;
g_pFrameSchedulingManager->onPresent(PMONITOR);
}

View File

@ -3,15 +3,26 @@
#include "MiscFunctions.hpp"
#include "../Compositor.hpp"
#include "../managers/FrameSchedulingManager.hpp"
#include "../config/ConfigValue.hpp"
int ratHandler(void* data) {
static int ratHandler(void* data) {
g_pHyprRenderer->renderMonitor((CMonitor*)data);
return 1;
}
static int wlFrameCallback(void* data) {
const auto PMONITOR = (CMonitor*)data;
Debug::log(LOG, "wlFrameCallback");
g_pFrameSchedulingManager->onFrameNeeded(PMONITOR);
return 1;
}
CMonitor::CMonitor() : state(this) {
wlr_damage_ring_init(&damage);
}
@ -25,6 +36,7 @@ CMonitor::~CMonitor() {
hyprListener_monitorDamage.removeCallback();
hyprListener_monitorNeedsFrame.removeCallback();
hyprListener_monitorCommit.removeCallback();
hyprListener_monitorPresent.removeCallback();
hyprListener_monitorBind.removeCallback();
}
@ -36,6 +48,7 @@ void CMonitor::onConnect(bool noRule) {
hyprListener_monitorNeedsFrame.removeCallback();
hyprListener_monitorCommit.removeCallback();
hyprListener_monitorBind.removeCallback();
hyprListener_monitorPresent.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);
@ -43,6 +56,7 @@ void CMonitor::onConnect(bool noRule) {
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_monitorPresent.initCallback(&output->events.present, &Events::listener_monitorPresent, this);
tearingState.canTear = wlr_backend_is_drm(output->backend); // tearing only works on drm
@ -211,9 +225,12 @@ void CMonitor::onConnect(bool noRule) {
if (!found)
g_pCompositor->setActiveMonitor(this);
renderTimer = wl_event_loop_add_timer(g_pCompositor->m_sWLEventLoop, ratHandler, this);
renderTimer = wl_event_loop_add_timer(g_pCompositor->m_sWLEventLoop, ratHandler, this);
frameNeededSource = wl_event_loop_add_timer(g_pCompositor->m_sWLEventLoop, wlFrameCallback, this);
g_pCompositor->scheduleFrameForMonitor(this);
g_pFrameSchedulingManager->registerMonitor(this);
}
void CMonitor::onDisconnect(bool destroy) {
@ -223,6 +240,8 @@ void CMonitor::onDisconnect(bool destroy) {
renderTimer = nullptr;
}
g_pFrameSchedulingManager->unregisterMonitor(this);
if (!m_bEnabled || g_pCompositor->m_bIsShuttingDown)
return;
@ -256,6 +275,7 @@ void CMonitor::onDisconnect(bool destroy) {
hyprListener_monitorNeedsFrame.removeCallback();
hyprListener_monitorCommit.removeCallback();
hyprListener_monitorBind.removeCallback();
hyprListener_monitorPresent.removeCallback();
for (size_t i = 0; i < 4; ++i) {
for (auto& ls : m_aLayerSurfaceLayers[i]) {

View File

@ -98,8 +98,9 @@ class CMonitor {
bool pendingFrame = false; // if we schedule a frame during rendering, reschedule it after
bool renderingActive = false;
wl_event_source* renderTimer = nullptr; // for RAT
bool RATScheduled = false;
wl_event_source* renderTimer = nullptr; // for RAT
wl_event_source* frameNeededSource = nullptr; // for ::scheduleFrameForMonitor
bool RATScheduled = false;
CTimer lastPresentationTimer;
SMonitorRule activeMonitorRule;
@ -129,6 +130,7 @@ class CMonitor {
DYNLISTENER(monitorNeedsFrame);
DYNLISTENER(monitorCommit);
DYNLISTENER(monitorBind);
DYNLISTENER(monitorPresent);
// methods
void onConnect(bool noRule);

View File

@ -19,12 +19,12 @@
CEventManager::CEventManager() {}
int fdHandleWrite(int fd, uint32_t mask, void* data) {
static int fdHandleWrite(int fd, uint32_t mask, void* data) {
const auto PEVMGR = (CEventManager*)data;
return PEVMGR->onFDWrite(fd, mask);
}
int socket2HandleWrite(int fd, uint32_t mask, void* data) {
static int socket2HandleWrite(int fd, uint32_t mask, void* data) {
const auto PEVMGR = (CEventManager*)data;
return PEVMGR->onSocket2Write(fd, mask);
}

View File

@ -0,0 +1,196 @@
#include "FrameSchedulingManager.hpp"
#include "../debug/Log.hpp"
#include "../Compositor.hpp"
int onPresentTimer(void* data) {
return g_pFrameSchedulingManager->onVblankTimer(data);
}
void CFrameSchedulingManager::registerMonitor(CMonitor* pMonitor) {
if (dataFor(pMonitor)) {
Debug::log(ERR, "BUG THIS: Attempted to double register to CFrameSchedulingManager");
return;
}
SSchedulingData* DATA = &m_vSchedulingData.emplace_back(SSchedulingData{pMonitor});
DATA->event = wl_event_loop_add_timer(g_pCompositor->m_sWLEventLoop, onPresentTimer, DATA);
}
void CFrameSchedulingManager::unregisterMonitor(CMonitor* pMonitor) {
std::erase_if(m_vSchedulingData, [pMonitor](const auto& d) { return d.pMonitor == pMonitor; });
}
void CFrameSchedulingManager::onFrameNeeded(CMonitor* pMonitor) {
const auto DATA = dataFor(pMonitor);
RASSERT(DATA, "No data in gpuDone");
if (pMonitor->output->frame_pending || pMonitor->tearingState.activelyTearing)
return;
Debug::log(LOG, "onFrameNeeded");
onPresent(pMonitor);
}
void CFrameSchedulingManager::gpuDone(wlr_buffer* pBuffer) {
const auto DATA = dataFor(pBuffer);
RASSERT(DATA, "No data in gpuDone");
if (!DATA->delayed)
return;
// delayed frame, let's render immediately, our shit will be presented soon
// if we finish rendering before the next vblank somehow, kernel will be mad, but oh well
DATA->gpuDoneCall = true;
g_pHyprRenderer->renderMonitor(DATA->pMonitor);
DATA->delayedFrameSubmitted = true;
}
void CFrameSchedulingManager::registerBuffer(wlr_buffer* pBuffer, CMonitor* pMonitor) {
const auto DATA = dataFor(pMonitor);
RASSERT(DATA, "No data in registerBuffer");
if (std::find(DATA->buffers.begin(), DATA->buffers.end(), pBuffer) != DATA->buffers.end())
return;
DATA->buffers.push_back(pBuffer);
}
void CFrameSchedulingManager::dropBuffer(wlr_buffer* pBuffer) {
for (auto& d : m_vSchedulingData) {
std::erase(d.buffers, pBuffer);
}
}
void CFrameSchedulingManager::onPresent(CMonitor* pMonitor) {
const auto DATA = dataFor(pMonitor);
RASSERT(DATA, "No data in onPresent");
if (pMonitor->tearingState.activelyTearing) {
DATA->activelyPushing = false;
return; // don't render
}
if (DATA->delayedFrameSubmitted) {
DATA->delayedFrameSubmitted = false;
DATA->activelyPushing = false;
return;
}
Debug::log(LOG, "onPresent");
int forceFrames = DATA->forceFrames + pMonitor->forceFullFrames;
DATA->lastPresent.reset();
// reset state, request a render if necessary
DATA->delayed = false;
if (DATA->forceFrames > 0)
DATA->forceFrames--;
DATA->rendered = false;
DATA->gpuReady = false;
DATA->activelyPushing = true;
// check if there is damage
bool hasDamage = pixman_region32_not_empty(&pMonitor->damage.current);
if (!hasDamage) {
for (int i = 0; i < WLR_DAMAGE_RING_PREVIOUS_LEN; ++i) {
hasDamage = hasDamage || pixman_region32_not_empty(&pMonitor->damage.previous[i]);
}
}
if (!hasDamage && forceFrames <= 0) {
DATA->activelyPushing = false;
return;
}
Debug::log(LOG, "remder!");
// we can't do this on wayland
if (!wlr_backend_is_wl(pMonitor->output->backend) && !DATA->gpuDoneCall) {
const float TIMEUNTILVBLANK = 1000.0 / pMonitor->refreshRate;
wl_event_source_timer_update(DATA->event, 0);
wl_event_source_timer_update(DATA->event, std::floor(TIMEUNTILVBLANK));
}
renderMonitor(DATA);
DATA->gpuDoneCall = false;
}
CFrameSchedulingManager::SSchedulingData* CFrameSchedulingManager::dataFor(CMonitor* pMonitor) {
for (auto& d : m_vSchedulingData) {
if (d.pMonitor == pMonitor)
return &d;
}
return nullptr;
}
CFrameSchedulingManager::SSchedulingData* CFrameSchedulingManager::dataFor(wlr_buffer* pBuffer) {
for (auto& d : m_vSchedulingData) {
if (std::find(d.buffers.begin(), d.buffers.end(), pBuffer) != d.buffers.end())
return &d;
}
return nullptr;
}
void CFrameSchedulingManager::renderMonitor(SSchedulingData* data) {
CMonitor* pMonitor = data->pMonitor;
if ((g_pCompositor->m_sWLRSession && !g_pCompositor->m_sWLRSession->active) || !g_pCompositor->m_bSessionActive || g_pCompositor->m_bUnsafeState) {
Debug::log(WARN, "Attempted to render frame on inactive session!");
if (g_pCompositor->m_bUnsafeState && std::ranges::any_of(g_pCompositor->m_vMonitors.begin(), g_pCompositor->m_vMonitors.end(), [&](auto& m) {
return m->output != g_pCompositor->m_pUnsafeOutput->output;
})) {
// restore from unsafe state
g_pCompositor->leaveUnsafeState();
}
return; // cannot draw on session inactive (different tty)
}
if (!pMonitor->m_bEnabled)
return;
g_pHyprRenderer->recheckSolitaryForMonitor(pMonitor);
pMonitor->tearingState.busy = false;
if (pMonitor->tearingState.activelyTearing && pMonitor->solitaryClient /* can be invalidated by a recheck */) {
if (!pMonitor->tearingState.frameScheduledWhileBusy)
return; // we did not schedule a frame yet to be displayed, but we are tearing. Why render?
pMonitor->tearingState.nextRenderTorn = true;
pMonitor->tearingState.frameScheduledWhileBusy = false;
}
g_pHyprRenderer->renderMonitor(pMonitor);
data->rendered = true;
}
int CFrameSchedulingManager::onVblankTimer(void* data) {
auto DATA = (SSchedulingData*)data;
if (DATA->rendered && DATA->gpuReady) {
// cool, we don't need to do anything. Wait for present.
return 0;
}
if (DATA->rendered && !DATA->gpuReady) {
// we missed a vblank :(
DATA->delayed = true;
return 0;
}
// what the fuck?
Debug::log(ERR, "Vblank timer fired without a frame????");
return 0;
}

View File

@ -0,0 +1,69 @@
#pragma once
#include <memory>
#include <vector>
#include "../helpers/Timer.hpp"
class CMonitor;
struct wlr_buffer;
class CFrameSchedulingManager {
public:
void registerMonitor(CMonitor* pMonitor);
void unregisterMonitor(CMonitor* pMonitor);
void gpuDone(wlr_buffer* pBuffer);
void registerBuffer(wlr_buffer* pBuffer, CMonitor* pMonitor);
void dropBuffer(wlr_buffer* pBuffer);
void onFrameNeeded(CMonitor* pMonitor);
void onPresent(CMonitor* pMonitor);
int onVblankTimer(void* data);
private:
struct SSchedulingData {
CMonitor* pMonitor = nullptr;
// CPU frame rendering has been finished
bool rendered = false;
// GPU frame rendering has been finished
bool gpuReady = false;
// GPU didn't manage to render last frame in time.
// we got a vblank before we got a gpuDone()
bool delayed = false;
// whether the frame was submitted from gpuDone
bool delayedFrameSubmitted = false;
// whether this call comes from gpuDone
bool gpuDoneCall = false;
// we need to render a few full frames at the beginning to catch all buffers
int forceFrames = 5;
// last present timer
CTimer lastPresent;
// buffers associated with this monitor
std::vector<wlr_buffer*> buffers;
// event source for the vblank timer
wl_event_source* event = nullptr;
// whether we're actively pushing frames
bool activelyPushing = false;
};
std::vector<SSchedulingData> m_vSchedulingData;
SSchedulingData* dataFor(CMonitor* pMonitor);
SSchedulingData* dataFor(wlr_buffer* pBuffer);
void renderMonitor(SSchedulingData* data);
};
inline std::unique_ptr<CFrameSchedulingManager> g_pFrameSchedulingManager;

View File

@ -1,6 +1,7 @@
#include "Renderbuffer.hpp"
#include "OpenGL.hpp"
#include "../Compositor.hpp"
#include "../managers/FrameSchedulingManager.hpp"
#include <dlfcn.h>
@ -15,9 +16,24 @@ CRenderbuffer::~CRenderbuffer() {
glDeleteRenderbuffers(1, &m_iRBO);
g_pHyprOpenGL->m_sProc.eglDestroyImageKHR(wlr_egl_get_display(g_pCompositor->m_sWLREGL), m_iImage);
wl_event_source_remove(m_pFDWrite);
g_pFrameSchedulingManager->dropBuffer(m_pWlrBuffer);
}
CRenderbuffer::CRenderbuffer(wlr_buffer* buffer, uint32_t format) : m_pWlrBuffer(buffer) {
static int fdHandleWrite(int fd, uint32_t mask, void* data) {
if (mask & WL_EVENT_ERROR || mask & WL_EVENT_HANGUP)
return 0;
const auto RB = (CRenderbuffer*)data;
g_pFrameSchedulingManager->gpuDone(RB->m_pWlrBuffer);
return 0;
}
CRenderbuffer::CRenderbuffer(wlr_buffer* buffer, uint32_t format, CMonitor* pMonitor) : m_pWlrBuffer(buffer), m_pMonitor(pMonitor) {
// EVIL, but we can't include a hidden header because nixos is fucking special
static EGLImageKHR (*PWLREGLCREATEIMAGEFROMDMABUF)(wlr_egl*, wlr_dmabuf_attributes*, bool*);
@ -58,7 +74,20 @@ CRenderbuffer::CRenderbuffer(wlr_buffer* buffer, uint32_t format) : m_pWlrBuffer
glBindFramebuffer(GL_FRAMEBUFFER, 0);
hyprListener_destroyBuffer.initCallback(
&buffer->events.destroy, [](void* owner, void* data) { g_pHyprRenderer->onRenderbufferDestroy((CRenderbuffer*)owner); }, this, "CRenderbuffer");
&buffer->events.destroy,
[](void* owner, void* data) {
const auto RB = (CRenderbuffer*)owner;
g_pHyprRenderer->onRenderbufferDestroy(RB);
g_pFrameSchedulingManager->dropBuffer(RB->m_pWlrBuffer);
},
this, "CRenderbuffer");
wlr_dmabuf_attributes attrs = {0};
wlr_buffer_get_dmabuf(m_pWlrBuffer, &attrs);
m_pFDWrite = wl_event_loop_add_fd(g_pCompositor->m_sWLEventLoop, attrs.fd[0], WL_EVENT_READABLE, fdHandleWrite, this);
g_pFrameSchedulingManager->registerBuffer(m_pWlrBuffer, pMonitor);
}
void CRenderbuffer::bind() {

View File

@ -6,7 +6,7 @@ class CMonitor;
class CRenderbuffer {
public:
CRenderbuffer(wlr_buffer* buffer, uint32_t format);
CRenderbuffer(wlr_buffer* buffer, uint32_t format, CMonitor* pMonitor);
~CRenderbuffer();
void bind();
@ -22,4 +22,6 @@ class CRenderbuffer {
EGLImageKHR m_iImage = 0;
GLuint m_iRBO = 0;
CFramebuffer m_sFramebuffer;
CMonitor* m_pMonitor = nullptr;
wl_event_source* m_pFDWrite = nullptr;
};

View File

@ -1063,6 +1063,7 @@ bool CHyprRenderer::attemptDirectScanout(CMonitor* pMonitor) {
}
void CHyprRenderer::renderMonitor(CMonitor* pMonitor) {
static std::chrono::high_resolution_clock::time_point renderStart = std::chrono::high_resolution_clock::now();
static std::chrono::high_resolution_clock::time_point renderStartOverlay = std::chrono::high_resolution_clock::now();
static std::chrono::high_resolution_clock::time_point endRenderOverlay = std::chrono::high_resolution_clock::now();
@ -2273,6 +2274,8 @@ bool CHyprRenderer::applyMonitorRule(CMonitor* pMonitor, SMonitorRule* pMonitorR
Events::listener_change(nullptr, nullptr);
g_pCompositor->scheduleFrameForMonitor(pMonitor);
return true;
}
@ -2585,13 +2588,13 @@ void CHyprRenderer::renderSoftwareCursors(CMonitor* pMonitor, const CRegion& dam
}
}
CRenderbuffer* CHyprRenderer::getOrCreateRenderbuffer(wlr_buffer* buffer, uint32_t fmt) {
CRenderbuffer* CHyprRenderer::getOrCreateRenderbuffer(wlr_buffer* buffer, uint32_t fmt, CMonitor* pMonitor) {
auto it = std::find_if(m_vRenderbuffers.begin(), m_vRenderbuffers.end(), [&](const auto& other) { return other->m_pWlrBuffer == buffer; });
if (it != m_vRenderbuffers.end())
return it->get();
return m_vRenderbuffers.emplace_back(std::make_unique<CRenderbuffer>(buffer, fmt)).get();
return m_vRenderbuffers.emplace_back(std::make_unique<CRenderbuffer>(buffer, fmt, pMonitor)).get();
}
void CHyprRenderer::makeEGLCurrent() {
@ -2636,7 +2639,7 @@ bool CHyprRenderer::beginRender(CMonitor* pMonitor, CRegion& damage, eRenderMode
m_pCurrentWlrBuffer = wlr_buffer_lock(buffer);
try {
m_pCurrentRenderbuffer = getOrCreateRenderbuffer(m_pCurrentWlrBuffer, pMonitor->drmFormat);
m_pCurrentRenderbuffer = getOrCreateRenderbuffer(m_pCurrentWlrBuffer, pMonitor->drmFormat, pMonitor);
} catch (std::exception& e) {
Debug::log(ERR, "getOrCreateRenderbuffer failed for {}", pMonitor->szName);
wlr_buffer_unlock(m_pCurrentWlrBuffer);

View File

@ -135,7 +135,8 @@ class CHyprRenderer {
bool hiddenOnKeyboard = false;
} m_sCursorHiddenConditions;
CRenderbuffer* getOrCreateRenderbuffer(wlr_buffer* buffer, uint32_t fmt);
CRenderbuffer* getOrCreateRenderbuffer(wlr_buffer* buffer, uint32_t fmt, CMonitor* pMonitor);
std::vector<std::unique_ptr<CRenderbuffer>> m_vRenderbuffers;
friend class CHyprOpenGLImpl;