diff options
| author | Vito Caputo <vcaputo@pengaru.com> | 2024-04-13 11:59:57 -0700 | 
|---|---|---|
| committer | Vito Caputo <vcaputo@pengaru.com> | 2025-06-14 14:05:08 -0700 | 
| commit | 60157752d9cb030b8545f8cde93b13813c621fd5 (patch) | |
| tree | 525bbb5e9889e806966296c86ad7642c3ca57e89 /src | |
| parent | 79a763cee752e272319572792f4c8431f0d4eca9 (diff) | |
modules/droste: implement a droste effect
Diffstat (limited to 'src')
| -rw-r--r-- | src/Makefile.am | 1 | ||||
| -rw-r--r-- | src/modules/Makefile.am | 1 | ||||
| -rw-r--r-- | src/modules/droste/Makefile.am | 3 | ||||
| -rw-r--r-- | src/modules/droste/droste.c | 353 | ||||
| -rw-r--r-- | src/til.c | 2 | 
5 files changed, 360 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index d2fe42b..454d1ca 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -41,6 +41,7 @@ libtil_la_LIBADD =						\  			modules/checkers/libcheckers.la		\  			modules/compose/libcompose.la		\  			modules/drizzle/libdrizzle.la		\ +			modules/droste/libdroste.la		\  			modules/flow/libflow.la			\  			modules/flui2d/libflui2d.la		\  			modules/julia/libjulia.la		\ diff --git a/src/modules/Makefile.am b/src/modules/Makefile.am index 825295d..74ec649 100644 --- a/src/modules/Makefile.am +++ b/src/modules/Makefile.am @@ -5,6 +5,7 @@ SUBDIRS =			\  		checkers	\  		compose		\  		drizzle		\ +		droste		\  		flow		\  		flui2d		\  		julia		\ diff --git a/src/modules/droste/Makefile.am b/src/modules/droste/Makefile.am new file mode 100644 index 0000000..3285815 --- /dev/null +++ b/src/modules/droste/Makefile.am @@ -0,0 +1,3 @@ +noinst_LTLIBRARIES = libdroste.la +libdroste_la_SOURCES = droste.c +libdroste_la_CPPFLAGS = -I@top_srcdir@/src -I@top_srcdir@/src/libs diff --git a/src/modules/droste/droste.c b/src/modules/droste/droste.c new file mode 100644 index 0000000..9475df7 --- /dev/null +++ b/src/modules/droste/droste.c @@ -0,0 +1,353 @@ +/* + *  Copyright (C) 2024-2025 - Vito Caputo - <vcaputo@pengaru.com> + * + *  This program is free software: you can redistribute it and/or modify it + *  under the terms of the GNU General Public License version 2 as published + *  by the Free Software Foundation. + * + *  This program is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + *  GNU General Public License for more details. + * + *  You should have received a copy of the GNU General Public License + *  along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdint.h> + +#include "til.h" +#include "til_fb.h" +#include "til_module_context.h" + +/* This implements a "droste effect", also known as an infinity mirror: + * https://en.wikipedia.org/wiki/Droste_effect + * https://en.wikipedia.org/wiki/Infinity_mirror + */ + +/* Some potential TODO items: + * + * - Fractional, or at least runtime-configurable scaling... though + *   exposing a tap for it would be fun + * + * - Runtime-configurable multisampled scaling (slow though) + * + * - The current implementation is very simple but relies on the + *   preservation of original contents in til_fb_fragment_snapshot(), + *   which causes a full copy.  This is wasteful since we only want + *   the unzoomed periphery preserved from the original.  Maybe there + *   should be a clip mask for the preservation in + *   til_fb_fragment_snapshot(), or just do the peripheral copy ourselves + *   and stop asking the snapshot code to do that for us. + * + * - The base module is currently always setup but conditionally used if + *   (!fragment->cleared).  It should probably be possible to specify + *   forcing the base module's render + * + */ + +#define DROSTE_DEFAULT_BASE_MODULE	"blinds" + +typedef struct droste_context_t { +	til_module_context_t	til_module_context; + +	til_module_context_t	*base_module_context;	/* a base module is used for non-overlay situations */ +	til_fb_fragment_t	*snapshot; +} droste_context_t; + +typedef struct droste_setup_t { +	til_setup_t		til_setup; + +	til_setup_t		*base_module_setup; +} droste_setup_t; + + +static til_module_context_t * droste_create_context(const til_module_t *module, til_stream_t *stream, unsigned seed, unsigned ticks, unsigned n_cpus, til_setup_t *setup) +{ +	droste_context_t	*ctxt; + +	if (!((droste_setup_t *)setup)->base_module_setup) +		return NULL; + +	ctxt = til_module_context_new(module, sizeof(droste_context_t), stream, seed, ticks, n_cpus, setup); +	if (!ctxt) +		return NULL; + +	{ +		const til_module_t	*module = ((droste_setup_t *)setup)->base_module_setup->creator; + +		if (til_module_create_contexts(module, +					       stream, +					       seed, +					       ticks, +					       n_cpus, +					       ((droste_setup_t *)setup)->base_module_setup, +					       1, +					       &ctxt->base_module_context) < 0) +			return til_module_context_free(&ctxt->til_module_context); +	} + +	return &ctxt->til_module_context; +} + + +static void droste_destroy_context(til_module_context_t *context) +{ +	droste_context_t	*ctxt = (droste_context_t *)context; + +	if (ctxt->snapshot) +		ctxt->snapshot = til_fb_fragment_reclaim(ctxt->snapshot); + +	til_module_context_free(ctxt->base_module_context); +	free(context); +} + + +/* derived from til_fragmenter_slice_per_cpu_x16(), but tweaked to only fragment the inset area */ +static int droste_fragmenter(til_module_context_t *context, const til_fb_fragment_t *fragment, unsigned number, til_fb_fragment_t *res_fragment) +{ +	til_fb_fragment_t	inset = *fragment; +	unsigned		slice, yoff; + +	assert(fragment); +	assert(res_fragment); + +	inset.width = fragment->width >> 1; +	inset.height = fragment->height >> 1; +	inset.frame_width = inset.width; +	inset.frame_height = inset.height; +	inset.x = inset.y = 0; +	inset.buf += inset.pitch * ((fragment->height - inset.height) >> 1) + ((fragment->width - inset.width) >> 1); +	inset.stride += fragment->width >> 1; + +	slice = MAX(inset.height / context->n_cpus * 16, 1); +	yoff = slice * number; + +	if (yoff >= inset.height) +		return 0; + +	if (fragment->texture) { +		til_fb_fragment_t	inset_texture; + +		inset_texture = *(fragment->texture); +		inset.texture = &inset_texture; + +		/* TODO */ + +		assert(res_fragment->texture); +		assert(fragment->frame_width == fragment->texture->frame_width); +		assert(fragment->frame_height == fragment->texture->frame_height); +		assert(fragment->width == fragment->texture->width); +		assert(fragment->height == fragment->texture->height); +		assert(fragment->x == fragment->texture->x); +		assert(fragment->y == fragment->texture->y); + +		*(res_fragment->texture) = (til_fb_fragment_t){ +				.buf = fragment->texture->buf + yoff * fragment->texture->pitch, +				.x = fragment->x, +				.y = fragment->y + yoff, +				.width = fragment->width, +				.height = MIN(fragment->height - yoff, slice), +				.frame_width = fragment->frame_width, +				.frame_height = fragment->frame_height, +				.stride = fragment->texture->stride, +				.pitch = fragment->texture->pitch, +				.cleared = fragment->texture->cleared, +		}; + +	} + +	*res_fragment = (til_fb_fragment_t){ +				.texture = inset.texture ? res_fragment->texture : NULL, +				.buf = inset.buf + yoff * inset.pitch, +				.x = inset.x, +				.y = inset.y + yoff, +				.width = inset.width, +				.height = MIN(inset.height - yoff, slice), +				.frame_width = inset.frame_width, +				.frame_height = inset.frame_height, +				.stride = inset.stride, +				.pitch = inset.pitch, +				.number = number, +				.cleared = inset.cleared, +			}; + +	return 1; + +} + + +/* Prepare a frame for concurrent drawing of fragment using multiple fragments */ +static void droste_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) +{ +	droste_context_t	*ctxt = (droste_context_t *)context; + +	if (!(*fragment_ptr)->cleared) +		til_module_render(ctxt->base_module_context, stream, ticks, fragment_ptr); + +	*res_frame_plan = (til_frame_plan_t){ .fragmenter = droste_fragmenter }; + +	{ +		til_fb_fragment_t	*fragment = *fragment_ptr; +		til_fb_fragment_t	*snapshot = ctxt->snapshot; + +		if (!snapshot) +			return; + +		if (fragment->frame_width != snapshot->frame_width || +		    fragment->frame_height != snapshot->frame_height || +		    fragment->height != snapshot->height || +		    fragment->width != snapshot->width) { + +			/* discard the snapshot which will prevent doing anything this frame, +			 * since it doesn't match the incoming fragment (like a resize situation) +			 */ +			ctxt->snapshot = til_fb_fragment_reclaim(ctxt->snapshot); + +			return; +		} + +		/* TODO: if we're not used as an overlay, here'd be a good place to generate something or +		 * just use another module as a base layer...  until we do something sensible here, we should +		 * keep this as an experimental module so it doesn't get used by automation as a base layer.. +		 * it also needs something to show in montage. +		 */ +	} +} + + +static void droste_render_fragment(til_module_context_t *context, til_stream_t *stream, unsigned ticks, unsigned cpu, til_fb_fragment_t **fragment_ptr) +{ +	droste_context_t	*ctxt = (droste_context_t *)context; +	til_fb_fragment_t	*fragment = *fragment_ptr; +	til_fb_fragment_t	*snapshot = ctxt->snapshot; + +	if (!snapshot) +		return; + +	for (unsigned y = fragment->y; y < fragment->y + fragment->height; y++) { +		for (unsigned x = fragment->x; x < fragment->x + fragment->width; x++) { +			uint32_t	color; + +			color = til_fb_fragment_get_pixel_clipped(snapshot, x << 1, y << 1); +			til_fb_fragment_put_pixel_unchecked(fragment, 0, x, y, color); +		} +	} +} + + +static int droste_finish_frame(til_module_context_t *context, til_stream_t *stream, unsigned int ticks, til_fb_fragment_t **fragment_ptr) +{ +	droste_context_t	*ctxt = (droste_context_t *)context; + +	/* if we have a stowed frame clean it up */ +	if (ctxt->snapshot) +		ctxt->snapshot = til_fb_fragment_reclaim(ctxt->snapshot); + +	/* stow the new frame for the next time around to sample from */ +	ctxt->snapshot = til_fb_fragment_snapshot(fragment_ptr, 1); + +	return 0; +} + + +static void droste_setup_free(til_setup_t *setup) +{ +	droste_setup_t	*s = (droste_setup_t *)setup; + +	til_setup_free(s->base_module_setup); +	free(setup); +} + + +static int droste_base_module_setup(const til_settings_t *settings, til_setting_t **res_setting, const til_setting_desc_t **res_desc, til_setup_t **res_setup) +{ +	return til_module_setup_full(settings, +				     res_setting, +				     res_desc, +				     res_setup, +				     "Base module name", +				     DROSTE_DEFAULT_BASE_MODULE, +				     (TIL_MODULE_EXPERIMENTAL | TIL_MODULE_HERMETIC | TIL_MODULE_AUDIO_ONLY), +				     NULL); +} + + +static int droste_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	droste_module = { +	.create_context = droste_create_context, +	.destroy_context = droste_destroy_context, +	.prepare_frame = droste_prepare_frame, +	.render_fragment = droste_render_fragment, +	.finish_frame = droste_finish_frame, +	.setup = droste_setup, +	.name = "droste", +	.description = "Droste effect (threaded)", +	.author = "Vito Caputo <vcaputo@pengaru.com>", +	.flags = TIL_MODULE_OVERLAYABLE, +}; + + +static int droste_setup(const til_settings_t *settings, til_setting_t **res_setting, const til_setting_desc_t **res_desc, til_setup_t **res_setup) +{ +	til_setting_t		*base_module; +	const til_settings_t	*base_module_settings; +	const char		*base_module_values[] = { +					"blinds", +					"book", +					"moire", +					"plasma", +					"plato", +					"roto", +					NULL +				}; +	int			r; + +	r = til_settings_get_and_describe_setting(settings, +						&(til_setting_spec_t){ +							.name = "Base module", +							.key = "base_module", +							.preferred = base_module_values[0], +							.values = base_module_values, +							.annotations = NULL, +							.as_nested_settings = 1, +						}, +						&base_module, /* XXX: this isn't really of direct use now that it's a potentially full-blown settings string, see base_module_settings */ +						res_setting, +						res_desc); +	if (r) +		return r; + +	base_module_settings = base_module->value_as_nested_settings; +	assert(base_module_settings); + +	r = droste_base_module_setup(base_module_settings, +				   res_setting, +				   res_desc, +				   NULL); /* XXX: note no res_setup, must defer finalize */ +	if (r) +		return r; + +	if (res_setup) { +		droste_setup_t	*setup; + +		setup = til_setup_new(settings, sizeof(*setup), droste_setup_free, &droste_module); +		if (!setup) +			return -ENOMEM; + +		r = droste_base_module_setup(base_module_settings, +					   res_setting, +					   res_desc, +					   &setup->base_module_setup); /* finalize! */ +		if (r < 0) +			return til_setup_free_with_ret_err(&setup->til_setup, r); + +		assert(r == 0); + +		*res_setup = &setup->til_setup; +	} + +	return 0; +} @@ -33,6 +33,7 @@ extern til_module_t	book_module;  extern til_module_t	checkers_module;  extern til_module_t	compose_module;  extern til_module_t	drizzle_module; +extern til_module_t	droste_module;  extern til_module_t	flow_module;  extern til_module_t	flui2d_module;  extern til_module_t	julia_module; @@ -77,6 +78,7 @@ static const til_module_t	*modules[] = {  	&checkers_module,  	&compose_module,  	&drizzle_module, +	&droste_module,  	&flow_module,  	&flui2d_module,  	&julia_module,  | 
