mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-22 07:01:30 +00:00
[client] wayland: test presentation feedback lifecycle
This commit is contained in:
497
client/tests/wayland_presentation_test.c
Normal file
497
client/tests/wayland_presentation_test.c
Normal file
@@ -0,0 +1,497 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2026 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "wayland.h"
|
||||
#include "test.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#define ARRAY_LENGTH(a) (sizeof(a) / sizeof((a)[0]))
|
||||
#define MAX_FEEDBACK 8U
|
||||
#define MAX_NOTICE 8U
|
||||
#define SEC UINT64_C(1000000000)
|
||||
|
||||
enum ProxyKind
|
||||
{
|
||||
PROXY_PRESENTATION,
|
||||
PROXY_FEEDBACK,
|
||||
};
|
||||
|
||||
struct Proxy
|
||||
{
|
||||
enum ProxyKind kind;
|
||||
void (**listener)(void);
|
||||
void * data;
|
||||
bool dead;
|
||||
};
|
||||
|
||||
struct Proto
|
||||
{
|
||||
struct Proxy presentation;
|
||||
struct Proxy feedback[MAX_FEEDBACK];
|
||||
unsigned int feedbackN;
|
||||
unsigned int feedbackDestroyN;
|
||||
unsigned int presentationDestroyN;
|
||||
};
|
||||
|
||||
struct Notice
|
||||
{
|
||||
uint64_t token;
|
||||
uint64_t time;
|
||||
bool valid;
|
||||
};
|
||||
|
||||
struct Log
|
||||
{
|
||||
struct Notice notice[MAX_NOTICE];
|
||||
unsigned int noticeN;
|
||||
unsigned int graphRegisterN;
|
||||
unsigned int graphCompactN;
|
||||
unsigned int graphUnregisterN;
|
||||
uint64_t period;
|
||||
bool havePeriod;
|
||||
};
|
||||
|
||||
struct WaylandDSState wlWm;
|
||||
|
||||
static struct Proto proto;
|
||||
static struct Log rec;
|
||||
static uint64_t now;
|
||||
|
||||
const struct wl_interface wp_presentation_feedback_interface =
|
||||
{
|
||||
.name = "wp_presentation_feedback",
|
||||
.version = 1,
|
||||
};
|
||||
|
||||
int clock_gettime(clockid_t id, struct timespec * ts)
|
||||
{
|
||||
CHECK(id == CLOCK_MONOTONIC);
|
||||
ts->tv_sec = now / SEC;
|
||||
ts->tv_nsec = now % SEC;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void wl_list_init(struct wl_list * list)
|
||||
{
|
||||
list->next = list;
|
||||
list->prev = list;
|
||||
}
|
||||
|
||||
void wl_list_insert(struct wl_list * list, struct wl_list * elm)
|
||||
{
|
||||
elm->prev = list;
|
||||
elm->next = list->next;
|
||||
list->next->prev = elm;
|
||||
list->next = elm;
|
||||
}
|
||||
|
||||
void wl_list_remove(struct wl_list * elm)
|
||||
{
|
||||
elm->prev->next = elm->next;
|
||||
elm->next->prev = elm->prev;
|
||||
elm->next = NULL;
|
||||
elm->prev = NULL;
|
||||
}
|
||||
|
||||
int wl_list_empty(const struct wl_list * list)
|
||||
{
|
||||
return list->next == list;
|
||||
}
|
||||
|
||||
uint32_t wl_proxy_get_version(struct wl_proxy * proxy)
|
||||
{
|
||||
CHECK(proxy);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int wl_proxy_add_listener(struct wl_proxy * proxy,
|
||||
void (**listener)(void), void * data)
|
||||
{
|
||||
struct Proxy * p = (struct Proxy *)proxy;
|
||||
CHECK(p);
|
||||
CHECK(!p->dead);
|
||||
CHECK(!p->listener);
|
||||
p->listener = listener;
|
||||
p->data = data;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void destroy(struct Proxy * p)
|
||||
{
|
||||
CHECK(p);
|
||||
CHECK(!p->dead);
|
||||
p->dead = true;
|
||||
|
||||
if (p->kind == PROXY_PRESENTATION)
|
||||
++proto.presentationDestroyN;
|
||||
else
|
||||
++proto.feedbackDestroyN;
|
||||
}
|
||||
|
||||
struct wl_proxy * wl_proxy_marshal_flags(struct wl_proxy * proxy,
|
||||
uint32_t opcode, const struct wl_interface * interface,
|
||||
uint32_t version, uint32_t flags, ...)
|
||||
{
|
||||
struct Proxy * p = (struct Proxy *)proxy;
|
||||
(void)opcode;
|
||||
(void)version;
|
||||
|
||||
CHECK(p == &proto.presentation);
|
||||
if (flags & WL_MARSHAL_FLAG_DESTROY)
|
||||
{
|
||||
CHECK(!interface);
|
||||
destroy(p);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
CHECK(interface == &wp_presentation_feedback_interface);
|
||||
CHECK(proto.feedbackN < ARRAY_LENGTH(proto.feedback));
|
||||
p = &proto.feedback[proto.feedbackN++];
|
||||
p->kind = PROXY_FEEDBACK;
|
||||
return (struct wl_proxy *)p;
|
||||
}
|
||||
|
||||
void wl_proxy_destroy(struct wl_proxy * proxy)
|
||||
{
|
||||
destroy((struct Proxy *)proxy);
|
||||
}
|
||||
|
||||
void app_handleFramePresented(uint64_t token, uint64_t time, bool valid)
|
||||
{
|
||||
CHECK(rec.noticeN < ARRAY_LENGTH(rec.notice));
|
||||
struct Notice * notice = &rec.notice[rec.noticeN++];
|
||||
notice->token = token;
|
||||
notice->time = time;
|
||||
notice->valid = valid;
|
||||
}
|
||||
|
||||
GraphHandle app_registerGraph(const char * name, RingBuffer buffer,
|
||||
float min, float max, GraphFormatFn formatFn)
|
||||
{
|
||||
CHECK(strcmp(name, "PHOTON") == 0);
|
||||
CHECK(buffer);
|
||||
CHECK(min == 0.0f);
|
||||
CHECK(max == 30.0f);
|
||||
CHECK(!formatFn);
|
||||
++rec.graphRegisterN;
|
||||
return (GraphHandle)&rec;
|
||||
}
|
||||
|
||||
void app_setGraphCompact(GraphHandle handle, bool compact)
|
||||
{
|
||||
CHECK(handle == (GraphHandle)&rec);
|
||||
CHECK(compact);
|
||||
++rec.graphCompactN;
|
||||
}
|
||||
|
||||
void app_unregisterGraph(GraphHandle handle)
|
||||
{
|
||||
CHECK(handle == (GraphHandle)&rec);
|
||||
++rec.graphUnregisterN;
|
||||
}
|
||||
|
||||
bool waylandOutputGetFramePeriod(uint64_t * period)
|
||||
{
|
||||
if (!rec.havePeriod)
|
||||
return false;
|
||||
*period = rec.period;
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct Proxy * feedback(unsigned int no)
|
||||
{
|
||||
CHECK(no < proto.feedbackN);
|
||||
struct Proxy * p = &proto.feedback[no];
|
||||
CHECK(p->kind == PROXY_FEEDBACK);
|
||||
CHECK(p->listener);
|
||||
CHECK(!p->dead);
|
||||
return p;
|
||||
}
|
||||
|
||||
static void clockReady(void)
|
||||
{
|
||||
struct Proxy * p = &proto.presentation;
|
||||
CHECK(p->listener);
|
||||
const struct wp_presentation_listener * listener =
|
||||
(const struct wp_presentation_listener *)p->listener;
|
||||
listener->clock_id(p->data, (struct wp_presentation *)p, CLOCK_MONOTONIC);
|
||||
}
|
||||
|
||||
static void present(unsigned int no, uint64_t time, uint32_t nsec)
|
||||
{
|
||||
struct Proxy * p = feedback(no);
|
||||
const struct wp_presentation_feedback_listener * listener =
|
||||
(const struct wp_presentation_feedback_listener *)p->listener;
|
||||
const uint64_t sec = time / SEC;
|
||||
listener->presented(p->data, (struct wp_presentation_feedback *)p,
|
||||
sec >> 32, sec, nsec, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
static void discard(unsigned int no)
|
||||
{
|
||||
struct Proxy * p = feedback(no);
|
||||
const struct wp_presentation_feedback_listener * listener =
|
||||
(const struct wp_presentation_feedback_listener *)p->listener;
|
||||
listener->discarded(p->data, (struct wp_presentation_feedback *)p);
|
||||
}
|
||||
|
||||
static void checkNotice(unsigned int no, uint64_t token,
|
||||
uint64_t time, bool valid)
|
||||
{
|
||||
CHECK(no < rec.noticeN);
|
||||
CHECK(rec.notice[no].token == token);
|
||||
CHECK(rec.notice[no].time == time);
|
||||
CHECK(rec.notice[no].valid == valid);
|
||||
}
|
||||
|
||||
static void start(void)
|
||||
{
|
||||
memset(&wlWm, 0, sizeof(wlWm));
|
||||
memset(&proto, 0, sizeof(proto));
|
||||
memset(&rec, 0, sizeof(rec));
|
||||
now = 10 * SEC;
|
||||
proto.presentation.kind = PROXY_PRESENTATION;
|
||||
wlWm.presentation = (struct wp_presentation *)&proto.presentation;
|
||||
wlWm.surface = (struct wl_surface *)(uintptr_t)1;
|
||||
|
||||
CHECK(waylandPresentationInit());
|
||||
CHECK(rec.graphRegisterN == 1);
|
||||
CHECK(rec.graphCompactN == 1);
|
||||
CHECK(rec.graphUnregisterN == 0);
|
||||
CHECK(!waylandPresentationFrame(1));
|
||||
CHECK(proto.feedbackN == 0);
|
||||
clockReady();
|
||||
}
|
||||
|
||||
static void finish(void)
|
||||
{
|
||||
waylandPresentationFree();
|
||||
CHECK(!wlWm.presentation);
|
||||
CHECK(proto.presentation.dead);
|
||||
CHECK(proto.presentationDestroyN == 1);
|
||||
CHECK(rec.graphUnregisterN == 1);
|
||||
}
|
||||
|
||||
static void testFeedbackFirst(void)
|
||||
{
|
||||
start();
|
||||
struct WaylandPresentationFrame * frame = waylandPresentationFrame(11);
|
||||
CHECK(frame);
|
||||
|
||||
present(0, 10 * SEC, 200000000);
|
||||
CHECK(rec.noticeN == 0);
|
||||
CHECK(proto.feedbackDestroyN == 1);
|
||||
|
||||
now = 10 * SEC + 100000000;
|
||||
bool tracked;
|
||||
waylandPresentationSwapDone(frame, true, &tracked);
|
||||
CHECK(tracked);
|
||||
CHECK(rec.noticeN == 1);
|
||||
checkNotice(0, 11, 100000000, true);
|
||||
|
||||
finish();
|
||||
CHECK(rec.noticeN == 1);
|
||||
CHECK(proto.feedbackDestroyN == 1);
|
||||
}
|
||||
|
||||
static void testSwapFirst(void)
|
||||
{
|
||||
start();
|
||||
struct WaylandPresentationFrame * frame = waylandPresentationFrame(12);
|
||||
CHECK(frame);
|
||||
|
||||
now = 10 * SEC + 100000000;
|
||||
bool tracked;
|
||||
waylandPresentationSwapDone(frame, true, &tracked);
|
||||
CHECK(tracked);
|
||||
CHECK(rec.noticeN == 0);
|
||||
|
||||
present(0, 10 * SEC, 200000000);
|
||||
CHECK(rec.noticeN == 1);
|
||||
checkNotice(0, 12, 100000000, true);
|
||||
|
||||
finish();
|
||||
CHECK(rec.noticeN == 1);
|
||||
CHECK(proto.feedbackDestroyN == 1);
|
||||
}
|
||||
|
||||
static void testDiscard(void)
|
||||
{
|
||||
start();
|
||||
struct WaylandPresentationFrame * first = waylandPresentationFrame(21);
|
||||
CHECK(first);
|
||||
now = 10 * SEC + 100000000;
|
||||
bool tracked;
|
||||
waylandPresentationSwapDone(first, true, &tracked);
|
||||
CHECK(tracked);
|
||||
discard(0);
|
||||
CHECK(rec.noticeN == 1);
|
||||
checkNotice(0, 21, 0, false);
|
||||
|
||||
struct WaylandPresentationFrame * second = waylandPresentationFrame(22);
|
||||
CHECK(second);
|
||||
discard(1);
|
||||
CHECK(rec.noticeN == 1);
|
||||
waylandPresentationSwapDone(second, true, &tracked);
|
||||
CHECK(tracked);
|
||||
CHECK(rec.noticeN == 2);
|
||||
checkNotice(1, 22, 0, false);
|
||||
|
||||
finish();
|
||||
CHECK(rec.noticeN == 2);
|
||||
CHECK(proto.feedbackDestroyN == 2);
|
||||
}
|
||||
|
||||
static void testSwapFail(void)
|
||||
{
|
||||
start();
|
||||
struct WaylandPresentationFrame * frame = waylandPresentationFrame(31);
|
||||
CHECK(frame);
|
||||
|
||||
bool tracked = true;
|
||||
waylandPresentationSwapDone(frame, false, &tracked);
|
||||
CHECK(!tracked);
|
||||
CHECK(rec.noticeN == 1);
|
||||
checkNotice(0, 31, 0, false);
|
||||
|
||||
present(0, 10 * SEC, 200000000);
|
||||
CHECK(rec.noticeN == 1);
|
||||
CHECK(proto.feedbackDestroyN == 1);
|
||||
|
||||
tracked = true;
|
||||
waylandPresentationSwapDone(NULL, true, &tracked);
|
||||
CHECK(!tracked);
|
||||
CHECK(rec.noticeN == 1);
|
||||
|
||||
finish();
|
||||
CHECK(rec.noticeN == 1);
|
||||
}
|
||||
|
||||
static void testTokenZero(void)
|
||||
{
|
||||
start();
|
||||
struct WaylandPresentationFrame * frame = waylandPresentationFrame(0);
|
||||
CHECK(frame);
|
||||
|
||||
now = 10 * SEC + 100000000;
|
||||
bool tracked = true;
|
||||
waylandPresentationSwapDone(frame, true, &tracked);
|
||||
CHECK(!tracked);
|
||||
present(0, 10 * SEC, 200000000);
|
||||
CHECK(rec.noticeN == 0);
|
||||
|
||||
finish();
|
||||
CHECK(rec.noticeN == 0);
|
||||
CHECK(proto.feedbackDestroyN == 1);
|
||||
}
|
||||
|
||||
static void testInvalidTime(void)
|
||||
{
|
||||
start();
|
||||
struct WaylandPresentationFrame * frame = waylandPresentationFrame(41);
|
||||
CHECK(frame);
|
||||
now = 10 * SEC + 100000000;
|
||||
bool tracked;
|
||||
waylandPresentationSwapDone(frame, true, &tracked);
|
||||
CHECK(tracked);
|
||||
present(0, 10 * SEC, 1000000000);
|
||||
CHECK(rec.noticeN == 1);
|
||||
checkNotice(0, 41, 0, false);
|
||||
|
||||
frame = waylandPresentationFrame(42);
|
||||
CHECK(frame);
|
||||
now = 10 * SEC + 200000000;
|
||||
waylandPresentationSwapDone(frame, true, &tracked);
|
||||
CHECK(tracked);
|
||||
present(1, 10 * SEC, 100000000);
|
||||
CHECK(rec.noticeN == 2);
|
||||
checkNotice(1, 42, 0, false);
|
||||
|
||||
finish();
|
||||
CHECK(rec.noticeN == 2);
|
||||
CHECK(proto.feedbackDestroyN == 2);
|
||||
}
|
||||
|
||||
static void testFreePending(void)
|
||||
{
|
||||
start();
|
||||
struct WaylandPresentationFrame * frame = waylandPresentationFrame(51);
|
||||
CHECK(frame);
|
||||
now = 10 * SEC + 100000000;
|
||||
bool tracked;
|
||||
waylandPresentationSwapDone(frame, true, &tracked);
|
||||
CHECK(tracked);
|
||||
CHECK(rec.noticeN == 0);
|
||||
CHECK(proto.feedbackDestroyN == 0);
|
||||
|
||||
finish();
|
||||
CHECK(rec.noticeN == 1);
|
||||
checkNotice(0, 51, 0, false);
|
||||
CHECK(proto.feedbackDestroyN == 1);
|
||||
}
|
||||
|
||||
struct Test
|
||||
{
|
||||
const char * name;
|
||||
void (*run)(void);
|
||||
};
|
||||
|
||||
static const struct Test tests[] =
|
||||
{
|
||||
{ "feedback-first", testFeedbackFirst },
|
||||
{ "swap-first" , testSwapFirst },
|
||||
{ "discard" , testDiscard },
|
||||
{ "swap-fail" , testSwapFail },
|
||||
{ "token-zero" , testTokenZero },
|
||||
{ "invalid-time" , testInvalidTime },
|
||||
{ "free-pending" , testFreePending },
|
||||
};
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
{
|
||||
debug_init();
|
||||
|
||||
if (argc == 2)
|
||||
{
|
||||
for (size_t i = 0; i < ARRAY_LENGTH(tests); ++i)
|
||||
if (strcmp(argv[1], tests[i].name) == 0)
|
||||
{
|
||||
tests[i].run();
|
||||
return 0;
|
||||
}
|
||||
|
||||
fprintf(stderr, "unknown test: %s\n", argv[1]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (argc != 1)
|
||||
return EXIT_FAILURE;
|
||||
|
||||
for (size_t i = 0; i < ARRAY_LENGTH(tests); ++i)
|
||||
tests[i].run();
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user