|
The first step to optimizing our filter would be to use assembly code, but
we'll spend some time on MMX optimizations first, because MMX can really speed
this code up. The trick to using MMX is knowing when we can and can't use it;
detection code isn't good enough because the user may have intentionally forced
or disabled MMX support in VirtualDub's preferences dialog.
Checking to see if CPU optimizations are available
Two functions in the FilterFunctions struct are useful here.
- ff^.isFPUEnabled returns true when FPU optimizations should be used.
- ff^.isMMXEnabled returns true when MMX optimizations should be used.
FPU optimizations are rare in pixel processing; the only VirtualDub filter that
uses them is the bilinear resize filter, because it requires 6 64-bit multiplies
per pixel, which are more quickly done with the FPU than the integer units.
MMX optimizations are the biggie; use them if at all possible.
The FPU and MMX enable flags are guaranteed not to change between filters or
during processing. It is highly recommended that you globally cache the flags
during startProc processing, and read the global flag in runProc.
Adding MMX optimizations to our filter
First, modify startProc to cache the MMX flag.
var
(...)
g_MMXenabled: boolean;
Function tutorialStartProc(fa: PFilterActivation; const ff: PFilterFunctions): Integer; cdecl;
var
mfd: PFilterData;
i: Integer;
begin {tutorialStartProc}
mfd := PFilterData(fa^.filter_data);
g_MMXenabled := ff^.isMMXEnabled;
(...)
end; {tutorialStartProc}
Now, write the MMX acceleration routine. For simplicity, it will only handle
one scanline. I'm not usually very clear when writing MMX code, so don't
feel bad if it takes a couple of passes to understand. ^^;;
procedure doscan_MMX(dst: PPixel32; src: PPixel32; w: integer; frac: longint; bias: longint; fDouble: integer); cdecl;
const Rmask: int64 = $0000FFFF00000000;
label xloop1, xloop2, xit;
asm
push ebp
push edi
push esi
push ebx
mov eax,[esp+4+16]
mov edx,[esp+8+16]
mov ecx,[esp+12+16]
neg ecx
shl ecx,2
sub eax,ecx
sub edx,ecx
movq mm6,Rmask
movd mm4,[esp+20+16]
psllq mm4,16
movd mm5,[esp+16+16]
punpcklwd mm5,mm5
pxor mm7,mm7
mov ebx,dword ptr [esp+24+16]
or ebx,ebx
jz xloop1
sub eax,ecx
xloop2:
movd mm0,[edx+ecx] //mm0 = pixel
movq mm1,mm6 //mm1 = R mask
punpcklbw mm0,mm7 //unpack pixel to words
pand mm1,mm0 //mm1 = red component
pmulhw mm0,mm5 //scale green and blue
paddw mm1,mm4 //add green bias
paddw mm0,mm1 //add scaled green/blue
packuswb mm0,mm0 //repack pixel to bytes
movq [eax+ecx*2],mm0 //write 2 pixels
add ecx,4
jne xloop2
jmp xit
xloop1:
movd mm0,[edx+ecx] //mm0 = pixel
movq mm1,mm6 //mm1 = R mask
punpcklbw mm0,mm7 //unpack pixel to words
pand mm1,mm0 //mm1 = red component
paddw mm0,mm0 //double g/b channels beforehand
pmulhw mm0,mm5 //scale green and blue
paddw mm1,mm4 //add green bias
paddw mm0,mm1 //add scaled green/blue
packuswb mm0,mm0 //repack pixel to bytes
movd [eax+ecx],mm0 //write pixel
add ecx,4
jne xloop1
xit:
pop ebx
pop esi
pop edi
pop ebp
end;
Finally, add the MMX optimizations to runProc.
Function tutorialRunProc(const fa: PFilterActivation; const ff: PFilterFunctions): Integer; cdecl;
var
w, h: TPixDim;
src, dst: PPixel32;
old_pixel, new_pixel: TPixel32;
mfd: PFilterData;
begin {tutorialRunProc}
src := PPixel32(fa^.src^.data);
dst := PPixel32(fa^.dst^.data);
mfd := PFilterData(fa^.filter_data);
h := fa^.src^.h;
repeat
w := fa^.src^.w;
if g_MMXenabled = true then begin
if mfd^.fThird = true then
doscan_MMX(dst, src, w, $2AAA, $55, integer(mfd^.fExpand))
else
doscan_MMX(dst, src, w, $4000, $80, integer(mfd^.fExpand));
//If X is a pointer type, Inc increments X by N times the size of the type pointed to.
Inc(src, fa^.src^.pitch div SizeOf(PPixel32));
Inc(dst, fa^.dst^.pitch div SizeOf(PPixel32));
// double the routine for speed; an if would kill us in the
// inner loop, but in the outer loop it's ok
end
else begin
if (mfd^.fExpand = True) then
repeat
old_pixel := src^;
new_pixel := (old_pixel AND $FF0000)
+ mfd^.grn_tab[(old_pixel shr 8) AND $FF]
+ mfd^.blu_tab[old_pixel AND $FF];
dst^ := new_pixel;
Inc(dst);
dst^ := new_pixel;
Inc(src);
Inc(dst);
Dec(w);
until w = 0
else
repeat
old_pixel := src^;
new_pixel := (old_pixel AND $FF0000)
+ mfd^.grn_tab[(old_pixel shr 8) AND $FF]
+ mfd^.blu_tab[old_pixel AND $FF];
dst^ := new_pixel;
Inc(src);
Inc(dst);
Dec(w);
until w = 0;
Inc(src, fa^.src^._modulo);
Inc(dst, fa^.dst^._modulo);
end;
Dec(h);
until h = 0;
if g_MMXenabled = true then asm emms end;
Result := 0;
end; {tutorialRunProc}
Look here how your main project file should look like.
MMX optimizations basically just require the MMX code; you spend 99.99% of
your time wringing out more speed. Two more important notes:
- Make sure your non-MMX code still works! It's easy to break your non-MMX code if you're developing on an MMX machine, since VirtualDub will default to MMX enabled.
- Do not forget the emms instruction at the end of your filter, but make sure not to call it if MMX is not enabled.
Onto the next, and the final, chapter of this tutorial!
|