rearrange-, link in and fix further content (user/design docs)
This commit is contained in:
parent
1081e9fe8c
commit
991e78633f
10 changed files with 621 additions and 400 deletions
|
|
@ -3,6 +3,13 @@ Lumiera Design: Application and Session Configuration
|
|||
|
||||
*TODO* : 'write design document'
|
||||
|
||||
The Lumiera application uses two quite different sources for configuration
|
||||
|
||||
- individual __settings values__ can be loaded from an application- and user configuration in the conventional way
|
||||
- various facilities, especially in the Proc-Layer, base the actual behavour on __queries__, which are
|
||||
to be resolve employing a rules based system '(planned)'. Configuration rules will be provided by the
|
||||
application (defaults), a session template and rules stored in the actual session.
|
||||
|
||||
-> see also the link:{ldoc}/technical/backend/ConfigLoader.html[Config Loader brainstorming from 2008] (implementation details)
|
||||
|
||||
[icon="warning.png"]
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ Lumiera Design Documents
|
|||
* link:application/index.html[Application]
|
||||
* link:plugins/index.html[Plug-ins]
|
||||
|
||||
The following document might become an introduction into
|
||||
Lumiera internals: link:innerCore/the_inner_core.html[Lumiera the Inner Core]
|
||||
|
||||
|
||||
[icon="warning.png"]
|
||||
WARNING: Website under construction
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ the source directory structures.
|
|||
|
||||
This three Layers are:
|
||||
The User Interface::
|
||||
User interfaces are implemented as plugins, most commonly one will see
|
||||
User interfaces are implemented as plug-ins, most commonly one will see
|
||||
the default GUI. But also scripting interfaces or specialized GUI's are
|
||||
possible.
|
||||
|
||||
|
|
@ -35,12 +35,11 @@ The extra components are:
|
|||
The main program itself, basically acts only as loader to pull the rest up.
|
||||
|
||||
Common::
|
||||
Vital components which must be available for pulling the system up, part
|
||||
of the main program.
|
||||
Vital components and common services which must be available for pulling up
|
||||
any part of the system.
|
||||
|
||||
Library::
|
||||
A lot of (mostly) stateless helper functionality which is used by all the
|
||||
rest.
|
||||
A lot of (largely) stateless helper functionality used throughout the code.
|
||||
|
||||
|
||||
Coding Style
|
||||
|
|
@ -48,85 +47,349 @@ Coding Style
|
|||
|
||||
The Lumiera team agreed on using GNU coding style with some exceptions (no
|
||||
tabs, line wrap must not always be enforced). Otherwise we are a bit pedantic
|
||||
to be consistent to make the codebase uniform. Function nameing conventions
|
||||
and other details are described in sereral RFCs.
|
||||
to be consistent to make the codebase uniform. Function naming conventions
|
||||
and other details are described in several RFCs.
|
||||
|
||||
Documentation
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Every public function should be documented with doxygen comments. The design
|
||||
is documented in this text, in various RFC's. Bootstrapping the Lumiera design
|
||||
used serveral other places, some tiddlywikis, an uml model and cehteh's
|
||||
private wiki, most of this information is asciidoced meanwhile and in progress
|
||||
to be integrated in a now documentation hierarchy.
|
||||
Every public function should be documented with doxygen comments. The overall
|
||||
design is outlined in this text and documented in the various detail pages and
|
||||
accompanying RFCs. Bootstrapping the Lumiera design used several other places,
|
||||
in several TiddlyWikis, an UML model and cehteh's private wiki, most of this
|
||||
information is asciidoced meanwhile and in progress to be integrated in the
|
||||
central documentation hierarchy.
|
||||
|
||||
|
||||
Test Driven Development
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
We strive to use _Test-Driven-Development_ this means tests are written first,
|
||||
We strive to use _Test-Driven-Development:_ tests are to be written first,
|
||||
defining the specification of the entity being tested. Then things get
|
||||
implemented until they pass their tests. Practice shows that this approach
|
||||
isn't always suiteable, nevertheless we try to follow it closely and maintain
|
||||
rigid testsuites.
|
||||
implemented until they pass their tests. While increasing the initial
|
||||
development effort significantly, this approach is known to lead to
|
||||
clearly defined components and overall increases code quality.
|
||||
In practice, this approach might not be suitable at times,
|
||||
nevertheless we try to sick to it as far as possible
|
||||
and maintain fairly complete test coverage.
|
||||
|
||||
|
||||
Lumiera Main
|
||||
------------
|
||||
Lumiera Application
|
||||
-------------------
|
||||
Generally speaking, the Application is comprised of several self contained
|
||||
_subsystems_, which may depend on each other. Dependencies between components
|
||||
are to be abstracted through interfaces. Based on the current configuration,
|
||||
the application framework brings up the necessary subsystems and finally
|
||||
conducts a clean shutdown. Beyond that, the application framework remains
|
||||
passive, yet provides vital services commonly used.
|
||||
|
||||
While the core components are linked into a coherent application and may
|
||||
utilise each other's services directly based on C/C++ language facilities,
|
||||
several important parts of the applications are loaded as plug-ins, starting
|
||||
with the GUI.
|
||||
|
||||
|
||||
Common
|
||||
------
|
||||
User Interfaces
|
||||
---------------
|
||||
|
||||
Config System
|
||||
The purpose of the user interface(s) is to act on the _high-level data model_
|
||||
contained within the Session, which belongs to the _processing layer_ below.
|
||||
User interfaces are implemented as plug-ins and are pulled up on demand,
|
||||
they won't contain any relevant persistent state beyond presentation.
|
||||
|
||||
|
||||
|
||||
Processing Layer
|
||||
----------------
|
||||
|
||||
High Level Model
|
||||
~~~~~~~~~~~~~~~~
|
||||
_tbw_
|
||||
|
||||
Assets
|
||||
^^^^^^
|
||||
_tbw_
|
||||
|
||||
Placement
|
||||
^^^^^^^^^
|
||||
_tbw_
|
||||
|
||||
Scoping
|
||||
^^^^^^^
|
||||
_tbw_
|
||||
|
||||
MObject References
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
_tbw_
|
||||
|
||||
QueryFocus
|
||||
^^^^^^^^^^
|
||||
_tbw_
|
||||
|
||||
Output Management
|
||||
~~~~~~~~~~~~~~~~~
|
||||
_tbw_
|
||||
|
||||
Stream Type System
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
_tbw_
|
||||
|
||||
Command Frontend
|
||||
~~~~~~~~~~~~~~~~
|
||||
_tbw_
|
||||
|
||||
Defaults Manager
|
||||
~~~~~~~~~~~~~~~~
|
||||
_tbw_
|
||||
|
||||
Rules System
|
||||
~~~~~~~~~~~~
|
||||
_tbw_
|
||||
|
||||
Builder
|
||||
~~~~~~~
|
||||
_tbw_
|
||||
|
||||
Low Level Model
|
||||
~~~~~~~~~~~~~~~
|
||||
_tbw_
|
||||
|
||||
Play/Render processes
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
_tbw_
|
||||
|
||||
|
||||
|
||||
|
||||
Backend
|
||||
-------
|
||||
|
||||
I/O Subsystem
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Plugin loader and interfaces
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
.OS Filehandles
|
||||
as mru cache, round robin reused
|
||||
|
||||
.Files
|
||||
Lumiera has its own abstract file handles which store the state and name of a
|
||||
file. The associated filehandle doesn't need to be kept open and will be
|
||||
reopened on demand. Hardlinked files are recognized and opened only once.
|
||||
|
||||
.Memory Mapping
|
||||
All file access is done by memory mapping to reduce data copies between
|
||||
userland and kernel. Moreover the kernel becomes responsible to schedule
|
||||
paging (which will be augmented by lumiera) to make the best use of available
|
||||
resources. Memory is mapped in biggier possibly overlapping windows of
|
||||
resonable sized chunks. Requests asking for a contingous set of data from the
|
||||
file in memory.
|
||||
|
||||
|
||||
.Indexing
|
||||
|
||||
.Frameprovider
|
||||
|
||||
|
||||
Threadpools
|
||||
~~~~~~~~~~~
|
||||
|
||||
Manages serveral classes of threads in pools. The threadpool is reasonable
|
||||
dumb. Higher level management will be done by the Schedulers and Jobs.
|
||||
|
||||
|
||||
Schedulers
|
||||
~~~~~~~~~~
|
||||
|
||||
Scheduling Queues for different purposes:
|
||||
|
||||
.Deadline
|
||||
Higher priority jobs ordered by a deadline time plus some (negative) hystersis. Jobs are
|
||||
started when they approach their deadline. Jobs who miss their deadline are
|
||||
never scheduled here.
|
||||
|
||||
.Background
|
||||
Background jobs scheduled by priority and timeout.
|
||||
|
||||
|
||||
.Realtime
|
||||
Timer driven queue which starts jobs at defined absolute times. Timer might be
|
||||
also an external synchronization entity.
|
||||
|
||||
|
||||
Job
|
||||
^^^
|
||||
a job can be part of multiple queues, the queue which picks them first runs
|
||||
them. When other queues hit a running job they either just drop it or promote
|
||||
its priority (to be decided).
|
||||
|
||||
|
||||
Resource Management
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Running Lumiera requires a lot different resources, such as CPU-Time, Threads,
|
||||
IO Bandwidth, Memory, Address space and so on. Many of this resources are rather
|
||||
hard limited and the system will return errors when this limits are hit, but
|
||||
often one does not even reach this hard limits because performance will
|
||||
degrade way before coming into the realm of this limits. The goal for Lumiera
|
||||
is to find a sweet spot for operating with optimal performance. Thus we have
|
||||
some facilities to monitor and adjust resource usage depending and adapting to
|
||||
the system and current circumstances.
|
||||
|
||||
|
||||
Profiler
|
||||
^^^^^^^^
|
||||
|
||||
Collects statistic about resource load, helps to decide if job constraints can
|
||||
be fulfilled.
|
||||
|
||||
Things to watch:
|
||||
* cpu utilization
|
||||
* memory usage (swapping, paging)
|
||||
* I/O load, latency
|
||||
|
||||
|
||||
Budget Manager
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
resources need to be distributed among a lot subsystems and jobs. Each of this
|
||||
component can become part of a budgeting system which accounts resource usage
|
||||
and helps to distribute it. Resource usage is only voluntary managed.
|
||||
|
||||
|
||||
Resource collector
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Handles system errors related to resource shortage. There are several classes
|
||||
of resources defined. Other subsystems can hook in functions to free
|
||||
resources. Has multiple policies about how aggressive resources should be freed.
|
||||
|
||||
If no one cares it does a final abort(). So all systems should hook better
|
||||
recovery here in!
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Common Services
|
||||
---------------
|
||||
|
||||
Subsystem runner
|
||||
~~~~~~~~~~~~~~~~
|
||||
_tbw_
|
||||
|
||||
Lifecycle events
|
||||
~~~~~~~~~~~~~~~~
|
||||
_tbw_
|
||||
|
||||
Interface system
|
||||
~~~~~~~~~~~~~~~~
|
||||
_tbw_
|
||||
|
||||
Plugin loader
|
||||
~~~~~~~~~~~~~
|
||||
_tbw_
|
||||
|
||||
Rules system
|
||||
~~~~~~~~~~~~
|
||||
_tbw_
|
||||
|
||||
Serialiser
|
||||
~~~~~~~~~~
|
||||
_tbw_
|
||||
|
||||
Config loader
|
||||
~~~~~~~~~~~~~
|
||||
_tbw_
|
||||
|
||||
Lua Scripting
|
||||
~~~~~~~~~~~~~
|
||||
_tbw_
|
||||
|
||||
|
||||
Library
|
||||
-------
|
||||
|
||||
Meant to be extended as we go
|
||||
The Lumiera support library contains lots of helper functionality
|
||||
factored out from the internals and re-used. It is extended as we go.
|
||||
|
||||
|
||||
Locking
|
||||
~~~~~~~
|
||||
Based on object monitors. Performance critical code
|
||||
uses mutexes, condition vars and rwlocks direcly.
|
||||
Intentionally no semaphores.
|
||||
|
||||
mutex, condition vars, rwlocks. intentionally no semaphores.
|
||||
- C++ locks are managed by scoped automatic variables
|
||||
- C code uses macros to wrap critical sections
|
||||
|
||||
macros to put scoped around critical sections
|
||||
|
||||
//sync-classlock.hpp
|
||||
//sync.hpp
|
||||
|
||||
Time
|
||||
~~~~
|
||||
time handling and conversion at one central point
|
||||
Time values are represented by an opaque date type `lumiera::Time`
|
||||
with overloaded operators. The implementation is based on `gavl_time_t`,
|
||||
an integral (µsec) time tick value. Any Time handling and conversions
|
||||
is centralised in library routines.
|
||||
|
||||
We distinguish between time values and a _quantisation_ into a frame
|
||||
or sample grid. In any case, quantisation has to be done once, explicitly
|
||||
and as late as possible. See the link:{rfc}/TimeHandling.html[Time handling RfC].
|
||||
|
||||
|
||||
Errors
|
||||
~~~~~~
|
||||
|
||||
* As a Rule, Exceptions + RAII are to be preferred over error
|
||||
codes and manual cleanup. At external interfaces we rely on
|
||||
error states though.
|
||||
* Exceptions can happen everywhere and any time
|
||||
* Exceptions and Errors shall only be dealt with at locations where it's actually
|
||||
possible to _handle_ them (the so called ``fail not repair'' rule)
|
||||
* API functions are categorised by _error safety guarantee_
|
||||
- *EX_FREE* functions won't raise an exception or set an error state,
|
||||
unless the runtime system is corrupted.
|
||||
- *EX_STRONG* functions are known to have no tangible effect in
|
||||
case of raising an exception / error state (transactional behaviour).
|
||||
- *EX_SANE* functions might leave a partial change, but care to leave
|
||||
any involved objects in a sane state.
|
||||
* Raising an Exception creates an _error state_ -- error states can also
|
||||
be set directly per thread.
|
||||
* Error states are identified by pointers to static strings.
|
||||
* Error states are thread local and sticky (a new state can't be set
|
||||
unless a pending state got cleared).
|
||||
|
||||
* Errors are identified by pointers to static strings.
|
||||
* Errors are sticky (you cant set a new error unless the pending one got
|
||||
cleared).
|
||||
|
||||
.General definition and declarations of errors
|
||||
Errors get declared in headers with the `LUMIERA_ERROR_DECLARE(err)` macro.
|
||||
Exception hierarchy
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
Typically, when an error situation is detected, the error will be categorised
|
||||
by throwing the appropriate exception. Exceptions provide a mechanism to attach
|
||||
the root cause. The classification happens according to the _relevance for the
|
||||
application_ as a whole -- _not_ according to the cause.
|
||||
|
||||
Then the implementaton file uses the macros `LUMIERA_ERROR_DEFINE(err, msg)`
|
||||
to instance the error. There is no central registry, Any component can
|
||||
introduce its own errorcodes but must ensure that the error identifier is
|
||||
`lumiera::Error`:: root of error hierarchy. extends `std::exception`
|
||||
`error::Logic`:: contradiction to internal logic assumptions detected
|
||||
`error::Fatal`:: (⤷ `Logic`) unable to cope with, internal logic floundered
|
||||
`error::Config`:: execution aborted due to misconfiguration
|
||||
`error::State`:: unforeseen internal state (usually causes component restart)
|
||||
`error::Flag`:: (⤷ `State`) non-cleared error state from C code
|
||||
`error::Invalid`:: invalid input or parameters encountered
|
||||
`error::External`:: failure in external service the application relies on
|
||||
`error::Assertion`:: assertion failure
|
||||
|
||||
Please be sure to clear the error state whenever catching and handling an exception.
|
||||
|
||||
|
||||
Error states
|
||||
^^^^^^^^^^^^
|
||||
Errors states get declared in headers with the `LUMIERA_ERROR_DECLARE(err)` macro.
|
||||
A matching definition needs to reside in some translation unit, using the
|
||||
`LUMIERA_ERROR_DEFINE(err, msg)` macro. There is no central registry, any component
|
||||
can introduce its own errorcodes but must ensure that the error identifier is
|
||||
unique.
|
||||
|
||||
.Error handling in C
|
||||
There are two helper macro forms for setting up error conditions, one is
|
||||
`LUMIERA_ERROR_SET..(flag, err, extra)` and the other one is
|
||||
`LUMIERA_ERROR_GOTO..(flag, err, extra)`. Each for differnt logging levels.
|
||||
`LUMIERA_ERROR_GOTO..(flag, err, extra)`. Each for different logging levels.
|
||||
The `SET` form just logs an error and sets it, the `GOTO` form also jumps to
|
||||
an error handler. Both take a NoBug flag used for logging and a optional
|
||||
`extra` c-string.
|
||||
|
|
@ -156,8 +419,71 @@ mayfail()
|
|||
--------------------------------------------------------------------------------
|
||||
|
||||
|
||||
.C++ exceptions
|
||||
|
||||
Singletons
|
||||
~~~~~~~~~~
|
||||
_tbw_
|
||||
|
||||
Extensible Factory
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
_tbw_
|
||||
|
||||
Visiting Tool
|
||||
~~~~~~~~~~~~~
|
||||
_tbw_
|
||||
|
||||
Iterators
|
||||
~~~~~~~~~
|
||||
Lumiera Forward Iterator
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
_tbw_
|
||||
|
||||
Iterator Adapters
|
||||
^^^^^^^^^^^^^^^^^
|
||||
_tbw_
|
||||
|
||||
Itertools
|
||||
^^^^^^^^^
|
||||
_tbw_
|
||||
|
||||
|
||||
Wrappers and Opaque Holders
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- smart handle
|
||||
- unified value/ptr/reference holder
|
||||
- ownership managing collection
|
||||
- opaque holder to ``piggypack'' an object inline,
|
||||
without the need for heap allocated storage
|
||||
- vector of references
|
||||
|
||||
Unique Identifiers
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
LUID
|
||||
^^^^
|
||||
Generating 128 bit non cryptographic strong unique identifiers.
|
||||
|
||||
- having an alternative representation to store a pointer
|
||||
- may be extended for a strong (slow) and a fast (weak) variant in future
|
||||
|
||||
EntryID
|
||||
^^^^^^^
|
||||
Combines an user readable ID, a (compile time) type tag and a hash-ID.
|
||||
The latter is based on the symbolic ID and the type tag, which is discarded
|
||||
at runtime (type erasure)
|
||||
|
||||
Typed Lookup
|
||||
^^^^^^^^^^^^
|
||||
_planned_ a system of per-type lookup tables, based on `EntryID`, together
|
||||
with an type specific access functor. Together, this allows to translate
|
||||
transparently and typesafe from symbolic ID to object instance, which
|
||||
is an prerequisite for integrating a rules based system. Besides, these
|
||||
tables allow unique IDs per type
|
||||
|
||||
|
||||
Allocators
|
||||
~~~~~~~~~~
|
||||
_tbw_
|
||||
|
||||
|
||||
Memory Pools
|
||||
|
|
@ -170,40 +496,6 @@ dynamic situations.
|
|||
* supporting a destructor callback to free all objects
|
||||
|
||||
|
||||
|
||||
Polymorphic Programming in C
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Just a macro for simplyfying vtable function calls
|
||||
VCALL(Handle, function, arguments...)
|
||||
translates to
|
||||
Handle->vtable->function (Handle, arguments...)
|
||||
|
||||
The user is responsible for setting up a `vtable` member in his datastructures
|
||||
this macro does some NoBug checks that self and function are initialized.
|
||||
|
||||
|
||||
Unique Identifiers
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Generating 128 bit non cryptographic strong unique identifiers.
|
||||
|
||||
* having an alternative representation to store a pointer
|
||||
|
||||
* may be extended for a strong (slow) and a fast (weak) variant in future
|
||||
|
||||
|
||||
CLib wrappers
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Some wrapers for the C memory management functions malloc, calloc, realloc and
|
||||
free which never fail. In case of an error the resourcecollector in the
|
||||
backend is invoked to free resources or doing an emergency shutdown.
|
||||
|
||||
Safe wrapers for some string functions from the C-library which also never
|
||||
fail. NULL strings are propagated to "" empty strings.
|
||||
|
||||
|
||||
Temporary Buffers
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
@ -215,6 +507,21 @@ Very fast and efficient from smallest too hugest allocations. No need to care
|
|||
for 'free()'
|
||||
|
||||
|
||||
Template Metaprogramming
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Typelists
|
||||
^^^^^^^^^
|
||||
_tbw_
|
||||
|
||||
Tuples
|
||||
^^^^^^
|
||||
_tbw_
|
||||
|
||||
Functor Utils
|
||||
^^^^^^^^^^^^^
|
||||
_tbw_
|
||||
|
||||
|
||||
Preprocessor Metaprogramming
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
|
@ -222,6 +529,29 @@ Preprocessor Metaprogramming
|
|||
ppmpl.h
|
||||
|
||||
|
||||
CLib wrappers
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Some wrapers for the C memory management functions malloc, calloc, realloc and
|
||||
free which never fail. In case of an error the resourcecollector in the
|
||||
backend is invoked to free resources or doing an emergency shutdown.
|
||||
|
||||
Safe wrapers for some string functions from the C-library which also never
|
||||
fail. NULL strings are propagated to "" empty strings.
|
||||
|
||||
|
||||
Polymorphic Programming in C
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Just a macro for simplyfying vtable function calls
|
||||
VCALL(Handle, function, arguments...)
|
||||
translates to
|
||||
Handle->vtable->function (Handle, arguments...)
|
||||
|
||||
The user is responsible for setting up a `vtable` member in his datastructures
|
||||
this macro does some NoBug checks that self and function are initialized.
|
||||
|
||||
|
||||
Algorithms & Datastructures
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
@ -247,19 +577,17 @@ actual implementation.
|
|||
|
||||
* Fine grained (block level) locking with different modes
|
||||
* supports cursors to iterate over the data, in both directions
|
||||
* exact and inexact searched (whats close before/after something)
|
||||
* exact and inexact searched (what's close before/after something)
|
||||
|
||||
|
||||
Cuckoo Hashing
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
Currently defunct, to be revieved someday.
|
||||
_Currently defunct, to be revived someday_
|
||||
|
||||
|
||||
Hash functions
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
planned
|
||||
_planned_
|
||||
|
||||
|
||||
Linked Lists
|
||||
|
|
@ -347,139 +675,3 @@ Items not used propagate towards the tail where they will be reused.
|
|||
//wrapperptr.hpp
|
||||
|
||||
|
||||
|
||||
User Interfaces
|
||||
---------------
|
||||
|
||||
User interfaces are stateless and act only as manipulators on the Session
|
||||
which is kept in the processing layer below. User interfaces are implemented
|
||||
as plugins and are pulled up on demand.
|
||||
|
||||
|
||||
Processing Layer
|
||||
----------------
|
||||
|
||||
High Level Model
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Rules System
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Low Level Model
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
|
||||
Backend
|
||||
-------
|
||||
|
||||
I/O Subsystem
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
.OS Filehandles
|
||||
|
||||
* as mru cache, round robin reused
|
||||
|
||||
.Files
|
||||
|
||||
Lumiera has its own abstract file handles which store the state and name of a
|
||||
file. The associated filehandle doesn't need to be kept open and will be
|
||||
reopened on demand. Hardlinked files are recognized and opened only once.
|
||||
|
||||
.Memory Mapping
|
||||
|
||||
All file access is done by memory mapping to reduce data copies between
|
||||
userland and kernel. Moreover the kernel becomes responsible to schedule
|
||||
paging (which will be augmented by lumiera) to make the best use of available
|
||||
resources. Memory is mapped in biggier possibly overlapping windows of
|
||||
resonable sized chunks. Requests asking for a contingous set of data from the
|
||||
file in memory.
|
||||
|
||||
|
||||
.Indexing
|
||||
|
||||
.Frameprovider
|
||||
|
||||
|
||||
Threadpools
|
||||
~~~~~~~~~~~
|
||||
|
||||
Manages serveral classes of threads in pools. The threadpool is reasonable
|
||||
dumb. Higher level management will be done by the Schedulers and Jobs.
|
||||
|
||||
|
||||
Schedulers
|
||||
~~~~~~~~~~
|
||||
|
||||
Scheduling Queues for different purposes:
|
||||
|
||||
.Deadline
|
||||
Higher priority jobs ordered by a deadline time plus some (negative) hystersis. Jobs are
|
||||
started when they approach their deadline. Jobs who miss their deadline are
|
||||
never scheduled here.
|
||||
|
||||
.Background
|
||||
Background jobs scheduled by priority and timeout.
|
||||
|
||||
|
||||
.Realtime
|
||||
Timer driven queue which starts jobs at defined absolute times. Timer might be
|
||||
also an external synchronization entity.
|
||||
|
||||
|
||||
Job
|
||||
^^^
|
||||
|
||||
a job can be part of multiple queues, the queue which picks them first runs
|
||||
them. When other queues hit a running job they either just drop it or promote
|
||||
its priority (to be decided).
|
||||
|
||||
|
||||
Resource Management
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Running Lumiera requires a lot different resources, such as CPU-Time, Threads,
|
||||
IO Bandwidth, Memory, Address space and so on. Many of this resources are rather
|
||||
hard limited and the system will return errors when this limits are hit, but
|
||||
often one does not even reach this hard limits because performance will
|
||||
degrade way before coming into the realm of this limits. The goal for Lumiera
|
||||
is to find a sweet spot for operating with optimal performance. Thus we have
|
||||
some facilities to monitor and adjust resource usage depending and adapting to
|
||||
the system and current circumstances.
|
||||
|
||||
|
||||
Profiler
|
||||
^^^^^^^^
|
||||
|
||||
Collects statistic about resource load, helps to decide if job constraints can
|
||||
be fulfilled.
|
||||
|
||||
Things to watch:
|
||||
* cpu utilization
|
||||
* memory usage (swapping, paging)
|
||||
* I/O load, latency
|
||||
|
||||
|
||||
Budget Manager
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
resources need to be distributed among a lot subsystems and jobs. Each of this
|
||||
component can become part of a budgeting system which accounts resource usage
|
||||
and helps to distribute it. Resource usage is only voluntary managed.
|
||||
|
||||
|
||||
Resourcecollector
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
Handles system errors related to resource shortage. There are serveral classes
|
||||
of resources defined. Other subsystems can hook in functions to free
|
||||
resources. Has multiple policies about how aggressive resources should be freed.
|
||||
|
||||
If no one cares it does a final abort(). So all systems should hook better
|
||||
recovery here in!
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ Description
|
|||
nominal start time is)
|
||||
* Each frame starts when the locator hits its lower border (inclusively) and
|
||||
ends when the locator is on its upper border (exclusively)
|
||||
image:images/Lumi.FramePositions1.png[]
|
||||
image:{imgd}/Lumi.FramePositions1.png[]
|
||||
* When the locator snaps to frames this means it can be placed on the start
|
||||
positions of the frames solely
|
||||
* When the locator is placed on such a start position, this means 'always'
|
||||
|
|
@ -50,7 +50,7 @@ Description
|
|||
direction
|
||||
* There is no single best choice where to put this "POE", thus we provide a
|
||||
switch
|
||||
image:images/Lumi.FramePositions2.png[]
|
||||
image:{imgd}/Lumi.FramePositions2.png[]
|
||||
- 'Point of evaluation' of the automation is in the middle of the timespan
|
||||
covered by a frame
|
||||
- 'Point of evaluation' is on the lower bound of each frame
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ User Documentation
|
|||
'to be written...'
|
||||
|
||||
* link:manual.html[We need to write a user manual....]
|
||||
|
||||
* The following document might become an introductory overview: +
|
||||
link:outerSpace/lumiera_from_outer_space.html[Lumiera from Outer Space]
|
||||
* link:outerSpace/Glossary.html[Glossary of common terms]
|
||||
|
||||
[icon="warning.png"]
|
||||
WARNING: Website under construction
|
||||
|
|
|
|||
196
doc/user/outerSpace/Glossary.txt
Normal file
196
doc/user/outerSpace/Glossary.txt
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
Glossary
|
||||
========
|
||||
|
||||
|
||||
|
||||
'NOTE Draft, please help rephrase/review and sort this terms, shorten
|
||||
explanations, the long explanation is the topic of the document above..'
|
||||
|
||||
|
||||
Project::
|
||||
the top-level context in which all edit work is done over an extended
|
||||
period of time. The Project can be saved and re-opened. It is
|
||||
comprised of the collection of all things the user is working on, it
|
||||
contains all informations, assets, state and objects to be edited.
|
||||
|
||||
Session::
|
||||
the current in-memory representation of the Project when opened within
|
||||
an instance of Lumiera. This is an implementation-internal term. For
|
||||
the GUI and the users POV we should always prefer the term "Project"
|
||||
for the general concept.
|
||||
|
||||
Timeline View::
|
||||
A view in the GUI featuring a given Timeline. There might be multiple
|
||||
views of the same timeline, all sharing the same PlayController. A
|
||||
proposed extension is the ability to 'focus' a timeline view to a
|
||||
sub-Sequence contained within the top-level sequence of the underlying
|
||||
Timeline. (Intended for editing meta-clips)
|
||||
|
||||
Track-head/patchbay::
|
||||
TODO: better term for this
|
||||
|
||||
//Note by Ichthyo: while I like the term "patchbay", my concern with this is that
|
||||
// it has already a very specific meaning in audio applications; and while our track heads
|
||||
// certainly can serve as a patchbay, that is not the main purpose and they can do things
|
||||
// beyond that..
|
||||
|
||||
the box in front of a track allowing to control properties of the
|
||||
elements contained within this track, unfold nested tracks and so on.
|
||||
To a large extent, it corresponds to the placement of this track and
|
||||
allows to manipulate this placement
|
||||
|
||||
|
||||
Timeline::
|
||||
the top level element(s) within the Project. It is visible within a
|
||||
'timeline view' in the GUI and represents the effective (resulting)
|
||||
arrangement of media objects, resolved to a finite time axis, to be
|
||||
rendered for output or viewed in a Monitor (viewer window).
|
||||
Timeline(s) are top-level and may not be further combined. A timeline
|
||||
is comprised of:
|
||||
* Time axis, defining the time base
|
||||
* Play Controller (WIP: discussion if thats belongs to the timeline
|
||||
and if we want a 1:N relation here). Note by Ichthyo: yes, our
|
||||
current discussion showed us that a play controller rather gets
|
||||
allocated to a timeline, but isn't contained therein.
|
||||
* global pipes, i.e. global busses like in a mixing desk
|
||||
* exactly one top level Sequence
|
||||
|
||||
Time Axis::
|
||||
|
||||
An entity defining the temporal properties of a timeline. A time axis
|
||||
defines the time base, kind of timecode and absolute anchor point.
|
||||
Besides, it manages a set of frame quantisation grids, corresponding
|
||||
to the outputs configured for this timeline (through the global
|
||||
busses). The GUI representation is a time ruler with configurable time
|
||||
ticks showed on top of the timeline view
|
||||
|
||||
|
||||
Busses::
|
||||
A list of 'global Pipes' representing the possible outputs (master
|
||||
busses) similar to audio mixing desk. A bus defines the properties of
|
||||
the rendered output (Framerate, Resolution, Colorformat and so on).
|
||||
Busses are part of a Timeline.
|
||||
|
||||
Sequence::
|
||||
A collection of *Media Objects* (clips, effects, transitions, labels,
|
||||
automation) placed onto a tree of tracks. By means of this placement,
|
||||
the objects could be anchored relative to each other, relative to
|
||||
external objects, absolute in time. A sequence can connect to the
|
||||
global pipes when used as top-level sequence within a timeline, or
|
||||
alternatively it can act as a virtual-media when used within a
|
||||
meta-clip (nested sequence). In the default configuration, a Sequence
|
||||
contains just a single root track and sends directly to the master bus
|
||||
of the timeline.
|
||||
|
||||
Placement::
|
||||
A Placement represents a relation: it is always linked to a Subject
|
||||
(this being a Media Object) and has the meaning to place this Subject
|
||||
in some manner, either relatively to other Media Objects, by some
|
||||
Constraint or simply absolute at (time, output). Placements are used
|
||||
to stitch together the objects in the high-level-model. Placements
|
||||
thus are organised hierarchically and need to be _resolved_ to obtain
|
||||
a specific value (time point, output routing, layering, fade,...)
|
||||
|
||||
Pipe::
|
||||
Conceptual building block of the high-level model. It can be thought
|
||||
off as simple linear processing chain. A stream can be 'sent to' a
|
||||
pipe, in which case it will be mixed in at the input, and you can
|
||||
'plug' the output of a pipe to another destination. Further, effects
|
||||
or processors can be attached to the pipe. Besides the global pipes
|
||||
(busses) in each Timeline, each clip automatically creates N pipes
|
||||
(one for each distinct content stream. Typically N=2, for video and
|
||||
audio)
|
||||
|
||||
OutputDesignation::
|
||||
A specification denoting where to connect the output of a pipe.
|
||||
It might either be given _absoulutely_, i.e as Pipe-ID,
|
||||
or by an _relative_ or _indirect_ specification
|
||||
|
||||
PlayController::
|
||||
coordinating playback, cueing and rewinding of a playback position,
|
||||
visible as 'Playhead' cursor in the GUI. When in play state, a
|
||||
PlayController requests and directs a render process to deliver the
|
||||
media data needed for playback.
|
||||
|
||||
//TODO not sure about the term and if it's appropriate to include it here
|
||||
|
||||
RenderTask::
|
||||
basically a PlayController, but collecting output directly, without
|
||||
moving a PlayheadCursor (maybe a progress indicator) and not operating
|
||||
in a timed fashion, but freewheeling or in background mode
|
||||
|
||||
Controller Gui::
|
||||
This can be either a full Software implementation for a Transport
|
||||
control (Widgets for Start/Stop/Rev/Ffw etc) or some Gui managing an
|
||||
Input Device. They share some feature to attach them to controllable
|
||||
gui-entities (Viewers, Timeline Views)
|
||||
|
||||
Viewer::
|
||||
the display destination showing video frame and possibly some effect
|
||||
overlays (masking etc.). When attached to a timeline, a viewer
|
||||
reflects the state of the timeline's associated PlayController, and it
|
||||
attaches to the timeline's global pipes (stream-type match or
|
||||
explicitly), showing video as monitor image and sending audio to the
|
||||
system audio port. Possible extensions are for a viewer to be able to
|
||||
attach to probe points within the render network, to show a second
|
||||
stream as (partial) overlay for comparison, or to be collapsed to a
|
||||
mere control for sending video to a dedicated monitor (separate X
|
||||
display or firewire)
|
||||
|
||||
High Level Model::
|
||||
All the session content to be edited and manipulated by the user
|
||||
through the GUI. The high-level-model will be translated by the
|
||||
Builder into the Low Level Model for rendering.
|
||||
|
||||
Low Level Model::
|
||||
The generated Processing Graph, to be ``performed'' within the engine
|
||||
to yield rendered output
|
||||
|
||||
Builder::
|
||||
A kind of compiler which creates Low Level/Processing Graphs, by
|
||||
traversing and evaluating the relevant parts of the high-level-model
|
||||
and using the Rules System.
|
||||
|
||||
Timeline Segment::
|
||||
A range in the timeline which yields in one Processing graph, commonly
|
||||
the range between cut points (which require a reconfiguration of the
|
||||
graph).
|
||||
|
||||
// Note by Ichthyo: "Extent" sounds somewhat cool, just it didn't occur to me as a term.
|
||||
// We may well agree on it, if "extent" communicates the meaning better. Up to now, I called it "segment"
|
||||
|
||||
Assets View::
|
||||
The windows showing and managing the available things to work with.
|
||||
This are the ingested footage, already composed Clips, available
|
||||
Sub-Projects, Effects, Transitions and internal artefacts.
|
||||
|
||||
Rules System::
|
||||
Translating the Timeline to the underlying Processing Graphs involves
|
||||
some logic and knowledge about handling/converting data. This may be
|
||||
configued with this Rules System. Typically Lumiera will provide sane
|
||||
defaults for most purposes but may extended/refined for site specific
|
||||
things.
|
||||
|
||||
Processing Graph::
|
||||
Rendering is expressed as detailed network of Nodes, each defining a
|
||||
processing step.
|
||||
|
||||
Config System/Preferences::
|
||||
TODO: agree on one term here
|
||||
Provides defaults for all kinds of application configurations. These
|
||||
include machine specific configurations for performance
|
||||
characteristics, File and Plugins Paths and configuration data and so
|
||||
on. Note that this only provides defaults for otherwise not yet set
|
||||
data. Many settings will then be stored within the project and the
|
||||
Config/Preferences becomes overridden by that.
|
||||
|
||||
Input Device::
|
||||
some hardware controler, like a extra Keyboard, Midi Mixer, Jog, ..
|
||||
TODO: decide if the main keyboard as special (global) state.
|
||||
|
||||
Focus::
|
||||
TBD
|
||||
|
||||
Cursor::
|
||||
playback- or edit position
|
||||
|
||||
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
|
|
@ -118,7 +118,7 @@ Processing of Video (and audio) data can be generalized as graph processing
|
|||
(more precisely ``directed acyclic graphs''). Data flows on the edges of these
|
||||
graphs and is processed in the nodes.
|
||||
|
||||
image:graph.svg[Example for a graph]
|
||||
image:lumiera_big_graph.svg[Example for a graph]
|
||||
|
||||
When we look at this model we discover that we only need to build
|
||||
xref:builder[->] such graphs, the nodes themselves can be seen as black boxes
|
||||
|
|
@ -419,186 +419,7 @@ for intermediate data, to be reused later.
|
|||
Glossary
|
||||
--------
|
||||
|
||||
// NOTE Draft, please help rephrase/review and sort this terms, shorten
|
||||
// explanations, the long explanation is the topic of the document above
|
||||
|
||||
|
||||
Project::
|
||||
the top-level context in which all edit work is done over an extended
|
||||
period of time. The Project can be saved and re-opened. It is
|
||||
comprised of the collection of all things the user is working on, it
|
||||
contains all informations, assets, state and objects to be edited.
|
||||
|
||||
Session::
|
||||
the current in-memory representation of the Project when opened within
|
||||
an instance of Lumiera. This is an implementation-internal term. For
|
||||
the GUI and the users POV we should always prefer the term "Project"
|
||||
for the general concept.
|
||||
|
||||
Timeline View::
|
||||
A view in the GUI featuring a given Timeline. There might be multiple
|
||||
views of the same timeline, all sharing the same PlayController. A
|
||||
proposed extension is the ability to 'focus' a timeline view to a
|
||||
sub-Sequence contained within the top-level sequence of the underlying
|
||||
Timeline. (Intended for editing meta-clips)
|
||||
|
||||
Track-head/patchbay::
|
||||
TODO: better term for this
|
||||
|
||||
//Note by Ichthyo: while I like the term "patchbay", my concern with this is that
|
||||
// it has already a very specific meaning in audio applications; and while our track heads
|
||||
// certainly can serve as a patchbay, that is not the main purpose and they can do things
|
||||
// beyond that..
|
||||
|
||||
the box in front of a track allowing to control properties of the
|
||||
elements contained within this track, unfold nested tracks and so on.
|
||||
To a large extent, it corresponds to the placement of this track and
|
||||
allows to manipulate this placement
|
||||
|
||||
|
||||
Timeline::
|
||||
the top level element(s) within the Project. It is visible within a
|
||||
'timeline view' in the GUI and represents the effective (resulting)
|
||||
arrangement of media objects, resolved to a finite time axis, to be
|
||||
rendered for output or viewed in a Monitor (viewer window).
|
||||
Timeline(s) are top-level and may not be further combined. A timeline
|
||||
is comprised of:
|
||||
* Time axis, defining the time base
|
||||
* Play Controller (WIP: discussion if thats belongs to the timeline
|
||||
and if we want a 1:N relation here). Note by Ichthyo: yes, our
|
||||
current discussion showed us that a play controller rather gets
|
||||
allocated to a timeline, but isn't contained therein.
|
||||
* global pipes, i.e. global busses like in a mixing desk
|
||||
* exactly one top level Sequence
|
||||
|
||||
Time Axis::
|
||||
|
||||
An entity defining the temporal properties of a timeline. A time axis
|
||||
defines the time base, kind of timecode and absolute anchor point.
|
||||
Besides, it manages a set of frame quantisation grids, corresponding
|
||||
to the outputs configured for this timeline (through the global
|
||||
busses). The GUI representation is a time ruler with configurable time
|
||||
ticks showed on top of the timeline view
|
||||
|
||||
|
||||
Busses::
|
||||
A list of 'global Pipes' representing the possible outputs (master
|
||||
busses) similar to audio mixing desk. A bus defines the properties of
|
||||
the rendered output (Framerate, Resolution, Colorformat and so on).
|
||||
Busses are part of a Timeline.
|
||||
|
||||
Sequence::
|
||||
A collection of *Media Objects* (clips, effects, transitions, labels,
|
||||
automation) placed onto a tree of tracks. By means of this placement,
|
||||
the objects could be anchored relative to each other, relative to
|
||||
external objects, absolute in time. A sequence can connect to the
|
||||
global pipes when used as top-level sequence within a timeline, or
|
||||
alternatively it can act as a virtual-media when used within a
|
||||
meta-clip (nested sequence). In the default configuration, a Sequence
|
||||
contains just a single root track and sends directly to the master bus
|
||||
of the timeline.
|
||||
|
||||
Placement::
|
||||
A Placement represents a relation: it is always linked to a Subject
|
||||
(this being a Media Object) and has the meaning to place this Subject
|
||||
in some manner, either relatively to other Media Objects, by some
|
||||
Constraint or simply absolute at (time, output). Placements are used
|
||||
to stitch together the objects in the high-level-model. Placements
|
||||
thus are organised hierarchically and need to be _resolved_ to obtain
|
||||
a specific value (time point, output routing, layering, fade,...)
|
||||
|
||||
Pipe::
|
||||
Conceptual building block of the high-level model. It can be thought
|
||||
off as simple linear processing chain. A stream can be 'sent to' a
|
||||
pipe, in which case it will be mixed in at the input, and you can
|
||||
'plug' the output of a pipe to another destination. Further, effects
|
||||
or processors can be attached to the pipe. Besides the global pipes
|
||||
(busses) in each Timeline, each clip automatically creates N pipes
|
||||
(one for each distinct content stream. Typically N=2, for video and
|
||||
audio)
|
||||
|
||||
PlayController::
|
||||
coordinating playback, cueing and rewinding of a playback position,
|
||||
visible as 'Playhead' cursor in the GUI. When in play state, a
|
||||
PlayController requests and directs a render process to deliver the
|
||||
media data needed for playback.
|
||||
|
||||
//TODO not sure about the term and if it's appropriate to include it here
|
||||
|
||||
RenderTask::
|
||||
basically a PlayController, but collecting output directly, without
|
||||
moving a PlayheadCursor (maybe a progress indicator) and not operating
|
||||
in a timed fashion, but freewheeling or in background mode
|
||||
|
||||
Controller Gui::
|
||||
This can be either a full Software implementation for a Transport
|
||||
control (Widgets for Start/Stop/Rev/Ffw etc) or some Gui managing an
|
||||
Input Device. They share some feature to attach them to controllable
|
||||
gui-entities (Viewers, Timeline Views)
|
||||
|
||||
Viewer::
|
||||
the display destination showing video frame and possibly some effect
|
||||
overlays (masking etc.). When attached to a timeline, a viewer
|
||||
reflects the state of the timeline's associated PlayController, and it
|
||||
attaches to the timeline's global pipes (stream-type match or
|
||||
explicitly), showing video as monitor image and sending audio to the
|
||||
system audio port. Possible extensions are for a viewer to be able to
|
||||
attach to probe points within the render network, to show a second
|
||||
stream as (partial) overlay for comparison, or to be collapsed to a
|
||||
mere control for sending video to a dedicated monitor (separate X
|
||||
display or firewire)
|
||||
|
||||
High Level Model::
|
||||
All the session content to be edited and manipulated by the user
|
||||
through the GUI. The high-level-model will be translated by the
|
||||
Builder into the Low Level Model for rendering.
|
||||
|
||||
Low Level Model::
|
||||
The generated Processing Graph, to be ``performed'' within the engine
|
||||
to yield rendered output
|
||||
|
||||
Builder::
|
||||
A kind of compiler which creates Low Level/Processing Graphs, by
|
||||
traversing and evaluating the relevant parts of the high-level-model
|
||||
and using the Rules System.
|
||||
|
||||
Timeline Segment::
|
||||
A range in the timeline which yields in one Processing graph, commonly
|
||||
the range between cut points (which require a reconfiguration of the
|
||||
graph).
|
||||
|
||||
// Note by Ichthyo: "Extent" sounds somewhat cool, just it didn't occur to me as a term.
|
||||
// We may well agree on it, if "extent" communicates the meaning better. Up to now, I called it "segment"
|
||||
|
||||
Assets View::
|
||||
The windows showing and managing the available things to work with.
|
||||
This are the ingested footage, already composed Clips, available
|
||||
Sub-Projects, Effects, Transitions and internal artefacts.
|
||||
|
||||
Rules System::
|
||||
Translating the Timeline to the underlying Processing Graphs involves
|
||||
some logic and knowledge about handling/converting data. This may be
|
||||
configued with this Rules System. Typically Lumiera will provide sane
|
||||
defaults for most purposes but may extended/refined for site specific
|
||||
things.
|
||||
|
||||
Processing Graph::
|
||||
Rendering is expressed as detailed network of Nodes, each defining a
|
||||
processing step.
|
||||
|
||||
Config System/Preferences::
|
||||
TODO: agree on one term here
|
||||
Provides defaults for all kinds of application configurations. These
|
||||
include machine specific configurations for performance
|
||||
characteristics, File and Plugins Paths and configuration data and so
|
||||
on. Note that this only provides defaults for otherwise not yet set
|
||||
data. Many settings will then be stored within the project and the
|
||||
Config/Preferences becomes overridden by that.
|
||||
|
||||
Input Device::
|
||||
some hardware controler, like a extra Keyboard, Midi Mixer, Jog, ..
|
||||
TODO: decide if the main keyboard as special (global) state.
|
||||
|
||||
Cursor::
|
||||
TBD
|
||||
The above outline of the design uses a lot of special terms and common termes
|
||||
used with specific meaning within Lumiera. To ease the understanding, we've
|
||||
collected a link:Glossary.html[Glossary of common terms].
|
||||
|
||||
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
Loading…
Reference in a new issue