/*
 * Direct3D Vertex Buffer implementation using ARB_vertex_buffer_object (VBO)
 *
 * Copyright 2004 TransGaming Technologies, Inc.
 */

#include "config.h"
#include "x11drv.h"
#include "glx.h"
#include "d3d.h"
#include "d3dhal.h"
#include "wine/wine_gl.h"
#include "wine/d3dhalgl.h"

#include "winbase.h"
#include "bitmap.h"
#include "winternl.h"
#include "wine/debug.h"

#include "glx_private.h"

#include <stddef.h> /*needed by ARB_vbo fptrs */

WINE_DEFAULT_DEBUG_CHANNEL(x11drv);

static GLX_D3D_VBUFFER_callbacks VBUFFER_vbo;

#ifndef GL_ARB_vertex_buffer_object
#define GL_BUFFER_SIZE_ARB                0x8764
#define GL_BUFFER_USAGE_ARB               0x8765
#define GL_ARRAY_BUFFER_ARB               0x8892
#define GL_ELEMENT_ARRAY_BUFFER_ARB       0x8893
#define GL_ARRAY_BUFFER_BINDING_ARB       0x8894
#define GL_ELEMENT_ARRAY_BUFFER_BINDING_ARB 0x8895
#define GL_VERTEX_ARRAY_BUFFER_BINDING_ARB 0x8896
#define GL_NORMAL_ARRAY_BUFFER_BINDING_ARB 0x8897
#define GL_COLOR_ARRAY_BUFFER_BINDING_ARB 0x8898
#define GL_INDEX_ARRAY_BUFFER_BINDING_ARB 0x8899
#define GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING_ARB 0x889A
#define GL_EDGE_FLAG_ARRAY_BUFFER_BINDING_ARB 0x889B
#define GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING_ARB 0x889C
#define GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING_ARB 0x889D
#define GL_WEIGHT_ARRAY_BUFFER_BINDING_ARB 0x889E
#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING_ARB 0x889F
#define GL_READ_ONLY_ARB                  0x88B8
#define GL_WRITE_ONLY_ARB                 0x88B9
#define GL_READ_WRITE_ARB                 0x88BA
#define GL_BUFFER_ACCESS_ARB              0x88BB
#define GL_BUFFER_MAPPED_ARB              0x88BC
#define GL_BUFFER_MAP_POINTER_ARB         0x88BD
#define GL_STREAM_DRAW_ARB                0x88E0
#define GL_STREAM_READ_ARB                0x88E1
#define GL_STREAM_COPY_ARB                0x88E2
#define GL_STATIC_DRAW_ARB                0x88E4
#define GL_STATIC_READ_ARB                0x88E5
#define GL_STATIC_COPY_ARB                0x88E6
#define GL_DYNAMIC_DRAW_ARB               0x88E8
#define GL_DYNAMIC_READ_ARB               0x88E9
#define GL_DYNAMIC_COPY_ARB               0x88EA
#endif

#if defined(WINE_DDRAW_need_ARB_VBO_pfns) || !defined(GL_ARB_vertex_buffer_object)
typedef INT_PTR GLintptrARB;
typedef INT_PTR GLsizeiptrARB;
typedef void (APIENTRY * PFNGLBINDBUFFERARBPROC) (GLenum target, GLuint buffer);
typedef void (APIENTRY * PFNGLDELETEBUFFERSARBPROC) (GLsizei n, const GLuint *buffers);
typedef void (APIENTRY * PFNGLGENBUFFERSARBPROC) (GLsizei n, GLuint *buffers);
typedef GLboolean (APIENTRY * PFNGLISBUFFERARBPROC) (GLuint buffer);
typedef void (APIENTRY * PFNGLBUFFERDATAARBPROC) (GLenum target, GLsizeiptrARB size, const GLvoid *data, GLenum usage);
typedef void (APIENTRY * PFNGLBUFFERSUBDATAARBPROC) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, const GLvoid *data);
typedef void (APIENTRY * PFNGLGETBUFFERSUBDATAARBPROC) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, GLvoid *data);
typedef GLvoid* (APIENTRY * PFNGLMAPBUFFERARBPROC) (GLenum target, GLenum access);
typedef GLboolean (APIENTRY * PFNGLUNMAPBUFFERARBPROC) (GLenum target);
typedef void (APIENTRY * PFNGLGETBUFFERPARAMETERIVARBPROC) (GLenum target, GLenum pname, GLint *params);
typedef void (APIENTRY * PFNGLGETBUFFERPOINTERVARBPROC) (GLenum target, GLenum pname, GLvoid* *params);
#endif

