Age | Commit message (Collapse) | Author |
|
Now that til_stream_t is implemented, let's wire up the taps.
Note that nothing actually creates the stream and puts it in the
fragment yet, so stream is still always NULL for these
effectively turning this into a NOP.
|
|
Until now there's just been a forward declared type for
til_fb_fragment_t.stream's type, and it's been completely unused.
The purpose of the stream is to provide a continous inter-frame
object where information can be stored pertaining to the stream
of frames.
Right now, that information is limited to emergent "pipes" formed
by using taps against a given stream. Taps at new paths in the
stream become added as pipes for those paths, with the
responsible tap hooked in as the driving tap. Taps at existing
paths become diverted to the driving taps, enabling potential for
external drivers for tapped variables.
This commit only adds the implementation, nothing is actually
using it yet.
|
|
The purpose of the context path is to aid in locating the context
instance. The initial application of this will be in service of
the taps, which require their module's path as a sort of
containing directory of the tap name. It'd be convenient to
simply add the path hash with the tap hash to produce the tap's
"absolute path" hash when looking up in the hash table.
|
|
The purpose of the tap is ultimately to be indexed by name so
it's discoverable. I'm leaning towards using a hash table for
that, and it'd be silly to keep recomputing the hash of an
unchanging name.
|
|
Presently just for hashing paths and names
|
|
There needs to be a way to address module context instances
by name externally, in a manner complementary to settings and
taps.
This commit adds a string-based path to til_module_context_t, and
modifies til_module_create_context() to accept a parent path
which is then concatenated with the name of the module to produce
the module instance's new path.
The name separator used in the paths is '/' just like filesystem
paths, but these paths have no relationship to filesystems or
files.
The root module context creation in rototiller's main simply
passes "" as the parent path, resulting in a "/" root as one
would expect.
There are some obvious complications introduced here however:
- checkers in particular creates a context per cpu, simply using
the same seed and setup to try make the contexts identical at
the same ticks value. With this commit I'm simply passing the
incoming path as the parent for creating those contexts, but
it's unclear to me if that will work OK. With an eye towards
taps deriving their parent path from the context path, I guess
these taps would all get the same parent and hash to the same
value despite being duplicated. Maybe it Just Works, but one
thing is clear - there won't be any way to address the per-cpu
taps as-is. Maybe that's desirable though, there's probably
not much use in trying to control the taps at the CPU
granularity.
- when the recursive settings stuff lands, it should bring along
the ability to explicitly name settings blocks. Those names
should override the module name in constructing the path.
I've noted as such in the code.
- these paths probably need to be hashed @ initialization time
so there needs to be a hash function added to til, and a hash
value accompanying the name in the module context. It'd be
dumb to keep recomputing the hash when these paths get used
for hash table lookups multiple times per frame...
there's probably more I'm forgetting right now, but this seems
like a good first step.
fixup root path
|
|
Wiring up some minimal taps to see how this will work...
This only initializes the taps and changes the render to access
the rates indirectly via the tapped pointers.
|
|
Wiring up some minimal taps to see how this will work...
This only initializes the taps and changes the render to access
the rates indirectly via the tapped pointers.
|
|
this is slightly more ergonomic by having one less pointer to get in
the right place in the parameters
|
|
The previous commit implemented taps in a way requiring
allocations and cleanup. Let's experiment a bit and just make
them absolutely minimal named typed variable+indirection
bindings.
The helpers are retained, but converted to initializers rather
than new() style constructors. They provide type-checking of the
variable and indirection pointer, and prevent incorrect TIL_TYPE
values going in til_type_t.type for the supplied bound
elems+ptr.
|
|
The idea here is for modules to bind variables to names @ context
create time w/til_tap_new(). Pseudo-code sample:
```
typedef struct foo_context_t {
struct {
til_tap_t *position;
} taps;
struct {
v2f_t position;
} vars;
v2f_t *position;
} foo_context_t;
foo_context_t * foo_create_context(void)
{
foo_context_t *foo = malloc(sizeof(foo_context_t));
/* This creates an isolated (pipe-)tap binding our local position variable and pointer
* to a name for later "tapping" onto a stream.
*/
foo->taps.position = til_tap_new_v2f(&foo->position, "position", 1, &foo->vars.position);
return foo;
}
foo_render(foo_context_t *foo, til_fb_fragment_t *fragment)
{
if (!til_stream_tap(fragment->stream, &foo->pipes.position)) {
/* got nothing, we're driving */
foo->position->x = cosf(ticks);
foo->position->y = sinf(ticks);
} /* else { got something, just use foo->position as-is */
draw_stuff_using_position(foo->position);
}
```
Note til_stream_tap() doesn't exist yet, this commit only adds
the tap (til_tap_new()).
The stream will probably implement a hash table for looking up
the tap by name, verifying its type and nelems match if found,
and update the pointer to point at the instance actually driving
for the name. (in the example that's the foo_context_t.position
pointer which draw_stuff_using_position() then dereferences)
Also note that in the example, "position" alone is too simplistic
for handling complex real-life compositions where a given module
may recur in a given stream. That identifier would need to be
derived from the module's context/setup producing a distinctly
unique path to the tap. i.e.
"/compose/layers/checkers/fill_module/foo:position" or something,
to be dynamically generated. And the foo:position syntax isn't
set in stone either. Maybe foo/position would suffice, the whole
heirarchical syntax needs to be thought through and defined yet.
Since the absolute path to the tap would be setup-dependent, there
will have to be some glue tying together the setup used by the
context and the tap within that context. The stream may be the
natural place where that occurs.
This also currently is barebones in terms of the tap types
supported. The only higher-order types are rudimentary 2-4d
vectors and 4x4 matrices. There are no semantics associated
with the types, and it's likely in the future either the tap
types themselves will expand to be semantic. Think things like
a camera type, composed both a point and direction vector.
As-is the few higher-order types in til_tap.h are simply forward
declared, and at least in terms of the taps alone further type
visibility isn't necessary.
It may make more sense to build upon these bare taps with another
semantic layer bringing the higher-order types to the table in a
more concrete form. All those higher-order types would then be
composed from the bare taps.
There's some conceptual overlap with the knobs stubbed out in
til_knobs.h as well. I think this likely at least partially
replaces what's there, and what it doesn't will probably end up
somewhere else.
|
|
This adds an optional stream member and type for introducing some
persistent potentially cross-fragment state bound to the logical
frame being rendered to by the fragment.
It's a preparatory commit for adding things like arbitrary
stream-bound state modules can create/read/modify for passing
non-pixel information between modules bound to the fragment's
frame that may vary frame to frame, but may also only be updated
occasionally within a stream while still being accessible by
every frame for the lifetime of the stream.
This may evolve to encompass the fragment's texture member,
turning the texture into just another arbitrary thingy tied to
the stream which modules/rendering primitives may lookup and make
use of if present.
This commit only adds the type and member though, no
implementation yet.
|
|
The existing code only really cared about preserving the incoming
texture on behalf of the caller when the compose code itself was
doing something with the texture.
But there's scenarios where the underlying module being rendered
(or its descendants) might play with the texture, and in such a
situation when the outer compose wasn't involving a texture it'd
let the descandants installed texture leak out to the callers
making the texture apply more than one'd expect.
Arguably none of the modules should be missing restoration of the
incoming texture after installing+rendering with their own. But
with this change in place, compose will clean up after nested
modules leaking their texture up.
|
|
At low framerates, the strobe module timer can expire from frame to
frame. This has the effect of appearing "always on." This condition was
obscuring output in compose mode.
#BirdwalkCommit #WinterStormWarning
|
|
Mechanical rearrangement, but ultimately there probably needs to
be an initialize function added to til_module_t. With all the
threading chaos going on, this approach to implicit
initialization with a static flag is racy without using atomics.
For now it's probably marginally better to do this in context
create vs. prepare frame. Context creates *tend* to happen in
single-threaded phases of execution, and infrequently. Prepare
frame is a serialized phase of the rendering for a given context,
but there can be many contexts in-flight simultaneously now with
all the forms of compositing happening, sometimes from multiple
threads. So that assumption no longer holds...
|
|
This gets rid of the static accumulator hack used for the blinds
phase.
|
|
There's still a handful of modules doing ad-hoc radians
accumulation in a static variable by simply adding a small value
like .01 every render.
This worked OK in the early days when 1. no rototiller instance
was ever run long enough for that accumulator to become a large
value where floating point precision started rearing its ugly
head. and 2. rototiller never really drew the same module
multiple times in compositing a frame.
Now that rototiller can produce some rather interesting outputs
the first assumption isn't really true - I've fixed memory leaks
to enable long-running sessions, so these potential precision
problems should get dealth with.
And with rtv+compose/checkers+fill_module it's quite common to
have a module rendering things many times in a single frame. So
that previously tolerable laziness of using a static accumulator
is no longer acceptable, since every invocation of the module's
renderer would bump the accumulator. When you have something
like checkers using blinds for the filler, every individual cell
is unintentionally advancing the blinds when they're intended to
be at the same phase.
So this helper is being added to conveniently turn ticks into
something you'd pass directly into cosf/sinf without worrying
about precision issues.
Future commits will start bringing modules over to use this
helper instead of whatever they're doing with static variables
or in-context accumulators etc.
There's also another reason to prefer deriving "T" from ticks
on every frame; we can do things like fast-forward/rewind effects
on modules by manipulating the ticks input value. If the modules
are accumulating this state privately, manipulating ticks won't
have the intended effect. Of course not all modules are amenable
to this kind of thing, stuff like swarm and sparkler where they
do a sort of simulation contain a pile of state that isn't
ticks-derived on every frame and can't really be converted to do
so.
|
|
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.
|
|
Just looking to spice up plato a bit. It seems to make a lot of
appearances in rtv, or is just one of those highly visible things
when it's participating. Way too monotonous as-is.
|
|
moire makes for an interesting texture, esp. mixed with itself:
--seed=0x62f00a7b --module=compose,layers=julia:moire:drizzle,texture=moire
|
|
Show the info, but skip the wait step.
|
|
this introduces a style= setting with values:
style=mask simple alpha mask overlay
style=map displacement mapped overlay
I might add a lighting option for the style=map mode with a
moving light source or something like that, but it's already
pretty slow as-is.
This is mostly just for more testing of the snapshotting, but
there's some interesting compositions enabled like:
module=compose,layers=submit:moire:drizzle
or just moire:drizzle, when style=map happens.
|
|
This arguably doesn't require snapshotting to work, since it's
doing a rudimentary in-place single pixel alpha-blended
replacement using a single pixel at the same location.
But the moment it startus using multiple adjacent super-samples,
the snapshot becomes necessary. If it gets further complicated
with displacements and maybe bump-mapping or something, then the
samples can become quite distant from the pixel being written,
spreading out into neighboring fragments being rendered
simultaneously etc. These are all cause for snapshotting...
For now though it's a very simple implementation that at least
makes drizzle overlayable, while also providing a smoke test for
the new snapshotting functionality.
|
|
It seems like it might be most ergonomic and convenient for
everything to just use til_fb_fragment_t and rely on ops.submit
to determine if the fragment is a page or not, and if it is how
to submit it.
This commit brings things into that state of the world, it feels
kind of gross at the til_fb_page_*() API. See the large comment
in til_fb.c added by this commit for more information.
I'm probably going to just run with this for now, it can always
get cleaned up later. What's important is to get the general
snapshotting concept and functionality in place so modules can
make use of it. There will always be things to cleanup in this
messy tangle of a program.
|
|
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.
|
|
There's a need for the ability to efficiently snapshot fragments
via buffer swapping when possible, for modules that want to do
overlay effects which sample the input fragment at arbitrary
pixels other than the one being written to, while producing
output pixels.
Without first making a stable snapshot of the input fragment's
contents, you can't implement such algorithms because you destroy
the input fragment while writing the output pixels.
A simple solution would be to just allocate memory and copy the
input fragment's contents into the allocation, then sample the
copy while writing to the input (now output) fragment's memory.
But when the input fragment represents the entire framebuffer
page/window, it's technically practical to instead simply swap
out the input fragment for a fresh fragment acquired from the
framebuffer/window provider. Then just sample from the original
fragment while writing to the freshly acquired one now taking the
original's place.
Simple enough.
Except til_fb_fragment_t is also used to describe subfragments
within a larger buffer, and these can't be made discontiguous and
swapped out. For these fragments there's no escaping the need
for a copy to be made of the contents. So there needs to be a
way for the fragment itself to furnish an appropriate
snapshotting mechanism, and when what the cloning mechanism
returns can vary. Depending on the snapshotting mechanism's
implementation, there's also a need for the fragment to furnish
an appropriate free method. If the snapshot is an entire page
from the native video backend, the backend must free it. If it's
just libc heap-allocated memory, then a plain old free()
suffices. If for some reason the memory can't be freed, then a
NULL free() method would be appropriate to simply do nothing.
So this commit introduces such free() and snapshot() methods in
the form of a til_fb_fragment_ops_t struct. There's no
implementations or use of these as of yet, this is purely
preparatory.
In addition to free() and snapshot(), a submit() method has also
been introduced for submitting ready frames to be displayed. Not
all fragments may be submitted, only "root" fragments which
represent an entire page from the video backend. It's these
fragments which will have a non-NULL submit() method, which the
video backend will have initialized appropriately in returning
the page's root fragment. This is a preparatory change in
anticipation of removing the til_fb_page_t type altogether,
replacing it with a simple til_fb_fragment_t having the
ops.submit() method set.
|
|
If you hit ^D during interactive setup it'd send things
infinitely spinning.
This commit treats eof when expecting more input as -EIO and
simply gives up. Which I imagine technically means it's possible
to terminate the last interactive question with EOF/^D instead of
newline and have it work, since we only check it before the
fgets() used to get more input.
|
|
This introduces a bespoke fragmenter for checkers. The generic
til_fb tiler isn't concerned with aesthetics so it doesn't
particularly care if clipped tiles are asymmetrically
distributed. It worked fine to get checkers developed and
working, but it's really unattractive to have the whole be
off-centered when the checkers don't perfectly align with the
frame size.
There's some gross aspects like leaving the frame_{width,height}
to be corrected at render time so render_fragment can access the
incoming frame_width for cell state determination.
|
|
quick and dirty fixup for proper use as checkers fill_module=
note this thing already relies on _checked() put_pixel variant
|
|
s/unchecked/checked/ and use frame dimensions, probably more fixes
needed but this prevents crashing as checkers fill_module= at least.
|
|
Sorry but more s/unchecked/checked/ and now this seems to not
crash when used as a checkers fill_module.
|
|
This is a first approximation of correct handling of arbitrarily
clipped frames described by the incoming fragment.
It's still relying on the _checked() put_pixel variants for
clipping. That should probably be improved by constraining the
loops to the clipped fragment edges.
|
|
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.
|
|
These modules have been doing their work in prepare_frame(), but
aren't actually threaded modules and don't return a frame plan
from prepare_frame() nor do they provide a render_fragment() to
complement the prepare_frame().
The convention thus far has been that single-threaded modules
just provide a render_fragment and by not providing a
prepare_frame they will be executed serially.
These two modules break the contract in a sense by using
prepare_frame() without following up with render_fragment().
I'm not sure why it happened that way, maybe at one time
prepare_frame() had access to some things that render_fragment()
didn't.
In any case, just make these use render_fragment() like any other
simple non-threaded module would.
This was actually causing a crash when n_cpus=1 because
module_render_fragment() was assuming the prepare_frame() branch
would include a render_fragment(). It should probably be
asserting as such.
|
|
This is a step towards properly handling nested settings, so we
can do stuff like:
--module=rtv,channels=compose\,layers=checkers\\\,fill_module=shapes\\\,size=64\,texture=plasma
and have rtv actually cycle through just compose with
checkers+plasma layers but holding the specified checkers
settings to shapes filler with a size of 64, randomizing the
rest.
There's more work to do before that can actually happen, but
first thing is to just support escaping the settings values.
|
|
Wire up seed via til_module_context.seed in the obvious manner,
minimal change to the code, no functional difference besides
giving pixbounces instances an isolated random seed state.
|
|
Normalized all the randomizers to use til_module_context.seed
while in here.
|
|
Nothing uses libs/sig yet, but this will probably become an issue
once that changes.
|
|
wired up to til_module_context.seed
|
|
This is a little contorted but not too bad. The input to
particles_new() is just a const conf struct, so instead of
passing in the seed value for particles_t to contain, a pointer
to where the seed lives is passed in via the conf. This requires
the caller to persist a seed somewhere outside the particles
instance, but at least in rototiller we already have that
conveniently in til_module_context_t.
|
|
More obvious migrations to using the supplied seed
|
|
also update call sites in modules/{meta2d,swab} accordingly
|
|
More plumbing seed to rand in the obvious way...
|
|
Just plumbing seed down in the obvoius manner, this could
probably be cleaned up a bit in the future.
|
|
The purpose of printing the setup is to enable reproducing it,
the seed is part of that reconstruction - especially when it's
been autogenerated.
|
|
Due to how the values get matched as strings this extraneous 0
was interfering with FLUI2D_DEFAULT_CLOCKSTEP actually matching
anything.
|
|
just fixing up some vestigial rand() invocations to use the seed
|
|
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.
|