mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-09 08:41:31 +00:00
[client] metrics: correlate Wayland presentation timing
This commit is contained in:
@@ -30,11 +30,77 @@
|
||||
#include "common/debug.h"
|
||||
#include "common/time.h"
|
||||
|
||||
struct FrameData
|
||||
struct WaylandPresentationFrame
|
||||
{
|
||||
struct timespec sent;
|
||||
atomic_uint refs;
|
||||
struct wl_list link;
|
||||
struct wp_presentation_feedback * feedback;
|
||||
uint64_t frameToken;
|
||||
struct timespec sent;
|
||||
struct timespec swapReturn;
|
||||
struct timespec presented;
|
||||
bool swapDone;
|
||||
bool swapValid;
|
||||
bool feedbackDone;
|
||||
bool feedbackValid;
|
||||
bool timingComplete;
|
||||
};
|
||||
|
||||
struct PresentationCompletion
|
||||
{
|
||||
uint64_t frameToken;
|
||||
uint64_t presentTime;
|
||||
bool notify;
|
||||
bool valid;
|
||||
};
|
||||
|
||||
static void presentationFrameRelease(struct WaylandPresentationFrame * frame)
|
||||
{
|
||||
if (atomic_fetch_sub_explicit(
|
||||
&frame->refs, 1, memory_order_acq_rel) == 1)
|
||||
free(frame);
|
||||
}
|
||||
|
||||
static bool presentationOrdered(const struct timespec * left,
|
||||
const struct timespec * right)
|
||||
{
|
||||
return left->tv_sec > right->tv_sec ||
|
||||
(left->tv_sec == right->tv_sec && left->tv_nsec >= right->tv_nsec);
|
||||
}
|
||||
|
||||
static uint64_t presentationDelta(const struct timespec * left,
|
||||
const struct timespec * right)
|
||||
{
|
||||
struct timespec delta;
|
||||
tsDiff(&delta, left, right);
|
||||
return (uint64_t)delta.tv_sec * 1000000000ULL + delta.tv_nsec;
|
||||
}
|
||||
|
||||
static void presentationCompleteLocked(
|
||||
struct WaylandPresentationFrame * frame,
|
||||
struct PresentationCompletion * completion)
|
||||
{
|
||||
if (frame->timingComplete || !frame->swapDone || !frame->feedbackDone)
|
||||
return;
|
||||
|
||||
frame->timingComplete = true;
|
||||
completion->notify = frame->frameToken != 0;
|
||||
completion->frameToken = frame->frameToken;
|
||||
completion->valid = frame->swapValid && frame->feedbackValid &&
|
||||
presentationOrdered(&frame->presented, &frame->swapReturn);
|
||||
if (completion->valid)
|
||||
completion->presentTime = presentationDelta(
|
||||
&frame->presented, &frame->swapReturn);
|
||||
}
|
||||
|
||||
static void presentationNotify(
|
||||
const struct PresentationCompletion * completion)
|
||||
{
|
||||
if (completion->notify)
|
||||
app_handleFramePresented(completion->frameToken,
|
||||
completion->presentTime, completion->valid);
|
||||
}
|
||||
|
||||
static void presentationClockId(void * data,
|
||||
struct wp_presentation * presentation, uint32_t clkId)
|
||||
{
|
||||
@@ -57,18 +123,34 @@ static void presentationFeedbackPresented(void * opaque,
|
||||
struct wp_presentation_feedback * feedback, uint32_t tvSecHi, uint32_t tvSecLo,
|
||||
uint32_t tvNsec, uint32_t refresh, uint32_t seqHi, uint32_t seqLo, uint32_t flags)
|
||||
{
|
||||
struct FrameData * data = opaque;
|
||||
struct timespec present = {
|
||||
struct WaylandPresentationFrame * frame = opaque;
|
||||
const struct timespec present = {
|
||||
.tv_sec = (uint64_t) tvSecHi << 32 | tvSecLo,
|
||||
.tv_nsec = tvNsec,
|
||||
};
|
||||
struct timespec delta;
|
||||
struct PresentationCompletion completion = {};
|
||||
|
||||
tsDiff(&delta, &present, &data->sent);
|
||||
ringbuffer_push(wlWm.photonTimings,
|
||||
&(float){ delta.tv_sec * 1e3f + delta.tv_nsec * 1e-6f });
|
||||
free(data);
|
||||
if (tvNsec < 1000000000)
|
||||
{
|
||||
struct timespec delta;
|
||||
tsDiff(&delta, &present, &frame->sent);
|
||||
ringbuffer_push(wlWm.photonTimings,
|
||||
&(float){ delta.tv_sec * 1e3f + delta.tv_nsec * 1e-6f });
|
||||
}
|
||||
|
||||
INTERLOCKED_SECTION(wlWm.presentationLock,
|
||||
{
|
||||
wl_list_remove(&frame->link);
|
||||
frame->feedback = NULL;
|
||||
frame->feedbackDone = true;
|
||||
frame->feedbackValid = tvNsec < 1000000000;
|
||||
frame->presented = present;
|
||||
presentationCompleteLocked(frame, &completion);
|
||||
});
|
||||
|
||||
presentationNotify(&completion);
|
||||
wp_presentation_feedback_destroy(feedback);
|
||||
presentationFrameRelease(frame);
|
||||
}
|
||||
|
||||
bool waylandGetFramePeriod(uint64_t * period)
|
||||
@@ -82,8 +164,21 @@ bool waylandGetFramePeriod(uint64_t * period)
|
||||
static void presentationFeedbackDiscarded(void * data,
|
||||
struct wp_presentation_feedback * feedback)
|
||||
{
|
||||
free(data);
|
||||
struct WaylandPresentationFrame * frame = data;
|
||||
struct PresentationCompletion completion = {};
|
||||
|
||||
INTERLOCKED_SECTION(wlWm.presentationLock,
|
||||
{
|
||||
wl_list_remove(&frame->link);
|
||||
frame->feedback = NULL;
|
||||
frame->feedbackDone = true;
|
||||
frame->feedbackValid = false;
|
||||
presentationCompleteLocked(frame, &completion);
|
||||
});
|
||||
|
||||
presentationNotify(&completion);
|
||||
wp_presentation_feedback_destroy(feedback);
|
||||
presentationFrameRelease(frame);
|
||||
}
|
||||
|
||||
static const struct wp_presentation_feedback_listener presentationFeedbackListener = {
|
||||
@@ -94,10 +189,13 @@ static const struct wp_presentation_feedback_listener presentationFeedbackListen
|
||||
|
||||
bool waylandPresentationInit(void)
|
||||
{
|
||||
LG_LOCK_INIT(wlWm.presentationLock);
|
||||
wl_list_init(&wlWm.presentationFrames);
|
||||
atomic_store_explicit(
|
||||
&wlWm.presentationClockValid, false, memory_order_release);
|
||||
|
||||
if (wlWm.presentation)
|
||||
{
|
||||
atomic_store_explicit(
|
||||
&wlWm.presentationClockValid, false, memory_order_release);
|
||||
wlWm.photonTimings = ringbuffer_new(256, sizeof(float));
|
||||
wlWm.photonGraph = app_registerGraph("PHOTON", wlWm.photonTimings,
|
||||
0.0f, 30.0f, NULL);
|
||||
@@ -109,36 +207,129 @@ bool waylandPresentationInit(void)
|
||||
|
||||
void waylandPresentationFree(void)
|
||||
{
|
||||
if (!wlWm.presentation)
|
||||
return;
|
||||
|
||||
atomic_store_explicit(
|
||||
&wlWm.presentationClockValid, false, memory_order_release);
|
||||
wp_presentation_destroy(wlWm.presentation);
|
||||
app_unregisterGraph(wlWm.photonGraph);
|
||||
ringbuffer_free(&wlWm.photonTimings);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
struct WaylandPresentationFrame * frame = NULL;
|
||||
struct wp_presentation_feedback * feedback = NULL;
|
||||
struct PresentationCompletion completion = {};
|
||||
|
||||
LG_LOCK(wlWm.presentationLock);
|
||||
if (!wl_list_empty(&wlWm.presentationFrames))
|
||||
{
|
||||
frame = wl_container_of(
|
||||
wlWm.presentationFrames.next, frame, link);
|
||||
wl_list_remove(&frame->link);
|
||||
feedback = frame->feedback;
|
||||
frame->feedback = NULL;
|
||||
frame->swapDone = true;
|
||||
frame->swapValid = false;
|
||||
frame->feedbackDone = true;
|
||||
frame->feedbackValid = false;
|
||||
presentationCompleteLocked(frame, &completion);
|
||||
}
|
||||
LG_UNLOCK(wlWm.presentationLock);
|
||||
|
||||
if (!frame)
|
||||
break;
|
||||
|
||||
presentationNotify(&completion);
|
||||
wp_presentation_feedback_destroy(feedback);
|
||||
presentationFrameRelease(frame);
|
||||
}
|
||||
|
||||
if (wlWm.presentation)
|
||||
{
|
||||
wp_presentation_destroy(wlWm.presentation);
|
||||
wlWm.presentation = NULL;
|
||||
app_unregisterGraph(wlWm.photonGraph);
|
||||
ringbuffer_free(&wlWm.photonTimings);
|
||||
}
|
||||
LG_LOCK_FREE(wlWm.presentationLock);
|
||||
}
|
||||
|
||||
void waylandPresentationFrame(void)
|
||||
struct WaylandPresentationFrame * waylandPresentationFrame(
|
||||
uint64_t frameToken)
|
||||
{
|
||||
if (!wlWm.presentation || !atomic_load_explicit(
|
||||
&wlWm.presentationClockValid, memory_order_acquire))
|
||||
return;
|
||||
return NULL;
|
||||
|
||||
struct FrameData * data = malloc(sizeof(*data));
|
||||
if (!data)
|
||||
struct WaylandPresentationFrame * frame = calloc(1, sizeof(*frame));
|
||||
if (!frame)
|
||||
{
|
||||
DEBUG_ERROR("out of memory");
|
||||
return;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (clock_gettime(wlWm.clkId, &data->sent))
|
||||
if (clock_gettime(wlWm.clkId, &frame->sent))
|
||||
{
|
||||
DEBUG_ERROR("clock_gettime failed: %s\n", strerror(errno));
|
||||
free(data);
|
||||
return;
|
||||
DEBUG_ERROR("clock_gettime failed: %s", strerror(errno));
|
||||
free(frame);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct wp_presentation_feedback * feedback = wp_presentation_feedback(wlWm.presentation, wlWm.surface);
|
||||
wp_presentation_feedback_add_listener(feedback, &presentationFeedbackListener, data);
|
||||
frame->feedback =
|
||||
wp_presentation_feedback(wlWm.presentation, wlWm.surface);
|
||||
if (!frame->feedback)
|
||||
{
|
||||
free(frame);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
frame->frameToken = frameToken;
|
||||
frame->timingComplete = frameToken == 0;
|
||||
atomic_init(&frame->refs, 2);
|
||||
if (wp_presentation_feedback_add_listener(frame->feedback,
|
||||
&presentationFeedbackListener, frame) < 0)
|
||||
{
|
||||
wp_presentation_feedback_destroy(frame->feedback);
|
||||
free(frame);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
INTERLOCKED_SECTION(wlWm.presentationLock,
|
||||
{
|
||||
wl_list_insert(&wlWm.presentationFrames, &frame->link);
|
||||
});
|
||||
return frame;
|
||||
}
|
||||
|
||||
void waylandPresentationSwapDone(struct WaylandPresentationFrame * frame,
|
||||
bool result, bool * presentTracked)
|
||||
{
|
||||
*presentTracked = false;
|
||||
if (!frame)
|
||||
return;
|
||||
|
||||
struct timespec swapReturn = {};
|
||||
const bool swapValid = result &&
|
||||
clock_gettime(wlWm.clkId, &swapReturn) == 0;
|
||||
if (result && !swapValid)
|
||||
DEBUG_ERROR("clock_gettime failed: %s", strerror(errno));
|
||||
|
||||
struct PresentationCompletion completion = {};
|
||||
INTERLOCKED_SECTION(wlWm.presentationLock,
|
||||
{
|
||||
frame->swapDone = true;
|
||||
frame->swapValid = swapValid;
|
||||
frame->swapReturn = swapReturn;
|
||||
|
||||
if (!swapValid && !frame->timingComplete)
|
||||
{
|
||||
frame->timingComplete = true;
|
||||
completion.notify = true;
|
||||
completion.frameToken = frame->frameToken;
|
||||
completion.presentTime = 0;
|
||||
completion.valid = false;
|
||||
}
|
||||
else
|
||||
presentationCompleteLocked(frame, &completion);
|
||||
});
|
||||
|
||||
*presentTracked = result && frame->frameToken != 0;
|
||||
presentationNotify(&completion);
|
||||
presentationFrameRelease(frame);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user