/*
 * Direct3D GLX execute buffer implementation
 *
 * Copyright 2001 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);

/* 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

#define D3DFVF_TEXCOUNT(x) (((x) & D3DFVF_TEXCOUNT_MASK) >> D3DFVF_TEXCOUNT_SHIFT)
#define D3DFVF_TEXCOORDSIZE(fvf,idx) ( ((((fvf)>>((idx)*2 + 16)) + 1) & 0x0003) + 1 )

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;
static HANDLE X11DRV_GLX_SystemBufferHeap;

#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

void X11DRV_GLX_EB_Init(void)
{
    X11DRV_GLX_SystemBufferHeap = HeapCreate(HEAP_GROWABLE, 0, 0);
}

void X11DRV_GLX_EB_Cleanup(void)
{
    if (X11DRV_GLX_VAR_Enable && (X11DRV_GLX_VertexArray != NULL))
    {
#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;
    }
    HeapDestroy(X11DRV_GLX_SystemBufferHeap);
    X11DRV_GLX_SystemBufferHeap = 0;
}

void X11DRV_GLX_EB_Context_Init(void)
{
    const char *exts = glGetString(GL_EXTENSIONS);

    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) return;

	    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("allocated %p %ld\n", X11DRV_GLX_VertexArray,
		  X11DRV_GLX_VertexArraySize);

	    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");
		return;
	    }

#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);
	}
    }
}

static inline DWORD X11DRV_GLX_PositionSize(DWORD fvf)
{
    static const DWORD position_size[] = { 0, 3, 4, 4, 5, 6, 7, 8 };
    return position_size[(fvf & 0xe) / 2];
}

static inline DWORD X11DRV_GLX_BlendWeights(DWORD fvf)
{
    return max((fvf & 0xe) / 2, 2) - 2;
}

/** Generic VAR allocation */

#ifdef USE_HEAP

static LPVOID X11DRV_GLX_AllocateVARMemory(DWORD size, DWORD align)
{
    LPVOID ptr = NULL;

    if (align) return NULL; /* we can't align */

    if (X11DRV_GLX_VAR_Enable && X11DRV_GLX_VertexArray)
    {
      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( ptr ) HeapFree(X11DRV_GLX_VertexArrayHeap, 0, ptr);
    TRACE("(%p)\n", ptr);
}

/* #define X11DRV_GLX_AllocateVARMemory(size,align) ((align) ? NULL : HeapAlloc(X11DRV_GLX_VertexArrayHeap, 0, size)) */
/* #define X11DRV_GLX_FreeVARMemory(ptr,size) HeapFree(X11DRV_GLX_VertexArrayHeap, 0, ptr) */

#else

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

    if (!X11DRV_GLX_VAR_Enable) return NULL;
    if (!X11DRV_GLX_VertexArray) 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");
	return NULL;
    }

    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");
	return NULL;
    }

    /* 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);
    return (LPBYTE)X11DRV_GLX_VertexArray + ofs;
}

static void X11DRV_GLX_FreeVARMemory(LPVOID ptr, DWORD size)
{
    struct X11DRV_GLX_FreeBlock *avail;
    struct X11DRV_GLX_FreeBlock *newblock;
    DWORD ofs;

    if (ptr == NULL) return;
    if (!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;
    }
}

#if 0
struct X11DRV_GLX_VARBlock
{
    BOOL free;
    DWORD size;
    struct X11DRV_GLX_VARBlock* prev;
    struct X11DRV_GLX_VARBlock* next;
};

static struct X11DRV_GLX_VARBlock *X11DRV_GLX_var_head = NULL;

static LPVOID X11DRV_GLX_AllocateVARMemory(DWORD size, DWORD align)
{
    struct X11DRV_GLX_VARBlock* avail;

    if (!X11DRV_GLX_VAR_Enable) return NULL;
    if (!X11DRV_GLX_VertexArray) return NULL;

    if (align) return NULL; /* we can't align */

    /* one-time init */
    if (X11DRV_GLX_var_head == NULL)
    {
	X11DRV_GLX_var_head = X11DRV_GLX_VertexArray;
	X11DRV_GLX_var_head->free = TRUE;
	X11DRV_GLX_var_head->size = X11DRV_GLX_VertexArraySize;
	X11DRV_GLX_var_head->prev = X11DRV_GLX_var_head;
	X11DRV_GLX_var_head->next = X11DRV_GLX_var_head;
    }

    size = (size + 31) &~ 31;

    /* find free block */

    avail = X11DRV_GLX_var_head;

    do
    {
	if (avail->free && avail->size >= size + 32) break;
	avail = avail->next;
    }
    while (avail != X11DRV_GLX_var_head);

    if (!avail->free)
    {
	WARN("oh no! out of memory\n");
	return NULL;
    }

    /* split block */
    if (avail->size >= size + 96)
    {
	struct X11DRV_GLX_VARBlock *newblock;

	newblock = (struct X11DRV_GLX_VARBlock *)((char *)avail + 32 + size);
	newblock->free = TRUE;

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

	newblock->size = avail->size - size - 32;
	avail->size = size + 32;
    }

    avail->free = FALSE;

    return (LPBYTE)avail + 32;
}

static void
X11DRV_GLX_CombineFreeVARBlocks(struct X11DRV_GLX_VARBlock* first,
				struct X11DRV_GLX_VARBlock* second)
{
    /* When there's only one block, we might try to combine it with itself.
     * The caller is responsible for checking that it isn't trying to combine
     * the last block with the first block, since it needs to know about that
     * case anyway. */
    if (first != second)
    {
	first->next = second->next;
	second->next->prev = first;

	first->size += second->size;
    }
}

static void X11DRV_GLX_FreeVARMemory(LPVOID ptr, DWORD size)
{
    struct X11DRV_GLX_VARBlock *current_block
	= (struct X11DRV_GLX_VARBlock*)((LPBYTE)ptr - 32);

    if (ptr == NULL) return;

    current_block->free = TRUE;

    if (current_block != X11DRV_GLX_var_head && current_block->prev->free)
    {
	X11DRV_GLX_CombineFreeVARBlocks(current_block->prev, current_block);
	current_block = current_block->prev;
    }

    if (current_block->next != X11DRV_GLX_var_head
	&& current_block->next->free)
    {
	X11DRV_GLX_CombineFreeVARBlocks(current_block, current_block->next);
    }
}
#endif

#endif /* USE_HEAP */

/** VAR rename buffers */

#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];
};

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.)
 */
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, 0);
    if (buffer->data == NULL)
    {
	X11DRV_GLX_VAR_ReclaimBuffers(ctl);

	buffer->data = X11DRV_GLX_AllocateVARMemory(ctl->buffer_size, 0);
	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. */
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;
}


/** Execute buffer implementation */

struct X11DRV_GLX_EB_Info
{
    LPVOID lock_buffer;
    LPVOID render_buffer;

    BOOL var;
    BOOL lock_buffer_is_var;
    BOOL render_buffer_is_var;

    struct X11DRV_GLX_VAR_BufferControl var_buffers;
    LPVOID system_buffer;

    DWORD num_vertices;
    DWORD size;

    DWORD original_fvf;
    DWORD converted_fvf;
    DWORD original_fvf_size;
    DWORD converted_fvf_size;

    GLDRAWPRIMITIVESTRIDEDDATA lock_strided_data, render_strided_data;

    /* These always apply to the current buffer. */
    DWORD overwritten;
    BYTE uptodate[1];
};

/* This is common to setting up from either a D3D stride, or from
 * a pointer and FVF. */
static void
X11DRV_GLX_EB_SetupStrideBlendWeights(DWORD fvf,
				      LPGLDRAWPRIMITIVESTRIDEDDATA gl)
{
    int num_weights = X11DRV_GLX_BlendWeights(fvf);

    /* Blend weights immediately follow the position data.
     * There are a maximum of 4: XYZB5 does not actually specify 5 blend
     * weights since it must be used with D3DFVF_LASTBETA_UBYTE4 flag,
     * meaning that the last float is actually 4 matrix palette indices. */
    gl->blend_weights.lpvData = (LPFLOAT)gl->position.lpvData + 3;
    gl->blend_weights.dwStride = gl->position.dwStride;

    /* Setup the last blend weight as the matrix index pointer.
     * (In case that's what it actually is.) */
    gl->matrix_indices.lpvData
	= (LPFLOAT)gl->position.lpvData + 3 + num_weights - 1;
}

/* Apparently there is a super-secret fog value hidden in specular alpha. */
static void
X11DRV_GLX_EB_SetupStrideFogCoordinate(DWORD fvf,
				       LPGLDRAWPRIMITIVESTRIDEDDATA gl)
{
    if (!(fvf & D3DFVF_SPECULAR)) {
	/* nothing to do */
    }
    else if (fvf & D3DFVF_GLCOLORORDER) {
	gl->fog_coordinate.lpvData = ((LPD3DCOLOR)gl->color[1].lpvData) + 1;
	gl->fog_coordinate.dwStride = gl->color[1].dwStride;
    } else {
	/* FIXME: not endian-safe */
	gl->fog_coordinate.lpvData = ((LPBYTE)gl->color[1].lpvData) + 3;
	gl->fog_coordinate.dwStride = gl->color[1].dwStride;
    }
}

static void
X11DRV_GLX_EB_ConvertInterleavedToGLStride(DWORD fvf, LPCVOID lpVertices,
					   LPGLDRAWPRIMITIVESTRIDEDDATA gl)
{
    LPBYTE p = (LPBYTE)lpVertices;
    DWORD stride;
    int tex_count = D3DFVF_TEXCOUNT(fvf);
    int i;

    gl->position.lpvData = p;
    stride = X11DRV_GLX_PositionSize(fvf) * sizeof(D3DVALUE);

    if (fvf & D3DFVF_RESERVED1)
	stride += sizeof(D3DVALUE);

    if (fvf & D3DFVF_NORMAL)
    {
	gl->normal.lpvData = p + stride;
	stride += 3 * sizeof(D3DVALUE);
    }

    if (fvf & D3DFVF_DIFFUSE)
    {
	gl->color[0].lpvData = p + stride;
	stride += sizeof(D3DCOLOR);
    }

    if (fvf & D3DFVF_SPECULAR)
    {
	gl->color[1].lpvData = p + stride;
	stride += sizeof(D3DCOLOR);
	if (fvf & D3DFVF_GLCOLORORDER)
	    /* room for converted fog coordinate */
	    stride += sizeof(float);
    }

    if (fvf & D3DFVF_TEXCOUNT_MASK)
    {
	int num_coords; 
	for (i = 0; i < tex_count; i++) {
	    num_coords = D3DFVF_TEXCOORDSIZE(fvf,i);
	    gl->textureCoords[i].lpvData = p + stride;
	    stride += num_coords * sizeof(D3DVALUE);
	}
	/* initialize unused pointers/strides */
	for (i = tex_count; i < D3DDP_MAXTEXCOORD; i++) {
	  gl->textureCoords[i].lpvData = NULL;
	  gl->textureCoords[i].dwStride = 0;
	}
    }

    gl->position.dwStride = stride;
    gl->normal.dwStride = stride;
    gl->color[0].dwStride = stride;
    gl->color[1].dwStride = stride;
    for (i=0; i < tex_count; i++)
	gl->textureCoords[i].dwStride = stride;

    X11DRV_GLX_EB_SetupStrideBlendWeights(fvf, gl);
    X11DRV_GLX_EB_SetupStrideFogCoordinate(fvf, gl);
}

static BOOL X11DRV_GLX_DoesFVFNeedConversion(DWORD fvf)
{
    return ((fvf & D3DFVF_POSITION_MASK) == D3DFVF_XYZRHW
	    || (fvf & (D3DFVF_DIFFUSE|D3DFVF_SPECULAR)));
}

static DWORD X11DRV_GLX_ConvertedFVF(DWORD fvf)
{
    if ((fvf & D3DFVF_POSITION_MASK) == D3DFVF_XYZRHW)
	fvf |= D3DFVF_XYZGLW;

    if (fvf & (D3DFVF_DIFFUSE|D3DFVF_SPECULAR))
	fvf |= D3DFVF_GLCOLORORDER;

    return fvf;
}

static DWORD X11DRV_GLX_FVFSize(DWORD fvf)
{
    DWORD size;
    int tex_count = D3DFVF_TEXCOUNT(fvf);
    int num_coords;
    int i;

    size = X11DRV_GLX_PositionSize(fvf) * sizeof(DWORD);

    if (fvf & D3DFVF_RESERVED1)
	size += sizeof(DWORD);

    if (fvf & D3DFVF_NORMAL)
	size += 3 * sizeof(D3DVALUE);

    if (fvf & D3DFVF_DIFFUSE)
	size += sizeof(D3DCOLOR);

    if (fvf & D3DFVF_SPECULAR) {
	size += sizeof(D3DCOLOR);
	if (fvf & D3DFVF_GLCOLORORDER)
	    size += sizeof(float);
    }

    for( i=0; i<tex_count; i++ ) {
      num_coords = D3DFVF_TEXCOORDSIZE(fvf,i);
      size += num_coords * sizeof(D3DVALUE);
    }
    
    return size;
}

static inline DWORD rot16(DWORD x)
{
    return (x >> 16) | (x << 16);
}

static inline void
X11DRV_GLX_ConvertVertex(LPCVOID _in, LPVOID _out, DWORD fvf)
{
    const DWORD* in = _in;
    LPDWORD out = _out;

    switch (fvf & D3DFVF_POSITION_MASK)
    {
    case D3DFVF_XYZRHW:
    {
	float w;
	const FLOAT* inf = (const FLOAT*)in;
	LPFLOAT outf = (LPFLOAT)out;

	/* Reportedly Microsoft uses 2^20 in their ProcessVertices impl. */
	if (inf[3] == 0.0) w = 1048576.0;
	else w = 1.0 / inf[3];

	outf[0] = inf[0] * w;
	outf[1] = inf[1] * w;
	outf[2] = inf[2] * w;
	outf[3] = w;

	in += 4;
	out += 4;
	}
    break;

    case D3DFVF_XYZB5:
	*out++ = *in++;
    case D3DFVF_XYZB4:
	*out++ = *in++;
    case D3DFVF_XYZB3:
	*out++ = *in++;
    case D3DFVF_XYZB2:
	*out++ = *in++;
    case D3DFVF_XYZB1:
	*out++ = *in++;
    case D3DFVF_XYZ:
	*out++ = *in++;
	*out++ = *in++;
	*out++ = *in++;
	break;
    }

    if (fvf & D3DFVF_NORMAL)
    {
	*out++ = *in++;
	*out++ = *in++;
	*out++ = *in++;
    }

    if (fvf & D3DFVF_RESERVED1)
    {
	out++;
	in++;
    }

    if (fvf & D3DFVF_DIFFUSE)
    {
	DWORD tmp = *in++;
	*out++ = (tmp & 0xFF00FF00) | (rot16(tmp) & 0x00FF00FF);
    }

    if (fvf & D3DFVF_SPECULAR)
    {
	DWORD tmp = *in++;
	*out++ = (tmp & 0xFF00FF00) | (rot16(tmp) & 0x00FF00FF);
	*((float*)out)++
#ifdef COMPUTE_FOG_COORDINATE
	  = RGBA_GETALPHA(tmp) / 255.0
#endif
	;
    }

#define TEXCOORDSIZE_SWITCH(x) switch( D3DFVF_TEXCOORDSIZE(fvf,(x)) ) { \
	  case 4: *out++ = *in++; \
	  case 3: *out++ = *in++; \
	  case 2: *out++ = *in++; \
	  case 1: *out++ = *in++; \
	  case 0: break; \
	}
 
    switch (D3DFVF_TEXCOUNT(fvf))
    {
    case 8:
      TEXCOORDSIZE_SWITCH(7)
    case 7:
      TEXCOORDSIZE_SWITCH(6)
    case 6:
      TEXCOORDSIZE_SWITCH(5)
    case 5:
      TEXCOORDSIZE_SWITCH(4)
    case 4:
      TEXCOORDSIZE_SWITCH(3)
    case 3:
      TEXCOORDSIZE_SWITCH(2)
    case 2:
      TEXCOORDSIZE_SWITCH(1)
    case 1:
      TEXCOORDSIZE_SWITCH(0)
    case 0:
	break;
    }
#undef TEXCOORDSIZE_SWITCH
}

static inline BOOL X11DRV_GLX_EB_IsUptodate(const BYTE* uptodate, DWORD index)
{
    return uptodate[index / 8] & (1 << (index % 8));
}

static inline void X11DRV_GLX_EB_MarkUptodate(LPBYTE uptodate, DWORD index)
{
    uptodate[index / 8] |= (1 << (index % 8));
}

static void X11DRV_GLX_EB_ResetUptodate(struct X11DRV_GLX_EB_Info* info)
{
    memset(info->uptodate, 0, (info->num_vertices + 7) / 8);
}

static void X11DRV_GLX_Convert(struct X11DRV_GLX_EB_Info* info,
			       DWORD offset, DWORD num,
			       const WORD* indices, DWORD nindices,
			       DWORD indexbase)
{
    const BYTE* in;
    LPBYTE out;

    DWORD fvf = info->original_fvf;
    DWORD in_size = info->original_fvf_size;
    DWORD out_size = info->converted_fvf_size;

    LPBYTE uptodate = info->uptodate;

    if (indices != NULL)
    {
	in = (LPBYTE)info->lock_buffer;
	out = (LPBYTE)info->render_buffer;

	TRACE("indices=%ld, in=%p, out=%p\n", nindices, in, out);
	while (nindices--)
	{
	    DWORD index = indexbase + *indices++;

	    if (!X11DRV_GLX_EB_IsUptodate(uptodate, index))
	    {
		X11DRV_GLX_EB_MarkUptodate(uptodate, index);
		X11DRV_GLX_ConvertVertex(in + index * in_size,
					 out + index * out_size,
					 fvf);
	    }
	}
    }
    else
    {
	in = (LPBYTE)info->lock_buffer + offset * in_size;
	out = (LPBYTE)info->render_buffer + offset * out_size;

	while (num--)
	{
	    if (!X11DRV_GLX_EB_IsUptodate(uptodate, offset))
	    {
		X11DRV_GLX_EB_MarkUptodate(uptodate, offset);
		X11DRV_GLX_ConvertVertex(in, out, fvf);
	    }

	    in += in_size;
	    out += out_size;
	    offset++;
	}
    }
}

static DWORD WINAPI X11DRV_GLX_CanCreateEB(LPDDHAL_CANCREATESURFACEDATA data)
{
    data->ddRVal = DD_OK;
    return DDHAL_DRIVER_HANDLED;
}

/* not necessarily called with locks held */
static DWORD WINAPI X11DRV_GLX_CreateEB(LPDDHAL_CREATESURFACEDATA data)
{
    LPDDRAWI_DDRAWSURFACE_LCL lcl = *data->lplpSList;
    LPDDRAWI_DDRAWSURFACE_GBL gbl = lcl->lpGbl;
    LPDDRAWI_DDRAWSURFACE_MORE more = lcl->lpSurfMore;

    DWORD size = gbl->u4.dwLinearSize;
    DWORD fvf = more->dwFVF;
    DWORD cfvf, fsize, cfsize, csize;

    struct X11DRV_GLX_EB_Info* info;
    DWORD num_vertices;
    DWORD uptodate_bytes;

    TRACE("\n");

    cfvf = X11DRV_GLX_ConvertedFVF(fvf);
    fsize = X11DRV_GLX_FVFSize(fvf);
    cfsize = X11DRV_GLX_FVFSize(cfvf);

    num_vertices = fvf ? (size / fsize) : 0;
    uptodate_bytes = (num_vertices + 7) / 8;

    csize = fvf ? (num_vertices * cfsize) : size;

    info = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
		     sizeof(struct X11DRV_GLX_EB_Info) + uptodate_bytes);
    if (info == NULL)
    {
	data->ddRVal = E_OUTOFMEMORY;
	return DDHAL_DRIVER_HANDLED;
    }

    info->original_fvf = fvf;
    info->converted_fvf = cfvf;

    info->original_fvf_size = fsize;
    info->converted_fvf_size = cfsize;

    info->num_vertices = num_vertices;
    info->size = size;

    /* This hack makes it possible to safely call FreeBufferControl even
     * if InitBufferControl fails (or isn't called at all). */
    info->var_buffers.prev = &info->var_buffers;
    info->var_buffers.next = &info->var_buffers;

    /* Don't waste VAR memory on command buffers and implicit vertex buffers.*/
    if (more->ddsCapsEx.dwCaps2 & DDSCAPS2_VERTEXBUFFER)
	info->var = X11DRV_GLX_VAR_InitBufferControl(&info->var_buffers, csize);

    if (!info->var
	|| X11DRV_GLX_DoesFVFNeedConversion(fvf)
	|| (lcl->ddsCaps.dwCaps & DDSCAPS_SYSTEMMEMORY)
	|| !(lcl->ddsCaps.dwCaps & DDSCAPS_WRITEONLY))
    {
	info->system_buffer = HeapAlloc(X11DRV_GLX_SystemBufferHeap, HEAP_ZERO_MEMORY,
					size + DRIVER_SAFETY);

	if (info->system_buffer == NULL)
	{
	    HeapFree(GetProcessHeap(), 0, info);

	    data->ddRVal = D3DERR_VBUF_CREATE_FAILED;
	    return DDHAL_DRIVER_HANDLED;
	}
    }

    if (info->system_buffer)
	info->lock_buffer = info->system_buffer;
    else
    {
	info->lock_buffer = X11DRV_GLX_VAR_GetCurrentBuffer(&info->var_buffers);
	info->lock_buffer_is_var = TRUE;
    }

    if (info->var)
    {
	info->render_buffer = X11DRV_GLX_VAR_GetCurrentBuffer(&info->var_buffers);
	info->render_buffer_is_var = TRUE;
    }
    else
	info->render_buffer = info->system_buffer;

    info->overwritten = FALSE;
    if( !info->var ) {
	info->converted_fvf = info->original_fvf;
	info->converted_fvf_size = info->original_fvf_size;
    }

    TRACE("%lx %lx\n", info->original_fvf, info->converted_fvf);

    X11DRV_GLX_EB_ConvertInterleavedToGLStride(info->original_fvf, NULL,
					       &info->lock_strided_data);

    X11DRV_GLX_EB_ConvertInterleavedToGLStride(info->converted_fvf, NULL,
					       &info->render_strided_data);

    gbl->dwReserved1 = (DWORD)info;

    gbl->fpVidMem = (FLATPTR)info->lock_buffer;
    data->ddRVal = DD_OK;

    return DDHAL_DRIVER_HANDLED;
}

/* not necessarily called with locks held */
static DWORD WINAPI X11DRV_GLX_DestroyEB(LPDDHAL_DESTROYSURFACEDATA data)
{
    LPDDRAWI_DDRAWSURFACE_GBL gbl = data->lpDDSurface->lpGbl;
    struct X11DRV_GLX_EB_Info* info
	= (struct X11DRV_GLX_EB_Info*)gbl->dwReserved1;

    TRACE("\n");

    /* 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(&info->var_buffers);
    /* glXWaitGL();
     * LEAVE_GL(); */

    if (info->system_buffer)
    {
	HeapFree(X11DRV_GLX_SystemBufferHeap, 0, info->system_buffer);
    }

    HeapFree(GetProcessHeap(), 0, info);
    gbl->dwReserved1 = 0;

    gbl->fpVidMem = (FLATPTR)NULL;
    data->ddRVal = DD_OK;
    return DDHAL_DRIVER_HANDLED;
}

static void X11DRV_GLX_EB_Rename(struct X11DRV_GLX_EB_Info* info,
				 BOOL can_rename)
{
    LPVOID new_buffer;

    X11DRV_GLX_VAR_Overwrite(&info->var_buffers, can_rename);

    new_buffer = X11DRV_GLX_VAR_GetCurrentBuffer(&info->var_buffers);

    if (info->lock_buffer_is_var)
	info->lock_buffer = new_buffer;

    if (info->render_buffer_is_var)
	info->render_buffer = new_buffer;
}

static void X11DRV_GLX_EB_OverwriteLock(struct X11DRV_GLX_EB_Info* info,
					BOOL discard_contents)
{
    info->overwritten = TRUE;
    X11DRV_GLX_EB_ResetUptodate(info);

    if (info->lock_buffer_is_var)
	X11DRV_GLX_EB_Rename(info, discard_contents);
}

static void X11DRV_GLX_EB_OverwriteRender(struct X11DRV_GLX_EB_Info* info)
{
    if (info->render_buffer_is_var && !info->lock_buffer_is_var)
	X11DRV_GLX_EB_Rename(info, TRUE);
}

/* not necessarily called with locks held */
static DWORD WINAPI X11DRV_GLX_LockEB(LPDDHAL_LOCKDATA data)
{
    struct X11DRV_GLX_EB_Info* info
	= (struct X11DRV_GLX_EB_Info*)data->lpDDSurface->lpGbl->dwReserved1;

    TRACE("\n");

    if (!(data->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_EB_OverwriteLock(info,
				    data->dwFlags & DDLOCK_DISCARDCONTENTS);
	/* glXWaitGL();
	 * LEAVE_GL(); */
    }

    data->lpSurfData = info->lock_buffer;

    data->ddRVal = DD_OK;
    return DDHAL_DRIVER_HANDLED;
}

/* not necessarily called with locks held */
static DWORD WINAPI X11DRV_GLX_UnlockEB(LPDDHAL_UNLOCKDATA data)
{
    TRACE("\n");
    data->ddRVal = DD_OK;
    return DDHAL_DRIVER_HANDLED;
}

static DWORD X11DRV_GLX_D3dVbGetConvertedFVF(LPDDRAWI_DDRAWSURFACE_LCL vb)
{
    struct X11DRV_GLX_EB_Info* info
	= (struct X11DRV_GLX_EB_Info*)vb->lpGbl->dwReserved1;

    if (!info) return vb->lpSurfMore->dwFVF;
    return info->converted_fvf;
}

static inline void
X11DRV_GLX_EB_SSBAOOne(D3DDP_PTRSTRIDE* in, D3DDP_PTRSTRIDE* out, LPVOID base,
		       DWORD ofs)
{
    out->lpvData = (LPBYTE)base + (DWORD)in->lpvData + ofs * in->dwStride;
    out->dwStride = in->dwStride;
}

/* If a GLDRAWPRIMITIVESTRIDEDDATA struct was generated with a NULL pointer,
 * then each stride pointer is actually just an offset relative to a vertex
 * pointer.
 *
 * This adds in the vertex pointer to create a real stride structure, then
 * shifts it by offset vertices.
 */
static void
X11DRV_GLX_EB_SetStridedBaseAndOffset(LPGLDRAWPRIMITIVESTRIDEDDATA in,
				      LPGLDRAWPRIMITIVESTRIDEDDATA out,
				      LPVOID base, DWORD offset)
{
    int i;

    if (!out) return;

    X11DRV_GLX_EB_SSBAOOne(&in->position, &out->position, base, offset);
    X11DRV_GLX_EB_SSBAOOne(&in->normal, &out->normal, base, offset);
    X11DRV_GLX_EB_SSBAOOne(&in->color[0], &out->color[0], base, offset);
    X11DRV_GLX_EB_SSBAOOne(&in->color[1], &out->color[1], base, offset);

    for (i = 0; i < D3DDP_MAXTEXCOORD; i++)
	X11DRV_GLX_EB_SSBAOOne(&in->textureCoords[i],
			       &out->textureCoords[i], base, offset);

    X11DRV_GLX_EB_SSBAOOne(&in->blend_weights, &out->blend_weights, base,
			   offset);
    X11DRV_GLX_EB_SSBAOOne(&in->matrix_indices, &out->matrix_indices, base,
			   offset);
    X11DRV_GLX_EB_SSBAOOne(&in->fog_coordinate, &out->fog_coordinate, base,
			   offset);
}

static LPVOID X11DRV_GLX_D3dVbPreRender(LPDDRAWI_DDRAWSURFACE_LCL vb,
					LPGLDRAWPRIMITIVESTRIDEDDATA data,
					BOOL convert, DWORD offset, DWORD num,
					const WORD* indices, DWORD nindices,
					DWORD indexbase)
{
    struct X11DRV_GLX_EB_Info* info
	= (struct X11DRV_GLX_EB_Info*)vb->lpGbl->dwReserved1;
    BOOL var;
    LPVOID ret = NULL;

    TRACE("(%p,%p,%d,%ld,%ld,%p,%ld,%ld)\n",vb,data,convert,offset,num,indices,nindices,indexbase);
    if (!info) return (LPVOID)vb->lpGbl->fpVidMem;
    TRACE("lockbuf=%p, renderbuf=%p, overwritten=%ld\n",info->lock_buffer,info->render_buffer,info->overwritten);
    if (!data)
    {
	/* FVF-less (vertex program) rendering */
	if (convert && info->lock_buffer != info->render_buffer) {
	    if (info->overwritten) {
		X11DRV_GLX_EB_OverwriteRender(info);
		TRACE("unoptimized copy to VAR memory\n");
		memcpy(info->render_buffer, info->lock_buffer, info->size);
		info->overwritten = FALSE;
	    }
	    var = info->render_buffer_is_var;
	    ret = info->render_buffer;
	}
	else
	{
	    /* if convert is FALSE, stream zero may be in system memory,
	     * in which case it's not safe to use VAR */
	    var = convert && info->lock_buffer_is_var;
	    ret = info->lock_buffer;
	}
	if (convert && !var) ERR("rendering FVF-less from non-VAR memory, could be bad\n");
    }
    else if (convert && info->lock_buffer != info->render_buffer)
    {
	/* As long as they only lock with DDLOCK_NOOVERWRITE, we don't
	 * need to wait since any in-use vertex data we overwrite will be
	 * overwritten with the previous data. */
	if (info->overwritten)
	    X11DRV_GLX_EB_OverwriteRender(info);

	X11DRV_GLX_Convert(info, offset, num, indices, nindices, indexbase);

	info->overwritten = FALSE;

	X11DRV_GLX_EB_SetStridedBaseAndOffset(&info->render_strided_data,
					      data, info->render_buffer,
					      0);

	var = info->render_buffer_is_var;
    }
    else
    {
	X11DRV_GLX_EB_SetStridedBaseAndOffset(&info->lock_strided_data,
					      data, info->lock_buffer,
					      0);
	var = info->lock_buffer_is_var;
    }

    TRACE("is_var=%d\n",var);
    if (var)
	glEnableClientState(GL_VERTEX_ARRAY_RANGE_WITHOUT_FLUSH_NV);
    return ret;
}

static void X11DRV_GLX_D3dVbPostRender(LPDDRAWI_DDRAWSURFACE_LCL vb)
{
    struct X11DRV_GLX_EB_Info* info
	= (struct X11DRV_GLX_EB_Info*)vb->lpGbl->dwReserved1;

    if (!info) return;
    TRACE("(%p): render_is_var=%d\n",vb,info->render_buffer_is_var);
    if (info->render_buffer_is_var)
    {
#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

	/* It would be nice to only SetFence if we actually rendered
	 * from VAR memory. */
	X11DRV_GLX_glSetFenceNV(X11DRV_GLX_VAR_GetCurrentFence(&info->var_buffers),
				GL_ALL_COMPLETED_NV);

	glDisableClientState(GL_VERTEX_ARRAY_RANGE_WITHOUT_FLUSH_NV);
    }
}

DDHAL_DDEXEBUFCALLBACKS X11DRV_GLX_exebufcallbacks = {
  sizeof(DDHAL_DDEXEBUFCALLBACKS),
  0x1f, /* all callbacks are 32-bit */
  X11DRV_GLX_CanCreateEB, /* CanCreateExecuteBuffer */
  X11DRV_GLX_CreateEB, /* CreateExecuteBuffer */
  X11DRV_GLX_DestroyEB, /* DestroyExecuteBuffer */
  X11DRV_GLX_LockEB, /* LockExecuteBuffer */
  X11DRV_GLX_UnlockEB  /* UnlockExecuteBuffer */
};

struct GL_D3D_DriverCallbacks X11DRV_GLX_GL_D3D_driver_callbacks =
{
    sizeof(struct GL_D3D_DriverCallbacks),
    X11DRV_GLX_D3dVbGetConvertedFVF,
    X11DRV_GLX_D3dVbPreRender,
    X11DRV_GLX_D3dVbPostRender,
    X11DRV_GLX_D3dRenderLock,
    X11DRV_GLX_D3dRenderUnlock,
    X11DRV_GLX_D3dRenderArea
};

/** DirectDraw surface implementation */

int X11DRV_GLX_VAR_CreateDriver(LPDDHALINFO info)
{
  return TRUE;
}
