View Full Version : Writing Common Avisynth/VapourSynth Code
MysteryX
13th June 2021, 03:16
I'm taking a quick look at converting Avisynth code to VapourSynth. As I'm looking at the code structure, I want to have a Common folder with the shared code, and wrapper/registration code for Avisynth and VapourSynth.
At first glance, Init, GetFrame and Free functions must be rewritten for each platform. OK. Perhaps most complex code inside can be shared.
A deeper problem: IScriptEnvironment is used everywhere. How can shared code be written without relying on IScriptEnvironment?
I'm looking at standard functions I had imported for custom needs, merge_plane relies on env to get CPU flags. MergeChroma::GetFrame uses "env" everywhere, hard to re-use the code without entirely duplicating it.
Do I have to copy/rewrite most of the code, or what's the strategies to share code?
...
Creating a IScriptEnvironment wrapper could be an option. But then would also have to create a wrapper for PVideoFrame.
And what's the equivalent of env->GetCPUFlags in VapourSynth?
real.finder
13th June 2021, 04:53
see if this help you https://github.com/HomeOfAviSynthPlusEvolution/neo_f3kdb/tree/master/include/dualsynth
MysteryX
13th June 2021, 05:44
Interesting approach. Another approach I'm exploring right now is to write common code against a base abstract class.
IVideoFrame.h
typedef unsigned char BYTE;
class IVideoFrame {
public:
virtual int GetPitch(int plane = 0) = 0;
virtual int GetRowSize(int plane = 0) = 0;
virtual int GetHeight(int plane = 0) = 0;
virtual BYTE* GetWritePtr(int plane = 0) = 0;
virtual const BYTE* GetReadPtr(int plane = 0) = 0;
};
AvsFrame.h
#include "IVideoFrame.h"
#include "../Avisynth/avisynth.h"
class AvsFrame : IVideoFrame {
public:
AvsFrame(PVideoFrame _frame) :
frame(_frame) {
}
int GetPitch(int plane = 0) {
return frame->GetPitch(plane);
}
int GetRowSize(int plane = 0) {
return frame->GetRowSize(plane);
}
int GetHeight(int plane = 0) {
return frame->GetHeight(plane);
}
BYTE* GetWritePtr(int plane = 0) {
return frame->GetWritePtr(plane);
}
const BYTE* GetReadPtr(int plane = 0) {
return frame->GetReadPtr(plane);
}
private:
const PVideoFrame frame;
};
VsFrame.h
#include "../VapourSynth/VapourSynth.h"
#include "IVideoFrame.h"
class VsFrame : IVideoFrame {
public:
VsFrame(const VSAPI* _api, const VSFrameRef* _frame) :
api(_api), f(_frame) {
}
int GetPitch(int plane = 0) {
return api->getStride(f, plane);
}
int GetRowSize(int plane = 0) {
return api->getFrameWidth(f, plane) * api->getFrameFormat(f)->bytesPerSample;
}
int GetHeight(int plane = 0) {
return api->getFrameWidth(f, plane);
}
int GetWidth(int plane = 0) {
return api->getFrameWidth(f, plane);
}
BYTE* GetWritePtr(int plane = 0) {
return api->getWritePtr((VSFrameRef*)f, plane);
}
const BYTE* GetReadPtr(int plane = 0) {
return api->getReadPtr(f, plane);
}
private:
const VSAPI* api;
const VSFrameRef* f;
};
feisty2
13th June 2021, 06:36
the structure of a vaporsynth filter is very simple. you can register any callable object as a vaporsynth plugin function
PluginInstantiator::RegisterFunction("ShowMessage(msg: string?)", [](auto Arguments, auto Core) {
if (Arguments["msg"].Exists())
Core.Print(static_cast<std::string>(Arguments["msg"]));
else
Core.Print("hello world");
});
then in python
core.???.ShowMessage('the eagle has landed') #prints the eagle has landed
core.???.ShowMessage() #prints hello world
or you create a plugin function from a user defined type using
PluginInstantiator::RegisterFilter<FilterType>();
where FilterType is an arbitrary type with some special members, see the GaussBlur (https://github.com/IFeelBloated/vapoursynth-plusplus/blob/master/Examples/GaussBlur.hxx) example
as you can see there isn't much bureaucratic boilerplate code needed to define the skeleton of a filter, you can simply define a separate function which does the actual work, and call that function in your filter skeleton. the function can be shared by both avisynth and vaporsynth
feisty2
13th June 2021, 07:19
Interesting approach. Another approach I'm exploring right now is to write common code against a base abstract class.
IVideoFrame.h
AvsFrame.h
VsFrame.h
no need, you can simply define a Canvas type (https://github.com/IFeelBloated/vapoursynth-plusplus/blob/master/include/Plane.vxx#L5) as the representation of a bounded 2D surface, and define your shared function as something that works on canvases. a vaporsynth plane is already a canvas, and this type is trivial, you can easily construct it using the avisynth API as well.
MysteryX
13th June 2021, 14:33
no need, you can simply define a Canvas type (https://github.com/IFeelBloated/vapoursynth-plusplus/blob/master/include/Plane.vxx#L5) as the representation of a bounded 2D surface, and define your shared function as something that works on canvases. a vaporsynth plane is already a canvas, and this type is trivial, you can easily construct it using the avisynth API as well.
That code is C++20 requiring GCC11 to compile. I'm not finding any documentation for the 'field' keyword.
feisty2
13th June 2021, 15:43
if you're not in a rush to write new plugins, support for the latest version of MSVC should be soon available, there's only one C++20 core language feature (requires expressions) that MSVC currently lacks. I'll add support for MSVC asap when it fills in the final missing piece of C++20.
field is simply a macro that makes indentation prettier in a struct, it declares a field (a non-static data member) with a default initialization, the exact definition is here (https://github.com/IFeelBloated/vapoursynth-plusplus/blob/master/include/Infrastructure.vxx#L29)
MysteryX
13th June 2021, 23:58
I managed to trim down a simple filter class Avisynth-only code to this
#pragma once
#include "../CommonCore/AvsEnvironment.hpp"
#include "../Common/ContinuousMaskCommon.hpp"
class ContinuousMask : public GenericVideoFilter, public ContinuousMaskCommon
{
private:
const int radius;
public:
ContinuousMask(PClip _child, int _radius, IScriptEnvironment* env) :
GenericVideoFilter(_child), radius(_radius),
ContinuousMaskCommon(AvsVideo(_child), vi.width, vi.height, radius, AvsEnvironment(env))
{
}
PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* env)
{
PVideoFrame src = child->GetFrame(n, env);
PVideoFrame dst = env->NewVideoFrame(vi);
GetFrameCommon(AvsFrame(src, vi), AvsFrame(dst, vi));
return dst;
}
int __stdcall SetCacheHints(int cachehints, int frame_range)
{
return cachehints == CachePolicyHint::CACHE_GET_MTMODE ? MT_NICE_FILTER : 0;
}
};
It reminded me of the a basic principle when working with Dependency Injection: isolate your components, and only communicate between components via interfaces. Write those standard interfaces to expose only the features you need, without regards to how they're implemented. That layer of abstraction works perfect here.
Thus, I'm creating ICommonEnvironment, ICommonVideo and ICommonFrame, with Avisynth and VapourSynth derived implementations. ContinuousMaskCommon has no reference to either Avisynth nor VapourSynth.
MysteryX
15th June 2021, 04:04
Using my approach is working pretty well so far. Rewrote several Avisynth plugins where most of the code has no reference to Avisynth at all.
Up until I try to re-use the base class in VapourSynth. VapourSynth doesn't initialize classes, but rather calls static classes and stores everything in "void* instanceData"
feisty, how did you get it to work with classes? ... or maybe create the class, send the pointers to its functions, and store the class pointer itself in instanceData? Ugly, but I guess that could work actually...
feisty2
15th June 2021, 10:48
I strongly dislike java style OOP (inheritance, hard coded interface, etc.) and I don't use "class" or "design patterns" or whatsoever to reason with the complexity of the program. I am more of a functional programmer (minus the hostility towards side effects) and I program at a rather "meta" level, I create types, then I write metaprograms that directly manipulate C++'s type system to reflect type morphisms.
a base class is generally a very bad idea because it "pollutes" any type inheriting it, and makes it harder to reason with type relationships. like the problem you mentioned above, a type cannot be constructed because a dependency of its base class cannot be satisfied, even if that dependency may be useless to the type itself. the correct way to implement subtype polymorphism is thru the use of an existential type (https://github.com/IFeelBloated/Type-System-Zoo/blob/master/existential%20type.cxx)
MysteryX
15th June 2021, 19:50
When it comes to writing cross-platform code, are there differences in the in-memory data representation between VapourSynth and Avisynth?
Doc about BytesPerSample says:
Number of bytes needed for a sample. This is always a power of 2 and the smallest possible that can fit the number of bits used per sample.
Does that mean that all 8-bit data is stored as 2 bytes instead of 1 byte in VapourSynth? If that's the case, it would make porting the code considerably more difficult.
feisty2
15th June 2021, 20:04
1 is a power of 2, 1 = 2^0
vBulletin® v3.8.11, Copyright ©2000-2026, vBulletin Solutions Inc.