// zlib open source license // // Copyright (c) 2019 David Forsgren Piuva // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. #ifndef DFPSR_API_MEDIA_MACHINE #define DFPSR_API_MEDIA_MACHINE #include "../implementation/image/Image.h" #include "../base/Handle.h" #include "../math/FixedPoint.h" namespace dsr { // A handle to a media machine. // Media machines can be used to generate, filter and analyze images. // Everything running in a media machine is guaranteed to be 100% deterministic to the last bit. // This reduces the amount of code where maintenance has to be performed during porting. // It also means that any use of float or double is forbidden. struct VirtualMachine; using MediaMachine = Handle; // TODO: Complete VirtualMachine with conditional jumps and document the language dialect used by MediaMachine. // Side-effect: Creates a media machine from Media Machine Code (*.mmc file). // Post-condition: Returns a reference counted MediaMachine handle to the virtual machine. MediaMachine machine_create(const ReadableString& code); // Post-condition: Returns true iff machine exists. bool machine_exists(const MediaMachine& machine); // Pre-condition: // * The machine must exist. // machine_exists(machine) // Returns the index to the method who's name matches methodName with case insensitivity in machine, or -1 if it does not exist in machine. int machine_findMethod(const MediaMachine& machine, const ReadableString& methodName); // Assign an input argument of a method before your call to machine_executeMethod. // Pre-condition: // * The machine must exist. // machine_exists(machine) // * methodIndex must refer to the method to call in machine. // 0 <= methodIndex < machine_getMethodCount(machine) // * inputIndex must refer to an input of the method in machine. // 0 <= inputIndex < machine_getInputCount(machine, methodIndex) // Side-effect: Sets the input at inputIndex in machine's method at methodIndex to input. void machine_setInputByIndex(MediaMachine& machine, int methodIndex, int inputIndex, int32_t input); void machine_setInputByIndex(MediaMachine& machine, int methodIndex, int inputIndex, const FixedPoint& input); void machine_setInputByIndex(MediaMachine& machine, int methodIndex, int inputIndex, const AlignedImageU8& input); void machine_setInputByIndex(MediaMachine& machine, int methodIndex, int inputIndex, const OrderedImageRgbaU8& input); // Call a method in the media machine, reading from input registers and writing to output registers. // Pre-condition: // * The machine must exist. // machine_exists(machine) // * All inputs of the method must be assigned before the call using the same methodIndex. // * methodIndex must refer to the method to call in machine. // 0 <= methodIndex < machine_getMethodCount(machine) // Side-effect: Writes the results to output arguments. void machine_executeMethod(MediaMachine& machine, int methodIndex); // Read output register at outputIndex // Pre-condition: // * The machine must exist. // machine_exists(machine) // * methodIndex must refer to the method in machine's last call to machine_executeMethod. // 0 <= methodIndex < machine_getMethodCount(machine) // * The output index must be within range. // 0 <= outputIndex < machine_getOutputCount(machine, methodIndex) // Post-condition: Returns the output at outputIndex. FixedPoint machine_getFixedPointOutputByIndex(const MediaMachine& machine, int methodIndex, int outputIndex); AlignedImageU8 machine_getImageU8OutputByIndex(const MediaMachine& machine, int methodIndex, int outputIndex); OrderedImageRgbaU8 machine_getImageRgbaU8OutputByIndex(const MediaMachine& machine, int methodIndex, int outputIndex); // Pre-condition: // * The machine must exist. // machine_exists(machine) // Post-condition: Returns the number of methods in machine. int machine_getMethodCount(const MediaMachine& machine); // Pre-condition: // * The machine must exist. // machine_exists(machine) // * methodIndex must refer to the method that you want to get the name from. // 0 <= methodIndex < machine_getMethodCount(machine) // Post-condition: Returns the name of the method at methodIndex in machine. String machine_getMethodName(const MediaMachine& machine, int methodIndex); // Pre-condition: // * The machine must exist. // machine_exists(machine) // * methodIndex must refer to the method that you want to get the input count from. // 0 <= methodIndex < machine_getMethodCount(machine) // Post-condition: Returns the input argument count for the method at methodIndex in machine. int machine_getInputCount(const MediaMachine& machine, int methodIndex); // Pre-condition: // * The machine must exist. // machine_exists(machine) // * methodIndex must refer to the method that you want to get the output count from. // 0 <= methodIndex < machine_getMethodCount(machine) // Post-condition: Returns the output argument count for the method at methodIndex in machine. int machine_getOutputCount(const MediaMachine& machine, int methodIndex); // Pre-condition: // * The machine must exist. // machine_exists(machine) // * methodIndex must refer to the method that you want to get an input argument's name from. // 0 <= methodIndex < machine_getMethodCount(machine) // * The inputIndex must refer to the input argument that you want to get the name of. // 0 <= inputIndex < machine_getInputCount(machine, methodIndex) // Post-condition: Returns the input argument name at inputIndex for the method at methodIndex in machine. String machine_getInputName(const MediaMachine& machine, int methodIndex, int inputIndex); // Pre-condition: // * The machine must exist. // machine_exists(machine) // * methodIndex must refer to the method that you want to get an output argument's name from. // 0 <= methodIndex < machine_getMethodCount(machine) // * The outputIndex must refer to the output argument that you want to get the name of. // 0 <= outputIndex < machine_getOutputCount(machine, methodIndex) // Post-condition: Returns the output argument name at outputIndex for the method at methodIndex in machine. String machine_getOutputName(const MediaMachine& machine, int methodIndex, int outputIndex); // Helper function counting the number of arguments given to it. inline constexpr int machine_argCount() { return 0; } template inline constexpr int machine_argCount(HEAD& first, TAIL&... args) { return machine_argCount(args...) + 1; } // A temporary type generated from () calls to MediaMethod, which is used for writing outputs to targets within the next (). class MediaResult { private: // Holding the machine by reference prevents storing MediaResult, // because it may not be used after other calls to the machine. // It is only used to assign outputs with the () operator. MediaMachine &machine; int methodIndex; void writeResult(int outputIndex, int8_t& target) { target = fixedPoint_round(machine_getFixedPointOutputByIndex(this->machine, this->methodIndex, outputIndex)); } void writeResult(int outputIndex, int16_t& target) { target = fixedPoint_round(machine_getFixedPointOutputByIndex(this->machine, this->methodIndex, outputIndex)); } void writeResult(int outputIndex, int32_t& target) { target = fixedPoint_round(machine_getFixedPointOutputByIndex(this->machine, this->methodIndex, outputIndex)); } void writeResult(int outputIndex, int64_t& target) { target = fixedPoint_round(machine_getFixedPointOutputByIndex(this->machine, this->methodIndex, outputIndex)); } void writeResult(int outputIndex, FixedPoint& target) { target = machine_getFixedPointOutputByIndex(this->machine, this->methodIndex, outputIndex); } void writeResult(int outputIndex, AlignedImageU8& target) { target = machine_getImageU8OutputByIndex(this->machine, this->methodIndex, outputIndex); } void writeResult(int outputIndex, OrderedImageRgbaU8& target) { target = machine_getImageRgbaU8OutputByIndex(this->machine, this->methodIndex, outputIndex); } inline void writeResults(int firstInputIndex) {} template inline void writeResults(int firstInputIndex, HEAD& first, TAIL&... args) { this->writeResult(firstInputIndex, first); this->writeResults(firstInputIndex + 1, args...); } public: MediaResult(MediaMachine& machine, int methodIndex) : machine(machine), methodIndex(methodIndex) {} // Write target references within () after a call to assign multiple outputs template void operator () (ARGS&... args) { int givenCount = machine_argCount(args...); int expectedCount = machine_getOutputCount(this->machine, this->methodIndex); if (givenCount != expectedCount) { throwError("The call to ", machine_getMethodName(this->machine, this->methodIndex), " expected ", expectedCount, " outputs, but ", givenCount, " references were assigned.\n"); } this->writeResults(0, args...); } }; // How to call a MediaMethod: // * Using arguments in the same order as declared in the Media Machine Code. // myMediaMethod(inputA, inputB, inputC...)(outputX, outputY...) // This allow returning more than one return value without having to declare any structures in the virtual machine. // * Using keyword arguments. // You can also call myMediaMethod and just define a lambda that is called with the argument name and index for each input argument. // Then your function calls machine_setInputByIndex with the given arguments and the input data identified by name. class MediaMethod { public: MediaMachine machine; int methodIndex; // Index of the method being called. int contextIndex; // Index used to know from which context implicit variables are being fetched. private: inline void setInputs(int firstInputIndex) {} template inline void setInputs(int firstInputIndex, const HEAD &first, TAIL&&... args) { machine_setInputByIndex(this->machine, this->methodIndex, firstInputIndex, first); this->setInputs(firstInputIndex + 1, args...); } public: MediaMethod() : methodIndex(-1), contextIndex(0) {} MediaMethod(const MediaMachine& machine, int methodIndex, int contextIndex) : machine(machine), methodIndex(methodIndex), contextIndex(contextIndex) {} // MediaMethod can be called like a function using arguments, returning MediaResult for assigning outputs by reference. // Useful when you know the arguments in advance. template MediaResult operator () (ARGS&&... args) { int givenCount = machine_argCount(args...); int expectedCount = machine_getInputCount(this->machine, this->methodIndex); if (givenCount != expectedCount) { throwError("The call to ", machine_getMethodName(this->machine, this->methodIndex), " expected ", expectedCount, " inputs, but ", givenCount, " values were given.\n"); } this->setInputs(0, args...); machine_executeMethod(this->machine, this->methodIndex); return MediaResult(this->machine, this->methodIndex); } // MediaMethod can also take the inputs as keyword arguments by getting a callback with the index and name of each input to assign. // The function setInputAction should simply make a call to machine_setInputByIndex with the provided machine, methodIndex, inputIndex and the value corresponding to argumentName in setInputAction. // If you don't recognize argumentName, then throw an exception because default input arguments are currently not implemented. // Useful when the called function can be extended or reduced with only the arguments needed. MediaResult callUsingKeywords(std::function setInputAction); }; // Post-condition: Returns a MediaMethod structure, which can be stored as a reference counting function pointer that keeps the virtual machine alive. MediaMethod machine_getMethod(MediaMachine& machine, const ReadableString& methodName, int contextIndex, bool mustExist = true); } #endif