static PFNGLBINDBUFFERARBPROC X11DRV_VBO_glBindBufferARB;
static PFNGLDELETEBUFFERSARBPROC X11DRV_VBO_glDeleteBuffersARB;
static PFNGLGENBUFFERSARBPROC X11DRV_VBO_glGenBuffersARB;
static PFNGLISBUFFERARBPROC X11DRV_VBO_glIsBufferARB;
static PFNGLBUFFERDATAARBPROC X11DRV_VBO_glBufferDataARB;
static PFNGLBUFFERSUBDATAARBPROC X11DRV_VBO_glBufferSubDataARB;
static PFNGLGETBUFFERSUBDATAARBPROC X11DRV_VBO_glGetBufferSubDataARB;
static PFNGLMAPBUFFERARBPROC X11DRV_VBO_glMapBufferARB;
static PFNGLUNMAPBUFFERARBPROC X11DRV_VBO_glUnmapBufferARB;
static PFNGLGETBUFFERPARAMETERIVARBPROC X11DRV_VBO_glGetBufferParameterivARB;
static PFNGLGETBUFFERPOINTERVARBPROC X11DRV_VBO_glGetBufferPointervARB;

static BOOL X11DRV_VBO_Init = FALSE;

typedef struct _GLX_D3D_VBUFFER_priv
{
    int size;
    GLuint bufferID;
    BOOL writeonly;
} VBUFFER_PRIV_vbo;

static inline void *VBO_bind(VBUFFER_PRIV_vbo *vb)
{
    X11DRV_VBO_glBindBufferARB(GL_ARRAY_BUFFER_ARB, vb->bufferID);
    return NULL;
}

static inline void VBO_unbind(VBUFFER_PRIV_vbo *vb)
{
    X11DRV_VBO_glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
}


/* callbacks (implementations) */

GLX_D3D_VBUFFER_callbacks *VB_VBO_init()
{
    const char *exts = glGetString(GL_EXTENSIONS);

    TRACE("()\n");

    if (exts
        && strstr(exts, "GL_ARB_vertex_buffer_object"))
    {
        if (!X11DRV_VBO_Init)
        {
#ifdef HAVE_GLX_GETPROCADDRESS
#define GET_EXT(ext) (void*)X11DRV_VBO_gl##ext = glXGetProcAddressARB("gl" #ext); \
            if (!X11DRV_VBO_gl##ext) goto fail;
            GET_EXT(BindBufferARB);
            GET_EXT(DeleteBuffersARB);
            GET_EXT(GenBuffersARB);
            GET_EXT(IsBufferARB);
            GET_EXT(BufferDataARB);
            GET_EXT(BufferSubDataARB);
            GET_EXT(GetBufferSubDataARB);
            GET_EXT(MapBufferARB);
            GET_EXT(UnmapBufferARB);
            GET_EXT(GetBufferParameterivARB);
            GET_EXT(GetBufferPointervARB);
#undef GET_EXT
#else /* HAVE_GLX_GETPROCADDRESS */
            goto fail;
#endif /* HAVE_GLX_GETPROCADDRESS */

            X11DRV_VBO_Init = TRUE;
        }
        return &VBUFFER_vbo;
    }
fail:
    TRACE("failing VBO init\n");
    return NULL;
}

static void VB_VBO_cleanup()
{
    /* do nothing */
}

static GLX_D3D_VBUFFER_priv *VB_VBO_allocate(DWORD size, DWORD dwCaps,
                                             DWORD dwCaps2)
{
    VBUFFER_PRIV_vbo *vb;
    GLenum usage;

    TRACE("(%li)\n", size);

    vb = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(VBUFFER_PRIV_vbo));
    if (!vb) return NULL;

    if (dwCaps & D3DVBCAPS_WRITEONLY)
        vb->writeonly = TRUE;

    vb->size = size;

    X11DRV_VBO_glGenBuffersARB(1, &vb->bufferID);

    /* FIXME: using COPY unless writeonly is specified.
     * this may slow apps down that don't use writeonly.
     * in GL _DRAW_ is faster for just writing and drawing whereas
     *       _COPY_ is faster for reading/writing and drawing.
     *     But BOTH are allowed to read/write/draw.. so it may not
     *     be worth using _DRAW_ - we should do some tests.
     */
    usage = GL_DYNAMIC_DRAW_ARB;
    if (dwCaps2 & DDSCAPS2_HINTDYNAMIC)
        usage = vb->writeonly ? GL_DYNAMIC_DRAW_ARB : GL_DYNAMIC_COPY_ARB;
    else if (dwCaps2 & DDSCAPS2_HINTSTATIC)
        usage = vb->writeonly ? GL_STATIC_DRAW_ARB : GL_STATIC_COPY_ARB;

    VBO_bind(vb);
    X11DRV_VBO_glBufferDataARB(GL_ARRAY_BUFFER_ARB, size,
                               NULL, usage);
    VBO_unbind(vb);

    if (glGetError() == GL_OUT_OF_MEMORY)
    {
        X11DRV_VBO_glDeleteBuffersARB(1, &vb->bufferID);
        HeapFree(GetProcessHeap(), 0, vb);

        return NULL;
    }

    TRACE("-> %p\n", vb);

    return vb;
}

