Age | Commit message (Collapse) | Author |
|
After reading about the Dreamachine[0], I wanted to experience
this phenomenon. The javascript-based web implementations
struggled to hold a steady 10Hz rate and would flicker like
crazy, so here we are.
Only setting right now is period=float_seconds, defaults to .1
for 10Hz.
One limitation in the current implementation is when the frame
rate can't keep up with the period the strobe will just stick on
without ever going off, because the period will always be
expired. There should probably be a setting to force turning off
for at least one frame when it can't keep up.
[0] https://en.wikipedia.org/wiki/Dreamachine
|
|
Until now when fragmenting with a texture present the texture
pointer was simply copied through to the new logical fragment.
The problem with that is when sampling pixels from the texture in
a nested frame scenario, the locations didn't align with the
placement of the logical fragment.
With this change when the incoming fragment has a texture, the
output fragment gets some uninitialized memory attached in the
outgoing fragment's texture pointer. Then the fragmenter is
expected to do the same populating of res_fragment->texture it
already did for res_fragment, just relative to
fragment->texture->{buf,stride,pitch} etc.
It's a bit hairy/janky because til_fb_fragment_t.texture is just
a pointer to another til_fb_fragment_t. So the ephemeral/logical
fragments fragmenting/tiling produces which tend to just be
sitting on the stack need to get another til_fb_fragment_t
instance somewhere and made available at the ephemeral
til_fb_fragment_t's .texture member. We don't want to be
allocating and freeing these things constantly, so for now I'm
just ad-hoc stowing the pointer of an adjacent on-stack texture
fragment in the .texture member when the incoming fragment has a
texture. But this is gross because the rest of the fragment
contents don't get initialized _at_all_, and currently if the
incoming fragment has no texture the res_fragment->texture member
isn't even initialized. The fragmenters aren't really supposed
to be expecting anything sensible in *res_fragment, but now we're
making use of res_fragment->texture *if* fragment->texture is
set. This is just gross.
So there's a bunch of asserts sprinkled around to help police
this fragility for now, but if someone writes new fragmenters
there's a good chance this will trip them up.
|
|
Preparatory commit for enabling cloneable/swappable fragments
There's an outstanding issue with the til_fb_page_t submission,
see comments. Doesn't matter for now since cloning doesn't happen
yet, but will need to be addressed before they do.
|
|
This consolidates the prepare_frame+render_fragment
potentially-threaded branch but more importantly introduces some
asserts codifying the whole prepare_frame() must return a
fragmenter /and/ be accompanied by a render_fragment().
Any single-threaded modules are expected to just populate
render_fragment() and leave prepare_frame() unused.
|
|
til_setting_desc_t.random() and til_module_randomize_setup() now
take seeds.
Note they are not taking a pointer to a shared seed, but instead
receive the seed by value.
If a caller wishes the seed to evolve on every invocation into
these functions, it should simply insert a rand_r(&seed) in
producing the supplied seed value.
Within a given randomizer, the seed evolves when appropriate.
But isolating the effects by default seems appropriate, so
callers can easily have determinism within their respective scope
regardless of how much nested random use occurs.
|
|
This enables reproducible yet pseudo-randomized visuals, at least
for the fully procedural modules.
The modules that are more simulation-y like sparkler and swarm
will still have runtime variations since they are dependent on
how much the simulation can run and there's been a lot of
sloppiness surrounding delta-t correctness and such.
But still, in a general sense, you'll find more or less similar
results even when doing randomized things like
module=rtv,channels=compose using the same seed value.
For the moment it only accepts a hexadecimal value, the leading
0x is optional.
e.g. these are all valid:
--seed=0xdeadbeef
--seed=0xdEAdBeFf
--seed=0x (produces 0)
--seed=0xff
--seed=deadbeef
--seed=ff
--seed= (produces 0)
--seed=0 (produces 0)
when you exceed the natural word size of an unsigned int on your
host architecture, an overflow error will be returned.
there are remaining issues to be fixed surrounding PRNG
reproducibility, in that things like til_module_randomize_setup()
doesn't currently accept a seed value. However it doesn't even
use rand_r() currently, but when it invokes desc->random() the
module's random() implementation should be able to use rand_r()
and needs to be fed the seed. So that all still needs wiring up
to propagate the root seed down everywhere it may be relevant.
|
|
modules/checkers w/fill_module=$module requires a consistent
mapping of cpu to fragnum since it creates a per-cpu
til_module_context_t for the fill_module.
The existing implementation for threaded rendering maximizes
performance by letting *any* scheduled to run thread advance
fragnum atomically and render the acquired fragnum
indiscriminately. A side effect of this is any given frame, even
rendered by the same module, will have a random mapping of
cpus/threads to fragnums.
With this change, the simple til_module_t.prepare_frame() API of
returning a bare fragmenter function is changed to instead return
a "frame plan" in til_frame_plan_t. Right now til_frame_plan_t
just contains the same fragmenter as before, but also has a
.cpu_affinity member for setting if the frame requires a stable
relationship of cpu/thread to fragnum.
Setting .cpu_affinity should be avoided if unnecessary, and that
is the default if you don't mention .cpu_affinity at all when
initializing the plan in the ergonomic manner w/designated
initializers. This is because the way .cpu_affinity is
implemented will leave threads spinning while they poll for
*their* next fragnum using atomic intrinsics. There's probably
some room for improvement here, but this is good enough for now
to get things working and correct.
|
|
Also wire this up to the til_module_context_new() helper and
all its callers.
This is in preparation for modules doing more correct delta-T
derived animation.
|
|
This introduces a very naive unoptimized moire interference
pattern module, it's rather slow complete with a sqrtf() per
pixel per center.
|
|
- modules now allocate their contexts using
til_module_context_new() instead of [cm]alloc().
- modules simply embed til_module_context_t at the start of their
respective private context structs, if they do anything with
contexts
- modules that do nothing with contexts (lack a create_context()
method), will now *always* get a til_module_context_t supplied
to their other methods regardless of their create_context()
presence. So even if you don't have a create_context(), your
prepare_frame() and/or render_fragment() methods can still
access seed and n_cpus from within the til_module_context_t
passed in as context, *always*.
- modules that *do* have a create_context() method, implying they
have their own private context type, will have to cast the
til_module_context_t supplied to the other methods to their
private context type. By embedding the til_module_context_t at
the *start* of their private context struct, a simple cast is
all that's needed. If it's placed somewhere else, more
annoying container_of() style macros are needed - this is
strongly discouraged, just put it at the start of struct.
- til_module_create_context() now takes n_cpus, which may be set
to 0 for automatically assigning the number of threads in its
place. Any non-zero value is treated as an explicit n_cpus,
primarily intended for setting it to 1 for single-threaded
contexts necessary when embedded within an already-threaded
composite module.
- modules like montage which open-coded a single-threaded render
are now using the same til_module_render_fragment() as
everything else, since til_module_create_context() is accepting
n_cpus.
- til_module_create_context() now produces a real type, not void
*, that is til_module_context_t *. All the other module
context functions now operate on this type, and since
til_module_context_t.module tracks the module this context
relates to, those functions no longer require both the module
and context be passed in. This is especially helpful for
compositing modules which do a lot of module context creation
and destruction; the module handle is now only needed to create
the contexts. Everything else operating on that context only
needs the single context pointer, not module+context pairs,
which was unnecessarily annoying.
- if your module's context can be destroyed with a simple free(),
without any deeper knowledge or freeing of nested pointers, you
can now simply omit destroy_context() altogether. When
destroy_context() is missing, til_module_context_free() will
automatically use libc's free() on the pointer returned from
your create_context() (or on the pointer that was automatically
created if you omitted create_context() too, for the
bare til_module_context_t that got created on your behalf
anyways).
For the most part, these changes don't affect module creation.
In some ways this eases module creation by making it more
convenient access seed and n_cpus if you had no further
requirement for a context struct.
In other ways it's slightly annoying to have to do type-casts
when you're working with your own context type, since before it
was all void* and didn't require casts when assigning to your
typed context variables.
The elimination for requiring a destroy_context() method in
simple free() of private context scenarios removes some
boilerplate in simple cases.
I think it's a wash for module writers, or maybe a slight win for
the simple cases.
|
|
Just assume a fragment has been logically cleared after
til_module_render() has done all its potential steps.
I'm not certain this doesn't break some existing assumptions WRT
fragmented/threaded clears and their propagation out to the outer
frame.
But I've been operating under the assumption that this was
already happening in terms of an implicit setting of
til_fb_fragment_t.cleared after a module's render happened.
Except I don't see anything in the existing code or history
actually doing that, which is odd.
For modules that don't invoke til_fb_fragment_clear() explicitly
because they are frame-fillers (think submit, swab, ray, julia,
plasma, these are all full-frame renders that don't benefit from
pre-clearing), they weren't leaving fragment->cleared set,
despite having fully initialized the frame's contents.
We should be able to just assume after prepare/render/finish has
happened for a given module, the target fragment has been
cleared.
Commit 4e5286 had introduced somewhat complicated .cleared
maintenance and propagation for threaded renders, but when we
just treat all finished module renders into a given fragment as
logically clearing the fragment we can just skip all that.
|
|
In the recent surge of ADD-style rtv+compose focused development,
a bunch of modules were changed to randomize initial states at
context_create() so they wouldn't be so repetitive.
But the way this was done in a way that made it impossible to
suppress the randomized initial state, which sometimes may be
desirable in compositions. Imagine for instance something like
the checkers module, rendering one module in the odd cells, and
another module into the even cells. Imagine if these modules are
actually the same, but if checkers used one seed for all the odd
cells and another seed for all the even cells. If the modules
used actually utilized the seed provided, checkers would be able
to differentiate the odd from even by seeding them differently
even when the modules are the same.
This commit is a step in that direction, but rototiller and all
the composite modules (rtv,compose,montage) are simply passing
rand() as the seeds. Also none of the modules have yet been
modified to actually make use of these seeds.
Subsequent commits will update modules to seed their
pseudo-randomized initial state from the seed value rather than
always calling things like rand() themselves.
|
|
Mostly for compositing purposes, here will be a corpus of 2D
shapes, parameterized/procedurally generated and able to rotate
and perhaps have other dynamics added.
What shapes are there presently I had started implementing in
checkers as "styles", before realizing they really should just be
a separate module checkers can call into.
Not terribly interesting by itself, but as blinds and checkers
demonstrated, these things deliver a lot of value in
compositional situations. They're creating the palette to draw
from.
|
|
rtv special-cased handling a nil module to mean clear the
fragment, and called this "none"
But it really makes more sense for rtv to treat "none" as not
doing anything at all for its snow_module - not even blanking.
And it would be nice to have a consistent way to express a blank
module throughout rototiller, so this introduces a concept of
built-in modules accessible only via explicit lookup by name
which don't get enumerated via til_get_modules(), as they're
inherently uninteresting more utility-oriented modules for use by
other modules.
For now it's only "blank" that constitutes the built-ins list,
but expanding this is only a matter of introducing more modules
there. Future commits will rework rtv to use "blank" in place of
its current "none", and rtv's "none" will be reworked to
represent no snow mechanism at all, obviating the need to specify
snow_duration=0,snow_module=none required today.
|
|
Most of the threaded modules have settled down on two basic forms
of fragmenter function:
1. a slice per cpu, where tile-oriented locality isn't useful
2. ~64x64 tiles, in scenarios where screen-space locality helps
Now that n_cpus is wired up to the fragmenter, #1 can be
fulfilled without requiring a module-private context plumbing
n_cpus from create_context().
A future commit will replace some module-specific fragmenters by
returning one of these instead as res_fragmenter in their
prepare_frame(), wherever applicable.
|
|
This adds a voronoi diagram module, which when used as an overlay
produces a mosaic effect.
Some settings:
cells=N number of voronoi cells
randomize={on,off} randomizes the cell locations every frame
dirty={on,off} uses a faster sloppy/dithery-looking method
Some TODO items:
- use a more space efficient representation of the distance
buffer, maybe use uint16_t relative offsets into the cells
rather than pointers - capping their quantity to 64KiB
- anti-alias edges between cells
|
|
This brings something resembling an actual type to the private
objects returrned in *res_setup. Internally libtil/rototiller
wants this to be a til_setup_t, and it's up to the private users
of what's returned in *res_setup to embed this appropriately and
either use container_of() or casting when simply embedded at the
start to go between til_setup_t and their private containing
struct.
Everywhere *res_setup was previously allocated using calloc() is
now using til_setup_new() with a free_func, which til_setup_new()
will initialize appropriately. There's still some remaining work
to do with the supplied free_func in some modules, where free()
isn't quite appropriate.
Setup freeing isn't actually being performed yet, but this sets
the foundation for that to happen in a subsequent commit that
cleans up the setup leaks.
Many modules use a static default setup for when no setup has
been provided. In those cases, the free_func would be NULL,
which til_setup_new() refuses to do. When setup freeing actually
starts happening, it'll simply skip freeing when
til_setup_t.free_func is NULL.
|
|
This makes the arg return optional by using a res_arg pointer,
instead returning -ENOMEM when it would have returned NULL on
allocation failures.
This also makes it possible to detect when no setup was
performed, by returning 0 in such a case. Now returns 1 when
setup occurs and res pointers populated.
|
|
This commit pulls the setup randomizer out of rtv into libtil
proper, so other modules may make use of it.
Other than adding an assert no functional changes occurred.
It may make sense to split this into two functions; one which
takes a til_module_t as-is, and a lower-level bare setup function
callback based function that doesn't know about til_module_t the
former would call into. That way generic setup randomization can
occur (the same setup machinery is used in video contexts for
example) without necessarily having a til_module_t on hand.
|
|
expose the checkers module to the rest of the world
|
|
This isn't super interesting but I might just start adding
simplistic overlay style modules for compositing/transition use.
|
|
A future commit will remove srand() calls from modules, relying
instead on this srand() in til_init().
As mentioned in the comment, if modules actually want
reproducible deterministic pseudo-random values they should use
rand_r() (or their own PRNG) where they can control the seed.
|
|
This is a preparatory commit for cleaning up the existing sloppy
global-ish application of settings during the iterative _setup()
call sequences.
Due to how this has evolved from a very rudimentary thing
enjoying many assumptions about there ever only being a single
module instance being configured by the settings, there's a lot
of weirdness and inconsistency surrounding module setup WRT
changes being applied instantaneously to /all/ existing and
future context's renderings of a given module vs. requiring a new
context be created to realize changes.
This commit doesn't actually change any of that, but puts the
plumbing in place for the setup methods to allocate and
initialize a private struct encapsulating the parsed and
validated setup once the settings are complete. This opaque
setup pointer will then be provided to the associated
create_context() method as the setup pointer. Then the created
context can configure itself using the provided setup when
non-NULL, or simply use defaults when NULL.
A future commit will update the setup methods to allocate and
populate their respective setup structs, adding the structs as
needed, as well as updating their create_context() methods to
utilize those setups.
One consequence of these changes when fully realized will be that
every setting change will require a new context be created from
the changed settings for the change to be realized.
For settings appropriately manipulated at runtime the concept of
knobs was introduced but never finished. That will have to be
finished in the future to enable more immediate/interactive
changing of settings-like values appropriate for interactive
manipulation
|
|
Now that til_setting_t.desc is not only a thing, but a thing that
is intended to be refreshed regularly in the course of things
like GUI interactive settings construction, it's not really
appropriate to try even act like this these are const anymore.
|
|
|
|
Always only capitalize the first letter, never capitalize like
titles.
|
|
The existing iterative *_setup() interface only described
settings not found, quietly accepting usable settings already
present in the til_settings_t.
This worked fine for the existing interactive text setup thing,
but it's especially problematic for providing a GUI setup
frontend.
This commit makes it so the *_setup() methods always describe
undescribed settings they recognize, leaving the setup frontend
loop calling into the *_setup() methods to both apply the
description validation if wanted and actually tie the description
to respective setting returned by the _setup() methods as being
related to the returned description.
A new helper called til_settings_get_and_describe_value() has
been introduced primarily for use of module setup methods to
simplify this nonsense, replacing the til_settings_get_value()
calls and surrounding logic, but retaining the til_setting_desc_t
definitions largely verbatim.
This also results in discarding of some ad-hoc
til_setting_desc_check() calls, now that there's a centralized
place where settings become "described" (setup_interactively in
the case of rototiller).
Now a GUI frontend (like glimmer) would just provide its own
setup_interactively() equivalent for constructing its widgets for
a given *_setup() method's chain of returned descs. Whereas in
the past this wasn't really feasible unless there was never going
to be pre-supplied settings.
I suspect the til_setting_desc_check() integration into
setup_interactively() needs more work, but I think this is good
enough for now and I'm out of spare time for the moment.
|
|
Largely mechanical rename of librototiller -> libtil, but
introducing a til_ prefix to all librototiller (now libtil)
functions and types where a rototiller prefix was absent.
This is just a step towards a more libized librototiller, and til
is just a nicer to type/read prefix than rototiller_.
|