Tutorial step 5: changing the output frame size

How about changing the filter to produce a double-width output? Stupid, yes. Easy, yes. It only involves a minor change to the processing function and an additiona paramProc function. So let's jump in!

The paramProc function

There are too many possibilities for output sizes filters might produce given an input frame size. If this were DirectX, we'd provide a huge filterdef structure and a million caps bits to try and hold everything, but since VirtualDub's not DirectX (thank god), we have a function do it instead, paramProc:

Function paramProc(fa: PFilterActivation; const ff: PFilterFunctions): Integer; cdecl;

paramProc's job is simple: set up fa^.dst to reflect the output frame format, given the input format in fa^.src, and return flags indicating the behavior of the filter. The current set of flags are:

  • FILTERPARAM_SWAP_BUFFERS: Indicates that your filter will need a separate output buffer. Otherwise, VirtualDub overlaps your input and output buffers, meaning that fa^.src and fa^.dst will point to the same buffer. Filters should be implemented single-buffer if possible, for better cache coherency and possibly lower memory buffer requirements.
  • FILTERPARAM_NEEDS_LAST: Your filter needs a buffer holding the previous input frame. This is the input frame as it enters the filter chain, not your filter instance's input. The buffer is made available as fa^.last.

paramProc is responsible for setting the w, h, pitch, and offset fields of fa^.dst. By default, fa^.dst is initialized with the same width, height, and pitch as the source bitmap, and an offset of 0. Most paramProcs will set w and h, and adjust pitch to 4*w rounded up to the next 8. (8 is good for MMX.)

The offset field allows the destination window to 'float' ahead of the beginning of the buffer, and is most useful in filters that do not specify FILTERPARAM_SWAP_BUFFERS, because it allows the destination window to float anywhere in the source rectangle. Be careful, because fa^.src.offset will not be zero if the (x1,y1) clipping point to the filter is not (0,0), and this will need to be added to the fa^.dst.offset value.

paramProc is called as part of VirtualDub's buffer size negotiation process, before buffers are allocated. The data fields of the VBuffer structs are not valid when paramProc is called. Filters that compile tables or code dependent upon the buffer addresses should do so in startProc.

Modifying the tutorial filter for double width

First, update the FilterDefinition record:

Const
 filterDef_tutorial: TFilterDefinition = (
   (...)
   paramProc: tutorialParamProc;
   (...)
   );

Second, write paramProc. The height field is already set, so just change the width and pitch, leave the offset at zero, and inform VirtualDub that we'll be needing that separate output buffer.

Function tutorialParamProc(fa: PFilterActivation; const ff: PFilterFunctions): Integer; cdecl;
begin {tutorialParamProc}
fa^.dst.w := fa^.dst.w*2;
fa^.dst.pitch := (fa^.dst.w*4 + 7) AND -8;
Result := FILTERPARAM_SWAP_BUFFERS;
end; {tutorialParamProc}

The pitch can also be set to simply fa^.dst.w*4, but rounding it up to the next 8 speeds up any subsequent MMX routines using 64-bit reads.

Finally, tweak the runProc routine, by duplicating these two lines:

dst^ := new_pixel;
Inc(dst);

That's it. The filter now produces double width images. It doesn't apply filter transforms to smooth the enlargement, but that's... um... an exercise for the reader! Check this file to see how your delphi project should look now.