mailarchive of the pengutronix oss-tools mailing list
 help / color / mirror / Atom feed
* [OSS-Tools] [PATCH platsch V5 1/5] platsch: constify draw_buffer
@ 2024-06-19 10:22 LI Qingwu
  2024-06-19 10:22 ` [OSS-Tools] [PATCH platsch V5 2/5] convert to meson build LI Qingwu
                   ` (3 more replies)
  0 siblings, 4 replies; 11+ messages in thread
From: LI Qingwu @ 2024-06-19 10:22 UTC (permalink / raw)
  To: Qing-wu.Li, oss-tools, m.felsch; +Cc: bsp-development.geo

From: Marco Felsch <m.felsch@pengutronix.de>

Make the dir and base const.

Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
 platsch.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/platsch.c b/platsch.c
index 535b589..1aaa8d5 100644
--- a/platsch.c
+++ b/platsch.c
@@ -111,7 +111,7 @@ struct modeset_dev {
 	uint32_t crtc_id;
 };
 
-void draw_buffer(struct modeset_dev *dev, char *dir, char *base)
+void draw_buffer(struct modeset_dev *dev, const char *dir, const char *base)
 {
 	int fd_src;
 	char filename[128];
-- 
2.34.1




^ permalink raw reply	[flat|nested] 11+ messages in thread

* [OSS-Tools] [PATCH platsch V5 2/5] convert to meson build
  2024-06-19 10:22 [OSS-Tools] [PATCH platsch V5 1/5] platsch: constify draw_buffer LI Qingwu
@ 2024-06-19 10:22 ` LI Qingwu
  2024-06-20 12:19   ` Philipp Zabel
  2024-06-19 10:22 ` [OSS-Tools] [PATCH platsch V5 3/5] platsch: split into platsch and libplatsch LI Qingwu
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 11+ messages in thread
From: LI Qingwu @ 2024-06-19 10:22 UTC (permalink / raw)
  To: Qing-wu.Li, oss-tools, m.felsch; +Cc: bsp-development.geo

Convert to meson build and update the README.rst
version update to 2024.06.0
cleanup .gitignore

Signed-off-by: LI Qingwu <Qing-wu.Li@leica-geosystems.com.cn>
---
 .gitignore   | 14 --------------
 Makefile.am  | 23 -----------------------
 README.rst   |  8 ++++++++
 configure.ac | 13 -------------
 meson.build  | 14 ++++++++++++++
 5 files changed, 22 insertions(+), 50 deletions(-)
 delete mode 100644 Makefile.am
 delete mode 100644 configure.ac
 create mode 100644 meson.build

diff --git a/.gitignore b/.gitignore
index bef7e68..e69de29 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,14 +0,0 @@
-/*.o
-/aclocal.m4
-/autom4te.cache
-/compile
-/config.log
-/config.status
-/configure
-/depcomp
-/.deps
-/install-sh
-/Makefile
-/Makefile.in
-/missing
-/platsch
diff --git a/Makefile.am b/Makefile.am
deleted file mode 100644
index d149ae0..0000000
--- a/Makefile.am
+++ /dev/null
@@ -1,23 +0,0 @@
-EXTRA_DIST = README.rst LICENSE
-
-sbin_PROGRAMS = platsch
-
-platsch_SOURCES = platsch.c
-platsch_CFLAGS = $(LIBDRM_CFLAGS)
-platsch_LDADD = $(LIBDRM_LIBS)
-
-CLEANFILES = \
-        $(DIST_ARCHIVES)
-
-DISTCLEAN = \
-        config.log \
-        config.status \
-        Makefile
-
-MAINTAINERCLEANFILES = \
-	aclocal.m4 \
-	configure \
-	depcomp \
-	install-sh \
-	Makefile.in \
-	missing
diff --git a/README.rst b/README.rst
index e318120..f1c0812 100644
--- a/README.rst
+++ b/README.rst
@@ -141,3 +141,11 @@ By adding a Signed-off-by line (e.g. using ``git commit -s``) saying::
 
 (using your real name and e-mail address), you state that your contributions
 are in line with the DCO.
+
+Compiling Instructions
+----------------------------
+
+.. code-block:: shell
+
+    meson setup build
+    meson compile -C build
diff --git a/configure.ac b/configure.ac
deleted file mode 100644
index 18878db..0000000
--- a/configure.ac
+++ /dev/null
@@ -1,13 +0,0 @@
-AC_PREREQ([2.69])
-AC_INIT([platsch], [2019.12.0], [oss-tools@pengutronix.de])
-AC_CONFIG_SRCDIR([platsch.c])
-AM_INIT_AUTOMAKE([foreign dist-xz])
-
-AC_PROG_CC
-AC_PROG_MAKE_SET
-
-PKG_CHECK_MODULES([LIBDRM], [libdrm >= 2.4.112])
-
-AC_CONFIG_FILES([Makefile])
-
-AC_OUTPUT
diff --git a/meson.build b/meson.build
new file mode 100644
index 0000000..b732a06
--- /dev/null
+++ b/meson.build
@@ -0,0 +1,14 @@
+project('platsch', 'c', version: '2024.06.0', license : '0BSD')
+
+dep_libdrm = dependency('libdrm',
+    version: '>= 2.4.112',
+    static: true
+)
+
+executable('platsch',
+    'platsch.c',
+    dependencies: dep_libdrm,
+    link_args: '-static',
+    install: true,
+    install_dir : 'sbin'
+)
-- 
2.34.1




^ permalink raw reply	[flat|nested] 11+ messages in thread

* [OSS-Tools] [PATCH platsch V5 3/5] platsch: split into platsch and libplatsch
  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-19 10:22 ` 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
  3 siblings, 1 reply; 11+ messages in thread
From: LI Qingwu @ 2024-06-19 10:22 UTC (permalink / raw)
  To: Qing-wu.Li, oss-tools, m.felsch; +Cc: bsp-development.geo

Signed-off-by: LI Qingwu <Qing-wu.Li@leica-geosystems.com.cn>
---
 libplatsch.c | 563 +++++++++++++++++++++++++++++++++++++++++++++++++
 libplatsch.h |  47 +++++
 meson.build  |  12 +-
 platsch.c    | 582 ++-------------------------------------------------
 4 files changed, 639 insertions(+), 565 deletions(-)
 create mode 100644 libplatsch.c
 create mode 100644 libplatsch.h

diff --git a/libplatsch.c b/libplatsch.c
new file mode 100644
index 0000000..2e0dc41
--- /dev/null
+++ b/libplatsch.c
@@ -0,0 +1,563 @@
+/*
+ * Copyright (C) 2019 Pengutronix, Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
+ *
+ * Permission to use, copy, modify, and/or distribute this software
+ * for any purpose with or without fee is hereby granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
+ * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Some code parts base on example code written in 2012 by David Herrmann
+ * <dh.herrmann@googlemail.com> and dedicated to the Public Domain. It was found
+ * in 2019 on
+ * https://raw.githubusercontent.com/dvdhrm/docs/master/drm-howto/modeset.c
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <getopt.h>
+#include <libgen.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+
+#include "libplatsch.h"
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof(*a))
+
+ssize_t readfull(int fd, void *buf, size_t count)
+{
+	ssize_t ret = 0, err;
+
+	while (count > 0) {
+		err = read(fd, buf, count);
+		if (err < 0)
+			return err;
+		else if (err > 0) {
+			buf += err;
+			count -= err;
+			ret += err;
+		} else {
+			return ret;
+		}
+	}
+
+	return ret;
+}
+
+void draw_buffer(struct modeset_dev *dev, const char *dir, const char *base)
+{
+	int fd_src;
+	char filename[128];
+	ssize_t size;
+	int ret;
+
+	/*
+	 * make it easy and load a raw file in the right format instead of
+	 * opening an (say) PNG and convert the image data to the right format.
+	 */
+	ret = snprintf(filename, sizeof(filename), "%s/%s-%ux%u-%s.bin", dir, base, dev->width,
+		       dev->height, dev->format->name);
+	if (ret >= sizeof(filename)) {
+		platsch_error("Failed to fit filename into buffer\n");
+		return;
+	}
+
+	fd_src = open(filename, O_RDONLY | O_CLOEXEC);
+	if (fd_src < 0) {
+		platsch_error("Failed to open %s: %m\n", filename);
+		return;
+	}
+
+	size = readfull(fd_src, dev->map, dev->size);
+	if (size < dev->size) {
+		if (size < 0)
+			platsch_error("Failed to read from %s: %m\n", filename);
+		else
+			platsch_error("Could only read %zd/%u bytes from %s\n", size, dev->size, filename);
+	}
+
+	ret = close(fd_src);
+	if (ret < 0) {
+		/* Nothing we can do about this, so just warn */
+		platsch_error("Failed to close image file\n");
+	}
+
+	return;
+}
+
+static struct modeset_dev *modeset_list = NULL;
+
+static int drmprepare_crtc(int fd, drmModeRes *res, drmModeConnector *conn, struct modeset_dev *dev)
+{
+	drmModeEncoder *enc;
+	unsigned int i, j;
+	int32_t crtc_id;
+	struct modeset_dev *iter;
+
+	/* first try the currently connected encoder+crtc */
+	if (conn->encoder_id) {
+		platsch_debug("connector #%d uses encoder #%d\n", conn->connector_id, conn->encoder_id);
+		enc = drmModeGetEncoder(fd, conn->encoder_id);
+		assert(enc);
+		assert(enc->encoder_id == conn->encoder_id);
+	} else {
+		platsch_debug("connector #%d has no active encoder\n", conn->connector_id);
+		enc = NULL;
+		dev->setmode = 1;
+	}
+
+	if (enc) {
+		if (enc->crtc_id) {
+			crtc_id = enc->crtc_id;
+			assert(crtc_id >= 0);
+
+			for (iter = modeset_list; iter; iter = iter->next) {
+				if (iter->crtc_id == crtc_id) {
+					crtc_id = -1;
+					break;
+				}
+			}
+
+			if (crtc_id > 0) {
+				platsch_debug("encoder #%d uses crtc #%d\n", enc->encoder_id, enc->crtc_id);
+				drmModeFreeEncoder(enc);
+				dev->crtc_id = crtc_id;
+				return 0;
+			} else {
+				platsch_debug("encoder #%d used crtc #%d, but that's in use\n",
+				      enc->encoder_id, iter->crtc_id);
+			}
+		} else {
+			platsch_debug("encoder #%d doesn't have an active crtc\n", enc->encoder_id);
+		}
+
+		drmModeFreeEncoder(enc);
+	}
+
+	/* If the connector is not currently bound to an encoder or if the
+	 * encoder+crtc is already used by another connector (actually unlikely
+	 * but let's be safe), iterate all other available encoders to find a
+	 * matching CRTC. */
+	for (i = 0; i < conn->count_encoders; ++i) {
+		enc = drmModeGetEncoder(fd, conn->encoders[i]);
+		if (!enc) {
+			platsch_error("Cannot retrieve encoder %u: %m\n", conn->encoders[i]);
+			continue;
+		}
+		assert(enc->encoder_id == conn->encoders[i]);
+
+		/* iterate all global CRTCs */
+		for (j = 0; j < res->count_crtcs; ++j) {
+			/* check whether this CRTC works with the encoder */
+			if (!(enc->possible_crtcs & (1 << j)))
+				continue;
+
+			/* check that no other device already uses this CRTC */
+			crtc_id = res->crtcs[j];
+			for (iter = modeset_list; iter; iter = iter->next) {
+				if (iter->crtc_id == crtc_id) {
+					crtc_id = -1;
+					break;
+				}
+			}
+
+			/* we have found a CRTC, so save it and return */
+			if (crtc_id >= 0) {
+				platsch_debug("encoder #%d will use crtc #%d\n", enc->encoder_id, crtc_id);
+				drmModeFreeEncoder(enc);
+				dev->crtc_id = crtc_id;
+				return 0;
+			}
+		}
+		drmModeFreeEncoder(enc);
+	}
+
+	platsch_error("Cannot find suitable CRTC for connector #%u\n", conn->connector_id);
+	return -ENOENT;
+}
+
+static int modeset_create_fb(int fd, struct modeset_dev *dev)
+{
+	struct drm_mode_create_dumb creq;
+	struct drm_mode_destroy_dumb dreq;
+	struct drm_mode_map_dumb mreq;
+	int ret;
+
+	/* create dumb buffer */
+	memset(&creq, 0, sizeof(creq));
+	creq.width = dev->width;
+	creq.height = dev->height;
+	creq.bpp = dev->format->bpp;
+	ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq);
+	if (ret < 0) {
+		platsch_error("Cannot create dumb buffer: %m\n");
+		return -errno;
+	}
+	dev->stride = creq.pitch;
+	dev->size = creq.size;
+	dev->handle = creq.handle;
+
+	/* create framebuffer object for the dumb-buffer */
+	ret = drmModeAddFB2(fd, dev->width, dev->height,
+			    dev->format->format,
+			    (uint32_t[4]){ dev->handle, },
+			    (uint32_t[4]){ dev->stride, },
+			    (uint32_t[4]){ 0, },
+			    &dev->fb_id, 0);
+	if (ret) {
+		ret = -errno;
+		platsch_error("Cannot create framebuffer: %m\n");
+		goto err_destroy;
+	}
+
+	/* prepare buffer for memory mapping */
+	memset(&mreq, 0, sizeof(mreq));
+	mreq.handle = dev->handle;
+	ret = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq);
+	if (ret) {
+		ret = -errno;
+		platsch_error("Cannot get mmap offset: %m\n");
+		goto err_fb;
+	}
+
+	/* perform actual memory mapping */
+	dev->map = mmap(0, dev->size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, mreq.offset);
+	if (dev->map == MAP_FAILED) {
+		ret = -errno;
+		platsch_error("Cannot mmap dumb buffer: %m\n");
+		goto err_fb;
+	}
+
+	/*
+	 * Clear the framebuffer. Normally it's overwritten later with some
+	 * image data, but in case this fails, initialize to all-black.
+	 */
+	memset(dev->map, 0x0, dev->size);
+
+	return 0;
+
+err_fb:
+	drmModeRmFB(fd, dev->fb_id);
+err_destroy:
+	memset(&dreq, 0, sizeof(dreq));
+	dreq.handle = dev->handle;
+	drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
+	return ret;
+}
+
+/* Returns lowercase connector type names with '_' for '-' */
+static char *get_normalized_conn_type_name(uint32_t connector_type)
+{
+	int i;
+	const char *connector_name;
+	char *normalized_name;
+
+	connector_name = drmModeGetConnectorTypeName(connector_type);
+	if (!connector_name)
+		return NULL;
+
+	normalized_name = strdup(connector_name);
+
+	for (i = 0; normalized_name[i]; i++) {
+		normalized_name[i] = tolower(normalized_name[i]);
+		if (normalized_name[i] == '-')
+			normalized_name[i] = '_';
+	}
+
+	return normalized_name;
+}
+
+static const struct platsch_format *platsch_format_find(const char *name)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(platsch_formats); i++)
+		if (!strcmp(platsch_formats[i].name, name))
+			return &platsch_formats[i];
+
+	return NULL;
+}
+
+static int set_env_connector_mode(drmModeConnector *conn, struct modeset_dev *dev)
+{
+	int ret, i = 0;
+	u_int32_t width = 0, height = 0;
+	const char *mode;
+	char *connector_type_name, mode_env_name[32], fmt_specifier[32] = "";
+	const struct platsch_format *format = NULL;
+
+	connector_type_name = get_normalized_conn_type_name(conn->connector_type);
+	if (!connector_type_name) {
+		platsch_error("could not look up name for connector type %u\n", conn->connector_type);
+		goto fallback;
+	}
+
+	ret = snprintf(mode_env_name, sizeof(mode_env_name), "platsch_%s%u_mode",
+		       connector_type_name, conn->connector_type_id);
+	free(connector_type_name);
+	if (ret >= sizeof(mode_env_name)) {
+		platsch_error("failed to fit platsch env mode variable name into buffer\n");
+		return -EFAULT;
+	}
+
+	/* check for connector mode configuration in environment */
+	platsch_debug("looking up %s env variable\n", mode_env_name);
+	mode = getenv(mode_env_name);
+	if (!mode)
+		goto fallback;
+
+	/* format suffix is optional */
+	ret = sscanf(mode, "%ux%u@%s", &width, &height, fmt_specifier);
+	if (ret < 2) {
+		platsch_error("error while scanning %s for mode\n", mode_env_name);
+		return -EFAULT;
+	}
+
+	/* use first mode matching given resolution */
+	for (i = 0; i < conn->count_modes; i++) {
+		drmModeModeInfo mode = conn->modes[i];
+		if (mode.hdisplay == width && mode.vdisplay == height) {
+			memcpy(&dev->mode, &mode, sizeof(dev->mode));
+			dev->width = width;
+			dev->height = height;
+			break;
+		}
+	}
+
+	if (i == conn->count_modes) {
+		platsch_error("no mode available matching %ux%u\n", width, height);
+		return -ENOENT;
+	}
+
+	format = platsch_format_find(fmt_specifier);
+	if (!format) {
+		if (strlen(fmt_specifier))
+			platsch_error("unknown format specifier %s\n", fmt_specifier);
+		goto fallback_format;
+	}
+
+	dev->format = format;
+
+	return 0;
+
+fallback:
+	memcpy(&dev->mode, &conn->modes[0], sizeof(dev->mode));
+	dev->width = conn->modes[0].hdisplay;
+	dev->height = conn->modes[0].vdisplay;
+	platsch_debug("using default mode for connector #%u\n", conn->connector_id);
+
+fallback_format:
+	dev->format = &platsch_formats[0];
+	platsch_debug("using default format %s for connector #%u\n", dev->format->name, conn->connector_id);
+
+	return 0;
+}
+
+static int drmprepare_connector(int fd, drmModeRes *res, drmModeConnector *conn,
+				struct modeset_dev *dev)
+{
+	int ret;
+
+	/* check if a monitor is connected */
+	if (conn->connection != DRM_MODE_CONNECTED) {
+		platsch_error("Ignoring unused connector #%u\n", conn->connector_id);
+		return -ENOENT;
+	}
+
+	/* check if there is at least one valid mode */
+	if (conn->count_modes == 0) {
+		platsch_error("no valid mode for connector #%u\n", conn->connector_id);
+		return -EFAULT;
+	}
+
+	/* configure mode information in our device structure */
+	ret = set_env_connector_mode(conn, dev);
+	if (ret) {
+		platsch_error("no valid mode for connector #%u\n", conn->connector_id);
+		return ret;
+	}
+	platsch_debug("mode for connector #%u is %ux%u@%s\n", conn->connector_id, dev->width, dev->height,
+	      dev->format->name);
+
+	/* find a crtc for this connector */
+	ret = drmprepare_crtc(fd, res, conn, dev);
+	if (ret) {
+		platsch_error("no valid crtc for connector #%u\n", conn->connector_id);
+		return ret;
+	}
+
+	/* create a framebuffer for this CRTC */
+	ret = modeset_create_fb(fd, dev);
+	if (ret) {
+		platsch_error("cannot create framebuffer for connector #%u\n", conn->connector_id);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int drmprepare(int fd)
+{
+	drmModeRes *res;
+	drmModeConnector *conn;
+	unsigned int i;
+	struct modeset_dev *dev;
+	int ret;
+
+	/* retrieve resources */
+	res = drmModeGetResources(fd);
+	if (!res) {
+		platsch_error("cannot retrieve DRM resources: %m\n");
+		return -errno;
+	}
+
+	platsch_debug("Found %d connectors\n", res->count_connectors);
+
+	/* iterate all connectors */
+	for (i = 0; i < res->count_connectors; ++i) {
+		/* get information for each connector */
+		conn = drmModeGetConnector(fd, res->connectors[i]);
+		if (!conn) {
+			platsch_error("Cannot retrieve DRM connector #%u: %m\n", res->connectors[i]);
+			continue;
+		}
+		assert(conn->connector_id == res->connectors[i]);
+
+		platsch_debug("Connector #%u has type %s\n", conn->connector_id,
+		      drmModeGetConnectorTypeName(conn->connector_type));
+
+		/* create a device structure */
+		dev = malloc(sizeof(*dev));
+		if (!dev) {
+			platsch_error("Cannot allocate memory for connector #%u: %m\n", res->connectors[i]);
+			continue;
+		}
+		memset(dev, 0, sizeof(*dev));
+		dev->conn_id = conn->connector_id;
+
+		ret = drmprepare_connector(fd, res, conn, dev);
+		if (ret) {
+			if (ret != -ENOENT) {
+				platsch_error("Cannot setup device for connector #%u: %m\n",
+				      res->connectors[i]);
+			}
+			free(dev);
+			drmModeFreeConnector(conn);
+			continue;
+		}
+
+		/* free connector data and link device into global list */
+		drmModeFreeConnector(conn);
+		dev->next = modeset_list;
+		modeset_list = dev;
+	}
+
+	/* free resources again */
+	drmModeFreeResources(res);
+	return 0;
+}
+
+static int drmfd;
+
+struct modeset_dev *platsch_init(void)
+{
+	char drmdev[128];
+	int ret = 0, i;
+
+	for (i = 0; i < 64; i++) {
+		struct drm_mode_card_res res = {0};
+
+		/*
+		 * XXX: Maybe use drmOpen instead?
+		 * (Where should name/busid come from?)
+		 * XXX: Loop through drm devices to find one with connectors.
+		 */
+		ret = snprintf(drmdev, sizeof(drmdev), DRM_DEV_NAME, DRM_DIR_NAME, i);
+		if (ret >= sizeof(drmdev)) {
+			platsch_error("Huh, device name overflowed buffer\n");
+			goto fail;
+		}
+
+		drmfd = open(drmdev, O_RDWR | O_CLOEXEC, 0);
+		if (drmfd < 0) {
+			platsch_error("Failed to open drm device: %m\n");
+			goto fail;
+		}
+
+		ret = drmIoctl(drmfd, DRM_IOCTL_MODE_GETRESOURCES, &res);
+		if (ret < 0) {
+			close(drmfd);
+			continue;
+		} else {
+			/* Device found */
+			break;
+		}
+	}
+
+	if (i == 64) {
+		platsch_error("No suitable DRM device found\n");
+		goto fail;
+	}
+
+	ret = drmprepare(drmfd);
+	if (ret) {
+		platsch_error("Failed to prepare DRM device\n");
+		goto fail;
+	}
+
+	return modeset_list;
+
+fail:
+	return NULL;
+}
+
+void platsch_setup_display(struct modeset_dev *dev)
+{
+	int ret = 0;
+
+	if (dev->setmode) {
+		ret = drmModeSetCrtc(drmfd, dev->crtc_id, dev->fb_id, 0, 0, &dev->conn_id, 1,
+				     &dev->mode);
+		if (ret) {
+			platsch_error("Cannot set CRTC for connector #%u: %m\n", dev->conn_id);
+		}
+		/* don't need to set the mode again */
+		dev->setmode = 0;
+	} else {
+		ret = drmModePageFlip(drmfd, dev->crtc_id, dev->fb_id, 0, NULL);
+		if (ret) {
+			platsch_error("Page flip failed on connector #%u: %m\n", dev->conn_id);
+		}
+	}
+}
+
+void platsch_draw(struct modeset_dev *dev, const char *dir, const char *base)
+{
+	draw_buffer(dev, dir, base);
+	platsch_setup_display(dev);
+}
+
+void platsch_drmDropMaster(void)
+{
+	int ret = 0;
+
+	ret = drmDropMaster(drmfd);
+	if (ret)
+		platsch_error("Failed to drop master on drm device\n");
+
+}
diff --git a/libplatsch.h b/libplatsch.h
new file mode 100644
index 0000000..f7d93d5
--- /dev/null
+++ b/libplatsch.h
@@ -0,0 +1,47 @@
+#ifndef __LIBPLATSCH_H__
+#define __LIBPLATSCH_H__
+
+#include <unistd.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+#include <drm_fourcc.h>
+
+
+#define platsch_debug(fmt, ...) printf("%s:%d: " fmt, __func__, __LINE__, ##__VA_ARGS__)
+#define platsch_error(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__)
+
+
+struct platsch_format {
+	uint32_t format;
+	uint32_t bpp;
+	const char *name;
+};
+
+struct modeset_dev {
+	struct modeset_dev *next;
+
+	uint32_t width;
+	uint32_t height;
+	uint32_t stride;
+	uint32_t size;
+	const struct platsch_format *format;
+	uint32_t handle;
+	void *map;
+	bool setmode;
+	drmModeModeInfo mode;
+	uint32_t fb_id;
+	uint32_t conn_id;
+	uint32_t crtc_id;
+};
+
+static const struct platsch_format platsch_formats[] = {
+	{ DRM_FORMAT_RGB565, 16, "RGB565" }, /* default */
+	{ DRM_FORMAT_XRGB8888, 32, "XRGB8888" },
+};
+
+struct modeset_dev *platsch_init(void);
+void platsch_draw(struct modeset_dev *dev, const char *dir, const char *base);
+void platsch_drmDropMaster(void);
+void platsch_setup_display(struct modeset_dev *dev);
+
+#endif
diff --git a/meson.build b/meson.build
index b732a06..ccc7a66 100644
--- a/meson.build
+++ b/meson.build
@@ -5,8 +5,18 @@ dep_libdrm = dependency('libdrm',
     static: true
 )
 
+libplasch_sources = ['libplatsch.c']
+
+libplatsch = static_library('platsch',
+    libplasch_sources,
+    dependencies: dep_libdrm
+)
+
+platsch_sources = ['platsch.c']
+
 executable('platsch',
-    'platsch.c',
+    platsch_sources,
+    link_with: libplatsch,
     dependencies: dep_libdrm,
     link_args: '-static',
     install: true,
diff --git a/platsch.c b/platsch.c
index 1aaa8d5..6503066 100644
--- a/platsch.c
+++ b/platsch.c
@@ -19,48 +19,22 @@
  * https://raw.githubusercontent.com/dvdhrm/docs/master/drm-howto/modeset.c
  */
 
-#include <assert.h>
-#include <ctype.h>
-#include <errno.h>
+#include <fcntl.h>
 #include <getopt.h>
 #include <libgen.h>
-#include <stdarg.h>
-#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/mman.h>
-#include <fcntl.h>
-
-#include <xf86drm.h>
-#include <xf86drmMode.h>
-#include <drm_fourcc.h>
-
-#define debug(fmt, ...) printf("%s:%d: " fmt, __func__, __LINE__, ##__VA_ARGS__)
-#define error(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__)
-
-#define ARRAY_SIZE(a) (sizeof(a) / sizeof(*a))
 
-struct platsch_format {
-	uint32_t format;
-	uint32_t bpp;
-	const char *name;
-};
-
-static const struct platsch_format platsch_formats[] = {
-	{ DRM_FORMAT_RGB565, 16, "RGB565" }, /* default */
-	{ DRM_FORMAT_XRGB8888, 32, "XRGB8888" },
-};
+#include "libplatsch.h"
 
-void redirect_stdfd(void)
+void platsch_redirect_stdfd(void)
 {
 	int devnull = open("/dev/null", O_RDWR, 0);
 
 	if (devnull < 0) {
-		error("Failed to open /dev/null: %m\n");
+		platsch_error("Failed to open /dev/null: %m\n");
 		return;
 	}
 
@@ -73,475 +47,6 @@ void redirect_stdfd(void)
 	close(devnull);
 }
 
-ssize_t readfull(int fd, void *buf, size_t count)
-{
-	ssize_t ret = 0, err;
-
-	while (count > 0) {
-		err = read(fd, buf, count);
-		if (err < 0)
-			return err;
-		else if (err > 0) {
-			buf += err;
-			count -= err;
-			ret += err;
-		} else {
-			return ret;
-		}
-	}
-
-	return ret;
-}
-
-struct modeset_dev {
-	struct modeset_dev *next;
-
-	uint32_t width;
-	uint32_t height;
-	uint32_t stride;
-	uint32_t size;
-	const struct platsch_format *format;
-	uint32_t handle;
-	void *map;
-
-	bool setmode;
-	drmModeModeInfo mode;
-	uint32_t fb_id;
-	uint32_t conn_id;
-	uint32_t crtc_id;
-};
-
-void draw_buffer(struct modeset_dev *dev, const char *dir, const char *base)
-{
-	int fd_src;
-	char filename[128];
-	ssize_t size;
-	int ret;
-
-	/*
-	 * make it easy and load a raw file in the right format instead of
-	 * opening an (say) PNG and convert the image data to the right format.
-	 */
-	ret = snprintf(filename, sizeof(filename),
-		       "%s/%s-%ux%u-%s.bin",
-		       dir, base, dev->width, dev->height, dev->format->name);
-	if (ret >= sizeof(filename)) {
-		error("Failed to fit filename into buffer\n");
-		return;
-	}
-
-	fd_src = open(filename, O_RDONLY | O_CLOEXEC);
-	if (fd_src < 0) {
-		error("Failed to open %s: %m\n", filename);
-		return;
-	}
-
-	size = readfull(fd_src, dev->map, dev->size);
-	if (size < dev->size) {
-		if (size < 0)
-			error("Failed to read from %s: %m\n", filename);
-		else
-			error("Could only read %zd/%u bytes from %s\n",
-			      size, dev->size, filename);
-	}
-
-	ret = close(fd_src);
-	if (ret < 0) {
-		/* Nothing we can do about this, so just warn */
-		error("Failed to close image file\n");
-	}
-
-	return;
-}
-
-static struct modeset_dev *modeset_list = NULL;
-
-static int drmprepare_crtc(int fd, drmModeRes *res, drmModeConnector *conn,
-			   struct modeset_dev *dev)
-{
-	drmModeEncoder *enc;
-	unsigned int i, j;
-	int32_t crtc_id;
-	struct modeset_dev *iter;
-
-	/* first try the currently connected encoder+crtc */
-	if (conn->encoder_id) {
-		debug("connector #%d uses encoder #%d\n", conn->connector_id,
-		      conn->encoder_id);
-		enc = drmModeGetEncoder(fd, conn->encoder_id);
-		assert(enc);
-		assert(enc->encoder_id == conn->encoder_id);
-	} else {
-		debug("connector #%d has no active encoder\n",
-		      conn->connector_id);
-		enc = NULL;
-		dev->setmode = 1;
-	}
-
-	if (enc) {
-		if (enc->crtc_id) {
-			crtc_id = enc->crtc_id;
-			assert(crtc_id >= 0);
-
-			for (iter = modeset_list; iter; iter = iter->next) {
-				if (iter->crtc_id == crtc_id) {
-					crtc_id = -1;
-					break;
-				}
-			}
-
-			if (crtc_id > 0) {
-				debug("encoder #%d uses crtc #%d\n",
-				      enc->encoder_id, enc->crtc_id);
-				drmModeFreeEncoder(enc);
-				dev->crtc_id = crtc_id;
-				return 0;
-			} else {
-				debug("encoder #%d used crtc #%d, but that's in use\n",
-				      enc->encoder_id, iter->crtc_id);
-			}
-		} else {
-			debug("encoder #%d doesn't have an active crtc\n",
-			      enc->encoder_id);
-		}
-
-		drmModeFreeEncoder(enc);
-	}
-
-	/* If the connector is not currently bound to an encoder or if the
-	 * encoder+crtc is already used by another connector (actually unlikely
-	 * but let's be safe), iterate all other available encoders to find a
-	 * matching CRTC. */
-	for (i = 0; i < conn->count_encoders; ++i) {
-		enc = drmModeGetEncoder(fd, conn->encoders[i]);
-		if (!enc) {
-			error("Cannot retrieve encoder %u: %m\n",
-			      conn->encoders[i]);
-			continue;
-		}
-		assert(enc->encoder_id == conn->encoders[i]);
-
-		/* iterate all global CRTCs */
-		for (j = 0; j < res->count_crtcs; ++j) {
-			/* check whether this CRTC works with the encoder */
-			if (!(enc->possible_crtcs & (1 << j)))
-				continue;
-
-			/* check that no other device already uses this CRTC */
-			crtc_id = res->crtcs[j];
-			for (iter = modeset_list; iter; iter = iter->next) {
-				if (iter->crtc_id == crtc_id) {
-					crtc_id = -1;
-					break;
-				}
-			}
-
-			/* we have found a CRTC, so save it and return */
-			if (crtc_id >= 0) {
-				debug("encoder #%d will use crtc #%d\n",
-				      enc->encoder_id, crtc_id);
-				drmModeFreeEncoder(enc);
-				dev->crtc_id = crtc_id;
-				return 0;
-			}
-
-		}
-		drmModeFreeEncoder(enc);
-	}
-
-	error("Cannot find suitable CRTC for connector #%u\n",
-	      conn->connector_id);
-	return -ENOENT;
-}
-
-static int modeset_create_fb(int fd, struct modeset_dev *dev)
-{
-	struct drm_mode_create_dumb creq;
-	struct drm_mode_destroy_dumb dreq;
-	struct drm_mode_map_dumb mreq;
-	int ret;
-
-	/* create dumb buffer */
-	memset(&creq, 0, sizeof(creq));
-	creq.width = dev->width;
-	creq.height = dev->height;
-	creq.bpp = dev->format->bpp;
-	ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq);
-	if (ret < 0) {
-		error("Cannot create dumb buffer: %m\n");
-		return -errno;
-	}
-	dev->stride = creq.pitch;
-	dev->size = creq.size;
-	dev->handle = creq.handle;
-
-	/* create framebuffer object for the dumb-buffer */
-	ret = drmModeAddFB2(fd, dev->width, dev->height,
-			    dev->format->format,
-			    (uint32_t[4]){ dev->handle, },
-			    (uint32_t[4]){ dev->stride, },
-			    (uint32_t[4]){ 0, },
-			    &dev->fb_id, 0);
-	if (ret) {
-		ret = -errno;
-		error("Cannot create framebuffer: %m\n");
-		goto err_destroy;
-	}
-
-	/* prepare buffer for memory mapping */
-	memset(&mreq, 0, sizeof(mreq));
-	mreq.handle = dev->handle;
-	ret = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq);
-	if (ret) {
-		ret = -errno;
-		error("Cannot get mmap offset: %m\n");
-		goto err_fb;
-	}
-
-	/* perform actual memory mapping */
-	dev->map = mmap(0, dev->size, PROT_READ | PROT_WRITE, MAP_SHARED,
-			fd, mreq.offset);
-	if (dev->map == MAP_FAILED) {
-		ret = -errno;
-		error("Cannot mmap dumb buffer: %m\n");
-		goto err_fb;
-	}
-
-	/*
-	 * Clear the framebuffer. Normally it's overwritten later with some
-	 * image data, but in case this fails, initialize to all-black.
-	 */
-	memset(dev->map, 0x0, dev->size);
-
-	return 0;
-
-err_fb:
-	drmModeRmFB(fd, dev->fb_id);
-err_destroy:
-	memset(&dreq, 0, sizeof(dreq));
-	dreq.handle = dev->handle;
-	drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
-	return ret;
-}
-
-/* Returns lowercase connector type names with '_' for '-' */
-static char *get_normalized_conn_type_name(uint32_t connector_type)
-{
-	int i;
-	const char *connector_name;
-	char *normalized_name;
-
-	connector_name = drmModeGetConnectorTypeName(connector_type);
-	if (!connector_name)
-		return NULL;
-
-	normalized_name = strdup(connector_name);
-
-	for (i = 0; normalized_name[i]; i++) {
-		normalized_name[i] = tolower(normalized_name[i]);
-		if (normalized_name[i] == '-')
-			normalized_name[i] = '_';
-	}
-
-	return normalized_name;
-}
-
-static const struct platsch_format *platsch_format_find(const char *name)
-{
-	int i;
-
-	for (i = 0; i < ARRAY_SIZE(platsch_formats); i++)
-		if (!strcmp(platsch_formats[i].name, name))
-			return &platsch_formats[i];
-
-	return NULL;
-}
-
-static int set_env_connector_mode(drmModeConnector *conn,
-				  struct modeset_dev *dev)
-{
-	int ret, i = 0;
-	u_int32_t width = 0, height = 0;
-	const char *mode;
-	char *connector_type_name, mode_env_name[32], fmt_specifier[32] = "";
-	const struct platsch_format *format = NULL;
-
-	connector_type_name = get_normalized_conn_type_name(conn->connector_type);
-	if (!connector_type_name) {
-		error("could not look up name for connector type %u\n",
-		      conn->connector_type);
-		goto fallback;
-	}
-
-	ret = snprintf(mode_env_name, sizeof(mode_env_name), "platsch_%s%u_mode",
-		       connector_type_name, conn->connector_type_id);
-	free(connector_type_name);
-	if (ret >= sizeof(mode_env_name)) {
-		error("failed to fit platsch env mode variable name into buffer\n");
-		return -EFAULT;
-	}
-
-	/* check for connector mode configuration in environment */
-	debug("looking up %s env variable\n", mode_env_name);
-	mode = getenv(mode_env_name);
-	if (!mode)
-		goto fallback;
-
-	/* format suffix is optional */
-	ret = sscanf(mode, "%ux%u@%s", &width, &height, fmt_specifier);
-	if (ret < 2) {
-		error("error while scanning %s for mode\n", mode_env_name);
-		return -EFAULT;
-	}
-
-	/* use first mode matching given resolution */
-	for (i = 0; i < conn->count_modes; i++) {
-		drmModeModeInfo mode = conn->modes[i];
-		if (mode.hdisplay == width && mode.vdisplay == height) {
-			memcpy(&dev->mode, &mode, sizeof(dev->mode));
-			dev->width = width;
-			dev->height = height;
-			break;
-		}
-	}
-
-	if (i == conn->count_modes) {
-		error("no mode available matching %ux%u\n", width, height);
-		return -ENOENT;
-	}
-
-	format = platsch_format_find(fmt_specifier);
-	if (!format) {
-		if (strlen(fmt_specifier))
-			error("unknown format specifier %s\n", fmt_specifier);
-		goto fallback_format;
-	}
-
-	dev->format = format;
-
-	return 0;
-
-fallback:
-	memcpy(&dev->mode, &conn->modes[0], sizeof(dev->mode));
-	dev->width = conn->modes[0].hdisplay;
-	dev->height = conn->modes[0].vdisplay;
-	debug("using default mode for connector #%u\n", conn->connector_id);
-
-fallback_format:
-	dev->format = &platsch_formats[0];
-	debug("using default format %s for connector #%u\n", dev->format->name,
-	      conn->connector_id);
-
-	return 0;
-}
-
-static int drmprepare_connector(int fd, drmModeRes *res, drmModeConnector *conn,
-				struct modeset_dev *dev)
-{
-	int ret;
-
-	/* check if a monitor is connected */
-	if (conn->connection != DRM_MODE_CONNECTED) {
-		error("Ignoring unused connector #%u\n", conn->connector_id);
-		return -ENOENT;
-	}
-
-	/* check if there is at least one valid mode */
-	if (conn->count_modes == 0) {
-		error("no valid mode for connector #%u\n", conn->connector_id);
-		return -EFAULT;
-	}
-
-	/* configure mode information in our device structure */
-	ret = set_env_connector_mode(conn, dev);
-	if (ret) {
-		error("no valid mode for connector #%u\n", conn->connector_id);
-		return ret;
-	}
-	debug("mode for connector #%u is %ux%u@%s\n",
-	      conn->connector_id, dev->width, dev->height, dev->format->name);
-
-	/* find a crtc for this connector */
-	ret = drmprepare_crtc(fd, res, conn, dev);
-	if (ret) {
-		error("no valid crtc for connector #%u\n", conn->connector_id);
-		return ret;
-	}
-
-	/* create a framebuffer for this CRTC */
-	ret = modeset_create_fb(fd, dev);
-	if (ret) {
-		error("cannot create framebuffer for connector #%u\n",
-		      conn->connector_id);
-		return ret;
-	}
-
-	return 0;
-}
-
-static int drmprepare(int fd)
-{
-	drmModeRes *res;
-	drmModeConnector *conn;
-	unsigned int i;
-	struct modeset_dev *dev;
-	int ret;
-
-	/* retrieve resources */
-	res = drmModeGetResources(fd);
-	if (!res) {
-		error("cannot retrieve DRM resources: %m\n");
-		return -errno;
-	}
-
-	debug("Found %d connectors\n", res->count_connectors);
-
-	/* iterate all connectors */
-	for (i = 0; i < res->count_connectors; ++i) {
-		/* get information for each connector */
-		conn = drmModeGetConnector(fd, res->connectors[i]);
-		if (!conn) {
-			error("Cannot retrieve DRM connector #%u: %m\n",
-				res->connectors[i]);
-			continue;
-		}
-		assert(conn->connector_id == res->connectors[i]);
-
-		debug("Connector #%u has type %s\n", conn->connector_id,
-		      drmModeGetConnectorTypeName(conn->connector_type));
-
-		/* create a device structure */
-		dev = malloc(sizeof(*dev));
-		if (!dev) {
-			error("Cannot allocate memory for connector #%u: %m\n",
-			      res->connectors[i]);
-			continue;
-		}
-		memset(dev, 0, sizeof(*dev));
-		dev->conn_id = conn->connector_id;
-
-		ret = drmprepare_connector(fd, res, conn, dev);
-		if (ret) {
-			if (ret != -ENOENT) {
-				error("Cannot setup device for connector #%u: %m\n",
-				      res->connectors[i]);
-			}
-			free(dev);
-			drmModeFreeConnector(conn);
-			continue;
-		}
-
-		/* free connector data and link device into global list */
-		drmModeFreeConnector(conn);
-		dev->next = modeset_list;
-		modeset_list = dev;
-	}
-
-	/* free resources again */
-	drmModeFreeResources(res);
-	return 0;
-}
-
 static struct option longopts[] =
 {
 	{ "help",      no_argument,       0, 'h' },
@@ -552,7 +57,7 @@ static struct option longopts[] =
 
 static void usage(const char *prog)
 {
-	error("Usage:\n"
+	platsch_error("Usage:\n"
 	      "%s [-d|--directory <dir>] [-b|--basename <name>]\n"
 	      "   [-h|--help]\n",
 	      prog);
@@ -561,14 +66,12 @@ static void usage(const char *prog)
 int main(int argc, char *argv[])
 {
 	char **initsargv;
-	int drmfd;
-	char drmdev[128];
 	struct modeset_dev *iter;
 	bool pid1 = getpid() == 1;
 	const char *dir = "/usr/share/platsch";
 	const char *base = "splash";
 	const char *env;
-	int ret = 0, c, i;
+	int ret = 0, c;
 
 	env = getenv("platsch_directory");
 	if (env)
@@ -580,7 +83,7 @@ int main(int argc, char *argv[])
 
 	if (!pid1) {
 		while ((c = getopt_long(argc, argv, "hd:b:", longopts, NULL)) != EOF) {
-			switch(c) {
+			switch (c) {
 			case 'd':
 				dir = optarg;
 				break;
@@ -598,77 +101,28 @@ int main(int argc, char *argv[])
 		}
 
 		if (optind < argc) {
-			error("Too many arguments!\n");
+			platsch_error("Too many arguments!\n");
 			usage(basename(argv[0]));
 			exit(1);
 		}
 	}
 
-	for (i = 0; i < 64; i++) {
-		struct drm_mode_card_res res = {0};
-
-		/*
-		 * XXX: Maybe use drmOpen instead?
-		 * (Where should name/busid come from?)
-		 * XXX: Loop through drm devices to find one with connectors.
-		 */
-		ret = snprintf(drmdev, sizeof(drmdev), DRM_DEV_NAME, DRM_DIR_NAME, i);
-		if (ret >= sizeof(drmdev)) {
-			error("Huh, device name overflowed buffer\n");
-			goto execinit;
-		}
-
-		drmfd = open(drmdev, O_RDWR | O_CLOEXEC, 0);
-		if (drmfd < 0) {
-			error("Failed to open drm device: %m\n");
-			goto execinit;
-		}
-
-		ret = drmIoctl(drmfd, DRM_IOCTL_MODE_GETRESOURCES, &res);
-		if (ret < 0) {
-			close(drmfd);
-			continue;
-		} else {
-			/* Device found */
-			break;
-		}
+	struct modeset_dev *modeset_list = platsch_init();
+	if (!modeset_list) {
+		platsch_error("Failed to initialize modeset\n");
+		return EXIT_FAILURE;
 	}
 
-	ret = drmprepare(drmfd);
-	assert(!ret);
-
 	for (iter = modeset_list; iter; iter = iter->next) {
-
-		/* draw first then set the mode */
-		draw_buffer(iter, dir, base);
-
-		if (iter->setmode) {
-			debug("set crtc\n");
-
-			ret = drmModeSetCrtc(drmfd, iter->crtc_id, iter->fb_id,
-					     0, 0, &iter->conn_id, 1, &iter->mode);
-			if (ret)
-				error("Cannot set CRTC for connector #%u: %m\n",
-				      iter->conn_id);
-		} else {
-			debug("page flip\n");
-			ret = drmModePageFlip(drmfd, iter->crtc_id, iter->fb_id,
-					      0, NULL);
-			if (ret)
-				error("Page flip failed on connector #%u: %m\n",
-				      iter->conn_id);
-		}
+		platsch_draw(iter, dir, base);
 	}
 
-	ret = drmDropMaster(drmfd);
-	if (ret)
-		error("Failed to drop master on drm device\n");
+	platsch_drmDropMaster();
 
-execinit:
 	if (pid1) {
 		ret = fork();
 		if (ret < 0) {
-			error("failed to fork for init: %m\n");
+			platsch_error("failed to fork for init: %m\n");
 		} else if (ret == 0) {
 			/*
 			 * in the child go to sleep to keep the drm device open
@@ -679,7 +133,7 @@ execinit:
 
 		initsargv = calloc(sizeof(argv[0]), argc + 1);
 		if (!initsargv) {
-			error("failed to allocate argv for init\n");
+			platsch_error("failed to allocate argv for init\n");
 			return EXIT_FAILURE;
 		}
 		memcpy(initsargv, argv, argc * sizeof(argv[0]));
@@ -688,13 +142,13 @@ execinit:
 
 		execv("/sbin/init", initsargv);
 
-		error("failed to exec init: %m\n");
+		platsch_error("failed to exec init: %m\n");
 
 		return EXIT_FAILURE;
 	}
 
 sleep:
-	redirect_stdfd();
+	platsch_redirect_stdfd();
 
 	do {
 		sleep(10);
-- 
2.34.1




^ permalink raw reply	[flat|nested] 11+ messages in thread

* [OSS-Tools] [PATCH platsch V5 4/5] Platsch: always fork child process
  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-19 10:22 ` [OSS-Tools] [PATCH platsch V5 3/5] platsch: split into platsch and libplatsch LI Qingwu
@ 2024-06-19 10:22 ` 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
  3 siblings, 0 replies; 11+ messages in thread
From: LI Qingwu @ 2024-06-19 10:22 UTC (permalink / raw)
  To: Qing-wu.Li, oss-tools, m.felsch; +Cc: bsp-development.geo

When running Platsch in the background with an ampersand,
the initramfs may disappear after the rootfs is mounted.
In such cases, Platsch may fail to read the resources.
To address this, Platsch now always forks a child process
after acquiring the resources.
The chiled process keeps the DRM devices remain open
while the parent process exits with a clear status.

Signed-off-by: LI Qingwu <Qing-wu.Li@leica-geosystems.com.cn>
---
 platsch.c | 23 +++++++++++++----------
 1 file changed, 13 insertions(+), 10 deletions(-)

diff --git a/platsch.c b/platsch.c
index 6503066..756ba1c 100644
--- a/platsch.c
+++ b/platsch.c
@@ -119,17 +119,18 @@ int main(int argc, char *argv[])
 
 	platsch_drmDropMaster();
 
+	ret = fork();
+	if (ret < 0) {
+		platsch_error("failed to fork for init: %m\n");
+	} else if (ret == 0) {
+		/*
+		* in the child go to sleep to keep the drm device open
+		* and give pid 1 to init.
+		*/
+		goto sleep;
+	}
+
 	if (pid1) {
-		ret = fork();
-		if (ret < 0) {
-			platsch_error("failed to fork for init: %m\n");
-		} else if (ret == 0) {
-			/*
-			 * in the child go to sleep to keep the drm device open
-			 * and give pid 1 to init.
-			 */
-			goto sleep;
-		}
 
 		initsargv = calloc(sizeof(argv[0]), argc + 1);
 		if (!initsargv) {
@@ -147,6 +148,8 @@ int main(int argc, char *argv[])
 		return EXIT_FAILURE;
 	}
 
+	return EXIT_SUCCESS;
+
 sleep:
 	platsch_redirect_stdfd();
 
-- 
2.34.1




^ permalink raw reply	[flat|nested] 11+ messages in thread

* [OSS-Tools] [PATCH platsch V5 5/5] Add spinner executable for boot animation and text show
  2024-06-19 10:22 [OSS-Tools] [PATCH platsch V5 1/5] platsch: constify draw_buffer LI Qingwu
                   ` (2 preceding siblings ...)
  2024-06-19 10:22 ` [OSS-Tools] [PATCH platsch V5 4/5] Platsch: always fork child process LI Qingwu
@ 2024-06-19 10:22 ` LI Qingwu
  2024-06-20 12:46   ` Philipp Zabel
  3 siblings, 1 reply; 11+ messages in thread
From: LI Qingwu @ 2024-06-19 10:22 UTC (permalink / raw)
  To: Qing-wu.Li, oss-tools, m.felsch; +Cc: bsp-development.geo

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.

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),
+        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')
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);
+
+	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);
+	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);
+			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);
+		}
+		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);
+	}
+}
diff --git a/spinner_conf.c b/spinner_conf.c
new file mode 100644
index 0000000..491b1c3
--- /dev/null
+++ b/spinner_conf.c
@@ -0,0 +1,67 @@
+#include "spinner_conf.h"
+#include <errno.h>
+
+int parseConfig(const char *filename, Config *config)
+{
+	char *value_end;
+	char *value_start;
+	char key[MAX_LINE_LENGTH];
+	char line[MAX_LINE_LENGTH];
+	char value[MAX_LINE_LENGTH];
+	FILE *file;
+
+	file = fopen(filename, "r");
+	if (file == NULL) {
+		fprintf(stderr, "Unable to open file: %s\n", filename);
+		return -EFAULT;
+	}
+
+	while (fgets(line, sizeof(line), file)) {
+		if (strlen(line) > MAX_LINE_LENGTH) {
+			fprintf(stderr, "conf string too long\n");
+			continue;
+		}
+		if (line[0] != '#' && sscanf(line, "%[^=]=%[^\n]", key, value) == 2) {
+			value_start = strchr(line, '=') + 1;
+			value_end = line + strlen(line) - 1;
+
+			while (isspace(*value_start))
+				value_start++;
+			while (isspace(*value_end) || *value_end == '"')
+				value_end--;
+
+			if (*value_start == '"')
+				value_start++;
+			if (*value_end == '"')
+					value_end--;
+
+			if (value_end < value_start) {
+				memset(value, 0, sizeof(value));
+				continue;
+			} else {
+				strncpy(value, value_start, value_end - value_start + 1);
+				value[value_end - value_start + 1] = '\0';
+				value[sizeof(value) - 1] = '\0';
+			}
+			value[MAX_LINE_LENGTH - 1] = '\0';
+
+			if (strcmp(key, "symbol") == 0) {
+				strncpy(config->symbol, value, MAX_LINE_LENGTH);
+			} else if (strcmp(key, "text") == 0) {
+				strncpy(config->text, value, MAX_LINE_LENGTH);
+			} else if (strcmp(key, "fps") == 0) {
+				config->fps = atoi(value);
+			} else if (strcmp(key, "text_x") == 0) {
+				config->text_x = atoi(value);
+			} else if (strcmp(key, "text_y") == 0) {
+				config->text_y = atoi(value);
+			} else if (strcmp(key, "text_font") == 0) {
+				strncpy(config->text_font, value, MAX_LINE_LENGTH);
+			} else if (strcmp(key, "text_size") == 0) {
+				config->text_size = atoi(value);
+			}
+		}
+	}
+	fclose(file);
+	return 0;
+}
diff --git a/spinner_conf.h b/spinner_conf.h
new file mode 100644
index 0000000..4f8501b
--- /dev/null
+++ b/spinner_conf.h
@@ -0,0 +1,33 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#ifndef __SPINNER_CONF_H__
+#define __SPINNER_CONF_H__
+
+
+#define MAX_LINE_LENGTH 128
+
+typedef struct {
+	char symbol[MAX_LINE_LENGTH];
+	int fps;
+	int text_x;
+	int text_y;
+	char text_font[MAX_LINE_LENGTH];
+	int text_size;
+	char text[MAX_LINE_LENGTH];
+} Config;
+
+int parseConfig(const char *filename, Config *config);
+
+#define DEFAULT_CONFIG { \
+	.symbol = "", \
+	.fps = 20, \
+	.text_x = 0, \
+	.text_y = 0, \
+	.text_font = "Sans", \
+	.text_size = 30, \
+	.text = "" \
+}
+#endif
-- 
2.34.1




