diff --git a/include/libweston/libweston.h b/include/libweston/libweston.h index def0da875..ffc603ec9 100644 --- a/include/libweston/libweston.h +++ b/include/libweston/libweston.h @@ -567,6 +567,10 @@ struct weston_output { struct weston_coord_global pos; int32_t width, height; + uint64_t gpu_track_id; + uint64_t paint_track_id; + uint64_t presentation_track_id; + /** List of paint nodes in z-order, from top to bottom, maybe pruned * * struct weston_paint_node::z_order_link @@ -2072,6 +2076,8 @@ struct weston_surface { * this list. */ struct wl_list cm_feedback_surface_resource_list; struct wl_resource *cm_surface; + + uint64_t damage_track_id; }; struct weston_subsurface { diff --git a/libweston/compositor.c b/libweston/compositor.c index 7dd024f6d..0d97af908 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -8195,6 +8195,9 @@ weston_output_init(struct weston_output *output, * free to set the color profile to whatever they want later on. */ cm = compositor->color_manager; output->color_profile = cm->ref_stock_sRGB_color_profile(cm); + output->gpu_track_id = 0; + output->paint_track_id = 0; + output->presentation_track_id = 0; } /** Adds weston_output object to pending output list. diff --git a/libweston/meson.build b/libweston/meson.build index 8fcc6fc81..b99ff39a7 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -108,6 +108,7 @@ if get_option('perfetto') srcs_libweston += [ 'perfetto/u_perfetto.cc', 'perfetto/u_perfetto.h', + 'timeline-perfetto.c' ] dep_perfetto = dependency('perfetto', fallback: ['perfetto', 'dep_perfetto']) deps_libweston += dep_perfetto diff --git a/libweston/timeline-perfetto.c b/libweston/timeline-perfetto.c new file mode 100644 index 000000000..24007de62 --- /dev/null +++ b/libweston/timeline-perfetto.c @@ -0,0 +1,190 @@ +/* + * Copyright © 2025 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include +#include "shared/timespec-util.h" +#include "timeline.h" +#include "weston-trace.h" + +static void +weston_perfetto_ensure_output_ids(struct weston_output *output) +{ + char track_name[512]; + + if (output->gpu_track_id) + return; + + snprintf(track_name, sizeof(track_name), "%s GPU activity", output->name); + output->gpu_track_id = util_perfetto_new_track(track_name); + + snprintf(track_name, sizeof(track_name), "%s paint", output->name); + output->paint_track_id = util_perfetto_new_track(track_name); + + snprintf(track_name, sizeof(track_name), "%s present", output->name); + output->presentation_track_id = util_perfetto_new_track(track_name); +} + +static void +build_track_name(struct weston_surface *surface, char *name, int size) +{ + static int disambiguator = 0; + char surface_label[512]; + + /* Make sure we only call this once, so we don't accidentally + * make multiple names for the same surface */ + assert(surface->damage_track_id == 0); + + if (surface->get_label) + surface->get_label(surface, surface_label, sizeof(surface_label)); + else { + uint32_t res_id; + + res_id = wl_resource_get_id(surface->resource); + snprintf(surface_label, sizeof(surface_label), "surface %d", res_id); + } + + disambiguator++; + + snprintf(name, size, "%s #%d", surface_label, disambiguator); +} + +static void +weston_perfetto_ensure_surface_id(struct weston_surface *surface) +{ + char track_name[600]; + + if (surface->damage_track_id) + return; + + build_track_name(surface, track_name, sizeof(track_name)); + + surface->damage_track_id = util_perfetto_new_track(track_name); +} + +/** + * Translates a timeline point for perfetto. + * + * The TL_POINT() is a wrapper over this function, but it uses the weston_compositor + * instance to pass the timeline scope. + * + * @param timeline_scope the timeline scope + * @param tlp_name the name of the timeline point. + * + * @ingroup log + */ +WL_EXPORT void +weston_timeline_perfetto(struct weston_log_scope *timeline_scope, + enum timeline_point_name tlp_name, ...) +{ + struct weston_output *output = NULL; + struct weston_surface *surface = NULL; + struct timespec ts; + uint64_t now_ns; + uint64_t vblank_ns = 0, gpu_ns = 0; + va_list argp; + + if (!util_perfetto_is_tracing_enabled()) + return; + + clock_gettime(CLOCK_MONOTONIC, &ts); + now_ns = timespec_to_nsec(&ts); + + va_start(argp, tlp_name); + while (1) { + enum timeline_type otype; + void *obj; + + otype = va_arg(argp, enum timeline_type); + if (otype == TLT_END) + break; + + obj = va_arg(argp, void *); + switch (otype) { + case TLT_OUTPUT: + output = obj; + weston_perfetto_ensure_output_ids(output); + break; + case TLT_SURFACE: + surface = obj; + weston_perfetto_ensure_surface_id(surface); + break; + case TLT_VBLANK: + vblank_ns = timespec_to_nsec(obj); + break; + case TLT_GPU: + gpu_ns = timespec_to_nsec(obj); + break; + default: + assert(!"not reached"); + } + } + va_end(argp); + + switch (tlp_name) { + case TLP_CORE_REPAINT_ENTER_LOOP: + case TLP_CORE_REPAINT_RESTART: + case TLP_CORE_REPAINT_EXIT_LOOP: + break; + case TLP_CORE_FLUSH_DAMAGE: + WESTON_TRACE_TIMESTAMP_END("Damaged", surface->damage_track_id, CLOCK_MONOTONIC, now_ns); + WESTON_TRACE_TIMESTAMP_BEGIN("Clean", surface->damage_track_id, 0, CLOCK_MONOTONIC, now_ns); + break; + case TLP_CORE_REPAINT_BEGIN: + WESTON_TRACE_TIMESTAMP_END("Scheduled", output->paint_track_id, CLOCK_MONOTONIC, now_ns); + WESTON_TRACE_TIMESTAMP_BEGIN("Paint", output->paint_track_id, 0, CLOCK_MONOTONIC, now_ns); + break; + case TLP_CORE_REPAINT_POSTED: + WESTON_TRACE_TIMESTAMP_END("Paint", output->paint_track_id, CLOCK_MONOTONIC, now_ns); + WESTON_TRACE_TIMESTAMP_BEGIN("Posted", output->presentation_track_id, 0, CLOCK_MONOTONIC, now_ns); + break; + case TLP_CORE_REPAINT_FINISHED: + WESTON_TRACE_TIMESTAMP_END("Posted", output->presentation_track_id, CLOCK_MONOTONIC, vblank_ns); + break; + case TLP_CORE_REPAINT_REQ: + WESTON_TRACE_TIMESTAMP_BEGIN("Scheduled", output->paint_track_id, 0, CLOCK_MONOTONIC, now_ns); + break; + case TLP_CORE_COMMIT_DAMAGE: + WESTON_TRACE_TIMESTAMP_END("Clean", surface->damage_track_id, CLOCK_MONOTONIC, now_ns); + WESTON_TRACE_TIMESTAMP_END("Damaged", surface->damage_track_id, CLOCK_MONOTONIC, now_ns); + WESTON_TRACE_TIMESTAMP_BEGIN("Damaged", surface->damage_track_id, 0, CLOCK_MONOTONIC, now_ns); + break; + case TLP_RENDERER_GPU_BEGIN: + WESTON_TRACE_TIMESTAMP_BEGIN("Active", output->gpu_track_id, 0, CLOCK_MONOTONIC, gpu_ns); + break; + case TLP_RENDERER_GPU_END: + WESTON_TRACE_TIMESTAMP_END("Active", output->gpu_track_id, CLOCK_MONOTONIC, gpu_ns); + break; + default: + assert(!"not reached"); + } +} diff --git a/libweston/timeline.h b/libweston/timeline.h index ddcbcc6c8..95b65b52c 100644 --- a/libweston/timeline.h +++ b/libweston/timeline.h @@ -27,6 +27,8 @@ #ifndef WESTON_TIMELINE_H #define WESTON_TIMELINE_H +#include "config.h" + #include #include @@ -99,18 +101,32 @@ struct weston_timeline_subscription_object { * * Use TLP_END when done for the vargs. * + * Timeline points are fed to the timeline log scope, and + * to perfetto if it was enabled at build time. + * * @param ec weston_compositor instance * * @ingroup log */ +#ifdef HAVE_PERFETTO +#define TL_POINT(ec, ...) do { \ + weston_timeline_perfetto(ec->timeline, __VA_ARGS__); \ + weston_timeline_point(ec->timeline, __VA_ARGS__); \ +} while (0) +#else #define TL_POINT(ec, ...) do { \ weston_timeline_point(ec->timeline, __VA_ARGS__); \ } while (0) +#endif /* HAVE_PERFETTO */ void weston_timeline_point(struct weston_log_scope *timeline_scope, enum timeline_point_name tlp_name, ...); +void +weston_timeline_perfetto(struct weston_log_scope *timeline_scope, + enum timeline_point_name tlp_name, ...); + bool weston_timeline_profiling(struct weston_log_scope *timeline_scope);