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/checkers/checkers.c9
-rw-r--r--src/modules/droste/Makefile.am3
-rw-r--r--src/modules/droste/droste.c349
-rw-r--r--src/modules/flow/flow.c47
-rw-r--r--src/modules/julia/julia.c45
-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/modules/submit/submit.c87
-rw-r--r--src/setup.c3
-rw-r--r--src/til.c16
17 files changed, 576 insertions, 116 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/checkers/checkers.c b/src/modules/checkers/checkers.c
index 59421c3..4fe3207 100644
--- a/src/modules/checkers/checkers.c
+++ b/src/modules/checkers/checkers.c
@@ -148,7 +148,7 @@ static til_module_context_t * checkers_create_context(const til_module_t *module
* adding a size member to til_module_context_t, which is straightforward to add.
*/
- ctxt->waste_fb = (til_fb_fragment_t){
+ ctxt->waste_fb = (til_fb_fragment_t){
.buf = malloc(waste_fb_size * waste_fb_size * sizeof(uint32_t)),
.frame_width = waste_fb_size,
.frame_height = waste_fb_size,
@@ -476,6 +476,13 @@ static int checkers_finish_frame(til_module_context_t *context, til_stream_t *st
* modules wanting to do the same thing with concurrent clones so it's worth sorting
* out all the details.
*/
+
+ /* It's important that the waste_fb's cleared state match the actual fragment_ptr's,
+ * since some modules do drastically different things when overlayed vs. not - which
+ * they detect via the cleared flag.
+ */
+ ctxt->waste_fb.cleared = (*fragment_ptr)->cleared;
+
til_module_render(ctxt->fill_module_contexts[i], stream, ticks, &waste_fb_ptr);
#if 0
/* This is probably an interesting thing to measure. On a busy system, it's not surprising
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/flow/flow.c b/src/modules/flow/flow.c
index c9b94e4..d28f9f3 100644
--- a/src/modules/flow/flow.c
+++ b/src/modules/flow/flow.c
@@ -35,6 +35,17 @@ typedef struct flow_element_t {
v3f_t position_a, position_b;
v3f_t velocity; /* per-iter step + direction applicable directly to position_a */
v3f_t color;
+
+ /* In the first pass we visit each element just once for the simulation, while there
+ * the particles are projected into the 2d fragment space with those coordinates cached
+ * in the element. Then the second pass accesses these to determine if the particle is
+ * in the fragment being drawn. As-is the second pass visits all elements since they're
+ * not (yet) kept organized spatially.
+ */
+ struct {
+ unsigned x1, y1;
+ unsigned x2, y2;
+ } fragspace;
} flow_element_t;
typedef struct flow_context_t {
@@ -83,7 +94,7 @@ static void flow_ff_populator(void *context, unsigned size, const ff_data_t *oth
v3f_t c = v3f_rand(seedp, 0.f, 1.0f);
size_t idx = x * size * size + y * size + z;
- field[idx].direction = v3f_lerp(&other[idx].direction, &v, .75f);
+ field[idx].direction = v3f_lerp(&other[idx].direction, &v, .50f);
field[idx].color = v3f_lerp(&other[idx].color, &c, .75f);
}
}
@@ -219,6 +230,8 @@ static void flow_render_fragment(til_module_context_t *context, til_stream_t *st
flow_element_t *e = &ctxt->elements[fragment->number * ctxt->n_elements_per_cpu];
unsigned n = ctxt->n_elements_per_cpu;
float w = ctxt->w * .5f + .5f;
+ unsigned ffw = fragment->frame_width,
+ ffh = fragment->frame_height;
/* XXX: note the fragment->number is used above as the cpu number, this is to ensure all cpu #s
* are actually used. Since our noop_fragmenter_per_cpu always produces a fragment per cpu,
@@ -261,6 +274,15 @@ static void flow_render_fragment(til_module_context_t *context, til_stream_t *st
*/
d.direction = v3f_mult_scalar(&d.direction, (float)ctxt->n_iters);
e->position_b = v3f_add(&pos, &d.direction);
+
+ /* Now do the 2D projection part and store those results in the element, where
+ * it can be quickly checked by the second pass.
+ */
+#define ZCONST 1.0f
+ e->fragspace.x1 = pos.x / (pos.z + ZCONST) * ffw + (ffw >> 1);
+ e->fragspace.y1 = pos.y / (pos.z + ZCONST) * ffh + (ffh >> 1) ;
+ e->fragspace.x2 = e->position_b.x / (e->position_b.z + ZCONST) * ffw + (ffw >> 1);
+ e->fragspace.y2 = e->position_b.y / (e->position_b.z + ZCONST) * ffh + (ffh >> 1) ;
}
return;
@@ -281,20 +303,16 @@ static void flow_render_fragment(til_module_context_t *context, til_stream_t *st
flow_element_t *e = &ctxt->elements[i];
v3f_t pos = e->position_a;
v3f_t v = e->velocity;
- unsigned x1, y1, x2, y2;
+ unsigned x1 = e->fragspace.x1,
+ y1 = e->fragspace.y1,
+ x2 = e->fragspace.x2,
+ y2 = e->fragspace.y2;
uint32_t pixel;
- /* Perspective-project the endpoints of the element's travel, this is
- * the part we can't currently avoid doing per-element per-fragment.
- */
-#define ZCONST 1.0f
- x1 = pos.x / (pos.z + ZCONST) * ffw + (ffw >> 1);
- y1 = pos.y / (pos.z + ZCONST) * ffh + (ffh >> 1) ;
- x2 = e->position_b.x / (e->position_b.z + ZCONST) * ffw + (ffw >> 1);
- y2 = e->position_b.y / (e->position_b.z + ZCONST) * ffh + (ffh >> 1) ;
-
/* for cases obviously outside the fragment, don't draw anything */
+ /* FIXME: these early-outs don't consider the ctxt->n_iters interpolated coordinates */
+
/* totally outside (above) */
if (y1 < fy1 && y2 < fy1)
continue;
@@ -324,6 +342,10 @@ static void flow_render_fragment(til_module_context_t *context, til_stream_t *st
if (!ctxt->n_iters)
continue;
+ /* TODO: why isn't this a simple line draw of (x1,y1)..(x2,y2)?
+ * that would eliminate the cumulative error potential for going out of bounds,
+ * which is the real reason put_pixel_unchecked() can't be used in this loop.
+ */
for (unsigned j = 1; j < ctxt->n_iters - 1; j++) {
pos = v3f_add(&pos, &v);
@@ -331,7 +353,8 @@ static void flow_render_fragment(til_module_context_t *context, til_stream_t *st
x1 = pos.x / (pos.z + ZCONST) * ffw + (ffw >> 1);
y1 = pos.y / (pos.z + ZCONST) * ffh + (ffh >> 1);
- (void) til_fb_fragment_put_pixel_unchecked(fragment, TIL_FB_DRAW_FLAG_TEXTURABLE, x1, y1, pixel);
+ /* XXX: now that [xy]1 are changed, unchecked can't be used, it could be done more cleverly */
+ (void) til_fb_fragment_put_pixel_checked(fragment, TIL_FB_DRAW_FLAG_TEXTURABLE, x1, y1, pixel);
}
continue;
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/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/modules/submit/submit.c b/src/modules/submit/submit.c
index fea9444..415f795 100644
--- a/src/modules/submit/submit.c
+++ b/src/modules/submit/submit.c
@@ -33,19 +33,19 @@
#define TICKS_PER_FRAME 8000
typedef struct color_t {
- float r, g, b;
+ float r, g, b, a;
} color_t;
static color_t colors[NUM_PLAYERS + 1] = {
- {}, /* uninitialized cell starts black, becomes winner colors */
- {1.f, .317f, 0.f }, /* orange */
- {.627f, .125f, 1.f }, /* blue */
- {.878f, 0.f, 0.f }, /* red */
- {.165f, .843f, .149f }, /* green */
- {0.f, .878f, .815f }, /* cyan */
- {.878f, 0.f, 1.f }, /* purple */
- {.906f, .937f, 0.f }, /* yellow */
- {}, /* black */
+ {}, /* uninitialized cell starts black, becomes winner colors */
+ {1.f, .317f, 0.f, 1.f }, /* orange */
+ {.627f, .125f, 1.f, 1.f }, /* blue */
+ {.878f, 0.f, 0.f, 1.f }, /* red */
+ {.165f, .843f, .149f, 1.f }, /* green */
+ {0.f, .878f, .815f, 1.f }, /* cyan */
+ {.878f, 0.f, 1.f, 1.f }, /* purple */
+ {.906f, .937f, 0.f, 1.f }, /* yellow */
+ {}, /* black */
};
@@ -71,8 +71,11 @@ static inline uint32_t color_to_uint32(color_t color) {
if (color.r > 1.0f) color.r = 1.0f;
if (color.g > 1.0f) color.g = 1.0f;
if (color.b > 1.0f) color.b = 1.0f;
+ if (color.a > 1.0f) color.a = 1.0f;
- pixel = (uint32_t)(color.r * 255.0f);
+ pixel = (uint32_t)(color.a * 255.0f);
+ pixel <<= 8;
+ pixel |= (uint32_t)(color.r * 255.0f);
pixel <<= 8;
pixel |= (uint32_t)(color.g * 255.0f);
pixel <<= 8;
@@ -108,6 +111,7 @@ static color_t color_lerp(color_t *a, color_t *b, float t)
.r = a->r * (1.f - t) + b->r * t,
.g = a->g * (1.f - t) + b->g * t,
.b = a->b * (1.f - t) + b->b * t,
+ .a = a->a * (1.f - t) + b->a * t,
};
return res;
@@ -194,14 +198,28 @@ static void draw_grid(submit_context_t *ctxt, til_fb_fragment_t *fragment)
float xscale = ((float)GRID_SIZE - 1.f) / (float)fragment->frame_width;
float yscale = ((float)GRID_SIZE - 1.f) / (float)fragment->frame_height;
- for (int y = 0; y < fragment->height; y++) {
- for (int x = 0; x < fragment->width; x++) {
- uint32_t color;
+ if (!fragment->cleared) {
+ for (int y = 0; y < fragment->height; y++) {
+ for (int x = 0; x < fragment->width; x++) {
+ uint32_t color;
- /* TODO: this could be optimized a bit! i.e. don't recompute the y for every x etc. */
- color = sample_grid(ctxt, .5f + ((float)(fragment->x + x)) * xscale, .5f + ((float)(fragment->y + y)) * yscale);
- til_fb_fragment_put_pixel_unchecked(fragment, 0, fragment->x + x, fragment->y + y, color);
+ /* TODO: this could be optimized a bit! i.e. don't recompute the y for every x etc. */
+ color = sample_grid(ctxt, .5f + ((float)(fragment->x + x)) * xscale, .5f + ((float)(fragment->y + y)) * yscale);
+ til_fb_fragment_put_pixel_unchecked(fragment, 0, fragment->x + x, fragment->y + y, color);
+ }
+ }
+ } else {
+ for (int y = 0; y < fragment->height; y++) {
+ for (int x = 0; x < fragment->width; x++) {
+ uint32_t color;
+
+ /* TODO: this could be optimized a bit! i.e. don't recompute the y for every x etc. */
+ color = sample_grid(ctxt, .5f + ((float)(fragment->x + x)) * xscale, .5f + ((float)(fragment->y + y)) * yscale);
+ if ((color & 0xff000000) == 0xff000000)
+ til_fb_fragment_put_pixel_unchecked(fragment, 0, fragment->x + x, fragment->y + y, color);
+ }
}
+
}
}
@@ -211,13 +229,26 @@ static void draw_grid_bilerp(submit_context_t *ctxt, til_fb_fragment_t *fragment
float xscale = ((float)GRID_SIZE - 2.f) / (float)fragment->frame_width;
float yscale = ((float)GRID_SIZE - 2.f) / (float)fragment->frame_height;
- for (int y = 0; y < fragment->height; y++) {
- for (int x = 0; x < fragment->width; x++) {
- uint32_t color;
+ if (!fragment->cleared) {
+ for (int y = 0; y < fragment->height; y++) {
+ for (int x = 0; x < fragment->width; x++) {
+ uint32_t color;
- /* TODO: this could be optimized a bit! i.e. don't recompute the y for every x etc. */
- color = sample_grid_bilerp(ctxt, 1.f + ((float)(fragment->x + x)) * xscale, 1.f + ((float)(fragment->y + y)) * yscale);
- til_fb_fragment_put_pixel_unchecked(fragment, 0, fragment->x + x, fragment->y + y, color);
+ /* TODO: this could be optimized a bit! i.e. don't recompute the y for every x etc. */
+ color = sample_grid_bilerp(ctxt, 1.f + ((float)(fragment->x + x)) * xscale, 1.f + ((float)(fragment->y + y)) * yscale);
+ til_fb_fragment_put_pixel_unchecked(fragment, 0, fragment->x + x, fragment->y + y, color);
+ }
+ }
+ } else {
+ for (int y = 0; y < fragment->height; y++) {
+ for (int x = 0; x < fragment->width; x++) {
+ uint32_t color;
+
+ /* TODO: this could be optimized a bit! i.e. don't recompute the y for every x etc. */
+ color = sample_grid_bilerp(ctxt, 1.f + ((float)(fragment->x + x)) * xscale, 1.f + ((float)(fragment->y + y)) * yscale);
+ if ((color & 0xff000000) == 0xff000000)
+ til_fb_fragment_put_pixel_unchecked(fragment, 0, fragment->x + x, fragment->y + y, color);
+ }
}
}
}
@@ -292,15 +323,22 @@ static void submit_destroy_context(til_module_context_t *context)
static void submit_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)
{
submit_context_t *ctxt = (submit_context_t *)context;
+ til_fb_fragment_t *fragment = *fragment_ptr;
*res_frame_plan = (til_frame_plan_t){ .fragmenter = til_fragmenter_tile64 };
+ if (ticks == context->last_ticks)
+ return;
+
if (ctxt->game_winner)
setup_grid(ctxt);
for (int i = 0; i < NUM_PLAYERS; i++) {
int moves = rand_r(&ctxt->til_module_context.seed) % TICKS_PER_FRAME;
+ if (fragment->cleared && colors[i + 1].a == 0.f)
+ moves = MIN(TICKS_PER_FRAME, moves + (TICKS_PER_FRAME / 10));
+
for (int j = 0; j < moves; j++)
grid_player_plan(ctxt->players[i], ctxt->seq++,
rand_r(&ctxt->til_module_context.seed) % GRID_SIZE,
@@ -332,10 +370,11 @@ til_module_t submit_module = {
.destroy_context = submit_destroy_context,
.prepare_frame = submit_prepare_frame,
.render_fragment = submit_render_fragment,
+ .setup = submit_setup,
.name = "submit",
.description = "Cellular automata conquest game sim (threaded (poorly))",
.author = "Vito Caputo <vcaputo@pengaru.com>",
- .setup = submit_setup,
+ .flags = TIL_MODULE_OVERLAYABLE,
};
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