/*
 * generic alpha renderers for all YUV modes and RGB depths
 * These are "reference implementations", should be optimized later (MMX, etc).
 * templating code by Michael Niedermayer (michaelni@gmx.at)
 *
 * This file is part of MPlayer.
 *
 * MPlayer is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * MPlayer 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with MPlayer; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

//#define FAST_OSD
//#define FAST_OSD_TABLE

#include "config.h"
#include "osd.h"
#include "mp_msg.h"
#include <inttypes.h>
#include <stdlib.h>
#include "mpmem.h"
#include "libmpcodecs/img_format.h"
#include "cpudetect.h"

#if ARCH_X86 && (!HAVE_SSE2 || CONFIG_RUNTIME_CPUDETECT)
DECLARE_ALIGNED(8, static const uint64_t, bFF) = 0xFFFFFFFFFFFFFFFFULL;
DECLARE_ALIGNED(8, static const unsigned long long, mask24lh) = 0xFFFF000000000000ULL;
DECLARE_ALIGNED(8, static const unsigned long long, mask24hl) = 0x0000FFFFFFFFFFFFULL;
#endif

//Note: we have C, X86-nommx, MMX, MMX2, 3DNOW version therse no 3DNOW+MMX2 one
//Plain C versions
#if (!HAVE_MMX && !HAVE_SSE2) || CONFIG_RUNTIME_CPUDETECT
#define COMPILE_C
#endif

#if ARCH_X86

#if (HAVE_MMX && !HAVE_AMD3DNOW && !HAVE_MMX2 && !HAVE_SSE2) || CONFIG_RUNTIME_CPUDETECT
#define COMPILE_MMX
#endif

#if (HAVE_MMX2 && !HAVE_SSE2) || CONFIG_RUNTIME_CPUDETECT
#define COMPILE_MMX2
#endif

#if (HAVE_AMD3DNOW && !HAVE_MMX2 && !HAVE_SSE2) || CONFIG_RUNTIME_CPUDETECT
#define COMPILE_3DNOW
#endif

#if HAVE_EMMINTRIN_H && (HAVE_SSE2 || CONFIG_RUNTIME_CPUDETECT)
// crashes on win32 due to alignment issues of unclear cause
#if !defined(_WIN32) || !ARCH_X86_32
#define COMPILE_SSE2
#endif
#endif
#endif /* ARCH_X86 */

#undef HAVE_MMX
#undef HAVE_MMX2
#undef HAVE_AMD3DNOW
#undef HAVE_SSE2
#define HAVE_MMX 0
#define HAVE_MMX2 0
#define HAVE_AMD3DNOW 0
#define HAVE_SSE2 0

#if ! ARCH_X86

#ifdef COMPILE_C
#undef HAVE_MMX
#undef HAVE_MMX2
#undef HAVE_AMD3DNOW
#undef HAVE_SSE2
#define HAVE_MMX 0
#define HAVE_MMX2 0
#define HAVE_AMD3DNOW 0
#define HAVE_SSE2 0
#define RENAME(a) a ## _C
#include "osd_template.c"
#endif

#else

//X86 noMMX versions
#ifdef COMPILE_C
#undef RENAME
#undef HAVE_MMX
#undef HAVE_MMX2
#undef HAVE_AMD3DNOW
#undef HAVE_SSE2
#define HAVE_MMX 0
#define HAVE_MMX2 0
#define HAVE_AMD3DNOW 0
#define HAVE_SSE2 0
#define RENAME(a) a ## _X86
#include "osd_template.c"
#endif

//MMX versions
#ifdef COMPILE_MMX
#undef RENAME
#undef HAVE_MMX
#undef HAVE_MMX2
#undef HAVE_AMD3DNOW
#undef HAVE_SSE2
#define HAVE_MMX 1
#define HAVE_MMX2 0
#define HAVE_AMD3DNOW 0
#define HAVE_SSE2 0
#define RENAME(a) a ## _MMX
#include "osd_template.c"
#endif

