382 lines
11 KiB
C
382 lines
11 KiB
C
/*
|
|
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-(byte*)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();
|
|
}
|