在前面已经详细的分析了边界填充算法的C语言优化实现,在C语言的实现中主要是用memcpy来实现,为了提高效率FFmpeg利用了mmx、xmm和ymm等寄存器进行指令集优化。FFmpeg中和边界填充相关的优化主要在 “videodsp.asm”中实现,在下面的源代码中已经详细的分析了边界填充汇编优化代码的实现。
;******************************************************************************
;* Core video DSP functions
;* Copyright (c) 2012 Ronald S. Bultje <rsbultje@gmail.com>
;*
;* This file is part of FFmpeg.
;*
;* FFmpeg is free software; you can redistribute it and/or
;* modify it under the terms of the GNU Lesser General Public
;* License as published by the Free Software Foundation; either
;* version 2.1 of the License, or (at your option) any later version.
;*
;* FFmpeg is distributed in the hope that it will be useful,
;* but WITHOUT ANY WARRANTY; without even the implied warranty of
;* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
;* Lesser General Public License for more details.
;*
;* You should have received a copy of the GNU Lesser General Public
;* License along with FFmpeg; if not, write to the Free Software
;* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
;******************************************************************************
%include "libavutil/x86/x86util.asm"
SECTION .text
; slow vertical extension loop function. Works with variable-width, and
; does per-line reading/writing of source data
%macro V_COPY_ROW 2 ; type (top/body/bottom), h
.%1_y_loop: ; do {
mov wq, r7mp ; initialize w (r7mp = wmp) ;r7mp在前面已经取值为负数
.%1_x_loop: ; do {
movu m0, [srcq+wq] ; m0 = read($mmsize) ;
movu [dstq+wq], m0 ; write(m0, $mmsize)
add wq, mmsize ; w -= $mmsize
cmp wq, -mmsize ; } while (w > $mmsize); ;最后剩余wq- (-mmsize)个像素未拷贝
jl .%1_x_loop
movu m0, [srcq-mmsize] ; m0 = read($mmsize) ;剩余不足mmsize的像素,按照mmsize大小读取
movu [dstq-mmsize], m0 ; write(m0, $mmsize)
%ifidn %1, body ; if ($type == body) {
add srcq, src_strideq ; src += src_stride
%endif ; }
add dstq, dst_strideq ; dst += dst_stride
dec %2 ; } while (--$h);
jnz .%1_y_loop
%endmacro
%macro vvar_fn 0
; .----. <- zero
; | | <- top is copied from first line in body of source
; |----| <- start_y
; | | <- body is copied verbatim (line-by-line) from source
; |----| <- end_y
; | | <- bottom is copied from last line in body of source
; '----' <- bh
%if ARCH_X86_64
cglobal emu_edge_vvar, 7, 8, 1, dst, dst_stride, src, src_stride, \
start_y, end_y, bh, w
%else ; x86-32
cglobal emu_edge_vvar, 1, 6, 1, dst, src, start_y, end_y, bh, w
%define src_strideq r3mp
%define dst_strideq r1mp
mov srcq, r2mp
mov start_yq, r4mp
mov end_yq, r5mp
mov bhq, r6mp
%endif
sub bhq, end_yq ; bh -= end_q ;bottom部分的高度
sub end_yq, start_yq ; end_q -= start_q ;body部分高度
add srcq, r7mp ; (r7mp = wmp) ;拷贝行时,从右往左拷贝
add dstq, r7mp ; (r7mp = wmp)
neg r7mp ; (r7mp = wmp) ;宽度取反
test start_yq, start_yq ; if (start_q) {
jz .body
V_COPY_ROW top, start_yq ; v_copy_row(top, start_yq)
.body: ; }
V_COPY_ROW body, end_yq ; v_copy_row(body, end_yq)
test bhq, bhq ; if (bh) {
jz .end
sub srcq, src_strideq ; src -= src_stride
V_COPY_ROW bottom, bhq ; v_copy_row(bottom, bh)
.end: ; }
RET
%endmacro
%if ARCH_X86_32
INIT_MMX mmx
vvar_fn
%endif
INIT_XMM sse
vvar_fn
%macro hvar_fn 0
cglobal emu_edge_hvar, 5, 6, 1, dst, dst_stride, start_x, n_words, h, w
lea dstq, [dstq+n_wordsq*2] ;拷贝n_words个字
neg n_wordsq ;取反
lea start_xq, [start_xq+n_wordsq*2] ;start_x+=2*n_words
.y_loop: ; do {
%if cpuflag(avx2)
vpbroadcastb m0, [dstq+start_xq]
mov wq, n_wordsq ; initialize w
%else
movzx wd, byte [dstq+start_xq] ; w = read(1) ;读取1字节,这里其实就是原始的dst+start_x处的像素值
imul wd, 0x01010101 ; w *= 0x01010101 ;扩展为4个相同的字节
movd m0, wd
mov wq, n_wordsq ; initialize w
%if cpuflag(sse2)
pshufd m0, m0, q0000 ; splat ;扩展
%else ; mmx
punpckldq m0, m0 ; splat ;扩展
%endif ; mmx/sse
%endif ; avx2
.x_loop: ; do {
movu [dstq+wq*2], m0 ; write($reg, $mmsize) ;从左往右拷贝
add wq, mmsize/2 ; w -= $mmsize/2
cmp wq, -mmsize/2 ; } while (w > $mmsize/2)
jl .x_loop
movu [dstq-mmsize], m0 ; write($reg, $mmsize) ;拷贝该行剩余像素
add dstq, dst_strideq ; dst += dst_stride
dec hq ; } while (h--)
jnz .y_loop
RET
%endmacro
%if ARCH_X86_32
INIT_MMX mmx
hvar_fn
%endif
INIT_XMM sse2
hvar_fn
%if HAVE_AVX2_EXTERNAL
INIT_XMM avx2
hvar_fn
%endif
; macro to read/write a horizontal number of pixels (%2) to/from registers
; on sse, - fills xmm0-15 for consecutive sets of 16 pixels
; - if (%2 & 8) fills 8 bytes into xmm$next
; - if (%2 & 4) fills 4 bytes into xmm$next
; - if (%2 & 3) fills 1, 2 or 4 bytes in eax
; on mmx, - fills mm0-7 for consecutive sets of 8 pixels
; - if (%2 & 4) fills 4 bytes into mm$next
; - if (%2 & 3) fills 1, 2 or 4 bytes in eax
; writing data out is in the same way
%macro READ_NUM_BYTES 2 ;读取连续的%2个像素值
%assign %%off 0 ; offset in source buffer
%assign %%mmx_idx 0 ; mmx register index
%assign %%xmm_idx 0 ; xmm register index
%rep %2/mmsize ;计算需要读取多少次mmsize个像素
%if mmsize == 16 ;为sse
movu xmm %+ %%xmm_idx, [srcq+%%off]
%assign %%xmm_idx %%xmm_idx+1
%else ; mmx ;为mmx
movu mm %+ %%mmx_idx, [srcq+%%off]
%assign %%mmx_idx %%mmx_idx+1
%endif
%assign %%off %%off+mmsize
%endrep ; %2/mmsize
%if mmsize == 16 ;sse,剩余像素数在[8, 16)之间
%if (%2-%%off) >= 8 ;剩下大于8个像素
%if %2 > 16 && (%2-%%off) > 8 ;初始需要拷贝的像素数大于16个,且剩下的像素数在(8, 15)之间
movu xmm %+ %%xmm_idx, [srcq+%2-16] ;将最后的16个像素拷贝到xmm寄存器中,这里拷贝的16个像素会和上次拷贝的部分元素重叠,但他们的值相同,并不会造成错误
%assign %%xmm_idx %%xmm_idx+1
%assign %%off %2
%else ;((%2 <= 16) || (%2 - %%off) <= 8),由于mmsize==16,所以%2>=16,同时还要满足(%2 - %%off) >= 8,满足以上条件的只有剩下8个像素的情况,拷贝到mmx寄存器中
movq mm %+ %%mmx_idx, [srcq+%%off] ;这里也可以像if语句里那样一次性拷贝16个像素,但并没有这个必要。
%assign %%mmx_idx %%mmx_idx+1
%assign %%off %%off+8
%endif ;%2 > 16 && (%2-%%off) > 8
%endif ; (%2-%%off) >= 8
%endif ;mmsize == 16
%if (%2-%%off) >= 4 ;剩余的像素数在[4, 8)之间
%if %2 > 8 && (%2-%%off) > 4 ;如果初始需要拷贝的像素个数大于8个,而且现在剩余的像素个数在[4,8)之间,所以可以直接拷贝8个像素,
movq mm %+ %%mmx_idx, [srcq+%2-8] ;本次拷贝的8个像素中会有8 - (%2 - %%off) 个像素和上一次拷贝的元素重叠,但不会造成影响
%assign %%off %2
%else ;(%2 <= 8 || (%2 - %%off) <= 4), 同时还要满足 (%2 - %%off) >= 4, 拷贝到mmx寄存器中
movd mm %+ %%mmx_idx, [srcq+%%off] ;进入该分支的情况有两种,第一是初始需要拷贝的总像素数小于等于8;第二种是当前待拷贝的像素数为4个。
%assign %%off %%off+4 ;当初始需要拷贝的总像素数个数小于等于8时,不能使用if条件中的[srcq+%2-8]这种方式进行拷贝,因为
%endif ;地址[srcq+%2-8]不属于当前需要拷贝的地址范围,这样会篡改其他的内存,当待拷贝的像素个数为4时,
%assign %%mmx_idx %%mmx_idx+1 ;可以按照如此正常进行拷贝
%endif ; (%2-%%off) >= 4
%if (%2-%%off) >= 1 ;剩余像素在[1, 4)之间
%if %2 >= 4 ;初始需要拷贝的所有像素个数大于等于4,拷贝到mmx寄存器
movd mm %+ %%mmx_idx, [srcq+%2-4]
%elif (%2-%%off) == 1 ;只需要拷贝一个像素,val对应的是eax寄存器
mov valb, [srcq+%2-1]
%elif (%2-%%off) == 2 ;拷贝两个像素
mov valw, [srcq+%2-2]
%else ;拷贝三个像素
mov valb, [srcq+%2-1] ;拷贝一个像素
ror vald, 16
mov valw, [srcq+%2-3] ;拷贝两个像素
%endif
%endif ; (%2-%%off) >= 1
%endmacro ; READ_NUM_BYTES
%macro WRITE_NUM_BYTES 2
%assign %%off 0 ; offset in destination buffer
%assign %%mmx_idx 0 ; mmx register index
%assign %%xmm_idx 0 ; xmm register index
%rep %2/mmsize ;计算需要写多少次mmsize个像素
%if mmsize == 16 ;sse
movu [dstq+%%off], xmm %+ %%xmm_idx
%assign %%xmm_idx %%xmm_idx+1
%else ; mmx
movu [dstq+%%off], mm %+ %%mmx_idx
%assign %%mmx_idx %%mmx_idx+1
%endif
%assign %%off %%off+mmsize
%endrep ; %2/mmsize
%if mmsize == 16 ;sse
%if (%2-%%off) >= 8 ;sse,剩余像素个数[8, 16)
%if %2 > 16 && (%2-%%off) > 8 ;如果初始需要写入的像素个数大于16,且剩余的像素数大于8个
movu [dstq+%2-16], xmm %+ %%xmm_idx ;此时剩余需要写入的元素个数实际上是小于16个的,但这里仍然写入16个元素,并不会有任何的问题,
%assign %%xmm_idx %%xmm_idx+1 ;因为在%2>16时,上一次写入的16个像素和这里写入的16个像素中有16-(%2-%%off)个像素是相同的,并不会造成错误
%assign %%off %2
%else ;(%2 <= 16 || (%2-%%off) <= 8),在sse条件下%2>=16,而且这里(%2-%%off) >= 8,但是当%2为16的时候不会进入该分支,
movq [dstq+%%off], mm %+ %%mmx_idx ;所以能进入该分支的就是%2-%%off==8,此时也可以使用if语句中的方式,也并无影响。
%assign %%mmx_idx %%mmx_idx+1
%assign %%off %%off+8
%endif
%endif ; (%2-%%off) >= 8
%endif
%if (%2-%%off) >= 4 ;剩余像素个数[4, 8)
%if %2 > 8 && (%2-%%off) > 4 ;如果初始需要写入的像素个数大于8个(也就是%2>8),且剩余的像素个数大于4,此时需要写入的有效像素个数为%2-%%off,
movq [dstq+%2-8], mm %+ %%mmx_idx ;但这里将mmx写入到dstq+%2-8,会覆盖上次写入的8-(%2-%%off),但对结果并不会有影响,因为重叠的这几个元素是相同的
%assign %%off %2
%else ;%2 <= 8 || (%2-%%off) <= 4,同时还需要满足(%2-%%off) >= 4,所以这里的条件相当于%2 <= 8 || (%2-%%off) == 4,
movd [dstq+%%off], mm %+ %%mmx_idx ;当%2<=8时(这里肯定是%2<8,如果等于会在上面写入,所以能进入这个分支,一定是%2<8),不能像上面if分支那样使用[dstq+%2-8]的方式进行写入,因为这样会造成8-(%2-%%off)个元素被覆写,此处被覆写
%assign %%off %%off+4 ;会对内存造成影响,因为当%2<8是,地址[dstq+%2-8]是属于其他模块的内存;当%2-%%off==4时可正常写入。
%endif
%assign %%mmx_idx %%mmx_idx+1
%endif ; (%2-%%off) >= 4
%if (%2-%%off) >= 1
%if %2 >= 4
movd [dstq+%2-4], mm %+ %%mmx_idx ;这里写入4个像素虽然有重叠部分,但不会有内存越界的问题
%elif (%2-%%off) == 1
mov [dstq+%2-1], valb ;写一个字节,val对应的是eax寄存器
%elif (%2-%%off) == 2
mov [dstq+%2-2], valw ;写两个字节
%else
mov [dstq+%2-3], valw ;写两个字节
ror vald, 16
mov [dstq+%2-1], valb ;写一个字节,一共写入三个字节
%ifnidn %1, body
ror vald, 16
%endif
%endif
%endif ; (%2-%%off) >= 1
%endmacro ; WRITE_NUM_BYTES
; vertical top/bottom extend and body copy fast loops
; these are function pointers to set-width line copy functions, i.e.
; they read a fixed number of pixels into set registers, and write
; those out into the destination buffer
%macro VERTICAL_EXTEND 2
%assign %%n %1
%rep 1+%2-%1
%if %%n <= 3
%if ARCH_X86_64;当需要拷贝的像素个数少于4个的时候,需要将像素值拷贝到eax中,在64位系统下eax对应第7个寄存器,所以此处val放在第7个元素的位置
cglobal emu_edge_vfix %+ %%n, 6, 8, 0, dst, dst_stride, src, src_stride, \
start_y, end_y, val, bh;由于val处于第7个位置,所以只能自动加载前6个元素到对应寄存器中,后面的参数手动加载
mov bhq, r6mp ; r6mp = bhmp
%else ; x86-32;在32位系统下,eax对应第1个寄存器,所以这里val要放在第一个位置,所以这里不能自动加载参数,32位能够自由使用的寄存器只有6个
cglobal emu_edge_vfix %+ %%n, 0, 6, 0, val, dst, src, start_y, end_y, bh
mov dstq, r0mp
mov srcq, r2mp
mov start_yq, r4mp
mov end_yq, r5mp
mov bhq, r6mp
%define dst_strideq r1mp ;32位能够自由使用的寄存器只有6个,val使用了一个,这里其他参数使用了5个,已经被使用完,所以这两个参数放在内存中
%define src_strideq r3mp
%endif ; x86-64/32
%else
%if ARCH_X86_64
cglobal emu_edge_vfix %+ %%n, 7, 7, 1, dst, dst_stride, src, src_stride, \
start_y, end_y, bh
%else ; x86-32
cglobal emu_edge_vfix %+ %%n, 1, 5, 1, dst, src, start_y, end_y, bh
mov srcq, r2mp
mov start_yq, r4mp
mov end_yq, r5mp
mov bhq, r6mp
%define dst_strideq r1mp
%define src_strideq r3mp
%endif ; x86-64/32
%endif
; FIXME move this to c wrapper?
sub bhq, end_yq ; bh -= end_y
sub end_yq, start_yq ; end_y -= start_y
; extend pixels above body
test start_yq, start_yq ; if (start_y) {
jz .body_loop
READ_NUM_BYTES top, %%n ; $variable_regs = read($n) ;将上边界第一行拷贝到寄存器中
.top_loop: ; do {
WRITE_NUM_BYTES top, %%n ; write($variable_regs, $n) ;将寄存器中的像素值写入到内存
add dstq, dst_strideq ; dst += linesize
dec start_yq ; } while (--start_y)
jnz .top_loop ; }
; copy body pixels
.body_loop: ; do { ;正常拷贝中间未越出边界的像素值
READ_NUM_BYTES body, %%n ; $variable_regs = read($n)
WRITE_NUM_BYTES body, %%n ; write($variable_regs, $n)
add dstq, dst_strideq ; dst += dst_stride
add srcq, src_strideq ; src += src_stride
dec end_yq ; } while (--end_y)
jnz .body_loop
; copy bottom pixels
test bhq, bhq ; if (block_h) {
jz .end
sub srcq, src_strideq ; src -= linesize
READ_NUM_BYTES bottom, %%n ; $variable_regs = read($n) ;将下边界的像素值拷贝到寄存器中
.bottom_loop: ; do { ;将寄存器中的值写入到越出下边界的内存中
WRITE_NUM_BYTES bottom, %%n ; write($variable_regs, $n)
add dstq, dst_strideq ; dst += linesize
dec bhq ; } while (--bh)
jnz .bottom_loop ; }
.end:
RET
%assign %%n %%n+1
%endrep ; 1+%2-%1
%endmacro ; VERTICAL_EXTEND
INIT_MMX mmx
VERTICAL_EXTEND 1, 15
%if ARCH_X86_32
VERTICAL_EXTEND 16, 22
%endif
INIT_XMM sse
VERTICAL_EXTEND 16, 22
; left/right (horizontal) fast extend functions
; these are essentially identical to the vertical extend ones above,
; just left/right separated because number of pixels to extend is
; obviously not the same on both sides.
%macro READ_V_PIXEL 2
%if cpuflag(avx2)
vpbroadcastb m0, %2
%else
movzx vald, byte %2 ;从[dstq+start_xq]读取一个字节
imul vald, 0x01010101 ;将该字节扩展为4个相同的字节
%if %1 >= 8
movd m0, vald ;放入到m0指令集寄存器中(mmx,xmm等)
%if mmsize == 16
pshufd m0, m0, q0000 ;扩展为16字节
%else
punpckldq m0, m0 ;扩展
%endif ; mmsize == 16
%endif ; %1 > 16
%endif ; avx2
%endmacro ; READ_V_PIXEL
%macro WRITE_V_PIXEL 2
%assign %%off 0
%if %1 >= 8
%rep %1/mmsize
movu [%2+%%off], m0 ;拷贝
%assign %%off %%off+mmsize
%endrep ; %1/mmsize
%if mmsize == 16
%if %1-%%off >= 8 ;剩余待拷贝像素数[8, 16)
%if %1 > 16 && %1-%%off > 8 ;拷贝到[%2+%1-16],虽然会和上一次的拷贝有重叠的部分,但并不会造成问题
movu [%2+%1-16], m0
%assign %%off %1
%else
movq [%2+%%off], m0 ;剩余8个像素
%assign %%off %%off+8
%endif
%endif ; %1-%%off >= 8
%endif ; mmsize == 16
%if %1-%%off >= 4 ;剩余[4,8)
%if %1 > 8 && %1-%%off > 4
movq [%2+%1-8], m0
%assign %%off %1
%else ;剩余4个像素
movd [%2+%%off], m0
%assign %%off %%off+4
%endif
%endif ; %1-%%off >= 4
%else ; %1 < 8
%rep %1/4
mov [%2+%%off], vald
%assign %%off %%off+4
%endrep ; %1/4
%endif ; %1 >=/< 8
%if %1-%%off == 2 ;剩余2个像素
%if cpuflag(avx2)
movd [%2+%%off-2], m0
%else
mov [%2+%%off], valw
%endif ; avx2
%endif ; (%1-%%off)/2
%endmacro ; WRITE_V_PIXEL
%macro H_EXTEND 2
%assign %%n %1
%rep 1+(%2-%1)/2
%if cpuflag(avx2)
cglobal emu_edge_hfix %+ %%n, 4, 4, 1, dst, dst_stride, start_x, bh
%else
cglobal emu_edge_hfix %+ %%n, 4, 5, 1, dst, dst_stride, start_x, bh, val
%endif
.loop_y: ; do {
READ_V_PIXEL %%n, [dstq+start_xq] ; $variable_regs = read($n)
WRITE_V_PIXEL %%n, dstq ; write($variable_regs, $n)
add dstq, dst_strideq ; dst += dst_stride
dec bhq ; } while (--bh)
jnz .loop_y
RET
%assign %%n %%n+2 ;2个像素为单位进行拷贝
%endrep ; 1+(%2-%1)/2
%endmacro ; H_EXTEND
INIT_MMX mmx
H_EXTEND 2, 14
%if ARCH_X86_32
H_EXTEND 16, 22
%endif
INIT_XMM sse2
H_EXTEND 16, 22
%if HAVE_AVX2_EXTERNAL
INIT_XMM avx2
H_EXTEND 8, 22
%endif
%macro PREFETCH_FN 1
cglobal prefetch, 3, 3, 0, buf, stride, h
.loop:
%1 [bufq]
add bufq, strideq
dec hd
jg .loop
REP_RET
%endmacro
INIT_MMX mmxext
PREFETCH_FN prefetcht0
%if ARCH_X86_32
INIT_MMX 3dnow
PREFETCH_FN prefetch
%endif