229 changes: 229 additions & 0 deletions quakespasm/Quake/gl_mesh.c
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ void BuildTris (void)
alltris += pheader->numtris;
}

void GL_MakeAliasModelDisplayLists_VBO (void);

/*
================
Expand Down Expand Up @@ -346,5 +347,233 @@ void GL_MakeAliasModelDisplayLists (qmodel_t *m, aliashdr_t *hdr)
for (i=0 ; i<paliashdr->numposes ; i++)
for (j=0 ; j<numorder ; j++)
*verts++ = poseverts[i][vertexorder[j]];

// ericw
GL_MakeAliasModelDisplayLists_VBO ();
}

unsigned int r_meshindexbuffer = 0;
unsigned int r_meshvertexbuffer = 0;

/*
================
GL_MakeAliasModelDisplayLists_VBO
Saves data needed to build the VBO for this model on the hunk. Afterwards this
is copied to Mod_Extradata.
Original code by MH from RMQEngine
================
*/
void GL_MakeAliasModelDisplayLists_VBO (void)
{
int i, j;
int maxverts_vbo;
trivertx_t *verts;
unsigned short *indexes;
aliasmesh_t *desc;

if (!GLAlias_SupportsShaders())
return;

// first, copy the verts onto the hunk
verts = (trivertx_t *) Hunk_Alloc (paliashdr->numposes * paliashdr->numverts * sizeof(trivertx_t));
paliashdr->vertexes = (byte *)verts - (byte *)paliashdr;
for (i=0 ; i<paliashdr->numposes ; i++)
for (j=0 ; j<paliashdr->numverts ; j++)
verts[i*paliashdr->numverts + j] = poseverts[i][j];

// there can never be more than this number of verts and we just put them all on the hunk
maxverts_vbo = pheader->numtris * 3;
desc = (aliasmesh_t *) Hunk_Alloc (sizeof (aliasmesh_t) * maxverts_vbo);

// there will always be this number of indexes
indexes = (unsigned short *) Hunk_Alloc (sizeof (unsigned short) * maxverts_vbo);

pheader->indexes = (intptr_t) indexes - (intptr_t) pheader;
pheader->meshdesc = (intptr_t) desc - (intptr_t) pheader;
pheader->numindexes = 0;
pheader->numverts_vbo = 0;

for (i = 0; i < pheader->numtris; i++)
{
for (j = 0; j < 3; j++)
{
int v;

// index into hdr->vertexes
unsigned short vertindex = triangles[i].vertindex[j];

// basic s/t coords
int s = stverts[vertindex].s;
int t = stverts[vertindex].t;

// check for back side and adjust texcoord s
if (!triangles[i].facesfront && stverts[vertindex].onseam) s += pheader->skinwidth / 2;

// see does this vert already exist
for (v = 0; v < pheader->numverts_vbo; v++)
{
// it could use the same xyz but have different s and t
if (desc[v].vertindex == vertindex && (int) desc[v].st[0] == s && (int) desc[v].st[1] == t)
{
// exists; emit an index for it
indexes[pheader->numindexes++] = v;

// no need to check any more
break;
}
}

if (v == pheader->numverts_vbo)
{
// doesn't exist; emit a new vert and index
indexes[pheader->numindexes++] = pheader->numverts_vbo;

desc[pheader->numverts_vbo].vertindex = vertindex;
desc[pheader->numverts_vbo].st[0] = s;
desc[pheader->numverts_vbo++].st[1] = t;
}
}
}
}

#define NUMVERTEXNORMALS 162
extern float r_avertexnormals[NUMVERTEXNORMALS][3];

GLuint r_meshvbo = 0;
GLuint r_meshindexesvbo = 0;

