From 6e79300f342f2f21936365f8d91e758aa1494200 Mon Sep 17 00:00:00 2001
From: Vito Caputo <vcaputo@pengaru.com>
Date: Tue, 13 Jun 2023 22:42:37 -0700
Subject: modules/mixer: add a fade style, make default

This is not optimized at all and tends to hurt the FPS
significantly.  This is one of those things that would hugely
benefit from SIMD, but even without SIMD it could be done better.
I just slapped together something obvious for now, as I'd like to
focus more on the rkt side but need a better fader for scene
transitions than style=flicker.

Also changed {a,b}_module= preferred values to blank,compose
so you see something happen if you just run --defaults.
Otherwise, the compose,compose would just fade between two
identical compositions invisibly.
---
 src/modules/mixer/mixer.c | 147 +++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 127 insertions(+), 20 deletions(-)

diff --git a/src/modules/mixer/mixer.c b/src/modules/mixer/mixer.c
index f0cb174..7de91e6 100644
--- a/src/modules/mixer/mixer.c
+++ b/src/modules/mixer/mixer.c
@@ -16,6 +16,7 @@
 /* This implements a rudimentary mixing module for things like fades */
 
 typedef enum mixer_style_t {
+	MIXER_STYLE_FADE,
 	MIXER_STYLE_FLICKER,
 } mixer_style_t;
 
@@ -38,6 +39,7 @@ typedef struct mixer_context_t {
 	float			*T;
 
 	mixer_input_t		inputs[2];
+	til_fb_fragment_t	*snapshots[2];
 } mixer_context_t;
 
 typedef struct mixer_setup_input_t {
@@ -52,23 +54,7 @@ typedef struct mixer_setup_t {
 	mixer_setup_input_t	inputs[2];
 } mixer_setup_t;
 
-#define MIXER_DEFAULT_STYLE	MIXER_STYLE_FLICKER
-
-static til_module_context_t * mixer_create_context(const til_module_t *module, til_stream_t *stream, unsigned seed, unsigned ticks, unsigned n_cpus, til_setup_t *setup);
-static void mixer_destroy_context(til_module_context_t *context);
-static void mixer_render_fragment(til_module_context_t *context, til_stream_t *stream, unsigned ticks, unsigned cpu, til_fb_fragment_t **fragment_ptr);
-static int mixer_setup(const til_settings_t *settings, til_setting_t **res_setting, const til_setting_desc_t **res_desc, til_setup_t **res_setup);
-
-
-til_module_t	mixer_module = {
-	.create_context = mixer_create_context,
-	.destroy_context = mixer_destroy_context,
-	.render_fragment = mixer_render_fragment,
-	.name = "mixer",
-	.description = "Module blender",
-	.setup = mixer_setup,
-	.flags = TIL_MODULE_EXPERIMENTAL,
-};
+#define MIXER_DEFAULT_STYLE	MIXER_STYLE_FADE
 
 
 static til_module_context_t * mixer_create_context(const til_module_t *module, til_stream_t *stream, unsigned seed, unsigned ticks, unsigned n_cpus, til_setup_t *setup)
@@ -113,12 +99,15 @@ static inline float randf(unsigned *seed)
 	return 1.f / ((float)RAND_MAX) * rand_r(seed);
 }
 
-static void mixer_render_fragment(til_module_context_t *context, til_stream_t *stream, unsigned ticks, unsigned cpu, til_fb_fragment_t **fragment_ptr)
+
+static void mixer_prepare_frame(til_module_context_t *context, til_stream_t *stream, unsigned ticks, til_fb_fragment_t **fragment_ptr, til_frame_plan_t *res_frame_plan)
 {
 	mixer_context_t		*ctxt = (mixer_context_t *)context;
 	til_fb_fragment_t	*fragment = *fragment_ptr;
 	size_t			i = 0;
 
+	*res_frame_plan = (til_frame_plan_t){ .fragmenter = til_fragmenter_slice_per_cpu };
+
 	if (!til_stream_tap_context(stream, context, NULL, &ctxt->taps.T))
 		*ctxt->T = cosf(ticks * .001f) * .5f + .5f;
 
@@ -128,18 +117,121 @@ static void mixer_render_fragment(til_module_context_t *context, til_stream_t *s
 			i = 1;
 		else
 			i = 0;
+
+		til_module_render(ctxt->inputs[i].module_ctxt, stream, ticks, &fragment);
+		break;
+
+	case MIXER_STYLE_FADE:
+		if (*ctxt->T < 1.f) {
+			til_module_render(ctxt->inputs[0].module_ctxt, stream, ticks, &fragment);
+
+			if (*ctxt->T > 0.f)
+				ctxt->snapshots[0] = til_fb_fragment_snapshot(&fragment, 0);
+		}
+
+		if (*ctxt->T > 0.f) {
+			til_module_render(ctxt->inputs[1].module_ctxt, stream, ticks, &fragment);
+			if (*ctxt->T < 1.f)
+				ctxt->snapshots[1] = til_fb_fragment_snapshot(&fragment, 0);
+		}
 		break;
 
 	default:
 		assert(0);
 	}
 
-	til_module_render(ctxt->inputs[i].module_ctxt, stream, ticks, &fragment);
+	*fragment_ptr = fragment;
+}
+
+
+/* derived from modules/drizzle pixel_mult_scalar(), there's definitely room for optimizations */
+static inline uint32_t pixels_lerp(uint32_t a_pixel, uint32_t b_pixel, float t)
+{
+	uint32_t	pixel;
+	float		a, b;
+
+	/* r */
+	a = (a_pixel >> 16) & 0xff;
+	a *= 1.f - t;
+	b = (b_pixel >> 16) & 0xff;
+	b *= t;
+
+	pixel = (((uint32_t)(a+b)) << 16);
+
+	/* g */
+	a = (a_pixel >> 8) & 0xff;
+	a *= 1.f - t;
+	b = (b_pixel >> 8) & 0xff;
+	b *= t;
+
+	pixel |= (((uint32_t)(a+b)) << 8);
+
+	/* b */
+	a = a_pixel & 0xff;
+	a *= 1.f - t;
+	b = b_pixel & 0xff;
+	b *= t;
+
+	pixel |= ((uint32_t)(a+b));
+
+	return pixel;
+}
+
+
+static void mixer_render_fragment(til_module_context_t *context, til_stream_t *stream, unsigned ticks, unsigned cpu, til_fb_fragment_t **fragment_ptr)
+{
+	mixer_context_t		*ctxt = (mixer_context_t *)context;
+	til_fb_fragment_t	*fragment = *fragment_ptr;
+
+	switch (((mixer_setup_t *)context->setup)->style) {
+	case MIXER_STYLE_FLICKER:
+		/* handled in prepare_frame() */
+		break;
+
+	case MIXER_STYLE_FADE: {
+		float	T = *ctxt->T;
+
+		if (T <= 0.f || T  >= 1.f)
+			break;
+
+		/* for the tweens, we already have snapshots sitting in ctxt->snapshots[],
+		 * which we now interpolate the pixels out of in parallel
+		 */
+		for (int y = fragment->y; y < fragment->y + fragment->height; y++) {
+			for (int x = fragment->x; x < fragment->x + fragment->width; x++) {
+				uint32_t	a_pixel = til_fb_fragment_get_pixel_unchecked(ctxt->snapshots[0], x, y);
+				uint32_t	b_pixel = til_fb_fragment_get_pixel_unchecked(ctxt->snapshots[1], x, y);
+				uint32_t	pixel;
+
+				pixel = pixels_lerp(a_pixel, b_pixel, T);
+				til_fb_fragment_put_pixel_unchecked(fragment, 0, x, y, pixel);
+			}
+		}
+
+		break;
+	}
+
+	default:
+		assert(0);
+	}
 
 	*fragment_ptr = fragment;
 }
 
 
+static void mixer_finish_frame(til_module_context_t *context, til_stream_t *stream, unsigned int ticks, til_fb_fragment_t **fragment_ptr)
+{
+	mixer_context_t	*ctxt = (mixer_context_t *)context;
+
+	for (int i = 0; i < 2; i++) {
+		if (!ctxt->snapshots[i])
+			continue;
+
+		ctxt->snapshots[i] = til_fb_fragment_reclaim(ctxt->snapshots[i]);
+	}
+}
+
+
 static void mixer_setup_free(til_setup_t *setup)
 {
 	mixer_setup_t	*s = (mixer_setup_t *)setup;
@@ -157,6 +249,7 @@ static void mixer_setup_free(til_setup_t *setup)
 static int mixer_setup(const til_settings_t *settings, til_setting_t **res_setting, const til_setting_desc_t **res_desc, til_setup_t **res_setup)
 {
 	const char		*style_values[] = {
+					"fade",
 					"flicker",
 					NULL
 				};
@@ -185,6 +278,7 @@ static int mixer_setup(const til_settings_t *settings, til_setting_t **res_setti
 		const char *input_names[2] = { "First module to mix", "Second module to mix" };
 		const char *input_keys[2] = { "a_module", "b_module" };
 		const char *input_module_name_names[2] = { "First module's name", "Second module's name" };
+		const char *input_preferred[2] = { "blank", "compose" };
 
 		for (int i = 0; i < 2; i++) {
 			const til_module_t	*mod;
@@ -193,7 +287,7 @@ static int mixer_setup(const til_settings_t *settings, til_setting_t **res_setti
 								&(til_setting_spec_t){
 									.name = input_names[i],
 									.key = input_keys[i],
-									.preferred = "compose",
+									.preferred = input_preferred[i],
 									.annotations = NULL,
 									.as_nested_settings = 1,
 								},
@@ -282,3 +376,16 @@ static int mixer_setup(const til_settings_t *settings, til_setting_t **res_setti
 
 	return 0;
 }
+
+
+til_module_t	mixer_module = {
+	.create_context = mixer_create_context,
+	.destroy_context = mixer_destroy_context,
+	.prepare_frame = mixer_prepare_frame,
+	.render_fragment = mixer_render_fragment,
+	.finish_frame = mixer_finish_frame,
+	.name = "mixer",
+	.description = "Module blender",
+	.setup = mixer_setup,
+	.flags = TIL_MODULE_EXPERIMENTAL,
+};
-- 
cgit v1.2.3