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/checkers/checkers.c | 9 | ||||
| -rw-r--r-- | src/modules/droste/Makefile.am | 3 | ||||
| -rw-r--r-- | src/modules/droste/droste.c | 349 | ||||
| -rw-r--r-- | src/modules/flow/flow.c | 47 | ||||
| -rw-r--r-- | src/modules/julia/julia.c | 45 | ||||
| -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/modules/submit/submit.c | 87 | ||||
| -rw-r--r-- | src/setup.c | 3 | ||||
| -rw-r--r-- | src/til.c | 16 |
17 files changed, 576 insertions, 116 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/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') { @@ -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) |
