diff options
-rw-r--r-- | README | 77 | ||||
-rw-r--r-- | configure.ac | 1 | ||||
-rw-r--r-- | src/Makefile.am | 1 | ||||
-rw-r--r-- | src/drm_fb.c | 16 | ||||
-rw-r--r-- | src/main.c | 5 | ||||
-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 | 349 | ||||
-rw-r--r-- | src/modules/julia/julia.c | 45 | ||||
-rw-r--r-- | src/modules/mixer/mixer.c | 86 | ||||
-rw-r--r-- | src/modules/montage/montage.c | 6 | ||||
-rw-r--r-- | src/modules/plasma/plasma.c | 5 | ||||
-rw-r--r-- | src/modules/ray/ray.c | 21 | ||||
-rw-r--r-- | src/setup.c | 3 | ||||
-rw-r--r-- | src/til.c | 16 |
15 files changed, 555 insertions, 80 deletions
@@ -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; @@ -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') { @@ -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) |