mailarchive of the pengutronix oss-tools mailing list
 help / color / mirror / Atom feed
From: LI Qingwu <Qing-wu.Li@leica-geosystems.com.cn>
To: Philipp Zabel <p.zabel@pengutronix.de>,
	"oss-tools@pengutronix.de" <oss-tools@pengutronix.de>,
	"m.felsch@pengutronix.de" <m.felsch@pengutronix.de>
Cc: GEO-CHHER-bsp-development <bsp-development.geo@leica-geosystems.com>
Subject: Re: [OSS-Tools] [PATCH platsch V5 5/5] Add spinner executable for boot animation and text show
Date: Sun, 23 Jun 2024 11:52:04 +0000	[thread overview]
Message-ID: <AS8PR06MB74320ABB99C3C243CD359F20D7CB2@AS8PR06MB7432.eurprd06.prod.outlook.com> (raw)
In-Reply-To: <ca733f1ab5b28ac4814d4cb8f2c72b540d71421e.camel@pengutronix.de>



> -----Original Message-----
> From: Philipp Zabel <p.zabel@pengutronix.de>
> Sent: Thursday, June 20, 2024 8:47 PM
> To: LI Qingwu <Qing-wu.Li@leica-geosystems.com.cn>;
> oss-tools@pengutronix.de; m.felsch@pengutronix.de
> Cc: GEO-CHHER-bsp-development
> <bsp-development.geo@leica-geosystems.com>
> Subject: Re: [OSS-Tools] [PATCH platsch V5 5/5] Add spinner executable for boot
> animation and text show
> 
> [你通常不会收到来自 p.zabel@pengutronix.de 的电子邮件。请访问
> https://aka.ms/LearnAboutSenderIdentification,以了解这一点为什么很重要]
> 
> This email is not from Hexagon’s Office 365 instance. Please be careful while
> clicking links, opening attachments, or replying to this email.
> 
> 
> 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.

I just create a drawing_surface same size as the animation symbol instead of display,
And 20fps with 800*600 display, CPU load of rotate animation goes from 16.6% to 4.3%
And sequence animation goes from 12.3% to 0.7%

> 
> Also setting spinner = "" 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.

Yes, I stop the spinner process after booted and I update the document in V6.

> 
> > Signed-off-by: LI Qingwu <Qing-wu.Li@leica-geosystems.com.cn>
> > ---
> >  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
> >
> > diff --git a/README.rst b/README.rst
> > index f1c0812..a0f7f7e 100644
> > --- a/README.rst
> > +++ b/README.rst
> > @@ -149,3 +149,40 @@ Compiling Instructions
> >
> >      meson setup build
> >      meson compile -C build
> > +
> > +
> > +Spinner - Splash Screen with Animation
> > +======================================
> > +
> > +The `spinner` executable is designed to provide boot animations. It supports
> 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
> > +configuration file, with a default path of `/usr/share/platsch/spinner.conf`.
> > +The directory of the configuration file can be set via the `platsch_directory`
> environment variable.
> > +
> > +
> > +spinner Configuration
> > +---------------------
> > +
> > +Here is a sample  of a configuration file (`spinner.conf`):
> > +
> > +.. code-block:: ini
> > +
> > +    symbol="/usr/share/platsch/Spinner.png"
> > +    fps=20
> > +    text=""
> > +    text_x=350
> > +    text_y=400
> > +    text_font="Sans"
> > +    text_size=30
> > +
> > +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 = [
> > +        dependency('cairo', version: '>= 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 = [
> > +        '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
> > +spinner 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 <cairo.h>
> > +#include <math.h>
> > +#include <sys/time.h>
> > +
> > +#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 = data->symbol_width / data->symbol_height;
> > +     int frame_width = 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,
> frame_width);
> > +     cairo_clip(cr);
> > +     cairo_paint(cr);
> > +
> > +     cairo_restore(cr);
> > +
> > +     current_frame = (current_frame + 1) % num_frames; }
> > +
> > +void on_draw_rotation_animation(cairo_t *cr, spinner_t *data) {
> > +     static float angle = 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 += 0.1;
> > +     if (angle > 2 * M_PI)
> > +             angle = 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 =
> 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 =
> cairo_image_surface_get_width(disp_surface);
> > +     spinner_node->display_height =
> cairo_image_surface_get_height(disp_surface);
> > +     spinner_node->fmt = cairo_image_surface_get_format(disp_surface);
> > +     spinner_node->disp_cr = cairo_create(disp_surface);
> > +     return EXIT_SUCCESS;
> > +}
> > +
> > +// create a surface and paint background image static int
> > +cairo_create_background_surface(spinner_t *spinner_node, Config config,
> const char *dir,
> > +                                        const char *base) {
> > +     char filename[128];
> > +     int ret;
> > +
> > +     ret = snprintf(filename, sizeof(filename), "%s/%s.png", dir, base);
> > +     if (ret >= sizeof(filename)) {
> > +             platsch_error("Failed to fit filename into buffer\n");
> > +             return EXIT_FAILURE;
> > +     }
> > +
> > +     spinner_node->background_surface = cairo_image_surface_create(
> > +         spinner_node->fmt, spinner_node->display_width,
> spinner_node->display_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 =
> > + 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 = cairo_image_surface_get_width(bk_img_surface);
> > +     int image_height = cairo_image_surface_get_height(bk_img_surface);
> > +     double scale_x = (double)spinner_node->display_width / image_width;
> > +     double scale_y = (double)spinner_node->display_height /
> > + image_height;
> > +
> > +     spinner_node->cr_background =
> cairo_create(spinner_node->background_surface);
> > +     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 =
> > +
> cairo_image_surface_get_width(spinner_node->background_surface);
> > +     spinner_node->background_height =
> > +
> > + 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 config) {
> > +     if (strlen(config.symbol) == 0)
> > +             return EXIT_FAILURE;
> > +
> > +     spinner_node->symbol_surface =
> cairo_image_surface_create_from_png(config.symbol);
> > +     if (cairo_surface_status(spinner_node->symbol_surface)) {
> > +             platsch_error("Failed loading %s\n", config.symbol);
> > +             spinner_node->symbol_surface = NULL;
> > +             return EXIT_FAILURE;
> > +     }
> > +     spinner_node->symbol_width =
> cairo_image_surface_get_width(spinner_node->symbol_surface);
> > +     spinner_node->symbol_height =
> cairo_image_surface_get_height(spinner_node->symbol_surface);
> > +     return EXIT_SUCCESS;
> > +}
> > +
> > +int main(int argc, char *argv[])
> > +{
> > +     bool pid1 = getpid() == 1;
> > +     char **initsargv;
> > +     char filename[128];
> > +     Config config = DEFAULT_CONFIG;
> > +     const char *base = "splash";
> > +     const char *dir = "/usr/share/platsch";
> > +     const char *env;
> > +     int ret;
> > +     long elapsed_time;
> > +
> > +     spinner_t *spinner_list = NULL, *spinner_node = NULL, *spinner_iter =
> NULL;
> > +     struct modeset_dev *iter;
> > +     struct timeval start, end;
> > +
> > +     env = getenv("platsch_directory");
> > +     if (env)
> > +             dir = env;
> > +
> > +     env = getenv("platsch_basename");
> > +     if (env)
> > +             base = env;
> > +
> > +     ret = snprintf(filename, sizeof(filename), "%s/spinner.conf", dir);
> > +     if (ret >= sizeof(filename)) {
> > +             platsch_error("Failed to fit filename\n");
> > +             return EXIT_FAILURE;
> > +     }
> > +
> > +     parseConfig(filename, &config);
> > +
> > +     struct modeset_dev *modeset_list = platsch_init();
> > +
> > +     if (!modeset_list) {
> > +             platsch_error("Failed to initialize modeset\n");
> > +             return EXIT_FAILURE;
> > +     }
> > +
> > +     for (iter = modeset_list; iter; iter = iter->next) {
> > +             spinner_node = (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 =
> cairo_image_surface_create(
> > +                 spinner_node->fmt, spinner_node->display_width,
> spinner_node->display_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 =
> cairo_create(spinner_node->drawing_surface);
> > +             // draw static image with text as first frame
> > +             cairo_set_source_surface(spinner_node->disp_cr,
> spinner_node->background_surface, 0,
> > +                                      0);
> > +             cairo_paint(spinner_node->disp_cr);
> > +             platsch_setup_display(iter);
> > +
> > +             spinner_node->next = spinner_list;
> > +             spinner_list = spinner_node;
> > +     }
> > +
> > +     platsch_drmDropMaster();
> > +
> > +     ret = fork();
> > +     if (ret < 0)
> > +             platsch_error("failed to fork for init: %m\n");
> > +     else if (ret == 0)
> > +             /*
> > +              * Always fork to make sure we got the required
> > +              * resources for drawing the animation in the child.
> > +              */
> > +             goto drawing;
> > +
> > +     if (pid1) {
> > +             initsargv = 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] = "/sbin/init";
> > +             initsargv[argc] = 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 = spinner_list; spinner_iter; spinner_iter =
> spinner_iter->next) {
> > +                     if (spinner_node->symbol_surface == 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 =
> > +                 (end.tv_sec - start.tv_sec) * 1000000 + (end.tv_usec
> > + - start.tv_usec);
> > +
> > +             long sleep_time = (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

  reply	other threads:[~2024-06-26  8:41 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-06-19 10:22 [OSS-Tools] [PATCH platsch V5 1/5] platsch: constify draw_buffer LI Qingwu
2024-06-19 10:22 ` [OSS-Tools] [PATCH platsch V5 2/5] convert to meson build LI Qingwu
2024-06-20 12:19   ` Philipp Zabel
2024-06-23 11:46     ` LI Qingwu
2024-06-19 10:22 ` [OSS-Tools] [PATCH platsch V5 3/5] platsch: split into platsch and libplatsch LI Qingwu
2024-06-20 12:55   ` Philipp Zabel
2024-06-19 10:22 ` [OSS-Tools] [PATCH platsch V5 4/5] Platsch: always fork child process LI Qingwu
2024-06-19 10:22 ` [OSS-Tools] [PATCH platsch V5 5/5] Add spinner executable for boot animation and text show LI Qingwu
2024-06-20 12:46   ` Philipp Zabel
2024-06-23 11:52     ` LI Qingwu [this message]
2024-06-24  7:55       ` Philipp Zabel

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=AS8PR06MB74320ABB99C3C243CD359F20D7CB2@AS8PR06MB7432.eurprd06.prod.outlook.com \
    --to=qing-wu.li@leica-geosystems.com.cn \
    --cc=bsp-development.geo@leica-geosystems.com \
    --cc=m.felsch@pengutronix.de \
    --cc=oss-tools@pengutronix.de \
    --cc=p.zabel@pengutronix.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox