From mboxrd@z Thu Jan 1 00:00:00 1970 Delivery-date: Thu, 20 Jun 2024 14:46:56 +0200 Received: from metis.whiteo.stw.pengutronix.de ([2a0a:edc0:2:b01:1d::104]) by lore.white.stw.pengutronix.de with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1sKHBo-009Fuf-2L for lore@lore.pengutronix.de; Thu, 20 Jun 2024 14:46:56 +0200 Received: from localhost ([127.0.0.1] helo=metis.whiteo.stw.pengutronix.de) by metis.whiteo.stw.pengutronix.de with esmtp (Exim 4.92) (envelope-from ) id 1sKHBn-000755-G5; Thu, 20 Jun 2024 14:46:55 +0200 Received: from drehscheibe.grey.stw.pengutronix.de ([2a0a:edc0:0:c01:1d::a2]) by metis.whiteo.stw.pengutronix.de with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1sKHBl-00074h-Sk; Thu, 20 Jun 2024 14:46:53 +0200 Received: from [2a0a:edc0:0:900:1d::4e] (helo=lupine) by drehscheibe.grey.stw.pengutronix.de with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1sKHBl-003hmQ-D8; Thu, 20 Jun 2024 14:46:53 +0200 Received: from pza by lupine with local (Exim 4.96) (envelope-from ) id 1sKHBl-00098I-17; Thu, 20 Jun 2024 14:46:53 +0200 Message-ID: From: Philipp Zabel To: LI Qingwu , oss-tools@pengutronix.de, m.felsch@pengutronix.de Date: Thu, 20 Jun 2024 14:46:53 +0200 In-Reply-To: <20240619102227.2013556-5-Qing-wu.Li@leica-geosystems.com.cn> References: <20240619102227.2013556-1-Qing-wu.Li@leica-geosystems.com.cn> <20240619102227.2013556-5-Qing-wu.Li@leica-geosystems.com.cn> Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable User-Agent: Evolution 3.46.4-2 MIME-Version: 1.0 Subject: Re: [OSS-Tools] [PATCH platsch V5 5/5] Add spinner executable for boot animation and text show X-BeenThere: oss-tools@pengutronix.de X-Mailman-Version: 2.1.29 Precedence: list List-Id: Pengutronix Public Open-Source-Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: bsp-development.geo@leica-geosystems.com Sender: "OSS-Tools" X-SA-Exim-Connect-IP: 127.0.0.1 X-SA-Exim-Mail-From: oss-tools-bounces@pengutronix.de X-SA-Exim-Scanned: No (on metis.whiteo.stw.pengutronix.de); SAEximRunCond expanded to false On Mi, 2024-06-19 at 12:22 +0200, LI Qingwu wrote: > This commit introduces a new executable, spinner, > which supports two types of animations for boot sequences: > 1 static PNG and text support. > 2 rotates square PNG images per frame > 3 shows a sequence of square images from a strip of PNG images. I've tried this and the CPU load was a bit unexpected. I suggest clipping the background_surface->cr blits in on_draw_..._animation() and and the drawing_surface->disp_cr blit in the drawing loop to the minimum rectangle necessary, commented below. Also setting spinner =3D "" should skip the drawing loop somehow. There should be minimal CPU load after disabling the animation as documented. Are you stopping the spinner process at some point after the system has booted? That would be nice to suggest in the documentation, otherwise it keeps causing unnecessary CPU load. > Signed-off-by: LI Qingwu > --- > README.rst | 37 ++++++ > meson.build | 21 ++++ > meson_options.txt | 1 + > spinner.c | 299 ++++++++++++++++++++++++++++++++++++++++++++++ > spinner_conf.c | 67 +++++++++++ > spinner_conf.h | 33 +++++ > 6 files changed, 458 insertions(+) > create mode 100644 meson_options.txt > create mode 100644 spinner.c > create mode 100644 spinner_conf.c > create mode 100644 spinner_conf.h >=20 > diff --git a/README.rst b/README.rst > index f1c0812..a0f7f7e 100644 > --- a/README.rst > +++ b/README.rst > @@ -149,3 +149,40 @@ Compiling Instructions > =20 > meson setup build > meson compile -C build > + > + > +Spinner - Splash Screen with Animation > +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D > + > +The `spinner` executable is designed to provide boot animations. It supp= orts two types of animations: > + > +1. **static PNG and text support**: Show a static png image and text. > +1. **Square PNG Rotation Animation**: Rotates a square PNG image. > +2. **Sequence Move Rectangle Animation**: Displays a sequence of square = images from a strip of PNG images. > + > +spinner Configuration > +--------------------- > + > +The configuration for the `spinner` executable is read from a configurat= ion file, > +with a default path of `/usr/share/platsch/spinner.conf`. > +The directory of the configuration file can be set via the `platsch_dire= ctory` environment variable. > + > + > +spinner Configuration > +--------------------- > + > +Here is a sample of a configuration file (`spinner.conf`): > + > +.. code-block:: ini > + > + symbol=3D"/usr/share/platsch/Spinner.png" > + fps=3D20 > + text=3D"" > + text_x=3D350 > + text_y=3D400 > + text_font=3D"Sans" > + text_size=3D30 > + > +Set text to empty if you don't want to display any text. > +Set the symbol to empty if you don't want to display any animation. > +The background image is indicated by ``--directory`` and ``--basename``. > diff --git a/meson.build b/meson.build > index ccc7a66..936e7ca 100644 > --- a/meson.build > +++ b/meson.build > @@ -22,3 +22,24 @@ executable('platsch', > install: true, > install_dir : 'sbin' > ) > + > + > +if get_option('SPINNER') > + spinner_dep =3D [ > + dependency('cairo', version: '>=3D 1.0', static: true), I think it would be best to just drop the "static: true" for now. The spinner executable isn't linked statically anyway. > + dep_libdrm, > + ] > + > + spinner_src =3D [ > + 'spinner.c', > + 'spinner_conf.c' > + ] > + > + executable('spinner', > + spinner_src, > + dependencies: spinner_dep, > + link_with: libplatsch, > + install: true, > + install_dir : 'sbin' > + ) > +endif > diff --git a/meson_options.txt b/meson_options.txt > new file mode 100644 > index 0000000..d1149fe > --- /dev/null > +++ b/meson_options.txt > @@ -0,0 +1 @@ > +option('SPINNER', type: 'boolean', value: false, description: 'Enable sp= inner build') Please make this lowercase, as is common practice for Meson options. > diff --git a/spinner.c b/spinner.c > new file mode 100644 > index 0000000..dbf8a4b > --- /dev/null > +++ b/spinner.c > @@ -0,0 +1,299 @@ > + > +#include > +#include > +#include > + > +#include "libplatsch.h" > +#include "spinner_conf.h" > + > +typedef struct spinner { > + cairo_format_t fmt; > + cairo_surface_t *background_surface; > + cairo_surface_t *symbol_surface; > + cairo_surface_t *drawing_surface; > + cairo_t *cr_background; > + cairo_t *cr_drawing; > + cairo_t *disp_cr; > + int background_height; > + int background_width; > + int display_height; > + int display_width; > + int symbol_height; > + int symbol_width; > + struct modeset_dev *dev; > + struct spinner *next; > +} spinner_t; > + > +void on_draw_Sequence_animation(cairo_t *cr, spinner_t *data) > +{ > + static int current_frame; > + int num_frames =3D data->symbol_width / data->symbol_height; > + int frame_width =3D data->symbol_height; > + > + cairo_set_source_surface(cr, data->background_surface, 0, 0); > + cairo_paint(cr); This paint is likely wasteful and should be clipped to the rectangle that is overwritten by the animation frame. > + > + cairo_save(cr); > + > + cairo_translate(cr, data->display_width / 2, data->display_height / 2); > + > + cairo_set_source_surface(cr, data->symbol_surface, > + -frame_width / 2 - current_frame * frame_width, -frame_width / 2); > + > + cairo_rectangle(cr, -frame_width / 2, -frame_width / 2, frame_width, fr= ame_width); > + cairo_clip(cr); > + cairo_paint(cr); > + > + cairo_restore(cr); > + > + current_frame =3D (current_frame + 1) % num_frames; > +} > + > +void on_draw_rotation_animation(cairo_t *cr, spinner_t *data) > +{ > + static float angle =3D 0.0; > + > + cairo_set_source_surface(cr, data->background_surface, 0, 0); > + cairo_paint(cr); This paint likely is wasteful and should be clipped to the rectangle that was overwritten by the previous animation frame, or at least to a square the size of the diagonal of the symbol surface. > + cairo_save(cr); > + cairo_translate(cr, data->background_width / 2, data->background_height= / 2); > + cairo_rotate(cr, angle); > + cairo_translate(cr, -data->symbol_width / 2, -data->symbol_height / 2); > + cairo_set_source_surface(cr, data->symbol_surface, 0, 0); > + cairo_paint(cr); > + cairo_restore(cr); > + angle +=3D 0.1; > + if (angle > 2 * M_PI) > + angle =3D 0.0; > +} > + > +static uint32_t convert_to_cairo_format(uint32_t format) > +{ > + switch (format) { > + case DRM_FORMAT_RGB565: > + return CAIRO_FORMAT_RGB16_565; > + case DRM_FORMAT_XRGB8888: > + return CAIRO_FORMAT_ARGB32; > + } > + return CAIRO_FORMAT_INVALID; > +} > + > +static void cairo_draw_text(cairo_t *cr, Config config) > +{ > + if (strlen(config.text) > 0) { > + cairo_select_font_face(cr, config.text_font, CAIRO_FONT_SLANT_NORMAL, > + CAIRO_FONT_WEIGHT_NORMAL); > + cairo_set_font_size(cr, (double)config.text_size); > + cairo_move_to(cr, config.text_x, config.text_y); > + cairo_set_source_rgb(cr, 0, 0, 0); > + cairo_show_text(cr, config.text); > + } > +} > + > +static int cairo_create_display_surface(struct modeset_dev *dev, spinner= _t *spinner_node) > +{ > + cairo_surface_t *disp_surface =3D cairo_image_surface_create_for_data( > + dev->map, convert_to_cairo_format(dev->format->format), dev->width,= dev->height, > + dev->stride); > + if (cairo_surface_status(disp_surface)) { > + platsch_error("Failed to create cairo surface\n"); > + return EXIT_FAILURE; > + } > + spinner_node->display_width =3D cairo_image_surface_get_width(disp_surf= ace); > + spinner_node->display_height =3D cairo_image_surface_get_height(disp_su= rface); > + spinner_node->fmt =3D cairo_image_surface_get_format(disp_surface); > + spinner_node->disp_cr =3D cairo_create(disp_surface); > + return EXIT_SUCCESS; > +} > + > +// create a surface and paint background image > +static int cairo_create_background_surface(spinner_t *spinner_node, Conf= ig config, const char *dir, > + const char *base) > +{ > + char filename[128]; > + int ret; > + > + ret =3D snprintf(filename, sizeof(filename), "%s/%s.png", dir, base); > + if (ret >=3D sizeof(filename)) { > + platsch_error("Failed to fit filename into buffer\n"); > + return EXIT_FAILURE; > + } > + > + spinner_node->background_surface =3D cairo_image_surface_create( > + spinner_node->fmt, spinner_node->display_width, spinner_node->displ= ay_height); > + if (cairo_surface_status(spinner_node->background_surface)) { > + platsch_error("Failed to create background surface\n"); > + return EXIT_FAILURE; > + } > + > + cairo_surface_t *bk_img_surface =3D cairo_image_surface_create_from_png= (filename); > + > + if (cairo_surface_status(bk_img_surface)) { > + platsch_error("Failed to create cairo surface from %s\n", filename); > + return EXIT_FAILURE; > + } > + > + int image_width =3D cairo_image_surface_get_width(bk_img_surface); > + int image_height =3D cairo_image_surface_get_height(bk_img_surface); > + double scale_x =3D (double)spinner_node->display_width / image_width; > + double scale_y =3D (double)spinner_node->display_height / image_height; > + > + spinner_node->cr_background =3D cairo_create(spinner_node->background_s= urface); > + cairo_scale(spinner_node->cr_background, scale_x, scale_y); > + cairo_set_source_surface(spinner_node->cr_background, bk_img_surface, 0= , 0); > + > + spinner_node->background_width =3D > + cairo_image_surface_get_width(spinner_node->background_surface); > + spinner_node->background_height =3D > + cairo_image_surface_get_height(spinner_node->background_surface); > + > + cairo_paint(spinner_node->cr_background); > + cairo_draw_text(spinner_node->cr_background, config); > + return EXIT_SUCCESS; > +} > + > +static int cairo_create_symbol_surface(spinner_t *spinner_node, Config c= onfig) > +{ > + if (strlen(config.symbol) =3D=3D 0) > + return EXIT_FAILURE; > + > + spinner_node->symbol_surface =3D cairo_image_surface_create_from_png(co= nfig.symbol); > + if (cairo_surface_status(spinner_node->symbol_surface)) { > + platsch_error("Failed loading %s\n", config.symbol); > + spinner_node->symbol_surface =3D NULL; > + return EXIT_FAILURE; > + } > + spinner_node->symbol_width =3D cairo_image_surface_get_width(spinner_no= de->symbol_surface); > + spinner_node->symbol_height =3D cairo_image_surface_get_height(spinner_= node->symbol_surface); > + return EXIT_SUCCESS; > +} > + > +int main(int argc, char *argv[]) > +{ > + bool pid1 =3D getpid() =3D=3D 1; > + char **initsargv; > + char filename[128]; > + Config config =3D DEFAULT_CONFIG; > + const char *base =3D "splash"; > + const char *dir =3D "/usr/share/platsch"; > + const char *env; > + int ret; > + long elapsed_time; > + > + spinner_t *spinner_list =3D NULL, *spinner_node =3D NULL, *spinner_iter= =3D NULL; > + struct modeset_dev *iter; > + struct timeval start, end; > + > + env =3D getenv("platsch_directory"); > + if (env) > + dir =3D env; > + > + env =3D getenv("platsch_basename"); > + if (env) > + base =3D env; > + > + ret =3D snprintf(filename, sizeof(filename), "%s/spinner.conf", dir); > + if (ret >=3D sizeof(filename)) { > + platsch_error("Failed to fit filename\n"); > + return EXIT_FAILURE; > + } > + > + parseConfig(filename, &config); > + > + struct modeset_dev *modeset_list =3D platsch_init(); > + > + if (!modeset_list) { > + platsch_error("Failed to initialize modeset\n"); > + return EXIT_FAILURE; > + } > + > + for (iter =3D modeset_list; iter; iter =3D iter->next) { > + spinner_node =3D (spinner_t *)malloc(sizeof(spinner_t)); > + if (!spinner_node) { > + platsch_error("Failed to allocate memory for spinner_node\n"); > + return EXIT_FAILURE; > + } > + memset(spinner_node, 0, sizeof(*spinner_node)); > + > + if (cairo_create_display_surface(iter, spinner_node)) > + return EXIT_FAILURE; > + > + if (cairo_create_background_surface(spinner_node, config, dir, base)) > + return EXIT_FAILURE; > + > + cairo_create_symbol_surface(spinner_node, config); > + > + spinner_node->drawing_surface =3D cairo_image_surface_create( > + spinner_node->fmt, spinner_node->display_width, spinner_node->disp= lay_height); > + if (cairo_surface_status(spinner_node->drawing_surface)) { > + platsch_error("Failed to create drawing surface\n"); > + return EXIT_FAILURE; > + } > + // create cairo context for drawing surface to avoid frlickering > + spinner_node->cr_drawing =3D cairo_create(spinner_node->drawing_surfac= e); > + // draw static image with text as first frame > + cairo_set_source_surface(spinner_node->disp_cr, spinner_node->backgrou= nd_surface, 0, > + 0); > + cairo_paint(spinner_node->disp_cr); > + platsch_setup_display(iter); > + > + spinner_node->next =3D spinner_list; > + spinner_list =3D spinner_node; > + } > + > + platsch_drmDropMaster(); > + > + ret =3D fork(); > + if (ret < 0) > + platsch_error("failed to fork for init: %m\n"); > + else if (ret =3D=3D 0) > + /* > + * Always fork to make sure we got the required > + * resources for drawing the animation in the child. > + */ > + goto drawing; > + > + if (pid1) { > + initsargv =3D calloc(sizeof(argv[0]), argc + 1); > + > + if (!initsargv) { > + platsch_error("failed to allocate argv for init\n"); > + return EXIT_FAILURE; > + } > + memcpy(initsargv, argv, argc * sizeof(argv[0])); > + initsargv[0] =3D "/sbin/init"; > + initsargv[argc] =3D NULL; > + > + execv("/sbin/init", initsargv); > + > + platsch_error("failed to exec init: %m\n"); > + > + return EXIT_FAILURE; > + } > + return EXIT_SUCCESS; > + > +drawing: > + while (true) { > + gettimeofday(&start, NULL); > + for (spinner_iter =3D spinner_list; spinner_iter; spinner_iter =3D spi= nner_iter->next) { > + if (spinner_node->symbol_surface =3D=3D NULL) > + usleep(1000000 / config.fps); What is the purpose of this delay? > + if (spinner_node->symbol_width / spinner_node->symbol_height > 2) > + on_draw_Sequence_animation(spinner_iter->cr_drawing, spinner_iter); > + else > + on_draw_rotation_animation(spinner_iter->cr_drawing, spinner_iter); > + > + cairo_set_source_surface(spinner_iter->disp_cr, > + spinner_iter->drawing_surface, 0, 0); > + cairo_paint(spinner_iter->disp_cr); This paint is likely wasteful and should be clipped to the rectangle that was overwritten in on_draw_..._animation(). > + } > + gettimeofday(&end, NULL); > + elapsed_time =3D > + (end.tv_sec - start.tv_sec) * 1000000 + (end.tv_usec - start.tv_us= ec); > + > + long sleep_time =3D (1000000 / config.fps) - elapsed_time; > + > + if (sleep_time > 0) > + usleep(sleep_time); I'd use clock_gettime and clock_nanosleep with CLOCK_MONOTONIC instead of gettimeofday and usleep. That should allow advancing the target time without having to measure the elapsed time. regards Philipp