//MMX2 versions
#ifdef COMPILE_MMX2
#undef RENAME
#undef HAVE_MMX
#undef HAVE_MMX2
#undef HAVE_AMD3DNOW
#undef HAVE_SSE2
#define HAVE_MMX 1
#define HAVE_MMX2 1
#define HAVE_AMD3DNOW 0
#define HAVE_SSE2 0
#define RENAME(a) a ## _MMX2
#include "osd_template.c"
#endif

//3DNOW versions
#ifdef COMPILE_3DNOW
#undef RENAME
#undef HAVE_MMX
#undef HAVE_MMX2
#undef HAVE_AMD3DNOW
#undef HAVE_SSE2
#define HAVE_MMX 1
#define HAVE_MMX2 0
#define HAVE_AMD3DNOW 1
#define HAVE_SSE2 0
#define RENAME(a) a ## _3DNow
#include "osd_template.c"
#endif

//SSE2 versions
#ifdef COMPILE_SSE2
#undef RENAME
#undef HAVE_MMX
#undef HAVE_MMX2
#undef HAVE_AMD3DNOW
#undef HAVE_SSE2
#define HAVE_MMX 0
#define HAVE_MMX2 0
#define HAVE_AMD3DNOW 0
#define HAVE_SSE2 1
#define RENAME(a) a ## _SSE2
#include <emmintrin.h>
#include "osd_template.c"
#endif
#endif /* ARCH_X86 */

void vo_draw_alpha_yv12(int w,int h, unsigned char* src, unsigned char *srca, int srcstride, unsigned char* dstbase,int dststride){
#if CONFIG_RUNTIME_CPUDETECT
#if ARCH_X86
	// ordered by speed / fastest first
#ifdef COMPILE_SSE2
	if(gCpuCaps.hasSSE2)
		vo_draw_alpha_yv12_SSE2(w, h, src, srca, srcstride, dstbase, dststride);
	else
#endif
	if(gCpuCaps.hasMMX2)
		vo_draw_alpha_yv12_MMX2(w, h, src, srca, srcstride, dstbase, dststride);
	else if(gCpuCaps.has3DNow)
		vo_draw_alpha_yv12_3DNow(w, h, src, srca, srcstride, dstbase, dststride);
	else if(gCpuCaps.hasMMX)
		vo_draw_alpha_yv12_MMX(w, h, src, srca, srcstride, dstbase, dststride);
	else
		vo_draw_alpha_yv12_X86(w, h, src, srca, srcstride, dstbase, dststride);
#else
		vo_draw_alpha_yv12_C(w, h, src, srca, srcstride, dstbase, dststride);
#endif
#else //CONFIG_RUNTIME_CPUDETECT
#if HAVE_SSE2
		vo_draw_alpha_yv12_SSE2(w, h, src, srca, srcstride, dstbase, dststride);
#elif HAVE_MMX2
		vo_draw_alpha_yv12_MMX2(w, h, src, srca, srcstride, dstbase, dststride);
#elif HAVE_AMD3DNOW
		vo_draw_alpha_yv12_3DNow(w, h, src, srca, srcstride, dstbase, dststride);
#elif HAVE_MMX
		vo_draw_alpha_yv12_MMX(w, h, src, srca, srcstride, dstbase, dststride);
#elif ARCH_X86
		vo_draw_alpha_yv12_X86(w, h, src, srca, srcstride, dstbase, dststride);
#else
		vo_draw_alpha_yv12_C(w, h, src, srca, srcstride, dstbase, dststride);
#endif
#endif //!CONFIG_RUNTIME_CPUDETECT
}

void vo_draw_alpha_yuy2(int w,int h, unsigned char* src, unsigned char *srca, int srcstride, unsigned char* dstbase,int dststride){
#if CONFIG_RUNTIME_CPUDETECT
#if ARCH_X86
	// ordered by speed / fastest first
#ifdef COMPILE_SSE2
	if(gCpuCaps.hasSSE2)
		vo_draw_alpha_yuy2_SSE2(w, h, src, srca, srcstride, dstbase, dststride);
	else
#endif
	if(gCpuCaps.hasMMX2)
		vo_draw_alpha_yuy2_MMX2(w, h, src, srca, srcstride, dstbase, dststride);
	else if(gCpuCaps.has3DNow)
		vo_draw_alpha_yuy2_3DNow(w, h, src, srca, srcstride, dstbase, dststride);
	else if(gCpuCaps.hasMMX)
		vo_draw_alpha_yuy2_MMX(w, h, src, srca, srcstride, dstbase, dststride);
	else
		vo_draw_alpha_yuy2_X86(w, h, src, srca, srcstride, dstbase, dststride);
#else
		vo_draw_alpha_yuy2_C(w, h, src, srca, srcstride, dstbase, dststride);
#endif
#else //CONFIG_RUNTIME_CPUDETECT
#if HAVE_SSE2
		vo_draw_alpha_yuy2_SSE2(w, h, src, srca, srcstride, dstbase, dststride);
#elif HAVE_MMX2
		vo_draw_alpha_yuy2_MMX2(w, h, src, srca, srcstride, dstbase, dststride);
#elif HAVE_AMD3DNOW
		vo_draw_alpha_yuy2_3DNow(w, h, src, srca, srcstride, dstbase, dststride);
#elif HAVE_MMX
		vo_draw_alpha_yuy2_MMX(w, h, src, srca, srcstride, dstbase, dststride);
#elif ARCH_X86
		vo_draw_alpha_yuy2_X86(w, h, src, srca, srcstride, dstbase, dststride);
#else
		vo_draw_alpha_yuy2_C(w, h, src, srca, srcstride, dstbase, dststride);
#endif
#endif //!CONFIG_RUNTIME_CPUDETECT
}

void vo_draw_alpha_uyvy(int w,int h, unsigned char* src, unsigned char *srca, int srcstride, unsigned char* dstbase,int dststride){
#if CONFIG_RUNTIME_CPUDETECT
#if ARCH_X86
	// ordered by speed / fastest first
#ifdef COMPILE_SSE2
	if(gCpuCaps.hasSSE2)
		vo_draw_alpha_uyvy_SSE2(w, h, src, srca, srcstride, dstbase, dststride);
	else
#endif
	if(gCpuCaps.hasMMX2)
		vo_draw_alpha_uyvy_MMX2(w, h, src, srca, srcstride, dstbase, dststride);
	else if(gCpuCaps.has3DNow)
		vo_draw_alpha_uyvy_3DNow(w, h, src, srca, srcstride, dstbase, dststride);
	else if(gCpuCaps.hasMMX)
		vo_draw_alpha_uyvy_MMX(w, h, src, srca, srcstride, dstbase, dststride);
	else
		vo_draw_alpha_uyvy_X86(w, h, src, srca, srcstride, dstbase, dststride);
#else
		vo_draw_alpha_uyvy_C(w, h, src, srca, srcstride, dstbase, dststride);
#endif
#else //CONFIG_RUNTIME_CPUDETECT
#if HAVE_SSE2
		vo_draw_alpha_uyvy_SSE2(w, h, src, srca, srcstride, dstbase, dststride);
#elif HAVE_MMX2
		vo_draw_alpha_uyvy_MMX2(w, h, src, srca, srcstride, dstbase, dststride);
#elif HAVE_AMD3DNOW
		vo_draw_alpha_uyvy_3DNow(w, h, src, srca, srcstride, dstbase, dststride);
#elif HAVE_MMX
		vo_draw_alpha_uyvy_MMX(w, h, src, srca, srcstride, dstbase, dststride);
#elif ARCH_X86
		vo_draw_alpha_uyvy_X86(w, h, src, srca, srcstride, dstbase, dststride);
#else
		vo_draw_alpha_uyvy_C(w, h, src, srca, srcstride, dstbase, dststride);
#endif
#endif //!CONFIG_RUNTIME_CPUDETECT
}

