summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README77
-rw-r--r--configure.ac1
-rw-r--r--src/Makefile.am1
-rw-r--r--src/drm_fb.c16
-rw-r--r--src/main.c5
-rw-r--r--src/modules/Makefile.am1
-rw-r--r--src/modules/droste/Makefile.am3
-rw-r--r--src/modules/droste/droste.c349
-rw-r--r--src/modules/julia/julia.c45
-rw-r--r--src/modules/mixer/mixer.c86
-rw-r--r--src/modules/montage/montage.c6
-rw-r--r--src/modules/plasma/plasma.c5
-rw-r--r--src/modules/ray/ray.c21
-rw-r--r--src/setup.c3
-rw-r--r--src/til.c16
15 files changed, 555 insertions, 80 deletions
diff --git a/README b/README
index 219e6bb..911a566 100644
--- a/README
+++ b/README
@@ -51,47 +51,54 @@ Quickly jump sections of this text by searching forward/reverse for ---
tool `rototiller` without any flags. This enters an interactive setup
mode, beginning with selecting the module to use from a list.
- Here's what's output as of writing this document:
+ Here's what's output as of last updating this document:
```
$ build/src/rototiller
/module:
Renderer module:
- 0: blinds: Retro 80s-inspired window blinds (threaded)
- 1: checkers: Checker-patterned overlay (threaded)
- 2: compose: Layered modules compositor
- 3: drizzle: Classic 2D rain effect (threaded (poorly))
- 4: flow: 3D flow field (threaded)
- 5: flui2d: Fluid dynamics simulation in 2D (threaded (poorly))
- 6: julia: Julia set fractal morpher (threaded)
- 7: meta2d: Classic 2D metaballs (threaded)
- 8: mixer: Module blender (threaded)
- 9: moire: 2D Moire interference patterns (threaded)
- 10: montage: Rototiller montage (threaded)
- 11: pixbounce: Pixmap bounce (threaded)
- 12: plasma: Oldskool plasma effect (threaded)
- 13: plato: Platonic solids rendered in 3D
- 14: ray: Ray tracer (threaded)
- 15: rkt: GNU Rocket module sequencer
- 16: roto: Anti-aliased tiled texture rotation (threaded)
- 17: rtv: Rototiller TV
- 18: shapes: Procedural 2D shapes (threaded)
- 19: snow: TV snow / white noise (threaded)
- 20: sparkler: Particle system with spatial interactions (threaded (poorly))
- 21: spiro: Spirograph emulator
- 22: stars: Basic starfield
- 23: strobe: Strobe light (threaded)
- 24: submit: Cellular automata conquest game sim (threaded (poorly))
- 25: swab: Colorful perlin-noise visualization (threaded)
- 26: swarm: "Boids"-inspired particle swarm in 3D
- 27: voronoi: Voronoi diagram (threaded)
- 28: blank: Blanker (built-in)
- 29: none: Disabled (built-in)
- 30: noop: Nothing-doer (built-in)
- 31: ref: Context referencer (built-in)
- Enter a value 0-31 [17 (rtv)]:
- ```
+ 0: book: Flipbook module
+ 1: blinds: Retro 80s-inspired window blinds (threaded)
+ 2: checkers: Checker-patterned overlay (threaded)
+ 3: compose: Layered modules compositor
+ 4: drizzle: Classic 2D rain effect (threaded (poorly))
+ 5: droste: Droste effect (threaded)
+ 6: flow: 3D flow field (threaded)
+ 7: flui2d: Fluid dynamics simulation in 2D (threaded (poorly))
+ 8: julia: Julia set fractal morpher (threaded)
+ 9: meta2d: Classic 2D metaballs (threaded)
+ 10: mixer: Module blender (threaded)
+ 11: moire: 2D Moire interference patterns (threaded)
+ 12: montage: Rototiller montage (threaded)
+ 13: pan: Simple panning effect (threaded)
+ 14: pixbounce: Pixmap bounce (threaded)
+ 15: plasma: Oldskool plasma effect (threaded)
+ 16: plato: Platonic solids rendered in 3D
+ 17: playit: .IT tracked music file player
+ 18: ray: Ray tracer (threaded)
+ 19: rkt: GNU Rocket module sequencer
+ 20: roto: Anti-aliased tiled texture rotation (threaded)
+ 21: rtv: Rototiller TV
+ 22: shapes: Procedural 2D shapes (threaded)
+ 23: snow: TV snow / white noise (threaded)
+ 24: sparkler: Particle system with spatial interactions (threaded (poorly))
+ 25: spiro: Spirograph emulator
+ 26: spokes: Twisted spokes
+ 27: stars: Basic starfield
+ 28: strobe: Strobe light (threaded)
+ 29: submit: Cellular automata conquest game sim (threaded (poorly))
+ 30: swab: Colorful perlin-noise visualization (threaded)
+ 31: swarm: "Boids"-inspired particle swarm in 3D
+ 32: voronoi: Voronoi diagram (threaded)
+ 33: asc: ASCII text (built-in)
+ 34: blank: Blanker (built-in)
+ 35: none: Disabled (built-in)
+ 36: noop: Nothing-doer (built-in)
+ 37: pre: Pre-render hook registration (built-in)
+ 38: ref: Context referencer (built-in)
+ Enter a value 0-38 [21 (rtv)]:
+ ```
Most modules are simple rendering modules, implementing a single
real-time graphics algorithm.
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/drm_fb.c b/src/drm_fb.c
index 8ba097c..7ac6f3f 100644
--- a/src/drm_fb.c
+++ b/src/drm_fb.c
@@ -339,12 +339,19 @@ static int drm_fb_init(const char *title, const til_video_setup_t *setup, void *
int r;
assert(setup);
-
+#if 0
+ /* This has proven to produce false negatives - on my X230 now I for some reason
+ * only have /dev/dri/card1, no card0, and this is causing libdrm to fail here.
+ * But if I disable this check, and specify /dev/dri/card1 instead of card0, I'm
+ * able to use rototiller on drm. I don't have time to investigate why libdrm
+ * is broken in this check, so I'm just disabling this check.
+ * FIXME
+ */
if (!drmAvailable()) {
r = -errno;
goto _err;
}
-
+#endif
if (!s->dev || !s->connector || !s->mode) {
r = -EINVAL;
goto _err;
@@ -592,10 +599,11 @@ static int drm_fb_setup(const til_settings_t *settings, til_setting_t **res_sett
.func = mode_desc_generator
},
};
-
+#if 0
+ /* FIXME: see comment above by other drmAvailable() call. */
if (!drmAvailable())
return -ENOSYS;
-
+#endif
if (!setup)
return -ENOMEM;
diff --git a/src/main.c b/src/main.c
index 28f897f..e4b0cbc 100644
--- a/src/main.c
+++ b/src/main.c
@@ -451,7 +451,10 @@ static int print_setup_as_args(setup_t *setup, int wait)
if (r < 0)
goto _out_video;
- (void) fgets(buf, sizeof(buf), stdin);
+ if (fgets(buf, sizeof(buf), stdin) == NULL) {
+ r = -EBADF;
+ goto _out_video;
+ }
}
_out_video:
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..717e513
--- /dev/null
+++ b/src/modules/droste/droste.c
@@ -0,0 +1,349 @@
+/*
+ * 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);
+
+ /* This should be using half of the frame_{width,height}, but must be clipped
+ * to the fragment dimensions. As-is this is quite broken in scenarios like droste in a checkers filler
+ * when the perimeter checkers get clipped. FIXME FIXME FIXME
+ */
+ 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(snapshot);
+ }
+ }
+}
+
+
+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;
+}
diff --git a/src/modules/julia/julia.c b/src/modules/julia/julia.c
index 82c899f..25f9d80 100644
--- a/src/modules/julia/julia.c
+++ b/src/modules/julia/julia.c
@@ -112,23 +112,34 @@ static void julia_prepare_frame(til_module_context_t *context, til_stream_t *str
*res_frame_plan = (til_frame_plan_t){ .fragmenter = til_fragmenter_slice_per_cpu_x16 };
- ctxt->rr += .01;
- /* Rather than just sweeping creal,cimag from -2.0-+2.0, I try to keep things confined
- * to an interesting (visually) range. TODO: could certainly use refinement.
- */
- ctxt->realscale = 0.01f * cosf(ctxt->rr) + 0.01f;
- ctxt->imagscale = 0.01f * sinf(ctxt->rr * 3.0f) + 0.01f;
- ctxt->creal = (1.01f + (ctxt->realscale * cosf(1.5f * M_PI + ctxt->rr) + ctxt->realscale)) * cosf(ctxt->rr * .3f);
- ctxt->cimag = (1.01f + (ctxt->imagscale * sinf(ctxt->rr * 3.0f) + ctxt->imagscale)) * sinf(ctxt->rr);
-
- /* Vary the divergent threshold, this has been tuned to dwell around 1 a bit since it's
- * quite different looking, then shoot up to a very huge value approaching FLT_MAX which
- * is also interesting.
- */
- ctxt->threshold = cosf(M_PI + ctxt->rr * .1f) * .5f + .5f;
- ctxt->threshold *= ctxt->threshold * ctxt->threshold;
- ctxt->threshold *= 35.f;
- ctxt->threshold = powf(10.f, ctxt->threshold);
+ if (ticks != context->last_ticks) {
+ /* TODO: this cumulative state in the context is problematic.
+ * It'd be better to absolutely derive this from ticks every prepare, so we could do things like
+ * rewind and jump around by changing ticks. As-s, this is assuming rr advances a constant rate
+ * in a uniform direction.
+ * To behave better in multi-render situations like as a checkers fill_module, this was all made
+ * conditional on ticks differing from context->last_ticks, so it would at least suppress accumulating
+ * more movement when rendered more times in a given frame... but it should just all be reworked.
+ * roto has the same problem, these asssumptions are from a simpler time.
+ */
+ ctxt->rr += .01;
+ /* Rather than just sweeping creal,cimag from -2.0-+2.0, I try to keep things confined
+ * to an interesting (visually) range. TODO: could certainly use refinement.
+ */
+ ctxt->realscale = 0.01f * cosf(ctxt->rr) + 0.01f;
+ ctxt->imagscale = 0.01f * sinf(ctxt->rr * 3.0f) + 0.01f;
+ ctxt->creal = (1.01f + (ctxt->realscale * cosf(1.5f * M_PI + ctxt->rr) + ctxt->realscale)) * cosf(ctxt->rr * .3f);
+ ctxt->cimag = (1.01f + (ctxt->imagscale * sinf(ctxt->rr * 3.0f) + ctxt->imagscale)) * sinf(ctxt->rr);
+
+ /* Vary the divergent threshold, this has been tuned to dwell around 1 a bit since it's
+ * quite different looking, then shoot up to a very huge value approaching FLT_MAX which
+ * is also interesting.
+ */
+ ctxt->threshold = cosf(M_PI + ctxt->rr * .1f) * .5f + .5f;
+ ctxt->threshold *= ctxt->threshold * ctxt->threshold;
+ ctxt->threshold *= 35.f;
+ ctxt->threshold = powf(10.f, ctxt->threshold);
+ }
}
diff --git a/src/modules/mixer/mixer.c b/src/modules/mixer/mixer.c
index 3012f1e..8e29597 100644
--- a/src/modules/mixer/mixer.c
+++ b/src/modules/mixer/mixer.c
@@ -28,6 +28,7 @@ typedef enum mixer_style_t {
MIXER_STYLE_INTERLACE,
MIXER_STYLE_PAINTROLLER,
MIXER_STYLE_SINE,
+ MIXER_STYLE_DISSOLVE,
} mixer_style_t;
typedef enum mixer_orientation_t {
@@ -45,6 +46,11 @@ typedef enum mixer_bottom_t {
MIXER_BOTTOM_B,
} mixer_bottom_t;
+typedef enum mixer_stability_t {
+ MIXER_STABILITY_UNSTABLE,
+ MIXER_STABILITY_STABLE,
+} mixer_stability_t;
+
typedef struct mixer_input_t {
til_module_context_t *module_ctxt;
/* XXX: it's expected that inputs will get more settable attributes to stick in here */
@@ -85,6 +91,7 @@ typedef struct mixer_setup_t {
mixer_orientation_t orientation;
mixer_tee_t tee;
mixer_bottom_t bottom;
+ mixer_stability_t stability;
unsigned n_passes;
} mixer_setup_t;
@@ -93,6 +100,7 @@ typedef struct mixer_setup_t {
#define MIXER_DEFAULT_ORIENTATION MIXER_ORIENTATION_VERTICAL
#define MIXER_DEFAULT_BOTTOM MIXER_BOTTOM_A
#define MIXER_DEFAULT_TEE MIXER_TEE_NORMAL
+#define MIXER_DEFAULT_STABILITY MIXER_STABILITY_STABLE
static void mixer_update_taps(mixer_context_t *ctxt, til_stream_t *stream, unsigned ticks)
{
@@ -186,6 +194,8 @@ static void mixer_prepare_frame(til_module_context_t *context, til_stream_t *str
break;
case MIXER_STYLE_INTERLACE:
+ /* fallthrough */
+ case MIXER_STYLE_DISSOLVE:
for (int i = 0; i < context->n_cpus; i++)
ctxt->seeds[i].state = rand_r(&context->seed);
/* fallthrough */
@@ -491,6 +501,49 @@ static void mixer_render_fragment(til_module_context_t *context, til_stream_t *s
break;
}
+ /* dissolve is like a static/noise transition, except stable for a given context */
+ case MIXER_STYLE_DISSOLVE: {
+ til_fb_fragment_t *snapshot_b;
+ float T = mixer_get_T(ctxt);
+
+ if (T <= 0.001f || T >= .999f)
+ break;
+
+ assert(ctxt->snapshots[1]);
+
+ /* to make the dissolve "stable" we deterministically set the random seed for
+ * a given fragment.
+ */
+ switch (((mixer_setup_t *)context->setup)->stability) {
+ case MIXER_STABILITY_UNSTABLE:
+ ctxt->seeds[cpu].state = rand_r(&(unsigned){context->seed + fragment->number});
+ break;
+ case MIXER_STABILITY_STABLE:
+ ctxt->seeds[cpu].state = rand_r(&(unsigned){fragment->number});
+ break;
+ default:
+ assert(0);
+ }
+
+ snapshot_b = ctxt->snapshots[1];
+
+ for (unsigned y = 0; y < fragment->height; y++) {
+ int ycoord = fragment->y + y;
+
+ for (unsigned x = 0; x < fragment->width; x++) {
+ int xcoord = fragment->x + x;
+ uint32_t pixel;
+
+ if (randf(&ctxt->seeds[cpu].state) >= T)
+ continue;
+
+ pixel = til_fb_fragment_get_pixel_unchecked(snapshot_b, xcoord, ycoord);
+ til_fb_fragment_put_pixel_unchecked(fragment, 0, xcoord, ycoord, pixel);
+ }
+ }
+ break;
+ }
+
default:
assert(0);
}
@@ -582,6 +635,7 @@ static int mixer_setup(const til_settings_t *settings, til_setting_t **res_setti
"interlace",
"paintroller",
"sine",
+ "dissolve",
NULL
};
const char *passes_values[] = {
@@ -611,11 +665,17 @@ static int mixer_setup(const til_settings_t *settings, til_setting_t **res_setti
"inverted",
NULL
};
+ const char *stability_values[] = {
+ "unstable",
+ "stable",
+ NULL
+ };
til_setting_t *style;
til_setting_t *passes;
til_setting_t *orientation;
til_setting_t *bottom;
til_setting_t *tee;
+ til_setting_t *stability;
const til_settings_t *inputs_settings[2];
til_setting_t *inputs[2];
int r;
@@ -655,7 +715,8 @@ static int mixer_setup(const til_settings_t *settings, til_setting_t **res_setti
*/
if (!strcasecmp(style->value, style_values[MIXER_STYLE_INTERLACE]) ||
!strcasecmp(style->value, style_values[MIXER_STYLE_PAINTROLLER]) ||
- !strcasecmp(style->value, style_values[MIXER_STYLE_SINE])) {
+ !strcasecmp(style->value, style_values[MIXER_STYLE_SINE]) ||
+ !strcasecmp(style->value, style_values[MIXER_STYLE_DISSOLVE])) {
r = til_settings_get_and_describe_setting(settings,
&(til_setting_spec_t){
@@ -698,6 +759,20 @@ static int mixer_setup(const til_settings_t *settings, til_setting_t **res_setti
res_desc);
if (r)
return r;
+
+ } else if (!strcasecmp(style->value, style_values[MIXER_STYLE_DISSOLVE])) {
+ r = til_settings_get_and_describe_setting(settings,
+ &(til_setting_spec_t){
+ .name = "Dissolve stability",
+ .key = "stability",
+ .values = stability_values,
+ .preferred = stability_values[MIXER_DEFAULT_STABILITY],
+ },
+ &stability,
+ res_setting,
+ res_desc);
+ if (r)
+ return r;
}
for (int i = 0; i < 2; i++) {
@@ -758,9 +833,18 @@ static int mixer_setup(const til_settings_t *settings, til_setting_t **res_setti
case MIXER_STYLE_INTERLACE:
/* fallthrough */
case MIXER_STYLE_SINE:
+ /* fallthrough */
+ case MIXER_STYLE_DISSOLVE:
r = til_value_to_pos(bottom_values, bottom->value, (unsigned *)&setup->bottom);
if (r < 0)
return til_setup_free_with_failed_setting_ret_err(&setup->til_setup, bottom, res_setting, -EINVAL);
+
+ if (setup->style == MIXER_STYLE_DISSOLVE) {
+ /* TODO: stability should likely apply to interlace too */
+ r = til_value_to_pos(stability_values, stability->value, (unsigned *)&setup->stability);
+ if (r < 0)
+ return til_setup_free_with_failed_setting_ret_err(&setup->til_setup, stability, res_setting, -EINVAL);
+ }
break;
default:
diff --git a/src/modules/montage/montage.c b/src/modules/montage/montage.c
index 7d20389..ff374bf 100644
--- a/src/modules/montage/montage.c
+++ b/src/modules/montage/montage.c
@@ -287,9 +287,9 @@ static int montage_setup(const til_settings_t *settings, til_setting_t **res_set
}
if (res_setup) {
- size_t n_tiles = til_settings_get_count(tiles_settings);
- til_setting_t *tile_setting;
- montage_setup_t *setup;
+ size_t n_tiles = til_settings_get_count(tiles_settings);
+ til_setting_t *tile_setting;
+ montage_setup_t *setup;
setup = til_setup_new(settings, sizeof(*setup) + n_tiles * sizeof(*setup->tiles), montage_setup_free, &montage_module);
if (!setup)
diff --git a/src/modules/plasma/plasma.c b/src/modules/plasma/plasma.c
index de14ed1..ddd58d8 100644
--- a/src/modules/plasma/plasma.c
+++ b/src/modules/plasma/plasma.c
@@ -80,7 +80,10 @@ static void plasma_prepare_frame(til_module_context_t *context, til_stream_t *st
plasma_context_t *ctxt = (plasma_context_t *)context;
*res_frame_plan = (til_frame_plan_t){ .fragmenter = til_fragmenter_slice_per_cpu_x16 };
- ctxt->rr += 3;
+
+ /* FIXME: see comment in julia.c about this recurring kludge */
+ if (ticks != context->last_ticks)
+ ctxt->rr += 3;
}
diff --git a/src/modules/ray/ray.c b/src/modules/ray/ray.c
index 4c4bdd0..3b66de0 100644
--- a/src/modules/ray/ray.c
+++ b/src/modules/ray/ray.c
@@ -123,12 +123,11 @@ static ray_scene_t scene = {
.gamma = .55f,
};
-static float r;
-
typedef struct ray_context_t {
til_module_context_t til_module_context;
ray_render_t *render;
+ float r;
} ray_context_t;
@@ -154,24 +153,26 @@ static void ray_prepare_frame(til_module_context_t *context, til_stream_t *strea
#if 1
/* animated point light source */
- r += -.02;
+ /* FIXME: see comment in julia.c about this recurring kludge */
+ if (ticks != context->last_ticks)
+ ctxt->r += -.02;
- scene.lights[0].light.emitter.point.center.x = cosf(r) * 4.5f;
- scene.lights[0].light.emitter.point.center.z = sinf(r * 3.0f) * 4.5f;
+ scene.lights[0].light.emitter.point.center.x = cosf(ctxt->r) * 4.5f;
+ scene.lights[0].light.emitter.point.center.z = sinf(ctxt->r * 3.0f) * 4.5f;
/* move the camera in a circle */
- camera.position.x = sinf(r) * (cosf(r) * 2.0f + 5.0f);
- camera.position.z = cosf(r) * (cosf(r) * 2.0f + 5.0f);
+ camera.position.x = sinf(ctxt->r) * (cosf(ctxt->r) * 2.0f + 5.0f);
+ camera.position.z = cosf(ctxt->r) * (cosf(ctxt->r) * 2.0f + 5.0f);
/* also move up and down */
- camera.position.y = cosf(r * 1.3f) * 4.0f + 2.08f;
+ camera.position.y = cosf(ctxt->r * 1.3f) * 4.0f + 2.08f;
/* keep camera facing the origin */
- camera.orientation.yaw = r + RAY_EULER_DEGREES(180.0f);
+ camera.orientation.yaw = ctxt->r + RAY_EULER_DEGREES(180.0f);
/* tilt camera pitch in time with up and down movements, phase shifted appreciably */
- camera.orientation.pitch = -(sinf((M_PI * 1.5f) + r * 1.3f) * .6f + -.35f);
+ camera.orientation.pitch = -(sinf((M_PI * 1.5f) + ctxt->r * 1.3f) * .6f + -.35f);
#endif
ctxt->render = ray_render_new(&scene, &camera, fragment->frame_width, fragment->frame_height);
}
diff --git a/src/setup.c b/src/setup.c
index 7b5cecb..95387e3 100644
--- a/src/setup.c
+++ b/src/setup.c
@@ -146,7 +146,8 @@ int setup_interactively(til_settings_t *settings, int (*setup_func)(const til_se
if (feof(stdin))
return -EIO;
- (void) fgets(buf, sizeof(buf), stdin);
+ if (fgets(buf, sizeof(buf), stdin) == NULL)
+ return -EIO;
}
if (*buf == '\n') {
diff --git a/src/til.c b/src/til.c
index 709aabb..0ce7ebb 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,
@@ -327,7 +329,7 @@ void til_module_render(til_module_context_t *context, til_stream_t *stream, unsi
}
-/* Identical to til_module_render() except with a parameterized upper bound for context->c_cpus,
+/* Identical to til_module_render() except with a parameterized upper bound for context->n_cpus,
* this is primarily intended for modules performing nested rendering
*/
void til_module_render_limited(til_module_context_t *context, til_stream_t *stream, unsigned ticks, unsigned max_cpus, til_fb_fragment_t **fragment_ptr)
@@ -336,13 +338,13 @@ void til_module_render_limited(til_module_context_t *context, til_stream_t *stre
}
-/* if n_cpus == 0, it will be automatically set to n_threads.
- * to explicitly set n_cpus, just pass the value. This is primarily intended for
+/* If n_cpus == 0, it will be automatically set to n_threads.
+ * To explicitly set n_cpus, just pass the value. This is primarily intended for
* the purpose of explicitly constraining rendering parallelization to less than n_threads,
* if n_cpus is specified > n_threads it won't increase n_threads...
*
- * if stream is non-NULL, the created contexts will be registered on-stream w/handle @setup->path.
- * any existing contexts @setup->path, will be replaced by the new one.
+ * If stream is non-NULL, the created contexts will be registered on-stream w/handle @setup->path.
+ * Any existing contexts @setup->path, will be replaced by the new one.
*/
int til_module_create_contexts(const til_module_t *module, til_stream_t *stream, unsigned seed, unsigned ticks, unsigned n_cpus, til_setup_t *setup, size_t n_contexts, til_module_context_t **res_contexts)
{
@@ -485,7 +487,7 @@ int til_module_setup(const til_settings_t *settings, til_setting_t **res_setting
/* originally taken from rtv, this randomizes a module's setup @res_setup, args @res_arg
- * returns 0 on on setup successful with results stored @res_*, -errno on error.
+ * returns 0 on setup successful with results stored @res_*, -errno on error.
*/
int til_module_settings_randomize(const til_module_t *module, til_settings_t *settings, unsigned seed, til_setup_t **res_setup, char **res_arg)
{
@@ -613,7 +615,7 @@ int til_module_settings_randomize(const til_module_t *module, til_settings_t *se
}
-/* This turns the incoming module+setings into a "baked" til_setup_t,
+/* This turns the incoming module+settings into a "baked" til_setup_t,
* if module->setup() isn't provided, a minimal til_setup_t is still produced.
*/
int til_module_settings_finalize(const til_module_t *module, const til_settings_t *module_settings, til_setup_t **res_setup)
© All Rights Reserved