^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [OSS-Tools] [PATCH platsch V5 2/5] convert to meson build
  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
  0 siblings, 1 reply; 11+ messages in thread
From: Philipp Zabel @ 2024-06-20 12:19 UTC (permalink / raw)
  To: LI Qingwu, oss-tools, m.felsch; +Cc: bsp-development.geo

Hi,

On Mi, 2024-06-19 at 12:22 +0200, LI Qingwu wrote:
> Convert to meson build and update the README.rst
> version update to 2024.06.0
> cleanup .gitignore
> 
> Signed-off-by: LI Qingwu <Qing-wu.Li@leica-geosystems.com.cn>
> ---
>  .gitignore   | 14 --------------
>  Makefile.am  | 23 -----------------------
>  README.rst   |  8 ++++++++
>  configure.ac | 13 -------------
>  meson.build  | 14 ++++++++++++++
>  5 files changed, 22 insertions(+), 50 deletions(-)
>  delete mode 100644 Makefile.am
>  delete mode 100644 configure.ac
>  create mode 100644 meson.build
> 
> diff --git a/.gitignore b/.gitignore
> index bef7e68..e69de29 100644
> --- a/.gitignore
> +++ b/.gitignore
> @@ -1,14 +0,0 @@
> -/*.o
> -/aclocal.m4
> -/autom4te.cache
> -/compile
> -/config.log
> -/config.status
> -/configure
> -/depcomp
> -/.deps
> -/install-sh
> -/Makefile
> -/Makefile.in
> -/missing
> -/platsch
> diff --git a/Makefile.am b/Makefile.am
> deleted file mode 100644
> index d149ae0..0000000
> --- a/Makefile.am
> +++ /dev/null
> @@ -1,23 +0,0 @@
> -EXTRA_DIST = README.rst LICENSE
> -
> -sbin_PROGRAMS = platsch
> -
> -platsch_SOURCES = platsch.c
> -platsch_CFLAGS = $(LIBDRM_CFLAGS)
> -platsch_LDADD = $(LIBDRM_LIBS)
> -
> -CLEANFILES = \
> -        $(DIST_ARCHIVES)
> -
> -DISTCLEAN = \
> -        config.log \
> -        config.status \
> -        Makefile
> -
> -MAINTAINERCLEANFILES = \
> -	aclocal.m4 \
> -	configure \
> -	depcomp \
> -	install-sh \
> -	Makefile.in \
> -	missing
> diff --git a/README.rst b/README.rst
> index e318120..f1c0812 100644
> --- a/README.rst
> +++ b/README.rst
> @@ -141,3 +141,11 @@ By adding a Signed-off-by line (e.g. using ``git commit -s``) saying::
>  
>  (using your real name and e-mail address), you state that your contributions
>  are in line with the DCO.
> +
> +Compiling Instructions
> +----------------------------
> +
> +.. code-block:: shell
> +
> +    meson setup build
> +    meson compile -C build
> diff --git a/configure.ac b/configure.ac
> deleted file mode 100644
> index 18878db..0000000
> --- a/configure.ac
> +++ /dev/null
> @@ -1,13 +0,0 @@
> -AC_PREREQ([2.69])
> -AC_INIT([platsch], [2019.12.0], [oss-tools@pengutronix.de])
> -AC_CONFIG_SRCDIR([platsch.c])
> -AM_INIT_AUTOMAKE([foreign dist-xz])
> -
> -AC_PROG_CC
> -AC_PROG_MAKE_SET
> -
> -PKG_CHECK_MODULES([LIBDRM], [libdrm >= 2.4.112])
> -
> -AC_CONFIG_FILES([Makefile])
> -
> -AC_OUTPUT
> diff --git a/meson.build b/meson.build
> new file mode 100644
> index 0000000..b732a06
> --- /dev/null
> +++ b/meson.build
> @@ -0,0 +1,14 @@
> +project('platsch', 'c', version: '2024.06.0', license : '0BSD')
> +
> +dep_libdrm = dependency('libdrm',
> +    version: '>= 2.4.112',
> +    static: true
> +)
> +
> +executable('platsch',
> +    'platsch.c',
> +    dependencies: dep_libdrm,
> +    link_args: '-static',

Dynamic linking should be kept working in case the build environment
does not provide static libraries. I think we could gate static linking
behind the --prefer-static Meson option, like this:

dep_libdrm = dependency('libdrm', version: '>= 2.4.112')

platsch_link_args = []

if get_option('prefer_static')
  cc = meson.get_compiler('c')
  if cc.find_library('drm', static: true, required: false).found()
    platsch_link_args += '-static'
  endif
endif

executable('platsch',
  'platsch.c',
  dependencies: dep_libdrm,
  link_args: platsch_link_args,
  install: true,
  install_dir: 'sbin',
)

Another option would be to drop static linking for now and only add it
in a separate patch later.

> +    install: true,
> +    install_dir : 'sbin'

Please pick a formatting style and apply it consistently, e.g. remove
the space before all colons, or add it everywhere.

Apart from the static linking issue, this looks good to me now.

regards
Philipp



^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [OSS-Tools] [PATCH platsch V5 5/5] Add spinner executable for boot animation and text show
  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
  0 siblings, 1 reply; 11+ messages in thread
From: Philipp Zabel @ 2024-06-20 12:46 UTC (permalink / raw)
  To: LI Qingwu, oss-tools, m.felsch; +Cc: bsp-development.geo

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



^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [OSS-Tools] [PATCH platsch V5 3/5] platsch: split into platsch and libplatsch
  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
  0 siblings, 0 replies; 11+ messages in thread
From: Philipp Zabel @ 2024-06-20 12:55 UTC (permalink / raw)
  To: LI Qingwu, oss-tools, m.felsch; +Cc: bsp-development.geo

On Mi, 2024-06-19 at 12:22 +0200, LI Qingwu wrote:
> Signed-off-by: LI Qingwu <Qing-wu.Li@leica-geosystems.com.cn>
> ---
>  libplatsch.c | 563 +++++++++++++++++++++++++++++++++++++++++++++++++
>  libplatsch.h |  47 +++++
>  meson.build  |  12 +-
>  platsch.c    | 582 ++-------------------------------------------------
>  4 files changed, 639 insertions(+), 565 deletions(-)
>  create mode 100644 libplatsch.c
>  create mode 100644 libplatsch.h
> 
> diff --git a/libplatsch.c b/libplatsch.c
> new file mode 100644
> index 0000000..2e0dc41
> --- /dev/null
> +++ b/libplatsch.c
> @@ -0,0 +1,563 @@
> +/*
> + * Copyright (C) 2019 Pengutronix, Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
> + *
> + * Permission to use, copy, modify, and/or distribute this software
> + * for any purpose with or without fee is hereby granted.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
> + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
> + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
> + * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
> + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
> + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
> + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
> + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
> + *
> + * Some code parts base on example code written in 2012 by David Herrmann
> + * <dh.herrmann@googlemail.com> and dedicated to the Public Domain. It was found
> + * in 2019 on
> + * https://raw.githubusercontent.com/dvdhrm/docs/master/drm-howto/modeset.c
> + */
> +
> +#include <assert.h>
> +#include <ctype.h>
> +#include <errno.h>
> +#include <getopt.h>