void vo_draw_alpha_rgb24(int w,int h, unsigned char* src, unsigned char *srca, int srcstride, unsigned char* dstbase,int dststride){
#if CONFIG_RUNTIME_CPUDETECT
#if ARCH_X86
	// ordered by speed / fastest first
#ifdef COMPILE_SSE2
	if(gCpuCaps.hasSSE2)
		vo_draw_alpha_rgb24_SSE2(w, h, src, srca, srcstride, dstbase, dststride);
	else
#endif
	if(gCpuCaps.hasMMX2)
		vo_draw_alpha_rgb24_MMX2(w, h, src, srca, srcstride, dstbase, dststride);
	else if(gCpuCaps.has3DNow)
		vo_draw_alpha_rgb24_3DNow(w, h, src, srca, srcstride, dstbase, dststride);
	else if(gCpuCaps.hasMMX)
		vo_draw_alpha_rgb24_MMX(w, h, src, srca, srcstride, dstbase, dststride);
	else
		vo_draw_alpha_rgb24_X86(w, h, src, srca, srcstride, dstbase, dststride);
#else
		vo_draw_alpha_rgb24_C(w, h, src, srca, srcstride, dstbase, dststride);
#endif
#else //CONFIG_RUNTIME_CPUDETECT
#if HAVE_SSE2
		vo_draw_alpha_rgb24_SSE2(w, h, src, srca, srcstride, dstbase, dststride);
#elif HAVE_MMX2
		vo_draw_alpha_rgb24_MMX2(w, h, src, srca, srcstride, dstbase, dststride);
#elif HAVE_AMD3DNOW
		vo_draw_alpha_rgb24_3DNow(w, h, src, srca, srcstride, dstbase, dststride);
#elif HAVE_MMX
		vo_draw_alpha_rgb24_MMX(w, h, src, srca, srcstride, dstbase, dststride);
#elif ARCH_X86
		vo_draw_alpha_rgb24_X86(w, h, src, srca, srcstride, dstbase, dststride);
#else
		vo_draw_alpha_rgb24_C(w, h, src, srca, srcstride, dstbase, dststride);
#endif
#endif //!CONFIG_RUNTIME_CPUDETECT
}

void vo_draw_alpha_rgb32(int w,int h, unsigned char* src, unsigned char *srca, int srcstride, unsigned char* dstbase,int dststride){
#if CONFIG_RUNTIME_CPUDETECT
#if ARCH_X86
	// ordered by speed / fastest first
#ifdef COMPILE_SSE2
	if(gCpuCaps.hasSSE2)
		vo_draw_alpha_rgb32_SSE2(w, h, src, srca, srcstride, dstbase, dststride);
	else
#endif
	if(gCpuCaps.hasMMX2)
		vo_draw_alpha_rgb32_MMX2(w, h, src, srca, srcstride, dstbase, dststride);
	else if(gCpuCaps.has3DNow)
		vo_draw_alpha_rgb32_3DNow(w, h, src, srca, srcstride, dstbase, dststride);
	else if(gCpuCaps.hasMMX)
		vo_draw_alpha_rgb32_MMX(w, h, src, srca, srcstride, dstbase, dststride);
	else
		vo_draw_alpha_rgb32_X86(w, h, src, srca, srcstride, dstbase, dststride);
#else
		vo_draw_alpha_rgb32_C(w, h, src, srca, srcstride, dstbase, dststride);
#endif
#else //CONFIG_RUNTIME_CPUDETECT
#if HAVE_SSE2
		vo_draw_alpha_rgb32_SSE2(w, h, src, srca, srcstride, dstbase, dststride);
#elif HAVE_MMX2
		vo_draw_alpha_rgb32_MMX2(w, h, src, srca, srcstride, dstbase, dststride);
#elif HAVE_AMD3DNOW
		vo_draw_alpha_rgb32_3DNow(w, h, src, srca, srcstride, dstbase, dststride);
#elif HAVE_MMX
		vo_draw_alpha_rgb32_MMX(w, h, src, srca, srcstride, dstbase, dststride);
#elif ARCH_X86
		vo_draw_alpha_rgb32_X86(w, h, src, srca, srcstride, dstbase, dststride);
#else
		vo_draw_alpha_rgb32_C(w, h, src, srca, srcstride, dstbase, dststride);
#endif
#endif //!CONFIG_RUNTIME_CPUDETECT
}

