diff --git a/client/displayservers/Wayland/presentation.c b/client/displayservers/Wayland/presentation.c index 74a594fb..be5fa98d 100644 --- a/client/displayservers/Wayland/presentation.c +++ b/client/displayservers/Wayland/presentation.c @@ -76,15 +76,7 @@ bool waylandGetFramePeriod(uint64_t * period) /* Frame demand must use the output's nominal rate. Basing demand on the * observed presentation interval can lock a VRR session at its current, * slower cadence. */ - if (waylandOutputGetFramePeriod(period)) - return true; - - const uint64_t value = atomic_load(&wlWm.presentationPeriod); - if (!value) - return false; - - *period = value; - return true; + return waylandOutputGetFramePeriod(period); } static void presentationFeedbackDiscarded(void * data, diff --git a/client/displayservers/Wayland/window.c b/client/displayservers/Wayland/window.c index 26c51b8b..546e3752 100644 --- a/client/displayservers/Wayland/window.c +++ b/client/displayservers/Wayland/window.c @@ -133,6 +133,8 @@ bool waylandWindowInit(const char * title, const char * appId, bool fullscreen, return false; } + wl_surface_add_listener(wlWm.surface, &wlSurfaceListener, NULL); + if (wlWm.fractionalScaleManager) { wlWm.fractionalScaleInterface = wp_fractional_scale_manager_v1_get_fractional_scale( @@ -140,8 +142,6 @@ bool waylandWindowInit(const char * title, const char * appId, bool fullscreen, wp_fractional_scale_v1_add_listener(wlWm.fractionalScaleInterface, &fractionalScaleListener, NULL); } - else - wl_surface_add_listener(wlWm.surface, &wlSurfaceListener, NULL); if (wlWm.contentTypeManager) { diff --git a/client/displayservers/X11/CMakeLists.txt b/client/displayservers/X11/CMakeLists.txt index 44ae2814..e366e6cb 100644 --- a/client/displayservers/X11/CMakeLists.txt +++ b/client/displayservers/X11/CMakeLists.txt @@ -10,6 +10,7 @@ pkg_check_modules(DISPLAYSERVER_X11 REQUIRED IMPORTED_TARGET xinerama xcursor xpresent + xrandr xkbcommon ) diff --git a/client/displayservers/X11/x11.c b/client/displayservers/X11/x11.c index 3b1dfd14..433fc0eb 100644 --- a/client/displayservers/X11/x11.c +++ b/client/displayservers/X11/x11.c @@ -38,6 +38,7 @@ #include #include #include +#include #include #include @@ -94,6 +95,67 @@ static void x11XPresentEvent(XGenericEventCookie *cookie); static void x11UpdateKeyboardGroup(void); static void x11GrabPointer(void); +static uint64_t x11ModePeriod( + const XRRScreenResources * resources, RRMode modeID) +{ + for (int i = 0; i < resources->nmode; ++i) + { + const XRRModeInfo * mode = resources->modes + i; + if (mode->id != modeID || !mode->dotClock || + !mode->hTotal || !mode->vTotal) + continue; + + uint64_t pixels = (uint64_t)mode->hTotal * mode->vTotal; + if (mode->modeFlags & RR_DoubleScan) + pixels *= 2; + if (mode->modeFlags & RR_Interlace) + pixels /= 2; + + return pixels / mode->dotClock * 1000000000ULL + + pixels % mode->dotClock * 1000000000ULL / mode->dotClock; + } + + return 0; +} + +static void x11UpdateFramePeriod(void) +{ + uint64_t fastest = 0; + const Window root = DefaultRootWindow(x11.display); + XRRScreenResources * resources = + XRRGetScreenResourcesCurrent(x11.display, root); + if (!resources) + { + atomic_store(&x11.nominalPeriod, 0); + return; + } + + for (int i = 0; i < resources->ncrtc; ++i) + { + XRRCrtcInfo * crtc = + XRRGetCrtcInfo(x11.display, resources, resources->crtcs[i]); + if (!crtc) + continue; + + const bool intersects = crtc->mode && crtc->width && crtc->height && + x11.rect.x < crtc->x + (int)crtc->width && + x11.rect.x + x11.rect.w > crtc->x && + x11.rect.y < crtc->y + (int)crtc->height && + x11.rect.y + x11.rect.h > crtc->y; + if (intersects) + { + const uint64_t candidate = x11ModePeriod(resources, crtc->mode); + if (candidate && (!fastest || candidate < fastest)) + fastest = candidate; + } + + XRRFreeCrtcInfo(crtc); + } + + XRRFreeScreenResources(resources); + atomic_store(&x11.nominalPeriod, fastest); +} + static void x11DoPresent(uint64_t msc) { static bool startup = true; @@ -284,6 +346,12 @@ static bool x11Init(const LG_DSInitParams params) x11.display = XOpenDisplay(NULL); x11.jitRender = params.jitRender; x11.wm = &X11WM_Default; + x11.rect = (struct Rect) { + .x = params.x, + .y = params.y, + .w = params.w, + .h = params.h, + }; XSetWindowAttributes swa = { @@ -689,6 +757,18 @@ static bool x11Init(const LG_DSInitParams params) /* default to the square cursor */ XDefineCursor(x11.display, x11.window, x11.cursors[LG_POINTER_SQUARE]); + x11.xrandrSupported = + XRRQueryExtension(x11.display, &x11.xrandrEvent, &error); + if (x11.xrandrSupported) + { + XRRSelectInput(x11.display, DefaultRootWindow(x11.display), + RRScreenChangeNotifyMask | RRCrtcChangeNotifyMask | + RROutputChangeNotifyMask); + x11UpdateFramePeriod(); + } + else + DEBUG_WARN("X11 nominal output refresh is unavailable"); + if (x11.jitRender) { x11.frameEvent = lgCreateEvent(true, 0); @@ -899,6 +979,16 @@ static int x11EventThread(void * unused) XEvent xe; XNextEvent(x11.display, &xe); + if (x11.xrandrSupported && + (xe.type == x11.xrandrEvent + RRScreenChangeNotify || + xe.type == x11.xrandrEvent + RRNotify)) + { + if (xe.type == x11.xrandrEvent + RRScreenChangeNotify) + XRRUpdateConfiguration(&xe); + x11UpdateFramePeriod(); + continue; + } + // call the clipboard handling code if (x11CBEventThread(&xe)) continue; @@ -932,6 +1022,8 @@ static int x11EventThread(void * unused) x11.rect.y = y; x11.rect.w = xe.xconfigure.width; x11.rect.h = xe.xconfigure.height; + if (x11.xrandrSupported) + x11UpdateFramePeriod(); app_updateWindowPos(x, y); @@ -1507,7 +1599,7 @@ static void x11XPresentEvent(XGenericEventCookie *cookie) static bool x11GetFramePeriod(uint64_t * period) { - const uint64_t value = atomic_load(&x11.presentPeriod); + const uint64_t value = atomic_load(&x11.nominalPeriod); if (!value) return false; diff --git a/client/displayservers/X11/x11.h b/client/displayservers/X11/x11.h index 8a73672c..60b13c6a 100644 --- a/client/displayservers/X11/x11.h +++ b/client/displayservers/X11/x11.h @@ -76,11 +76,15 @@ struct X11DSState bool jitRender; _Atomic(uint64_t) presentMsc, presentUst; _Atomic(uint64_t) presentPeriod; + _Atomic(uint64_t) nominalPeriod; uint32_t presentSerial; Pixmap presentPixmap; XserverRegion presentRegion; LGEvent * frameEvent; + bool xrandrSupported; + int xrandrEvent; + LGThread * eventThread; int xinputOp; diff --git a/client/src/frame_scheduler.c b/client/src/frame_scheduler.c index e4a29b36..b6a0f8dc 100644 --- a/client/src/frame_scheduler.c +++ b/client/src/frame_scheduler.c @@ -26,16 +26,15 @@ #include "common/time.h" #include -#include #include -#define FRAME_SCHEDULER_LEASE_MS 1000U -#define FRAME_SCHEDULER_RENEW_NS 250000000ULL -#define FRAME_SCHEDULER_FEEDBACK_NS 50000000ULL -#define FRAME_SCHEDULER_TARGET_SLACK_NS 500000ULL -#define FRAME_SCHEDULER_MIN_PERIOD_NS 2000000ULL -#define FRAME_SCHEDULER_MAX_PERIOD_NS 1000000000ULL -#define FRAME_SCHEDULER_SAMPLE_COUNT 9U +#define FRAME_SCHEDULER_LEASE_MS 1000U +#define FRAME_SCHEDULER_RENEW_NS 250000000ULL +#define FRAME_SCHEDULER_FEEDBACK_NS 50000000ULL +#define FRAME_SCHEDULER_CADENCE_GRACE_NS 500000000ULL +#define FRAME_SCHEDULER_TARGET_SLACK_NS 500000ULL +#define FRAME_SCHEDULER_MIN_PERIOD_NS 2000000ULL +#define FRAME_SCHEDULER_MAX_PERIOD_NS 1000000000ULL static struct { @@ -47,6 +46,7 @@ static struct _Atomic(uint32_t) generation; _Atomic(uint64_t) period; uint64_t lastSend; + uint64_t lastCadence; int64_t phaseError; uint32_t feedbackFrameSerial; @@ -54,43 +54,9 @@ static struct bool feedbackDirty; LG_TransportControlToken controlToken; - - uint64_t renderSamples[FRAME_SCHEDULER_SAMPLE_COUNT]; - unsigned renderSampleIndex; - unsigned renderSampleCount; - uint64_t lastRender; } l_frameScheduler; -static int compareU64(const void * a, const void * b) -{ - const uint64_t lhs = *(const uint64_t *)a; - const uint64_t rhs = *(const uint64_t *)b; - return (lhs > rhs) - (lhs < rhs); -} - -static uint64_t fallbackPeriod(void) -{ - uint64_t samples[FRAME_SCHEDULER_SAMPLE_COUNT]; - unsigned count; - - LG_LOCK(l_frameScheduler.lock); - count = l_frameScheduler.renderSampleCount; - memcpy(samples, l_frameScheduler.renderSamples, - count * sizeof(*samples)); - LG_UNLOCK(l_frameScheduler.lock); - - if (count < 5) - return 0; - - qsort(samples, count, sizeof(*samples), compareU64); - const uint64_t period = samples[count / 2]; - if (samples[count * 3 / 4] - samples[count / 4] > period / 20) - return 0; - - return period; -} - static uint64_t presentationPeriod(void) { uint64_t period = 0; @@ -98,7 +64,7 @@ static uint64_t presentationPeriod(void) g_state.ds->getFramePeriod(&period)) return period; - return fallbackPeriod(); + return 0; } static bool controlReady(void) @@ -177,6 +143,7 @@ void frameScheduler_start(LG_TransportFeatureFlags features) l_frameScheduler.active = false; l_frameScheduler.controlPending = false; l_frameScheduler.lastSend = 0; + l_frameScheduler.lastCadence = 0; ++l_frameScheduler.generation; LG_LOCK(l_frameScheduler.lock); @@ -203,15 +170,27 @@ void frameScheduler_update(void) return; const uint64_t now = nanotime(); - uint64_t period = presentationPeriod(); + const uint64_t period = presentationPeriod(); if (period < FRAME_SCHEDULER_MIN_PERIOD_NS || period > FRAME_SCHEDULER_MAX_PERIOD_NS) { - period = l_frameScheduler.period; - if (period < FRAME_SCHEDULER_MIN_PERIOD_NS || - period > FRAME_SCHEDULER_MAX_PERIOD_NS) - return; + if (l_frameScheduler.active && l_frameScheduler.lastCadence && + now - l_frameScheduler.lastCadence > + FRAME_SCHEDULER_CADENCE_GRACE_NS && + sendSchedule(LG_TRANSPORT_FRAME_SCHEDULE_RELEASE, 0)) + { + l_frameScheduler.active = false; + l_frameScheduler.period = 0; + LG_LOCK(l_frameScheduler.lock); + l_frameScheduler.phaseError = 0; + l_frameScheduler.feedbackFrameSerial = 0; + l_frameScheduler.feedbackSamples = 0; + l_frameScheduler.feedbackDirty = false; + LG_UNLOCK(l_frameScheduler.lock); + } + return; } + l_frameScheduler.lastCadence = now; bool reset = !l_frameScheduler.period; if (!reset) @@ -259,24 +238,6 @@ void frameScheduler_update(void) } } -void frameScheduler_observeRender(uint64_t timestamp) -{ - LG_LOCK(l_frameScheduler.lock); - if (l_frameScheduler.lastRender && - timestamp > l_frameScheduler.lastRender) - { - l_frameScheduler.renderSamples[l_frameScheduler.renderSampleIndex] = - timestamp - l_frameScheduler.lastRender; - l_frameScheduler.renderSampleIndex = - (l_frameScheduler.renderSampleIndex + 1) % - FRAME_SCHEDULER_SAMPLE_COUNT; - if (l_frameScheduler.renderSampleCount < FRAME_SCHEDULER_SAMPLE_COUNT) - ++l_frameScheduler.renderSampleCount; - } - l_frameScheduler.lastRender = timestamp; - LG_UNLOCK(l_frameScheduler.lock); -} - void frameScheduler_feedback(uint64_t frameSerial, uint32_t generation, uint64_t measuredPhase) { diff --git a/client/src/frame_scheduler.h b/client/src/frame_scheduler.h index 419920dd..ae7a3981 100644 --- a/client/src/frame_scheduler.h +++ b/client/src/frame_scheduler.h @@ -30,7 +30,6 @@ void frameScheduler_free(void); void frameScheduler_start(LG_TransportFeatureFlags features); void frameScheduler_stop(void); void frameScheduler_update(void); -void frameScheduler_observeRender(uint64_t timestamp); void frameScheduler_feedback(uint64_t frameSerial, uint32_t generation, uint64_t measuredPhase); diff --git a/client/src/main.c b/client/src/main.c index 3e8bc837..1e2cf021 100644 --- a/client/src/main.c +++ b/client/src/main.c @@ -727,7 +727,6 @@ static int renderThread(void * unused) const uint64_t delta = t - g_state.lastRenderTime; g_state.lastRenderTime = t; - frameScheduler_observeRender(t); atomic_fetch_add_explicit(&g_state.renderCount, 1, memory_order_relaxed); if (!g_state.jitRender && g_params.fpsMin != 0)