/*
 * Direct3D Vertex Buffer implementation using NV_vertex_array_range (VAR)
 *
 * Copyright 2001-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"

#define DRIVER_SAFETY 32 /* my 43.49 driver seem to overread the vertex buffer for no apparent reason */

#define USE_HEAP

WINE_DEFAULT_DEBUG_CHANNEL(x11drv);

static GLX_D3D_VBUFFER_callbacks VBUFFER_var;

/* GL_NV_vertex_array_range(2) & GL_NV_fence
 * The VAR arena is shared by all contexts, as are the function pointers,
 * but the extension must be tested in each context.
 */
#ifndef GL_NV_fence
#define GL_ALL_COMPLETED_NV 0x84F2
#endif

#ifndef GL_NV_vertex_array_range2
#define GL_VERTEX_ARRAY_RANGE_WITHOUT_FLUSH_NV 0x8533
#endif

#ifndef GL_NV_pixel_data_range
#define GL_WRITE_PIXEL_DATA_RANGE_NV 0x8878
#define GL_READ_PIXEL_DATA_RANGE_NV  0x8879
#endif

#if defined(WINE_DDRAW_need_NV_VAR_pfns) || !defined(GL_NV_vertex_array_range)
typedef void (*PFNGLFLUSHVERTEXARRAYRANGENVPROC)(void);
typedef void (*PFNGLVERTEXARRAYRANGENVPROC)(GLsizei size, const GLvoid *pointer);
#endif

#if defined(WINE_DDRAW_need_NV_fence_pfns) || !defined(GL_NV_fence)
typedef void (*PFNGLGENFENCESNVPROC)(GLsizei n, GLuint *fences);
typedef void (*PFNGLDELETEFENCESNVPROC)(GLsizei n, const GLuint *fences);
typedef void (*PFNGLSETFENCENVPROC)(GLuint fence, GLenum condition);
typedef GLboolean (*PFNGLTESTFENCENVPROC)(GLuint fence);
typedef void (*PFNGLFINISHFENCENVPROC)(GLuint fence);
typedef GLboolean (*PFNGLISFENCENVPROC)(GLuint fence);
typedef void (*PFNGLGETFENCEIVNVPROC)(GLuint fence, GLenum pname,
                                      GLint *params);
#endif

#if defined(WINE_DDRAW_need_NV_PDR_pfns) || !defined(GL_NV_pixel_data_range)
typedef void (*PFNGLPIXELDATARANGENVPROC)(GLenum target, GLsizei length, GLvoid *pointer);
typedef void (*PFNGLFLUSHPIXELDATARANGENVPROC)(GLenum target);
#endif

extern unsigned int agpvertexarrayram;

static BOOL X11DRV_GLX_VAR_Enable;
static PFNGLFLUSHVERTEXARRAYRANGENVPROC X11DRV_GLX_glFlushVertexArrayRangeNV;
static PFNGLVERTEXARRAYRANGENVPROC X11DRV_GLX_glVertexArrayRangeNV;
static void *(*X11DRV_GLX_glXAllocateMemoryNV)(GLsizei size, GLfloat readfrequency, GLfloat writefrequency, GLfloat priority);
static void (*X11DRV_GLX_glXFreeMemoryNV)(void *pointer);

static PFNGLGENFENCESNVPROC X11DRV_GLX_glGenFencesNV;
static PFNGLDELETEFENCESNVPROC X11DRV_GLX_glDeleteFencesNV;
static PFNGLSETFENCENVPROC X11DRV_GLX_glSetFenceNV;
static PFNGLTESTFENCENVPROC X11DRV_GLX_glTestFenceNV;
static PFNGLFINISHFENCENVPROC X11DRV_GLX_glFinishFenceNV;
static PFNGLISFENCENVPROC X11DRV_GLX_glIsFenceNV;

static LPVOID X11DRV_GLX_VertexArray;
static DWORD X11DRV_GLX_VertexArraySize;

#ifdef USE_HEAP
static HANDLE X11DRV_GLX_VertexArrayHeap;
#else
struct X11DRV_GLX_FreeBlock
{
    DWORD ofs, size;
    struct X11DRV_GLX_FreeBlock* prev;
    struct X11DRV_GLX_FreeBlock* next;
};

static struct X11DRV_GLX_FreeBlock *X11DRV_GLX_FreeList = NULL;
#endif

/* VAR buffers (fence / rename stuff)
 *
 * Implementation notes:
 * Each D3D 'VertexBuffer' contains several (up to 8) VAR buffers.
 * As the app writes to the buffers we can cycle the various buffers,
 * because we can't always use them right away.
 * We use NV_Fence to see when they are ready to use.
 * As an aside, 'renaming' a buffer means that we change the
 * bit of VAR memory that a VB points to.
 */

#define MAX_RENAME_DEPTH 8

struct X11DRV_GLX_VAR_Buffer
{
    LPVOID data;
    GLuint fence;
};

struct X11DRV_GLX_VAR_BufferControl
{
    DWORD num_buffers;
    DWORD current_buffer;
    DWORD buffer_size;

    struct X11DRV_GLX_VAR_BufferControl *prev, *next;

    struct X11DRV_GLX_VAR_Buffer buffers[MAX_RENAME_DEPTH];
};

typedef struct _GLX_D3D_VBUFFER_priv
{
    struct X11DRV_GLX_VAR_BufferControl buf_control;
} VBUFFER_PRIV_var;

/* VAR allocation */

#ifdef USE_HEAP

static LPVOID *X11DRV_GLX_AllocateVARMemory(DWORD size)
{
    LPVOID ptr;

    if (!X11DRV_GLX_VAR_Enable || (X11DRV_GLX_VertexArray == NULL))
    {
        ERR("shouldn't be called\n");
        return NULL;
    }

    ptr = HeapAlloc(X11DRV_GLX_VertexArrayHeap, 0, size + DRIVER_SAFETY);

    TRACE("(%ld) => %p\n", size, ptr);

    return ptr;
}

static void X11DRV_GLX_FreeVARMemory(LPVOID ptr, DWORD size)
{
    if (!X11DRV_GLX_VAR_Enable || (X11DRV_GLX_VertexArray == NULL))
    {
        ERR("shouldn't be called\n");
        return;
    }

    if (ptr) HeapFree(X11DRV_GLX_VertexArrayHeap, 0, ptr);
    TRACE("(%p)\n", ptr);
}

#else

static LPVOID X11DRV_GLX_AllocateVARMemory(DWORD size)
{
    struct X11DRV_GLX_FreeBlock *avail;
    DWORD sum, ofs, nofs;
    BOOL force_align;

    DWORD align = 0; /* HACK, copying from old code where this
                      * was a parameter. though it was always 0.
                      * important?
                      */


    if (!X11DRV_GLX_VAR_Enable || (X11DRV_GLX_VertexArray == NULL))
    {
        ERR("shouldn't be called\n");
        return NULL;
    }

    /* use a minimum alignment of 32 bytes */
    size = (size + 31) &~ 31;
    if (align < 32) align = 32;
    /* the alignment must be a power of 2 */
    /* assert( (align&(align-1)) == 0); */

    /* find free block */

    avail = X11DRV_GLX_FreeList;
    sum = 0;
    force_align = align > 32; /* try aligned block first */

    if (!avail) {
        WARN("oh no! completely out of AGP memory\n");
        goto out_of_mem;
    }

    do
    {
        ofs = avail->ofs;
        nofs = (ofs + align-1) & ~(align-1);
        if ((nofs == ofs || !force_align) &&
            (avail->size >= size + (nofs - ofs))) break;
        sum += avail->size;
        avail = avail->next;
        if (avail == X11DRV_GLX_FreeList) {
            if (force_align) force_align = FALSE;
            else break;
        }
    }
    while (TRUE);

    if (avail->size < size)
    {
        if (sum > size) {
            /* it *should* be possible for us to "defragment" the AGP heap since
             * all we need to do to do it is to keep track of which surface
             * allocated a particular memory block, move the memory, and write
             * the new address into the surface structure (as long as the surface
             * is not locked). */
            FIXME("AGP allocation failed because of fragmentation\n");
        }
        WARN("oh no! out of AGP memory!\n");
        goto out_of_mem;
    }

    /* split beginning of block if required by alignment */
    if (nofs > ofs) {
        struct X11DRV_GLX_FreeBlock *newblock;

        newblock = HeapAlloc(GetProcessHeap(), 0, sizeof(struct X11DRV_GLX_FreeBlock));
        newblock->ofs = nofs;
        newblock->size = avail->size - (nofs - ofs);

        newblock->next = avail->next;
        newblock->prev = avail;
        newblock->next->prev = newblock;
        newblock->prev->next = newblock;

        avail->size = nofs - ofs;

        avail = newblock;
        ofs = nofs;
    }

    /* allocate from block */
    avail->ofs = ofs + size;
    avail->size -= size;
    if (avail->size == 0) {
        avail->next->prev = avail->prev;
        avail->prev->next = avail->next;
        if (avail == X11DRV_GLX_FreeList) {
            if (avail->next == X11DRV_GLX_FreeList) X11DRV_GLX_FreeList = NULL;
            else X11DRV_GLX_FreeList = avail->next;
        }
        HeapFree(GetProcessHeap(), 0, avail);
    }

    TRACE("ofs=%ld, size=%ld\n", ofs, size);

    ptr = (LPBYTE)X11DRV_GLX_VertexArray + ofs;

    return ptr;

out_of_mem:
    return NULL;
}

