From e80fd944d333023af333244d2b2b44369126fdcb Mon Sep 17 00:00:00 2001 From: Vito Caputo Date: Sat, 13 Apr 2024 11:59:57 -0700 Subject: modules/droste: implement a droste overlay effect --- configure.ac | 1 + src/Makefile.am | 1 + src/modules/Makefile.am | 1 + src/modules/droste/Makefile.am | 3 + src/modules/droste/droste.c | 231 +++++++++++++++++++++++++++++++++++++++++ src/til.c | 2 + 6 files changed, 239 insertions(+) create mode 100644 src/modules/droste/Makefile.am create mode 100644 src/modules/droste/droste.c diff --git a/configure.ac b/configure.ac index 71a47f3..713f5ba 100644 --- a/configure.ac +++ b/configure.ac @@ -48,6 +48,7 @@ AC_CONFIG_FILES([ src/modules/checkers/Makefile src/modules/compose/Makefile src/modules/drizzle/Makefile + src/modules/droste/Makefile src/modules/flow/Makefile src/modules/flui2d/Makefile src/modules/julia/Makefile 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..41a1fcc --- /dev/null +++ b/src/modules/droste/droste.c @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2024 - Vito Caputo - + * + * 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 . + */ + +#include + +#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: + * + * - A default base layer so we can clear the experimental flag + * + * - Fractional, or at least runtime-configurable scaling... though + * exposing a tap for it would be fun + * + * - Runtime-configurable multisampled scaling + * + * - 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. + * + */ + +typedef struct droste_context_t { + til_module_context_t til_module_context; + + til_fb_fragment_t *snapshot; +} droste_context_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; + + ctxt = til_module_context_new(module, sizeof(droste_context_t), stream, seed, ticks, n_cpus, setup); + if (!ctxt) + return NULL; + + 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); + + 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) +{ + *res_frame_plan = (til_frame_plan_t){ .fragmenter = droste_fragmenter }; + + { + 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; + + 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; +} + + +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, + .name = "droste", + .description = "Droste effect (threaded)", + .author = "Vito Caputo ", + .flags = TIL_MODULE_OVERLAYABLE | TIL_MODULE_EXPERIMENTAL, +}; diff --git a/src/til.c b/src/til.c index 709aabb..043b8ae 100644 --- a/src/til.c +++ b/src/til.c @@ -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, -- cgit v1.2.1