Welcome to Doom9's Forum, THE in-place to be for everyone interested in DVD conversion.

Before you start posting please read the forum rules. By posting to this forum you agree to abide by the rules.

 

Go Back   Doom9's Forum > Capturing and Editing Video > Avisynth Development

Reply
 
Thread Tools Search this Thread Display Modes
Old 13th June 2021, 03:16   #1  |  Link
MysteryX
Soul Architect
 
MysteryX's Avatar
 
Join Date: Apr 2014
Posts: 2,559
Writing Common Avisynth/VapourSynth Code

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?

Last edited by MysteryX; 13th June 2021 at 04:42.
MysteryX is offline   Reply With Quote
Old 13th June 2021, 04:53   #2  |  Link
real.finder
Registered User
 
Join Date: Jan 2012
Location: Mesopotamia
Posts: 2,587
see if this help you https://github.com/HomeOfAviSynthPlu...lude/dualsynth
__________________
See My Avisynth Stuff
real.finder is offline   Reply With Quote
Old 13th June 2021, 05:44   #3  |  Link
MysteryX
Soul Architect
 
MysteryX's Avatar
 
Join Date: Apr 2014
Posts: 2,559
Interesting approach. Another approach I'm exploring right now is to write common code against a base abstract class.

IVideoFrame.h
Quote:
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
Quote:
#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
Quote:
#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;
};

Last edited by MysteryX; 13th June 2021 at 06:18.
MysteryX is offline   Reply With Quote
Old 13th June 2021, 06:36   #4  |  Link
feisty2
I'm Siri
 
feisty2's Avatar
 
Join Date: Oct 2012
Location: void
Posts: 2,633
the structure of a vaporsynth filter is very simple. you can register any callable object as a vaporsynth plugin function
Code:
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
Code:
PluginInstantiator::RegisterFilter<FilterType>();
where FilterType is an arbitrary type with some special members, see the GaussBlur 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

Last edited by feisty2; 13th June 2021 at 06:50.
feisty2 is offline   Reply With Quote
Old 13th June 2021, 07:19   #5  |  Link
feisty2
I'm Siri
 
feisty2's Avatar
 
Join Date: Oct 2012
Location: void
Posts: 2,633
Quote:
Originally Posted by MysteryX View Post
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 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.
feisty2 is offline   Reply With Quote
Old 13th June 2021, 14:33   #6  |  Link
MysteryX
Soul Architect
 
MysteryX's Avatar
 
Join Date: Apr 2014
Posts: 2,559
Quote:
Originally Posted by feisty2 View Post
no need, you can simply define a Canvas type 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.
MysteryX is offline   Reply With Quote
Old 13th June 2021, 15:43   #7  |  Link
feisty2
I'm Siri
 
feisty2's Avatar
 
Join Date: Oct 2012
Location: void
Posts: 2,633
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
feisty2 is offline   Reply With Quote
Old 13th June 2021, 23:58   #8  |  Link
MysteryX
Soul Architect
 
MysteryX's Avatar
 
Join Date: Apr 2014
Posts: 2,559
I managed to trim down a simple filter class Avisynth-only code to this

Quote:
#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 is offline   Reply With Quote
Old 15th June 2021, 04:04   #9  |  Link
MysteryX
Soul Architect
 
MysteryX's Avatar
 
Join Date: Apr 2014
Posts: 2,559
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...
MysteryX is offline   Reply With Quote
Old 15th June 2021, 10:48   #10  |  Link
feisty2
I'm Siri
 
feisty2's Avatar
 
Join Date: Oct 2012
Location: void
Posts: 2,633
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
feisty2 is offline   Reply With Quote
Old 15th June 2021, 19:50   #11  |  Link
MysteryX
Soul Architect
 
MysteryX's Avatar
 
Join Date: Apr 2014
Posts: 2,559
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:
Quote:
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.
MysteryX is offline   Reply With Quote
Old 15th June 2021, 20:04   #12  |  Link
feisty2
I'm Siri
 
feisty2's Avatar
 
Join Date: Oct 2012
Location: void
Posts: 2,633
1 is a power of 2, 1 = 2^0
feisty2 is offline   Reply With Quote
Reply

Thread Tools Search this Thread
Search this Thread:

Advanced Search
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT +1. The time now is 09:24.


Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2024, vBulletin Solutions Inc.