From b4765809b5e7da43a850d455c98a468992843db9 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Fri, 23 Sep 2022 21:55:29 +0200 Subject: [PATCH 1/6] render: add wlr_renderer_get_time() This queries the current GPU time. [1]: https://registry.khronos.org/OpenGL/extensions/EXT/EXT_disjoint_timer_query.txt References: https://gitlab.freedesktop.org/wlroots/wlroots/-/issues/3025 --- include/render/gles2.h | 6 ++++++ include/wlr/render/interface.h | 1 + include/wlr/render/wlr_renderer.h | 7 +++++++ render/gles2/renderer.c | 24 ++++++++++++++++++++++++ render/wlr_renderer.c | 7 +++++++ 5 files changed, 45 insertions(+) diff --git a/include/render/gles2.h b/include/render/gles2.h index 714bacf56..5ce9c6771 100644 --- a/include/render/gles2.h +++ b/include/render/gles2.h @@ -32,6 +32,10 @@ struct wlr_gles2_tex_shader { GLint tex_attrib; }; +// Fixed here, but missing from any libglvnd release: +// https://gitlab.freedesktop.org/glvnd/libglvnd/-/merge_requests/268 +typedef void (GL_APIENTRYP PFNGLGETINTEGER64VEXTPROC) (GLenum pname, GLint64 *data); + struct wlr_gles2_renderer { struct wlr_renderer wlr_renderer; @@ -48,6 +52,7 @@ struct wlr_gles2_renderer { bool EXT_texture_type_2_10_10_10_REV; bool OES_texture_half_float_linear; bool EXT_texture_norm16; + bool EXT_disjoint_timer_query; } exts; struct { @@ -57,6 +62,7 @@ struct wlr_gles2_renderer { PFNGLPOPDEBUGGROUPKHRPROC glPopDebugGroupKHR; PFNGLPUSHDEBUGGROUPKHRPROC glPushDebugGroupKHR; PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC glEGLImageTargetRenderbufferStorageOES; + PFNGLGETINTEGER64VEXTPROC glGetInteger64vEXT; } procs; struct { diff --git a/include/wlr/render/interface.h b/include/wlr/render/interface.h index e347f29be..a9828827e 100644 --- a/include/wlr/render/interface.h +++ b/include/wlr/render/interface.h @@ -48,6 +48,7 @@ struct wlr_renderer_impl { uint32_t (*get_render_buffer_caps)(struct wlr_renderer *renderer); struct wlr_texture *(*texture_from_buffer)(struct wlr_renderer *renderer, struct wlr_buffer *buffer); + bool (*get_time)(struct wlr_renderer *r, struct timespec *t); }; void wlr_renderer_init(struct wlr_renderer *renderer, diff --git a/include/wlr/render/wlr_renderer.h b/include/wlr/render/wlr_renderer.h index e240897d4..70aeb6cdf 100644 --- a/include/wlr/render/wlr_renderer.h +++ b/include/wlr/render/wlr_renderer.h @@ -14,6 +14,8 @@ #include #include +struct timespec; + struct wlr_renderer_impl; struct wlr_drm_format_set; struct wlr_buffer; @@ -112,6 +114,11 @@ bool wlr_renderer_init_wl_shm(struct wlr_renderer *r, */ int wlr_renderer_get_drm_fd(struct wlr_renderer *r); +/** + * Get the current GPU time. + */ +bool wlr_renderer_get_time(struct wlr_renderer *r, struct timespec *t); + /** * Destroys the renderer. * diff --git a/render/gles2/renderer.c b/render/gles2/renderer.c index b1543cddc..4a8e42e16 100644 --- a/render/gles2/renderer.c +++ b/render/gles2/renderer.c @@ -18,6 +18,7 @@ #include "render/gles2.h" #include "render/pixel_format.h" #include "types/wlr_matrix.h" +#include "util/time.h" static const GLfloat verts[] = { 1, 0, // top right @@ -485,6 +486,23 @@ struct wlr_egl *wlr_gles2_renderer_get_egl(struct wlr_renderer *wlr_renderer) { return renderer->egl; } +static bool gles2_get_time(struct wlr_renderer *wlr_renderer, struct timespec *t) { + struct wlr_gles2_renderer *renderer = + gles2_get_renderer_in_context(wlr_renderer); + + if (!renderer->exts.EXT_disjoint_timer_query) { + return false; + } + + GLint64 nsec = 0; + push_gles2_debug(renderer); + renderer->procs.glGetInteger64vEXT(GL_TIMESTAMP_EXT, &nsec); + pop_gles2_debug(renderer); + + timespec_from_nsec(t, nsec); + return true; +} + static void gles2_destroy(struct wlr_renderer *wlr_renderer) { struct wlr_gles2_renderer *renderer = gles2_get_renderer(wlr_renderer); @@ -539,6 +557,7 @@ static const struct wlr_renderer_impl renderer_impl = { .get_drm_fd = gles2_get_drm_fd, .get_render_buffer_caps = gles2_get_render_buffer_caps, .texture_from_buffer = gles2_texture_from_buffer, + .get_time = gles2_get_time, }; void push_gles2_debug_(struct wlr_gles2_renderer *renderer, @@ -768,6 +787,11 @@ struct wlr_renderer *wlr_gles2_renderer_create(struct wlr_egl *egl) { "glEGLImageTargetRenderbufferStorageOES"); } + if (check_gl_ext(exts_str, "GL_EXT_disjoint_timer_query")) { + renderer->exts.EXT_disjoint_timer_query = true; + load_gl_proc(&renderer->procs.glGetInteger64vEXT, "glGetInteger64vEXT"); + } + if (renderer->exts.KHR_debug) { glEnable(GL_DEBUG_OUTPUT_KHR); glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR); diff --git a/render/wlr_renderer.c b/render/wlr_renderer.c index 2d6481f00..669d2e86e 100644 --- a/render/wlr_renderer.c +++ b/render/wlr_renderer.c @@ -433,3 +433,10 @@ int wlr_renderer_get_drm_fd(struct wlr_renderer *r) { } return r->impl->get_drm_fd(r); } + +bool wlr_renderer_get_time(struct wlr_renderer *r, struct timespec *t) { + if (!r->impl->get_time) { + return false; + } + return r->impl->get_time(r, t); +} From c38c08074211801f4ffcba1cd04d148e661fef11 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Fri, 23 Sep 2022 22:47:59 +0200 Subject: [PATCH 2/6] render: add wlr_render_timestamp --- include/render/gles2.h | 10 ++++ include/wlr/render/interface.h | 14 +++++ include/wlr/render/wlr_renderer.h | 9 ++++ render/gles2/renderer.c | 87 +++++++++++++++++++++++++++++++ render/wlr_renderer.c | 26 +++++++++ 5 files changed, 146 insertions(+) diff --git a/include/render/gles2.h b/include/render/gles2.h index 5ce9c6771..d74879320 100644 --- a/include/render/gles2.h +++ b/include/render/gles2.h @@ -63,6 +63,10 @@ struct wlr_gles2_renderer { PFNGLPUSHDEBUGGROUPKHRPROC glPushDebugGroupKHR; PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC glEGLImageTargetRenderbufferStorageOES; PFNGLGETINTEGER64VEXTPROC glGetInteger64vEXT; + PFNGLGENQUERIESEXTPROC glGenQueriesEXT; + PFNGLDELETEQUERIESEXTPROC glDeleteQueriesEXT; + PFNGLQUERYCOUNTEREXTPROC glQueryCounterEXT; + PFNGLGETQUERYOBJECTI64VEXTPROC glGetQueryObjecti64vEXT; } procs; struct { @@ -118,6 +122,12 @@ struct wlr_gles2_texture { struct wlr_addon buffer_addon; }; +struct wlr_gles2_timestamp { + struct wlr_render_timestamp base; + struct wlr_gles2_renderer *renderer; + GLuint query; +}; + bool is_gles2_pixel_format_supported(const struct wlr_gles2_renderer *renderer, const struct wlr_gles2_pixel_format *format); diff --git a/include/wlr/render/interface.h b/include/wlr/render/interface.h index a9828827e..e1b35ee27 100644 --- a/include/wlr/render/interface.h +++ b/include/wlr/render/interface.h @@ -49,6 +49,7 @@ struct wlr_renderer_impl { struct wlr_texture *(*texture_from_buffer)(struct wlr_renderer *renderer, struct wlr_buffer *buffer); bool (*get_time)(struct wlr_renderer *r, struct timespec *t); + struct wlr_render_timestamp *(*create_timestamp)(struct wlr_renderer *r); }; void wlr_renderer_init(struct wlr_renderer *renderer, @@ -63,4 +64,17 @@ struct wlr_texture_impl { void wlr_texture_init(struct wlr_texture *texture, const struct wlr_texture_impl *impl, uint32_t width, uint32_t height); +struct wlr_render_timestamp { + const struct wlr_render_timestamp_impl *impl; +}; + +struct wlr_render_timestamp_impl { + void (*destroy)(struct wlr_render_timestamp *timestamp); + bool (*get_time)(struct wlr_render_timestamp *timestamp, + struct timespec *t); +}; + +void wlr_render_timestamp_init(struct wlr_render_timestamp *timestamp, + const struct wlr_render_timestamp_impl *impl); + #endif diff --git a/include/wlr/render/wlr_renderer.h b/include/wlr/render/wlr_renderer.h index 70aeb6cdf..d1f02645d 100644 --- a/include/wlr/render/wlr_renderer.h +++ b/include/wlr/render/wlr_renderer.h @@ -119,6 +119,15 @@ int wlr_renderer_get_drm_fd(struct wlr_renderer *r); */ bool wlr_renderer_get_time(struct wlr_renderer *r, struct timespec *t); +struct wlr_render_timestamp; + +struct wlr_render_timestamp *wlr_renderer_create_timestamp(struct wlr_renderer *r); + +bool wlr_render_timestamp_get_time(struct wlr_render_timestamp *timestamp, + struct timespec *t); + +void wlr_render_timestamp_destroy(struct wlr_render_timestamp *timestamp); + /** * Destroys the renderer. * diff --git a/render/gles2/renderer.c b/render/gles2/renderer.c index 4a8e42e16..c8ce13d8d 100644 --- a/render/gles2/renderer.c +++ b/render/gles2/renderer.c @@ -503,6 +503,35 @@ static bool gles2_get_time(struct wlr_renderer *wlr_renderer, struct timespec *t return true; } +static const struct wlr_render_timestamp_impl timestamp_impl; + +static struct wlr_render_timestamp *gles2_create_timestamp( + struct wlr_renderer *wlr_renderer) { + struct wlr_gles2_renderer *renderer = + gles2_get_renderer_in_context(wlr_renderer); + + if (!renderer->exts.EXT_disjoint_timer_query) { + return NULL; + } + + struct wlr_gles2_timestamp *timestamp = calloc(1, sizeof(*timestamp)); + if (timestamp == NULL) { + return NULL; + } + wlr_render_timestamp_init(×tamp->base, ×tamp_impl); + + timestamp->renderer = renderer; + + GLint disjoint = 0; + push_gles2_debug(renderer); + glGetIntegerv(GL_GPU_DISJOINT_EXT, &disjoint); // clear the disjoint flag + renderer->procs.glGenQueriesEXT(1, ×tamp->query); + renderer->procs.glQueryCounterEXT(timestamp->query, GL_TIMESTAMP_EXT); + pop_gles2_debug(renderer); + + return ×tamp->base; +} + static void gles2_destroy(struct wlr_renderer *wlr_renderer) { struct wlr_gles2_renderer *renderer = gles2_get_renderer(wlr_renderer); @@ -558,6 +587,7 @@ static const struct wlr_renderer_impl renderer_impl = { .get_render_buffer_caps = gles2_get_render_buffer_caps, .texture_from_buffer = gles2_texture_from_buffer, .get_time = gles2_get_time, + .create_timestamp = gles2_create_timestamp, }; void push_gles2_debug_(struct wlr_gles2_renderer *renderer, @@ -790,6 +820,10 @@ struct wlr_renderer *wlr_gles2_renderer_create(struct wlr_egl *egl) { if (check_gl_ext(exts_str, "GL_EXT_disjoint_timer_query")) { renderer->exts.EXT_disjoint_timer_query = true; load_gl_proc(&renderer->procs.glGetInteger64vEXT, "glGetInteger64vEXT"); + load_gl_proc(&renderer->procs.glGenQueriesEXT, "glGenQueriesEXT"); + load_gl_proc(&renderer->procs.glDeleteQueriesEXT, "glDeleteQueriesEXT"); + load_gl_proc(&renderer->procs.glQueryCounterEXT, "glQueryCounterEXT"); + load_gl_proc(&renderer->procs.glGetQueryObjecti64vEXT, "glGetQueryObjecti64vEXT"); } if (renderer->exts.KHR_debug) { @@ -887,3 +921,56 @@ GLuint wlr_gles2_renderer_get_current_fbo(struct wlr_renderer *wlr_renderer) { assert(renderer->current_buffer); return renderer->current_buffer->fbo; } + +static struct wlr_gles2_timestamp *get_timestamp( + struct wlr_render_timestamp *wlr_timestamp) { + assert(wlr_timestamp->impl == ×tamp_impl); + struct wlr_gles2_timestamp *timestamp = + wl_container_of(wlr_timestamp, timestamp, base); + return timestamp; +} + +static void timestamp_destroy(struct wlr_render_timestamp *wlr_timestamp) { + struct wlr_gles2_timestamp *timestamp = get_timestamp(wlr_timestamp); + struct wlr_gles2_renderer *renderer = timestamp->renderer; + + struct wlr_egl_context prev_ctx; + wlr_egl_save_context(&prev_ctx); + wlr_egl_make_current(renderer->egl); + + push_gles2_debug(renderer); + renderer->procs.glDeleteQueriesEXT(1, ×tamp->query); + pop_gles2_debug(renderer); + + wlr_egl_restore_context(&prev_ctx); +} + +static bool timestamp_get_time(struct wlr_render_timestamp *wlr_timestamp, + struct timespec *t) { + struct wlr_gles2_timestamp *timestamp = get_timestamp(wlr_timestamp); + struct wlr_gles2_renderer *renderer = timestamp->renderer; + + struct wlr_egl_context prev_ctx; + wlr_egl_save_context(&prev_ctx); + wlr_egl_make_current(renderer->egl); + + GLint disjoint = 0; + GLint64 available = 0, nsec = 0; + push_gles2_debug(renderer); + glGetIntegerv(GL_GPU_DISJOINT_EXT, &disjoint); + renderer->procs.glGetQueryObjecti64vEXT(timestamp->query, + GL_QUERY_RESULT_AVAILABLE_EXT, &available); + renderer->procs.glGetQueryObjecti64vEXT(timestamp->query, + GL_QUERY_RESULT_EXT, &nsec); + pop_gles2_debug(renderer); + + wlr_egl_restore_context(&prev_ctx); + + timespec_from_nsec(t, nsec); + return available && !disjoint; +} + +static const struct wlr_render_timestamp_impl timestamp_impl = { + .destroy = timestamp_destroy, + .get_time = timestamp_get_time, +}; diff --git a/render/wlr_renderer.c b/render/wlr_renderer.c index 669d2e86e..b8aa2294c 100644 --- a/render/wlr_renderer.c +++ b/render/wlr_renderer.c @@ -440,3 +440,29 @@ bool wlr_renderer_get_time(struct wlr_renderer *r, struct timespec *t) { } return r->impl->get_time(r, t); } + +struct wlr_render_timestamp *wlr_renderer_create_timestamp(struct wlr_renderer *r) { + if (!r->impl->create_timestamp) { + return false; + } + return r->impl->create_timestamp(r); +} + +void wlr_render_timestamp_init(struct wlr_render_timestamp *timestamp, + const struct wlr_render_timestamp_impl *impl) { + assert(impl->get_time && impl->destroy); + memset(timestamp, 0, sizeof(*timestamp)); + timestamp->impl = impl; +} + +bool wlr_render_timestamp_get_time(struct wlr_render_timestamp *timestamp, + struct timespec *t) { + return timestamp->impl->get_time(timestamp, t); +} + +void wlr_render_timestamp_destroy(struct wlr_render_timestamp *timestamp) { + if (timestamp == NULL) { + return; + } + timestamp->impl->destroy(timestamp); +} From 293d78777dd2206a128346f067bbd9679b8d2bbf Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 4 Oct 2022 12:11:36 +0200 Subject: [PATCH 3/6] util/time: add timespec_to_nsec() --- include/util/time.h | 5 +++++ util/time.c | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/include/util/time.h b/include/util/time.h index 287698dec..76ec2d7f1 100644 --- a/include/util/time.h +++ b/include/util/time.h @@ -13,6 +13,11 @@ uint32_t get_current_time_msec(void); */ int64_t timespec_to_msec(const struct timespec *a); +/** + * Convert a timespec to nanoseconds. + */ +int64_t timespec_to_nsec(const struct timespec *t); + /** * Convert nanoseconds to a timespec. */ diff --git a/util/time.c b/util/time.c index 06e42b40d..e3a4590ad 100644 --- a/util/time.c +++ b/util/time.c @@ -10,6 +10,10 @@ int64_t timespec_to_msec(const struct timespec *a) { return (int64_t)a->tv_sec * 1000 + a->tv_nsec / 1000000; } +int64_t timespec_to_nsec(const struct timespec *t) { + return (int64_t)t->tv_sec * NSEC_PER_SEC + t->tv_nsec; +} + void timespec_from_nsec(struct timespec *r, int64_t nsec) { r->tv_sec = nsec / NSEC_PER_SEC; r->tv_nsec = nsec % NSEC_PER_SEC; From 639bacdb9a16c8e6f6c09ae7af5ea9ae4e04cf13 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 4 Oct 2022 12:11:54 +0200 Subject: [PATCH 4/6] util/time: add timespec_add() --- include/util/time.h | 6 ++++++ util/time.c | 20 ++++++++++++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/include/util/time.h b/include/util/time.h index 76ec2d7f1..5ab4cb103 100644 --- a/include/util/time.h +++ b/include/util/time.h @@ -23,6 +23,12 @@ int64_t timespec_to_nsec(const struct timespec *t); */ void timespec_from_nsec(struct timespec *r, int64_t nsec); +/** + * Add two timespec values `a` and `b`, and store the result in `r`. + */ +void timespec_add(struct timespec *r, const struct timespec *a, + const struct timespec *b); + /** * Subtracts timespec `b` from timespec `a`, and stores the difference in `r`. */ diff --git a/util/time.c b/util/time.c index e3a4590ad..c02e585b5 100644 --- a/util/time.c +++ b/util/time.c @@ -25,12 +25,24 @@ uint32_t get_current_time_msec(void) { return timespec_to_msec(&now); } -void timespec_sub(struct timespec *r, const struct timespec *a, +void timespec_add(struct timespec *r, const struct timespec *a, const struct timespec *b) { - r->tv_sec = a->tv_sec - b->tv_sec; - r->tv_nsec = a->tv_nsec - b->tv_nsec; - if (r->tv_nsec < 0) { + r->tv_sec = a->tv_sec + b->tv_sec; + r->tv_nsec = a->tv_nsec + b->tv_nsec; + if (r->tv_nsec >= NSEC_PER_SEC) { + r->tv_sec++; + r->tv_nsec -= NSEC_PER_SEC; + } else if (r->tv_nsec < 0) { r->tv_sec--; r->tv_nsec += NSEC_PER_SEC; } } + +void timespec_sub(struct timespec *r, const struct timespec *a, + const struct timespec *b) { + struct timespec tmp = { + .tv_sec = -b->tv_sec, + .tv_nsec = -b->tv_nsec, + }; + timespec_add(r, a, &tmp); +} From 374a4a8d7ededa51228bd6d28ad5aa17cd5a7cd5 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 4 Oct 2022 12:13:44 +0200 Subject: [PATCH 5/6] Introduce wlr_frame_scheduler --- include/wlr/types/wlr_frame_scheduler.h | 55 +++++++ types/meson.build | 1 + types/wlr_frame_scheduler.c | 189 ++++++++++++++++++++++++ 3 files changed, 245 insertions(+) create mode 100644 include/wlr/types/wlr_frame_scheduler.h create mode 100644 types/wlr_frame_scheduler.c diff --git a/include/wlr/types/wlr_frame_scheduler.h b/include/wlr/types/wlr_frame_scheduler.h new file mode 100644 index 000000000..ad2b3dee1 --- /dev/null +++ b/include/wlr/types/wlr_frame_scheduler.h @@ -0,0 +1,55 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_FRAME_SCHEDULER_H +#define WLR_TYPES_WLR_FRAME_SCHEDULER_H + +#include +#include + +struct wlr_renderer; +struct wlr_render_timestamp; + +#define WLR_FRAME_SCHEDULER_HISTOGRAM_LEN 128 + +struct wlr_frame_scheduler_bucket { + int64_t cpu_duration_ns, gpu_duration_ns; +}; + +struct wlr_frame_scheduler { + struct wlr_output *output; + clockid_t presentation_clock; + + struct { + struct wl_signal frame; + } events; + + // private state + + struct wl_event_source *timer; + + struct wlr_frame_scheduler_bucket histogram[WLR_FRAME_SCHEDULER_HISTOGRAM_LEN]; + size_t histogram_cur; + + struct wl_listener output_present; + + struct { + uint32_t commit_seq; + struct timespec frame_emitted, frame_submitted; // CPU time + struct timespec render_submitted; // GPU time + struct wlr_render_timestamp *render_complete; + } queued; +}; + +bool wlr_frame_scheduler_init(struct wlr_frame_scheduler *scheduler, + struct wlr_output *output); +void wlr_frame_scheduler_finish(struct wlr_frame_scheduler *scheduler); +void wlr_frame_scheduler_mark_render_submitted( + struct wlr_frame_scheduler *scheduler, struct wlr_renderer *renderer); + +#endif diff --git a/types/meson.build b/types/meson.build index 853d31c7a..a9e8e6172 100644 --- a/types/meson.build +++ b/types/meson.build @@ -41,6 +41,7 @@ wlr_files += files( 'wlr_export_dmabuf_v1.c', 'wlr_foreign_toplevel_management_v1.c', 'wlr_fullscreen_shell_v1.c', + 'wlr_frame_scheduler.c', 'wlr_gamma_control_v1.c', 'wlr_idle_inhibit_v1.c', 'wlr_idle.c', diff --git a/types/wlr_frame_scheduler.c b/types/wlr_frame_scheduler.c new file mode 100644 index 000000000..e2d63ab95 --- /dev/null +++ b/types/wlr_frame_scheduler.c @@ -0,0 +1,189 @@ +#define _POSIX_C_SOURCE 199309L +#include +#include +#include +#include +#include "util/time.h" + +static int handle_timer(void *data) { + struct wlr_frame_scheduler *scheduler = data; + + if (clock_gettime(CLOCK_MONOTONIC, &scheduler->queued.frame_emitted) != 0) { + wlr_log_errno(WLR_ERROR, "clock_gettime() failed"); + return 0; + } + + wl_signal_emit_mutable(&scheduler->events.frame, NULL); + + return 0; +} + +static void schedule_frame(struct wlr_frame_scheduler *scheduler, int64_t delay_ns) { + if (delay_ns <= 0) { + handle_timer(scheduler); + return; + } + + int delay_ms = delay_ns / 1000000; + wl_event_source_timer_update(scheduler->timer, delay_ms); +} + +static void handle_output_present(struct wl_listener *listener, void *data) { + struct wlr_frame_scheduler *scheduler = + wl_container_of(listener, scheduler, output_present); + const struct wlr_output_event_present *event = data; + + if (scheduler->queued.render_complete == NULL || + event->commit_seq != scheduler->queued.commit_seq) { + goto out; + } + + struct timespec render_complete; + if (!wlr_render_timestamp_get_time(scheduler->queued.render_complete, + &render_complete)) { + goto out; + } + + struct timespec cpu_duration; + timespec_sub(&cpu_duration, &scheduler->queued.frame_submitted, + &scheduler->queued.frame_emitted); + int64_t cpu_duration_ns = timespec_to_nsec(&cpu_duration); + if (cpu_duration_ns <= 0) { + wlr_log(WLR_ERROR, "CPU duration is negative (%f ms)", + (float)cpu_duration_ns / 1000 / 1000); + goto out; + } + + struct timespec gpu_duration; + timespec_sub(&gpu_duration, &render_complete, + &scheduler->queued.render_submitted); + int64_t gpu_duration_ns = timespec_to_nsec(&gpu_duration); + if (gpu_duration_ns <= 0) { + wlr_log(WLR_ERROR, "GPU duration is negative (%f ms)", + (float)gpu_duration_ns / 1000 / 1000); + goto out; + } + + wlr_log(WLR_INFO, "Render duration: CPU %f ns, GPU %f ms", + (float)cpu_duration_ns / 1000000, + (float)gpu_duration_ns / 1000000); + + scheduler->histogram[scheduler->histogram_cur] = (struct wlr_frame_scheduler_bucket){ + .cpu_duration_ns = cpu_duration_ns, + .gpu_duration_ns = gpu_duration_ns, + }; + scheduler->histogram_cur++; + scheduler->histogram_cur %= WLR_FRAME_SCHEDULER_HISTOGRAM_LEN; + +out: + wlr_render_timestamp_destroy(scheduler->queued.render_complete); + memset(&scheduler->queued, 0, sizeof(scheduler->queued)); + + if (event->when == NULL || event->refresh <= 0) { + schedule_frame(scheduler, 2 * 1000 * 1000); + return; + } + + size_t n_samples = 0; + int64_t max_cpu_duration_ns = 0, max_gpu_duration_ns = 0; + for (size_t i = 0; i < WLR_FRAME_SCHEDULER_HISTOGRAM_LEN; i++) { + const struct wlr_frame_scheduler_bucket bucket = scheduler->histogram[i]; + if (bucket.cpu_duration_ns <= 0 || bucket.gpu_duration_ns <= 0) { + continue; + } + n_samples++; + if (bucket.cpu_duration_ns > max_cpu_duration_ns) { + max_cpu_duration_ns = bucket.cpu_duration_ns; + } + if (bucket.gpu_duration_ns > max_gpu_duration_ns) { + max_gpu_duration_ns = bucket.gpu_duration_ns; + } + } + if (n_samples < WLR_FRAME_SCHEDULER_HISTOGRAM_LEN) { + schedule_frame(scheduler, 2 * 1000 * 1000); + return; + } + + int64_t max_duration_ns = max_cpu_duration_ns + max_gpu_duration_ns + 2000000; + struct timespec max_duration; + timespec_from_nsec(&max_duration, max_duration_ns); + + if (max_duration_ns > event->refresh) { + wlr_log(WLR_ERROR, "Max render duration (%f ms) exceeds " + "refresh period (%f ms)", (float)max_duration_ns / 1000 / 1000, + (float)event->refresh / 1000 / 1000); + schedule_frame(scheduler, 2 * 1000 * 1000); + return; + } + + wlr_log(WLR_INFO, "Max duration: %f ms", (float)max_duration_ns / 1000000); + + struct timespec predicted_refresh_duration; + timespec_from_nsec(&predicted_refresh_duration, event->refresh); + + struct timespec predicted_next_refresh; + timespec_add(&predicted_next_refresh, event->when, &predicted_refresh_duration); + + struct timespec next_frame; + timespec_sub(&next_frame, &predicted_next_refresh, &max_duration); + + struct timespec now; + if (clock_gettime(scheduler->presentation_clock, &now) != 0) { + wlr_log_errno(WLR_ERROR, "clock_gettime() failed"); + schedule_frame(scheduler, 2 * 1000 * 1000); + return; + } + + struct timespec delay; + timespec_sub(&delay, &next_frame, &now); + int64_t delay_ns = timespec_to_nsec(&delay); + schedule_frame(scheduler, delay_ns); +} + +bool wlr_frame_scheduler_init(struct wlr_frame_scheduler *scheduler, + struct wlr_output *output) { + memset(scheduler, 0, sizeof(*scheduler)); + + scheduler->output = output; + scheduler->presentation_clock = wlr_backend_get_presentation_clock(output->backend); + + struct wl_event_loop *loop = wl_display_get_event_loop(output->display); + scheduler->timer = wl_event_loop_add_timer(loop, handle_timer, scheduler); + if (!scheduler->timer) { + wlr_log(WLR_ERROR, "wl_event_loop_add_timer() failed"); + return false; + } + + wl_signal_init(&scheduler->events.frame); + + scheduler->output_present.notify = handle_output_present; + wl_signal_add(&output->events.present, &scheduler->output_present); + + schedule_frame(scheduler, 2 * 1000 * 1000); + + return true; +} + +void wlr_frame_scheduler_finish(struct wlr_frame_scheduler *scheduler) { + wl_list_remove(&scheduler->output_present.link); + wlr_render_timestamp_destroy(scheduler->queued.render_complete); + wl_event_source_remove(scheduler->timer); +} + +void wlr_frame_scheduler_mark_render_submitted( + struct wlr_frame_scheduler *scheduler, struct wlr_renderer *renderer) { + wlr_render_timestamp_destroy(scheduler->queued.render_complete); + scheduler->queued.render_complete = NULL; + + if (clock_gettime(CLOCK_MONOTONIC, &scheduler->queued.frame_submitted) != 0) { + wlr_log_errno(WLR_ERROR, "clock_gettime() failed"); + return; + } + + if (!wlr_renderer_get_time(renderer, &scheduler->queued.render_submitted)) { + return; + } + + scheduler->queued.render_complete = wlr_renderer_create_timestamp(renderer); + scheduler->queued.commit_seq = scheduler->output->commit_seq; +} From 5424b2d44de8ec23f4c4da8ab6d2e314480adc21 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 4 Oct 2022 12:14:35 +0200 Subject: [PATCH 6/6] scene: use wlr_frame_scheduler --- include/wlr/types/wlr_scene.h | 2 ++ tinywl/tinywl.c | 9 ++++++--- types/scene/wlr_scene.c | 12 ++++++++---- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/include/wlr/types/wlr_scene.h b/include/wlr/types/wlr_scene.h index 614397f71..13e63def1 100644 --- a/include/wlr/types/wlr_scene.h +++ b/include/wlr/types/wlr_scene.h @@ -23,6 +23,7 @@ #include #include #include +#include struct wlr_output; struct wlr_output_layout; @@ -165,6 +166,7 @@ struct wlr_scene_output { struct wlr_addon addon; struct wlr_damage_ring damage_ring; + struct wlr_frame_scheduler frame_scheduler; int x, y; diff --git a/tinywl/tinywl.c b/tinywl/tinywl.c index 4fc8477c7..de6c2a9af 100644 --- a/tinywl/tinywl.c +++ b/tinywl/tinywl.c @@ -601,9 +601,6 @@ static void server_new_output(struct wl_listener *listener, void *data) { calloc(1, sizeof(struct tinywl_output)); output->wlr_output = wlr_output; output->server = server; - /* Sets up a listener for the frame notify event. */ - output->frame.notify = output_frame; - wl_signal_add(&wlr_output->events.frame, &output->frame); /* Sets up a listener for the destroy notify event. */ output->destroy.notify = output_destroy; @@ -621,6 +618,12 @@ static void server_new_output(struct wl_listener *listener, void *data) { * output (such as DPI, scale factor, manufacturer, etc). */ wlr_output_layout_add_auto(server->output_layout, wlr_output); + + struct wlr_scene_output *scene_output = + wlr_scene_get_scene_output(server->scene, wlr_output); + /* Sets up a listener for the frame notify event. */ + output->frame.notify = output_frame; + wl_signal_add(&scene_output->frame_scheduler.events.frame, &output->frame); } static void xdg_toplevel_map(struct wl_listener *listener, void *data) { diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index c5c301a72..ac9430128 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -185,7 +185,7 @@ static void scene_node_get_size(struct wlr_scene_node *node, int *lx, int *ly); typedef bool (*scene_node_box_iterator_func_t)(struct wlr_scene_node *node, int sx, int sy, void *data); -static bool _scene_nodes_in_box(struct wlr_scene_node *node, struct wlr_box *box, +static bool _scene_nodes_in_box(struct wlr_scene_node *node, struct wlr_box *box, scene_node_box_iterator_func_t iterator, void *user_data, int lx, int ly) { if (!node->enabled) { return false; @@ -216,7 +216,7 @@ static bool _scene_nodes_in_box(struct wlr_scene_node *node, struct wlr_box *box return false; } -static bool scene_nodes_in_box(struct wlr_scene_node *node, struct wlr_box *box, +static bool scene_nodes_in_box(struct wlr_scene_node *node, struct wlr_box *box, scene_node_box_iterator_func_t iterator, void *user_data) { int x, y; wlr_scene_node_coords(node, &x, &y); @@ -341,7 +341,7 @@ static void update_node_update_outputs(struct wlr_scene_node *node, active_outputs |= 1ull << scene_output->index; } - + pixman_region32_fini(&intersection); } @@ -556,7 +556,7 @@ void wlr_scene_buffer_set_buffer_with_damage(struct wlr_scene_buffer *scene_buff struct wlr_buffer *buffer, pixman_region32_t *damage) { // specifying a region for a NULL buffer doesn't make sense. We need to know // about the buffer to scale the buffer local coordinates down to scene - // coordinates. + // coordinates. assert(buffer || !damage); if (buffer == scene_buffer->buffer) { @@ -1204,6 +1204,7 @@ struct wlr_scene_output *wlr_scene_output_create(struct wlr_scene *scene, wlr_addon_init(&scene_output->addon, &output->addons, scene, &output_addon_impl); wlr_damage_ring_init(&scene_output->damage_ring); + wlr_frame_scheduler_init(&scene_output->frame_scheduler, output); wl_list_init(&scene_output->damage_highlight_regions); int prev_output_index = -1; @@ -1265,6 +1266,7 @@ void wlr_scene_output_destroy(struct wlr_scene_output *scene_output) { wlr_addon_finish(&scene_output->addon); wlr_damage_ring_finish(&scene_output->damage_ring); + wlr_frame_scheduler_finish(&scene_output->frame_scheduler); wl_list_remove(&scene_output->link); wl_list_remove(&scene_output->output_commit.link); wl_list_remove(&scene_output->output_mode.link); @@ -1597,6 +1599,8 @@ bool wlr_scene_output_commit(struct wlr_scene_output *scene_output) { wlr_output_render_software_cursors(output, &damage); + wlr_frame_scheduler_mark_render_submitted(&scene_output->frame_scheduler, + renderer); wlr_renderer_end(renderer); pixman_region32_fini(&damage);