static void VB_VBO_free(GLX_D3D_VBUFFER_priv *vb)
{
    TRACE("(%p)\n", vb);
    X11DRV_VBO_glDeleteBuffersARB(1, &vb->bufferID);

    HeapFree(GetProcessHeap(), 0, vb);
}

static void *VB_VBO_lock(GLX_D3D_VBUFFER_priv *vb, DWORD dwFlags)
{
    GLenum access = GL_READ_WRITE_ARB;
    void *ret = NULL;

    TRACE("(%p, %08lx)\n", vb, dwFlags);

    if (dwFlags & DDLOCK_READONLY)
        access = GL_READ_ONLY_ARB;
    else if ((dwFlags & DDLOCK_DISCARDCONTENTS) || vb->writeonly)
        access = GL_WRITE_ONLY_ARB;

    /* hopefully any good implementation of VBO will give us a buffer
     * right away if we specify WRITE_ONLY.. kind of like our buffer renaming
     * in vb_var.c..
     * If that isn't the case, maybe we should do it ourselves?
     */
    VBO_bind(vb);
    ret = X11DRV_VBO_glMapBufferARB(GL_ARRAY_BUFFER_ARB, access);
    VBO_unbind(vb);

    return ret;
}

static void VB_VBO_unlock(GLX_D3D_VBUFFER_priv *vb)
{
    TRACE("(%p)\n", vb);

    VBO_bind(vb);
    X11DRV_VBO_glUnmapBufferARB(GL_ARRAY_BUFFER_ARB);
    VBO_unbind(vb);
}

static void *VB_VBO_renderptr(GLX_D3D_VBUFFER_priv *vb)
{
    TRACE("(%p)\n", vb);
    return NULL;
}

static void VB_VBO_prerender(GLX_D3D_VBUFFER_priv *vb)
{
    TRACE("(%p)\n", vb);
    VBO_bind(vb);
}

static void VB_VBO_postrender(GLX_D3D_VBUFFER_priv *vb)
{
    TRACE("(%p)\n", vb);
    VBO_unbind(vb);
}

static GLX_D3D_VBUFFER_callbacks VBUFFER_vbo =
{
    VB_VBO_cleanup,
    VB_VBO_allocate,
    VB_VBO_free,
    VB_VBO_lock,
    VB_VBO_unlock,
    VB_VBO_renderptr,
    VB_VBO_prerender,
    VB_VBO_postrender
};