static void X11DRV_GLX_FreeVARMemory(LPVOID ptr, DWORD size)
{
    if (!X11DRV_GLX_VAR_Enable || (X11DRV_GLX_VertexArray == NULL))
    {
        ERR("shouldn't be called\n");
        return NULL;
    }

    if (!ptr || !size) return;

    ofs = (LPBYTE)(ptr) - (LPBYTE)X11DRV_GLX_VertexArray;

    size = (size + 31) &~ 31;

    TRACE("ofs=%ld, size=%ld\n", ofs, size);

    /* find adjacent free block */
    avail = X11DRV_GLX_FreeList;

    if (!avail) {
        newblock = HeapAlloc(GetProcessHeap(), 0, sizeof(struct X11DRV_GLX_FreeBlock));
        newblock->ofs = ofs;
        newblock->size = size;
        newblock->next = newblock;
        newblock->prev = newblock;
        X11DRV_GLX_FreeList = newblock;
        return;
    }

    do
    {
        if (avail->ofs >= ofs) break;
        avail = avail->next;
        if (avail == X11DRV_GLX_FreeList) break;
    }
    while (TRUE);

    if (avail->ofs == ofs) {
        WARN("oh no! a double-free\n");
        return;
    }

    newblock = NULL;

    if (avail->ofs == ofs + size) {
        /* merge with free block after us */
        size += avail->size;
        avail->ofs = ofs;
        avail->size = size;
        newblock = avail;
    }

    avail = avail->prev;
    if (avail->ofs + avail->size == ofs) {
        /* merge with free block before us */
        size += avail->size;
        avail->size = size;
        if (newblock) {
            newblock->next->prev = newblock->prev;
            newblock->prev->next = newblock->next;
            HeapFree(GetProcessHeap(), 0, newblock);
        }
        newblock = avail;
    }

    if (!newblock) {
        newblock = HeapAlloc(GetProcessHeap(), 0, sizeof(struct X11DRV_GLX_FreeBlock));
        newblock->ofs = ofs;
        newblock->size = size;

        newblock->next = avail->next;
        newblock->prev = avail;
        newblock->next->prev = newblock;
        newblock->prev->next = newblock;

        /* keep lowest offset first in list */
        if (avail->ofs > ofs) X11DRV_GLX_FreeList = newblock;
    }
}

#endif

/* back to the buffers */

static struct X11DRV_GLX_VAR_BufferControl X11DRV_GLX_VAR_BufferControlList =
{
    0, 0, 0,
    &X11DRV_GLX_VAR_BufferControlList, &X11DRV_GLX_VAR_BufferControlList,
};


static BOOL X11DRV_GLX_VAR_TestBuffer(struct X11DRV_GLX_VAR_BufferControl* ctl,
                                      DWORD buffer)
{
    return !X11DRV_GLX_glIsFenceNV(ctl->buffers[buffer].fence)
        || X11DRV_GLX_glTestFenceNV(ctl->buffers[buffer].fence);
}

static void X11DRV_GLX_VAR_WaitBuffer(struct X11DRV_GLX_VAR_BufferControl* ctl,
                                      DWORD buffer)
{
    if (X11DRV_GLX_glIsFenceNV(ctl->buffers[buffer].fence))
        X11DRV_GLX_glFinishFenceNV(ctl->buffers[buffer].fence);
}

/* Returns the reclaimed size. (May be changed to TRUE/FALSE in the future.)
 * Does not attempt to reclaim from skip. (Which can be NULL.)
 *
 * Goes through the various vertex buffer looking for one that
 * may have unused VAR buffer associated with it, and frees any of them.
 * Skips the one in 'skip' because we call this when we are trying to
 * allocate more buffers in a buffer control but run out.
 */
static DWORD
X11DRV_GLX_VAR_ReclaimBuffers(struct X11DRV_GLX_VAR_BufferControl* skip)
{
    struct X11DRV_GLX_VAR_BufferControl* victim;
    DWORD reclaimed_size = 0;

    TRACE("(%p)\n", skip);
    for (victim = X11DRV_GLX_VAR_BufferControlList.next;
         victim != &X11DRV_GLX_VAR_BufferControlList; victim = victim->next)
    {
        int i;

        if (victim == skip) continue;

        /* we won't be able to steal any anyway. */
        if (victim->num_buffers < 2) continue;

        for (i=0; i < victim->num_buffers; i++)
        {
            if (i == victim->current_buffer) continue;

            if (X11DRV_GLX_VAR_TestBuffer(victim, i))
            {
                struct X11DRV_GLX_VAR_Buffer *buffer = &victim->buffers[i];

                /* free the buffer's resources */
                X11DRV_GLX_glDeleteFencesNV(1, &buffer->fence);
                X11DRV_GLX_FreeVARMemory(buffer->data, victim->buffer_size);

                /* remove the buffer from the array */
                victim->num_buffers--;

                /* If i was the last buffer, then i = num_buffers + 1,
                 * so we compute -sizeof(*buffer) below which overflows. */
                if (victim->num_buffers > i)
                    memmove(buffer, buffer+1,
                            sizeof(*buffer) * victim->num_buffers - i);

                if (victim->current_buffer >= i)
                {
                    victim->current_buffer--;
                }

                reclaimed_size += victim->buffer_size;
                /* cancel the i++ and rescan this entry. */
                i--;
            }
        }
    }

    return reclaimed_size;
}

static BOOL
X11DRV_GLX_VAR_AllocateBuffer(struct X11DRV_GLX_VAR_BufferControl* ctl)
{
    struct X11DRV_GLX_VAR_Buffer* buffer;

    /* What return value makes sense here? */
    if (ctl->num_buffers == MAX_RENAME_DEPTH) return FALSE;

    TRACE("(%p)\n", ctl);
    buffer = &ctl->buffers[ctl->num_buffers];

    buffer->data = X11DRV_GLX_AllocateVARMemory(ctl->buffer_size);
    if (buffer->data == NULL)
    {
        X11DRV_GLX_VAR_ReclaimBuffers(ctl);

        buffer->data = X11DRV_GLX_AllocateVARMemory(ctl->buffer_size);
        if (buffer->data == NULL)
            return FALSE;
    }

    X11DRV_GLX_glGenFencesNV(1, &buffer->fence);

    ctl->num_buffers++;

    return TRUE;
}

static BOOL
X11DRV_GLX_VAR_InitBufferControl(struct X11DRV_GLX_VAR_BufferControl* ctl,
                                 DWORD buffer_size)
{
    TRACE("(%p,%ld)\n", ctl, buffer_size);
    ctl->num_buffers = 0;
    ctl->current_buffer = 0;
    ctl->buffer_size = buffer_size;

    if (X11DRV_GLX_VAR_AllocateBuffer(ctl))
    {
        ctl->prev = &X11DRV_GLX_VAR_BufferControlList;
        ctl->next = X11DRV_GLX_VAR_BufferControlList.next;
        X11DRV_GLX_VAR_BufferControlList.next = ctl;
        ctl->next->prev = ctl;

        return TRUE;
    }
    else
    {
        return FALSE;
    }
}


static void
X11DRV_GLX_VAR_FreeBufferControl(struct X11DRV_GLX_VAR_BufferControl* ctl)
{
    DWORD i;

    TRACE("(%p)\n", ctl);
    for (i=0; i < ctl->num_buffers; i++)
    {
        if (X11DRV_GLX_glIsFenceNV(ctl->buffers[i].fence))
        {
            X11DRV_GLX_glFinishFenceNV(ctl->buffers[i].fence);
            X11DRV_GLX_glDeleteFencesNV(1, &ctl->buffers[i].fence);
        }

        X11DRV_GLX_FreeVARMemory(ctl->buffers[i].data, ctl->buffer_size);
    }

    ctl->prev->next = ctl->next;
    ctl->next->prev = ctl->prev;
}

