videofilt_managingfilterdata - shekh/VirtualDub2 GitHub Wiki

VirtualDub Plugin SDK 1.2

Managing filter data

Once you begin writing more complex video filters, you will want to allocate complex data structures and side buffers that must be deallocated at some point before program exit. The video filter API provides several hooks to do this.

Filter instance data

Because a filter can be used multiple times within the same filter chain, each entry in the filter chain associated with a filter is called a filter instance, and can have instance data associated with it. This instance data is pointed to by the fa->filter_data field. The instance data is allocated by the host, but is managed by the filter. The size of the instance data is set in the filter definition. Each instance of the filter can thus have its own parameters and operating data.

The easiest way to manage filter data is by storing it in a data structure:

struct MyData {
    int fillValue;
};

int runProc(const VDXFilterActivation *fa, const VDXFilterFunctions *ff) {
    MyData       *data     = (MyData *)fa->filter_data;
    int           w        = fa->dst.w;
    int           h        = fa->dst.h;
    uint32       *dst      = (uint32 *)fa->dst.data;
    ptrdiff_t     dstpitch = fa->dst.pitch;
    
    // loop over all rows
    for(int y=0; y<h; ++y) {
        memset(dst, data->fillValue, w * 4);
        
        // advance to next row in destination
        dst = (uint32 *)((char *)dst + dstpitch);
    }
    
    return 0;
}

static struct FilterDefinition myfilter={
    0,0,NULL,                       // next, prev, and module (set to zero)
    "my filter",                    // name
    "Description of my filter.",    // description
    "Author of my filter.",         // author / maker
    NULL,                           // no private data
    sizeof(MyData),                 // instance data size
    NULL,                           // initProc
    NULL,                           // deinitProc
    runProc,                        // runProc
};

startProc and endProc

While the instance data structure is useful, it would be rather inconvenient if everything in it had to be initialized on first use and there were no convenient place to clean it up.

The most useful entry points to implement are startProc and endProc, which are called at the beginning and end of each rendering operation, respectively. This is useful for allocating large image buffers that are only necessary when actually processing video — the buffers can be left unallocated to save memory, and allocated only when necessary and once the source image parameters are known. This also has the benefit of keeping paramProc simple.

One gotcha with endProc: it may be called more times than startProc and is required to be safe in this regard. Therefore, you should initialize any pointers to NULL on initialization and clear them when deallocating buffers:

int endProc(VDXFilterActivation *fa, const VDXFilterFunctions *ff) {
    MyData *p = (MyData *)fa->filter_data;

    if (p->buffer) {
        delete p->buffer;
        p->buffer = NULL;
    }
}

By default, the memory allocated and placed in fa->filter_data is zero-initialized, like global variables in C, so any pointers in that structure should already be NULL.

initProc and deinitProc

For data that persists across rendering operations, the initProc and deinitProc entry points should be used. These entry points handle initialization and shutdown for fa->filter_data, the data that persists for the life of the filter instance. initProc is called exactly once when the filter instance is initialized, and deinitProc exactly once before it is destroyed.

There is one nit in the above, and that's the copy operation. In certain cases, the host may need to temporarily copy the filter structure. As a result, by default, the filter data structure must be directly copyable. In other words, it must be OK to copy the structure with memcpy(). VirtualDub does this in its filter list UI. Here are a couple of the ugly cases:

  • The user makes a change to filter configuration and clicks OK in the dialog.
    • Filter data A is copied to temporary data B.
    • Data B is edited in the config dialog.
    • Data B is accepted, so data A is freed.
    • Data B is used for rendering.
    • deinitProc receives data B.
    • Data B is freed.
  • The user makes a change to filter configuration and clicks Cancel in the dialog.
    • Filter data A is copied to temporary data B.
    • Data B is edited in the config dialog.
    • Data B is rejected, so it is freed.
    • Data A is used for rendering.
    • deinitProc receives data A.
    • Data A is freed.

For the most part, the easiest way to deal with this is simply to embed data structures directly in the filter data structure as inline fields and arrays.

Flexible copying with copyProc (V9+ only)

Obviously, this is inconvenient for all but the simplest of filter data, so there is a better way: the copyProc entry point. This entry point allows the filter to control the copy process so that any cloned filter data structures are allocated properly. It also enables deinitProc to be called on any additional copies created. Therefore, both of the above scenarios are changed as follows:

  • The user makes a change to filter configuration and clicks OK in the dialog.
    • Filter data A is copied to temporary data B.
    • Data B is edited in the config dialog.
    • Data B is accepted.
    • deinitProc receives data A.
    • Data A is freed.
    • Data B is used for rendering.
    • deinitProc receives data B.
    • Data B is freed.
  • The user makes a change to filter configuration and clicks Cancel in the dialog.
    • Filter data A is copied to temporary data B.
    • Data B is edited in the config dialog.
    • Data B is rejected.
    • deinitProc receives data B.
    • Data B is freed.
    • Data A is used for rendering.
    • deinitProc receives data A.
    • Data A is freed.

With copyProc, it becomes possible to map the allocation and deallocation hooks onto traditional C++ object semantics, which makes filter data management much easier. In particular, the three hooks can be mapped as follows:

Filter API entry point Equivalent C++ method
initProc Constructor
deinitProc Destructor
copyProc Copy constructor

The tricky part is that, unlike the standard C++ mechanisms, the host does memory allocation instead of the filter. To solve this problem, placement versions of the above primitives are used. Explaining this aspect of C++ is out of scope for this SDK, so here's an example implementation:

// required to use placement new
#include <new>

struct MyData {
   // Normally, you couldn't include an object like this in the filter data, because
   // the host would copy it with memcpy() and cause havoc. With copyProc, however, the
   // filter invokes the standard copy constructor, and the std::string object is copied
   // correctly.
   std::string mFilename;
};

int initProc(VDXFilterActivation *fa, const VDXFilterFunctions *ff) {
    // Invoke placement new to construct the class object in the filter data memory
    // allocated by the host.
    new(fa->filter_data) MyData();
    return 0;
}

void deinitProc(VDXFilterActivation *fa, const VDXFilterFunctions *ff) {
    MyData *p = (MyData *)fa->filter_data;

    // Invoke the class destructor. This destroys any sub-object in the object without
    // invoking the memory deallocator, as the delete keyword would.
    p->~MyData();
}

void copyProc(VDXFilterActivation *fa, const VDXFilterFunctions *ff, void *dst) {
    MyData *p = (MyData *)fa->filter_data;

    // Invoke the copy constructor directly using placement new syntax, constructing the
    // new object instance directly into the supplied memory.
    new(dst) MyData(*p);
}

static struct FilterDefinition myfilter={
    0,0,NULL,                       // next, prev, and module (set to zero)
    "my filter",                    // name
    "Description of my filter.",    // description
    "Author of my filter.",         // author / maker
    NULL,                           // no private data
    sizeof(MyData),                 // instance data size
    initProc,                       // initProc
    deinitProc,                     // deinitProc
    runProc,                        // runProc
    NULL,                           // no paramProc
    NULL,                           // no configProc
    NULL,                           // no stringProc
    NULL,                           // no startProc
    NULL,                           // no endProc
    NULL,                           // no script object
    NULL,                           // no scriptStringProc
    NULL,                           // no stringProc2
    NULL,                           // no serializeProc
    NULL,                           // no deserializeProc
    copyProc                        // copyProc
};

Copyright (C) 2007-2012 Avery Lee.

⚠️ **GitHub.com Fallback** ⚠️