/*
================
GLMesh_LoadVertexBuffers
Loop over all precached alias models, and upload them into one big VBO plus
an GL_ELEMENT_ARRAY_BUFFER for the vertex indices.
Original code by MH from RMQEngine
================
*/
void GLMesh_LoadVertexBuffers (void)
{
int j;
qmodel_t *m;
int totalindexes = 0;
int totalvbosize = 0;

if (!GLAlias_SupportsShaders())
return;

// pass 1 - count the sizes we need
for (j = 1; j < MAX_MODELS; j++)
{
aliashdr_t *hdr;

if (!(m = cl.model_precache[j])) break;
if (m->type != mod_alias) continue;

hdr = Mod_Extradata (m);

// ericw -- RMQEngine stored these vbo*ofs values in aliashdr_t, but we must not
// mutate Mod_Extradata since it might be reloaded from disk, so I moved them to qmodel_t
// (test case: roman1.bsp from arwop, 64mb heap)
m->vboindexofs = (totalindexes * sizeof (unsigned short));
totalindexes += hdr->numindexes;

m->vboxyzofs = totalvbosize;
totalvbosize += (hdr->numposes * hdr->numverts_vbo * sizeof (meshxyz_t)); // ericw -- what RMQEngine called nummeshframes is called numposes in QuakeSpasm

m->vbostofs = totalvbosize;
totalvbosize += (hdr->numverts_vbo * sizeof (meshst_t));
}

if (!totalindexes) return;
if (!totalvbosize) return;

// pass 2 - create the buffers
GL_DeleteBuffersFunc (1, &r_meshindexesvbo);
GL_GenBuffersFunc (1, &r_meshindexesvbo);
GL_BindBufferFunc (GL_ELEMENT_ARRAY_BUFFER, r_meshindexesvbo);
GL_BufferDataFunc (GL_ELEMENT_ARRAY_BUFFER, totalindexes * sizeof (unsigned short), NULL, GL_STATIC_DRAW);

GL_DeleteBuffersFunc (1, &r_meshvbo);
GL_GenBuffersFunc (1, &r_meshvbo);
GL_BindBufferFunc (GL_ARRAY_BUFFER, r_meshvbo);
GL_BufferDataFunc (GL_ARRAY_BUFFER, totalvbosize, NULL, GL_STATIC_DRAW);

// pass 3 - fill in the buffers
for (j = 1; j < MAX_MODELS; j++)
{
int f;
aliashdr_t *hdr;
aliasmesh_t *desc;
meshst_t *st;
float hscale, vscale;

if (!(m = cl.model_precache[j])) break;
if (m->type != mod_alias) continue;

hdr = Mod_Extradata (m);
desc = (aliasmesh_t *) ((byte *) hdr + hdr->meshdesc);

//johnfitz -- padded skins
hscale = (float)hdr->skinwidth/(float)TexMgr_PadConditional(hdr->skinwidth);
vscale = (float)hdr->skinheight/(float)TexMgr_PadConditional(hdr->skinheight);
//johnfitz

GL_BufferSubDataFunc (GL_ELEMENT_ARRAY_BUFFER,
m->vboindexofs,
hdr->numindexes * sizeof (unsigned short),
((byte *) hdr + hdr->indexes));

for (f = 0; f < hdr->numposes; f++) // ericw -- what RMQEngine called nummeshframes is called numposes in QuakeSpasm
{
int v;
meshxyz_t *xyz = (meshxyz_t *) malloc (hdr->numverts_vbo * sizeof (meshxyz_t));
trivertx_t *tv = (trivertx_t *) ((byte *) hdr + hdr->vertexes + (hdr->numverts * sizeof(trivertx_t) * f));

for (v = 0; v < hdr->numverts_vbo; v++)
{
trivertx_t trivert = tv[desc[v].vertindex];

xyz[v].xyz[0] = trivert.v[0];
xyz[v].xyz[1] = trivert.v[1];
xyz[v].xyz[2] = trivert.v[2];
xyz[v].xyz[3] = 1; // need w 1 for 4 byte vertex compression

// map the normal coordinates in [-1..1] to [-127..127] and store in an unsigned char.
// this introduces some error (less than 0.004), but the normals were very coarse
// to begin with
xyz[v].normal[0] = 127 * r_avertexnormals[trivert.lightnormalindex][0];
xyz[v].normal[1] = 127 * r_avertexnormals[trivert.lightnormalindex][1];
xyz[v].normal[2] = 127 * r_avertexnormals[trivert.lightnormalindex][2];
xyz[v].normal[3] = 0; // unused; for 4-byte alignment
}

GL_BufferSubDataFunc (GL_ARRAY_BUFFER,
m->vboxyzofs + (f * hdr->numverts_vbo * sizeof (meshxyz_t)),
hdr->numverts_vbo * sizeof (meshxyz_t),
xyz);

free (xyz);
}

st = (meshst_t *) malloc (hdr->numverts_vbo * sizeof (meshst_t));

for (f = 0; f < hdr->numverts_vbo; f++)
{
st[f].st[0] = hscale * ((float) desc[f].st[0] + 0.5f) / (float) hdr->skinwidth;
st[f].st[1] = vscale * ((float) desc[f].st[1] + 0.5f) / (float) hdr->skinheight;
}

GL_BufferSubDataFunc (GL_ARRAY_BUFFER,
m->vbostofs,
hdr->numverts_vbo * sizeof (meshst_t),
st);

free (st);
}

// invalidate the cached bindings
GL_ClearBufferBindings ();
}
36 changes: 36 additions & 0 deletions quakespasm/Quake/gl_model.h
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,26 @@ Alias models are position independent, so the cache manager can move them.
==============================================================================
*/

