summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVito Caputo <vcaputo@pengaru.com>2024-04-13 11:59:57 -0700
committerVito Caputo <vcaputo@pengaru.com>2024-04-13 11:59:57 -0700
commite80fd944d333023af333244d2b2b44369126fdcb (patch)
tree21a65a88db5073c29767cae8937b31f0f43d66f7
parente857fdf9828267d8df859bcf3c88c3d88f12d462 (diff)
modules/droste: implement a droste overlay effectdroste
-rw-r--r--configure.ac1
-rw-r--r--src/Makefile.am1
-rw-r--r--src/modules/Makefile.am1
-rw-r--r--src/modules/droste/Makefile.am3
-rw-r--r--src/modules/droste/droste.c231
-rw-r--r--src/til.c2
6 files changed, 239 insertions, 0 deletions
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 - <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:
+ *
+ * - 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 <vcaputo@pengaru.com>",
+ .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,
© All Rights Reserved