/* The caller wishes to overwrite the current buffer.
 * Rename or wait as appropriate.
 * Generally can only rename of its a clean buffer (render etc)
 * or if they are ok to DISCARDCONTENTS */
static void X11DRV_GLX_VAR_Overwrite(struct X11DRV_GLX_VAR_BufferControl* ctl,
                                     BOOL can_rename)
{
    DWORD oldest_buffer;

    TRACE("(%p,%d)\n", ctl, can_rename);
    if (!can_rename)
    {
        X11DRV_GLX_VAR_WaitBuffer(ctl, ctl->current_buffer);
        return;
    }

    /* Try the current buffer first.
     * If we can reuse the current buffer, we don't need to do anything.
     */
    if (X11DRV_GLX_VAR_TestBuffer(ctl, ctl->current_buffer))
        return;

    /* Try the oldest buffer. */
    oldest_buffer = (ctl->current_buffer + 1) % ctl->num_buffers;

    if (X11DRV_GLX_VAR_TestBuffer(ctl, oldest_buffer))
    {
        /* rename to the oldest buffer */
        ctl->current_buffer = oldest_buffer;
        return;
    }

    /* Try to allocate a new rename buffer. */
    if (X11DRV_GLX_VAR_AllocateBuffer(ctl))
    {
        /* num_buffers has changed, so this is not the same as oldest_buffer */
        ctl->current_buffer = (ctl->current_buffer + 1) % ctl->num_buffers;
        return;
    }

    /* Just wait on the oldest buffer, and rename it to that.
     * (AllocateBuffer failed, so it hasn't changed num_buffers, so
     * oldest_buffer is still valid.) */
    X11DRV_GLX_VAR_WaitBuffer(ctl, oldest_buffer);
    ctl->current_buffer = oldest_buffer;
    return;
}

static inline LPVOID
X11DRV_GLX_VAR_GetCurrentBuffer(struct X11DRV_GLX_VAR_BufferControl* ctl)
{
    return ctl->buffers[ctl->current_buffer].data;
}

static inline GLuint
X11DRV_GLX_VAR_GetCurrentFence(struct X11DRV_GLX_VAR_BufferControl* ctl)
{
    return ctl->buffers[ctl->current_buffer].fence;
}


/* callbacks (implementations) */

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

    TRACE("()\n");

    if (exts
        && strstr(exts, "GL_NV_vertex_array_range")
        && strstr(exts, "GL_NV_vertex_array_range2")
        && strstr(exts, "GL_NV_fence"))
    {
        /* One-time VertexArrayRange setup. */
        if (!X11DRV_GLX_VertexArray)
        {
            static const GLfloat readfrequency = 0;
            static const GLfloat writefrequency = 0;
            static const GLfloat agp_priority = 0.75;

#ifdef HAVE_GLX_GETPROCADDRESS
            X11DRV_GLX_glFlushVertexArrayRangeNV
                = glXGetProcAddressARB("glFlushVertexArrayRangeNV");
            X11DRV_GLX_glVertexArrayRangeNV = (void*)glXGetProcAddressARB("glVertexArrayRangeNV");
            X11DRV_GLX_glXAllocateMemoryNV = (void*)glXGetProcAddressARB("glXAllocateMemoryNV");
            X11DRV_GLX_glXFreeMemoryNV = (void*)glXGetProcAddressARB("glXFreeMemoryNV");
            X11DRV_GLX_glGenFencesNV = (void*)glXGetProcAddressARB("glGenFencesNV");
            X11DRV_GLX_glDeleteFencesNV = (void*)glXGetProcAddressARB("glDeleteFencesNV");
            X11DRV_GLX_glSetFenceNV = (void*)glXGetProcAddressARB("glSetFenceNV");
            X11DRV_GLX_glTestFenceNV = (void*)glXGetProcAddressARB("glTestFenceNV");
            X11DRV_GLX_glFinishFenceNV = (void*)glXGetProcAddressARB("glFinishFenceNV");
            X11DRV_GLX_glIsFenceNV = (void*)glXGetProcAddressARB("glIsFenceNV");
#endif

            X11DRV_GLX_VAR_Enable = (X11DRV_GLX_glFlushVertexArrayRangeNV
                                     && X11DRV_GLX_glVertexArrayRangeNV
                                     && X11DRV_GLX_glXAllocateMemoryNV
                                     && X11DRV_GLX_glXFreeMemoryNV
                                     && X11DRV_GLX_glGenFencesNV
                                     && X11DRV_GLX_glDeleteFencesNV
                                     && X11DRV_GLX_glSetFenceNV
                                     && X11DRV_GLX_glTestFenceNV
                                     && X11DRV_GLX_glFinishFenceNV
                                     && X11DRV_GLX_glIsFenceNV);
            if (!X11DRV_GLX_VAR_Enable) goto fail;

            X11DRV_GLX_VertexArraySize = agpvertexarrayram * 1024 * 1024;

            X11DRV_GLX_VertexArraySize *= 2;

            do
            {
                X11DRV_GLX_VertexArraySize /= 2;

                X11DRV_GLX_VertexArray
                    = X11DRV_GLX_glXAllocateMemoryNV(X11DRV_GLX_VertexArraySize,
                                                     readfrequency,
                                                     writefrequency,
                                                     agp_priority);
            } while(X11DRV_GLX_VertexArray == NULL
                    && X11DRV_GLX_VertexArraySize > 1);

            TRACE("VAR: allocated %p %ld\n", X11DRV_GLX_VertexArray,
                  X11DRV_GLX_VertexArraySize);

            /* FIXME: change message, may fall back to VBO */
            if (X11DRV_GLX_VertexArray == NULL) {
                X11DRV_GLX_VAR_Enable = FALSE;
                MESSAGE("NV_vertex_array_range extension present, but no AGP memory could be allocated\n");
                MESSAGE("- will use system memory instead, which may reduce performance\n");
                MESSAGE("- try reloading the kernel modules or rebooting to fix this\n");
                goto fail;
            }

#ifdef USE_HEAP
            VirtualAlloc(X11DRV_GLX_VertexArray, X11DRV_GLX_VertexArraySize,
                         MEM_RESERVE|MEM_SYSTEM, PAGE_READWRITE);
            X11DRV_GLX_VertexArrayHeap = RtlCreateHeap(HEAP_SHARED, X11DRV_GLX_VertexArray,
                                                       X11DRV_GLX_VertexArraySize,
                                                       X11DRV_GLX_VertexArraySize,
                                                       NULL, NULL);
            TRACE("heap=%08x\n", X11DRV_GLX_VertexArrayHeap);
#else
            X11DRV_GLX_FreeList = HeapAlloc(GetProcessHeap(), 0, sizeof(struct X11DRV_GLX_FreeBlock));
            X11DRV_GLX_FreeList->ofs = 0;
            X11DRV_GLX_FreeList->size = X11DRV_GLX_VertexArraySize;
            X11DRV_GLX_FreeList->prev = X11DRV_GLX_FreeList;
            X11DRV_GLX_FreeList->next = X11DRV_GLX_FreeList;
#endif

        }

        /* Per-context setup */
        if (X11DRV_GLX_VAR_Enable)
        {
            X11DRV_GLX_glVertexArrayRangeNV(X11DRV_GLX_VertexArraySize,
                                            X11DRV_GLX_VertexArray);
            return &VBUFFER_var;
        }
    }
fail:
    TRACE("failing VAR init\n");
    return NULL;
}

static void VB_VAR_cleanup()
{
    if (!X11DRV_GLX_VAR_Enable || (X11DRV_GLX_VertexArray == NULL))
    {
        ERR("shouldn't be called\n");
        return;
    }

    TRACE("()\n");

#ifdef USE_HEAP
    HeapDestroy(X11DRV_GLX_VertexArrayHeap);
    VirtualFree(X11DRV_GLX_VertexArray, 0, MEM_RELEASE);
#else
    while (X11DRV_GLX_FreeList) {
        struct X11DRV_GLX_FreeBlock* cur = X11DRV_GLX_FreeList;
        cur->prev->next = cur->next;
        cur->next->prev = cur->prev;
        if (cur->next == cur) X11DRV_GLX_FreeList = NULL;
        else X11DRV_GLX_FreeList = cur->next;
        TRACE("freeing free block: ofs=%ld, size=%ld\n", cur->ofs, cur->size);
        HeapFree(GetProcessHeap(), 0, cur);
    }
#endif
    X11DRV_GLX_glXFreeMemoryNV(X11DRV_GLX_VertexArray);
    X11DRV_GLX_VertexArray = NULL;

}

static GLX_D3D_VBUFFER_priv *VB_VAR_allocate(DWORD size, DWORD dwCaps,
                                             DWORD dwCaps2)
{
    VBUFFER_PRIV_var *vb;

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

    if (!X11DRV_GLX_VAR_Enable || (X11DRV_GLX_VertexArray == NULL))
    {
        ERR("shouldn't be called\n");
        return NULL;
    }

    vb = HeapAlloc(GetProcessHeap(), 0, sizeof(VBUFFER_PRIV_var));
    if (!vb)
        return NULL;

    if (!X11DRV_GLX_VAR_InitBufferControl(&(vb->buf_control), size))
    {
        HeapFree(GetProcessHeap(), 0, vb);
        return NULL;
    }

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

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

    /* Not sure if this locking is really necessary, but will 
       revisit this later if it becomes an issue.
     * ENTER_GL();
     * glXWaitX(); */
    X11DRV_GLX_VAR_FreeBufferControl(&(vb->buf_control));
    /* glXWaitGL();
     * LEAVE_GL(); */

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

static void *VB_VAR_lock(GLX_D3D_VBUFFER_priv *vb, DWORD dwFlags)
{
    TRACE("(%p, %08lx)\n", vb, dwFlags);

    if (!X11DRV_GLX_VAR_Enable || (X11DRV_GLX_VertexArray == NULL))
    {
        ERR("shouldn't be called\n");
        return NULL;
    }

    if (!(dwFlags & (DDLOCK_READONLY|DDLOCK_NOOVERWRITE)))
    {
        /* Not sure if this locking is really necessary and 
         * furthermore the glXWait commands cause a 40 percent drop in
         * speed when this path is used (they eliminate the whole advantage 
         * of using VAR because they cause this to be syncronous).
         * ENTER_GL();
         * glXWaitX(); */
        X11DRV_GLX_VAR_Overwrite(&(vb->buf_control),
                                 dwFlags & DDLOCK_DISCARDCONTENTS);
        /* glXWaitGL();
         * LEAVE_GL(); */
    }
    return X11DRV_GLX_VAR_GetCurrentBuffer(&(vb->buf_control));
}

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

    if (!X11DRV_GLX_VAR_Enable || (X11DRV_GLX_VertexArray == NULL))
    {
        ERR("shouldn't be called\n");
        return;
    }
}

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

    if (!X11DRV_GLX_VAR_Enable || (X11DRV_GLX_VertexArray == NULL))
    {
        ERR("shouldn't be called\n");
        return NULL;
    }

    ret = X11DRV_GLX_VAR_GetCurrentBuffer(&(vb->buf_control));

    TRACE("Returning %p\n", ret);
    return ret;
}

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

    glEnableClientState(GL_VERTEX_ARRAY_RANGE_WITHOUT_FLUSH_NV);
}

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

#if 0
        GLboolean valid;
        glGetBooleanv(GL_VERTEX_ARRAY_RANGE_VALID_NV, &valid);
        if (!valid)
        {
            void* p;
            GLint x;

            ERR("VAR not consistent\n");

            if (!glXIsDirect(glXGetCurrentDisplay(), glXGetCurrentContext()))
                ERR("context not direct\n");

            if (!strstr(glGetString(GL_EXTENSIONS),
                        "GL_NV_vertex_array_range2"))
                ERR("VAR2 extension missing\n");

            if (!strstr(glGetString(GL_EXTENSIONS), "GL_NV_fence"))
                ERR("fence extension missing\n");

            if (!glIsEnabled(GL_VERTEX_ARRAY_RANGE_NV))
                ERR("VAR not enabled\n");

            glGetPointerv(GL_VERTEX_ARRAY_RANGE_POINTER_NV, &p);
            if ((DWORD)p % 32 != 0) ERR("pointer not aligned\n");

            glGetIntegerv(GL_VERTEX_ARRAY_RANGE_LENGTH_NV, &x);

            ERR("%p %d\n", p, x);

            if (!glIsEnabled(GL_VERTEX_ARRAY))
                ERR("GL_VERTEX_ARRAY disabled\n");
            else
            {
                glGetIntegerv(GL_VERTEX_ARRAY_STRIDE, &x);
                if (x >= 256) ERR("vertex stride too large\n");
                if (x % 4 != 0) ERR("vertex stride unaligned\n");

                glGetPointerv(GL_VERTEX_ARRAY_POINTER, &p);
                if ((DWORD)p % 4 != 0) ERR("vertex pointer unaligned\n");

                glGetIntegerv(GL_VERTEX_ARRAY_SIZE, &x);
                if (x < 2 || x > 4) ERR("vertex array size wrong\n");
            }

            if (glIsEnabled(GL_NORMAL_ARRAY))
            {
                glGetIntegerv(GL_NORMAL_ARRAY_STRIDE, &x);
                if (x >= 256) ERR("normal stride too large\n");
                if (x % 4 != 0) ERR("normal stride unaligned\n");

                glGetPointerv(GL_NORMAL_ARRAY_POINTER, &p);
                if ((DWORD)p % 4 != 0) ERR("normal pointer unaligned\n");
            }

            if (glIsEnabled(GL_COLOR_ARRAY))
            {
                glGetIntegerv(GL_COLOR_ARRAY_TYPE, &x);
                if (x != GL_UNSIGNED_BYTE) ERR("color array type wrong\n");

                glGetIntegerv(GL_COLOR_ARRAY_STRIDE, &x);
                if (x >= 256) ERR("color stride too large\n");
                if (x % 4 != 0) ERR("color stride unaligned\n");

                glGetPointerv(GL_COLOR_ARRAY_POINTER, &p);
                if ((DWORD)p % 4 != 0) ERR("color pointer unaligned\n");

                glGetIntegerv(GL_COLOR_ARRAY_SIZE, &x);
                if (x != 4) ERR("color array size wrong\n");
            }

            if (glIsEnabled(GL_SECONDARY_COLOR_ARRAY_EXT))
            {
                glGetIntegerv(GL_SECONDARY_COLOR_ARRAY_TYPE_EXT, &x);
                if (x != GL_UNSIGNED_BYTE) ERR("2color array type wrong\n");

                glGetIntegerv(GL_SECONDARY_COLOR_ARRAY_STRIDE_EXT, &x);
                if (x >= 256) ERR("2color stride too large\n");
                if (x % 4 != 0) ERR("2color stride unaligned\n");

                glGetPointerv(GL_SECONDARY_COLOR_ARRAY_POINTER_EXT, &p);
                if ((DWORD)p % 4 != 0) ERR("2color pointer unaligned\n");

                glGetIntegerv(GL_SECONDARY_COLOR_ARRAY_SIZE_EXT, &x);
                if (x != 4) ERR("2color array size wrong\n");
            }

            if (glIsEnabled(GL_TEXTURE_COORD_ARRAY))
            {
                glGetIntegerv(GL_TEXTURE_COORD_ARRAY_TYPE, &x);
                if (x != GL_FLOAT) ERR("tex array type wrong\n");

                glGetIntegerv(GL_TEXTURE_COORD_ARRAY_STRIDE, &x);
                if (x >= 256) ERR("tex stride too large\n");
                if (x % 4 != 0) ERR("tex stride unaligned\n");

                glGetPointerv(GL_TEXTURE_COORD_ARRAY_POINTER, &p);
                if ((DWORD)p % 4 != 0) ERR("tex pointer unaligned\n");

                glGetIntegerv(GL_TEXTURE_COORD_ARRAY_SIZE, &x);
                if (x < 1 || x > 4) ERR("tex array size wrong\n");
            }

            if (glIsEnabled(GL_EDGE_FLAG_ARRAY))
                ERR("edge flag array enabled\n");

#if 0
            if (glIsEnabled(GL_VERTEX_WEIGHT_ARRAY_NV))
                ERR("vertex weight array enabled\n");
#endif

            if (glIsEnabled(GL_FOG_COORDINATE_ARRAY_EXT))
                ERR("fog coordinary array enabled\n");

            glGetIntegerv(GL_LIGHT_MODEL_TWO_SIDE, &x);
            if (x) ERR("two-sided lighting is on\n");
        }
#endif


    X11DRV_GLX_glSetFenceNV(X11DRV_GLX_VAR_GetCurrentFence(&vb->buf_control),
                            GL_ALL_COMPLETED_NV);

    /* It would be nice to only SetFence if we actually rendered
    * from VAR memory. */
    glDisableClientState(GL_VERTEX_ARRAY_RANGE_WITHOUT_FLUSH_NV);

}

static GLX_D3D_VBUFFER_callbacks VBUFFER_var =
{
    VB_VAR_cleanup,
    VB_VAR_allocate,
    VB_VAR_free,
    VB_VAR_lock,
    VB_VAR_unlock,
    VB_VAR_renderptr,
    VB_VAR_prerender,
    VB_VAR_postrender
};

