summaryrefslogtreecommitdiff
AgeCommit message (Collapse)Author
2023-01-12modules/compose: fix segfault introduced by 83e41dVito Caputo
It was assumed (n_modules - n_overlayable) would give the number of non-overlayable modules appropriate as base layers. But with the skipping of hermetic and experimental modules the base_idx could be out of reach leaving layers NULL after the loop, which will segfault later when strlen() assumes it's non-NULL. This commit does the simple thing and also counts the unusable modules to subtract from those eligible for base layers along with n_overlayable.
2023-01-11til_{stream,tap}: add GPL headersVito Caputo
Just some banal paperwork...
2023-01-11til: omit experimental modules from til_module_setup()Vito Caputo
Don't make experimental modules available to the regular/potentially-interactive setup routine. There should be a flag like --experimental to generally enable these.
2023-01-11modules/montage: omit experimental and hermetic modulesVito Caputo
As with the other composite modules, if --experimental happens this will need adjustment to honor it. For now let's just prevent things from breaking when those modules start appearing.
2023-01-11modules/compose: omit experimental and hermetic modulesVito Caputo
This only omits the modules from the random layers Note the texture_values list is enumerated in compose_setup, so there's no corresponding change needed there. It might make sense to change that to a runtime-discovered list though, I think that was done in the pre-flags era.
2023-01-11modules/rtv: skip hermetic/experimental modules for "all"Vito Caputo
This allows explicit listing of such modules as channels, while protecting the automagic/defaults scenario. If there's a future --experimental flag or such added, then the TIL_MODULE_EXPERIMENTAL check will have to become conditional on it.
2023-01-11main: experimenting with ANSI codes for --print-pipesVito Caputo
This turns --print-pipes into a more top-like display. Redirect the FPS on stderr somewhere else to get less flickering e.g. 2>/dev/null pipes print to stdout.
2023-01-11til: intrdouce a couple new module flagsVito Caputo
TIL_MODULE_HERMETIC: There's likely to be some new modules that are more orchestration style components having external runtime dependencies. Think stuff like a sequencer talking to GNU Rocket, or something that plays back pattern data from external files. Those would need a GNU Rocket process to talk to somewhere, or input pattern file paths. So they shouldn't participate in stuff like random rtv shows unless they have some fallbacks for when the dependencies are unavailable. For pattern data it's realistic to include some builtin patterns to fallback on, but we're not there yet. So this flag when specified should opt out of things like rtv or checkers fill_module random selections. TIL_MODULE_EXPERIMENTAL: Theres no current way to have knowingly unstable/unfinished modules available in-tree for development/collaboration purposes without having them also make stuff like rtv unstable. Modules having this flag set should be excluded from random inclusion without a --experimental or some such runtime flag specified. This commit only assigns values and names for the flags, it doesn't implement anything.
2023-01-11* turn til_fb_fragment_t.stream into a discrete parameterVito Caputo
This was mostly done out of convenience at the expense of turning the fragment struct into more of a junk drawer. But properly cleaning up owned stream pipes on context destroy makes the inappropriateness of being part of til_fb_fragment_t glaringly apparent. Now the stream is just a separate thing passed to context create, with a reference kept in the context for use throughout. Cleanup of the owned pipes on the stream supplied to context create is automagic when the context gets destroyed. Note that despite there being a stream in the module context, the stream to use is still supplied to all the rendering family functions (prepare/render/finish) and it's the passed-in stream which should be used by these functions. This is done to support the possibility of switching out the stream frame-to-frame, which may be interesting. Imagine doing things like a latent stream and a future stream and switching between them on the fly for instance. If there's a sequencing composite module, it could flip between multiple sets of tracks or jump around multiple streams with the visuals immediately flipping accordingly. This should fix the --print-pipes crashing issues caused by lack of cleanup when contexts were removed (like rtv does so often).
2023-01-11main,til_args: employ the stream, add --print-pipesVito Caputo
This is a rudimentary integration of the new til_stream_t into rototiller. If the stream is going to continue living in til_fb_fragment_t, the fragmenters and other nested frame scenarios likely need to be updated to copy the stream through to make the pipes available to the nested renders. --print-pipes dumps the values found at the pipes' driver taps to stdout on every frame. Right now there's no way to externally write these values, but with --print-pipes you can already see where things are going and it's a nice visibility tool for tapped variables in modules. Only stars and plato tap variables presently, but that will improve.
2023-01-11fps: move FPS printing to stderrVito Caputo
With --print-pipes there will be a potential shitload of stuff getting printed out, and it'd be nice to easily distinguish that content from the FPS counter. Since stderr is normally less buffered than stdout (line buffered) not lose debugging information, just put the low-bandwidth periodic FPS print there instead. This leaves stdout for --print-pipes output which occurs every frame *and* may have a lot of content per print.
2023-01-11main: move args to rototiller_tVito Caputo
Particularly for simple boolean args it's desirable to just access their values directly in the args without any further cooking required. Rather than pointlessly duplicating those cases, just give visibility into the raw args.
2023-01-11src/modules/{stars,plato}: stream tapped variablesVito Caputo
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.
2023-01-11til_stream: first stab at implementing til_stream_tVito Caputo
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.
2023-01-10til_module_context: hash the pathVito Caputo
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.
2023-01-10til_tap: hash tap nameVito Caputo
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.
2023-01-10til_jenkins: add a rudimentary hash functionVito Caputo
Presently just for hashing paths and names
2023-01-10*: introduce paths for module contextsVito Caputo
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
2023-01-10modules/stars: add taps for some varsVito Caputo
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.
2023-01-10modules/plato: add taps for {spin,orbit}_rate varsVito Caputo
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.
2023-01-10til_tap: return tap by value instead of taking ptrVito Caputo
this is slightly more ergonomic by having one less pointer to get in the right place in the parameters
2023-01-10til_tap: convert taps to intrusive public structsVito Caputo
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.
2023-01-09til_tap: first stab at a tap interfaceVito Caputo
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.
2023-01-07til_fb: introduce til_fb_fragment_t.streamVito Caputo
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.
2022-11-12modules/compose: more robust texture preservationVito Caputo
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.
2022-11-09modules/strobe: force flash even at low FPSPhilip J Freeman
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
2022-09-05modules/roto: move tables init to context createVito Caputo
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...
2022-09-05modules/blinds: use til_ticks_to_rads(ticks)Vito Caputo
This gets rid of the static accumulator hack used for the blinds phase.
2022-09-05til_util: add helper for turning ticks into radiansVito Caputo
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.
2022-09-04modules/strobe: add rudimentary strobe light moduleVito Caputo
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
2022-09-04til: fixup til_fb_fragment_t.texture fragmentingVito Caputo
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.
2022-08-11modules/plato: add some rudimentary settingsVito Caputo
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.
2022-08-07modules/compose: add moire as a texture moduleVito Caputo
moire makes for an interesting texture, esp. mixed with itself: --seed=0x62f00a7b --module=compose,layers=julia:moire:drizzle,texture=moire
2022-08-07main: still show configured flags with --goVito Caputo
Show the info, but skip the wait step.
2022-08-07modules/drizzle: add a mapped overlay styleVito Caputo
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.
2022-08-07modules/drizzle: experimenting with the new snapshottingVito Caputo
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.
2022-08-07til: experimentally fragment-centric page apiVito Caputo
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.
2022-08-07til: til_fb_fragment_t **fragment_ptr all the thingsVito Caputo
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.
2022-08-07til_fb: introduce til_fb_fragment_t.opsVito Caputo
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.
2022-07-27setup: don't spin on EOF in setup_interactively()Vito Caputo
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.
2022-07-24modules/checkers: center unaligned checkers scenariosVito Caputo
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.
2022-07-24modules/plato: scale to frame sizeVito Caputo
quick and dirty fixup for proper use as checkers fill_module= note this thing already relies on _checked() put_pixel variant
2022-07-24modules/stars: more fast and nasty fragment clip to frame fixesVito Caputo
s/unchecked/checked/ and use frame dimensions, probably more fixes needed but this prevents crashing as checkers fill_module= at least.
2022-07-24modules/spiro: fast and dirty frame clipping fixupVito Caputo
Sorry but more s/unchecked/checked/ and now this seems to not crash when used as a checkers fill_module.
2022-07-24modules/shapes: fix up clipped fragment/frameVito Caputo
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.
2022-07-21til: simplify and clarify module_render_fragment()Vito Caputo
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.
2022-07-21modules/{compose,rtv}: s/prepare_frame/render_fragment/Vito Caputo
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.
2022-07-20til_settings: support rudimentary =value escapingVito Caputo
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.
2022-07-20modules/pixbounce: s/rand/rand_r/Vito Caputo
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.
2022-07-20modules/meta2d: more rand()->rand_r() conversionsVito Caputo
Normalized all the randomizers to use til_module_context.seed while in here.
© All Rights Reserved