From 51a038c76d3989577ad439d4488465be4548ef20 Mon Sep 17 00:00:00 2001 From: ewasylishen Date: Tue, 20 Jan 2015 18:59:15 +0000 Subject: [PATCH] Alias model rendering fast-path using OpenGL 2. In GL_MakeAliasModelDisplayLists, after saving the standerd triangle strips and fans onto the hunk, if the system is GL2 capable, we also save a second version of the mdl on the hunk designed to be loaded into a VBO (GL_MakeAliasModelDisplayLists_VBO). In R_NewMap, and on video mode changes, we call GLMesh_LoadVertexBuffers which loops over all precached mdl's and loads the data into a pair of VBO's (vertices and vertex indices). Finally, in R_DrawAliasModel, assuming no rendering options are disabling the fast-path (r_drawflat 1, r_lightmap 1, or r_fullbright 1 would disable it), we call GL_DrawAliasFrame_GLSL, which sets up all of the bindings and draws the (possibly lerped) mdl in one glDrawElements call. Special thanks to MH for some of the code from RMQEngine and the general concept. git-svn-id: svn+ssh://svn.code.sf.net/p/quakespasm/code/trunk@1151 af15c1b1-3010-417e-b628-4374ebc0bcbd --- quakespasm/Quake/gl_mesh.c | 229 +++++++++++++++++++++++++++++ quakespasm/Quake/gl_model.h | 36 +++++ quakespasm/Quake/gl_rmain.c | 13 +- quakespasm/Quake/gl_rmisc.c | 183 +++++++++++++++++++++++ quakespasm/Quake/gl_vidsdl.c | 101 ++++++++++++- quakespasm/Quake/glquake.h | 67 +++++++++ quakespasm/Quake/r_alias.c | 275 ++++++++++++++++++++++++++++++++++- quakespasm/Quake/r_brush.c | 22 +-- quakespasm/Quake/r_world.c | 37 ++++- 9 files changed, 939 insertions(+), 24 deletions(-) diff --git a/quakespasm/Quake/gl_mesh.c b/quakespasm/Quake/gl_mesh.c index fa13617d..98b2d7cd 100644 --- a/quakespasm/Quake/gl_mesh.c +++ b/quakespasm/Quake/gl_mesh.c @@ -287,6 +287,7 @@ void BuildTris (void) alltris += pheader->numtris; } +void GL_MakeAliasModelDisplayLists_VBO (void); /* ================ @@ -346,5 +347,233 @@ void GL_MakeAliasModelDisplayLists (qmodel_t *m, aliashdr_t *hdr) for (i=0 ; inumposes ; i++) for (j=0 ; jnumposes * paliashdr->numverts * sizeof(trivertx_t)); + paliashdr->vertexes = (byte *)verts - (byte *)paliashdr; + for (i=0 ; inumposes ; i++) + for (j=0 ; jnumverts ; 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 (); +} diff --git a/quakespasm/Quake/gl_model.h b/quakespasm/Quake/gl_model.h index db9f206a..023e4847 100644 --- a/quakespasm/Quake/gl_model.h +++ b/quakespasm/Quake/gl_model.h @@ -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; @@ -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 @@ -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 // diff --git a/quakespasm/Quake/gl_rmain.c b/quakespasm/Quake/gl_rmain.c index 3ae20cd5..cc3fbad1 100644 --- a/quakespasm/Quake/gl_rmain.c +++ b/quakespasm/Quake/gl_rmain.c @@ -447,6 +447,11 @@ 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 @@ -454,15 +459,21 @@ 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= 2) + { + GL_CreateShaderFunc = (QS_PFNGLCREATESHADERPROC) SDL_GL_GetProcAddress("glCreateShader"); + GL_DeleteShaderFunc = (QS_PFNGLDELETESHADERPROC) SDL_GL_GetProcAddress("glDeleteShader"); + GL_DeleteProgramFunc = (QS_PFNGLDELETEPROGRAMPROC) SDL_GL_GetProcAddress("glDeleteProgram"); + GL_ShaderSourceFunc = (QS_PFNGLSHADERSOURCEPROC) SDL_GL_GetProcAddress("glShaderSource"); + GL_CompileShaderFunc = (QS_PFNGLCOMPILESHADERPROC) SDL_GL_GetProcAddress("glCompileShader"); + GL_GetShaderivFunc = (QS_PFNGLGETSHADERIVPROC) SDL_GL_GetProcAddress("glGetShaderiv"); + GL_GetShaderInfoLogFunc = (QS_PFNGLGETSHADERINFOLOGPROC) SDL_GL_GetProcAddress("glGetShaderInfoLog"); + GL_GetProgramivFunc = (QS_PFNGLGETPROGRAMIVPROC) SDL_GL_GetProcAddress("glGetProgramiv"); + GL_GetProgramInfoLogFunc = (QS_PFNGLGETPROGRAMINFOLOGPROC) SDL_GL_GetProcAddress("glGetProgramInfoLog"); + GL_CreateProgramFunc = (QS_PFNGLCREATEPROGRAMPROC) SDL_GL_GetProcAddress("glCreateProgram"); + GL_AttachShaderFunc = (QS_PFNGLATTACHSHADERPROC) SDL_GL_GetProcAddress("glAttachShader"); + GL_LinkProgramFunc = (QS_PFNGLLINKPROGRAMPROC) SDL_GL_GetProcAddress("glLinkProgram"); + GL_BindAttribLocationFunc = (QS_PFNGLBINDATTRIBLOCATIONFUNC) SDL_GL_GetProcAddress("glBindAttribLocation"); + GL_UseProgramFunc = (QS_PFNGLUSEPROGRAMPROC) SDL_GL_GetProcAddress("glUseProgram"); + GL_GetAttribLocationFunc = (QS_PFNGLGETATTRIBLOCATIONPROC) SDL_GL_GetProcAddress("glGetAttribLocation"); + GL_VertexAttribPointerFunc = (QS_PFNGLVERTEXATTRIBPOINTERPROC) SDL_GL_GetProcAddress("glVertexAttribPointer"); + GL_EnableVertexAttribArrayFunc = (QS_PFNGLENABLEVERTEXATTRIBARRAYPROC) SDL_GL_GetProcAddress("glEnableVertexAttribArray"); + GL_DisableVertexAttribArrayFunc = (QS_PFNGLDISABLEVERTEXATTRIBARRAYPROC) SDL_GL_GetProcAddress("glDisableVertexAttribArray"); + GL_GetUniformLocationFunc = (QS_PFNGLGETUNIFORMLOCATIONPROC) SDL_GL_GetProcAddress("glGetUniformLocation"); + GL_Uniform1iFunc = (QS_PFNGLUNIFORM1IPROC) SDL_GL_GetProcAddress("glUniform1i"); + GL_Uniform1fFunc = (QS_PFNGLUNIFORM1FPROC) SDL_GL_GetProcAddress("glUniform1f"); + GL_Uniform3fFunc = (QS_PFNGLUNIFORM3FPROC) SDL_GL_GetProcAddress("glUniform3f"); + GL_Uniform4fFunc = (QS_PFNGLUNIFORM4FPROC) SDL_GL_GetProcAddress("glUniform4f"); + + if (GL_CreateShaderFunc && + GL_DeleteShaderFunc && + GL_DeleteProgramFunc && + GL_ShaderSourceFunc && + GL_CompileShaderFunc && + GL_GetShaderivFunc && + GL_GetShaderInfoLogFunc && + GL_GetProgramivFunc && + GL_GetProgramInfoLogFunc && + GL_CreateProgramFunc && + GL_AttachShaderFunc && + GL_LinkProgramFunc && + GL_BindAttribLocationFunc && + GL_UseProgramFunc && + GL_GetAttribLocationFunc && + GL_VertexAttribPointerFunc && + GL_EnableVertexAttribArrayFunc && + GL_DisableVertexAttribArrayFunc && + GL_GetUniformLocationFunc && + GL_Uniform1iFunc && + GL_Uniform1fFunc && + GL_Uniform3fFunc && + GL_Uniform4fFunc) + { + Con_Printf("FOUND: GLSL\n"); + gl_glsl_able = true; + } + else + { + Con_Warning ("GLSL not available\n"); + } + } + else + { + Con_Warning ("OpenGL version < 2, GLSL not available\n"); + } } /* @@ -1065,6 +1160,10 @@ static void GL_Init (void) Cbuf_AddText ("gl_clear 1"); } //johnfitz + + R_DeleteShaders (); + GLAlias_CreateShaders (); + GL_ClearBufferBindings (); } /* diff --git a/quakespasm/Quake/glquake.h b/quakespasm/Quake/glquake.h index dbebc4bc..66b7254a 100644 --- a/quakespasm/Quake/glquake.h +++ b/quakespasm/Quake/glquake.h @@ -165,11 +165,65 @@ extern qboolean gl_anisotropy_able; //ericw -- VBO extern PFNGLBINDBUFFERARBPROC GL_BindBufferFunc; extern PFNGLBUFFERDATAARBPROC GL_BufferDataFunc; +extern PFNGLBUFFERSUBDATAARBPROC GL_BufferSubDataFunc; extern PFNGLDELETEBUFFERSARBPROC GL_DeleteBuffersFunc; extern PFNGLGENBUFFERSARBPROC GL_GenBuffersFunc; extern qboolean gl_vbo_able; //ericw +//ericw -- GLSL + +// SDL 1.2 has a bug where it doesn't provide these typedefs on OS X! +typedef GLuint (APIENTRYP QS_PFNGLCREATESHADERPROC) (GLenum type); +typedef void (APIENTRYP QS_PFNGLDELETESHADERPROC) (GLuint shader); +typedef void (APIENTRYP QS_PFNGLDELETEPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP QS_PFNGLSHADERSOURCEPROC) (GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length); +typedef void (APIENTRYP QS_PFNGLCOMPILESHADERPROC) (GLuint shader); +typedef void (APIENTRYP QS_PFNGLGETSHADERIVPROC) (GLuint shader, GLenum pname, GLint *params); +typedef void (APIENTRYP QS_PFNGLGETSHADERINFOLOGPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef void (APIENTRYP QS_PFNGLGETPROGRAMIVPROC) (GLuint program, GLenum pname, GLint *params); +typedef void (APIENTRYP QS_PFNGLGETPROGRAMINFOLOGPROC) (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef GLuint (APIENTRYP QS_PFNGLCREATEPROGRAMPROC) (void); +typedef void (APIENTRYP QS_PFNGLATTACHSHADERPROC) (GLuint program, GLuint shader); +typedef void (APIENTRYP QS_PFNGLLINKPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP QS_PFNGLBINDATTRIBLOCATIONFUNC) (GLuint program, GLuint index, const GLchar *name); +typedef void (APIENTRYP QS_PFNGLUSEPROGRAMPROC) (GLuint program); +typedef GLint (APIENTRYP QS_PFNGLGETATTRIBLOCATIONPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP QS_PFNGLVERTEXATTRIBPOINTERPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer); +typedef void (APIENTRYP QS_PFNGLENABLEVERTEXATTRIBARRAYPROC) (GLuint index); +typedef void (APIENTRYP QS_PFNGLDISABLEVERTEXATTRIBARRAYPROC) (GLuint index); +typedef GLint (APIENTRYP QS_PFNGLGETUNIFORMLOCATIONPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP QS_PFNGLUNIFORM1IPROC) (GLint location, GLint v0); +typedef void (APIENTRYP QS_PFNGLUNIFORM1FPROC) (GLint location, GLfloat v0); +typedef void (APIENTRYP QS_PFNGLUNIFORM3FPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +typedef void (APIENTRYP QS_PFNGLUNIFORM4FPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); + +extern QS_PFNGLCREATESHADERPROC GL_CreateShaderFunc; +extern QS_PFNGLDELETESHADERPROC GL_DeleteShaderFunc; +extern QS_PFNGLDELETEPROGRAMPROC GL_DeleteProgramFunc; +extern QS_PFNGLSHADERSOURCEPROC GL_ShaderSourceFunc; +extern QS_PFNGLCOMPILESHADERPROC GL_CompileShaderFunc; +extern QS_PFNGLGETSHADERIVPROC GL_GetShaderivFunc; +extern QS_PFNGLGETSHADERINFOLOGPROC GL_GetShaderInfoLogFunc; +extern QS_PFNGLGETPROGRAMIVPROC GL_GetProgramivFunc; +extern QS_PFNGLGETPROGRAMINFOLOGPROC GL_GetProgramInfoLogFunc; +extern QS_PFNGLCREATEPROGRAMPROC GL_CreateProgramFunc; +extern QS_PFNGLATTACHSHADERPROC GL_AttachShaderFunc; +extern QS_PFNGLLINKPROGRAMPROC GL_LinkProgramFunc; +extern QS_PFNGLBINDATTRIBLOCATIONFUNC GL_BindAttribLocationFunc; +extern QS_PFNGLUSEPROGRAMPROC GL_UseProgramFunc; +extern QS_PFNGLGETATTRIBLOCATIONPROC GL_GetAttribLocationFunc; +extern QS_PFNGLVERTEXATTRIBPOINTERPROC GL_VertexAttribPointerFunc; +extern QS_PFNGLENABLEVERTEXATTRIBARRAYPROC GL_EnableVertexAttribArrayFunc; +extern QS_PFNGLDISABLEVERTEXATTRIBARRAYPROC GL_DisableVertexAttribArrayFunc; +extern QS_PFNGLGETUNIFORMLOCATIONPROC GL_GetUniformLocationFunc; +extern QS_PFNGLUNIFORM1IPROC GL_Uniform1iFunc; +extern QS_PFNGLUNIFORM1FPROC GL_Uniform1fFunc; +extern QS_PFNGLUNIFORM3FPROC GL_Uniform3fFunc; +extern QS_PFNGLUNIFORM4FPROC GL_Uniform4fFunc; +extern qboolean gl_glsl_able; +// ericw -- + //ericw -- NPOT texture support extern qboolean gl_texture_NPOT; @@ -233,6 +287,11 @@ extern int gl_warpimagesize; //johnfitz -- for water warp extern qboolean r_drawflat_cheatsafe, r_fullbright_cheatsafe, r_lightmap_cheatsafe, r_drawworld_cheatsafe; //johnfitz +typedef struct glsl_attrib_binding_s { + const char *name; + GLuint attrib; +} glsl_attrib_binding_t; + //johnfitz -- fog functions called from outside gl_fog.c void Fog_ParseServerMessage (void); float *Fog_GetColor (void); @@ -275,6 +334,7 @@ void R_DrawTextureChains_Water (qmodel_t *model, entity_t *ent, texchain_t chain void R_RenderDlights (void); void GL_BuildLightmaps (void); void GL_BuildVBOs (void); +void GLMesh_LoadVertexBuffers (void); void R_RebuildAllLightmaps (void); int R_LightPoint (vec3_t p); @@ -289,6 +349,11 @@ void R_DrawBrushModel_ShowTris (entity_t *e); void R_DrawAliasModel_ShowTris (entity_t *e); void R_DrawParticles_ShowTris (void); +GLuint GL_CreateProgram (const GLchar *vertSource, const GLchar *fragSource, int numbindings, const glsl_attrib_binding_t *bindings); +void R_DeleteShaders (void); + +qboolean GLAlias_SupportsShaders (void); +void GLAlias_CreateShaders (void); void GL_DrawAliasShadow (entity_t *e); void DrawGLTriangleFan (glpoly_t *p); void DrawGLPoly (glpoly_t *p); @@ -308,5 +373,7 @@ void R_ChainSurface (msurface_t *surf, texchain_t chain); void R_DrawTextureChains (qmodel_t *model, entity_t *ent, texchain_t chain); void R_DrawWorld_Water (void); +void GL_BindBuffer (GLenum target, GLuint buffer); +void GL_ClearBufferBindings (); #endif /* __GLQUAKE_H */ diff --git a/quakespasm/Quake/r_alias.c b/quakespasm/Quake/r_alias.c index b1999182..0f1e9759 100644 --- a/quakespasm/Quake/r_alias.c +++ b/quakespasm/Quake/r_alias.c @@ -50,7 +50,7 @@ float r_avertexnormal_dots[SHADEDOT_QUANT][256] = extern vec3_t lightspot; float *shadedots = r_avertexnormal_dots[0]; - +vec3_t shadevector; float entalpha; //johnfitz qboolean overbright; //johnfitz @@ -67,6 +67,256 @@ typedef struct { } lerpdata_t; //johnfitz +static GLuint r_alias_program; + +// uniforms used in vert shader +static GLuint blendLoc; +static GLuint shadevectorLoc; +static GLuint lightColorLoc; + +// uniforms used in frag shader +static GLuint texLoc; +static GLuint fullbrightTexLoc; +static GLuint useFullbrightTexLoc; +static GLuint useOverbrightLoc; + +static const GLint pose1VertexAttrIndex = 0; +static const GLint pose1NormalAttrIndex = 1; +static const GLint pose2VertexAttrIndex = 2; +static const GLint pose2NormalAttrIndex = 3; +static const GLint texCoordsAttrIndex = 4; + +extern GLuint r_meshvbo; +extern GLuint r_meshindexesvbo; + +/* +============= +GLARB_GetXYZOffset + +Returns the offset of the first vertex's meshxyz_t.xyz in the vbo for the given +model and pose. +============= +*/ +static void *GLARB_GetXYZOffset (aliashdr_t *hdr, int pose) +{ + meshxyz_t dummy; + int xyzoffs = ((char*)&dummy.xyz - (char*)&dummy); + return (void *) (currententity->model->vboxyzofs + (hdr->numverts_vbo * pose * sizeof (meshxyz_t)) + xyzoffs); +} + +/* +============= +GLARB_GetNormalOffset + +Returns the offset of the first vertex's meshxyz_t.normal in the vbo for the +given model and pose. +============= +*/ +static void *GLARB_GetNormalOffset (aliashdr_t *hdr, int pose) +{ + meshxyz_t dummy; + int normaloffs = ((char*)&dummy.normal - (char*)&dummy); + return (void *)(currententity->model->vboxyzofs + (hdr->numverts_vbo * pose * sizeof (meshxyz_t)) + normaloffs); +} + +/* +============= +GLAlias_SupportsShaders + +Returns whether OpenGL has the capabilities we need for the shader path. +============= +*/ +qboolean GLAlias_SupportsShaders (void) +{ + return gl_glsl_able && gl_vbo_able && gl_max_texture_units >= 3; +} + +/* +============= +GLAlias_GetUniformLocation +============= +*/ +static GLint GLAlias_GetUniformLocation (const char *name) +{ + GLint location; + location = GL_GetUniformLocationFunc(r_alias_program, name); + if (location == -1) + { + Con_Warning("GL_GetUniformLocationFunc %s failed", name); + r_alias_program = 0; + } + return location; +} + +/* +============= +GLAlias_CreateShaders +============= +*/ +void GLAlias_CreateShaders (void) +{ + const glsl_attrib_binding_t bindings[] = { + { "TexCoords", texCoordsAttrIndex }, + { "Pose1Vert", pose1VertexAttrIndex }, + { "Pose1Normal", pose1NormalAttrIndex }, + { "Pose2Vert", pose2VertexAttrIndex }, + { "Pose2Normal", pose2NormalAttrIndex } + }; + + const GLchar *vertSource = \ + "#version 110\n" + "\n" + "uniform float Blend;\n" + "uniform vec3 ShadeVector;\n" + "uniform vec4 LightColor;\n" + "attribute vec4 TexCoords; // only xy are used \n" + "attribute vec4 Pose1Vert;\n" + "attribute vec3 Pose1Normal;\n" + "attribute vec4 Pose2Vert;\n" + "attribute vec3 Pose2Normal;\n" + "float r_avertexnormal_dot(vec3 vertexnormal) // from MH \n" + "{\n" + " float dot = dot(vertexnormal, ShadeVector);\n" + " // wtf - this reproduces anorm_dots within as reasonable a degree of tolerance as the >= 0 case\n" + " if (dot < 0.0)\n" + " return 1.0 + dot * (13.0 / 44.0);\n" + " else\n" + " return 1.0 + dot;\n" + "}\n" + "void main()\n" + "{\n" + " gl_TexCoord[0] = TexCoords;\n" + " vec4 lerpedVert = mix(Pose1Vert, Pose2Vert, Blend);\n" + " gl_Position = gl_ModelViewProjectionMatrix * lerpedVert;\n" + " float dot1 = r_avertexnormal_dot(Pose1Normal);\n" + " float dot2 = r_avertexnormal_dot(Pose2Normal);\n" + " gl_FrontColor = LightColor * vec4(vec3(mix(dot1, dot2, Blend)), 1.0);\n" + " // fog\n" + " vec3 ecPosition = vec3(gl_ModelViewMatrix * lerpedVert);\n" + " gl_FogFragCoord = abs(ecPosition.z);\n" + "}\n"; + + const GLchar *fragSource = \ + "#version 110\n" + "\n" + "uniform sampler2D Tex;\n" + "uniform sampler2D FullbrightTex;\n" + "uniform bool UseFullbrightTex;\n" + "uniform bool UseOverbright;\n" + "void main()\n" + "{\n" + " vec4 result = texture2D(Tex, gl_TexCoord[0].xy);\n" + " result *= gl_Color;\n" + " if (UseOverbright)\n" + " result.rgb *= 2.0;\n" + " if (UseFullbrightTex)\n" + " result += texture2D(FullbrightTex, gl_TexCoord[0].xy);\n" + " result = clamp(result, 0.0, 1.0);\n" + " // apply GL_EXP2 fog (from the orange book)\n" + " float fog = exp(-gl_Fog.density * gl_Fog.density * gl_FogFragCoord * gl_FogFragCoord);\n" + " fog = clamp(fog, 0.0, 1.0);\n" + " result = vec4(mix(gl_Fog.color.rgb, result.rgb, fog), result.a);\n" + " gl_FragColor = result;\n" + "}\n"; + + if (!GLAlias_SupportsShaders()) + return; + + r_alias_program = GL_CreateProgram (vertSource, fragSource, sizeof(bindings)/sizeof(bindings[0]), bindings); + + if (r_alias_program != 0) + { + // get uniform locations + blendLoc = GLAlias_GetUniformLocation ("Blend"); + shadevectorLoc = GLAlias_GetUniformLocation ("ShadeVector"); + lightColorLoc = GLAlias_GetUniformLocation ("LightColor"); + texLoc = GLAlias_GetUniformLocation("Tex"); + fullbrightTexLoc = GLAlias_GetUniformLocation("FullbrightTex"); + useFullbrightTexLoc = GLAlias_GetUniformLocation("UseFullbrightTex"); + useOverbrightLoc = GLAlias_GetUniformLocation("UseOverbright"); + } +} + +/* +============= +GL_DrawAliasFrame_GLSL -- ericw + +Optimized alias model drawing codepath. +Compared to the original GL_DrawAliasFrame, this makes 1 draw call, +no vertex data is uploaded (it's already in the r_meshvbo and r_meshindexesvbo +static VBOs), and lerping and lighting is done in the vertex shader. + +Supports optional overbright, optional fullbright pixels. + +Based on code by MH from RMQEngine +============= +*/ +void GL_DrawAliasFrame_GLSL (aliashdr_t *paliashdr, lerpdata_t lerpdata, gltexture_t *tx, gltexture_t *fb) +{ + float blend; + + if (lerpdata.pose1 != lerpdata.pose2) + { + blend = lerpdata.blend; + } + else // poses the same means either 1. the entity has paused its animation, or 2. r_lerpmodels is disabled + { + blend = 0; + } + + GL_UseProgramFunc (r_alias_program); + + GL_BindBuffer (GL_ARRAY_BUFFER, r_meshvbo); + GL_BindBuffer (GL_ELEMENT_ARRAY_BUFFER, r_meshindexesvbo); + + GL_EnableVertexAttribArrayFunc (texCoordsAttrIndex); + GL_EnableVertexAttribArrayFunc (pose1VertexAttrIndex); + GL_EnableVertexAttribArrayFunc (pose2VertexAttrIndex); + GL_EnableVertexAttribArrayFunc (pose1NormalAttrIndex); + GL_EnableVertexAttribArrayFunc (pose2NormalAttrIndex); + + GL_VertexAttribPointerFunc (texCoordsAttrIndex, 2, GL_FLOAT, GL_FALSE, 0, (void *)(intptr_t)currententity->model->vbostofs); + GL_VertexAttribPointerFunc (pose1VertexAttrIndex, 4, GL_UNSIGNED_BYTE, GL_FALSE, sizeof (meshxyz_t), GLARB_GetXYZOffset (paliashdr, lerpdata.pose1)); + GL_VertexAttribPointerFunc (pose2VertexAttrIndex, 4, GL_UNSIGNED_BYTE, GL_FALSE, sizeof (meshxyz_t), GLARB_GetXYZOffset (paliashdr, lerpdata.pose2)); +// GL_TRUE to normalize the signed bytes to [-1 .. 1] + GL_VertexAttribPointerFunc (pose1NormalAttrIndex, 4, GL_BYTE, GL_TRUE, sizeof (meshxyz_t), GLARB_GetNormalOffset (paliashdr, lerpdata.pose1)); + GL_VertexAttribPointerFunc (pose2NormalAttrIndex, 4, GL_BYTE, GL_TRUE, sizeof (meshxyz_t), GLARB_GetNormalOffset (paliashdr, lerpdata.pose2)); + +// set uniforms + GL_Uniform1fFunc (blendLoc, blend); + GL_Uniform3fFunc (shadevectorLoc, shadevector[0], shadevector[1], shadevector[2]); + GL_Uniform4fFunc (lightColorLoc, lightcolor[0], lightcolor[1], lightcolor[2], entalpha); + GL_Uniform1iFunc (texLoc, 0); + GL_Uniform1iFunc (fullbrightTexLoc, 1); + GL_Uniform1iFunc (useFullbrightTexLoc, (fb != NULL) ? 1 : 0); + GL_Uniform1fFunc (useOverbrightLoc, overbright ? 1 : 0); + +// set textures + GL_SelectTexture (GL_TEXTURE0); + GL_Bind (tx); + + if (fb) + { + GL_SelectTexture (GL_TEXTURE1); + GL_Bind (fb); + } + +// draw + glDrawElements (GL_TRIANGLES, paliashdr->numindexes, GL_UNSIGNED_SHORT, (void *)(intptr_t)currententity->model->vboindexofs); + +// clean up + GL_DisableVertexAttribArrayFunc (texCoordsAttrIndex); + GL_DisableVertexAttribArrayFunc (pose1VertexAttrIndex); + GL_DisableVertexAttribArrayFunc (pose2VertexAttrIndex); + GL_DisableVertexAttribArrayFunc (pose1NormalAttrIndex); + GL_DisableVertexAttribArrayFunc (pose2NormalAttrIndex); + + GL_UseProgramFunc (0); + GL_SelectTexture (GL_TEXTURE0); + + rs_aliaspasses += paliashdr->numtris; +} + /* ============= GL_DrawAliasFrame -- johnfitz -- rewritten to support colored light, lerping, entalpha, multitexture, and r_drawflat @@ -319,6 +569,8 @@ void R_SetupAliasLighting (entity_t *e) vec3_t dist; float add; int i; + int quantizedangle; + float radiansangle; R_LightPoint (e->origin); @@ -375,8 +627,19 @@ void R_SetupAliasLighting (entity_t *e) lightcolor[2] = 256.0f; } - shadedots = r_avertexnormal_dots[((int)(e->angles[1] * (SHADEDOT_QUANT / 360.0))) & (SHADEDOT_QUANT - 1)]; - VectorScale(lightcolor, 1.0f / 200.0f, lightcolor); + quantizedangle = ((int)(e->angles[1] * (SHADEDOT_QUANT / 360.0))) & (SHADEDOT_QUANT - 1); + +//ericw -- shadevector is passed to the shader to compute shadedots inside the +//shader, see GLAlias_CreateShaders() + radiansangle = (quantizedangle / 16.0) * 2.0 * 3.14159; + shadevector[0] = cos(-radiansangle); + shadevector[1] = sin(-radiansangle); + shadevector[2] = 1; + VectorNormalize(shadevector); +//ericw -- + + shadedots = r_avertexnormal_dots[quantizedangle]; + VectorScale (lightcolor, 1.0f / 200.0f, lightcolor); } /* @@ -500,6 +763,12 @@ void R_DrawAliasModel (entity_t *e) GL_DrawAliasFrame (paliashdr, lerpdata); glEnable (GL_TEXTURE_2D); } +// call fast path if possible. if the shader compliation failed for some reason, +// r_alias_program will be 0. + else if (GLAlias_SupportsShaders() && (r_alias_program != 0)) + { + GL_DrawAliasFrame_GLSL (paliashdr, lerpdata, tx, fb); + } else if (overbright) { if (gl_texture_env_combine && gl_mtexable && gl_texture_env_add && fb) //case 1: everything in one pass diff --git a/quakespasm/Quake/r_brush.c b/quakespasm/Quake/r_brush.c index b5203ebc..e84a44e3 100644 --- a/quakespasm/Quake/r_brush.c +++ b/quakespasm/Quake/r_brush.c @@ -959,7 +959,7 @@ void GL_BuildLightmaps (void) ============================================================= */ -static GLuint gl_bmodel_vbo = 0; +GLuint gl_bmodel_vbo = 0; /* ================== @@ -1021,23 +1021,9 @@ void GL_BuildVBOs (void) GL_BindBufferFunc (GL_ARRAY_BUFFER, gl_bmodel_vbo); GL_BufferDataFunc (GL_ARRAY_BUFFER, varray_bytes, varray, GL_STATIC_DRAW); free (varray); - -// setup vertex array. this will need to move if we use vertex arrays for other things - glVertexPointer (3, GL_FLOAT, VERTEXSIZE * sizeof(float), ((float *)0)); - glEnableClientState (GL_VERTEX_ARRAY); - - GL_ClientActiveTextureFunc (GL_TEXTURE0_ARB); - glTexCoordPointer (2, GL_FLOAT, VERTEXSIZE * sizeof(float), ((float *)0) + 3); - glEnableClientState (GL_TEXTURE_COORD_ARRAY); - - GL_ClientActiveTextureFunc (GL_TEXTURE1_ARB); - glTexCoordPointer (2, GL_FLOAT, VERTEXSIZE * sizeof(float), ((float *)0) + 5); - glEnableClientState (GL_TEXTURE_COORD_ARRAY); - -// TMU 2 is for fullbrights; same texture coordinates as TMU 0 - GL_ClientActiveTextureFunc (GL_TEXTURE2_ARB); - glTexCoordPointer (2, GL_FLOAT, VERTEXSIZE * sizeof(float), ((float *)0) + 3); - glEnableClientState (GL_TEXTURE_COORD_ARRAY); + +// invalidate the cached bindings + GL_ClearBufferBindings (); } /* diff --git a/quakespasm/Quake/r_world.c b/quakespasm/Quake/r_world.c index 1f9c41be..05adceac 100644 --- a/quakespasm/Quake/r_world.c +++ b/quakespasm/Quake/r_world.c @@ -779,6 +779,8 @@ void R_DrawLightmapChains (void) } } +extern GLuint gl_bmodel_vbo; + /* ================ R_DrawTextureChains_Multitexture_VBO -- ericw @@ -796,6 +798,27 @@ void R_DrawTextureChains_Multitexture_VBO (qmodel_t *model, entity_t *ent, texch int lastlightmap; gltexture_t *fullbright = NULL; +// Bind the buffers + GL_BindBuffer (GL_ARRAY_BUFFER, gl_bmodel_vbo); + GL_BindBuffer (GL_ELEMENT_ARRAY_BUFFER, 0); // indices come from client memory! + +// Setup vertex array pointers + glVertexPointer (3, GL_FLOAT, VERTEXSIZE * sizeof(float), ((float *)0)); + glEnableClientState (GL_VERTEX_ARRAY); + + GL_ClientActiveTextureFunc (GL_TEXTURE0_ARB); + glTexCoordPointer (2, GL_FLOAT, VERTEXSIZE * sizeof(float), ((float *)0) + 3); + glEnableClientState (GL_TEXTURE_COORD_ARRAY); + + GL_ClientActiveTextureFunc (GL_TEXTURE1_ARB); + glTexCoordPointer (2, GL_FLOAT, VERTEXSIZE * sizeof(float), ((float *)0) + 5); + glEnableClientState (GL_TEXTURE_COORD_ARRAY); + +// TMU 2 is for fullbrights; same texture coordinates as TMU 0 + GL_ClientActiveTextureFunc (GL_TEXTURE2_ARB); + glTexCoordPointer (2, GL_FLOAT, VERTEXSIZE * sizeof(float), ((float *)0) + 3); + glEnableClientState (GL_TEXTURE_COORD_ARRAY); + // Setup TMU 1 (lightmap) GL_SelectTexture (GL_TEXTURE1_ARB); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT); @@ -804,7 +827,7 @@ void R_DrawTextureChains_Multitexture_VBO (qmodel_t *model, entity_t *ent, texch glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_TEXTURE); glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, gl_overbright.value ? 2.0f : 1.0f); glEnable(GL_TEXTURE_2D); - + // Setup TMU 2 (fullbrights) GL_SelectTexture (GL_TEXTURE2_ARB); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD); @@ -873,6 +896,18 @@ void R_DrawTextureChains_Multitexture_VBO (qmodel_t *model, entity_t *ent, texch GL_SelectTexture (GL_TEXTURE0_ARB); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + +// Disable client state + glDisableClientState (GL_VERTEX_ARRAY); + + GL_ClientActiveTextureFunc (GL_TEXTURE0_ARB); + glDisableClientState (GL_TEXTURE_COORD_ARRAY); + + GL_ClientActiveTextureFunc (GL_TEXTURE1_ARB); + glDisableClientState (GL_TEXTURE_COORD_ARRAY); + + GL_ClientActiveTextureFunc (GL_TEXTURE2_ARB); + glDisableClientState (GL_TEXTURE_COORD_ARRAY); } /*