//-- from RMQEngine
// split out to keep vertex sizes down
typedef struct aliasmesh_s
{
float st[2];
unsigned short vertindex;
} aliasmesh_t;

typedef struct meshxyz_s
{
byte xyz[4];
signed char normal[4];
} meshxyz_t;

typedef struct meshst_s
{
float st[2];
} meshst_t;
//--

typedef struct
{
int firstpose;
Expand Down Expand Up @@ -332,6 +352,14 @@ typedef struct {
int flags;
float size;

//ericw -- used to populate vbo
int numverts_vbo; // number of verts with unique x,y,z,s,t
intptr_t meshdesc; // offset into extradata: numverts_vbo aliasmesh_t
int numindexes;
intptr_t indexes; // offset into extradata: numindexes unsigned shorts
intptr_t vertexes; // offset into extradata: numposes*vertsperframe trivertx_t
//ericw --

int numposes;
int poseverts;
int posedata; // numposes*poseverts trivert_t
Expand Down Expand Up @@ -449,6 +477,14 @@ typedef struct qmodel_s

int bspversion;

//
// alias model
//

int vboindexofs; // offset in vbo of the hdr->numindexes unsigned shorts
int vboxyzofs; // offset in vbo of hdr->numposes*hdr->numverts_vbo meshxyz_t
int vbostofs; // offset in vbo of hdr->numverts_vbo meshst_t

//
// additional model data
//
Expand Down
13 changes: 12 additions & 1 deletion quakespasm/Quake/gl_rmain.c
Original file line number Diff line number Diff line change
Expand Up @@ -447,22 +447,33 @@ void R_SetupView (void)
//
//==============================================================================

static int R_EntitySortFunc(const void *a, const void *b)
{
return (*(entity_t **)a)->model - (*(entity_t **)b)->model;
}

/*
=============
R_DrawEntitiesOnList
=============
*/
void R_DrawEntitiesOnList (qboolean alphapass) //johnfitz -- added parameter
{
entity_t *entities_sorted[MAX_VISEDICTS];
int i;

if (!r_drawentities.value)
return;

//ericw -- draw the entities sorted by model, to eliminate redundant texture binding
memcpy (entities_sorted, cl_visedicts, cl_numvisedicts * sizeof(entity_t *));
qsort (entities_sorted, cl_numvisedicts, sizeof(entity_t *), R_EntitySortFunc);
//ericw --

//johnfitz -- sprites are not a special case
for (i=0 ; i<cl_numvisedicts ; i++)
{
currententity = cl_visedicts[i];
currententity = entities_sorted[i];

//johnfitz -- if alphapass is true, draw only alpha entites this time
//if alphapass is false, draw only nonalpha entities this time
Expand Down
Loading