Age | Commit message (Collapse) | Author |
|
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.
|
|
Now that there's the mem_fb backend, there's no need to disable
producing a rototiller binary in lieu of libdrm and libsdl2.
This commit also rejiggers some of the DEFAULT_VIDEO junk in
main.c to ensure it falls back on "mem" should there be no drm or
sdl2.
For now I'm going to leave the AM_CONDITIONAL junk surrounding
enabling rototiller in configure.ac, the define can just be
ignored for now.
|
|
The immediate impetus for adding this is to enable running
rototiller even on headless machines just for the sake of getting
some FPS measurements.
It'd be nice to get a sense for what FPS rototiller would
experience on larger modern machines like big EPYC or
Threadripper systems. But it seems most of those I can get
access to via others running them on work hardware or the like
can at most just run it over ssh without any display or risk of
disrupting the physical console.
But this is probably also useful for testing/debugging purposes,
especially since it doesn't bother to synchronize flips on
anything not even a timer. So a bunch of display complexity is
removed running with video=mem as well as letting the framerate
run unbounded.
Having said that, it might be nice to add an fps=N setting where
mem_fb uses a plain timer for scheduling the flips.
Currently the only setting is size=WxH identical to the sdl_fb
size= setting, defaulting to 640x480.
|
|
Until now the fb init has been receiving a til_settings_t to
access its setup. Now that there's a til_setup_t for
representing the fully baked setup, let's bring the fb stuff
up to speed so their init() behaves more like
til_module_t.create_context() WRT settings/setup.
This involves some reworking of how settings are handled in
{drm,sdl}_fb.c but nothing majorly different.
The only real funcitonal change that happened in the course of
this work is I made it possible now to actually instruct SDL to
do a more legacy SDL_WINDOW_FULLSCREEN vs.
SDL_WINDOW_FULLSCREEN_DESKTOP where SDL will attempt to switch
the video mode.
This is triggered by specifying both a size=WxH and fullscreen=on
for video=sdl. Be careful though, I've observed some broken
display states when specifying goofy sizes, which look like Xorg
bugs.
|
|
Once upon a time this thing asserted the pinned chunks were
empty, and that code is still sitting there commented out. But
when it was commented out ages ago, to enable bulk freeing of
chunkers without requiring unrefs of every allocation as an
optimization, no code was added to free the pinned chunks.
It's been easily ignored all this time since nobody really runs
rototiller long enough to notice, but that's becoming less true
now with how interesting something like:
--module=rtv,layers=compose,duration=1,context_duration=1,snow_module=none
is becoming...
There are other leaks still, largely surrounding settings, but
they are quite small. Eventually those will get tidied up as
well.
|
|
this introduces a color= setting syntax:
color=#rrggbb
color=0xrrggbb
color=rrggbb
where rrggbb is case-insensitive html-style hexadecimal
also introduces a fill= setting:
fill=color
fill=sampled
fill=textured
fill=random
fill=mixed
sampled draws the color from the incoming fragment when layered,
textured draws the pixels from the texture when available,
random randomizes the choice from color,sampled,textured.
mixed isn't implemented fully and is just aliased to random
currently. The thinking for mixed is to allow specifying
proportions for color,sampled,textured which would then be
applied as weights when randomizing the selection from the three
at every filled checker.
the current implementation is just calling rand() when
randomized, but should really be like the other dynamics in
checkers with rate control and hash-based.
and introduces a fill_module= setting:
this is a first stab at employing other modules for filling the
filled cells.
Note since checkers is already a threaded module, the fill module
context gets created per-cpu but with an n_cpus=1.
This is kind of the first time module contexts are being rendered
manifold for the same frame, and that's illuminating some
shortcomings which needed to be dealt with. Some modules
automatically advance a phase/T value on every render which gets
persisted in their context struct. With how checkers is using
contexts, it's desirable for multiple renders of the same context
using the same ticks to produce the same output. So modules need
to be more careful about time and determine "dt" (delta-time)
values, and animate proportional to ticks elapsed. When ticks
doesn't change between renders, dt is zero, and nothing should
change.
For now this is using a hard-coded list of modules to choose
from, you specify the module by name or "none" for no
fill_module (solid checker fill). ex: "fill_module=shapes"
There's a need for something like fragment color and flag
overrides to allow til_module_render() to be treated as more of a
brush where the caller gets to specify what colors to use, or if
texturing should be allowed. For now, when fill_module=$module
is employed, the color determination stuff within checkers
doesn't get applied. That will need to be fixed in the future.
|
|
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.
|
|
The palette mutates across frames, on a context-specific
schedule. Meaning the palette is per-context, so move it into
roto_context_t.
The phase also needs to be driven by ticks. And when ticks
doesn't change in cases where the same context is rendered
manifold in the same frame, the phase shouldn't move.
|
|
This way if a given context gets rendered repeatedly for the same
tick, no movement occurs, until ticks changes.
|
|
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 is leftover from 4e5286 which was mostly removed when
frame zeroing was simplified, but for some reason this was
missed.
Just get rid of the count as it's not used.
|
|
While testing an experimental checkers w/fill_module=blinds with
ASAN it became clear this module is making flawed assumptions
about fragment->frame_{width,height} and fragment->{width,height}
being equal.
When used by checkers for filling cells, there are situations
where the edge cell fragments need to describe a frame slightly
larger than the drawn area, because the cell size doesn't align
perfectly to the overall window/screen dimensions. So in these
cases the synthesized frame will still be a full cell's
dimensions while the width,height serve to clip within that area.
If modules aren't properly clipping their rendering, instead just
using frame_{width,height}, then they will have to use the
_checked() variants to ensure clipping occurs properly on a
per-pixel (slower) basis.
|
|
Contexts aren't void* anymore, and free the contexts array too on
failure.
|
|
This initializer could perform an out-of-bounds read since it
occurs before the n_modules bounds check.
Since the variable isn't even being used anymore just get rid of
this. Also found via ASAN.
|
|
While testing a checkers change that fills cells using other
modules, ASAN kept tripping on pixbounce:
==147817==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7fc78a31c10c at pc 0x55b30cd406e2 bp 0x7fc790afd0d0 sp 0x7fc790afd0c8
WRITE of size 4 at 0x7fc78a31c10c thread T2
#0 0x55b30cd406e1 in til_fb_fragment_put_pixel_unchecked pixbounce.c
#1 0x55b30cd3f8ae in pixbounce_render_fragment pixbounce.c
#2 0x55b30cd1dffb in module_render_fragment til.c
#3 0x55b30cd1d989 in til_module_render (/home/foo/src/rototiller/build/src/rototiller+0x134989)
#4 0x55b30cd22534 in checkers_render_fragment checkers.c
#5 0x55b30cd14681 in thread_func til_threads.c
#6 0x7fc792b3d5c1 in start_thread pthread_create.c
#7 0x7fc792bc2583 in __clone (/usr/lib/libc.so.6+0x112583)
0x7fc78a31c10c is located 2276 bytes to the right of 1228840-byte region [0x7fc78a1ef800,0x7fc78a31b828)
allocated by thread T0 here:
#0 0x55b30cccf219 in __interceptor_malloc (/home/foo/src/rototiller/build/src/rototiller+0xe6219)
#1 0x7fc792d0e528 (/usr/lib/libSDL2-2.0.so.0+0x39528)
Thread T2 created by T0 here:
#0 0x55b30cc3cfa8 in pthread_create (/home/foo/src/rototiller/build/src/rototiller+0x53fa8)
#1 0x55b30cd13fff in til_threads_create (/home/foo/src/rototiller/build/src/rototiller+0x12afff)
#2 0x55b30cd1d573 in til_init (/home/foo/src/rototiller/build/src/rototiller+0x134573)
#3 0x55b30cd08f6c in main (/home/foo/src/rototiller/build/src/rototiller+0x11ff6c)
#4 0x7fc792add30f in __libc_start_call_main libc-start.c
SUMMARY: AddressSanitizer: heap-buffer-overflow pixbounce.c in til_fb_fragment_put_pixel_unchecked
Shadow bytes around the buggy address:
0x0ff97145b7d0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0ff97145b7e0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0ff97145b7f0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0ff97145b800: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0ff97145b810: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x0ff97145b820: fa[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0ff97145b830: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0ff97145b840: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0ff97145b850: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0ff97145b860: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0ff97145b870: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==147817==ABORTING
---
Rather than spend time digging into pixbounce's arithmetic, just
using the checked variant for now.
|
|
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.
|
|
Preparatory commit for embedding a til type in the module
contexts, similar to til_setup_t for the module setups.
This will provide a convenient way to embed seed and n_cpus in
every module context, without having to implement that yourself.
But it also makes it so modules with no need for a context can
continue not implementing those methods, without obstructing
libtil from transparently doing it anyways with a bare
til_module_context_t.
This is kind of important as the current architecture made it
difficult to do things like create contexts with explicit n_cpus,
like in composite/meta modules which are already threaded and
wish to run embedded modules with n_cpus=1.
With this addition, n_cpus could be specified at context create
time, and it will always become remembered in the
til_module_context_t regardless of what the module implements.
That way it can definitely be carried into the prepare/render
methods, with no opportunity for disconnect between what was
passed to context create and what goes to the render methods.
Nothing is functionally changed in this commit alone, subsequent
commits will actually make use of it and realize what I've said
above.
|
|
This commit improves the error printed when cli-supplied args
fail, adding at least the key name to what used to be just a
stringified errno:
```
$ src/rototiller --module=shapes,scale=99
Shape type:
0: circle
1: pinwheel
2: rhombus
3: star
Enter a value 0-3 [1 (pinwheel)]:
Fatal error: unable to use args for setting "scale": Invalid argument
$
```
|
|
Forgot to add this when adding --go
|
|
eventually this file needs to get rewritten comprehensively with
the original content moved somewhere like ORIGIN.txt or whatever.
|
|
In rototiller this disables the automatic displaying of settings
actually used when they differ from what was explicitly specified
as args. Which also disables the waiting to press a key.
This should also get used by glimmer to automatically start
rendering without just putting up the configured settings panel
and waiting for a click on "go!".
|
|
I don't think rototiller is an appropriate place for being so
uncooperative, if someone gets the case wrong anywhere just make
it work. We should avoid making different things so subtly
different that case alone is the distinction anyways, so I don't
see this creating any future namespace collision problems.
|
|
A bunch of these have just been done
|
|
I didn't like the too fast spins where you can't even really get
a read on what's going on in the shape.
Suspect this will get tweaked more in the future...
|
|
This is kind of experimental, not sure how I feel about it.
pinch=0..1 with a bunch of fractions, 0 disables it.
pinch_spin=-1..1 same as spin=
pinches=1..10 number of pinches, which come in pairs
This applies to all the current shapes, for a tour:
--module=rtv,channels=shapes,duration=2,context_duration=2,snow_module=none --defaults
I think the speeds might go to high atm, I kind of liked the
slower spins before all this more.
|