#ifdef FAST_OSD_TABLE
static unsigned short fast_osd_12bpp_table[256];
static unsigned short fast_osd_15bpp_table[256];
static unsigned short fast_osd_16bpp_table[256];
#endif

void vo_draw_alpha_init(void){
#ifdef FAST_OSD_TABLE
    int i;
    for(i=0;i<256;i++){
        fast_osd_12bpp_table[i]=((i>>4)<< 8)|((i>>4)<<4)|(i>>4);
        fast_osd_15bpp_table[i]=((i>>3)<<10)|((i>>3)<<5)|(i>>3);
        fast_osd_16bpp_table[i]=((i>>3)<<11)|((i>>2)<<5)|(i>>3);
    }
#endif
//FIXME the optimized stuff is a lie for 15/16bpp as they aren't optimized yet
	if( mp_msg_test(MSGT_OSD,MSGL_V) )
	{
#if CONFIG_RUNTIME_CPUDETECT
#if ARCH_X86
		// ordered per speed fastest first
#ifdef COMPILE_SSE2
		if(gCpuCaps.hasSSE2)
			mp_msg(MSGT_OSD,MSGL_INFO,"Using SSE2 Optimized OnScreenDisplay\n");
		else
#endif
		if(gCpuCaps.hasMMX2)
			mp_msg(MSGT_OSD,MSGL_INFO,"Using MMX (with tiny bit MMX2) Optimized OnScreenDisplay\n");
		else if(gCpuCaps.has3DNow)
			mp_msg(MSGT_OSD,MSGL_INFO,"Using MMX (with tiny bit 3DNow) Optimized OnScreenDisplay\n");
		else if(gCpuCaps.hasMMX)
			mp_msg(MSGT_OSD,MSGL_INFO,"Using MMX Optimized OnScreenDisplay\n");
		else
			mp_msg(MSGT_OSD,MSGL_INFO,"Using X86 Optimized OnScreenDisplay\n");
#else
			mp_msg(MSGT_OSD,MSGL_INFO,"Using Unoptimized OnScreenDisplay\n");
#endif
#else //CONFIG_RUNTIME_CPUDETECT
#if HAVE_SSE2
			mp_msg(MSGT_OSD,MSGL_INFO,"Using SSE2 Optimized OnScreenDisplay\n");
#elif HAVE_MMX2
			mp_msg(MSGT_OSD,MSGL_INFO,"Using MMX (with tiny bit MMX2) Optimized OnScreenDisplay\n");
#elif HAVE_AMD3DNOW
			mp_msg(MSGT_OSD,MSGL_INFO,"Using MMX (with tiny bit 3DNow) Optimized OnScreenDisplay\n");
#elif HAVE_MMX
			mp_msg(MSGT_OSD,MSGL_INFO,"Using MMX Optimized OnScreenDisplay\n");
#elif ARCH_X86
			mp_msg(MSGT_OSD,MSGL_INFO,"Using X86 Optimized OnScreenDisplay\n");
#else
			mp_msg(MSGT_OSD,MSGL_INFO,"Using Unoptimized OnScreenDisplay\n");
#endif
#endif //!CONFIG_RUNTIME_CPUDETECT
	}
}

void vo_draw_alpha_rgb12(int w, int h, unsigned char* src, unsigned char *srca,
                         int srcstride, unsigned char* dstbase, int dststride) {
    int y;
    for (y = 0; y < h; y++) {
        register unsigned short *dst = (unsigned short*) dstbase;
        register int x;
        for (x = 0; x < w; x++) {
            if(srca[x]){
#ifdef FAST_OSD
#ifdef FAST_OSD_TABLE
                dst[x] = fast_osd_12bpp_table[src[x]];
#else
                register unsigned int a = src[x] >> 4;
                dst[x] = (a << 8) | (a << 4) | a;
#endif
#else
                unsigned char r = dst[x] & 0x0F;
                unsigned char g = (dst[x] >> 4) & 0x0F;
                unsigned char b = (dst[x] >> 8) & 0x0F;
                r = (((r*srca[x]) >> 4) + src[x]) >> 4;
                g = (((g*srca[x]) >> 4) + src[x]) >> 4;
                b = (((b*srca[x]) >> 4) + src[x]) >> 4;
                dst[x] = (b << 8) | (g << 4) | r;
#endif
            }
        }
        src += srcstride;
        srca += srcstride;
        dstbase += dststride;
    }
    return;
}

