VBO code
This commit is contained in:
parent
07b04e8cdb
commit
827204fd68
4 changed files with 682 additions and 30 deletions
41
bumpdriver.h
41
bumpdriver.h
|
@ -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
382
gl_drivermem.c
Normal 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
20
gl_drivermem.h
Normal 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
269
gl_vertexcache.c
Normal 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);
|
||||
}
|
Loading…
Reference in a new issue