diff options
| -rw-r--r-- | README | 77 | ||||
| -rw-r--r-- | src/modules/checkers/checkers.c | 9 | ||||
| -rw-r--r-- | src/modules/droste/droste.c | 4 | ||||
| -rw-r--r-- | src/modules/flow/flow.c | 47 | ||||
| -rw-r--r-- | src/modules/submit/submit.c | 87 |
5 files changed, 152 insertions, 72 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/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/droste.c b/src/modules/droste/droste.c index 9cbfbac..717e513 100644 --- a/src/modules/droste/droste.c +++ b/src/modules/droste/droste.c @@ -112,6 +112,10 @@ static int droste_fragmenter(til_module_context_t *context, const til_fb_fragmen 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; 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/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, }; |
