mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-09 08:41:31 +00:00
[client] display: use trusted nominal cadence
Always track Wayland surface outputs alongside fractional scaling and use RandR modes for X11 cadence. Remove render-derived feedback so a throttled client cannot teach the producer to remain throttled. Release scheduling after nominal output timing remains unavailable.
This commit is contained in:
@@ -76,15 +76,7 @@ bool waylandGetFramePeriod(uint64_t * period)
|
|||||||
/* Frame demand must use the output's nominal rate. Basing demand on the
|
/* Frame demand must use the output's nominal rate. Basing demand on the
|
||||||
* observed presentation interval can lock a VRR session at its current,
|
* observed presentation interval can lock a VRR session at its current,
|
||||||
* slower cadence. */
|
* slower cadence. */
|
||||||
if (waylandOutputGetFramePeriod(period))
|
return waylandOutputGetFramePeriod(period);
|
||||||
return true;
|
|
||||||
|
|
||||||
const uint64_t value = atomic_load(&wlWm.presentationPeriod);
|
|
||||||
if (!value)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
*period = value;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void presentationFeedbackDiscarded(void * data,
|
static void presentationFeedbackDiscarded(void * data,
|
||||||
|
|||||||
@@ -133,6 +133,8 @@ bool waylandWindowInit(const char * title, const char * appId, bool fullscreen,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wl_surface_add_listener(wlWm.surface, &wlSurfaceListener, NULL);
|
||||||
|
|
||||||
if (wlWm.fractionalScaleManager)
|
if (wlWm.fractionalScaleManager)
|
||||||
{
|
{
|
||||||
wlWm.fractionalScaleInterface = wp_fractional_scale_manager_v1_get_fractional_scale(
|
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,
|
wp_fractional_scale_v1_add_listener(wlWm.fractionalScaleInterface,
|
||||||
&fractionalScaleListener, NULL);
|
&fractionalScaleListener, NULL);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
wl_surface_add_listener(wlWm.surface, &wlSurfaceListener, NULL);
|
|
||||||
|
|
||||||
if (wlWm.contentTypeManager)
|
if (wlWm.contentTypeManager)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ pkg_check_modules(DISPLAYSERVER_X11 REQUIRED IMPORTED_TARGET
|
|||||||
xinerama
|
xinerama
|
||||||
xcursor
|
xcursor
|
||||||
xpresent
|
xpresent
|
||||||
|
xrandr
|
||||||
xkbcommon
|
xkbcommon
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
#include <X11/extensions/scrnsaver.h>
|
#include <X11/extensions/scrnsaver.h>
|
||||||
#include <X11/extensions/Xinerama.h>
|
#include <X11/extensions/Xinerama.h>
|
||||||
#include <X11/extensions/Xpresent.h>
|
#include <X11/extensions/Xpresent.h>
|
||||||
|
#include <X11/extensions/Xrandr.h>
|
||||||
#include <X11/Xcursor/Xcursor.h>
|
#include <X11/Xcursor/Xcursor.h>
|
||||||
#include <X11/XKBlib.h>
|
#include <X11/XKBlib.h>
|
||||||
|
|
||||||
@@ -94,6 +95,67 @@ static void x11XPresentEvent(XGenericEventCookie *cookie);
|
|||||||
static void x11UpdateKeyboardGroup(void);
|
static void x11UpdateKeyboardGroup(void);
|
||||||
static void x11GrabPointer(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 void x11DoPresent(uint64_t msc)
|
||||||
{
|
{
|
||||||
static bool startup = true;
|
static bool startup = true;
|
||||||
@@ -284,6 +346,12 @@ static bool x11Init(const LG_DSInitParams params)
|
|||||||
x11.display = XOpenDisplay(NULL);
|
x11.display = XOpenDisplay(NULL);
|
||||||
x11.jitRender = params.jitRender;
|
x11.jitRender = params.jitRender;
|
||||||
x11.wm = &X11WM_Default;
|
x11.wm = &X11WM_Default;
|
||||||
|
x11.rect = (struct Rect) {
|
||||||
|
.x = params.x,
|
||||||
|
.y = params.y,
|
||||||
|
.w = params.w,
|
||||||
|
.h = params.h,
|
||||||
|
};
|
||||||
|
|
||||||
XSetWindowAttributes swa =
|
XSetWindowAttributes swa =
|
||||||
{
|
{
|
||||||
@@ -689,6 +757,18 @@ static bool x11Init(const LG_DSInitParams params)
|
|||||||
/* default to the square cursor */
|
/* default to the square cursor */
|
||||||
XDefineCursor(x11.display, x11.window, x11.cursors[LG_POINTER_SQUARE]);
|
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)
|
if (x11.jitRender)
|
||||||
{
|
{
|
||||||
x11.frameEvent = lgCreateEvent(true, 0);
|
x11.frameEvent = lgCreateEvent(true, 0);
|
||||||
@@ -899,6 +979,16 @@ static int x11EventThread(void * unused)
|
|||||||
XEvent xe;
|
XEvent xe;
|
||||||
XNextEvent(x11.display, &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
|
// call the clipboard handling code
|
||||||
if (x11CBEventThread(&xe))
|
if (x11CBEventThread(&xe))
|
||||||
continue;
|
continue;
|
||||||
@@ -932,6 +1022,8 @@ static int x11EventThread(void * unused)
|
|||||||
x11.rect.y = y;
|
x11.rect.y = y;
|
||||||
x11.rect.w = xe.xconfigure.width;
|
x11.rect.w = xe.xconfigure.width;
|
||||||
x11.rect.h = xe.xconfigure.height;
|
x11.rect.h = xe.xconfigure.height;
|
||||||
|
if (x11.xrandrSupported)
|
||||||
|
x11UpdateFramePeriod();
|
||||||
|
|
||||||
app_updateWindowPos(x, y);
|
app_updateWindowPos(x, y);
|
||||||
|
|
||||||
@@ -1507,7 +1599,7 @@ static void x11XPresentEvent(XGenericEventCookie *cookie)
|
|||||||
|
|
||||||
static bool x11GetFramePeriod(uint64_t * period)
|
static bool x11GetFramePeriod(uint64_t * period)
|
||||||
{
|
{
|
||||||
const uint64_t value = atomic_load(&x11.presentPeriod);
|
const uint64_t value = atomic_load(&x11.nominalPeriod);
|
||||||
if (!value)
|
if (!value)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|||||||
@@ -76,11 +76,15 @@ struct X11DSState
|
|||||||
bool jitRender;
|
bool jitRender;
|
||||||
_Atomic(uint64_t) presentMsc, presentUst;
|
_Atomic(uint64_t) presentMsc, presentUst;
|
||||||
_Atomic(uint64_t) presentPeriod;
|
_Atomic(uint64_t) presentPeriod;
|
||||||
|
_Atomic(uint64_t) nominalPeriod;
|
||||||
uint32_t presentSerial;
|
uint32_t presentSerial;
|
||||||
Pixmap presentPixmap;
|
Pixmap presentPixmap;
|
||||||
XserverRegion presentRegion;
|
XserverRegion presentRegion;
|
||||||
LGEvent * frameEvent;
|
LGEvent * frameEvent;
|
||||||
|
|
||||||
|
bool xrandrSupported;
|
||||||
|
int xrandrEvent;
|
||||||
|
|
||||||
LGThread * eventThread;
|
LGThread * eventThread;
|
||||||
|
|
||||||
int xinputOp;
|
int xinputOp;
|
||||||
|
|||||||
@@ -26,16 +26,15 @@
|
|||||||
#include "common/time.h"
|
#include "common/time.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#define FRAME_SCHEDULER_LEASE_MS 1000U
|
#define FRAME_SCHEDULER_LEASE_MS 1000U
|
||||||
#define FRAME_SCHEDULER_RENEW_NS 250000000ULL
|
#define FRAME_SCHEDULER_RENEW_NS 250000000ULL
|
||||||
#define FRAME_SCHEDULER_FEEDBACK_NS 50000000ULL
|
#define FRAME_SCHEDULER_FEEDBACK_NS 50000000ULL
|
||||||
#define FRAME_SCHEDULER_TARGET_SLACK_NS 500000ULL
|
#define FRAME_SCHEDULER_CADENCE_GRACE_NS 500000000ULL
|
||||||
#define FRAME_SCHEDULER_MIN_PERIOD_NS 2000000ULL
|
#define FRAME_SCHEDULER_TARGET_SLACK_NS 500000ULL
|
||||||
#define FRAME_SCHEDULER_MAX_PERIOD_NS 1000000000ULL
|
#define FRAME_SCHEDULER_MIN_PERIOD_NS 2000000ULL
|
||||||
#define FRAME_SCHEDULER_SAMPLE_COUNT 9U
|
#define FRAME_SCHEDULER_MAX_PERIOD_NS 1000000000ULL
|
||||||
|
|
||||||
static struct
|
static struct
|
||||||
{
|
{
|
||||||
@@ -47,6 +46,7 @@ static struct
|
|||||||
_Atomic(uint32_t) generation;
|
_Atomic(uint32_t) generation;
|
||||||
_Atomic(uint64_t) period;
|
_Atomic(uint64_t) period;
|
||||||
uint64_t lastSend;
|
uint64_t lastSend;
|
||||||
|
uint64_t lastCadence;
|
||||||
|
|
||||||
int64_t phaseError;
|
int64_t phaseError;
|
||||||
uint32_t feedbackFrameSerial;
|
uint32_t feedbackFrameSerial;
|
||||||
@@ -54,43 +54,9 @@ static struct
|
|||||||
bool feedbackDirty;
|
bool feedbackDirty;
|
||||||
|
|
||||||
LG_TransportControlToken controlToken;
|
LG_TransportControlToken controlToken;
|
||||||
|
|
||||||
uint64_t renderSamples[FRAME_SCHEDULER_SAMPLE_COUNT];
|
|
||||||
unsigned renderSampleIndex;
|
|
||||||
unsigned renderSampleCount;
|
|
||||||
uint64_t lastRender;
|
|
||||||
}
|
}
|
||||||
l_frameScheduler;
|
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)
|
static uint64_t presentationPeriod(void)
|
||||||
{
|
{
|
||||||
uint64_t period = 0;
|
uint64_t period = 0;
|
||||||
@@ -98,7 +64,7 @@ static uint64_t presentationPeriod(void)
|
|||||||
g_state.ds->getFramePeriod(&period))
|
g_state.ds->getFramePeriod(&period))
|
||||||
return period;
|
return period;
|
||||||
|
|
||||||
return fallbackPeriod();
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool controlReady(void)
|
static bool controlReady(void)
|
||||||
@@ -177,6 +143,7 @@ void frameScheduler_start(LG_TransportFeatureFlags features)
|
|||||||
l_frameScheduler.active = false;
|
l_frameScheduler.active = false;
|
||||||
l_frameScheduler.controlPending = false;
|
l_frameScheduler.controlPending = false;
|
||||||
l_frameScheduler.lastSend = 0;
|
l_frameScheduler.lastSend = 0;
|
||||||
|
l_frameScheduler.lastCadence = 0;
|
||||||
++l_frameScheduler.generation;
|
++l_frameScheduler.generation;
|
||||||
|
|
||||||
LG_LOCK(l_frameScheduler.lock);
|
LG_LOCK(l_frameScheduler.lock);
|
||||||
@@ -203,15 +170,27 @@ void frameScheduler_update(void)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
const uint64_t now = nanotime();
|
const uint64_t now = nanotime();
|
||||||
uint64_t period = presentationPeriod();
|
const uint64_t period = presentationPeriod();
|
||||||
if (period < FRAME_SCHEDULER_MIN_PERIOD_NS ||
|
if (period < FRAME_SCHEDULER_MIN_PERIOD_NS ||
|
||||||
period > FRAME_SCHEDULER_MAX_PERIOD_NS)
|
period > FRAME_SCHEDULER_MAX_PERIOD_NS)
|
||||||
{
|
{
|
||||||
period = l_frameScheduler.period;
|
if (l_frameScheduler.active && l_frameScheduler.lastCadence &&
|
||||||
if (period < FRAME_SCHEDULER_MIN_PERIOD_NS ||
|
now - l_frameScheduler.lastCadence >
|
||||||
period > FRAME_SCHEDULER_MAX_PERIOD_NS)
|
FRAME_SCHEDULER_CADENCE_GRACE_NS &&
|
||||||
return;
|
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;
|
bool reset = !l_frameScheduler.period;
|
||||||
if (!reset)
|
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,
|
void frameScheduler_feedback(uint64_t frameSerial, uint32_t generation,
|
||||||
uint64_t measuredPhase)
|
uint64_t measuredPhase)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ void frameScheduler_free(void);
|
|||||||
void frameScheduler_start(LG_TransportFeatureFlags features);
|
void frameScheduler_start(LG_TransportFeatureFlags features);
|
||||||
void frameScheduler_stop(void);
|
void frameScheduler_stop(void);
|
||||||
void frameScheduler_update(void);
|
void frameScheduler_update(void);
|
||||||
void frameScheduler_observeRender(uint64_t timestamp);
|
|
||||||
void frameScheduler_feedback(uint64_t frameSerial, uint32_t generation,
|
void frameScheduler_feedback(uint64_t frameSerial, uint32_t generation,
|
||||||
uint64_t measuredPhase);
|
uint64_t measuredPhase);
|
||||||
|
|
||||||
|
|||||||
@@ -727,7 +727,6 @@ static int renderThread(void * unused)
|
|||||||
const uint64_t delta = t - g_state.lastRenderTime;
|
const uint64_t delta = t - g_state.lastRenderTime;
|
||||||
|
|
||||||
g_state.lastRenderTime = t;
|
g_state.lastRenderTime = t;
|
||||||
frameScheduler_observeRender(t);
|
|
||||||
atomic_fetch_add_explicit(&g_state.renderCount, 1, memory_order_relaxed);
|
atomic_fetch_add_explicit(&g_state.renderCount, 1, memory_order_relaxed);
|
||||||
|
|
||||||
if (!g_state.jitRender && g_params.fpsMin != 0)
|
if (!g_state.jitRender && g_params.fpsMin != 0)
|
||||||
|
|||||||
Reference in New Issue
Block a user