;******************************************************************************
;* Copyright (c) Lynne
;*
;* 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_RODATA 32

one_tab: times 4 dq 1.0
seq_tab_avx2: dq 3.0, 2.0, 1.0, 0.0
sub_tab: dq -1.0, -2.0, -3.0, -4.0
add_tab_avx2: times 4 dq  4.0
dec_tab_avx2: times 4 dq -4.0
add_tab_sse2: times 2 dq  2.0
dec_tab_sse2: times 2 dq -2.0
dec_tab_scalar: times 2 dq -1.0
seq_tab_sse2: dq 1.0, 0.0

SECTION .text

%macro APPLY_WELCH_FN 0
cglobal lpc_apply_welch_window, 3, 5, 8, data, len, out, off1, off2
    cmp lenq, 0
    je .end_e
    cmp lenq, 2
    je .two
    cmp lenq, 1
    je .one

    movapd m6, [one_tab]

    movd xm1, lend
    cvtdq2pd xm1, xm1      ; len
%if cpuflag(avx2)
    vbroadcastsd m1, xm1
%else
    shufpd m1, m1, 00b
%endif

    addpd m0, m6, m6       ; 2.0
    subpd m1, m6           ; len - 1
    divpd m0, m1           ; 2.0 / (len - 1)

    mov off1q, lenq
    and off1q, 1
    je .even

    movapd m5, m0
    addpd m0, [sub_tab]

    lea off2q, [lenq*4 - mmsize/2]
    sub lenq, mmsize/4     ; avoid overwriting
    xor off1q, off1q

    cmp lenq, mmsize/4
    jl .scalar_o

%if cpuflag(avx2)
    movapd m7, [dec_tab_avx2]
%else
    movapd m7, [dec_tab_sse2]
%endif

.loop_o:
    movapd m1, m6
%if cpuflag(avx2)
    fnmaddpd m1, m0, m0, m1
    vpermpd m2, m1, q0123
%else
    mulpd m2, m0, m0
    subpd m1, m2
    shufpd m2, m1, m1, 01b
%endif

    cvtdq2pd m3, [dataq + off1q]
    cvtdq2pd m4, [dataq + off2q]

    mulpd m1, m3
    mulpd m2, m4

    movupd [outq + off1q*2], m1
    movupd [outq + off2q*2], m2

    addpd m0, m7
    add off1q, mmsize/2
    sub off2q, mmsize/2
    sub lenq, mmsize/4
    jg .loop_o

    add lend, (mmsize/4 - 1)
    cmp lend, 0
    je .end_o
    sub lenq, (mmsize/4 - 1)

.scalar_o:
    movapd xm7, [dec_tab_scalar]

    ; Set offsets
    add off2q, (mmsize/4) + 4*cpuflag(avx2)
    add lenq, mmsize/4 - 2

.loop_o_scalar:
    movapd xm1, xm6
%if cpuflag(avx2)
    fnmaddpd xm1, xm0, xm0, xm1
%else
    mulpd xm2, xm0, xm0
    subpd xm1, xm2
%endif

    cvtdq2pd xm3, [dataq + off1q]
    cvtdq2pd xm4, [dataq + off2q]

    mulpd xm3, xm1
    mulpd xm4, xm1

    movlpd [outq + off1q*2], xm3
    movlpd [outq + off2q*2], xm4

    addpd xm0, xm7

    add off1q, 4
    sub off2q, 4

    sub lenq, 2
    jg .loop_o_scalar

.end_o:
    xorpd xm3, xm3
    movlpd [outq + off1q*2], xm3
    RET

.even:
%if cpuflag(avx2)
    addpd m0, [seq_tab_avx2]
%else
    addpd m0, [seq_tab_sse2]
%endif

    mov off1d, lend
    shr off1d, 1
    movd xm1, off1d
    cvtdq2pd xm1, xm1      ; len/2
%if cpuflag(avx2)
    vbroadcastsd m1, xm1
%else
    shufpd m1, m1, 00b
%endif
    subpd m0, m1

%if cpuflag(avx2)
    movapd m7, [add_tab_avx2]
%else
    movapd m7, [add_tab_sse2]
%endif

    lea off2q, [lenq*2]
    lea off1q, [lenq*2 - mmsize/2]
    sub lenq, mmsize/4

    cmp lenq, mmsize/4
    jl .scalar_e

.loop_e:
    movapd m1, m6
%if cpuflag(avx2)
    fnmaddpd m1, m0, m0, m1
%else
    mulpd m2, m0, m0
    subpd m1, m2
%endif
%if cpuflag(avx2)
    vpermpd m2, m1, q0123
%else
    shufpd m2, m1, m1, 01b
%endif

    cvtdq2pd m3, [dataq + off1q]
    cvtdq2pd m4, [dataq + off2q]

    mulpd m1, m3
    mulpd m2, m4

    movupd [outq + off1q*2], m1
    movupd [outq + off2q*2], m2

    addpd m0, m7
    add off2q, mmsize/2
    sub off1q, mmsize/2
    sub lenq, mmsize/4
    jge .loop_e

.scalar_e:
    subpd xm0, xm7
    movapd xm7, [dec_tab_scalar]
    subpd xm0, xm7

    add off1q, (mmsize/2)
    sub off2q, (mmsize/2) - 8*cpuflag(avx2)
    add lenq, 6 + 4*cpuflag(avx2)

    addpd xm0, [sub_tab]

.loop_e_scalar:
    movapd xm1, xm6
%if cpuflag(avx2)
    fnmaddpd xm1, xm0, xm0, xm1
%else
    mulpd xm2, xm0, xm0
    subpd xm1, xm2
%endif

    cvtdq2pd xm3, [dataq + off1q]
    cvtdq2pd xm4, [dataq + off2q]

    mulpd xm3, xm1
    shufpd xm1, xm1, 00b
    mulpd xm4, xm1

    movlpd [outq + off1q*2], xm3
    movhpd [outq + off2q*2 + 8], xm4

    subpd xm0, xm7

    add off2q, 4
    sub off1q, 4
    sub lenq, 2
    jg .loop_e_scalar
    RET

.two:
    xorpd xm0, xm0
    movhpd [outq + 8], xm0
.one:
    xorpd xm0, xm0
    movhpd [outq], xm0
.end_e:
    RET
%endmacro

INIT_XMM sse2
APPLY_WELCH_FN

%if HAVE_AVX2_EXTERNAL
INIT_YMM avx2
APPLY_WELCH_FN
%endif