mailarchive of the pengutronix oss-tools mailing list
 help / color / mirror / Atom feed
From: Philipp Zabel <p.zabel@pengutronix.de>
To: LI Qingwu <Qing-wu.Li@leica-geosystems.com.cn>,
	oss-tools@pengutronix.de,  m.felsch@pengutronix.de
Cc: 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: Thu, 20 Jun 2024 14:46:53 +0200	[thread overview]
Message-ID: <ca733f1ab5b28ac4814d4cb8f2c72b540d71421e.camel@pengutronix.de> (raw)
In-Reply-To: <20240619102227.2013556-5-Qing-wu.Li@leica-geosystems.com.cn>

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 = "" 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 <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-20 12:46 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 [this message]
2024-06-23 11:52     ` LI Qingwu
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=ca733f1ab5b28ac4814d4cb8f2c72b540d71421e.camel@pengutronix.de \
    --to=p.zabel@pengutronix.de \
    --cc=Qing-wu.Li@leica-geosystems.com.cn \
    --cc=bsp-development.geo@leica-geosystems.com \
    --cc=m.felsch@pengutronix.de \
    --cc=oss-tools@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