void vo_draw_alpha_rgb15(int w,int h, unsigned char* src, unsigned char *srca, int srcstride, unsigned char* dstbase,int dststride){
    int y;
    for(y=0;y<h;y++){
        register unsigned short *dst = (unsigned short*) dstbase;
        register int x;
        for(x=0;x<w;x++){
            if(srca[x]){
#ifdef FAST_OSD
#ifdef FAST_OSD_TABLE
                dst[x]=fast_osd_15bpp_table[src[x]];
#else
		register unsigned int a=src[x]>>3;
                dst[x]=(a<<10)|(a<<5)|a;
#endif
#else
                unsigned char r=dst[x]&0x1F;
                unsigned char g=(dst[x]>>5)&0x1F;
                unsigned char b=(dst[x]>>10)&0x1F;
                r=(((r*srca[x])>>5)+src[x])>>3;
                g=(((g*srca[x])>>5)+src[x])>>3;
                b=(((b*srca[x])>>5)+src[x])>>3;
                dst[x]=(b<<10)|(g<<5)|r;
#endif
            }
        }
        src+=srcstride;
        srca+=srcstride;
        dstbase+=dststride;
    }
    return;
}

void vo_draw_alpha_rgb16(int w,int h, unsigned char* src, unsigned char *srca, int srcstride, unsigned char* dstbase,int dststride){
    int y;
    for(y=0;y<h;y++){
        register unsigned short *dst = (unsigned short*) dstbase;
        register int x;
        for(x=0;x<w;x++){
            if(srca[x]){
#ifdef FAST_OSD
#ifdef FAST_OSD_TABLE
                dst[x]=fast_osd_16bpp_table[src[x]];
#else
                dst[x]=((src[x]>>3)<<11)|((src[x]>>2)<<5)|(src[x]>>3);
#endif
#else
                unsigned char r=dst[x]&0x1F;
                unsigned char g=(dst[x]>>5)&0x3F;
                unsigned char b=(dst[x]>>11)&0x1F;
                r=(((r*srca[x])>>5)+src[x])>>3;
                g=(((g*srca[x])>>6)+src[x])>>2;
                b=(((b*srca[x])>>5)+src[x])>>3;
                dst[x]=(b<<11)|(g<<5)|r;
#endif
            }
        }
        src+=srcstride;
        srca+=srcstride;
        dstbase+=dststride;
    }
    return;
}

vo_draw_alpha_func vo_get_draw_alpha(unsigned fmt) {
    if (IMGFMT_IS_RGB(fmt) || IMGFMT_IS_BGR(fmt)) {
        switch (IMGFMT_RGB_DEPTH(fmt))
        {
        case 12:
            return vo_draw_alpha_rgb12;
        case 15:
            return vo_draw_alpha_rgb15;
        case 16:
            return vo_draw_alpha_rgb16;
        case 24:
            return vo_draw_alpha_rgb24;
        case 32:
            return vo_draw_alpha_rgb32;
        }
        return NULL;
    }
    switch (fmt) {
    case IMGFMT_YV12:
    case IMGFMT_I420:
    case IMGFMT_IYUV:
    case IMGFMT_NV12:
    case IMGFMT_NV21:
        return vo_draw_alpha_yv12;
    case IMGFMT_YUY2:
    case IMGFMT_YVYU:
        return vo_draw_alpha_yuy2;
    case IMGFMT_UYVY:
        return vo_draw_alpha_uyvy;
    }
    return NULL;
}