Please double check the needed headers. At least getopt code stayed in
platsch.c, and I think libgen.h, stdarg.h, stdbool.h, sys/types.h, and
sys/stat.h are not needed either.

> +#include <libgen.h>
> +#include <stdarg.h>
> +#include <stdbool.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <unistd.h>
> +#include <sys/types.h>
> +#include <sys/stat.h>
> +#include <sys/mman.h>
> +#include <fcntl.h>

regards
Philipp



^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [OSS-Tools] [PATCH platsch V5 2/5] convert to meson build
  2024-06-20 12:19   ` Philipp Zabel
@ 2024-06-23 11:46     ` LI Qingwu
  0 siblings, 0 replies; 11+ messages in thread
From: LI Qingwu @ 2024-06-23 11:46 UTC (permalink / raw)
  To: Philipp Zabel, oss-tools, m.felsch; +Cc: GEO-CHHER-bsp-development

Hi Philipp,

> -----Original Message-----
> From: Philipp Zabel <p.zabel@pengutronix.de>
> Sent: Thursday, June 20, 2024 8:19 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 2/5] convert to meson build
> 
> [Some people who received this message don't often get email from
> p.zabel@pengutronix.de. Learn why this is important at
> 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.
> 
> 
> Hi,
> 
> On Mi, 2024-06-19 at 12:22 +0200, LI Qingwu wrote:
> > Convert to meson build and update the README.rst version update to
> > 2024.06.0 cleanup .gitignore
> >
> > Signed-off-by: LI Qingwu <Qing-wu.Li@leica-geosystems.com.cn>
> > ---
> >  .gitignore   | 14 --------------
> >  Makefile.am  | 23 -----------------------
> >  README.rst   |  8 ++++++++
> >  configure.ac | 13 -------------
> >  meson.build  | 14 ++++++++++++++
> >  5 files changed, 22 insertions(+), 50 deletions(-)  delete mode
> > 100644 Makefile.am  delete mode 100644 configure.ac  create mode
> > 100644 meson.build
> >
> > diff --git a/.gitignore b/.gitignore
> > index bef7e68..e69de29 100644
> > --- a/.gitignore
> > +++ b/.gitignore
> > @@ -1,14 +0,0 @@
> > -/*.o
> > -/aclocal.m4
> > -/autom4te.cache
> > -/compile
> > -/config.log
> > -/config.status
> > -/configure
> > -/depcomp
> > -/.deps
> > -/install-sh
> > -/Makefile
> > -/Makefile.in
> > -/missing
> > -/platsch
> > diff --git a/Makefile.am b/Makefile.am deleted file mode 100644 index
> > d149ae0..0000000
> > --- a/Makefile.am
> > +++ /dev/null
> > @@ -1,23 +0,0 @@
> > -EXTRA_DIST = README.rst LICENSE
> > -
> > -sbin_PROGRAMS = platsch
> > -
> > -platsch_SOURCES = platsch.c
> > -platsch_CFLAGS = $(LIBDRM_CFLAGS)
> > -platsch_LDADD = $(LIBDRM_LIBS)
> > -
> > -CLEANFILES = \
> > -        $(DIST_ARCHIVES)
> > -
> > -DISTCLEAN = \
> > -        config.log \
> > -        config.status \
> > -        Makefile
> > -
> > -MAINTAINERCLEANFILES = \
> > -     aclocal.m4 \
> > -     configure \
> > -     depcomp \
> > -     install-sh \
> > -     Makefile.in \
> > -     missing
> > diff --git a/README.rst b/README.rst
> > index e318120..f1c0812 100644
> > --- a/README.rst
> > +++ b/README.rst
> > @@ -141,3 +141,11 @@ By adding a Signed-off-by line (e.g. using ``git
> commit -s``) saying::
> >
> >  (using your real name and e-mail address), you state that your
> > contributions  are in line with the DCO.
> > +
> > +Compiling Instructions
> > +----------------------------
> > +
> > +.. code-block:: shell
> > +
> > +    meson setup build
> > +    meson compile -C build
> > diff --git a/configure.ac b/configure.ac deleted file mode 100644
> > index 18878db..0000000
> > --- a/configure.ac
> > +++ /dev/null
> > @@ -1,13 +0,0 @@
> > -AC_PREREQ([2.69])
> > -AC_INIT([platsch], [2019.12.0], [oss-tools@pengutronix.de])
> > -AC_CONFIG_SRCDIR([platsch.c])
> > -AM_INIT_AUTOMAKE([foreign dist-xz])
> > -
> > -AC_PROG_CC
> > -AC_PROG_MAKE_SET
> > -
> > -PKG_CHECK_MODULES([LIBDRM], [libdrm >= 2.4.112])
> > -
> > -AC_CONFIG_FILES([Makefile])
> > -
> > -AC_OUTPUT
> > diff --git a/meson.build b/meson.build new file mode 100644 index
> > 0000000..b732a06
> > --- /dev/null
> > +++ b/meson.build
> > @@ -0,0 +1,14 @@
> > +project('platsch', 'c', version: '2024.06.0', license : '0BSD')
> > +
> > +dep_libdrm = dependency('libdrm',
> > +    version: '>= 2.4.112',
> > +    static: true
> > +)
> > +
> > +executable('platsch',
> > +    'platsch.c',
> > +    dependencies: dep_libdrm,
> > +    link_args: '-static',
> 
> Dynamic linking should be kept working in case the build environment does not
> provide static libraries. I think we could gate static linking behind the
> --prefer-static Meson option, like this:
> 
> dep_libdrm = dependency('libdrm', version: '>= 2.4.112')
> 
> platsch_link_args = []
> 
> if get_option('prefer_static')
>   cc = meson.get_compiler('c')
>   if cc.find_library('drm', static: true, required: false).found()
>     platsch_link_args += '-static'
>   endif
> endif
> 
> executable('platsch',
>   'platsch.c',
>   dependencies: dep_libdrm,
>   link_args: platsch_link_args,
>   install: true,
>   install_dir: 'sbin',
> )

During my test, meson build will link static if set prefer_static=true,
I just update the document in v6, user just set prefer_static=true during setup:
meson setup -Dspinner=true -Dprefer_static=true build



> Another option would be to drop static linking for now and only add it in a
> separate patch later.
> 
> > +    install: true,
> > +    install_dir : 'sbin'
> 
> Please pick a formatting style and apply it consistently, e.g. remove the space
> before all colons, or add it everywhere.
> 
> Apart from the static linking issue, this looks good to me now.
> 
> regards
> Philipp



^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [OSS-Tools] [PATCH platsch V5 5/5] Add spinner executable for boot animation and text show
  2024-06-20 12:46   ` Philipp Zabel
@ 2024-06-23 11:52     ` LI Qingwu
  2024-06-24  7:55       ` Philipp Zabel
  0 siblings, 1 reply; 11+ messages in thread
From: LI Qingwu @ 2024-06-23 11:52 UTC (permalink / raw)
  To: Philipp Zabel, oss-tools, m.felsch; +Cc: GEO-CHHER-bsp-development



> -----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

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [OSS-Tools] [PATCH platsch V5 5/5] Add spinner executable for boot animation and text show
  2024-06-23 11:52     ` LI Qingwu
@ 2024-06-24  7:55       ` Philipp Zabel
  0 siblings, 0 replies; 11+ messages in thread
From: Philipp Zabel @ 2024-06-24  7:55 UTC (permalink / raw)
  To: LI Qingwu, oss-tools, m.felsch; +Cc: GEO-CHHER-bsp-development

On So, 2024-06-23 at 11:52 +0000, LI Qingwu wrote:
> 
> > -----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%

Yes, that's even better.

regards
Philipp



^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2024-06-26  8:41 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
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
2024-06-24  7:55       ` Philipp Zabel

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox