This commit is contained in:
cholleme 2004-02-08 09:52:47 +00:00
parent 07b04e8cdb
commit 827204fd68
4 changed files with 682 additions and 30 deletions

View file

@ -1,28 +1,32 @@
DriverPtr GL_WrapUserPointer(void *p);
DriverPtr GL_OffsetDriverPtr(DriverPtr p, int offset);
/**
* Gives a definition of the vertices passed to the shedule* functions.
* Use NULL if the data is not available/applicable to the suff you send.
*/
typedef struct {
float *vertices;
DriverPtr vertices;
int vertexstride;
float *texcoords;
DriverPtr texcoords;
int texcoordstride;
float *lightmapcoords;
DriverPtr lightmapcoords;
int lightmapstride;
float *tangents;
DriverPtr tangents;
int tangentstride;
float *binormals;
DriverPtr binormals;
int binormalstride;
float *normals;
DriverPtr normals;
int normalstride;
unsigned char *colors;
DriverPtr colors;
int colorstride;
} vertexdef_t;
@ -33,18 +37,6 @@ typedef struct {
vec3_t objectvieworg;
} lightobject_t;
/**
* Defines driver managed memory types
*/
typedef enum {DM_SLOWREADWRITE, DM_SLOWREAD, DM_NORMAL} drivermem_t;
//DM_SLOWREADWRITE: the memory is slow in writing and reading, won't be updated outside of
//the driver much
//DM_SLOWREAD: the memory is slow in reading (uncached or worse...), won't be read outside
//of the driver much. It supports decent writing speeds. (This is probably what you'll want
//most of the time.)
//DM_NORMAL: Fast reading and writing
/**
* This is a generic bumpdriver struct, it contains al the driver routines
*/
@ -56,18 +48,7 @@ typedef struct {
void (*initDriver) (void);
void (*freeDriver) (void);
//gets a pointer to driver memory, it can just return system memory too if the driver
//doesn't support it.
void *(*getDriverMem) (size_t size, drivermem_t hint);
//frees all driver mem
//FIXME: do we need real deallocation support
void (*freeAllDriverMem) (void);
//FIXME: Do we need fence like support?
//drawing code
void (*drawTriangleListBase) (vertexdef_t *verts, int *indecies, int numIndecies, shader_t *shader, int lightmapIndex);//-1 for no lightmap
void (*drawTriangleListBump) (const vertexdef_t *verts, int *indecies, int numIndecies, shader_t *shader, const transform_t *tr, const lightobject_t *lo);
void (*drawTriangleListSys) (vertexdef_t *verts, int *indecies, int numIndecies, shader_t *shader);

382
gl_drivermem.c Normal file
View file

@ -0,0 +1,382 @@
/*
Copyright (C) 2003 Tenebrae Team
This program 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.
This program 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 this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
---
Driver managed vertex memory
The all routines return DriverPtr's that can be translated and used trough
the use of these routines.
A segment is just a single opengl VBO object, this should abstract the memory management
spanning difference VBO objects because of driver maximum VBO sizes.
*/
#include "quakedef.h"
//number of segments to keep track of
#define MAX_SEGMENTS 256
//maximum size of a segment (this maybe driver dependent)
#define MAX_SEGMENTSIZE (1024*1024*16)
//size of the allocated segments
#define OPTIMAL_SEGMENTSIZE (1024*1024*4)
DriverPtr nullDriver = {0, 0}; //Segment
typedef struct {
int size;
int freeOffset;
int mapCount;
qboolean isSystemMem; //This segment is system memory and not vbo mem (not supported or no free vbo space left)
void *nonVboData; //only nonull if isSystemMem is true
void *mapData; //only nonull if isSystemMem is true
} segmentdescriptor_t;
static segmentdescriptor_t segmentDescriptors[MAX_SEGMENTS];
static int numSegments = 0;
/*******************************************************************
Our own little abstraction layer that abstracts vbo and system mem
if vbo is not available.
*******************************************************************/
static int GL_CreateSegment(size_t size, void *data, int usage) {
void *nonVboData;
qboolean isSystemMem = false;
if (numSegments >= MAX_SEGMENTS)
Sys_Error("GL_CreateSegment: No segments left\n");
if (gl_vbo) {
qglBindBufferARB(GL_ARRAY_BUFFER_ARB, numSegments);
qglBufferDataARB(GL_ARRAY_BUFFER_ARB, size, data,usage);
qglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
nonVboData = NULL;
//fixme check for OUT_OF_MEMORY error
} else {
nonVboData = malloc(size);
isSystemMem = true;
if (data) {
memcpy(nonVboData,data, size);
}
}
segmentDescriptors[numSegments].mapCount = 0;
segmentDescriptors[numSegments].nonVboData = nonVboData;
segmentDescriptors[numSegments].isSystemMem = isSystemMem;
segmentDescriptors[numSegments].size = size;
segmentDescriptors[numSegments].freeOffset = 0;
numSegments++;
return numSegments-1;
}
static void GL_FreeSegment(int segment) {
segmentdescriptor_t *s;
if (segment != (numSegments-1))
Sys_Error("GL_FreeSegment: Only last created segment can be freed\n");
s = &segmentDescriptors[segment];
if (s->isSystemMem) {
if (s->nonVboData)
free(s->nonVboData);
} else {
qglBindBufferARB(GL_ARRAY_BUFFER_ARB, segment);
qglBufferDataARB(GL_ARRAY_BUFFER_ARB, 0, NULL, GL_STREAM_DRAW_ARB);
qglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
}
numSegments--;
}
/*
void *GL_MapToUserSpace(DriverPtr p) {
byte *data;
segmentdescriptor_t *s;
if ((p.segment < 0) || (p.segment >= numSegments)) Sys_Error("GL_MapToUserSpace: Invalid segment");
s = &segmentDescriptors[p.segment];
if (s->isSystemMem) {
return ((byte *)s->nonVboData)+p.offset;
} else {
qglBindBufferARB(GL_ARRAY_BUFFER_ARB, p.segment);
data = qglMapBufferARB(GL_ARRAY_BUFFER_ARB, GL_READ_WRITE_ARB);
qglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
return data+p.offset;
}
}
void GL_UnmapFromUserSpace(DriverPtr p) {
segmentdescriptor_t *s;
if ((p.segment < 0) || (p.segment >= numSegments)) Sys_Error("GL_UnmapFromUserSpace: Invalid segment");
s = &segmentDescriptors[p.segment];
if (!s->isSystemMem) {
qglBindBufferARB(GL_ARRAY_BUFFER_ARB, p.segment);
if (!qglUnmapBufferARB(GL_ARRAY_BUFFER_ARB)) {
Sys_Error("GL_UnmapFromUserSpace: Buffer data store was corrupted\n");
}
qglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
}
}
*/
/**
Maps the driver mem pointer to the proces's adress space
*/
void *GL_MapToUserSpace(DriverPtr p) {
byte *data;
segmentdescriptor_t *s;
if ((p.segment < 0) || (p.segment >= numSegments)) Sys_Error("GL_MapToUserSpace: Invalid segment");
s = &segmentDescriptors[p.segment];
if (s->isSystemMem) {
return ((byte *)s->nonVboData)+p.offset;
} else {
if (s->mapCount == 0) {
qglBindBufferARB(GL_ARRAY_BUFFER_ARB, p.segment);
s->mapData = qglMapBufferARB(GL_ARRAY_BUFFER_ARB, GL_READ_WRITE_ARB);
qglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
}
s->mapCount++;
return (byte *)s->mapData+p.offset;
}
}
/**
Unmaps the driver mem pointer from the adress space
This MUST! be called before the driver can use the memory
*/
void GL_UnmapFromUserSpace(DriverPtr p) {
segmentdescriptor_t *s;
if ((p.segment < 0) || (p.segment >= numSegments)) Sys_Error("GL_UnmapFromUserSpace: Invalid segment");
s = &segmentDescriptors[p.segment];
if (!s->isSystemMem) {
s->mapCount--;
if (s->mapCount == 0) {
qglBindBufferARB(GL_ARRAY_BUFFER_ARB, p.segment);
if (!qglUnmapBufferARB(GL_ARRAY_BUFFER_ARB)) {
Sys_Error("GL_UnmapFromUserSpace: Buffer data store was corrupted\n");
}
qglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
}
}
}
/**
Converts a normal pointer to a VBO compatible pointer (used for some of the geometry that is still in system mem)
*/
DriverPtr GL_WrapUserPointer(void *p) {
DriverPtr dr;
dr.segment = 0;
dr.offset = (byte *)p-NULL;
return dr;
}
/**
This is probably faster than doing a Map and then a memcpy and then an unmap
*/
void drivermemcpy(DriverPtr dest, void *src, size_t size) {
segmentdescriptor_t *s;
if ((dest.segment < 0) || (dest.segment >= numSegments)) Sys_Error("GL_UnmapFromUserSpace: Invalid segment");
s = &segmentDescriptors[dest.segment];
if (!s->isSystemMem) {
qglBindBufferARB(GL_ARRAY_BUFFER_ARB, dest.segment);
qglBufferSubDataARB(GL_ARRAY_BUFFER_ARB, dest.offset, size, src);
qglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
} else {
memcpy((byte *)s->nonVboData+dest.offset, src, size);
}
}
//This will have less vbo state changes, but the spec mentions binding another is cheap anyway
//it's the pointer changing that is expensive
#define RESET_VBO 1
void GL_TexCoordPointer(GLint size, GLenum type, GLsizei stride, DriverPtr p) {
segmentdescriptor_t *s;
if ((p.segment < 0) || (p.segment >= numSegments)) Sys_Error("GL_MapToUserSpace: Invalid segment");
s = &segmentDescriptors[p.segment];
if (s->isSystemMem) {
if (gl_vbo) qglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
glTexCoordPointer(size, type, stride, (byte *)s->nonVboData+p.offset);
} else {
qglBindBufferARB(GL_ARRAY_BUFFER_ARB, p.segment);
glTexCoordPointer(size, type, stride, (byte *)s->nonVboData+p.offset);
#ifdef RESET_VBO
qglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
#endif
}
}
void GL_VertexPointer(GLint size, GLenum type, GLsizei stride, DriverPtr p) {
segmentdescriptor_t *s;
if ((p.segment < 0) || (p.segment >= numSegments)) Sys_Error("GL_MapToUserSpace: Invalid segment");
s = &segmentDescriptors[p.segment];
if (s->isSystemMem) {
if (gl_vbo) qglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
glVertexPointer(size, type, stride, (byte *)s->nonVboData+p.offset);
} else {
qglBindBufferARB(GL_ARRAY_BUFFER_ARB, p.segment);
glVertexPointer(size, type, stride, (byte *)s->nonVboData+p.offset);
#ifdef RESET_VBO
qglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
#endif
}
}
void GL_NormalPointer(GLenum type, GLsizei stride, DriverPtr p) {
segmentdescriptor_t *s;
if ((p.segment < 0) || (p.segment >= numSegments)) Sys_Error("GL_MapToUserSpace: Invalid segment");
s = &segmentDescriptors[p.segment];
if (s->isSystemMem) {
if (gl_vbo) qglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
glNormalPointer(type, stride, (byte *)s->nonVboData+p.offset);
} else {
qglBindBufferARB(GL_ARRAY_BUFFER_ARB, p.segment);
glNormalPointer(type, stride, (byte *)s->nonVboData+p.offset);
#ifdef RESET_VBO
qglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
#endif
}
}
void GL_ColorPointer(GLint size, GLenum type, GLsizei stride, DriverPtr p) {
segmentdescriptor_t *s;
if ((p.segment < 0) || (p.segment >= numSegments)) Sys_Error("GL_MapToUserSpace: Invalid segment");
s = &segmentDescriptors[p.segment];
if (s->isSystemMem) {
if (gl_vbo) qglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
glColorPointer(size, type, stride, (byte *)s->nonVboData+p.offset);
} else {
qglBindBufferARB(GL_ARRAY_BUFFER_ARB, p.segment);
glColorPointer(size, type, stride, (byte *)s->nonVboData+p.offset);
#ifdef RESET_VBO
qglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
#endif
}
}
DriverPtr GL_OffsetDriverPtr (DriverPtr p, int offset) {
p.offset+=offset;
return p;
}
/*******************************************************************
Driver memory public interface
*******************************************************************/
/**
Allocates size bytes of memory and copies data to the block.
Returns a pointer to driver memory.
This memory is "slow" if you use the map routines, it should
not be written or read often.
This memory cannot be freed, it is freed automatically when
GL_FreeAll is called.
*/
DriverPtr GL_StaticAlloc(size_t size, void *data) {
segmentdescriptor_t *s;
int i, segment;
DriverPtr p;
if (size > MAX_SEGMENTSIZE)
Sys_Error("GL_StaticAlloc: Tried to allocate more than MAX_SEGMENTSIZE vertex buffer memory\n");
//If it's to big put it in a separate segment
if (size > OPTIMAL_SEGMENTSIZE-(OPTIMAL_SEGMENTSIZE/4)) {
int segment = GL_CreateSegment(size, data, GL_STATIC_DRAW_ARB);
segmentDescriptors[segment].freeOffset = size;
p.segment = segment;
p.offset = 0;
return p;
}
//Find space in an existing segment
for (i=0, s=segmentDescriptors; i<numSegments; i++, s++) {
if (size < (s->size - s->freeOffset)) {
p.segment = i;
p.offset = s->freeOffset;
s->freeOffset += size;
drivermemcpy(p, data, size);
return p;
}
}
//Allocate a new segment
segment = GL_CreateSegment(OPTIMAL_SEGMENTSIZE, NULL, GL_STATIC_DRAW_ARB);
segmentDescriptors[segment].freeOffset = size;
p.segment = segment;
p.offset = 0;
drivermemcpy(p, data, size);
return p;
}
/**
Allocates size bytes of memory and copies data to the block.
Returns a pointer to driver memory.
This is write once every few frames use multiple times every frame
sort of memory, so reasonably fast to write.
This memory cannot be freed, it is freed automatically when
GL_FreeAll is called (Use the vertex cache for dynamically changing
memory)
*/
DriverPtr GL_DynamicAlloc(size_t size, void *data) {
//Allocate a new segment
int segment = GL_CreateSegment(size, data, GL_DYNAMIC_DRAW_ARB);
DriverPtr p;
segmentDescriptors[segment].freeOffset = size;
p.segment = segment;
p.offset = 0;
return p;
}
/**
Frees all driver memory.
(This should be called at level changes for example)
*/
void GL_FreeAll(void) {
int i;
for (i=numSegments-1; i>=0; i--) {
//free this segment
GL_FreeSegment(i);
}
}
void GL_InitDriverMem(void) {
//vbo with id 0 is a special case in opengl make a fake entry in our segment table
segmentDescriptors[0].freeOffset = 0;
segmentDescriptors[0].size = 0;
segmentDescriptors[0].nonVboData = 0;
segmentDescriptors[0].isSystemMem = true;
numSegments = 1;
}
void GL_FreeDriverMem(void) {
GL_FreeAll();
}

20
gl_drivermem.h Normal file
View file

@ -0,0 +1,20 @@
extern DriverPtr nullDriver;
#define DRVNULL nullDriver
#define IsNullDriver(d) (((d).segment == nullDriver.segment) && ((d).offset == nullDriver.offset))
void *GL_MapToUserSpace(DriverPtr p);
void GL_UnmapFromUserSpace(DriverPtr p);
void drivermemcpy(DriverPtr dest, void *src, size_t size);
DriverPtr GL_StaticAlloc(size_t size, void *data);
DriverPtr GL_DynamicAlloc(size_t size, void *data);
void GL_FreeAll(void);
void GL_InitDriverMem(void);
void GL_FreeDriverMem(void);
void GL_InitVertexCache(void);
void GL_FreeVertexCache(void);
void GL_AllocVertexCache(const size_t size, DriverPtr *owner);
void GL_FlushVertexCache(void);

269
gl_vertexcache.c Normal file
View file

@ -0,0 +1,269 @@
/*
Copyright (C) 2003 Tenebrae Team
This program 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.
This program 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 this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
---
Vertex memory cache, this is used for dynamically changing stuff, like
skinned meshes or interpolated keyframes. (Wich are all done on the
cpu because of shadow volumes)
Oh, and this code is based on the Q1 surface cache... (A rover that
throws away old stuff untill it has enough space)
*/
#include "quakedef.h"
typedef struct vertexcacheitem_s
{
struct vertexcacheitem_s *next;
DriverPtr *owner;// NULL is an empty chunk of memory
size_t size; // of data part
DriverPtr buffer; //Actual memory
} vertexcacheitem_t;
//A single vertex cache, a cache is never bigger than 65k verts so we may have
//multiple caches
typedef struct {
DriverPtr buffer; //Actual memory this cache is controlling
vertexcacheitem_t *items;
vertexcacheitem_t *rover;
size_t size; //Size in bytes of this cache
size_t free; //Free memory in this cache, it may be fragmented tough
} vertexcache_t;
#define VERTEXCACHE_DEBUG 1
#define GUARDSIZE 4
//These are used to detect if we are thrasing the cache
static qboolean gl_roverwrapped;
static size_t gl_initial_offset;
static vertexcacheitem_t *AllocCacheItem(void) {
return malloc(sizeof(vertexcacheitem_t));
}
static void FreeCacheItem(vertexcacheitem_t *i) {
free(i);
}
/**
This may be dead slow, if it's uncached mem: debug only
*/
static void VC_CheckCacheGuard (vertexcache_t *c)
{
int i;
byte *s = GL_MapToUserSpace(c->buffer);
s += c->size;
for (i=0 ; i<GUARDSIZE ; i++) {
if (s[i] != (byte)i)
Sys_Error ("GL_CheckCacheGuard failed: Vertex cache is corrupted");
}
GL_UnmapFromUserSpace(c->buffer);
}
/**
This may be dead slow, if it's uncached mem: debug only
*/
static void VC_ClearCacheGuard (vertexcache_t *c)
{
int i;
byte *s = GL_MapToUserSpace(c->buffer);
s += c->size;
for (i=0 ; i<GUARDSIZE ; i++) {
s[i] = (byte)i;
}
GL_UnmapFromUserSpace(c->buffer);
}
static void VC_InitVertexCache(vertexcache_t *c, size_t size)
{
vertexcacheitem_t *i;
Con_Printf ("%ik vertex cache\n", size/1024);
c->buffer = GL_DynamicAlloc(size + GUARDSIZE, NULL);
c->size = size;
c->free = size;
c->items = i = AllocCacheItem();
c->rover = i;
i->next = NULL;
i->owner = NULL;
i->size = size;
i->buffer = c->buffer;
VC_ClearCacheGuard(c);
}
static void VC_FlushVertexCache(vertexcache_t *c)
{
vertexcacheitem_t *i, *n;
i=c->items;
while(i)
{
if (i->owner)
*i->owner = DRVNULL;
n=i->next;
FreeCacheItem(i);
i = n;
}
c->items = i = AllocCacheItem();
i->next = NULL;
i->owner = NULL;
i->size = c->size;
i->buffer = c->buffer;
c->free = c->size;
c->rover = i;
}
static void VC_FreeVertexCache(vertexcache_t *c)
{
VC_FlushVertexCache(c);
FreeCacheItem(c->items);
c->items = NULL;
}
static vertexcacheitem_t *VC_VertecCacheAlloc (vertexcache_t *c, size_t size)
{
vertexcacheitem_t *new;
qboolean wrapped_this_time;
if ((size <= 0) || (size > 0x400000)) //4 megabytes santity check
Sys_Error ("GL_VertecCacheAlloc: bad cache size %d\n", size);
size = (size + 3) & ~3;
if (size > c->size)
Sys_Error ("GL_VertecCacheAlloc: %i > cache size",size);
if (size > c->free)
Sys_Error ("GL_VertecCacheAlloc: %i > cache free",size);
// if there is not size bytes after the rover, reset to the start
wrapped_this_time = false;
if ( !c->rover || c->rover->buffer.offset > c->size - size)
{
if (c->rover)
{
wrapped_this_time = true;
}
c->rover = c->items;
}
// colect and free surfcache_t blocks until the rover block is large enough
new = c->rover;
if (c->rover->owner)
*c->rover->owner = DRVNULL;
while (new->size < size)
{
// free another
vertexcacheitem_t *old = c->rover;
c->rover = c->rover->next;
FreeCacheItem(old);
if (!c->rover)
Sys_Error ("GL_VertecCacheAlloc: hit the end of memory");
if (c->rover->owner)
*c->rover->owner = DRVNULL;
new->size += c->rover->size;
new->next = c->rover->next;
}
// create a fragment out of any leftovers
if (new->size - size > 256)
{
c->rover = AllocCacheItem();
c->rover->size = new->size - size;
c->rover->next = new->next;
c->rover->owner = NULL;
c->rover->buffer.segment = new->buffer.segment;
c->rover->buffer.offset = new->buffer.offset+size;
new->next = c->rover;
new->size = size;
}
else
c->rover = new->next;
new->owner = NULL; // should be set properly after return
if (gl_roverwrapped)
{
if (wrapped_this_time || (c->rover->buffer.offset >= gl_initial_offset))
r_cache_thrash = true;
}
else if (wrapped_this_time)
{
gl_roverwrapped = true;
}
#ifdef VERTEXCACHE_DEBUG
//Only in debug as this can be very slow due to the driver mem being uncached
VC_CheckCacheGuard (c);
#endif
return new;
}
static void VC_VertecCacheDump (vertexcache_t *c)
{
vertexcacheitem_t *test;
for (test = c->items; test; test=test->next) {
if (test == c->rover)
Con_Printf("ROVER:\n");
Con_Printf("%p : segment(%i) offset(%i) bytes(%i) owner(%p)\n",test, test->buffer.segment, test->buffer.offset, test->size, test->owner);
}
}
/*************************
Public interface
*************************/
#define VERTEX_CACHE_SIZE (1024*1024*8)
vertexcache_t cache;
void GL_InitVertexCache(void) {
VC_InitVertexCache(&cache, VERTEX_CACHE_SIZE);
}
void GL_FreeVertexCache(void) {
VC_FreeVertexCache(&cache);
}
/**
Owner will we overwritten with the pointer, if the cache later decides to free the allocated
cache spot, owner will be overwritten with NULL
So owner should point to "stable" memory, no temporaries on the stack please!
*/
void GL_AllocVertexCache(const size_t size, DriverPtr *owner) {
vertexcacheitem_t *r = VC_VertecCacheAlloc(&cache, size);
*owner = r->buffer;
r->owner = owner;
}
void GL_FlushVertexCache(void) {
VC_FlushVertexCache(&cache);
}